Skip to content
This repository was archived by the owner on Oct 17, 2018. It is now read-only.

Commit 3c55806

Browse files
author
Nate McMaster
committed
Merge branch 'release/2.2'
2 parents 1962967 + 2af1365 commit 3c55806

File tree

7 files changed

+118
-49
lines changed

7 files changed

+118
-49
lines changed

.vscode/launch.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"configurations": [
3+
{
4+
"name": ".NET Core Attach",
5+
"type": "coreclr",
6+
"request": "attach",
7+
"processId": "${command:pickProcess}"
8+
}
9+
]
10+
}

src/Microsoft.AspNetCore.DataProtection/XmlEncryption/EncryptedXmlDecryptor.cs

Lines changed: 33 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

44
using System;
5-
using System.Collections.Generic;
65
using System.Security.Cryptography;
76
using System.Security.Cryptography.X509Certificates;
87
using System.Security.Cryptography.Xml;
@@ -63,8 +62,7 @@ public XElement Decrypt(XElement encryptedElement)
6362
var elementToDecrypt = (XmlElement)xmlDocument.DocumentElement.FirstChild;
6463

6564
// Perform the decryption and update the document in-place.
66-
var decryptionCerts = _options?.KeyDecryptionCertificates;
67-
var encryptedXml = new EncryptedXmlWithCertificateKeys(decryptionCerts, xmlDocument);
65+
var encryptedXml = new EncryptedXmlWithCertificateKeys(_options, xmlDocument);
6866
_decryptor.PerformPreDecryptionSetup(encryptedXml);
6967

7068
encryptedXml.DecryptDocument();
@@ -83,48 +81,40 @@ void IInternalEncryptedXmlDecryptor.PerformPreDecryptionSetup(EncryptedXml encry
8381
/// </summary>
8482
private class EncryptedXmlWithCertificateKeys : EncryptedXml
8583
{
86-
private readonly IReadOnlyDictionary<string, X509Certificate2> _certificates;
84+
private readonly XmlKeyDecryptionOptions _options;
8785

88-
public EncryptedXmlWithCertificateKeys(IReadOnlyDictionary<string, X509Certificate2> certificates, XmlDocument document)
86+
public EncryptedXmlWithCertificateKeys(XmlKeyDecryptionOptions options, XmlDocument document)
8987
: base(document)
9088
{
91-
_certificates = certificates;
89+
_options = options;
9290
}
9391

9492
public override byte[] DecryptEncryptedKey(EncryptedKey encryptedKey)
9593
{
96-
byte[] key = base.DecryptEncryptedKey(encryptedKey);
97-
if (key != null)
94+
if (_options != null && _options.KeyDecryptionCertificateCount > 0)
9895
{
99-
return key;
100-
}
101-
102-
if (_certificates == null || _certificates.Count == 0)
103-
{
104-
return null;
105-
}
106-
107-
var keyInfoEnum = encryptedKey.KeyInfo?.GetEnumerator();
108-
if (keyInfoEnum == null)
109-
{
110-
return null;
111-
}
112-
113-
while (keyInfoEnum.MoveNext())
114-
{
115-
if (!(keyInfoEnum.Current is KeyInfoX509Data kiX509Data))
96+
var keyInfoEnum = encryptedKey.KeyInfo?.GetEnumerator();
97+
if (keyInfoEnum == null)
11698
{
117-
continue;
99+
return null;
118100
}
119101

120-
key = GetKeyFromCert(encryptedKey, kiX509Data);
121-
if (key != null)
102+
while (keyInfoEnum.MoveNext())
122103
{
123-
return key;
104+
if (!(keyInfoEnum.Current is KeyInfoX509Data kiX509Data))
105+
{
106+
continue;
107+
}
108+
109+
byte[] key = GetKeyFromCert(encryptedKey, kiX509Data);
110+
if (key != null)
111+
{
112+
return key;
113+
}
124114
}
125115
}
126116

127-
return null;
117+
return base.DecryptEncryptedKey(encryptedKey);
128118
}
129119

130120
private byte[] GetKeyFromCert(EncryptedKey encryptedKey, KeyInfoX509Data keyInfo)
@@ -142,17 +132,25 @@ private byte[] GetKeyFromCert(EncryptedKey encryptedKey, KeyInfoX509Data keyInfo
142132
continue;
143133
}
144134

145-
if (!_certificates.TryGetValue(certInfo.Thumbprint, out var certificate))
135+
if (!_options.TryGetKeyDecryptionCertificates(certInfo, out var keyDecryptionCerts))
146136
{
147137
continue;
148138
}
149139

150-
using (RSA privateKey = certificate.GetRSAPrivateKey())
140+
foreach (var keyDecryptionCert in keyDecryptionCerts)
151141
{
152-
if (privateKey != null)
142+
if (!keyDecryptionCert.HasPrivateKey)
143+
{
144+
continue;
145+
}
146+
147+
using (RSA privateKey = keyDecryptionCert.GetRSAPrivateKey())
153148
{
154-
var useOAEP = encryptedKey.EncryptionMethod?.KeyAlgorithm == XmlEncRSAOAEPUrl;
155-
return DecryptKey(encryptedKey.CipherData.CipherValue, privateKey, useOAEP);
149+
if (privateKey != null)
150+
{
151+
var useOAEP = encryptedKey.EncryptionMethod?.KeyAlgorithm == XmlEncRSAOAEPUrl;
152+
return DecryptKey(encryptedKey.CipherData.CipherValue, privateKey, useOAEP);
153+
}
156154
}
157155
}
158156
}

src/Microsoft.AspNetCore.DataProtection/XmlEncryption/XmlKeyDecryptionOptions.cs

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,28 @@ namespace Microsoft.AspNetCore.DataProtection.XmlEncryption
1212
/// </summary>
1313
internal class XmlKeyDecryptionOptions
1414
{
15-
private readonly Dictionary<string, X509Certificate2> _certs = new Dictionary<string, X509Certificate2>(StringComparer.Ordinal);
15+
private readonly Dictionary<string, List<X509Certificate2>> _certs = new Dictionary<string, List<X509Certificate2>>(StringComparer.Ordinal);
1616

17-
/// <summary>
18-
/// A mapping of key thumbprint to the X509Certificate2
19-
/// </summary>
20-
public IReadOnlyDictionary<string, X509Certificate2> KeyDecryptionCertificates => _certs;
17+
public int KeyDecryptionCertificateCount => _certs.Count;
18+
19+
public bool TryGetKeyDecryptionCertificates(X509Certificate2 certInfo, out IReadOnlyList<X509Certificate2> keyDecryptionCerts)
20+
{
21+
var key = GetKey(certInfo);
22+
var retVal = _certs.TryGetValue(key, out var keyDecryptionCertsRetVal);
23+
keyDecryptionCerts = keyDecryptionCertsRetVal;
24+
return retVal;
25+
}
2126

2227
public void AddKeyDecryptionCertificate(X509Certificate2 certificate)
2328
{
24-
_certs[certificate.Thumbprint] = certificate;
29+
var key = GetKey(certificate);
30+
if (!_certs.TryGetValue(key, out var certificates))
31+
{
32+
certificates = _certs[key] = new List<X509Certificate2>();
33+
}
34+
certificates.Add(certificate);
2535
}
36+
37+
private string GetKey(X509Certificate2 cert) => cert.Thumbprint;
2638
}
2739
}

test/Microsoft.AspNetCore.DataProtection.Extensions.Test/DataProtectionProviderTests.cs

Lines changed: 57 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33

44
using System;
55
using System.IO;
6-
using System.Reflection;
76
using System.Runtime.InteropServices;
87
using System.Security.Cryptography;
98
using System.Security.Cryptography.X509Certificates;
@@ -13,7 +12,6 @@
1312
using Microsoft.AspNetCore.DataProtection.Test.Shared;
1413
using Microsoft.AspNetCore.Testing.xunit;
1514
using Microsoft.Extensions.DependencyInjection;
16-
using Microsoft.Extensions.Logging;
1715
using Microsoft.Extensions.Logging.Abstractions;
1816
using Microsoft.Extensions.Options;
1917
using Moq;
@@ -120,10 +118,12 @@ public void System_UsesProvidedDirectory_WithConfigurationCallback()
120118
public void System_UsesProvidedDirectoryAndCertificate()
121119
{
122120
var filePath = Path.Combine(GetTestFilesPath(), "TestCert.pfx");
123-
var store = new X509Store(StoreName.My, StoreLocation.CurrentUser);
124-
store.Open(OpenFlags.ReadWrite);
125-
store.Add(new X509Certificate2(filePath, "password", X509KeyStorageFlags.Exportable));
126-
store.Close();
121+
using (var store = new X509Store(StoreName.My, StoreLocation.CurrentUser))
122+
{
123+
store.Open(OpenFlags.ReadWrite);
124+
store.Add(new X509Certificate2(filePath, "password", X509KeyStorageFlags.Exportable));
125+
store.Close();
126+
}
127127

128128
WithUniqueTempDirectory(directory =>
129129
{
@@ -139,7 +139,12 @@ public void System_UsesProvidedDirectoryAndCertificate()
139139

140140
// Step 2: instantiate the system and round-trip a payload
141141
var protector = DataProtectionProvider.Create(directory, certificate).CreateProtector("purpose");
142-
Assert.Equal("payload", protector.Unprotect(protector.Protect("payload")));
142+
var data = protector.Protect("payload");
143+
144+
// add a cert without the private key to ensure the decryption will still fallback to the cert store
145+
var certWithoutKey = new X509Certificate2(Path.Combine(GetTestFilesPath(), "TestCertWithoutPrivateKey.pfx"), "password");
146+
var unprotector = DataProtectionProvider.Create(directory, o => o.UnprotectKeysWithAnyCertificate(certWithoutKey)).CreateProtector("purpose");
147+
Assert.Equal("payload", unprotector.Unprotect(data));
143148

144149
// Step 3: validate that there's now a single key in the directory and that it's is protected using the certificate
145150
var allFiles = directory.GetFiles();
@@ -157,6 +162,50 @@ public void System_UsesProvidedDirectoryAndCertificate()
157162
});
158163
}
159164

165+
[ConditionalFact]
166+
[X509StoreIsAvailable(StoreName.My, StoreLocation.CurrentUser)]
167+
public void System_UsesProvidedCertificateNotFromStore()
168+
{
169+
using (var store = new X509Store(StoreName.My, StoreLocation.CurrentUser))
170+
{
171+
store.Open(OpenFlags.ReadWrite);
172+
var certWithoutKey = new X509Certificate2(Path.Combine(GetTestFilesPath(), "TestCert3WithoutPrivateKey.pfx"), "password3", X509KeyStorageFlags.Exportable);
173+
Assert.False(certWithoutKey.HasPrivateKey, "Cert should not have private key");
174+
store.Add(certWithoutKey);
175+
store.Close();
176+
}
177+
178+
WithUniqueTempDirectory(directory =>
179+
{
180+
using (var certificateStore = new X509Store(StoreName.My, StoreLocation.CurrentUser))
181+
{
182+
certificateStore.Open(OpenFlags.ReadWrite);
183+
var certInStore = certificateStore.Certificates.Find(X509FindType.FindBySubjectName, "TestCert", false)[0];
184+
Assert.NotNull(certInStore);
185+
Assert.False(certInStore.HasPrivateKey);
186+
187+
try
188+
{
189+
var certWithKey = new X509Certificate2(Path.Combine(GetTestFilesPath(), "TestCert3.pfx"), "password3");
190+
191+
var protector = DataProtectionProvider.Create(directory, certWithKey).CreateProtector("purpose");
192+
var data = protector.Protect("payload");
193+
194+
var keylessUnprotector = DataProtectionProvider.Create(directory).CreateProtector("purpose");
195+
Assert.Throws<CryptographicException>(() => keylessUnprotector.Unprotect(data));
196+
197+
var unprotector = DataProtectionProvider.Create(directory, o => o.UnprotectKeysWithAnyCertificate(certInStore, certWithKey)).CreateProtector("purpose");
198+
Assert.Equal("payload", unprotector.Unprotect(data));
199+
}
200+
finally
201+
{
202+
certificateStore.Remove(certInStore);
203+
certificateStore.Close();
204+
}
205+
}
206+
});
207+
}
208+
160209
[Fact]
161210
public void System_UsesInMemoryCertificate()
162211
{
@@ -242,7 +291,7 @@ public void System_CanUnprotectWithCert()
242291
/// </summary>
243292
private static void WithUniqueTempDirectory(Action<DirectoryInfo> testCode)
244293
{
245-
string uniqueTempPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
294+
string uniqueTempPath = Path.Combine(AppContext.BaseDirectory, Path.GetRandomFileName());
246295
var dirInfo = new DirectoryInfo(uniqueTempPath);
247296
try
248297
{
Binary file not shown.
Binary file not shown.
Binary file not shown.

0 commit comments

Comments
 (0)