diff --git a/README.md b/README.md index 77ac69b..6f43478 100644 --- a/README.md +++ b/README.md @@ -170,6 +170,7 @@ JweConfig config = JweConfigBuilder.aJweEncryptionConfig() .withEncryptionPath("$.path.to.foo", "$.path.to.encryptedFoo") .withDecryptionPath("$.path.to.encryptedFoo.encryptedValue", "$.path.to.foo") .withEncryptedValueFieldName("encryptedValue") + .withIVSize(16) // available values are 12 or 16. If not specified, default value is 16. .build(); ``` diff --git a/pom.xml b/pom.xml index 378b13f..4187cfc 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ com.mastercard.developer client-encryption - 1.7.13-SNAPSHOT + 1.8.0 jar Library for Mastercard API compliant payload encryption/decryption https://github.com/Mastercard/client-encryption-java @@ -96,7 +96,6 @@ 4.13.1 test - org.junit.jupiter junit-jupiter-params diff --git a/src/main/java/com/mastercard/developer/encryption/EncryptionConfig.java b/src/main/java/com/mastercard/developer/encryption/EncryptionConfig.java index b2b78d4..7f6a1bf 100644 --- a/src/main/java/com/mastercard/developer/encryption/EncryptionConfig.java +++ b/src/main/java/com/mastercard/developer/encryption/EncryptionConfig.java @@ -46,6 +46,13 @@ public enum Scheme { */ PrivateKey decryptionKey; + + /** + * IV size in bytes + */ + + Integer ivSize = 16; + /** * A list of JSON paths to encrypt in request payloads. * Example: @@ -107,4 +114,6 @@ Map getDecryptionPaths() { String getEncryptedValueFieldName() { return encryptedValueFieldName; } + + public Integer getIVSize() { return ivSize; } } diff --git a/src/main/java/com/mastercard/developer/encryption/EncryptionConfigBuilder.java b/src/main/java/com/mastercard/developer/encryption/EncryptionConfigBuilder.java index f0c02ed..8350483 100644 --- a/src/main/java/com/mastercard/developer/encryption/EncryptionConfigBuilder.java +++ b/src/main/java/com/mastercard/developer/encryption/EncryptionConfigBuilder.java @@ -23,6 +23,8 @@ abstract class EncryptionConfigBuilder { protected Map decryptionPaths = new HashMap<>(); protected String encryptedValueFieldName; + protected Integer ivSize = 16; + void computeEncryptionKeyFingerprintWhenNeeded() throws EncryptionException { try { if ((encryptionCertificate == null && encryptionKey == null) || !isNullOrEmpty(encryptionKeyFingerprint)) { diff --git a/src/main/java/com/mastercard/developer/encryption/FieldLevelEncryptionConfigBuilder.java b/src/main/java/com/mastercard/developer/encryption/FieldLevelEncryptionConfigBuilder.java index 0f77351..c135c37 100644 --- a/src/main/java/com/mastercard/developer/encryption/FieldLevelEncryptionConfigBuilder.java +++ b/src/main/java/com/mastercard/developer/encryption/FieldLevelEncryptionConfigBuilder.java @@ -186,6 +186,16 @@ public FieldLevelEncryptionConfigBuilder withEncryptionKeyFingerprintHeaderName( return this; } + /** + * See: {@link EncryptionConfig#ivSize}. + */ + public FieldLevelEncryptionConfigBuilder withEncryptionIVSize(Integer ivSize) { + if (ivSize == 12 || ivSize == 16) { + this.ivSize = ivSize; + return this; + } + throw new IllegalArgumentException("Supported IV Sizes are either 12 or 16!"); + } /** * Build a {@link com.mastercard.developer.encryption.FieldLevelEncryptionConfig}. * @throws EncryptionException @@ -209,6 +219,7 @@ public FieldLevelEncryptionConfig build() throws EncryptionException { config.encryptionCertificate = this.encryptionCertificate; config.oaepPaddingDigestAlgorithm = this.oaepPaddingDigestAlgorithm; config.ivFieldName = this.ivFieldName; + config.ivSize = this.ivSize; config.oaepPaddingDigestAlgorithmFieldName = this.oaepPaddingDigestAlgorithmFieldName; config.decryptionPaths = this.decryptionPaths; config.encryptedKeyFieldName = this.encryptedKeyFieldName; diff --git a/src/main/java/com/mastercard/developer/encryption/FieldLevelEncryptionParams.java b/src/main/java/com/mastercard/developer/encryption/FieldLevelEncryptionParams.java index 1c09b83..8d30410 100644 --- a/src/main/java/com/mastercard/developer/encryption/FieldLevelEncryptionParams.java +++ b/src/main/java/com/mastercard/developer/encryption/FieldLevelEncryptionParams.java @@ -43,7 +43,7 @@ public FieldLevelEncryptionParams(String ivValue, String encryptedKeyValue, Stri public static FieldLevelEncryptionParams generate(FieldLevelEncryptionConfig config) throws EncryptionException { // Generate a random IV - IvParameterSpec ivParameterSpec = AESEncryption.generateIv(); + IvParameterSpec ivParameterSpec = AESEncryption.generateIv(config.getIVSize()); String ivSpecValue = encodeBytes(ivParameterSpec.getIV(), config.fieldValueEncoding); // Generate an AES secret key diff --git a/src/main/java/com/mastercard/developer/encryption/JweConfigBuilder.java b/src/main/java/com/mastercard/developer/encryption/JweConfigBuilder.java index 5616632..59659cb 100644 --- a/src/main/java/com/mastercard/developer/encryption/JweConfigBuilder.java +++ b/src/main/java/com/mastercard/developer/encryption/JweConfigBuilder.java @@ -33,6 +33,7 @@ public JweConfig build() throws EncryptionException { config.decryptionPaths = this.decryptionPaths.isEmpty() ? Collections.singletonMap("$.encryptedData", "$") : this.decryptionPaths; config.encryptedValueFieldName = this.encryptedValueFieldName == null ? "encryptedData" : this.encryptedValueFieldName; config.scheme = EncryptionConfig.Scheme.JWE; + config.ivSize = ivSize; return config; } @@ -82,9 +83,8 @@ public JweConfigBuilder withDecryptionPath(String jsonPathIn, String jsonPathOut return this; } - /** - * See: {@link EncryptionConfig#encryptedValueFieldName}. - */ + + public JweConfigBuilder withEncryptedValueFieldName(String encryptedValueFieldName) { this.encryptedValueFieldName = encryptedValueFieldName; return this; @@ -95,9 +95,21 @@ public JweConfigBuilder withEncryptionKeyFingerprint(String encryptionKeyFingerp return this; } + /** + * See: {@link EncryptionConfig#ivSize}. + */ + public JweConfigBuilder withEncryptionIVSize(Integer ivSize) { + if (ivSize == 12 || ivSize == 16) { + this.ivSize = ivSize; + return this; + } + throw new IllegalArgumentException("Supported IV Sizes are either 12 or 16!"); + } + private void checkParameterValues() { if (decryptionKey == null && encryptionCertificate == null && encryptionKey == null) { throw new IllegalArgumentException("You must include at least an encryption key/certificate or a decryption key"); } } + } diff --git a/src/main/java/com/mastercard/developer/encryption/aes/AESEncryption.java b/src/main/java/com/mastercard/developer/encryption/aes/AESEncryption.java index 9a39297..c68348d 100644 --- a/src/main/java/com/mastercard/developer/encryption/aes/AESEncryption.java +++ b/src/main/java/com/mastercard/developer/encryption/aes/AESEncryption.java @@ -14,10 +14,10 @@ private AESEncryption() { // Nothing to do here } - public static IvParameterSpec generateIv() throws EncryptionException { + public static IvParameterSpec generateIv(Integer ivSize) throws EncryptionException { try { SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG"); - byte[] ivBytes = new byte[16]; + byte[] ivBytes = new byte[ivSize]; secureRandom.nextBytes(ivBytes); return new IvParameterSpec(ivBytes); } catch (GeneralSecurityException e) { diff --git a/src/main/java/com/mastercard/developer/encryption/jwe/JweObject.java b/src/main/java/com/mastercard/developer/encryption/jwe/JweObject.java index cd2d205..7e38a36 100644 --- a/src/main/java/com/mastercard/developer/encryption/jwe/JweObject.java +++ b/src/main/java/com/mastercard/developer/encryption/jwe/JweObject.java @@ -62,7 +62,7 @@ public static String encrypt(JweConfig config, String payload, JweHeader header) byte[] encryptedSecretKeyBytes = RSA.wrapSecretKey(config.getEncryptionKey(), cek, "SHA-256"); String encryptedKey = EncodingUtils.base64UrlEncode(encryptedSecretKeyBytes); - byte[] iv = AESEncryption.generateIv().getIV(); + byte[] iv = AESEncryption.generateIv(config.getIVSize()).getIV(); byte[] payloadBytes = payload.getBytes(); GCMParameterSpec gcmSpec = new GCMParameterSpec(128, iv); diff --git a/src/test/java/com/mastercard/developer/encryption/FieldLevelEncryptionConfigBuilderTest.java b/src/test/java/com/mastercard/developer/encryption/FieldLevelEncryptionConfigBuilderTest.java index 38aa471..13cb574 100644 --- a/src/test/java/com/mastercard/developer/encryption/FieldLevelEncryptionConfigBuilderTest.java +++ b/src/test/java/com/mastercard/developer/encryption/FieldLevelEncryptionConfigBuilderTest.java @@ -7,6 +7,9 @@ import org.junit.rules.ExpectedException; import static com.mastercard.developer.encryption.FieldLevelEncryptionConfig.FieldValueEncoding.HEX; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.core.IsEqual.equalTo; +import static org.junit.Assert.assertFalse; public class FieldLevelEncryptionConfigBuilderTest { @@ -14,7 +17,7 @@ public class FieldLevelEncryptionConfigBuilderTest { public ExpectedException expectedException = ExpectedException.none(); @Test - public void testBuild_Nominal() throws Exception { + public void testBuild_Nominal_iv12() throws Exception { FieldLevelEncryptionConfig config = FieldLevelEncryptionConfigBuilder.aFieldLevelEncryptionConfig() .withEncryptionPath("$.payload", "$.encryptedPayload") .withEncryptionCertificate(TestUtils.getTestEncryptionCertificate()) @@ -35,6 +38,7 @@ public void testBuild_Nominal() throws Exception { .withIvFieldName("iv") .withIvHeaderName("x-iv") .withFieldValueEncoding(HEX) + .withEncryptionIVSize(12) .build(); Assert.assertNotNull(config); Assert.assertEquals(1, config.encryptionPaths.size()); @@ -56,6 +60,85 @@ public void testBuild_Nominal() throws Exception { Assert.assertEquals("oaepPaddingDigestAlgorithm", config.oaepPaddingDigestAlgorithmFieldName); Assert.assertEquals("x-oaep-padding-digest-algorithm", config.oaepPaddingDigestAlgorithmHeaderName); Assert.assertEquals(HEX, config.fieldValueEncoding); + assertThat(config.getIVSize().intValue(),equalTo(12)); + } + + @Test + public void testBuild_Nominal_iv16() throws Exception { + FieldLevelEncryptionConfig config = FieldLevelEncryptionConfigBuilder.aFieldLevelEncryptionConfig() + .withEncryptionPath("$.payload", "$.encryptedPayload") + .withEncryptionCertificate(TestUtils.getTestEncryptionCertificate()) + .withEncryptionCertificateFingerprint("97A2FFE9F0D48960EF31E87FCD7A55BF7843FB4A9EEEF01BDB6032AD6FEF146B") + .withEncryptionKeyFingerprint("F806B26BC4870E26986C70B6590AF87BAF4C2B56BB50622C51B12212DAFF2810") + .withEncryptionCertificateFingerprintFieldName("publicCertificateFingerprint") + .withEncryptionCertificateFingerprintHeaderName("x-public-certificate-fingerprint") + .withEncryptionKeyFingerprintFieldName("publicKeyFingerprint") + .withEncryptionKeyFingerprintHeaderName("x-public-key-fingerprint") + .withDecryptionPath("$.encryptedPayload", "$.payload") + .withDecryptionKey(TestUtils.getTestDecryptionKey()) + .withOaepPaddingDigestAlgorithm("SHA-512") + .withOaepPaddingDigestAlgorithmFieldName("oaepPaddingDigestAlgorithm") + .withOaepPaddingDigestAlgorithmHeaderName("x-oaep-padding-digest-algorithm") + .withEncryptedValueFieldName("encryptedValue") + .withEncryptedKeyFieldName("encryptedKey") + .withEncryptedKeyHeaderName("x-encrypted-key") + .withIvFieldName("iv") + .withIvHeaderName("x-iv") + .withFieldValueEncoding(HEX) + .withEncryptionIVSize(16) + .build(); + Assert.assertNotNull(config); + Assert.assertEquals(1, config.encryptionPaths.size()); + Assert.assertNotNull(config.encryptionCertificate); + Assert.assertEquals("97A2FFE9F0D48960EF31E87FCD7A55BF7843FB4A9EEEF01BDB6032AD6FEF146B", config.encryptionCertificateFingerprint); + Assert.assertEquals("F806B26BC4870E26986C70B6590AF87BAF4C2B56BB50622C51B12212DAFF2810", config.encryptionKeyFingerprint); + Assert.assertEquals("publicCertificateFingerprint", config.encryptionCertificateFingerprintFieldName); + Assert.assertEquals("x-public-certificate-fingerprint", config.encryptionCertificateFingerprintHeaderName); + Assert.assertEquals("publicKeyFingerprint", config.encryptionKeyFingerprintFieldName); + Assert.assertEquals("x-public-key-fingerprint", config.encryptionKeyFingerprintHeaderName); + Assert.assertEquals(1, config.decryptionPaths.size()); + Assert.assertNotNull(config.decryptionKey); + Assert.assertEquals("SHA-512", config.oaepPaddingDigestAlgorithm); + Assert.assertEquals("encryptedValue", config.encryptedValueFieldName); + Assert.assertEquals("encryptedKey", config.encryptedKeyFieldName); + Assert.assertEquals("x-encrypted-key", config.encryptedKeyHeaderName); + Assert.assertEquals("iv", config.ivFieldName); + Assert.assertEquals("x-iv", config.ivHeaderName); + Assert.assertEquals("oaepPaddingDigestAlgorithm", config.oaepPaddingDigestAlgorithmFieldName); + Assert.assertEquals("x-oaep-padding-digest-algorithm", config.oaepPaddingDigestAlgorithmHeaderName); + Assert.assertEquals(HEX, config.fieldValueEncoding); + assertThat(config.getIVSize().intValue(),equalTo(16)); + } + + @Test + public void testBuild_FailedIV() throws Exception { + try { + FieldLevelEncryptionConfig config = FieldLevelEncryptionConfigBuilder.aFieldLevelEncryptionConfig() + .withEncryptionPath("$.payload", "$.encryptedPayload") + .withEncryptionCertificate(TestUtils.getTestEncryptionCertificate()) + .withEncryptionCertificateFingerprint("97A2FFE9F0D48960EF31E87FCD7A55BF7843FB4A9EEEF01BDB6032AD6FEF146B") + .withEncryptionKeyFingerprint("F806B26BC4870E26986C70B6590AF87BAF4C2B56BB50622C51B12212DAFF2810") + .withEncryptionCertificateFingerprintFieldName("publicCertificateFingerprint") + .withEncryptionCertificateFingerprintHeaderName("x-public-certificate-fingerprint") + .withEncryptionKeyFingerprintFieldName("publicKeyFingerprint") + .withEncryptionKeyFingerprintHeaderName("x-public-key-fingerprint") + .withDecryptionPath("$.encryptedPayload", "$.payload") + .withDecryptionKey(TestUtils.getTestDecryptionKey()) + .withOaepPaddingDigestAlgorithm("SHA-512") + .withOaepPaddingDigestAlgorithmFieldName("oaepPaddingDigestAlgorithm") + .withOaepPaddingDigestAlgorithmHeaderName("x-oaep-padding-digest-algorithm") + .withEncryptedValueFieldName("encryptedValue") + .withEncryptedKeyFieldName("encryptedKey") + .withEncryptedKeyHeaderName("x-encrypted-key") + .withIvFieldName("iv") + .withIvHeaderName("x-iv") + .withFieldValueEncoding(HEX) + .withEncryptionIVSize(23) + .build(); + assertFalse("It should raise an exception, but it didn't", true); + } catch ( IllegalArgumentException e) { + assertThat(e.getMessage(), equalTo("Supported IV Sizes are either 12 or 16!")); + } } @Test diff --git a/src/test/java/com/mastercard/developer/encryption/JweConfigBuilderTest.java b/src/test/java/com/mastercard/developer/encryption/JweConfigBuilderTest.java index 511f377..76d7a4b 100644 --- a/src/test/java/com/mastercard/developer/encryption/JweConfigBuilderTest.java +++ b/src/test/java/com/mastercard/developer/encryption/JweConfigBuilderTest.java @@ -8,19 +8,44 @@ import java.util.Collections; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.core.IsEqual.equalTo; +import static org.junit.Assert.assertFalse; + public class JweConfigBuilderTest { @Rule public ExpectedException expectedException = ExpectedException.none(); @Test - public void testBuild_Nominal() throws Exception { + public void testBuild_Nominal_iv12() throws Exception { + JweConfig config = JweConfigBuilder.aJweEncryptionConfig() + .withEncryptionCertificate(TestUtils.getTestEncryptionCertificate()) + .withDecryptionKey(TestUtils.getTestDecryptionKey()) + .withEncryptionPath("$", "$") + .withDecryptionPath("$.encryptedPayload", "$") + .withEncryptedValueFieldName("encryptedPayload") + .withEncryptionIVSize(12) + .build(); + Assert.assertNotNull(config); + Assert.assertEquals(EncryptionConfig.Scheme.JWE, config.getScheme()); + Assert.assertEquals(TestUtils.getTestDecryptionKey(), config.getDecryptionKey()); + Assert.assertEquals(TestUtils.getTestEncryptionCertificate(), config.getEncryptionCertificate()); + Assert.assertEquals("encryptedPayload", config.getEncryptedValueFieldName()); + Assert.assertEquals(Collections.singletonMap("$.encryptedPayload", "$"), config.getDecryptionPaths()); + Assert.assertEquals(Collections.singletonMap("$", "$"), config.getEncryptionPaths()); + assertThat(config.getIVSize().intValue(),equalTo(12)); + } + + @Test + public void testBuild_Nominal_iv16() throws Exception { JweConfig config = JweConfigBuilder.aJweEncryptionConfig() .withEncryptionCertificate(TestUtils.getTestEncryptionCertificate()) .withDecryptionKey(TestUtils.getTestDecryptionKey()) .withEncryptionPath("$", "$") .withDecryptionPath("$.encryptedPayload", "$") .withEncryptedValueFieldName("encryptedPayload") + .withEncryptionIVSize(16) .build(); Assert.assertNotNull(config); Assert.assertEquals(EncryptionConfig.Scheme.JWE, config.getScheme()); @@ -29,8 +54,25 @@ public void testBuild_Nominal() throws Exception { Assert.assertEquals("encryptedPayload", config.getEncryptedValueFieldName()); Assert.assertEquals(Collections.singletonMap("$.encryptedPayload", "$"), config.getDecryptionPaths()); Assert.assertEquals(Collections.singletonMap("$", "$"), config.getEncryptionPaths()); + assertThat(config.getIVSize().intValue(),equalTo(16)); } + @Test + public void testBuild_FailedIV() throws Exception { + try { + JweConfig config = JweConfigBuilder.aJweEncryptionConfig() + .withEncryptionCertificate(TestUtils.getTestEncryptionCertificate()) + .withDecryptionKey(TestUtils.getTestDecryptionKey()) + .withEncryptionPath("$", "$") + .withDecryptionPath("$.encryptedPayload", "$") + .withEncryptedValueFieldName("encryptedPayload") + .withEncryptionIVSize(24) + .build(); + assertFalse("It should raise an exception, but it didn't", true); + } catch ( IllegalArgumentException e) { + assertThat(e.getMessage(), equalTo("Supported IV Sizes are either 12 or 16!")); + } + } @Test public void testBuild_EncryptionKeyNoDecryptionKey() throws Exception { JweConfig config = JweConfigBuilder.aJweEncryptionConfig() diff --git a/src/test/java/com/mastercard/developer/encryption/JweEncryptionWithDefaultJsonEngineTest.java b/src/test/java/com/mastercard/developer/encryption/JweEncryptionWithDefaultJsonEngineTest.java index 0d3c053..9d9eefd 100644 --- a/src/test/java/com/mastercard/developer/encryption/JweEncryptionWithDefaultJsonEngineTest.java +++ b/src/test/java/com/mastercard/developer/encryption/JweEncryptionWithDefaultJsonEngineTest.java @@ -85,6 +85,7 @@ public void testDecryptPayload_ShouldDecryptRootArrays() throws Exception { " \"encryptedData\": \"eyJraWQiOiI3NjFiMDAzYzFlYWRlM2E1NDkwZTUwMDBkMzc4ODdiYWE1ZTZlYzBlMjI2YzA3NzA2ZTU5OTQ1MWZjMDMyYTc5IiwiY3R5IjoiYXBwbGljYXRpb24vanNvbiIsImVuYyI6IkEyNTZHQ00iLCJhbGciOiJSU0EtT0FFUC0yNTYifQ.IcTIce59pgtjODJn4PhR7oK3F-gxcd7dishTrT7T9y5VC0U5ZS_JdMoRe59_UTkJMY8Nykb2rv3Oh_jSDYRmGB_CWMIciXYMLHQptLTF5xI1ZauDPnooDMWoOCBD_d3I0wTJNcM7I658rK0ZWSByVK9YqhEo8UaIf4e6egRHQdZ2_IGKgICwmglv_uXQrYewOWFTKR1uMpya1N50MDnWax2NtnW3SljP3mARUBLBnRmOyubQCg-Mgn8fsOWWXm-KL9RrQq9AF_HJceoJl1rRgzPW7g6SLK6EjiGW_ArTmrLaOHg9bYOY_LrbyokK_M1pMo9qup70DHvjHkMZqIL3aQ.vtma3jBIo2STkquxTUX9PQ.9ZoQG0sFvQ.ms4bW3OFd03neRlex-zZ8w\"" + "}"; JweConfig config = getTestJweConfigBuilder() + .withEncryptedValueFieldName("encryptedResponse") .withDecryptionPath("$.encryptedData", "$") .build(); diff --git a/src/test/resources/log4j.properties b/src/test/resources/log4j.properties new file mode 100644 index 0000000..393e087 --- /dev/null +++ b/src/test/resources/log4j.properties @@ -0,0 +1,8 @@ +# Root logger option +log4j.rootLogger=INFO, stdout + +# Direct log messages to stdout +log4j.appender.stdout=org.apache.log4j.ConsoleAppender +log4j.appender.stdout.Target=System.out +log4j.appender.stdout.layout=org.apache.log4j.PatternLayout +log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n \ No newline at end of file