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
: Nastaví se natrue
hodnotu, 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
: Je nastavena natrue
hodnotu, pokud byl klíč použitý k ochraně této datové části odvolán.
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 určitou 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
*/