Снять защиту с полезных данных, ключи которых были отозваны в ASP.NET Core

ASP.NET Core api-интерфейсы защиты данных в основном не предназначены для неопределенного сохранения конфиденциальных полезных данных. другие технологии, такие как Windows CNG DPAPI и Azure Rights Management , более подходят для сценария неопределенного хранилища и имеют соответствующие возможности управления ключами. с другой стороны, разработчик не запрещает использовать ASP.NET Core api-интерфейсы защиты данных для долгосрочной защиты конфиденциальных данных. Ключи никогда не удаляются из кольца ключей, поэтому IDataProtector.Unprotect всегда могут восстанавливать существующие полезные данные, если ключи доступны и действительны.

Однако проблема возникает, когда разработчик пытается снять защиту с данных, защищенных с помощью отозванного ключа, так как IDataProtector.Unprotect в этом случае будет выдано исключение. Это может быть удобно для кратковременных или временных полезных данных (например, маркеров проверки подлинности), так как эти типы полезных данных могут легко воссоздаться системой, и в худшем случае посетителям сайта может потребоваться снова войти в систему. Но для сохраненных полезных данных использование Unprotect throw может привести к неприемлемой потери данных.

иперсистеддатапротектор

Для поддержки сценария, позволяющего снять защиту полезных данных даже в случае отозванных ключей, система защиты данных содержит IPersistedDataProtector тип. Чтобы получить экземпляр IPersistedDataProtector , просто получите экземпляр IDataProtector обычным образом и попробуйте приведение IDataProtector к IPersistedDataProtector .

Примечание

Не все IDataProtector экземпляры могут быть приведены к IPersistedDataProtector . Разработчики должны использовать оператор C# AS или аналогичный, чтобы избежать исключений среды выполнения, вызванных недопустимыми приведениями, и должны быть готовы к обработке случайного сбоя.

IPersistedDataProtector предоставляет следующую поверхность API:

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

Этот API принимает защищенные полезные данные (в виде массива байтов) и возвращает незащищенные полезные данные. Перегрузка на основе строк отсутствует. Ниже приведены два выходных параметра.

  • requiresMigration: будет иметь значение true, если ключ, используемый для защиты этих полезных данных, больше не является активным ключом по умолчанию, например, ключ, используемый для защиты этой полезной нагрузки, устарел и операция с предыдущим ключом уже выполнена. Вызывающий объект может попытаться повторно защитить полезные данные в зависимости от бизнес-потребностей.

  • wasRevoked: будет иметь значение true, если ключ, используемый для защиты полезной нагрузки, был отозван.

Предупреждение

При передаче ignoreRevocationErrors: true в метод Будьте предельно осторожны DangerousUnprotect . Если после вызова этого метода wasRevoked значение равно true, то ключ, используемый для защиты этих полезных данных, был отозван, а подлинность полезной нагрузки должна рассматриваться как подозрительная. В этом случае продолжайте работать только с незащищенными полезными данными, только если у вас есть отдельная гарантия, которую она подлинна, например, она поступает из защищенной базы данных, а не отправляется недоверенным веб-клиентом.

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