取消保护已吊销在 ASP.NET Core中键的有效负载Unprotect payloads whose keys have been revoked in ASP.NET Core

本文内容

ASP.NET Core 数据保护 Api 主要不用于机密负载的无限期持久性。其他技术(如WINDOWS CNG DPAPIAzure Rights Management )更适用于无限存储的情况,并且具有相应的强大密钥管理功能。也就是说,无需进行任何开发人员禁止使用 ASP.NET Core 数据保护 Api 进行长期保护的机密数据。密钥永远不会从密钥环中删除,因此,只要密钥可用且有效,IDataProtector.Unprotect 就可以始终恢复现有有效负载。

但是,当开发人员尝试取消保护已被吊销密钥保护的数据时,会出现问题,因为在这种情况下 IDataProtector.Unprotect 会引发异常。这对于短期或暂时性负载(例如身份验证令牌)可能很合适,因为系统可以轻松地重新创建这种类型的负载,并且在最糟糕的情况下,站点访问者可能需要再次登录。但对于持久化有效负载,具有 Unprotect 引发可能导致数据丢失不可接受。

IPersistedDataProtectorIPersistedDataProtector

为支持允许负载不受保护的方案(即使在面对吊销密钥的情况下),数据保护系统包含一个 IPersistedDataProtector 类型。若要获取 IPersistedDataProtector的实例,只需以正常方式获取 IDataProtector 的实例,然后尝试将 IDataProtector 强制转换为 IPersistedDataProtector

备注

并非所有 IDataProtector 实例都可以转换为 IPersistedDataProtector开发人员应使用C# as 运算符或类似的方法来避免由无效强制转换导致的运行时异常,并应准备适当地处理故障情况。

IPersistedDataProtector 公开以下 API 图面:

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

此 API 获取受保护的负载(作为字节数组)并返回未受保护的负载。没有基于字符串的重载。这两个 out 参数如下所示。

  • requiresMigration:如果用于保护此有效负载的密钥不再是活动的默认密钥,则将设置为 true,例如,用于保护此有效负载的密钥是旧的,并已发生密钥滚动操作。调用方可能需要考虑根据其业务需求重新保护负载。

  • wasRevoked:如果用于保护此负载的密钥已被吊销,则设置为 true。

警告

ignoreRevocationErrors: true 传递到 DangerousUnprotect 方法时要格外小心。如果在调用此方法后,wasRevoked 值为 true,则将吊销用于保护此负载的密钥,并且应将有效负载的真实性视为可疑。在这种情况下,如果有一些单独的保证是可信的,例如它来自安全的数据库,而不是由不受信任的 web 客户端发送,则只能继续对未受保护的有效负载进行操作。

  1. using System;
  2. using System.IO;
  3. using System.Text;
  4. using Microsoft.AspNetCore.DataProtection;
  5. using Microsoft.AspNetCore.DataProtection.KeyManagement;
  6. using Microsoft.Extensions.DependencyInjection;
  7. public class Program
  8. {
  9. public static void Main(string[] args)
  10. {
  11. var serviceCollection = new ServiceCollection();
  12. serviceCollection.AddDataProtection()
  13. // point at a specific folder and use DPAPI to encrypt keys
  14. .PersistKeysToFileSystem(new DirectoryInfo(@"c:\temp-keys"))
  15. .ProtectKeysWithDpapi();
  16. var services = serviceCollection.BuildServiceProvider();
  17. // get a protector and perform a protect operation
  18. var protector = services.GetDataProtector("Sample.DangerousUnprotect");
  19. Console.Write("Input: ");
  20. byte[] input = Encoding.UTF8.GetBytes(Console.ReadLine());
  21. var protectedData = protector.Protect(input);
  22. Console.WriteLine($"Protected payload: {Convert.ToBase64String(protectedData)}");
  23. // demonstrate that the payload round-trips properly
  24. var roundTripped = protector.Unprotect(protectedData);
  25. Console.WriteLine($"Round-tripped payload: {Encoding.UTF8.GetString(roundTripped)}");
  26. // get a reference to the key manager and revoke all keys in the key ring
  27. var keyManager = services.GetService<IKeyManager>();
  28. Console.WriteLine("Revoking all keys in the key ring...");
  29. keyManager.RevokeAllKeys(DateTimeOffset.Now, "Sample revocation.");
  30. // try calling Protect - this should throw
  31. Console.WriteLine("Calling Unprotect...");
  32. try
  33. {
  34. var unprotectedPayload = protector.Unprotect(protectedData);
  35. Console.WriteLine($"Unprotected payload: {Encoding.UTF8.GetString(unprotectedPayload)}");
  36. }
  37. catch (Exception ex)
  38. {
  39. Console.WriteLine($"{ex.GetType().Name}: {ex.Message}");
  40. }
  41. // try calling DangerousUnprotect
  42. Console.WriteLine("Calling DangerousUnprotect...");
  43. try
  44. {
  45. IPersistedDataProtector persistedProtector = protector as IPersistedDataProtector;
  46. if (persistedProtector == null)
  47. {
  48. throw new Exception("Can't call DangerousUnprotect.");
  49. }
  50. bool requiresMigration, wasRevoked;
  51. var unprotectedPayload = persistedProtector.DangerousUnprotect(
  52. protectedData: protectedData,
  53. ignoreRevocationErrors: true,
  54. requiresMigration: out requiresMigration,
  55. wasRevoked: out wasRevoked);
  56. Console.WriteLine($"Unprotected payload: {Encoding.UTF8.GetString(unprotectedPayload)}");
  57. Console.WriteLine($"Requires migration = {requiresMigration}, was revoked = {wasRevoked}");
  58. }
  59. catch (Exception ex)
  60. {
  61. Console.WriteLine($"{ex.GetType().Name}: {ex.Message}");
  62. }
  63. }
  64. }
  65. /*
  66. * SAMPLE OUTPUT
  67. *
  68. * Input: Hello!
  69. * Protected payload: CfDJ8LHIzUCX1ZVBn2BZ...
  70. * Round-tripped payload: Hello!
  71. * Revoking all keys in the key ring...
  72. * Calling Unprotect...
  73. * CryptographicException: The key {...} has been revoked.
  74. * Calling DangerousUnprotect...
  75. * Unprotected payload: Hello!
  76. * Requires migration = True, was revoked = True
  77. */