5201952164e0dc4532123cb6665bbebdffe0065a
[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;
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;
10
11 using Microsoft.Win32.SafeHandles;
12
13 using static Interop.Crypt32;
14
15 namespace Internal.Cryptography.Pal.Windows
16 {
17     internal sealed partial class DecryptorPalWindows : DecryptorPal
18     {
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)
26         {
27             Debug.Assert((cert != null) ^ (privateKey != null));
28
29             if (privateKey != null)
30             {
31                 RSA? key = privateKey as RSA;
32
33                 if (key == null)
34                 {
35                     exception = new CryptographicException(SR.Cryptography_Cms_Ktri_RSARequired);
36                     return null;
37                 }
38
39                 ContentInfo contentInfo = _hCryptMsg.GetContentInfo();
40                 byte[]? cek = AnyOS.ManagedPkcsPal.ManagedKeyTransPal.DecryptCekCore(
41                     cert,
42                     key,
43                     recipientInfo.EncryptedKey,
44                     recipientInfo.KeyEncryptionAlgorithm.Oid.Value,
45                     recipientInfo.KeyEncryptionAlgorithm.Parameters,
46                     out exception);
47
48                 // Pin CEK to prevent it from getting copied during heap compaction.
49                 fixed (byte* pinnedCek = cek)
50                 {
51                     try
52                     {
53                         if (exception != null)
54                         {
55                             return null;
56                         }
57
58                         return AnyOS.ManagedPkcsPal.ManagedDecryptorPal.TryDecryptCore(
59                             cek!,
60                             contentInfo.ContentType.Value!,
61                             contentInfo.Content,
62                             _contentEncryptionAlgorithm,
63                             out exception);
64                     }
65                     finally
66                     {
67                         if (cek != null)
68                         {
69                             Array.Clear(cek, 0, cek.Length);
70                         }
71                     }
72                 }
73             }
74
75             Debug.Assert(recipientInfo != null);
76             Debug.Assert(cert != null);
77             Debug.Assert(originatorCerts != null);
78             Debug.Assert(extraStore != null);
79
80             CryptKeySpec keySpec;
81             exception = TryGetKeySpecForCertificate(cert, out keySpec);
82             if (exception != null)
83                 return null;
84
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))
92             {
93                 if (hKey == null)
94                     return null;
95
96                 RecipientInfoType type = recipientInfo.Type;
97                 switch (type)
98                 {
99                     case RecipientInfoType.KeyTransport:
100                         exception = TryDecryptTrans((KeyTransRecipientInfo)recipientInfo, hKey, keySpec);
101                         break;
102
103                     case RecipientInfoType.KeyAgreement:
104                         exception = TryDecryptAgree((KeyAgreeRecipientInfo)recipientInfo, hKey, keySpec, originatorCerts, extraStore);
105                         break;
106
107                     default:
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();
112                 }
113
114                 if (exception != null)
115                     return null;
116
117                 // If we got here, we successfully decrypted. Return the decrypted content.
118                 return _hCryptMsg.GetContentInfo();
119             }
120         }
121
122         private static Exception? TryGetKeySpecForCertificate(X509Certificate2 cert, out CryptKeySpec keySpec)
123         {
124             using (SafeCertContextHandle hCertContext = cert.CreateCertContextHandle())
125             {
126                 int cbSize = 0;
127
128                 if (Interop.Crypt32.CertGetCertificateContextProperty(
129                     hCertContext,
130                     CertContextPropId.CERT_NCRYPT_KEY_HANDLE_PROP_ID,
131                     null,
132                     ref cbSize))
133                 {
134                     keySpec = CryptKeySpec.CERT_NCRYPT_KEY_SPEC;
135                     return null;
136                 }
137
138                 if (!Interop.Crypt32.CertGetCertificateContextProperty(hCertContext, CertContextPropId.CERT_KEY_PROV_INFO_PROP_ID, null, ref cbSize))
139                 {
140                     ErrorCode errorCode = (ErrorCode)(Marshal.GetLastPInvokeError());
141                     keySpec = default(CryptKeySpec);
142                     return errorCode.ToCryptographicException();
143                 }
144
145                 byte[] pData = new byte[cbSize];
146                 unsafe
147                 {
148                     fixed (byte* pvData = pData)
149                     {
150                         if (!Interop.Crypt32.CertGetCertificateContextProperty(hCertContext, CertContextPropId.CERT_KEY_PROV_INFO_PROP_ID, pData, ref cbSize))
151                         {
152                             ErrorCode errorCode = (ErrorCode)(Marshal.GetLastPInvokeError());
153                             keySpec = default(CryptKeySpec);
154                             return errorCode.ToCryptographicException();
155                         }
156
157                         CRYPT_KEY_PROV_INFO* pCryptKeyProvInfo = (CRYPT_KEY_PROV_INFO*)pvData;
158                         keySpec = pCryptKeyProvInfo->dwKeySpec;
159                         return null;
160                     }
161                 }
162             }
163         }
164
165         private unsafe Exception? TryDecryptTrans(KeyTransRecipientInfo recipientInfo, SafeProvOrNCryptKeyHandle hKey, CryptKeySpec keySpec)
166         {
167             KeyTransRecipientInfoPalWindows pal = (KeyTransRecipientInfoPalWindows)(recipientInfo.Pal);
168
169             bool keyAddRefd = false;
170
171             try
172             {
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;
179
180                 bool success = Interop.Crypt32.CryptMsgControl(_hCryptMsg, 0, MsgControlType.CMSG_CTRL_DECRYPT, ref decryptPara);
181                 if (!success)
182                     return Marshal.GetHRForLastWin32Error().ToCryptographicException();
183
184                 return null;
185             }
186             finally
187             {
188                 if (keyAddRefd)
189                 {
190                     hKey.DangerousRelease();
191                 }
192             }
193         }
194
195         private Exception? TryDecryptAgree(KeyAgreeRecipientInfo keyAgreeRecipientInfo, SafeProvOrNCryptKeyHandle hKey, CryptKeySpec keySpec, X509Certificate2Collection originatorCerts, X509Certificate2Collection extraStore)
196         {
197             unsafe
198             {
199                 KeyAgreeRecipientInfoPalWindows pal = (KeyAgreeRecipientInfoPalWindows)(keyAgreeRecipientInfo.Pal);
200                 return pal.WithCmsgCmsRecipientInfo<Exception?>(
201                     delegate (CMSG_KEY_AGREE_RECIPIENT_INFO* pKeyAgreeRecipientInfo)
202                     {
203                         bool keyAddRefd = false;
204                         try
205                         {
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)
216                             {
217                                 case CMsgKeyAgreeOriginatorChoice.CMSG_KEY_AGREE_ORIGINATOR_CERT:
218                                     {
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())
229                                         {
230                                             CERT_CONTEXT* pOriginatorCertContext = hCertContext.DangerousGetCertContext();
231                                             decryptPara.OriginatorPublicKey = pOriginatorCertContext->pCertInfo->SubjectPublicKeyInfo.PublicKey;
232
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);
236                                         }
237                                     }
238
239                                 case CMsgKeyAgreeOriginatorChoice.CMSG_KEY_AGREE_ORIGINATOR_PUBLIC_KEY:
240                                     {
241                                         decryptPara.OriginatorPublicKey = pKeyAgreeRecipientInfo->OriginatorPublicKeyInfo.PublicKey;
242                                         return TryExecuteDecryptAgree(ref decryptPara);
243                                     }
244
245                                 default:
246                                     return new CryptographicException(SR.Format(SR.Cryptography_Cms_Invalid_Originator_Identifier_Choice, originatorChoice));
247                             }
248                         }
249                         finally
250                         {
251                             if (keyAddRefd)
252                             {
253                                 hKey.DangerousRelease();
254                             }
255                         }
256                     });
257             }
258         }
259
260         private Exception? TryExecuteDecryptAgree(ref CMSG_CTRL_KEY_AGREE_DECRYPT_PARA decryptPara)
261         {
262             if (!Interop.Crypt32.CryptMsgControl(_hCryptMsg, 0, MsgControlType.CMSG_CTRL_KEY_AGREE_DECRYPT, ref decryptPara))
263             {
264                 ErrorCode errorCode = (ErrorCode)(Marshal.GetHRForLastWin32Error());
265                 return errorCode.ToCryptographicException();
266             }
267             return null;
268         }
269     }
270 }