From acf5d1b4215cd28983f0adc99eb5d9cbe8f5ac02 Mon Sep 17 00:00:00 2001 From: jrclark2 Date: Fri, 6 Dec 2019 12:43:02 -0600 Subject: [PATCH] Add decryption of AES-encrypted keys --- pom-android-with-async-io.xml | 5 + pom-android.xml | 5 + pom-without-protobuf.xml | 5 + pom.xml | 13 ++ .../jndn/security/tpm/TpmBackEndFile.java | 4 +- .../jndn/security/tpm/TpmPrivateKey.java | 210 ++++++------------ .../tests/integration_tests/TestKeyChain.java | 43 +++- .../tests/unit_tests/TestTpmPrivateKey.java | 8 +- 8 files changed, 138 insertions(+), 155 deletions(-) diff --git a/pom-android-with-async-io.xml b/pom-android-with-async-io.xml index 157d5976..71cee258 100644 --- a/pom-android-with-async-io.xml +++ b/pom-android-with-async-io.xml @@ -43,6 +43,11 @@ 4.10 test + + com.madgag.spongycastle + bcpkix-jdk15on + 1.58.0.0 + diff --git a/pom-android.xml b/pom-android.xml index 332a0918..b524a9fc 100644 --- a/pom-android.xml +++ b/pom-android.xml @@ -43,6 +43,11 @@ 4.10 test + + com.madgag.spongycastle + bcpkix-jdk15on + 1.58.0.0 + diff --git a/pom-without-protobuf.xml b/pom-without-protobuf.xml index f9035b59..432ec4d3 100644 --- a/pom-without-protobuf.xml +++ b/pom-without-protobuf.xml @@ -43,6 +43,11 @@ 4.10 test + + com.madgag.spongycastle + bcpkix-jdk15on + 1.58.0.0 + diff --git a/pom.xml b/pom.xml index 3eba8492..e2808934 100644 --- a/pom.xml +++ b/pom.xml @@ -44,6 +44,11 @@ 4.10 test + + com.madgag.spongycastle + bcpkix-jdk15on + 1.58.0.0 + @@ -66,6 +71,14 @@ + + org.apache.maven.plugins + maven-compiler-plugin + + 9 + 9 + + diff --git a/src/net/named_data/jndn/security/tpm/TpmBackEndFile.java b/src/net/named_data/jndn/security/tpm/TpmBackEndFile.java index c29c4869..fc583f82 100644 --- a/src/net/named_data/jndn/security/tpm/TpmBackEndFile.java +++ b/src/net/named_data/jndn/security/tpm/TpmBackEndFile.java @@ -280,7 +280,7 @@ public TpmBackEndFile(String locationPath) byte[] pkcs = Common.base64Decode(base64.toString()); try { - key.loadPkcs1(ByteBuffer.wrap(pkcs), null); + key.loadPkcs8(ByteBuffer.wrap(pkcs)); } catch (TpmPrivateKey.Error ex) { throw new TpmBackEnd.Error("Error decoding private key file: " + ex); } @@ -298,7 +298,7 @@ public TpmBackEndFile(String locationPath) String filePath = toFilePath(keyName).getAbsolutePath(); String base64; try { - base64 = Common.base64Encode(key.toPkcs1().getImmutableArray(), true); + base64 = Common.base64Encode(key.toPkcs8().getImmutableArray(), true); } catch (TpmPrivateKey.Error ex) { throw new TpmBackEnd.Error("Error encoding private key file: " + ex); } diff --git a/src/net/named_data/jndn/security/tpm/TpmPrivateKey.java b/src/net/named_data/jndn/security/tpm/TpmPrivateKey.java index 83810e3b..8eac20d4 100644 --- a/src/net/named_data/jndn/security/tpm/TpmPrivateKey.java +++ b/src/net/named_data/jndn/security/tpm/TpmPrivateKey.java @@ -20,17 +20,21 @@ package net.named_data.jndn.security.tpm; +import java.io.IOException; import java.math.BigInteger; import java.nio.ByteBuffer; +import java.nio.CharBuffer; +import java.nio.charset.Charset; import java.security.InvalidKeyException; import java.security.KeyFactory; -import java.security.KeyPair; -import java.security.KeyPairGenerator; import java.security.NoSuchAlgorithmException; import java.security.SignatureException; import java.security.spec.InvalidKeySpecException; import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.RSAPublicKeySpec; +import java.security.KeyPairGenerator; +import java.security.KeyPair; +import java.security.Security; import java.util.List; import javax.crypto.Cipher; import javax.crypto.spec.IvParameterSpec; @@ -48,12 +52,24 @@ import net.named_data.jndn.security.RsaKeyParams; import net.named_data.jndn.util.Blob; import net.named_data.jndn.util.Common; +import org.spongycastle.asn1.pkcs.PrivateKeyInfo; +import org.spongycastle.openssl.jcajce.JcaPEMKeyConverter; +import org.spongycastle.openssl.jcajce.JceOpenSSLPKCS8DecryptorProviderBuilder; +import org.spongycastle.operator.InputDecryptorProvider; +import org.spongycastle.operator.OperatorCreationException; +import org.spongycastle.pkcs.PKCS8EncryptedPrivateKeyInfo; +import org.spongycastle.pkcs.PKCSException; + /** * A TpmPrivateKey holds an in-memory private key and provides cryptographic * operations such as for signing by the in-memory TPM. */ public class TpmPrivateKey { + static { + Security.insertProviderAt(new org.spongycastle.jce.provider.BouncyCastleProvider(), 1); + } + /** * A TpmPrivateKey.Error extends Exception and represents an error in private * key processing. @@ -127,6 +143,48 @@ else if (keyType == KeyType.RSA) loadPkcs8(pkcs8.buf(), keyType); } + /** + * Load the encrypted private key from a buffer with the PKCS #8 encoding of + * the EncryptedPrivateKeyInfo. + * This replaces any existing private key in this object. + * @param encoding The byte buffer with the private key encoding. + * @param password The password for decrypting the private key, which should + * have characters in the range of 1 to 127. + * @throws TpmPrivateKey.Error for errors decoding or decrypting the key. + */ + public final void + loadEncryptedPkcs8 + (ByteBuffer encoding, ByteBuffer password) throws TpmPrivateKey.Error { + //BouncyCastle classes expect a byte array and char array + byte[] encodingBytes = new byte[10]; + encodingBytes = new byte[encoding.capacity()]; + encoding.get(encodingBytes, 0, encodingBytes.length); + encoding.clear(); + + CharBuffer charBuffer = Charset.forName("ISO-8859-1").decode(password); + char[] passwordBytes = charBuffer.array(); + + try { + PKCS8EncryptedPrivateKeyInfo privateKeyInfo = new PKCS8EncryptedPrivateKeyInfo(encodingBytes); + JceOpenSSLPKCS8DecryptorProviderBuilder jce = new JceOpenSSLPKCS8DecryptorProviderBuilder(); + jce.setProvider("SC"); + InputDecryptorProvider decProv = jce.build(passwordBytes); + PrivateKeyInfo info = privateKeyInfo.decryptPrivateKeyInfo(decProv); + JcaPEMKeyConverter converter = new JcaPEMKeyConverter(); + privateKey_ = converter.getPrivateKey(info); + String keyTypeString = privateKey_.getAlgorithm(); + if (keyTypeString.equals("RSA")) keyType_ = KeyType.RSA; + else if (keyTypeString.equals("ECDSA")) keyType_= KeyType.EC; + else throw new TpmPrivateKey.Error + ("loadEncryptedPkcs8: Key type " + keyTypeString + " not supported"); + + } catch (IOException | OperatorCreationException | PKCSException ex) { + throw new TpmPrivateKey.Error + ("loadEncryptedPkcs8: Error parsing PrivateKey info: " + ex); + } + + } + /** * Load the unencrypted private key from a buffer with the PKCS #1 encoding. * This replaces any existing private key in this object. This partially @@ -228,149 +286,6 @@ else if (keyType == KeyType.RSA) { loadPkcs8(encoding, null); } - /** - * Load the encrypted private key from a buffer with the PKCS #8 encoding of - * the EncryptedPrivateKeyInfo. - * This replaces any existing private key in this object. This partially - * decodes the private key to determine the key type. - * @param encoding The byte buffer with the private key encoding. - * @param password The password for decrypting the private key, which should - * have characters in the range of 1 to 127. - * @throws TpmPrivateKey.Error for errors decoding or decrypting the key. - */ - public final void - loadEncryptedPkcs8(ByteBuffer encoding, ByteBuffer password) - throws TpmPrivateKey.Error - { - // Decode the PKCS #8 EncryptedPrivateKeyInfo. - // See https://tools.ietf.org/html/rfc5208. - String oidString; - Object parameters; - Blob encryptedKey; - try { - DerNode parsedNode = DerNode.parse(encoding, 0); - List encryptedPkcs8Children = parsedNode.getChildren(); - List algorithmIdChildren = DerNode.getSequence - (encryptedPkcs8Children, 0).getChildren(); - oidString = "" + ((DerNode.DerOid)algorithmIdChildren.get(0)).toVal(); - parameters = algorithmIdChildren.get(1); - - encryptedKey = - (Blob)((DerNode.DerOctetString)encryptedPkcs8Children.get(1)).toVal(); - } - catch (Throwable ex) { - throw new TpmPrivateKey.Error - ("Cannot decode the PKCS #8 EncryptedPrivateKeyInfo: " + ex); - } - - // Use the password to get the unencrypted pkcs8Encoding. - byte[] pkcs8Encoding; - if (oidString.equals(PBES2_OID)) { - // Decode the PBES2 parameters. See https://www.ietf.org/rfc/rfc2898.txt . - String keyDerivationOidString; - Object keyDerivationParameters; - String encryptionSchemeOidString; - Object encryptionSchemeParameters; - try { - List parametersChildren = ((DerNode.DerSequence)parameters).getChildren(); - - List keyDerivationAlgorithmIdChildren = DerNode.getSequence - (parametersChildren, 0).getChildren(); - keyDerivationOidString = "" + - ((DerNode.DerOid)keyDerivationAlgorithmIdChildren.get(0)).toVal(); - keyDerivationParameters = keyDerivationAlgorithmIdChildren.get(1); - - List encryptionSchemeAlgorithmIdChildren = DerNode.getSequence - (parametersChildren, 1).getChildren(); - encryptionSchemeOidString = "" + - ((DerNode.DerOid)encryptionSchemeAlgorithmIdChildren.get(0)).toVal(); - encryptionSchemeParameters = encryptionSchemeAlgorithmIdChildren.get(1); - } - catch (Throwable ex) { - throw new TpmPrivateKey.Error - ("Cannot decode the PBES2 parameters: " + ex); - } - - // Get the derived key from the password. - byte[] derivedKey = null; - if (keyDerivationOidString.equals(PBKDF2_OID)) { - // Decode the PBKDF2 parameters. - Blob salt; - int nIterations; - try { - List pbkdf2ParametersChildren = - ((DerNode.DerSequence)keyDerivationParameters).getChildren(); - salt = (Blob) - ((DerNode.DerOctetString)pbkdf2ParametersChildren.get(0)).toVal(); - nIterations = (int) - ((DerNode.DerInteger)pbkdf2ParametersChildren.get(1)).toVal(); - } - catch (Throwable ex) { - throw new TpmPrivateKey.Error - ("Cannot decode the PBES2 parameters: " + ex); - } - - // Check the encryption scheme here to get the needed result length. - int resultLength; - if (encryptionSchemeOidString.equals(DES_EDE3_CBC_OID)) - resultLength = DES_EDE3_KEY_LENGTH; - else - throw new TpmPrivateKey.Error - ("Unrecognized PBES2 encryption scheme OID: " + - encryptionSchemeOidString); - - try { - derivedKey = Common.computePbkdf2WithHmacSha1 - (new Blob(password, false).getImmutableArray(), - salt.getImmutableArray(), nIterations, resultLength); - } - catch (Throwable ex) { - throw new TpmPrivateKey.Error - ("Error computing the derived key using PBKDF2 with HMAC SHA1: " + ex); - } - } - else - throw new TpmPrivateKey.Error - ("Unrecognized PBES2 key derivation OID: " + keyDerivationOidString); - - // Use the derived key to get the unencrypted pkcs8Encoding. - if (encryptionSchemeOidString.equals(DES_EDE3_CBC_OID)) { - // Decode the DES-EDE3-CBC parameters. - Blob initialVector; - try { - initialVector = (Blob) - ((DerNode.DerOctetString)encryptionSchemeParameters).toVal(); - } - catch (Throwable ex) { - throw new TpmPrivateKey.Error - ("Cannot decode the DES-EDE3-CBC parameters: " + ex); - } - - try { - Cipher cipher = Cipher.getInstance("DESede/CBC/PKCS5Padding"); - cipher.init - (Cipher.DECRYPT_MODE, - new SecretKeySpec(derivedKey, "DESede"), - new IvParameterSpec(initialVector.getImmutableArray())); - pkcs8Encoding = cipher.doFinal(encryptedKey.getImmutableArray()); - } - catch (Throwable ex) { - throw new TpmPrivateKey.Error - ("Error decrypting PKCS #8 key with DES-EDE3-CBC: " + ex); - } - } - else - throw new TpmPrivateKey.Error - ("Unrecognized PBES2 encryption scheme OID: " + - encryptionSchemeOidString); - } - else - throw new TpmPrivateKey.Error - ("Unrecognized PKCS #8 EncryptedPrivateKeyInfo OID: " + oidString); - - loadPkcs8(ByteBuffer.wrap(pkcs8Encoding)); - } - /** * Get the encoded public key for this private key. * @return The public key encoding Blob. @@ -554,8 +469,7 @@ else if (keyType_ == KeyType.RSA) { /** * Get the encoded encrypted private key in PKCS #8. - * @param password The password for encrypting the private key, which should - * have characters in the range of 1 to 127. + * @param password The password for encrypting the private key. * @return The encoding Blob of the EncryptedPrivateKeyInfo. * @throws TpmPrivateKey.Error if no private key is loaded, or error encoding. */ @@ -692,7 +606,7 @@ else if (keyParams.getKeyType() == KeyType.EC) { */ private static Blob encodePkcs8PrivateKey(ByteBuffer privateKeyDer, OID oid, DerNode parameters) - throws TpmPrivateKey.Error + throws TpmPrivateKey.Error { try { DerSequence algorithmIdentifier = new DerSequence(); diff --git a/tests/src/net/named_data/jndn/tests/integration_tests/TestKeyChain.java b/tests/src/net/named_data/jndn/tests/integration_tests/TestKeyChain.java index 2e7fc404..95c26ddf 100644 --- a/tests/src/net/named_data/jndn/tests/integration_tests/TestKeyChain.java +++ b/tests/src/net/named_data/jndn/tests/integration_tests/TestKeyChain.java @@ -32,7 +32,9 @@ import net.named_data.jndn.security.tpm.Tpm; import net.named_data.jndn.security.tpm.TpmBackEnd; import net.named_data.jndn.security.v2.CertificateV2; +import net.named_data.jndn.security.SafeBag; import net.named_data.jndn.util.Common; +import net.named_data.jndn.util.Blob; import org.junit.Before; import org.junit.Test; import static org.junit.Assert.assertTrue; @@ -216,7 +218,46 @@ public class TestKeyChain { assertTrue(!fixture_.keyChain_.getPib().getIdentities_().getIdentities_().containsKey (identityName)); - } + + // Test import key + Blob testKey = new Blob(new byte[] + {-128, -3, 2, 23, 6, -3, 1, 43, 7, 43, 8, 3, 110, 100, 110, 8, 4, 116, 101, 115, 116, 8, 3, 75, 69, 89, 8, + 8, -109, 32, -119, 15, 65, 56, 25, 127, 8, 4, 115, 101, 108, 102, 8, 9, -3, 0, 0, 1, 110, -40, -122, -80, + 60, 20, 9, 24, 1, 2, 25, 4, 0, 54, -18, -128, 21, 91, 48, 89, 48, 19, 6, 7, 42, -122, 72, -50, 61, 2, 1, 6, + 8, 42, -122, 72, -50, 61, 3, 1, 7, 3, 66, 0, 4, 44, 23, -66, 73, -9, 119, -44, 66, -10, -82, -91, 73, -96, + -15, 104, -6, 53, 119, 41, -34, -73, -71, 76, 40, 34, -67, -66, -37, 113, 32, -110, 87, 10, 46, -81, 101, + -54, 38, 98, 81, -46, 110, 4, -69, 78, -123, -109, -15, -103, -120, -64, 124, 49, 72, -47, 77, -122, -62, + 64, 82, 17, 2, -26, -39, 22, 75, 27, 1, 3, 28, 28, 7, 26, 8, 3, 110, 100, 110, 8, 4, 116, 101, 115, 116, 8, + 3, 75, 69, 89, 8, 8, -109, 32, -119, 15, 65, 56, 25, 127, -3, 0, -3, 38, -3, 0, -2, 15, 49, 57, 55, 48, 48, + 49, 48, 49, 84, 48, 48, 48, 48, 48, 48, -3, 0, -1, 15, 50, 48, 51, 57, 49, 50, 48, 49, 84, 48, 48, 48, 53, + 51, 53, 23, 71, 48, 69, 2, 33, 0, -61, -49, -65, 125, 21, 121, -34, -98, 60, -25, -5, 53, -32, -120, -65, + 26, 8, -37, -63, -125, 117, -48, -86, 121, -65, -100, 61, 21, -94, -118, 120, 40, 2, 32, 73, 114, -77, 120, + 113, -5, -52, -126, -26, -88, -81, 115, -11, 15, 78, 21, -85, -113, -38, -114, 127, -107, 13, -81, 13, 23, + -67, -5, 118, 119, 19, -112, -127, -26, 48, -127, -29, 48, 78, 6, 9, 42, -122, 72, -122, -9, 13, 1, 5, 13, + 48, 65, 48, 41, 6, 9, 42, -122, 72, -122, -9, 13, 1, 5, 12, 48, 28, 4, 8, 62, -1, 17, 54, -107, 101, 80, + 109, 2, 2, 8, 0, 48, 12, 6, 8, 42, -122, 72, -122, -9, 13, 2, 9, 5, 0, 48, 20, 6, 8, 42, -122, 72, -122, -9, + 13, 3, 7, 4, 8, 17, -36, -101, 19, 19, 38, 9, 7, 4, -127, -112, -45, -43, -18, -67, -55, 85, 91, 124, 27, + -123, -48, 127, 107, -49, -98, 51, 36, -123, 124, -78, -17, -118, 100, 126, -61, -64, 8, -73, 69, 40, 39, + -96, -14, -18, -23, -37, -16, -88, -80, 90, 6, -48, 108, 109, 101, 49, 122, 74, -120, -112, -27, -18, -6, + -111, -101, 117, -116, -100, 83, -45, 7, 19, 79, -87, 107, -47, 13, -51, -113, 40, -117, -17, 113, -59, 100, + 107, -66, -40, -71, 55, 39, 79, 97, -100, 82, -94, -110, -104, -91, -110, 21, 1, -1, 102, 95, -22, -111, + 112, -25, -59, 97, 60, -80, 107, -6, -70, 18, -17, -83, -53, -122, 42, -58, 82, 96, -30, -76, -18, -34, -5, + -71, -70, 66, 114, 34, -7, 24, -31, -2, -49, -23, 20, 75, 94, -98, 25, -69, 46, 85, -76, 127, 125, -88, 117} + ); + Blob password = new Blob(new byte[] + {112, 97, 115, 115, 119, 111, 114, 100} + ); + Name testName = new Name("/ndn/test/"); + + try { + SafeBag safebag = new SafeBag(testKey); + fixture_.keyChain_.importSafeBag(safebag, password.buf()); + } catch (Throwable ex) { + fail("Unexpected exception: " + ex.getMessage()); + } + assertTrue(fixture_.keyChain_.getPib().getIdentities_().getIdentities_().containsKey + (testName)); + } @Test public void diff --git a/tests/src/net/named_data/jndn/tests/unit_tests/TestTpmPrivateKey.java b/tests/src/net/named_data/jndn/tests/unit_tests/TestTpmPrivateKey.java index 8bc97e89..f42d01f5 100644 --- a/tests/src/net/named_data/jndn/tests/unit_tests/TestTpmPrivateKey.java +++ b/tests/src/net/named_data/jndn/tests/unit_tests/TestTpmPrivateKey.java @@ -196,7 +196,7 @@ public EcKeyTestData() // debug KeyTestData[] keyTestData = new KeyTestData[2]; KeyTestData[] keyTestData = new KeyTestData[1]; - + @Before public void setUp() throws EncodingException, CertificateV2.Error @@ -263,12 +263,12 @@ public EcKeyTestData() // Save the key in encrypted PKCS #8 format and resave as unencrypted. Blob savedEncryptedPkcs8Key = null; try { - savedEncryptedPkcs8Key = encryptedKey8.toEncryptedPkcs8(password); + savedEncryptedPkcs8Key = encryptedKey8.toEncryptedPkcs8(new Blob("password").buf()); } catch (Throwable ex) { fail("Unexpected exception: " + ex.getMessage()); } key8 = new TpmPrivateKey(); - key8.loadEncryptedPkcs8(savedEncryptedPkcs8Key.buf(), password); + key8.loadEncryptedPkcs8(savedEncryptedPkcs8Key.buf(), new Blob("password").buf()); Blob resavedPkcs8Key = key8.toPkcs8(); assertTrue(resavedPkcs8Key.equals(new Blob(pkcs8))); } @@ -325,7 +325,7 @@ public EcKeyTestData() @Test public void - testGenerateKey() + testGenerateKey() throws TpmPrivateKey.Error, UnrecognizedKeyFormatException, NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException, SignatureException