From 008ed59d3b7ee947aee2d94afb441629b706a126 Mon Sep 17 00:00:00 2001 From: lambour Date: Wed, 12 Aug 2015 16:39:23 +0200 Subject: [PATCH 1/2] adds a embeddedProvider BC available within the fatJar PEMReader and PEMUtilities have been cloned and adapted from BouncyCastle Activated only with the option ProviderEmbedded on the command line otherwise it use a regular installed provider --- .../oss/client/OSSSshAgentAddIdentity.java | 96 ++- .../bouncycastle/openssl/PEMReader.java | 786 ++++++++++++++++++ .../bouncycastle/openssl/PEMUtilities.java | 312 +++++++ 3 files changed, 1164 insertions(+), 30 deletions(-) create mode 100644 src/main/java/com/geoxp/oss/client/bouncycastle/openssl/PEMReader.java create mode 100644 src/main/java/com/geoxp/oss/client/bouncycastle/openssl/PEMUtilities.java diff --git a/src/main/java/com/geoxp/oss/client/OSSSshAgentAddIdentity.java b/src/main/java/com/geoxp/oss/client/OSSSshAgentAddIdentity.java index e5f2e69..11648f5 100644 --- a/src/main/java/com/geoxp/oss/client/OSSSshAgentAddIdentity.java +++ b/src/main/java/com/geoxp/oss/client/OSSSshAgentAddIdentity.java @@ -1,22 +1,19 @@ package com.geoxp.oss.client; -import java.io.BufferedReader; -import java.io.File; -import java.io.FileReader; -import java.io.Reader; +import java.io.*; import java.security.KeyPair; import java.util.ArrayList; import java.util.Arrays; import java.util.List; + +import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.util.encoders.Hex; import org.bouncycastle.openssl.EncryptionException; -import org.bouncycastle.openssl.PEMReader; import org.bouncycastle.openssl.PasswordFinder; import com.geoxp.oss.CryptoHelper; import com.geoxp.oss.CryptoHelper.SSHAgentClient; import com.geoxp.oss.CryptoHelper.SSHAgentClient.SSHKey; -import com.geoxp.oss.client.OSSClient; public class OSSSshAgentAddIdentity { private static class DefaultPasswordFinder implements PasswordFinder { @@ -54,12 +51,13 @@ private static List getDefaultsKeyFiles() { */ public static void main(String[] args) throws Exception { if (args.length < 3) { - System.err.println("Usage: OSSLoadAgent OSS_URL SECRET_NAME WRAPPED_PASSPHRASE AGENT_AUTH_SOCK [KEY_FILE]"); + System.err.println("Usage: OSSLoadAgent OSS_URL SECRET_NAME AGENT_AUTH_SOCK WRAPPED_PASSPHRASE [ProviderEmbedded default false] [KEY_FILE]"); System.exit(1); } SSHAgentClient sshAgent = new SSHAgentClient(args[2]); + System.out.println("Unwrap secret"); // Get the secret from OSS // FIXME ? Provide a way to specify the ssh signing key fingerprint byte[] secret = OSSClient.getSecret(args[0], args[1], null); @@ -67,45 +65,83 @@ public static void main(String[] args) throws Exception { byte[] unwrap = CryptoHelper.unwrapBlob(secret, Hex.decode(args[3])); String password = new String(unwrap, "UTF-8"); + boolean providerEmbedded = false; + // Read private keys - // openssh store it in PEM format - List sshKeyFiles; - if (args.length > 4) { - sshKeyFiles = new ArrayList(1); - sshKeyFiles.add(new File(args[4])); - } else { + // openssh store it in PEM format + List sshKeyFiles = new ArrayList(1); + + for (int i=4; i identities = sshAgent.requestIdentities(); for (SSHKey identity : identities) { System.out.println(identity); } - } + private static void loadKeyPair(KeyPair kp, File sshKeyFile, SSHAgentClient sshAgent) { + // Add the identity in the ssh-agent + try { + byte[] keyblob = CryptoHelper.sshPrivateKeyBlobFromKeyPair(kp); + System.out.println("Loading " + sshKeyFile.getPath()); + sshAgent.addIdentity(keyblob, sshKeyFile.getPath()); + } catch (IOException e) { + System.err.println("Can't read private key in " + sshKeyFile.getAbsolutePath()); + e.printStackTrace(); + } + } } diff --git a/src/main/java/com/geoxp/oss/client/bouncycastle/openssl/PEMReader.java b/src/main/java/com/geoxp/oss/client/bouncycastle/openssl/PEMReader.java new file mode 100644 index 0000000..ee1c19d --- /dev/null +++ b/src/main/java/com/geoxp/oss/client/bouncycastle/openssl/PEMReader.java @@ -0,0 +1,786 @@ +package com.geoxp.oss.client.bouncycastle.openssl; + +import org.bouncycastle.asn1.cms.ContentInfo; +import org.bouncycastle.asn1.x509.AlgorithmIdentifier; +import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo; +import org.bouncycastle.asn1.x9.X9ObjectIdentifiers; +import org.bouncycastle.jce.ECNamedCurveTable; +import org.bouncycastle.jce.PKCS10CertificationRequest; +import org.bouncycastle.openssl.PEMException; +import org.bouncycastle.openssl.PasswordException; +import org.bouncycastle.openssl.PasswordFinder; +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; + +import javax.crypto.Cipher; +import javax.crypto.SecretKey; +import javax.crypto.SecretKeyFactory; +import javax.crypto.spec.PBEKeySpec; +import javax.crypto.spec.PBEParameterSpec; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.Reader; +import java.security.*; +import java.security.cert.CertificateFactory; + +import java.security.AlgorithmParameters; +import java.security.KeyFactory; +import java.security.KeyPair; +import java.security.NoSuchAlgorithmException; +import java.security.PublicKey; +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 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.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; + +/** + * Class for reading OpenSSL PEM encoded streams containing + * X509 certificates, PKCS8 encoded keys and PKCS7 objects. + *

+ * 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. + *

+ */ +public class PEMReader + extends PemReader +{ + private final Map parsers = new HashMap(); + + private PasswordFinder pFinder; + + + /** + * Create a new PEMReader + * + * @param reader the Reader + + 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 + + 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 customProvider the cryptography provider to use + */ + + public PEMReader( + Reader reader, + PasswordFinder pFinder, + Provider customProvider) + { + super(reader); + + this.pFinder = pFinder; + + parsers.put("CERTIFICATE REQUEST", new PKCS10CertificationRequestParser()); + parsers.put("NEW CERTIFICATE REQUEST", new PKCS10CertificationRequestParser()); + parsers.put("CERTIFICATE", new X509CertificateParser(customProvider)); + parsers.put("X509 CERTIFICATE", new X509CertificateParser(customProvider)); + parsers.put("X509 CRL", new X509CRLParser(customProvider)); + parsers.put("PKCS7", new PKCS7Parser()); + parsers.put("ATTRIBUTE CERTIFICATE", new X509AttributeCertificateParser()); + parsers.put("EC PARAMETERS", new ECNamedCurveSpecParser()); + parsers.put("PUBLIC KEY", new PublicKeyParser(customProvider)); + parsers.put("RSA PUBLIC KEY", new RSAPublicKeyParser(customProvider)); + parsers.put("RSA PRIVATE KEY", new RSAKeyPairParser(customProvider)); + parsers.put("DSA PRIVATE KEY", new DSAKeyPairParser(customProvider)); + parsers.put("EC PRIVATE KEY", new ECDSAKeyPairParser(customProvider)); + parsers.put("ENCRYPTED PRIVATE KEY", new EncryptedPrivateKeyParser(customProvider, customProvider)); + parsers.put("PRIVATE KEY", new PrivateKeyParser(customProvider)); + } + + 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 Provider provider; + + public KeyPairParser(Provider provider) + { + this.provider = provider; + } + + /** + * 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 = PEMUtilities.crypt(false, provider, 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 + { + public DSAKeyPairParser(Provider provider) + { + super(provider); + } + + 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", provider); + + 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 + { + public ECDSAKeyPairParser(Provider provider) + { + super(provider); + } + + 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", provider); + + + 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 + { + public RSAKeyPairParser(Provider provider) + { + super(provider); + } + + 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", provider); + + 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 Provider provider; + + public PublicKeyParser(Provider 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 Provider provider; + + public RSAPublicKeyParser(Provider 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 Provider provider; + + public X509CertificateParser(Provider 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 Provider provider; + + public X509CRLParser(Provider 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 Provider symProvider; + private Provider asymProvider; + + public EncryptedPrivateKeyParser(Provider symProvider, Provider 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 = PEMUtilities.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.getAlgorithmId().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.getAlgorithmId().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 Provider provider; + + public PrivateKeyParser(Provider 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.getAlgorithmId().getAlgorithm().getId(), provider); + + return keyFact.generatePrivate(keySpec); + } + catch (Exception e) + { + throw new PEMException("problem parsing PRIVATE KEY: " + e.toString(), e); + } + } + } +} + diff --git a/src/main/java/com/geoxp/oss/client/bouncycastle/openssl/PEMUtilities.java b/src/main/java/com/geoxp/oss/client/bouncycastle/openssl/PEMUtilities.java new file mode 100644 index 0000000..f9a9d48 --- /dev/null +++ b/src/main/java/com/geoxp/oss/client/bouncycastle/openssl/PEMUtilities.java @@ -0,0 +1,312 @@ +package com.geoxp.oss.client.bouncycastle.openssl; + +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.BlockCipher; +import org.bouncycastle.crypto.BufferedBlockCipher; +import org.bouncycastle.crypto.PBEParametersGenerator; +import org.bouncycastle.crypto.engines.AESEngine; +import org.bouncycastle.crypto.generators.OpenSSLPBEParametersGenerator; +import org.bouncycastle.crypto.generators.PKCS5S2ParametersGenerator; +import org.bouncycastle.crypto.modes.CBCBlockCipher; +import org.bouncycastle.crypto.modes.OFBBlockCipher; +import org.bouncycastle.crypto.paddings.PKCS7Padding; +import org.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher; +import org.bouncycastle.crypto.params.KeyParameter; +import org.bouncycastle.crypto.params.ParametersWithIV; +import org.bouncycastle.openssl.EncryptionException; + +import javax.crypto.SecretKey; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.RC2ParameterSpec; +import javax.crypto.spec.SecretKeySpec; +import java.io.IOException; +import java.security.Key; +import java.security.Provider; +import java.security.Security; +import java.security.spec.AlgorithmParameterSpec; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +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(), new Integer(192)); + KEYSIZES.put(NISTObjectIdentifiers.id_aes128_CBC.getId(), new Integer(128)); + KEYSIZES.put(NISTObjectIdentifiers.id_aes192_CBC.getId(), new Integer(192)); + KEYSIZES.put(NISTObjectIdentifiers.id_aes256_CBC.getId(), new Integer(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); + } + + static boolean isPKCS12(DERObjectIdentifier algOid) + { + return algOid.getId().startsWith(PKCSObjectIdentifiers.pkcs_12PbeIds.getId()); + } + + 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, + 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; + + // Instantiate manually the AES Engine without the Javax.Security.Provider + // in oder to avoid securityException "JCE cannot authenticate the provider" + try { + // Here alg = AES + AESEngine engine = new AESEngine(); + + // use the right block mode + BufferedBlockCipher bbc = null; + BlockCipher bc = null; + if ("CBC".equals(blockMode)) { + bc = new CBCBlockCipher(engine); + + } else if ("OFB".equals(blockMode)) { + bc = new OFBBlockCipher(engine, 128); + } else { + throw new EncryptionException("unknown encryption block mode " + blockMode); + } + + // add the padding + if ("NoPadding".equals(padding)) { + bbc = new BufferedBlockCipher(bc); + } else if ("PKCS5Padding".equals(padding) || "PKCS7Padding".equals(padding)) { + // PKCS7Padding implementation supports both PKCS5 and PKCS7 padding + bbc = new PaddedBufferedBlockCipher(bc, new PKCS7Padding()); + } else { + throw new EncryptionException("unknown encryption padding " + padding); + } + + KeyParameter kp = new KeyParameter(sKey.getEncoded()); + + bbc.init(encrypt, new ParametersWithIV(kp, iv)); + + byte[] out = new byte[bbc.getOutputSize(bytes.length)]; + + int len = bbc.processBytes(bytes, 0, bytes.length, out, 0); + + bbc.doFinal(out, len); + + return out; + } + 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); + } +} + From a08955429f1693ca7098bed3ee39c8508bad8d60 Mon Sep 17 00:00:00 2001 From: lambour Date: Wed, 19 Aug 2015 15:14:18 +0200 Subject: [PATCH 2/2] fix iqmp, p & q missing in SSH2_AGENTC_ADD_IDENTITY request for RSA keys --- src/main/java/com/geoxp/oss/CryptoHelper.java | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/main/java/com/geoxp/oss/CryptoHelper.java b/src/main/java/com/geoxp/oss/CryptoHelper.java index 40bf809..44e6b9e 100644 --- a/src/main/java/com/geoxp/oss/CryptoHelper.java +++ b/src/main/java/com/geoxp/oss/CryptoHelper.java @@ -41,10 +41,7 @@ import java.security.Security; import java.security.Signature; import java.security.SignatureException; -import java.security.interfaces.DSAPrivateKey; -import java.security.interfaces.DSAPublicKey; -import java.security.interfaces.RSAPrivateKey; -import java.security.interfaces.RSAPublicKey; +import java.security.interfaces.*; import java.security.spec.DSAPublicKeySpec; import java.security.spec.InvalidKeySpecException; import java.security.spec.KeySpec; @@ -64,6 +61,7 @@ import org.bouncycastle.crypto.paddings.PKCS7Padding; import org.bouncycastle.crypto.params.KeyParameter; import org.bouncycastle.crypto.params.RSAKeyParameters; +import org.bouncycastle.jcajce.provider.asymmetric.rsa.BCRSAPrivateCrtKey; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.openpgp.PGPCompressedDataGenerator; import org.bouncycastle.openpgp.PGPEncryptedDataGenerator; @@ -607,10 +605,12 @@ public static byte[] sshPrivateKeyBlobFromKeyPair(KeyPair kp) { BigInteger e = ((RSAPublicKey) kp.getPublic()).getPublicExponent(); BigInteger d = ((RSAPrivateKey) kp.getPrivate()).getPrivateExponent(); - // Not available and not used by ssh-agent anyway ... - BigInteger iqmp = BigInteger.ZERO; - BigInteger p = BigInteger.ZERO; - BigInteger q = BigInteger.ZERO; + // + // CrtCoef, P & Q are needed by the ssh-agent + // + BigInteger iqmp = ((BCRSAPrivateCrtKey) kp.getPrivate()).getCrtCoefficient(); + BigInteger p = ((BCRSAPrivateCrtKey) kp.getPrivate()).getPrimeP(); + BigInteger q = ((BCRSAPrivateCrtKey) kp.getPrivate()).getPrimeQ(); byte[] tns = null; try { tns = encodeNetworkString(SSH_RSA_PREFIX.getBytes("UTF-8")); } catch (UnsupportedEncodingException uee) {}