Sdílet prostřednictvím


Zrušení ochrany datových částí, jejichž klíče byly odvolány v ASP.NET Core

Rozhraní API ASP.NET základní ochrany dat nejsou primárně určená pro neomezenou trvalost důvěrných datových částí. Další technologie, jako je Windows CNG DPAPI a Azure Rights Management , jsou vhodnější pro scénář neomezeného úložiště a mají odpovídajícím způsobem silné možnosti správy klíčů. To znamená, že vývojáři nezakázali používat rozhraní API ochrany dat ASP.NET Core pro dlouhodobou ochranu důvěrných dat. Klíče se nikdy neodeberou z okruhu klíčů, takže IDataProtector.Unprotect je možné vždy obnovit stávající datové části, pokud jsou klíče dostupné a platné.

K problému však dojde, když se vývojář pokusí odemknout data chráněná pomocí odvolaného klíče, protože IDataProtector.Unprotect v tomto případě vyvolá výjimku. To může být v pořádku u krátkodobých nebo přechodných datových částí (jako jsou ověřovací tokeny), protože tyto druhy datových částí může systém snadno znovu vytvořit a v nejhorším případě se návštěvník webu může muset znovu přihlásit. Ale u trvalých datových částí by mohlo Unprotect dojít k nepřijatelné ztrátě dat.

IPersistedDataProtector

Pro podporu scénáře povolení nechráněných datových částí i v případě odvolaných klíčů systém ochrany dat obsahuje typ IPersistedDataProtector . Chcete-li získat instanci IPersistedDataProtector, jednoduše získat instanci IDataProtector v normálním způsobem a zkuste přetypovat IDataProtector na IPersistedDataProtector.

Poznámka

Ne všechny IDataProtector instance lze přetypovat na IPersistedDataProtector. Vývojáři by měli jako operátor jazyka C# použít operátor nebo podobný, aby se zabránilo výjimkám za běhu způsobeným neplatným přetypováním a měli by být připraveni odpovídajícím způsobem zpracovat případ selhání.

IPersistedDataProtector zveřejňuje následující plochu rozhraní API:

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

Toto rozhraní API převezme chráněnou datovou část (jako pole bajtů) a vrátí nechráněnou datovou část. Neexistuje žádné přetížení založené na řetězci. Dva parametry jsou následující.

  • requiresMigration: Bude nastavena na hodnotu True, pokud klíč použitý k ochraně této datové části už není aktivním výchozím klíčem, například klíč použitý k ochraně této datové části je starý a od té doby došlo k operaci postupného provozu klíče. Volající může zvážit opětovné nastavení ochrany datové části v závislosti na jejich obchodních potřebách.

  • wasRevoked: Pokud byl klíč použitý k ochraně této datové části odvolán, nastaví se na hodnotu true.

Upozorňující

Při předávání ignoreRevocationErrors: true do DangerousUnprotect metody buďte velmi opatrní. Pokud po volání této metody wasRevoked je hodnota true, klíč použitý k ochraně této datové části byl odvolán a pravost datové části by měla být považována za podezřelou. V takovém případě pokračujte pouze v provozu na nechráněné datové části, pokud máte samostatnou jistotu, že je autentická, například že pochází ze zabezpečené databáze, a ne odesílají nedůvěryhodný webový klient.

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