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 IPersistedDataProtector
klasy , 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 natrue
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 natrue
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
*/