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 System.Security.Cryptography;
8 using System.Security.Cryptography.Pkcs;
9 using System.Security.Cryptography.X509Certificates;
11 using Microsoft.Win32.SafeHandles;
13 using static Interop.Crypt32;
15 namespace Internal.Cryptography.Pal.Windows
17 internal sealed partial class DecryptorPalWindows : DecryptorPal
19 public sealed override unsafe ContentInfo? TryDecrypt(
20 RecipientInfo recipientInfo,
21 X509Certificate2? cert,
22 AsymmetricAlgorithm? privateKey,
23 X509Certificate2Collection originatorCerts,
24 X509Certificate2Collection extraStore,
25 out Exception? exception)
27 Debug.Assert((cert != null) ^ (privateKey != null));
29 if (privateKey != null)
31 RSA? key = privateKey as RSA;
35 exception = new CryptographicException(SR.Cryptography_Cms_Ktri_RSARequired);
39 ContentInfo contentInfo = _hCryptMsg.GetContentInfo();
40 byte[]? cek = AnyOS.ManagedPkcsPal.ManagedKeyTransPal.DecryptCekCore(
43 recipientInfo.EncryptedKey,
44 recipientInfo.KeyEncryptionAlgorithm.Oid.Value,
45 recipientInfo.KeyEncryptionAlgorithm.Parameters,
48 // Pin CEK to prevent it from getting copied during heap compaction.
49 fixed (byte* pinnedCek = cek)
53 if (exception != null)
58 return AnyOS.ManagedPkcsPal.ManagedDecryptorPal.TryDecryptCore(
60 contentInfo.ContentType.Value!,
62 _contentEncryptionAlgorithm,
69 Array.Clear(cek, 0, cek.Length);
75 Debug.Assert(recipientInfo != null);
76 Debug.Assert(cert != null);
77 Debug.Assert(originatorCerts != null);
78 Debug.Assert(extraStore != null);
81 exception = TryGetKeySpecForCertificate(cert, out keySpec);
82 if (exception != null)
85 // .NET Framework compat: We pass false for "silent" here (thus allowing crypto providers to display UI.)
86 const bool Silent = false;
87 // Note: Using CRYPT_ACQUIRE_ALLOW_NCRYPT_KEY_FLAG rather than CRYPT_ACQUIRE_PREFER_NCRYPT_KEY_FLAG
88 // because wrapping an NCrypt wrapper over CAPI keys unconditionally causes some legacy features
89 // (such as RC4 support) to break.
90 const bool PreferNCrypt = false;
91 using (SafeProvOrNCryptKeyHandle? hKey = PkcsPalWindows.GetCertificatePrivateKey(cert, Silent, PreferNCrypt, out _, out exception))
96 RecipientInfoType type = recipientInfo.Type;
99 case RecipientInfoType.KeyTransport:
100 exception = TryDecryptTrans((KeyTransRecipientInfo)recipientInfo, hKey, keySpec);
103 case RecipientInfoType.KeyAgreement:
104 exception = TryDecryptAgree((KeyAgreeRecipientInfo)recipientInfo, hKey, keySpec, originatorCerts, extraStore);
108 // Since only the framework can construct RecipientInfo's, we're at fault if we get here. So it's okay to assert and throw rather than
109 // returning to the caller.
110 Debug.Fail($"Unexpected RecipientInfoType: {type}");
111 throw new NotSupportedException();
114 if (exception != null)
117 // If we got here, we successfully decrypted. Return the decrypted content.
118 return _hCryptMsg.GetContentInfo();
122 private static Exception? TryGetKeySpecForCertificate(X509Certificate2 cert, out CryptKeySpec keySpec)
124 using (SafeCertContextHandle hCertContext = cert.CreateCertContextHandle())
128 if (Interop.Crypt32.CertGetCertificateContextProperty(
130 CertContextPropId.CERT_NCRYPT_KEY_HANDLE_PROP_ID,
134 keySpec = CryptKeySpec.CERT_NCRYPT_KEY_SPEC;
138 if (!Interop.Crypt32.CertGetCertificateContextProperty(hCertContext, CertContextPropId.CERT_KEY_PROV_INFO_PROP_ID, null, ref cbSize))
140 ErrorCode errorCode = (ErrorCode)(Marshal.GetLastPInvokeError());
141 keySpec = default(CryptKeySpec);
142 return errorCode.ToCryptographicException();
145 byte[] pData = new byte[cbSize];
148 fixed (byte* pvData = pData)
150 if (!Interop.Crypt32.CertGetCertificateContextProperty(hCertContext, CertContextPropId.CERT_KEY_PROV_INFO_PROP_ID, pData, ref cbSize))
152 ErrorCode errorCode = (ErrorCode)(Marshal.GetLastPInvokeError());
153 keySpec = default(CryptKeySpec);
154 return errorCode.ToCryptographicException();
157 CRYPT_KEY_PROV_INFO* pCryptKeyProvInfo = (CRYPT_KEY_PROV_INFO*)pvData;
158 keySpec = pCryptKeyProvInfo->dwKeySpec;
165 private unsafe Exception? TryDecryptTrans(KeyTransRecipientInfo recipientInfo, SafeProvOrNCryptKeyHandle hKey, CryptKeySpec keySpec)
167 KeyTransRecipientInfoPalWindows pal = (KeyTransRecipientInfoPalWindows)(recipientInfo.Pal);
169 bool keyAddRefd = false;
173 CMSG_CTRL_DECRYPT_PARA decryptPara;
174 decryptPara.cbSize = sizeof(CMSG_CTRL_DECRYPT_PARA);
175 hKey.DangerousAddRef(ref keyAddRefd);
176 decryptPara.hKey = hKey.DangerousGetHandle();
177 decryptPara.dwKeySpec = keySpec;
178 decryptPara.dwRecipientIndex = pal.Index;
180 bool success = Interop.Crypt32.CryptMsgControl(_hCryptMsg, 0, MsgControlType.CMSG_CTRL_DECRYPT, ref decryptPara);
182 return Marshal.GetHRForLastWin32Error().ToCryptographicException();
190 hKey.DangerousRelease();
195 private Exception? TryDecryptAgree(KeyAgreeRecipientInfo keyAgreeRecipientInfo, SafeProvOrNCryptKeyHandle hKey, CryptKeySpec keySpec, X509Certificate2Collection originatorCerts, X509Certificate2Collection extraStore)
199 KeyAgreeRecipientInfoPalWindows pal = (KeyAgreeRecipientInfoPalWindows)(keyAgreeRecipientInfo.Pal);
200 return pal.WithCmsgCmsRecipientInfo<Exception?>(
201 delegate (CMSG_KEY_AGREE_RECIPIENT_INFO* pKeyAgreeRecipientInfo)
203 bool keyAddRefd = false;
206 CMSG_CTRL_KEY_AGREE_DECRYPT_PARA decryptPara = default(CMSG_CTRL_KEY_AGREE_DECRYPT_PARA);
207 decryptPara.cbSize = sizeof(CMSG_CTRL_KEY_AGREE_DECRYPT_PARA);
208 hKey.DangerousAddRef(ref keyAddRefd);
209 decryptPara.hProv = hKey.DangerousGetHandle();
210 decryptPara.dwKeySpec = keySpec;
211 decryptPara.pKeyAgree = pKeyAgreeRecipientInfo;
212 decryptPara.dwRecipientIndex = pal.Index;
213 decryptPara.dwRecipientEncryptedKeyIndex = pal.SubIndex;
214 CMsgKeyAgreeOriginatorChoice originatorChoice = pKeyAgreeRecipientInfo->dwOriginatorChoice;
215 switch (originatorChoice)
217 case CMsgKeyAgreeOriginatorChoice.CMSG_KEY_AGREE_ORIGINATOR_CERT:
219 X509Certificate2Collection candidateCerts = new X509Certificate2Collection();
220 candidateCerts.AddRange(PkcsHelpers.GetStoreCertificates(StoreName.AddressBook, StoreLocation.CurrentUser, openExistingOnly: true));
221 candidateCerts.AddRange(PkcsHelpers.GetStoreCertificates(StoreName.AddressBook, StoreLocation.LocalMachine, openExistingOnly: true));
222 candidateCerts.AddRange(originatorCerts);
223 candidateCerts.AddRange(extraStore);
224 SubjectIdentifier originatorId = pKeyAgreeRecipientInfo->OriginatorCertId.ToSubjectIdentifier();
225 X509Certificate2? originatorCert = candidateCerts.TryFindMatchingCertificate(originatorId);
226 if (originatorCert == null)
227 return ErrorCode.CRYPT_E_NOT_FOUND.ToCryptographicException();
228 using (SafeCertContextHandle hCertContext = originatorCert.CreateCertContextHandle())
230 CERT_CONTEXT* pOriginatorCertContext = hCertContext.DangerousGetCertContext();
231 decryptPara.OriginatorPublicKey = pOriginatorCertContext->pCertInfo->SubjectPublicKeyInfo.PublicKey;
233 // Do not factor this call out of the switch statement as leaving this "using" block will free up
234 // native memory that decryptPara points to.
235 return TryExecuteDecryptAgree(ref decryptPara);
239 case CMsgKeyAgreeOriginatorChoice.CMSG_KEY_AGREE_ORIGINATOR_PUBLIC_KEY:
241 decryptPara.OriginatorPublicKey = pKeyAgreeRecipientInfo->OriginatorPublicKeyInfo.PublicKey;
242 return TryExecuteDecryptAgree(ref decryptPara);
246 return new CryptographicException(SR.Format(SR.Cryptography_Cms_Invalid_Originator_Identifier_Choice, originatorChoice));
253 hKey.DangerousRelease();
260 private Exception? TryExecuteDecryptAgree(ref CMSG_CTRL_KEY_AGREE_DECRYPT_PARA decryptPara)
262 if (!Interop.Crypt32.CryptMsgControl(_hCryptMsg, 0, MsgControlType.CMSG_CTRL_KEY_AGREE_DECRYPT, ref decryptPara))
264 ErrorCode errorCode = (ErrorCode)(Marshal.GetHRForLastWin32Error());
265 return errorCode.ToCryptographicException();