Rimuovere la protezione dei payload le cui chiavi sono state revocate in ASP.NET Core

Le API di protezione dei dati di base di ASP.NET non sono destinate principalmente alla persistenza illimitata dei payload riservati. Altre tecnologie, ad esempio DPAPI CNG di Windows e Azure Rights Management , sono più adatte allo scenario di archiviazione a tempo indeterminato e hanno funzionalità di gestione delle chiavi avanzate corrispondenti. Detto questo, non c'è nulla che impedisca a uno sviluppatore di usare le API di protezione dei dati core ASP.NET per la protezione a lungo termine dei dati riservati. Le chiavi non vengono mai rimosse dall'anello di tasti, quindi IDataProtector.Unprotect è sempre possibile recuperare i payload esistenti purché le chiavi siano disponibili e valide.

Tuttavia, si verifica un problema quando lo sviluppatore tenta di rimuovere la protezione dei dati protetti con una chiave revocata, come IDataProtector.Unprotect genererà un'eccezione in questo caso. Questo potrebbe essere utile per payload temporanei o di breve durata (ad esempio token di autenticazione), poiché questi tipi di payload possono essere facilmente ricreati dal sistema e, nel peggiore dei casi, il visitatore del sito potrebbe essere necessario per accedere di nuovo. Tuttavia, per i payload persistenti, la presenza Unprotect di un'eccezione potrebbe causare una perdita di dati inaccettabile.

IPersistedDataProtector

Per supportare lo scenario di mancata protezione dei payload anche in caso di chiavi revocate, il sistema di protezione dei dati contiene un IPersistedDataProtector tipo. Per ottenere un'istanza di IPersistedDataProtector, è sufficiente ottenere un'istanza di IDataProtector in modo normale e provare a eseguire il cast di IDataProtector in IPersistedDataProtector.

Nota

Non è possibile eseguire il cast di tutte le IDataProtector istanze di a IPersistedDataProtector. Gli sviluppatori devono usare C# come operatore o simili per evitare eccezioni di runtime causate da cast non validi e devono essere preparati a gestire il caso di errore in modo appropriato.

IPersistedDataProtector espone la superficie API seguente:

DangerousUnprotect(byte[] protectedData, bool ignoreRevocationErrors,
     out bool requiresMigration, out bool wasRevoked) : byte[]

Questa API accetta il payload protetto (come matrice di byte) e restituisce il payload non protetto. Non esiste alcun overload basato su stringa. I due parametri out sono i seguenti.

  • requiresMigration: verrà impostato su true se la chiave usata per proteggere questo payload non è più la chiave predefinita attiva, ad esempio la chiave usata per proteggere questo payload è obsoleta e da quando è stata eseguita un'operazione di sequenza delle chiavi. Il chiamante potrebbe voler valutare la riprotezione del payload a seconda delle esigenze aziendali.

  • wasRevoked: verrà impostato su true se la chiave usata per proteggere questo payload è stata revocata.

Avviso

Prestare particolare attenzione quando si ignoreRevocationErrors: true passa al DangerousUnprotect metodo . Se dopo aver chiamato questo metodo il wasRevoked valore è true, la chiave usata per proteggere questo payload è stata revocata e l'autenticità del payload deve essere considerata sospetta. In questo caso, continuare a operare solo sul payload non protetto se si dispone di una garanzia separata che sia autentica, ad esempio che proviene da un database sicuro anziché essere inviato da un client Web non attendibile.

using System;
using System.IO;
using System.Text;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.DataProtection.KeyManagement;
using Microsoft.Extensions.DependencyInjection;

public class Program
{
    public static void Main(string[] args)
    {
        var serviceCollection = new ServiceCollection();
        serviceCollection.AddDataProtection()
            // point at a specific folder and use DPAPI to encrypt keys
            .PersistKeysToFileSystem(new DirectoryInfo(@"c:\temp-keys"))
            .ProtectKeysWithDpapi();
        var services = serviceCollection.BuildServiceProvider();

        // get a protector and perform a protect operation
        var protector = services.GetDataProtector("Sample.DangerousUnprotect");
        Console.Write("Input: ");
        byte[] input = Encoding.UTF8.GetBytes(Console.ReadLine());
        var protectedData = protector.Protect(input);
        Console.WriteLine($"Protected payload: {Convert.ToBase64String(protectedData)}");

        // demonstrate that the payload round-trips properly
        var roundTripped = protector.Unprotect(protectedData);
        Console.WriteLine($"Round-tripped payload: {Encoding.UTF8.GetString(roundTripped)}");

        // get a reference to the key manager and revoke all keys in the key ring
        var keyManager = services.GetService<IKeyManager>();
        Console.WriteLine("Revoking all keys in the key ring...");
        keyManager.RevokeAllKeys(DateTimeOffset.Now, "Sample revocation.");

        // try calling Protect - this should throw
        Console.WriteLine("Calling Unprotect...");
        try
        {
            var unprotectedPayload = protector.Unprotect(protectedData);
            Console.WriteLine($"Unprotected payload: {Encoding.UTF8.GetString(unprotectedPayload)}");
        }
        catch (Exception ex)
        {
            Console.WriteLine($"{ex.GetType().Name}: {ex.Message}");
        }

        // try calling DangerousUnprotect
        Console.WriteLine("Calling DangerousUnprotect...");
        try
        {
            IPersistedDataProtector persistedProtector = protector as IPersistedDataProtector;
            if (persistedProtector == null)
            {
                throw new Exception("Can't call DangerousUnprotect.");
            }

            bool requiresMigration, wasRevoked;
            var unprotectedPayload = persistedProtector.DangerousUnprotect(
                protectedData: protectedData,
                ignoreRevocationErrors: true,
                requiresMigration: out requiresMigration,
                wasRevoked: out wasRevoked);
            Console.WriteLine($"Unprotected payload: {Encoding.UTF8.GetString(unprotectedPayload)}");
            Console.WriteLine($"Requires migration = {requiresMigration}, was revoked = {wasRevoked}");
        }
        catch (Exception ex)
        {
            Console.WriteLine($"{ex.GetType().Name}: {ex.Message}");
        }
    }
}

/*
 * SAMPLE OUTPUT
 *
 * Input: Hello!
 * Protected payload: CfDJ8LHIzUCX1ZVBn2BZ...
 * Round-tripped payload: Hello!
 * Revoking all keys in the key ring...
 * Calling Unprotect...
 * CryptographicException: The key {...} has been revoked.
 * Calling DangerousUnprotect...
 * Unprotected payload: Hello!
 * Requires migration = True, was revoked = True
 */