Skip to content

Commit

Permalink
feat: remove Bouncy Castle dependencies
Browse files Browse the repository at this point in the history
  • Loading branch information
brusdev committed Nov 18, 2024
1 parent c2e79e7 commit fd51f76
Show file tree
Hide file tree
Showing 7 changed files with 437 additions and 38 deletions.
13 changes: 4 additions & 9 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
<maven.version>3.3.9</maven.version>
<java.source>1.8</java.source>

<bouncycastle.version>1.78</bouncycastle.version>
<asn.one.version>0.6.0</asn.one.version>

<junit.jupiter.version>5.9.0</junit.jupiter.version>
<junit.platform.version>1.2.0</junit.platform.version>
Expand All @@ -68,14 +68,9 @@

<dependencies>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk18on</artifactId>
<version>${bouncycastle.version}</version>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcpkix-jdk18on</artifactId>
<version>${bouncycastle.version}</version>
<groupId>com.hierynomus</groupId>
<artifactId>asn-one</artifactId>
<version>${asn.one.version}</version>
</dependency>

<!-- test -->
Expand Down
253 changes: 253 additions & 0 deletions src/main/java/de/dentrassi/crypto/pem/PemReader.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,253 @@
/*
* Copyright (c) 2018 Red Hat Inc and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Domenico Francesco Bruscino - initial PemReader implementation
*/

package de.dentrassi.crypto.pem;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.Reader;
import java.math.BigInteger;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.interfaces.ECPrivateKey;
import java.security.spec.DSAPrivateKeySpec;
import java.security.spec.DSAPublicKeySpec;
import java.security.spec.ECPoint;
import java.security.spec.ECPublicKeySpec;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.RSAPrivateCrtKeySpec;
import java.security.spec.RSAPublicKeySpec;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Base64;
import java.util.List;

import com.hierynomus.asn1.ASN1InputStream;
import com.hierynomus.asn1.ASN1OutputStream;
import com.hierynomus.asn1.encodingrules.der.DERDecoder;
import com.hierynomus.asn1.encodingrules.der.DEREncoder;
import com.hierynomus.asn1.types.ASN1Object;
import com.hierynomus.asn1.types.constructed.ASN1Sequence;
import com.hierynomus.asn1.types.constructed.ASN1TaggedObject;
import com.hierynomus.asn1.types.primitive.ASN1Integer;
import com.hierynomus.asn1.types.primitive.ASN1ObjectIdentifier;
import com.hierynomus.asn1.types.string.ASN1BitString;
import com.hierynomus.asn1.types.string.ASN1OctetString;

public class PemReader extends BufferedReader {

private static final String BEGIN = "-----BEGIN ";
private static final String CERTIFICATE = "CERTIFICATE";
private static final String X509_CERTIFICATE = "X509 CERTIFICATE";
private static final String EC_PRIVATE_KEY = "EC PRIVATE KEY";
private static final String EC_PUBLIC_KEY_OBJ_ID = "1.2.840.10045.2.1";
private static final String DSA_PRIVATE_KEY = "DSA PRIVATE KEY";
private static final String RSA_PRIVATE_KEY = "RSA PRIVATE KEY";
private static final String PRIVATE_KEY = "PRIVATE KEY";
private static final String END = "-----END ";
private static final List<String> KEY_ALGORITHMS = Arrays.asList("RSA", "DSA", "EC");

private static List<KeyFactory> keyFactories;

private static List<KeyFactory> getKeyFactories() {
if (keyFactories == null) {
keyFactories = new ArrayList<>();

KEY_ALGORITHMS.forEach(s -> {
try {
keyFactories.add(KeyFactory.getInstance(s));
} catch (Exception e) {
e.printStackTrace();
}
});
}

return keyFactories;
}

public PemReader(Reader in) {
super(in);
}

public Object readObject() throws CertificateException, IOException {
byte[] objectContent = null;
String objectType = null;

String line = readLine();

while (line != null && !line.startsWith(BEGIN)) {
line = readLine();
}

if (line != null) {
line = line.substring(BEGIN.length()).trim();
int index = line.indexOf('-');

if (index > 0 && line.endsWith("-----") && (line.length() - index) == 5) {
objectType = line.substring(0, index);

StringBuffer buffer = new StringBuffer();
String endMarker = END + objectType + "-----";
while ((line = readLine()) != null && line.indexOf(endMarker) != 0) {
if (line.indexOf(':') < 0) {
buffer.append(line.trim());
}
}
objectContent = Base64.getDecoder().decode(buffer.toString());
}
}

if (objectContent != null) {
if (CERTIFICATE.equals(objectType) || X509_CERTIFICATE.equals(objectType)) {
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
try (ByteArrayInputStream contentInputStream = new ByteArrayInputStream(objectContent)) {
return certificateFactory.generateCertificate(contentInputStream);
}
} else if (EC_PRIVATE_KEY.equals(objectType)) {
/*
https://datatracker.ietf.org/doc/html/rfc5915
ECPrivateKey ::= SEQUENCE {
version INTEGER { ecPrivkeyVer1(1) } (ecPrivkeyVer1),
privateKey OCTET STRING,
parameters [0] ECParameters {{ NamedCurve }} OPTIONAL,
publicKey [1] BIT STRING OPTIONAL
}
*/
try (ASN1InputStream asn1In = new ASN1InputStream(new DERDecoder(), objectContent)) {
ASN1Sequence pkcs1Sequence = asn1In.readObject();
try (ByteArrayOutputStream pkcs8Out = new ByteArrayOutputStream()) {
try (ASN1OutputStream asn1Out = new ASN1OutputStream(new DEREncoder(), pkcs8Out)) {
List<ASN1Object> outObjects = new ArrayList<>();
outObjects.add(new ASN1Integer(0));
outObjects.add(new ASN1Sequence(Arrays.asList(new ASN1ObjectIdentifier(EC_PUBLIC_KEY_OBJ_ID), ((ASN1TaggedObject) pkcs1Sequence.get(2)).getObject())));
outObjects.add(new ASN1OctetString(objectContent));
asn1Out.writeObject(new ASN1Sequence(outObjects));
}

PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(pkcs8Out.toByteArray());
try {
KeyFactory ecKeyFactory = KeyFactory.getInstance("EC");

PublicKey ecPublicKey = null;
ECPrivateKey ecPrivateKey = (ECPrivateKey) ecKeyFactory.generatePrivate(keySpec);

if (pkcs1Sequence.size() > 3) {
byte[] ecPointBytes = ((ASN1BitString) ((ASN1TaggedObject) pkcs1Sequence.get(3)).getObject()).getValueBytes();
if (ecPointBytes[0] == 4) {
byte[] ecPointXBytes = new byte[32];
byte[] ecPointYBytes = new byte[32];
System.arraycopy(ecPointBytes, 1, ecPointXBytes, 0, 32);
System.arraycopy(ecPointBytes, 33, ecPointYBytes, 0, 32);
ECPoint ecPoint = new ECPoint(new BigInteger(1, ecPointXBytes), new BigInteger(1, ecPointYBytes));
ECPublicKeySpec publicKeySpec = new ECPublicKeySpec(ecPoint, ecPrivateKey.getParams());
ecPublicKey = ecKeyFactory.generatePublic(publicKeySpec);
}
}

return new KeyPair(ecPublicKey, ecPrivateKey);
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
throw new IOException(e);
}
}
}
} else if (DSA_PRIVATE_KEY.equals(objectType)) {
/*
https://datatracker.ietf.org/doc/html/draft-woodhouse-cert-best-practice-01
DSAPrivateKey ::= SEQUENCE {
version INTEGER, -- should be zero
p INTEGER,
q INTEGER,
g INTEGER,
pub INTEGER, -- public
priv INTEGER, -- private
}
*/
try (ASN1InputStream asn1InputStream = new ASN1InputStream(new DERDecoder(), objectContent)) {
ASN1Sequence pkcs1Sequence = asn1InputStream.readObject();

//BigInteger y, BigInteger p, BigInteger q, BigInteger g)
DSAPublicKeySpec publicKeySpec = new DSAPublicKeySpec(((ASN1Integer) pkcs1Sequence.get(4)).getValue(), ((ASN1Integer) pkcs1Sequence.get(1)).getValue(), ((ASN1Integer) pkcs1Sequence.get(2)).getValue(), ((ASN1Integer) pkcs1Sequence.get(3)).getValue());

//BigInteger x, BigInteger p, BigInteger q, BigInteger g
DSAPrivateKeySpec privateKeySpec = new DSAPrivateKeySpec(((ASN1Integer) pkcs1Sequence.get(5)).getValue(), ((ASN1Integer) pkcs1Sequence.get(1)).getValue(), ((ASN1Integer) pkcs1Sequence.get(2)).getValue(), ((ASN1Integer) pkcs1Sequence.get(3)).getValue());

try {
KeyFactory dsaKeyFactory = KeyFactory.getInstance("DSA");
PublicKey dsaPublicKey = dsaKeyFactory.generatePublic(publicKeySpec);
PrivateKey dsaPrivateKey = dsaKeyFactory.generatePrivate(privateKeySpec);
return new KeyPair(dsaPublicKey, dsaPrivateKey);
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
throw new IOException(e);
}
}
} else if (RSA_PRIVATE_KEY.equals(objectType)) {
/*
https://datatracker.ietf.org/doc/html/rfc8017
RSAPrivateKey ::= SEQUENCE {
version Version,
modulus INTEGER, -- n
publicExponent INTEGER, -- e
privateExponent INTEGER, -- d
prime1 INTEGER, -- p
prime2 INTEGER, -- q
exponent1 INTEGER, -- d mod (p-1)
exponent2 INTEGER, -- d mod (q-1)
coefficient INTEGER, -- (inverse of q) mod p
otherPrimeInfos OtherPrimeInfos OPTIONAL
}
*/
try (ASN1InputStream asn1InputStream = new ASN1InputStream(new DERDecoder(), objectContent)) {
ASN1Sequence pkcs1Sequence = asn1InputStream.readObject();

//BigInteger modulus, BigInteger publicExponent
RSAPublicKeySpec publicKeySpec = new RSAPublicKeySpec(((ASN1Integer) pkcs1Sequence.get(1)).getValue(), ((ASN1Integer) pkcs1Sequence.get(2)).getValue());

//BigInteger modulus, BigInteger publicExponent, BigInteger privateExponent, BigInteger primeP, BigInteger primeQ, BigInteger primeExponentP, BigInteger primeExponentQ, BigInteger crtCoefficient
RSAPrivateCrtKeySpec privateKeySpec = new RSAPrivateCrtKeySpec(((ASN1Integer) pkcs1Sequence.get(1)).getValue(), ((ASN1Integer) pkcs1Sequence.get(2)).getValue(), ((ASN1Integer) pkcs1Sequence.get(3)).getValue(), ((ASN1Integer) pkcs1Sequence.get(4)).getValue(), ((ASN1Integer) pkcs1Sequence.get(5)).getValue(), ((ASN1Integer) pkcs1Sequence.get(6)).getValue(), ((ASN1Integer) pkcs1Sequence.get(7)).getValue(), ((ASN1Integer) pkcs1Sequence.get(8)).getValue());

try {
KeyFactory rsaKeyFactory = KeyFactory.getInstance("RSA");
PublicKey rsaPublicKey = rsaKeyFactory.generatePublic(publicKeySpec);
PrivateKey rsaPrivateKey = rsaKeyFactory.generatePrivate(privateKeySpec);
return new KeyPair(rsaPublicKey, rsaPrivateKey);
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
throw new IOException(e);
}
}
} else if (PRIVATE_KEY.equals(objectType)) {
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(objectContent);

InvalidKeySpecException firstException = null;
for (KeyFactory factory : getKeyFactories()) {
try {
return factory.generatePrivate(keySpec);
} catch (InvalidKeySpecException e) {
if (firstException == null)
firstException = e;
}
}
throw new IOException("Private key could not be loaded", firstException);
} else {
throw new IOException("Invalid object: " + objectType);
}
}

return null;
}
}
40 changes: 11 additions & 29 deletions src/main/java/de/dentrassi/crypto/pem/PemUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,31 +11,23 @@

package de.dentrassi.crypto.pem;

import java.io.ByteArrayInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.security.Key;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.openssl.PEMKeyPair;
import org.bouncycastle.openssl.PEMParser;
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;

import de.dentrassi.crypto.pem.AbstractPemKeyStore.Entry;

public class PemUtils {
Expand Down Expand Up @@ -85,44 +77,35 @@ private static InputStream openResource(final String uri) throws IOException {
private static void loadFrom(final Map<String, Entry> result, final String alias, final boolean chained,
final InputStream stream) throws CertificateException, IOException {

final CertificateFactory factory = CertificateFactory.getInstance("X.509");
final JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider(new BouncyCastleProvider());

@SuppressWarnings("resource")
final PEMParser reader = new PEMParser(new InputStreamReader(stream, StandardCharsets.UTF_8));

final List<Certificate> chain = new ArrayList<>();
Key key = null;
int counter = 0;

Object object;
while ((object = reader.readObject()) != null) {

if (object instanceof X509CertificateHolder) {
try (PemReader pemReader = new PemReader(new InputStreamReader(stream, StandardCharsets.UTF_8))) {
while ((object = pemReader.readObject()) != null) {

final X509CertificateHolder certHolder = (X509CertificateHolder) object;
if (object instanceof Certificate) {

final Collection<? extends Certificate> certs = factory
.generateCertificates(new ByteArrayInputStream(certHolder.getEncoded()));
final Certificate cert = (Certificate)object;

for (final Certificate cert : certs) {
if (chained) {
if (cert instanceof X509Certificate) {
chain.add(cert);
}
} else {
result.put(alias + "-" + counter++, new Entry(null, new Certificate[] { cert }));
}
}

} else if (object instanceof PEMKeyPair) {
} else if (object instanceof KeyPair) {

key = converter.getKeyPair((PEMKeyPair) object).getPrivate();
key = ((KeyPair)object).getPrivate();

} else if (object instanceof PrivateKeyInfo) {
} else if (object instanceof PrivateKey) {

key = converter.getPrivateKey((PrivateKeyInfo) object);
key = (PrivateKey)object;

}
}
}

Expand All @@ -140,5 +123,4 @@ private static void loadFrom(final Map<String, Entry> result, final String alias
});

}

}
Loading

0 comments on commit fd51f76

Please sign in to comment.