/
CertificateXmlEncryptor.cs
148 lines (129 loc) · 5.92 KB
/
CertificateXmlEncryptor.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.Security.Cryptography.X509Certificates;
using System.Security.Cryptography.Xml;
using System.Xml;
using System.Xml.Linq;
using Microsoft.AspNetCore.Cryptography;
using Microsoft.Extensions.Logging;
namespace Microsoft.AspNetCore.DataProtection.XmlEncryption
{
/// <summary>
/// An <see cref="IXmlEncryptor"/> that can perform XML encryption by using an X.509 certificate.
/// </summary>
public sealed class CertificateXmlEncryptor : IInternalCertificateXmlEncryptor, IXmlEncryptor
{
private readonly Func<X509Certificate2> _certFactory;
private readonly IInternalCertificateXmlEncryptor _encryptor;
private readonly ILogger _logger;
/// <summary>
/// Creates a <see cref="CertificateXmlEncryptor"/> given a certificate's thumbprint, an
/// <see cref="ICertificateResolver"/> that can be used to resolve the certificate, and
/// an <see cref="IServiceProvider"/>.
/// </summary>
public CertificateXmlEncryptor(string thumbprint, ICertificateResolver certificateResolver, ILoggerFactory loggerFactory)
: this(loggerFactory, encryptor: null)
{
if (thumbprint == null)
{
throw new ArgumentNullException(nameof(thumbprint));
}
if (certificateResolver == null)
{
throw new ArgumentNullException(nameof(certificateResolver));
}
_certFactory = CreateCertFactory(thumbprint, certificateResolver);
}
/// <summary>
/// Creates a <see cref="CertificateXmlEncryptor"/> given an <see cref="X509Certificate2"/> instance
/// and an <see cref="IServiceProvider"/>.
/// </summary>
public CertificateXmlEncryptor(X509Certificate2 certificate, ILoggerFactory loggerFactory)
: this(loggerFactory, encryptor: null)
{
if (certificate == null)
{
throw new ArgumentNullException(nameof(certificate));
}
_certFactory = () => certificate;
}
internal CertificateXmlEncryptor(ILoggerFactory loggerFactory, IInternalCertificateXmlEncryptor? encryptor)
{
_encryptor = encryptor ?? this;
_logger = loggerFactory.CreateLogger<CertificateXmlEncryptor>();
_certFactory = default!; // Set by calling ctors
}
/// <summary>
/// Encrypts the specified <see cref="XElement"/> with an X.509 certificate.
/// </summary>
/// <param name="plaintextElement">The plaintext to encrypt.</param>
/// <returns>
/// An <see cref="EncryptedXmlInfo"/> that contains the encrypted value of
/// <paramref name="plaintextElement"/> along with information about how to
/// decrypt it.
/// </returns>
public EncryptedXmlInfo Encrypt(XElement plaintextElement)
{
if (plaintextElement == null)
{
throw new ArgumentNullException(nameof(plaintextElement));
}
// <EncryptedData Type="http://www.w3.org/2001/04/xmlenc#Element" xmlns="http://www.w3.org/2001/04/xmlenc#">
// ...
// </EncryptedData>
var encryptedElement = EncryptElement(plaintextElement);
return new EncryptedXmlInfo(encryptedElement, typeof(EncryptedXmlDecryptor));
}
private XElement EncryptElement(XElement plaintextElement)
{
// EncryptedXml works with XmlDocument, not XLinq. When we perform the conversion
// we'll wrap the incoming element in a dummy <root /> element since encrypted XML
// doesn't handle encrypting the root element all that well.
var xmlDocument = new XmlDocument();
xmlDocument.Load(new XElement("root", plaintextElement).CreateReader());
var elementToEncrypt = (XmlElement)xmlDocument.DocumentElement!.FirstChild!;
// Perform the encryption and update the document in-place.
var encryptedXml = new EncryptedXml(xmlDocument);
var encryptedData = _encryptor.PerformEncryption(encryptedXml, elementToEncrypt);
EncryptedXml.ReplaceElement(elementToEncrypt, encryptedData, content: false);
// Strip the <root /> element back off and convert the XmlDocument to an XElement.
return XElement.Load(xmlDocument.DocumentElement.FirstChild!.CreateNavigator()!.ReadSubtree());
}
private Func<X509Certificate2> CreateCertFactory(string thumbprint, ICertificateResolver resolver)
{
return () =>
{
try
{
var cert = resolver.ResolveCertificate(thumbprint);
if (cert == null)
{
throw Error.CertificateXmlEncryptor_CertificateNotFound(thumbprint);
}
return cert;
}
catch (Exception ex)
{
_logger.ExceptionWhileTryingToResolveCertificateWithThumbprint(thumbprint, ex);
throw;
}
};
}
EncryptedData IInternalCertificateXmlEncryptor.PerformEncryption(EncryptedXml encryptedXml, XmlElement elementToEncrypt)
{
var cert = _certFactory()
?? CryptoUtil.Fail<X509Certificate2>("Cert factory returned null.");
_logger.EncryptingToX509CertificateWithThumbprint(cert.Thumbprint);
try
{
return encryptedXml.Encrypt(elementToEncrypt, cert);
}
catch (Exception ex)
{
_logger.AnErrorOccurredWhileEncryptingToX509CertificateWithThumbprint(cert.Thumbprint, ex);
throw;
}
}
}
}