Skip to content

Commit 5843ef1

Browse files
hari9-9HariSC30
andauthored
Implement External Key and support service version 2026-01-01-preview (#49708)
* autogenerate Keys SDK for 2026-01-01-preview * implemnt external key * added live tests * added samples * Fix AI review comments * Recorded tests * pinned tests to old version * add todo item * tag myself in todo for linting --------- Co-authored-by: Hari K <kha@microsoft.com>
1 parent 2dbc2b1 commit 5843ef1

30 files changed

Lines changed: 1719 additions & 14 deletions

sdk/keyvault/azure-security-keyvault-administration/src/test/java/com/azure/security/keyvault/administration/KeyVaultBackupAsyncClientTest.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import com.azure.core.util.polling.LongRunningOperationStatus;
99
import com.azure.security.keyvault.keys.KeyClient;
1010
import com.azure.security.keyvault.keys.KeyClientBuilder;
11+
import com.azure.security.keyvault.keys.KeyServiceVersion;
1112
import com.azure.security.keyvault.keys.models.CreateRsaKeyOptions;
1213
import com.azure.security.keyvault.keys.models.KeyVaultKey;
1314
import org.junit.jupiter.params.ParameterizedTest;
@@ -133,8 +134,11 @@ public void beginRestore(HttpClient httpClient) {
133134
@ParameterizedTest(name = DISPLAY_NAME)
134135
@MethodSource("com.azure.security.keyvault.administration.KeyVaultAdministrationClientTestBase#createHttpClients")
135136
public void beginSelectiveKeyRestore(HttpClient httpClient) {
136-
KeyClient keyClient
137-
= new KeyClientBuilder().vaultUrl(getEndpoint()).pipeline(getPipeline(httpClient, false)).buildClient();
137+
// TODO (hari9-9): Un-pin the 2025-07-01 version once we find a workaround for allowSharedKeyAccess in storage account
138+
KeyClient keyClient = new KeyClientBuilder().vaultUrl(getEndpoint())
139+
.serviceVersion(KeyServiceVersion.V2025_07_01)
140+
.pipeline(getPipeline(httpClient, false))
141+
.buildClient();
138142

139143
String keyName = testResourceNamer.randomName("backupKey", 20);
140144
CreateRsaKeyOptions rsaKeyOptions

sdk/keyvault/azure-security-keyvault-administration/src/test/java/com/azure/security/keyvault/administration/KeyVaultBackupClientTest.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import com.azure.security.keyvault.administration.models.KeyVaultSelectiveKeyRestoreResult;
1515
import com.azure.security.keyvault.keys.KeyClient;
1616
import com.azure.security.keyvault.keys.KeyClientBuilder;
17+
import com.azure.security.keyvault.keys.KeyServiceVersion;
1718
import com.azure.security.keyvault.keys.models.CreateRsaKeyOptions;
1819
import com.azure.security.keyvault.keys.models.KeyVaultKey;
1920
import org.junit.jupiter.params.ParameterizedTest;
@@ -141,8 +142,11 @@ public void beginRestore(HttpClient httpClient) {
141142
@ParameterizedTest(name = DISPLAY_NAME)
142143
@MethodSource("com.azure.security.keyvault.administration.KeyVaultAdministrationClientTestBase#createHttpClients")
143144
public void beginSelectiveKeyRestore(HttpClient httpClient) {
144-
KeyClient keyClient
145-
= new KeyClientBuilder().vaultUrl(getEndpoint()).pipeline(getPipeline(httpClient, false)).buildClient();
145+
// TODO (hari9-9): Un-pin the 2025-07-01 version once we find a workaround for allowSharedKeyAccess in storage account
146+
KeyClient keyClient = new KeyClientBuilder().vaultUrl(getEndpoint())
147+
.serviceVersion(KeyServiceVersion.V2025_07_01)
148+
.pipeline(getPipeline(httpClient, false))
149+
.buildClient();
146150

147151
String keyName = testResourceNamer.randomName("backupKey", 20);
148152
CreateRsaKeyOptions rsaKeyOptions

sdk/keyvault/azure-security-keyvault-keys/CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44

55
### Features Added
66

7+
- Added the `ExternalKey` model and the `CreateExternalKeyOptions` options class, along with the `KeyClient.createExternalKey` and `KeyAsyncClient.createExternalKey` methods (and their `WithResponse` variants), for registering a Managed HSM key whose material is held in an external HSM (External Key Management). Only supported on Managed HSM with service version `2026-01-01-preview` or newer.
8+
- Added support for service version `2026-01-01-preview`.
9+
710
### Breaking Changes
811

912
### Bugs Fixed

sdk/keyvault/azure-security-keyvault-keys/README.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ The cryptography client performs the cryptographic operations locally or calls t
107107
### Sync API
108108
The following sections provide several code snippets covering some of the most common Azure Key Vault Key service tasks, including:
109109
- [Create a key](#create-a-key)
110+
- [Create an external key](#create-an-external-key)
110111
- [Retrieve a key](#retrieve-a-key)
111112
- [Update an existing key](#update-an-existing-key)
112113
- [Delete a key](#delete-a-key)
@@ -130,6 +131,18 @@ KeyVaultKey ecKey = keyClient.createEcKey(new CreateEcKeyOptions("CloudEcKey")
130131
System.out.printf("Key created with name \"%s\" and id %s%n", ecKey.getName(), ecKey.getId());
131132
```
132133

134+
#### Create an external key
135+
Register an external key with a Managed HSM. An external key references key material that is owned by an external Hardware Security Module (HSM); the Managed HSM stores only a reference to that material.
136+
- `createExternalKey` registers the external key in the Managed HSM. This is only supported on a Managed HSM configured to use External Key Management (EKM).
137+
138+
```java readme-sample-createExternalKey
139+
// External keys are only supported on a Managed HSM configured to use External Key Management (EKM).
140+
KeyVaultKey externalKey = keyClient.createExternalKey(
141+
new CreateExternalKeyOptions("CloudExternalKey", new ExternalKey("external-key-reference-id"))
142+
.setExpiresOn(OffsetDateTime.now().plusYears(1)));
143+
System.out.printf("Key created with name \"%s\" and id %s%n", externalKey.getName(), externalKey.getId());
144+
```
145+
133146
#### Retrieve a key
134147
Retrieve a previously stored key by calling `getKey`.
135148

sdk/keyvault/azure-security-keyvault-keys/assets.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,5 @@
22
"AssetsRepo": "Azure/azure-sdk-assets",
33
"AssetsRepoPrefixPath": "java",
44
"TagPrefix": "java/keyvault/azure-security-keyvault-keys",
5-
"Tag": "java/keyvault/azure-security-keyvault-keys_a67026297a"
5+
"Tag": "java/keyvault/azure-security-keyvault-keys_7960632950"
66
}

sdk/keyvault/azure-security-keyvault-keys/customizations/src/main/java/KeysCustomizations.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,9 @@ private static void customizeServiceVersion(LibraryCustomization customization)
5555
.addImplementedType("ServiceVersion")
5656
.setJavadocComment("The versions of Azure Key Vault Keys supported by this client library.");
5757

58-
for (String version : Arrays.asList("7.0", "7.1", "7.2", "7.3", "7.4", "7.5", "7.6", "2025-07-01")) {
58+
for (String version : Arrays.asList(
59+
"7.0", "7.1", "7.2", "7.3", "7.4", "7.5", "7.6",
60+
"2025-07-01", "2026-01-01-preview")) {
5961
enumDeclaration.addEnumConstant("V" + version.replace('.', '_').replace('-', '_').toUpperCase())
6062
.setJavadocComment("Service version {@code " + version + "}.")
6163
.addArgument(new StringLiteralExpr(version));
@@ -76,7 +78,7 @@ private static void customizeServiceVersion(LibraryCustomization customization)
7678
.setType("KeyServiceVersion")
7779
.setJavadocComment(new Javadoc(parseText("Gets the latest service version supported by this client library."))
7880
.addBlockTag("return", "The latest {@link KeyServiceVersion}."))
79-
.setBody(StaticJavaParser.parseBlock("{ return V2025_07_01; }"));
81+
.setBody(StaticJavaParser.parseBlock("{ return V2026_01_01_PREVIEW; }"));
8082

8183
customization.getRawEditor()
8284
.addFile("src/main/java/com/azure/security/keyvault/keys/KeyServiceVersion.java",

sdk/keyvault/azure-security-keyvault-keys/src/main/java/com/azure/security/keyvault/keys/KeyAsyncClient.java

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
import com.azure.security.keyvault.keys.implementation.models.KeyVaultKeysModelsUtils;
4242
import com.azure.security.keyvault.keys.implementation.models.RandomBytes;
4343
import com.azure.security.keyvault.keys.models.CreateEcKeyOptions;
44+
import com.azure.security.keyvault.keys.models.CreateExternalKeyOptions;
4445
import com.azure.security.keyvault.keys.models.CreateKeyOptions;
4546
import com.azure.security.keyvault.keys.models.CreateOctKeyOptions;
4647
import com.azure.security.keyvault.keys.models.CreateRsaKeyOptions;
@@ -370,6 +371,109 @@ static HttpResponseException mapCreateKeyException(HttpResponseException e) {
370371
: e;
371372
}
372373

374+
/**
375+
* Registers an external key with a Managed HSM and stores it in the key vault. An external key references key
376+
* material that is owned by an external Hardware Security Module (HSM); the Managed HSM stores only a reference to
377+
* that material. It requires the {@code keys/create} permission.
378+
*
379+
* <p>External keys are mutually exclusive with a {@link KeyType key type}, so no key type is sent to the service.
380+
* External keys are only supported on Managed HSM configured to use External Key Management (EKM), with service
381+
* version {@code 2026-01-01-preview} or newer. They are not supported on a standard Key Vault.</p>
382+
*
383+
* <p><strong>Code Samples</strong></p>
384+
* <p>Registers an external key which activates in one day and expires in one year. Subscribes to the call
385+
* asynchronously and prints out the newly {@link KeyVaultKey created key} details when a response has been
386+
* received.</p>
387+
* <!-- src_embed com.azure.security.keyvault.keys.KeyAsyncClient.createExternalKey#CreateExternalKeyOptions -->
388+
* <pre>
389+
* CreateExternalKeyOptions createExternalKeyOptions =
390+
* new CreateExternalKeyOptions&#40;&quot;keyName&quot;, new ExternalKey&#40;&quot;external-key-reference-id&quot;&#41;&#41;
391+
* .setNotBefore&#40;OffsetDateTime.now&#40;&#41;.plusDays&#40;1&#41;&#41;
392+
* .setExpiresOn&#40;OffsetDateTime.now&#40;&#41;.plusYears&#40;1&#41;&#41;;
393+
*
394+
* keyAsyncClient.createExternalKey&#40;createExternalKeyOptions&#41;
395+
* .contextWrite&#40;Context.of&#40;&quot;key1&quot;, &quot;value1&quot;, &quot;key2&quot;, &quot;value2&quot;&#41;&#41;
396+
* .subscribe&#40;key -&gt;
397+
* System.out.printf&#40;&quot;Created external key with name: %s and id: %s %n&quot;, key.getName&#40;&#41;,
398+
* key.getId&#40;&#41;&#41;&#41;;
399+
* </pre>
400+
* <!-- end com.azure.security.keyvault.keys.KeyAsyncClient.createExternalKey#CreateExternalKeyOptions -->
401+
*
402+
* @param createExternalKeyOptions The {@link CreateExternalKeyOptions options object} containing information about
403+
* the external key being registered.
404+
*
405+
* @return A {@link Mono} containing the {@link KeyVaultKey created key}.
406+
*
407+
* @throws HttpResponseException If {@link CreateExternalKeyOptions#getName()} is an empty string.
408+
* @throws NullPointerException If {@code createExternalKeyOptions} is null.
409+
*/
410+
@ServiceMethod(returns = ReturnType.SINGLE)
411+
public Mono<KeyVaultKey> createExternalKey(CreateExternalKeyOptions createExternalKeyOptions) {
412+
return createExternalKeyWithResponse(createExternalKeyOptions).flatMap(FluxUtil::toMono);
413+
}
414+
415+
/**
416+
* Registers an external key with a Managed HSM and stores it in the key vault. An external key references key
417+
* material that is owned by an external Hardware Security Module (HSM); the Managed HSM stores only a reference to
418+
* that material. It requires the {@code keys/create} permission.
419+
*
420+
* <p>External keys are mutually exclusive with a {@link KeyType key type}, so no key type is sent to the service.
421+
* External keys are only supported on Managed HSM configured to use External Key Management (EKM), with service
422+
* version {@code 2026-01-01-preview} or newer. They are not supported on a standard Key Vault.</p>
423+
*
424+
* <p><strong>Code Samples</strong></p>
425+
* <p>Registers an external key which activates in one day and expires in one year. Subscribes to the call
426+
* asynchronously and prints out the newly {@link KeyVaultKey created key} details contained in the
427+
* {@link Response HTTP response} when it has been received.</p>
428+
* <!-- src_embed com.azure.security.keyvault.keys.KeyAsyncClient.createExternalKeyWithResponse#CreateExternalKeyOptions -->
429+
* <pre>
430+
* CreateExternalKeyOptions options =
431+
* new CreateExternalKeyOptions&#40;&quot;keyName&quot;, new ExternalKey&#40;&quot;external-key-reference-id&quot;&#41;&#41;
432+
* .setNotBefore&#40;OffsetDateTime.now&#40;&#41;.plusDays&#40;1&#41;&#41;
433+
* .setExpiresOn&#40;OffsetDateTime.now&#40;&#41;.plusYears&#40;1&#41;&#41;;
434+
*
435+
* keyAsyncClient.createExternalKeyWithResponse&#40;options&#41;
436+
* .contextWrite&#40;Context.of&#40;&quot;key1&quot;, &quot;value1&quot;, &quot;key2&quot;, &quot;value2&quot;&#41;&#41;
437+
* .subscribe&#40;response -&gt;
438+
* System.out.printf&#40;&quot;Created external key with name: %s and id: %s %n&quot;,
439+
* response.getValue&#40;&#41;.getName&#40;&#41;, response.getValue&#40;&#41;.getId&#40;&#41;&#41;&#41;;
440+
* </pre>
441+
* <!-- end com.azure.security.keyvault.keys.KeyAsyncClient.createExternalKeyWithResponse#CreateExternalKeyOptions -->
442+
*
443+
* @param createExternalKeyOptions The {@link CreateExternalKeyOptions options object} containing information about
444+
* the external key being registered.
445+
*
446+
* @return A {@link Mono} containing a {@link Response} whose {@link Response#getValue() value} contains the
447+
* {@link KeyVaultKey created key}.
448+
*
449+
* @throws HttpResponseException If {@link CreateExternalKeyOptions#getName()} is an empty string.
450+
* @throws NullPointerException If {@code createExternalKeyOptions} is null.
451+
*/
452+
@ServiceMethod(returns = ReturnType.SINGLE)
453+
public Mono<Response<KeyVaultKey>>
454+
createExternalKeyWithResponse(CreateExternalKeyOptions createExternalKeyOptions) {
455+
try {
456+
if (createExternalKeyOptions == null) {
457+
return monoError(LOGGER, new NullPointerException("'createExternalKeyOptions' cannot be null."));
458+
}
459+
460+
KeyCreateParameters keyCreateParameters = new KeyCreateParameters(createExternalKeyOptions.getKeyType())
461+
.setKeyOps(createExternalKeyOptions.getKeyOperations())
462+
.setKeyAttributes(createKeyAttributes(createExternalKeyOptions))
463+
.setTags(createExternalKeyOptions.getTags())
464+
.setReleasePolicy(mapKeyReleasePolicy(createExternalKeyOptions.getReleasePolicy()));
465+
466+
return implClient
467+
.createKeyWithResponseAsync(createExternalKeyOptions.getName(),
468+
BinaryData.fromObject(keyCreateParameters), EMPTY_OPTIONS)
469+
.onErrorMap(HttpResponseException.class, KeyAsyncClient::mapCreateKeyException)
470+
.map(response -> new SimpleResponse<>(response,
471+
createKeyVaultKey(response.getValue().toObject(KeyBundle.class))));
472+
} catch (RuntimeException e) {
473+
return monoError(LOGGER, e);
474+
}
475+
}
476+
373477
/**
374478
* Creates a new {@link KeyVaultKey key} and stores it in the key vault. The create key operation can be used to
375479
* create any {@link KeyType keyType} in Azure Key Vault. If a {@link KeyVaultKey key} with the provided name

0 commit comments

Comments
 (0)