-
Notifications
You must be signed in to change notification settings - Fork 1.3k
/
Copy pathRSACertificateExtensions.cs
185 lines (155 loc) · 6.99 KB
/
RSACertificateExtensions.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
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
// Copyright (c) Microsoft Corporation. All rights reserved.
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
using Microsoft.Win32.SafeHandles;
namespace System.Security.Cryptography.X509Certificates
{
/// <summary>
/// Provides extension methods for retrieving <see cref="RSA" /> implementations for the
/// public and private keys of a <see cref="X509Certificate2" />.
/// </summary>
public static class RSACertificateExtensions
{
/// <summary>
/// Gets the <see cref="RSA" /> public key from the certificate or null if the certificate does not have an RSA public key.
/// </summary>
[SecuritySafeCritical]
public static RSA GetRSAPublicKey(this X509Certificate2 certificate)
{
if (certificate == null)
{
throw new ArgumentNullException("certificate");
}
if (!IsRSA(certificate))
{
return null;
}
PublicKey publicKey = certificate.PublicKey;
AsnEncodedData asn = publicKey.EncodedKeyValue;
IntPtr structType = new IntPtr(CapiNative.CNG_RSA_PUBLIC_KEY_BLOB);
SafeLocalAllocHandle cngBlobHandle;
uint cngBlobLength;
bool result = CapiNative.DecodeObject(structType, asn.RawData, out cngBlobHandle, out cngBlobLength);
if (!result)
{
throw new CryptographicException(Marshal.GetLastWin32Error());
}
byte[] cngBlob = new byte[cngBlobLength];
using (cngBlobHandle)
{
Marshal.Copy(cngBlobHandle.DangerousGetHandle(), cngBlob, 0, cngBlob.Length);
}
CngKey key = CngKey.Import(cngBlob, CngKeyBlobFormat.GenericPublicBlob);
return new RSACng(key);
}
/// <summary>
/// Gets the <see cref="RSA" /> private key from the certificate or null if the certificate does not have an RSA private key.
/// </summary>
[SecuritySafeCritical]
public static RSA GetRSAPrivateKey(this X509Certificate2 certificate)
{
if (certificate == null)
{
throw new ArgumentNullException("certificate");
}
if (!certificate.HasPrivateKey || !IsRSA(certificate))
{
return null;
}
CngKeyHandleOpenOptions openOptions;
using (SafeCertContextHandle certificateContext = X509Native.GetCertificateContext(certificate))
using (SafeNCryptKeyHandle privateKeyHandle = X509Native.TryAcquireCngPrivateKey(certificateContext, out openOptions))
{
if (privateKeyHandle == null)
{
if (LocalAppContextSwitches.DontReliablyClonePrivateKey)
return (RSA)certificate.PrivateKey;
// fall back to CAPI if we cannot acquire the key using CNG.
RSACryptoServiceProvider rsaCsp = (RSACryptoServiceProvider)certificate.PrivateKey;
CspParameters cspParameters = DSACertificateExtensions.CopyCspParameters(rsaCsp);
RSACryptoServiceProvider clone = new RSACryptoServiceProvider(cspParameters);
return clone;
}
CngKey key = CngKey.Open(privateKeyHandle, openOptions);
return new RSACng(key);
}
}
[SecuritySafeCritical]
public static X509Certificate2 CopyWithPrivateKey(this X509Certificate2 certificate, RSA privateKey)
{
if (certificate == null)
throw new ArgumentNullException(nameof(certificate));
if (privateKey == null)
throw new ArgumentNullException(nameof(privateKey));
if (certificate.HasPrivateKey)
throw new InvalidOperationException(SR.GetString(SR.Cryptography_Cert_AlreadyHasPrivateKey));
using (RSA publicKey = GetRSAPublicKey(certificate))
{
if (publicKey == null)
throw new ArgumentException(SR.GetString(SR.Cryptography_PrivateKey_WrongAlgorithm));
RSAParameters currentParameters = publicKey.ExportParameters(false);
RSAParameters newParameters = privateKey.ExportParameters(false);
if (!currentParameters.Modulus.SequenceEqual(newParameters.Modulus) ||
!currentParameters.Exponent.SequenceEqual(newParameters.Exponent))
{
throw new ArgumentException(SR.GetString(SR.Cryptography_PrivateKey_DoesNotMatch), nameof(privateKey));
}
}
RSACng rsaCng = privateKey as RSACng;
X509Certificate2 newCert = null;
if (rsaCng != null)
{
newCert = CertificateExtensionsCommon.CopyWithPersistedCngKey(certificate, rsaCng.Key);
}
if (newCert == null)
{
RSACryptoServiceProvider rsaCsp = privateKey as RSACryptoServiceProvider;
if (rsaCsp != null)
{
newCert = CertificateExtensionsCommon.CopyWithPersistedCapiKey(certificate, rsaCsp.CspKeyContainerInfo);
}
}
if (newCert == null)
{
RSAParameters parameters = privateKey.ExportParameters(true);
using (PinAndClear.Track(parameters.D))
using (PinAndClear.Track(parameters.P))
using (PinAndClear.Track(parameters.Q))
using (PinAndClear.Track(parameters.DP))
using (PinAndClear.Track(parameters.DQ))
using (PinAndClear.Track(parameters.InverseQ))
using (rsaCng = new RSACng())
{
rsaCng.ImportParameters(parameters);
newCert = CertificateExtensionsCommon.CopyWithEphemeralCngKey(certificate, rsaCng.Key);
}
}
Debug.Assert(newCert != null);
Debug.Assert(!ReferenceEquals(certificate, newCert));
Debug.Assert(!certificate.HasPrivateKey);
Debug.Assert(newCert.HasPrivateKey);
return newCert;
}
private static bool IsRSA(X509Certificate2 certificate)
{
uint algorithmId = OidToAlgorithmId(certificate.PublicKey.Oid);
switch (algorithmId)
{
case CapiNative.CALG_RSA_SIGN:
case CapiNative.CALG_RSA_KEYX:
return true;
default:
return false;
}
}
private static uint OidToAlgorithmId(Oid oid)
{
using (SafeLocalAllocHandle oidHandle = X509Utils.StringToAnsiPtr(oid.Value))
{
CapiNative.CRYPT_OID_INFO oidInfo = CapiNative.CryptFindOIDInfo(CapiNative.CRYPT_OID_INFO_OID_KEY, oidHandle, 0);
return oidInfo.Algid;
}
}
}
}