File: AuthenticatedEncryption\ConfigurationModel\AuthenticatedEncryptorDescriptorTests.cs
Web Access
Project: src\src\DataProtection\DataProtection\test\Microsoft.AspNetCore.DataProtection.Tests\Microsoft.AspNetCore.DataProtection.Tests.csproj (Microsoft.AspNetCore.DataProtection.Tests)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Globalization;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
using Microsoft.AspNetCore.Cryptography.Cng;
using Microsoft.AspNetCore.Cryptography.SafeHandles;
using Microsoft.AspNetCore.DataProtection.Cng;
using Microsoft.AspNetCore.DataProtection.KeyManagement;
using Microsoft.AspNetCore.DataProtection.Managed;
using Microsoft.AspNetCore.DataProtection.Test.Shared;
using Microsoft.AspNetCore.InternalTesting;
using Microsoft.Extensions.Logging.Abstractions;
 
namespace Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel;
 
public class AuthenticatedEncryptorDescriptorTests
{
    [ConditionalTheory]
    [ConditionalRunTestOnlyOnWindows]
    [InlineData(EncryptionAlgorithm.AES_128_CBC, ValidationAlgorithm.HMACSHA256)]
    [InlineData(EncryptionAlgorithm.AES_192_CBC, ValidationAlgorithm.HMACSHA256)]
    [InlineData(EncryptionAlgorithm.AES_256_CBC, ValidationAlgorithm.HMACSHA256)]
    [InlineData(EncryptionAlgorithm.AES_128_CBC, ValidationAlgorithm.HMACSHA512)]
    [InlineData(EncryptionAlgorithm.AES_192_CBC, ValidationAlgorithm.HMACSHA512)]
    [InlineData(EncryptionAlgorithm.AES_256_CBC, ValidationAlgorithm.HMACSHA512)]
    public void CreateAuthenticatedEncryptor_RoundTripsData_CngCbcImplementation(EncryptionAlgorithm encryptionAlgorithm, ValidationAlgorithm validationAlgorithm)
    {
        // Parse test input
        int keyLengthInBits = Int32.Parse(Regex.Match(encryptionAlgorithm.ToString(), @"^AES_(?<keyLength>\d{3})_CBC$").Groups["keyLength"].Value, CultureInfo.InvariantCulture);
        string hashAlgorithm = Regex.Match(validationAlgorithm.ToString(), @"^HMAC(?<hashAlgorithm>.*)$").Groups["hashAlgorithm"].Value;
 
        // Arrange
        var masterKey = Secret.Random(512 / 8);
        var control = new CbcAuthenticatedEncryptor(
            keyDerivationKey: masterKey,
            symmetricAlgorithmHandle: CachedAlgorithmHandles.AES_CBC,
            symmetricAlgorithmKeySizeInBytes: (uint)(keyLengthInBits / 8),
            hmacAlgorithmHandle: BCryptAlgorithmHandle.OpenAlgorithmHandle(hashAlgorithm, hmac: true));
        var test = CreateEncryptorInstanceFromDescriptor(CreateDescriptor(encryptionAlgorithm, validationAlgorithm, masterKey));
 
        // Act & assert - data round trips properly from control to test
        byte[] plaintext = new byte[] { 1, 2, 3, 4, 5 };
        byte[] aad = new byte[] { 2, 4, 6, 8, 0 };
        byte[] ciphertext = control.Encrypt(new ArraySegment<byte>(plaintext), new ArraySegment<byte>(aad));
        byte[] roundTripPlaintext = test.Decrypt(new ArraySegment<byte>(ciphertext), new ArraySegment<byte>(aad));
        Assert.Equal(plaintext, roundTripPlaintext);
    }
 
    [ConditionalTheory]
    [ConditionalRunTestOnlyOnWindows]
    [InlineData(EncryptionAlgorithm.AES_128_GCM)]
    [InlineData(EncryptionAlgorithm.AES_192_GCM)]
    [InlineData(EncryptionAlgorithm.AES_256_GCM)]
    public void CreateAuthenticatedEncryptor_RoundTripsData_CngGcmImplementation(EncryptionAlgorithm encryptionAlgorithm)
    {
        // Parse test input
        int keyLengthInBits = Int32.Parse(Regex.Match(encryptionAlgorithm.ToString(), @"^AES_(?<keyLength>\d{3})_GCM$").Groups["keyLength"].Value, CultureInfo.InvariantCulture);
 
        // Arrange
        var masterKey = Secret.Random(512 / 8);
        var control = new CngGcmAuthenticatedEncryptor(
            keyDerivationKey: masterKey,
            symmetricAlgorithmHandle: CachedAlgorithmHandles.AES_GCM,
            symmetricAlgorithmKeySizeInBytes: (uint)(keyLengthInBits / 8));
        var test = CreateEncryptorInstanceFromDescriptor(CreateDescriptor(encryptionAlgorithm, ValidationAlgorithm.HMACSHA256 /* unused */, masterKey));
 
        // Act & assert - data round trips properly from control to test
        byte[] plaintext = new byte[] { 1, 2, 3, 4, 5 };
        byte[] aad = new byte[] { 2, 4, 6, 8, 0 };
        byte[] ciphertext = control.Encrypt(new ArraySegment<byte>(plaintext), new ArraySegment<byte>(aad));
        byte[] roundTripPlaintext = test.Decrypt(new ArraySegment<byte>(ciphertext), new ArraySegment<byte>(aad));
        Assert.Equal(plaintext, roundTripPlaintext);
    }
 
    [ConditionalTheory]
    [ConditionalRunTestOnlyOnWindows]
    [InlineData(EncryptionAlgorithm.AES_128_GCM)]
    [InlineData(EncryptionAlgorithm.AES_192_GCM)]
    [InlineData(EncryptionAlgorithm.AES_256_GCM)]
    public void CreateAuthenticatedEncryptor_RoundTripsData_AesGcmImplementation(EncryptionAlgorithm encryptionAlgorithm)
    {
        // Parse test input
        int keyLengthInBits = Int32.Parse(Regex.Match(encryptionAlgorithm.ToString(), @"^AES_(?<keyLength>\d{3})_GCM$").Groups["keyLength"].Value, CultureInfo.InvariantCulture);
 
        // Arrange
        var masterKey = Secret.Random(512 / 8);
        var control = new CngGcmAuthenticatedEncryptor(
            keyDerivationKey: masterKey,
            symmetricAlgorithmHandle: CachedAlgorithmHandles.AES_GCM,
            symmetricAlgorithmKeySizeInBytes: (uint)(keyLengthInBits / 8));
        var test = CreateEncryptorInstanceFromDescriptor(CreateDescriptor(encryptionAlgorithm, ValidationAlgorithm.HMACSHA256 /* unused */, masterKey));
 
        // Act & assert - data round trips properly from control to test
        byte[] plaintext = new byte[] { 1, 2, 3, 4, 5 };
        byte[] aad = new byte[] { 2, 4, 6, 8, 0 };
        byte[] ciphertext = control.Encrypt(new ArraySegment<byte>(plaintext), new ArraySegment<byte>(aad));
        byte[] roundTripPlaintext = test.Decrypt(new ArraySegment<byte>(ciphertext), new ArraySegment<byte>(aad));
        Assert.Equal(plaintext, roundTripPlaintext);
    }
 
    public static TheoryData<EncryptionAlgorithm, ValidationAlgorithm, Func<HMAC>> CreateAuthenticatedEncryptor_RoundTripsData_ManagedImplementationData
        => new()
        {
                { EncryptionAlgorithm.AES_128_CBC, ValidationAlgorithm.HMACSHA256, () => new HMACSHA256() },
                { EncryptionAlgorithm.AES_192_CBC, ValidationAlgorithm.HMACSHA256, () => new HMACSHA256() },
                { EncryptionAlgorithm.AES_256_CBC, ValidationAlgorithm.HMACSHA256, () => new HMACSHA256() },
                { EncryptionAlgorithm.AES_128_CBC, ValidationAlgorithm.HMACSHA512, () => new HMACSHA512() },
                { EncryptionAlgorithm.AES_192_CBC, ValidationAlgorithm.HMACSHA512, () => new HMACSHA512() },
                { EncryptionAlgorithm.AES_256_CBC, ValidationAlgorithm.HMACSHA512, () => new HMACSHA512() },
        };
 
    [Theory]
    [MemberData(nameof(CreateAuthenticatedEncryptor_RoundTripsData_ManagedImplementationData))]
    public void CreateAuthenticatedEncryptor_RoundTripsData_ManagedImplementation(
        EncryptionAlgorithm encryptionAlgorithm,
        ValidationAlgorithm validationAlgorithm,
        Func<HMAC> validationAlgorithmFactory)
    {
        // Parse test input
        int keyLengthInBits = Int32.Parse(Regex.Match(encryptionAlgorithm.ToString(), @"^AES_(?<keyLength>\d{3})_CBC$").Groups["keyLength"].Value, CultureInfo.InvariantCulture);
 
        // Arrange
        var masterKey = Secret.Random(512 / 8);
        var control = new ManagedAuthenticatedEncryptor(
            keyDerivationKey: masterKey,
            symmetricAlgorithmFactory: () => Aes.Create(),
            symmetricAlgorithmKeySizeInBytes: keyLengthInBits / 8,
            validationAlgorithmFactory: validationAlgorithmFactory);
        var test = CreateEncryptorInstanceFromDescriptor(CreateDescriptor(encryptionAlgorithm, validationAlgorithm, masterKey));
 
        // Act & assert - data round trips properly from control to test
        byte[] plaintext = new byte[] { 1, 2, 3, 4, 5 };
        byte[] aad = new byte[] { 2, 4, 6, 8, 0 };
        byte[] ciphertext = control.Encrypt(new ArraySegment<byte>(plaintext), new ArraySegment<byte>(aad));
        byte[] roundTripPlaintext = test.Decrypt(new ArraySegment<byte>(ciphertext), new ArraySegment<byte>(aad));
        Assert.Equal(plaintext, roundTripPlaintext);
    }
 
    [Fact]
    public void ExportToXml_ProducesCorrectPayload_Cbc()
    {
        // Arrange
        var masterKey = Convert.ToBase64String(Encoding.UTF8.GetBytes("[PLACEHOLDER]"));
        var descriptor = CreateDescriptor(EncryptionAlgorithm.AES_192_CBC, ValidationAlgorithm.HMACSHA512, masterKey.ToSecret());
 
        // Act
        var retVal = descriptor.ExportToXml();
 
        // Assert
        Assert.Equal(typeof(AuthenticatedEncryptorDescriptorDeserializer), retVal.DeserializerType);
        var expectedXml = $@"
                <descriptor>
                  <encryption algorithm='AES_192_CBC' />
                  <validation algorithm='HMACSHA512' />
                  <masterKey enc:requiresEncryption='true' xmlns:enc='http://schemas.asp.net/2015/03/dataProtection'>
                    <value>{masterKey}</value>
                  </masterKey>
                </descriptor>";
        XmlAssert.Equal(expectedXml, retVal.SerializedDescriptorElement);
    }
 
    [Fact]
    public void ExportToXml_ProducesCorrectPayload_Gcm()
    {
        // Arrange
        var masterKey = Convert.ToBase64String(Encoding.UTF8.GetBytes("[PLACEHOLDER]"));
        var descriptor = CreateDescriptor(EncryptionAlgorithm.AES_192_GCM, ValidationAlgorithm.HMACSHA512, masterKey.ToSecret());
 
        // Act
        var retVal = descriptor.ExportToXml();
 
        // Assert
        Assert.Equal(typeof(AuthenticatedEncryptorDescriptorDeserializer), retVal.DeserializerType);
        var expectedXml = $@"
                <descriptor>
                  <encryption algorithm='AES_192_GCM' />
                  <!-- some comment here -->
                  <masterKey enc:requiresEncryption='true' xmlns:enc='http://schemas.asp.net/2015/03/dataProtection'>
                    <value>{masterKey}</value>
                  </masterKey>
                </descriptor>";
        XmlAssert.Equal(expectedXml, retVal.SerializedDescriptorElement);
    }
 
    private static AuthenticatedEncryptorDescriptor CreateDescriptor(EncryptionAlgorithm encryptionAlgorithm, ValidationAlgorithm validationAlgorithm, ISecret masterKey)
    {
        return new AuthenticatedEncryptorDescriptor(new AuthenticatedEncryptorConfiguration()
        {
            EncryptionAlgorithm = encryptionAlgorithm,
            ValidationAlgorithm = validationAlgorithm
        }, masterKey);
    }
 
    private static IAuthenticatedEncryptor CreateEncryptorInstanceFromDescriptor(AuthenticatedEncryptorDescriptor descriptor)
    {
        var encryptorFactory = new AuthenticatedEncryptorFactory(NullLoggerFactory.Instance);
 
        // Dummy key with the specified descriptor.
        var key = new Key(
            Guid.NewGuid(),
            DateTimeOffset.Now,
            DateTimeOffset.Now + TimeSpan.FromHours(1),
            DateTimeOffset.Now + TimeSpan.FromDays(30),
            descriptor,
            new[] { encryptorFactory });
 
        return key.CreateEncryptor();
    }
}