summaryrefslogtreecommitdiff
path: root/bcpkix/src/main/java/org/bouncycastle/openssl
diff options
context:
space:
mode:
Diffstat (limited to 'bcpkix/src/main/java/org/bouncycastle/openssl')
-rw-r--r--bcpkix/src/main/java/org/bouncycastle/openssl/EncryptionException.java23
-rw-r--r--bcpkix/src/main/java/org/bouncycastle/openssl/MiscPEMGenerator.java211
-rw-r--r--bcpkix/src/main/java/org/bouncycastle/openssl/PEMDecryptor.java7
-rw-r--r--bcpkix/src/main/java/org/bouncycastle/openssl/PEMDecryptorProvider.java9
-rw-r--r--bcpkix/src/main/java/org/bouncycastle/openssl/PEMEncryptedKeyPair.java44
-rw-r--r--bcpkix/src/main/java/org/bouncycastle/openssl/PEMEncryptor.java11
-rw-r--r--bcpkix/src/main/java/org/bouncycastle/openssl/PEMException.java34
-rw-r--r--bcpkix/src/main/java/org/bouncycastle/openssl/PEMKeyPair.java26
-rw-r--r--bcpkix/src/main/java/org/bouncycastle/openssl/PEMKeyPairParser.java9
-rw-r--r--bcpkix/src/main/java/org/bouncycastle/openssl/PEMParser.java509
-rw-r--r--bcpkix/src/main/java/org/bouncycastle/openssl/PEMReader.java1023
-rw-r--r--bcpkix/src/main/java/org/bouncycastle/openssl/PEMUtilities.java65
-rw-r--r--bcpkix/src/main/java/org/bouncycastle/openssl/PEMWriter.java91
-rw-r--r--bcpkix/src/main/java/org/bouncycastle/openssl/PKCS8Generator.java196
-rw-r--r--bcpkix/src/main/java/org/bouncycastle/openssl/PasswordException.java10
-rw-r--r--bcpkix/src/main/java/org/bouncycastle/openssl/PasswordFinder.java9
-rw-r--r--bcpkix/src/main/java/org/bouncycastle/openssl/jcajce/JcaMiscPEMGenerator.java98
-rw-r--r--bcpkix/src/main/java/org/bouncycastle/openssl/jcajce/JcaPEMKeyConverter.java105
-rw-r--r--bcpkix/src/main/java/org/bouncycastle/openssl/jcajce/JcaPKCS8Generator.java18
-rw-r--r--bcpkix/src/main/java/org/bouncycastle/openssl/jcajce/JceOpenSSLPKCS8DecryptorProviderBuilder.java141
-rw-r--r--bcpkix/src/main/java/org/bouncycastle/openssl/jcajce/JceOpenSSLPKCS8EncryptorBuilder.java221
-rw-r--r--bcpkix/src/main/java/org/bouncycastle/openssl/jcajce/JcePEMDecryptorProviderBuilder.java54
-rw-r--r--bcpkix/src/main/java/org/bouncycastle/openssl/jcajce/JcePEMEncryptorBuilder.java78
-rw-r--r--bcpkix/src/main/java/org/bouncycastle/openssl/jcajce/PEMUtilities.java258
-rw-r--r--bcpkix/src/main/java/org/bouncycastle/openssl/package.html5
-rw-r--r--bcpkix/src/main/java/org/bouncycastle/openssl/test/AllTests.java200
-rw-r--r--bcpkix/src/main/java/org/bouncycastle/openssl/test/ParserTest.java500
-rw-r--r--bcpkix/src/main/java/org/bouncycastle/openssl/test/ReaderTest.java417
-rw-r--r--bcpkix/src/main/java/org/bouncycastle/openssl/test/WriterTest.java243
-rw-r--r--bcpkix/src/main/java/org/bouncycastle/openssl/test/package.html5
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>