Skip to content

Commit

Permalink
Kv keys updates (Azure#6657)
Browse files Browse the repository at this point in the history
Kv Keys update
  • Loading branch information
g2vinay authored Dec 6, 2019
1 parent 4db0118 commit 9c8ab03
Show file tree
Hide file tree
Showing 21 changed files with 1,282 additions and 44 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -492,6 +492,7 @@
<Class name="com.azure.security.keyvault.secrets.models.KeyVaultSecret"/>
<Class name="com.azure.security.keyvault.secrets.models.SecretProperties"/>
<Class name="com.azure.security.keyvault.keys.models.DeletedKey"/>
<Class name="com.azure.security.keyvault.keys.cryptography.SecretKey"/>
<Class name="com.azure.security.keyvault.keys.models.KeyVaultKey"/>
<Class name="com.azure.security.keyvault.keys.models.KeyProperties"/>
<Class name="com.azure.security.keyvault.certificates.models.DeletedCertificate"/>
Expand Down Expand Up @@ -531,6 +532,10 @@
<Class name="com.azure.security.keyvault.keys.models.ByteExtensions"/>
<Method name="clone"/>
</And>
<And>
<Class name="com.azure.security.keyvault.keys.models.KeyProperties"/>
<Method name="decode"/>
</And>
</Or>
<Bug pattern="PZLA_PREFER_ZERO_LENGTH_ARRAYS"/>
</Match>
Expand Down
7 changes: 6 additions & 1 deletion sdk/keyvault/azure-security-keyvault-keys/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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.
Expand Down
6 changes: 0 additions & 6 deletions sdk/keyvault/azure-security-keyvault-keys/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,6 @@
<version>1.1.0</version> <!-- {x-version-update;com.azure:azure-core-http-netty;dependency} -->
</dependency>

<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.13</version> <!-- {x-version-update;commons-codec:commons-codec;external_dependency} -->
</dependency>

<!-- Test dependencies -->
<dependency>
<groupId>org.junit.jupiter</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -168,6 +170,14 @@ Mono<Response<KeyVaultKey>> getKeyWithResponse(Context context) {
return cryptographyServiceClient.getKey(context);
}

Mono<JsonWebKey> 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.
Expand Down Expand Up @@ -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)) {
Expand All @@ -609,10 +620,14 @@ private boolean checkKeyPermissions(List<KeyOperation> 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) {
Expand All @@ -627,4 +642,8 @@ private boolean ensureValidKeyAvailable() {
CryptographyServiceClient getCryptographyServiceClient() {
return cryptographyServiceClient;
}

void setCryptographyServiceClient(CryptographyServiceClient serviceClient) {
this.cryptographyServiceClient = serviceClient;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -130,4 +131,29 @@ Mono<Response<KeyVaultKey>> 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<Response<SecretKey>> 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<Response<SecretKey>> 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);
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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 {
Expand Down Expand Up @@ -57,6 +67,56 @@ private Mono<Response<KeyVaultKey>> getKey(String name, String version, Context
.doOnError(error -> logger.warning("Failed to get key - {}", name, error));
}

Mono<Response<JsonWebKey>> 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<Response<SecretKey>> 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<EncryptResult> encrypt(EncryptionAlgorithm algorithm, byte[] plaintext, Context context) {

KeyOperationParameters parameters = new KeyOperationParameters().setAlgorithm(algorithm).setValue(plaintext);
Expand Down Expand Up @@ -176,6 +236,4 @@ private void unpackId(String keyId) {
}
}
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
Original file line number Diff line number Diff line change
@@ -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<String, Object> 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<String, String> tags) {
properties.tags = tags;
}
}

Loading

0 comments on commit 9c8ab03

Please sign in to comment.