1 // Licensed to the .NET Foundation under one or more agreements.
2 // The .NET Foundation licenses this file to you under the MIT license.
5 using System.Diagnostics;
6 using System.Runtime.InteropServices;
7 using Internal.Cryptography;
8 using Microsoft.Win32.SafeHandles;
10 namespace System.Security.Cryptography.X509Certificates
12 internal sealed partial class CertificatePal
14 internal static partial ICertificatePal FromBlob(ReadOnlySpan<byte> rawData, SafePasswordHandle password, X509KeyStorageFlags keyStorageFlags)
16 return FromBlobOrFile(rawData, null, password, keyStorageFlags);
19 internal static partial ICertificatePal FromFile(string fileName, SafePasswordHandle password, X509KeyStorageFlags keyStorageFlags)
21 return FromBlobOrFile(ReadOnlySpan<byte>.Empty, fileName, password, keyStorageFlags);
24 private static ICertificatePal FromBlobOrFile(ReadOnlySpan<byte> rawData, string? fileName, SafePasswordHandle password, X509KeyStorageFlags keyStorageFlags)
26 Debug.Assert(!rawData.IsEmpty || fileName != null);
27 Debug.Assert(password != null);
29 bool loadFromFile = (fileName != null);
31 Interop.Crypt32.PfxCertStoreFlags pfxCertStoreFlags = MapKeyStorageFlags(keyStorageFlags);
32 bool deleteKeyContainer = false;
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;
45 fixed (byte* pRawData = rawData)
47 fixed (char* pFileName = fileName)
49 Interop.Crypt32.DATA_BLOB certBlob = new Interop.Crypt32.DATA_BLOB(new IntPtr(pRawData), (uint)(loadFromFile ? 0 : rawData.Length));
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;
54 bool success = Interop.Crypt32.CryptQueryObject(
57 X509ExpectedContentTypeFlags,
58 X509ExpectedFormatTypeFlags,
60 out msgAndCertEncodingType,
69 int hr = Marshal.GetHRForLastWin32Error();
70 throw hr.ToCryptographicException();
75 if (contentType == Interop.Crypt32.ContentType.CERT_QUERY_CONTENT_PKCS7_SIGNED || contentType == Interop.Crypt32.ContentType.CERT_QUERY_CONTENT_PKCS7_SIGNED_EMBED)
77 pCertContext?.Dispose();
78 pCertContext = GetSignerInPKCS7Store(hCertStore, hCryptMsg);
80 else if (contentType == Interop.Crypt32.ContentType.CERT_QUERY_CONTENT_PFX)
84 rawData = File.ReadAllBytes(fileName!);
87 pCertContext?.Dispose();
88 pCertContext = FilterPFXStore(rawData, password, pfxCertStoreFlags);
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;
95 deleteKeyContainer = ((keyStorageFlags & DeleteUnless) == 0);
98 CertificatePal pal = new CertificatePal(pCertContext, deleteKeyContainer);
105 hCertStore?.Dispose();
106 hCryptMsg?.Dispose();
107 pCertContext?.Dispose();
111 private static unsafe SafeCertContextHandle GetSignerInPKCS7Store(SafeCertStoreHandle hCertStore, SafeCryptMsgHandle hCryptMsg)
113 // make sure that there is at least one signer of the certificate store
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();
119 throw ErrorCode.CRYPT_E_SIGNER_NOT_FOUND.ToCryptographicException();
121 // get the first signer from the store, and use that as the loaded certificate
123 if (!Interop.Crypt32.CryptMsgGetParam(hCryptMsg, Interop.Crypt32.CryptMsgParamType.CMSG_SIGNER_INFO_PARAM, 0, default(byte*), ref cbData))
124 throw Marshal.GetHRForLastWin32Error().ToCryptographicException();
126 fixed (byte* pCmsgSignerBytes = new byte[cbData])
128 if (!Interop.Crypt32.CryptMsgGetParam(hCryptMsg, Interop.Crypt32.CryptMsgParamType.CMSG_SIGNER_INFO_PARAM, 0, pCmsgSignerBytes, ref cbData))
129 throw Marshal.GetHRForLastWin32Error().ToCryptographicException();
131 CMSG_SIGNER_INFO_Partial* pCmsgSignerInfo = (CMSG_SIGNER_INFO_Partial*)pCmsgSignerBytes;
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;
139 SafeCertContextHandle? pCertContext = null;
140 if (!Interop.crypt32.CertFindCertificateInStore(hCertStore, Interop.Crypt32.CertFindType.CERT_FIND_SUBJECT_CERT, &certInfo, ref pCertContext))
142 Exception e = Marshal.GetHRForLastWin32Error().ToCryptographicException();
143 pCertContext.Dispose();
151 private static SafeCertContextHandle FilterPFXStore(
152 ReadOnlySpan<byte> rawData,
153 SafePasswordHandle password,
154 Interop.Crypt32.PfxCertStoreFlags pfxCertStoreFlags)
156 SafeCertStoreHandle hStore;
159 fixed (byte* pbRawData = rawData)
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)
165 Exception e = Marshal.GetHRForLastWin32Error().ToCryptographicException();
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))
180 if (pEnumContext.ContainsPrivateKey)
182 if ((!pCertContext.IsInvalid) && pCertContext.ContainsPrivateKey)
184 // We already found our chosen one. Free up this one's key and move on.
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)
190 SafeCertContextHandleWithKeyContainerDeletion.DeleteKeyContainer(pEnumContext);
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();
203 if (pCertContext.IsInvalid)
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();
212 if (pCertContext.IsInvalid)
214 pCertContext.Dispose();
215 throw new CryptographicException(SR.Cryptography_Pfx_NoCertificates);
226 private static Interop.Crypt32.PfxCertStoreFlags MapKeyStorageFlags(X509KeyStorageFlags keyStorageFlags)
228 if ((keyStorageFlags & X509Certificate.KeyStorageFlagsAll) != keyStorageFlags)
229 throw new ArgumentException(SR.Argument_InvalidFlag, nameof(keyStorageFlags));
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;
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;
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;
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.
252 // For .NET Core this behavior is being retained.
254 return pfxCertStoreFlags;
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;
264 private const Interop.Crypt32.ExpectedFormatTypeFlags X509ExpectedFormatTypeFlags = Interop.Crypt32.ExpectedFormatTypeFlags.CERT_QUERY_FORMAT_FLAG_ALL;