Udostępnij za pośrednictwem


Cofnij ochronę ładunków, których klucze zostały odwołane w programie ASP.NET Core

Podstawowe interfejsy API ochrony danych ASP.NET nie są przeznaczone przede wszystkim do trwałego przechowywania poufnych ładunków. Inne technologie, takie jak Windows CNG DPAPI i Azure Rights Management , są bardziej dostosowane do scenariusza magazynu bezterminowego i mają odpowiednio silne możliwości zarządzania kluczami. Oznacza to, że deweloper nie może używać interfejsów API ochrony danych ASP.NET Core w celu długoterminowej ochrony poufnych danych. Klucze nigdy nie są usuwane z pierścienia kluczy, więc IDataProtector.Unprotect zawsze można odzyskać istniejące ładunki, o ile klucze są dostępne i prawidłowe.

Jednak pojawia się problem, gdy deweloper próbuje wyłączyć ochronę danych chronionych przy użyciu odwołanego klucza, co IDataProtector.Unprotect spowoduje zgłoszenie wyjątku w tym przypadku. Może to być możliwe w przypadku krótkotrwałych lub przejściowych ładunków (takich jak tokeny uwierzytelniania), ponieważ tego rodzaju ładunki można łatwo odtworzyć przez system, a w najgorszym przypadku użytkownik witryny może wymagać ponownego zalogowania się. Jednak w przypadku utrwalonego ładunku Unprotect zgłoszenie może prowadzić do niedopuszczalnej utraty danych.

IPersistedDataProtector

Aby zapewnić obsługę scenariusza zezwalania na niechronienie ładunków nawet w przypadku odwołanych kluczy, system ochrony danych zawiera IPersistedDataProtector typ. Aby uzyskać wystąpienie IPersistedDataProtectorklasy , po prostu uzyskaj wystąpienie IDataProtector klasy w normalny sposób i spróbuj odlecieć element IDataProtector do IPersistedDataProtector.

Uwaga

Nie wszystkie IDataProtector wystąpienia można rzutować na IPersistedDataProtector. Deweloperzy powinni używać języka C# jako operatora lub podobnego, aby uniknąć wyjątków środowiska uruchomieniowego spowodowanych nieprawidłowymi rzutami i powinni być przygotowani do odpowiedniego obsługi przypadku awarii.

IPersistedDataProtector Uwidacznia następującą powierzchnię interfejsu API:

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

Ten interfejs API pobiera chroniony ładunek (jako tablicę bajtów) i zwraca niechroniony ładunek. Nie ma przeciążenia opartego na ciągach. Dwa parametry out są następujące.

  • requiresMigration: jest ustawiona na true wartość , jeśli klucz używany do ochrony tego ładunku nie jest już aktywnym kluczem domyślnym. Na przykład klucz używany do ochrony tego ładunku jest stary, a od tego czasu wykonywana jest operacja krocząca klucza. Obiekt wywołujący może chcieć rozważyć ponowne włączenie ochrony ładunku w zależności od potrzeb biznesowych.

  • wasRevoked: jest ustawiona na true wartość , jeśli klucz używany do ochrony tego ładunku został odwołany.

Ostrzeżenie

Podczas przekazywania ignoreRevocationErrors: true metody do metody należy zachować szczególną DangerousUnprotect ostrożność. Jeśli po wywołaniu tej metody wasRevoked wartość ma wartość true, klucz używany do ochrony tego ładunku został odwołany, a autentyczność ładunku powinna być traktowana jako podejrzany. W takim przypadku kontynuuj działanie tylko na niechronionym ładunku, jeśli masz oddzielne pewność, że jest on autentyczny, na przykład że pochodzi z bezpiecznej bazy danych, a nie jest wysyłany przez niezaufanego klienta internetowego.

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
 */