From 9c8ab0339cd1b0a6946e612b2af2531c6ff77e19 Mon Sep 17 00:00:00 2001 From: Vinay Gera Date: Fri, 6 Dec 2019 21:22:17 +0530 Subject: [PATCH] Kv keys updates (#6657) Kv Keys update --- .../resources/spotbugs/spotbugs-exclude.xml | 5 + .../azure-security-keyvault-keys/CHANGELOG.md | 7 +- .../azure-security-keyvault-keys/pom.xml | 6 - .../cryptography/CryptographyAsyncClient.java | 27 +- .../cryptography/CryptographyService.java | 26 ++ .../CryptographyServiceClient.java | 62 +++- .../cryptography/KeyEncryptionKeyClient.java | 4 + .../keyvault/keys/cryptography/SecretKey.java | 128 +++++++ .../keys/cryptography/SecretProperties.java | 335 ++++++++++++++++++ .../cryptography/SecretRequestAttributes.java | 171 +++++++++ .../cryptography/SecretRequestParameters.java | 119 +++++++ .../keys/cryptography/SignatureEncoding.java | 22 +- .../models/Base64UrlJsonDeserializer.java | 6 +- .../keys/models/Base64UrlJsonSerializer.java | 8 +- .../keyvault/keys/models/KeyProperties.java | 34 +- .../src/main/java/module-info.java | 1 - .../CryptographyClientTestBase.java | 3 +- .../KeyEncryptionKeyClientTest.java | 61 ++++ .../KeyEncryptionKeyClientTestBase.java | 181 ++++++++++ .../wrapUnwrapSymmetricAK128.json | 60 ++++ .../wrapUnwrapSymmetricAK192.json | 60 ++++ 21 files changed, 1282 insertions(+), 44 deletions(-) create mode 100644 sdk/keyvault/azure-security-keyvault-keys/src/main/java/com/azure/security/keyvault/keys/cryptography/SecretKey.java create mode 100644 sdk/keyvault/azure-security-keyvault-keys/src/main/java/com/azure/security/keyvault/keys/cryptography/SecretProperties.java create mode 100644 sdk/keyvault/azure-security-keyvault-keys/src/main/java/com/azure/security/keyvault/keys/cryptography/SecretRequestAttributes.java create mode 100644 sdk/keyvault/azure-security-keyvault-keys/src/main/java/com/azure/security/keyvault/keys/cryptography/SecretRequestParameters.java create mode 100644 sdk/keyvault/azure-security-keyvault-keys/src/test/java/com/azure/security/keyvault/keys/cryptography/KeyEncryptionKeyClientTest.java create mode 100644 sdk/keyvault/azure-security-keyvault-keys/src/test/java/com/azure/security/keyvault/keys/cryptography/KeyEncryptionKeyClientTestBase.java create mode 100644 sdk/keyvault/azure-security-keyvault-keys/src/test/resources/session-records/wrapUnwrapSymmetricAK128.json create mode 100644 sdk/keyvault/azure-security-keyvault-keys/src/test/resources/session-records/wrapUnwrapSymmetricAK192.json diff --git a/eng/code-quality-reports/src/main/resources/spotbugs/spotbugs-exclude.xml b/eng/code-quality-reports/src/main/resources/spotbugs/spotbugs-exclude.xml index 42061ffd305c0..78823a5fbb881 100755 --- a/eng/code-quality-reports/src/main/resources/spotbugs/spotbugs-exclude.xml +++ b/eng/code-quality-reports/src/main/resources/spotbugs/spotbugs-exclude.xml @@ -492,6 +492,7 @@ + @@ -531,6 +532,10 @@ + + + + diff --git a/sdk/keyvault/azure-security-keyvault-keys/CHANGELOG.md b/sdk/keyvault/azure-security-keyvault-keys/CHANGELOG.md index 8d62d5dcedb44..1307e65fc5c96 100644 --- a/sdk/keyvault/azure-security-keyvault-keys/CHANGELOG.md +++ b/sdk/keyvault/azure-security-keyvault-keys/CHANGELOG.md @@ -1,5 +1,10 @@ # Release History -## 4.0.0 (2019-10-31) +## 4.0.1 (2019-12-06) + +### Major changes +- `KeyEncryptionKeyClientBuilder.buildKeyEncryptionKey` and `KeyEncryptionKeyClientBuilder.buildAsyncKeyEncryptionKey`supports consumption of a secret id representing the symmetric key stored in the Key Vault as a secret. +- Dropped third party dependency on apache commons codec library. + ### Breaking changes - Key has been renamed to KeyVaultKey to avoid ambiguity with other libraries and to yield better search results. diff --git a/sdk/keyvault/azure-security-keyvault-keys/pom.xml b/sdk/keyvault/azure-security-keyvault-keys/pom.xml index 4147223c8eb6c..07c875219e47a 100644 --- a/sdk/keyvault/azure-security-keyvault-keys/pom.xml +++ b/sdk/keyvault/azure-security-keyvault-keys/pom.xml @@ -46,12 +46,6 @@ 1.1.0 - - commons-codec - commons-codec - 1.13 - - org.junit.jupiter diff --git a/sdk/keyvault/azure-security-keyvault-keys/src/main/java/com/azure/security/keyvault/keys/cryptography/CryptographyAsyncClient.java b/sdk/keyvault/azure-security-keyvault-keys/src/main/java/com/azure/security/keyvault/keys/cryptography/CryptographyAsyncClient.java index 3ae35f0480eed..8143a33328c3b 100644 --- a/sdk/keyvault/azure-security-keyvault-keys/src/main/java/com/azure/security/keyvault/keys/cryptography/CryptographyAsyncClient.java +++ b/sdk/keyvault/azure-security-keyvault-keys/src/main/java/com/azure/security/keyvault/keys/cryptography/CryptographyAsyncClient.java @@ -55,11 +55,13 @@ @ServiceClient(builder = CryptographyClientBuilder.class, isAsync = true, serviceInterfaces = CryptographyService.class) public class CryptographyAsyncClient { static final String KEY_VAULT_SCOPE = "https://vault.azure.net/.default"; + static final String SECRETS_COLLECTION = "secrets"; JsonWebKey key; private final CryptographyService service; - private final CryptographyServiceClient cryptographyServiceClient; + private CryptographyServiceClient cryptographyServiceClient; private LocalKeyCryptographyClient localKeyCryptographyClient; private final ClientLogger logger = new ClientLogger(CryptographyAsyncClient.class); + private String keyCollection; /** * Creates a CryptographyAsyncClient that uses {@code pipeline} to service requests @@ -168,6 +170,14 @@ Mono> getKeyWithResponse(Context context) { return cryptographyServiceClient.getKey(context); } + Mono getSecretKey() { + try { + return withContext(context -> cryptographyServiceClient.getSecretKey(context)).flatMap(FluxUtil::toMono); + } catch (RuntimeException ex) { + return monoError(logger, ex); + } + } + /** * Encrypts an arbitrary sequence of bytes using the configured key. Note that the encrypt operation only supports a * single block of data, the size of which is dependent on the target key and the encryption algorithm to be used. @@ -591,6 +601,7 @@ private void unpackAndValidateId(String keyId) { String endpoint = url.getProtocol() + "://" + url.getHost(); String keyName = (tokens.length >= 3 ? tokens[2] : null); String version = (tokens.length >= 4 ? tokens[3] : null); + this.keyCollection = (tokens.length >= 2 ? tokens[1] : null); if (Strings.isNullOrEmpty(endpoint)) { throw logger.logExceptionAsError(new IllegalArgumentException("Key endpoint in key id is invalid")); } else if (Strings.isNullOrEmpty(keyName)) { @@ -609,10 +620,14 @@ private boolean checkKeyPermissions(List operations, KeyOperation private boolean ensureValidKeyAvailable() { boolean keyAvailableLocally = true; - if (this.key == null) { + if (this.key == null && keyCollection != null) { try { - KeyVaultKey keyVaultKey = getKey().block(); - this.key = keyVaultKey.getKey(); + if (keyCollection.equals(SECRETS_COLLECTION)) { + this.key = getSecretKey().block(); + } else { + KeyVaultKey keyVaultKey = getKey().block(); + this.key = keyVaultKey.getKey(); + } keyAvailableLocally = this.key.isValid(); initializeCryptoClients(); } catch (HttpResponseException | NullPointerException e) { @@ -627,4 +642,8 @@ private boolean ensureValidKeyAvailable() { CryptographyServiceClient getCryptographyServiceClient() { return cryptographyServiceClient; } + + void setCryptographyServiceClient(CryptographyServiceClient serviceClient) { + this.cryptographyServiceClient = serviceClient; + } } diff --git a/sdk/keyvault/azure-security-keyvault-keys/src/main/java/com/azure/security/keyvault/keys/cryptography/CryptographyService.java b/sdk/keyvault/azure-security-keyvault-keys/src/main/java/com/azure/security/keyvault/keys/cryptography/CryptographyService.java index c8943ae83226a..1ae31cec43599 100644 --- a/sdk/keyvault/azure-security-keyvault-keys/src/main/java/com/azure/security/keyvault/keys/cryptography/CryptographyService.java +++ b/sdk/keyvault/azure-security-keyvault-keys/src/main/java/com/azure/security/keyvault/keys/cryptography/CryptographyService.java @@ -3,6 +3,7 @@ package com.azure.security.keyvault.keys.cryptography; +import com.azure.core.annotation.Put; import com.azure.core.exception.HttpResponseException; import com.azure.core.exception.ResourceModifiedException; import com.azure.core.exception.ResourceNotFoundException; @@ -130,4 +131,29 @@ Mono> getKey(@HostParam("url") String url, @HeaderParam("accept-language") String acceptLanguage, @HeaderParam("Content-Type") String type, Context context); + + @Get("secrets/{secret-name}/{secret-version}") + @ExpectedResponses({200}) + @UnexpectedResponseExceptionType(code = {404}, value = ResourceNotFoundException.class) + @UnexpectedResponseExceptionType(code = {403}, value = ResourceModifiedException.class) + @UnexpectedResponseExceptionType(HttpResponseException.class) + Mono> getSecret(@HostParam("url") String url, + @PathParam("secret-name") String keyName, + @PathParam("secret-version") String keyVersion, + @QueryParam("api-version") String apiVersion, + @HeaderParam("accept-language") String acceptLanguage, + @HeaderParam("Content-Type") String type, + Context context); + + @Put("secrets/{secret-name}") + @ExpectedResponses({200}) + @UnexpectedResponseExceptionType(code = {400}, value = ResourceModifiedException.class) + @UnexpectedResponseExceptionType(HttpResponseException.class) + Mono> setSecret(@HostParam("url") String url, + @PathParam("secret-name") String secretName, + @QueryParam("api-version") String apiVersion, + @HeaderParam("accept-language") String acceptLanguage, + @BodyParam("body") SecretRequestParameters parameters, + @HeaderParam("Content-Type") String type, + Context context); } diff --git a/sdk/keyvault/azure-security-keyvault-keys/src/main/java/com/azure/security/keyvault/keys/cryptography/CryptographyServiceClient.java b/sdk/keyvault/azure-security-keyvault-keys/src/main/java/com/azure/security/keyvault/keys/cryptography/CryptographyServiceClient.java index 5c6f6eea0494f..5b5ce913eb42c 100644 --- a/sdk/keyvault/azure-security-keyvault-keys/src/main/java/com/azure/security/keyvault/keys/cryptography/CryptographyServiceClient.java +++ b/sdk/keyvault/azure-security-keyvault-keys/src/main/java/com/azure/security/keyvault/keys/cryptography/CryptographyServiceClient.java @@ -4,6 +4,7 @@ package com.azure.security.keyvault.keys.cryptography; import com.azure.core.http.rest.Response; +import com.azure.core.http.rest.SimpleResponse; import com.azure.core.util.Context; import com.azure.core.util.logging.ClientLogger; import com.azure.security.keyvault.keys.cryptography.models.DecryptResult; @@ -15,13 +16,22 @@ import com.azure.security.keyvault.keys.cryptography.models.SignResult; import com.azure.security.keyvault.keys.cryptography.models.VerifyResult; import com.azure.security.keyvault.keys.cryptography.models.WrapResult; +import com.azure.security.keyvault.keys.models.JsonWebKey; +import com.azure.security.keyvault.keys.models.KeyOperation; +import com.azure.security.keyvault.keys.models.KeyType; import com.azure.security.keyvault.keys.models.KeyVaultKey; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; import reactor.core.publisher.Mono; import java.net.MalformedURLException; import java.net.URL; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; +import java.util.Base64; import java.util.Objects; class CryptographyServiceClient { @@ -57,6 +67,56 @@ private Mono> getKey(String name, String version, Context .doOnError(error -> logger.warning("Failed to get key - {}", name, error)); } + Mono> getSecretKey(Context context) { + return service.getSecret(vaultUrl, keyName, version, API_VERSION, ACCEPT_LANGUAGE, CONTENT_TYPE_HEADER_VALUE, context) + .doOnRequest(ignored -> logger.info("Retrieving key - {}", keyName)) + .doOnSuccess(response -> logger.info("Retrieved key - {}", response.getValue().getName())) + .doOnError(error -> logger.warning("Failed to get key - {}", keyName, error)) + .flatMap((stringResponse -> { + KeyVaultKey key = null; + try { + return Mono.just(new SimpleResponse<>(stringResponse.getRequest(), + stringResponse.getStatusCode(), + stringResponse.getHeaders(), transformSecretKey(stringResponse.getValue()))); + } catch (JsonProcessingException e) { + return Mono.error(e); + } + })); + } + + Mono> setSecretKey(SecretKey secret, Context context) { + Objects.requireNonNull(secret, "The Secret input parameter cannot be null."); + SecretRequestParameters parameters = new SecretRequestParameters() + .setValue(secret.getValue()) + .setTags(secret.getProperties().getTags()) + .setContentType(secret.getProperties().getContentType()) + .setSecretAttributes(new SecretRequestAttributes(secret.getProperties())); + + return service.setSecret(vaultUrl, secret.getName(), API_VERSION, ACCEPT_LANGUAGE, parameters, + CONTENT_TYPE_HEADER_VALUE, context) + .doOnRequest(ignored -> logger.info("Setting secret - {}", secret.getName())) + .doOnSuccess(response -> logger.info("Set secret - {}", response.getValue().getName())) + .doOnError(error -> logger.warning("Failed to set secret - {}", secret.getName(), error)); + } + + JsonWebKey transformSecretKey(SecretKey secretKey) throws JsonProcessingException { + ObjectMapper mapper = new ObjectMapper(); + JsonNode rootNode = mapper.createObjectNode(); + ArrayNode a = mapper.createArrayNode(); + a.add(KeyOperation.WRAP_KEY.toString()); + a.add(KeyOperation.UNWRAP_KEY.toString()); + a.add(KeyOperation.ENCRYPT.toString()); + a.add(KeyOperation.DECRYPT.toString()); + + ((ObjectNode) rootNode).put("k", Base64.getUrlDecoder().decode(secretKey.getValue())); + ((ObjectNode) rootNode).put("kid", this.keyId); + ((ObjectNode) rootNode).put("kty", KeyType.OCT.toString()); + ((ObjectNode) rootNode).put("key_ops", a); + + String jsonString = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(rootNode); + return mapper.readValue(jsonString, JsonWebKey.class); + } + Mono encrypt(EncryptionAlgorithm algorithm, byte[] plaintext, Context context) { KeyOperationParameters parameters = new KeyOperationParameters().setAlgorithm(algorithm).setValue(plaintext); @@ -176,6 +236,4 @@ private void unpackId(String keyId) { } } } - - } diff --git a/sdk/keyvault/azure-security-keyvault-keys/src/main/java/com/azure/security/keyvault/keys/cryptography/KeyEncryptionKeyClient.java b/sdk/keyvault/azure-security-keyvault-keys/src/main/java/com/azure/security/keyvault/keys/cryptography/KeyEncryptionKeyClient.java index 40d11864167b7..494bfad933734 100644 --- a/sdk/keyvault/azure-security-keyvault-keys/src/main/java/com/azure/security/keyvault/keys/cryptography/KeyEncryptionKeyClient.java +++ b/sdk/keyvault/azure-security-keyvault-keys/src/main/java/com/azure/security/keyvault/keys/cryptography/KeyEncryptionKeyClient.java @@ -43,4 +43,8 @@ public byte[] wrapKey(String algorithm, byte[] key) { public byte[] unwrapKey(String algorithm, byte[] encryptedKey) { return client.unwrapKey(algorithm, encryptedKey).block(); } + + KeyEncryptionKeyAsyncClient getKeyEncryptionKeyAsyncClient() { + return client; + } } diff --git a/sdk/keyvault/azure-security-keyvault-keys/src/main/java/com/azure/security/keyvault/keys/cryptography/SecretKey.java b/sdk/keyvault/azure-security-keyvault-keys/src/main/java/com/azure/security/keyvault/keys/cryptography/SecretKey.java new file mode 100644 index 0000000000000..1164c92f91c85 --- /dev/null +++ b/sdk/keyvault/azure-security-keyvault-keys/src/main/java/com/azure/security/keyvault/keys/cryptography/SecretKey.java @@ -0,0 +1,128 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.security.keyvault.keys.cryptography; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.Map; +import java.util.Objects; + + +class SecretKey { + + /* + * The value of the secret. + */ + @JsonProperty(value = "value") + private String value; + + /* + * The secret properties. + */ + private SecretProperties properties; + + /* + * Creates an empty instance of the Secret. + */ + SecretKey() { + properties = new SecretProperties(); + } + + /* + * Creates a Secret with {@code name} and {@code value}. + * + * @param name The name of the secret. + * @param value the value of the secret. + */ + SecretKey(String name, String value) { + properties = new SecretProperties(name); + this.value = value; + } + + /* + * Get the value of the secret. + * + * @return the secret value + */ + String getValue() { + return this.value; + } + + /* + * Get the secret identifier. + * + * @return the secret identifier. + */ + String getId() { + return properties.getId(); + } + + /* + * Get the secret name. + * + * @return the secret name. + */ + String getName() { + return properties.getName(); + } + + /* + * Get the secret properties + * @return the Secret properties + */ + SecretProperties getProperties() { + return this.properties; + } + + /* + * Set the secret properties + * @param properties The Secret properties + * @throws NullPointerException if {@code properties} is null. + * @return the updated secret key object + */ + SecretKey setProperties(SecretProperties properties) { + Objects.requireNonNull(properties); + properties.name = this.properties.name; + this.properties = properties; + return this; + } + + @JsonProperty(value = "id") + private void unpackId(String id) { + properties.unpackId(id); + } + + /* + * Unpacks the attributes json response and updates the variables in the Secret Attributes object. + * Uses Lazy Update to set values for variables id, tags, contentType, managed and keyId as these variables are + * part of main json body and not attributes json body when the secret response comes from list Secrets operations. + * @param attributes The key value mapping of the Secret attributes + */ + @JsonProperty("attributes") + @SuppressWarnings("unchecked") + private void unpackAttributes(Map attributes) { + properties.unpackAttributes(attributes); + } + + @JsonProperty("managed") + private void unpackManaged(Boolean managed) { + properties.managed = managed; + } + + @JsonProperty("kid") + private void unpackKid(String kid) { + properties.keyId = kid; + } + + @JsonProperty("contentType") + private void unpackContentType(String contentType) { + properties.contentType = contentType; + } + + @JsonProperty("tags") + private void unpackTags(Map tags) { + properties.tags = tags; + } +} + diff --git a/sdk/keyvault/azure-security-keyvault-keys/src/main/java/com/azure/security/keyvault/keys/cryptography/SecretProperties.java b/sdk/keyvault/azure-security-keyvault-keys/src/main/java/com/azure/security/keyvault/keys/cryptography/SecretProperties.java new file mode 100644 index 0000000000000..47f272b40f15b --- /dev/null +++ b/sdk/keyvault/azure-security-keyvault-keys/src/main/java/com/azure/security/keyvault/keys/cryptography/SecretProperties.java @@ -0,0 +1,335 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.security.keyvault.keys.cryptography; + +import com.azure.core.util.logging.ClientLogger; +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.net.MalformedURLException; +import java.net.URL; +import java.time.Instant; +import java.time.OffsetDateTime; +import java.time.ZoneOffset; +import java.util.Map; +import java.util.Objects; + + +class SecretProperties { + private final ClientLogger logger = new ClientLogger(SecretProperties.class); + + /* + * The secret id. + */ + String id; + + /* + * The secret version. + */ + String version; + + /* + * Determines whether the object is enabled. + */ + Boolean enabled; + + /* + * Not before date in UTC. + */ + OffsetDateTime notBefore; + + /* + * Expiry date in UTC. + */ + OffsetDateTime expiresOn; + + /* + * Creation time in UTC. + */ + OffsetDateTime createdOn; + + /* + * Last updated time in UTC. + */ + OffsetDateTime updatedOn; + + /* + * The secret name. + */ + String name; + + /* + * Reflects the deletion recovery level currently in effect for secrets in + * the current vault. If it contains 'Purgeable', the secret can be + * permanently deleted by a privileged user; otherwise, only the system can + * purge the secret, at the end of the retention interval. Possible values + * include: 'Purgeable', 'Recoverable+Purgeable', 'Recoverable', + * 'Recoverable+ProtectedSubscription'. + */ + String recoveryLevel; + + /* + * The content type of the secret. + */ + @JsonProperty(value = "contentType") + String contentType; + + /* + * Application specific metadata in the form of key-value pairs. + */ + @JsonProperty(value = "tags") + Map tags; + + /* + * If this is a secret backing a KV certificate, then this field specifies + * the corresponding key backing the KV certificate. + */ + @JsonProperty(value = "kid", access = JsonProperty.Access.WRITE_ONLY) + String keyId; + + /* + * True if the secret's lifetime is managed by key vault. If this is a + * secret backing a certificate, then managed will be true. + */ + @JsonProperty(value = "managed", access = JsonProperty.Access.WRITE_ONLY) + Boolean managed; + + SecretProperties(String secretName) { + this.name = secretName; + } + + /* + * Creates empty instance of SecretProperties. + */ + SecretProperties() { } + + /* + * Get the secret name. + * + * @return the name of the secret. + */ + String getName() { + return this.name; + } + + /* + * Get the recovery level of the secret. + + * @return the recoveryLevel of the secret. + */ + String getRecoveryLevel() { + return recoveryLevel; + } + + /* + * Get the enabled value. + * + * @return the enabled value + */ + Boolean isEnabled() { + return this.enabled; + } + + /* + * Set the enabled value. + * + * @param enabled The enabled value to set + * @throws NullPointerException if {@code enabled} is null. + * @return the SecretProperties object itself. + */ + SecretProperties setEnabled(Boolean enabled) { + Objects.requireNonNull(enabled); + this.enabled = enabled; + return this; + } + + /* + * Get the notBefore UTC time. + * + * @return the notBefore UTC time. + */ + OffsetDateTime getNotBefore() { + return notBefore; + } + + /* + * Set the {@link OffsetDateTime notBefore} UTC time. + * + * @param notBefore The notBefore UTC time to set + * @return the SecretProperties object itself. + */ + SecretProperties setNotBefore(OffsetDateTime notBefore) { + this.notBefore = notBefore; + return this; + } + + /* + * Get the Secret Expiry time in UTC. + * + * @return the expires UTC time. + */ + OffsetDateTime getExpiresOn() { + if (this.expiresOn == null) { + return null; + } + return this.expiresOn; + } + + /* + * Set the {@link OffsetDateTime expires} UTC time. + * + * @param expiresOn The expiry time to set for the secret. + * @return the SecretProperties object itself. + */ + SecretProperties setExpiresOn(OffsetDateTime expiresOn) { + this.expiresOn = expiresOn; + return this; + } + + /* + * Get the the UTC time at which secret was created. + * + * @return the created UTC time. + */ + OffsetDateTime getCreatedOn() { + return createdOn; + } + + /* + * Get the UTC time at which secret was last updated. + * + * @return the last updated UTC time. + */ + OffsetDateTime getUpdatedOn() { + return updatedOn; + } + + /* + * Get the secret identifier. + * + * @return the secret identifier. + */ + String getId() { + return this.id; + } + + /* + * Get the content type. + * + * @return the content type. + */ + String getContentType() { + return this.contentType; + } + + /* + * Set the contentType. + * + * @param contentType The contentType to set + * @return the updated SecretProperties object itself. + */ + SecretProperties setContentType(String contentType) { + this.contentType = contentType; + return this; + } + + /* + * Get the tags associated with the secret. + * + * @return the value of the tags. + */ + Map getTags() { + return this.tags; + } + + /* + * Set the tags to be associated with the secret. + * + * @param tags The tags to set + * @return the updated SecretProperties object itself. + */ + SecretProperties setTags(Map tags) { + this.tags = tags; + return this; + } + + /* + * Get the keyId identifier. + * + * @return the keyId identifier. + */ + String getKeyId() { + return this.keyId; + } + + /* + * Get the managed value. + * + * @return the managed value + */ + Boolean isManaged() { + return this.managed; + } + + /* + * Get the version of the secret. + * + * @return the version of the secret. + */ + String getVersion() { + return this.version; + } + + /* + * Unpacks the attributes json response and updates the variables in the Secret Attributes object. + * Uses Lazy Update to set values for variables id, tags, contentType, managed and keyId as these variables are + * part of main json body and not attributes json body when the secret response comes from list Secrets operations. + * @param attributes The key value mapping of the Secret attributes + */ + @JsonProperty("attributes") + @SuppressWarnings("unchecked") + void unpackAttributes(Map attributes) { + this.enabled = (Boolean) attributes.get("enabled"); + this.notBefore = epochToOffsetDateTime(attributes.get("nbf")); + this.expiresOn = epochToOffsetDateTime(attributes.get("exp")); + this.createdOn = epochToOffsetDateTime(attributes.get("created")); + this.updatedOn = epochToOffsetDateTime(attributes.get("updated")); + this.recoveryLevel = (String) attributes.get("recoveryLevel"); + this.contentType = (String) lazyValueSelection(attributes.get("contentType"), this.contentType); + this.keyId = (String) lazyValueSelection(attributes.get("keyId"), this.keyId); + this.tags = (Map) lazyValueSelection(attributes.get("tags"), this.tags); + this.managed = (Boolean) lazyValueSelection(attributes.get("managed"), this.managed); + unpackId((String) attributes.get("id")); + } + + @JsonProperty(value = "id") + void unpackId(String id) { + if (id != null && id.length() > 0) { + this.id = id; + try { + URL url = new URL(id); + String[] tokens = url.getPath().split("/"); + this.name = (tokens.length >= 3 ? tokens[2] : null); + this.version = (tokens.length >= 4 ? tokens[3] : null); + } catch (MalformedURLException e) { + // Should never come here. + logger.error("Received Malformed Secret Id URL from KV Service"); + } + } + } + + private OffsetDateTime epochToOffsetDateTime(Object epochValue) { + if (epochValue != null) { + Instant instant = Instant.ofEpochMilli(((Number) epochValue).longValue() * 1000L); + return OffsetDateTime.ofInstant(instant, ZoneOffset.UTC); + } + return null; + } + + private Object lazyValueSelection(Object input1, Object input2) { + if (input1 == null) { + return input2; + } + return input1; + } +} diff --git a/sdk/keyvault/azure-security-keyvault-keys/src/main/java/com/azure/security/keyvault/keys/cryptography/SecretRequestAttributes.java b/sdk/keyvault/azure-security-keyvault-keys/src/main/java/com/azure/security/keyvault/keys/cryptography/SecretRequestAttributes.java new file mode 100644 index 0000000000000..f35dfd2c111b7 --- /dev/null +++ b/sdk/keyvault/azure-security-keyvault-keys/src/main/java/com/azure/security/keyvault/keys/cryptography/SecretRequestAttributes.java @@ -0,0 +1,171 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.security.keyvault.keys.cryptography; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.time.Instant; +import java.time.OffsetDateTime; +import java.time.ZoneOffset; + +/* + * The object attributes managed by the Cryptography service. + */ +class SecretRequestAttributes { + + /* + * Creates an instance of SecretRequestAttributes. Reads secretProperties.notBefore, secretProperties.expires and + * secretProperties.enabled fields from {@code secretProperties} + * @param secretProperties the {@link SecretProperties} object with populated attributes + */ + SecretRequestAttributes(SecretProperties secretProperties) { + if (secretProperties.getNotBefore() != null) { + this.notBefore = secretProperties.getNotBefore().toEpochSecond(); + } + if (secretProperties.getExpiresOn() != null) { + this.expires = secretProperties.getExpiresOn().toEpochSecond(); + } + this.enabled = secretProperties.isEnabled(); + } + + /* + * The secret value. + */ + @JsonProperty(value = "value") + private String value; + + /* + * The secret id. + */ + @JsonProperty(value = "id") + private String id; + + /* + * Determines whether the object is enabled. + */ + @JsonProperty(value = "enabled") + private Boolean enabled; + + /* + * Not before date in UTC. + */ + @JsonProperty(value = "nbf") + private Long notBefore; + + /* + * Expiry date in UTC. + */ + @JsonProperty(value = "exp") + private Long expires; + + /* + * Creation time in UTC. + */ + @JsonProperty(value = "created", access = JsonProperty.Access.WRITE_ONLY) + private Long created; + + /* + * Last updated time in UTC. + */ + @JsonProperty(value = "updated", access = JsonProperty.Access.WRITE_ONLY) + private Long updated; + + /* + * Get the enabled value. + * + * @return the enabled value + */ + public Boolean isEnabled() { + return this.enabled; + } + + /* + * Set the enabled value. + * + * @param enabled the enabled value to set + * @return the Attributes object itself. + */ + public SecretRequestAttributes getEnabled(Boolean enabled) { + this.enabled = enabled; + return this; + } + + /* + * Get the notBefore value. + * + * @return the notBefore value + */ + public OffsetDateTime getNotBefore() { + if (this.notBefore == null) { + return null; + } + return OffsetDateTime.ofInstant(Instant.ofEpochMilli(this.notBefore * 1000L), ZoneOffset.UTC); + } + + /* + * Set the notBefore value. + * + * @param notBefore the notBefore value to set + * @return the Attributes object itself. + */ + public SecretRequestAttributes setNotBefore(OffsetDateTime notBefore) { + if (notBefore == null) { + this.notBefore = null; + } else { + this.notBefore = OffsetDateTime.ofInstant(notBefore.toInstant(), ZoneOffset.UTC).toEpochSecond(); + } + return this; + } + + /* + * Get the expires value. + * + * @return the expires value + */ + public OffsetDateTime getExpires() { + if (this.expires == null) { + return null; + } + return OffsetDateTime.ofInstant(Instant.ofEpochMilli(this.expires * 1000L), ZoneOffset.UTC); + } + + /* + * Set the expires value. + * + * @param expires the expires value to set + * @return the Attributes object itself. + */ + public SecretRequestAttributes setExpires(OffsetDateTime expires) { + if (expires == null) { + this.expires = null; + } else { + this.expires = OffsetDateTime.ofInstant(expires.toInstant(), ZoneOffset.UTC).toEpochSecond(); + } + return this; + } + + /* + * Get the created value. + * + * @return the created value + */ + public OffsetDateTime getCreated() { + if (this.created == null) { + return null; + } + return OffsetDateTime.ofInstant(Instant.ofEpochMilli(this.created * 1000L), ZoneOffset.UTC); + } + + /* + * Get the updated value. + * + * @return the updated value + */ + public OffsetDateTime getUpdated() { + if (this.updated == null) { + return null; + } + return OffsetDateTime.ofInstant(Instant.ofEpochMilli(this.updated * 1000L), ZoneOffset.UTC); + } +} diff --git a/sdk/keyvault/azure-security-keyvault-keys/src/main/java/com/azure/security/keyvault/keys/cryptography/SecretRequestParameters.java b/sdk/keyvault/azure-security-keyvault-keys/src/main/java/com/azure/security/keyvault/keys/cryptography/SecretRequestParameters.java new file mode 100644 index 0000000000000..3ec66a786e7f6 --- /dev/null +++ b/sdk/keyvault/azure-security-keyvault-keys/src/main/java/com/azure/security/keyvault/keys/cryptography/SecretRequestParameters.java @@ -0,0 +1,119 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.security.keyvault.keys.cryptography; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.Map; + + +/* + * Represents a set of request options used in REST requests intitiated by Cryptography service. + */ +class SecretRequestParameters { + /* + * The value of the secret. + */ + @JsonProperty(value = "value", required = true) + private String value; + + /* + * Application specific metadata in the form of key-value pairs. + */ + @JsonProperty(value = "tags") + private Map tags; + + /* + * Type of the secret value such as a password. + */ + @JsonProperty(value = "contentType") + private String contentType; + + /* + * The secret management attributes. + */ + @JsonProperty(value = "attributes") + private SecretRequestAttributes secretRequestAttributes; + + /* + * Get the value value. + * + * @return the value value + */ + public String getValue() { + return this.value; + } + + /* + * Set the value value. + * + * @param value the value value to set + * @return the SecretRequestParameters object itself. + */ + public SecretRequestParameters setValue(String value) { + this.value = value; + return this; + } + + /* + * Get the tags value. + * + * @return the tags value + */ + public Map getTags() { + return this.tags; + } + + /* + * Set the tags value. + * + * @param tags the tags value to set + * @return the SecretRequestParameters object itself. + */ + public SecretRequestParameters setTags(Map tags) { + this.tags = tags; + return this; + } + + /* + * Get the contentType value. + * + * @return the contentType value + */ + public String getContentType() { + return this.contentType; + } + + /* + * Set the contentType value. + * + * @param contentType the contentType value to set + * @return the SecretRequestParameters object itself. + */ + public SecretRequestParameters setContentType(String contentType) { + this.contentType = contentType; + return this; + } + + /* + * Get the secretRequestAttributes value. + * + * @return the SecretRequestAttributes value + */ + public SecretRequestAttributes getSecretAttributes() { + return this.secretRequestAttributes; + } + + /* + * Set the secretRequestAttributes value. + * + * @param secretRequestAttributes the secretRequestAttributes to set + * @return the SecretRequestParameters object itself. + */ + public SecretRequestParameters setSecretAttributes(SecretRequestAttributes secretRequestAttributes) { + this.secretRequestAttributes = secretRequestAttributes; + return this; + } + +} diff --git a/sdk/keyvault/azure-security-keyvault-keys/src/main/java/com/azure/security/keyvault/keys/cryptography/SignatureEncoding.java b/sdk/keyvault/azure-security-keyvault-keys/src/main/java/com/azure/security/keyvault/keys/cryptography/SignatureEncoding.java index 7492da6330ffd..aa241b4f9518a 100644 --- a/sdk/keyvault/azure-security-keyvault-keys/src/main/java/com/azure/security/keyvault/keys/cryptography/SignatureEncoding.java +++ b/sdk/keyvault/azure-security-keyvault-keys/src/main/java/com/azure/security/keyvault/keys/cryptography/SignatureEncoding.java @@ -3,8 +3,6 @@ package com.azure.security.keyvault.keys.cryptography; -import org.apache.commons.codec.binary.Hex; - import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.math.BigInteger; @@ -15,6 +13,9 @@ final class SignatureEncoding { // SignatureEncoding is intended to be a static class private SignatureEncoding() { } + private static final char[] HEX_LOWER = new char[]{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; + + /* * 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 @@ -49,7 +50,7 @@ static byte[] fromAsn1Der(byte[] asn1DerSignature, Ecdsa algorithm) { return Asn1DerSignatureEncoding.decode(asn1DerSignature, algorithm); } catch (IllegalArgumentException ex) { throw (IllegalArgumentException) new IllegalArgumentException( - ex.getMessage() + " " + Hex.encodeHexString(asn1DerSignature)).initCause(ex); + ex.getMessage() + " " + Arrays.toString(encodeHex(asn1DerSignature, HEX_LOWER))).initCause(ex); } } @@ -86,8 +87,21 @@ static byte[] toAsn1Der(byte[] signature, Ecdsa algorithm) { return Asn1DerSignatureEncoding.encode(signature, algorithm); } catch (IllegalArgumentException ex) { throw (IllegalArgumentException) new IllegalArgumentException( - ex.getMessage() + " " + Hex.encodeHexString(signature)).initCause(ex); + ex.getMessage() + " " + Arrays.toString(encodeHex(signature, HEX_LOWER))).initCause(ex); + } + } + + private static char[] encodeHex(byte[] data, char[] toDigits) { + int l = data.length; + char[] out = new char[l << 1]; + int i = 0; + + for (int j = 0; i < l; ++i) { + out[j++] = toDigits[(240 & data[i]) >>> 4]; + out[j++] = toDigits[15 & data[i]]; } + + return out; } } diff --git a/sdk/keyvault/azure-security-keyvault-keys/src/main/java/com/azure/security/keyvault/keys/models/Base64UrlJsonDeserializer.java b/sdk/keyvault/azure-security-keyvault-keys/src/main/java/com/azure/security/keyvault/keys/models/Base64UrlJsonDeserializer.java index c60696153dfe9..0405f4a43b783 100644 --- a/sdk/keyvault/azure-security-keyvault-keys/src/main/java/com/azure/security/keyvault/keys/models/Base64UrlJsonDeserializer.java +++ b/sdk/keyvault/azure-security-keyvault-keys/src/main/java/com/azure/security/keyvault/keys/models/Base64UrlJsonDeserializer.java @@ -7,22 +7,20 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonDeserializer; -import org.apache.commons.codec.binary.Base64; import java.io.IOException; +import java.util.Base64; /** * The base64 URL JSON deserializer. */ class Base64UrlJsonDeserializer extends JsonDeserializer { - static final Base64 BASE64 = new Base64(-1, null, true); - @Override public byte[] deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException { String text = jp.getText(); if (text != null) { - return BASE64.decode(text); + return Base64.getDecoder().decode(text); } return null; } diff --git a/sdk/keyvault/azure-security-keyvault-keys/src/main/java/com/azure/security/keyvault/keys/models/Base64UrlJsonSerializer.java b/sdk/keyvault/azure-security-keyvault-keys/src/main/java/com/azure/security/keyvault/keys/models/Base64UrlJsonSerializer.java index 153c81a5767e1..8318a238176b9 100644 --- a/sdk/keyvault/azure-security-keyvault-keys/src/main/java/com/azure/security/keyvault/keys/models/Base64UrlJsonSerializer.java +++ b/sdk/keyvault/azure-security-keyvault-keys/src/main/java/com/azure/security/keyvault/keys/models/Base64UrlJsonSerializer.java @@ -7,17 +7,14 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.SerializerProvider; -import org.apache.commons.codec.binary.Base64; import java.io.IOException; +import java.util.Base64; /** * The base64 URL JSON serializer. */ class Base64UrlJsonSerializer extends JsonSerializer { - - static final Base64 BASE64 = new Base64(-1, null, true); - @Override public void serialize(byte[] value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException { @@ -27,9 +24,8 @@ public void serialize(byte[] value, JsonGenerator jgen, SerializerProvider provi } else if (value.length == 0) { text = ""; } else { - text = BASE64.encodeAsString(value); + text = Base64.getEncoder().encodeToString(value); } jgen.writeString(text); } - } diff --git a/sdk/keyvault/azure-security-keyvault-keys/src/main/java/com/azure/security/keyvault/keys/models/KeyProperties.java b/sdk/keyvault/azure-security-keyvault-keys/src/main/java/com/azure/security/keyvault/keys/models/KeyProperties.java index 2cbc4d7a0dd76..23a32a61b21ad 100644 --- a/sdk/keyvault/azure-security-keyvault-keys/src/main/java/com/azure/security/keyvault/keys/models/KeyProperties.java +++ b/sdk/keyvault/azure-security-keyvault-keys/src/main/java/com/azure/security/keyvault/keys/models/KeyProperties.java @@ -7,7 +7,6 @@ import com.azure.security.keyvault.keys.KeyAsyncClient; import com.azure.security.keyvault.keys.KeyClient; import com.fasterxml.jackson.annotation.JsonProperty; -import org.apache.commons.codec.binary.Base64; import java.net.MalformedURLException; import java.net.URL; @@ -15,6 +14,7 @@ import java.time.OffsetDateTime; import java.time.ZoneOffset; import java.util.ArrayList; +import java.util.Base64; import java.util.List; import java.util.Map; @@ -297,25 +297,31 @@ List getKeyOperations(List jsonWebKeyOps) { @SuppressWarnings("unchecked") JsonWebKey createKeyMaterialFromJson(Map key) { - final Base64 base64 = new Base64(-1, null, true); JsonWebKey outputKey = new JsonWebKey() - .setY(base64.decode((String) key.get("y"))) - .setX(base64.decode((String) key.get("x"))) + .setY(decode((String) key.get("y"))) + .setX(decode((String) key.get("x"))) .setCurveName(KeyCurveName.fromString((String) key.get("crv"))) .setKeyOps(getKeyOperations((List) key.get("key_ops"))) - .setT(base64.decode((String) key.get("key_hsm"))) - .setK(base64.decode((String) key.get("k"))) - .setQ(base64.decode((String) key.get("q"))) - .setP(base64.decode((String) key.get("p"))) - .setQi(base64.decode((String) key.get("qi"))) - .setDq(base64.decode((String) key.get("dq"))) - .setDp(base64.decode((String) key.get("dp"))) - .setD(base64.decode((String) key.get("d"))) - .setE(base64.decode((String) key.get("e"))) - .setN(base64.decode((String) key.get("n"))) + .setT(decode((String) key.get("key_hsm"))) + .setK(decode((String) key.get("k"))) + .setQ(decode((String) key.get("q"))) + .setP(decode((String) key.get("p"))) + .setQi(decode((String) key.get("qi"))) + .setDq(decode((String) key.get("dq"))) + .setDp(decode((String) key.get("dp"))) + .setD(decode((String) key.get("d"))) + .setE(decode((String) key.get("e"))) + .setN(decode((String) key.get("n"))) .setKeyType(KeyType.fromString((String) key.get("kty"))) .setId((String) key.get("kid")); unpackId((String) key.get("kid")); return outputKey; } + + private byte[] decode(String in) { + if (in != null) { + return Base64.getUrlDecoder().decode(in); + } + return null; + } } diff --git a/sdk/keyvault/azure-security-keyvault-keys/src/main/java/module-info.java b/sdk/keyvault/azure-security-keyvault-keys/src/main/java/module-info.java index e0f59d6ce7f38..48f7a994357e0 100644 --- a/sdk/keyvault/azure-security-keyvault-keys/src/main/java/module-info.java +++ b/sdk/keyvault/azure-security-keyvault-keys/src/main/java/module-info.java @@ -3,7 +3,6 @@ module com.azure.security.keyvault.keys { requires transitive com.azure.core; - requires org.apache.commons.codec; requires java.xml.crypto; exports com.azure.security.keyvault.keys.cryptography; diff --git a/sdk/keyvault/azure-security-keyvault-keys/src/test/java/com/azure/security/keyvault/keys/cryptography/CryptographyClientTestBase.java b/sdk/keyvault/azure-security-keyvault-keys/src/test/java/com/azure/security/keyvault/keys/cryptography/CryptographyClientTestBase.java index 7aef1f6c4f36d..31df034c7ffca 100644 --- a/sdk/keyvault/azure-security-keyvault-keys/src/test/java/com/azure/security/keyvault/keys/cryptography/CryptographyClientTestBase.java +++ b/sdk/keyvault/azure-security-keyvault-keys/src/test/java/com/azure/security/keyvault/keys/cryptography/CryptographyClientTestBase.java @@ -145,8 +145,7 @@ private static KeyPair getWellKnownKey() throws Exception { public String getEndpoint() { final String endpoint = interceptorManager.isPlaybackMode() ? "http://localhost:8080" - : "https://cameravault.vault.azure.net"; - // : System.getenv("AZURE_KEYVAULT_ENDPOINT"); + : System.getenv("AZURE_KEYVAULT_ENDPOINT"); Objects.requireNonNull(endpoint); return endpoint; } diff --git a/sdk/keyvault/azure-security-keyvault-keys/src/test/java/com/azure/security/keyvault/keys/cryptography/KeyEncryptionKeyClientTest.java b/sdk/keyvault/azure-security-keyvault-keys/src/test/java/com/azure/security/keyvault/keys/cryptography/KeyEncryptionKeyClientTest.java new file mode 100644 index 0000000000000..854815677cc8d --- /dev/null +++ b/sdk/keyvault/azure-security-keyvault-keys/src/test/java/com/azure/security/keyvault/keys/cryptography/KeyEncryptionKeyClientTest.java @@ -0,0 +1,61 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.security.keyvault.keys.cryptography; + +import com.azure.core.cryptography.KeyEncryptionKey; +import com.azure.core.http.HttpPipeline; +import com.azure.core.http.rest.RestProxy; +import com.azure.core.util.Context; +import org.junit.jupiter.api.Test; + +import java.util.Base64; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; + +public class KeyEncryptionKeyClientTest extends KeyEncryptionKeyClientTestBase { + + private KeyEncryptionKey client; + private HttpPipeline pipeline; + private SecretKey secretKey; + + @Override + protected void beforeTest() { + beforeTestSetup(); + pipeline = getHttpPipeline(); + } + + private void setupSecretKeyAndClient(byte[] kek) { + if (secretKey == null) { + CryptographyServiceClient serviceClient = new CryptographyServiceClient(getEndpoint(), RestProxy.create(CryptographyService.class, pipeline)); + secretKey = serviceClient.setSecretKey(new SecretKey("secretKey", Base64.getEncoder().encodeToString(kek)), Context.NONE).block().getValue(); + client = new KeyEncryptionKeyClientBuilder() + .pipeline(pipeline) + .buildKeyEncryptionKey(secretKey.getId()); + } + } + + @Test + public void wrapUnwrapSymmetricAK128() { + byte[] kek = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F }; + setupSecretKeyAndClient(kek); + byte[] cek = { 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, (byte) 0x88, (byte) 0x99, (byte) 0xAA, (byte) 0xBB, (byte) 0xCC, (byte) 0xDD, (byte) 0xEE, (byte) 0xFF }; + byte[] encrypted = client.wrapKey("A128KW", cek); + byte[] ek = { 0x1F, (byte) 0xA6, (byte) 0x8B, 0x0A, (byte) 0x81, 0x12, (byte) 0xB4, 0x47, (byte) 0xAE, (byte) 0xF3, 0x4B, (byte) 0xD8, (byte) 0xFB, 0x5A, 0x7B, (byte) 0x82, (byte) 0x9D, 0x3E, (byte) 0x86, 0x23, 0x71, (byte) 0xD2, (byte) 0xCF, (byte) 0xE5 }; + assertArrayEquals(ek, encrypted); + byte[] dek = client.unwrapKey("A128KW", ek); + assertArrayEquals(dek, cek); + } + + @Test + public void wrapUnwrapSymmetricAK192() { + byte[] kek = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17 }; + setupSecretKeyAndClient(kek); + byte[] cek = { 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, (byte) 0x88, (byte) 0x99, (byte) 0xAA, (byte) 0xBB, (byte) 0xCC, (byte) 0xDD, (byte) 0xEE, (byte) 0xFF }; + byte[] encrypted = client.wrapKey("A192KW", cek); + byte[] ek = { (byte) 0x96, 0x77, (byte) 0x8B, 0x25, (byte) 0xAE, 0x6C, (byte) 0xA4, 0x35, (byte) 0xF9, 0x2B, 0x5B, (byte) 0x97, (byte) 0xC0, 0x50, (byte) 0xAE, (byte) 0xD2, 0x46, (byte) 0x8A, (byte) 0xB8, (byte) 0xA1, 0x7A, (byte) 0xD8, 0x4E, 0x5D }; + assertArrayEquals(ek, encrypted); + byte[] dek = client.unwrapKey("A192KW", ek); + assertArrayEquals(dek, cek); + } +} diff --git a/sdk/keyvault/azure-security-keyvault-keys/src/test/java/com/azure/security/keyvault/keys/cryptography/KeyEncryptionKeyClientTestBase.java b/sdk/keyvault/azure-security-keyvault-keys/src/test/java/com/azure/security/keyvault/keys/cryptography/KeyEncryptionKeyClientTestBase.java new file mode 100644 index 0000000000000..136504fcc6d56 --- /dev/null +++ b/sdk/keyvault/azure-security-keyvault-keys/src/test/java/com/azure/security/keyvault/keys/cryptography/KeyEncryptionKeyClientTestBase.java @@ -0,0 +1,181 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.security.keyvault.keys.cryptography; + +import com.azure.core.credential.TokenCredential; +import com.azure.core.exception.HttpResponseException; +import com.azure.core.http.HttpClient; +import com.azure.core.http.HttpPipeline; +import com.azure.core.http.HttpPipelineBuilder; +import com.azure.core.http.netty.NettyAsyncHttpClientBuilder; +import com.azure.core.http.policy.BearerTokenAuthenticationPolicy; +import com.azure.core.http.policy.HttpLogDetailLevel; +import com.azure.core.http.policy.HttpLogOptions; +import com.azure.core.http.policy.HttpLoggingPolicy; +import com.azure.core.http.policy.HttpPipelinePolicy; +import com.azure.core.http.policy.HttpPolicyProviders; +import com.azure.core.http.policy.RetryPolicy; +import com.azure.core.http.policy.UserAgentPolicy; +import com.azure.core.test.TestBase; +import com.azure.core.util.Configuration; +import com.azure.identity.ClientSecretCredentialBuilder; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.function.Function; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.fail; + + +public abstract class KeyEncryptionKeyClientTestBase extends TestBase { + private static final String SDK_NAME = "client_name"; + private static final String SDK_VERSION = "client_version"; + + @Override + protected String getTestName() { + return ""; + } + + void beforeTestSetup() { + } + + T clientSetup(Function clientBuilder) { + HttpPipeline pipeline = getHttpPipeline(); + + T client; + client = clientBuilder.apply(pipeline); + + return Objects.requireNonNull(client); + } + + HttpPipeline getHttpPipeline() { + final String endpoint = interceptorManager.isPlaybackMode() + ? "http://localhost:8080" + : System.getenv("AZURE_KEYVAULT_ENDPOINT"); + + TokenCredential credential = null; + HttpClient httpClient; + + String tenantId = System.getenv("AZURE_TENANT_ID"); + String clientId = System.getenv("AZURE_CLIENT_ID"); + String clientSecret = System.getenv("AZURE_CLIENT_SECRET"); + if (!interceptorManager.isPlaybackMode()) { + assertNotNull(tenantId); + assertNotNull(clientId); + assertNotNull(clientSecret); + } + + if (!interceptorManager.isPlaybackMode()) { + credential = new ClientSecretCredentialBuilder() + .clientSecret(clientSecret) + .tenantId(tenantId) + .clientId(clientId) + .build(); + } + + // Closest to API goes first, closest to wire goes last. + final List policies = new ArrayList<>(); + policies.add(new UserAgentPolicy(SDK_NAME, SDK_VERSION, Configuration.getGlobalConfiguration().clone(), CryptographyServiceVersion.getLatest())); + HttpPolicyProviders.addBeforeRetryPolicies(policies); + policies.add(new RetryPolicy()); + if (credential != null) { + policies.add(new BearerTokenAuthenticationPolicy(credential, CryptographyAsyncClient.KEY_VAULT_SCOPE)); + } + HttpPolicyProviders.addAfterRetryPolicies(policies); + policies.add(new HttpLoggingPolicy(new HttpLogOptions().setLogLevel(HttpLogDetailLevel.BODY_AND_HEADERS))); + + if (interceptorManager.isPlaybackMode()) { + httpClient = interceptorManager.getPlaybackClient(); + policies.add(interceptorManager.getRecordPolicy()); + } else { + httpClient = new NettyAsyncHttpClientBuilder().wiretap(true).build(); + policies.add(interceptorManager.getRecordPolicy()); + } + + return new HttpPipelineBuilder() + .policies(policies.toArray(new HttpPipelinePolicy[0])) + .httpClient(httpClient) + .build(); + } + + @Test + public abstract void wrapUnwrapSymmetricAK128(); + + @Test + public abstract void wrapUnwrapSymmetricAK192(); + + + public String getEndpoint() { + final String endpoint = interceptorManager.isPlaybackMode() + ? "http://localhost:8080" + : System.getenv("AZURE_KEYVAULT_ENDPOINT"); + Objects.requireNonNull(endpoint); + return endpoint; + } + + static void assertRestException(Runnable exceptionThrower, int expectedStatusCode) { + assertRestException(exceptionThrower, HttpResponseException.class, expectedStatusCode); + } + + static void assertRestException(Runnable exceptionThrower, Class expectedExceptionType, int expectedStatusCode) { + try { + exceptionThrower.run(); + fail(); + } catch (Throwable ex) { + assertRestException(ex, expectedExceptionType, expectedStatusCode); + } + } + + /** + * Helper method to verify the error was a HttpRequestException and it has a specific HTTP response code. + * + * @param exception Expected error thrown during the test + * @param expectedStatusCode Expected HTTP status code contained in the error response + */ + static void assertRestException(Throwable exception, int expectedStatusCode) { + assertRestException(exception, HttpResponseException.class, expectedStatusCode); + } + + static void assertRestException(Throwable exception, Class expectedExceptionType, int expectedStatusCode) { + assertEquals(expectedExceptionType, exception.getClass()); + assertEquals(expectedStatusCode, ((HttpResponseException) exception).getResponse().getStatusCode()); + } + + /** + * Helper method to verify that a command throws an IllegalArgumentException. + * + * @param exceptionThrower Command that should throw the exception + */ + static void assertRunnableThrowsException(Runnable exceptionThrower, Class exception) { + try { + exceptionThrower.run(); + fail(); + } catch (Exception ex) { + assertEquals(exception, ex.getClass()); + } + } + + public void sleepInRecordMode(long millis) { + if (interceptorManager.isPlaybackMode()) { + return; + } + try { + Thread.sleep(millis); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + + public void sleep(long millis) { + try { + Thread.sleep(millis); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } +} diff --git a/sdk/keyvault/azure-security-keyvault-keys/src/test/resources/session-records/wrapUnwrapSymmetricAK128.json b/sdk/keyvault/azure-security-keyvault-keys/src/test/resources/session-records/wrapUnwrapSymmetricAK128.json new file mode 100644 index 0000000000000..06aeb60aa4fee --- /dev/null +++ b/sdk/keyvault/azure-security-keyvault-keys/src/test/resources/session-records/wrapUnwrapSymmetricAK128.json @@ -0,0 +1,60 @@ +{ + "networkCallRecords" : [ { + "Method" : "PUT", + "Uri" : "https://cameravault.vault.azure.net/secrets/secretKey?api-version=7.0", + "Headers" : { + "User-Agent" : "azsdk-java-Azure-Keyvault/4.1.0-beta.1 (11.0.5; Mac OS X 10.14.3)", + "Content-Type" : "application/json" + }, + "Response" : { + "Server" : "Microsoft-IIS/10.0", + "X-Content-Type-Options" : "nosniff", + "Pragma" : "no-cache", + "retry-after" : "0", + "StatusCode" : "200", + "Date" : "Thu, 05 Dec 2019 13:55:31 GMT", + "Strict-Transport-Security" : "max-age=31536000;includeSubDomains", + "Cache-Control" : "no-cache", + "X-AspNet-Version" : "4.0.30319", + "x-ms-keyvault-region" : "centralus", + "x-ms-keyvault-network-info" : "addr=182.68.109.216;act_addr_fam=InterNetwork;", + "Expires" : "-1", + "Content-Length" : "242", + "x-ms-request-id" : "1e9e27cb-9ee5-4f21-b1e0-3b0034114ba6", + "x-ms-keyvault-service-version" : "1.1.0.883", + "Body" : "{\"value\":\"AAECAwQFBgcICQoLDA0ODw==\",\"id\":\"https://cameravault.vault.azure.net/secrets/secretKey/3cceded86c644f5e808c070c5c6dd194\",\"attributes\":{\"enabled\":true,\"created\":1575554131,\"updated\":1575554131,\"recoveryLevel\":\"Recoverable+Purgeable\"}}", + "X-Powered-By" : "ASP.NET", + "Content-Type" : "application/json; charset=utf-8" + }, + "Exception" : null + }, { + "Method" : "GET", + "Uri" : "https://cameravault.vault.azure.net/secrets/secretKey/3cceded86c644f5e808c070c5c6dd194?api-version=7.0", + "Headers" : { + "User-Agent" : "azsdk-java-Azure-Keyvault/4.1.0-beta.1 (11.0.5; Mac OS X 10.14.3)", + "Content-Type" : "application/json" + }, + "Response" : { + "Server" : "Microsoft-IIS/10.0", + "X-Content-Type-Options" : "nosniff", + "Pragma" : "no-cache", + "retry-after" : "0", + "StatusCode" : "200", + "Date" : "Thu, 05 Dec 2019 13:55:31 GMT", + "Strict-Transport-Security" : "max-age=31536000;includeSubDomains", + "Cache-Control" : "no-cache", + "X-AspNet-Version" : "4.0.30319", + "x-ms-keyvault-region" : "centralus", + "x-ms-keyvault-network-info" : "addr=182.68.109.216;act_addr_fam=InterNetwork;", + "Expires" : "-1", + "Content-Length" : "242", + "x-ms-request-id" : "e14c6cf3-302e-49f2-90e5-5af9d0af0d0d", + "x-ms-keyvault-service-version" : "1.1.0.883", + "Body" : "{\"value\":\"AAECAwQFBgcICQoLDA0ODw==\",\"id\":\"https://cameravault.vault.azure.net/secrets/secretKey/3cceded86c644f5e808c070c5c6dd194\",\"attributes\":{\"enabled\":true,\"created\":1575554131,\"updated\":1575554131,\"recoveryLevel\":\"Recoverable+Purgeable\"}}", + "X-Powered-By" : "ASP.NET", + "Content-Type" : "application/json; charset=utf-8" + }, + "Exception" : null + } ], + "variables" : [ ] +} \ No newline at end of file diff --git a/sdk/keyvault/azure-security-keyvault-keys/src/test/resources/session-records/wrapUnwrapSymmetricAK192.json b/sdk/keyvault/azure-security-keyvault-keys/src/test/resources/session-records/wrapUnwrapSymmetricAK192.json new file mode 100644 index 0000000000000..32bc319633199 --- /dev/null +++ b/sdk/keyvault/azure-security-keyvault-keys/src/test/resources/session-records/wrapUnwrapSymmetricAK192.json @@ -0,0 +1,60 @@ +{ + "networkCallRecords" : [ { + "Method" : "PUT", + "Uri" : "https://cameravault.vault.azure.net/secrets/secretKey?api-version=7.0", + "Headers" : { + "User-Agent" : "azsdk-java-Azure-Keyvault/4.1.0-beta.1 (11.0.5; Mac OS X 10.14.3)", + "Content-Type" : "application/json" + }, + "Response" : { + "Server" : "Microsoft-IIS/10.0", + "X-Content-Type-Options" : "nosniff", + "Pragma" : "no-cache", + "retry-after" : "0", + "StatusCode" : "200", + "Date" : "Thu, 05 Dec 2019 13:55:32 GMT", + "Strict-Transport-Security" : "max-age=31536000;includeSubDomains", + "Cache-Control" : "no-cache", + "X-AspNet-Version" : "4.0.30319", + "x-ms-keyvault-region" : "centralus", + "x-ms-keyvault-network-info" : "addr=182.68.109.216;act_addr_fam=InterNetwork;", + "Expires" : "-1", + "Content-Length" : "250", + "x-ms-request-id" : "fa1c861a-cac8-4e11-92a0-598befd359d8", + "x-ms-keyvault-service-version" : "1.1.0.883", + "Body" : "{\"value\":\"AAECAwQFBgcICQoLDA0ODxAREhMUFRYX\",\"id\":\"https://cameravault.vault.azure.net/secrets/secretKey/6dd095137e474864a6518c005bb74ae7\",\"attributes\":{\"enabled\":true,\"created\":1575554132,\"updated\":1575554132,\"recoveryLevel\":\"Recoverable+Purgeable\"}}", + "X-Powered-By" : "ASP.NET", + "Content-Type" : "application/json; charset=utf-8" + }, + "Exception" : null + }, { + "Method" : "GET", + "Uri" : "https://cameravault.vault.azure.net/secrets/secretKey/6dd095137e474864a6518c005bb74ae7?api-version=7.0", + "Headers" : { + "User-Agent" : "azsdk-java-Azure-Keyvault/4.1.0-beta.1 (11.0.5; Mac OS X 10.14.3)", + "Content-Type" : "application/json" + }, + "Response" : { + "Server" : "Microsoft-IIS/10.0", + "X-Content-Type-Options" : "nosniff", + "Pragma" : "no-cache", + "retry-after" : "0", + "StatusCode" : "200", + "Date" : "Thu, 05 Dec 2019 13:55:32 GMT", + "Strict-Transport-Security" : "max-age=31536000;includeSubDomains", + "Cache-Control" : "no-cache", + "X-AspNet-Version" : "4.0.30319", + "x-ms-keyvault-region" : "centralus", + "x-ms-keyvault-network-info" : "addr=182.68.109.216;act_addr_fam=InterNetwork;", + "Expires" : "-1", + "Content-Length" : "250", + "x-ms-request-id" : "ffbccf2d-6dc2-4910-a4e8-c2d60ddc7ad1", + "x-ms-keyvault-service-version" : "1.1.0.883", + "Body" : "{\"value\":\"AAECAwQFBgcICQoLDA0ODxAREhMUFRYX\",\"id\":\"https://cameravault.vault.azure.net/secrets/secretKey/6dd095137e474864a6518c005bb74ae7\",\"attributes\":{\"enabled\":true,\"created\":1575554132,\"updated\":1575554132,\"recoveryLevel\":\"Recoverable+Purgeable\"}}", + "X-Powered-By" : "ASP.NET", + "Content-Type" : "application/json; charset=utf-8" + }, + "Exception" : null + } ], + "variables" : [ ] +} \ No newline at end of file