Extensibilidade de gerenciamento de chaves no ASP.NET Core
Leia a seção de gerenciamento de chaves antes de ler esta seção, pois ela explica alguns dos conceitos fundamentais por trás dessas APIs.
Aviso: os tipos que implementam qualquer uma das interfaces a seguir devem ser thread-safe para vários chamadores.
Chave
A interface IKey
é a representação básica de uma chave no sistema de criptografia. O termo "chave" é usado aqui no sentido abstrato, não no sentido literal de "material de chave criptográfica". Uma chave tem as seguintes propriedades:
Datas de ativação, criação e expiração
Status de revogação
Identificador de chave (um GUID)
Além disso, IKey
expõe um método CreateEncryptor
que pode ser usado para criar uma instância IAuthenticatedEncryptor vinculada a essa chave.
Além disso, IKey
expõe um método CreateEncryptorInstance
que pode ser usado para criar uma instância IAuthenticatedEncryptor vinculada a essa chave.
Observação
Não há nenhuma API para recuperar o material criptográfico bruto de uma instância IKey
.
IKeyManager
A interface IKeyManager
representa um objeto responsável pelo armazenamento, recuperação e manipulação de chaves de maneira geral. Ele expõe três operações de alto nível:
Crie uma nova chave e persista-a no armazenamento.
Obtenha todas as chaves do armazenamento.
Revogue uma ou mais chaves e persista as informações de revogação no armazenamento.
Aviso
Escrever um IKeyManager
é uma tarefa muito avançada que a maioria dos desenvolvedores não deve tentar. Em vez disso, a maioria dos desenvolvedores deve aproveitar as instalações oferecidas pela classe XmlKeyManager.
XmlKeyManager
O tipo XmlKeyManager
é a implementação concreta in-box de IKeyManager
. Ele fornece várias instalações úteis, incluindo o armazenamento de chaves e a criptografia de chaves em rest. As chaves nesse sistema são representadas como elementos XML (especificamente, XElement).
XmlKeyManager
depende de vários outros componentes no curso do cumprimento de suas tarefas:
AlgorithmConfiguration
, que determina os algoritmos usados por novas chaves.IXmlRepository
, que controla onde as chaves são mantidas no armazenamento.IXmlEncryptor
[opcional], que permite criptografar chaves em rest.IKeyEscrowSink
[opcional], que fornece os principais serviços de caução.
IXmlRepository
, que controla onde as chaves são mantidas no armazenamento.IXmlEncryptor
[opcional], que permite criptografar chaves em rest.IKeyEscrowSink
[opcional], que fornece os principais serviços de caução.
Abaixo estão diagramas de alto nível que indicam como esses componentes são conectados em conjunto no XmlKeyManager
.
Criação de chave/CreateNewKey
Na implementação de CreateNewKey
, o componente AlgorithmConfiguration
é usado para criar um IAuthenticatedEncryptorDescriptor
exclusivo, que é serializado como XML. Se um coletor de caução de chave estiver presente, o XML bruto (não criptografado) será fornecido ao coletor para armazenamento de longo prazo. O XML não criptografado é executado por meio de um IXmlEncryptor
(se necessário) para gerar o documento XML criptografado. Esse documento criptografado é persistido no armazenamento de longo prazo por meio do IXmlRepository
. (Se nenhum IXmlEncryptor
estiver configurado, o documento não criptografado será persistido no IXmlRepository
.)
Criação de chave/CreateNewKey
Na implementação de CreateNewKey
, o componente IAuthenticatedEncryptorConfiguration
é usado para criar um IAuthenticatedEncryptorDescriptor
exclusivo, que é serializado como XML. Se um coletor de caução de chave estiver presente, o XML bruto (não criptografado) será fornecido ao coletor para armazenamento de longo prazo. O XML não criptografado é executado por meio de um IXmlEncryptor
(se necessário) para gerar o documento XML criptografado. Esse documento criptografado é persistido no armazenamento de longo prazo por meio do IXmlRepository
. (Se nenhum IXmlEncryptor
estiver configurado, o documento não criptografado será persistido no IXmlRepository
.)
Recuperação de chave/GetAllKeys
Na implementação de GetAllKeys
, os documentos XML que representam chaves e revogações são lidos do IXmlRepository
subjacente. Se esses documentos forem criptografados, o sistema os descriptografará automaticamente. XmlKeyManager
cria as instâncias apropriadas de IAuthenticatedEncryptorDescriptorDeserializer
para desserializar os documentos de volta em instâncias de IAuthenticatedEncryptorDescriptor
, que são então encapsuladas em instâncias individuais de IKey
. Essa coleção de instâncias de IKey
é retornada ao chamador.
Mais informações sobre os elementos XML específicos podem ser encontradas no documento de formato de armazenamento de chaves.
IXmlRepository
A interface IXmlRepository
representa um tipo que pode persistir XML para um repositório de backup e recuperar XML de um repositório de backup. Ele expõe duas APIs:
GetAllElements
:IReadOnlyCollection<XElement>
StoreElement(XElement element, string friendlyName)
As implementações de IXmlRepository
não precisam analisar o XML que passa por elas. Elas devem tratar os documentos XML como opacos e permitir que camadas mais altas se preocupem em gerar e analisar os documentos.
Há quatro tipos concretos internos que implementam IXmlRepository
:
Confira o documento de provedores de armazenamento de chaves para obter mais informações.
Registrar um IXmlRepository
personalizado é apropriado ao usar um repositório de backup diferente (por exemplo, Armazenamento de Tabelas do Azure).
Para alterar o repositório padrão em todo o aplicativo, registre uma instância IXmlRepository
personalizada:
services.Configure<KeyManagementOptions>(options => options.XmlRepository = new MyCustomXmlRepository());
services.AddSingleton<IXmlRepository>(new MyCustomXmlRepository());
IXmlEncryptor
A interface IXmlEncryptor
representa um tipo que pode criptografar um elemento XML de texto não criptografado. Ele expõe uma única API:
- Encrypt(XElement plaintextElement) : EncryptedXmlInfo
Se um serializado IAuthenticatedEncryptorDescriptor
contiver elementos marcados como "requer criptografia", XmlKeyManager
executará esses elementos por meio do método configurado Encrypt
de IXmlEncryptor
e persistirá o elemento criptografado em vez do elemento de texto não criptografado para o IXmlRepository
. A saída do método Encrypt
é um objeto EncryptedXmlInfo
. Esse objeto é um wrapper que contém o XElement
criptografado resultante e o Tipo, que representa um IXmlDecryptor
que pode ser usado para decifrar o elemento correspondente.
Há quatro tipos concretos internos que implementam IXmlEncryptor
:
Confira o documento de criptografia de chave em rest para mais informações.
Para alterar o mecanismo padrão de criptografia de chave em rest em todo o aplicativo, registre uma instância IXmlEncryptor
personalizada:
services.Configure<KeyManagementOptions>(options => options.XmlEncryptor = new MyCustomXmlEncryptor());
services.AddSingleton<IXmlEncryptor>(new MyCustomXmlEncryptor());
IXmlDecryptor
A interface IXmlDecryptor
representa um tipo que sabe como descriptografar um XElement
que foi codificado por meio de um IXmlEncryptor
. Ele expõe uma única API:
- Decrypt(XElement encryptedElement) : XElement
O método Decrypt
desfaz a criptografia executada por IXmlEncryptor.Encrypt
. Em geral, cada implementação concreta IXmlEncryptor
terá uma implementação concreta IXmlDecryptor
correspondente.
Os tipos que implementam IXmlDecryptor
devem ter um dos dois construtores públicos a seguir:
- .ctor(IServiceProvider)
- .ctor()
Observação
O IServiceProvider
passado para o construtor pode ser nulo.
IKeyEscrowSink
A interface IKeyEscrowSink
representa um tipo que pode executar o caução de informações confidenciais. Lembre-se de que descritores serializados podem conter informações confidenciais (como material criptográfico), e foi isso que levou à introdução do tipo IXmlEncryptor em primeiro lugar. No entanto, ocorrem acidentes e os anéis de chave podem ser excluídos ou corrompidos.
A interface de caução fornece uma hachura de escape de emergência, permitindo o acesso ao XML serializado bruto antes de ser transformado por qualquer IXmlEncryptor configurado. A interface expõe uma única API:
- Store(Guid keyId, elemento XElement)
Cabe à implementação de IKeyEscrowSink
lidar com o elemento fornecido de maneira segura e consistente com a política de negócios. Uma implementação possível pode ser que o coletor de caução criptografe o elemento XML usando um certificado X.509 corporativo conhecido em que a chave privada do certificado foi gerada; o tipo CertificateXmlEncryptor
pode ajudar com isso. A implementação de IKeyEscrowSink
também é responsável por persistir o elemento fornecido adequadamente.
Por padrão, nenhum mecanismo de caução está habilitado, embora os administradores de servidores possam configurar isso globalmente. Ele também pode ser configurado programaticamente por meio do método IDataProtectionBuilder.AddKeyEscrowSink
, conforme mostrado no exemplo abaixo. O método AddKeyEscrowSink
sobrecarrega espelho as sobrecargas IServiceCollection.AddSingleton
e IServiceCollection.AddInstance
, pois as instâncias de IKeyEscrowSink
destinam-se a serem singletons. Se várias instâncias de IKeyEscrowSink
forem registradas, cada uma será chamada durante a geração de chaves, para que as chaves possam ser geradas simultaneamente para vários mecanismos.
Não há nenhuma API para ler o material de uma instância de IKeyEscrowSink
. Isso é consistente com a teoria do design do mecanismo de caução: ele se destina a tornar o material chave acessível a uma autoridade confiável e, como o aplicativo não é uma autoridade confiável, ele não deve ter acesso ao seu próprio material com caução.
O código de exemplo a seguir demonstra como criar e registrar um IKeyEscrowSink
em que as chaves são geradas de modo que somente membros de "CONTOSODomain Admins" possam recuperá-las.
Observação
Para executar este exemplo, você deve estar em um computador do Windows 8/Windows Server 2012 ingressado no domínio e o controlador de domínio deve ser do Windows Server 2012 ou posterior.
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>
*/