Skip to content

Commit

Permalink
Add KeyClient.GetCryptographyClient (#23856)
Browse files Browse the repository at this point in the history
* Add KeyClient.GetCryptographyClient

Resolves #23786

* Resolve PR feedback
  • Loading branch information
heaths committed Sep 10, 2021
1 parent 6a5d9d5 commit dcd0678
Show file tree
Hide file tree
Showing 6 changed files with 97 additions and 9 deletions.
1 change: 1 addition & 0 deletions sdk/keyvault/Azure.Security.KeyVault.Keys/CHANGELOG.md
Expand Up @@ -4,6 +4,7 @@

### Features Added

- Added `KeyClient.GetCryptographyClient` to get a `CryptographyClient` that uses the same options, policies, and pipeline as the `KeyClient` that created it. ([#23786](https://github.com/Azure/azure-sdk-for-net/issues/23786))
- Added `KeyVaultKeyIdentifier.TryCreate` to parse key URIs without throwing an exception when invalid. ([#23146](https://github.com/Azure/azure-sdk-for-net/issues/23146))

### Breaking Changes
Expand Down
10 changes: 7 additions & 3 deletions sdk/keyvault/Azure.Security.KeyVault.Keys/README.md
Expand Up @@ -144,9 +144,9 @@ key = client.GetKey("key-name");
Once you've created a `KeyVaultKey` in the Azure Key Vault, you can also create the [CryptographyClient][crypto_client_class]:

```C# Snippet:CreateCryptographyClient
// Create a new cryptography client using the default credential from Azure.Identity using environment variables previously set,
// including AZURE_CLIENT_ID, AZURE_CLIENT_SECRET, and AZURE_TENANT_ID.
var cryptoClient = new CryptographyClient(keyId: key.Id, credential: new DefaultAzureCredential());
// Create a new cryptography client using the same Key Vault or Managed HSM endpoint, service version,
// and options as the KeyClient created earlier.
var cryptoClient = client.GetCryptographyClient(key.Name, key.Properties.Version);
```

## Key concepts
Expand Down Expand Up @@ -294,6 +294,10 @@ foreach (KeyProperties keyProperties in allKeys)
This example creates a `CryptographyClient` and uses it to encrypt and decrypt with a key in Azure Key Vault.

```C# Snippet:EncryptDecrypt
// Create a new cryptography client using the same Key Vault or Managed HSM endpoint, service version,
// and options as the KeyClient created earlier.
var cryptoClient = client.GetCryptographyClient(key.Name, key.Properties.Version);

byte[] plaintext = Encoding.UTF8.GetBytes("A single block of plaintext");

// encrypt the data using the algorithm RSAOAEP
Expand Down
Expand Up @@ -119,6 +119,7 @@ public partial class KeyClient
public virtual System.Threading.Tasks.Task<Azure.Response<Azure.Security.KeyVault.Keys.KeyVaultKey>> CreateOctKeyAsync(Azure.Security.KeyVault.Keys.CreateOctKeyOptions octKeyOptions, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
public virtual Azure.Response<Azure.Security.KeyVault.Keys.KeyVaultKey> CreateRsaKey(Azure.Security.KeyVault.Keys.CreateRsaKeyOptions rsaKeyOptions, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
public virtual System.Threading.Tasks.Task<Azure.Response<Azure.Security.KeyVault.Keys.KeyVaultKey>> CreateRsaKeyAsync(Azure.Security.KeyVault.Keys.CreateRsaKeyOptions rsaKeyOptions, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
public virtual Azure.Security.KeyVault.Keys.Cryptography.CryptographyClient GetCryptographyClient(string name, string version = null) { throw null; }
public virtual Azure.Response<Azure.Security.KeyVault.Keys.DeletedKey> GetDeletedKey(string name, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
public virtual System.Threading.Tasks.Task<Azure.Response<Azure.Security.KeyVault.Keys.DeletedKey>> GetDeletedKeyAsync(string name, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
public virtual Azure.Pageable<Azure.Security.KeyVault.Keys.DeletedKey> GetDeletedKeys(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
Expand Down
39 changes: 39 additions & 0 deletions sdk/keyvault/Azure.Security.KeyVault.Keys/src/KeyClient.cs
Expand Up @@ -3,6 +3,7 @@

using Azure.Core;
using Azure.Core.Pipeline;
using Azure.Security.KeyVault.Keys.Cryptography;
using System;
using System.Collections.Generic;
using System.Threading;
Expand Down Expand Up @@ -1334,5 +1335,43 @@ public virtual async Task<Response<ReleaseKeyResult>> ReleaseKeyAsync(string nam
throw;
}
}

/// <summary>
/// Get a <see cref="CryptographyClient"/> for the given key.
/// </summary>
/// <param name="name">The name of the key used to perform cryptographic operations.</param>
/// <param name="version">Optional version of the key used to perform cryptographic operations.</param>
/// <returns>A <see cref="CryptographyClient"/> using the same options and pipeline as this <see cref="KeyClient"/>.</returns>
/// <remarks>
/// <para>
/// Given a key <paramref name="name"/> and optional <paramref name="version"/>, a new <see cref="CryptographyClient"/> will be created
/// using the same <see cref="VaultUri"/> and options passed to this <see cref="KeyClient"/>, including the <see cref="KeyClientOptions.ServiceVersion"/>,
/// <see cref="ClientOptions.Diagnostics"/>, <see cref="ClientOptions.Retry"/>, and other options.
/// </para>
/// <para>
/// If you want to create a <see cref="CryptographyClient"/> using a different Key Vault or Managed HSM endpoint, with different options, or even with a
/// <see cref="JsonWebKey"/> you already have acquired, you can create a <see cref="CryptographyClient"/> directly with any of those alternatives.
/// </para>
/// </remarks>
/// <exception cref="ArgumentException"><paramref name="name"/> is an empty string.</exception>
/// <exception cref="ArgumentNullException"><paramref name="name"/> is null.</exception>
public virtual CryptographyClient GetCryptographyClient(string name, string version = null)
{
Argument.AssertNotNullOrEmpty(name, nameof(name));

Uri keyUri = CreateKeyUri(VaultUri, name, version);
return new(keyUri, _pipeline);
}

/// <summary>
/// Constructs a key <see cref="Uri"/>.
/// </summary>
/// <param name="vaultUri">The <see cref="Uri"/> to the Key Vault or Managed HSM.</param>
/// <param name="name">Name of the key.</param>
/// <param name="version">Optional version of the key.</param>
/// <returns>A key <see cref="Uri"/>.</returns>
internal static Uri CreateKeyUri(Uri vaultUri, string name, string version) => version is null
? new(vaultUri, KeysPath + name)
: new(vaultUri, $"{KeysPath}{name}/{version}");
}
}
37 changes: 37 additions & 0 deletions sdk/keyvault/Azure.Security.KeyVault.Keys/tests/KeyClientTests.cs
Expand Up @@ -10,6 +10,7 @@
using Azure.Core;
using Azure.Core.TestFramework;
using Azure.Identity;
using Azure.Security.KeyVault.Keys.Cryptography;
using Azure.Security.KeyVault.Tests;
using NUnit.Framework;

Expand Down Expand Up @@ -222,6 +223,42 @@ public void ReleaseKeyParameterValidation()
Assert.AreEqual("target", ex.ParamName);
}

[Test]
[SyncOnly]
public void GetCryptographyClientValidation()
{
ArgumentException ex = Assert.Throws<ArgumentNullException>(() => Client.GetCryptographyClient(null));
Assert.AreEqual("name", ex.ParamName);

ex = Assert.Throws<ArgumentException>(() => Client.GetCryptographyClient(string.Empty));
Assert.AreEqual("name", ex.ParamName);
}

[Test]
public async Task GetCryptographyClientUsesSamePipeline()
{
// Make sure the created CryptographyClient uses the same mock transport as the KeyVault that created it.
MockTransport transport = new(new[]
{
new MockResponse(200).WithContent(@"{""attributes"":{""created"":1626299777,""enabled"":true,""exportable"":false,""updated"":1626299777},""key"":{""key_ops"":[""wrapKey"",""unwrapKey""],""kid"":""https://test.managedhsm.azure.net/keys/test/abcd1234"",""kty"":""oct-HSM""}}"),
new MockResponse(200).WithContent(@"{""alg"":""A128KW"",""kid"":""https://test.managedhsm.azure.net/keys/test/abcd1234"",""value"":""dGVzdA""}"),
});

KeyClientOptions options = new()
{
Transport = transport,
};

KeyClient keyClient = new(new Uri("https://localhost"), new MockCredential(), options);
KeyVaultKey key = await keyClient.CreateEcKeyAsync(new CreateEcKeyOptions("test"));
Assert.AreEqual("test", key.Name);
Assert.AreEqual("abcd1234", key.Properties.Version);

CryptographyClient cryptographyClient = keyClient.GetCryptographyClient(key.Name, key.Properties.Version);
WrapResult result = await cryptographyClient.WrapKeyAsync(KeyWrapAlgorithm.A128KW, new byte[] { 0x74, 0x65, 0x73, 0x74 });
Assert.AreEqual(Convert.FromBase64String("dGVzdA=="), result.EncryptedKey);
}

private class MockCredential : TokenCredential
{
public override AccessToken GetToken(TokenRequestContext requestContext, CancellationToken cancellationToken) =>
Expand Down
Expand Up @@ -45,9 +45,9 @@ public void CreateClient()
#endregion

#region Snippet:CreateCryptographyClient
// Create a new cryptography client using the default credential from Azure.Identity using environment variables previously set,
// including AZURE_CLIENT_ID, AZURE_CLIENT_SECRET, and AZURE_TENANT_ID.
var cryptoClient = new CryptographyClient(keyId: key.Id, credential: new DefaultAzureCredential());
// Create a new cryptography client using the same Key Vault or Managed HSM endpoint, service version,
// and options as the KeyClient created earlier.
var cryptoClient = client.GetCryptographyClient(key.Name, key.Properties.Version);
#endregion

this.client = client;
Expand Down Expand Up @@ -117,7 +117,7 @@ public async Task CreateKeyAsync()
public void RetrieveKey()
{
// Make sure a key exists.
client.CreateKey("key-name", KeyType.Rsa);
client.CreateKey("key-name", KeyType.Rsa);

#region Snippet:RetrieveKey
KeyVaultKey key = client.GetKey("key-name");
Expand Down Expand Up @@ -174,6 +174,12 @@ await foreach (KeyProperties keyProperties in allKeys)
public void EncryptDecrypt()
{
#region Snippet:EncryptDecrypt
#if SNIPPET
// Create a new cryptography client using the same Key Vault or Managed HSM endpoint, service version,
// and options as the KeyClient created earlier.
var cryptoClient = client.GetCryptographyClient(key.Name, key.Properties.Version);
#endif

byte[] plaintext = Encoding.UTF8.GetBytes("A single block of plaintext");

// encrypt the data using the algorithm RSAOAEP
Expand Down Expand Up @@ -224,8 +230,8 @@ public async Task DeleteAndPurgeKey()
await client.PurgeDeletedKeyAsync(key.Name);
#endregion

DeleteKeyOperation rsaKeyOperation = client.StartDeleteKey("rsa-key-name");
DeleteKeyOperation ecKeyOperation = client.StartDeleteKey("ec-key-name");
DeleteKeyOperation rsaKeyOperation = client.StartDeleteKey("rsa-key-name");
DeleteKeyOperation ecKeyOperation = client.StartDeleteKey("ec-key-name");

try
{
Expand Down

0 comments on commit dcd0678

Please sign in to comment.