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 42061ffd305c..78823a5fbb88 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 8d62d5dcedb4..1307e65fc5c9 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 4147223c8eb6..07c875219e47 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 3ae35f0480ee..8143a33328c3 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 c8943ae83226..1ae31cec4359 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 5c6f6eea0494..5b5ce913eb42 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 40d11864167b..494bfad93373 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 000000000000..1164c92f91c8
--- /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 000000000000..47f272b40f15
--- /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 000000000000..f35dfd2c111b
--- /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 000000000000..3ec66a786e7f
--- /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 7492da6330ff..aa241b4f9518 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 c60696153dfe..0405f4a43b78 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 153c81a5767e..8318a238176b 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 2cbc4d7a0dd7..23a32a61b21a 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 e0f59d6ce7f3..48f7a994357e 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 7aef1f6c4f36..31df034c7ffc 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 000000000000..854815677cc8
--- /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 000000000000..136504fcc6d5
--- /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 extends HttpResponseException> 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 extends HttpResponseException> 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 000000000000..06aeb60aa4fe
--- /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 000000000000..32bc31963319
--- /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