namespace Microsoft.ChannelSDK.Tools.FindPrivateKey { using System; using System.IO; using System.Security.Cryptography.X509Certificates; using System.Runtime.InteropServices; class FindPrivateKey { static void PrintHelp() { Console.WriteLine("FindPrivateKey helps user to find the location of the Private Key file of a X.509 Certificate."); Console.WriteLine("Usage: FindPrivateKey <storeName> <storeLocation> [{ {-n <subjectName>} | {-t <thumbprint>} } [-f | -d | -a]]"); Console.WriteLine(" <subjectName> subject name of the certificate"); Console.WriteLine(" <thumbprint> thumbprint of the certificate (use certmgr.exe to get it)"); Console.WriteLine(" -f output file name only"); Console.WriteLine(" -d output directory only"); Console.WriteLine(" -a output absolute file name"); Console.WriteLine("e.g. FindPrivateKey My CurrentUser -n \"CN=John Doe\""); Console.WriteLine("e.g. FindPrivateKey My LocalMachine -t \"03 33 98 63 d0 47 e7 48 71 33 62 64 76 5c 4c 9d 42 1d 6b 52\" -a"); } static void Main(string[] args) { if (args.Length < 2 || args.Length == 3 || args.Length > 5 || (args.Length > 2 && args[2] != "-n" && args[2] != "-t") || (args.Length == 5 && args[4] != "-f" && args[4] != "-d" && args[4] != "-a")) { PrintHelp(); return; } try { StoreName storeName = (StoreName)Enum.Parse(typeof(StoreName), args[0], true); StoreLocation storeLocation = (StoreLocation)Enum.Parse(typeof(StoreLocation), args[1], true); X509Certificate2 cert; if (args.Length > 2) { // insert a comma followed by a space for store.Certificates.Find(findType, key, false) // to successful find the certificate string key = args[3]; string[] keys = key.Split(','); key = string.Empty; for (int i = 0; i < keys.Length; i++) { key += keys[i]; if ( i != keys.Length -1 ) key += ", "; } if (args[2] == "-n") cert = LoadCertificate(storeName, storeLocation, key, X509FindType.FindBySubjectDistinguishedName); else cert = LoadCertificate(storeName, storeLocation, key, X509FindType.FindByThumbprint); } else { cert = SelectCertificate(storeName, storeLocation); if (cert == null) return; } string privateKeyFile = GetKeyFileName(cert); string privateKeyDirectory = GetKeyFileDirectory(privateKeyFile); if (args.Length == 5) { if (args[4] == "-f") Console.WriteLine(privateKeyFile); else if (args[4] == "-d") Console.WriteLine(privateKeyDirectory); else Console.WriteLine("a:{0}\\{1}", privateKeyDirectory, privateKeyFile); } else { Console.WriteLine("Private key directory:"); Console.WriteLine(privateKeyDirectory); Console.WriteLine("Private key file name:"); Console.WriteLine(privateKeyFile); } } catch (Exception ex) { Console.WriteLine("FindPrivateKey failed for the following reason:"); Console.WriteLine(ex.Message); Console.WriteLine("\nUse /? option for help"); } } static X509Certificate2 SelectCertificate(StoreName storeName, StoreLocation storeLocation) { X509Certificate2 result; X509Store store = new X509Store(storeName, storeLocation); store.Open(OpenFlags.ReadOnly); try { X509Certificate2Collection matches; matches = X509Certificate2UI.SelectFromCollection(store.Certificates, "Select certificate", "Select the certificate to find the location of the associated private key file:", X509SelectionFlag.SingleSelection); if (matches.Count != 1) result = null; else result = matches[0]; } finally { store.Close(); } return result; } static X509Certificate2 LoadCertificate(StoreName storeName, StoreLocation storeLocation, string key, X509FindType findType) { X509Certificate2 result; X509Store store = new X509Store(storeName, storeLocation); store.Open(OpenFlags.ReadOnly); try { X509Certificate2Collection matches; matches = store.Certificates.Find(findType, key, false); if (matches.Count > 1) throw new InvalidOperationException(String.Format("More than one certificate with key '{0}' found in the store.", key)); if (matches.Count == 0) throw new InvalidOperationException(String.Format("No certificates with key '{0}' found in the store.", key)); result = matches[0]; } finally { store.Close(); } return result; } static string GetKeyFileName(X509Certificate2 cert) { IntPtr hProvider = IntPtr.Zero; // CSP handle bool freeProvider = false; // Do we need to free the CSP ? uint acquireFlags = 0; int _keyNumber = 0; string keyFileName = null; byte[] keyFileBytes = null; // // Determine whether there is private key information available for this certificate in the key store // if ( CryptAcquireCertificatePrivateKey(cert.Handle, acquireFlags, IntPtr.Zero, ref hProvider, ref _keyNumber, ref freeProvider) ) { IntPtr pBytes = IntPtr.Zero; // Native Memory for the CRYPT_KEY_PROV_INFO structure int cbBytes = 0; // Native Memory size try { if ( CryptGetProvParam(hProvider, CryptGetProvParamType.PP_UNIQUE_CONTAINER, IntPtr.Zero, ref cbBytes, 0) ) { pBytes = Marshal.AllocHGlobal(cbBytes); if ( CryptGetProvParam(hProvider, CryptGetProvParamType.PP_UNIQUE_CONTAINER, pBytes, ref cbBytes, 0) ) { keyFileBytes = new byte[cbBytes]; Marshal.Copy(pBytes,keyFileBytes,0,cbBytes); // Copy eveything except tailing null byte keyFileName = System.Text.Encoding.ASCII.GetString(keyFileBytes, 0, keyFileBytes.Length-1); } } } finally { if ( freeProvider ) CryptReleaseContext(hProvider,0); // // Free our native memory // if ( pBytes != IntPtr.Zero ) Marshal.FreeHGlobal(pBytes); } } if (keyFileName == null) throw new InvalidOperationException("Unable to obtain private key file name"); return keyFileName; } static string GetKeyFileDirectory(string keyFileName) { // Look up All User profile from environment variable string allUserProfile = System.Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData); // set up searching directory string machineKeyDir = allUserProfile + "\\Microsoft\\Crypto\\RSA\\MachineKeys"; // Seach the key file string[] fs = System.IO.Directory.GetFiles(machineKeyDir, keyFileName); // If found if (fs.Length > 0) return machineKeyDir; // Next try current user profile string currentUserProfile = System.Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData); // seach all sub directory string userKeyDir = currentUserProfile + "\\Microsoft\\Crypto\\RSA\\"; fs = System.IO.Directory.GetDirectories(userKeyDir); if (fs.Length > 0) { // for each sub directory foreach (string keyDir in fs) { fs = System.IO.Directory.GetFiles(keyDir, keyFileName); if (fs.Length == 0) continue; else // found return keyDir; } } throw new InvalidOperationException("Unable to locate private key file directory"); } [DllImport("crypt32", CharSet = CharSet.Unicode, SetLastError = true)] internal extern static bool CryptAcquireCertificatePrivateKey(IntPtr pCert, uint dwFlags, IntPtr pvReserved, ref IntPtr phCryptProv, ref int pdwKeySpec, ref bool pfCallerFreeProv); [DllImport("advapi32", CharSet = CharSet.Unicode, SetLastError = true)] internal extern static bool CryptGetProvParam(IntPtr hCryptProv, CryptGetProvParamType dwParam, IntPtr pvData, ref int pcbData, uint dwFlags); [DllImport("advapi32", SetLastError = true)] internal extern static bool CryptReleaseContext(IntPtr hProv, uint dwFlags); } enum CryptGetProvParamType { PP_ENUMALGS = 1, PP_ENUMCONTAINERS = 2, PP_IMPTYPE = 3, PP_NAME = 4, PP_VERSION = 5, PP_CONTAINER = 6, PP_CHANGE_PASSWORD = 7, PP_KEYSET_SEC_DESCR = 8, // get/set security descriptor of keyset PP_CERTCHAIN = 9, // for retrieving certificates from tokens PP_KEY_TYPE_SUBTYPE = 10, PP_PROVTYPE = 16, PP_KEYSTORAGE = 17, PP_APPLI_CERT = 18, PP_SYM_KEYSIZE = 19, PP_SESSION_KEYSIZE = 20, PP_UI_PROMPT = 21, PP_ENUMALGS_EX = 22, PP_ENUMMANDROOTS = 25, PP_ENUMELECTROOTS = 26, PP_KEYSET_TYPE = 27, PP_ADMIN_PIN = 31, PP_KEYEXCHANGE_PIN = 32, PP_SIGNATURE_PIN = 33, PP_SIG_KEYSIZE_INC = 34, PP_KEYX_KEYSIZE_INC = 35, PP_UNIQUE_CONTAINER = 36, PP_SGC_INFO = 37, PP_USE_HARDWARE_RNG = 38, PP_KEYSPEC = 39, PP_ENUMEX_SIGNING_PROT = 40, PP_CRYPT_COUNT_KEY_USE = 41, } }