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