DataProtectionServices should use keys stored in HKLM auto-gen registry when running on IIS without user profile.

This commit is contained in:
Levi B 2014-10-20 14:12:04 -07:00
parent ca95189a3b
commit 76b76ba099
5 changed files with 150 additions and 10 deletions

View File

@ -22,7 +22,7 @@ namespace Microsoft.AspNet.Security.DataProtection.Cng
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);
@ -34,7 +34,7 @@ namespace Microsoft.AspNet.Security.DataProtection.Cng
secret.WriteSecretIntoBuffer(new ArraySegment<byte>(plaintextSecret));
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

View File

@ -72,18 +72,33 @@ namespace Microsoft.AspNet.Security.DataProtection
{
descriptors.AddRange(new[]
{
describe.Singleton<IXmlEncryptor, DpapiXmlEncryptor>(),
describe.Instance<IXmlEncryptor>(new DpapiXmlEncryptor(protectToLocalMachine: false)),
describe.Instance<IXmlRepository>(new FileSystemXmlRepository(localAppDataKeysFolder))
});
}
else
{
// Are we running with no user profile (e.g., IIS service)?
// Fall back to DPAPI for now.
// TODO: We should use the IIS auto-gen reg keys as our repository.
return new[] {
describe.Instance<IDataProtectionProvider>(new DpapiDataProtectionProvider(DataProtectionScope.LocalMachine))
};
// If we've reached this point, we have no user profile loaded.
RegistryXmlRepository hklmRegXmlRepository = RegistryXmlRepository.GetDefaultRepositoryForHKLMRegistry();
if (hklmRegXmlRepository != null)
{
// 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))
};
}
}
}

View File

@ -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);
}
}
}

View File

@ -16,6 +16,13 @@ namespace Microsoft.AspNet.Security.DataProtection.XmlEncryption
{
internal static readonly XName DpapiEncryptedSecretElementName = XmlKeyManager.KeyManagementXmlNamespace.GetName("dpapiEncryptedSecret");
private readonly bool _protectToLocalMachine;
public DpapiXmlEncryptor(bool protectToLocalMachine)
{
_protectToLocalMachine = protectToLocalMachine;
}
/// <summary>
/// Encrypts the specified XML element using Windows DPAPI.
/// </summary>
@ -45,7 +52,7 @@ namespace Microsoft.AspNet.Security.DataProtection.XmlEncryption
// <secret decryptor="{TYPE}">
// ... base64 data ...
// </secret>
byte[] encryptedBytes = DpapiSecretSerializerHelper.ProtectWithDpapi(secret);
byte[] encryptedBytes = DpapiSecretSerializerHelper.ProtectWithDpapi(secret, protectToLocalMachine: _protectToLocalMachine);
return new XElement(DpapiEncryptedSecretElementName,
new XAttribute("decryptor", typeof(DpapiXmlDecryptor).AssemblyQualifiedName),
new XAttribute("version", 1),

View File

@ -27,6 +27,7 @@
"dependencies": {
"Microsoft.Framework.DependencyInjection": "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.Tools": "4.0.0-beta-*",
"System.Globalization": "4.0.10-beta-*",