// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Microsoft.AspNetCore.Cryptography;
using Microsoft.AspNetCore.Cryptography.Cng;
using Microsoft.AspNetCore.Cryptography.SafeHandles;
using Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel;
using Microsoft.AspNetCore.DataProtection.Cng;
using Microsoft.Extensions.Logging;
namespace Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption
{
///
/// Settings for configuring an authenticated encryption mechanism which uses
/// Windows CNG algorithms in GCM encryption + authentication modes.
///
public sealed class CngGcmAuthenticatedEncryptionSettings : IInternalAuthenticatedEncryptionSettings
{
///
/// The name of the algorithm to use for symmetric encryption.
/// This property corresponds to the 'pszAlgId' parameter of BCryptOpenAlgorithmProvider.
/// This property is required to have a value.
///
///
/// The algorithm must support CBC-style encryption and must have a block size exactly
/// 128 bits.
/// The default value is 'AES'.
///
[ApplyPolicy]
public string EncryptionAlgorithm { get; set; } = Constants.BCRYPT_AES_ALGORITHM;
///
/// The name of the provider which contains the implementation of the symmetric encryption algorithm.
/// This property corresponds to the 'pszImplementation' parameter of BCryptOpenAlgorithmProvider.
/// This property is optional.
///
///
/// The default value is null.
///
[ApplyPolicy]
public string EncryptionAlgorithmProvider { get; set; } = null;
///
/// The length (in bits) of the key that will be used for symmetric encryption.
/// This property is required to have a value.
///
///
/// The key length must be 128 bits or greater.
/// The default value is 256.
///
[ApplyPolicy]
public int EncryptionAlgorithmKeySize { get; set; } = 256;
///
/// Validates that this is well-formed, i.e.,
/// that the specified algorithm actually exists and can be instantiated properly.
/// An exception will be thrown if validation fails.
///
public void Validate()
{
// Run a sample payload through an encrypt -> decrypt operation to make sure data round-trips properly.
using (var encryptor = CreateAuthenticatedEncryptorInstance(Secret.Random(512 / 8)))
{
encryptor.PerformSelfTest();
}
}
/*
* HELPER ROUTINES
*/
internal GcmAuthenticatedEncryptor CreateAuthenticatedEncryptorInstance(ISecret secret, ILogger logger = null)
{
return new GcmAuthenticatedEncryptor(
keyDerivationKey: new Secret(secret),
symmetricAlgorithmHandle: GetSymmetricBlockCipherAlgorithmHandle(logger),
symmetricAlgorithmKeySizeInBytes: (uint)(EncryptionAlgorithmKeySize / 8));
}
private BCryptAlgorithmHandle GetSymmetricBlockCipherAlgorithmHandle(ILogger logger)
{
// basic argument checking
if (String.IsNullOrEmpty(EncryptionAlgorithm))
{
throw Error.Common_PropertyCannotBeNullOrEmpty(nameof(EncryptionAlgorithm));
}
if (EncryptionAlgorithmKeySize < 0)
{
throw Error.Common_PropertyMustBeNonNegative(nameof(EncryptionAlgorithmKeySize));
}
BCryptAlgorithmHandle algorithmHandle = null;
logger?.OpeningCNGAlgorithmFromProviderWithChainingModeGCM(EncryptionAlgorithm, EncryptionAlgorithmProvider);
// Special-case cached providers
if (EncryptionAlgorithmProvider == null)
{
if (EncryptionAlgorithm == Constants.BCRYPT_AES_ALGORITHM) { algorithmHandle = CachedAlgorithmHandles.AES_GCM; }
}
// Look up the provider dynamically if we couldn't fetch a cached instance
if (algorithmHandle == null)
{
algorithmHandle = BCryptAlgorithmHandle.OpenAlgorithmHandle(EncryptionAlgorithm, EncryptionAlgorithmProvider);
algorithmHandle.SetChainingMode(Constants.BCRYPT_CHAIN_MODE_GCM);
}
// make sure we're using a block cipher with an appropriate key size & block size
CryptoUtil.Assert(algorithmHandle.GetCipherBlockLength() == 128 / 8, "GCM requires a block cipher algorithm with a 128-bit block size.");
AlgorithmAssert.IsAllowableSymmetricAlgorithmKeySize(checked((uint)EncryptionAlgorithmKeySize));
// make sure the provided key length is valid
algorithmHandle.GetSupportedKeyLengths().EnsureValidKeyLength((uint)EncryptionAlgorithmKeySize);
// all good!
return algorithmHandle;
}
IInternalAuthenticatedEncryptorConfiguration IInternalAuthenticatedEncryptionSettings.ToConfiguration(IServiceProvider services)
{
return new CngGcmAuthenticatedEncryptorConfiguration(this, services);
}
}
}