Merge branch 'release/2.2'

This commit is contained in:
Nate McMaster 2018-07-05 11:33:09 -07:00
commit 3c558069f4
No known key found for this signature in database
GPG Key ID: A778D9601BD78810
7 changed files with 118 additions and 49 deletions

10
.vscode/launch.json vendored Normal file
View File

@ -0,0 +1,10 @@
{
"configurations": [
{
"name": ".NET Core Attach",
"type": "coreclr",
"request": "attach",
"processId": "${command:pickProcess}"
}
]
}

View File

@ -2,7 +2,6 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System; using System;
using System.Collections.Generic;
using System.Security.Cryptography; using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates; using System.Security.Cryptography.X509Certificates;
using System.Security.Cryptography.Xml; using System.Security.Cryptography.Xml;
@ -63,8 +62,7 @@ namespace Microsoft.AspNetCore.DataProtection.XmlEncryption
var elementToDecrypt = (XmlElement)xmlDocument.DocumentElement.FirstChild; var elementToDecrypt = (XmlElement)xmlDocument.DocumentElement.FirstChild;
// Perform the decryption and update the document in-place. // Perform the decryption and update the document in-place.
var decryptionCerts = _options?.KeyDecryptionCertificates; var encryptedXml = new EncryptedXmlWithCertificateKeys(_options, xmlDocument);
var encryptedXml = new EncryptedXmlWithCertificateKeys(decryptionCerts, xmlDocument);
_decryptor.PerformPreDecryptionSetup(encryptedXml); _decryptor.PerformPreDecryptionSetup(encryptedXml);
encryptedXml.DecryptDocument(); encryptedXml.DecryptDocument();
@ -83,48 +81,40 @@ namespace Microsoft.AspNetCore.DataProtection.XmlEncryption
/// </summary> /// </summary>
private class EncryptedXmlWithCertificateKeys : EncryptedXml 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) : base(document)
{ {
_certificates = certificates; _options = options;
} }
public override byte[] DecryptEncryptedKey(EncryptedKey encryptedKey) public override byte[] DecryptEncryptedKey(EncryptedKey encryptedKey)
{ {
byte[] key = base.DecryptEncryptedKey(encryptedKey); if (_options != null && _options.KeyDecryptionCertificateCount > 0)
if (key != null)
{ {
return key; var keyInfoEnum = encryptedKey.KeyInfo?.GetEnumerator();
} if (keyInfoEnum == null)
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))
{ {
continue; return null;
} }
key = GetKeyFromCert(encryptedKey, kiX509Data); while (keyInfoEnum.MoveNext())
if (key != null)
{ {
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) private byte[] GetKeyFromCert(EncryptedKey encryptedKey, KeyInfoX509Data keyInfo)
@ -142,17 +132,25 @@ namespace Microsoft.AspNetCore.DataProtection.XmlEncryption
continue; continue;
} }
if (!_certificates.TryGetValue(certInfo.Thumbprint, out var certificate)) if (!_options.TryGetKeyDecryptionCertificates(certInfo, out var keyDecryptionCerts))
{ {
continue; continue;
} }
using (RSA privateKey = certificate.GetRSAPrivateKey()) foreach (var keyDecryptionCert in keyDecryptionCerts)
{ {
if (privateKey != null) if (!keyDecryptionCert.HasPrivateKey)
{ {
var useOAEP = encryptedKey.EncryptionMethod?.KeyAlgorithm == XmlEncRSAOAEPUrl; continue;
return DecryptKey(encryptedKey.CipherData.CipherValue, privateKey, useOAEP); }
using (RSA privateKey = keyDecryptionCert.GetRSAPrivateKey())
{
if (privateKey != null)
{
var useOAEP = encryptedKey.EncryptionMethod?.KeyAlgorithm == XmlEncRSAOAEPUrl;
return DecryptKey(encryptedKey.CipherData.CipherValue, privateKey, useOAEP);
}
} }
} }
} }

View File

@ -12,16 +12,28 @@ namespace Microsoft.AspNetCore.DataProtection.XmlEncryption
/// </summary> /// </summary>
internal class XmlKeyDecryptionOptions 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> public int KeyDecryptionCertificateCount => _certs.Count;
/// A mapping of key thumbprint to the X509Certificate2
/// </summary> public bool TryGetKeyDecryptionCertificates(X509Certificate2 certInfo, out IReadOnlyList<X509Certificate2> keyDecryptionCerts)
public IReadOnlyDictionary<string, X509Certificate2> KeyDecryptionCertificates => _certs; {
var key = GetKey(certInfo);
var retVal = _certs.TryGetValue(key, out var keyDecryptionCertsRetVal);
keyDecryptionCerts = keyDecryptionCertsRetVal;
return retVal;
}
public void AddKeyDecryptionCertificate(X509Certificate2 certificate) 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;
} }
} }

View File

@ -3,7 +3,6 @@
using System; using System;
using System.IO; using System.IO;
using System.Reflection;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Security.Cryptography; using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates; using System.Security.Cryptography.X509Certificates;
@ -13,7 +12,6 @@ using Microsoft.AspNetCore.DataProtection.Repositories;
using Microsoft.AspNetCore.DataProtection.Test.Shared; using Microsoft.AspNetCore.DataProtection.Test.Shared;
using Microsoft.AspNetCore.Testing.xunit; using Microsoft.AspNetCore.Testing.xunit;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Moq; using Moq;
@ -120,10 +118,12 @@ namespace Microsoft.AspNetCore.DataProtection
public void System_UsesProvidedDirectoryAndCertificate() public void System_UsesProvidedDirectoryAndCertificate()
{ {
var filePath = Path.Combine(GetTestFilesPath(), "TestCert.pfx"); var filePath = Path.Combine(GetTestFilesPath(), "TestCert.pfx");
var store = new X509Store(StoreName.My, StoreLocation.CurrentUser); using (var store = new X509Store(StoreName.My, StoreLocation.CurrentUser))
store.Open(OpenFlags.ReadWrite); {
store.Add(new X509Certificate2(filePath, "password", X509KeyStorageFlags.Exportable)); store.Open(OpenFlags.ReadWrite);
store.Close(); store.Add(new X509Certificate2(filePath, "password", X509KeyStorageFlags.Exportable));
store.Close();
}
WithUniqueTempDirectory(directory => WithUniqueTempDirectory(directory =>
{ {
@ -139,7 +139,12 @@ namespace Microsoft.AspNetCore.DataProtection
// Step 2: instantiate the system and round-trip a payload // Step 2: instantiate the system and round-trip a payload
var protector = DataProtectionProvider.Create(directory, certificate).CreateProtector("purpose"); 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 // 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(); 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] [Fact]
public void System_UsesInMemoryCertificate() public void System_UsesInMemoryCertificate()
{ {
@ -242,7 +291,7 @@ namespace Microsoft.AspNetCore.DataProtection
/// </summary> /// </summary>
private static void WithUniqueTempDirectory(Action<DirectoryInfo> testCode) 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); var dirInfo = new DirectoryInfo(uniqueTempPath);
try try
{ {