Skip to content

Commit

Permalink
Client Encryption : Adds MDE library code in Encryption Project to ta…
Browse files Browse the repository at this point in the history
…ke direct dependency on code and not on MDE nuget package. (Azure#2967)

This PR adds the MDE library code directly into Always Encrypted Cosmos DB project and removes the nuget dependency. This is to align with GA contract since MDE has not been GA'd yet. All the relevant files have been directly copied and all the public classes have been made internal.

FixMdePublicInterfaces.ps1 can be run if new files are to be added or if you need to merge fixes into this code by just replacing the files. This script adds the required header to exclude StyleCop checks and makes public classes internal.
  • Loading branch information
kr-santosh authored Jan 6, 2022
1 parent 56c75b5 commit 4609fbb
Show file tree
Hide file tree
Showing 107 changed files with 8,574 additions and 3 deletions.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
//------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//------------------------------------------------------------

// This file isn't generated, but this comment is necessary to exclude it from StyleCop analysis.
// <auto-generated/>

namespace Microsoft.Data.Encryption.AzureKeyVaultProvider
{
internal static class Constants
{
/// <summary>
/// Azure Key Vault Domain Name
/// </summary>
internal static readonly string[] AzureKeyVaultPublicDomainNames = new string[] {
@"vault.azure.net", // default
@"vault.azure.cn", // Azure China
@"vault.usgovcloudapi.net", // US Government
@"vault.microsoftazure.de", // Azure Germany
@"managedhsm.azure.net", // public HSM vault
@"managedhsm.azure.cn", // Azure China HSM vault
@"managedhsm.usgovcloudapi.net", // US Government HSM vault
@"managedhsm.microsoftazure.de" // Azure Germany HSM vault
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
//------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//------------------------------------------------------------

// This file isn't generated, but this comment is necessary to exclude it from StyleCop analysis.
// <auto-generated/>

using Azure;
using Azure.Core;
using Azure.Security.KeyVault.Keys;
using Azure.Security.KeyVault.Keys.Cryptography;
using System;
using System.Collections.Concurrent;
using System.Threading.Tasks;

using static Azure.Security.KeyVault.Keys.Cryptography.SignatureAlgorithm;
using static Microsoft.Data.Encryption.Resources.Strings;

namespace Microsoft.Data.Encryption.AzureKeyVaultProvider
{
internal class KeyCryptographer
{
/// <summary>
/// TokenCredential to be used with the KeyClient
/// </summary>
private TokenCredential TokenCredential { get; set; }

/// <summary>
/// A mapping of the KeyClient objects to the corresponding Azure Key Vault URI
/// </summary>
private readonly ConcurrentDictionary<Uri, KeyClient> _keyClientDictionary = new ConcurrentDictionary<Uri, KeyClient>();

/// <summary>
/// Holds references to the fetch key tasks and maps them to their corresponding Azure Key Vault Key Identifier (URI).
/// These tasks will be used for returning the key in the event that the fetch task has not finished depositing the
/// key into the key dictionary.
/// </summary>
private readonly ConcurrentDictionary<string, Task<Response<KeyVaultKey>>> _keyFetchTaskDictionary = new ConcurrentDictionary<string, Task<Response<KeyVaultKey>>>();

/// <summary>
/// Holds references to the Azure Key Vault keys and maps them to their corresponding Azure Key Vault Key Identifier (URI).
/// </summary>
private readonly ConcurrentDictionary<string, KeyVaultKey> _keyDictionary = new ConcurrentDictionary<string, KeyVaultKey>();

/// <summary>
/// Holds references to the Azure Key Vault CryptographyClient objects and maps them to their corresponding Azure Key Vault Key Identifier (URI).
/// </summary>
private readonly ConcurrentDictionary<string, CryptographyClient> _cryptoClientDictionary = new ConcurrentDictionary<string, CryptographyClient>();

/// <summary>
/// Constructs a new KeyCryptographer
/// </summary>
/// <param name="tokenCredential"></param>
internal KeyCryptographer(TokenCredential tokenCredential)
{
TokenCredential = tokenCredential;
}

/// <summary>
/// Adds the key, specified by the Key Identifier URI, to the cache.
/// </summary>
/// <param name="keyIdentifierUri"></param>
internal void AddKey(string keyIdentifierUri)
{
if (TheKeyHasNotBeenCached(keyIdentifierUri))
{
ParseAKVPath(keyIdentifierUri, out Uri vaultUri, out string keyName, out string keyVersion);
CreateKeyClient(vaultUri);
FetchKey(vaultUri, keyName, keyVersion, keyIdentifierUri);
}

bool TheKeyHasNotBeenCached(string k) => !_keyDictionary.ContainsKey(k) && !_keyFetchTaskDictionary.ContainsKey(k);
}

/// <summary>
/// Returns the key specified by the Key Identifier URI
/// </summary>
/// <param name="keyIdentifierUri"></param>
/// <returns></returns>
internal KeyVaultKey GetKey(string keyIdentifierUri)
{
if (_keyDictionary.ContainsKey(keyIdentifierUri))
{
_keyDictionary.TryGetValue(keyIdentifierUri, out KeyVaultKey key);
return key;
}

if (_keyFetchTaskDictionary.ContainsKey(keyIdentifierUri))
{
_keyFetchTaskDictionary.TryGetValue(keyIdentifierUri, out Task<Response<KeyVaultKey>> task);
return Task.Run(() => task).GetAwaiter().GetResult();
}

// Not a public exception - not likely to occur.
throw new MicrosoftDataEncryptionException(AzureKeyVaultKeyNotFound.Format(keyIdentifierUri));
}

/// <summary>
/// Gets the public Key size in bytes.
/// </summary>
/// <param name="keyIdentifierUri">The key vault key identifier URI</param>
/// <returns></returns>
internal int GetKeySize(string keyIdentifierUri)
{
return GetKey(keyIdentifierUri).Key.N.Length;
}

/// <summary>
/// Generates signature based on RSA PKCS#v1.5 scheme using a specified Azure Key Vault Key URL.
/// </summary>
/// <param name="message">The data to sign</param>
/// <param name="keyIdentifierUri">The key vault key identifier URI</param>
/// <returns></returns>
internal byte[] SignData(byte[] message, string keyIdentifierUri)
{
CryptographyClient cryptographyClient = GetCryptographyClient(keyIdentifierUri);
return cryptographyClient.SignData(RS256, message).Signature;
}

internal bool VerifyData(byte[] message, byte[] signature, string keyIdentifierUri)
{
CryptographyClient cryptographyClient = GetCryptographyClient(keyIdentifierUri);
return cryptographyClient.VerifyData(RS256, message, signature).IsValid;
}

internal byte[] UnwrapKey(KeyWrapAlgorithm keyWrapAlgorithm, byte[] encryptedKey, string keyIdentifierUri)
{
CryptographyClient cryptographyClient = GetCryptographyClient(keyIdentifierUri);
return cryptographyClient.UnwrapKey(keyWrapAlgorithm, encryptedKey).Key;
}

internal byte[] WrapKey(KeyWrapAlgorithm keyWrapAlgorithm, byte[] key, string keyIdentifierUri)
{
CryptographyClient cryptographyClient = GetCryptographyClient(keyIdentifierUri);
return cryptographyClient.WrapKey(keyWrapAlgorithm, key).EncryptedKey;
}

private CryptographyClient GetCryptographyClient(string keyIdentifierUri)
{
if (_cryptoClientDictionary.ContainsKey(keyIdentifierUri))
{
_cryptoClientDictionary.TryGetValue(keyIdentifierUri, out CryptographyClient client);
return client;
}

CryptographyClient cryptographyClient = new CryptographyClient(GetKey(keyIdentifierUri).Id, TokenCredential);
_cryptoClientDictionary.TryAdd(keyIdentifierUri, cryptographyClient);

return cryptographyClient;
}

/// <summary>
///
/// </summary>
/// <param name="vaultUri">The Azure Key Vault URI</param>
/// <param name="keyName">The name of the Azure Key Vault key</param>
/// <param name="keyVersion">The version of the Azure Key Vault key</param>
/// <param name="keyResourceUri">The Azure Key Vault key identifier</param>
private void FetchKey(Uri vaultUri, string keyName, string keyVersion, string keyResourceUri)
{
Task<Response<KeyVaultKey>> fetchKeyTask = FetchKeyFromKeyVault(vaultUri, keyName, keyVersion);
_keyFetchTaskDictionary.AddOrUpdate(keyResourceUri, fetchKeyTask, (k, v) => fetchKeyTask);

fetchKeyTask
.ContinueWith(k => ValidateRsaKey(k.GetAwaiter().GetResult()))
.ContinueWith(k => _keyDictionary.AddOrUpdate(keyResourceUri, k.GetAwaiter().GetResult(), (key, v) => k.GetAwaiter().GetResult()));

Task.Run(() => fetchKeyTask);
}

/// <summary>
/// Looks up the KeyClient object by it's URI and then fetches the key by name.
/// </summary>
/// <param name="vaultUri">The Azure Key Vault URI</param>
/// <param name="keyName">Then name of the key</param>
/// <param name="keyVersion">Then version of the key</param>
/// <returns></returns>
private Task<Response<KeyVaultKey>> FetchKeyFromKeyVault(Uri vaultUri, string keyName, string keyVersion)
{
_keyClientDictionary.TryGetValue(vaultUri, out KeyClient keyClient);
return keyClient.GetKeyAsync(keyName, keyVersion);
}

/// <summary>
/// Validates that a key is of type RSA
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
private KeyVaultKey ValidateRsaKey(KeyVaultKey key)
{
if (key.KeyType != KeyType.Rsa && key.KeyType != KeyType.RsaHsm)
{
throw new MicrosoftDataEncryptionException(NonRsaKeyTemplate.Format(key.KeyType));
}

return key;
}

/// <summary>
/// Instantiates and adds a KeyClient to the KeyClient dictionary
/// </summary>
/// <param name="vaultUri">The Azure Key Vault URI</param>
private void CreateKeyClient(Uri vaultUri)
{
if (!_keyClientDictionary.ContainsKey(vaultUri))
{
_keyClientDictionary.TryAdd(vaultUri, new KeyClient(vaultUri, TokenCredential));
}
}

/// <summary>
/// Validates and parses the Azure Key Vault URI and key name.
/// </summary>
/// <param name="keyEncryptionKeyPath">The Azure Key Vault key identifier</param>
/// <param name="vaultUri">The Azure Key Vault URI</param>
/// <param name="keyEncryptionKeyName">The name of the key</param>
/// <param name="keyEncryptionKeyVersion">The version of the key</param>
private void ParseAKVPath(string keyEncryptionKeyPath, out Uri vaultUri, out string keyEncryptionKeyName, out string keyEncryptionKeyVersion)
{
Uri masterKeyPathUri = new Uri(keyEncryptionKeyPath);
vaultUri = new Uri(masterKeyPathUri.GetLeftPart(UriPartial.Authority));
keyEncryptionKeyName = masterKeyPathUri.Segments[2];
keyEncryptionKeyVersion = masterKeyPathUri.Segments.Length > 3 ? masterKeyPathUri.Segments[3] : null;
}
}
}
Loading

0 comments on commit 4609fbb

Please sign in to comment.