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