// KeyToPfx.cs using System; using System.IO; using System.Runtime.InteropServices; using System.ComponentModel; using System.Security.Cryptography.X509Certificates; using System.Security; namespace JenSign { public class Win32 { [DllImport("advapi32.dll", SetLastError=true)] public static extern bool CryptAcquireContext( ref IntPtr hProv, string pszContainer, string pszProvider, uint dwProvType, uint dwFlags) ; [DllImport("advapi32.dll")] public static extern bool CryptReleaseContext( IntPtr hProv, uint dwFlags) ; [DllImport("crypt32.dll", SetLastError=true)] public static extern bool CryptExportPublicKeyInfoEx( IntPtr hProv, uint dwKeySpec, uint dwCertEncodingType, String pxzPublicKeyObjId, uint dwFlags, IntPtr pvAuxInfo, IntPtr pInfo, ref uint pcbInfo ) ; [DllImport("crypt32.dll", SetLastError=true)] public static extern int CertGetPublicKeyLength( uint dwCertEncodingType, IntPtr pPublicKeyInfo) ; [DllImport("crypt32.dll", SetLastError=true)] public static extern IntPtr CertCreateSelfSignCertificate( IntPtr hProv, ref CERT_NAME_BLOB pSubjectIssuerBlob, uint dwFlagsm, ref CRYPT_KEY_PROV_INFO pKeyProvInfo, IntPtr pSignatureAlgorithm, IntPtr pStartTime, IntPtr pEndTime, IntPtr other) ; [DllImport("crypt32.dll", SetLastError=true)] public static extern bool CertStrToName( uint dwCertEncodingType, String pszX500, uint dwStrType, IntPtr pvReserved, [In, Out] byte[] pbEncoded, ref uint pcbEncoded, IntPtr other); [DllImport("crypt32.dll", CharSet=CharSet.Auto, SetLastError=true)] //overloaded public static extern IntPtr CertOpenStore( [MarshalAs(UnmanagedType.LPStr)] String storeProvider, uint dwMsgAndCertEncodingType, IntPtr hCryptProv, uint dwFlags, IntPtr pvPara) ; [DllImport("crypt32.dll", CharSet=CharSet.Auto, SetLastError=true)] //overloaded public static extern IntPtr CertOpenStore( [MarshalAs(UnmanagedType.LPStr)] String storeProvider, uint dwMsgAndCertEncodingType, IntPtr hCryptProv, uint dwFlags, String cchNameString) ; [DllImport("crypt32.dll", SetLastError=true)] public static extern IntPtr CertFindCertificateInStore( IntPtr hCertStore, uint dwCertEncodingType, uint dwFindFlags, uint dwFindType, IntPtr pvFindPara, IntPtr pPrevCertCntxt) ; [DllImport("crypt32.dll", SetLastError=true)] public static extern bool CertAddCertificateContextToStore( IntPtr hCertStore, IntPtr pCertContext, uint dwAddDisposition, IntPtr ppStoreContext); [DllImport("crypt32.dll", SetLastError=true)] public static extern bool CertCloseStore( IntPtr hCertStore, uint dwFlags) ; [DllImport("crypt32.dll", SetLastError=true)] public static extern bool PFXExportCertStoreEx( IntPtr hCertStore, ref CRYPT_DATA_BLOB pPFX, [MarshalAs(UnmanagedType.LPWStr)] String szPassword, IntPtr pvReserved, uint dwFlags); [DllImport("crypt32.dll", SetLastError=true)] public static extern bool CertFreeCertificateContext( IntPtr pCertContext ); } [StructLayout(LayoutKind.Sequential)] public struct CERT_NAME_BLOB { public int cbData; public IntPtr pbData; } [StructLayout(LayoutKind.Sequential)] public struct CRYPT_DATA_BLOB { public int cbData; public IntPtr pbData; } [StructLayout(LayoutKind.Sequential)] public struct CERT_CONTEXT { public uint dwCertEncodingType; public IntPtr pbCertEncoded; public int cbCertEncoded; public IntPtr pCertInfo; public IntPtr hCertStore; } [StructLayout(LayoutKind.Sequential)] public struct CRYPT_KEY_PROV_INFO { [MarshalAs(UnmanagedType.LPWStr)] public String pwszContainerName; [MarshalAs(UnmanagedType.LPWStr)] public String pwszProvName; public uint dwProvType; public uint dwFlags; public uint cProvParam; public IntPtr rgProvParam; public uint dwKeySpec; } public class KeyToPfx { const uint X509_ASN_ENCODING = 0x00000001; const uint PKCS_7_ASN_ENCODING = 0x00010000; const uint CRYPT_VERIFYCONTEXT = 0xF0000000; //no private key access flag const uint CRYPT_MACHINE_KEYSET = 0x00000020; const uint CERT_CREATE_SELFSIGN_NO_SIGN = 1 ; const uint CERT_STORE_ADD_NEW = 1; const uint PROV_RSA_FULL = 0x00000001; const String MS_DEF_PROV = "Microsoft Base Cryptographic Provider v1.0"; const String MS_STRONG_PROV = "Microsoft Strong Cryptographic Provider"; const String MS_ENHANCED_PROV = "Microsoft Enhanced Cryptographic Provider v1.0"; const String sz_CERT_STORE_PROV_MEMORY = "Memory"; const String szOID_RSA_RSA = "1.2.840.113549.1.1.1" ; const uint AT_KEYEXCHANGE = 0x00000001; const uint AT_SIGNATURE = 0x00000002; static String[] keyspecs = {null, "AT_KEYEXCHANGE", "AT_SIGNATURE"}; const uint CERT_SYSTEM_STORE_CURRENT_USER = 0x00010000; const uint CERT_SYSTEM_STORE_LOCAL_MACHINE = 0x00020000; const uint CERT_STORE_READONLY_FLAG = 0x00008000; const uint CERT_STORE_OPEN_EXISTING_FLAG = 0x00004000; const uint CERT_FIND_PUBLIC_KEY = 6 <<16; static uint ENCODING_TYPE = PKCS_7_ASN_ENCODING | X509_ASN_ENCODING ; static uint CSPKEYTYPE = 0; // default is Current User; could also be CRYPT_MACHINE_KEYSET static uint storetype = CERT_SYSTEM_STORE_CURRENT_USER ; static string MYSTORE = "MY"; static uint KEYSPEC = AT_SIGNATURE; //default static byte[] pfxblob = null; public static void Main(String[] args){ if(args.Length <1 || args.Length >2 ) { Console.WriteLine("Usage: Keytopfx [ E | 1 | S | 2 ] ") ; return; } String keycontainer = args[0]; if (args.Length == 2) { if(args[1].ToUpper() == "E" || args[1] == "1") KEYSPEC = AT_KEYEXCHANGE; if(args[1].ToUpper() == "S" || args[1] == "2") KEYSPEC = AT_SIGNATURE; } IntPtr hProv = IntPtr.Zero; IntPtr hCertCntxt = IntPtr.Zero; IntPtr hCertStore = IntPtr.Zero; IntPtr pKeyInfo = IntPtr.Zero; String DN = "CN=Dummy Unsigned Certificate"; uint cspflags = CSPKEYTYPE; uint pcbInfo = 0; Console.WriteLine(); if(!Win32.CryptAcquireContext(ref hProv, keycontainer, MS_DEF_PROV, PROV_RSA_FULL, cspflags)) { Console.WriteLine("Couldn't get crypt context for keycontainer {0}", keycontainer); return; } if(!Win32.CryptExportPublicKeyInfoEx(hProv, KEYSPEC, ENCODING_TYPE, szOID_RSA_RSA, 0, IntPtr.Zero, IntPtr.Zero, ref pcbInfo)) { Console.WriteLine("Keycontainer does not have a keypair of type {0}", keyspecs[KEYSPEC]); Win32.CryptReleaseContext(hProv,0) ; return; } pKeyInfo = Marshal.AllocHGlobal((int)pcbInfo); Win32.CryptExportPublicKeyInfoEx(hProv, KEYSPEC, ENCODING_TYPE, szOID_RSA_RSA, 0, IntPtr.Zero, pKeyInfo, ref pcbInfo) ; //Console.WriteLine("\nMemory allocated for exported CERT_PUBLIC_KEY_INFO {0} bytes", pcbInfo) ; int keylength = Win32.CertGetPublicKeyLength(ENCODING_TYPE, pKeyInfo); Console.WriteLine("Keylength: {0} bits", keylength); uint openflags = storetype | CERT_STORE_READONLY_FLAG | CERT_STORE_OPEN_EXISTING_FLAG; hCertStore = Win32.CertOpenStore("System", ENCODING_TYPE, IntPtr.Zero, openflags, MYSTORE ); hCertCntxt = Win32.CertFindCertificateInStore(hCertStore, ENCODING_TYPE, 0, CERT_FIND_PUBLIC_KEY, pKeyInfo, IntPtr.Zero); if(hCertCntxt != IntPtr.Zero) Console.WriteLine("Found certificate matching the specified container keypair\n"); else { Console.WriteLine("NO certificate matching the specified container keypair.\nCreating an unsigned dummy certificate .."); hCertCntxt = CreateUnsignedCertCntxt(keycontainer, KEYSPEC, cspflags, DN) ; if(hCertCntxt != IntPtr.Zero) Console.WriteLine("Created an unsigned-certificate context\n"); } Console.WriteLine("Trying to export {0} keypair and certificate to pkcs12 ... ", keyspecs[KEYSPEC]) ; // pfxblob = CertCntxtToPFX(hCertCntxt, "pswd"); //export the pfx blob with either store cert or created unsigned dummy cert. try{ X509Certificate cert = new X509Certificate(hCertCntxt) ; //create certificate object from cert context. SecureString pswd = GetSecPswd() ; pfxblob = cert.Export(X509ContentType.Pkcs12, pswd); } catch(Exception exc) { Console.WriteLine(exc.Message) ; pfxblob = null;} if(pfxblob !=null) Console.WriteLine("Successfully exported to pkcs12\n"); else Console.WriteLine("PROBLEM exporting to pkcs12\n"); if(KEYSPEC == AT_KEYEXCHANGE) { DumpCert(hCertCntxt, "dercert_EX.cer"); Console.WriteLine(); showBytes("pfx blob:", pfxblob) ; WriteBlob("pfxselfsigned_EX.pfx", pfxblob); } else { DumpCert(hCertCntxt, "dercert_SIG.cer") ; Console.WriteLine(); showBytes("pfx blob:", pfxblob) ; WriteBlob("pfxselfsigned_SIG.pfx", pfxblob); } if(pKeyInfo != IntPtr.Zero) Marshal.FreeHGlobal(pKeyInfo); if(hCertCntxt != IntPtr.Zero) Win32.CertFreeCertificateContext(hCertCntxt) ; if(hCertStore != IntPtr.Zero) Win32.CertCloseStore(hCertStore, 0) ; if (hProv != IntPtr.Zero) Win32.CryptReleaseContext(hProv,0) ; } private static IntPtr CreateUnsignedCertCntxt(String keycontainer, uint KEYSPEC, uint cspflags, String DN) { const uint X509_ASN_ENCODING = 0x00000001; const uint CERT_X500_NAME_STR = 3; IntPtr hCertCntxt = IntPtr.Zero; byte[] encodedName = null; uint cbName = 0; if(Win32.CertStrToName(X509_ASN_ENCODING, DN, CERT_X500_NAME_STR, IntPtr.Zero, null, ref cbName, IntPtr.Zero)) { encodedName = new byte[cbName] ; Win32.CertStrToName(X509_ASN_ENCODING, DN, CERT_X500_NAME_STR, IntPtr.Zero, encodedName, ref cbName, IntPtr.Zero); //Console.WriteLine("Encoded name string has {0} bytes", cbName) ; //showBytes("Encoded SubjectName: ", encodedName) ; } CERT_NAME_BLOB subjectblob = new CERT_NAME_BLOB(); subjectblob.pbData = Marshal.AllocHGlobal(encodedName.Length); Marshal.Copy(encodedName, 0, subjectblob.pbData, encodedName.Length); subjectblob.cbData = encodedName.Length; CRYPT_KEY_PROV_INFO pInfo = new CRYPT_KEY_PROV_INFO(); pInfo.pwszContainerName = keycontainer; pInfo.pwszProvName = MS_DEF_PROV; pInfo.dwProvType = PROV_RSA_FULL; pInfo.dwFlags = cspflags; pInfo.cProvParam = 0; pInfo.rgProvParam = IntPtr.Zero; pInfo.dwKeySpec = KEYSPEC; hCertCntxt = Win32.CertCreateSelfSignCertificate(IntPtr.Zero, ref subjectblob, CERT_CREATE_SELFSIGN_NO_SIGN, ref pInfo, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero); if(hCertCntxt == IntPtr.Zero){ Console.WriteLine("Couldn't create unsigned certificate") ; showWin32Error(Marshal.GetLastWin32Error()); } Marshal.FreeHGlobal(subjectblob.pbData); return hCertCntxt ; } private static byte[] CertCntxtToPFX(IntPtr hCertCntxt, String pswd) { if(hCertCntxt == IntPtr.Zero) return null; byte[] pfxblob = null; const uint EXPORT_PRIVATE_KEYS = 0x0004; const uint REPORT_NOT_ABLE_TO_EXPORT_PRIVATE_KEY = 0x0002; const uint pfxflags = EXPORT_PRIVATE_KEYS | REPORT_NOT_ABLE_TO_EXPORT_PRIVATE_KEY ; const uint CERT_STORE_ADD_NEW = 1; const String sz_CERT_STORE_PROV_MEMORY = "Memory"; IntPtr hCertStore = IntPtr.Zero; hCertStore = Win32.CertOpenStore(sz_CERT_STORE_PROV_MEMORY, ENCODING_TYPE, IntPtr.Zero, 0, IntPtr.Zero) ; if(hCertStore != IntPtr.Zero) { if (Win32.CertAddCertificateContextToStore(hCertStore, hCertCntxt, CERT_STORE_ADD_NEW, IntPtr.Zero)) { CRYPT_DATA_BLOB pfx = new CRYPT_DATA_BLOB(); pfx.pbData = IntPtr.Zero; pfx.cbData = 0; if(Win32.PFXExportCertStoreEx(hCertStore, ref pfx, pswd , IntPtr.Zero, pfxflags)) { //Console.WriteLine("Got pfx blob size: {0} bytes", pfx.cbData) ; pfx.pbData = Marshal.AllocHGlobal(pfx.cbData); if (Win32.PFXExportCertStoreEx(hCertStore, ref pfx, "pswd" , IntPtr.Zero, pfxflags)) { pfxblob = new byte[pfx.cbData]; Marshal.Copy(pfx.pbData, pfxblob, 0, pfx.cbData); Marshal.FreeHGlobal(pfx.pbData); } else showWin32Error(Marshal.GetLastWin32Error()); } else showWin32Error(Marshal.GetLastWin32Error()); } } // --------- Clean up ------------ if(hCertStore != IntPtr.Zero) Win32.CertCloseStore(hCertStore, 0) ; return pfxblob; } private static SecureString GetSecPswd() { SecureString password = new SecureString(); Console.ForegroundColor = ConsoleColor.White; Console.Write("Set PFX Password ==> "); Console.ForegroundColor = ConsoleColor.Magenta; while (true) { ConsoleKeyInfo cki = Console.ReadKey(true); if (cki.Key == ConsoleKey.Enter) { Console.ForegroundColor = ConsoleColor.White; Console.WriteLine(); return password; } else if (cki.Key == ConsoleKey.Backspace) { // remove the last asterisk from the screen... if (password.Length > 0) { Console.SetCursorPosition(Console.CursorLeft - 1, Console.CursorTop); Console.Write(" "); Console.SetCursorPosition(Console.CursorLeft - 1, Console.CursorTop); password.RemoveAt(password.Length - 1); } } else if (cki.Key == ConsoleKey.Escape) { Console.ForegroundColor = ConsoleColor.White; Console.WriteLine(); return password; } else if (Char.IsLetterOrDigit(cki.KeyChar) || Char.IsSymbol(cki.KeyChar)) { if (password.Length < 20) { password.AppendChar(cki.KeyChar); Console.Write("*"); } else { Console.Beep(); } } else { Console.Beep(); } } } private static void DumpCert(IntPtr hCertCntxt, String fname) { CERT_CONTEXT cntxt = (CERT_CONTEXT)Marshal.PtrToStructure(hCertCntxt, typeof(CERT_CONTEXT)); Console.WriteLine("Certificate blob size: {0} bytes", cntxt.cbCertEncoded) ; byte [] dercert = new byte[cntxt.cbCertEncoded]; Marshal.Copy(cntxt.pbCertEncoded, dercert, 0, cntxt.cbCertEncoded) ; //get the DER certificate showBytes("\nDER unsigned certificate: ", dercert) ; WriteBlob(fname, dercert) ; } private static byte[] GetFileBytes(String filename){ if(!File.Exists(filename)) return null; Stream stream=new FileStream(filename,FileMode.Open); int datalen = (int)stream.Length; byte[] filebytes =new byte[datalen]; stream.Seek(0,SeekOrigin.Begin); stream.Read(filebytes,0,datalen); stream.Close(); return filebytes; } private static void showBytes(String info, byte[] data){ if(data == null){ Console.WriteLine("No data to show"); return; } Console.WriteLine("{0} [{1} bytes]", info, data.Length); for(int i=1; i<=data.Length; i++){ Console.Write("{0:X2} ", data[i-1]) ; if(i%16 == 0) Console.WriteLine(); } Console.WriteLine(); } private static void WriteBlob(String keyblobfile, byte[] keydata) { if(keydata == null){ Console.WriteLine("No data to write"); return; } FileStream fs = null; try{ fs = new FileStream(keyblobfile, FileMode.Create); fs.Write(keydata, 0, keydata.Length); Console.WriteLine("Wrote file '{0}'", keyblobfile) ; } catch(Exception e) { Console.WriteLine(e.Message) ; } finally { fs.Close(); } } private static void showWin32Error(int errorcode){ Win32Exception myEx=new Win32Exception(errorcode); //Console.WriteLine("Error code:\t 0x{0:X}", myEx.ErrorCode); Console.WriteLine("Error:\t " + myEx.Message); } } }