// Copyright (c) Microsoft Open Technologies, Inc. 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.AspNet.Cryptography; using Microsoft.AspNet.Cryptography.Cng; using Microsoft.AspNet.Cryptography.SafeHandles; using Microsoft.AspNet.DataProtection.Cng; namespace Microsoft.AspNet.DataProtection.AuthenticatedEncryption { /// /// Options for configuring an authenticated encryption mechanism which uses /// Windows CNG algorithms in CBC encryption + HMAC validation modes. /// public sealed class CngCbcAuthenticatedEncryptorConfigurationOptions : IInternalConfigurationOptions { /// /// 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 of 64 bits or greater. /// The default value is 'AES'. /// 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. /// 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. /// public int EncryptionAlgorithmKeySize { get; set; } = 256; /// /// The name of the algorithm to use for hashing data. /// This property corresponds to the 'pszAlgId' parameter of BCryptOpenAlgorithmProvider. /// This property is required to have a value. /// /// /// The algorithm must support being opened in HMAC mode and must have a digest length /// of 128 bits or greater. /// The default value is 'SHA256'. /// public string HashAlgorithm { get; set; } = Constants.BCRYPT_SHA256_ALGORITHM; /// /// The name of the provider which contains the implementation of the hash algorithm. /// This property corresponds to the 'pszImplementation' parameter of BCryptOpenAlgorithmProvider. /// This property is optional. /// /// /// The default value is null. /// public string HashAlgorithmProvider { get; set; } = null; /// /// Makes a duplicate of this object, which allows the original object to remain mutable. /// internal CngCbcAuthenticatedEncryptorConfigurationOptions Clone() { return new CngCbcAuthenticatedEncryptorConfigurationOptions() { EncryptionAlgorithm = this.EncryptionAlgorithm, EncryptionAlgorithmKeySize = this.EncryptionAlgorithmKeySize, EncryptionAlgorithmProvider = this.EncryptionAlgorithmProvider, HashAlgorithm = this.HashAlgorithm, HashAlgorithmProvider = this.HashAlgorithmProvider }; } internal IAuthenticatedEncryptor CreateAuthenticatedEncryptor([NotNull] ISecret secret) { // Create the encryption object string encryptionAlgorithm = GetPropertyValueNotNullOrEmpty(EncryptionAlgorithm, nameof(EncryptionAlgorithm)); string encryptionAlgorithmProvider = GetPropertyValueNormalizeToNull(EncryptionAlgorithmProvider); uint encryptionAlgorithmKeySizeInBits = GetKeySizeInBits(EncryptionAlgorithmKeySize); BCryptAlgorithmHandle encryptionAlgorithmHandle = GetEncryptionAlgorithmHandleAndCheckKeySize(encryptionAlgorithm, encryptionAlgorithmProvider, encryptionAlgorithmKeySizeInBits); // Create the validation object string hashAlgorithm = GetPropertyValueNotNullOrEmpty(HashAlgorithm, nameof(HashAlgorithm)); string hashAlgorithmProvider = GetPropertyValueNormalizeToNull(HashAlgorithmProvider); BCryptAlgorithmHandle hashAlgorithmHandle = GetHashAlgorithmHandle(hashAlgorithm, hashAlgorithmProvider); // and we're good to go! return new CbcAuthenticatedEncryptor( keyDerivationKey: new Secret(secret), symmetricAlgorithmHandle: encryptionAlgorithmHandle, symmetricAlgorithmKeySizeInBytes: encryptionAlgorithmKeySizeInBits / 8, hmacAlgorithmHandle: hashAlgorithmHandle); } private static BCryptAlgorithmHandle GetEncryptionAlgorithmHandleAndCheckKeySize(string encryptionAlgorithm, string encryptionAlgorithmProvider, uint keyLengthInBits) { BCryptAlgorithmHandle algorithmHandle = null; // Special-case cached providers if (encryptionAlgorithmProvider == null) { if (encryptionAlgorithm == Constants.BCRYPT_AES_ALGORITHM) { algorithmHandle = CachedAlgorithmHandles.AES_CBC; } } // 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_CBC); } // make sure we're using a block cipher with an appropriate block size uint cipherBlockSizeInBytes = algorithmHandle.GetCipherBlockLength(); CryptoUtil.Assert(cipherBlockSizeInBytes >= CbcAuthenticatedEncryptor.SYMMETRIC_ALG_MIN_BLOCK_SIZE_IN_BYTES, "cipherBlockSizeInBytes >= CbcAuthenticatedEncryptor.SYMMETRIC_ALG_MIN_BLOCK_SIZE_IN_BYTES"); // make sure the provided key length is valid algorithmHandle.GetSupportedKeyLengths().EnsureValidKeyLength(keyLengthInBits); // all good! return algorithmHandle; } private static BCryptAlgorithmHandle GetHashAlgorithmHandle(string hashAlgorithm, string hashAlgorithmProvider) { BCryptAlgorithmHandle algorithmHandle = null; // Special-case cached providers if (hashAlgorithmProvider == null) { if (hashAlgorithm == Constants.BCRYPT_SHA1_ALGORITHM) { algorithmHandle = CachedAlgorithmHandles.HMAC_SHA1; } else if (hashAlgorithm == Constants.BCRYPT_SHA256_ALGORITHM) { algorithmHandle = CachedAlgorithmHandles.HMAC_SHA256; } else if (hashAlgorithm == Constants.BCRYPT_SHA512_ALGORITHM) { algorithmHandle = CachedAlgorithmHandles.HMAC_SHA512; } } // Look up the provider dynamically if we couldn't fetch a cached instance if (algorithmHandle == null) { algorithmHandle = BCryptAlgorithmHandle.OpenAlgorithmHandle(hashAlgorithm, hashAlgorithmProvider, hmac: true); } // Make sure we're using a hash algorithm. We require a minimum 128-bit digest. uint digestSize = algorithmHandle.GetHashDigestLength(); CryptoUtil.Assert(digestSize >= CbcAuthenticatedEncryptor.HASH_ALG_MIN_DIGEST_LENGTH_IN_BYTES, "digestSize >= CbcAuthenticatedEncryptor.HASH_ALG_MIN_DIGEST_LENGTH_IN_BYTES"); // all good! return algorithmHandle; } private static uint GetKeySizeInBits(int value) { CryptoUtil.Assert(value >= 0, "value >= 0"); CryptoUtil.Assert(value % 8 == 0, "value % 8 == 0"); return (uint)value; } private static string GetPropertyValueNormalizeToNull(string value) { return (String.IsNullOrEmpty(value)) ? null : value; } private static string GetPropertyValueNotNullOrEmpty(string value, string propertyName) { if (String.IsNullOrEmpty(value)) { throw Error.Common_PropertyCannotBeNullOrEmpty(propertyName); } return value; } IAuthenticatedEncryptor IInternalConfigurationOptions.CreateAuthenticatedEncryptor(ISecret secret) { return CreateAuthenticatedEncryptor(secret); } } }