From af0e969bd980c710dbd284ea0996163b357ebb6c Mon Sep 17 00:00:00 2001 From: Wesley Rosenblum Date: Thu, 17 Oct 2019 14:02:29 -0700 Subject: [PATCH 1/4] Remove use of BouncyCastle for HMAC key derivation *Issue #, if available:* #41 *Description of changes:* Removes explicit use of BouncyCastle for deriving an HMAC in `CryptoAlgorithm`. By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license. # Check any applicable: - [ ] Were any files moved? Moving files changes their URL, which breaks all hyperlinks to the files. --- .../encryptionsdk/CryptoAlgorithm.java | 28 +-- .../internal/HmacKeyDerivationFunction.java | 171 +++++++++++++++ .../HmacKeyDerivationFunctionTest.java | 200 ++++++++++++++++++ 3 files changed, 385 insertions(+), 14 deletions(-) create mode 100644 src/main/java/com/amazonaws/encryptionsdk/internal/HmacKeyDerivationFunction.java create mode 100644 src/test/java/com/amazonaws/encryptionsdk/internal/HmacKeyDerivationFunctionTest.java diff --git a/src/main/java/com/amazonaws/encryptionsdk/CryptoAlgorithm.java b/src/main/java/com/amazonaws/encryptionsdk/CryptoAlgorithm.java index 6d5ea166a..5fbd4a3c6 100644 --- a/src/main/java/com/amazonaws/encryptionsdk/CryptoAlgorithm.java +++ b/src/main/java/com/amazonaws/encryptionsdk/CryptoAlgorithm.java @@ -16,7 +16,7 @@ import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.security.InvalidKeyException; -import java.security.Security; +import java.security.NoSuchAlgorithmException; import java.util.EnumSet; import java.util.HashMap; import java.util.Map; @@ -25,11 +25,7 @@ import javax.crypto.spec.SecretKeySpec; import com.amazonaws.encryptionsdk.internal.BouncyCastleConfiguration; -import org.bouncycastle.crypto.Digest; -import org.bouncycastle.crypto.digests.SHA256Digest; -import org.bouncycastle.crypto.digests.SHA384Digest; -import org.bouncycastle.crypto.generators.HKDFBytesGenerator; -import org.bouncycastle.crypto.params.HKDFParameters; +import com.amazonaws.encryptionsdk.internal.HmacKeyDerivationFunction; import com.amazonaws.encryptionsdk.internal.Constants; import com.amazonaws.encryptionsdk.model.CiphertextHeaders; @@ -267,7 +263,7 @@ public SecretKey getEncryptionKeyFromDataKey(final SecretKey dataKey, final Ciph + dataKey.getAlgorithm()); } - final Digest dgst; + final String macAlgorithm; switch (this) { case ALG_AES_128_GCM_IV12_TAG16_NO_KDF: @@ -278,11 +274,11 @@ public SecretKey getEncryptionKeyFromDataKey(final SecretKey dataKey, final Ciph case ALG_AES_192_GCM_IV12_TAG16_HKDF_SHA256: case ALG_AES_256_GCM_IV12_TAG16_HKDF_SHA256: case ALG_AES_128_GCM_IV12_TAG16_HKDF_SHA256_ECDSA_P256: - dgst = new SHA256Digest(); + macAlgorithm = "HmacSHA256"; break; case ALG_AES_192_GCM_IV12_TAG16_HKDF_SHA384_ECDSA_P384: case ALG_AES_256_GCM_IV12_TAG16_HKDF_SHA384_ECDSA_P384: - dgst = new SHA384Digest(); + macAlgorithm = "HmacSHA384"; break; default: throw new UnsupportedOperationException("Support for " + this + " not yet built."); @@ -304,10 +300,14 @@ public SecretKey getEncryptionKeyFromDataKey(final SecretKey dataKey, final Ciph + rawDataKey.length); } - final byte[] rawEncKey = new byte[getKeyLength()]; - final HKDFBytesGenerator hkdf = new HKDFBytesGenerator(dgst); - hkdf.init(new HKDFParameters(rawDataKey, null, info.array())); - hkdf.generateBytes(rawEncKey, 0, getKeyLength()); - return new SecretKeySpec(rawEncKey, getKeyAlgo()); + final HmacKeyDerivationFunction hkdf; + try { + hkdf = HmacKeyDerivationFunction.getInstance(macAlgorithm); + } catch (NoSuchAlgorithmException e) { + throw new IllegalStateException(e); + } + + hkdf.init(rawDataKey); + return new SecretKeySpec(hkdf.deriveKey(info.array(), getKeyLength()), getKeyAlgo()); } } diff --git a/src/main/java/com/amazonaws/encryptionsdk/internal/HmacKeyDerivationFunction.java b/src/main/java/com/amazonaws/encryptionsdk/internal/HmacKeyDerivationFunction.java new file mode 100644 index 000000000..dd867f3ea --- /dev/null +++ b/src/main/java/com/amazonaws/encryptionsdk/internal/HmacKeyDerivationFunction.java @@ -0,0 +1,171 @@ +/* + * Copyright 2014 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ +package com.amazonaws.encryptionsdk.internal; + +import java.security.GeneralSecurityException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.security.Provider; +import java.util.Arrays; + +import javax.crypto.Mac; +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; + +import static org.apache.commons.lang3.Validate.isTrue; + +/** + * HMAC-based Key Derivation Function. + * Adapted from Hkdf.java in aws-dynamodb-encryption-java + * + * @see RFC 5869 + */ +public final class HmacKeyDerivationFunction { + private static final byte[] EMPTY_ARRAY = new byte[0]; + private final String algorithm; + private final Provider provider; + private SecretKey prk = null; + + /** + * Returns an Hkdf object using the specified algorithm. + * + * @param algorithm the standard name of the requested MAC algorithm. See the Mac + * section in the Java Cryptography Architecture Standard Algorithm Name + * Documentation for information about standard algorithm + * names. + * @return the new Hkdf object + * @throws NoSuchAlgorithmException if no Provider supports a MacSpi implementation for the + * specified algorithm. + */ + public static HmacKeyDerivationFunction getInstance(final String algorithm) + throws NoSuchAlgorithmException { + // Constructed specifically to sanity-test arguments. + Mac mac = Mac.getInstance(algorithm); + return new HmacKeyDerivationFunction(algorithm, mac.getProvider()); + } + + /** + * Initializes this Hkdf with input keying material. A default salt of + * HashLen zeros will be used (where HashLen is the length of the return + * value of the supplied algorithm). + * + * @param ikm the Input Keying Material + */ + public void init(final byte[] ikm) { + init(ikm, null); + } + + /** + * Initializes this Hkdf with input keying material and a salt. If + * salt is null or of length 0, then a default salt of + * HashLen zeros will be used (where HashLen is the length of the return + * value of the supplied algorithm). + * + * @param salt the salt used for key extraction (optional) + * @param ikm the Input Keying Material + */ + public void init(final byte[] ikm, final byte[] salt) { + byte[] realSalt = (salt == null) ? EMPTY_ARRAY : salt.clone(); + byte[] rawKeyMaterial = EMPTY_ARRAY; + try { + Mac extractionMac = Mac.getInstance(algorithm, provider); + if (realSalt.length == 0) { + realSalt = new byte[extractionMac.getMacLength()]; + Arrays.fill(realSalt, (byte) 0); + } + extractionMac.init(new SecretKeySpec(realSalt, algorithm)); + rawKeyMaterial = extractionMac.doFinal(ikm); + this.prk = new SecretKeySpec(rawKeyMaterial, algorithm); + } catch (GeneralSecurityException e) { + // We've already checked all of the parameters so no exceptions + // should be possible here. + throw new RuntimeException("Unexpected exception", e); + } finally { + Arrays.fill(rawKeyMaterial, (byte) 0); // Zeroize temporary array + } + } + + private HmacKeyDerivationFunction(final String algorithm, final Provider provider) { + isTrue(algorithm.startsWith("Hmac"), "Invalid algorithm " + algorithm + + ". Hkdf may only be used with Hmac algorithms."); + this.algorithm = algorithm; + this.provider = provider; + } + + /** + * Returns a pseudorandom key of length bytes. + * + * @param info optional context and application specific information (can be + * a zero-length array). + * @param length the length of the output key in bytes + * @return a pseudorandom key of length bytes. + * @throws IllegalStateException if this object has not been initialized + */ + public byte[] deriveKey(final byte[] info, final int length) throws IllegalStateException { + isTrue(length >= 0, "Length must be a non-negative value."); + assertInitialized(); + final byte[] result = new byte[length]; + Mac mac = createMac(); + + isTrue(length <= 255 * mac.getMacLength(), + "Requested keys may not be longer than 255 times the underlying HMAC length."); + + byte[] t = EMPTY_ARRAY; + try { + int loc = 0; + byte i = 1; + while (loc < length) { + mac.update(t); + mac.update(info); + mac.update(i); + t = mac.doFinal(); + + for (int x = 0; x < t.length && loc < length; x++, loc++) { + result[loc] = t[x]; + } + + i++; + } + } finally { + Arrays.fill(t, (byte) 0); // Zeroize temporary array + } + return result; + } + + private Mac createMac() { + try { + Mac mac = Mac.getInstance(algorithm, provider); + mac.init(prk); + return mac; + } catch (NoSuchAlgorithmException | InvalidKeyException ex) { + // We've already validated that this algorithm/key is correct. + throw new RuntimeException(ex); + } + } + + /** + * Throws an IllegalStateException if this object has not been + * initialized. + * + * @throws IllegalStateException if this object has not been initialized + */ + private void assertInitialized() throws IllegalStateException { + if (prk == null) { + throw new IllegalStateException("Hkdf has not been initialized"); + } + } +} \ No newline at end of file diff --git a/src/test/java/com/amazonaws/encryptionsdk/internal/HmacKeyDerivationFunctionTest.java b/src/test/java/com/amazonaws/encryptionsdk/internal/HmacKeyDerivationFunctionTest.java new file mode 100644 index 000000000..c30f9f477 --- /dev/null +++ b/src/test/java/com/amazonaws/encryptionsdk/internal/HmacKeyDerivationFunctionTest.java @@ -0,0 +1,200 @@ +/* + * Copyright 2015 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except + * in compliance with the License. A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package com.amazonaws.encryptionsdk.internal; + +import com.amazonaws.util.StringUtils; +import org.junit.Test; + +import static org.junit.Assert.assertArrayEquals; + +public class HmacKeyDerivationFunctionTest { + private static final testCase[] testCases = new testCase[]{ + new testCase( + "HmacSHA256", + fromCHex("\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b" + + "\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b"), + fromCHex("\\x00\\x01\\x02\\x03\\x04\\x05\\x06\\x07\\x08\\x09\\x0a\\x0b\\x0c"), + fromCHex("\\xf0\\xf1\\xf2\\xf3\\xf4\\xf5\\xf6\\xf7\\xf8\\xf9"), + fromHex("3CB25F25FAACD57A90434F64D0362F2A2D2D0A90CF1A5A4C5DB02D56ECC4C5BF34007208D5B887185865")), + new testCase( + "HmacSHA256", + fromCHex("\\x00\\x01\\x02\\x03\\x04\\x05\\x06\\x07\\x08\\x09\\x0a\\x0b\\x0c\\x0d" + + "\\x0e\\x0f\\x10\\x11\\x12\\x13\\x14\\x15\\x16\\x17\\x18\\x19\\x1a\\x1b" + + "\\x1c\\x1d\\x1e\\x1f\\x20\\x21\\x22\\x23\\x24\\x25\\x26\\x27\\x28\\x29" + + "\\x2a\\x2b\\x2c\\x2d\\x2e\\x2f\\x30\\x31\\x32\\x33\\x34\\x35\\x36\\x37" + + "\\x38\\x39\\x3a\\x3b\\x3c\\x3d\\x3e\\x3f\\x40\\x41\\x42\\x43\\x44\\x45" + + "\\x46\\x47\\x48\\x49\\x4a\\x4b\\x4c\\x4d\\x4e\\x4f"), + fromCHex("\\x60\\x61\\x62\\x63\\x64\\x65\\x66\\x67\\x68\\x69\\x6a\\x6b\\x6c\\x6d" + + "\\x6e\\x6f\\x70\\x71\\x72\\x73\\x74\\x75\\x76\\x77\\x78\\x79\\x7a\\x7b" + + "\\x7c\\x7d\\x7e\\x7f\\x80\\x81\\x82\\x83\\x84\\x85\\x86\\x87\\x88\\x89" + + "\\x8a\\x8b\\x8c\\x8d\\x8e\\x8f\\x90\\x91\\x92\\x93\\x94\\x95\\x96\\x97" + + "\\x98\\x99\\x9a\\x9b\\x9c\\x9d\\x9e\\x9f\\xa0\\xa1\\xa2\\xa3\\xa4\\xa5" + + "\\xa6\\xa7\\xa8\\xa9\\xaa\\xab\\xac\\xad\\xae\\xaf"), + fromCHex("\\xb0\\xb1\\xb2\\xb3\\xb4\\xb5\\xb6\\xb7\\xb8\\xb9\\xba\\xbb\\xbc\\xbd" + + "\\xbe\\xbf\\xc0\\xc1\\xc2\\xc3\\xc4\\xc5\\xc6\\xc7\\xc8\\xc9\\xca\\xcb" + + "\\xcc\\xcd\\xce\\xcf\\xd0\\xd1\\xd2\\xd3\\xd4\\xd5\\xd6\\xd7\\xd8\\xd9" + + "\\xda\\xdb\\xdc\\xdd\\xde\\xdf\\xe0\\xe1\\xe2\\xe3\\xe4\\xe5\\xe6\\xe7" + + "\\xe8\\xe9\\xea\\xeb\\xec\\xed\\xee\\xef\\xf0\\xf1\\xf2\\xf3\\xf4\\xf5" + + "\\xf6\\xf7\\xf8\\xf9\\xfa\\xfb\\xfc\\xfd\\xfe\\xff"), + fromHex("B11E398DC80327A1C8E7F78C596A4934" + + "4F012EDA2D4EFAD8A050CC4C19AFA97C" + + "59045A99CAC7827271CB41C65E590E09" + + "DA3275600C2F09B8367793A9ACA3DB71" + + "CC30C58179EC3E87C14C01D5C1F3434F" + "1D87")), + new testCase( + "HmacSHA256", + fromCHex("\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b" + + "\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b"), + new byte[0], new byte[0], + fromHex("8DA4E775A563C18F715F802A063C5A31" + + "B8A11F5C5EE1879EC3454E5F3C738D2D" + + "9D201395FAA4B61A96C8")), + new testCase( + "HmacSHA1", + fromCHex("\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b"), + fromCHex("\\x00\\x01\\x02\\x03\\x04\\x05\\x06\\x07\\x08\\x09\\x0a\\x0b\\x0c"), + fromCHex("\\xf0\\xf1\\xf2\\xf3\\xf4\\xf5\\xf6\\xf7\\xf8\\xf9"), + fromHex("085A01EA1B10F36933068B56EFA5AD81" + + "A4F14B822F5B091568A9CDD4F155FDA2" + + "C22E422478D305F3F896")), + new testCase( + "HmacSHA1", + fromCHex("\\x00\\x01\\x02\\x03\\x04\\x05\\x06\\x07\\x08\\x09\\x0a\\x0b\\x0c\\x0d" + + "\\x0e\\x0f\\x10\\x11\\x12\\x13\\x14\\x15\\x16\\x17\\x18\\x19\\x1a\\x1b" + + "\\x1c\\x1d\\x1e\\x1f\\x20\\x21\\x22\\x23\\x24\\x25\\x26\\x27\\x28\\x29" + + "\\x2a\\x2b\\x2c\\x2d\\x2e\\x2f\\x30\\x31\\x32\\x33\\x34\\x35\\x36\\x37" + + "\\x38\\x39\\x3a\\x3b\\x3c\\x3d\\x3e\\x3f\\x40\\x41\\x42\\x43\\x44\\x45" + + "\\x46\\x47\\x48\\x49\\x4a\\x4b\\x4c\\x4d\\x4e\\x4f"), + fromCHex("\\x60\\x61\\x62\\x63\\x64\\x65\\x66\\x67\\x68\\x69\\x6A\\x6B\\x6C\\x6D" + + "\\x6E\\x6F\\x70\\x71\\x72\\x73\\x74\\x75\\x76\\x77\\x78\\x79\\x7A\\x7B" + + "\\x7C\\x7D\\x7E\\x7F\\x80\\x81\\x82\\x83\\x84\\x85\\x86\\x87\\x88\\x89" + + "\\x8A\\x8B\\x8C\\x8D\\x8E\\x8F\\x90\\x91\\x92\\x93\\x94\\x95\\x96\\x97" + + "\\x98\\x99\\x9A\\x9B\\x9C\\x9D\\x9E\\x9F\\xA0\\xA1\\xA2\\xA3\\xA4\\xA5" + + "\\xA6\\xA7\\xA8\\xA9\\xAA\\xAB\\xAC\\xAD\\xAE\\xAF"), + fromCHex("\\xB0\\xB1\\xB2\\xB3\\xB4\\xB5\\xB6\\xB7\\xB8\\xB9\\xBA\\xBB\\xBC\\xBD" + + "\\xBE\\xBF\\xC0\\xC1\\xC2\\xC3\\xC4\\xC5\\xC6\\xC7\\xC8\\xC9\\xCA\\xCB" + + "\\xCC\\xCD\\xCE\\xCF\\xD0\\xD1\\xD2\\xD3\\xD4\\xD5\\xD6\\xD7\\xD8\\xD9" + + "\\xDA\\xDB\\xDC\\xDD\\xDE\\xDF\\xE0\\xE1\\xE2\\xE3\\xE4\\xE5\\xE6\\xE7" + + "\\xE8\\xE9\\xEA\\xEB\\xEC\\xED\\xEE\\xEF\\xF0\\xF1\\xF2\\xF3\\xF4\\xF5" + + "\\xF6\\xF7\\xF8\\xF9\\xFA\\xFB\\xFC\\xFD\\xFE\\xFF"), + fromHex("0BD770A74D1160F7C9F12CD5912A06EB" + + "FF6ADCAE899D92191FE4305673BA2FFE" + + "8FA3F1A4E5AD79F3F334B3B202B2173C" + + "486EA37CE3D397ED034C7F9DFEB15C5E" + + "927336D0441F4C4300E2CFF0D0900B52D3B4")), + new testCase( + "HmacSHA1", + fromCHex("\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b" + + "\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b"), + new byte[0], new byte[0], + fromHex("0AC1AF7002B3D761D1E55298DA9D0506" + + "B9AE52057220A306E07B6B87E8DF21D0")), + new testCase( + "HmacSHA1", + fromCHex("\\x0c\\x0c\\x0c\\x0c\\x0c\\x0c\\x0c\\x0c\\x0c\\x0c\\x0c\\x0c\\x0c\\x0c" + + "\\x0c\\x0c\\x0c\\x0c\\x0c\\x0c\\x0c\\x0c"), + null, new byte[0], + fromHex("2C91117204D745F3500D636A62F64F0A" + + "B3BAE548AA53D423B0D1F27EBBA6F5E5" + + "673A081D70CCE7ACFC48"))}; + + @Test + public void rfc5869Tests() throws Exception { + for (int x = 0; x < testCases.length; x++) { + testCase trial = testCases[x]; + System.out.println("Test case A." + (x + 1)); + HmacKeyDerivationFunction kdf = HmacKeyDerivationFunction.getInstance(trial.algo); + kdf.init(trial.ikm, trial.salt); + byte[] result = kdf.deriveKey(trial.info, trial.expected.length); + assertArrayEquals("Trial A." + x, trial.expected, result); + } + } + + @Test + public void nullTests() throws Exception { + testCase trial = testCases[0]; + HmacKeyDerivationFunction kdf = HmacKeyDerivationFunction.getInstance(trial.algo); + kdf.init(trial.ikm, trial.salt); + // Just ensuring no exceptions are thrown + kdf.deriveKey(null, 16); + } + + @Test(expected = IllegalArgumentException.class) + public void invalidLength() throws Exception { + testCase trial = testCases[0]; + HmacKeyDerivationFunction kdf = HmacKeyDerivationFunction.getInstance(trial.algo); + kdf.init(trial.ikm, trial.salt); + kdf.deriveKey(trial.info, -1); + } + + @Test + public void defaultSalt() throws Exception { + // Tests all the different ways to get the default salt + + testCase trial = testCases[0]; + HmacKeyDerivationFunction kdf1 = HmacKeyDerivationFunction.getInstance(trial.algo); + kdf1.init(trial.ikm, null); + HmacKeyDerivationFunction kdf2 = HmacKeyDerivationFunction.getInstance(trial.algo); + kdf2.init(trial.ikm, new byte[0]); + HmacKeyDerivationFunction kdf3 = HmacKeyDerivationFunction.getInstance(trial.algo); + kdf3.init(trial.ikm); + HmacKeyDerivationFunction kdf4 = HmacKeyDerivationFunction.getInstance(trial.algo); + kdf4.init(trial.ikm, new byte[32]); + + byte[] testBytes = "Test".getBytes(StringUtils.UTF8); + byte[] key1 = kdf1.deriveKey(testBytes, 16); + byte[] key2 = kdf2.deriveKey(testBytes, 16); + byte[] key3 = kdf3.deriveKey(testBytes, 16); + byte[] key4 = kdf4.deriveKey(testBytes, 16); + + assertArrayEquals(key1, key2); + assertArrayEquals(key1, key3); + assertArrayEquals(key1, key4); + } + + private static byte[] fromHex(String data) { + byte[] result = new byte[data.length() / 2]; + for (int x = 0; x < result.length; x++) { + result[x] = (byte) Integer.parseInt( + data.substring(2 * x, 2 * x + 2), 16); + } + return result; + } + + private static byte[] fromCHex(String data) { + byte[] result = new byte[data.length() / 4]; + for (int x = 0; x < result.length; x++) { + result[x] = (byte) Integer.parseInt( + data.substring(4 * x + 2, 4 * x + 4), 16); + } + return result; + } + + private static class testCase { + public final String algo; + public final byte[] ikm; + public final byte[] salt; + public final byte[] info; + public final byte[] expected; + + testCase(String algo, byte[] ikm, byte[] salt, byte[] info, + byte[] expected) { + super(); + this.algo = algo; + this.ikm = ikm; + this.salt = salt; + this.info = info; + this.expected = expected; + } + } +} \ No newline at end of file From 3fa1cf812066a632e5c75274770846a705e9eba8 Mon Sep 17 00:00:00 2001 From: Wesley Rosenblum Date: Fri, 18 Oct 2019 11:01:26 -0700 Subject: [PATCH 2/4] Fixing copyright dates --- .../encryptionsdk/internal/HmacKeyDerivationFunction.java | 4 ++-- .../encryptionsdk/internal/HmacKeyDerivationFunctionTest.java | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/amazonaws/encryptionsdk/internal/HmacKeyDerivationFunction.java b/src/main/java/com/amazonaws/encryptionsdk/internal/HmacKeyDerivationFunction.java index dd867f3ea..ede4d8c46 100644 --- a/src/main/java/com/amazonaws/encryptionsdk/internal/HmacKeyDerivationFunction.java +++ b/src/main/java/com/amazonaws/encryptionsdk/internal/HmacKeyDerivationFunction.java @@ -1,5 +1,5 @@ /* - * Copyright 2014 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. @@ -39,7 +39,7 @@ public final class HmacKeyDerivationFunction { private SecretKey prk = null; /** - * Returns an Hkdf object using the specified algorithm. + * Returns an HmacKeyDerivationFunction object using the specified algorithm. * * @param algorithm the standard name of the requested MAC algorithm. See the Mac * section in the Date: Fri, 18 Oct 2019 11:31:10 -0700 Subject: [PATCH 3/4] *Issue #, if available:* #41 *Description of changes:* Remaining fixes to allow BouncyCastle to be swapped out with other implementations. By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license. # Check any applicable: - [ ] Were any files moved? Moving files changes their URL, which breaks all hyperlinks to the files. --- .../encryptionsdk/CryptoAlgorithm.java | 18 +--- .../internal/BouncyCastleConfiguration.java | 37 -------- .../internal/TrailingSignatureAlgorithm.java | 8 +- .../encryptionsdk/internal/Utils.java | 2 +- .../internal/CipherHandlerTest.java | 4 +- .../jce/KeyStoreProviderTest.java | 84 +++++++++---------- 6 files changed, 52 insertions(+), 101 deletions(-) delete mode 100644 src/main/java/com/amazonaws/encryptionsdk/internal/BouncyCastleConfiguration.java diff --git a/src/main/java/com/amazonaws/encryptionsdk/CryptoAlgorithm.java b/src/main/java/com/amazonaws/encryptionsdk/CryptoAlgorithm.java index 5fbd4a3c6..71cafa20e 100644 --- a/src/main/java/com/amazonaws/encryptionsdk/CryptoAlgorithm.java +++ b/src/main/java/com/amazonaws/encryptionsdk/CryptoAlgorithm.java @@ -24,7 +24,6 @@ import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; -import com.amazonaws.encryptionsdk.internal.BouncyCastleConfiguration; import com.amazonaws.encryptionsdk.internal.HmacKeyDerivationFunction; import com.amazonaws.encryptionsdk.internal.Constants; @@ -99,27 +98,19 @@ public enum CryptoAlgorithm { private final int dataKeyLen_; private final boolean safeToCache_; - /** - * This block is used to ensure static blocks of BouncyCastleConfiguration are evaluated as a dependency of - * the CryptoAlgorithm class - */ - static { - BouncyCastleConfiguration.init(); - } - /* * Create a mapping between the CiphertextType object and its byte value representation. Make * this is a static method so the map is created when the object is created. This enables fast * lookups of the CryptoAlgorithm given its short value representation. */ - private static final Map ID_MAPPING = new HashMap(); + private static final Map ID_MAPPING = new HashMap<>(); static { for (final CryptoAlgorithm s : EnumSet.allOf(CryptoAlgorithm.class)) { ID_MAPPING.put(s.value_, s); } } - private CryptoAlgorithm( + CryptoAlgorithm( final int blockSizeBits, final int nonceLenBytes, final int tagLenBytes, final long maxContentLen, final String keyAlgo, final int keyLenBytes, final int value, final String dataKeyAlgo, final int dataKeyLen, boolean safeToCache @@ -130,7 +121,7 @@ private CryptoAlgorithm( } - private CryptoAlgorithm( + CryptoAlgorithm( final int blockSizeBits, final int nonceLenBytes, final int tagLenBytes, final long maxContentLen, final String keyAlgo, final int keyLenBytes, final int value, final String dataKeyAlgo, final int dataKeyLen, @@ -164,8 +155,7 @@ private CryptoAlgorithm( * @return the CryptoAlgorithm object that matches the given value, null if no match is found. */ public static CryptoAlgorithm deserialize(final short value) { - final CryptoAlgorithm result = ID_MAPPING.get(value); - return result; + return ID_MAPPING.get(value); } /** diff --git a/src/main/java/com/amazonaws/encryptionsdk/internal/BouncyCastleConfiguration.java b/src/main/java/com/amazonaws/encryptionsdk/internal/BouncyCastleConfiguration.java deleted file mode 100644 index deecac895..000000000 --- a/src/main/java/com/amazonaws/encryptionsdk/internal/BouncyCastleConfiguration.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.amazonaws.encryptionsdk.internal; - -import org.bouncycastle.jce.provider.BouncyCastleProvider; - -import java.security.Security; - -/** - * This API is internal and subject to change. It is used to add BouncyCastleProvider to the - * java.security.Provider list, and to provide a static reference to BouncyCastleProvider for internal - * classes. - */ -public class BouncyCastleConfiguration { - static final BouncyCastleProvider INTERNAL_BOUNCY_CASTLE_PROVIDER; - static { - BouncyCastleProvider bouncyCastleProvider; - try { - bouncyCastleProvider = new BouncyCastleProvider(); - Security.addProvider(bouncyCastleProvider); - } catch (final Throwable ex) { - bouncyCastleProvider = null; - // Swallow this error. We'll either succeed or fail later with reasonable - // stacktraces. - } - INTERNAL_BOUNCY_CASTLE_PROVIDER = bouncyCastleProvider; - } - - /** - * Prevent instantiation - */ - private BouncyCastleConfiguration() { - } - - /** - * No-op used to force class loading on first call, which will cause the static blocks to be executed - */ - public static void init() {} -} diff --git a/src/main/java/com/amazonaws/encryptionsdk/internal/TrailingSignatureAlgorithm.java b/src/main/java/com/amazonaws/encryptionsdk/internal/TrailingSignatureAlgorithm.java index 11a418380..db1c4a8c7 100644 --- a/src/main/java/com/amazonaws/encryptionsdk/internal/TrailingSignatureAlgorithm.java +++ b/src/main/java/com/amazonaws/encryptionsdk/internal/TrailingSignatureAlgorithm.java @@ -59,14 +59,14 @@ private static final class ECDSASignatureAlgorithm extends TrailingSignatureAlgo private static final BigInteger THREE = BigInteger.valueOf(3); private static final BigInteger FOUR = BigInteger.valueOf(4); - private ECDSASignatureAlgorithm(ECGenParameterSpec ecSpec, String messageDigestAlgorithm) { + private ECDSASignatureAlgorithm(ECGenParameterSpec ecSpec, String messageDigestAlgorithm, String hashAndSignAlgorithm) { if (!ecSpec.getName().startsWith(SEC_PRIME_FIELD_PREFIX)) { throw new IllegalStateException("Non-prime curves are not supported at this time"); } this.ecSpec = ecSpec; this.messageDigestAlgorithm = messageDigestAlgorithm; - this.hashAndSignAlgorithm = messageDigestAlgorithm + "withECDSA"; + this.hashAndSignAlgorithm = hashAndSignAlgorithm; try { final AlgorithmParameters parameters = AlgorithmParameters.getInstance(ELLIPTIC_CURVE_ALGORITHM); @@ -190,9 +190,9 @@ public KeyPair generateKey() throws GeneralSecurityException { } private static final ECDSASignatureAlgorithm SHA256_ECDSA_P256 - = new ECDSASignatureAlgorithm(new ECGenParameterSpec(SEC_PRIME_FIELD_PREFIX + "256r1"), "SHA256"); + = new ECDSASignatureAlgorithm(new ECGenParameterSpec(SEC_PRIME_FIELD_PREFIX + "256r1"), "SHA-256", "SHA256withECDSA"); private static final ECDSASignatureAlgorithm SHA384_ECDSA_P384 - = new ECDSASignatureAlgorithm(new ECGenParameterSpec(SEC_PRIME_FIELD_PREFIX + "384r1"), "SHA384"); + = new ECDSASignatureAlgorithm(new ECGenParameterSpec(SEC_PRIME_FIELD_PREFIX + "384r1"), "SHA-384", "SHA384withECDSA"); public static TrailingSignatureAlgorithm forCryptoAlgorithm(CryptoAlgorithm algorithm) { switch (algorithm) { diff --git a/src/main/java/com/amazonaws/encryptionsdk/internal/Utils.java b/src/main/java/com/amazonaws/encryptionsdk/internal/Utils.java index b64c53143..33aee3cb1 100644 --- a/src/main/java/com/amazonaws/encryptionsdk/internal/Utils.java +++ b/src/main/java/com/amazonaws/encryptionsdk/internal/Utils.java @@ -268,7 +268,7 @@ public static ByteBuffer limit(final ByteBuffer buff, final int newLimit) { * @return decoded data as a byte array */ public static byte[] decodeBase64String(final String encoded) { - return Base64.decode(encoded); + return encoded.isEmpty() ? new byte[0] : Base64.decode(encoded); } /** diff --git a/src/test/java/com/amazonaws/encryptionsdk/internal/CipherHandlerTest.java b/src/test/java/com/amazonaws/encryptionsdk/internal/CipherHandlerTest.java index bf7ca0b33..d85a0220b 100644 --- a/src/test/java/com/amazonaws/encryptionsdk/internal/CipherHandlerTest.java +++ b/src/test/java/com/amazonaws/encryptionsdk/internal/CipherHandlerTest.java @@ -47,7 +47,7 @@ public void tamperCiphertext() { final byte[] keyBytes = RandomBytesGenerator.generate(cryptoAlgorithm.getKeyLength()); final byte[] nonce = RandomBytesGenerator.generate(cryptoAlgorithm.getNonceLen()); - final SecretKey key = new SecretKeySpec(keyBytes, cryptoAlgorithm.name()); + final SecretKey key = new SecretKeySpec(keyBytes, cryptoAlgorithm.getKeyAlgo()); CipherHandler cipherHandler = createCipherHandler(key, cryptoAlgorithm, Cipher.ENCRYPT_MODE); final byte[] encryptedBytes = cipherHandler.cipherData(nonce, contentAad_, content, 0, content.length); @@ -72,7 +72,7 @@ private byte[] encryptDecrypt(final byte[] content, final CryptoAlgorithm crypto final byte[] keyBytes = RandomBytesGenerator.generate(cryptoAlgorithm.getKeyLength()); final byte[] nonce = RandomBytesGenerator.generate(cryptoAlgorithm.getNonceLen()); - final SecretKey key = new SecretKeySpec(keyBytes, cryptoAlgorithm.name()); + final SecretKey key = new SecretKeySpec(keyBytes, cryptoAlgorithm.getKeyAlgo()); CipherHandler cipherHandler = createCipherHandler(key, cryptoAlgorithm, Cipher.ENCRYPT_MODE); final byte[] encryptedBytes = cipherHandler.cipherData( nonce, contentAad_, content, 0, content.length); diff --git a/src/test/java/com/amazonaws/encryptionsdk/jce/KeyStoreProviderTest.java b/src/test/java/com/amazonaws/encryptionsdk/jce/KeyStoreProviderTest.java index ce059c8e3..21a4f40d3 100644 --- a/src/test/java/com/amazonaws/encryptionsdk/jce/KeyStoreProviderTest.java +++ b/src/test/java/com/amazonaws/encryptionsdk/jce/KeyStoreProviderTest.java @@ -27,17 +27,14 @@ import java.security.KeyStore.PasswordProtection; import java.security.KeyStoreException; import java.security.SecureRandom; -import java.security.Security; import java.security.cert.Certificate; import java.security.cert.X509Certificate; +import java.time.Instant; +import java.time.temporal.ChronoUnit; import java.util.Date; import javax.crypto.spec.SecretKeySpec; -import javax.security.auth.x500.X500Principal; -import org.bouncycastle.asn1.x509.X509Name; -import org.bouncycastle.jce.provider.BouncyCastleProvider; -import org.bouncycastle.x509.X509V3CertificateGenerator; import org.junit.Before; import org.junit.Test; @@ -46,8 +43,15 @@ import com.amazonaws.encryptionsdk.MasterKeyProvider; import com.amazonaws.encryptionsdk.exception.CannotUnwrapDataKeyException; import com.amazonaws.encryptionsdk.multi.MultipleProviderFactory; +import sun.security.x509.AlgorithmId; +import sun.security.x509.CertificateAlgorithmId; +import sun.security.x509.CertificateSerialNumber; +import sun.security.x509.CertificateValidity; +import sun.security.x509.CertificateX509Key; +import sun.security.x509.X500Name; +import sun.security.x509.X509CertImpl; +import sun.security.x509.X509CertInfo; -@SuppressWarnings("deprecation") public class KeyStoreProviderTest { private static final SecureRandom RND = new SecureRandom(); private static final KeyPairGenerator KG; @@ -72,7 +76,7 @@ public void setup() throws Exception { } @Test - public void singleKeyPkcs1() throws GeneralSecurityException { + public void singleKeyPkcs1() throws Exception { addEntry("key1"); final KeyStoreProvider mkp = new KeyStoreProvider(ks, PP, "KeyStore", "RSA/ECB/PKCS1Padding", "key1"); final JceMasterKey mk1 = mkp.getMasterKey("key1"); @@ -87,7 +91,7 @@ public void singleKeyPkcs1() throws GeneralSecurityException { } @Test - public void singleKeyOaepSha1() throws GeneralSecurityException { + public void singleKeyOaepSha1() throws Exception { addEntry("key1"); final KeyStoreProvider mkp = new KeyStoreProvider(ks, PP, "KeyStore", "RSA/ECB/OAEPWithSHA-1AndMGF1Padding", "key1"); @@ -103,7 +107,7 @@ public void singleKeyOaepSha1() throws GeneralSecurityException { } @Test - public void singleKeyOaepSha256() throws GeneralSecurityException { + public void singleKeyOaepSha256() throws Exception { addEntry("key1"); final KeyStoreProvider mkp = new KeyStoreProvider(ks, PP, "KeyStore", "RSA/ECB/OAEPWithSHA-256AndMGF1Padding", "key1"); @@ -119,7 +123,7 @@ public void singleKeyOaepSha256() throws GeneralSecurityException { } @Test - public void multipleKeys() throws GeneralSecurityException { + public void multipleKeys() throws Exception { addEntry("key1"); addEntry("key2"); final KeyStoreProvider mkp = new KeyStoreProvider(ks, PP, "KeyStore", "RSA/ECB/OAEPWithSHA-256AndMGF1Padding", @@ -146,7 +150,7 @@ public void multipleKeys() throws GeneralSecurityException { } @Test(expected = CannotUnwrapDataKeyException.class) - public void encryptOnly() throws GeneralSecurityException { + public void encryptOnly() throws Exception { addPublicEntry("key1"); final KeyStoreProvider mkp = new KeyStoreProvider(ks, PP, "KeyStore", "RSA/ECB/OAEPWithSHA-256AndMGF1Padding", "key1"); @@ -157,7 +161,7 @@ public void encryptOnly() throws GeneralSecurityException { } @Test - public void escrowAndSymmetric() throws GeneralSecurityException { + public void escrowAndSymmetric() throws Exception { addPublicEntry("key1"); addEntry("key2"); final KeyStoreProvider mkp = new KeyStoreProvider(ks, PP, "KeyStore", "RSA/ECB/OAEPWithSHA-256AndMGF1Padding", @@ -185,7 +189,7 @@ public void escrowAndSymmetric() throws GeneralSecurityException { } @Test - public void escrowAndSymmetricSecondProvider() throws GeneralSecurityException { + public void escrowAndSymmetricSecondProvider() throws GeneralSecurityException, IOException { addPublicEntry("key1"); addEntry("key2"); final KeyStoreProvider mkp = new KeyStoreProvider(ks, PP, "KeyStore", "RSA/ECB/OAEPWithSHA-256AndMGF1Padding", @@ -263,40 +267,34 @@ public void keystoreAndRawProvider() throws GeneralSecurityException, IOExceptio assertArrayEquals(PLAINTEXT, crypto.decryptData(ksp, ct.getResult()).getResult()); } - private void addEntry(final String alias) throws GeneralSecurityException { + private void addEntry(final String alias) throws GeneralSecurityException, IOException { final KeyPair pair = KG.generateKeyPair(); - // build a certificate generator - final X509V3CertificateGenerator certGen = new X509V3CertificateGenerator(); - final X500Principal dnName = new X500Principal("cn=" + alias); - - certGen.setSerialNumber(new BigInteger(256, RND)); - certGen.setSubjectDN(new X509Name("dc=" + alias)); - certGen.setIssuerDN(dnName); // use the same - certGen.setNotBefore(new Date(System.currentTimeMillis() - 24 * 60 * 60 * 1000)); - certGen.setNotAfter(new Date(System.currentTimeMillis() + 2 * 365 * 24 * 60 * 60 * 1000)); - certGen.setPublicKey(pair.getPublic()); - certGen.setSignatureAlgorithm("SHA256WithRSA"); - final X509Certificate cert = certGen.generate(pair.getPrivate(), "BC"); - - ks.setEntry(alias, new KeyStore.PrivateKeyEntry(pair.getPrivate(), new X509Certificate[] { cert }), PP); + ks.setEntry(alias, new KeyStore.PrivateKeyEntry(pair.getPrivate(), + new X509Certificate[] { generateCertificate(pair, alias) }), PP); } - private void addPublicEntry(final String alias) throws GeneralSecurityException { + private void addPublicEntry(final String alias) throws GeneralSecurityException, IOException { final KeyPair pair = KG.generateKeyPair(); - // build a certificate generator - final X509V3CertificateGenerator certGen = new X509V3CertificateGenerator(); - final X500Principal dnName = new X500Principal("cn=" + alias); - - certGen.setSerialNumber(new BigInteger(256, RND)); - certGen.setSubjectDN(new X509Name("dc=" + alias)); - certGen.setIssuerDN(dnName); // use the same - certGen.setNotBefore(new Date(System.currentTimeMillis() - 24 * 60 * 60 * 1000)); - certGen.setNotAfter(new Date(System.currentTimeMillis() + 2 * 365 * 24 * 60 * 60 * 1000)); - certGen.setPublicKey(pair.getPublic()); - certGen.setSignatureAlgorithm("SHA256WithRSA"); - final X509Certificate cert = certGen.generate(pair.getPrivate(), "BC"); - - ks.setEntry(alias, new KeyStore.TrustedCertificateEntry(cert), null); + ks.setEntry(alias, new KeyStore.TrustedCertificateEntry(generateCertificate(pair, alias)), null); + } + + private X509Certificate generateCertificate(final KeyPair pair, final String alias) throws GeneralSecurityException, IOException { + final X509CertInfo info = new X509CertInfo(); + final X500Name name = new X500Name("dc=" + alias); + info.set(X509CertInfo.SERIAL_NUMBER, new CertificateSerialNumber(new BigInteger(256, RND))); + info.set(X509CertInfo.SUBJECT, name); + info.set(X509CertInfo.ISSUER, name); + info.set(X509CertInfo.VALIDITY, + new CertificateValidity(Date.from(Instant.now().minus(1, ChronoUnit.DAYS)), + Date.from(Instant.now().plus(730, ChronoUnit.DAYS)))); + info.set(X509CertInfo.KEY, new CertificateX509Key(pair.getPublic())); + info.set(X509CertInfo.ALGORITHM_ID, + new CertificateAlgorithmId(new AlgorithmId(AlgorithmId.sha256WithRSAEncryption_oid))); + + final X509CertImpl cert = new X509CertImpl(info); + cert.sign(pair.getPrivate(), AlgorithmId.sha256WithRSAEncryption_oid.toString()); + + return cert; } private void copyPublicPart(final KeyStore src, final KeyStore dst, final String alias) throws KeyStoreException { From 4c2c0a56ffeef37fdf17ef5216fa77b366b1160c Mon Sep 17 00:00:00 2001 From: Wesley Rosenblum Date: Fri, 18 Oct 2019 16:56:33 -0700 Subject: [PATCH 4/4] Adding comment about sun classes usage and using a constant for empty byte array --- src/main/java/com/amazonaws/encryptionsdk/internal/Utils.java | 3 ++- .../com/amazonaws/encryptionsdk/jce/KeyStoreProviderTest.java | 4 ++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/amazonaws/encryptionsdk/internal/Utils.java b/src/main/java/com/amazonaws/encryptionsdk/internal/Utils.java index 33aee3cb1..adedea54a 100644 --- a/src/main/java/com/amazonaws/encryptionsdk/internal/Utils.java +++ b/src/main/java/com/amazonaws/encryptionsdk/internal/Utils.java @@ -24,6 +24,7 @@ import java.util.WeakHashMap; import java.util.concurrent.atomic.AtomicLong; +import org.apache.commons.lang3.ArrayUtils; import org.bouncycastle.util.encoders.Base64; /** @@ -268,7 +269,7 @@ public static ByteBuffer limit(final ByteBuffer buff, final int newLimit) { * @return decoded data as a byte array */ public static byte[] decodeBase64String(final String encoded) { - return encoded.isEmpty() ? new byte[0] : Base64.decode(encoded); + return encoded.isEmpty() ? ArrayUtils.EMPTY_BYTE_ARRAY : Base64.decode(encoded); } /** diff --git a/src/test/java/com/amazonaws/encryptionsdk/jce/KeyStoreProviderTest.java b/src/test/java/com/amazonaws/encryptionsdk/jce/KeyStoreProviderTest.java index 21a4f40d3..985001ca3 100644 --- a/src/test/java/com/amazonaws/encryptionsdk/jce/KeyStoreProviderTest.java +++ b/src/test/java/com/amazonaws/encryptionsdk/jce/KeyStoreProviderTest.java @@ -43,6 +43,10 @@ import com.amazonaws.encryptionsdk.MasterKeyProvider; import com.amazonaws.encryptionsdk.exception.CannotUnwrapDataKeyException; import com.amazonaws.encryptionsdk.multi.MultipleProviderFactory; + +/* These internal sun classes are included solely for test purposes as + this test cannot use BouncyCastle cert generation, as there are incompatibilities + between how standard BC and FIPS BC perform cert generation. */ import sun.security.x509.AlgorithmId; import sun.security.x509.CertificateAlgorithmId; import sun.security.x509.CertificateSerialNumber;