Skip to content

Commit

Permalink
WIP: Implement secure key release
Browse files Browse the repository at this point in the history
Resolves Azure#14892
  • Loading branch information
heaths committed Oct 14, 2020
1 parent a69a529 commit fa7f009
Show file tree
Hide file tree
Showing 22 changed files with 643 additions and 14 deletions.
10 changes: 10 additions & 0 deletions sdk/keyvault/Azure.Security.KeyVault.Keys/src/CreateKeyOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,16 @@ public CreateKeyOptions()
/// </summary>
public bool? Enabled { get; set; }

/// <summary>
/// Gets or sets a value indicating whether the private key can be exported.
/// </summary>
public bool? Exportable { get; set; }

/// <summary>
/// Gets or sets the policy rules under which the key can be exported.
/// </summary>
public KeyReleasePolicy ReleasePolicy { get; set; }

/// <summary>
/// Gets a dictionary of tags with specific metadata about the key. Although this collection cannot be set, it can be modified
/// or initialized with a <see href="https://docs.microsoft.com/dotnet/csharp/programming-guide/classes-and-structs/how-to-initialize-a-dictionary-with-a-collection-initializer">collection initializer</see>.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ public class CreateRsaKeyOptions : CreateKeyOptions
/// </summary>
public int? KeySize { get; set; }

/// <summary>
/// Gets or sets the public exponent for a RSA key.
/// </summary>
public int? PublicExponent { get; set; }

/// <summary>
/// Gets a value indicating whether to create a hardware-protected key in a hardware security module (HSM).
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ protected CryptographyClient()
/// Initializes a new instance of the <see cref="CryptographyClient"/> class.
/// </summary>
/// <param name="keyId">
/// The <see cref="KeyProperties.Id"/> of the <see cref="KeyVaultKey"/> which will be used for cryptographic operations.
/// The key identifier of the <see cref="KeyVaultKey"/> which will be used for cryptographic operations.
/// If you have a key <see cref="Uri"/>, use <see cref="KeyVaultKeyIdentifier"/> to parse the <see cref="KeyVaultKeyIdentifier.VaultUri"/> and other information.
/// </param>
/// <param name="credential">A <see cref="TokenCredential"/> used to authenticate requests to the vault, like DefaultAzureCredential.</param>
Expand All @@ -48,7 +48,7 @@ public CryptographyClient(Uri keyId, TokenCredential credential)
/// Initializes a new instance of the <see cref="CryptographyClient"/> class.
/// </summary>
/// <param name="keyId">
/// The <see cref="KeyProperties.Id"/> of the <see cref="KeyVaultKey"/> which will be used for cryptographic operations.
/// The key identifier of the <see cref="KeyVaultKey"/> which will be used for cryptographic operations.
/// If you have a key <see cref="Uri"/>, use <see cref="KeyVaultKeyIdentifier"/> to parse the <see cref="KeyVaultKeyIdentifier.VaultUri"/> and other information.
/// </param>
/// <param name="credential">A <see cref="TokenCredential"/> used to authenticate requests to the vault, like DefaultAzureCredential.</param>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ internal DecryptResult()
}

/// <summary>
/// Gets the <see cref="KeyProperties.Id"/> of the <see cref="KeyVaultKey"/> used to decrypt.
/// Gets the key identifier of the <see cref="KeyVaultKey"/> used to decrypt.
/// </summary>
public string KeyId { get; internal set; }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ internal EncryptResult()
}

/// <summary>
/// Gets the <see cref="KeyProperties.Id"/> of the <see cref="KeyVaultKey"/> used to encrypt. This must be stored alongside the <see cref="Ciphertext"/> as the same key must be used to decrypt it.
/// Gets the key identifier of the <see cref="KeyVaultKey"/> used to encrypt. This must be stored alongside the <see cref="Ciphertext"/> as the same key must be used to decrypt it.
/// </summary>
public string KeyId { get; internal set; }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ internal SignResult()
}

/// <summary>
/// Gets the <see cref="KeyProperties.Id"/> of the <see cref="KeyVaultKey"/> used to sign. This must be stored alongside the <see cref="Signature"/> as the same key must be used to verify it.
/// Gets the key identifier of the <see cref="KeyVaultKey"/> used to sign. This must be stored alongside the <see cref="Signature"/> as the same key must be used to verify it.
/// </summary>
public string KeyId { get; internal set; }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ internal UnwrapResult()
}

/// <summary>
/// Gets the <see cref="KeyProperties.Id"/> of the <see cref="Key"/> used to uwrap.
/// Gets the key identifier of the <see cref="Key"/> used to uwrap.
/// </summary>
public string KeyId { get; internal set; }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ internal VerifyResult()
}

/// <summary>
/// Gets the <see cref="KeyProperties.Id"/> of the <see cref="KeyVaultKey"/> used to verify.
/// Gets the key identifier of the <see cref="KeyVaultKey"/> used to verify.
/// </summary>
public string KeyId { get; internal set; }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ internal WrapResult()
}

/// <summary>
/// Gets the <see cref="KeyProperties.Id"/> of the <see cref="KeyVaultKey"/> used to wrap the <see cref="EncryptedKey"/>. This must be stored alongside the <see cref="EncryptedKey"/> as the same key must be used to unwrap it.
/// Gets the key identifier of the <see cref="KeyVaultKey"/> used to wrap the <see cref="EncryptedKey"/>. This must be stored alongside the <see cref="EncryptedKey"/> as the same key must be used to unwrap it.
/// </summary>
public string KeyId { get; internal set; }

Expand Down
16 changes: 16 additions & 0 deletions sdk/keyvault/Azure.Security.KeyVault.Keys/src/ImportKeyOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,12 @@ public class ImportKeyOptions : IJsonSerializable
private const string KeyPropertyName = "key";
private const string TagsPropertyName = "tags";
private const string HsmPropertyName = "hsm";
private const string ReleasePolicyPropertyName = "release_policy";

private static readonly JsonEncodedText s_keyPropertyNameBytes = JsonEncodedText.Encode(KeyPropertyName);
private static readonly JsonEncodedText s_tagsPropertyNameBytes = JsonEncodedText.Encode(TagsPropertyName);
private static readonly JsonEncodedText s_hsmPropertyNameBytes = JsonEncodedText.Encode(HsmPropertyName);
private static readonly JsonEncodedText s_releasePolicyPropertyNameBytes = JsonEncodedText.Encode(ReleasePolicyPropertyName);

/// <summary>
/// Initializes a new instance of the <see cref="ImportKeyOptions"/> class.
Expand Down Expand Up @@ -54,6 +56,11 @@ public ImportKeyOptions(string name, JsonWebKey keyMaterial)
/// </summary>
public bool? HardwareProtected { get; set; }

/// <summary>
/// Gets or sets the policy rules under which the key can be exported.
/// </summary>
public KeyReleasePolicy ReleasePolicy { get; set; }

/// <summary>
/// Gets additional properties of the <see cref="KeyVaultKey"/>.
/// </summary>
Expand Down Expand Up @@ -88,6 +95,15 @@ void IJsonSerializable.WriteProperties(Utf8JsonWriter json)
{
json.WriteBoolean(s_hsmPropertyNameBytes, HardwareProtected.Value);
}

if (ReleasePolicy != null)
{
json.WriteStartObject(s_releasePolicyPropertyNameBytes);

ReleasePolicy.WriteProperties(json);

json.WriteEndObject();
}
}
}
}
12 changes: 12 additions & 0 deletions sdk/keyvault/Azure.Security.KeyVault.Keys/src/KeyAttributes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,12 @@ internal struct KeyAttributes
private const string UpdatedPropertyName = "updated";
private const string RecoverableDaysPropertyName = "recoverableDays";
private const string RecoveryLevelPropertyName = "recoveryLevel";
private const string ExportablePropertyName = "exportable";

private static readonly JsonEncodedText s_enabledPropertyNameBytes = JsonEncodedText.Encode(EnabledPropertyName);
private static readonly JsonEncodedText s_notBeforePropertyNameBytes = JsonEncodedText.Encode(NotBeforePropertyName);
private static readonly JsonEncodedText s_expiresPropertyNameBytes = JsonEncodedText.Encode(ExpiresPropertyName);
private static readonly JsonEncodedText s_exportablePropertyNameBytes = JsonEncodedText.Encode(ExportablePropertyName);

public bool? Enabled { get; set; }

Expand All @@ -34,6 +36,8 @@ internal struct KeyAttributes

public string RecoveryLevel { get; internal set; }

public bool? Exportable { get; internal set; }

internal bool ShouldSerialize => Enabled.HasValue && NotBefore.HasValue && ExpiresOn.HasValue;

internal void ReadProperties(JsonElement json)
Expand Down Expand Up @@ -63,6 +67,9 @@ internal void ReadProperties(JsonElement json)
case RecoveryLevelPropertyName:
RecoveryLevel = prop.Value.GetString();
break;
case ExportablePropertyName:
Exportable = prop.Value.GetBoolean();
break;
}
}
}
Expand All @@ -84,6 +91,11 @@ internal void WriteProperties(Utf8JsonWriter json)
json.WriteNumber(s_expiresPropertyNameBytes, ExpiresOn.Value.ToUnixTimeSeconds());
}

if (Exportable.HasValue)
{
json.WriteBoolean(s_exportablePropertyNameBytes, Exportable.Value);
}

// Created is read-only don't serialize
// Updated is read-only don't serialize
// RecoverableDays is read-only don't serialize
Expand Down
84 changes: 78 additions & 6 deletions sdk/keyvault/Azure.Security.KeyVault.Keys/src/KeyClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -411,7 +411,7 @@ public virtual Pageable<KeyProperties> GetPropertiesOfKeys(CancellationToken can
{
Uri firstPageUri = _pipeline.CreateFirstPageUri(KeysPath);

return PageResponseEnumerator.CreateEnumerable(nextLink => _pipeline.GetPage(firstPageUri, nextLink, () => new KeyProperties(), "KeyClient.GetPropertiesOfKeys", cancellationToken));
return PageResponseEnumerator.CreateEnumerable(nextLink => _pipeline.GetPage(firstPageUri, nextLink, () => new KeyProperties(), $"{nameof(KeyClient)}.{nameof(GetPropertiesOfKeys)}", cancellationToken));
}

/// <summary>
Expand All @@ -429,7 +429,7 @@ public virtual AsyncPageable<KeyProperties> GetPropertiesOfKeysAsync(Cancellatio
{
Uri firstPageUri = _pipeline.CreateFirstPageUri(KeysPath);

return PageResponseEnumerator.CreateAsyncEnumerable(nextLink => _pipeline.GetPageAsync(firstPageUri, nextLink, () => new KeyProperties(), "KeyClient.GetPropertiesOfKeys", cancellationToken));
return PageResponseEnumerator.CreateAsyncEnumerable(nextLink => _pipeline.GetPageAsync(firstPageUri, nextLink, () => new KeyProperties(), $"{nameof(KeyClient)}.{nameof(GetPropertiesOfKeys)}", cancellationToken));
}

/// <summary>
Expand All @@ -450,7 +450,7 @@ public virtual Pageable<KeyProperties> GetPropertiesOfKeyVersions(string name, C

Uri firstPageUri = _pipeline.CreateFirstPageUri($"{KeysPath}{name}/versions");

return PageResponseEnumerator.CreateEnumerable(nextLink => _pipeline.GetPage(firstPageUri, nextLink, () => new KeyProperties(), "KeyClient.GetPropertiesOfKeyVersions", cancellationToken));
return PageResponseEnumerator.CreateEnumerable(nextLink => _pipeline.GetPage(firstPageUri, nextLink, () => new KeyProperties(), $"{nameof(KeyClient)}.{nameof(GetPropertiesOfKeyVersions)}", cancellationToken));
}

/// <summary>
Expand All @@ -471,7 +471,7 @@ public virtual AsyncPageable<KeyProperties> GetPropertiesOfKeyVersionsAsync(stri

Uri firstPageUri = _pipeline.CreateFirstPageUri($"{KeysPath}{name}/versions");

return PageResponseEnumerator.CreateAsyncEnumerable(nextLink => _pipeline.GetPageAsync(firstPageUri, nextLink, () => new KeyProperties(), "KeyClient.GetPropertiesOfKeyVersions", cancellationToken));
return PageResponseEnumerator.CreateAsyncEnumerable(nextLink => _pipeline.GetPageAsync(firstPageUri, nextLink, () => new KeyProperties(), $"{nameof(KeyClient)}.{nameof(GetPropertiesOfKeyVersions)}", cancellationToken));
}

/// <summary>
Expand Down Expand Up @@ -637,7 +637,7 @@ public virtual Pageable<DeletedKey> GetDeletedKeys(CancellationToken cancellatio
{
Uri firstPageUri = _pipeline.CreateFirstPageUri(DeletedKeysPath);

return PageResponseEnumerator.CreateEnumerable(nextLink => _pipeline.GetPage(firstPageUri, nextLink, () => new DeletedKey(), "KeyClient.GetDeletedKeys", cancellationToken));
return PageResponseEnumerator.CreateEnumerable(nextLink => _pipeline.GetPage(firstPageUri, nextLink, () => new DeletedKey(), $"{nameof(KeyClient)}.{nameof(GetDeletedKeys)}", cancellationToken));
}

/// <summary>
Expand All @@ -657,7 +657,7 @@ public virtual AsyncPageable<DeletedKey> GetDeletedKeysAsync(CancellationToken c
{
Uri firstPageUri = _pipeline.CreateFirstPageUri(DeletedKeysPath);

return PageResponseEnumerator.CreateAsyncEnumerable(nextLink => _pipeline.GetPageAsync(firstPageUri, nextLink, () => new DeletedKey(), "KeyClient.GetDeletedKeys", cancellationToken));
return PageResponseEnumerator.CreateAsyncEnumerable(nextLink => _pipeline.GetPageAsync(firstPageUri, nextLink, () => new DeletedKey(), $"{nameof(KeyClient)}.{nameof(GetDeletedKeys)}", cancellationToken));
}

/// <summary>
Expand Down Expand Up @@ -1053,6 +1053,7 @@ public virtual async Task<Response<KeyVaultKey>> ImportKeyAsync(string name, Jso
/// </remarks>
/// <param name="importKeyOptions">The key import configuration object containing information about the <see cref="JsonWebKey"/> being imported.</param>
/// <param name="cancellationToken">A <see cref="CancellationToken"/> controlling the request lifetime.</param>
/// <returns>The <see cref="KeyVaultKey"/> that was imported.</returns>
/// <exception cref="ArgumentNullException"><paramref name="importKeyOptions"/> is null.</exception>
/// <exception cref="RequestFailedException">The server returned an error. See <see cref="Exception.Message"/> for details returned from the server.</exception>
public virtual Response<KeyVaultKey> ImportKey(ImportKeyOptions importKeyOptions, CancellationToken cancellationToken = default)
Expand Down Expand Up @@ -1086,6 +1087,7 @@ public virtual Response<KeyVaultKey> ImportKey(ImportKeyOptions importKeyOptions
/// </remarks>
/// <param name="importKeyOptions">The key import configuration object containing information about the <see cref="JsonWebKey"/> being imported.</param>
/// <param name="cancellationToken">A <see cref="CancellationToken"/> controlling the request lifetime.</param>
/// <returns>The <see cref="KeyVaultKey"/> that was imported.</returns>
/// <exception cref="ArgumentNullException"><paramref name="importKeyOptions"/> is null.</exception>
/// <exception cref="RequestFailedException">The server returned an error. See <see cref="Exception.Message"/> for details returned from the server.</exception>
public virtual async Task<Response<KeyVaultKey>> ImportKeyAsync(ImportKeyOptions importKeyOptions, CancellationToken cancellationToken = default)
Expand All @@ -1106,5 +1108,75 @@ public virtual async Task<Response<KeyVaultKey>> ImportKeyAsync(ImportKeyOptions
throw;
}
}

/// <summary>
/// Exports a <see cref="KeyVaultKey"/> including the private key if originally created with <see cref="CreateKeyOptions.Exportable"/> set to true,
/// or imported with <see cref="KeyProperties.Exportable"/> in <see cref="ImportKeyOptions"/> set to true.
/// </summary>
/// <remarks>
/// Requires the <see cref="KeyOperation.Export"/> permission.
/// </remarks>
/// <param name="name">The name of the key to export.</param>
/// <param name="version">The version of the key to export.</param>
/// <param name="environment">The target environment assertion.</param>
/// <param name="cancellationToken">A <see cref="CancellationToken"/> controlling the request lifetime.</param>
/// <returns>The <see cref="KeyVaultKey"/> that was exported along with the private key if exportable.</returns>
/// <exception cref="ArgumentException"><paramref name="name"/>, <paramref name="version"/>, or <paramref name="environment"/> is an empty string.</exception>
/// <exception cref="ArgumentNullException"><paramref name="name"/>, <paramref name="version"/>, or <paramref name="environment"/> is null.</exception>
public virtual Response<KeyVaultKey> ExportKey(string name, string version, string environment, CancellationToken cancellationToken = default)
{
Argument.AssertNotNullOrEmpty(name, nameof(name));
Argument.AssertNotNullOrEmpty(version, nameof(version));
Argument.AssertNotNullOrEmpty(environment, nameof(environment));

using DiagnosticScope scope = _pipeline.CreateScope($"{nameof(KeyClient)}.{nameof(ExportKey)}");
scope.AddAttribute("key", name);
scope.Start();

try
{
return _pipeline.SendRequest(RequestMethod.Post, new KeyExportParameters(environment), () => new KeyVaultKey(name), cancellationToken, KeysPath, name, "/", version, "/export");
}
catch (Exception e)
{
scope.Failed(e);
throw;
}
}

/// <summary>
/// Exports a <see cref="KeyVaultKey"/> including the private key if originally created with <see cref="CreateKeyOptions.Exportable"/> set to true,
/// or imported with <see cref="KeyProperties.Exportable"/> in <see cref="ImportKeyOptions"/> set to true.
/// </summary>
/// <remarks>
/// Requires the <see cref="KeyOperation.Export"/> permission.
/// </remarks>
/// <param name="name">The name of the key to export.</param>
/// <param name="version">The version of the key to export.</param>
/// <param name="environment">The target environment assertion.</param>
/// <param name="cancellationToken">A <see cref="CancellationToken"/> controlling the request lifetime.</param>
/// <returns>The <see cref="KeyVaultKey"/> that was exported along with the private key if exportable.</returns>
/// <exception cref="ArgumentException"><paramref name="name"/>, <paramref name="version"/>, or <paramref name="environment"/> is an empty string.</exception>
/// <exception cref="ArgumentNullException"><paramref name="name"/>, <paramref name="version"/>, or <paramref name="environment"/> is null.</exception>
public virtual async Task<Response<KeyVaultKey>> ExportKeyAsync(string name, string version, string environment, CancellationToken cancellationToken = default)
{
Argument.AssertNotNullOrEmpty(name, nameof(name));
Argument.AssertNotNullOrEmpty(version, nameof(version));
Argument.AssertNotNullOrEmpty(environment, nameof(environment));

using DiagnosticScope scope = _pipeline.CreateScope($"{nameof(KeyClient)}.{nameof(ExportKey)}");
scope.AddAttribute("key", name);
scope.Start();

try
{
return await _pipeline.SendRequestAsync(RequestMethod.Post, new KeyExportParameters(environment), () => new KeyVaultKey(name), cancellationToken, KeysPath, name, "/", version, "/export").ConfigureAwait(false);
}
catch (Exception e)
{
scope.Failed(e);
throw;
}
}
}
}
Loading

0 comments on commit fa7f009

Please sign in to comment.