/// <summary>
        /// Returns a collection of default <see cref="ServiceDescriptor"/> instances that can be
        /// used to bootstrap the Data Protection system.
        /// </summary>
        public static IEnumerable <ServiceDescriptor> 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 <IDefaultKeyServices>(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 <IXmlRepository>(
                                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 <IKeyManager>(services => new XmlKeyManager(services)));

            yield return(ServiceDescriptor.Singleton <IDataProtectionProvider>(
                             services => DataProtectionProviderFactory.GetProviderFromServices(
                                 options: services.GetRequiredService <IOptions <DataProtectionOptions> >().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 <IConfigureOptions <DataProtectionOptions> >(services =>
            {
                return new ConfigureOptions <DataProtectionOptions>(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()));;
            }
        }
Example #2
0
        /// <summary>
        /// Returns a collection of default <see cref="ServiceDescriptor"/> instances that can be
        /// used to bootstrap the Data Protection system.
        /// </summary>
        public static IEnumerable <ServiceDescriptor> 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 <IDefaultKeyServices>(services =>
            {
                ILogger 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)
                {
                    if (log.IsInformationLevelEnabled())
                    {
                        log.LogInformationF($"Azure Web Sites environment detected. Using '{azureWebSitesKeysFolder.FullName}' as key repository; keys will not be encrypted at rest.");
                    }

                    // 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 (log.IsInformationLevelEnabled())
                        {
                            if (keyEncryptorDescriptor != null)
                            {
                                log.LogInformationF($"User profile is available. Using '{localAppDataKeysFolder.FullName}' as key repository and Windows DPAPI to encrypt keys at rest.");
                            }
                            else
                            {
                                log.LogInformationF($"User profile is available. Using '{localAppDataKeysFolder.FullName}' as key repository; keys will not be encrypted at rest.");
                            }
                        }
                    }
                    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);

                            if (log.IsInformationLevelEnabled())
                            {
                                log.LogInformationF($"User profile not available. Using '{regKeyStorageKey.Name}' as key repository and Windows DPAPI to encrypt keys at rest.");
                            }
                        }
                        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 = DataProtectionServiceDescriptors.IXmlRepository_InMemory();

                            if (log.IsWarningLevelEnabled())
                            {
                                log.LogWarning("Neither user profile nor HKLM registry available. Using an ephemeral key repository. Protected data will be unavailable when application exits.");
                            }
                        }
                    }
                }

                return new DefaultKeyServices(
                    services: services,
                    keyEncryptorDescriptor: keyEncryptorDescriptor,
                    keyRepositoryDescriptor: keyRepositoryDescriptor);
            }));

            // Provide root key management and data protection services
            yield return(DataProtectionServiceDescriptors.IKeyManager_Default());

            yield return(DataProtectionServiceDescriptors.IDataProtectionProvider_Default());

            // Provide services required for XML encryption
#if !DOTNET5_4 // [[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(DataProtectionServiceDescriptors.ConfigureOptions_DataProtectionOptions());

            // Read and apply policy from the registry, overriding any other defaults.
            bool 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_Default());
            }
        }