Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: remove Bouncy Castle dependencies #29

Merged
merged 1 commit into from
Nov 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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) 2024 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