diff --git a/src/Microsoft.AspNetCore.DataProtection/DataProtectionBuilderExtensions.cs b/src/Microsoft.AspNetCore.DataProtection/DataProtectionBuilderExtensions.cs
index f37dab4331..7789ca074f 100644
--- a/src/Microsoft.AspNetCore.DataProtection/DataProtectionBuilderExtensions.cs
+++ b/src/Microsoft.AspNetCore.DataProtection/DataProtectionBuilderExtensions.cs
@@ -320,6 +320,33 @@ namespace Microsoft.AspNetCore.DataProtection
return builder;
}
+ ///
+ /// Configures certificates which can be used to decrypt keys loaded from storage.
+ ///
+ /// The .
+ /// Certificates that can be used to decrypt key data.
+ /// A reference to the after this operation has completed.
+ public static IDataProtectionBuilder UnprotectKeysWithAnyCertificate(this IDataProtectionBuilder builder, params X509Certificate2[] certificates)
+ {
+ if (builder == null)
+ {
+ throw new ArgumentNullException(nameof(builder));
+ }
+
+ builder.Services.Configure(o =>
+ {
+ if (certificates != null)
+ {
+ foreach (var certificate in certificates)
+ {
+ o.AddKeyDecryptionCertificate(certificate);
+ }
+ }
+ });
+
+ return builder;
+ }
+
///
/// Configures keys to be encrypted with Windows DPAPI before being persisted to
/// storage. The encrypted key will only be decryptable by the current Windows user account.
diff --git a/test/Microsoft.AspNetCore.DataProtection.Extensions.Test/DataProtectionProviderTests.cs b/test/Microsoft.AspNetCore.DataProtection.Extensions.Test/DataProtectionProviderTests.cs
index ad3dbb3a27..63931f2f8f 100644
--- a/test/Microsoft.AspNetCore.DataProtection.Extensions.Test/DataProtectionProviderTests.cs
+++ b/test/Microsoft.AspNetCore.DataProtection.Extensions.Test/DataProtectionProviderTests.cs
@@ -171,16 +171,16 @@ namespace Microsoft.AspNetCore.DataProtection
WithUniqueTempDirectory(directory =>
{
- // Step 1: directory should be completely empty
- directory.Create();
+ // Step 1: directory should be completely empty
+ directory.Create();
Assert.Empty(directory.GetFiles());
- // Step 2: instantiate the system and round-trip a payload
- var protector = DataProtectionProvider.Create(directory, certificate).CreateProtector("purpose");
+ // Step 2: instantiate the system and round-trip a payload
+ var protector = DataProtectionProvider.Create(directory, certificate).CreateProtector("purpose");
Assert.Equal("payload", protector.Unprotect(protector.Protect("payload")));
- // Step 3: validate that there's now a single key in the directory and that it's is protected using the certificate
- var allFiles = directory.GetFiles();
+ // Step 3: validate that there's now a single key in the directory and that it's is protected using the certificate
+ var allFiles = directory.GetFiles();
Assert.Single(allFiles);
Assert.StartsWith("key-", allFiles[0].Name, StringComparison.OrdinalIgnoreCase);
string fileText = File.ReadAllText(allFiles[0].FullName);
@@ -189,6 +189,40 @@ namespace Microsoft.AspNetCore.DataProtection
});
}
+ [Fact]
+ public void System_CanUnprotectWithCert()
+ {
+ var filePath = Path.Combine(GetTestFilesPath(), "TestCert2.pfx");
+ var certificate = new X509Certificate2(filePath, "password");
+
+ WithUniqueTempDirectory(directory =>
+ {
+ // Step 1: directory should be completely empty
+ directory.Create();
+ Assert.Empty(directory.GetFiles());
+
+ // Step 2: instantiate the system and create some data
+ var protector = DataProtectionProvider
+ .Create(directory, certificate)
+ .CreateProtector("purpose");
+
+ var data = protector.Protect("payload");
+
+ // Step 3: validate that there's now a single key in the directory and that it's is protected using the certificate
+ var allFiles = directory.GetFiles();
+ Assert.Single(allFiles);
+ Assert.StartsWith("key-", allFiles[0].Name, StringComparison.OrdinalIgnoreCase);
+ string fileText = File.ReadAllText(allFiles[0].FullName);
+ Assert.DoesNotContain("Warning: the key below is in an unencrypted form.", fileText, StringComparison.Ordinal);
+ Assert.Contains("X509Certificate", fileText, StringComparison.Ordinal);
+
+ // Step 4: setup a second system and validate it can decrypt keys and unprotect data
+ var unprotector = DataProtectionProvider.Create(directory,
+ b => b.UnprotectKeysWithAnyCertificate(certificate));
+ Assert.Equal("payload", unprotector.CreateProtector("purpose").Unprotect(data));
+ });
+ }
+
///
/// Runs a test and cleans up the temp directory afterward.
///