static unsafe int Main() { HRESULT hr = HRESULT.S_OK; IntPtr[] rgParaEKU = { szOID_PKIX_KP_EMAIL_PROTECTION }; var EKUCriteria = new CERT_SELECT_CRITERIA { dwType = CertSelectBy.CERT_SELECT_BY_ENHKEY_USAGE, cPara = (uint)rgParaEKU.Length, }; fixed(IntPtr *pArr = rgParaEKU) EKUCriteria.ppPara = (IntPtr)(void *)pArr; var bDigSig = (byte)CertKeyUsage.CERT_DIGITAL_SIGNATURE_KEY_USAGE; // in byte 0 var pDigSig = &bDigSig; var extDigSig = new CERT_EXTENSION { Value = new CRYPTOAPI_BLOB { cbData = 1, pbData = (IntPtr)pDigSig } }; using var pExtDigSig = SafeCoTaskMemHandle.CreateFromStructure(extDigSig); IntPtr[] rgParaKU = { pExtDigSig }; var KUCriteria = new CERT_SELECT_CRITERIA { dwType = CertSelectBy.CERT_SELECT_BY_KEY_USAGE, cPara = (uint)rgParaKU.Length }; fixed(IntPtr *pArr = rgParaKU) KUCriteria.ppPara = (IntPtr)(void *)pArr; CERT_SELECT_CRITERIA[] rgCriteriaFilter = { EKUCriteria, KUCriteria }; using var hStore = CertOpenStore(CertStoreProvider.CERT_STORE_PROV_SYSTEM, 0, default, CertStoreFlags.CERT_SYSTEM_STORE_CURRENT_USER, "MY"); if (hStore.IsInvalid) { hr = (HRESULT)Win32Error.GetLastError(); goto CleanUp; } Console.Write("Looking for certificates in MY store ...\n"); if (!CertSelectCertificateChains(default, CertSelection.CERT_SELECT_TRUSTED_ROOT | CertSelection.CERT_SELECT_HAS_PRIVATE_KEY, default,
public MarshalX509Extension(X509Extension extension) { _extension = extension; _blobPtr = Marshal.AllocHGlobal(extension.RawData.Length); Marshal.Copy(extension.RawData, 0, _blobPtr, extension.RawData.Length); var blob = new CRYPT_OBJID_BLOB(); blob.cbData = (uint)extension.RawData.Length; blob.pbData = _blobPtr; var nativeExtension = new CERT_EXTENSION(); nativeExtension.fCritical = extension.Critical; nativeExtension.pszObjId = extension.Oid.Value; nativeExtension.Value = blob; _value = nativeExtension; }
public SafeX509Extension(X509Extension extension) { this.blobPtr = Marshal.AllocHGlobal(extension.RawData.Length); Marshal.Copy(extension.RawData, 0, this.blobPtr, extension.RawData.Length); var blob = new CRYPTOAPI_BLOB { cbData = (uint)extension.RawData.Length, pbData = this.blobPtr }; var nativeExtension = new CERT_EXTENSION { fCritical = extension.Critical, pszObjId = extension.Oid.Value, Value = blob }; this.value = nativeExtension; }
public SafeX509Extension(X509Extension extension) { this.blobPtr = Marshal.AllocHGlobal(extension.RawData.Length); Marshal.Copy(extension.RawData, 0, this.blobPtr, extension.RawData.Length); var blob = new CRYPTOAPI_BLOB { cbData = (uint)extension.RawData.Length, pbData = this.blobPtr }; var nativeExtension = new CERT_EXTENSION { fCritical = extension.Critical, pszObjId = extension.Oid.Value, Value = blob }; this.value = nativeExtension; }
internal static SafeLocalAllocHandle DecodeExtension(CERT_EXTENSION extension) { SafeLocalAllocHandle decodedExtension = null; int decodedSize = 0; bool decoded = UnsafeNativeMethods.CryptDecodeObjectEx(CertificateEncodingType.Pkcs7AsnEncoding | CertificateEncodingType.X509AsnEncoding, extension.pszObjId, extension.Value.pbData, extension.Value.cbData, DecodeObjectFlags.AllocateMemory | DecodeObjectFlags.NoCopy | DecodeObjectFlags.ShareOidStrings, IntPtr.Zero, out decodedExtension, ref decodedSize); if (!decoded) { Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error()); } return(decodedExtension); }
public CERT_EXTENSIONS(CERT_EXTENSION[] extensions) { this.cExtension = (uint)extensions.Length; if (extensions.Length > 0) { IntPtr buf = Marshal.AllocHGlobal(extensions.Length * Marshal.SizeOf(typeof(CERT_EXTENSION))); this.rgExtension = buf; for (int i = 0; i < extensions.Length; i++) { Marshal.StructureToPtr(extensions[i], buf, false); buf += Marshal.SizeOf(typeof(CERT_EXTENSION)); } } }
internal static SafeCertificateContextHandle CreateSelfSignedCertificate(SafeNCryptKeyHandle key, byte[] subjectName, X509CertificateCreationOptions creationOptions, string signatureAlgorithmOid, DateTime startTime, DateTime endTime, X509ExtensionCollection extensions) { Debug.Assert(key != null, "key != null"); Debug.Assert(!key.IsClosed && !key.IsInvalid, "!key.IsClosed && !key.IsInvalid"); Debug.Assert(subjectName != null, "subjectName != null"); Debug.Assert(!String.IsNullOrEmpty(signatureAlgorithmOid), "!String.IsNullOrEmpty(signatureAlgorithmOid)"); Debug.Assert(extensions != null, "extensions != null"); // Create an algorithm identifier structure for the signature algorithm CapiNative.CRYPT_ALGORITHM_IDENTIFIER nativeSignatureAlgorithm = new CapiNative.CRYPT_ALGORITHM_IDENTIFIER(); nativeSignatureAlgorithm.pszObjId = signatureAlgorithmOid; nativeSignatureAlgorithm.Parameters = new CapiNative.CRYPTOAPI_BLOB(); nativeSignatureAlgorithm.Parameters.cbData = 0; nativeSignatureAlgorithm.Parameters.pbData = IntPtr.Zero; // Convert the begin and expire dates to system time structures Win32Native.SYSTEMTIME nativeStartTime = new Win32Native.SYSTEMTIME(startTime); Win32Native.SYSTEMTIME nativeEndTime = new Win32Native.SYSTEMTIME(endTime); // Map the extensions into CERT_EXTENSIONS. This involves several steps to get the // CERT_EXTENSIONS ready for interop with the native APIs. // 1. Build up the CERT_EXTENSIONS structure in managed code // 2. For each extension, create a managed CERT_EXTENSION structure; this requires allocating // native memory for the blob pointer in the CERT_EXTENSION. These extensions are stored in // the nativeExtensionArray variable. // 3. Get a block of native memory that can hold a native array of CERT_EXTENSION structures. // This is the block referenced by the CERT_EXTENSIONS structure. // 4. For each of the extension structures created in step 2, marshal the extension into the // native buffer allocated in step 3. CERT_EXTENSIONS nativeExtensions = new CERT_EXTENSIONS(); nativeExtensions.cExtension = extensions.Count; CERT_EXTENSION[] nativeExtensionArray = new CERT_EXTENSION[extensions.Count]; // Run this in a CER to ensure that we release any native memory allocated for the certificate // extensions. RuntimeHelpers.PrepareConstrainedRegions(); try { // Copy over each extension into a native extension structure, including allocating native // memory for its blob if necessary. for (int i = 0; i < extensions.Count; ++i) { nativeExtensionArray[i] = new CERT_EXTENSION(); nativeExtensionArray[i].pszObjId = extensions[i].Oid.Value; nativeExtensionArray[i].fCritical = extensions[i].Critical; nativeExtensionArray[i].Value = new CapiNative.CRYPTOAPI_BLOB(); nativeExtensionArray[i].Value.cbData = extensions[i].RawData.Length; if (nativeExtensionArray[i].Value.cbData > 0) { nativeExtensionArray[i].Value.pbData = Marshal.AllocCoTaskMem(nativeExtensionArray[i].Value.cbData); Marshal.Copy(extensions[i].RawData, 0, nativeExtensionArray[i].Value.pbData, nativeExtensionArray[i].Value.cbData); } } // Now that we've built up the extension array, create a block of native memory to marshal // them into. if (nativeExtensionArray.Length > 0) { checked { // CERT_EXTENSION structures end with a pointer field, which means on all supported // platforms they won't require any padding between elements of the array. nativeExtensions.rgExtension = Marshal.AllocCoTaskMem(Marshal.SizeOf(typeof(CERT_EXTENSION)) * nativeExtensionArray.Length); for (int i = 0; i < nativeExtensionArray.Length; ++i) { ulong offset = (uint)i * (uint)Marshal.SizeOf(typeof(CERT_EXTENSION)); ulong next = offset + (ulong)nativeExtensions.rgExtension.ToInt64(); IntPtr nextExtensionAddr = new IntPtr((long)next); Marshal.StructureToPtr(nativeExtensionArray[i], nextExtensionAddr, false); } } } // // Now that all of the needed data structures are setup, we can create the certificate // unsafe { fixed(byte *pSubjectName = &subjectName[0]) { // Create a CRYPTOAPI_BLOB for the subject of the cert CapiNative.CRYPTOAPI_BLOB nativeSubjectName = new CapiNative.CRYPTOAPI_BLOB(); nativeSubjectName.cbData = subjectName.Length; nativeSubjectName.pbData = new IntPtr(pSubjectName); // Now that we've converted all the inputs to native data structures, we can generate // the self signed certificate for the input key. SafeCertificateContextHandle selfSignedCertHandle = UnsafeNativeMethods.CertCreateSelfSignCertificate(key, ref nativeSubjectName, creationOptions, IntPtr.Zero, ref nativeSignatureAlgorithm, ref nativeStartTime, ref nativeEndTime, ref nativeExtensions); if (selfSignedCertHandle.IsInvalid) { throw new CryptographicException(Marshal.GetLastWin32Error()); } return(selfSignedCertHandle); } } } finally { // // In order to release all resources held by the CERT_EXTENSIONS we need to do three things // 1. Destroy each structure marshaled into the native CERT_EXTENSION array // 2. Release the memory used for the CERT_EXTENSION array // 3. Release the memory used in each individual CERT_EXTENSION // // Release each extension marshaled into the native buffer as well if (nativeExtensions.rgExtension != IntPtr.Zero) { for (int i = 0; i < nativeExtensionArray.Length; ++i) { ulong offset = (uint)i * (uint)Marshal.SizeOf(typeof(CERT_EXTENSION)); ulong next = offset + (ulong)nativeExtensions.rgExtension.ToInt64(); IntPtr nextExtensionAddr = new IntPtr((long)next); Marshal.DestroyStructure(nextExtensionAddr, typeof(CERT_EXTENSION)); } Marshal.FreeCoTaskMem(nativeExtensions.rgExtension); } // If we allocated memory for any extensions, make sure to free it now for (int i = 0; i < nativeExtensionArray.Length; ++i) { if (nativeExtensionArray[i].Value.pbData != IntPtr.Zero) { Marshal.FreeCoTaskMem(nativeExtensionArray[i].Value.pbData); } } } }
internal static SafeCertContextHandle CreateSelfSignedCertificate(CngKey key, bool takeOwnershipOfKey, byte[] subjectName, X509CertificateCreationOptions creationOptions, string signatureAlgorithmOid, DateTime startTime, DateTime endTime, X509ExtensionCollection extensions) { Debug.Assert(key != null, "key != null"); Debug.Assert(subjectName != null, "subjectName != null"); Debug.Assert(!String.IsNullOrEmpty(signatureAlgorithmOid), "!String.IsNullOrEmpty(signatureAlgorithmOid)"); Debug.Assert(extensions != null, "extensions != null"); // Create an algorithm identifier structure for the signature algorithm CapiNative.CRYPT_ALGORITHM_IDENTIFIER nativeSignatureAlgorithm = new CapiNative.CRYPT_ALGORITHM_IDENTIFIER(); nativeSignatureAlgorithm.pszObjId = signatureAlgorithmOid; nativeSignatureAlgorithm.Parameters = new CapiNative.CRYPTOAPI_BLOB(); nativeSignatureAlgorithm.Parameters.cbData = 0; nativeSignatureAlgorithm.Parameters.pbData = IntPtr.Zero; // Convert the begin and expire dates to system time structures Win32Native.SYSTEMTIME nativeStartTime = new Win32Native.SYSTEMTIME(startTime); Win32Native.SYSTEMTIME nativeEndTime = new Win32Native.SYSTEMTIME(endTime); // Map the extensions into CERT_EXTENSIONS. This involves several steps to get the // CERT_EXTENSIONS ready for interop with the native APIs. // 1. Build up the CERT_EXTENSIONS structure in managed code // 2. For each extension, create a managed CERT_EXTENSION structure; this requires allocating // native memory for the blob pointer in the CERT_EXTENSION. These extensions are stored in // the nativeExtensionArray variable. // 3. Get a block of native memory that can hold a native array of CERT_EXTENSION structures. // This is the block referenced by the CERT_EXTENSIONS structure. // 4. For each of the extension structures created in step 2, marshal the extension into the // native buffer allocated in step 3. CERT_EXTENSIONS nativeExtensions = new CERT_EXTENSIONS(); nativeExtensions.cExtension = extensions.Count; CERT_EXTENSION[] nativeExtensionArray = new CERT_EXTENSION[extensions.Count]; // Run this in a CER to ensure that we release any native memory allocated for the certificate // extensions. RuntimeHelpers.PrepareConstrainedRegions(); try { // Copy over each extension into a native extension structure, including allocating native // memory for its blob if necessary. for (int i = 0; i < extensions.Count; ++i) { nativeExtensionArray[i] = new CERT_EXTENSION(); nativeExtensionArray[i].pszObjId = extensions[i].Oid.Value; nativeExtensionArray[i].fCritical = extensions[i].Critical; nativeExtensionArray[i].Value = new CapiNative.CRYPTOAPI_BLOB(); nativeExtensionArray[i].Value.cbData = extensions[i].RawData.Length; if (nativeExtensionArray[i].Value.cbData > 0) { nativeExtensionArray[i].Value.pbData = Marshal.AllocCoTaskMem(nativeExtensionArray[i].Value.cbData); Marshal.Copy(extensions[i].RawData, 0, nativeExtensionArray[i].Value.pbData, nativeExtensionArray[i].Value.cbData); } } // Now that we've built up the extension array, create a block of native memory to marshal // them into. if (nativeExtensionArray.Length > 0) { checked { // CERT_EXTENSION structures end with a pointer field, which means on all supported // platforms they won't require any padding between elements of the array. nativeExtensions.rgExtension = Marshal.AllocCoTaskMem(Marshal.SizeOf(typeof(CERT_EXTENSION)) * nativeExtensionArray.Length); for (int i = 0; i < nativeExtensionArray.Length; ++i) { ulong offset = (uint)i * (uint)Marshal.SizeOf(typeof(CERT_EXTENSION)); ulong next = offset + (ulong)nativeExtensions.rgExtension.ToInt64(); IntPtr nextExtensionAddr = new IntPtr((long)next); Marshal.StructureToPtr(nativeExtensionArray[i], nextExtensionAddr, false); } } } // Setup a CRYPT_KEY_PROV_INFO for the key CRYPT_KEY_PROV_INFO keyProvInfo = new CRYPT_KEY_PROV_INFO(); keyProvInfo.pwszContainerName = key.UniqueName; keyProvInfo.pwszProvName = key.Provider.Provider; keyProvInfo.dwProvType = 0; // NCRYPT keyProvInfo.dwFlags = 0; keyProvInfo.cProvParam = 0; keyProvInfo.rgProvParam = IntPtr.Zero; keyProvInfo.dwKeySpec = 0; // // Now that all of the needed data structures are setup, we can create the certificate // SafeCertContextHandle selfSignedCertHandle = null; unsafe { fixed(byte *pSubjectName = &subjectName[0]) { // Create a CRYPTOAPI_BLOB for the subject of the cert CapiNative.CRYPTOAPI_BLOB nativeSubjectName = new CapiNative.CRYPTOAPI_BLOB(); nativeSubjectName.cbData = subjectName.Length; nativeSubjectName.pbData = new IntPtr(pSubjectName); // Now that we've converted all the inputs to native data structures, we can generate // the self signed certificate for the input key. using (SafeNCryptKeyHandle keyHandle = key.Handle) { selfSignedCertHandle = UnsafeNativeMethods.CertCreateSelfSignCertificate(keyHandle, ref nativeSubjectName, creationOptions, ref keyProvInfo, ref nativeSignatureAlgorithm, ref nativeStartTime, ref nativeEndTime, ref nativeExtensions); if (selfSignedCertHandle.IsInvalid) { throw new CryptographicException(Marshal.GetLastWin32Error()); } } } } Debug.Assert(selfSignedCertHandle != null, "selfSignedCertHandle != null"); // Attach a key context to the certificate which will allow Windows to find the private key // associated with the certificate if the NCRYPT_KEY_HANDLE is ephemeral. // is done. using (SafeNCryptKeyHandle keyHandle = key.Handle) { CERT_KEY_CONTEXT keyContext = new CERT_KEY_CONTEXT(); keyContext.cbSize = Marshal.SizeOf(typeof(CERT_KEY_CONTEXT)); keyContext.hNCryptKey = keyHandle.DangerousGetHandle(); keyContext.dwKeySpec = KeySpec.NCryptKey; bool attachedProperty = false; int setContextError = 0; // Run in a CER to ensure accurate tracking of the transfer of handle ownership RuntimeHelpers.PrepareConstrainedRegions(); try { } finally { CertificatePropertySetFlags flags = CertificatePropertySetFlags.None; if (!takeOwnershipOfKey) { // If the certificate is not taking ownership of the key handle, then it should // not release the handle when the context is released. flags |= CertificatePropertySetFlags.NoCryptRelease; } attachedProperty = UnsafeNativeMethods.CertSetCertificateContextProperty(selfSignedCertHandle, CertificateProperty.KeyContext, flags, ref keyContext); setContextError = Marshal.GetLastWin32Error(); // If we succesfully transferred ownership of the key to the certificate, // then we need to ensure that we no longer release its handle. if (attachedProperty && takeOwnershipOfKey) { keyHandle.SetHandleAsInvalid(); } } if (!attachedProperty) { throw new CryptographicException(setContextError); } } return(selfSignedCertHandle); } finally { // // In order to release all resources held by the CERT_EXTENSIONS we need to do three things // 1. Destroy each structure marshaled into the native CERT_EXTENSION array // 2. Release the memory used for the CERT_EXTENSION array // 3. Release the memory used in each individual CERT_EXTENSION // // Release each extension marshaled into the native buffer as well if (nativeExtensions.rgExtension != IntPtr.Zero) { for (int i = 0; i < nativeExtensionArray.Length; ++i) { ulong offset = (uint)i * (uint)Marshal.SizeOf(typeof(CERT_EXTENSION)); ulong next = offset + (ulong)nativeExtensions.rgExtension.ToInt64(); IntPtr nextExtensionAddr = new IntPtr((long)next); Marshal.DestroyStructure(nextExtensionAddr, typeof(CERT_EXTENSION)); } Marshal.FreeCoTaskMem(nativeExtensions.rgExtension); } // If we allocated memory for any extensions, make sure to free it now for (int i = 0; i < nativeExtensionArray.Length; ++i) { if (nativeExtensionArray[i].Value.pbData != IntPtr.Zero) { Marshal.FreeCoTaskMem(nativeExtensionArray[i].Value.pbData); } } } }
// returns a list of IntPtr's; the first IntPtr is the pointer to the CERT_EXTENSIONS // strucuture; the other pointers are pointers that have to be released in order // to avoid leaking memory private static unsafe List<IntPtr> ConvertExtensions(X509ExtensionCollection extensions) { List<IntPtr> ret = new List<IntPtr>(); if (extensions == null && extensions.Count == 0) { ret.Add(IntPtr.Zero); return ret; } int extensionStructSize = Marshal.SizeOf(typeof(CERT_EXTENSION)); // create the pointer to the CERT_EXTENSIONS object CERT_EXTENSIONS extensionsStruct = new CERT_EXTENSIONS(); IntPtr extensionsPtr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(CERT_EXTENSIONS))); ret.Add(extensionsPtr); extensionsStruct.cExtension = extensions.Count; extensionsStruct.rgExtension = Marshal.AllocHGlobal(extensionStructSize * extensions.Count); ; ret.Add(extensionsStruct.rgExtension); Marshal.StructureToPtr(extensionsStruct, extensionsPtr, false); // create the array of CERT_EXTENSION objects CERT_EXTENSION extensionStruct = new CERT_EXTENSION(); byte* workPointer = (byte*)extensionsStruct.rgExtension.ToPointer(); foreach(X509Extension ext in extensions) { // initialize the extension structure extensionStruct.pszObjId = Marshal.StringToHGlobalAnsi(ext.Oid.Value); ret.Add(extensionStruct.pszObjId); extensionStruct.fCritical = ext.Critical ? 1 : 0; byte[] rawData = ext.RawData; extensionStruct.cbData = rawData.Length; extensionStruct.pbData = Marshal.AllocHGlobal(rawData.Length); ; Marshal.Copy(rawData, 0, extensionStruct.pbData, rawData.Length); ret.Add(extensionStruct.pbData); // copy it to unmanaged memory Marshal.StructureToPtr(extensionStruct, new IntPtr(workPointer), false); workPointer += extensionStructSize; } // everything successfully created; return return ret; }
// creates the alternate name extension for the certificate. static void CreateSubjectAltNameExtension( string applicationUri, IList<string> hostNames, ref CERT_EXTENSION pExtension) { int count = hostNames.Count + 1; // initialize extension. pExtension.pszObjId = szOID_SUBJECT_ALT_NAME2; pExtension.fCritical = 0; IntPtr pData = IntPtr.Zero; int dwDataSize = 0; // build list of alternate names. IntPtr pAlternateNames = IntPtr.Zero; IntPtr pEntries = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(CERT_ALT_NAME_ENTRY))*count); // create structure to encode. try { // set application uri. CERT_ALT_NAME_ENTRY pEntry = new CERT_ALT_NAME_ENTRY(); pEntry.dwAltNameChoice = CERT_ALT_NAME_URL; pEntry.Value.pwszURL = Marshal.StringToHGlobalUni(applicationUri); Marshal.StructureToPtr(pEntry, pEntries, false); IntPtr pPos = new IntPtr(pEntries.ToInt64() + Marshal.SizeOf(typeof(CERT_ALT_NAME_ENTRY))); for (int ii = 0; ii < hostNames.Count; ii++) { System.Net.IPAddress ipAddress = null; // check for ip address. if (System.Net.IPAddress.TryParse(hostNames[ii], out ipAddress)) { byte[] bytes = ipAddress.GetAddressBytes(); pEntry.dwAltNameChoice = CERT_ALT_NAME_IP_ADDRESS; pEntry.Value.IPAddress.cbData = bytes.Length; pEntry.Value.IPAddress.pbData = AllocBytes(bytes); } // treat as DNS host name. else { pEntry.dwAltNameChoice = CERT_ALT_NAME_DNS_NAME; pEntry.Value.pwszDNSName = Marshal.StringToHGlobalUni(hostNames[ii]); } Marshal.StructureToPtr(pEntry, pPos, false); pPos = new IntPtr(pPos.ToInt64() + Marshal.SizeOf(typeof(CERT_ALT_NAME_ENTRY))); } CERT_ALT_NAME_INFO alternateNames = new CERT_ALT_NAME_INFO(); alternateNames.cAltEntry = count; alternateNames.rgAltEntry = pEntries; pAlternateNames = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(CERT_ALT_NAME_INFO))); Marshal.StructureToPtr(alternateNames, pAlternateNames, false); // calculate amount of memory required. int bResult = NativeMethods.CryptEncodeObjectEx( X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, szOID_SUBJECT_ALT_NAME2, // X509_ALTERNATE_NAME, pAlternateNames, 0, IntPtr.Zero, IntPtr.Zero, ref dwDataSize); if (bResult == 0) { throw new InvalidOperationException("Could not get size for subject alternate name extension."); } // allocate memory. pData = Marshal.AllocHGlobal(dwDataSize); // encode blob. bResult = NativeMethods.CryptEncodeObjectEx( X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, szOID_SUBJECT_ALT_NAME2, // X509_ALTERNATE_NAME, pAlternateNames, 0, IntPtr.Zero, pData, ref dwDataSize); if (bResult == 0) { throw new InvalidOperationException("Could not create subject alternate name extension."); } pExtension.Value.cbData = dwDataSize; pExtension.Value.pbData = pData; pData = IntPtr.Zero; } finally { if (pData != IntPtr.Zero) { Marshal.FreeHGlobal(pData); } if (pAlternateNames != IntPtr.Zero) { Marshal.DestroyStructure(pAlternateNames, typeof(CERT_ALT_NAME_INFO)); Marshal.FreeHGlobal(pAlternateNames); } if (pEntries != IntPtr.Zero) { IntPtr pPos = pEntries; for (int ii = 0; ii < count; ii++) { CERT_ALT_NAME_ENTRY pEntry = (CERT_ALT_NAME_ENTRY)Marshal.PtrToStructure(pPos, typeof(CERT_ALT_NAME_ENTRY)); pPos = new IntPtr(pPos.ToInt64() + Marshal.SizeOf(typeof(CERT_ALT_NAME_ENTRY))); switch (pEntry.dwAltNameChoice) { case CERT_ALT_NAME_URL: { Marshal.FreeHGlobal(pEntry.Value.pwszURL); break; } case CERT_ALT_NAME_DNS_NAME: { Marshal.FreeHGlobal(pEntry.Value.pwszDNSName); break; } case CERT_ALT_NAME_IP_ADDRESS: { Marshal.FreeHGlobal(pEntry.Value.IPAddress.pbData); break; } } } Marshal.FreeHGlobal(pEntries); } } }
// creates the extended key usage extension static void CreateExtendedKeyUsageExtension(ref CERT_EXTENSION pExtension) { // build list of allowed key uses IntPtr[] allowedUses = new IntPtr[2]; allowedUses[0] = Marshal.StringToHGlobalAnsi(szOID_PKIX_KP_SERVER_AUTH); allowedUses[1] = Marshal.StringToHGlobalAnsi(szOID_PKIX_KP_CLIENT_AUTH); CERT_ENHKEY_USAGE usage; usage.cUsageIdentifier = 2; usage.rgpszUsageIdentifier = Marshal.AllocHGlobal(IntPtr.Size*2); Marshal.Copy(allowedUses, 0, usage.rgpszUsageIdentifier, allowedUses.Length); // initialize extension. pExtension.pszObjId = szOID_ENHANCED_KEY_USAGE; pExtension.fCritical = 1; GCHandle hUsage = GCHandle.Alloc(usage, GCHandleType.Pinned); IntPtr pData = IntPtr.Zero; int dwDataSize = 0; try { // calculate amount of memory required. int bResult = NativeMethods.CryptEncodeObjectEx( X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, szOID_ENHANCED_KEY_USAGE, // X509_ENHANCED_KEY_USAGE, hUsage.AddrOfPinnedObject(), 0, IntPtr.Zero, IntPtr.Zero, ref dwDataSize); if (bResult == 0) { throw new InvalidOperationException("Could not get size for extended key usage extension."); } // allocate memory. pData = Marshal.AllocHGlobal(dwDataSize); // encode blob. bResult = NativeMethods.CryptEncodeObjectEx( X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, szOID_ENHANCED_KEY_USAGE, // X509_ENHANCED_KEY_USAGE, hUsage.AddrOfPinnedObject(), 0, IntPtr.Zero, pData, ref dwDataSize); if (bResult == 0) { throw new InvalidOperationException("Could not create extended key usage extension."); } pExtension.Value.cbData = dwDataSize; pExtension.Value.pbData = pData; pData = IntPtr.Zero; } finally { if (pData != IntPtr.Zero) { Marshal.FreeHGlobal(pData); } Marshal.FreeHGlobal(allowedUses[0]); Marshal.FreeHGlobal(allowedUses[1]); Marshal.FreeHGlobal(usage.rgpszUsageIdentifier); if (hUsage.IsAllocated) { hUsage.Free(); } } }
// creates the key usage constraints extension. static void CreateKeyUsageExtension(ref CERT_EXTENSION pExtension, bool isCA) { // build list of allowed key uses int allowedUses = 0; if (isCA) { allowedUses |= CERT_KEY_CERT_SIGN_KEY_USAGE; allowedUses |= CERT_OFFLINE_CRL_SIGN_KEY_USAGE; allowedUses |= CERT_CRL_SIGN_KEY_USAGE; allowedUses |= CERT_NON_REPUDIATION_KEY_USAGE; } else { allowedUses |= CERT_DATA_ENCIPHERMENT_KEY_USAGE; allowedUses |= CERT_DIGITAL_SIGNATURE_KEY_USAGE; allowedUses |= CERT_KEY_ENCIPHERMENT_KEY_USAGE; allowedUses |= CERT_NON_REPUDIATION_KEY_USAGE; allowedUses |= CERT_KEY_CERT_SIGN_KEY_USAGE; } GCHandle hAllowedUses = GCHandle.Alloc(allowedUses, GCHandleType.Pinned); CRYPT_BIT_BLOB usage; usage.cbData = 1; usage.pbData = hAllowedUses.AddrOfPinnedObject(); usage.cUnusedBits = 0; // initialize extension. pExtension.pszObjId = szOID_KEY_USAGE; pExtension.fCritical = 1; GCHandle hUsage = GCHandle.Alloc(usage, GCHandleType.Pinned); IntPtr pData = IntPtr.Zero; int dwDataSize = 0; try { // calculate amount of memory required. int bResult = NativeMethods.CryptEncodeObjectEx( X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, szOID_KEY_USAGE, // X509_KEY_USAGE, hUsage.AddrOfPinnedObject(), 0, IntPtr.Zero, IntPtr.Zero, ref dwDataSize); if (bResult == 0) { throw new InvalidOperationException("Could not get size for key usage extension."); } // allocate memory. pData = Marshal.AllocHGlobal(dwDataSize); // encode blob. bResult = NativeMethods.CryptEncodeObjectEx( X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, szOID_KEY_USAGE, // X509_KEY_USAGE, hUsage.AddrOfPinnedObject(), 0, IntPtr.Zero, pData, ref dwDataSize); if (bResult == 0) { throw new InvalidOperationException("Could not create key usage extension."); } pExtension.Value.cbData = dwDataSize; pExtension.Value.pbData = pData; pData = IntPtr.Zero; } finally { if (pData != IntPtr.Zero) { Marshal.FreeHGlobal(pData); } if (hAllowedUses.IsAllocated) { hAllowedUses.Free(); } if (hUsage.IsAllocated) { hUsage.Free(); } } }
// creates the basic constraints extension. static void CreateBasicConstraintsExtension(ref CERT_EXTENSION pExtension, bool isCA) { // set the certificate as a non-CA certificate. CERT_BASIC_CONSTRAINTS2_INFO constraints; constraints.fCA = (isCA)?1:0; constraints.fPathLenConstraint = 0; constraints.dwPathLenConstraint = 0; pExtension.pszObjId = szOID_BASIC_CONSTRAINTS2; pExtension.fCritical = 1; GCHandle hConstraints = GCHandle.Alloc(constraints, GCHandleType.Pinned); IntPtr pData = IntPtr.Zero; int dwDataSize = 0; try { // calculate amount of memory required. int bResult = NativeMethods.CryptEncodeObjectEx( X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, szOID_BASIC_CONSTRAINTS2, // X509_BASIC_CONSTRAINTS2, hConstraints.AddrOfPinnedObject(), 0, IntPtr.Zero, IntPtr.Zero, ref dwDataSize); if (bResult == 0) { throw new InvalidOperationException("Could not get size for basic constraints extension."); } // allocate memory. pData = Marshal.AllocHGlobal(dwDataSize); // encode blob. bResult = NativeMethods.CryptEncodeObjectEx( X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, szOID_BASIC_CONSTRAINTS2, // X509_BASIC_CONSTRAINTS2, hConstraints.AddrOfPinnedObject(), 0, IntPtr.Zero, pData, ref dwDataSize); if (bResult == 0) { throw new InvalidOperationException("Could not create for basic constraints extension."); } pExtension.Value.cbData = dwDataSize; pExtension.Value.pbData = pData; pData = IntPtr.Zero; } finally { if (pData != IntPtr.Zero) { Marshal.FreeHGlobal(pData); } if (hConstraints.IsAllocated) { hConstraints.Free(); } } }
// creates the basic constraints extension. static void CreateAuthorityKeyIdentifierExtension( ref CERT_EXTENSION pExtension, ref CRYPT_DATA_BLOB pKeyId) { // set the certificate as a non-CA certificate. CERT_AUTHORITY_KEY_ID2_INFO keyInfo = new CERT_AUTHORITY_KEY_ID2_INFO(); keyInfo.KeyId.cbData = pKeyId.cbData; keyInfo.KeyId.pbData = pKeyId.pbData; pExtension.pszObjId = szOID_AUTHORITY_KEY_IDENTIFIER2; pExtension.fCritical = 0; GCHandle hKeyInfo = GCHandle.Alloc(keyInfo, GCHandleType.Pinned); IntPtr pData = IntPtr.Zero; int dwDataSize = 0; try { // calculate amount of memory required. int bResult = NativeMethods.CryptEncodeObjectEx( X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, szOID_AUTHORITY_KEY_IDENTIFIER2, // X509_AUTHORITY_KEY_ID, hKeyInfo.AddrOfPinnedObject(), 0, IntPtr.Zero, IntPtr.Zero, ref dwDataSize); if (bResult == 0) { throw new InvalidOperationException("Could not get size for basic constraints extension."); } // allocate memory. pData = Marshal.AllocHGlobal(dwDataSize); // encode blob. bResult = NativeMethods.CryptEncodeObjectEx( X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, szOID_AUTHORITY_KEY_IDENTIFIER2, // X509_AUTHORITY_KEY_ID, hKeyInfo.AddrOfPinnedObject(), 0, IntPtr.Zero, pData, ref dwDataSize); if (bResult == 0) { throw new InvalidOperationException("Could not create for basic constraints extension."); } pExtension.Value.cbData = dwDataSize; pExtension.Value.pbData = pData; pData = IntPtr.Zero; } finally { if (pData != IntPtr.Zero) { Marshal.FreeHGlobal(pData); } if (hKeyInfo.IsAllocated) { hKeyInfo.Free(); } } }
// creates the basic constraints extension. private static void CreateSubjectKeyIdentifierExtension(ref CERT_EXTENSION pExtension, ref CRYPT_DATA_BLOB pKeyId) { pExtension.pszObjId = szOID_SUBJECT_KEY_IDENTIFIER; pExtension.fCritical = 0; GCHandle hKey = GCHandle.Alloc(pKeyId, GCHandleType.Pinned); IntPtr pData = IntPtr.Zero; int dwDataSize = 0; try { // calculate amount of memory required. int bResult = NativeMethods.CryptEncodeObjectEx( X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, szOID_SUBJECT_KEY_IDENTIFIER, hKey.AddrOfPinnedObject(), 0, IntPtr.Zero, IntPtr.Zero, ref dwDataSize); if (bResult == 0) { throw new InvalidOperationException("Could not get size for subject key info extension."); } // allocate memory. pData = Marshal.AllocHGlobal(dwDataSize); // encode blob. bResult = NativeMethods.CryptEncodeObjectEx( X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, szOID_SUBJECT_KEY_IDENTIFIER, hKey.AddrOfPinnedObject(), 0, IntPtr.Zero, pData, ref dwDataSize); if (bResult == 0) { throw new InvalidOperationException("Could not create for subject key info extension."); } pExtension.Value.cbData = dwDataSize; pExtension.Value.pbData = pData; pData = IntPtr.Zero; } finally { if (pData != IntPtr.Zero) { Marshal.FreeHGlobal(pData); } if (hKey.IsAllocated) { hKey.Free(); } } }
/// <summary> /// Creates the certificate and adds it to the store. /// </summary> private static string CreateSelfSignedCertificate( IntPtr hProvider, IntPtr hStore, bool useMachineStore, string applicationName, string applicationUri, string subjectName, IList<string> hostNames, ushort keySize, ushort lifetimeInMonths, ushort algorithm = 0) { IntPtr hKey = IntPtr.Zero; IntPtr pKpi = IntPtr.Zero; IntPtr pThumbprint = IntPtr.Zero; IntPtr pContext = IntPtr.Zero; IntPtr pAlgorithmId = IntPtr.Zero; IntPtr pNewContext = IntPtr.Zero; CRYPT_DATA_BLOB publicKeyId = new CRYPT_DATA_BLOB(); CERT_NAME_BLOB subjectNameBlob = new CERT_NAME_BLOB(); SYSTEMTIME stValidTo = new SYSTEMTIME(); CERT_EXTENSIONS extensions = new CERT_EXTENSIONS(); CRYPT_DATA_BLOB friendlyName = new CRYPT_DATA_BLOB(); GCHandle hValidTo = new GCHandle(); GCHandle hExtensionList = new GCHandle(); GCHandle hSubjectNameBlob = new GCHandle(); GCHandle hFriendlyName = new GCHandle(); try { // create a new key pair. int bResult = NativeMethods.CryptGenKey( hProvider, AT_KEYEXCHANGE, CRYPT_EXPORTABLE | (keySize << 16), ref hKey); if (bResult == 0) { Throw("Could not generate a new key pair. Error={0:X8}", Marshal.GetLastWin32Error()); } // gey the public key identifier. GetPublicKeyIdentifier(hProvider, ref publicKeyId); // construct the certificate subject name. CreateX500Name(subjectName, ref subjectNameBlob); GCHandle hSubjectName = GCHandle.Alloc(subjectNameBlob, GCHandleType.Pinned); // allocate memory for all possible extensions. extensions.cExtension = 0; extensions.rgExtension = Marshal.AllocHGlobal(6 * Marshal.SizeOf(typeof(CERT_EXTENSION))); // create the subject key info extension. IntPtr pPos = extensions.rgExtension; CERT_EXTENSION extension = new CERT_EXTENSION(); CreateSubjectKeyIdentifierExtension(ref extension, ref publicKeyId); Marshal.StructureToPtr(extension, pPos, false); pPos = new IntPtr(pPos.ToInt64() + Marshal.SizeOf(typeof(CERT_EXTENSION))); extensions.cExtension++; // create the authority key info extension. extension = new CERT_EXTENSION(); CreateAuthorityKeyIdentifierExtension(ref extension, ref publicKeyId); Marshal.StructureToPtr(extension, pPos, false); pPos = new IntPtr(pPos.ToInt64() + Marshal.SizeOf(typeof(CERT_EXTENSION))); extensions.cExtension++; // create the basic constraints extension. extension = new CERT_EXTENSION(); CreateBasicConstraintsExtension(ref extension, false); Marshal.StructureToPtr(extension, pPos, false); pPos = new IntPtr(pPos.ToInt64() + Marshal.SizeOf(typeof(CERT_EXTENSION))); extensions.cExtension++; // create the key usage extension. extension = new CERT_EXTENSION(); CreateKeyUsageExtension(ref extension, false); Marshal.StructureToPtr(extension, pPos, false); pPos = new IntPtr(pPos.ToInt64() + Marshal.SizeOf(typeof(CERT_EXTENSION))); extensions.cExtension++; // create the extended key usage extension. extension = new CERT_EXTENSION(); CreateExtendedKeyUsageExtension(ref extension); Marshal.StructureToPtr(extension, pPos, false); pPos = new IntPtr(pPos.ToInt64() + Marshal.SizeOf(typeof(CERT_EXTENSION))); extensions.cExtension++; // create the subject alternate name extension. extension = new CERT_EXTENSION(); CreateSubjectAltNameExtension(applicationUri, hostNames, ref extension); Marshal.StructureToPtr(extension, pPos, false); pPos = new IntPtr(pPos.ToInt64() + Marshal.SizeOf(typeof(CERT_EXTENSION))); extensions.cExtension++; // set the expiration date. DateTime validTo = DateTime.UtcNow.AddMonths(lifetimeInMonths); System.Runtime.InteropServices.ComTypes.FILETIME ftValidTo = new System.Runtime.InteropServices.ComTypes.FILETIME(); ulong ticks = (ulong)(validTo.Ticks - new DateTime(1601, 1, 1).Ticks); ftValidTo.dwHighDateTime = (int)((0xFFFFFFFF00000000 & (ulong)ticks) >> 32); ftValidTo.dwLowDateTime = (int)((ulong)ticks & 0x00000000FFFFFFFF); NativeMethods.FileTimeToSystemTime(ref ftValidTo, ref stValidTo); // specify what key is being used to sign the certificate. CRYPT_KEY_PROV_INFO kpi = new CRYPT_KEY_PROV_INFO(); kpi.pwszContainerName = KEY_CONTAINER_NAME; // must be the same as the hProvider kpi.pwszProvName = DEFAULT_CRYPTO_PROVIDER; kpi.dwProvType = PROV_RSA_FULL; kpi.dwFlags = CERT_SET_KEY_CONTEXT_PROP_ID; kpi.dwKeySpec = AT_KEYEXCHANGE; if (useMachineStore) { kpi.dwFlags |= CRYPT_MACHINE_KEYSET; } else { kpi.dwFlags |= CRYPT_USER_KEYSET; } pKpi = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(CRYPT_KEY_PROV_INFO))); Marshal.StructureToPtr(kpi, pKpi, false); hValidTo = GCHandle.Alloc(stValidTo, GCHandleType.Pinned); hExtensionList = GCHandle.Alloc(extensions, GCHandleType.Pinned); hSubjectNameBlob = GCHandle.Alloc(subjectNameBlob, GCHandleType.Pinned); if (algorithm == 1) { CRYPT_ALGORITHM_IDENTIFIER algorithmID = new CRYPT_ALGORITHM_IDENTIFIER(); algorithmID.pszObjId = "1.2.840.113549.1.1.11"; //SHA256 pAlgorithmId = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(CRYPT_ALGORITHM_IDENTIFIER))); Marshal.StructureToPtr(algorithmID, pAlgorithmId, false); //create the certificate pContext = NativeMethods.CertCreateSelfSignCertificate( hProvider, hSubjectNameBlob.AddrOfPinnedObject(), 0, pKpi, pAlgorithmId, IntPtr.Zero, hValidTo.AddrOfPinnedObject(), hExtensionList.AddrOfPinnedObject()); } else { // (default) create the certificate. pContext = NativeMethods.CertCreateSelfSignCertificate( hProvider, hSubjectNameBlob.AddrOfPinnedObject(), 0, pKpi, IntPtr.Zero, IntPtr.Zero, hValidTo.AddrOfPinnedObject(), hExtensionList.AddrOfPinnedObject()); } if (pContext == IntPtr.Zero) { Throw("Could not create self-signed certificate. Error={0:X8}", Marshal.GetLastWin32Error()); } // get the thumbprint. int dwThumbprintSize = 20; pThumbprint = Marshal.AllocHGlobal(dwThumbprintSize); bResult = NativeMethods.CertGetCertificateContextProperty( pContext, CERT_SHA1_HASH_PROP_ID, pThumbprint, ref dwThumbprintSize); if (bResult == 0) { Throw("Could not get the thumbprint of the new certificate. Error={0:X8}", Marshal.GetLastWin32Error()); } byte[] bytes = new byte[dwThumbprintSize]; Marshal.Copy(pThumbprint, bytes, 0, dwThumbprintSize); string thumbprint = Utils.ToHexString(bytes); // set the friendly name. friendlyName.pbData = Marshal.StringToHGlobalUni(applicationName); friendlyName.cbData = (applicationName.Length+1)*Marshal.SizeOf(typeof(ushort)); hFriendlyName = GCHandle.Alloc(friendlyName, GCHandleType.Pinned); bResult = NativeMethods.CertSetCertificateContextProperty( pContext, CERT_FRIENDLY_NAME_PROP_ID, 0, hFriendlyName.AddrOfPinnedObject()); if (bResult == 0) { Throw("Could not set the friendly name for the certificate. Error={0:X8}", Marshal.GetLastWin32Error()); } // add into store. bResult = NativeMethods.CertAddCertificateContextToStore( hStore, pContext, CERT_STORE_ADD_REPLACE_EXISTING, ref pNewContext); if (bResult == 0) { Throw("Could not add the certificate to the store. Error={0:X8}", Marshal.GetLastWin32Error()); } return thumbprint; } finally { if (pContext != IntPtr.Zero) { NativeMethods.CertFreeCertificateContext(pContext); } if (pNewContext != IntPtr.Zero) { NativeMethods.CertFreeCertificateContext(pNewContext); } if (friendlyName.pbData != IntPtr.Zero) { Marshal.FreeHGlobal(friendlyName.pbData); } if (pThumbprint != IntPtr.Zero) { Marshal.FreeHGlobal(pThumbprint); } if (pAlgorithmId != IntPtr.Zero) { Marshal.DestroyStructure(pAlgorithmId, typeof(CRYPT_ALGORITHM_IDENTIFIER)); Marshal.FreeHGlobal(pAlgorithmId); } if (hValidTo.IsAllocated) hValidTo.Free(); if (hExtensionList.IsAllocated) hExtensionList.Free(); if (hSubjectNameBlob.IsAllocated) hSubjectNameBlob.Free(); if (hFriendlyName.IsAllocated) hFriendlyName.Free(); if (pKpi != IntPtr.Zero) { Marshal.DestroyStructure(pKpi, typeof(CRYPT_KEY_PROV_INFO)); Marshal.FreeHGlobal(pKpi); } DeleteExtensions(ref extensions.rgExtension, extensions.cExtension); DeleteX500Name(ref subjectNameBlob); if (publicKeyId.pbData != IntPtr.Zero) { Marshal.FreeHGlobal(publicKeyId.pbData); } if (hKey != IntPtr.Zero) { NativeMethods.CryptDestroyKey(hKey); } } }