File: AuthenticatedEncryption\CngCbcAuthenticatedEncryptorFactory.cs
Web Access
Project: src\src\DataProtection\DataProtection\src\Microsoft.AspNetCore.DataProtection.csproj (Microsoft.AspNetCore.DataProtection)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
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.AspNetCore.DataProtection.KeyManagement;
using Microsoft.Extensions.Logging;
 
namespace Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption;
 
/// <summary>
/// An <see cref="IAuthenticatedEncryptorFactory"/> for <see cref="CbcAuthenticatedEncryptor"/>.
/// </summary>
public sealed class CngCbcAuthenticatedEncryptorFactory : IAuthenticatedEncryptorFactory
{
    private readonly ILogger _logger;
 
    /// <summary>
    /// Initializes a new instance of <see cref="CngCbcAuthenticatedEncryptorFactory"/>.
    /// </summary>
    /// <param name="loggerFactory">The <see cref="ILoggerFactory"/>.</param>
    public CngCbcAuthenticatedEncryptorFactory(ILoggerFactory loggerFactory)
    {
        _logger = loggerFactory.CreateLogger<CngCbcAuthenticatedEncryptorFactory>();
    }
 
    /// <inheritdoc />
    public IAuthenticatedEncryptor? CreateEncryptorInstance(IKey key)
    {
        if (key.Descriptor is not CngCbcAuthenticatedEncryptorDescriptor descriptor)
        {
            return null;
        }
 
        Debug.Assert(RuntimeInformation.IsOSPlatform(OSPlatform.Windows));
        return CreateAuthenticatedEncryptorInstance(descriptor.MasterKey, descriptor.Configuration);
    }
 
    [SupportedOSPlatform("windows")]
    [return: NotNullIfNotNull("configuration")]
    internal CbcAuthenticatedEncryptor? CreateAuthenticatedEncryptorInstance(
        ISecret secret,
        CngCbcAuthenticatedEncryptorConfiguration? configuration)
    {
        if (configuration == null)
        {
            return null;
        }
 
        using var key = new Secret(secret);
        return new CbcAuthenticatedEncryptor(
            keyDerivationKey: key,
            symmetricAlgorithmHandle: GetSymmetricBlockCipherAlgorithmHandle(configuration),
            symmetricAlgorithmKeySizeInBytes: (uint)(configuration.EncryptionAlgorithmKeySize / 8),
            hmacAlgorithmHandle: GetHmacAlgorithmHandle(configuration));
    }
 
    [SupportedOSPlatform("windows")]
    private BCryptAlgorithmHandle GetHmacAlgorithmHandle(CngCbcAuthenticatedEncryptorConfiguration configuration)
    {
        // basic argument checking
        if (String.IsNullOrEmpty(configuration.HashAlgorithm))
        {
            throw Error.Common_PropertyCannotBeNullOrEmpty(nameof(configuration.HashAlgorithm));
        }
 
        _logger.OpeningCNGAlgorithmFromProviderWithHMAC(configuration.HashAlgorithm, configuration.HashAlgorithmProvider);
        BCryptAlgorithmHandle? algorithmHandle = null;
 
        // Special-case cached providers
        if (configuration.HashAlgorithmProvider == null)
        {
            if (configuration.HashAlgorithm == Constants.BCRYPT_SHA1_ALGORITHM) { algorithmHandle = CachedAlgorithmHandles.HMAC_SHA1; }
            else if (configuration.HashAlgorithm == Constants.BCRYPT_SHA256_ALGORITHM) { algorithmHandle = CachedAlgorithmHandles.HMAC_SHA256; }
            else if (configuration.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(configuration.HashAlgorithm, configuration.HashAlgorithmProvider, hmac: true);
        }
 
        // Make sure we're using a hash algorithm. We require a minimum 128-bit digest.
        uint digestSize = algorithmHandle.GetHashDigestLength();
        AlgorithmAssert.IsAllowableValidationAlgorithmDigestSize(checked(digestSize * 8));
 
        // all good!
        return algorithmHandle;
    }
 
    [SupportedOSPlatform("windows")]
    private BCryptAlgorithmHandle GetSymmetricBlockCipherAlgorithmHandle(CngCbcAuthenticatedEncryptorConfiguration configuration)
    {
        // basic argument checking
        if (String.IsNullOrEmpty(configuration.EncryptionAlgorithm))
        {
            throw Error.Common_PropertyCannotBeNullOrEmpty(nameof(EncryptionAlgorithm));
        }
        if (configuration.EncryptionAlgorithmKeySize < 0)
        {
            throw Error.Common_PropertyMustBeNonNegative(nameof(configuration.EncryptionAlgorithmKeySize));
        }
 
        _logger.OpeningCNGAlgorithmFromProviderWithChainingModeCBC(configuration.EncryptionAlgorithm, configuration.EncryptionAlgorithmProvider);
 
        BCryptAlgorithmHandle? algorithmHandle = null;
 
        // Special-case cached providers
        if (configuration.EncryptionAlgorithmProvider == null)
        {
            if (configuration.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(configuration.EncryptionAlgorithm, configuration.EncryptionAlgorithmProvider);
            algorithmHandle.SetChainingMode(Constants.BCRYPT_CHAIN_MODE_CBC);
        }
 
        // make sure we're using a block cipher with an appropriate key size & block size
        AlgorithmAssert.IsAllowableSymmetricAlgorithmBlockSize(checked(algorithmHandle.GetCipherBlockLength() * 8));
        AlgorithmAssert.IsAllowableSymmetricAlgorithmKeySize(checked((uint)configuration.EncryptionAlgorithmKeySize));
 
        // make sure the provided key length is valid
        algorithmHandle.GetSupportedKeyLengths().EnsureValidKeyLength((uint)configuration.EncryptionAlgorithmKeySize);
 
        // all good!
        return algorithmHandle;
    }
}