904d84b8c6c63b8cbf0ed2bb2bd9029f86ad1382
[platform/upstream/dotnet/runtime.git] /
1 // Licensed to the .NET Foundation under one or more agreements.
2 // The .NET Foundation licenses this file to you under the MIT license.
3
4 using System.IO;
5 using System.Diagnostics;
6 using System.Runtime.InteropServices;
7 using Internal.Cryptography;
8 using Microsoft.Win32.SafeHandles;
9
10 namespace System.Security.Cryptography.X509Certificates
11 {
12     internal sealed partial class CertificatePal
13     {
14         internal static partial ICertificatePal FromBlob(ReadOnlySpan<byte> rawData, SafePasswordHandle password, X509KeyStorageFlags keyStorageFlags)
15         {
16             return FromBlobOrFile(rawData, null, password, keyStorageFlags);
17         }
18
19         internal static partial ICertificatePal FromFile(string fileName, SafePasswordHandle password, X509KeyStorageFlags keyStorageFlags)
20         {
21             return FromBlobOrFile(ReadOnlySpan<byte>.Empty, fileName, password, keyStorageFlags);
22         }
23
24         private static ICertificatePal FromBlobOrFile(ReadOnlySpan<byte> rawData, string? fileName, SafePasswordHandle password, X509KeyStorageFlags keyStorageFlags)
25         {
26             Debug.Assert(!rawData.IsEmpty || fileName != null);
27             Debug.Assert(password != null);
28
29             bool loadFromFile = (fileName != null);
30
31             Interop.Crypt32.PfxCertStoreFlags pfxCertStoreFlags = MapKeyStorageFlags(keyStorageFlags);
32             bool deleteKeyContainer = false;
33
34             Interop.Crypt32.CertEncodingType msgAndCertEncodingType;
35             Interop.Crypt32.ContentType contentType;
36             Interop.Crypt32.FormatType formatType;
37             SafeCertStoreHandle? hCertStore = null;
38             SafeCryptMsgHandle? hCryptMsg = null;
39             SafeCertContextHandle? pCertContext = null;
40
41             try
42             {
43                 unsafe
44                 {
45                     fixed (byte* pRawData = rawData)
46                     {
47                         fixed (char* pFileName = fileName)
48                         {
49                             Interop.Crypt32.DATA_BLOB certBlob = new Interop.Crypt32.DATA_BLOB(new IntPtr(pRawData), (uint)(loadFromFile ? 0 : rawData.Length));
50
51                             Interop.Crypt32.CertQueryObjectType objectType = loadFromFile ? Interop.Crypt32.CertQueryObjectType.CERT_QUERY_OBJECT_FILE : Interop.Crypt32.CertQueryObjectType.CERT_QUERY_OBJECT_BLOB;
52                             void* pvObject = loadFromFile ? (void*)pFileName : (void*)&certBlob;
53
54                             bool success = Interop.Crypt32.CryptQueryObject(
55                                 objectType,
56                                 pvObject,
57                                 X509ExpectedContentTypeFlags,
58                                 X509ExpectedFormatTypeFlags,
59                                 0,
60                                 out msgAndCertEncodingType,
61                                 out contentType,
62                                 out formatType,
63                                 out hCertStore,
64                                 out hCryptMsg,
65                                 out pCertContext
66                                     );
67                             if (!success)
68                             {
69                                 int hr = Marshal.GetHRForLastWin32Error();
70                                 throw hr.ToCryptographicException();
71                             }
72                         }
73                     }
74
75                     if (contentType == Interop.Crypt32.ContentType.CERT_QUERY_CONTENT_PKCS7_SIGNED || contentType == Interop.Crypt32.ContentType.CERT_QUERY_CONTENT_PKCS7_SIGNED_EMBED)
76                     {
77                         pCertContext?.Dispose();
78                         pCertContext = GetSignerInPKCS7Store(hCertStore, hCryptMsg);
79                     }
80                     else if (contentType == Interop.Crypt32.ContentType.CERT_QUERY_CONTENT_PFX)
81                     {
82                         if (loadFromFile)
83                         {
84                             rawData = File.ReadAllBytes(fileName!);
85                         }
86
87                         pCertContext?.Dispose();
88                         pCertContext = FilterPFXStore(rawData, password, pfxCertStoreFlags);
89
90                         // If PersistKeySet is set we don't delete the key, so that it persists.
91                         // If EphemeralKeySet is set we don't delete the key, because there's no file, so it's a wasteful call.
92                         const X509KeyStorageFlags DeleteUnless =
93                             X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.EphemeralKeySet;
94
95                         deleteKeyContainer = ((keyStorageFlags & DeleteUnless) == 0);
96                     }
97
98                     CertificatePal pal = new CertificatePal(pCertContext, deleteKeyContainer);
99                     pCertContext = null;
100                     return pal;
101                 }
102             }
103             finally
104             {
105                 hCertStore?.Dispose();
106                 hCryptMsg?.Dispose();
107                 pCertContext?.Dispose();
108             }
109         }
110
111         private static unsafe SafeCertContextHandle GetSignerInPKCS7Store(SafeCertStoreHandle hCertStore, SafeCryptMsgHandle hCryptMsg)
112         {
113             // make sure that there is at least one signer of the certificate store
114             int dwSigners;
115             int cbSigners = sizeof(int);
116             if (!Interop.Crypt32.CryptMsgGetParam(hCryptMsg, Interop.Crypt32.CryptMsgParamType.CMSG_SIGNER_COUNT_PARAM, 0, out dwSigners, ref cbSigners))
117                 throw Marshal.GetHRForLastWin32Error().ToCryptographicException();
118             if (dwSigners == 0)
119                 throw ErrorCode.CRYPT_E_SIGNER_NOT_FOUND.ToCryptographicException();
120
121             // get the first signer from the store, and use that as the loaded certificate
122             int cbData = 0;
123             if (!Interop.Crypt32.CryptMsgGetParam(hCryptMsg, Interop.Crypt32.CryptMsgParamType.CMSG_SIGNER_INFO_PARAM, 0, default(byte*), ref cbData))
124                 throw Marshal.GetHRForLastWin32Error().ToCryptographicException();
125
126             fixed (byte* pCmsgSignerBytes = new byte[cbData])
127             {
128                 if (!Interop.Crypt32.CryptMsgGetParam(hCryptMsg, Interop.Crypt32.CryptMsgParamType.CMSG_SIGNER_INFO_PARAM, 0, pCmsgSignerBytes, ref cbData))
129                     throw Marshal.GetHRForLastWin32Error().ToCryptographicException();
130
131                 CMSG_SIGNER_INFO_Partial* pCmsgSignerInfo = (CMSG_SIGNER_INFO_Partial*)pCmsgSignerBytes;
132
133                 Interop.Crypt32.CERT_INFO certInfo = default(Interop.Crypt32.CERT_INFO);
134                 certInfo.Issuer.cbData = pCmsgSignerInfo->Issuer.cbData;
135                 certInfo.Issuer.pbData = pCmsgSignerInfo->Issuer.pbData;
136                 certInfo.SerialNumber.cbData = pCmsgSignerInfo->SerialNumber.cbData;
137                 certInfo.SerialNumber.pbData = pCmsgSignerInfo->SerialNumber.pbData;
138
139                 SafeCertContextHandle? pCertContext = null;
140                 if (!Interop.crypt32.CertFindCertificateInStore(hCertStore, Interop.Crypt32.CertFindType.CERT_FIND_SUBJECT_CERT, &certInfo, ref pCertContext))
141                 {
142                     Exception e = Marshal.GetHRForLastWin32Error().ToCryptographicException();
143                     pCertContext.Dispose();
144                     throw e;
145                 }
146
147                 return pCertContext;
148             }
149         }
150
151         private static SafeCertContextHandle FilterPFXStore(
152             ReadOnlySpan<byte> rawData,
153             SafePasswordHandle password,
154             Interop.Crypt32.PfxCertStoreFlags pfxCertStoreFlags)
155         {
156             SafeCertStoreHandle hStore;
157             unsafe
158             {
159                 fixed (byte* pbRawData = rawData)
160                 {
161                     Interop.Crypt32.DATA_BLOB certBlob = new Interop.Crypt32.DATA_BLOB(new IntPtr(pbRawData), (uint)rawData.Length);
162                     hStore = Interop.Crypt32.PFXImportCertStore(ref certBlob, password, pfxCertStoreFlags);
163                     if (hStore.IsInvalid)
164                     {
165                         Exception e = Marshal.GetHRForLastWin32Error().ToCryptographicException();
166                         hStore.Dispose();
167                         throw e;
168                     }
169                 }
170             }
171
172             try
173             {
174                 // Find the first cert with private key. If none, then simply take the very first cert. Along the way, delete the keycontainers
175                 // of any cert we don't accept.
176                 SafeCertContextHandle pCertContext = SafeCertContextHandle.InvalidHandle;
177                 SafeCertContextHandle? pEnumContext = null;
178                 while (Interop.crypt32.CertEnumCertificatesInStore(hStore, ref pEnumContext))
179                 {
180                     if (pEnumContext.ContainsPrivateKey)
181                     {
182                         if ((!pCertContext.IsInvalid) && pCertContext.ContainsPrivateKey)
183                         {
184                             // We already found our chosen one. Free up this one's key and move on.
185
186                             // If this one has a persisted private key, clean up the key file.
187                             // If it was an ephemeral private key no action is required.
188                             if (pEnumContext.HasPersistedPrivateKey)
189                             {
190                                 SafeCertContextHandleWithKeyContainerDeletion.DeleteKeyContainer(pEnumContext);
191                             }
192                         }
193                         else
194                         {
195                             // Found our first cert that has a private key. Set it up as our chosen one but keep iterating
196                             // as we need to free up the keys of any remaining certs.
197                             pCertContext.Dispose();
198                             pCertContext = pEnumContext.Duplicate();
199                         }
200                     }
201                     else
202                     {
203                         if (pCertContext.IsInvalid)
204                         {
205                             // Doesn't have a private key but hang on to it anyway in case we don't find any certs with a private key.
206                             pCertContext.Dispose();
207                             pCertContext = pEnumContext.Duplicate();
208                         }
209                     }
210                 }
211
212                 if (pCertContext.IsInvalid)
213                 {
214                     pCertContext.Dispose();
215                     throw new CryptographicException(SR.Cryptography_Pfx_NoCertificates);
216                 }
217
218                 return pCertContext;
219             }
220             finally
221             {
222                 hStore.Dispose();
223             }
224         }
225
226         private static Interop.Crypt32.PfxCertStoreFlags MapKeyStorageFlags(X509KeyStorageFlags keyStorageFlags)
227         {
228             if ((keyStorageFlags & X509Certificate.KeyStorageFlagsAll) != keyStorageFlags)
229                 throw new ArgumentException(SR.Argument_InvalidFlag, nameof(keyStorageFlags));
230
231             Interop.Crypt32.PfxCertStoreFlags pfxCertStoreFlags = 0;
232             if ((keyStorageFlags & X509KeyStorageFlags.UserKeySet) == X509KeyStorageFlags.UserKeySet)
233                 pfxCertStoreFlags |= Interop.Crypt32.PfxCertStoreFlags.CRYPT_USER_KEYSET;
234             else if ((keyStorageFlags & X509KeyStorageFlags.MachineKeySet) == X509KeyStorageFlags.MachineKeySet)
235                 pfxCertStoreFlags |= Interop.Crypt32.PfxCertStoreFlags.CRYPT_MACHINE_KEYSET;
236
237             if ((keyStorageFlags & X509KeyStorageFlags.Exportable) == X509KeyStorageFlags.Exportable)
238                 pfxCertStoreFlags |= Interop.Crypt32.PfxCertStoreFlags.CRYPT_EXPORTABLE;
239             if ((keyStorageFlags & X509KeyStorageFlags.UserProtected) == X509KeyStorageFlags.UserProtected)
240                 pfxCertStoreFlags |= Interop.Crypt32.PfxCertStoreFlags.CRYPT_USER_PROTECTED;
241
242             // If a user is asking for an Ephemeral key they should be willing to test their code to find out
243             // that it will no longer import into CAPI. This solves problems of legacy CSPs being
244             // difficult to do SHA-2 RSA signatures with, simplifies the story for UWP, and reduces the
245             // complexity of pointer interpretation.
246             if ((keyStorageFlags & X509KeyStorageFlags.EphemeralKeySet) == X509KeyStorageFlags.EphemeralKeySet)
247                 pfxCertStoreFlags |= Interop.Crypt32.PfxCertStoreFlags.PKCS12_NO_PERSIST_KEY | Interop.Crypt32.PfxCertStoreFlags.PKCS12_ALWAYS_CNG_KSP;
248
249             // In .NET Framework loading a PFX then adding the key to the Windows Certificate Store would
250             // enable a native application compiled against CAPI to find that private key and interoperate with it.
251             //
252             // For .NET Core this behavior is being retained.
253
254             return pfxCertStoreFlags;
255         }
256
257         private const Interop.Crypt32.ExpectedContentTypeFlags X509ExpectedContentTypeFlags =
258             Interop.Crypt32.ExpectedContentTypeFlags.CERT_QUERY_CONTENT_FLAG_CERT |
259             Interop.Crypt32.ExpectedContentTypeFlags.CERT_QUERY_CONTENT_FLAG_SERIALIZED_CERT |
260             Interop.Crypt32.ExpectedContentTypeFlags.CERT_QUERY_CONTENT_FLAG_PKCS7_SIGNED |
261             Interop.Crypt32.ExpectedContentTypeFlags.CERT_QUERY_CONTENT_FLAG_PKCS7_SIGNED_EMBED |
262             Interop.Crypt32.ExpectedContentTypeFlags.CERT_QUERY_CONTENT_FLAG_PFX;
263
264         private const Interop.Crypt32.ExpectedFormatTypeFlags X509ExpectedFormatTypeFlags = Interop.Crypt32.ExpectedFormatTypeFlags.CERT_QUERY_FORMAT_FLAG_ALL;
265     }
266 }