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 repouso. 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 repouso.

  • 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 repouso.

  • 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.

Key Creation

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.)

Key Retrieval

Key Creation

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.)

Key Retrieval

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 repouso para obter mais informações.

Para alterar o mecanismo padrão de criptografia de chave 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>
 */