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

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

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

IPersistedDataProtector

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

Примечание

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

IPersistedDataProtector предлагает следующий API:

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

Этот API принимает защищенные данные (в виде массива байтов) и возвращает незащищенные данные. У него нет переопределенного варианта для типа string. Вот два его параметра:

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

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

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
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
 */
Поделись хорошей новостью с друзьями!
Следи за новостями!