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 System.Runtime.InteropServices;
6 using Test.Cryptography;
7 using Xunit;
8 
9 namespace System.Security.Cryptography.X509Certificates.Tests
10 {
11     public static class InteropTests
12     {
13         [Fact]
14         [PlatformSpecific(TestPlatforms.Windows)]  // Uses P/Invokes
TestHandle()15         public static void TestHandle()
16         {
17             //
18             // Ensure that the Handle property returns a valid CER_CONTEXT pointer.
19             //
20             using (X509Certificate2 c = new X509Certificate2(TestData.MsCertificate))
21             {
22                 IntPtr h = c.Handle;
23                 unsafe
24                 {
25                     CERT_CONTEXT* pCertContext = (CERT_CONTEXT*)h;
26 
27                     // Does the blob data match?
28                     int cbCertEncoded = pCertContext->cbCertEncoded;
29                     Assert.Equal(TestData.MsCertificate.Length, cbCertEncoded);
30 
31                     byte[] pCertEncoded = new byte[cbCertEncoded];
32                     Marshal.Copy((IntPtr)(pCertContext->pbCertEncoded), pCertEncoded, 0, cbCertEncoded);
33                     Assert.Equal(TestData.MsCertificate, pCertEncoded);
34 
35                     // Does the serial number match?
36                     CERT_INFO* pCertInfo = pCertContext->pCertInfo;
37                     byte[] serialNumber = pCertInfo->SerialNumber.ToByteArray();
38                     byte[] expectedSerial = "b00000000100dd9f3bd08b0aaf11b000000033".HexToByteArray();
39                     Assert.Equal(expectedSerial, serialNumber);
40                 }
41             }
42         }
43 
44         [Fact]
45         [PlatformSpecific(TestPlatforms.Windows)]  // Uses P/Invokes
TestHandleCtor()46         public static void TestHandleCtor()
47         {
48             IntPtr pCertContext = IntPtr.Zero;
49             byte[] rawData = TestData.MsCertificate;
50             unsafe
51             {
52                 fixed (byte* pRawData = rawData)
53                 {
54                     CRYPTOAPI_BLOB certBlob = new CRYPTOAPI_BLOB() { cbData = rawData.Length, pbData = pRawData };
55                     bool success = CryptQueryObject(
56                         CertQueryObjectType.CERT_QUERY_OBJECT_BLOB,
57                         ref certBlob,
58                         ExpectedContentTypeFlags.CERT_QUERY_CONTENT_FLAG_CERT,
59                         ExpectedFormatTypeFlags.CERT_QUERY_FORMAT_FLAG_BINARY,
60                         0,
61                         IntPtr.Zero,
62                         IntPtr.Zero,
63                         IntPtr.Zero,
64                         IntPtr.Zero,
65                         IntPtr.Zero,
66                         out pCertContext
67                             );
68 
69                     if (!success)
70                     {
71                         int hr = Marshal.GetHRForLastWin32Error();
72                         throw new CryptographicException(hr);
73                     }
74                 }
75             }
76 
77             // Now, create an X509Certificate around our handle.
78             using (X509Certificate2 c = new X509Certificate2(pCertContext))
79             {
80                 // And release our ref-count on the handle. X509Certificate better be maintaining its own.
81                 CertFreeCertificateContext(pCertContext);
82 
83                 // Now, test various properties to make sure the X509Certificate actually wraps our CERT_CONTEXT.
84                 IntPtr h = c.Handle;
85                 Assert.Equal(pCertContext, h);
86                 pCertContext = IntPtr.Zero;
87 
88                 Assert.Equal(rawData, c.GetRawCertData());
89                 Assert.Equal(rawData, c.GetRawCertDataString().HexToByteArray());
90 
91                 string issuer = c.Issuer;
92                 Assert.Equal(
93                     "CN=Microsoft Code Signing PCA, O=Microsoft Corporation, L=Redmond, S=Washington, C=US",
94                     issuer);
95 
96                 byte[] expectedPublicKey = (
97                     "3082010a0282010100e8af5ca2200df8287cbc057b7fadeeeb76ac28533f3adb" +
98                     "407db38e33e6573fa551153454a5cfb48ba93fa837e12d50ed35164eef4d7adb" +
99                     "137688b02cf0595ca9ebe1d72975e41b85279bf3f82d9e41362b0b40fbbe3bba" +
100                     "b95c759316524bca33c537b0f3eb7ea8f541155c08651d2137f02cba220b10b1" +
101                     "109d772285847c4fb91b90b0f5a3fe8bf40c9a4ea0f5c90a21e2aae3013647fd" +
102                     "2f826a8103f5a935dc94579dfb4bd40e82db388f12fee3d67a748864e162c425" +
103                     "2e2aae9d181f0e1eb6c2af24b40e50bcde1c935c49a679b5b6dbcef9707b2801" +
104                     "84b82a29cfbfa90505e1e00f714dfdad5c238329ebc7c54ac8e82784d37ec643" +
105                     "0b950005b14f6571c50203010001").HexToByteArray();
106 
107                 byte[] publicKey = c.GetPublicKey();
108                 Assert.Equal(expectedPublicKey, publicKey);
109 
110                 byte[] expectedThumbPrint = "108e2ba23632620c427c570b6d9db51ac31387fe".HexToByteArray();
111                 byte[] thumbPrint = c.GetCertHash();
112                 Assert.Equal(expectedThumbPrint, thumbPrint);
113             }
114         }
115 
116         [DllImport("crypt32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
CryptQueryObject( CertQueryObjectType dwObjectType, [In] ref CRYPTOAPI_BLOB pvObject, ExpectedContentTypeFlags dwExpectedContentTypeFlags, ExpectedFormatTypeFlags dwExpectedFormatTypeFlags, int dwFlags, IntPtr pdwMsgAndCertEncodingType, IntPtr pdwContentType, IntPtr pdwFormatType, IntPtr phCertStore, IntPtr phMsg, out IntPtr ppvContext )117         private static extern bool CryptQueryObject(
118             CertQueryObjectType dwObjectType,
119             [In] ref CRYPTOAPI_BLOB pvObject,
120             ExpectedContentTypeFlags dwExpectedContentTypeFlags,
121             ExpectedFormatTypeFlags dwExpectedFormatTypeFlags,
122             int dwFlags, // reserved - always pass 0
123             IntPtr pdwMsgAndCertEncodingType,
124             IntPtr pdwContentType,
125             IntPtr pdwFormatType,
126             IntPtr phCertStore,
127             IntPtr phMsg,
128             out IntPtr ppvContext
129             );
130 
131         [DllImport("crypt32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
CertFreeCertificateContext(IntPtr pCertContext)132         private static extern bool CertFreeCertificateContext(IntPtr pCertContext);
133 
134         private enum CertQueryObjectType : int
135         {
136             CERT_QUERY_OBJECT_FILE = 0x00000001,
137             CERT_QUERY_OBJECT_BLOB = 0x00000002,
138         }
139 
140         [Flags]
141         private enum ExpectedContentTypeFlags : int
142         {
143             //encoded single certificate
144             CERT_QUERY_CONTENT_FLAG_CERT = 1 << ContentType.CERT_QUERY_CONTENT_CERT,
145 
146             //encoded single CTL
147             CERT_QUERY_CONTENT_FLAG_CTL = 1 << ContentType.CERT_QUERY_CONTENT_CTL,
148 
149             //encoded single CRL
150             CERT_QUERY_CONTENT_FLAG_CRL = 1 << ContentType.CERT_QUERY_CONTENT_CRL,
151 
152             //serialized store
153             CERT_QUERY_CONTENT_FLAG_SERIALIZED_STORE = 1 << ContentType.CERT_QUERY_CONTENT_SERIALIZED_STORE,
154 
155             //serialized single certificate
156             CERT_QUERY_CONTENT_FLAG_SERIALIZED_CERT = 1 << ContentType.CERT_QUERY_CONTENT_SERIALIZED_CERT,
157 
158             //serialized single CTL
159             CERT_QUERY_CONTENT_FLAG_SERIALIZED_CTL = 1 << ContentType.CERT_QUERY_CONTENT_SERIALIZED_CTL,
160 
161             //serialized single CRL
162             CERT_QUERY_CONTENT_FLAG_SERIALIZED_CRL = 1 << ContentType.CERT_QUERY_CONTENT_SERIALIZED_CRL,
163 
164             //an encoded PKCS#7 signed message
165             CERT_QUERY_CONTENT_FLAG_PKCS7_SIGNED = 1 << ContentType.CERT_QUERY_CONTENT_PKCS7_SIGNED,
166 
167             //an encoded PKCS#7 message.  But it is not a signed message
168             CERT_QUERY_CONTENT_FLAG_PKCS7_UNSIGNED = 1 << ContentType.CERT_QUERY_CONTENT_PKCS7_UNSIGNED,
169 
170             //the content includes an embedded PKCS7 signed message
171             CERT_QUERY_CONTENT_FLAG_PKCS7_SIGNED_EMBED = 1 << ContentType.CERT_QUERY_CONTENT_PKCS7_SIGNED_EMBED,
172 
173             //an encoded PKCS#10
174             CERT_QUERY_CONTENT_FLAG_PKCS10 = 1 << ContentType.CERT_QUERY_CONTENT_PKCS10,
175 
176             //an encoded PFX BLOB
177             CERT_QUERY_CONTENT_FLAG_PFX = 1 << ContentType.CERT_QUERY_CONTENT_PFX,
178 
179             //an encoded CertificatePair (contains forward and/or reverse cross certs)
180             CERT_QUERY_CONTENT_FLAG_CERT_PAIR = 1 << ContentType.CERT_QUERY_CONTENT_CERT_PAIR,
181 
182             //an encoded PFX BLOB, and we do want to load it (not included in
183             //CERT_QUERY_CONTENT_FLAG_ALL)
184             CERT_QUERY_CONTENT_FLAG_PFX_AND_LOAD = 1 << ContentType.CERT_QUERY_CONTENT_PFX_AND_LOAD,
185         }
186 
187         [Flags]
188         private enum ExpectedFormatTypeFlags : int
189         {
190             CERT_QUERY_FORMAT_FLAG_BINARY = 1 << FormatType.CERT_QUERY_FORMAT_BINARY,
191             CERT_QUERY_FORMAT_FLAG_BASE64_ENCODED = 1 << FormatType.CERT_QUERY_FORMAT_BASE64_ENCODED,
192             CERT_QUERY_FORMAT_FLAG_ASN_ASCII_HEX_ENCODED = 1 << FormatType.CERT_QUERY_FORMAT_ASN_ASCII_HEX_ENCODED,
193 
194             CERT_QUERY_FORMAT_FLAG_ALL = CERT_QUERY_FORMAT_FLAG_BINARY | CERT_QUERY_FORMAT_FLAG_BASE64_ENCODED | CERT_QUERY_FORMAT_FLAG_ASN_ASCII_HEX_ENCODED,
195         }
196 
197         private enum MsgAndCertEncodingType : int
198         {
199             PKCS_7_ASN_ENCODING = 0x10000,
200             X509_ASN_ENCODING = 0x1,
201         }
202 
203         private enum ContentType : int
204         {
205             //encoded single certificate
206             CERT_QUERY_CONTENT_CERT = 1,
207             //encoded single CTL
208             CERT_QUERY_CONTENT_CTL = 2,
209             //encoded single CRL
210             CERT_QUERY_CONTENT_CRL = 3,
211             //serialized store
212             CERT_QUERY_CONTENT_SERIALIZED_STORE = 4,
213             //serialized single certificate
214             CERT_QUERY_CONTENT_SERIALIZED_CERT = 5,
215             //serialized single CTL
216             CERT_QUERY_CONTENT_SERIALIZED_CTL = 6,
217             //serialized single CRL
218             CERT_QUERY_CONTENT_SERIALIZED_CRL = 7,
219             //a PKCS#7 signed message
220             CERT_QUERY_CONTENT_PKCS7_SIGNED = 8,
221             //a PKCS#7 message, such as enveloped message.  But it is not a signed message,
222             CERT_QUERY_CONTENT_PKCS7_UNSIGNED = 9,
223             //a PKCS7 signed message embedded in a file
224             CERT_QUERY_CONTENT_PKCS7_SIGNED_EMBED = 10,
225             //an encoded PKCS#10
226             CERT_QUERY_CONTENT_PKCS10 = 11,
227             //an encoded PFX BLOB
228             CERT_QUERY_CONTENT_PFX = 12,
229             //an encoded CertificatePair (contains forward and/or reverse cross certs)
230             CERT_QUERY_CONTENT_CERT_PAIR = 13,
231             //an encoded PFX BLOB, which was loaded to phCertStore
232             CERT_QUERY_CONTENT_PFX_AND_LOAD = 14,
233         }
234 
235         private enum FormatType : int
236         {
237             CERT_QUERY_FORMAT_BINARY = 1,
238             CERT_QUERY_FORMAT_BASE64_ENCODED = 2,
239             CERT_QUERY_FORMAT_ASN_ASCII_HEX_ENCODED = 3,
240         }
241 
242         // CRYPTOAPI_BLOB has many typedef aliases in the C++ world (CERT_BLOB, DATA_BLOB, etc.) We'll just stick to one name here.
243         [StructLayout(LayoutKind.Sequential)]
244         private unsafe struct CRYPTOAPI_BLOB
245         {
246             public int cbData;
247             public byte* pbData;
248 
ToByteArraySystem.Security.Cryptography.X509Certificates.Tests.InteropTests.CRYPTOAPI_BLOB249             public byte[] ToByteArray()
250             {
251                 if (cbData == 0)
252                 {
253                     return Array.Empty<byte>();
254                 }
255 
256                 byte[] array = new byte[cbData];
257                 Marshal.Copy((IntPtr)pbData, array, 0, cbData);
258                 return array;
259             }
260         }
261 
262         [StructLayout(LayoutKind.Sequential)]
263         private unsafe struct CERT_CONTEXT
264         {
265             public readonly MsgAndCertEncodingType dwCertEncodingType;
266             public readonly byte* pbCertEncoded;
267             public readonly int cbCertEncoded;
268             public readonly CERT_INFO* pCertInfo;
269             public readonly IntPtr hCertStore;
270         }
271 
272         [StructLayout(LayoutKind.Sequential)]
273         private unsafe struct CERT_INFO
274         {
275             public readonly int dwVersion;
276             public CRYPTOAPI_BLOB SerialNumber;
277             public readonly CRYPT_ALGORITHM_IDENTIFIER SignatureAlgorithm;
278             public readonly CRYPTOAPI_BLOB Issuer;
279             public readonly FILETIME NotBefore;
280             public readonly FILETIME NotAfter;
281             public readonly CRYPTOAPI_BLOB Subject;
282             public readonly CERT_PUBLIC_KEY_INFO SubjectPublicKeyInfo;
283             public readonly CRYPT_BIT_BLOB IssuerUniqueId;
284             public readonly CRYPT_BIT_BLOB SubjectUniqueId;
285             public readonly int cExtension;
286             public readonly CERT_EXTENSION* rgExtension;
287         }
288 
289         [StructLayout(LayoutKind.Sequential)]
290         private struct CRYPT_ALGORITHM_IDENTIFIER
291         {
292             public readonly IntPtr pszObjId;
293             public readonly CRYPTOAPI_BLOB Parameters;
294         }
295 
296         [StructLayout(LayoutKind.Sequential)]
297         private struct CERT_PUBLIC_KEY_INFO
298         {
299             public readonly CRYPT_ALGORITHM_IDENTIFIER Algorithm;
300             public readonly CRYPT_BIT_BLOB PublicKey;
301         }
302 
303         [StructLayout(LayoutKind.Sequential)]
304         private unsafe struct CRYPT_BIT_BLOB
305         {
306             public readonly int cbData;
307             public readonly byte* pbData;
308             public readonly int cUnusedBits;
309 
ToByteArraySystem.Security.Cryptography.X509Certificates.Tests.InteropTests.CRYPT_BIT_BLOB310             public byte[] ToByteArray()
311             {
312                 if (cbData == 0)
313                 {
314                     return Array.Empty<byte>();
315                 }
316 
317                 byte[] array = new byte[cbData];
318                 Marshal.Copy((IntPtr)pbData, array, 0, cbData);
319                 return array;
320             }
321         }
322 
323         [StructLayout(LayoutKind.Sequential)]
324         private unsafe struct CERT_EXTENSION
325         {
326             public readonly IntPtr pszObjId;
327             public readonly int fCritical;
328             public readonly CRYPTOAPI_BLOB Value;
329         }
330 
331         [StructLayout(LayoutKind.Sequential)]
332         private struct FILETIME
333         {
334             private readonly uint _ftTimeLow;
335             private readonly uint _ftTimeHigh;
336 
ToDateTimeSystem.Security.Cryptography.X509Certificates.Tests.InteropTests.FILETIME337             public DateTime ToDateTime()
338             {
339                 long fileTime = (((long)_ftTimeHigh) << 32) + _ftTimeLow;
340                 return DateTime.FromFileTime(fileTime);
341             }
342         }
343     }
344 }
345