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");
|
||||
|
||||
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
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
||||
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),
|
||||
|
|
|
|||
|
|
@ -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-*",
|
||||
|
|
|
|||
Loading…
Reference in New Issue