diff --git a/keyvault/data-plane/README.md b/keyvault/data-plane/README.md index d90d0450abdc..7d68af7378e6 100644 --- a/keyvault/data-plane/README.md +++ b/keyvault/data-plane/README.md @@ -83,7 +83,8 @@ To get the binaries of this library as distributed by Microsoft, ready for use w com.microsoft.azure azure-keyvault-complete - 1.1.2 + 1.2.0 + pom ``` @@ -114,6 +115,7 @@ If you would like to become an active contributor to this project please follow | Version | Comments | | :-------: | :-------- | +| [1.1.2](https://github.com/Azure/azure-keyvault-java/tree/1.1.2) | Version 1.1.2 release | | [1.1.1](https://github.com/Azure/azure-keyvault-java/tree/1.1.1) | Version 1.1.1 release | | [1.1](https://github.com/Azure/azure-keyvault-java/tree/1.1) | Version 1.1 release | | [1.1-beta-1](https://github.com/Azure/azure-keyvault-java/tree/1.1-beta-1) | Version 1.1.0 **beta** release | diff --git a/keyvault/data-plane/azure-keyvault-complete/pom.xml b/keyvault/data-plane/azure-keyvault-complete/pom.xml index 11fd13065e2e..00f69fd66c81 100644 --- a/keyvault/data-plane/azure-keyvault-complete/pom.xml +++ b/keyvault/data-plane/azure-keyvault-complete/pom.xml @@ -7,13 +7,13 @@ the MIT License. See License.txt in the project root for license information. -- com.microsoft.azure azure-keyvault-parent - 1.1.2 + 1.2.0 ../pom.xml com.microsoft.azure azure-keyvault-complete - 1.1.2 + 1.2.0 pom diff --git a/keyvault/data-plane/azure-keyvault-core/pom.xml b/keyvault/data-plane/azure-keyvault-core/pom.xml index cee82573a28b..6c7dfb51eadd 100644 --- a/keyvault/data-plane/azure-keyvault-core/pom.xml +++ b/keyvault/data-plane/azure-keyvault-core/pom.xml @@ -8,12 +8,12 @@ com.microsoft.azure azure-keyvault-parent - 1.1.2 + 1.2.0 ../pom.xml azure-keyvault-core - 1.1.2 + 1.2.0 jar Microsoft Azure SDK for Key Vault Core diff --git a/keyvault/data-plane/azure-keyvault-cryptography/pom.xml b/keyvault/data-plane/azure-keyvault-cryptography/pom.xml index edcde42ea81d..9d1d354c8f84 100644 --- a/keyvault/data-plane/azure-keyvault-cryptography/pom.xml +++ b/keyvault/data-plane/azure-keyvault-cryptography/pom.xml @@ -7,12 +7,12 @@ com.microsoft.azure azure-keyvault-parent - 1.1.2 + 1.2.0 ../pom.xml azure-keyvault-cryptography - 1.1.2 + 1.2.0 jar Microsoft Azure SDK for Key Vault Cryptography diff --git a/keyvault/data-plane/azure-keyvault-cryptography/src/main/java/com/microsoft/azure/keyvault/cryptography/SignatureEncoding.java b/keyvault/data-plane/azure-keyvault-cryptography/src/main/java/com/microsoft/azure/keyvault/cryptography/SignatureEncoding.java new file mode 100644 index 000000000000..b9fbc358ce03 --- /dev/null +++ b/keyvault/data-plane/azure-keyvault-cryptography/src/main/java/com/microsoft/azure/keyvault/cryptography/SignatureEncoding.java @@ -0,0 +1,310 @@ +/** + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for + * license information. + */ + +package com.microsoft.azure.keyvault.cryptography; + +import java.util.Arrays; +import java.util.List; +import java.util.ArrayList; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.math.BigInteger; +import java.security.NoSuchAlgorithmException; +import org.apache.commons.lang3.ArrayUtils; + +import com.microsoft.azure.keyvault.cryptography.algorithms.Ecdsa; +import org.apache.commons.codec.binary.Hex; + +public final class SignatureEncoding { + // SignatureEncoding is intended to be a static class + private SignatureEncoding() { } + + /** + * Converts an ASN.1 DER encoded ECDSA signature to a raw signature in the form R|S + * @param asn1DerSignature An ASN.1 DER encoded signature + * @param algorithm The algorithm used to produce the given ASN.1 DER encoded signature + * @return The raw format of the given ASN.1 DER encoded signature in the form R|S + */ + public static byte[] fromAsn1Der(byte[] asn1DerSignature, String algorithm) throws NoSuchAlgorithmException { + Algorithm baseAlgorithm = AlgorithmResolver.Default.get(algorithm); + + // verify the given algoritm could be resolved + if (baseAlgorithm == null) + { + throw new NoSuchAlgorithmException(algorithm); + } + + // verify the given algoritm is an Ecdsa signature algorithm + if (!(baseAlgorithm instanceof Ecdsa)) + { + throw new IllegalArgumentException("Invalid algorithm; must be an instance of ECDSA."); + } + + return SignatureEncoding.fromAsn1Der(asn1DerSignature, (Ecdsa)baseAlgorithm); + } + + /** + * Converts an ASN.1 DER encoded ECDSA signature to a raw signature in the form R|S + * @param asn1DerSignature An ASN.1 DER encoded signature + * @param algorithm The algorithm used to produce the given ASN.1 DER encoded signature + * @return The raw format of the given ASN.1 DER encoded signature in the form R|S + */ + public static byte[] fromAsn1Der(byte[] asn1DerSignature, Ecdsa algorithm) { + + try + { + return Asn1DerSignatureEncoding.Decode(asn1DerSignature, algorithm); + } + catch(IllegalArgumentException ex) + { + throw (IllegalArgumentException)new IllegalArgumentException(ex.getMessage() + " " + Hex.encodeHexString( asn1DerSignature )).initCause(ex); + + } + } + + /** + * Converts a raw ECDSA signature in the form R|S to an ASN.1 DER encoded signature. + * @param signature A raw ECDSA signature in the form R|S. + * @param algorithm The algorithm used to produce the given signature. + * @return The ASN.1 DER encoded signature of the given signature. + */ + public static byte[] toAsn1Der(byte[] signature, String algorithm) throws NoSuchAlgorithmException { + Algorithm baseAlgorithm = AlgorithmResolver.Default.get(algorithm); + + // verify the given algoritm could be resolved + if (baseAlgorithm == null) + { + throw new NoSuchAlgorithmException(algorithm); + } + + // verify the given algoritm is an Ecdsa signature algorithm + if (!(baseAlgorithm instanceof Ecdsa)) + { + throw new IllegalArgumentException("Invalid algorithm; must be an instance of ECDSA."); + } + + return SignatureEncoding.toAsn1Der(signature, (Ecdsa)baseAlgorithm); + } + + /** + * Converts a raw ECDSA signature in the form R|S to an ASN.1 DER encoded signature. + * @param signature A raw ECDSA signature in the form R|S. + * @param algorithm The algorithm used to produce the given signature. + * @return The ASN.1 DER encoded signature of the given signature. + */ + public static byte[] toAsn1Der(byte[] signature, Ecdsa algorithm) { + + try + { + return Asn1DerSignatureEncoding.Encode(signature, algorithm); + } + catch(IllegalArgumentException ex) + { + throw (IllegalArgumentException)new IllegalArgumentException(ex.getMessage() + " " + Hex.encodeHexString( signature )).initCause(ex); + + } + } +} + + +final class Asn1DerSignatureEncoding { + // the EDCSA ASN.1 DER signature is in the format: + // 0x30 b1 0x02 b2 (vr) 0x02 b3 (vs) + // where: + // *b1 one or more bytes equal to the length, in bytes, of the remaining list of bytes (from the first 0x02 to the end of the encoding) + // *b2 one or more bytes equal to the length, in bytes, of (vr) + // *b3 one or more bytes equal to the length, in bytes, of (vs) + // (vr) is the signed big-endian encoding of the value "r", of minimal length + // (vs) is the signed big-endian encoding of the value "s", of minimal length + // + // * lengths which are less than 0x80 can be expressed in one byte. For lengths greater then 0x80 the first byte denotes the + // length in bytes of the length with the most significant bit masked off, i.e. 0x81 denotes the length is one byte long. + + private Asn1DerSignatureEncoding() { + + } + + public static byte[] Encode(byte[] signature, Ecdsa algorithm) + { + int coordLength = algorithm.getCoordLength(); + + // verify that the signature is the correct length for the given algorithm + if (signature.length != (coordLength * 2)) + { + throw new IllegalArgumentException("Invalid signature."); + } + + // r is the first half of the signature + BigInteger r = new BigInteger(1, Arrays.copyOfRange(signature, 0, signature.length / 2)); + + // s is the second half of the signature + BigInteger s = new BigInteger(1, Arrays.copyOfRange(signature, signature.length / 2, signature.length)); + + // vr and vs are the compacted ASN.1 integer encoding, same as BigInteger encoding + byte[] rfield = encodeIntField(r); + + byte[] sfield = encodeIntField(s); + + ByteArrayOutputStream asn1DerSignature = new ByteArrayOutputStream(); + + asn1DerSignature.write(0x30); + + // add the length of the fields + writeFieldLength(asn1DerSignature, rfield.length + sfield.length); + + // write the fields + asn1DerSignature.write(rfield, 0, rfield.length); + + asn1DerSignature.write(sfield, 0, sfield.length); + + return asn1DerSignature.toByteArray(); + } + + public static byte[] Decode(byte[] bytes, Ecdsa algorithm) + { + int coordLength = algorithm.getCoordLength(); + + ByteArrayInputStream asn1DerSignature = new ByteArrayInputStream(bytes); + + // verify byte 0 is 0x30 + if (asn1DerSignature.read() != 0x30) + { + throw new IllegalArgumentException("Invalid signature."); + } + + int objLen = readFieldLength(asn1DerSignature); + + // verify the object lenth is equal to the remaining length of the _asn1DerSignature + if (objLen != asn1DerSignature.available()) + { + throw new IllegalArgumentException(String.format("Invalid signature; invalid field len %d", objLen)); + } + + byte[] rawSignature = new byte[coordLength * 2]; + + // decode the r feild to the first half of _rawSignature + decodeIntField(asn1DerSignature, rawSignature, 0, coordLength); + + // decode the s feild to the second half of _rawSignature + decodeIntField(asn1DerSignature, rawSignature, rawSignature.length / 2, coordLength); + + return rawSignature; + } + + private static byte[] encodeIntField(BigInteger i) + { + ByteArrayOutputStream field = new ByteArrayOutputStream(); + + field.write(0x02); + + // get this byte array for the asn1 encoded integer + byte[] vi = i.toByteArray(); + + // write the length of the field + writeFieldLength(field, vi.length); + + // write the field value + field.write(vi, 0, vi.length); + + return field.toByteArray(); + } + + private static void writeFieldLength(ByteArrayOutputStream field, int len) + { + // if the length of vi is less then 0x80 we can fit the length in one byte + if(len < 0x80) + { + field.write(len); + } + // otherwise + else + { + // get the len as a byte array + byte[] blen = BigInteger.valueOf(len).toByteArray(); + + int lenlen = blen.length; + + // the byte array might have a leading zero byte if so we need to discard this + if ( blen[0] == 0 ) + { + lenlen--; + } + + // write the continuation byte containing the length length in bytes + field.write(0x80 | lenlen); + + // write the field lenth bytes + field.write(blen, blen.length - lenlen, lenlen); + } + } + + private static void decodeIntField(ByteArrayInputStream bytes, byte[] dest, int index, int intlen) + { + // verify the first byte of field is 0x02 + if (bytes.read() != 0x02) + { + throw new IllegalArgumentException("Invalid signature."); + } + + //get the length of the field + int len = readFieldLength(bytes); + + // if the most significant bit of the raw int was set an extra zero byte will be prepended to + // the asn1der encoded value so len can have a max value of intlen + 1 + + // validate that that len is within the max range and doesn't run past the end of bytes + if (len > intlen + 1 || len > bytes.available()) + { + throw new IllegalArgumentException("Invalid signature."); + } + + // if len is greater than intlen increment _bytesRead and decrement len + if (len > intlen) + { + bytes.skip(1); + len--; + } + + bytes.read(dest, index + (intlen - len), len); + } + + private static int readFieldLength(ByteArrayInputStream bytes) + { + int firstLenByte = bytes.read(); + + // if the high order bit of len is not set it is a single byte length so return + if ((firstLenByte & 0x80) == 0x00) + { + return firstLenByte; + } + + // otherwise mask off the high order bit to get the number of bytes to read + int numLenBytes = firstLenByte ^ 0x80; + + // if the number of len bytes is greater than the remaining signature the signature is invalid + if (numLenBytes > bytes.available()) + { + throw new IllegalArgumentException("Invalid signature."); + } + + byte[] lenBytes = new byte[numLenBytes]; + + bytes.read(lenBytes, 0, numLenBytes); + + BigInteger bigLen = new BigInteger(1, lenBytes); + + // for DSA signatures no feilds should be longer than can be expressed in an integer + // this means that the bitLength must be 31 or less to account for the leading zero of + // a positive integer + if (bigLen.bitLength() >= 31) + { + throw new IllegalArgumentException("Invalid signature."); + } + + return bigLen.intValue(); + } +} + diff --git a/keyvault/data-plane/azure-keyvault-cryptography/src/main/java/com/microsoft/azure/keyvault/cryptography/algorithms/Ecdsa.java b/keyvault/data-plane/azure-keyvault-cryptography/src/main/java/com/microsoft/azure/keyvault/cryptography/algorithms/Ecdsa.java index a13fa0767997..cd9ceacd9317 100644 --- a/keyvault/data-plane/azure-keyvault-cryptography/src/main/java/com/microsoft/azure/keyvault/cryptography/algorithms/Ecdsa.java +++ b/keyvault/data-plane/azure-keyvault-cryptography/src/main/java/com/microsoft/azure/keyvault/cryptography/algorithms/Ecdsa.java @@ -7,6 +7,7 @@ import com.microsoft.azure.keyvault.cryptography.AsymmetricSignatureAlgorithm; import com.microsoft.azure.keyvault.cryptography.ISignatureTransform; +import com.microsoft.azure.keyvault.cryptography.SignatureEncoding; public abstract class Ecdsa extends AsymmetricSignatureAlgorithm { @@ -15,21 +16,30 @@ protected Ecdsa() { } public ISignatureTransform createSignatureTransform(KeyPair key, Provider provider) { - return new EcdsaSignatureTransform(key, provider); + return new EcdsaSignatureTransform(key, provider, this); } - abstract void checkDigestLength(byte[] digest); - + public abstract int getDigestLength(); + public abstract int getCoordLength(); + + private void checkDigestLength(byte[] digest) + { + if (digest.length != this.getDigestLength()) { + throw new IllegalArgumentException("Invalid digest length."); + } + } + class EcdsaSignatureTransform implements ISignatureTransform { - private final String ALGORITHM = "NONEwithECDSA"; + private final String ALGORITHM = "NONEwithECDSA"; private final KeyPair _keyPair; - private final Provider _provider; - - public EcdsaSignatureTransform(KeyPair keyPair, Provider provider) { + private final Ecdsa _algorithm; + + public EcdsaSignatureTransform(KeyPair keyPair, Provider provider, Ecdsa algorithm) { _keyPair = keyPair; _provider = provider; + _algorithm = algorithm; } @Override @@ -38,17 +48,17 @@ public byte[] sign(byte[] digest) throws GeneralSecurityException { Signature signature = Signature.getInstance(ALGORITHM, _provider); signature.initSign(_keyPair.getPrivate()); signature.update(digest); - return signature.sign(); + return SignatureEncoding.fromAsn1Der(signature.sign(), _algorithm); } @Override public boolean verify(byte[] digest, byte[] signature) throws GeneralSecurityException { Signature verify = Signature.getInstance(ALGORITHM, _provider); - checkDigestLength(digest); + checkDigestLength(digest); + signature = SignatureEncoding.toAsn1Der(signature, _algorithm); verify.initVerify(_keyPair.getPublic()); verify.update(digest); return verify.verify(signature); } - } } diff --git a/keyvault/data-plane/azure-keyvault-cryptography/src/main/java/com/microsoft/azure/keyvault/cryptography/algorithms/Es256.java b/keyvault/data-plane/azure-keyvault-cryptography/src/main/java/com/microsoft/azure/keyvault/cryptography/algorithms/Es256.java index c9ae074fbb56..9766f0018466 100644 --- a/keyvault/data-plane/azure-keyvault-cryptography/src/main/java/com/microsoft/azure/keyvault/cryptography/algorithms/Es256.java +++ b/keyvault/data-plane/azure-keyvault-cryptography/src/main/java/com/microsoft/azure/keyvault/cryptography/algorithms/Es256.java @@ -1,12 +1,15 @@ package com.microsoft.azure.keyvault.cryptography.algorithms; public class Es256 extends Ecdsa { - public final static String ALGORITHM_NAME = "SHA256withECDSA"; + public final static String ALGORITHM_NAME = "ES256"; @Override - public void checkDigestLength(byte[] digest) { - if (digest.length != 32) { - throw new IllegalArgumentException("Invalid digest length."); - } + public int getDigestLength() { + return 32; + } + + @Override + public int getCoordLength() { + return 32; } } diff --git a/keyvault/data-plane/azure-keyvault-cryptography/src/main/java/com/microsoft/azure/keyvault/cryptography/algorithms/Es256k.java b/keyvault/data-plane/azure-keyvault-cryptography/src/main/java/com/microsoft/azure/keyvault/cryptography/algorithms/Es256k.java index b788979f2b26..9da729997a44 100644 --- a/keyvault/data-plane/azure-keyvault-cryptography/src/main/java/com/microsoft/azure/keyvault/cryptography/algorithms/Es256k.java +++ b/keyvault/data-plane/azure-keyvault-cryptography/src/main/java/com/microsoft/azure/keyvault/cryptography/algorithms/Es256k.java @@ -1,12 +1,15 @@ package com.microsoft.azure.keyvault.cryptography.algorithms; public class Es256k extends Ecdsa { - public final static String ALGORITHM_NAME = "NONEwithECDSA"; + public final static String ALGORITHM_NAME = "ES256K"; @Override - public void checkDigestLength(byte[] digest) { - if (digest.length != 32) { - throw new IllegalArgumentException("Invalid digest length."); - } + public int getDigestLength() { + return 32; + } + + @Override + public int getCoordLength() { + return 32; } } diff --git a/keyvault/data-plane/azure-keyvault-cryptography/src/main/java/com/microsoft/azure/keyvault/cryptography/algorithms/Es384.java b/keyvault/data-plane/azure-keyvault-cryptography/src/main/java/com/microsoft/azure/keyvault/cryptography/algorithms/Es384.java index 7785fdbda61a..16ff088c6526 100644 --- a/keyvault/data-plane/azure-keyvault-cryptography/src/main/java/com/microsoft/azure/keyvault/cryptography/algorithms/Es384.java +++ b/keyvault/data-plane/azure-keyvault-cryptography/src/main/java/com/microsoft/azure/keyvault/cryptography/algorithms/Es384.java @@ -2,12 +2,15 @@ public class Es384 extends Ecdsa { - public final static String ALGORITHM_NAME = "SHA384withECDSA"; + public final static String ALGORITHM_NAME = "ES384"; @Override - public void checkDigestLength(byte[] digest) { - if (digest.length != 48) { - throw new IllegalArgumentException("Invalid digest length."); - } + public int getDigestLength() { + return 48; + } + + @Override + public int getCoordLength() { + return 48; } } diff --git a/keyvault/data-plane/azure-keyvault-cryptography/src/main/java/com/microsoft/azure/keyvault/cryptography/algorithms/Es512.java b/keyvault/data-plane/azure-keyvault-cryptography/src/main/java/com/microsoft/azure/keyvault/cryptography/algorithms/Es512.java index 32fdd3769951..ef94fedb9de6 100644 --- a/keyvault/data-plane/azure-keyvault-cryptography/src/main/java/com/microsoft/azure/keyvault/cryptography/algorithms/Es512.java +++ b/keyvault/data-plane/azure-keyvault-cryptography/src/main/java/com/microsoft/azure/keyvault/cryptography/algorithms/Es512.java @@ -2,12 +2,15 @@ public class Es512 extends Ecdsa { - public final static String ALGORITHM_NAME = "SHA512withECDSA"; + public final static String ALGORITHM_NAME = "ES512"; @Override - public void checkDigestLength(byte[] digest) { - if (digest.length != 64) { - throw new IllegalArgumentException("Invalid digest length."); - } + public int getDigestLength() { + return 64; + } + + @Override + public int getCoordLength() { + return 66; } } diff --git a/keyvault/data-plane/azure-keyvault-cryptography/src/test/java/com/microsoft/azure/keyvault/cryptography/test/ECKeyTest.java b/keyvault/data-plane/azure-keyvault-cryptography/src/test/java/com/microsoft/azure/keyvault/cryptography/test/ECKeyTest.java index 748c75f6f159..ef0c036938a2 100644 --- a/keyvault/data-plane/azure-keyvault-cryptography/src/test/java/com/microsoft/azure/keyvault/cryptography/test/ECKeyTest.java +++ b/keyvault/data-plane/azure-keyvault-cryptography/src/test/java/com/microsoft/azure/keyvault/cryptography/test/ECKeyTest.java @@ -38,6 +38,11 @@ import com.google.common.collect.ImmutableMap; import com.microsoft.azure.keyvault.cryptography.EcKey; +import com.microsoft.azure.keyvault.cryptography.algorithms.Es256; +import com.microsoft.azure.keyvault.cryptography.algorithms.Es256k; +import com.microsoft.azure.keyvault.cryptography.algorithms.Es384; +import com.microsoft.azure.keyvault.cryptography.algorithms.Es512; +import com.microsoft.azure.keyvault.cryptography.SignatureEncoding; import com.microsoft.azure.keyvault.cryptography.test.resources.PemFile; import com.microsoft.azure.keyvault.webkey.JsonWebKey; import com.microsoft.azure.keyvault.webkey.JsonWebKeyCurveName; @@ -214,15 +219,15 @@ private KeyPair getKeyFromFile(String privateKeyPath, String publicKeyPath) thro return keyPair; } - private void testFromFile(String keyType, MessageDigest digest) throws Exception { + private void testFromFile(String keyType, MessageDigest digest, String algorithm) throws Exception { String privateKeyPath = "src/test/java/com/microsoft/azure/keyvault/cryptography/test/resources/" + keyType + "keynew.pem"; String publicKeyPath = "src/test/java/com/microsoft/azure/keyvault/cryptography/test/resources/" + keyType + "keypubnew.pem"; EcKey newKey = new EcKey("akey", getKeyFromFile(privateKeyPath, publicKeyPath)); Path signatureLocation = Paths.get("src/test/java/com/microsoft/azure/keyvault/cryptography/test/resources/" + keyType + "sig.der"); - byte[] signature = Files.readAllBytes(signatureLocation); - + byte[] signature = SignatureEncoding.fromAsn1Der(Files.readAllBytes(signatureLocation), algorithm); + doVerify(newKey, digest, signature); } @@ -238,22 +243,22 @@ public void testCreateSECP256K1Key() throws Exception { @Test public void testFromP384File() throws Exception { - testFromFile("p384", DIGEST_384); + testFromFile("p384", DIGEST_384, Es384.ALGORITHM_NAME); } @Test public void testFromP521File() throws Exception { - testFromFile("p521", DIGEST_512); + testFromFile("p521", DIGEST_512, Es512.ALGORITHM_NAME); } @Test public void testFromP256File() throws Exception { - testFromFile("p256", DIGEST_256); + testFromFile("p256", DIGEST_256, Es256.ALGORITHM_NAME); } @Test public void testFromSEC256File() throws Exception{ - testFromFile("secp256", DIGEST_256); + testFromFile("secp256", DIGEST_256, Es256k.ALGORITHM_NAME); } @Test diff --git a/keyvault/data-plane/azure-keyvault-extensions/pom.xml b/keyvault/data-plane/azure-keyvault-extensions/pom.xml index 6c3af7a2693f..d71685206ea2 100644 --- a/keyvault/data-plane/azure-keyvault-extensions/pom.xml +++ b/keyvault/data-plane/azure-keyvault-extensions/pom.xml @@ -8,12 +8,12 @@ com.microsoft.azure azure-keyvault-parent - 1.1.2 + 1.2.0 ../pom.xml azure-keyvault-extensions - 1.1.2 + 1.2.0 jar Microsoft Azure SDK for Key Vault Extensions diff --git a/keyvault/data-plane/azure-keyvault-test/pom.xml b/keyvault/data-plane/azure-keyvault-test/pom.xml new file mode 100644 index 000000000000..92e8224b170d --- /dev/null +++ b/keyvault/data-plane/azure-keyvault-test/pom.xml @@ -0,0 +1,81 @@ + + + 4.0.0 + + com.microsoft.azure + azure-keyvault-parent + 1.2.0 + ../pom.xml + + azure-keyvault-test + azure-keyvault-test + http://maven.apache.org + + UTF-8 + playback + + + + junit + junit + test + + + com.microsoft.azure + azure-keyvault-webkey + test + + + com.microsoft.azure + azure-keyvault + test + + + com.microsoft.azure + azure-keyvault-cryptography + test + + + com.microsoft.azure + azure-mgmt-storage + 1.3.0 + test + + + com.microsoft.azure + azure-mgmt-graph-rbac + 1.3.0 + test + + + com.microsoft.azure + azure-mgmt-resources + test + + + com.microsoft.azure + azure-mgmt-keyvault + test + + + com.microsoft.azure + azure-mgmt-resources + 1.3.1-SNAPSHOT + test-jar + test + + + com.microsoft.azure + adal4j + test + + + com.microsoft.azure + azure-storage + 4.4.0 + test + + + diff --git a/keyvault/data-plane/azure-keyvault-test/src/test/java/com/microsoft/azure/keyvault/test/EcKeyIntegrationTests.java b/keyvault/data-plane/azure-keyvault-test/src/test/java/com/microsoft/azure/keyvault/test/EcKeyIntegrationTests.java new file mode 100644 index 000000000000..92a006022bd2 --- /dev/null +++ b/keyvault/data-plane/azure-keyvault-test/src/test/java/com/microsoft/azure/keyvault/test/EcKeyIntegrationTests.java @@ -0,0 +1,334 @@ +package com.microsoft.azure.keyvault.test; + +import static org.junit.Assert.*; + +import java.io.IOException; +import java.math.BigInteger; +import java.security.KeyFactory; +import java.security.KeyPair; +import java.security.MessageDigest; +import java.security.spec.KeySpec; +import java.security.spec.RSAPrivateCrtKeySpec; +import java.security.spec.RSAPublicKeySpec; +import java.util.Arrays; +import java.util.Random; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; + +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TestName; + +import com.microsoft.aad.adal4j.AuthenticationContext; +import com.microsoft.aad.adal4j.AuthenticationResult; +import com.microsoft.aad.adal4j.ClientCredential; +import com.microsoft.azure.AzureResponseBuilder; +import com.microsoft.azure.keyvault.KeyIdentifier; +import com.microsoft.azure.keyvault.KeyVaultClient; +import com.microsoft.azure.keyvault.authentication.KeyVaultCredentials; +import com.microsoft.azure.keyvault.cryptography.EcKey; +import com.microsoft.azure.keyvault.models.KeyBundle; +import com.microsoft.azure.keyvault.models.KeyOperationResult; +import com.microsoft.azure.keyvault.models.KeyVerifyResult; +import com.microsoft.azure.keyvault.requests.ImportKeyRequest; +import com.microsoft.azure.keyvault.webkey.*; +import com.microsoft.azure.management.resources.core.InterceptorManager; +import com.microsoft.azure.management.resources.core.TestBase; +import com.microsoft.azure.management.resources.fluentcore.utils.ResourceManagerThrottlingInterceptor; +import com.microsoft.azure.serializer.AzureJacksonAdapter; +import com.microsoft.rest.LogLevel; +import com.microsoft.rest.RestClient; +import com.microsoft.rest.credentials.ServiceClientCredentials; +import com.microsoft.rest.interceptors.LoggingInterceptor; +public class EcKeyIntegrationTests { + + private static TestBase.TestMode testMode = null; + + protected InterceptorManager interceptorManager = null; + + protected final static String ZERO_SUBSCRIPTION = "00000000-0000-0000-0000-000000000000"; + protected final static String ZERO_TENANT = "00000000-0000-0000-0000-000000000000"; + private static final String PLAYBACK_URI_BASE = "http://localhost:"; + private static final String PLAYBACK_VAULT = "https://test-vault.vault.azure.net"; + + protected static String playbackUri = null; + + static KeyVaultClient keyVaultClient; + + static String VAULT_URI; + + + @Rule + public TestName testName = new TestName(); + + + + @BeforeClass + public static void setUp() throws Exception { + initTestMode(); + initPlaybackUri(); + } + + @Before + public void beforeMethod() throws Exception { + + RestClient restClient; + ServiceClientCredentials credentials = createTestCredentials(); + interceptorManager = InterceptorManager.create(testName.getMethodName(), testMode); + + if (isRecordMode()) { + VAULT_URI = System.getenv("VAULT_URI"); + restClient = new RestClient.Builder().withBaseUrl("https://{vaultBaseUrl}") + .withSerializerAdapter(new AzureJacksonAdapter()) + .withResponseBuilderFactory(new AzureResponseBuilder.Factory()).withCredentials(credentials) + .withLogLevel(LogLevel.NONE) + .withNetworkInterceptor(new LoggingInterceptor(LogLevel.BODY_AND_HEADERS)) + .withNetworkInterceptor(interceptorManager.initInterceptor()) + .withInterceptor(new ResourceManagerThrottlingInterceptor()).build(); + + interceptorManager.addTextReplacementRule("https://management.azure.com/", playbackUri + "/"); + interceptorManager.addTextReplacementRule("https://graph.windows.net/", playbackUri + "/"); + interceptorManager.addTextReplacementRule(VAULT_URI, PLAYBACK_VAULT); + keyVaultClient = new KeyVaultClient(restClient); + } else { // is Playback Mode + VAULT_URI = PLAYBACK_VAULT; + restClient = new RestClient.Builder().withBaseUrl(playbackUri + "/") + .withSerializerAdapter(new AzureJacksonAdapter()) + .withResponseBuilderFactory(new AzureResponseBuilder.Factory()).withCredentials(credentials) + .withLogLevel(LogLevel.NONE) + .withNetworkInterceptor(new LoggingInterceptor(LogLevel.BODY_AND_HEADERS)) + .withNetworkInterceptor(interceptorManager.initInterceptor()) + .withInterceptor(new ResourceManagerThrottlingInterceptor()).build(); + keyVaultClient = new KeyVaultClient(restClient); + } + } + + @Test + public void testSignVerifyIntegrationES256() throws Exception { + validateSignVerifyInterop(importTestKey("itwkk-p256", P256TestKey()), JsonWebKeySignatureAlgorithm.ES256, "SHA-256"); + } + + @Test + public void testSignVerifyIntegrationES256K() throws Exception { + validateSignVerifyInterop(importTestKey("itwkk-p256k", P256KTestKey()), JsonWebKeySignatureAlgorithm.ES256K, "SHA-256"); + } + + @Test + public void testSignVerifyIntegrationES384() throws Exception { + validateSignVerifyInterop(importTestKey("itwkk-p384", P384TestKey()), JsonWebKeySignatureAlgorithm.ES384, "SHA-384"); + } + + @Test + public void testSignVerifyIntegrationES521() throws Exception { + validateSignVerifyInterop(importTestKey("itwkk-p521", P521TestKey()), JsonWebKeySignatureAlgorithm.ES512, "SHA-512"); + } + + private void validateSignVerifyInterop(JsonWebKey jwk, JsonWebKeySignatureAlgorithm algorithm, String digestAlg) + throws Exception { + + EcKey key = EcKey.fromJsonWebKey(jwk, true); + + KeyIdentifier keyId = new KeyIdentifier(jwk.kid()); + + // Test variables + byte[] plainText = new byte[100]; + new Random(0x1234567L).nextBytes(plainText); + MessageDigest md = MessageDigest.getInstance(digestAlg); + md.update(plainText); + byte[] digest = md.digest(); + + // sign with both the client and the service + byte[] clientSig = key.signAsync(digest, algorithm.toString()).get().getLeft(); + byte[] serverSig = keyVaultClient.sign(jwk.kid(), algorithm, digest).result(); + + // verify client signature with service and vice versa + Assert.assertTrue(keyVaultClient.verify(jwk.kid(), algorithm, digest, clientSig).value()); + Assert.assertTrue(key.verifyAsync(digest, serverSig, algorithm.toString()).get()); + + key.close(); + } + + // crv P_256 + // x 11232949079473245496693243696083285102762129989847161609854555188949850883563 + // y 1879583613806065892642092774705384015240844626261169536236224087556053896803 + // d 110376418358044062637363537183067346723507769076789115121629366563620220951085 + private static JsonWebKey P256TestKey() + { + return new JsonWebKey() + .withKty(JsonWebKeyType.EC) + .withKeyOps(Arrays.asList(JsonWebKeyOperation.SIGN, JsonWebKeyOperation.VERIFY)) + .withCrv(JsonWebKeyCurveName.P_256) + .withX(new BigInteger("11232949079473245496693243696083285102762129989847161609854555188949850883563").toByteArray()) + .withY(new BigInteger("1879583613806065892642092774705384015240844626261169536236224087556053896803").toByteArray()) + .withD(new BigInteger("110376418358044062637363537183067346723507769076789115121629366563620220951085").toByteArray()); + } + + + // crv P_256K + // x 112542251246878300879834909875895196605604676102246979012590954738722135052808 + // y 6774601013471644037178985795211162469224640637200491504041212042624768103421 + // d 5788737892080795185076661111780678315827024120706807264074833863296072596641 + private static JsonWebKey P256KTestKey() + { + return new JsonWebKey() + .withKty(JsonWebKeyType.EC) + .withKeyOps(Arrays.asList(JsonWebKeyOperation.SIGN, JsonWebKeyOperation.VERIFY)) + .withCrv(JsonWebKeyCurveName.P_256K) + .withX(new BigInteger("112542251246878300879834909875895196605604676102246979012590954738722135052808").toByteArray()) + .withY(new BigInteger("6774601013471644037178985795211162469224640637200491504041212042624768103421").toByteArray()) + .withD(new BigInteger("5788737892080795185076661111780678315827024120706807264074833863296072596641").toByteArray()); + } + + // crv P_384 + // x 25940251081638606466066580153999823282664621938556856505612612711663486152226861175055792115101185005519603532468591 + // y 38849021239011943917620782277253508239698260816711858953045039688987325246933521190178660888358757011735327467604293 + // d 32295109630567236352165497564914579106522760535338683398753720328854294758072198979189259927479998588892483377447907 + private static JsonWebKey P384TestKey() + { + return new JsonWebKey() + .withKty(JsonWebKeyType.EC) + .withKeyOps(Arrays.asList(JsonWebKeyOperation.SIGN, JsonWebKeyOperation.VERIFY)) + .withCrv(JsonWebKeyCurveName.P_384) + .withX(new BigInteger("25940251081638606466066580153999823282664621938556856505612612711663486152226861175055792115101185005519603532468591").toByteArray()) + .withY(new BigInteger("38849021239011943917620782277253508239698260816711858953045039688987325246933521190178660888358757011735327467604293").toByteArray()) + .withD(new BigInteger("32295109630567236352165497564914579106522760535338683398753720328854294758072198979189259927479998588892483377447907").toByteArray()); + } + + // crv P_521 + // x 6855414495738791694053590132729898471597826721317714885490415738464754554924249115378421758975070989210614663357146557161470466328735789754640064414018012235 + // y 3677272094599002495753508473603911533283562539125734660410262665439216117639982407670262587277222630266240230828668340712916997947964051679058455330395658230 + // d 1119526436113918255892609748222452225184162390267181240143092765692579316239102968513115940220551308699050504250027618944913182129917648549423125636042752861 + private static JsonWebKey P521TestKey() + { + return new JsonWebKey() + .withKty(JsonWebKeyType.EC) + .withKeyOps(Arrays.asList(JsonWebKeyOperation.SIGN, JsonWebKeyOperation.VERIFY)) + .withCrv(JsonWebKeyCurveName.P_521) + .withX(new BigInteger("6855414495738791694053590132729898471597826721317714885490415738464754554924249115378421758975070989210614663357146557161470466328735789754640064414018012235").toByteArray()) + .withY(new BigInteger("3677272094599002495753508473603911533283562539125734660410262665439216117639982407670262587277222630266240230828668340712916997947964051679058455330395658230").toByteArray()) + .withD(new BigInteger("1119526436113918255892609748222452225184162390267181240143092765692579316239102968513115940220551308699050504250027618944913182129917648549423125636042752861").toByteArray()); + } + + private static JsonWebKey importTestKey(String keyName, JsonWebKey jwk) throws Exception { + + KeyBundle keyBundle = keyVaultClient.importKey(VAULT_URI, keyName, jwk); + + return jwk.withKid(keyBundle.key().kid()); + } + + private static AuthenticationResult getAccessToken(String authorization, String resource) throws Exception { + + String clientId = System.getenv("arm.clientid"); + + if (clientId == null) { + throw new Exception("Please inform arm.clientid in the environment settings."); + } + + String clientKey = System.getenv("arm.clientkey"); + String username = System.getenv("arm.username"); + String password = System.getenv("arm.password"); + + AuthenticationResult result = null; + ExecutorService service = null; + try { + service = Executors.newFixedThreadPool(1); + AuthenticationContext context = new AuthenticationContext(authorization, false, service); + + Future future = null; + + if (clientKey != null && password == null) { + ClientCredential credentials = new ClientCredential(clientId, clientKey); + future = context.acquireToken(resource, credentials, null); + } + + if (password != null && clientKey == null) { + future = context.acquireToken(resource, clientId, username, password, null); + } + + if (future == null) { + throw new Exception( + "Missing or ambiguous credentials - please inform exactly one of arm.clientkey or arm.password in the environment settings."); + } + + result = future.get(); + } finally { + service.shutdown(); + } + + if (result == null) { + throw new RuntimeException("authentication result was null"); + } + return result; + } + + private static ServiceClientCredentials createTestCredentials() throws Exception { + return new KeyVaultCredentials() { + + @Override + public String doAuthenticate(String authorization, String resource, String scope) { + try { + if (isRecordMode()) { + AuthenticationResult authResult = getAccessToken(authorization, resource); + return authResult.getAccessToken(); + } else { + return ""; + } + } catch (Exception ex) { + throw new RuntimeException(ex); + } + } + }; + } + + @After + public void afterMethod() throws IOException { + interceptorManager.finalizeInterceptor(); + } + + private static void initPlaybackUri() throws IOException { + if (isPlaybackMode()) { + // 11080 and 11081 needs to be in sync with values in jetty.xml file + playbackUri = PLAYBACK_URI_BASE + "11080"; + } else { + playbackUri = PLAYBACK_URI_BASE + "1234"; + } + } + + public static boolean isPlaybackMode() { + if (testMode == null) + try { + initTestMode(); + } catch (IOException e) { + e.printStackTrace(); + throw new RuntimeException("Can't init test mode."); + } + return testMode == TestBase.TestMode.PLAYBACK; + } + + public static boolean isRecordMode() { + return !isPlaybackMode(); + } + + private static void initTestMode() throws IOException { + String azureTestMode = System.getenv("AZURE_TEST_MODE"); + if (azureTestMode != null) { + if (azureTestMode.equalsIgnoreCase("Record")) { + testMode = TestBase.TestMode.RECORD; + } else if (azureTestMode.equalsIgnoreCase("Playback")) { + testMode = TestBase.TestMode.PLAYBACK; + } else { + throw new IOException("Unknown AZURE_TEST_MODE: " + azureTestMode); + } + } else { + // System.out.print("Environment variable 'AZURE_TEST_MODE' has not been set + // yet. Using 'Playback' mode."); + testMode = TestBase.TestMode.PLAYBACK; + } + } + +} diff --git a/keyvault/data-plane/azure-keyvault-webkey/pom.xml b/keyvault/data-plane/azure-keyvault-webkey/pom.xml index 19c30eeed8a0..d5d6477ab41c 100644 --- a/keyvault/data-plane/azure-keyvault-webkey/pom.xml +++ b/keyvault/data-plane/azure-keyvault-webkey/pom.xml @@ -6,12 +6,12 @@ com.microsoft.azure azure-keyvault-parent - 1.1.2 + 1.2.0 ../pom.xml azure-keyvault-webkey - 1.1.2 + 1.2.0 jar Microsoft Azure SDK for Key Vault WebKey diff --git a/keyvault/data-plane/azure-keyvault/pom.xml b/keyvault/data-plane/azure-keyvault/pom.xml index 5d6a418f514d..05a6b2c736d4 100644 --- a/keyvault/data-plane/azure-keyvault/pom.xml +++ b/keyvault/data-plane/azure-keyvault/pom.xml @@ -6,12 +6,12 @@ the MIT License. See License.txt in the project root for license information. -- com.microsoft.azure azure-keyvault-parent - 1.1.2 + 1.2.0 ../pom.xml azure-keyvault - 1.1.2 + 1.2.0 jar Microsoft Azure SDK for Key Vault @@ -193,7 +193,7 @@ the MIT License. See License.txt in the project root for license information. -- com.microsoft.azure azure-client-authentication - [1.1.0,2.0.0) + 1.6.3 test diff --git a/keyvault/data-plane/azure-keyvault/src/main/java/com/microsoft/azure/keyvault/authentication/KeyVaultCredentials.java b/keyvault/data-plane/azure-keyvault/src/main/java/com/microsoft/azure/keyvault/authentication/KeyVaultCredentials.java index aeb7fa06f4b8..2402f9d99b29 100644 --- a/keyvault/data-plane/azure-keyvault/src/main/java/com/microsoft/azure/keyvault/authentication/KeyVaultCredentials.java +++ b/keyvault/data-plane/azure-keyvault/src/main/java/com/microsoft/azure/keyvault/authentication/KeyVaultCredentials.java @@ -11,9 +11,13 @@ import java.util.Map; import java.util.Arrays; import java.util.List; +import java.util.UUID; +import java.security.KeyPairGenerator; +import java.security.NoSuchAlgorithmException; import com.microsoft.rest.credentials.ServiceClientCredentials; import com.microsoft.azure.keyvault.messagesecurity.HttpMessageSecurity; +import com.microsoft.azure.keyvault.webkey.JsonWebKey; import okhttp3.HttpUrl; import okhttp3.Interceptor; @@ -34,9 +38,13 @@ public abstract class KeyVaultCredentials implements ServiceClientCredentials { private static final String WWW_AUTHENTICATE = "WWW-Authenticate"; private static final String BEARER_TOKEP_REFIX = "Bearer "; + private static final String CLIENT_ENCRYPTION_KEY_TYPE = "RSA"; + private static final int CLIENT_ENCRYPTION_KEY_SIZE = 2048; private List supportedMethods = Arrays.asList("sign", "verify", "encrypt", "decrypt", "wrapkey", "unwrapkey"); + private JsonWebKey clientEncryptionKey = null; + private final ChallengeCache cache = new ChallengeCache(); @Override @@ -96,6 +104,22 @@ private Pair buildAuthenticatedRequest(Request ori Map challengeMap) throws IOException { Boolean supportsPop = supportsMessageProtection(originalRequest.url().toString(), challengeMap); + + // if the service supports pop and a clientEncryptionKey has not been generated yet, generate + // the key that will be used for encryption on this and all subsequent protected requests + if (supportsPop && this.clientEncryptionKey == null) { + try { + final KeyPairGenerator generator = KeyPairGenerator.getInstance(CLIENT_ENCRYPTION_KEY_TYPE); + + generator.initialize(CLIENT_ENCRYPTION_KEY_SIZE); + + this.clientEncryptionKey = JsonWebKey.fromRSA(generator.generateKeyPair()).withKid(UUID.randomUUID().toString()); + + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } + } + AuthenticationResult authResult = getAuthenticationCredentials(supportsPop, challengeMap); if (authResult == null) { @@ -105,7 +129,8 @@ private Pair buildAuthenticatedRequest(Request ori HttpMessageSecurity httpMessageSecurity = new HttpMessageSecurity(authResult.getAuthToken(), supportsPop ? authResult.getPopKey() : "", supportsPop ? challengeMap.get("x-ms-message-encryption-key") : "", - supportsPop ? challengeMap.get("x-ms-message-signing-key") : ""); + supportsPop ? challengeMap.get("x-ms-message-signing-key") : "", + this.clientEncryptionKey); Request request = httpMessageSecurity.protectRequest(originalRequest); return Pair.of(request, httpMessageSecurity); diff --git a/keyvault/data-plane/azure-keyvault/src/main/java/com/microsoft/azure/keyvault/messagesecurity/HttpMessageSecurity.java b/keyvault/data-plane/azure-keyvault/src/main/java/com/microsoft/azure/keyvault/messagesecurity/HttpMessageSecurity.java index ae55d1ea721a..938ed984559a 100644 --- a/keyvault/data-plane/azure-keyvault/src/main/java/com/microsoft/azure/keyvault/messagesecurity/HttpMessageSecurity.java +++ b/keyvault/data-plane/azure-keyvault/src/main/java/com/microsoft/azure/keyvault/messagesecurity/HttpMessageSecurity.java @@ -62,7 +62,34 @@ public class HttpMessageSecurity { */ public HttpMessageSecurity(String clientSecurityToken, String clientSignatureKeyString, String serverEncryptionKeyString, String serverSignatureKeyString) throws IOException { + + this(clientSecurityToken, clientSignatureKeyString, serverEncryptionKeyString, serverSignatureKeyString, MessageSecurityHelper.generateJsonWebKey()); + } + + /** + * Constructor. + * + * @param clientSecurityToken + * pop or bearer authentication token. + * @param clientSignatureKeyString + * string with client signing key (public + private parts) or null if + * not supported + * @param serverEncryptionKeyString + * string with server encryption key (public only) or null if not + * supported + * @param serverSignatureKeyString + * string with server signing key (public only) or null if not + * supported + * @param clientEncryptionKey + * client encryption key (public + private parts) or null if + * not supported + * @throws IOException + * throws IOException + */ + public HttpMessageSecurity(String clientSecurityToken, String clientSignatureKeyString, + String serverEncryptionKeyString, String serverSignatureKeyString, JsonWebKey clientEncryptionKey) throws IOException { + this.clientSecurityToken = clientSecurityToken; if (clientSignatureKeyString != null && !clientSignatureKeyString.equals("")) { @@ -75,7 +102,7 @@ public HttpMessageSecurity(String clientSecurityToken, String clientSignatureKey this.serverEncryptionKey = MessageSecurityHelper.jsonWebKeyFromString(serverEncryptionKeyString); } - this.clientEncryptionKey = MessageSecurityHelper.generateJsonWebKey(); + this.clientEncryptionKey = clientEncryptionKey; } /** diff --git a/keyvault/data-plane/azure-keyvault/src/main/java/com/microsoft/azure/keyvault/messagesecurity/MessageSecurityHelper.java b/keyvault/data-plane/azure-keyvault/src/main/java/com/microsoft/azure/keyvault/messagesecurity/MessageSecurityHelper.java index 7ded26891a4d..a94f8b5db86d 100644 --- a/keyvault/data-plane/azure-keyvault/src/main/java/com/microsoft/azure/keyvault/messagesecurity/MessageSecurityHelper.java +++ b/keyvault/data-plane/azure-keyvault/src/main/java/com/microsoft/azure/keyvault/messagesecurity/MessageSecurityHelper.java @@ -104,7 +104,7 @@ public static JsonWebKey generateJsonWebKey() { return result; } catch (NoSuchAlgorithmException e) { // Unexpected. Should never be thrown. - return null; + throw new RuntimeException(e); } } diff --git a/keyvault/data-plane/pom.xml b/keyvault/data-plane/pom.xml index be8ddf0150ad..1dd1cd0efc2c 100644 --- a/keyvault/data-plane/pom.xml +++ b/keyvault/data-plane/pom.xml @@ -11,7 +11,7 @@ 1.0-SNAPSHOT ../../pom.client.xml - 1.1.2 + 1.2.0 azure-keyvault-parent pom @@ -95,27 +95,27 @@ com.microsoft.azure azure-keyvault-webkey - 1.1.2 + 1.2.0 com.microsoft.azure azure-keyvault-cryptography - 1.1.2 + 1.2.0 com.microsoft.azure azure-keyvault-core - 1.1.2 + 1.2.0 com.microsoft.azure azure-keyvault - 1.1.2 + 1.2.0 com.microsoft.azure azure-keyvault-extensions - 1.1.2 + 1.2.0