diff options
Diffstat (limited to 'bcpkix/src/main/java/org/bouncycastle/openssl')
30 files changed, 4620 insertions, 0 deletions
diff --git a/bcpkix/src/main/java/org/bouncycastle/openssl/EncryptionException.java b/bcpkix/src/main/java/org/bouncycastle/openssl/EncryptionException.java new file mode 100644 index 00000000..67db2073 --- /dev/null +++ b/bcpkix/src/main/java/org/bouncycastle/openssl/EncryptionException.java @@ -0,0 +1,23 @@ +package org.bouncycastle.openssl; + +public class EncryptionException + extends PEMException +{ + private Throwable cause; + + public EncryptionException(String msg) + { + super(msg); + } + + public EncryptionException(String msg, Throwable ex) + { + super(msg); + this.cause = ex; + } + + public Throwable getCause() + { + return cause; + } +}
\ No newline at end of file diff --git a/bcpkix/src/main/java/org/bouncycastle/openssl/MiscPEMGenerator.java b/bcpkix/src/main/java/org/bouncycastle/openssl/MiscPEMGenerator.java new file mode 100644 index 00000000..488b9282 --- /dev/null +++ b/bcpkix/src/main/java/org/bouncycastle/openssl/MiscPEMGenerator.java @@ -0,0 +1,211 @@ +package org.bouncycastle.openssl; + +import java.io.IOException; +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.List; + +import org.bouncycastle.asn1.ASN1EncodableVector; +import org.bouncycastle.asn1.ASN1Integer; +import org.bouncycastle.asn1.ASN1ObjectIdentifier; +import org.bouncycastle.asn1.DERInteger; +import org.bouncycastle.asn1.DERSequence; +import org.bouncycastle.asn1.cms.ContentInfo; +import org.bouncycastle.asn1.oiw.OIWObjectIdentifiers; +import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers; +import org.bouncycastle.asn1.pkcs.PrivateKeyInfo; +import org.bouncycastle.asn1.x509.DSAParameter; +import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo; +import org.bouncycastle.asn1.x9.X9ObjectIdentifiers; +import org.bouncycastle.cert.X509AttributeCertificateHolder; +import org.bouncycastle.cert.X509CRLHolder; +import org.bouncycastle.cert.X509CertificateHolder; +import org.bouncycastle.pkcs.PKCS10CertificationRequest; +import org.bouncycastle.pkcs.PKCS8EncryptedPrivateKeyInfo; +import org.bouncycastle.util.Strings; +import org.bouncycastle.util.io.pem.PemGenerationException; +import org.bouncycastle.util.io.pem.PemHeader; +import org.bouncycastle.util.io.pem.PemObject; +import org.bouncycastle.util.io.pem.PemObjectGenerator; + +/** + * PEM generator for the original set of PEM objects used in Open SSL. + */ +public class MiscPEMGenerator + implements PemObjectGenerator +{ + private static final ASN1ObjectIdentifier[] dsaOids = + { + X9ObjectIdentifiers.id_dsa, + OIWObjectIdentifiers.dsaWithSHA1 + }; + + private static final byte[] hexEncodingTable = + { + (byte)'0', (byte)'1', (byte)'2', (byte)'3', (byte)'4', (byte)'5', (byte)'6', (byte)'7', + (byte)'8', (byte)'9', (byte)'A', (byte)'B', (byte)'C', (byte)'D', (byte)'E', (byte)'F' + }; + + private final Object obj; + private final PEMEncryptor encryptor; + + public MiscPEMGenerator(Object o) + { + this.obj = o; // use of this confuses some earlier JDKs. + this.encryptor = null; + } + + public MiscPEMGenerator(Object o, PEMEncryptor encryptor) + { + this.obj = o; + this.encryptor = encryptor; + } + + private PemObject createPemObject(Object o) + throws IOException + { + String type; + byte[] encoding; + + if (o instanceof PemObject) + { + return (PemObject)o; + } + if (o instanceof PemObjectGenerator) + { + return ((PemObjectGenerator)o).generate(); + } + if (o instanceof X509CertificateHolder) + { + type = "CERTIFICATE"; + + encoding = ((X509CertificateHolder)o).getEncoded(); + } + else if (o instanceof X509CRLHolder) + { + type = "X509 CRL"; + + encoding = ((X509CRLHolder)o).getEncoded(); + } + else if (o instanceof PrivateKeyInfo) + { + PrivateKeyInfo info = (PrivateKeyInfo)o; + ASN1ObjectIdentifier algOID = info.getPrivateKeyAlgorithm().getAlgorithm(); + + if (algOID.equals(PKCSObjectIdentifiers.rsaEncryption)) + { + type = "RSA PRIVATE KEY"; + + encoding = info.parsePrivateKey().toASN1Primitive().getEncoded(); + } + else if (algOID.equals(dsaOids[0]) || algOID.equals(dsaOids[1])) + { + type = "DSA PRIVATE KEY"; + + DSAParameter p = DSAParameter.getInstance(info.getPrivateKeyAlgorithm().getParameters()); + ASN1EncodableVector v = new ASN1EncodableVector(); + + v.add(new DERInteger(0)); + v.add(new DERInteger(p.getP())); + v.add(new DERInteger(p.getQ())); + v.add(new DERInteger(p.getG())); + + BigInteger x = ASN1Integer.getInstance(info.parsePrivateKey()).getValue(); + BigInteger y = p.getG().modPow(x, p.getP()); + + v.add(new DERInteger(y)); + v.add(new DERInteger(x)); + + encoding = new DERSequence(v).getEncoded(); + } + else if (algOID.equals(X9ObjectIdentifiers.id_ecPublicKey)) + { + type = "EC PRIVATE KEY"; + + encoding = info.parsePrivateKey().toASN1Primitive().getEncoded(); + } + else + { + throw new IOException("Cannot identify private key"); + } + } + else if (o instanceof SubjectPublicKeyInfo) + { + type = "PUBLIC KEY"; + + encoding = ((SubjectPublicKeyInfo)o).getEncoded(); + } + else if (o instanceof X509AttributeCertificateHolder) + { + type = "ATTRIBUTE CERTIFICATE"; + encoding = ((X509AttributeCertificateHolder)o).getEncoded(); + } + else if (o instanceof org.bouncycastle.pkcs.PKCS10CertificationRequest) + { + type = "CERTIFICATE REQUEST"; + encoding = ((PKCS10CertificationRequest)o).getEncoded(); + } + else if (o instanceof ContentInfo) + { + type = "PKCS7"; + encoding = ((ContentInfo)o).getEncoded(); + } + else + { + throw new PemGenerationException("unknown object passed - can't encode."); + } + + if (encryptor != null) + { + String dekAlgName = Strings.toUpperCase(encryptor.getAlgorithm()); + + // Note: For backward compatibility + if (dekAlgName.equals("DESEDE")) + { + dekAlgName = "DES-EDE3-CBC"; + } + + + byte[] iv = encryptor.getIV(); + + byte[] encData = encryptor.encrypt(encoding); + + List headers = new ArrayList(2); + + headers.add(new PemHeader("Proc-Type", "4,ENCRYPTED")); + headers.add(new PemHeader("DEK-Info", dekAlgName + "," + getHexEncoded(iv))); + + return new PemObject(type, headers, encData); + } + return new PemObject(type, encoding); + } + + private String getHexEncoded(byte[] bytes) + throws IOException + { + char[] chars = new char[bytes.length * 2]; + + for (int i = 0; i != bytes.length; i++) + { + int v = bytes[i] & 0xff; + + chars[2 * i] = (char)(hexEncodingTable[(v >>> 4)]); + chars[2 * i + 1] = (char)(hexEncodingTable[v & 0xf]); + } + + return new String(chars); + } + + public PemObject generate() + throws PemGenerationException + { + try + { + return createPemObject(obj); + } + catch (IOException e) + { + throw new PemGenerationException("encoding exception: " + e.getMessage(), e); + } + } +} diff --git a/bcpkix/src/main/java/org/bouncycastle/openssl/PEMDecryptor.java b/bcpkix/src/main/java/org/bouncycastle/openssl/PEMDecryptor.java new file mode 100644 index 00000000..09cef5b7 --- /dev/null +++ b/bcpkix/src/main/java/org/bouncycastle/openssl/PEMDecryptor.java @@ -0,0 +1,7 @@ +package org.bouncycastle.openssl; + +public interface PEMDecryptor +{ + byte[] decrypt(byte[] keyBytes, byte[] iv) + throws PEMException; +} diff --git a/bcpkix/src/main/java/org/bouncycastle/openssl/PEMDecryptorProvider.java b/bcpkix/src/main/java/org/bouncycastle/openssl/PEMDecryptorProvider.java new file mode 100644 index 00000000..b1827cde --- /dev/null +++ b/bcpkix/src/main/java/org/bouncycastle/openssl/PEMDecryptorProvider.java @@ -0,0 +1,9 @@ +package org.bouncycastle.openssl; + +import org.bouncycastle.operator.OperatorCreationException; + +public interface PEMDecryptorProvider +{ + PEMDecryptor get(String dekAlgName) + throws OperatorCreationException; +} diff --git a/bcpkix/src/main/java/org/bouncycastle/openssl/PEMEncryptedKeyPair.java b/bcpkix/src/main/java/org/bouncycastle/openssl/PEMEncryptedKeyPair.java new file mode 100644 index 00000000..4c28f8d1 --- /dev/null +++ b/bcpkix/src/main/java/org/bouncycastle/openssl/PEMEncryptedKeyPair.java @@ -0,0 +1,44 @@ +package org.bouncycastle.openssl; + +import java.io.IOException; + +import org.bouncycastle.operator.OperatorCreationException; + +public class PEMEncryptedKeyPair +{ + private final String dekAlgName; + private final byte[] iv; + private final byte[] keyBytes; + private final PEMKeyPairParser parser; + + PEMEncryptedKeyPair(String dekAlgName, byte[] iv, byte[] keyBytes, PEMKeyPairParser parser) + { + this.dekAlgName = dekAlgName; + this.iv = iv; + this.keyBytes = keyBytes; + this.parser = parser; + } + + public PEMKeyPair decryptKeyPair(PEMDecryptorProvider keyDecryptorProvider) + throws IOException + { + try + { + PEMDecryptor keyDecryptor = keyDecryptorProvider.get(dekAlgName); + + return parser.parse(keyDecryptor.decrypt(keyBytes, iv)); + } + catch (IOException e) + { + throw e; + } + catch (OperatorCreationException e) + { + throw new PEMException("cannot create extraction operator: " + e.getMessage(), e); + } + catch (Exception e) + { + throw new PEMException("exception processing key pair: " + e.getMessage(), e); + } + } +} diff --git a/bcpkix/src/main/java/org/bouncycastle/openssl/PEMEncryptor.java b/bcpkix/src/main/java/org/bouncycastle/openssl/PEMEncryptor.java new file mode 100644 index 00000000..5fb6647a --- /dev/null +++ b/bcpkix/src/main/java/org/bouncycastle/openssl/PEMEncryptor.java @@ -0,0 +1,11 @@ +package org.bouncycastle.openssl; + +public interface PEMEncryptor +{ + String getAlgorithm(); + + byte[] getIV(); + + byte[] encrypt(byte[] encoding) + throws PEMException; +} diff --git a/bcpkix/src/main/java/org/bouncycastle/openssl/PEMException.java b/bcpkix/src/main/java/org/bouncycastle/openssl/PEMException.java new file mode 100644 index 00000000..3753aece --- /dev/null +++ b/bcpkix/src/main/java/org/bouncycastle/openssl/PEMException.java @@ -0,0 +1,34 @@ +package org.bouncycastle.openssl; + +import java.io.IOException; + +public class PEMException + extends IOException +{ + Exception underlying; + + public PEMException( + String message) + { + super(message); + } + + public PEMException( + String message, + Exception underlying) + { + super(message); + this.underlying = underlying; + } + + public Exception getUnderlyingException() + { + return underlying; + } + + + public Throwable getCause() + { + return underlying; + } +} diff --git a/bcpkix/src/main/java/org/bouncycastle/openssl/PEMKeyPair.java b/bcpkix/src/main/java/org/bouncycastle/openssl/PEMKeyPair.java new file mode 100644 index 00000000..077934e1 --- /dev/null +++ b/bcpkix/src/main/java/org/bouncycastle/openssl/PEMKeyPair.java @@ -0,0 +1,26 @@ +package org.bouncycastle.openssl; + +import org.bouncycastle.asn1.pkcs.PrivateKeyInfo; +import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo; + +public class PEMKeyPair +{ + private final SubjectPublicKeyInfo publicKeyInfo; + private final PrivateKeyInfo privateKeyInfo; + + public PEMKeyPair(SubjectPublicKeyInfo publicKeyInfo, PrivateKeyInfo privateKeyInfo) + { + this.publicKeyInfo = publicKeyInfo; + this.privateKeyInfo = privateKeyInfo; + } + + public PrivateKeyInfo getPrivateKeyInfo() + { + return privateKeyInfo; + } + + public SubjectPublicKeyInfo getPublicKeyInfo() + { + return publicKeyInfo; + } +} diff --git a/bcpkix/src/main/java/org/bouncycastle/openssl/PEMKeyPairParser.java b/bcpkix/src/main/java/org/bouncycastle/openssl/PEMKeyPairParser.java new file mode 100644 index 00000000..fc0cb041 --- /dev/null +++ b/bcpkix/src/main/java/org/bouncycastle/openssl/PEMKeyPairParser.java @@ -0,0 +1,9 @@ +package org.bouncycastle.openssl; + +import java.io.IOException; + +interface PEMKeyPairParser +{ + PEMKeyPair parse(byte[] encoding) + throws IOException; +} diff --git a/bcpkix/src/main/java/org/bouncycastle/openssl/PEMParser.java b/bcpkix/src/main/java/org/bouncycastle/openssl/PEMParser.java new file mode 100644 index 00000000..672f3da5 --- /dev/null +++ b/bcpkix/src/main/java/org/bouncycastle/openssl/PEMParser.java @@ -0,0 +1,509 @@ +package org.bouncycastle.openssl; + +import java.io.IOException; +import java.io.Reader; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.StringTokenizer; + +import org.bouncycastle.asn1.ASN1InputStream; +import org.bouncycastle.asn1.ASN1Integer; +import org.bouncycastle.asn1.ASN1ObjectIdentifier; +import org.bouncycastle.asn1.ASN1Primitive; +import org.bouncycastle.asn1.ASN1Sequence; +import org.bouncycastle.asn1.DERNull; +import org.bouncycastle.asn1.cms.ContentInfo; +import org.bouncycastle.asn1.pkcs.EncryptedPrivateKeyInfo; +import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers; +import org.bouncycastle.asn1.pkcs.PrivateKeyInfo; +import org.bouncycastle.asn1.pkcs.RSAPublicKey; +import org.bouncycastle.asn1.x509.AlgorithmIdentifier; +import org.bouncycastle.asn1.x509.DSAParameter; +import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo; +import org.bouncycastle.asn1.x9.X9ECParameters; +import org.bouncycastle.asn1.x9.X9ObjectIdentifiers; +import org.bouncycastle.cert.X509AttributeCertificateHolder; +import org.bouncycastle.cert.X509CRLHolder; +import org.bouncycastle.cert.X509CertificateHolder; +import org.bouncycastle.pkcs.PKCS10CertificationRequest; +import org.bouncycastle.pkcs.PKCS8EncryptedPrivateKeyInfo; +import org.bouncycastle.util.encoders.Hex; +import org.bouncycastle.util.io.pem.PemHeader; +import org.bouncycastle.util.io.pem.PemObject; +import org.bouncycastle.util.io.pem.PemObjectParser; +import org.bouncycastle.util.io.pem.PemReader; + +/** + * Class for parsing OpenSSL PEM encoded streams containing + * X509 certificates, PKCS8 encoded keys and PKCS7 objects. + * <p> + * In the case of PKCS7 objects the reader will return a CMS ContentInfo object. Public keys will be returned as + * well formed SubjectPublicKeyInfo objects, private keys will be returned as well formed PrivateKeyInfo objects. In the + * case of a private key a PEMKeyPair will normally be returned if the encoding contains both the private and public + * key definition. CRLs, Certificates, PKCS#10 requests, and Attribute Certificates will generate the appropriate BC holder class. + * </p> + */ +public class PEMParser + extends PemReader +{ + private final Map parsers = new HashMap(); + + /** + * Create a new PEMReader + * + * @param reader the Reader + */ + public PEMParser( + Reader reader) + { + super(reader); + + parsers.put("CERTIFICATE REQUEST", new PKCS10CertificationRequestParser()); + parsers.put("NEW CERTIFICATE REQUEST", new PKCS10CertificationRequestParser()); + parsers.put("CERTIFICATE", new X509CertificateParser()); + parsers.put("X509 CERTIFICATE", new X509CertificateParser()); + parsers.put("X509 CRL", new X509CRLParser()); + parsers.put("PKCS7", new PKCS7Parser()); + parsers.put("ATTRIBUTE CERTIFICATE", new X509AttributeCertificateParser()); + parsers.put("EC PARAMETERS", new ECCurveParamsParser()); + parsers.put("PUBLIC KEY", new PublicKeyParser()); + parsers.put("RSA PUBLIC KEY", new RSAPublicKeyParser()); + parsers.put("RSA PRIVATE KEY", new KeyPairParser(new RSAKeyPairParser())); + parsers.put("DSA PRIVATE KEY", new KeyPairParser(new DSAKeyPairParser())); + parsers.put("EC PRIVATE KEY", new KeyPairParser(new ECDSAKeyPairParser())); + parsers.put("ENCRYPTED PRIVATE KEY", new EncryptedPrivateKeyParser()); + parsers.put("PRIVATE KEY", new PrivateKeyParser()); + } + + public Object readObject() + throws IOException + { + PemObject obj = readPemObject(); + + if (obj != null) + { + String type = obj.getType(); + if (parsers.containsKey(type)) + { + return ((PemObjectParser)parsers.get(type)).parseObject(obj); + } + else + { + throw new IOException("unrecognised object: " + type); + } + } + + return null; + } + + private class KeyPairParser + implements PemObjectParser + { + private final PEMKeyPairParser pemKeyPairParser; + + public KeyPairParser(PEMKeyPairParser pemKeyPairParser) + { + this.pemKeyPairParser = pemKeyPairParser; + } + + /** + * Read a Key Pair + */ + public Object parseObject( + PemObject obj) + throws IOException + { + boolean isEncrypted = false; + String dekInfo = null; + List headers = obj.getHeaders(); + + for (Iterator it = headers.iterator(); it.hasNext();) + { + PemHeader hdr = (PemHeader)it.next(); + + if (hdr.getName().equals("Proc-Type") && hdr.getValue().equals("4,ENCRYPTED")) + { + isEncrypted = true; + } + else if (hdr.getName().equals("DEK-Info")) + { + dekInfo = hdr.getValue(); + } + } + + // + // extract the key + // + byte[] keyBytes = obj.getContent(); + + try + { + if (isEncrypted) + { + StringTokenizer tknz = new StringTokenizer(dekInfo, ","); + String dekAlgName = tknz.nextToken(); + byte[] iv = Hex.decode(tknz.nextToken()); + + return new PEMEncryptedKeyPair(dekAlgName, iv, keyBytes, pemKeyPairParser); + } + + return pemKeyPairParser.parse(keyBytes); + } + catch (IOException e) + { + if (isEncrypted) + { + throw new PEMException("exception decoding - please check password and data.", e); + } + else + { + throw new PEMException(e.getMessage(), e); + } + } + catch (IllegalArgumentException e) + { + if (isEncrypted) + { + throw new PEMException("exception decoding - please check password and data.", e); + } + else + { + throw new PEMException(e.getMessage(), e); + } + } + } + } + + private class DSAKeyPairParser + implements PEMKeyPairParser + { + public PEMKeyPair parse(byte[] encoding) + throws IOException + { + try + { + ASN1Sequence seq = ASN1Sequence.getInstance(encoding); + + if (seq.size() != 6) + { + throw new PEMException("malformed sequence in DSA private key"); + } + + // ASN1Integer v = (ASN1Integer)seq.getObjectAt(0); + ASN1Integer p = ASN1Integer.getInstance(seq.getObjectAt(1)); + ASN1Integer q = ASN1Integer.getInstance(seq.getObjectAt(2)); + ASN1Integer g = ASN1Integer.getInstance(seq.getObjectAt(3)); + ASN1Integer y = ASN1Integer.getInstance(seq.getObjectAt(4)); + ASN1Integer x = ASN1Integer.getInstance(seq.getObjectAt(5)); + + return new PEMKeyPair( + new SubjectPublicKeyInfo(new AlgorithmIdentifier(X9ObjectIdentifiers.id_dsa, new DSAParameter(p.getValue(), q.getValue(), g.getValue())), y), + new PrivateKeyInfo(new AlgorithmIdentifier(X9ObjectIdentifiers.id_dsa, new DSAParameter(p.getValue(), q.getValue(), g.getValue())), x)); + } + catch (IOException e) + { + throw e; + } + catch (Exception e) + { + throw new PEMException( + "problem creating DSA private key: " + e.toString(), e); + } + } + } + + private class ECDSAKeyPairParser + implements PEMKeyPairParser + { + public PEMKeyPair parse(byte[] encoding) + throws IOException + { + try + { + ASN1Sequence seq = ASN1Sequence.getInstance(encoding); + + org.bouncycastle.asn1.sec.ECPrivateKey pKey = org.bouncycastle.asn1.sec.ECPrivateKey.getInstance(seq); + AlgorithmIdentifier algId = new AlgorithmIdentifier(X9ObjectIdentifiers.id_ecPublicKey, pKey.getParameters()); + PrivateKeyInfo privInfo = new PrivateKeyInfo(algId, pKey); + SubjectPublicKeyInfo pubInfo = new SubjectPublicKeyInfo(algId, pKey.getPublicKey().getBytes()); + + return new PEMKeyPair(pubInfo, privInfo); + } + catch (IOException e) + { + throw e; + } + catch (Exception e) + { + throw new PEMException( + "problem creating EC private key: " + e.toString(), e); + } + } + } + + private class RSAKeyPairParser + implements PEMKeyPairParser + { + public PEMKeyPair parse(byte[] encoding) + throws IOException + { + try + { + ASN1Sequence seq = ASN1Sequence.getInstance(encoding); + + if (seq.size() != 9) + { + throw new PEMException("malformed sequence in RSA private key"); + } + + org.bouncycastle.asn1.pkcs.RSAPrivateKey keyStruct = org.bouncycastle.asn1.pkcs.RSAPrivateKey.getInstance(seq); + + RSAPublicKey pubSpec = new RSAPublicKey( + keyStruct.getModulus(), keyStruct.getPublicExponent()); + + AlgorithmIdentifier algId = new AlgorithmIdentifier(PKCSObjectIdentifiers.rsaEncryption, DERNull.INSTANCE); + + return new PEMKeyPair(new SubjectPublicKeyInfo(algId, pubSpec), new PrivateKeyInfo(algId, keyStruct)); + } + catch (IOException e) + { + throw e; + } + catch (Exception e) + { + throw new PEMException( + "problem creating RSA private key: " + e.toString(), e); + } + } + } + + private class PublicKeyParser + implements PemObjectParser + { + public PublicKeyParser() + { + } + + public Object parseObject(PemObject obj) + throws IOException + { + return SubjectPublicKeyInfo.getInstance(obj.getContent()); + } + } + + private class RSAPublicKeyParser + implements PemObjectParser + { + public RSAPublicKeyParser() + { + } + + public Object parseObject(PemObject obj) + throws IOException + { + try + { + RSAPublicKey rsaPubStructure = RSAPublicKey.getInstance(obj.getContent()); + + return new SubjectPublicKeyInfo(new AlgorithmIdentifier(PKCSObjectIdentifiers.rsaEncryption, DERNull.INSTANCE), rsaPubStructure); + } + catch (IOException e) + { + throw e; + } + catch (Exception e) + { + throw new PEMException("problem extracting key: " + e.toString(), e); + } + } + } + + private class X509CertificateParser + implements PemObjectParser + { + /** + * Reads in a X509Certificate. + * + * @return the X509Certificate + * @throws java.io.IOException if an I/O error occured + */ + public Object parseObject(PemObject obj) + throws IOException + { + try + { + return new X509CertificateHolder(obj.getContent()); + } + catch (Exception e) + { + throw new PEMException("problem parsing cert: " + e.toString(), e); + } + } + } + + private class X509CRLParser + implements PemObjectParser + { + /** + * Reads in a X509CRL. + * + * @return the X509Certificate + * @throws java.io.IOException if an I/O error occured + */ + public Object parseObject(PemObject obj) + throws IOException + { + try + { + return new X509CRLHolder(obj.getContent()); + } + catch (Exception e) + { + throw new PEMException("problem parsing cert: " + e.toString(), e); + } + } + } + + private class PKCS10CertificationRequestParser + implements PemObjectParser + { + /** + * Reads in a PKCS10 certification request. + * + * @return the certificate request. + * @throws java.io.IOException if an I/O error occured + */ + public Object parseObject(PemObject obj) + throws IOException + { + try + { + return new PKCS10CertificationRequest(obj.getContent()); + } + catch (Exception e) + { + throw new PEMException("problem parsing certrequest: " + e.toString(), e); + } + } + } + + private class PKCS7Parser + implements PemObjectParser + { + /** + * Reads in a PKCS7 object. This returns a ContentInfo object suitable for use with the CMS + * API. + * + * @return the X509Certificate + * @throws java.io.IOException if an I/O error occured + */ + public Object parseObject(PemObject obj) + throws IOException + { + try + { + ASN1InputStream aIn = new ASN1InputStream(obj.getContent()); + + return ContentInfo.getInstance(aIn.readObject()); + } + catch (Exception e) + { + throw new PEMException("problem parsing PKCS7 object: " + e.toString(), e); + } + } + } + + private class X509AttributeCertificateParser + implements PemObjectParser + { + public Object parseObject(PemObject obj) + throws IOException + { + return new X509AttributeCertificateHolder(obj.getContent()); + } + } + + private class ECCurveParamsParser + implements PemObjectParser + { + public Object parseObject(PemObject obj) + throws IOException + { + try + { + Object param = ASN1Primitive.fromByteArray(obj.getContent()); + + if (param instanceof ASN1ObjectIdentifier) + { + return ASN1Primitive.fromByteArray(obj.getContent()); + } + else if (param instanceof ASN1Sequence) + { + return X9ECParameters.getInstance(param); + } + else + { + return null; // implicitly CA + } + } + catch (IOException e) + { + throw e; + } + catch (Exception e) + { + throw new PEMException("exception extracting EC named curve: " + e.toString()); + } + } + } + + private class EncryptedPrivateKeyParser + implements PemObjectParser + { + public EncryptedPrivateKeyParser() + { + } + + /** + * Reads in an EncryptedPrivateKeyInfo + * + * @return the X509Certificate + * @throws java.io.IOException if an I/O error occured + */ + public Object parseObject(PemObject obj) + throws IOException + { + try + { + return new PKCS8EncryptedPrivateKeyInfo(EncryptedPrivateKeyInfo.getInstance(obj.getContent())); + } + catch (Exception e) + { + throw new PEMException("problem parsing ENCRYPTED PRIVATE KEY: " + e.toString(), e); + } + } + } + + private class PrivateKeyParser + implements PemObjectParser + { + public PrivateKeyParser() + { + } + + public Object parseObject(PemObject obj) + throws IOException + { + try + { + return PrivateKeyInfo.getInstance(obj.getContent()); + } + catch (Exception e) + { + throw new PEMException("problem parsing PRIVATE KEY: " + e.toString(), e); + } + } + } +} diff --git a/bcpkix/src/main/java/org/bouncycastle/openssl/PEMReader.java b/bcpkix/src/main/java/org/bouncycastle/openssl/PEMReader.java new file mode 100644 index 00000000..b11ae12c --- /dev/null +++ b/bcpkix/src/main/java/org/bouncycastle/openssl/PEMReader.java @@ -0,0 +1,1023 @@ +package org.bouncycastle.openssl; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.Reader; +import java.security.AlgorithmParameters; +import java.security.Key; +import java.security.KeyFactory; +import java.security.KeyPair; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.Provider; +import java.security.PublicKey; +import java.security.Security; +import java.security.cert.CertificateFactory; +import java.security.spec.AlgorithmParameterSpec; +import java.security.spec.DSAPrivateKeySpec; +import java.security.spec.DSAPublicKeySpec; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.KeySpec; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.RSAPrivateCrtKeySpec; +import java.security.spec.RSAPublicKeySpec; +import java.security.spec.X509EncodedKeySpec; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.StringTokenizer; + +import javax.crypto.Cipher; +import javax.crypto.SecretKey; +import javax.crypto.SecretKeyFactory; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.PBEKeySpec; +import javax.crypto.spec.PBEParameterSpec; +import javax.crypto.spec.RC2ParameterSpec; +import javax.crypto.spec.SecretKeySpec; + +import org.bouncycastle.asn1.ASN1InputStream; +import org.bouncycastle.asn1.ASN1Primitive; +import org.bouncycastle.asn1.ASN1Sequence; +import org.bouncycastle.asn1.DERInteger; +import org.bouncycastle.asn1.DERObjectIdentifier; +import org.bouncycastle.asn1.cms.ContentInfo; +import org.bouncycastle.asn1.pkcs.EncryptedPrivateKeyInfo; +import org.bouncycastle.asn1.pkcs.EncryptionScheme; +import org.bouncycastle.asn1.pkcs.KeyDerivationFunc; +import org.bouncycastle.asn1.pkcs.PBEParameter; +import org.bouncycastle.asn1.pkcs.PBES2Parameters; +import org.bouncycastle.asn1.pkcs.PBKDF2Params; +import org.bouncycastle.asn1.pkcs.PKCS12PBEParams; +import org.bouncycastle.asn1.pkcs.PrivateKeyInfo; +import org.bouncycastle.asn1.pkcs.RSAPublicKey; +import org.bouncycastle.asn1.x509.AlgorithmIdentifier; +import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo; +import org.bouncycastle.asn1.x9.X9ObjectIdentifiers; +import org.bouncycastle.crypto.PBEParametersGenerator; +import org.bouncycastle.crypto.generators.OpenSSLPBEParametersGenerator; +import org.bouncycastle.crypto.generators.PKCS5S2ParametersGenerator; +import org.bouncycastle.crypto.params.KeyParameter; +import org.bouncycastle.jce.ECNamedCurveTable; +import org.bouncycastle.jce.PKCS10CertificationRequest; +import org.bouncycastle.util.encoders.Hex; +import org.bouncycastle.util.io.pem.PemHeader; +import org.bouncycastle.util.io.pem.PemObject; +import org.bouncycastle.util.io.pem.PemObjectParser; +import org.bouncycastle.util.io.pem.PemReader; +import org.bouncycastle.x509.X509V2AttributeCertificate; + +/** + * Class for reading OpenSSL PEM encoded streams containing + * X509 certificates, PKCS8 encoded keys and PKCS7 objects. + * <p> + * In the case of PKCS7 objects the reader will return a CMS ContentInfo object. Keys and + * Certificates will be returned using the appropriate java.security type (KeyPair, PublicKey, X509Certificate, + * or X509CRL). In the case of a Certificate Request a PKCS10CertificationRequest will be returned. + * </p> + * + * @deprecated use PEMParser + */ +public class PEMReader + extends PemReader +{ + private final Map parsers = new HashMap(); + + private PasswordFinder pFinder; + + + /** + * Create a new PEMReader + * + * @param reader the Reader + * @deprecated use PEMParser + */ + public PEMReader( + Reader reader) + { + this(reader, null, "BC"); + } + + /** + * Create a new PEMReader with a password finder + * + * @param reader the Reader + * @param pFinder the password finder + * @deprecated use PEMParser + */ + public PEMReader( + Reader reader, + PasswordFinder pFinder) + { + this(reader, pFinder, "BC"); + } + + /** + * Create a new PEMReader with a password finder + * + * @param reader the Reader + * @param pFinder the password finder + * @param provider the cryptography provider to use + * @deprecated use PEMParser + */ + public PEMReader( + Reader reader, + PasswordFinder pFinder, + String provider) + { + this(reader, pFinder, provider, provider); + } + + /** + * Create a new PEMReader with a password finder and differing providers for secret and public key + * operations. + * + * @param reader the Reader + * @param pFinder the password finder + * @param symProvider provider to use for symmetric operations + * @param asymProvider provider to use for asymmetric (public/private key) operations + * @deprecated use PEMParser + */ + public PEMReader( + Reader reader, + PasswordFinder pFinder, + String symProvider, + String asymProvider) + { + super(reader); + + this.pFinder = pFinder; + + parsers.put("CERTIFICATE REQUEST", new PKCS10CertificationRequestParser()); + parsers.put("NEW CERTIFICATE REQUEST", new PKCS10CertificationRequestParser()); + parsers.put("CERTIFICATE", new X509CertificateParser(asymProvider)); + parsers.put("X509 CERTIFICATE", new X509CertificateParser(asymProvider)); + parsers.put("X509 CRL", new X509CRLParser(asymProvider)); + parsers.put("PKCS7", new PKCS7Parser()); + parsers.put("ATTRIBUTE CERTIFICATE", new X509AttributeCertificateParser()); + parsers.put("EC PARAMETERS", new ECNamedCurveSpecParser()); + parsers.put("PUBLIC KEY", new PublicKeyParser(asymProvider)); + parsers.put("RSA PUBLIC KEY", new RSAPublicKeyParser(asymProvider)); + parsers.put("RSA PRIVATE KEY", new RSAKeyPairParser(symProvider, asymProvider)); + parsers.put("DSA PRIVATE KEY", new DSAKeyPairParser(symProvider, asymProvider)); + parsers.put("EC PRIVATE KEY", new ECDSAKeyPairParser(symProvider, asymProvider)); + parsers.put("ENCRYPTED PRIVATE KEY", new EncryptedPrivateKeyParser(symProvider, asymProvider)); + parsers.put("PRIVATE KEY", new PrivateKeyParser(asymProvider)); + } + + public Object readObject() + throws IOException + { + PemObject obj = readPemObject(); + + if (obj != null) + { + String type = obj.getType(); + if (parsers.containsKey(type)) + { + return ((PemObjectParser)parsers.get(type)).parseObject(obj); + } + else + { + throw new IOException("unrecognised object: " + type); + } + } + + return null; + } + + private abstract class KeyPairParser + implements PemObjectParser + { + protected String symProvider; + + public KeyPairParser(String symProvider) + { + this.symProvider = symProvider; + } + + /** + * Read a Key Pair + */ + protected ASN1Sequence readKeyPair( + PemObject obj) + throws IOException + { + boolean isEncrypted = false; + String dekInfo = null; + List headers = obj.getHeaders(); + + for (Iterator it = headers.iterator(); it.hasNext(); ) + { + PemHeader hdr = (PemHeader)it.next(); + + if (hdr.getName().equals("Proc-Type") && hdr.getValue().equals("4,ENCRYPTED")) + { + isEncrypted = true; + } + else if (hdr.getName().equals("DEK-Info")) + { + dekInfo = hdr.getValue(); + } + } + + // + // extract the key + // + byte[] keyBytes = obj.getContent(); + + if (isEncrypted) + { + if (pFinder == null) + { + throw new PasswordException("No password finder specified, but a password is required"); + } + + char[] password = pFinder.getPassword(); + + if (password == null) + { + throw new PasswordException("Password is null, but a password is required"); + } + + StringTokenizer tknz = new StringTokenizer(dekInfo, ","); + String dekAlgName = tknz.nextToken(); + byte[] iv = Hex.decode(tknz.nextToken()); + + keyBytes = crypt(false, symProvider, keyBytes, password, dekAlgName, iv); + } + + try + { + return ASN1Sequence.getInstance(ASN1Primitive.fromByteArray(keyBytes)); + } + catch (IOException e) + { + if (isEncrypted) + { + throw new PEMException("exception decoding - please check password and data.", e); + } + else + { + throw new PEMException(e.getMessage(), e); + } + } + catch (IllegalArgumentException e) + { + if (isEncrypted) + { + throw new PEMException("exception decoding - please check password and data.", e); + } + else + { + throw new PEMException(e.getMessage(), e); + } + } + } + } + + private class DSAKeyPairParser + extends KeyPairParser + { + private String asymProvider; + + public DSAKeyPairParser(String symProvider, String asymProvider) + { + super(symProvider); + + this.asymProvider = asymProvider; + } + + public Object parseObject(PemObject obj) + throws IOException + { + try + { + ASN1Sequence seq = readKeyPair(obj); + + if (seq.size() != 6) + { + throw new PEMException("malformed sequence in DSA private key"); + } + + // DERInteger v = (DERInteger)seq.getObjectAt(0); + DERInteger p = (DERInteger)seq.getObjectAt(1); + DERInteger q = (DERInteger)seq.getObjectAt(2); + DERInteger g = (DERInteger)seq.getObjectAt(3); + DERInteger y = (DERInteger)seq.getObjectAt(4); + DERInteger x = (DERInteger)seq.getObjectAt(5); + + DSAPrivateKeySpec privSpec = new DSAPrivateKeySpec( + x.getValue(), p.getValue(), + q.getValue(), g.getValue()); + DSAPublicKeySpec pubSpec = new DSAPublicKeySpec( + y.getValue(), p.getValue(), + q.getValue(), g.getValue()); + + KeyFactory fact = KeyFactory.getInstance("DSA", asymProvider); + + return new KeyPair( + fact.generatePublic(pubSpec), + fact.generatePrivate(privSpec)); + } + catch (IOException e) + { + throw e; + } + catch (Exception e) + { + throw new PEMException( + "problem creating DSA private key: " + e.toString(), e); + } + } + } + + private class ECDSAKeyPairParser + extends KeyPairParser + { + private String asymProvider; + + public ECDSAKeyPairParser(String symProvider, String asymProvider) + { + super(symProvider); + + this.asymProvider = asymProvider; + } + + public Object parseObject(PemObject obj) + throws IOException + { + try + { + ASN1Sequence seq = readKeyPair(obj); + + org.bouncycastle.asn1.sec.ECPrivateKey pKey = org.bouncycastle.asn1.sec.ECPrivateKey.getInstance(seq); + AlgorithmIdentifier algId = new AlgorithmIdentifier(X9ObjectIdentifiers.id_ecPublicKey, pKey.getParameters()); + PrivateKeyInfo privInfo = new PrivateKeyInfo(algId, pKey); + SubjectPublicKeyInfo pubInfo = new SubjectPublicKeyInfo(algId, pKey.getPublicKey().getBytes()); + + PKCS8EncodedKeySpec privSpec = new PKCS8EncodedKeySpec(privInfo.getEncoded()); + X509EncodedKeySpec pubSpec = new X509EncodedKeySpec(pubInfo.getEncoded()); + + + KeyFactory fact = KeyFactory.getInstance("ECDSA", asymProvider); + + + return new KeyPair( + fact.generatePublic(pubSpec), + fact.generatePrivate(privSpec)); + } + catch (IOException e) + { + throw e; + } + catch (Exception e) + { + throw new PEMException( + "problem creating EC private key: " + e.toString(), e); + } + } + } + + private class RSAKeyPairParser + extends KeyPairParser + { + private String asymProvider; + + public RSAKeyPairParser(String symProvider, String asymProvider) + { + super(symProvider); + + this.asymProvider = asymProvider; + } + + public Object parseObject(PemObject obj) + throws IOException + { + try + { + ASN1Sequence seq = readKeyPair(obj); + + if (seq.size() != 9) + { + throw new PEMException("malformed sequence in RSA private key"); + } + + org.bouncycastle.asn1.pkcs.RSAPrivateKey keyStruct = org.bouncycastle.asn1.pkcs.RSAPrivateKey.getInstance(seq); + + RSAPublicKeySpec pubSpec = new RSAPublicKeySpec( + keyStruct.getModulus(), keyStruct.getPublicExponent()); + RSAPrivateCrtKeySpec privSpec = new RSAPrivateCrtKeySpec( + keyStruct.getModulus(), keyStruct.getPublicExponent(), keyStruct.getPrivateExponent(), + keyStruct.getPrime1(), keyStruct.getPrime2(), + keyStruct.getExponent1(), keyStruct.getExponent2(), + keyStruct.getCoefficient()); + + KeyFactory fact = KeyFactory.getInstance("RSA", asymProvider); + + return new KeyPair( + fact.generatePublic(pubSpec), + fact.generatePrivate(privSpec)); + } + catch (IOException e) + { + throw e; + } + catch (Exception e) + { + throw new PEMException( + "problem creating RSA private key: " + e.toString(), e); + } + } + } + + private class PublicKeyParser + implements PemObjectParser + { + private String provider; + + public PublicKeyParser(String provider) + { + this.provider = provider; + } + + public Object parseObject(PemObject obj) + throws IOException + { + KeySpec keySpec = new X509EncodedKeySpec(obj.getContent()); + String[] algorithms = {"DSA", "RSA"}; + for (int i = 0; i < algorithms.length; i++) + { + try + { + KeyFactory keyFact = KeyFactory.getInstance(algorithms[i], provider); + PublicKey pubKey = keyFact.generatePublic(keySpec); + + return pubKey; + } + catch (NoSuchAlgorithmException e) + { + // ignore + } + catch (InvalidKeySpecException e) + { + // ignore + } + catch (NoSuchProviderException e) + { + throw new RuntimeException("can't find provider " + provider); + } + } + + return null; + } + } + + private class RSAPublicKeyParser + implements PemObjectParser + { + private String provider; + + public RSAPublicKeyParser(String provider) + { + this.provider = provider; + } + + public Object parseObject(PemObject obj) + throws IOException + { + try + { + ASN1InputStream ais = new ASN1InputStream(obj.getContent()); + Object asnObject = ais.readObject(); + ASN1Sequence sequence = (ASN1Sequence)asnObject; + RSAPublicKey rsaPubStructure = RSAPublicKey.getInstance(sequence); + RSAPublicKeySpec keySpec = new RSAPublicKeySpec( + rsaPubStructure.getModulus(), + rsaPubStructure.getPublicExponent()); + + + KeyFactory keyFact = KeyFactory.getInstance("RSA", provider); + + return keyFact.generatePublic(keySpec); + } + catch (IOException e) + { + throw e; + } + catch (NoSuchProviderException e) + { + throw new IOException("can't find provider " + provider); + } + catch (Exception e) + { + throw new PEMException("problem extracting key: " + e.toString(), e); + } + } + } + + private class X509CertificateParser + implements PemObjectParser + { + private String provider; + + public X509CertificateParser(String provider) + { + this.provider = provider; + } + + /** + * Reads in a X509Certificate. + * + * @return the X509Certificate + * @throws IOException if an I/O error occured + */ + public Object parseObject(PemObject obj) + throws IOException + { + ByteArrayInputStream bIn = new ByteArrayInputStream(obj.getContent()); + + try + { + CertificateFactory certFact + = CertificateFactory.getInstance("X.509", provider); + + return certFact.generateCertificate(bIn); + } + catch (Exception e) + { + throw new PEMException("problem parsing cert: " + e.toString(), e); + } + } + } + + private class X509CRLParser + implements PemObjectParser + { + private String provider; + + public X509CRLParser(String provider) + { + this.provider = provider; + } + + /** + * Reads in a X509CRL. + * + * @return the X509Certificate + * @throws IOException if an I/O error occured + */ + public Object parseObject(PemObject obj) + throws IOException + { + ByteArrayInputStream bIn = new ByteArrayInputStream(obj.getContent()); + + try + { + CertificateFactory certFact + = CertificateFactory.getInstance("X.509", provider); + + return certFact.generateCRL(bIn); + } + catch (Exception e) + { + throw new PEMException("problem parsing cert: " + e.toString(), e); + } + } + } + + private class PKCS10CertificationRequestParser + implements PemObjectParser + { + /** + * Reads in a PKCS10 certification request. + * + * @return the certificate request. + * @throws IOException if an I/O error occured + */ + public Object parseObject(PemObject obj) + throws IOException + { + try + { + return new PKCS10CertificationRequest(obj.getContent()); + } + catch (Exception e) + { + throw new PEMException("problem parsing certrequest: " + e.toString(), e); + } + } + } + + private class PKCS7Parser + implements PemObjectParser + { + /** + * Reads in a PKCS7 object. This returns a ContentInfo object suitable for use with the CMS + * API. + * + * @return the X509Certificate + * @throws IOException if an I/O error occured + */ + public Object parseObject(PemObject obj) + throws IOException + { + try + { + ASN1InputStream aIn = new ASN1InputStream(obj.getContent()); + + return ContentInfo.getInstance(aIn.readObject()); + } + catch (Exception e) + { + throw new PEMException("problem parsing PKCS7 object: " + e.toString(), e); + } + } + } + + private class X509AttributeCertificateParser + implements PemObjectParser + { + public Object parseObject(PemObject obj) + throws IOException + { + return new X509V2AttributeCertificate(obj.getContent()); + } + } + + private class ECNamedCurveSpecParser + implements PemObjectParser + { + public Object parseObject(PemObject obj) + throws IOException + { + try + { + DERObjectIdentifier oid = (DERObjectIdentifier)ASN1Primitive.fromByteArray(obj.getContent()); + + Object params = ECNamedCurveTable.getParameterSpec(oid.getId()); + + if (params == null) + { + throw new IOException("object ID not found in EC curve table"); + } + + return params; + } + catch (IOException e) + { + throw e; + } + catch (Exception e) + { + throw new PEMException("exception extracting EC named curve: " + e.toString()); + } + } + } + + private class EncryptedPrivateKeyParser + implements PemObjectParser + { + private String symProvider; + private String asymProvider; + + public EncryptedPrivateKeyParser(String symProvider, String asymProvider) + { + this.symProvider = symProvider; + this.asymProvider = asymProvider; + } + + /** + * Reads in a X509CRL. + * + * @return the X509Certificate + * @throws IOException if an I/O error occured + */ + public Object parseObject(PemObject obj) + throws IOException + { + try + { + EncryptedPrivateKeyInfo info = EncryptedPrivateKeyInfo.getInstance(ASN1Primitive.fromByteArray(obj.getContent())); + AlgorithmIdentifier algId = info.getEncryptionAlgorithm(); + + if (pFinder == null) + { + throw new PEMException("no PasswordFinder specified"); + } + + if (PEMUtilities.isPKCS5Scheme2(algId.getAlgorithm())) + { + PBES2Parameters params = PBES2Parameters.getInstance(algId.getParameters()); + KeyDerivationFunc func = params.getKeyDerivationFunc(); + EncryptionScheme scheme = params.getEncryptionScheme(); + PBKDF2Params defParams = (PBKDF2Params)func.getParameters(); + + int iterationCount = defParams.getIterationCount().intValue(); + byte[] salt = defParams.getSalt(); + + String algorithm = scheme.getAlgorithm().getId(); + + SecretKey key = generateSecretKeyForPKCS5Scheme2(algorithm, pFinder.getPassword(), salt, iterationCount); + + Cipher cipher = Cipher.getInstance(algorithm, symProvider); + AlgorithmParameters algParams = AlgorithmParameters.getInstance(algorithm, symProvider); + + algParams.init(scheme.getParameters().toASN1Primitive().getEncoded()); + + cipher.init(Cipher.DECRYPT_MODE, key, algParams); + + PrivateKeyInfo pInfo = PrivateKeyInfo.getInstance(ASN1Primitive.fromByteArray(cipher.doFinal(info.getEncryptedData()))); + PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(pInfo.getEncoded()); + + KeyFactory keyFact = KeyFactory.getInstance(pInfo.getPrivateKeyAlgorithm().getAlgorithm().getId(), asymProvider); + + return keyFact.generatePrivate(keySpec); + } + else if (PEMUtilities.isPKCS12(algId.getAlgorithm())) + { + PKCS12PBEParams params = PKCS12PBEParams.getInstance(algId.getParameters()); + String algorithm = algId.getAlgorithm().getId(); + PBEKeySpec pbeSpec = new PBEKeySpec(pFinder.getPassword()); + + SecretKeyFactory secKeyFact = SecretKeyFactory.getInstance(algorithm, symProvider); + PBEParameterSpec defParams = new PBEParameterSpec(params.getIV(), params.getIterations().intValue()); + + Cipher cipher = Cipher.getInstance(algorithm, symProvider); + + cipher.init(Cipher.DECRYPT_MODE, secKeyFact.generateSecret(pbeSpec), defParams); + + PrivateKeyInfo pInfo = PrivateKeyInfo.getInstance(ASN1Primitive.fromByteArray(cipher.doFinal(info.getEncryptedData()))); + PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(pInfo.getEncoded()); + + KeyFactory keyFact = KeyFactory.getInstance(pInfo.getPrivateKeyAlgorithm().getAlgorithm().getId(), asymProvider); + + return keyFact.generatePrivate(keySpec); + } + else if (PEMUtilities.isPKCS5Scheme1(algId.getAlgorithm())) + { + PBEParameter params = PBEParameter.getInstance(algId.getParameters()); + String algorithm = algId.getAlgorithm().getId(); + PBEKeySpec pbeSpec = new PBEKeySpec(pFinder.getPassword()); + + SecretKeyFactory secKeyFact = SecretKeyFactory.getInstance(algorithm, symProvider); + PBEParameterSpec defParams = new PBEParameterSpec(params.getSalt(), params.getIterationCount().intValue()); + + Cipher cipher = Cipher.getInstance(algorithm, symProvider); + + cipher.init(Cipher.DECRYPT_MODE, secKeyFact.generateSecret(pbeSpec), defParams); + + PrivateKeyInfo pInfo = PrivateKeyInfo.getInstance(ASN1Primitive.fromByteArray(cipher.doFinal(info.getEncryptedData()))); + PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(pInfo.getEncoded()); + + KeyFactory keyFact = KeyFactory.getInstance(pInfo.getPrivateKeyAlgorithm().getAlgorithm().getId(), asymProvider); + + return keyFact.generatePrivate(keySpec); + } + else + { + throw new PEMException("Unknown algorithm: " + algId.getAlgorithm()); + } + } + catch (IOException e) + { + throw e; + } + catch (Exception e) + { + throw new PEMException("problem parsing ENCRYPTED PRIVATE KEY: " + e.toString(), e); + } + } + } + + private class PrivateKeyParser + implements PemObjectParser + { + private String provider; + + public PrivateKeyParser(String provider) + { + this.provider = provider; + } + + public Object parseObject(PemObject obj) + throws IOException + { + try + { + PrivateKeyInfo info = PrivateKeyInfo.getInstance(ASN1Primitive.fromByteArray(obj.getContent())); + PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(obj.getContent()); + + KeyFactory keyFact = KeyFactory.getInstance(info.getPrivateKeyAlgorithm().getAlgorithm().getId(), provider); + + return keyFact.generatePrivate(keySpec); + } + catch (Exception e) + { + throw new PEMException("problem parsing PRIVATE KEY: " + e.toString(), e); + } + } + } + + static byte[] crypt( + boolean encrypt, + String provider, + byte[] bytes, + char[] password, + String dekAlgName, + byte[] iv) + throws IOException + { + Provider prov = null; + if (provider != null) + { + prov = Security.getProvider(provider); + if (prov == null) + { + throw new EncryptionException("cannot find provider: " + provider); + } + } + + return crypt(encrypt, prov, bytes, password, dekAlgName, iv); + } + + static byte[] crypt( + boolean encrypt, + Provider provider, + byte[] bytes, + char[] password, + String dekAlgName, + byte[] iv) + throws IOException + { + AlgorithmParameterSpec paramSpec = new IvParameterSpec(iv); + String alg; + String blockMode = "CBC"; + String padding = "PKCS5Padding"; + Key sKey; + + // Figure out block mode and padding. + if (dekAlgName.endsWith("-CFB")) + { + blockMode = "CFB"; + padding = "NoPadding"; + } + if (dekAlgName.endsWith("-ECB") || + "DES-EDE".equals(dekAlgName) || + "DES-EDE3".equals(dekAlgName)) + { + // ECB is actually the default (though seldom used) when OpenSSL + // uses DES-EDE (des2) or DES-EDE3 (des3). + blockMode = "ECB"; + paramSpec = null; + } + if (dekAlgName.endsWith("-OFB")) + { + blockMode = "OFB"; + padding = "NoPadding"; + } + + + // Figure out algorithm and key size. + if (dekAlgName.startsWith("DES-EDE")) + { + alg = "DESede"; + // "DES-EDE" is actually des2 in OpenSSL-speak! + // "DES-EDE3" is des3. + boolean des2 = !dekAlgName.startsWith("DES-EDE3"); + sKey = getKey(password, alg, 24, iv, des2); + } + else if (dekAlgName.startsWith("DES-")) + { + alg = "DES"; + sKey = getKey(password, alg, 8, iv); + } + else if (dekAlgName.startsWith("BF-")) + { + alg = "Blowfish"; + sKey = getKey(password, alg, 16, iv); + } + else if (dekAlgName.startsWith("RC2-")) + { + alg = "RC2"; + int keyBits = 128; + if (dekAlgName.startsWith("RC2-40-")) + { + keyBits = 40; + } + else if (dekAlgName.startsWith("RC2-64-")) + { + keyBits = 64; + } + sKey = getKey(password, alg, keyBits / 8, iv); + if (paramSpec == null) // ECB block mode + { + paramSpec = new RC2ParameterSpec(keyBits); + } + else + { + paramSpec = new RC2ParameterSpec(keyBits, iv); + } + } + else if (dekAlgName.startsWith("AES-")) + { + alg = "AES"; + byte[] salt = iv; + if (salt.length > 8) + { + salt = new byte[8]; + System.arraycopy(iv, 0, salt, 0, 8); + } + + int keyBits; + if (dekAlgName.startsWith("AES-128-")) + { + keyBits = 128; + } + else if (dekAlgName.startsWith("AES-192-")) + { + keyBits = 192; + } + else if (dekAlgName.startsWith("AES-256-")) + { + keyBits = 256; + } + else + { + throw new EncryptionException("unknown AES encryption with private key"); + } + sKey = getKey(password, "AES", keyBits / 8, salt); + } + else + { + throw new EncryptionException("unknown encryption with private key"); + } + + String transformation = alg + "/" + blockMode + "/" + padding; + + try + { + Cipher c = Cipher.getInstance(transformation, provider); + int mode = encrypt ? Cipher.ENCRYPT_MODE : Cipher.DECRYPT_MODE; + + if (paramSpec == null) // ECB block mode + { + c.init(mode, sKey); + } + else + { + c.init(mode, sKey, paramSpec); + } + return c.doFinal(bytes); + } + catch (Exception e) + { + throw new EncryptionException("exception using cipher - please check password and data.", e); + } + } + + private static SecretKey getKey( + char[] password, + String algorithm, + int keyLength, + byte[] salt) + { + return getKey(password, algorithm, keyLength, salt, false); + } + + private static SecretKey getKey( + char[] password, + String algorithm, + int keyLength, + byte[] salt, + boolean des2) + { + OpenSSLPBEParametersGenerator pGen = new OpenSSLPBEParametersGenerator(); + + pGen.init(PBEParametersGenerator.PKCS5PasswordToBytes(password), salt); + + KeyParameter keyParam; + keyParam = (KeyParameter)pGen.generateDerivedParameters(keyLength * 8); + byte[] key = keyParam.getKey(); + if (des2 && key.length >= 24) + { + // For DES2, we must copy first 8 bytes into the last 8 bytes. + System.arraycopy(key, 0, key, 16, 8); + } + return new javax.crypto.spec.SecretKeySpec(key, algorithm); + } + + + public static SecretKey generateSecretKeyForPKCS5Scheme2(String algorithm, char[] password, byte[] salt, int iterationCount) + { + PBEParametersGenerator generator = new PKCS5S2ParametersGenerator(); + + generator.init( + PBEParametersGenerator.PKCS5PasswordToBytes(password), + salt, + iterationCount); + + return new SecretKeySpec(((KeyParameter)generator.generateDerivedParameters(PEMUtilities.getKeySize(algorithm))).getKey(), algorithm); + } +} diff --git a/bcpkix/src/main/java/org/bouncycastle/openssl/PEMUtilities.java b/bcpkix/src/main/java/org/bouncycastle/openssl/PEMUtilities.java new file mode 100644 index 00000000..e6bd989d --- /dev/null +++ b/bcpkix/src/main/java/org/bouncycastle/openssl/PEMUtilities.java @@ -0,0 +1,65 @@ +package org.bouncycastle.openssl; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import org.bouncycastle.asn1.ASN1ObjectIdentifier; +import org.bouncycastle.asn1.DERObjectIdentifier; +import org.bouncycastle.asn1.nist.NISTObjectIdentifiers; +import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers; +import org.bouncycastle.util.Integers; + +public final class PEMUtilities +{ + private static final Map KEYSIZES = new HashMap(); + private static final Set PKCS5_SCHEME_1 = new HashSet(); + private static final Set PKCS5_SCHEME_2 = new HashSet(); + + static + { + PKCS5_SCHEME_1.add(PKCSObjectIdentifiers.pbeWithMD2AndDES_CBC); + PKCS5_SCHEME_1.add(PKCSObjectIdentifiers.pbeWithMD2AndRC2_CBC); + PKCS5_SCHEME_1.add(PKCSObjectIdentifiers.pbeWithMD5AndDES_CBC); + PKCS5_SCHEME_1.add(PKCSObjectIdentifiers.pbeWithMD5AndRC2_CBC); + PKCS5_SCHEME_1.add(PKCSObjectIdentifiers.pbeWithSHA1AndDES_CBC); + PKCS5_SCHEME_1.add(PKCSObjectIdentifiers.pbeWithSHA1AndRC2_CBC); + + PKCS5_SCHEME_2.add(PKCSObjectIdentifiers.id_PBES2); + PKCS5_SCHEME_2.add(PKCSObjectIdentifiers.des_EDE3_CBC); + PKCS5_SCHEME_2.add(NISTObjectIdentifiers.id_aes128_CBC); + PKCS5_SCHEME_2.add(NISTObjectIdentifiers.id_aes192_CBC); + PKCS5_SCHEME_2.add(NISTObjectIdentifiers.id_aes256_CBC); + + KEYSIZES.put(PKCSObjectIdentifiers.des_EDE3_CBC.getId(), Integers.valueOf(192)); + KEYSIZES.put(NISTObjectIdentifiers.id_aes128_CBC.getId(), Integers.valueOf(128)); + KEYSIZES.put(NISTObjectIdentifiers.id_aes192_CBC.getId(), Integers.valueOf(192)); + KEYSIZES.put(NISTObjectIdentifiers.id_aes256_CBC.getId(), Integers.valueOf(256)); + } + + static int getKeySize(String algorithm) + { + if (!KEYSIZES.containsKey(algorithm)) + { + throw new IllegalStateException("no key size for algorithm: " + algorithm); + } + + return ((Integer)KEYSIZES.get(algorithm)).intValue(); + } + + static boolean isPKCS5Scheme1(DERObjectIdentifier algOid) + { + return PKCS5_SCHEME_1.contains(algOid); + } + + public static boolean isPKCS5Scheme2(ASN1ObjectIdentifier algOid) + { + return PKCS5_SCHEME_2.contains(algOid); + } + + public static boolean isPKCS12(DERObjectIdentifier algOid) + { + return algOid.getId().startsWith(PKCSObjectIdentifiers.pkcs_12PbeIds.getId()); + } +} diff --git a/bcpkix/src/main/java/org/bouncycastle/openssl/PEMWriter.java b/bcpkix/src/main/java/org/bouncycastle/openssl/PEMWriter.java new file mode 100644 index 00000000..c9ef265a --- /dev/null +++ b/bcpkix/src/main/java/org/bouncycastle/openssl/PEMWriter.java @@ -0,0 +1,91 @@ +package org.bouncycastle.openssl; + +import java.io.IOException; +import java.io.Writer; +import java.security.SecureRandom; + +import org.bouncycastle.openssl.jcajce.JcaMiscPEMGenerator; +import org.bouncycastle.openssl.jcajce.JcePEMEncryptorBuilder; +import org.bouncycastle.util.io.pem.PemGenerationException; +import org.bouncycastle.util.io.pem.PemObjectGenerator; +import org.bouncycastle.util.io.pem.PemWriter; + +/** + * General purpose writer for OpenSSL PEM objects. + */ +public class PEMWriter + extends PemWriter +{ + private String provider; + + /** + * Base constructor. + * + * @param out output stream to use. + */ + public PEMWriter(Writer out) + { + this(out, "BC"); + } + + /** + * @deprecated use constructor that just takes out, and writeObject(PEMEncryptor) + * @param out + * @param provider + */ + public PEMWriter( + Writer out, + String provider) + { + super(out); + + this.provider = provider; + } + + public void writeObject( + Object obj) + throws IOException + { + writeObject(obj, null); + } + + public void writeObject( + Object obj, + PEMEncryptor encryptor) + throws IOException + { + try + { + super.writeObject(new JcaMiscPEMGenerator(obj, encryptor)); + } + catch (PemGenerationException e) + { + if (e.getCause() instanceof IOException) + { + throw (IOException)e.getCause(); + } + + throw e; + } + } + + public void writeObject( + PemObjectGenerator obj) + throws IOException + { + super.writeObject(obj); + } + + /** + * @deprecated use writeObject(obj, PEMEncryptor) + */ + public void writeObject( + Object obj, + String algorithm, + char[] password, + SecureRandom random) + throws IOException + { + this.writeObject(obj, new JcePEMEncryptorBuilder(algorithm).setSecureRandom(random).setProvider(provider).build(password)); + } +} diff --git a/bcpkix/src/main/java/org/bouncycastle/openssl/PKCS8Generator.java b/bcpkix/src/main/java/org/bouncycastle/openssl/PKCS8Generator.java new file mode 100644 index 00000000..448d885b --- /dev/null +++ b/bcpkix/src/main/java/org/bouncycastle/openssl/PKCS8Generator.java @@ -0,0 +1,196 @@ +package org.bouncycastle.openssl; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.PrivateKey; +import java.security.Provider; +import java.security.SecureRandom; +import java.security.Security; + +import org.bouncycastle.asn1.ASN1ObjectIdentifier; +import org.bouncycastle.asn1.nist.NISTObjectIdentifiers; +import org.bouncycastle.asn1.pkcs.EncryptedPrivateKeyInfo; +import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers; +import org.bouncycastle.asn1.pkcs.PrivateKeyInfo; +import org.bouncycastle.openssl.jcajce.JceOpenSSLPKCS8EncryptorBuilder; +import org.bouncycastle.operator.OperatorCreationException; +import org.bouncycastle.operator.OutputEncryptor; +import org.bouncycastle.util.io.pem.PemGenerationException; +import org.bouncycastle.util.io.pem.PemObject; +import org.bouncycastle.util.io.pem.PemObjectGenerator; + +public class PKCS8Generator + implements PemObjectGenerator +{ + public static final ASN1ObjectIdentifier AES_128_CBC = NISTObjectIdentifiers.id_aes128_CBC; + public static final ASN1ObjectIdentifier AES_192_CBC = NISTObjectIdentifiers.id_aes192_CBC; + public static final ASN1ObjectIdentifier AES_256_CBC = NISTObjectIdentifiers.id_aes256_CBC; + + public static final ASN1ObjectIdentifier DES3_CBC = PKCSObjectIdentifiers.des_EDE3_CBC; + + public static final ASN1ObjectIdentifier PBE_SHA1_RC4_128 = PKCSObjectIdentifiers.pbeWithSHAAnd128BitRC4; + public static final ASN1ObjectIdentifier PBE_SHA1_RC4_40 = PKCSObjectIdentifiers.pbeWithSHAAnd40BitRC4; + public static final ASN1ObjectIdentifier PBE_SHA1_3DES = PKCSObjectIdentifiers.pbeWithSHAAnd3_KeyTripleDES_CBC; + public static final ASN1ObjectIdentifier PBE_SHA1_2DES = PKCSObjectIdentifiers.pbeWithSHAAnd2_KeyTripleDES_CBC; + public static final ASN1ObjectIdentifier PBE_SHA1_RC2_128 = PKCSObjectIdentifiers.pbeWithSHAAnd128BitRC2_CBC; + public static final ASN1ObjectIdentifier PBE_SHA1_RC2_40 = PKCSObjectIdentifiers.pbeWithSHAAnd40BitRC2_CBC; + + private PrivateKeyInfo key; + private OutputEncryptor outputEncryptor; + private JceOpenSSLPKCS8EncryptorBuilder encryptorBuilder; + + /** + * Constructor for an unencrypted private key PEM object. + * + * @param key private key to be encoded. + * @deprecated use JcaPKCS8Generator + */ + public PKCS8Generator(PrivateKey key) + { + this.key = PrivateKeyInfo.getInstance(key.getEncoded()); + } + + /** + * Constructor for an encrypted private key PEM object. + * + * @param key private key to be encoded + * @param algorithm encryption algorithm to use + * @param provider name of provider to use + * @throws NoSuchProviderException if provider cannot be found + * @throws NoSuchAlgorithmException if algorithm/mode cannot be found + * @deprecated use JcaPKCS8Generator + */ + public PKCS8Generator(PrivateKey key, ASN1ObjectIdentifier algorithm, String provider) + throws NoSuchProviderException, NoSuchAlgorithmException + { + Provider prov = Security.getProvider(provider); + + if (prov == null) + { + throw new NoSuchProviderException("cannot find provider: " + provider); + } + + init(key, algorithm, prov); + } + + /** + * Constructor for an encrypted private key PEM object. + * + * @param key private key to be encoded + * @param algorithm encryption algorithm to use + * @param provider provider to use + * @throws NoSuchAlgorithmException if algorithm/mode cannot be found + * @deprecated use JcaPKCS8Generator + */ + public PKCS8Generator(PrivateKey key, ASN1ObjectIdentifier algorithm, Provider provider) + throws NoSuchAlgorithmException + { + init(key, algorithm, provider); + } + + /** + * Base constructor. + */ + public PKCS8Generator(PrivateKeyInfo key, OutputEncryptor outputEncryptor) + { + this.key = key; + this.outputEncryptor = outputEncryptor; + } + + private void init(PrivateKey key, ASN1ObjectIdentifier algorithm, Provider provider) + throws NoSuchAlgorithmException + { + this.key = PrivateKeyInfo.getInstance(key.getEncoded()); + this.encryptorBuilder = new JceOpenSSLPKCS8EncryptorBuilder(algorithm); + + encryptorBuilder.setProvider(provider); + } + + /** + * @deprecated ignored in the updated case. + */ + public PKCS8Generator setSecureRandom(SecureRandom random) + { + encryptorBuilder.setRandom(random); + + return this; + } + + /** + * @deprecated ignored in the updated case. + */ + public PKCS8Generator setPassword(char[] password) + { + encryptorBuilder.setPasssword(password); + + return this; + } + + /** + * @deprecated ignored in the updated case. + */ + public PKCS8Generator setIterationCount(int iterationCount) + { + encryptorBuilder.setIterationCount(iterationCount); + + return this; + } + + public PemObject generate() + throws PemGenerationException + { + try + { + if (encryptorBuilder != null) + { + outputEncryptor = encryptorBuilder.build(); + } + } + catch (OperatorCreationException e) + { + throw new PemGenerationException("unable to create operator: " + e.getMessage(), e); + } + + if (outputEncryptor != null) + { + return generate(key, outputEncryptor); + } + else + { + return generate(key, null); + } + } + + private PemObject generate(PrivateKeyInfo key, OutputEncryptor encryptor) + throws PemGenerationException + { + try + { + byte[] keyData = key.getEncoded(); + + if (encryptor == null) + { + return new PemObject("PRIVATE KEY", keyData); + } + + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + + OutputStream cOut = encryptor.getOutputStream(bOut); + + cOut.write(key.getEncoded()); + + cOut.close(); + + EncryptedPrivateKeyInfo info = new EncryptedPrivateKeyInfo(encryptor.getAlgorithmIdentifier(), bOut.toByteArray()); + + return new PemObject("ENCRYPTED PRIVATE KEY", info.getEncoded()); + } + catch (IOException e) + { + throw new PemGenerationException("unable to process encoded key data: " + e.getMessage(), e); + } + } +} diff --git a/bcpkix/src/main/java/org/bouncycastle/openssl/PasswordException.java b/bcpkix/src/main/java/org/bouncycastle/openssl/PasswordException.java new file mode 100644 index 00000000..89625e78 --- /dev/null +++ b/bcpkix/src/main/java/org/bouncycastle/openssl/PasswordException.java @@ -0,0 +1,10 @@ +package org.bouncycastle.openssl; + +public class PasswordException + extends PEMException +{ + public PasswordException(String msg) + { + super(msg); + } +} diff --git a/bcpkix/src/main/java/org/bouncycastle/openssl/PasswordFinder.java b/bcpkix/src/main/java/org/bouncycastle/openssl/PasswordFinder.java new file mode 100644 index 00000000..fb89cf08 --- /dev/null +++ b/bcpkix/src/main/java/org/bouncycastle/openssl/PasswordFinder.java @@ -0,0 +1,9 @@ +package org.bouncycastle.openssl; + +/** + * call back to allow a password to be fetched when one is requested. + */ +public interface PasswordFinder +{ + public char[] getPassword(); +} diff --git a/bcpkix/src/main/java/org/bouncycastle/openssl/jcajce/JcaMiscPEMGenerator.java b/bcpkix/src/main/java/org/bouncycastle/openssl/jcajce/JcaMiscPEMGenerator.java new file mode 100644 index 00000000..6547078d --- /dev/null +++ b/bcpkix/src/main/java/org/bouncycastle/openssl/jcajce/JcaMiscPEMGenerator.java @@ -0,0 +1,98 @@ +package org.bouncycastle.openssl.jcajce; + +import java.io.IOException; +import java.security.Key; +import java.security.KeyPair; +import java.security.PrivateKey; +import java.security.Provider; +import java.security.PublicKey; +import java.security.SecureRandom; +import java.security.cert.CRLException; +import java.security.cert.CertificateEncodingException; +import java.security.cert.X509CRL; +import java.security.cert.X509Certificate; + +import org.bouncycastle.asn1.pkcs.PrivateKeyInfo; +import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo; +import org.bouncycastle.cert.jcajce.JcaX509AttributeCertificateHolder; +import org.bouncycastle.cert.jcajce.JcaX509CRLHolder; +import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder; +import org.bouncycastle.jce.PKCS10CertificationRequest; +import org.bouncycastle.openssl.MiscPEMGenerator; +import org.bouncycastle.openssl.PEMEncryptor; +import org.bouncycastle.x509.X509AttributeCertificate; +import org.bouncycastle.x509.X509V2AttributeCertificate; + +/** + * PEM generator for the original set of PEM objects used in Open SSL. + */ +public class JcaMiscPEMGenerator + extends MiscPEMGenerator +{ + private Object obj; + private String algorithm; + private char[] password; + private SecureRandom random; + private Provider provider; + + public JcaMiscPEMGenerator(Object o) + throws IOException + { + super(convertObject(o)); + } + + public JcaMiscPEMGenerator(Object o, PEMEncryptor encryptor) + throws IOException + { + super(convertObject(o), encryptor); + } + + private static Object convertObject(Object o) + throws IOException + { + if (o instanceof X509Certificate) + { + try + { + return new JcaX509CertificateHolder((X509Certificate)o); + } + catch (CertificateEncodingException e) + { + throw new IllegalArgumentException("Cannot encode object: " + e.toString()); + } + } + else if (o instanceof X509CRL) + { + try + { + return new JcaX509CRLHolder((X509CRL)o); + } + catch (CRLException e) + { + throw new IllegalArgumentException("Cannot encode object: " + e.toString()); + } + } + else if (o instanceof KeyPair) + { + return convertObject(((KeyPair)o).getPrivate()); + } + else if (o instanceof PrivateKey) + { + return PrivateKeyInfo.getInstance(((Key)o).getEncoded()); + } + else if (o instanceof PublicKey) + { + return SubjectPublicKeyInfo.getInstance(((PublicKey)o).getEncoded()); + } + else if (o instanceof X509AttributeCertificate) + { + return new JcaX509AttributeCertificateHolder((X509V2AttributeCertificate)o); + } + else if (o instanceof PKCS10CertificationRequest) + { + return new org.bouncycastle.pkcs.PKCS10CertificationRequest(((PKCS10CertificationRequest)o).getEncoded()); + } + + return o; + } +} diff --git a/bcpkix/src/main/java/org/bouncycastle/openssl/jcajce/JcaPEMKeyConverter.java b/bcpkix/src/main/java/org/bouncycastle/openssl/jcajce/JcaPEMKeyConverter.java new file mode 100644 index 00000000..4d55aa36 --- /dev/null +++ b/bcpkix/src/main/java/org/bouncycastle/openssl/jcajce/JcaPEMKeyConverter.java @@ -0,0 +1,105 @@ +package org.bouncycastle.openssl.jcajce; + +import java.security.KeyFactory; +import java.security.KeyPair; +import java.security.PrivateKey; +import java.security.Provider; +import java.security.PublicKey; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.X509EncodedKeySpec; + +import org.bouncycastle.asn1.pkcs.PrivateKeyInfo; +import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo; +import org.bouncycastle.asn1.x9.X9ObjectIdentifiers; +import org.bouncycastle.jcajce.DefaultJcaJceHelper; +import org.bouncycastle.jcajce.JcaJceHelper; +import org.bouncycastle.jcajce.NamedJcaJceHelper; +import org.bouncycastle.jcajce.ProviderJcaJceHelper; +import org.bouncycastle.openssl.PEMException; +import org.bouncycastle.openssl.PEMKeyPair; + +public class JcaPEMKeyConverter +{ + private JcaJceHelper helper = new DefaultJcaJceHelper(); + + public JcaPEMKeyConverter setProvider(Provider provider) + { + this.helper = new ProviderJcaJceHelper(provider); + + return this; + } + + public JcaPEMKeyConverter setProvider(String providerName) + { + this.helper = new NamedJcaJceHelper(providerName); + + return this; + } + + public KeyPair getKeyPair(PEMKeyPair keyPair) + throws PEMException + { + try + { + String algorithm = keyPair.getPrivateKeyInfo().getPrivateKeyAlgorithm().getAlgorithm().getId(); + + if (X9ObjectIdentifiers.id_ecPublicKey.getId().equals(algorithm)) + { + algorithm = "ECDSA"; + } + + KeyFactory keyFactory = helper.createKeyFactory(algorithm); + + return new KeyPair(keyFactory.generatePublic(new X509EncodedKeySpec(keyPair.getPublicKeyInfo().getEncoded())), + keyFactory.generatePrivate(new PKCS8EncodedKeySpec(keyPair.getPrivateKeyInfo().getEncoded()))); + } + catch (Exception e) + { + throw new PEMException("unable to convert key pair: " + e.getMessage(), e); + } + } + + public PublicKey getPublicKey(SubjectPublicKeyInfo publicKeyInfo) + throws PEMException + { + try + { + String algorithm = publicKeyInfo.getAlgorithm().getAlgorithm().getId(); + + if (X9ObjectIdentifiers.id_ecPublicKey.getId().equals(algorithm)) + { + algorithm = "ECDSA"; + } + + KeyFactory keyFactory = helper.createKeyFactory(algorithm); + + return keyFactory.generatePublic(new X509EncodedKeySpec(publicKeyInfo.getEncoded())); + } + catch (Exception e) + { + throw new PEMException("unable to convert key pair: " + e.getMessage(), e); + } + } + + public PrivateKey getPrivateKey(PrivateKeyInfo privateKeyInfo) + throws PEMException + { + try + { + String algorithm = privateKeyInfo.getPrivateKeyAlgorithm().getAlgorithm().getId(); + + if (X9ObjectIdentifiers.id_ecPublicKey.getId().equals(algorithm)) + { + algorithm = "ECDSA"; + } + + KeyFactory keyFactory = helper.createKeyFactory(algorithm); + + return keyFactory.generatePrivate(new PKCS8EncodedKeySpec(privateKeyInfo.getEncoded())); + } + catch (Exception e) + { + throw new PEMException("unable to convert key pair: " + e.getMessage(), e); + } + } +} diff --git a/bcpkix/src/main/java/org/bouncycastle/openssl/jcajce/JcaPKCS8Generator.java b/bcpkix/src/main/java/org/bouncycastle/openssl/jcajce/JcaPKCS8Generator.java new file mode 100644 index 00000000..261dcecb --- /dev/null +++ b/bcpkix/src/main/java/org/bouncycastle/openssl/jcajce/JcaPKCS8Generator.java @@ -0,0 +1,18 @@ +package org.bouncycastle.openssl.jcajce; + +import java.security.PrivateKey; + +import org.bouncycastle.asn1.pkcs.PrivateKeyInfo; +import org.bouncycastle.openssl.PKCS8Generator; +import org.bouncycastle.operator.OutputEncryptor; +import org.bouncycastle.util.io.pem.PemGenerationException; + +public class JcaPKCS8Generator + extends PKCS8Generator +{ + public JcaPKCS8Generator(PrivateKey key, OutputEncryptor encryptor) + throws PemGenerationException + { + super(PrivateKeyInfo.getInstance(key.getEncoded()), encryptor); + } +} diff --git a/bcpkix/src/main/java/org/bouncycastle/openssl/jcajce/JceOpenSSLPKCS8DecryptorProviderBuilder.java b/bcpkix/src/main/java/org/bouncycastle/openssl/jcajce/JceOpenSSLPKCS8DecryptorProviderBuilder.java new file mode 100644 index 00000000..0880f780 --- /dev/null +++ b/bcpkix/src/main/java/org/bouncycastle/openssl/jcajce/JceOpenSSLPKCS8DecryptorProviderBuilder.java @@ -0,0 +1,141 @@ +package org.bouncycastle.openssl.jcajce; + +import java.io.IOException; +import java.io.InputStream; +import java.security.AlgorithmParameters; +import java.security.GeneralSecurityException; +import java.security.Provider; + +import javax.crypto.Cipher; +import javax.crypto.CipherInputStream; +import javax.crypto.SecretKey; +import javax.crypto.SecretKeyFactory; +import javax.crypto.spec.PBEKeySpec; +import javax.crypto.spec.PBEParameterSpec; + +import org.bouncycastle.asn1.pkcs.EncryptionScheme; +import org.bouncycastle.asn1.pkcs.KeyDerivationFunc; +import org.bouncycastle.asn1.pkcs.PBEParameter; +import org.bouncycastle.asn1.pkcs.PBES2Parameters; +import org.bouncycastle.asn1.pkcs.PBKDF2Params; +import org.bouncycastle.asn1.pkcs.PKCS12PBEParams; +import org.bouncycastle.asn1.x509.AlgorithmIdentifier; +import org.bouncycastle.jcajce.DefaultJcaJceHelper; +import org.bouncycastle.jcajce.JcaJceHelper; +import org.bouncycastle.jcajce.NamedJcaJceHelper; +import org.bouncycastle.jcajce.ProviderJcaJceHelper; +import org.bouncycastle.openssl.PEMException; +import org.bouncycastle.operator.InputDecryptor; +import org.bouncycastle.operator.InputDecryptorProvider; +import org.bouncycastle.operator.OperatorCreationException; + +public class JceOpenSSLPKCS8DecryptorProviderBuilder +{ + private JcaJceHelper helper = new DefaultJcaJceHelper(); + + public JceOpenSSLPKCS8DecryptorProviderBuilder() + { + helper = new DefaultJcaJceHelper(); + } + + public JceOpenSSLPKCS8DecryptorProviderBuilder setProvider(String providerName) + { + helper = new NamedJcaJceHelper(providerName); + + return this; + } + + public JceOpenSSLPKCS8DecryptorProviderBuilder setProvider(Provider provider) + { + helper = new ProviderJcaJceHelper(provider); + + return this; + } + + public InputDecryptorProvider build(final char[] password) + throws OperatorCreationException + { + return new InputDecryptorProvider() + { + public InputDecryptor get(final AlgorithmIdentifier algorithm) + throws OperatorCreationException + { + final Cipher cipher; + + try + { + if (PEMUtilities.isPKCS5Scheme2(algorithm.getAlgorithm())) + { + PBES2Parameters params = PBES2Parameters.getInstance(algorithm.getParameters()); + KeyDerivationFunc func = params.getKeyDerivationFunc(); + EncryptionScheme scheme = params.getEncryptionScheme(); + PBKDF2Params defParams = (PBKDF2Params)func.getParameters(); + + int iterationCount = defParams.getIterationCount().intValue(); + byte[] salt = defParams.getSalt(); + + String oid = scheme.getAlgorithm().getId(); + + SecretKey key = PEMUtilities.generateSecretKeyForPKCS5Scheme2(oid, password, salt, iterationCount); + + cipher = helper.createCipher(oid); + AlgorithmParameters algParams = helper.createAlgorithmParameters(oid); + + algParams.init(scheme.getParameters().toASN1Primitive().getEncoded()); + + cipher.init(Cipher.DECRYPT_MODE, key, algParams); + } + else if (PEMUtilities.isPKCS12(algorithm.getAlgorithm())) + { + PKCS12PBEParams params = PKCS12PBEParams.getInstance(algorithm.getParameters()); + PBEKeySpec pbeSpec = new PBEKeySpec(password); + + SecretKeyFactory secKeyFact = helper.createSecretKeyFactory(algorithm.getAlgorithm().getId()); + PBEParameterSpec defParams = new PBEParameterSpec(params.getIV(), params.getIterations().intValue()); + + cipher = helper.createCipher(algorithm.getAlgorithm().getId()); + + cipher.init(Cipher.DECRYPT_MODE, secKeyFact.generateSecret(pbeSpec), defParams); + } + else if (PEMUtilities.isPKCS5Scheme1(algorithm.getAlgorithm())) + { + PBEParameter params = PBEParameter.getInstance(algorithm.getParameters()); + PBEKeySpec pbeSpec = new PBEKeySpec(password); + + SecretKeyFactory secKeyFact = helper.createSecretKeyFactory(algorithm.getAlgorithm().getId()); + PBEParameterSpec defParams = new PBEParameterSpec(params.getSalt(), params.getIterationCount().intValue()); + + cipher = helper.createCipher(algorithm.getAlgorithm().getId()); + + cipher.init(Cipher.DECRYPT_MODE, secKeyFact.generateSecret(pbeSpec), defParams); + } + else + { + throw new PEMException("Unknown algorithm: " + algorithm.getAlgorithm()); + } + + return new InputDecryptor() + { + public AlgorithmIdentifier getAlgorithmIdentifier() + { + return algorithm; + } + + public InputStream getInputStream(InputStream encIn) + { + return new CipherInputStream(encIn, cipher); + } + }; + } + catch (IOException e) + { + throw new OperatorCreationException(algorithm.getAlgorithm() + " not available: " + e.getMessage(), e); + } + catch (GeneralSecurityException e) + { + throw new OperatorCreationException(algorithm.getAlgorithm() + " not available: " + e.getMessage(), e); + } + }; + }; + } +} diff --git a/bcpkix/src/main/java/org/bouncycastle/openssl/jcajce/JceOpenSSLPKCS8EncryptorBuilder.java b/bcpkix/src/main/java/org/bouncycastle/openssl/jcajce/JceOpenSSLPKCS8EncryptorBuilder.java new file mode 100644 index 00000000..f677ddf5 --- /dev/null +++ b/bcpkix/src/main/java/org/bouncycastle/openssl/jcajce/JceOpenSSLPKCS8EncryptorBuilder.java @@ -0,0 +1,221 @@ +package org.bouncycastle.openssl.jcajce; + +import java.io.IOException; +import java.io.OutputStream; +import java.security.AlgorithmParameterGenerator; +import java.security.AlgorithmParameters; +import java.security.GeneralSecurityException; +import java.security.Provider; +import java.security.SecureRandom; + +import javax.crypto.Cipher; +import javax.crypto.CipherOutputStream; +import javax.crypto.SecretKey; +import javax.crypto.SecretKeyFactory; +import javax.crypto.spec.PBEKeySpec; +import javax.crypto.spec.PBEParameterSpec; + +import org.bouncycastle.asn1.ASN1EncodableVector; +import org.bouncycastle.asn1.ASN1Integer; +import org.bouncycastle.asn1.ASN1ObjectIdentifier; +import org.bouncycastle.asn1.ASN1Primitive; +import org.bouncycastle.asn1.DEROctetString; +import org.bouncycastle.asn1.DERSequence; +import org.bouncycastle.asn1.nist.NISTObjectIdentifiers; +import org.bouncycastle.asn1.pkcs.KeyDerivationFunc; +import org.bouncycastle.asn1.pkcs.PBES2Parameters; +import org.bouncycastle.asn1.pkcs.PBKDF2Params; +import org.bouncycastle.asn1.pkcs.PKCS12PBEParams; +import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers; +import org.bouncycastle.asn1.x509.AlgorithmIdentifier; +import org.bouncycastle.jcajce.DefaultJcaJceHelper; +import org.bouncycastle.jcajce.JcaJceHelper; +import org.bouncycastle.jcajce.NamedJcaJceHelper; +import org.bouncycastle.jcajce.ProviderJcaJceHelper; +import org.bouncycastle.operator.GenericKey; +import org.bouncycastle.operator.OperatorCreationException; +import org.bouncycastle.operator.OutputEncryptor; +import org.bouncycastle.operator.jcajce.JceGenericKey; + +public class JceOpenSSLPKCS8EncryptorBuilder +{ + public static final String AES_128_CBC = NISTObjectIdentifiers.id_aes128_CBC.getId(); + public static final String AES_192_CBC = NISTObjectIdentifiers.id_aes192_CBC.getId(); + public static final String AES_256_CBC = NISTObjectIdentifiers.id_aes256_CBC.getId(); + + public static final String DES3_CBC = PKCSObjectIdentifiers.des_EDE3_CBC.getId(); + + public static final String PBE_SHA1_RC4_128 = PKCSObjectIdentifiers.pbeWithSHAAnd128BitRC4.getId(); + public static final String PBE_SHA1_RC4_40 = PKCSObjectIdentifiers.pbeWithSHAAnd40BitRC4.getId(); + public static final String PBE_SHA1_3DES = PKCSObjectIdentifiers.pbeWithSHAAnd3_KeyTripleDES_CBC.getId(); + public static final String PBE_SHA1_2DES = PKCSObjectIdentifiers.pbeWithSHAAnd2_KeyTripleDES_CBC.getId(); + public static final String PBE_SHA1_RC2_128 = PKCSObjectIdentifiers.pbeWithSHAAnd128BitRC2_CBC.getId(); + public static final String PBE_SHA1_RC2_40 = PKCSObjectIdentifiers.pbeWithSHAAnd40BitRC2_CBC.getId(); + + private JcaJceHelper helper = new DefaultJcaJceHelper(); + + private AlgorithmParameters params; + private ASN1ObjectIdentifier algOID; + byte[] salt; + int iterationCount; + private Cipher cipher; + private SecureRandom random; + private AlgorithmParameterGenerator paramGen; + private SecretKeyFactory secKeyFact; + private char[] password; + + private SecretKey key; + + public JceOpenSSLPKCS8EncryptorBuilder(ASN1ObjectIdentifier algorithm) + { + algOID = algorithm; + + this.iterationCount = 2048; + } + + public JceOpenSSLPKCS8EncryptorBuilder setRandom(SecureRandom random) + { + this.random = random; + + return this; + } + + public JceOpenSSLPKCS8EncryptorBuilder setPasssword(char[] password) + { + this.password = password; + + return this; + } + + public JceOpenSSLPKCS8EncryptorBuilder setIterationCount(int iterationCount) + { + this.iterationCount = iterationCount; + + return this; + } + + public JceOpenSSLPKCS8EncryptorBuilder setProvider(String providerName) + { + helper = new NamedJcaJceHelper(providerName); + + return this; + } + + public JceOpenSSLPKCS8EncryptorBuilder setProvider(Provider provider) + { + helper = new ProviderJcaJceHelper(provider); + + return this; + } + + public OutputEncryptor build() + throws OperatorCreationException + { + final AlgorithmIdentifier algID; + + salt = new byte[20]; + + if (random == null) + { + random = new SecureRandom(); + } + + random.nextBytes(salt); + + try + { + this.cipher = helper.createCipher(algOID.getId()); + + if (PEMUtilities.isPKCS5Scheme2(algOID)) + { + this.paramGen = helper.createAlgorithmParameterGenerator(algOID.getId()); + } + else + { + this.secKeyFact = helper.createSecretKeyFactory(algOID.getId()); + } + } + catch (GeneralSecurityException e) + { + throw new OperatorCreationException(algOID + " not available: " + e.getMessage(), e); + } + + if (PEMUtilities.isPKCS5Scheme2(algOID)) + { + params = paramGen.generateParameters(); + + try + { + KeyDerivationFunc scheme = new KeyDerivationFunc(algOID, ASN1Primitive.fromByteArray(params.getEncoded())); + KeyDerivationFunc func = new KeyDerivationFunc(PKCSObjectIdentifiers.id_PBKDF2, new PBKDF2Params(salt, iterationCount)); + + ASN1EncodableVector v = new ASN1EncodableVector(); + + v.add(func); + v.add(scheme); + + algID = new AlgorithmIdentifier(PKCSObjectIdentifiers.id_PBES2, PBES2Parameters.getInstance(new DERSequence(v))); + } + catch (IOException e) + { + throw new OperatorCreationException(e.getMessage(), e); + } + + key = PEMUtilities.generateSecretKeyForPKCS5Scheme2(algOID.getId(), password, salt, iterationCount); + + try + { + cipher.init(Cipher.ENCRYPT_MODE, key, params); + } + catch (GeneralSecurityException e) + { + throw new OperatorCreationException(e.getMessage(), e); + } + } + else if (PEMUtilities.isPKCS12(algOID)) + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + v.add(new DEROctetString(salt)); + v.add(new ASN1Integer(iterationCount)); + + algID = new AlgorithmIdentifier(algOID, PKCS12PBEParams.getInstance(new DERSequence(v))); + + try + { + PBEKeySpec pbeSpec = new PBEKeySpec(password); + PBEParameterSpec defParams = new PBEParameterSpec(salt, iterationCount); + + key = secKeyFact.generateSecret(pbeSpec); + + cipher.init(Cipher.ENCRYPT_MODE, key, defParams); + } + catch (GeneralSecurityException e) + { + throw new OperatorCreationException(e.getMessage(), e); + } + } + else + { + throw new OperatorCreationException("unknown algorithm: " + algOID, null); + } + + return new OutputEncryptor() + { + public AlgorithmIdentifier getAlgorithmIdentifier() + { + return algID; + } + + public OutputStream getOutputStream(OutputStream encOut) + { + return new CipherOutputStream(encOut, cipher); + } + + public GenericKey getKey() + { + return new JceGenericKey(algID, key); + } + }; + } +} diff --git a/bcpkix/src/main/java/org/bouncycastle/openssl/jcajce/JcePEMDecryptorProviderBuilder.java b/bcpkix/src/main/java/org/bouncycastle/openssl/jcajce/JcePEMDecryptorProviderBuilder.java new file mode 100644 index 00000000..35c0eb34 --- /dev/null +++ b/bcpkix/src/main/java/org/bouncycastle/openssl/jcajce/JcePEMDecryptorProviderBuilder.java @@ -0,0 +1,54 @@ +package org.bouncycastle.openssl.jcajce; + +import java.security.Provider; + +import org.bouncycastle.jcajce.DefaultJcaJceHelper; +import org.bouncycastle.jcajce.JcaJceHelper; +import org.bouncycastle.jcajce.NamedJcaJceHelper; +import org.bouncycastle.jcajce.ProviderJcaJceHelper; +import org.bouncycastle.openssl.PEMDecryptor; +import org.bouncycastle.openssl.PEMDecryptorProvider; +import org.bouncycastle.openssl.PEMException; +import org.bouncycastle.openssl.PasswordException; + +public class JcePEMDecryptorProviderBuilder +{ + private JcaJceHelper helper = new DefaultJcaJceHelper(); + + public JcePEMDecryptorProviderBuilder setProvider(Provider provider) + { + this.helper = new ProviderJcaJceHelper(provider); + + return this; + } + + public JcePEMDecryptorProviderBuilder setProvider(String providerName) + { + this.helper = new NamedJcaJceHelper(providerName); + + return this; + } + + public PEMDecryptorProvider build(final char[] password) + { + return new PEMDecryptorProvider() + { + public PEMDecryptor get(final String dekAlgName) + { + return new PEMDecryptor() + { + public byte[] decrypt(byte[] keyBytes, byte[] iv) + throws PEMException + { + if (password == null) + { + throw new PasswordException("Password is null, but a password is required"); + } + + return PEMUtilities.crypt(false, helper, keyBytes, password, dekAlgName, iv); + } + }; + } + }; + } +} diff --git a/bcpkix/src/main/java/org/bouncycastle/openssl/jcajce/JcePEMEncryptorBuilder.java b/bcpkix/src/main/java/org/bouncycastle/openssl/jcajce/JcePEMEncryptorBuilder.java new file mode 100644 index 00000000..020d0779 --- /dev/null +++ b/bcpkix/src/main/java/org/bouncycastle/openssl/jcajce/JcePEMEncryptorBuilder.java @@ -0,0 +1,78 @@ +package org.bouncycastle.openssl.jcajce; + +import java.security.Provider; +import java.security.SecureRandom; + +import org.bouncycastle.jcajce.DefaultJcaJceHelper; +import org.bouncycastle.jcajce.JcaJceHelper; +import org.bouncycastle.jcajce.NamedJcaJceHelper; +import org.bouncycastle.jcajce.ProviderJcaJceHelper; +import org.bouncycastle.openssl.PEMEncryptor; +import org.bouncycastle.openssl.PEMException; + +public class JcePEMEncryptorBuilder +{ + private final String algorithm; + + private JcaJceHelper helper = new DefaultJcaJceHelper(); + private SecureRandom random; + + public JcePEMEncryptorBuilder(String algorithm) + { + this.algorithm = algorithm; + } + + public JcePEMEncryptorBuilder setProvider(Provider provider) + { + this.helper = new ProviderJcaJceHelper(provider); + + return this; + } + + public JcePEMEncryptorBuilder setProvider(String providerName) + { + this.helper = new NamedJcaJceHelper(providerName); + + return this; + } + + public JcePEMEncryptorBuilder setSecureRandom(SecureRandom random) + { + this.random = random; + + return this; + } + + public PEMEncryptor build(final char[] password) + { + if (random == null) + { + random = new SecureRandom(); + } + + int ivLength = algorithm.startsWith("AES-") ? 16 : 8; + + final byte[] iv = new byte[ivLength]; + + random.nextBytes(iv); + + return new PEMEncryptor() + { + public String getAlgorithm() + { + return algorithm; + } + + public byte[] getIV() + { + return iv; + } + + public byte[] encrypt(byte[] encoding) + throws PEMException + { + return PEMUtilities.crypt(true, helper, encoding, password, algorithm, iv); + } + }; + } +} diff --git a/bcpkix/src/main/java/org/bouncycastle/openssl/jcajce/PEMUtilities.java b/bcpkix/src/main/java/org/bouncycastle/openssl/jcajce/PEMUtilities.java new file mode 100644 index 00000000..49aaa2fe --- /dev/null +++ b/bcpkix/src/main/java/org/bouncycastle/openssl/jcajce/PEMUtilities.java @@ -0,0 +1,258 @@ +package org.bouncycastle.openssl.jcajce; + +import java.security.Key; +import java.security.spec.AlgorithmParameterSpec; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import javax.crypto.Cipher; +import javax.crypto.SecretKey; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.RC2ParameterSpec; +import javax.crypto.spec.SecretKeySpec; + +import org.bouncycastle.asn1.ASN1ObjectIdentifier; +import org.bouncycastle.asn1.DERObjectIdentifier; +import org.bouncycastle.asn1.nist.NISTObjectIdentifiers; +import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers; +import org.bouncycastle.crypto.PBEParametersGenerator; +import org.bouncycastle.crypto.generators.OpenSSLPBEParametersGenerator; +import org.bouncycastle.crypto.generators.PKCS5S2ParametersGenerator; +import org.bouncycastle.crypto.params.KeyParameter; +import org.bouncycastle.jcajce.JcaJceHelper; +import org.bouncycastle.openssl.EncryptionException; +import org.bouncycastle.openssl.PEMException; +import org.bouncycastle.util.Integers; + +class PEMUtilities +{ + private static final Map KEYSIZES = new HashMap(); + private static final Set PKCS5_SCHEME_1 = new HashSet(); + private static final Set PKCS5_SCHEME_2 = new HashSet(); + + static + { + PKCS5_SCHEME_1.add(PKCSObjectIdentifiers.pbeWithMD2AndDES_CBC); + PKCS5_SCHEME_1.add(PKCSObjectIdentifiers.pbeWithMD2AndRC2_CBC); + PKCS5_SCHEME_1.add(PKCSObjectIdentifiers.pbeWithMD5AndDES_CBC); + PKCS5_SCHEME_1.add(PKCSObjectIdentifiers.pbeWithMD5AndRC2_CBC); + PKCS5_SCHEME_1.add(PKCSObjectIdentifiers.pbeWithSHA1AndDES_CBC); + PKCS5_SCHEME_1.add(PKCSObjectIdentifiers.pbeWithSHA1AndRC2_CBC); + + PKCS5_SCHEME_2.add(PKCSObjectIdentifiers.id_PBES2); + PKCS5_SCHEME_2.add(PKCSObjectIdentifiers.des_EDE3_CBC); + PKCS5_SCHEME_2.add(NISTObjectIdentifiers.id_aes128_CBC); + PKCS5_SCHEME_2.add(NISTObjectIdentifiers.id_aes192_CBC); + PKCS5_SCHEME_2.add(NISTObjectIdentifiers.id_aes256_CBC); + + KEYSIZES.put(PKCSObjectIdentifiers.des_EDE3_CBC.getId(), Integers.valueOf(192)); + KEYSIZES.put(NISTObjectIdentifiers.id_aes128_CBC.getId(), Integers.valueOf(128)); + KEYSIZES.put(NISTObjectIdentifiers.id_aes192_CBC.getId(), Integers.valueOf(192)); + KEYSIZES.put(NISTObjectIdentifiers.id_aes256_CBC.getId(), Integers.valueOf(256)); + } + + static int getKeySize(String algorithm) + { + if (!KEYSIZES.containsKey(algorithm)) + { + throw new IllegalStateException("no key size for algorithm: " + algorithm); + } + + return ((Integer)KEYSIZES.get(algorithm)).intValue(); + } + + static boolean isPKCS5Scheme1(DERObjectIdentifier algOid) + { + return PKCS5_SCHEME_1.contains(algOid); + } + + static boolean isPKCS5Scheme2(ASN1ObjectIdentifier algOid) + { + return PKCS5_SCHEME_2.contains(algOid); + } + + public static boolean isPKCS12(DERObjectIdentifier algOid) + { + return algOid.getId().startsWith(PKCSObjectIdentifiers.pkcs_12PbeIds.getId()); + } + + public static SecretKey generateSecretKeyForPKCS5Scheme2(String algorithm, char[] password, byte[] salt, int iterationCount) + { + PBEParametersGenerator generator = new PKCS5S2ParametersGenerator(); + + generator.init( + PBEParametersGenerator.PKCS5PasswordToBytes(password), + salt, + iterationCount); + + return new SecretKeySpec(((KeyParameter)generator.generateDerivedParameters(PEMUtilities.getKeySize(algorithm))).getKey(), algorithm); + } + + static byte[] crypt( + boolean encrypt, + JcaJceHelper helper, + byte[] bytes, + char[] password, + String dekAlgName, + byte[] iv) + throws PEMException + { + AlgorithmParameterSpec paramSpec = new IvParameterSpec(iv); + String alg; + String blockMode = "CBC"; + String padding = "PKCS5Padding"; + Key sKey; + + // Figure out block mode and padding. + if (dekAlgName.endsWith("-CFB")) + { + blockMode = "CFB"; + padding = "NoPadding"; + } + if (dekAlgName.endsWith("-ECB") || + "DES-EDE".equals(dekAlgName) || + "DES-EDE3".equals(dekAlgName)) + { + // ECB is actually the default (though seldom used) when OpenSSL + // uses DES-EDE (des2) or DES-EDE3 (des3). + blockMode = "ECB"; + paramSpec = null; + } + if (dekAlgName.endsWith("-OFB")) + { + blockMode = "OFB"; + padding = "NoPadding"; + } + + + // Figure out algorithm and key size. + if (dekAlgName.startsWith("DES-EDE")) + { + alg = "DESede"; + // "DES-EDE" is actually des2 in OpenSSL-speak! + // "DES-EDE3" is des3. + boolean des2 = !dekAlgName.startsWith("DES-EDE3"); + sKey = getKey(password, alg, 24, iv, des2); + } + else if (dekAlgName.startsWith("DES-")) + { + alg = "DES"; + sKey = getKey(password, alg, 8, iv); + } + else if (dekAlgName.startsWith("BF-")) + { + alg = "Blowfish"; + sKey = getKey(password, alg, 16, iv); + } + else if (dekAlgName.startsWith("RC2-")) + { + alg = "RC2"; + int keyBits = 128; + if (dekAlgName.startsWith("RC2-40-")) + { + keyBits = 40; + } + else if (dekAlgName.startsWith("RC2-64-")) + { + keyBits = 64; + } + sKey = getKey(password, alg, keyBits / 8, iv); + if (paramSpec == null) // ECB block mode + { + paramSpec = new RC2ParameterSpec(keyBits); + } + else + { + paramSpec = new RC2ParameterSpec(keyBits, iv); + } + } + else if (dekAlgName.startsWith("AES-")) + { + alg = "AES"; + byte[] salt = iv; + if (salt.length > 8) + { + salt = new byte[8]; + System.arraycopy(iv, 0, salt, 0, 8); + } + + int keyBits; + if (dekAlgName.startsWith("AES-128-")) + { + keyBits = 128; + } + else if (dekAlgName.startsWith("AES-192-")) + { + keyBits = 192; + } + else if (dekAlgName.startsWith("AES-256-")) + { + keyBits = 256; + } + else + { + throw new EncryptionException("unknown AES encryption with private key"); + } + sKey = getKey(password, "AES", keyBits / 8, salt); + } + else + { + throw new EncryptionException("unknown encryption with private key"); + } + + String transformation = alg + "/" + blockMode + "/" + padding; + + try + { + Cipher c = helper.createCipher(transformation); + int mode = encrypt ? Cipher.ENCRYPT_MODE : Cipher.DECRYPT_MODE; + + if (paramSpec == null) // ECB block mode + { + c.init(mode, sKey); + } + else + { + c.init(mode, sKey, paramSpec); + } + return c.doFinal(bytes); + } + catch (Exception e) + { + throw new EncryptionException("exception using cipher - please check password and data.", e); + } + } + + private static SecretKey getKey( + char[] password, + String algorithm, + int keyLength, + byte[] salt) + { + return getKey(password, algorithm, keyLength, salt, false); + } + + private static SecretKey getKey( + char[] password, + String algorithm, + int keyLength, + byte[] salt, + boolean des2) + { + OpenSSLPBEParametersGenerator pGen = new OpenSSLPBEParametersGenerator(); + + pGen.init(PBEParametersGenerator.PKCS5PasswordToBytes(password), salt); + + KeyParameter keyParam; + keyParam = (KeyParameter) pGen.generateDerivedParameters(keyLength * 8); + byte[] key = keyParam.getKey(); + if (des2 && key.length >= 24) + { + // For DES2, we must copy first 8 bytes into the last 8 bytes. + System.arraycopy(key, 0, key, 16, 8); + } + return new SecretKeySpec(key, algorithm); + } +} diff --git a/bcpkix/src/main/java/org/bouncycastle/openssl/package.html b/bcpkix/src/main/java/org/bouncycastle/openssl/package.html new file mode 100644 index 00000000..7e60a79e --- /dev/null +++ b/bcpkix/src/main/java/org/bouncycastle/openssl/package.html @@ -0,0 +1,5 @@ +<html> +<body bgcolor="#ffffff"> +Classes for dealing with OpenSSL PEM files. +</body> +</html> diff --git a/bcpkix/src/main/java/org/bouncycastle/openssl/test/AllTests.java b/bcpkix/src/main/java/org/bouncycastle/openssl/test/AllTests.java new file mode 100644 index 00000000..eb1d4da3 --- /dev/null +++ b/bcpkix/src/main/java/org/bouncycastle/openssl/test/AllTests.java @@ -0,0 +1,200 @@ +package org.bouncycastle.openssl.test; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; +import java.security.KeyPairGenerator; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.PrivateKey; +import java.security.Security; + +import junit.framework.Test; +import junit.framework.TestCase; +import junit.framework.TestSuite; +import org.bouncycastle.asn1.ASN1ObjectIdentifier; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.bouncycastle.openssl.PEMReader; +import org.bouncycastle.openssl.PEMWriter; +import org.bouncycastle.openssl.PKCS8Generator; +import org.bouncycastle.openssl.PasswordFinder; +import org.bouncycastle.openssl.jcajce.JcaPKCS8Generator; +import org.bouncycastle.openssl.jcajce.JceOpenSSLPKCS8EncryptorBuilder; +import org.bouncycastle.operator.OperatorCreationException; +import org.bouncycastle.util.test.SimpleTestResult; + +public class + AllTests + extends TestCase +{ + public void testOpenSSL() + { + Security.addProvider(new BouncyCastleProvider()); + + org.bouncycastle.util.test.Test[] tests = new org.bouncycastle.util.test.Test[] + { + new ReaderTest(), + new WriterTest(), + new ParserTest() + }; + + for (int i = 0; i != tests.length; i++) + { + SimpleTestResult result = (SimpleTestResult)tests[i].perform(); + + if (!result.isSuccessful()) + { + fail(result.toString()); + } + } + } + + public void testPKCS8Encrypted() + throws Exception + { + KeyPairGenerator kpGen = KeyPairGenerator.getInstance("RSA", "BC"); + + kpGen.initialize(1024); + + PrivateKey key = kpGen.generateKeyPair().getPrivate(); + + encryptedTest(key, PKCS8Generator.AES_256_CBC); + encryptedTest(key, PKCS8Generator.DES3_CBC); + encryptedTest(key, PKCS8Generator.PBE_SHA1_3DES); + encryptedTestNew(key, PKCS8Generator.AES_256_CBC); + encryptedTestNew(key, PKCS8Generator.DES3_CBC); + encryptedTestNew(key, PKCS8Generator.PBE_SHA1_3DES); + } + + private void encryptedTest(PrivateKey key, ASN1ObjectIdentifier algorithm) + throws NoSuchProviderException, NoSuchAlgorithmException, IOException + { + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + PEMWriter pWrt = new PEMWriter(new OutputStreamWriter(bOut), "BC"); + PKCS8Generator pkcs8 = new PKCS8Generator(key, algorithm, "BC"); + + pkcs8.setPassword("hello".toCharArray()); + + pWrt.writeObject(pkcs8); + + pWrt.close(); + + PEMReader pRd = new PEMReader(new InputStreamReader(new ByteArrayInputStream(bOut.toByteArray())), new PasswordFinder() + { + public char[] getPassword() + { + return "hello".toCharArray(); + } + }); + + PrivateKey rdKey = (PrivateKey)pRd.readObject(); + + assertEquals(key, rdKey); + } + + private void encryptedTestNew(PrivateKey key, ASN1ObjectIdentifier algorithm) + throws NoSuchProviderException, NoSuchAlgorithmException, IOException, OperatorCreationException + { + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + PEMWriter pWrt = new PEMWriter(new OutputStreamWriter(bOut), "BC"); + + JceOpenSSLPKCS8EncryptorBuilder encryptorBuilder = new JceOpenSSLPKCS8EncryptorBuilder(algorithm); + + encryptorBuilder.setProvider("BC"); + encryptorBuilder.setPasssword("hello".toCharArray()); + + PKCS8Generator pkcs8 = new JcaPKCS8Generator(key, encryptorBuilder.build()); + + pWrt.writeObject(pkcs8); + + pWrt.close(); + + PEMReader pRd = new PEMReader(new InputStreamReader(new ByteArrayInputStream(bOut.toByteArray())), new PasswordFinder() + { + public char[] getPassword() + { + return "hello".toCharArray(); + } + }); + + PrivateKey rdKey = (PrivateKey)pRd.readObject(); + + assertEquals(key, rdKey); + } + + public void testPKCS8Plain() + throws Exception + { + KeyPairGenerator kpGen = KeyPairGenerator.getInstance("RSA", "BC"); + + kpGen.initialize(1024); + + PrivateKey key = kpGen.generateKeyPair().getPrivate(); + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + PEMWriter pWrt = new PEMWriter(new OutputStreamWriter(bOut)); + PKCS8Generator pkcs8 = new PKCS8Generator(key); + + pWrt.writeObject(pkcs8); + + pWrt.close(); + + PEMReader pRd = new PEMReader(new InputStreamReader(new ByteArrayInputStream(bOut.toByteArray())), new PasswordFinder() + { + public char[] getPassword() + { + return "hello".toCharArray(); + } + }); + + PrivateKey rdKey = (PrivateKey)pRd.readObject(); + + assertEquals(key, rdKey); + } + + public void testPKCS8PlainNew() + throws Exception + { + KeyPairGenerator kpGen = KeyPairGenerator.getInstance("RSA", "BC"); + + kpGen.initialize(1024); + + PrivateKey key = kpGen.generateKeyPair().getPrivate(); + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + PEMWriter pWrt = new PEMWriter(new OutputStreamWriter(bOut)); + PKCS8Generator pkcs8 = new JcaPKCS8Generator(key, null); + + pWrt.writeObject(pkcs8); + + pWrt.close(); + + PEMReader pRd = new PEMReader(new InputStreamReader(new ByteArrayInputStream(bOut.toByteArray())), new PasswordFinder() + { + public char[] getPassword() + { + return "hello".toCharArray(); + } + }); + + PrivateKey rdKey = (PrivateKey)pRd.readObject(); + + assertEquals(key, rdKey); + } + + public static void main (String[] args) + { + Security.addProvider(new BouncyCastleProvider()); + + junit.textui.TestRunner.run(suite()); + } + + public static Test suite() + { + TestSuite suite = new TestSuite("OpenSSL Tests"); + + suite.addTestSuite(AllTests.class); + + return suite; + } +} diff --git a/bcpkix/src/main/java/org/bouncycastle/openssl/test/ParserTest.java b/bcpkix/src/main/java/org/bouncycastle/openssl/test/ParserTest.java new file mode 100644 index 00000000..521106b1 --- /dev/null +++ b/bcpkix/src/main/java/org/bouncycastle/openssl/test/ParserTest.java @@ -0,0 +1,500 @@ +package org.bouncycastle.openssl.test; + +import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; +import java.io.Reader; +import java.math.BigInteger; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.SecureRandom; +import java.security.Security; +import java.security.Signature; +import java.security.interfaces.DSAPrivateKey; +import java.security.interfaces.RSAPrivateCrtKey; +import java.security.interfaces.RSAPrivateKey; + +import org.bouncycastle.asn1.ASN1ObjectIdentifier; +import org.bouncycastle.asn1.cms.CMSObjectIdentifiers; +import org.bouncycastle.asn1.cms.ContentInfo; +import org.bouncycastle.asn1.pkcs.PrivateKeyInfo; +import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo; +import org.bouncycastle.asn1.x9.ECNamedCurveTable; +import org.bouncycastle.asn1.x9.X9ECParameters; +import org.bouncycastle.cert.X509CertificateHolder; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.bouncycastle.openssl.PEMDecryptorProvider; +import org.bouncycastle.openssl.PEMEncryptedKeyPair; +import org.bouncycastle.openssl.PEMKeyPair; +import org.bouncycastle.openssl.PEMParser; +import org.bouncycastle.openssl.PEMWriter; +import org.bouncycastle.openssl.PasswordFinder; +import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter; +import org.bouncycastle.openssl.jcajce.JceOpenSSLPKCS8DecryptorProviderBuilder; +import org.bouncycastle.openssl.jcajce.JcePEMDecryptorProviderBuilder; +import org.bouncycastle.operator.InputDecryptorProvider; +import org.bouncycastle.pkcs.PKCS8EncryptedPrivateKeyInfo; +import org.bouncycastle.util.test.SimpleTest; + +/** + * basic class for reading test.pem - the password is "secret" + */ +public class ParserTest + extends SimpleTest +{ + private static class Password + implements PasswordFinder + { + char[] password; + + Password( + char[] word) + { + this.password = word; + } + + public char[] getPassword() + { + return password; + } + } + + public String getName() + { + return "PEMParserTest"; + } + + private PEMParser openPEMResource( + String fileName) + { + InputStream res = this.getClass().getResourceAsStream(fileName); + Reader fRd = new BufferedReader(new InputStreamReader(res)); + return new PEMParser(fRd); + } + + public void performTest() + throws Exception + { + PEMParser pemRd = openPEMResource("test.pem"); + Object o; + PEMKeyPair pemPair; + KeyPair pair; + + while ((o = pemRd.readObject()) != null) + { + if (o instanceof KeyPair) + { + //pair = (KeyPair)o; + + //System.out.println(pair.getPublic()); + //System.out.println(pair.getPrivate()); + } + else + { + //System.out.println(o.toString()); + } + } + + // test bogus lines before begin are ignored. + pemRd = openPEMResource("extratest.pem"); + + while ((o = pemRd.readObject()) != null) + { + if (!(o instanceof X509CertificateHolder)) + { + fail("wrong object found"); + } + } + + // + // pkcs 7 data + // + pemRd = openPEMResource("pkcs7.pem"); + ContentInfo d = (ContentInfo)pemRd.readObject(); + + if (!d.getContentType().equals(CMSObjectIdentifiers.envelopedData)) + { + fail("failed envelopedData check"); + } + + // + // ECKey + // + pemRd = openPEMResource("eckey.pem"); + ASN1ObjectIdentifier ecOID = (ASN1ObjectIdentifier)pemRd.readObject(); + X9ECParameters ecSpec = ECNamedCurveTable.getByOID(ecOID); + + if (ecSpec == null) + { + fail("ecSpec not found for named curve"); + } + + pemPair = (PEMKeyPair)pemRd.readObject(); + + pair = new JcaPEMKeyConverter().setProvider("BC").getKeyPair(pemPair); + + Signature sgr = Signature.getInstance("ECDSA", "BC"); + + sgr.initSign(pair.getPrivate()); + + byte[] message = new byte[] { (byte)'a', (byte)'b', (byte)'c' }; + + sgr.update(message); + + byte[] sigBytes = sgr.sign(); + + sgr.initVerify(pair.getPublic()); + + sgr.update(message); + + if (!sgr.verify(sigBytes)) + { + fail("EC verification failed"); + } + + if (!pair.getPublic().getAlgorithm().equals("ECDSA")) + { + fail("wrong algorithm name on public got: " + pair.getPublic().getAlgorithm()); + } + + if (!pair.getPrivate().getAlgorithm().equals("ECDSA")) + { + fail("wrong algorithm name on private"); + } + + // + // ECKey -- explicit parameters + // + pemRd = openPEMResource("ecexpparam.pem"); + ecSpec = (X9ECParameters)pemRd.readObject(); + + pemPair = (PEMKeyPair)pemRd.readObject(); + + pair = new JcaPEMKeyConverter().setProvider("BC").getKeyPair(pemPair); + + sgr = Signature.getInstance("ECDSA", "BC"); + + sgr.initSign(pair.getPrivate()); + + message = new byte[] { (byte)'a', (byte)'b', (byte)'c' }; + + sgr.update(message); + + sigBytes = sgr.sign(); + + sgr.initVerify(pair.getPublic()); + + sgr.update(message); + + if (!sgr.verify(sigBytes)) + { + fail("EC verification failed"); + } + + if (!pair.getPublic().getAlgorithm().equals("ECDSA")) + { + fail("wrong algorithm name on public got: " + pair.getPublic().getAlgorithm()); + } + + if (!pair.getPrivate().getAlgorithm().equals("ECDSA")) + { + fail("wrong algorithm name on private"); + } + + // + // writer/parser test + // + KeyPairGenerator kpGen = KeyPairGenerator.getInstance("RSA", "BC"); + + pair = kpGen.generateKeyPair(); + + keyPairTest("RSA", pair); + + kpGen = KeyPairGenerator.getInstance("DSA", "BC"); + kpGen.initialize(512, new SecureRandom()); + pair = kpGen.generateKeyPair(); + + keyPairTest("DSA", pair); + + // + // PKCS7 + // + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + PEMWriter pWrt = new PEMWriter(new OutputStreamWriter(bOut)); + + pWrt.writeObject(d); + + pWrt.close(); + + pemRd = new PEMParser(new InputStreamReader(new ByteArrayInputStream(bOut.toByteArray()))); + d = (ContentInfo)pemRd.readObject(); + + if (!d.getContentType().equals(CMSObjectIdentifiers.envelopedData)) + { + fail("failed envelopedData recode check"); + } + + + // OpenSSL test cases (as embedded resources) + doOpenSslDsaTest("unencrypted"); + doOpenSslRsaTest("unencrypted"); + + doOpenSslTests("aes128"); + doOpenSslTests("aes192"); + doOpenSslTests("aes256"); + doOpenSslTests("blowfish"); + doOpenSslTests("des1"); + doOpenSslTests("des2"); + doOpenSslTests("des3"); + doOpenSslTests("rc2_128"); + + doOpenSslDsaTest("rc2_40_cbc"); + doOpenSslRsaTest("rc2_40_cbc"); + doOpenSslDsaTest("rc2_64_cbc"); + doOpenSslRsaTest("rc2_64_cbc"); + + doDudPasswordTest("7fd98", 0, "corrupted stream - out of bounds length found"); + doDudPasswordTest("ef677", 1, "corrupted stream - out of bounds length found"); + doDudPasswordTest("800ce", 2, "unknown tag 26 encountered"); + doDudPasswordTest("b6cd8", 3, "DEF length 81 object truncated by 56"); + doDudPasswordTest("28ce09", 4, "DEF length 110 object truncated by 28"); + doDudPasswordTest("2ac3b9", 5, "DER length more than 4 bytes: 11"); + doDudPasswordTest("2cba96", 6, "DEF length 100 object truncated by 35"); + doDudPasswordTest("2e3354", 7, "DEF length 42 object truncated by 9"); + doDudPasswordTest("2f4142", 8, "DER length more than 4 bytes: 14"); + doDudPasswordTest("2fe9bb", 9, "DER length more than 4 bytes: 65"); + doDudPasswordTest("3ee7a8", 10, "DER length more than 4 bytes: 57"); + doDudPasswordTest("41af75", 11, "unknown tag 16 encountered"); + doDudPasswordTest("1704a5", 12, "corrupted stream detected"); + doDudPasswordTest("1c5822", 13, "unknown object in getInstance: org.bouncycastle.asn1.DERUTF8String"); + doDudPasswordTest("5a3d16", 14, "corrupted stream detected"); + doDudPasswordTest("8d0c97", 15, "corrupted stream detected"); + doDudPasswordTest("bc0daf", 16, "corrupted stream detected"); + doDudPasswordTest("aaf9c4d",17, "corrupted stream - out of bounds length found"); + + doNoPasswordTest(); + + // encrypted private key test + InputDecryptorProvider pkcs8Prov = new JceOpenSSLPKCS8DecryptorProviderBuilder().build("password".toCharArray()); + pemRd = openPEMResource("enckey.pem"); + + PKCS8EncryptedPrivateKeyInfo encPrivKeyInfo = (PKCS8EncryptedPrivateKeyInfo)pemRd.readObject(); + JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider("BC"); + + RSAPrivateCrtKey privKey = (RSAPrivateCrtKey)converter.getPrivateKey(encPrivKeyInfo.decryptPrivateKeyInfo(pkcs8Prov)); + + if (!privKey.getPublicExponent().equals(new BigInteger("10001", 16))) + { + fail("decryption of private key data check failed"); + } + + // general PKCS8 test + + pemRd = openPEMResource("pkcs8test.pem"); + + Object privInfo; + + while ((privInfo = pemRd.readObject()) != null) + { + if (privInfo instanceof PrivateKeyInfo) + { + privKey = (RSAPrivateCrtKey)converter.getPrivateKey(PrivateKeyInfo.getInstance(privInfo)); + } + else + { + privKey = (RSAPrivateCrtKey)converter.getPrivateKey(((PKCS8EncryptedPrivateKeyInfo)privInfo).decryptPrivateKeyInfo(pkcs8Prov)); + } + if (!privKey.getPublicExponent().equals(new BigInteger("10001", 16))) + { + fail("decryption of private key data check failed"); + } + } + } + + private void keyPairTest( + String name, + KeyPair pair) + throws IOException + { + PEMParser pemRd; + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + PEMWriter pWrt = new PEMWriter(new OutputStreamWriter(bOut)); + + pWrt.writeObject(pair.getPublic()); + + pWrt.close(); + + pemRd = new PEMParser(new InputStreamReader(new ByteArrayInputStream(bOut.toByteArray()))); + + SubjectPublicKeyInfo pub = SubjectPublicKeyInfo.getInstance(pemRd.readObject()); + JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider("BC"); + + PublicKey k = converter.getPublicKey(pub); + + if (!k.equals(pair.getPublic())) + { + fail("Failed public key read: " + name); + } + + bOut = new ByteArrayOutputStream(); + pWrt = new PEMWriter(new OutputStreamWriter(bOut)); + + pWrt.writeObject(pair.getPrivate()); + + pWrt.close(); + + pemRd = new PEMParser(new InputStreamReader(new ByteArrayInputStream(bOut.toByteArray()))); + + KeyPair kPair = converter.getKeyPair((PEMKeyPair)pemRd.readObject()); + if (!kPair.getPrivate().equals(pair.getPrivate())) + { + fail("Failed private key read: " + name); + } + + if (!kPair.getPublic().equals(pair.getPublic())) + { + fail("Failed private key public read: " + name); + } + } + + private void doOpenSslTests( + String baseName) + throws IOException + { + doOpenSslDsaModesTest(baseName); + doOpenSslRsaModesTest(baseName); + } + + private void doOpenSslDsaModesTest( + String baseName) + throws IOException + { + doOpenSslDsaTest(baseName + "_cbc"); + doOpenSslDsaTest(baseName + "_cfb"); + doOpenSslDsaTest(baseName + "_ecb"); + doOpenSslDsaTest(baseName + "_ofb"); + } + + private void doOpenSslRsaModesTest( + String baseName) + throws IOException + { + doOpenSslRsaTest(baseName + "_cbc"); + doOpenSslRsaTest(baseName + "_cfb"); + doOpenSslRsaTest(baseName + "_ecb"); + doOpenSslRsaTest(baseName + "_ofb"); + } + + private void doOpenSslDsaTest( + String name) + throws IOException + { + String fileName = "dsa/openssl_dsa_" + name + ".pem"; + + doOpenSslTestFile(fileName, DSAPrivateKey.class); + } + + private void doOpenSslRsaTest( + String name) + throws IOException + { + String fileName = "rsa/openssl_rsa_" + name + ".pem"; + + doOpenSslTestFile(fileName, RSAPrivateKey.class); + } + + private void doOpenSslTestFile( + String fileName, + Class expectedPrivKeyClass) + throws IOException + { + JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider("BC"); + PEMDecryptorProvider decProv = new JcePEMDecryptorProviderBuilder().setProvider("BC").build("changeit".toCharArray()); + PEMParser pr = openPEMResource("data/" + fileName); + Object o = pr.readObject(); + + if (o == null || !((o instanceof PEMKeyPair) || (o instanceof PEMEncryptedKeyPair))) + { + fail("Didn't find OpenSSL key"); + } + + KeyPair kp = (o instanceof PEMEncryptedKeyPair) ? + converter.getKeyPair(((PEMEncryptedKeyPair)o).decryptKeyPair(decProv)) : converter.getKeyPair((PEMKeyPair)o); + + PrivateKey privKey = kp.getPrivate(); + + if (!expectedPrivKeyClass.isInstance(privKey)) + { + fail("Returned key not of correct type"); + } + } + + private void doDudPasswordTest(String password, int index, String message) + { + // illegal state exception check - in this case the wrong password will + // cause an underlying class cast exception. + try + { + PEMDecryptorProvider decProv = new JcePEMDecryptorProviderBuilder().setProvider("BC").build(password.toCharArray()); + + PEMParser pemRd = openPEMResource("test.pem"); + Object o; + + while ((o = pemRd.readObject()) != null) + { + if (o instanceof PEMEncryptedKeyPair) + { + ((PEMEncryptedKeyPair)o).decryptKeyPair(decProv); + } + } + + fail("issue not detected: " + index); + } + catch (IOException e) + { + if (e.getCause() != null && !e.getCause().getMessage().endsWith(message)) + { + fail("issue " + index + " exception thrown, but wrong message"); + } + else if (e.getCause() == null && !e.getMessage().equals(message)) + { + e.printStackTrace(); + fail("issue " + index + " exception thrown, but wrong message"); + } + } + } + + private void doNoPasswordTest() + throws IOException + { + PEMDecryptorProvider decProv = new JcePEMDecryptorProviderBuilder().setProvider("BC").build("".toCharArray()); + + PEMParser pemRd = openPEMResource("smimenopw.pem"); + Object o; + PrivateKeyInfo key = null; + + while ((o = pemRd.readObject()) != null) + { + key = (PrivateKeyInfo)o; + } + + if (key == null) + { + fail("private key not detected"); + } + } + + public static void main( + String[] args) + { + Security.addProvider(new BouncyCastleProvider()); + + runTest(new ParserTest()); + } +} diff --git a/bcpkix/src/main/java/org/bouncycastle/openssl/test/ReaderTest.java b/bcpkix/src/main/java/org/bouncycastle/openssl/test/ReaderTest.java new file mode 100644 index 00000000..23aee088 --- /dev/null +++ b/bcpkix/src/main/java/org/bouncycastle/openssl/test/ReaderTest.java @@ -0,0 +1,417 @@ +package org.bouncycastle.openssl.test; + +import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; +import java.io.Reader; +import java.math.BigInteger; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.SecureRandom; +import java.security.Security; +import java.security.Signature; +import java.security.cert.X509Certificate; +import java.security.interfaces.DSAPrivateKey; +import java.security.interfaces.RSAPrivateCrtKey; +import java.security.interfaces.RSAPrivateKey; + +import org.bouncycastle.asn1.cms.CMSObjectIdentifiers; +import org.bouncycastle.asn1.cms.ContentInfo; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec; +import org.bouncycastle.openssl.PEMReader; +import org.bouncycastle.openssl.PEMWriter; +import org.bouncycastle.openssl.PasswordFinder; +import org.bouncycastle.util.test.SimpleTest; + +/** + * basic class for reading test.pem - the password is "secret" + */ +public class ReaderTest + extends SimpleTest +{ + private static class Password + implements PasswordFinder + { + char[] password; + + Password( + char[] word) + { + this.password = word; + } + + public char[] getPassword() + { + return password; + } + } + + public String getName() + { + return "PEMReaderTest"; + } + + private PEMReader openPEMResource( + String fileName, + PasswordFinder pGet) + { + InputStream res = this.getClass().getResourceAsStream(fileName); + Reader fRd = new BufferedReader(new InputStreamReader(res)); + return new PEMReader(fRd, pGet); + } + + public void performTest() + throws Exception + { + PasswordFinder pGet = new Password("secret".toCharArray()); + PEMReader pemRd = openPEMResource("test.pem", pGet); + Object o; + KeyPair pair; + + while ((o = pemRd.readObject()) != null) + { + if (o instanceof KeyPair) + { + //pair = (KeyPair)o; + + //System.out.println(pair.getPublic()); + //System.out.println(pair.getPrivate()); + } + else + { + //System.out.println(o.toString()); + } + } + + // test bogus lines before begin are ignored. + pemRd = openPEMResource("extratest.pem", pGet); + + while ((o = pemRd.readObject()) != null) + { + if (!(o instanceof X509Certificate)) + { + fail("wrong object found"); + } + } + + // + // pkcs 7 data + // + pemRd = openPEMResource("pkcs7.pem", null); + ContentInfo d = (ContentInfo)pemRd.readObject(); + + if (!d.getContentType().equals(CMSObjectIdentifiers.envelopedData)) + { + fail("failed envelopedData check"); + } + + // + // ECKey + // + pemRd = openPEMResource("eckey.pem", null); + ECNamedCurveParameterSpec spec = (ECNamedCurveParameterSpec)pemRd.readObject(); + + pair = (KeyPair)pemRd.readObject(); + Signature sgr = Signature.getInstance("ECDSA", "BC"); + + sgr.initSign(pair.getPrivate()); + + byte[] message = new byte[] { (byte)'a', (byte)'b', (byte)'c' }; + + sgr.update(message); + + byte[] sigBytes = sgr.sign(); + + sgr.initVerify(pair.getPublic()); + + sgr.update(message); + + if (!sgr.verify(sigBytes)) + { + fail("EC verification failed"); + } + + if (!pair.getPublic().getAlgorithm().equals("ECDSA")) + { + fail("wrong algorithm name on public got: " + pair.getPublic().getAlgorithm()); + } + + if (!pair.getPrivate().getAlgorithm().equals("ECDSA")) + { + fail("wrong algorithm name on private"); + } + + // + // writer/parser test + // + KeyPairGenerator kpGen = KeyPairGenerator.getInstance("RSA", "BC"); + + pair = kpGen.generateKeyPair(); + + keyPairTest("RSA", pair); + + kpGen = KeyPairGenerator.getInstance("DSA", "BC"); + kpGen.initialize(512, new SecureRandom()); + pair = kpGen.generateKeyPair(); + + keyPairTest("DSA", pair); + + // + // PKCS7 + // + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + PEMWriter pWrt = new PEMWriter(new OutputStreamWriter(bOut)); + + pWrt.writeObject(d); + + pWrt.close(); + + pemRd = new PEMReader(new InputStreamReader(new ByteArrayInputStream(bOut.toByteArray()))); + d = (ContentInfo)pemRd.readObject(); + + if (!d.getContentType().equals(CMSObjectIdentifiers.envelopedData)) + { + fail("failed envelopedData recode check"); + } + + + // OpenSSL test cases (as embedded resources) + doOpenSslDsaTest("unencrypted"); + doOpenSslRsaTest("unencrypted"); + + doOpenSslTests("aes128"); + doOpenSslTests("aes192"); + doOpenSslTests("aes256"); + doOpenSslTests("blowfish"); + doOpenSslTests("des1"); + doOpenSslTests("des2"); + doOpenSslTests("des3"); + doOpenSslTests("rc2_128"); + + doOpenSslDsaTest("rc2_40_cbc"); + doOpenSslRsaTest("rc2_40_cbc"); + doOpenSslDsaTest("rc2_64_cbc"); + doOpenSslRsaTest("rc2_64_cbc"); + + doDudPasswordTest("7fd98", 0, "corrupted stream - out of bounds length found"); + doDudPasswordTest("ef677", 1, "corrupted stream - out of bounds length found"); + doDudPasswordTest("800ce", 2, "unknown tag 26 encountered"); + doDudPasswordTest("b6cd8", 3, "DEF length 81 object truncated by 56"); + doDudPasswordTest("28ce09", 4, "DEF length 110 object truncated by 28"); + doDudPasswordTest("2ac3b9", 5, "DER length more than 4 bytes: 11"); + doDudPasswordTest("2cba96", 6, "DEF length 100 object truncated by 35"); + doDudPasswordTest("2e3354", 7, "DEF length 42 object truncated by 9"); + doDudPasswordTest("2f4142", 8, "DER length more than 4 bytes: 14"); + doDudPasswordTest("2fe9bb", 9, "DER length more than 4 bytes: 65"); + doDudPasswordTest("3ee7a8", 10, "DER length more than 4 bytes: 57"); + doDudPasswordTest("41af75", 11, "unknown tag 16 encountered"); + doDudPasswordTest("1704a5", 12, "corrupted stream detected"); + doDudPasswordTest("1c5822", 13, "unknown object in getInstance: org.bouncycastle.asn1.DERUTF8String"); + doDudPasswordTest("5a3d16", 14, "corrupted stream detected"); + doDudPasswordTest("8d0c97", 15, "corrupted stream detected"); + doDudPasswordTest("bc0daf", 16, "corrupted stream detected"); + doDudPasswordTest("aaf9c4d",17, "corrupted stream - out of bounds length found"); + + doNoPasswordTest(); + + // encrypted private key test + pGet = new Password("password".toCharArray()); + pemRd = openPEMResource("enckey.pem", pGet); + + RSAPrivateCrtKey privKey = (RSAPrivateCrtKey)pemRd.readObject(); + + if (!privKey.getPublicExponent().equals(new BigInteger("10001", 16))) + { + fail("decryption of private key data check failed"); + } + + // general PKCS8 test + pGet = new Password("password".toCharArray()); + pemRd = openPEMResource("pkcs8test.pem", pGet); + + while ((privKey = (RSAPrivateCrtKey)pemRd.readObject()) != null) + { + if (!privKey.getPublicExponent().equals(new BigInteger("10001", 16))) + { + fail("decryption of private key data check failed"); + } + } + } + + private void keyPairTest( + String name, + KeyPair pair) + throws IOException + { + PEMReader pemRd; + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + PEMWriter pWrt = new PEMWriter(new OutputStreamWriter(bOut)); + + pWrt.writeObject(pair.getPublic()); + + pWrt.close(); + + pemRd = new PEMReader(new InputStreamReader(new ByteArrayInputStream(bOut.toByteArray()))); + + PublicKey k = (PublicKey)pemRd.readObject(); + if (!k.equals(pair.getPublic())) + { + fail("Failed public key read: " + name); + } + + bOut = new ByteArrayOutputStream(); + pWrt = new PEMWriter(new OutputStreamWriter(bOut)); + + pWrt.writeObject(pair.getPrivate()); + + pWrt.close(); + + pemRd = new PEMReader(new InputStreamReader(new ByteArrayInputStream(bOut.toByteArray()))); + + KeyPair kPair = (KeyPair)pemRd.readObject(); + if (!kPair.getPrivate().equals(pair.getPrivate())) + { + fail("Failed private key read: " + name); + } + + if (!kPair.getPublic().equals(pair.getPublic())) + { + fail("Failed private key public read: " + name); + } + } + + private void doOpenSslTests( + String baseName) + throws IOException + { + doOpenSslDsaModesTest(baseName); + doOpenSslRsaModesTest(baseName); + } + + private void doOpenSslDsaModesTest( + String baseName) + throws IOException + { + doOpenSslDsaTest(baseName + "_cbc"); + doOpenSslDsaTest(baseName + "_cfb"); + doOpenSslDsaTest(baseName + "_ecb"); + doOpenSslDsaTest(baseName + "_ofb"); + } + + private void doOpenSslRsaModesTest( + String baseName) + throws IOException + { + doOpenSslRsaTest(baseName + "_cbc"); + doOpenSslRsaTest(baseName + "_cfb"); + doOpenSslRsaTest(baseName + "_ecb"); + doOpenSslRsaTest(baseName + "_ofb"); + } + + private void doOpenSslDsaTest( + String name) + throws IOException + { + String fileName = "dsa/openssl_dsa_" + name + ".pem"; + + doOpenSslTestFile(fileName, DSAPrivateKey.class); + } + + private void doOpenSslRsaTest( + String name) + throws IOException + { + String fileName = "rsa/openssl_rsa_" + name + ".pem"; + + doOpenSslTestFile(fileName, RSAPrivateKey.class); + } + + private void doOpenSslTestFile( + String fileName, + Class expectedPrivKeyClass) + throws IOException + { + PEMReader pr = openPEMResource("data/" + fileName, new Password("changeit".toCharArray())); + Object o = pr.readObject(); + + if (o == null || !(o instanceof KeyPair)) + { + fail("Didn't find OpenSSL key"); + } + + KeyPair kp = (KeyPair) o; + PrivateKey privKey = kp.getPrivate(); + + if (!expectedPrivKeyClass.isInstance(privKey)) + { + fail("Returned key not of correct type"); + } + } + + private void doDudPasswordTest(String password, int index, String message) + { + // illegal state exception check - in this case the wrong password will + // cause an underlying class cast exception. + try + { + PasswordFinder pGet = new Password(password.toCharArray()); + + PEMReader pemRd = openPEMResource("test.pem", pGet); + Object o; + + while ((o = pemRd.readObject()) != null) + { + } + + fail("issue not detected: " + index); + } + catch (IOException e) + { + if (e.getCause() != null && !e.getCause().getMessage().equals(message)) + { + e.printStackTrace(); + fail("issue " + index + " exception thrown, but wrong message"); + } + else if (e.getCause() == null && !e.getMessage().equals(message)) + { + e.printStackTrace(); + fail("issue " + index + " exception thrown, but wrong message"); + } + } + } + + private void doNoPasswordTest() + throws IOException + { + PasswordFinder pGet = new Password("".toCharArray()); + + PEMReader pemRd = openPEMResource("smimenopw.pem", pGet); + Object o; + PrivateKey key = null; + + while ((o = pemRd.readObject()) != null) + { + key = (PrivateKey)o; + } + + if (key == null) + { + fail("private key not detected"); + } + } + + public static void main( + String[] args) + { + Security.addProvider(new BouncyCastleProvider()); + + runTest(new ReaderTest()); + } +} diff --git a/bcpkix/src/main/java/org/bouncycastle/openssl/test/WriterTest.java b/bcpkix/src/main/java/org/bouncycastle/openssl/test/WriterTest.java new file mode 100644 index 00000000..cb911eb1 --- /dev/null +++ b/bcpkix/src/main/java/org/bouncycastle/openssl/test/WriterTest.java @@ -0,0 +1,243 @@ +package org.bouncycastle.openssl.test; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.io.StringReader; +import java.io.StringWriter; +import java.math.BigInteger; +import java.security.KeyFactory; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.PrivateKey; +import java.security.SecureRandom; +import java.security.Security; +import java.security.spec.DSAParameterSpec; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.RSAPrivateCrtKeySpec; +import java.util.List; + +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.bouncycastle.openssl.PEMReader; +import org.bouncycastle.openssl.PEMWriter; +import org.bouncycastle.openssl.PasswordFinder; +import org.bouncycastle.util.encoders.Base64; +import org.bouncycastle.util.io.pem.PemHeader; +import org.bouncycastle.util.io.pem.PemObject; +import org.bouncycastle.util.io.pem.PemReader; +import org.bouncycastle.util.test.SimpleTest; + +public class WriterTest + extends SimpleTest +{ + private static final SecureRandom random = new SecureRandom(); + + // TODO Replace with a randomly generated key each test run? + private static final RSAPrivateCrtKeySpec testRsaKeySpec = new RSAPrivateCrtKeySpec( + new BigInteger("b4a7e46170574f16a97082b22be58b6a2a629798419be12872a4bdba626cfae9900f76abfb12139dce5de56564fab2b6543165a040c606887420e33d91ed7ed7", 16), + new BigInteger("11", 16), + new BigInteger("9f66f6b05410cd503b2709e88115d55daced94d1a34d4e32bf824d0dde6028ae79c5f07b580f5dce240d7111f7ddb130a7945cd7d957d1920994da389f490c89", 16), + new BigInteger("c0a0758cdf14256f78d4708c86becdead1b50ad4ad6c5c703e2168fbf37884cb", 16), + new BigInteger("f01734d7960ea60070f1b06f2bb81bfac48ff192ae18451d5e56c734a5aab8a5", 16), + new BigInteger("b54bb9edff22051d9ee60f9351a48591b6500a319429c069a3e335a1d6171391", 16), + new BigInteger("d3d83daf2a0cecd3367ae6f8ae1aeb82e9ac2f816c6fc483533d8297dd7884cd", 16), + new BigInteger("b8f52fc6f38593dabb661d3f50f8897f8106eee68b1bce78a95b132b4e5b5d19", 16)); + + private static final DSAParameterSpec testDsaParams = new DSAParameterSpec( + new BigInteger("7434410770759874867539421675728577177024889699586189000788950934679315164676852047058354758883833299702695428196962057871264685291775577130504050839126673"), + new BigInteger("1138656671590261728308283492178581223478058193247"), + new BigInteger("4182906737723181805517018315469082619513954319976782448649747742951189003482834321192692620856488639629011570381138542789803819092529658402611668375788410")); + + private static final PKCS8EncodedKeySpec testEcDsaKeySpec = new PKCS8EncodedKeySpec( + Base64.decode("MIG/AgEAMBAGByqGSM49AgEGBSuBBAAiBIGnMIGkAgEBBDCSBU3vo7ieeKs0ABQamy/ynxlde7Ylr8HmyfLaNnMr" + + "jAwPp9R+KMUEhB7zxSAXv9KgBwYFK4EEACKhZANiAQQyyolMpg+TyB4o9kPWqafHIOe8o9K1glus+w2sY8OIPQQWGb5i5LdAyi" + + "/SscwU24rZM0yiL3BHodp9ccwyhLrFYgXJUOQcCN2dno1GMols5497in5gL5+zn0yMsRtyv5o=") + ); + + private static final char[] testPassword = "bouncy".toCharArray(); + + private static final String[] algorithms = new String[] + { + "AES-128-CBC", "AES-128-CFB", "AES-128-ECB", "AES-128-OFB", + "AES-192-CBC", "AES-192-CFB", "AES-192-ECB", "AES-192-OFB", + "AES-256-CBC", "AES-256-CFB", "AES-256-ECB", "AES-256-OFB", + "BF-CBC", "BF-CFB", "BF-ECB", "BF-OFB", + "DES-CBC", "DES-CFB", "DES-ECB", "DES-OFB", + "DES-EDE", "DES-EDE-CBC", "DES-EDE-CFB", "DES-EDE-ECB", "DES-EDE-OFB", + "DES-EDE3", "DES-EDE3-CBC", "DES-EDE3-CFB", "DES-EDE3-ECB", "DES-EDE3-OFB", + "RC2-CBC", "RC2-CFB", "RC2-ECB", "RC2-OFB", + "RC2-40-CBC", + "RC2-64-CBC", + }; + + private class Password + implements PasswordFinder + { + private final char[] password; + + public Password( + char[] word) + { + this.password = (char[]) word.clone(); + } + + public char[] getPassword() + { + return (char[]) password.clone(); + } + } + + public String getName() + { + return "PEMWriterTest"; + } + + public void performTest() + throws Exception + { + final String provider = "BC"; + + KeyPairGenerator dsaKpg = KeyPairGenerator.getInstance("DSA", provider); + dsaKpg.initialize(testDsaParams, random); + + KeyPair dsaKp = dsaKpg.generateKeyPair(); + PrivateKey testDsaKey = dsaKp.getPrivate(); + + doWriteReadTest(testDsaKey, provider); + doWriteReadTests(testDsaKey, provider, algorithms); + + KeyFactory fact = KeyFactory.getInstance("RSA", provider); + PrivateKey testRsaKey = fact.generatePrivate(testRsaKeySpec); + + doWriteReadTest(testRsaKey, provider); + doWriteReadTests(testRsaKey, provider, algorithms); + + fact = KeyFactory.getInstance("ECDSA", provider); + PrivateKey testEcDsaKey = fact.generatePrivate(testEcDsaKeySpec); + + doWriteReadTest(testEcDsaKey, provider); + doWriteReadTests(testEcDsaKey, provider, algorithms); + + KeyPairGenerator kpGen = KeyPairGenerator.getInstance("ECDSA", "BC"); + + kpGen.initialize(239); + + PrivateKey privKey = kpGen.generateKeyPair().getPrivate(); + + doWriteReadTest(privKey, provider); + doWriteReadTests(privKey, "BC", algorithms); + + // override test + PEMWriter pWrt = new PEMWriter(new OutputStreamWriter(new ByteArrayOutputStream())); + + Object o = new PemObject("FRED", new byte[100]); + pWrt.writeObject(o); + + pWrt.close(); + } + + private void doWriteReadTests( + PrivateKey akp, + String provider, + String[] algorithms) + throws IOException + { + for (int i = 0; i < algorithms.length; ++i) + { + doWriteReadTest(akp, provider, algorithms[i]); + } + } + + private void doWriteReadTest( + PrivateKey akp, + String provider) + throws IOException + { + StringWriter sw = new StringWriter(); + PEMWriter pw = new PEMWriter(sw, provider); + + pw.writeObject(akp); + pw.close(); + + String data = sw.toString(); + + PEMReader pr = new PEMReader(new StringReader(data)); + + Object o = pr.readObject(); + + if (o == null || !(o instanceof KeyPair)) + { + fail("Didn't find OpenSSL key"); + } + + KeyPair kp = (KeyPair) o; + PrivateKey privKey = kp.getPrivate(); + + if (!akp.equals(privKey)) + { + fail("Failed to read back test"); + } + } + + private void doWriteReadTest( + PrivateKey akp, + String provider, + String algorithm) + throws IOException + { + StringWriter sw = new StringWriter(); + PEMWriter pw = new PEMWriter(sw, provider); + + pw.writeObject(akp, algorithm, testPassword, random); + pw.close(); + + String data = sw.toString(); + + PemReader pRaw = new PemReader(new StringReader(data)); + PemObject pemObject = pRaw.readPemObject(); + + List headers = pemObject.getHeaders(); + + for (int i = 0; i != headers.size(); i++) + { + PemHeader pemH = (PemHeader)headers.get(i); + + if (pemH.getName().equals("DEK-Info")) + { + String v = pemH.getValue(); + for (int j = 0; j != v.length(); j++) + { + if (v.charAt(j) >= 'a' && v.charAt(j) <= 'f') + { + fail("lower case detected in DEK-Info: " + v); + } + } + } + } + + PEMReader pr = new PEMReader(new StringReader(data), new Password(testPassword), provider); + + Object o = pr.readObject(); + + if (o == null || !(o instanceof KeyPair)) + { + fail("Didn't find OpenSSL key"); + } + + KeyPair kp = (KeyPair) o; + PrivateKey privKey = kp.getPrivate(); + + if (!akp.equals(privKey)) + { + fail("Failed to read back test key encoded with: " + algorithm); + } + } + + public static void main( + String[] args) + { + Security.addProvider(new BouncyCastleProvider()); + + runTest(new WriterTest()); + } +} diff --git a/bcpkix/src/main/java/org/bouncycastle/openssl/test/package.html b/bcpkix/src/main/java/org/bouncycastle/openssl/test/package.html new file mode 100644 index 00000000..368d7096 --- /dev/null +++ b/bcpkix/src/main/java/org/bouncycastle/openssl/test/package.html @@ -0,0 +1,5 @@ +<html> +<body bgcolor="#ffffff"> +Test class for OpenSSL PEMReader. +</body> +</html> |