diff options
Diffstat (limited to 'pkix/src/main/java/org/spongycastle/openssl/jcajce')
9 files changed, 1050 insertions, 0 deletions
diff --git a/pkix/src/main/java/org/spongycastle/openssl/jcajce/JcaMiscPEMGenerator.java b/pkix/src/main/java/org/spongycastle/openssl/jcajce/JcaMiscPEMGenerator.java new file mode 100644 index 00000000..88f2a32e --- /dev/null +++ b/pkix/src/main/java/org/spongycastle/openssl/jcajce/JcaMiscPEMGenerator.java @@ -0,0 +1,98 @@ +package org.spongycastle.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.spongycastle.asn1.pkcs.PrivateKeyInfo; +import org.spongycastle.asn1.x509.SubjectPublicKeyInfo; +import org.spongycastle.cert.jcajce.JcaX509AttributeCertificateHolder; +import org.spongycastle.cert.jcajce.JcaX509CRLHolder; +import org.spongycastle.cert.jcajce.JcaX509CertificateHolder; +import org.spongycastle.jce.PKCS10CertificationRequest; +import org.spongycastle.openssl.MiscPEMGenerator; +import org.spongycastle.openssl.PEMEncryptor; +import org.spongycastle.x509.X509AttributeCertificate; +import org.spongycastle.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.spongycastle.pkcs.PKCS10CertificationRequest(((PKCS10CertificationRequest)o).getEncoded()); + } + + return o; + } +} diff --git a/pkix/src/main/java/org/spongycastle/openssl/jcajce/JcaPEMKeyConverter.java b/pkix/src/main/java/org/spongycastle/openssl/jcajce/JcaPEMKeyConverter.java new file mode 100644 index 00000000..1b15639a --- /dev/null +++ b/pkix/src/main/java/org/spongycastle/openssl/jcajce/JcaPEMKeyConverter.java @@ -0,0 +1,115 @@ +package org.spongycastle.openssl.jcajce; + +import java.security.KeyFactory; +import java.security.KeyPair; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.PrivateKey; +import java.security.Provider; +import java.security.PublicKey; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.X509EncodedKeySpec; +import java.util.HashMap; +import java.util.Map; + +import org.spongycastle.asn1.ASN1ObjectIdentifier; +import org.spongycastle.asn1.pkcs.PKCSObjectIdentifiers; +import org.spongycastle.asn1.pkcs.PrivateKeyInfo; +import org.spongycastle.asn1.x509.AlgorithmIdentifier; +import org.spongycastle.asn1.x509.SubjectPublicKeyInfo; +import org.spongycastle.asn1.x9.X9ObjectIdentifiers; +import org.spongycastle.jcajce.util.DefaultJcaJceHelper; +import org.spongycastle.jcajce.util.JcaJceHelper; +import org.spongycastle.jcajce.util.NamedJcaJceHelper; +import org.spongycastle.jcajce.util.ProviderJcaJceHelper; +import org.spongycastle.openssl.PEMException; +import org.spongycastle.openssl.PEMKeyPair; + +public class JcaPEMKeyConverter +{ + private JcaJceHelper helper = new DefaultJcaJceHelper(); + + private static final Map algorithms = new HashMap(); + + static + { + algorithms.put(X9ObjectIdentifiers.id_ecPublicKey, "ECDSA"); + algorithms.put(PKCSObjectIdentifiers.rsaEncryption, "RSA"); + algorithms.put(X9ObjectIdentifiers.id_dsa, "DSA"); + } + + 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 + { + KeyFactory keyFactory = getKeyFactory(keyPair.getPrivateKeyInfo().getPrivateKeyAlgorithm()); + + 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 + { + KeyFactory keyFactory = getKeyFactory(publicKeyInfo.getAlgorithm()); + + 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 + { + KeyFactory keyFactory = getKeyFactory(privateKeyInfo.getPrivateKeyAlgorithm()); + + return keyFactory.generatePrivate(new PKCS8EncodedKeySpec(privateKeyInfo.getEncoded())); + } + catch (Exception e) + { + throw new PEMException("unable to convert key pair: " + e.getMessage(), e); + } + } + + private KeyFactory getKeyFactory(AlgorithmIdentifier algId) + throws NoSuchAlgorithmException, NoSuchProviderException + { + ASN1ObjectIdentifier algorithm = algId.getAlgorithm(); + + String algName = (String)algorithms.get(algorithm); + + if (algName == null) + { + algName = algorithm.getId(); + } + + return helper.createKeyFactory(algName); + } +} diff --git a/pkix/src/main/java/org/spongycastle/openssl/jcajce/JcaPEMWriter.java b/pkix/src/main/java/org/spongycastle/openssl/jcajce/JcaPEMWriter.java new file mode 100644 index 00000000..0224b89e --- /dev/null +++ b/pkix/src/main/java/org/spongycastle/openssl/jcajce/JcaPEMWriter.java @@ -0,0 +1,68 @@ +package org.spongycastle.openssl.jcajce; + +import java.io.IOException; +import java.io.Writer; + +import org.spongycastle.openssl.PEMEncryptor; +import org.spongycastle.util.io.pem.PemGenerationException; +import org.spongycastle.util.io.pem.PemObjectGenerator; +import org.spongycastle.util.io.pem.PemWriter; + +/** + * General purpose writer for OpenSSL PEM objects based on JCA/JCE classes. + */ +public class JcaPEMWriter + extends PemWriter +{ + /** + * Base constructor. + * + * @param out output stream to use. + */ + public JcaPEMWriter(Writer out) + { + super(out); + } + + /** + * @throws java.io.IOException + */ + public void writeObject( + Object obj) + throws IOException + { + writeObject(obj, null); + } + + /** + * @param obj + * @param encryptor + * @throws java.io.IOException + */ + 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); + } +} diff --git a/pkix/src/main/java/org/spongycastle/openssl/jcajce/JcaPKCS8Generator.java b/pkix/src/main/java/org/spongycastle/openssl/jcajce/JcaPKCS8Generator.java new file mode 100644 index 00000000..9c4e4f46 --- /dev/null +++ b/pkix/src/main/java/org/spongycastle/openssl/jcajce/JcaPKCS8Generator.java @@ -0,0 +1,18 @@ +package org.spongycastle.openssl.jcajce; + +import java.security.PrivateKey; + +import org.spongycastle.asn1.pkcs.PrivateKeyInfo; +import org.spongycastle.openssl.PKCS8Generator; +import org.spongycastle.operator.OutputEncryptor; +import org.spongycastle.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/pkix/src/main/java/org/spongycastle/openssl/jcajce/JceOpenSSLPKCS8DecryptorProviderBuilder.java b/pkix/src/main/java/org/spongycastle/openssl/jcajce/JceOpenSSLPKCS8DecryptorProviderBuilder.java new file mode 100644 index 00000000..5050f4b2 --- /dev/null +++ b/pkix/src/main/java/org/spongycastle/openssl/jcajce/JceOpenSSLPKCS8DecryptorProviderBuilder.java @@ -0,0 +1,141 @@ +package org.spongycastle.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.spongycastle.asn1.pkcs.EncryptionScheme; +import org.spongycastle.asn1.pkcs.KeyDerivationFunc; +import org.spongycastle.asn1.pkcs.PBEParameter; +import org.spongycastle.asn1.pkcs.PBES2Parameters; +import org.spongycastle.asn1.pkcs.PBKDF2Params; +import org.spongycastle.asn1.pkcs.PKCS12PBEParams; +import org.spongycastle.asn1.x509.AlgorithmIdentifier; +import org.spongycastle.jcajce.util.DefaultJcaJceHelper; +import org.spongycastle.jcajce.util.JcaJceHelper; +import org.spongycastle.jcajce.util.NamedJcaJceHelper; +import org.spongycastle.jcajce.util.ProviderJcaJceHelper; +import org.spongycastle.openssl.PEMException; +import org.spongycastle.operator.InputDecryptor; +import org.spongycastle.operator.InputDecryptorProvider; +import org.spongycastle.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/pkix/src/main/java/org/spongycastle/openssl/jcajce/JceOpenSSLPKCS8EncryptorBuilder.java b/pkix/src/main/java/org/spongycastle/openssl/jcajce/JceOpenSSLPKCS8EncryptorBuilder.java new file mode 100644 index 00000000..8404661f --- /dev/null +++ b/pkix/src/main/java/org/spongycastle/openssl/jcajce/JceOpenSSLPKCS8EncryptorBuilder.java @@ -0,0 +1,221 @@ +package org.spongycastle.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.spongycastle.asn1.ASN1EncodableVector; +import org.spongycastle.asn1.ASN1Integer; +import org.spongycastle.asn1.ASN1ObjectIdentifier; +import org.spongycastle.asn1.ASN1Primitive; +import org.spongycastle.asn1.DEROctetString; +import org.spongycastle.asn1.DERSequence; +import org.spongycastle.asn1.nist.NISTObjectIdentifiers; +import org.spongycastle.asn1.pkcs.KeyDerivationFunc; +import org.spongycastle.asn1.pkcs.PBES2Parameters; +import org.spongycastle.asn1.pkcs.PBKDF2Params; +import org.spongycastle.asn1.pkcs.PKCS12PBEParams; +import org.spongycastle.asn1.pkcs.PKCSObjectIdentifiers; +import org.spongycastle.asn1.x509.AlgorithmIdentifier; +import org.spongycastle.jcajce.util.DefaultJcaJceHelper; +import org.spongycastle.jcajce.util.JcaJceHelper; +import org.spongycastle.jcajce.util.NamedJcaJceHelper; +import org.spongycastle.jcajce.util.ProviderJcaJceHelper; +import org.spongycastle.operator.GenericKey; +import org.spongycastle.operator.OperatorCreationException; +import org.spongycastle.operator.OutputEncryptor; +import org.spongycastle.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/pkix/src/main/java/org/spongycastle/openssl/jcajce/JcePEMDecryptorProviderBuilder.java b/pkix/src/main/java/org/spongycastle/openssl/jcajce/JcePEMDecryptorProviderBuilder.java new file mode 100644 index 00000000..b54d7fd3 --- /dev/null +++ b/pkix/src/main/java/org/spongycastle/openssl/jcajce/JcePEMDecryptorProviderBuilder.java @@ -0,0 +1,54 @@ +package org.spongycastle.openssl.jcajce; + +import java.security.Provider; + +import org.spongycastle.jcajce.util.DefaultJcaJceHelper; +import org.spongycastle.jcajce.util.JcaJceHelper; +import org.spongycastle.jcajce.util.NamedJcaJceHelper; +import org.spongycastle.jcajce.util.ProviderJcaJceHelper; +import org.spongycastle.openssl.PEMDecryptor; +import org.spongycastle.openssl.PEMDecryptorProvider; +import org.spongycastle.openssl.PEMException; +import org.spongycastle.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/pkix/src/main/java/org/spongycastle/openssl/jcajce/JcePEMEncryptorBuilder.java b/pkix/src/main/java/org/spongycastle/openssl/jcajce/JcePEMEncryptorBuilder.java new file mode 100644 index 00000000..3360f8a8 --- /dev/null +++ b/pkix/src/main/java/org/spongycastle/openssl/jcajce/JcePEMEncryptorBuilder.java @@ -0,0 +1,78 @@ +package org.spongycastle.openssl.jcajce; + +import java.security.Provider; +import java.security.SecureRandom; + +import org.spongycastle.jcajce.util.DefaultJcaJceHelper; +import org.spongycastle.jcajce.util.JcaJceHelper; +import org.spongycastle.jcajce.util.NamedJcaJceHelper; +import org.spongycastle.jcajce.util.ProviderJcaJceHelper; +import org.spongycastle.openssl.PEMEncryptor; +import org.spongycastle.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/pkix/src/main/java/org/spongycastle/openssl/jcajce/PEMUtilities.java b/pkix/src/main/java/org/spongycastle/openssl/jcajce/PEMUtilities.java new file mode 100644 index 00000000..6bfa2a57 --- /dev/null +++ b/pkix/src/main/java/org/spongycastle/openssl/jcajce/PEMUtilities.java @@ -0,0 +1,257 @@ +package org.spongycastle.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.spongycastle.asn1.ASN1ObjectIdentifier; +import org.spongycastle.asn1.nist.NISTObjectIdentifiers; +import org.spongycastle.asn1.pkcs.PKCSObjectIdentifiers; +import org.spongycastle.crypto.PBEParametersGenerator; +import org.spongycastle.crypto.generators.OpenSSLPBEParametersGenerator; +import org.spongycastle.crypto.generators.PKCS5S2ParametersGenerator; +import org.spongycastle.crypto.params.KeyParameter; +import org.spongycastle.jcajce.util.JcaJceHelper; +import org.spongycastle.openssl.EncryptionException; +import org.spongycastle.openssl.PEMException; +import org.spongycastle.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(ASN1ObjectIdentifier algOid) + { + return PKCS5_SCHEME_1.contains(algOid); + } + + static boolean isPKCS5Scheme2(ASN1ObjectIdentifier algOid) + { + return PKCS5_SCHEME_2.contains(algOid); + } + + public static boolean isPKCS12(ASN1ObjectIdentifier 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); + } +} |