257 lines
12 KiB
C#
257 lines
12 KiB
C#
// 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.Diagnostics;
|
|
using System.Globalization;
|
|
using System.Linq;
|
|
using System.Reflection;
|
|
using System.Xml.Linq;
|
|
using Microsoft.AspNet.Security.DataProtection.AuthenticatedEncryption;
|
|
using Microsoft.AspNet.Security.DataProtection.Repositories;
|
|
using Microsoft.AspNet.Security.DataProtection.XmlEncryption;
|
|
using Microsoft.Framework.DependencyInjection;
|
|
|
|
namespace Microsoft.AspNet.Security.DataProtection.KeyManagement
|
|
{
|
|
public sealed class XmlKeyManager : IKeyManager
|
|
{
|
|
private const string KEY_MANAGEMENT_XML_NAMESPACE_STRING = "http://www.asp.net/dataProtection/2014";
|
|
internal static readonly XNamespace KeyManagementXmlNamespace = XNamespace.Get(KEY_MANAGEMENT_XML_NAMESPACE_STRING);
|
|
|
|
internal static readonly XName ActivationDateElementName = KeyManagementXmlNamespace.GetName("activationDate");
|
|
internal static readonly XName AuthenticatedEncryptorElementName = KeyManagementXmlNamespace.GetName("authenticatedEncryptor");
|
|
internal static readonly XName CreationDateElementName = KeyManagementXmlNamespace.GetName("creationDate");
|
|
internal static readonly XName ExpirationDateElementName = KeyManagementXmlNamespace.GetName("expirationDate");
|
|
internal static readonly XName IdAttributeName = XNamespace.None.GetName("id");
|
|
internal static readonly XName KeyElementName = KeyManagementXmlNamespace.GetName("key");
|
|
internal static readonly XName ReaderAttributeName = XNamespace.None.GetName("reader");
|
|
internal static readonly XName ReasonElementName = KeyManagementXmlNamespace.GetName("reason");
|
|
internal static readonly XName RevocationDateElementName = KeyManagementXmlNamespace.GetName("revocationDate");
|
|
internal static readonly XName RevocationElementName = KeyManagementXmlNamespace.GetName("revocation");
|
|
internal static readonly XName VersionAttributeName = XNamespace.None.GetName("version");
|
|
|
|
private readonly IAuthenticatedEncryptorConfigurationFactory _authenticatedEncryptorConfigurationFactory;
|
|
private readonly IServiceProvider _serviceProvider;
|
|
private readonly ITypeActivator _typeActivator;
|
|
private readonly IXmlRepository _xmlRepository;
|
|
private readonly IXmlEncryptor _xmlEncryptor;
|
|
|
|
public XmlKeyManager(
|
|
[NotNull] IServiceProvider serviceProvider,
|
|
[NotNull] IAuthenticatedEncryptorConfigurationFactory authenticatedEncryptorConfigurationFactory,
|
|
[NotNull] ITypeActivator typeActivator,
|
|
[NotNull] IXmlRepository xmlRepository,
|
|
[NotNull] IXmlEncryptor xmlEncryptor)
|
|
{
|
|
_serviceProvider = serviceProvider;
|
|
_authenticatedEncryptorConfigurationFactory = authenticatedEncryptorConfigurationFactory;
|
|
_typeActivator = typeActivator;
|
|
_xmlRepository = xmlRepository;
|
|
_xmlEncryptor = xmlEncryptor;
|
|
}
|
|
|
|
public IKey CreateNewKey(DateTimeOffset activationDate, DateTimeOffset expirationDate)
|
|
{
|
|
return CreateNewKey(Guid.NewGuid(), DateTimeOffset.UtcNow, activationDate, expirationDate);
|
|
}
|
|
|
|
private IKey CreateNewKey(Guid keyId, DateTimeOffset creationDate, DateTimeOffset activationDate, DateTimeOffset expirationDate)
|
|
{
|
|
// <key id="{GUID}" version="1" xmlns="{XMLNS}">
|
|
// <creationDate>...</creationDate>
|
|
// <activationDate>...</activationDate>
|
|
// <expirationDate>...</expirationDate>
|
|
// <authenticatedEncryptor>
|
|
// <... parser="{TYPE}" />
|
|
// </authenticatedEncryptor>
|
|
// </xxx:key>
|
|
|
|
// Create the <xxx:authenticatedEncryptor /> element and make sure it's well-formed.
|
|
var encryptorConfiguration = _authenticatedEncryptorConfigurationFactory.CreateNewConfiguration();
|
|
var encryptorElementAsXml = encryptorConfiguration.ToXml(_xmlEncryptor);
|
|
CryptoUtil.Assert(!String.IsNullOrEmpty((string)encryptorElementAsXml.Attribute(ReaderAttributeName)), "!String.IsNullOrEmpty((string)encryptorElementAsXml.Attribute(ParserAttributeName))");
|
|
|
|
// Create the <xxx:key /> element.
|
|
var keyElement = new XElement(KeyElementName,
|
|
new XAttribute(IdAttributeName, keyId),
|
|
new XAttribute(VersionAttributeName, 1),
|
|
new XElement(CreationDateElementName, creationDate),
|
|
new XElement(ActivationDateElementName, activationDate),
|
|
new XElement(ExpirationDateElementName, expirationDate),
|
|
new XElement(AuthenticatedEncryptorElementName,
|
|
encryptorElementAsXml));
|
|
|
|
// Persist it to the underlying repository
|
|
string friendlyName = String.Format(CultureInfo.InvariantCulture, "key-{0:D}", keyId);
|
|
_xmlRepository.StoreElement(keyElement, friendlyName);
|
|
|
|
// And we're done!
|
|
return new Key(
|
|
keyId: keyId,
|
|
creationDate: creationDate,
|
|
activationDate: activationDate,
|
|
expirationDate: expirationDate,
|
|
encryptorConfiguration: encryptorConfiguration);
|
|
}
|
|
|
|
public IReadOnlyCollection<IKey> GetAllKeys()
|
|
{
|
|
var allElements = _xmlRepository.GetAllElements();
|
|
|
|
Dictionary<Guid, Key> idToKeyMap = new Dictionary<Guid, Key>();
|
|
HashSet<Guid> revokedKeyIds = null;
|
|
DateTimeOffset? mostRecentMassRevocationDate = null;
|
|
|
|
foreach (var element in allElements)
|
|
{
|
|
if (element.Name == KeyElementName)
|
|
{
|
|
var thisKey = ParseKeyElement(element);
|
|
if (idToKeyMap.ContainsKey(thisKey.KeyId))
|
|
{
|
|
CryptoUtil.Fail("TODO: Duplicate key.");
|
|
}
|
|
idToKeyMap.Add(thisKey.KeyId, thisKey);
|
|
}
|
|
else if (element.Name == RevocationElementName)
|
|
{
|
|
object revocationInfo = ParseRevocationElement(element);
|
|
DateTimeOffset? revocationInfoAsDate = revocationInfo as DateTimeOffset?;
|
|
if (revocationInfoAsDate != null)
|
|
{
|
|
// We're revoking all keys created on or after a specific date.
|
|
if (!mostRecentMassRevocationDate.HasValue || mostRecentMassRevocationDate < revocationInfoAsDate)
|
|
{
|
|
// This new value is the most recent mass revocation date.
|
|
mostRecentMassRevocationDate = revocationInfoAsDate;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// We're revoking only a specific key
|
|
if (revokedKeyIds == null)
|
|
{
|
|
revokedKeyIds = new HashSet<Guid>();
|
|
}
|
|
revokedKeyIds.Add((Guid)revocationInfo);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
CryptoUtil.Fail("TODO: Unknown element.");
|
|
}
|
|
}
|
|
|
|
// Now process all revocations
|
|
if (revokedKeyIds != null || mostRecentMassRevocationDate.HasValue)
|
|
{
|
|
foreach (Key key in idToKeyMap.Values)
|
|
{
|
|
if ((revokedKeyIds != null && revokedKeyIds.Contains(key.KeyId))
|
|
|| (mostRecentMassRevocationDate.HasValue && mostRecentMassRevocationDate >= key.CreationDate))
|
|
{
|
|
key.SetRevoked();
|
|
}
|
|
}
|
|
}
|
|
|
|
// And we're done!
|
|
return idToKeyMap.Values.ToArray();
|
|
}
|
|
|
|
private Key ParseKeyElement(XElement keyElement)
|
|
{
|
|
Debug.Assert(keyElement.Name == KeyElementName);
|
|
|
|
int version = (int)keyElement.Attribute(VersionAttributeName);
|
|
CryptoUtil.Assert(version == 1, "TODO: version == 1");
|
|
|
|
XElement encryptorConfigurationAsXml = keyElement.Element(AuthenticatedEncryptorElementName).Elements().Single();
|
|
string encryptorConfigurationParserTypeName = (string)encryptorConfigurationAsXml.Attribute(ReaderAttributeName);
|
|
Type encryptorConfigurationParserType = Type.GetType(encryptorConfigurationParserTypeName, throwOnError: true);
|
|
CryptoUtil.Assert(typeof(IAuthenticatedEncryptorConfigurationXmlReader).IsAssignableFrom(encryptorConfigurationParserType),
|
|
"TODO: typeof(IAuthenticatedEncryptorConfigurationXmlReader).IsAssignableFrom(encryptorConfigurationParserType)");
|
|
|
|
var parser = (IAuthenticatedEncryptorConfigurationXmlReader)_typeActivator.CreateInstance(_serviceProvider, encryptorConfigurationParserType);
|
|
var encryptorConfiguration = parser.FromXml(encryptorConfigurationAsXml);
|
|
|
|
Guid keyId = (Guid)keyElement.Attribute(IdAttributeName);
|
|
DateTimeOffset creationDate = (DateTimeOffset)keyElement.Element(CreationDateElementName);
|
|
DateTimeOffset activationDate = (DateTimeOffset)keyElement.Element(ActivationDateElementName);
|
|
DateTimeOffset expirationDate = (DateTimeOffset)keyElement.Element(ExpirationDateElementName);
|
|
|
|
return new Key(
|
|
keyId: keyId,
|
|
creationDate: creationDate,
|
|
activationDate: activationDate,
|
|
expirationDate: expirationDate,
|
|
encryptorConfiguration: encryptorConfiguration);
|
|
}
|
|
|
|
// returns a Guid (for specific keys) or a DateTimeOffset (for all keys created on or before a specific date)
|
|
private object ParseRevocationElement(XElement revocationElement)
|
|
{
|
|
Debug.Assert(revocationElement.Name == RevocationElementName);
|
|
|
|
string keyIdAsString = revocationElement.Element(KeyElementName).Attribute(IdAttributeName).Value;
|
|
if (keyIdAsString == "*")
|
|
{
|
|
// all keys
|
|
return (DateTimeOffset)revocationElement.Element(RevocationDateElementName);
|
|
}
|
|
else
|
|
{
|
|
// only one key
|
|
return new Guid(keyIdAsString);
|
|
}
|
|
}
|
|
|
|
public void RevokeAllKeys(DateTimeOffset revocationDate, string reason = null)
|
|
{
|
|
// <revocation version="1" xmlns="{XMLNS}">
|
|
// <revocationDate>...</revocationDate>
|
|
// <key id="*" />
|
|
// <reason>...</reason>
|
|
// </revocation>
|
|
|
|
var revocationElement = new XElement(RevocationElementName,
|
|
new XAttribute(VersionAttributeName, 1),
|
|
new XElement(RevocationDateElementName, revocationDate),
|
|
new XElement(KeyElementName,
|
|
new XAttribute(IdAttributeName, "*")),
|
|
new XElement(ReasonElementName, reason));
|
|
|
|
// Persist it to the underlying repository
|
|
string friendlyName = String.Format(CultureInfo.InvariantCulture, "revocation-{0:X16}", (ulong)revocationDate.UtcTicks);
|
|
_xmlRepository.StoreElement(revocationElement, friendlyName);
|
|
}
|
|
|
|
public void RevokeKey(Guid keyId, string reason = null)
|
|
{
|
|
RevokeSingleKey(keyId, DateTimeOffset.UtcNow, reason);
|
|
}
|
|
|
|
private void RevokeSingleKey(Guid keyId, DateTimeOffset utcNow, string reason)
|
|
{
|
|
// <revocation version="1" xmlns="{XMLNS}">
|
|
// <revocationDate>...</revocationDate>
|
|
// <key id="{GUID}" />
|
|
// <reason>...</reason>
|
|
// </revocation>
|
|
|
|
var revocationElement = new XElement(RevocationElementName,
|
|
new XAttribute(VersionAttributeName, 1),
|
|
new XElement(RevocationDateElementName, utcNow),
|
|
new XElement(KeyElementName,
|
|
new XAttribute(IdAttributeName, keyId)),
|
|
new XElement(ReasonElementName, reason));
|
|
|
|
// Persist it to the underlying repository
|
|
string friendlyName = String.Format(CultureInfo.InvariantCulture, "revocation-{0:D}", keyId);
|
|
_xmlRepository.StoreElement(revocationElement, friendlyName);
|
|
}
|
|
}
|
|
}
|