DataProtectionServices should use keys stored in HKLM auto-gen registry when running on IIS without user profile.
This commit is contained in:
parent
ca95189a3b
commit
76b76ba099
|
|
@ -22,7 +22,7 @@ namespace Microsoft.AspNet.Security.DataProtection.Cng
|
||||||
|
|
||||||
private static readonly byte[] _purpose = Encoding.UTF8.GetBytes("DPAPI-Protected Secret");
|
private static readonly byte[] _purpose = Encoding.UTF8.GetBytes("DPAPI-Protected Secret");
|
||||||
|
|
||||||
public static byte[] ProtectWithDpapi(ISecret secret)
|
public static byte[] ProtectWithDpapi(ISecret secret, bool protectToLocalMachine = false)
|
||||||
{
|
{
|
||||||
Debug.Assert(secret != null);
|
Debug.Assert(secret != null);
|
||||||
|
|
||||||
|
|
@ -34,7 +34,7 @@ namespace Microsoft.AspNet.Security.DataProtection.Cng
|
||||||
secret.WriteSecretIntoBuffer(new ArraySegment<byte>(plaintextSecret));
|
secret.WriteSecretIntoBuffer(new ArraySegment<byte>(plaintextSecret));
|
||||||
fixed (byte* pbPurpose = _purpose)
|
fixed (byte* pbPurpose = _purpose)
|
||||||
{
|
{
|
||||||
return ProtectWithDpapiImpl(pbPlaintextSecret, (uint)plaintextSecret.Length, pbPurpose, (uint)_purpose.Length);
|
return ProtectWithDpapiImpl(pbPlaintextSecret, (uint)plaintextSecret.Length, pbPurpose, (uint)_purpose.Length, fLocalMachine: protectToLocalMachine);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
|
|
|
||||||
|
|
@ -72,18 +72,33 @@ namespace Microsoft.AspNet.Security.DataProtection
|
||||||
{
|
{
|
||||||
descriptors.AddRange(new[]
|
descriptors.AddRange(new[]
|
||||||
{
|
{
|
||||||
describe.Singleton<IXmlEncryptor, DpapiXmlEncryptor>(),
|
describe.Instance<IXmlEncryptor>(new DpapiXmlEncryptor(protectToLocalMachine: false)),
|
||||||
describe.Instance<IXmlRepository>(new FileSystemXmlRepository(localAppDataKeysFolder))
|
describe.Instance<IXmlRepository>(new FileSystemXmlRepository(localAppDataKeysFolder))
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Are we running with no user profile (e.g., IIS service)?
|
// If we've reached this point, we have no user profile loaded.
|
||||||
// Fall back to DPAPI for now.
|
|
||||||
// TODO: We should use the IIS auto-gen reg keys as our repository.
|
RegistryXmlRepository hklmRegXmlRepository = RegistryXmlRepository.GetDefaultRepositoryForHKLMRegistry();
|
||||||
return new[] {
|
if (hklmRegXmlRepository != null)
|
||||||
describe.Instance<IDataProtectionProvider>(new DpapiDataProtectionProvider(DataProtectionScope.LocalMachine))
|
{
|
||||||
};
|
// Have WAS and IIS created an auto-gen key folder in the HKLM registry for us?
|
||||||
|
// If so, use it as the repository, and use DPAPI as the key protection mechanism.
|
||||||
|
// We use same-machine DPAPI since we already know no user profile is loaded.
|
||||||
|
descriptors.AddRange(new[]
|
||||||
|
{
|
||||||
|
describe.Instance<IXmlEncryptor>(new DpapiXmlEncryptor(protectToLocalMachine: true)),
|
||||||
|
describe.Instance<IXmlRepository>(hklmRegXmlRepository)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Fall back to DPAPI for now
|
||||||
|
return new[] {
|
||||||
|
describe.Instance<IDataProtectionProvider>(new DpapiDataProtectionProvider(DataProtectionScope.LocalMachine))
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,117 @@
|
||||||
|
// Copyright (c) Microsoft Open Technologies, Inc. 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 System.Globalization;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Security.Principal;
|
||||||
|
using System.Xml.Linq;
|
||||||
|
using Microsoft.Win32;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Security.DataProtection.Repositories
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// An XML repository backed by the Windows registry.
|
||||||
|
/// </summary>
|
||||||
|
public class RegistryXmlRepository : IXmlRepository
|
||||||
|
{
|
||||||
|
public RegistryXmlRepository([NotNull] RegistryKey registryKey)
|
||||||
|
{
|
||||||
|
RegistryKey = registryKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected RegistryKey RegistryKey
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
private set;
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual IReadOnlyCollection<XElement> GetAllElements()
|
||||||
|
{
|
||||||
|
// forces complete enumeration
|
||||||
|
return GetAllElementsImpl().ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
private IEnumerable<XElement> GetAllElementsImpl()
|
||||||
|
{
|
||||||
|
string[] allValueNames = RegistryKey.GetValueNames();
|
||||||
|
foreach (var valueName in allValueNames)
|
||||||
|
{
|
||||||
|
string thisValue = RegistryKey.GetValue(valueName) as string;
|
||||||
|
if (!String.IsNullOrEmpty(thisValue))
|
||||||
|
{
|
||||||
|
XDocument document;
|
||||||
|
using (var textReader = new StringReader(thisValue))
|
||||||
|
{
|
||||||
|
document = XDocument.Load(textReader);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 'yield return' outside the preceding 'using' block so we can release the reader
|
||||||
|
yield return document.Root;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static RegistryXmlRepository GetDefaultRepositoryForHKLMRegistry()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Try reading the auto-generated machine key from HKLM
|
||||||
|
using (var hklmBaseKey = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry32))
|
||||||
|
{
|
||||||
|
// TODO: Do we need to change the version number below?
|
||||||
|
string aspnetAutoGenKeysBaseKeyName = String.Format(CultureInfo.InvariantCulture, @"SOFTWARE\Microsoft\ASP.NET\4.0.30319.0\AutoGenKeys\{0}", WindowsIdentity.GetCurrent().User.Value);
|
||||||
|
var aspnetBaseKey = hklmBaseKey.OpenSubKey(aspnetAutoGenKeysBaseKeyName, writable: true);
|
||||||
|
if (aspnetBaseKey == null)
|
||||||
|
{
|
||||||
|
return null; // couldn't find the auto-generated machine key
|
||||||
|
}
|
||||||
|
|
||||||
|
using (aspnetBaseKey) {
|
||||||
|
// TODO: Remove the ".BETA" moniker.
|
||||||
|
var dataProtectionKey = aspnetBaseKey.OpenSubKey("DataProtection.BETA", writable: true);
|
||||||
|
if (dataProtectionKey == null)
|
||||||
|
{
|
||||||
|
// TODO: Remove the ".BETA" moniker from here, also.
|
||||||
|
dataProtectionKey = aspnetBaseKey.CreateSubKey("DataProtection.BETA");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Once we've opened the HKLM reg key, return a repository which wraps it.
|
||||||
|
return new RegistryXmlRepository(dataProtectionKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// swallow all errors; they're not fatal
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void StoreElement([NotNull] XElement element, string friendlyName)
|
||||||
|
{
|
||||||
|
// We're going to ignore the friendly name for now and just use a GUID.
|
||||||
|
StoreElement(element, Guid.NewGuid());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void StoreElement(XElement element, Guid id)
|
||||||
|
{
|
||||||
|
// First, serialize the XElement to a string.
|
||||||
|
string serializedString;
|
||||||
|
using (var writer = new StringWriter())
|
||||||
|
{
|
||||||
|
new XDocument(element).Save(writer);
|
||||||
|
serializedString = writer.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Technically calls to RegSetValue* and RegGetValue* are atomic, so we don't have to worry about
|
||||||
|
// another thread trying to read this value while we're writing it. There's still a small risk of
|
||||||
|
// data corruption if power is lost while the registry file is being flushed to the file system,
|
||||||
|
// but the window for that should be small enough that we shouldn't have to worry about it.
|
||||||
|
string idAsString = id.ToString("D");
|
||||||
|
RegistryKey.SetValue(idAsString, serializedString, RegistryValueKind.String);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -16,6 +16,13 @@ namespace Microsoft.AspNet.Security.DataProtection.XmlEncryption
|
||||||
{
|
{
|
||||||
internal static readonly XName DpapiEncryptedSecretElementName = XmlKeyManager.KeyManagementXmlNamespace.GetName("dpapiEncryptedSecret");
|
internal static readonly XName DpapiEncryptedSecretElementName = XmlKeyManager.KeyManagementXmlNamespace.GetName("dpapiEncryptedSecret");
|
||||||
|
|
||||||
|
private readonly bool _protectToLocalMachine;
|
||||||
|
|
||||||
|
public DpapiXmlEncryptor(bool protectToLocalMachine)
|
||||||
|
{
|
||||||
|
_protectToLocalMachine = protectToLocalMachine;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Encrypts the specified XML element using Windows DPAPI.
|
/// Encrypts the specified XML element using Windows DPAPI.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -45,7 +52,7 @@ namespace Microsoft.AspNet.Security.DataProtection.XmlEncryption
|
||||||
// <secret decryptor="{TYPE}">
|
// <secret decryptor="{TYPE}">
|
||||||
// ... base64 data ...
|
// ... base64 data ...
|
||||||
// </secret>
|
// </secret>
|
||||||
byte[] encryptedBytes = DpapiSecretSerializerHelper.ProtectWithDpapi(secret);
|
byte[] encryptedBytes = DpapiSecretSerializerHelper.ProtectWithDpapi(secret, protectToLocalMachine: _protectToLocalMachine);
|
||||||
return new XElement(DpapiEncryptedSecretElementName,
|
return new XElement(DpapiEncryptedSecretElementName,
|
||||||
new XAttribute("decryptor", typeof(DpapiXmlDecryptor).AssemblyQualifiedName),
|
new XAttribute("decryptor", typeof(DpapiXmlDecryptor).AssemblyQualifiedName),
|
||||||
new XAttribute("version", 1),
|
new XAttribute("version", 1),
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,7 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Microsoft.Framework.DependencyInjection": "1.0.0-*",
|
"Microsoft.Framework.DependencyInjection": "1.0.0-*",
|
||||||
"Microsoft.Framework.OptionsModel": "1.0.0-*",
|
"Microsoft.Framework.OptionsModel": "1.0.0-*",
|
||||||
|
"Microsoft.Win32.Registry": "4.0.0-beta-*",
|
||||||
"System.Diagnostics.Debug": "4.0.10-beta-*",
|
"System.Diagnostics.Debug": "4.0.10-beta-*",
|
||||||
"System.Diagnostics.Tools": "4.0.0-beta-*",
|
"System.Diagnostics.Tools": "4.0.0-beta-*",
|
||||||
"System.Globalization": "4.0.10-beta-*",
|
"System.Globalization": "4.0.10-beta-*",
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue