Extensibilidad de administración de claves en ASP.NET Core

Lea la sección administración de claves antes de leer esta sección, ya que explica algunos de los conceptos fundamentales de estas API.

Advertencia: los tipos que implementan cualquiera de las siguientes interfaces deben ser seguros para múltiples llamadas.

Clave

La interfaz IKey es la representación básica de una clave en un sistema criptográfico. El término clave se usa aquí en el sentido abstracto, no en el sentido literal de "material de clave criptográfica". Una clave tiene las siguientes propiedades:

  • Fechas de activación, creación y expiración

  • Estado de revocación

  • Identificador de clave (un GUID)

Además, IKey expone un método CreateEncryptor que puede usarse para crear una instancia de IAuthenticatedEncryptor vinculada a esta clave.

Además, IKey expone un método CreateEncryptorInstance que puede usarse para crear una instancia de IAuthenticatedEncryptor vinculada a esta clave.

Nota

No hay ninguna API para recuperar el material criptográfico sin procesar de una instancia de IKey.

IKeyManager

La interfaz de IKeyManager representa un objeto responsable del almacenamiento, la recuperación y la manipulación de claves en general. Expone tres operaciones de alto nivel:

  • Crear una nueva clave y conservarla en el almacenamiento.

  • Obtener todas las claves del almacenamiento.

  • Revocar una o varias claves y conservar la información de la revocación en el almacenamiento.

Advertencia

Escribir un IKeyManager es una tarea muy avanzada y la mayoría de los desarrolladores no deben intentarlo. En su lugar, la mayoría de los desarrolladores deben aprovechar las ventajas que ofrece la clase XmlKeyManager .

XmlKeyManager

El tipo XmlKeyManager es la implementación concreta integrada de IKeyManager. Proporciona varias instalaciones útiles, como la custodia de claves y el cifrado de claves en reposo. Las claves de este sistema se representan como elementos XML (en concreto, XElement).

XmlKeyManager depende de otros componentes en el curso de cumplir sus tareas:

  • AlgorithmConfiguration, que dicta los algoritmos usados por nuevas claves.

  • IXmlRepository, que controla dónde se conservan las claves en el almacenamiento.

  • IXmlEncryptor [opcional], que permite cifrar claves en reposo.

  • IKeyEscrowSink [opcional], que proporciona servicios de custodia de claves.

  • IXmlRepository, que controla dónde se conservan las claves en el almacenamiento.

  • IXmlEncryptor [opcional], que permite cifrar claves en reposo.

  • IKeyEscrowSink [opcional], que proporciona servicios de custodia de claves.

A continuación se muestran diagramas de alto nivel que indican cómo estos componentes están conectados juntos dentro de XmlKeyManager.

Key Creation

Creación de claves/CreateNewKey

En la implementación de CreateNewKey, el componente AlgorithmConfiguration se usa para crear un IAuthenticatedEncryptorDescriptor único, que después se serializa como XML. Si existe un receptor de custodia de claves, el XML sin cifrar se proporciona al receptor para el almacenamiento a largo plazo. El XML sin cifrar se pasa después por un IXmlEncryptor (si es necesario) para generar el documento XML cifrado. Este documento cifrado se guarda a largo plazo a través del IXmlRepository. (Si no se configura el IXmlEncryptor, el documento sin cifrar se mantiene en el IXmlRepository.)

Key Retrieval

Key Creation

Creación de claves/CreateNewKey

En la implementación de CreateNewKey, el componente IAuthenticatedEncryptorConfiguration se usa para crear un IAuthenticatedEncryptorDescriptor único, que después se serializa como XML. Si existe un receptor de custodia de claves, el XML sin cifrar se proporciona al receptor para el almacenamiento a largo plazo. El XML sin cifrar se pasa después por un IXmlEncryptor (si es necesario) para generar el documento XML cifrado. Este documento cifrado se guarda a largo plazo a través del IXmlRepository. (Si no se configura el IXmlEncryptor, el documento sin cifrar se mantiene en el IXmlRepository.)

Key Retrieval

Recuperación de claves / GetAllKeys

En la implementación de GetAllKeys, los documentos XML que representan claves y revocaciones se leen del IXmlRepository subyacente. Si estos documentos están cifrados, el sistema los descifrará automáticamente. XmlKeyManager crea las instancias de IAuthenticatedEncryptorDescriptorDeserializer apropiadas para deserializar los documentos de nuevo en instancias de IAuthenticatedEncryptorDescriptor, que después se envuelven en instancias individuales de IKey. Esta colección de instancias de IKey se devuelve a la persona que llama.

Encontrará más información sobre los elementos XML concretos en el documento de formato de almacenamiento de claves.

IXmlRepository

La interfaz de IXmlRepository representa un tipo que puede hacer persistir XML en un almacén de respaldo y recoger XML de él. Expone dos API:

  • GetAllElements :IReadOnlyCollection<XElement>

  • StoreElement(XElement element, string friendlyName)

Las implementaciones de IXmlRepository no necesitan analizar el XML que pasa a través de ellas. Deben tratar los documentos XML como opacos y dejar que las capas superiores se preocupen por generar y analizar los documentos.

Hay cuatro tipos concretos integrados que implementan IXmlRepository:

Consulte el documento de proveedores de almacenamiento de claves para más información.

El registro de un IXmlRepository personalizado es apropiado cuando se usa un almacén de respaldo diferente (por ejemplo, Azure Table Storage).

Para cambiar el repositorio predeterminado en toda la aplicación, registre una instancia personalizada de IXmlRepository:

services.Configure<KeyManagementOptions>(options => options.XmlRepository = new MyCustomXmlRepository());
services.AddSingleton<IXmlRepository>(new MyCustomXmlRepository());

IXmlEncryptor

La interfaz de IXmlEncryptor representa un tipo que puede cifrar un elemento XML en texto sin formato. Expone una sola API:

  • Encrypt(XElement plaintextElement) : EncryptedXmlInfo

Si un IAuthenticatedEncryptorDescriptor serializado contiene cualquier elemento marcado como "requiere encriptación", entonces XmlKeyManager ejecutará esos elementos a través del método Encrypt del IXmlEncryptor configurado, y conservará el elemento cifrado en lugar del elemento de texto sin formato en el IXmlRepository. La salida del método Encrypt es un objeto EncryptedXmlInfo. Este objeto es un contenedor que contiene tanto el XElement cifrado resultante como el Tipo que representa un IXmlDecryptor que puede usarse para descifrar el elemento correspondiente.

Hay cuatro tipos concretos integrados que implementan IXmlEncryptor:

Consulte el documento de cifrado de claves en reposo para más información.

Para cambiar el mecanismo predeterminado de cifrado de claves en reposo en toda la aplicación, registre una instancia de IXmlEncryptor personalizada:

services.Configure<KeyManagementOptions>(options => options.XmlEncryptor = new MyCustomXmlEncryptor());
services.AddSingleton<IXmlEncryptor>(new MyCustomXmlEncryptor());

IXmlDecryptor

La interfaz de IXmlDecryptor representa un tipo que sabe descifrar un XElement que se ha cifrado a través de un IXmlEncryptor. Expone una sola API:

  • Decrypt(XElement encryptedElement) : XElement

El método Decrypt deshace el cifrado realizado por IXmlEncryptor.Encrypt. En general, cada implementación concreta de IXmlEncryptor tendrá su correspondiente implementación concreta de IXmlDecryptor.

Los tipos que implementan IXmlDecryptor deben tener uno de los dos constructores públicos siguientes:

  • .ctor(IServiceProvider)
  • .ctor()

Nota:

El IServiceProvider pasado al constructor puede ser null.

IKeyEscrowSink

La interfaz de IKeyEscrowSink representa un tipo que puede realizar la custodia de información confidencial. Recordemos que los descriptores serializados pueden contener información confidencial (como material criptográfico), y esto es lo que llevó a la introducción del tipo IXmlEncryptor en primer lugar. Sin embargo, los accidentes ocurren y los llaveros pueden eliminarse o dañarse.

La interfaz de custodia proporciona una puerta de escape de emergencia, permitiendo el acceso al XML serializado en bruto antes de que sea transformado por cualquier IXmlEncryptor configurado. La interfaz expone una sola API:

  • Store(Guid keyId, elemento XElement)Store(Guid keyId, XElement element)

Depende de la implementación de IKeyEscrowSink controlar el elemento proporcionado de forma segura y coherente con la directiva empresarial. Una posible implementación podría ser que el receptor de custodia cifrara el elemento XML usando un certificado X.509 corporativo conocido en el que la clave privada del certificado haya sido custodiada; el tipo CertificateXmlEncryptor puede ayudar con esto. La implementación de IKeyEscrowSink también es responsable de conservar adecuadamente el elemento proporcionado.

De forma predeterminada, no se habilita ningún mecanismo de custodia, aunque los administradores del servidor pueden configurar esto globalmente. También puede configurarse mediante programación a través del método IDataProtectionBuilder.AddKeyEscrowSink, como se muestra en el siguiente ejemplo. Las sobrecargas del método AddKeyEscrowSink reflejan las sobrecargas de IServiceCollection.AddSingleton y IServiceCollection.AddInstance, ya que se pretende que las instancias de IKeyEscrowSink sean singletons. Si se registran varias instancias de IKeyEscrowSink, se llamará a cada una de ellas durante la generación de claves, por lo que se pueden depositar claves en varios mecanismos simultáneamente.

No existe una API para leer material de una instancia de IKeyEscrowSink. Esto es coherente con la teoría de diseño del mecanismo de custodia: está pensado para que el material clave sea accesible para una autoridad de confianza y, dado que la aplicación no es una autoridad de confianza, no debe tener acceso a su propio material permitido.

En el código de ejemplo siguiente se muestra cómo crear y registrar un IKeyEscrowSink donde se permiten las claves, de modo que solo los miembros de "CONTOSODomain Admins" pueden recuperarlas.

Nota:

Para ejecutar este ejemplo, debe estar en una máquina Windows 8 o Windows Server 2012 unida a un dominio y el controlador de dominio debe ser Windows Server 2012 o 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>
 */