From 6eaf61a7dcfe3ef0f4693fba2717120fe8633c6a Mon Sep 17 00:00:00 2001 From: tiffanyachen Date: Thu, 22 Mar 2018 16:35:41 -0700 Subject: [PATCH] Updates to JsonWebKey to accomodate EC keys --- azure-keyvault-core/pom.xml | 2 +- azure-keyvault-cryptography/pom.xml | 6 +- azure-keyvault-extensions/pom.xml | 10 +- azure-keyvault-webkey/pom.xml | 216 +++++----- .../azure/keyvault/webkey/JsonWebKey.java | 373 ++++++++++++++++-- .../webkey/JsonWebKeyEncryptionAlgorithm.java | 8 +- .../webkey/JsonWebKeySignatureAlgorithm.java | 13 +- .../azure/keyvault/webkey/JsonWebKeyType.java | 4 +- .../webkey/test/EcHsmValidationTests.java | 45 +++ .../webkey/test/EcValidationTests.java | 114 ++++++ azure-keyvault/pom.xml | 2 +- pom.xml | 6 +- 12 files changed, 648 insertions(+), 151 deletions(-) create mode 100644 azure-keyvault-webkey/src/test/java/com/microsoft/azure/keyvault/webkey/test/EcHsmValidationTests.java create mode 100644 azure-keyvault-webkey/src/test/java/com/microsoft/azure/keyvault/webkey/test/EcValidationTests.java diff --git a/azure-keyvault-core/pom.xml b/azure-keyvault-core/pom.xml index 649015a..3602f35 100644 --- a/azure-keyvault-core/pom.xml +++ b/azure-keyvault-core/pom.xml @@ -8,7 +8,7 @@ com.microsoft.azure azure-keyvault-parent - 1.1-alpha-2 + 1.1-beta-1 ../pom.xml diff --git a/azure-keyvault-cryptography/pom.xml b/azure-keyvault-cryptography/pom.xml index 4cbe47c..c40de43 100644 --- a/azure-keyvault-cryptography/pom.xml +++ b/azure-keyvault-cryptography/pom.xml @@ -9,7 +9,7 @@ com.microsoft.azure azure-keyvault-parent - 1.1-alpha-2 + 1.1-beta-1 ../pom.xml @@ -57,12 +57,12 @@ com.microsoft.azure azure-keyvault-core - 1.1-alpha-2 + 1.1-beta-1 com.microsoft.azure azure-keyvault-webkey - 1.1-alpha-2 + 1.1-beta-1 diff --git a/azure-keyvault-extensions/pom.xml b/azure-keyvault-extensions/pom.xml index 78269a6..10e252d 100644 --- a/azure-keyvault-extensions/pom.xml +++ b/azure-keyvault-extensions/pom.xml @@ -8,7 +8,7 @@ com.microsoft.azure azure-keyvault-parent - 1.1-alpha-2 + 1.1-beta-1 ../pom.xml @@ -68,22 +68,22 @@ com.microsoft.azure azure-keyvault-core - 1.1-alpha-2 + 1.1-beta-1 com.microsoft.azure azure-keyvault-cryptography - 1.1-alpha-2 + 1.1-beta-1 com.microsoft.azure azure-keyvault - 1.1-alpha-2 + 1.1-beta-1 com.microsoft.azure azure-keyvault-webkey - 1.1-alpha-2 + 1.1-beta-1 com.microsoft.azure diff --git a/azure-keyvault-webkey/pom.xml b/azure-keyvault-webkey/pom.xml index b430666..3410e1d 100644 --- a/azure-keyvault-webkey/pom.xml +++ b/azure-keyvault-webkey/pom.xml @@ -1,117 +1,127 @@ - - - 4.0.0 - - com.microsoft.azure - azure-keyvault-parent - 1.1-alpha-2 - ../pom.xml - + + + 4.0.0 + + com.microsoft.azure + azure-keyvault-parent + 1.1-beta-1 + ../pom.xml + - azure-keyvault-webkey - jar + azure-keyvault-webkey + jar - Microsoft Azure SDK for Key Vault WebKey - This package contains Microsoft Azure Key Vault WebKey library. - https://github.com/Azure/azure-sdk-for-java + Microsoft Azure SDK for Key Vault WebKey + This package contains Microsoft Azure Key Vault WebKey library. + https://github.com/Azure/azure-sdk-for-java - - - The MIT License (MIT) - http://opensource.org/licenses/MIT - repo - - + + + The MIT License (MIT) + http://opensource.org/licenses/MIT + repo + + - - scm:git:https://github.com/Azure/azure-sdk-for-java - scm:git:git@github.com:Azure/azure-sdk-for-java.git - HEAD - + + scm:git:https://github.com/Azure/azure-sdk-for-java + scm:git:git@github.com:Azure/azure-sdk-for-java.git + HEAD + - - UTF-8 - - + + UTF-8 + + - - - microsoft - Microsoft - - + + + microsoft + Microsoft + + + + + com.fasterxml.jackson.core + jackson-databind + + + commons-codec + commons-codec + + + com.google.guava + guava + + + com.microsoft.rest + client-runtime + 1.3.0 + + + com.microsoft.azure + azure-client-authentication + 1.3.0 + + + com.microsoft.azure + azure-keyvault + 1.1-beta-1 + + + + junit + junit + test + + + + + + org.apache.maven.plugins + maven-jar-plugin + 3.0.2 + + + + true + true + + + + + + org.codehaus.mojo + build-helper-maven-plugin + 1.12 + - - - com.fasterxml.jackson.core - jackson-databind - - - commons-codec - commons-codec - - - com.google.guava - guava - 20.0 - - - - - junit - junit - test - - - - - - org.apache.maven.plugins - maven-jar-plugin - 3.0.2 - - - - true - true - - - - - - org.codehaus.mojo - build-helper-maven-plugin - 1.12 - + + org.apache.maven.plugins + maven-compiler-plugin + 3.1 + + 1.7 + 1.7 + + - - org.apache.maven.plugins - maven-compiler-plugin - 3.1 - - 1.7 - 1.7 - - - - - org.apache.maven.plugins - maven-javadoc-plugin - 2.8 - - com.microsoft.schemas._2003._10.serialization; - /** + + org.apache.maven.plugins + maven-javadoc-plugin + 2.8 + + com.microsoft.schemas._2003._10.serialization; + /**
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for
* license information.
*/]]>
-
-
-
-
+ + +
+
diff --git a/azure-keyvault-webkey/src/main/java/com/microsoft/azure/keyvault/webkey/JsonWebKey.java b/azure-keyvault-webkey/src/main/java/com/microsoft/azure/keyvault/webkey/JsonWebKey.java index 4e6ad1f..06b2740 100644 --- a/azure-keyvault-webkey/src/main/java/com/microsoft/azure/keyvault/webkey/JsonWebKey.java +++ b/azure-keyvault-webkey/src/main/java/com/microsoft/azure/keyvault/webkey/JsonWebKey.java @@ -9,24 +9,39 @@ import java.io.IOException; import java.math.BigInteger; import java.security.GeneralSecurityException; +import java.security.InvalidAlgorithmParameterException; import java.security.KeyFactory; import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.Provider; import java.security.PublicKey; +import java.security.interfaces.ECPrivateKey; +import java.security.interfaces.ECPublicKey; import java.security.interfaces.RSAPrivateCrtKey; import java.security.interfaces.RSAPublicKey; +import java.security.spec.ECGenParameterSpec; +import java.security.spec.ECParameterSpec; +import java.security.spec.ECPoint; +import java.security.spec.ECPrivateKeySpec; +import java.security.spec.ECPublicKeySpec; +import java.security.spec.EllipticCurve; +import java.security.spec.InvalidKeySpecException; import java.security.spec.RSAPrivateCrtKeySpec; import java.security.spec.RSAPrivateKeySpec; import java.security.spec.RSAPublicKeySpec; import java.util.Arrays; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Set; import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; +import org.bouncycastle.jce.provider.BouncyCastleProvider; + import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; @@ -36,6 +51,8 @@ import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize; import com.google.common.base.Objects; +import com.google.common.collect.ImmutableMap; +import com.microsoft.azure.keyvault.models.JsonWebKeyCurveName; /** * As of http://tools.ietf.org/html/draft-ietf-jose-json-web-key-18. @@ -46,32 +63,38 @@ public class JsonWebKey { /** * Key Identifier. */ + @JsonProperty(value = "kid") private String kid; /** - * Key type, usually RSA. Possible values include: 'EC', 'RSA', 'RSA-HSM', - * 'oct'. + * JsonWebKey key type (kty). Possible values include: 'EC', 'EC-HSM', + * 'RSA', 'RSA-HSM', 'oct'. */ + @JsonProperty(value = "kty") private JsonWebKeyType kty; /** * The keyOps property. */ + @JsonProperty(value = "key_ops") private List keyOps; /** * RSA modulus. */ + @JsonProperty(value = "n") private byte[] n; /** * RSA public exponent. */ + @JsonProperty(value = "e") private byte[] e; /** - * RSA private exponent. + * RSA private exponent, or the D component of an EC private key. */ + @JsonProperty(value = "d") private byte[] d; /** @@ -95,7 +118,7 @@ public class JsonWebKey { private byte[] p; /** - * RSA secret prime, with p < q. + * RSA secret prime, with p & q. */ private byte[] q; @@ -107,12 +130,32 @@ public class JsonWebKey { /** * HSM Token, used with Bring Your Own Key. */ + @JsonProperty(value = "key_hsm") private byte[] t; + + /** + * Elliptic curve name. For valid values, see JsonWebKeyCurveName. Possible + * values include: 'P-256', 'P-384', 'P-521', 'SECP256K1'. + */ + @JsonProperty(value = "crv") + private JsonWebKeyCurveName crv; /** - * Key Identifier. + * X component of an EC public key. + */ + @JsonProperty(value = "x") + private byte[] x; + + /** + * Y component of an EC public key. + */ + @JsonProperty(value = "y") + private byte[] y; + + /** + * Get the kid value. * - * @return the kid value. + * @return the kid value */ @JsonProperty("kid") public String kid() { @@ -122,7 +165,7 @@ public String kid() { /** * Set the key identifier value. * - * @param kid the key identifier + * @param kid the kid value to set * @return the JsonWebKey object itself. */ public JsonWebKey withKid(String kid) { @@ -131,10 +174,9 @@ public JsonWebKey withKid(String kid) { } /** - * Key type, usually RSA. Possible values include: 'EC', 'RSA', 'RSA-HSM', - * 'oct'. + * Get the kty value. * - * @return the key type. + * @return the kty value */ @JsonProperty("kty") public JsonWebKeyType kty() { @@ -153,9 +195,9 @@ public JsonWebKey withKty(JsonWebKeyType kty) { } /** - * Get the key operations. + * Get the keyOps value. * - * @return the key operations. + * @return the keyOps value */ @JsonProperty("key_ops") public List keyOps() { @@ -163,9 +205,9 @@ public List keyOps() { } /** - * Set the key operations value. + * Set the keyOps value. * - * @param keyOps the key operations value to set + * @param keyOps the keyOps value to set * @return the JsonWebKey object itself. */ public JsonWebKey withKeyOps(List keyOps) { @@ -174,9 +216,9 @@ public JsonWebKey withKeyOps(List keyOps) { } /** - * Get the RSA modulus value. + * Get the n value. * - * @return the RSA modulus value. + * @return the n value */ @JsonProperty("n") @JsonSerialize(using = Base64UrlJsonSerializer.class) @@ -186,9 +228,9 @@ public byte[] n() { } /** - * Set the RSA modulus value. + * Set the n value. * - * @param n the RSA modulus value to set + * @param n the n value to set * @return the JsonWebKey object itself. */ public JsonWebKey withN(byte[] n) { @@ -197,8 +239,9 @@ public JsonWebKey withN(byte[] n) { } /** - * Get the RSA public exponent value. - * @return the RSA public exponent value. + * Get the e value. + * + * @return the e value */ @JsonProperty("e") @JsonSerialize(using = Base64UrlJsonSerializer.class) @@ -208,9 +251,9 @@ public byte[] e() { } /** - * Set the RSA public exponent value. - * - * @param e RSA public exponent value to set + * Set the e value. + * + * @param e the e value to set * @return the JsonWebKey object itself. */ public JsonWebKey withE(byte[] e) { @@ -219,8 +262,9 @@ public JsonWebKey withE(byte[] e) { } /** - * Get the RSA private exponent value. - * @return the RSA private exponent value. + * Get the d value. + * + * @return the d value */ @JsonProperty("d") @JsonSerialize(using = Base64UrlJsonSerializer.class) @@ -230,9 +274,9 @@ public byte[] d() { } /** - * Set RSA private exponent value. - * - * @param d the RSA private exponent value to set. + * Set the d value. + * + * @param d the d value to set * @return the JsonWebKey object itself. */ public JsonWebKey withD(byte[] d) { @@ -401,6 +445,73 @@ public String toString() { } } + /** + * Get the crv value. + * + * @return the crv value + */ + @JsonProperty("crv") + public JsonWebKeyCurveName crv() { + return this.crv; + } + + /** + * Set the crv value. + * + * @param crv the crv value to set + * @return the JsonWebKey object itself. + */ + public JsonWebKey withCrv(JsonWebKeyCurveName crv) { + this.crv = crv; + return this; + } + + /** + * Get the x value. + * + * @return the x value + */ + @JsonProperty("x") + @JsonSerialize(using = Base64UrlJsonSerializer.class) + @JsonDeserialize(using = Base64UrlJsonDeserializer.class) + public byte[] x() { + return this.x; + } + + /** + * Set the x value. + * + * @param x the x value to set + * @return the JsonWebKey object itself. + */ + public JsonWebKey withX(byte[] x) { + this.x = x; + return this; + } + + /** + * Get the y value. + * + * @return the y value + */ + @JsonProperty("y") + @JsonSerialize(using = Base64UrlJsonSerializer.class) + @JsonDeserialize(using = Base64UrlJsonDeserializer.class) + public byte[] y() { + return this.y; + } + + /** + * Set the y value. + * + * @param y the y value to set + * @return the JsonWebKey object itself. + */ + public JsonWebKey withY(byte[] y) { + this.y = y; + return this; + } + /** * Get the RSA public key spec value. * @@ -457,6 +568,21 @@ private PrivateKey getRSAPrivateKey(Provider provider) { throw new IllegalStateException(e); } } + + + private static PublicKey getECPublicKey(ECPoint ecPoint, ECParameterSpec curveSpec, Provider provider) throws NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeySpecException { + // Create public key spec with given point + ECPublicKeySpec pubSpec = new ECPublicKeySpec(ecPoint, curveSpec); + KeyFactory kf = provider != null ? KeyFactory.getInstance("EC", provider) : KeyFactory.getInstance("EC"); + return (ECPublicKey) kf.generatePublic(pubSpec); + + } + + private static PrivateKey getECPrivateKey(byte[] d, ECParameterSpec curveSpec, Provider provider) throws NoSuchAlgorithmException, InvalidKeySpecException { + ECPrivateKeySpec priSpec = new ECPrivateKeySpec(new BigInteger(1, d), curveSpec); + KeyFactory kf = provider != null ? KeyFactory.getInstance("EC", provider) : KeyFactory.getInstance("EC"); + return (ECPrivateKey) kf.generatePrivate(priSpec); + } /** * Verifies if the key is an RSA key. @@ -564,7 +690,124 @@ public KeyPair toRSA(boolean includePrivateParameters, Provider provider) { return new KeyPair(getRSAPublicKey(provider), null); } } - + + /** + * Converts JSON web key to EC key pair and include the private key if set to true. + * @return EC key pair + */ + public KeyPair toEC() throws NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeySpecException { + return toEC(false, null); + } + + /** + * Converts JSON web key to EC key pair and include the private key if set to true. + * @param includePrivateParameters true if the EC key pair should include the private key. False otherwise. + * @return EC key pair + */ + public KeyPair toEC(boolean includePrivateParameters) throws NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeySpecException { + return toEC(includePrivateParameters, null); + } + + /** + * Converts JSON web key to EC key pair and include the private key if set to true. + * @param provider the Java security provider. + * @param includePrivateParameters true if the EC key pair should include the private key. False otherwise. + * @return EC key pair + */ + public KeyPair toEC(boolean includePrivateParameters, Provider provider) throws NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeySpecException { + + if (provider == null) { + //Our default provider for this class + provider = new BouncyCastleProvider(); + } + + if (!JsonWebKeyType.EC.equals(kty) && !JsonWebKeyType.EC_HSM.equals(kty)) { + throw new IllegalArgumentException("Not an EC key."); + } + + KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC", provider); + ECGenParameterSpec gps = new ECGenParameterSpec(CURVE_TO_SPEC_NAME.get(crv)); + kpg.initialize(gps); + + // Generate dummy keypair to get parameter spec. + KeyPair apair = kpg.generateKeyPair(); + ECPublicKey apub = (ECPublicKey) apair.getPublic(); + ECParameterSpec aspec = apub.getParams(); + + ECPoint ecPoint = new ECPoint(new BigInteger(1, x), new BigInteger(1, y)); + + KeyPair realKeyPair; + + if (includePrivateParameters) { + realKeyPair = new KeyPair(getECPublicKey(ecPoint, aspec, provider), getECPrivateKey(d, aspec, provider)); + } else { + realKeyPair = new KeyPair(getECPublicKey(ecPoint, aspec, provider), null); + } + + return realKeyPair; + } + + /** + * Converts EC key pair to JSON web key. + * @param keyPair EC key pair + * @provider Java security provider + * @return the JSON web key, converted from EC key pair. + * @throws InvalidAlgorithmParameterException + * @throws NoSuchAlgorithmException + */ + public static JsonWebKey fromEC(KeyPair keyPair, Provider provider) throws NoSuchAlgorithmException, InvalidAlgorithmParameterException { + + ECPublicKey apub = (ECPublicKey) keyPair.getPublic(); + ECPoint point = apub.getW(); + ECPrivateKey apriv = (ECPrivateKey) keyPair.getPrivate(); + + if (apriv != null) { + return new JsonWebKey() + .withKty(JsonWebKeyType.EC) + .withCrv(getCurveFromKeyPair(keyPair, provider)) + .withX(point.getAffineX().toByteArray()) + .withY(point.getAffineY().toByteArray()) + .withD(apriv.getS().toByteArray()) + .withKty(JsonWebKeyType.EC); + } else { + return new JsonWebKey() + .withKty(JsonWebKeyType.EC) + .withCrv(getCurveFromKeyPair(keyPair, provider)) + .withX(point.getAffineX().toByteArray()) + .withY(point.getAffineY().toByteArray()) + .withKty(JsonWebKeyType.EC); + } + } + + // Matches the curve of the keyPair to supported curves. + private static JsonWebKeyCurveName getCurveFromKeyPair(KeyPair keyPair, Provider provider) throws NoSuchAlgorithmException, InvalidAlgorithmParameterException { + ECPublicKey key = (ECPublicKey) keyPair.getPublic(); + ECParameterSpec spec = key.getParams(); + EllipticCurve crv = spec.getCurve(); + + List curveList = Arrays.asList(JsonWebKeyCurveName.P_256, JsonWebKeyCurveName.P_384, JsonWebKeyCurveName.P_521, JsonWebKeyCurveName.SECP256K1); + + for (JsonWebKeyCurveName curve : curveList) { + ECGenParameterSpec gps = new ECGenParameterSpec(CURVE_TO_SPEC_NAME.get(curve)); + KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC", provider); + kpg.initialize(gps); + + // Generate dummy keypair to get parameter spec. + KeyPair apair = kpg.generateKeyPair(); + ECPublicKey apub = (ECPublicKey) apair.getPublic(); + ECParameterSpec aspec = apub.getParams(); + EllipticCurve acurve = aspec.getCurve(); + + //Matches the parameter spec + if (acurve.equals(crv)) { + return curve; + } + } + + //Did not find a supported curve. + throw new NoSuchAlgorithmException("Curve not supported."); + } + /** * Converts AES key to JSON web key. * @param secretKey AES key @@ -626,6 +869,10 @@ public boolean equals(JsonWebKey jwk) { return false; } + if (!Objects.equal(crv, jwk.crv)) { + return false; + } + if (!Arrays.equals(k, jwk.k)) { return false; } @@ -657,6 +904,12 @@ public boolean equals(JsonWebKey jwk) { if (!Arrays.equals(q, jwk.q)) { return false; } + if (!Arrays.equals(x, jwk.x)) { + return false; + } + if (!Arrays.equals(y, jwk.y)) { + return false; + } // HSM token if (!Arrays.equals(t, jwk.t)) { @@ -680,6 +933,10 @@ else if (JsonWebKeyType.RSA.equals(kty) || JsonWebKeyType.RSA_HSM.equals(kty)) { return (d != null && dp != null && dq != null && qi != null && p != null && q != null); } + else if (JsonWebKeyType.EC.equals(kty) || JsonWebKeyType.EC_HSM.equals(kty)){ + return (d != null); + } + return false; } @@ -713,6 +970,15 @@ else if (JsonWebKeyType.RSA.equals(kty)) { else if (JsonWebKeyType.RSA_HSM.equals(kty)) { return isValidRsaHsm(); } + + else if (JsonWebKeyType.EC.equals(kty)) { + return isValidEc(); + } + + else if (JsonWebKeyType.EC_HSM.equals(kty)) { + return isValidEcHsm(); + } + return false; } @@ -754,6 +1020,38 @@ private boolean isValidRsaHsm() { return (tokenParameters || publicParameters); } + private boolean isValidEc() { + boolean ecPointParameters = (x != null && y != null); + if (!ecPointParameters || crv == null) { + return false; + } + + return hasPrivateKey() || (d == null); + } + + private boolean isValidEcHsm() { + // MAY have public key parameters + boolean ecPointParameters = (x != null && y != null); + if ((ecPointParameters && crv == null) || (!ecPointParameters && crv != null)) { + return false; + } + + // no private key + if (hasPrivateKey()) { + return false; + } + + // MUST have (T || (ecPointParameters && crv)) + boolean publicParameters = (ecPointParameters && crv != null); + boolean tokenParameters = t != null; + + if (tokenParameters && publicParameters) { + return false; + } + + return (tokenParameters || publicParameters); + } + /** * Clear key materials. */ @@ -768,6 +1066,8 @@ public void clearMemory() { zeroArray(p); p = null; zeroArray(q); q = null; zeroArray(t); t = null; + zeroArray(x); x = null; + zeroArray(y); y = null; } private static void zeroArray(byte[] bytes) { @@ -791,7 +1091,13 @@ else if (JsonWebKeyType.RSA.equals(kty)) { hashCode += hashCode(n); } - else if (JsonWebKeyType.RSA_HSM.equals(kty)) { + else if (JsonWebKeyType.EC.equals(kty)) { + hashCode += hashCode(x); + hashCode += hashCode(y); + hashCode += crv.hashCode(); + } + + else if (JsonWebKeyType.RSA_HSM.equals(kty) || JsonWebKeyType.EC_HSM.equals(kty)) { hashCode += hashCode(t); } @@ -810,4 +1116,11 @@ private static int hashCode(byte[] obj) { } return hashCode; } + + private final static Map CURVE_TO_SPEC_NAME = ImmutableMap.builder() + .put(JsonWebKeyCurveName.P_256, "secp256r1") + .put(JsonWebKeyCurveName.P_384, "secp384r1") + .put(JsonWebKeyCurveName.P_521, "secp521r1") + .put(JsonWebKeyCurveName.SECP256K1, "secp256k1") + .build(); } \ No newline at end of file diff --git a/azure-keyvault-webkey/src/main/java/com/microsoft/azure/keyvault/webkey/JsonWebKeyEncryptionAlgorithm.java b/azure-keyvault-webkey/src/main/java/com/microsoft/azure/keyvault/webkey/JsonWebKeyEncryptionAlgorithm.java index 67e5e99..ad3c888 100644 --- a/azure-keyvault-webkey/src/main/java/com/microsoft/azure/keyvault/webkey/JsonWebKeyEncryptionAlgorithm.java +++ b/azure-keyvault-webkey/src/main/java/com/microsoft/azure/keyvault/webkey/JsonWebKeyEncryptionAlgorithm.java @@ -16,8 +16,12 @@ * Defines values for JsonWebKeyEncryptionAlgorithm. */ public final class JsonWebKeyEncryptionAlgorithm { - /** Static value RSA-OAEP for JsonWebKeyEncryptionAlgorithm. */ + + /** Static value RSA-OAEP for JsonWebKeyEncryptionAlgorithm. */ public static final JsonWebKeyEncryptionAlgorithm RSA_OAEP = new JsonWebKeyEncryptionAlgorithm("RSA-OAEP"); + + /** Static value RSA-OAEP-256 for JsonWebKeyEncryptionAlgorithm. */ + public static final JsonWebKeyEncryptionAlgorithm RSA_OAEP_256 = new JsonWebKeyEncryptionAlgorithm("RSA-OAEP-256"); /** Static value RSA1_5 for JsonWebKeyEncryptionAlgorithm. */ public static final JsonWebKeyEncryptionAlgorithm RSA1_5 = new JsonWebKeyEncryptionAlgorithm("RSA1_5"); @@ -63,5 +67,5 @@ public boolean equals(Object obj) { * All the JWK encryption algorithms. */ public static final List ALL_ALGORITHMS = - Collections.unmodifiableList(Arrays.asList(RSA_OAEP, RSA1_5)); + Collections.unmodifiableList(Arrays.asList(RSA_OAEP, RSA1_5, RSA_OAEP_256)); } diff --git a/azure-keyvault-webkey/src/main/java/com/microsoft/azure/keyvault/webkey/JsonWebKeySignatureAlgorithm.java b/azure-keyvault-webkey/src/main/java/com/microsoft/azure/keyvault/webkey/JsonWebKeySignatureAlgorithm.java index edd9692..199b804 100644 --- a/azure-keyvault-webkey/src/main/java/com/microsoft/azure/keyvault/webkey/JsonWebKeySignatureAlgorithm.java +++ b/azure-keyvault-webkey/src/main/java/com/microsoft/azure/keyvault/webkey/JsonWebKeySignatureAlgorithm.java @@ -16,7 +16,8 @@ * Defines values for JsonWebKeySignatureAlgorithm. */ public final class JsonWebKeySignatureAlgorithm { - /** Static value PS256 for JsonWebKeySignatureAlgorithm. */ + + /** Static value PS256 for JsonWebKeySignatureAlgorithm. */ public static final JsonWebKeySignatureAlgorithm PS256 = new JsonWebKeySignatureAlgorithm("PS256"); /** Static value PS384 for JsonWebKeySignatureAlgorithm. */ @@ -36,6 +37,14 @@ public final class JsonWebKeySignatureAlgorithm { /** Static value RSNULL for JsonWebKeySignatureAlgorithm. */ public static final JsonWebKeySignatureAlgorithm RSNULL = new JsonWebKeySignatureAlgorithm("RSNULL"); + /** Static value ES256 for JsonWebKeySignatureAlgorithm. */ + public static final JsonWebKeySignatureAlgorithm ES256 = new JsonWebKeySignatureAlgorithm("ES256"); + /** Static value ES384 for JsonWebKeySignatureAlgorithm. */ + public static final JsonWebKeySignatureAlgorithm ES384 = new JsonWebKeySignatureAlgorithm("ES384"); + /** Static value ES512 for JsonWebKeySignatureAlgorithm. */ + public static final JsonWebKeySignatureAlgorithm ES512 = new JsonWebKeySignatureAlgorithm("ES512"); + /** Static value ECDSA256 for JsonWebKeySignatureAlgorithm. */ + public static final JsonWebKeySignatureAlgorithm ECDSA256 = new JsonWebKeySignatureAlgorithm("ECDSA256"); private String value; @@ -78,5 +87,5 @@ public boolean equals(Object obj) { * All the JWK signature algorithms. */ public static final List ALL_ALGORITHMS = - Collections.unmodifiableList(Arrays.asList(RS256, RS384, RS512, RSNULL)); + Collections.unmodifiableList(Arrays.asList(RS256, RS384, RS512, RSNULL, PS256, PS384, PS512, ES256, ES384, ES512, ECDSA256)); } diff --git a/azure-keyvault-webkey/src/main/java/com/microsoft/azure/keyvault/webkey/JsonWebKeyType.java b/azure-keyvault-webkey/src/main/java/com/microsoft/azure/keyvault/webkey/JsonWebKeyType.java index 8a7c9db..dc5a735 100644 --- a/azure-keyvault-webkey/src/main/java/com/microsoft/azure/keyvault/webkey/JsonWebKeyType.java +++ b/azure-keyvault-webkey/src/main/java/com/microsoft/azure/keyvault/webkey/JsonWebKeyType.java @@ -18,6 +18,8 @@ public final class JsonWebKeyType { /** Static value EC for JsonWebKeyType. */ public static final JsonWebKeyType EC = new JsonWebKeyType("EC"); + /** Static value EC-HSM for JsonWebKeyType. */ + public static final JsonWebKeyType EC_HSM = new JsonWebKeyType("EC-HSM"); /** Static value RSA for JsonWebKeyType. */ public static final JsonWebKeyType RSA = new JsonWebKeyType("RSA"); @@ -69,5 +71,5 @@ public boolean equals(Object obj) { * All the JWK key types. */ public static final List ALL_TYPES = - Collections.unmodifiableList(Arrays.asList(EC, RSA, RSA_HSM, OCT)); + Collections.unmodifiableList(Arrays.asList(EC, RSA, RSA_HSM, OCT, EC_HSM)); } diff --git a/azure-keyvault-webkey/src/test/java/com/microsoft/azure/keyvault/webkey/test/EcHsmValidationTests.java b/azure-keyvault-webkey/src/test/java/com/microsoft/azure/keyvault/webkey/test/EcHsmValidationTests.java new file mode 100644 index 0000000..cb1cb9e --- /dev/null +++ b/azure-keyvault-webkey/src/test/java/com/microsoft/azure/keyvault/webkey/test/EcHsmValidationTests.java @@ -0,0 +1,45 @@ +package com.microsoft.azure.keyvault.webkey.test; + +import org.junit.Assert; +import org.junit.Test; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.microsoft.azure.keyvault.webkey.JsonWebKey; + +public class EcHsmValidationTests { + + String keyWithoutT = "{\"kid\":\"key_id\",\"kty\":\"EC-HSM\",\"key_ops\":null,\"n\":null,\"e\":null,\"d\":null,\"dp\":null,\"dq\":null,\"qi\":null,\"p\":null,\"q\":null,\"k\":null,\"key_hsm\":null,\"crv\":\"P-256\",\"x\":\"KyjF795jLyVIgswKSQInEGYHNBKSKyPgNojEgYlldMI\",\"y\":\"AIl_ca1ZIKbJ5YGdgGr_7HySldI2aWeBaOImZEYIMpVe\"}"; + String keyWithT = "{\"kid\":\"key_id\",\"kty\":\"EC-HSM\",\"key_ops\":null,\"key_hsm\":\"T-TOKEN\"}"; + + @Test + public void ecHsmValidation() throws Exception { + ObjectMapper mapper = new ObjectMapper(); + JsonWebKey keyNoT = mapper.readValue(keyWithoutT, JsonWebKey.class); + JsonWebKey keyT = mapper.readValue(keyWithT, JsonWebKey.class); + + Assert.assertTrue(keyNoT.isValid()); + Assert.assertFalse(keyNoT.hasPrivateKey()); + + Assert.assertTrue(keyT.isValid()); + Assert.assertFalse(keyT.hasPrivateKey()); + } + + @Test + public void ecHsmHashCode() throws Exception { + ObjectMapper mapper = new ObjectMapper(); + JsonWebKey keyNoT = mapper.readValue(keyWithoutT, JsonWebKey.class); + JsonWebKey keyT = mapper.readValue(keyWithT, JsonWebKey.class); + + Assert.assertNotEquals(keyT.hashCode(), keyNoT.hashCode()); + + // Compare hash codes for unequal JWK that would not map to the same hash + Assert.assertNotEquals(keyT.hashCode(), new JsonWebKey().withKid(keyT.kid()).withT(keyT.t()).hashCode()); + Assert.assertNotEquals(keyT.hashCode(), new JsonWebKey().withKid(keyT.kid()).withKty(keyT.kty()).hashCode()); + Assert.assertNotEquals(keyNoT.hashCode(), new JsonWebKey().hashCode()); + + // Compare hash codes for unequal JWK that would map to the same hash + Assert.assertEquals(keyT.hashCode(), + new JsonWebKey().withKid(keyT.kid()).withKty(keyT.kty()).withT(keyT.t()).hashCode()); + Assert.assertEquals(keyNoT.hashCode(), new JsonWebKey().withKid(keyT.kid()).hashCode()); + } +} diff --git a/azure-keyvault-webkey/src/test/java/com/microsoft/azure/keyvault/webkey/test/EcValidationTests.java b/azure-keyvault-webkey/src/test/java/com/microsoft/azure/keyvault/webkey/test/EcValidationTests.java new file mode 100644 index 0000000..35a9c28 --- /dev/null +++ b/azure-keyvault-webkey/src/test/java/com/microsoft/azure/keyvault/webkey/test/EcValidationTests.java @@ -0,0 +1,114 @@ +package com.microsoft.azure.keyvault.webkey.test; + +import java.security.InvalidKeyException; +import java.security.KeyPair; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.Signature; +import java.security.SignatureException; +import java.util.Map; +import java.util.Random; + +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.junit.Assert; +import org.junit.Test; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.common.collect.ImmutableMap; +import com.microsoft.azure.keyvault.models.JsonWebKeyCurveName; +import com.microsoft.azure.keyvault.webkey.JsonWebKey; + +public class EcValidationTests { + + @Test + public void ecPublicKeyValidation() throws Exception { + + for (String keyStr : keys.values()) { + ObjectMapper mapper = new ObjectMapper(); + JsonWebKey key = mapper.readValue(keyStr, JsonWebKey.class); + Assert.assertTrue(key.hasPrivateKey()); + Assert.assertTrue(key.isValid()); + + KeyPair keyPair = key.toEC(); + validateEcKey(keyPair, key); + Assert.assertNull(keyPair.getPrivate()); + + // Compare equal JSON web keys + JsonWebKey sameKey = mapper.readValue(keyStr, JsonWebKey.class); + Assert.assertEquals(key, key); + Assert.assertEquals(key, sameKey); + Assert.assertEquals(key.hashCode(), sameKey.hashCode()); + } + } + + @Test + public void ecPrivateKeyValidation() throws Exception { + for (String keyStr : keys.values()) { + ObjectMapper mapper = new ObjectMapper(); + JsonWebKey key = mapper.readValue(keyStr, JsonWebKey.class); + KeyPair keyPairWithPrivate = key.toEC(true); + validateEcKey(keyPairWithPrivate, key); + signVerify(keyPairWithPrivate.getPublic(), keyPairWithPrivate.getPrivate(), key.crv()); + } + } + + private static void validateEcKey(KeyPair keyPair, JsonWebKey key) throws Exception { + JsonWebKey jsonWebKey = JsonWebKey.fromEC(keyPair, new BouncyCastleProvider()); + boolean includePrivateKey = keyPair.getPrivate() != null; + KeyPair keyPair2 = jsonWebKey.toEC(includePrivateKey); + + Assert.assertTrue(includePrivateKey == jsonWebKey.hasPrivateKey()); + + PublicKey publicKey = keyPair2.getPublic(); + PrivateKey privateKey = keyPair2.getPrivate(); + + if(includePrivateKey) { + + // set the missing properties to compare the keys + jsonWebKey.withKid(new String(key.kid())); + Assert.assertNotNull(privateKey); + Assert.assertEquals(jsonWebKey, key); + Assert.assertEquals(key.hashCode(), jsonWebKey.hashCode()); + signVerify(publicKey, privateKey, jsonWebKey.crv()); + } + } + + private static void signVerify(PublicKey publicKey, PrivateKey privateKey, JsonWebKeyCurveName curve) throws InvalidKeyException, NoSuchAlgorithmException, SignatureException { + Signature signature = Signature.getInstance(CURVE_TO_SIGNATURE.get(curve), new BouncyCastleProvider()); + signature.initSign(privateKey); + MessageDigest digest = MessageDigest.getInstance(algorithm.get(curve)); + byte[] plaintext = new byte[10]; + new Random().nextBytes(plaintext); + byte[] hash = digest.digest(plaintext); + signature.update(hash); + byte[] signed_hash = signature.sign(); + + signature.initVerify(publicKey); + signature.update(hash); + Assert.assertTrue(signature.verify(signed_hash)); + + } + + static Map algorithm = ImmutableMap.builder() + .put(JsonWebKeyCurveName.P_256, "SHA-256") + .put(JsonWebKeyCurveName.P_384, "SHA-384") + .put(JsonWebKeyCurveName.P_521, "SHA-512") + .put(JsonWebKeyCurveName.SECP256K1, "SHA-256") + .build(); + + Map keys = ImmutableMap.builder() + .put(256, "{\"kid\":\"key_id\",\"kty\":\"EC\",\"key_ops\":null,\"n\":null,\"e\":null,\"d\":\"AM_iqldq9VSqlf9v3w7lren4pJvZTG81v6_V5ZBLP7ZI\",\"dp\":null,\"dq\":null,\"qi\":null,\"p\":null,\"q\":null,\"k\":null,\"key_hsm\":null,\"crv\":\"P-256\",\"x\":\"KyjF795jLyVIgswKSQInEGYHNBKSKyPgNojEgYlldMI\",\"y\":\"AIl_ca1ZIKbJ5YGdgGr_7HySldI2aWeBaOImZEYIMpVe\"}") + .put(384, "{\"kid\":\"key_id\",\"kty\":\"EC\",\"key_ops\":null,\"n\":null,\"e\":null,\"d\":\"AJEYT00mAfa-_uJ8S9ob0-9uZbPEr56CFebUQW9O-jZQBrtrMSPeqVbjJvTVlzOwbg\",\"dp\":null,\"dq\":null,\"qi\":null,\"p\":null,\"q\":null,\"k\":null,\"key_hsm\":null,\"crv\":\"P-384\",\"x\":\"AKOdkhxTtVkLtaslZIOPQGnsdKRT2xo3Ynk-bnAVvTCf3iGrTpRiMxUmyq_tvzBLEg\",\"y\":\"QoHux2O2XGMh8w7a5sWwskAyCR0g3Lj7kPGuvnDq_bQ_-_VoTvsGMAe9MFexv68I\"}") + .put(521, "{\"kid\":\"key_id\",\"kty\":\"EC\",\"key_ops\":null,\"n\":null,\"e\":null,\"d\":\"AVW7TFJVOJ8jY5PqK0nnKyVYQwhkBEGKt0nhSZTS5io7U32dR7xZle77Gq6SpjrdFVa32jvGWgchlSguV3WKy3sj\",\"dp\":null,\"dq\":null,\"qi\":null,\"p\":null,\"q\":null,\"k\":null,\"key_hsm\":null,\"crv\":\"P-521\",\"x\":\"AIDmImOrJNKOjOGp7wD8Dzi_uz-00E7cs8iN5SwBkzBXktyRrLDFS_SMwVdnIWpLcdJQn5sTGDS121DhjQA2i2dO\",\"y\":\"AWRoeIfIoRoEx8V9ijjwaco3V6vUPUYvKMKxtCPvm8iwhB7pZAI7-mODSfkb3rZo3gxuWoM3G7L66FttUlKSLK4w\"}") + .put(265, "{\"kid\":\"key_id\",\"kty\":\"EC\",\"key_ops\":null,\"n\":null,\"e\":null,\"d\":\"YKv22AkpwBpKUcDodNhKhvI-bRpiWqoN8l0kNCo-Mds\",\"dp\":null,\"dq\":null,\"qi\":null,\"p\":null,\"q\":null,\"k\":null,\"key_hsm\":null,\"crv\":\"SECP256K1\",\"x\":\"Yw9Sln8gYf_oiFY1anQm0V_WwsRaCIcEccfbhu5hSJo\",\"y\":\"AJq3JT2YldszaohHaS7LkngPWS9y0yAn7HhHb5p0IUDS\"}") + .build(); + + public static final Map CURVE_TO_SIGNATURE = ImmutableMap.builder() + .put(JsonWebKeyCurveName.P_256, "SHA256withECDSA") + .put(JsonWebKeyCurveName.P_384, "SHA384withECDSA") + .put(JsonWebKeyCurveName.P_521, "SHA512withECDSA") + .put(JsonWebKeyCurveName.SECP256K1, "NONEwithECDSA") + .build(); +} diff --git a/azure-keyvault/pom.xml b/azure-keyvault/pom.xml index 992ea62..4bb6bb7 100644 --- a/azure-keyvault/pom.xml +++ b/azure-keyvault/pom.xml @@ -6,7 +6,7 @@ com.microsoft.azure azure-keyvault-parent - 1.1-alpha-2 + 1.1-beta-1 ../pom.xml diff --git a/pom.xml b/pom.xml index 663fd97..2727959 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ 4.0.0 com.microsoft.azure - 1.1-alpha-2 + 1.1-beta-1 azure-keyvault-parent pom @@ -109,7 +109,7 @@ com.microsoft.azure azure-client-runtime - 1.0.0 + 1.3.0 com.google.guava @@ -365,8 +365,8 @@ ./azure-keyvault ./azure-keyvault-core + ./azure-keyvault-webkey ./azure-keyvault-cryptography ./azure-keyvault-extensions - ./azure-keyvault-webkey