Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ private static string GetBCryptAlgorithmNameFromValidationAlgorithm(ValidationAl
}
}

[return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]
private static Type GetManagedTypeFromEncryptionAlgorithm(EncryptionAlgorithm algorithm)
{
switch (algorithm)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// 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.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Security.Cryptography;
using System.Xml.Linq;

namespace Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel;

internal static class ManagedAlgorithmHelpers
{
private static readonly List<Type> KnownAlgorithmTypes = new List<Type>
{
typeof(Aes),
typeof(HMACSHA1),
typeof(HMACSHA256),
typeof(HMACSHA384),
typeof(HMACSHA512)
};

// Any changes to this method should also be be reflected in FriendlyNameToType.
public static string TypeToFriendlyName(Type type)
{
if (KnownAlgorithmTypes.Contains(type))
{
return type.Name;
}
else
{
return type.AssemblyQualifiedName!;
}
}

// Any changes to this method should also be be reflected in TypeToFriendlyName.
[return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]
[UnconditionalSuppressMessage("Trimmer", "IL2075", Justification = "Unknown type is checked for whether it has a public parameterless constructor. Handle trimmed types by providing a useful error message.")]
[UnconditionalSuppressMessage("Trimmer", "IL2073", Justification = "Unknown type is checked for whether it has a public parameterless constructor. Handle trimmed types by providing a useful error message.")]
public static Type FriendlyNameToType(string typeName)
{
foreach (var knownType in KnownAlgorithmTypes)
{
if (knownType.Name == typeName)
{
return knownType;
}
}

var type = TypeExtensions.GetTypeWithTrimFriendlyErrorMessage(typeName);

// Type name could be full or assembly qualified name of known type.
if (KnownAlgorithmTypes.Contains(type))
{
return type;
}

// All other types are created using Activator.CreateInstance. Validate it has a valid constructor.
if (type.GetConstructor(Type.EmptyTypes) == null)
{
throw new InvalidOperationException($"Algorithm type {type} doesn't have a public parameterless constructor. If the app is published with trimming then the constructor may have been trimmed. Ensure the type's assembly is excluded from trimming.");
}

return type;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Diagnostics.CodeAnalysis;
using System.Security.Cryptography;
using Microsoft.Extensions.Logging.Abstractions;

Expand All @@ -24,6 +25,7 @@ public sealed class ManagedAuthenticatedEncryptorConfiguration : AlgorithmConfig
/// The default algorithm is AES.
/// </remarks>
[ApplyPolicy]
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]
public Type EncryptionAlgorithmType { get; set; } = typeof(Aes);

/// <summary>
Expand All @@ -47,6 +49,7 @@ public sealed class ManagedAuthenticatedEncryptorConfiguration : AlgorithmConfig
/// The default algorithm is HMACSHA256.
/// </remarks>
[ApplyPolicy]
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]
public Type ValidationAlgorithmType { get; set; } = typeof(HMACSHA256);

/// <inheritdoc />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,11 @@ public XmlSerializedDescriptorInfo ExportToXml()
// </descriptor>

var encryptionElement = new XElement("encryption",
new XAttribute("algorithm", TypeToFriendlyName(Configuration.EncryptionAlgorithmType)),
new XAttribute("algorithm", ManagedAlgorithmHelpers.TypeToFriendlyName(Configuration.EncryptionAlgorithmType)),
new XAttribute("keyLength", Configuration.EncryptionAlgorithmKeySize));

var validationElement = new XElement("validation",
new XAttribute("algorithm", TypeToFriendlyName(Configuration.ValidationAlgorithmType)));
new XAttribute("algorithm", ManagedAlgorithmHelpers.TypeToFriendlyName(Configuration.ValidationAlgorithmType)));

var rootElement = new XElement("descriptor",
new XComment(" Algorithms provided by specified SymmetricAlgorithm and KeyedHashAlgorithm "),
Expand All @@ -63,34 +63,4 @@ public XmlSerializedDescriptorInfo ExportToXml()

return new XmlSerializedDescriptorInfo(rootElement, typeof(ManagedAuthenticatedEncryptorDescriptorDeserializer));
}

// Any changes to this method should also be be reflected
// in ManagedAuthenticatedEncryptorDescriptorDeserializer.FriendlyNameToType.
private static string TypeToFriendlyName(Type type)
{
if (type == typeof(Aes))
{
return nameof(Aes);
}
else if (type == typeof(HMACSHA1))
{
return nameof(HMACSHA1);
}
else if (type == typeof(HMACSHA256))
{
return nameof(HMACSHA256);
}
else if (type == typeof(HMACSHA384))
{
return nameof(HMACSHA384);
}
else if (type == typeof(HMACSHA512))
{
return nameof(HMACSHA512);
}
else
{
return type.AssemblyQualifiedName!;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,44 +34,14 @@ public IAuthenticatedEncryptorDescriptor ImportFromXml(XElement element)
var configuration = new ManagedAuthenticatedEncryptorConfiguration();

var encryptionElement = element.Element("encryption")!;
configuration.EncryptionAlgorithmType = FriendlyNameToType((string)encryptionElement.Attribute("algorithm")!);
configuration.EncryptionAlgorithmType = ManagedAlgorithmHelpers.FriendlyNameToType((string)encryptionElement.Attribute("algorithm")!);
configuration.EncryptionAlgorithmKeySize = (int)encryptionElement.Attribute("keyLength")!;

var validationElement = element.Element("validation")!;
configuration.ValidationAlgorithmType = FriendlyNameToType((string)validationElement.Attribute("algorithm")!);
configuration.ValidationAlgorithmType = ManagedAlgorithmHelpers.FriendlyNameToType((string)validationElement.Attribute("algorithm")!);

Secret masterKey = ((string)element.Element("masterKey")!).ToSecret();

return new ManagedAuthenticatedEncryptorDescriptor(configuration, masterKey);
}

// Any changes to this method should also be be reflected
// in ManagedAuthenticatedEncryptorDescriptor.TypeToFriendlyName.
private static Type FriendlyNameToType(string typeName)
{
if (typeName == nameof(Aes))
{
return typeof(Aes);
}
else if (typeName == nameof(HMACSHA1))
{
return typeof(HMACSHA1);
}
else if (typeName == nameof(HMACSHA256))
{
return typeof(HMACSHA256);
}
else if (typeName == nameof(HMACSHA384))
{
return typeof(HMACSHA384);
}
else if (typeName == nameof(HMACSHA512))
{
return typeof(HMACSHA512);
}
else
{
return TypeExtensions.GetTypeWithTrimFriendlyErrorMessage(typeName);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ private static ManagedAuthenticatedEncryptorConfiguration GetManagedAuthenticate
var valueFromRegistry = key.GetValue(nameof(ManagedAuthenticatedEncryptorConfiguration.EncryptionAlgorithmType));
if (valueFromRegistry != null)
{
options.EncryptionAlgorithmType = TypeExtensions.GetTypeWithTrimFriendlyErrorMessage(Convert.ToString(valueFromRegistry, CultureInfo.InvariantCulture)!);
options.EncryptionAlgorithmType = ManagedAlgorithmHelpers.FriendlyNameToType(Convert.ToString(valueFromRegistry, CultureInfo.InvariantCulture)!);
}

valueFromRegistry = key.GetValue(nameof(ManagedAuthenticatedEncryptorConfiguration.EncryptionAlgorithmKeySize));
Expand All @@ -189,7 +189,7 @@ private static ManagedAuthenticatedEncryptorConfiguration GetManagedAuthenticate
valueFromRegistry = key.GetValue(nameof(ManagedAuthenticatedEncryptorConfiguration.ValidationAlgorithmType));
if (valueFromRegistry != null)
{
options.ValidationAlgorithmType = TypeExtensions.GetTypeWithTrimFriendlyErrorMessage(Convert.ToString(valueFromRegistry, CultureInfo.InvariantCulture)!);
options.ValidationAlgorithmType = ManagedAlgorithmHelpers.FriendlyNameToType(Convert.ToString(valueFromRegistry, CultureInfo.InvariantCulture)!);
}

return options;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ public void ImportFromXml_BuiltInTypes_CreatesAppropriateDescriptor(Type encrypt
}

[Fact]
public void ImportFromXml_CustomType_CreatesAppropriateDescriptor()
public void ImportFromXml_FullyQualifiedBuiltInTypes_CreatesAppropriateDescriptor()
{
// Arrange
var masterKey = Convert.ToBase64String(Encoding.UTF8.GetBytes("[PLACEHOLDER]"));
Expand Down Expand Up @@ -83,6 +83,69 @@ public void ImportFromXml_CustomType_CreatesAppropriateDescriptor()
Assert.Equal(plaintext, roundTripPlaintext);
}

[Fact]
public void ImportFromXml_CustomType_CreatesAppropriateDescriptor()
{
// Arrange
var masterKey = Convert.ToBase64String(Encoding.UTF8.GetBytes("[PLACEHOLDER]"));

var xml = $@"
<descriptor>
<encryption algorithm='{typeof(CustomAlgorithm).AssemblyQualifiedName}' keyLength='192' />
<validation algorithm='{typeof(HMACSHA384).AssemblyQualifiedName}' />
<masterKey enc:requiresEncryption='true' xmlns:enc='http://schemas.asp.net/2015/03/dataProtection'>
<value>{masterKey}</value>
</masterKey>
</descriptor>";

// Act
var deserializedDescriptor = new ManagedAuthenticatedEncryptorDescriptorDeserializer().ImportFromXml(XElement.Parse(xml));
var managedDescriptor = (ManagedAuthenticatedEncryptorDescriptor)deserializedDescriptor;

// Assert
Assert.Equal(typeof(CustomAlgorithm), managedDescriptor.Configuration.EncryptionAlgorithmType);
}

[Fact]
public void ImportFromXml_CustomTypeWithoutConstructor_CreatesAppropriateDescriptor()
{
// Arrange
var masterKey = Convert.ToBase64String(Encoding.UTF8.GetBytes("[PLACEHOLDER]"));

var xml = $@"
<descriptor>
<encryption algorithm='{typeof(CustomAlgorithmNoConstructor).AssemblyQualifiedName}' keyLength='192' />
<validation algorithm='{typeof(HMACSHA384).AssemblyQualifiedName}' />
<masterKey enc:requiresEncryption='true' xmlns:enc='http://schemas.asp.net/2015/03/dataProtection'>
<value>{masterKey}</value>
</masterKey>
</descriptor>";

// Act
var ex = Assert.Throws<InvalidOperationException>(() => new ManagedAuthenticatedEncryptorDescriptorDeserializer().ImportFromXml(XElement.Parse(xml)));

// Assert
Assert.Equal($"Algorithm type {typeof(CustomAlgorithmNoConstructor).FullName} doesn't have a public parameterless constructor. If the app is published with trimming then the constructor may have been trimmed. Ensure the type's assembly is excluded from trimming.", ex.Message);
}

public class CustomAlgorithm : SymmetricAlgorithm
{
public override ICryptoTransform CreateDecryptor(byte[] rgbKey, byte[] rgbIV) => throw new NotImplementedException();
public override ICryptoTransform CreateEncryptor(byte[] rgbKey, byte[] rgbIV) => throw new NotImplementedException();
public override void GenerateIV() => throw new NotImplementedException();
public override void GenerateKey() => throw new NotImplementedException();
}

public class CustomAlgorithmNoConstructor : SymmetricAlgorithm
{
private CustomAlgorithmNoConstructor() { }

public override ICryptoTransform CreateDecryptor(byte[] rgbKey, byte[] rgbIV) => throw new NotImplementedException();
public override ICryptoTransform CreateEncryptor(byte[] rgbKey, byte[] rgbIV) => throw new NotImplementedException();
public override void GenerateIV() => throw new NotImplementedException();
public override void GenerateKey() => throw new NotImplementedException();
}

private static IAuthenticatedEncryptor CreateEncryptorInstanceFromDescriptor(ManagedAuthenticatedEncryptorDescriptor descriptor)
{
var encryptorFactory = new ManagedAuthenticatedEncryptorFactory(NullLoggerFactory.Instance);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -226,13 +226,13 @@ public void ResolvePolicy_ManagedEncryption_WithExplicitSettings()
var registryEntries = new Dictionary<string, object>()
{
["EncryptionType"] = "managed",
["EncryptionAlgorithmType"] = typeof(TripleDES).AssemblyQualifiedName,
["EncryptionAlgorithmType"] = typeof(Aes).AssemblyQualifiedName,
["EncryptionAlgorithmKeySize"] = 2048,
["ValidationAlgorithmType"] = typeof(HMACSHA1).AssemblyQualifiedName
};
var expectedConfiguration = new ManagedAuthenticatedEncryptorConfiguration()
{
EncryptionAlgorithmType = typeof(TripleDES),
EncryptionAlgorithmType = typeof(Aes),
EncryptionAlgorithmKeySize = 2048,
ValidationAlgorithmType = typeof(HMACSHA1)
};
Expand Down