Merge branch 'release/2.2'
This commit is contained in:
commit
3c558069f4
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"configurations": [
|
||||
{
|
||||
"name": ".NET Core Attach",
|
||||
"type": "coreclr",
|
||||
"request": "attach",
|
||||
"processId": "${command:pickProcess}"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -2,7 +2,6 @@
|
|||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Security.Cryptography;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using System.Security.Cryptography.Xml;
|
||||
|
|
@ -63,8 +62,7 @@ namespace Microsoft.AspNetCore.DataProtection.XmlEncryption
|
|||
var elementToDecrypt = (XmlElement)xmlDocument.DocumentElement.FirstChild;
|
||||
|
||||
// Perform the decryption and update the document in-place.
|
||||
var decryptionCerts = _options?.KeyDecryptionCertificates;
|
||||
var encryptedXml = new EncryptedXmlWithCertificateKeys(decryptionCerts, xmlDocument);
|
||||
var encryptedXml = new EncryptedXmlWithCertificateKeys(_options, xmlDocument);
|
||||
_decryptor.PerformPreDecryptionSetup(encryptedXml);
|
||||
|
||||
encryptedXml.DecryptDocument();
|
||||
|
|
@ -83,48 +81,40 @@ namespace Microsoft.AspNetCore.DataProtection.XmlEncryption
|
|||
/// </summary>
|
||||
private class EncryptedXmlWithCertificateKeys : EncryptedXml
|
||||
{
|
||||
private readonly IReadOnlyDictionary<string, X509Certificate2> _certificates;
|
||||
private readonly XmlKeyDecryptionOptions _options;
|
||||
|
||||
public EncryptedXmlWithCertificateKeys(IReadOnlyDictionary<string, X509Certificate2> certificates, XmlDocument document)
|
||||
public EncryptedXmlWithCertificateKeys(XmlKeyDecryptionOptions options, XmlDocument document)
|
||||
: base(document)
|
||||
{
|
||||
_certificates = certificates;
|
||||
_options = options;
|
||||
}
|
||||
|
||||
public override byte[] DecryptEncryptedKey(EncryptedKey encryptedKey)
|
||||
{
|
||||
byte[] key = base.DecryptEncryptedKey(encryptedKey);
|
||||
if (key != null)
|
||||
if (_options != null && _options.KeyDecryptionCertificateCount > 0)
|
||||
{
|
||||
return key;
|
||||
}
|
||||
|
||||
if (_certificates == null || _certificates.Count == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var keyInfoEnum = encryptedKey.KeyInfo?.GetEnumerator();
|
||||
if (keyInfoEnum == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
while (keyInfoEnum.MoveNext())
|
||||
{
|
||||
if (!(keyInfoEnum.Current is KeyInfoX509Data kiX509Data))
|
||||
var keyInfoEnum = encryptedKey.KeyInfo?.GetEnumerator();
|
||||
if (keyInfoEnum == null)
|
||||
{
|
||||
continue;
|
||||
return null;
|
||||
}
|
||||
|
||||
key = GetKeyFromCert(encryptedKey, kiX509Data);
|
||||
if (key != null)
|
||||
while (keyInfoEnum.MoveNext())
|
||||
{
|
||||
return key;
|
||||
if (!(keyInfoEnum.Current is KeyInfoX509Data kiX509Data))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
byte[] key = GetKeyFromCert(encryptedKey, kiX509Data);
|
||||
if (key != null)
|
||||
{
|
||||
return key;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
return base.DecryptEncryptedKey(encryptedKey);
|
||||
}
|
||||
|
||||
private byte[] GetKeyFromCert(EncryptedKey encryptedKey, KeyInfoX509Data keyInfo)
|
||||
|
|
@ -142,17 +132,25 @@ namespace Microsoft.AspNetCore.DataProtection.XmlEncryption
|
|||
continue;
|
||||
}
|
||||
|
||||
if (!_certificates.TryGetValue(certInfo.Thumbprint, out var certificate))
|
||||
if (!_options.TryGetKeyDecryptionCertificates(certInfo, out var keyDecryptionCerts))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
using (RSA privateKey = certificate.GetRSAPrivateKey())
|
||||
foreach (var keyDecryptionCert in keyDecryptionCerts)
|
||||
{
|
||||
if (privateKey != null)
|
||||
if (!keyDecryptionCert.HasPrivateKey)
|
||||
{
|
||||
var useOAEP = encryptedKey.EncryptionMethod?.KeyAlgorithm == XmlEncRSAOAEPUrl;
|
||||
return DecryptKey(encryptedKey.CipherData.CipherValue, privateKey, useOAEP);
|
||||
continue;
|
||||
}
|
||||
|
||||
using (RSA privateKey = keyDecryptionCert.GetRSAPrivateKey())
|
||||
{
|
||||
if (privateKey != null)
|
||||
{
|
||||
var useOAEP = encryptedKey.EncryptionMethod?.KeyAlgorithm == XmlEncRSAOAEPUrl;
|
||||
return DecryptKey(encryptedKey.CipherData.CipherValue, privateKey, useOAEP);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,16 +12,28 @@ namespace Microsoft.AspNetCore.DataProtection.XmlEncryption
|
|||
/// </summary>
|
||||
internal class XmlKeyDecryptionOptions
|
||||
{
|
||||
private readonly Dictionary<string, X509Certificate2> _certs = new Dictionary<string, X509Certificate2>(StringComparer.Ordinal);
|
||||
private readonly Dictionary<string, List<X509Certificate2>> _certs = new Dictionary<string, List<X509Certificate2>>(StringComparer.Ordinal);
|
||||
|
||||
/// <summary>
|
||||
/// A mapping of key thumbprint to the X509Certificate2
|
||||
/// </summary>
|
||||
public IReadOnlyDictionary<string, X509Certificate2> KeyDecryptionCertificates => _certs;
|
||||
public int KeyDecryptionCertificateCount => _certs.Count;
|
||||
|
||||
public bool TryGetKeyDecryptionCertificates(X509Certificate2 certInfo, out IReadOnlyList<X509Certificate2> keyDecryptionCerts)
|
||||
{
|
||||
var key = GetKey(certInfo);
|
||||
var retVal = _certs.TryGetValue(key, out var keyDecryptionCertsRetVal);
|
||||
keyDecryptionCerts = keyDecryptionCertsRetVal;
|
||||
return retVal;
|
||||
}
|
||||
|
||||
public void AddKeyDecryptionCertificate(X509Certificate2 certificate)
|
||||
{
|
||||
_certs[certificate.Thumbprint] = certificate;
|
||||
var key = GetKey(certificate);
|
||||
if (!_certs.TryGetValue(key, out var certificates))
|
||||
{
|
||||
certificates = _certs[key] = new List<X509Certificate2>();
|
||||
}
|
||||
certificates.Add(certificate);
|
||||
}
|
||||
|
||||
private string GetKey(X509Certificate2 cert) => cert.Thumbprint;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@
|
|||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Security.Cryptography;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
|
|
@ -13,7 +12,6 @@ using Microsoft.AspNetCore.DataProtection.Repositories;
|
|||
using Microsoft.AspNetCore.DataProtection.Test.Shared;
|
||||
using Microsoft.AspNetCore.Testing.xunit;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Moq;
|
||||
|
|
@ -120,10 +118,12 @@ namespace Microsoft.AspNetCore.DataProtection
|
|||
public void System_UsesProvidedDirectoryAndCertificate()
|
||||
{
|
||||
var filePath = Path.Combine(GetTestFilesPath(), "TestCert.pfx");
|
||||
var store = new X509Store(StoreName.My, StoreLocation.CurrentUser);
|
||||
store.Open(OpenFlags.ReadWrite);
|
||||
store.Add(new X509Certificate2(filePath, "password", X509KeyStorageFlags.Exportable));
|
||||
store.Close();
|
||||
using (var store = new X509Store(StoreName.My, StoreLocation.CurrentUser))
|
||||
{
|
||||
store.Open(OpenFlags.ReadWrite);
|
||||
store.Add(new X509Certificate2(filePath, "password", X509KeyStorageFlags.Exportable));
|
||||
store.Close();
|
||||
}
|
||||
|
||||
WithUniqueTempDirectory(directory =>
|
||||
{
|
||||
|
|
@ -139,7 +139,12 @@ namespace Microsoft.AspNetCore.DataProtection
|
|||
|
||||
// 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")));
|
||||
var data = protector.Protect("payload");
|
||||
|
||||
// add a cert without the private key to ensure the decryption will still fallback to the cert store
|
||||
var certWithoutKey = new X509Certificate2(Path.Combine(GetTestFilesPath(), "TestCertWithoutPrivateKey.pfx"), "password");
|
||||
var unprotector = DataProtectionProvider.Create(directory, o => o.UnprotectKeysWithAnyCertificate(certWithoutKey)).CreateProtector("purpose");
|
||||
Assert.Equal("payload", unprotector.Unprotect(data));
|
||||
|
||||
// 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();
|
||||
|
|
@ -157,6 +162,50 @@ namespace Microsoft.AspNetCore.DataProtection
|
|||
});
|
||||
}
|
||||
|
||||
[ConditionalFact]
|
||||
[X509StoreIsAvailable(StoreName.My, StoreLocation.CurrentUser)]
|
||||
public void System_UsesProvidedCertificateNotFromStore()
|
||||
{
|
||||
using (var store = new X509Store(StoreName.My, StoreLocation.CurrentUser))
|
||||
{
|
||||
store.Open(OpenFlags.ReadWrite);
|
||||
var certWithoutKey = new X509Certificate2(Path.Combine(GetTestFilesPath(), "TestCert3WithoutPrivateKey.pfx"), "password3", X509KeyStorageFlags.Exportable);
|
||||
Assert.False(certWithoutKey.HasPrivateKey, "Cert should not have private key");
|
||||
store.Add(certWithoutKey);
|
||||
store.Close();
|
||||
}
|
||||
|
||||
WithUniqueTempDirectory(directory =>
|
||||
{
|
||||
using (var certificateStore = new X509Store(StoreName.My, StoreLocation.CurrentUser))
|
||||
{
|
||||
certificateStore.Open(OpenFlags.ReadWrite);
|
||||
var certInStore = certificateStore.Certificates.Find(X509FindType.FindBySubjectName, "TestCert", false)[0];
|
||||
Assert.NotNull(certInStore);
|
||||
Assert.False(certInStore.HasPrivateKey);
|
||||
|
||||
try
|
||||
{
|
||||
var certWithKey = new X509Certificate2(Path.Combine(GetTestFilesPath(), "TestCert3.pfx"), "password3");
|
||||
|
||||
var protector = DataProtectionProvider.Create(directory, certWithKey).CreateProtector("purpose");
|
||||
var data = protector.Protect("payload");
|
||||
|
||||
var keylessUnprotector = DataProtectionProvider.Create(directory).CreateProtector("purpose");
|
||||
Assert.Throws<CryptographicException>(() => keylessUnprotector.Unprotect(data));
|
||||
|
||||
var unprotector = DataProtectionProvider.Create(directory, o => o.UnprotectKeysWithAnyCertificate(certInStore, certWithKey)).CreateProtector("purpose");
|
||||
Assert.Equal("payload", unprotector.Unprotect(data));
|
||||
}
|
||||
finally
|
||||
{
|
||||
certificateStore.Remove(certInStore);
|
||||
certificateStore.Close();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void System_UsesInMemoryCertificate()
|
||||
{
|
||||
|
|
@ -242,7 +291,7 @@ namespace Microsoft.AspNetCore.DataProtection
|
|||
/// </summary>
|
||||
private static void WithUniqueTempDirectory(Action<DirectoryInfo> testCode)
|
||||
{
|
||||
string uniqueTempPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
|
||||
string uniqueTempPath = Path.Combine(AppContext.BaseDirectory, Path.GetRandomFileName());
|
||||
var dirInfo = new DirectoryInfo(uniqueTempPath);
|
||||
try
|
||||
{
|
||||
|
|
|
|||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Loading…
Reference in New Issue