// 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 System.ComponentModel;
using System.IO;
using Microsoft.AspNet.DataProtection.AuthenticatedEncryption;
using Microsoft.AspNet.DataProtection.KeyManagement;
using Microsoft.AspNet.DataProtection.XmlEncryption;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Win32;
#if !DOTNET5_4 // [[ISSUE60]] Remove this #ifdef when Core CLR gets support for EncryptedXml
using System.Security.Cryptography.X509Certificates;
#endif
namespace Microsoft.AspNet.DataProtection
{
///
/// Provides access to configuration for the data protection system, which allows the
/// developer to configure default cryptographic algorithms, key storage locations,
/// and the mechanism by which keys are protected at rest.
///
///
///
/// If the developer changes the at-rest key protection mechanism, it is intended that
/// he also change the key storage location, and vice versa. For instance, a call to
/// should generally be accompanied by
/// a call to , or exceptions may
/// occur at runtime due to the data protection system not knowing where to persist keys.
///
///
/// Similarly, when a developer modifies the default protected payload cryptographic
/// algorithms, it is intended that he also select an explitiy key storage location.
/// A call to
/// should therefore generally be paired with a call to ,
/// for example.
///
///
/// When the default cryptographic algorithms or at-rest key protection mechanisms are
/// changed, they only affect new keys in the repository. The repository may
/// contain existing keys that use older algorithms or protection mechanisms.
///
///
public class DataProtectionConfiguration
{
///
/// Creates a new configuration object linked to a .
///
public DataProtectionConfiguration(IServiceCollection services)
{
if (services == null)
{
throw new ArgumentNullException(nameof(services));
}
Services = services;
}
///
/// Provides access to the passed to this object's constructor.
///
[EditorBrowsable(EditorBrowsableState.Never)]
public IServiceCollection Services { get; }
///
/// Registers a to perform escrow before keys are persisted to storage.
///
/// The instance of the to register.
/// The 'this' instance.
///
/// Registrations are additive.
///
public DataProtectionConfiguration AddKeyEscrowSink(IKeyEscrowSink sink)
{
if (sink == null)
{
throw new ArgumentNullException(nameof(sink));
}
Services.AddSingleton(sink);
return this;
}
///
/// Registers a to perform escrow before keys are persisted to storage.
///
/// The concrete type of the to register.
/// The 'this' instance.
///
/// Registrations are additive. The factory is registered as .
///
public DataProtectionConfiguration AddKeyEscrowSink()
where TImplementation : class, IKeyEscrowSink
{
Services.AddSingleton();
return this;
}
///
/// Registers a to perform escrow before keys are persisted to storage.
///
/// A factory that creates the instance.
/// The 'this' instance.
///
/// Registrations are additive. The factory is registered as .
///
public DataProtectionConfiguration AddKeyEscrowSink(Func factory)
{
if (factory == null)
{
throw new ArgumentNullException(nameof(factory));
}
Services.AddSingleton(factory);
return this;
}
///
/// Configures miscellaneous global options.
///
/// A callback that configures the global options.
/// The 'this' instance.
public DataProtectionConfiguration ConfigureGlobalOptions(Action setupAction)
{
if (setupAction == null)
{
throw new ArgumentNullException(nameof(setupAction));
}
Services.Configure(setupAction);
return this;
}
///
/// Configures the data protection system not to generate new keys automatically.
///
/// The 'this' instance.
///
/// Calling this API corresponds to setting
/// to 'false'. See that property's documentation for more information.
///
public DataProtectionConfiguration DisableAutomaticKeyGeneration()
{
Services.Configure(options =>
{
options.AutoGenerateKeys = false;
});
return this;
}
///
/// Configures the data protection system to persist keys to the specified directory.
/// This path may be on the local machine or may point to a UNC share.
///
/// The directory in which to store keys.
/// The 'this' instance.
public DataProtectionConfiguration PersistKeysToFileSystem(DirectoryInfo directory)
{
if (directory == null)
{
throw new ArgumentNullException(nameof(directory));
}
Use(DataProtectionServiceDescriptors.IXmlRepository_FileSystem(directory));
return this;
}
///
/// Configures the data protection system to persist keys to the Windows registry.
///
/// The location in the registry where keys should be stored.
/// The 'this' instance.
public DataProtectionConfiguration PersistKeysToRegistry(RegistryKey registryKey)
{
if (registryKey == null)
{
throw new ArgumentNullException(nameof(registryKey));
}
Use(DataProtectionServiceDescriptors.IXmlRepository_Registry(registryKey));
return this;
}
#if !DOTNET5_4 // [[ISSUE60]] Remove this #ifdef when Core CLR gets support for EncryptedXml
///
/// Configures keys to be encrypted to a given certificate before being persisted to storage.
///
/// The certificate to use when encrypting keys.
/// The 'this' instance.
public DataProtectionConfiguration ProtectKeysWithCertificate(X509Certificate2 certificate)
{
if (certificate == null)
{
throw new ArgumentNullException(nameof(certificate));
}
Use(DataProtectionServiceDescriptors.IXmlEncryptor_Certificate(certificate));
return this;
}
///
/// Configures keys to be encrypted to a given certificate before being persisted to storage.
///
/// The thumbprint of the certificate to use when encrypting keys.
/// The 'this' instance.
public DataProtectionConfiguration ProtectKeysWithCertificate(string thumbprint)
{
if (thumbprint == null)
{
throw new ArgumentNullException(nameof(thumbprint));
}
// Make sure the thumbprint corresponds to a valid certificate.
if (new CertificateResolver().ResolveCertificate(thumbprint) == null)
{
throw Error.CertificateXmlEncryptor_CertificateNotFound(thumbprint);
}
// ICertificateResolver is necessary for this type to work correctly, so register it
// if it doesn't already exist.
Services.TryAdd(DataProtectionServiceDescriptors.ICertificateResolver_Default());
Use(DataProtectionServiceDescriptors.IXmlEncryptor_Certificate(thumbprint));
return this;
}
#endif
///
/// Configures keys to be encrypted with Windows DPAPI before being persisted to
/// storage. The encrypted key will only be decryptable by the current Windows user account.
///
/// The 'this' instance.
///
/// This API is only supported on Windows platforms.
///
public DataProtectionConfiguration ProtectKeysWithDpapi()
{
return ProtectKeysWithDpapi(protectToLocalMachine: false);
}
///
/// Configures keys to be encrypted with Windows DPAPI before being persisted to
/// storage.
///
/// 'true' if the key should be decryptable by any
/// use on the local machine, 'false' if the key should only be decryptable by the current
/// Windows user account.
/// The 'this' instance.
///
/// This API is only supported on Windows platforms.
///
public DataProtectionConfiguration ProtectKeysWithDpapi(bool protectToLocalMachine)
{
Use(DataProtectionServiceDescriptors.IXmlEncryptor_Dpapi(protectToLocalMachine));
return this;
}
///
/// Configures keys to be encrypted with Windows CNG DPAPI before being persisted
/// to storage. The keys will be decryptable by the current Windows user account.
///
/// The 'this' instance.
///
/// See https://msdn.microsoft.com/en-us/library/windows/desktop/hh706794(v=vs.85).aspx
/// for more information on DPAPI-NG. This API is only supported on Windows 8 / Windows Server 2012 and higher.
///
public DataProtectionConfiguration ProtectKeysWithDpapiNG()
{
return ProtectKeysWithDpapiNG(
protectionDescriptorRule: DpapiNGXmlEncryptor.GetDefaultProtectionDescriptorString(),
flags: DpapiNGProtectionDescriptorFlags.None);
}
///
/// Configures keys to be encrypted with Windows CNG DPAPI before being persisted to storage.
///
/// The descriptor rule string with which to protect the key material.
/// Flags that should be passed to the call to 'NCryptCreateProtectionDescriptor'.
/// The default value of this parameter is .
/// The 'this' instance.
///
/// See https://msdn.microsoft.com/en-us/library/windows/desktop/hh769091(v=vs.85).aspx
/// and https://msdn.microsoft.com/en-us/library/windows/desktop/hh706800(v=vs.85).aspx
/// for more information on valid values for the the
/// and arguments.
/// This API is only supported on Windows 8 / Windows Server 2012 and higher.
///
public DataProtectionConfiguration ProtectKeysWithDpapiNG(string protectionDescriptorRule, DpapiNGProtectionDescriptorFlags flags)
{
if (protectionDescriptorRule == null)
{
throw new ArgumentNullException(nameof(protectionDescriptorRule));
}
Use(DataProtectionServiceDescriptors.IXmlEncryptor_DpapiNG(protectionDescriptorRule, flags));
return this;
}
///
/// Sets the unique name of this application within the data protection system.
///
/// The application name.
/// The 'this' instance.
///
/// This API corresponds to setting the property
/// to the value of .
///
public DataProtectionConfiguration SetApplicationName(string applicationName)
{
return ConfigureGlobalOptions(options =>
{
options.ApplicationDiscriminator = applicationName;
});
}
///
/// Sets the default lifetime of keys created by the data protection system.
///
/// The lifetime (time before expiration) for newly-created keys.
/// See for more information and
/// usage notes.
/// The 'this' instance.
public DataProtectionConfiguration SetDefaultKeyLifetime(TimeSpan lifetime)
{
Services.Configure(options =>
{
options.NewKeyLifetime = lifetime;
});
return this;
}
///
/// Configures the data protection system to use the specified cryptographic algorithms
/// by default when generating protected payloads.
///
/// Information about what cryptographic algorithms should be used.
/// The 'this' instance.
public DataProtectionConfiguration UseCryptographicAlgorithms(AuthenticatedEncryptionOptions options)
{
if (options == null)
{
throw new ArgumentNullException(nameof(options));
}
return UseCryptographicAlgorithmsCore(options);
}
///
/// Configures the data protection system to use custom Windows CNG algorithms.
/// This API is intended for advanced scenarios where the developer cannot use the
/// algorithms specified in the and
/// enumerations.
///
/// Information about what cryptographic algorithms should be used.
/// The 'this' instance.
///
/// This API is only available on Windows.
///
[EditorBrowsable(EditorBrowsableState.Advanced)]
public DataProtectionConfiguration UseCustomCryptographicAlgorithms(CngCbcAuthenticatedEncryptionOptions options)
{
if (options == null)
{
throw new ArgumentNullException(nameof(options));
}
return UseCryptographicAlgorithmsCore(options);
}
///
/// Configures the data protection system to use custom Windows CNG algorithms.
/// This API is intended for advanced scenarios where the developer cannot use the
/// algorithms specified in the and
/// enumerations.
///
/// Information about what cryptographic algorithms should be used.
/// The 'this' instance.
///
/// This API is only available on Windows.
///
[EditorBrowsable(EditorBrowsableState.Advanced)]
public DataProtectionConfiguration UseCustomCryptographicAlgorithms(CngGcmAuthenticatedEncryptionOptions options)
{
if (options == null)
{
throw new ArgumentNullException(nameof(options));
}
return UseCryptographicAlgorithmsCore(options);
}
///
/// Configures the data protection system to use custom algorithms.
/// This API is intended for advanced scenarios where the developer cannot use the
/// algorithms specified in the and
/// enumerations.
///
/// Information about what cryptographic algorithms should be used.
/// The 'this' instance.
[EditorBrowsable(EditorBrowsableState.Advanced)]
public DataProtectionConfiguration UseCustomCryptographicAlgorithms(ManagedAuthenticatedEncryptionOptions options)
{
if (options == null)
{
throw new ArgumentNullException(nameof(options));
}
return UseCryptographicAlgorithmsCore(options);
}
private DataProtectionConfiguration UseCryptographicAlgorithmsCore(IInternalAuthenticatedEncryptionOptions options)
{
options.Validate(); // perform self-test
Use(DataProtectionServiceDescriptors.IAuthenticatedEncryptorConfiguration_FromOptions(options));
return this;
}
///
/// Configures the data protection system to use the
/// for data protection services.
///
/// The 'this' instance.
///
/// If this option is used, payloads protected by the data protection system will
/// be permanently undecipherable after the application exits.
///
public DataProtectionConfiguration UseEphemeralDataProtectionProvider()
{
Use(DataProtectionServiceDescriptors.IDataProtectionProvider_Ephemeral());
return this;
}
/*
* UTILITY ISERVICECOLLECTION METHODS
*/
private void RemoveAllServicesOfType(Type serviceType)
{
// We go backward since we're modifying the collection in-place.
for (int i = Services.Count - 1; i >= 0; i--)
{
if (Services[i]?.ServiceType == serviceType)
{
Services.RemoveAt(i);
}
}
}
private void Use(ServiceDescriptor descriptor)
{
RemoveAllServicesOfType(descriptor.ServiceType);
Services.Add(descriptor);
}
}
}