ASP.NET Core 的密钥管理可扩展性Key management extensibility in ASP.NET Core
本文内容
提示
阅读本部分之前,请阅读密钥管理部分,因为它说明了这些 api 背后的一些基本概念。
警告
实现以下接口的任何类型应该是线程安全的多个调用方。
密钥Key
IKey
接口是 cryptosystem 中密钥的基本表示形式。在抽象意义上,"加密密钥材料"字面意义上不在此处使用术语该键。一个键具有以下属性:
激活、 创建和过期日期
吊销状态
密钥标识符 (GUID)
此外,IKey
公开了可用于创建绑定到此密钥的IAuthenticatedEncryptor实例的 CreateEncryptor
方法。
此外,IKey
公开了可用于创建绑定到此密钥的IAuthenticatedEncryptor实例的 CreateEncryptorInstance
方法。
备注
没有用于从 IKey
实例检索原始加密材料的 API。
IKeyManagerIKeyManager
IKeyManager
接口表示负责常规密钥存储、检索和操作的对象。它公开三个高级操作:
创建新的密钥,并将其保存到存储。
从存储中获取所有键。
撤消一个或多个密钥并保存到存储吊销信息。
警告
编写 IKeyManager
是一种非常高级的任务,大多数开发人员都不应尝试。相反,大多数开发人员应充分利用XmlKeyManager类提供的功能。
XmlKeyManagerXmlKeyManager
XmlKeyManager
类型是 IKeyManager
的内置具体实现。它提供了几个有用的功能,包括密钥托管和静态密钥加密。此系统中的键表示为 XML 元素(特别是system.xml.linq.xelement>)。
XmlKeyManager
依赖于完成其任务的过程中的多个其他组件:
AlgorithmConfiguration
,用于指示新密钥使用的算法。IXmlRepository
,控制在存储中保留密钥的位置。IXmlEncryptor
[可选],这允许静态加密密钥。IKeyEscrowSink
[可选],它提供密钥委托服务。
IXmlRepository
,控制在存储中保留密钥的位置。IXmlEncryptor
[可选],这允许静态加密密钥。IKeyEscrowSink
[可选],它提供密钥委托服务。
下面是高级关系图,这些关系图指示如何在 XmlKeyManager
中将这些组件连接在一起。
密钥创建/CreateNewKey
在 CreateNewKey
的实现中,AlgorithmConfiguration
组件用于创建唯一 IAuthenticatedEncryptorDescriptor
,然后将其序列化为 XML。如果存在密钥托管接收器,则原始 (未加密) 的 XML 提供到接收器进行长期存储。然后,将通过 IXmlEncryptor
(如果需要)运行未加密的 XML,以生成加密的 XML 文档。此加密文档通过 IXmlRepository
持久保存到长期存储。(如果未配置任何 IXmlEncryptor
,则会将未加密的文档保留在 IXmlRepository
中。)
密钥创建/CreateNewKey
在 CreateNewKey
的实现中,IAuthenticatedEncryptorConfiguration
组件用于创建唯一 IAuthenticatedEncryptorDescriptor
,然后将其序列化为 XML。如果存在密钥托管接收器,则原始 (未加密) 的 XML 提供到接收器进行长期存储。然后,将通过 IXmlEncryptor
(如果需要)运行未加密的 XML,以生成加密的 XML 文档。此加密文档通过 IXmlRepository
持久保存到长期存储。(如果未配置任何 IXmlEncryptor
,则会将未加密的文档保留在 IXmlRepository
中。)
密钥检索/GetAllKeys
在 GetAllKeys
的实现中,将从基础 IXmlRepository
读取表示键和吊销的 XML 文档。如果这些文档进行加密,系统将自动解密机密。XmlKeyManager
将创建适当的 IAuthenticatedEncryptorDescriptorDeserializer
实例,将文档反序列化为 IAuthenticatedEncryptorDescriptor
实例,然后将其包装在单独的 IKey
实例中。此 IKey
实例的集合将返回到调用方。
有关特定 XML 元素的详细信息,请参阅密钥存储格式文档。
IXmlRepositoryIXmlRepository
IXmlRepository
接口表示可将 XML 保存到后备存储并从中检索 XML 的类型。它公开两个 Api:
GetAllElements
:IReadOnlyCollection<XElement>
StoreElement(XElement element, string friendlyName)
IXmlRepository
的实现不需要分析通过它们传递的 XML。它们应视为不透明的 XML 文档,让较高的层担心如何生成和分析文档。
有四种内置的具体类型可实现 IXmlRepository
:
有关详细信息,请参阅密钥存储提供程序文档。
使用其他后备存储(例如 Azure 表存储)时,注册自定义 IXmlRepository
是合适的。
若要更改应用程序范围内的默认存储库,请注册自定义 IXmlRepository
实例:
services.Configure<KeyManagementOptions>(options => options.XmlRepository = new MyCustomXmlRepository());
services.AddSingleton<IXmlRepository>(new MyCustomXmlRepository());
IXmlEncryptorIXmlEncryptor
IXmlEncryptor
接口表示可加密纯文本 XML 元素的类型。它公开一个 API:
- 加密 (XElement plaintextElement): EncryptedXmlInfo
如果序列化的 IAuthenticatedEncryptorDescriptor
包含任何标记为 "需要加密" 的元素,则 XmlKeyManager
将通过配置的 IXmlEncryptor
的 Encrypt
方法来运行这些元素,并将到加密元素而不是纯文本元素保存到 IXmlRepository
。Encrypt
方法的输出是 EncryptedXmlInfo
对象。此对象是包含所生成的到加密 XElement
的包装,该类型表示可用于破译相应元素的 IXmlDecryptor
。
有四种内置的具体类型可实现 IXmlEncryptor
:
有关详细信息,请参阅静态密钥加密文档。
若要更改应用程序范围内默认的密钥加密机制,请注册自定义 IXmlEncryptor
实例:
services.Configure<KeyManagementOptions>(options => options.XmlEncryptor = new MyCustomXmlEncryptor());
services.AddSingleton<IXmlEncryptor>(new MyCustomXmlEncryptor());
IXmlDecryptorIXmlDecryptor
IXmlDecryptor
接口表示一种类型,该类型知道如何解密通过 IXmlEncryptor
到加密的 XElement
。它公开一个 API:
- 解密 (XElement encryptedElement): XElement
Decrypt
方法撤销 IXmlEncryptor.Encrypt
所执行的加密。通常,每个具体 IXmlEncryptor
实现都有相应的具体 IXmlDecryptor
实现。
实现 IXmlDecryptor
的类型应该具有以下两个公共构造函数之一:
- .ctor(IServiceProvider)
- .ctor()
备注
传递给构造函数的 IServiceProvider
可能为 null。
IKeyEscrowSinkIKeyEscrowSink
IKeyEscrowSink
接口表示可以执行敏感信息的委托的类型。请记住,序列化描述符可能包含敏感信息(如加密材料),这是第一个位置引入IXmlEncryptor类型的结果。但是,意外的发生,并且密钥环可以删除或损坏。
托管接口提供了紧急转义影线,允许在任何已配置的IXmlEncryptor转换之前访问原始序列化的 XML。该接口公开单个 API:
- 应用商店 (Guid keyId、 XElement 元素)
这取决于 IKeyEscrowSink
实现,以安全的方式处理所提供的元素,与业务策略一致。一个可能的实现可能是,托管接收器使用已知的公司 x.509 证书(其中证书的私钥已托管)对 XML 元素进行加密;CertificateXmlEncryptor
类型可帮助进行此类处理。IKeyEscrowSink
实现还负责正确保存提供的元素。
默认情况下,不启用任何托管机制,尽管服务器管理员可对此进行全局配置。它还可以通过 IDataProtectionBuilder.AddKeyEscrowSink
方法以编程方式进行配置,如下面的示例中所示。AddKeyEscrowSink
方法重载会镜像 IServiceCollection.AddSingleton
并 IServiceCollection.AddInstance
重载,因为 IKeyEscrowSink
实例将被单一实例。如果注册了多个 IKeyEscrowSink
实例,则会在密钥生成过程中调用每个实例,因此密钥可同时托管多个机制。
没有用于从 IKeyEscrowSink
实例读取材料的 API。这与托管机制的设计从理论上讲是一致: 它可用于使密钥材料的受信任的颁发机构,并且由于应用程序本身不是受信任的颁发机构,它不应该有权访问其自身托管的材料。
下面的示例代码演示如何创建和注册 IKeyEscrowSink
,其中密钥是托管的,这样只有 "CONTOSODomain Admins" 的成员才能恢复它们。
备注
若要运行此示例,必须是到已加入域的 Windows 8 / Windows Server 2012 计算机和域控制器必须是 Windows Server 2012 或更高版本。
using System;
using System.IO;
using System.Xml.Linq;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.DataProtection.KeyManagement;
using Microsoft.AspNetCore.DataProtection.XmlEncryption;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
public class Program
{
public static void Main(string[] args)
{
var serviceCollection = new ServiceCollection();
serviceCollection.AddDataProtection()
.PersistKeysToFileSystem(new DirectoryInfo(@"c:\temp-keys"))
.ProtectKeysWithDpapi()
.AddKeyEscrowSink(sp => new MyKeyEscrowSink(sp));
var services = serviceCollection.BuildServiceProvider();
// get a reference to the key manager and force a new key to be generated
Console.WriteLine("Generating new key...");
var keyManager = services.GetService<IKeyManager>();
keyManager.CreateNewKey(
activationDate: DateTimeOffset.Now,
expirationDate: DateTimeOffset.Now.AddDays(7));
}
// A key escrow sink where keys are escrowed such that they
// can be read by members of the CONTOSO\Domain Admins group.
private class MyKeyEscrowSink : IKeyEscrowSink
{
private readonly IXmlEncryptor _escrowEncryptor;
public MyKeyEscrowSink(IServiceProvider services)
{
// Assuming I'm on a machine that's a member of the CONTOSO
// domain, I can use the Domain Admins SID to generate an
// encrypted payload that only they can read. Sample SID from
// https://technet.microsoft.com/library/cc778824(v=ws.10).aspx.
_escrowEncryptor = new DpapiNGXmlEncryptor(
"SID=S-1-5-21-1004336348-1177238915-682003330-512",
DpapiNGProtectionDescriptorFlags.None,
new LoggerFactory());
}
public void Store(Guid keyId, XElement element)
{
// Encrypt the key element to the escrow encryptor.
var encryptedXmlInfo = _escrowEncryptor.Encrypt(element);
// A real implementation would save the escrowed key to a
// write-only file share or some other stable storage, but
// in this sample we'll just write it out to the console.
Console.WriteLine($"Escrowing key {keyId}");
Console.WriteLine(encryptedXmlInfo.EncryptedElement);
// Note: We cannot read the escrowed key material ourselves.
// We need to get a member of CONTOSO\Domain Admins to read
// it for us in the event we need to recover it.
}
}
}
/*
* SAMPLE OUTPUT
*
* Generating new key...
* Escrowing key 38e74534-c1b8-4b43-aea1-79e856a822e5
* <encryptedKey>
* <!-- This key is encrypted with Windows DPAPI-NG. -->
* <!-- Rule: SID=S-1-5-21-1004336348-1177238915-682003330-512 -->
* <value>MIIIfAYJKoZIhvcNAQcDoIIIbTCCCGkCAQ...T5rA4g==</value>
* </encryptedKey>
*/