// 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.Collections.Generic; using Microsoft.AspNetCore.Cryptography.Cng; using Microsoft.AspNetCore.DataProtection; using Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption; using Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel; using Microsoft.AspNetCore.DataProtection.Cng; using Microsoft.AspNetCore.DataProtection.KeyManagement; using Microsoft.AspNetCore.DataProtection.KeyManagement.Internal; using Microsoft.AspNetCore.DataProtection.Repositories; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Microsoft.Win32; namespace Microsoft.Extensions.DependencyInjection { /// /// Provides access to default Data Protection instances. /// public static class DataProtectionServices { /// /// Returns a collection of default instances that can be /// used to bootstrap the Data Protection system. /// public static IEnumerable GetDefaultServices() { // The default key services are a strange beast. We don't want to return // IXmlEncryptor and IXmlRepository as-is because they almost always have to be // set as a matched pair. Instead, our built-in key manager will use a meta-service // which represents the default pairing (logic based on hosting environment as // demonstrated below), and if the developer explicitly specifies one or the other // we'll not use the fallback at all. yield return ServiceDescriptor.Singleton(services => { var log = services.GetLogger(typeof(DataProtectionServices)); ServiceDescriptor keyEncryptorDescriptor = null; ServiceDescriptor keyRepositoryDescriptor = null; // If we're running in Azure Web Sites, the key repository goes in the %HOME% directory. var azureWebSitesKeysFolder = FileSystemXmlRepository.GetKeyStorageDirectoryForAzureWebSites(); if (azureWebSitesKeysFolder != null) { log?.UsingAzureAsKeyRepository(azureWebSitesKeysFolder.FullName); // Cloud DPAPI isn't yet available, so we don't encrypt keys at rest. // This isn't all that different than what Azure Web Sites does today, and we can always add this later. keyRepositoryDescriptor = DataProtectionServiceDescriptors.IXmlRepository_FileSystem(azureWebSitesKeysFolder); } else { // If the user profile is available, store keys in the user profile directory. var localAppDataKeysFolder = FileSystemXmlRepository.DefaultKeyStorageDirectory; if (localAppDataKeysFolder != null) { if (OSVersionUtil.IsWindows()) { // If the user profile is available, we can protect using DPAPI. // Probe to see if protecting to local user is available, and use it as the default if so. keyEncryptorDescriptor = DataProtectionServiceDescriptors.IXmlEncryptor_Dpapi(protectToMachine: !DpapiSecretSerializerHelper.CanProtectToCurrentUserAccount()); } keyRepositoryDescriptor = DataProtectionServiceDescriptors.IXmlRepository_FileSystem(localAppDataKeysFolder); if (keyEncryptorDescriptor != null) { log?.UsingProfileAsKeyRepositoryWithDPAPI(localAppDataKeysFolder.FullName); } else { log?.UsingProfileAsKeyRepository(localAppDataKeysFolder.FullName); } } else { // Use profile isn't available - can we use the HKLM registry? RegistryKey regKeyStorageKey = null; if (OSVersionUtil.IsWindows()) { regKeyStorageKey = RegistryXmlRepository.DefaultRegistryKey; } if (regKeyStorageKey != null) { // If the user profile isn't available, we can protect using DPAPI (to machine). keyEncryptorDescriptor = DataProtectionServiceDescriptors.IXmlEncryptor_Dpapi(protectToMachine: true); keyRepositoryDescriptor = DataProtectionServiceDescriptors.IXmlRepository_Registry(regKeyStorageKey); log?.UsingRegistryAsKeyRepositoryWithDPAPI(regKeyStorageKey.Name); } else { // Final fallback - use an ephemeral repository since we don't know where else to go. // This can only be used for development scenarios. keyRepositoryDescriptor = ServiceDescriptor.Singleton( s => new EphemeralXmlRepository(s)); log?.UsingEphemeralKeyRepository(); } } } return new DefaultKeyServices( services: services, keyEncryptorDescriptor: keyEncryptorDescriptor, keyRepositoryDescriptor: keyRepositoryDescriptor); }); // Provide root key management and data protection services yield return ServiceDescriptor.Singleton(services => new XmlKeyManager(services)); yield return ServiceDescriptor.Singleton( services => DataProtectionProviderFactory.GetProviderFromServices( options: services.GetRequiredService>().Value, services: services, mustCreateImmediately: true /* this is the ultimate fallback */)); // Provide services required for XML encryption #if !NETSTANDARD1_3 // [[ISSUE60]] Remove this #ifdef when Core CLR gets support for EncryptedXml yield return DataProtectionServiceDescriptors.ICertificateResolver_Default(); #endif // Hook up the logic which allows populating default options yield return ServiceDescriptor.Transient>(services => { return new ConfigureOptions(options => { options.ApplicationDiscriminator = services.GetApplicationUniqueIdentifier(); }); }); // Read and apply policy from the registry, overriding any other defaults. var encryptorConfigurationReadFromRegistry = false; if (OSVersionUtil.IsWindows()) { foreach (var descriptor in RegistryPolicyResolver.ResolveDefaultPolicy()) { yield return descriptor; if (descriptor.ServiceType == typeof(IAuthenticatedEncryptorConfiguration)) { encryptorConfigurationReadFromRegistry = true; } } } // Finally, provide a fallback encryptor configuration if one wasn't already specified. if (!encryptorConfigurationReadFromRegistry) { yield return DataProtectionServiceDescriptors.IAuthenticatedEncryptorConfiguration_FromSettings( new AuthenticatedEncryptionSettings());; } } } }