Skip to content

Commit

Permalink
Added support for Secure Key Release (Azure#23276)
Browse files Browse the repository at this point in the history
* Added a new releaseKey() method to KeyClient and KeyAsyncClient. Added required models for this new operations; modified other related models as well. Updated CHANGELOG.

* Fixed CheckStyle issues.

* Fixed JavaDoc issue.

* Applied PR feedback.

* Corrected wrong naming on an internal `RandomBytes` class. Made data a required constructor parameter on `KeyReleasePolicy`.

* Fixed a few issues:

- Added an empty constructor to fix a serialization issue in KeyReleasePolicy.
- Changed `vaultBaseUrl` to `url` in `KeyService.release()`.
- Added ``.setReleasePolicy()` to `CreateEcKetOptions` and `CreateOctKetOptions`.

* Added tests and recordings.

* Updated ARM template.

* Fixed CheckStyle issues and renamed test recordings to unblock CI pipelines from failing.

* Applied PR feedback.
  • Loading branch information
vcolin7 authored Aug 9, 2021
1 parent d353a1b commit a804ef1
Show file tree
Hide file tree
Showing 24 changed files with 1,250 additions and 145 deletions.
3 changes: 3 additions & 0 deletions sdk/keyvault/azure-security-keyvault-keys/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

## 4.4.0-beta.2 (Unreleased)

### Features Added
- Added `Exportable` and `ReleasePolicy` to `CreateKeyOptions`, `ImportKeyOptions`, and `KeyProperties` to support Secure Key Release for Key Vault and Managed HSM.
- Added a `release()` operation to `KeyClient` and `KeyAsyncClient` to release a key for Key Vault and Managed HSM.

## 4.4.0-beta.1 (2021-07-09)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import com.azure.core.http.rest.RestProxy;
import com.azure.core.http.rest.SimpleResponse;
import com.azure.core.util.Context;
import com.azure.core.util.CoreUtils;
import com.azure.core.util.FluxUtil;
import com.azure.core.util.logging.ClientLogger;
import com.azure.core.util.polling.LongRunningOperationStatus;
Expand All @@ -36,6 +37,8 @@
import com.azure.security.keyvault.keys.models.KeyType;
import com.azure.security.keyvault.keys.models.KeyVaultKey;
import com.azure.security.keyvault.keys.models.RandomBytes;
import com.azure.security.keyvault.keys.models.ReleaseKeyOptions;
import com.azure.security.keyvault.keys.models.ReleaseKeyResult;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

Expand Down Expand Up @@ -228,7 +231,8 @@ Mono<Response<KeyVaultKey>> createKeyWithResponse(CreateKeyOptions createKeyOpti
.setKty(createKeyOptions.getKeyType())
.setKeyOps(createKeyOptions.getKeyOperations())
.setKeyAttributes(new KeyRequestAttributes(createKeyOptions))
.setTags(createKeyOptions.getTags());
.setTags(createKeyOptions.getTags())
.setReleasePolicy(createKeyOptions.getReleasePolicy());

return service.createKey(vaultUrl, createKeyOptions.getName(), apiVersion, ACCEPT_LANGUAGE, parameters,
CONTENT_TYPE_HEADER_VALUE, context.addData(AZ_TRACING_NAMESPACE_KEY, KEYVAULT_TRACING_NAMESPACE_VALUE))
Expand Down Expand Up @@ -311,7 +315,8 @@ Mono<Response<KeyVaultKey>> createRsaKeyWithResponse(CreateRsaKeyOptions createR
.setKeyOps(createRsaKeyOptions.getKeyOperations())
.setKeyAttributes(new KeyRequestAttributes(createRsaKeyOptions))
.setPublicExponent(createRsaKeyOptions.getPublicExponent())
.setTags(createRsaKeyOptions.getTags());
.setTags(createRsaKeyOptions.getTags())
.setReleasePolicy(createRsaKeyOptions.getReleasePolicy());

return service.createKey(vaultUrl, createRsaKeyOptions.getName(), apiVersion, ACCEPT_LANGUAGE, parameters,
CONTENT_TYPE_HEADER_VALUE, context.addData(AZ_TRACING_NAMESPACE_KEY, KEYVAULT_TRACING_NAMESPACE_VALUE))
Expand Down Expand Up @@ -399,7 +404,8 @@ Mono<Response<KeyVaultKey>> createEcKeyWithResponse(CreateEcKeyOptions createEcK
.setCurve(createEcKeyOptions.getCurveName())
.setKeyOps(createEcKeyOptions.getKeyOperations())
.setKeyAttributes(new KeyRequestAttributes(createEcKeyOptions))
.setTags(createEcKeyOptions.getTags());
.setTags(createEcKeyOptions.getTags())
.setReleasePolicy(createEcKeyOptions.getReleasePolicy());

return service.createKey(vaultUrl, createEcKeyOptions.getName(), apiVersion, ACCEPT_LANGUAGE, parameters,
CONTENT_TYPE_HEADER_VALUE, context.addData(AZ_TRACING_NAMESPACE_KEY, KEYVAULT_TRACING_NAMESPACE_VALUE))
Expand Down Expand Up @@ -484,7 +490,8 @@ Mono<Response<KeyVaultKey>> createOctKeyWithResponse(CreateOctKeyOptions createO
.setKty(createOctKeyOptions.getKeyType())
.setKeyOps(createOctKeyOptions.getKeyOperations())
.setKeyAttributes(new KeyRequestAttributes(createOctKeyOptions))
.setTags(createOctKeyOptions.getTags());
.setTags(createOctKeyOptions.getTags())
.setReleasePolicy(createOctKeyOptions.getReleasePolicy());

return service.createKey(vaultUrl, createOctKeyOptions.getName(), apiVersion, ACCEPT_LANGUAGE, parameters,
CONTENT_TYPE_HEADER_VALUE, context.addData(AZ_TRACING_NAMESPACE_KEY, KEYVAULT_TRACING_NAMESPACE_VALUE))
Expand Down Expand Up @@ -600,7 +607,8 @@ Mono<Response<KeyVaultKey>> importKeyWithResponse(ImportKeyOptions importKeyOpti
.setKey(importKeyOptions.getKey())
.setHsm(importKeyOptions.isHardwareProtected())
.setKeyAttributes(new KeyRequestAttributes(importKeyOptions))
.setTags(importKeyOptions.getTags());
.setTags(importKeyOptions.getTags())
.setReleasePolicy(importKeyOptions.getReleasePolicy());

return service.importKey(vaultUrl, importKeyOptions.getName(), apiVersion, ACCEPT_LANGUAGE, parameters,
CONTENT_TYPE_HEADER_VALUE, context.addData(AZ_TRACING_NAMESPACE_KEY, KEYVAULT_TRACING_NAMESPACE_VALUE))
Expand Down Expand Up @@ -768,10 +776,13 @@ Mono<Response<KeyVaultKey>> updateKeyPropertiesWithResponse(KeyProperties keyPro
context = context == null ? Context.NONE : context;
KeyRequestParameters parameters = new KeyRequestParameters()
.setTags(keyProperties.getTags())
.setKeyAttributes(new KeyRequestAttributes(keyProperties));
.setKeyAttributes(new KeyRequestAttributes(keyProperties))
.setReleasePolicy(keyProperties.getReleasePolicy());

if (keyOperations.length > 0) {
parameters.setKeyOps(Arrays.asList(keyOperations));
}

return service.updateKey(vaultUrl, keyProperties.getName(), keyProperties.getVersion(), apiVersion, ACCEPT_LANGUAGE, parameters,
CONTENT_TYPE_HEADER_VALUE, context.addData(AZ_TRACING_NAMESPACE_KEY, KEYVAULT_TRACING_NAMESPACE_VALUE))
.doOnRequest(ignored -> logger.verbose("Updating key - {}", keyProperties.getName()))
Expand Down Expand Up @@ -1380,7 +1391,7 @@ private Mono<PagedResponse<KeyProperties>> listKeyVersionsNextPage(String contin
*
* @param count The requested number of random bytes.
*
* @return The requested number of bytes containing random values from a managed HSM.
* @return A {@link Mono} containing the requested number of bytes containing random values from a managed HSM.
*/
@ServiceMethod(returns = ReturnType.SINGLE)
public Mono<RandomBytes> getRandomBytes(int count) {
Expand All @@ -1403,7 +1414,8 @@ public Mono<RandomBytes> getRandomBytes(int count) {
*
* @param count The requested number of random bytes.
*
* @return The requested number of bytes containing random values from a managed HSM.
* @return A {@link Mono} containing the {@link Response HTTP response} for this operation and the requested number
* of bytes containing random values from a managed HSM.
*/
@ServiceMethod(returns = ReturnType.SINGLE)
public Mono<Response<RandomBytes>> getRandomBytesWithResponse(int count) {
Expand All @@ -1426,5 +1438,105 @@ Mono<Response<RandomBytes>> getRandomBytesWithResponse(int count, Context contex
return monoError(logger, e);
}
}

/**
* Releases the latest version of a key.
*
* <p>The key must be exportable. This operation requires the 'keys/release' permission.</p>
*
* @param name The name of the key to release.
* @param target The attestation assertion for the target of the key release.
*
* @return A {@link Mono} containing the {@link ReleaseKeyResult} containing the released key.
*
* @throws IllegalArgumentException If {@code name} or {@code target} are {@code null} or empty.
*/
@ServiceMethod(returns = ReturnType.SINGLE)
public Mono<ReleaseKeyResult> releaseKey(String name, String target) {
try {
return releaseKeyWithResponse(name, "", target, new ReleaseKeyOptions())
.flatMap(FluxUtil::toMono);
} catch (RuntimeException e) {
return monoError(logger, e);
}
}

/**
* Releases a key.
*
* <p>The key must be exportable. This operation requires the 'keys/release' permission.</p>
*
* @param name The name of the key to release.
* @param version The version of the key to retrieve. If this is empty or {@code null}, this call is equivalent to
* calling {@link KeyAsyncClient#releaseKey(String, String)}, with the latest key version being released.
* @param target The attestation assertion for the target of the key release.
*
* @return A {@link Mono} containing the {@link ReleaseKeyResult} containing the released key.
*
* @throws IllegalArgumentException If {@code name} or {@code target} are {@code null} or empty.
*/
@ServiceMethod(returns = ReturnType.SINGLE)
public Mono<ReleaseKeyResult> releaseKey(String name, String version, String target) {
try {
return releaseKeyWithResponse(name, version, target, new ReleaseKeyOptions())
.flatMap(FluxUtil::toMono);
} catch (RuntimeException e) {
return monoError(logger, e);
}
}

/**
* Releases a key.
*
* <p>The key must be exportable. This operation requires the 'keys/release' permission.</p>
*
* @param name The name of the key to release.
* @param version The version of the key to retrieve. If this is empty or {@code null}, this call is equivalent to
* calling {@link KeyAsyncClient#releaseKey(String, String)}, with the latest key version being released.
* @param target The attestation assertion for the target of the key release.
* @param options Additional options for releasing a key.
*
* @return A {@link Mono} containing the {@link Response HTTP response} for this operation and the
* {@link ReleaseKeyResult} containing the released key.
*
* @throws IllegalArgumentException If {@code name} or {@code target} are {@code null} or empty.
*/
@ServiceMethod(returns = ReturnType.SINGLE)
public Mono<Response<ReleaseKeyResult>> releaseKeyWithResponse(String name, String version, String target,
ReleaseKeyOptions options) {
try {
return withContext(context -> releaseKeyWithResponse(name, version, target, options, context));
} catch (RuntimeException e) {
return monoError(logger, e);
}
}

Mono<Response<ReleaseKeyResult>> releaseKeyWithResponse(String name, String version, String target,
ReleaseKeyOptions options, Context context) {
try {
if (CoreUtils.isNullOrEmpty(name)) {
return monoError(logger, new IllegalArgumentException("'name' cannot be null or empty"));
}

if (CoreUtils.isNullOrEmpty(target)) {
return monoError(logger, new IllegalArgumentException("'target' cannot be null or empty"));
}

options = options == null ? new ReleaseKeyOptions() : options;

KeyReleaseParameters keyReleaseParameters = new KeyReleaseParameters()
.setTarget(target)
.setAlgorithm(options.getAlgorithm())
.setNonce(options.getNonce());

return service.release(vaultUrl, name, version, apiVersion, keyReleaseParameters, "application/json",
context.addData(AZ_TRACING_NAMESPACE_KEY, KEYVAULT_TRACING_NAMESPACE_VALUE))
.doOnRequest(ignored -> logger.verbose("Releasing key with name %s and version %s.", name, version))
.doOnSuccess(response -> logger.verbose("Released key with name %s and version %s.", name, version))
.doOnError(error -> logger.warning("Failed to release key - {}", error));
} catch (RuntimeException e) {
return monoError(logger, e);
}
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
import com.azure.security.keyvault.keys.models.KeyOperation;
import com.azure.security.keyvault.keys.models.KeyType;
import com.azure.security.keyvault.keys.models.RandomBytes;
import com.azure.security.keyvault.keys.models.ReleaseKeyOptions;
import com.azure.security.keyvault.keys.models.ReleaseKeyResult;

/**
* The KeyClient provides synchronous methods to manage {@link KeyVaultKey keys} in the Azure Key Vault. The client supports
Expand Down Expand Up @@ -915,9 +917,68 @@ public RandomBytes getRandomBytes(int count) {
* @param count The requested number of random bytes.
* @param context Additional context that is passed through the Http pipeline during the service call.
*
* @return The requested number of bytes containing random values from a managed HSM.
* @return The {@link Response HTTP response} for this operation and the requested number of bytes containing
* random values from a managed HSM.
*/
public Response<RandomBytes> getRandomBytesWithResponse(int count, Context context) {
return client.getRandomBytesWithResponse(count, context).block();
}

/**
* Releases the latest version of a key.
*
* <p>The key must be exportable. This operation requires the 'keys/release' permission.</p>
*
* @param name The name of the key to release.
* @param target The attestation assertion for the target of the key release.
*
* @return The key release result containing the released key.
*
* @throws IllegalArgumentException If {@code name} or {@code target} are {@code null} or empty.
*/
@ServiceMethod(returns = ReturnType.SINGLE)
public ReleaseKeyResult releaseKey(String name, String target) {
return client.releaseKey(name, target).block();
}

/**
* Releases a key.
*
* <p>The key must be exportable. This operation requires the 'keys/release' permission.</p>
*
* @param name The name of the key to release.
* @param version The version of the key to retrieve. If this is empty or {@code null}, this call is equivalent to
* calling {@link KeyAsyncClient#releaseKey(String, String)}, with the latest key version being released.
* @param target The attestation assertion for the target of the key release.
*
* @return The key release result containing the released key.
*
* @throws IllegalArgumentException If {@code name} or {@code target} are {@code null} or empty.
*/
@ServiceMethod(returns = ReturnType.SINGLE)
public ReleaseKeyResult releaseKey(String name, String version, String target) {
return client.releaseKey(name, version, target).block();
}

/**
* Releases a key.
*
* <p>The key must be exportable. This operation requires the 'keys/release' permission.</p>
*
* @param name The name of the key to release.
* @param version Version of the key to release.This parameter is optional.
* @param target The attestation assertion for the target of the key release.
* @param options Additional options for releasing a key.
* @param context Additional context that is passed through the Http pipeline during the service call.
*
* @return The {@link Response HTTP response} for this operation and the {@link ReleaseKeyResult} containing the
* released key.
*
* @throws IllegalArgumentException If {@code name} or {@code target} are {@code null} or empty.
*/
@ServiceMethod(returns = ReturnType.SINGLE)
public Response<ReleaseKeyResult> releaseKeyWithResponse(String name, String version, String target,
ReleaseKeyOptions options, Context context) {
return client.releaseKeyWithResponse(name, version, target, options, context).block();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import com.azure.core.annotation.Fluent;
import com.azure.security.keyvault.keys.models.JsonWebKey;
import com.azure.security.keyvault.keys.models.KeyReleasePolicy;
import com.fasterxml.jackson.annotation.JsonProperty;

import java.util.Map;
Expand Down Expand Up @@ -35,6 +36,12 @@ class KeyImportRequestParameters {
@JsonProperty(value = "tags")
private Map<String, String> tags;

/*
* The policy rules under which the key can be exported.
*/
@JsonProperty(value = "release_policy")
private KeyReleasePolicy releasePolicy;

/**
* Get the key attributes.
*
Expand Down Expand Up @@ -122,4 +129,26 @@ public KeyImportRequestParameters setKey(JsonWebKey key) {

return this;
}

/**
* Get the policy rules under which the key can be exported.
*
* @return The policy rules under which the key can be exported.
*/
public KeyReleasePolicy getReleasePolicy() {
return this.releasePolicy;
}

/**
* Set the policy rules under which the key can be exported.
*
* @param releasePolicy The policy rules under which the key can be exported.
*
* @return The updated {@link KeyImportRequestParameters} object.
*/
public KeyImportRequestParameters setReleasePolicy(KeyReleasePolicy releasePolicy) {
this.releasePolicy = releasePolicy;

return this;
}
}
Loading

0 comments on commit a804ef1

Please sign in to comment.