AwsEncryptionSDK/runtimes/net/TestVectorsNative/TestVectorLib/MaterialProviderFactory.cs (267 lines of code) (raw):
// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
using System.Diagnostics;
using Newtonsoft.Json;
using Amazon;
using Amazon.DynamoDBv2;
using Amazon.KeyManagementService;
using AWS.Cryptography.KeyStore;
using AWS.Cryptography.MaterialProviders;
using AWS.Cryptography.MaterialProvidersTestVectorKeys;
using RSAEncryption;
namespace TestVectors
{
public enum CryptoOperation
{
ENCRYPT, DECRYPT
}
public static class MaterialProviderFactory
{
private static readonly MaterialProviders materialProviders = new(new MaterialProvidersConfig());
private static KeyVectors singletonKeyVectors;
public static ICryptographicMaterialsManager CreateDecryptCmm(
DecryptVector vector,
Dictionary<string, Key> keys,
string vectorId
) {
ICryptographicMaterialsManager cmm;
ICryptographicMaterialsManager defaultCMM = materialProviders.CreateDefaultCryptographicMaterialsManager(
new CreateDefaultCryptographicMaterialsManagerInput
{
Keyring = CreateDecryptKeyring(vector, keys)
});
switch (vector.CMM)
{
case "RequiredEncryptionContext":
if (vector.EncryptionContext is null)
{
throw new Utils.InvalidDecryptVectorException(
$"RequiredEncryptionContext requires Encryption Context! ID = {vector}");
}
CreateRequiredEncryptionContextCMMInput requiredCMM = new CreateRequiredEncryptionContextCMMInput
{
UnderlyingCMM = defaultCMM,
RequiredEncryptionContextKeys = new List<string>(vector.EncryptionContext.Keys)
};
cmm = materialProviders.CreateRequiredEncryptionContextCMM(requiredCMM);
break;
default:
cmm = defaultCMM;
break;
}
return cmm;
}
private static IKeyring CreateDecryptKeyring(DecryptVector vector, Dictionary<string, Key> keys) {
List<IKeyring> children = new List<IKeyring>();
Debug.Assert(vector.MasterKeys != null, "vector.MasterKeys != null");
foreach (MasterKey keyInfo in vector.MasterKeys)
{
// Some keyrings, like discovery KMS keyrings, do not specify keys
Key key = keyInfo.Key == null ? null : keys[keyInfo.Key];
children.Add(CreateKeyring(keyInfo, key, CryptoOperation.DECRYPT));
}
CreateMultiKeyringInput createMultiKeyringInput = new CreateMultiKeyringInput
{
Generator = null,
ChildKeyrings = children
};
return materialProviders.CreateMultiKeyring(createMultiKeyringInput);
}
public static ICryptographicMaterialsManager CreateEncryptCmm(EncryptVector vector,
Dictionary<string, Key> keys)
{
ICryptographicMaterialsManager cmm;
ICryptographicMaterialsManager defaultCMM = materialProviders.CreateDefaultCryptographicMaterialsManager(
new CreateDefaultCryptographicMaterialsManagerInput
{
Keyring = CreateEncryptKeyring(vector, keys)
});
switch (vector.Scenario.CMM)
{
case "RequiredEncryptionContext":
CreateRequiredEncryptionContextCMMInput requiredCMM = new CreateRequiredEncryptionContextCMMInput
{
UnderlyingCMM = defaultCMM,
RequiredEncryptionContextKeys = new List<string>(vector.Scenario.EncryptionContext.Keys)
};
cmm = materialProviders.CreateRequiredEncryptionContextCMM(requiredCMM);
break;
default:
cmm = defaultCMM;
break;
}
return cmm;
}
private static IKeyring CreateEncryptKeyring(EncryptVector vector, Dictionary<string, Key> keys)
{
IList<MasterKey> masterKeys = vector.Scenario.MasterKeys;
Debug.Assert(masterKeys.Count >= 1);
Key generatorKey = keys[masterKeys[0].Key];
IKeyring generatorKeyring = CreateKeyring(masterKeys[0], generatorKey, CryptoOperation.ENCRYPT);
List<IKeyring> children = masterKeys
.Skip(1)
.Select(masterKey =>
{
Key key = keys[masterKey.Key];
return CreateKeyring(masterKey, key, CryptoOperation.ENCRYPT);
})
.ToList();
CreateMultiKeyringInput createMultiKeyringInput = new CreateMultiKeyringInput
{
Generator = generatorKeyring,
ChildKeyrings = children
};
return materialProviders.CreateMultiKeyring(createMultiKeyringInput);
}
private static IKeyring CreateKeyring(MasterKey keyInfo, Key key, CryptoOperation operation) {
if (keyInfo.Type == "aws-kms") {
CreateAwsKmsKeyringInput createKeyringInput = new CreateAwsKmsKeyringInput
{
KmsClient = new AmazonKeyManagementServiceClient(GetRegionForArn(key.Id)),
KmsKeyId = key.Id,
};
return materialProviders.CreateAwsKmsKeyring(createKeyringInput);
}
if (keyInfo.Type == "aws-kms-mrk-aware") {
CreateAwsKmsMrkKeyringInput createKeyringInput = new CreateAwsKmsMrkKeyringInput
{
KmsClient = new AmazonKeyManagementServiceClient(GetRegionForArn(key.Id)),
KmsKeyId = key.Id,
};
return materialProviders.CreateAwsKmsMrkKeyring(createKeyringInput);
}
if (keyInfo.Type == "aws-kms-mrk-aware-discovery" && operation == CryptoOperation.DECRYPT) {
AWS.Cryptography.MaterialProviders.DiscoveryFilter filter = null;
if (keyInfo.AwsKmsDiscoveryFilter != null)
{
filter = new AWS.Cryptography.MaterialProviders.DiscoveryFilter
{
AccountIds = (List<string>)keyInfo.AwsKmsDiscoveryFilter.AccountIds,
Partition = keyInfo.AwsKmsDiscoveryFilter.Partition,
};
}
CreateAwsKmsMrkDiscoveryKeyringInput createKeyringInput = new CreateAwsKmsMrkDiscoveryKeyringInput
{
KmsClient = new AmazonKeyManagementServiceClient(RegionEndpoint.GetBySystemName(keyInfo.DefaultMrkRegion)),
Region = keyInfo.DefaultMrkRegion,
DiscoveryFilter = filter,
};
return materialProviders.CreateAwsKmsMrkDiscoveryKeyring(createKeyringInput);
}
if (keyInfo.Type == "aws-kms-hierarchy") {
// Lazily create a singleton KeyVectors client.
// A KeyVectors manifest is only required if a test vector specifies a hierarchy keyring.
// This specification can only be determined at runtime while reading the test vector manifest.
if (singletonKeyVectors == null) {
string manifestPath;
try
{
manifestPath = Utils.GetEnvironmentVariableOrError("DAFNY_AWS_ESDK_TEST_VECTOR_MANIFEST_PATH");
}
catch (ArgumentException e)
{
throw new ArgumentException("Hierarchy keyring test vectors must supply a KeyVectors manifest", e);
}
DecryptManifest manifest = Utils.LoadObjectFromPath<DecryptManifest>(manifestPath);
KeyVectorsConfig keyVectorsConfig = new KeyVectorsConfig
{
KeyManifestPath = Utils.ManifestUriToPath(manifest.KeysUri, manifestPath)
};
singletonKeyVectors = new(keyVectorsConfig);
}
// Convert JSON to bytes for KeyVectors input
string jsonString = JsonConvert.SerializeObject(keyInfo);
var stream = new MemoryStream();
var writer = new StreamWriter(stream);
writer.Write(jsonString);
writer.Flush();
stream.Position = 0;
// Create KeyVectors keyring
var getKeyDescriptionInput = new GetKeyDescriptionInput
{
Json = stream
};
var desc = singletonKeyVectors.GetKeyDescription(getKeyDescriptionInput);
var testVectorKeyringInput = new TestVectorKeyringInput
{
KeyDescription = desc.KeyDescription
};
var keyring = singletonKeyVectors.CreateTestVectorKeyring(
testVectorKeyringInput
);
return keyring!;
}
if (keyInfo.Type == "raw" && keyInfo.EncryptionAlgorithm == "aes") {
CreateRawAesKeyringInput createKeyringInput = new CreateRawAesKeyringInput
{
KeyNamespace = keyInfo.ProviderId,
KeyName = key.Id,
WrappingKey = new MemoryStream(Convert.FromBase64String(key.Material)),
WrappingAlg = AesAlgorithmFromBits(key.Bits),
};
return materialProviders.CreateRawAesKeyring(createKeyringInput);
}
if (keyInfo.Type == "raw" && keyInfo.EncryptionAlgorithm == "rsa" && key.Type == "private") {
PaddingScheme padding = RSAPaddingFromStrings(keyInfo.PaddingAlgorithm, keyInfo.PaddingHash);
byte[] privateKey = RSA.ParsePEMString(key.Material);
CreateRawRsaKeyringInput createKeyringInput = new CreateRawRsaKeyringInput
{
KeyNamespace = keyInfo.ProviderId,
KeyName = key.Id,
PaddingScheme = padding,
PrivateKey = new MemoryStream(privateKey)
};
// Extract the public key if we need to encrypt
if (operation == CryptoOperation.ENCRYPT)
{
byte[] publicKey = RSA.GetPublicKeyFromPrivateKeyPemString(key.Material);
createKeyringInput.PublicKey = new MemoryStream(publicKey);
}
return materialProviders.CreateRawRsaKeyring(createKeyringInput);
}
if (keyInfo.Type == "raw" && keyInfo.EncryptionAlgorithm == "rsa" && key.Type == "public") {
PaddingScheme padding = RSAPaddingFromStrings(keyInfo.PaddingAlgorithm, keyInfo.PaddingHash);
byte[] publicKey = RSA.ParsePEMString(key.Material);
CreateRawRsaKeyringInput createKeyringInput = new CreateRawRsaKeyringInput
{
KeyNamespace = keyInfo.ProviderId,
KeyName = key.Id,
PaddingScheme = padding,
PublicKey = new MemoryStream(publicKey)
};
return materialProviders.CreateRawRsaKeyring(createKeyringInput);
}
// string operationStr = operation == CryptoOperation.ENCRYPT
// ? "encryption"
// : "decryption";
throw new Exception($"Unsupported keyring {keyInfo.Type} type for {operation}");
}
private static AesWrappingAlg AesAlgorithmFromBits(ushort bits) {
switch (bits) {
case 128:
return AesWrappingAlg.ALG_AES128_GCM_IV12_TAG16;
case 192:
return AesWrappingAlg.ALG_AES192_GCM_IV12_TAG16;
case 256:
return AesWrappingAlg.ALG_AES256_GCM_IV12_TAG16;
default: throw new Exception("Unsupported AES wrapping algorithm");
}
}
private static PaddingScheme RSAPaddingFromStrings(string strAlg, string strHash) {
switch (strAlg)
{
case "pkcs1":
return PaddingScheme.PKCS1;
case "oaep-mgf1":
switch (strHash)
{
case "sha1": return PaddingScheme.OAEP_SHA1_MGF1;
case "sha256": return PaddingScheme.OAEP_SHA256_MGF1;
case "sha384": return PaddingScheme.OAEP_SHA384_MGF1;
case "sha512": return PaddingScheme.OAEP_SHA512_MGF1;
}
break;
}
throw new Exception("Unsupported RSA Padding " + strAlg + strHash);
}
private static RegionEndpoint GetRegionForArn(string keyId)
{
try
{
Arn arn = Arn.Parse(keyId);
return RegionEndpoint.GetBySystemName(arn.Region);
} catch (ArgumentException)
{
// Some of our test vector key definitions use a variety of
// malformed key ids.
// These are usually meant to fail (since decryption requires
// matching configured key arns to ciphertext key arns),
// but in order for them to fail correctly
// we need to at least be able to create our client,
// so we choose a default region
return RegionEndpoint.GetBySystemName("us-west-2");
}
}
}
}