Skip to content

Instantly share code, notes, and snippets.

@haf
Last active January 23, 2019 20:59
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save haf/5629584 to your computer and use it in GitHub Desktop.
Save haf/5629584 to your computer and use it in GitHub Desktop.
Finding the private key of a Windows certificate from PowerShell/C#.
namespace PKI
{
class Results : IEquatable<Results>
{
internal static readonly Results NotFound = new Results
{
Directory = "",
KeyName = ""
};
public Results()
{
Directory = "";
KeyName = "";
}
public string Directory { get; set; }
public string KeyName { get; set; }
public override string ToString()
{
return string.Format(@"Directory: {0}
KeyName: {1}", Directory, KeyName);
}
public bool Equals(Results other)
{
if (ReferenceEquals(null, other)) return false;
if (ReferenceEquals(this, other)) return true;
return string.Equals(Directory, other.Directory) && string.Equals(KeyName, other.KeyName);
}
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true;
if (obj.GetType() != GetType()) return false;
return Equals((Results) obj);
}
public override int GetHashCode()
{
unchecked
{
return (Directory.GetHashCode()*397) ^ KeyName.GetHashCode();
}
}
public static bool operator ==(Results left, Results right)
{
return Equals(left, right);
}
public static bool operator !=(Results left, Results right)
{
return !Equals(left, right);
}
}
public static 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\" -c");
}
static void Entry(string[] args)
{
Options parser = new OptParser(args);
if (parser.ValidInput)
{
PrintHelp();
return;
}
try
{
var r = FindResults(parser);
Console.WriteLine(r.ToString());
}
catch (Exception ex)
{
Console.Error.WriteLine("FindPrivateKey failed for the following reason:");
Console.Error.WriteLine(ex.Message);
Console.Error.WriteLine("\nUse /? option for help");
}
}
public static string FindKeyByName(string storeName, string storeLocation, string commonName)
{
return Find(storeName, storeLocation, commonName, "-n");
}
public static string FindByThumbprint(string storeName, string storeLocation, string thumbPrint)
{
return Find(storeName, storeLocation, thumbPrint, "-f");
}
static string Find(string storeName, string storeLocation, string commonName, string nameFlag)
{
var results = FindResults(new OptParser(new[]
{
storeName,
storeLocation,
nameFlag,
commonName
}));
return results.Equals(Results.NotFound) ? "Not Found" : Path.Combine(results.Directory, results.KeyName);
}
static Results FindResults(Options parser)
{
var storeName = (StoreName) Enum.Parse(typeof (StoreName), parser.StoreName, true);
var storeLocation = (StoreLocation) Enum.Parse(typeof (StoreLocation), parser.StoreLocation, true);
var cert = parser.FindByPredicate
? LoadCertificate(storeName, storeLocation, parser.ComputeKey(), parser.FindType)
: SelectCertificate(storeName, storeLocation);
if (cert == null) return Results.NotFound;
var privateKeyFile = GetKeyFileName(cert);
return new Results
{
KeyName = privateKeyFile,
Directory = GetKeyFileDirectory(privateKeyFile)
};
}
static X509Certificate2 SelectCertificate(StoreName storeName, StoreLocation storeLocation)
{
X509Certificate2 result;
var 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;
var store = new X509Store(storeName, storeLocation);
store.Open(OpenFlags.ReadOnly);
try
{
var 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)
{
var hProvider = IntPtr.Zero; // CSP handle
var freeProvider = false; // Do we need to free the CSP ?
uint acquireFlags = 0;
var _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))
{
var pBytes = IntPtr.Zero; // Native Memory for the CRYPT_KEY_PROV_INFO structure
var 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 = 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
var allUserProfile = Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData);
// set up searching directory
var machineKeyDir = allUserProfile + "\\Microsoft\\Crypto\\RSA\\MachineKeys";
// Seach the key file
var fs = Directory.GetFiles(machineKeyDir, keyFileName);
// If found
if (fs.Length > 0)
return machineKeyDir;
// Next try current user profile
var currentUserProfile = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
// seach all sub directory
var userKeyDir = currentUserProfile + "\\Microsoft\\Crypto\\RSA\\";
fs = Directory.GetDirectories(userKeyDir);
if (fs.Length > 0)
{
// for each sub directory
foreach (var keyDir in fs)
{
fs = 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 static extern 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 static extern bool CryptGetProvParam(IntPtr hCryptProv, CryptGetProvParamType dwParam, IntPtr pvData,
ref int pcbData, uint dwFlags);
[DllImport("advapi32", SetLastError = true)]
internal static extern bool CryptReleaseContext(IntPtr hProv, uint dwFlags);
}
interface Options
{
bool ValidInput { get; }
bool FindByPredicate { get; }
X509FindType FindType { get; }
string StoreName { get; }
string StoreLocation { get; }
string ComputeKey();
}
class OptParser : Options
{
readonly string[] _args;
public OptParser(string[] args)
{
_args = args;
}
public bool ValidInput
{
get
{
return _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");
}
}
public bool FindByPredicate
{
get { return _args.Length > 2; }
}
public X509FindType FindType
{
get
{
return _args.Length > 2 && _args[2] == "-n"
? X509FindType.FindBySubjectDistinguishedName
: X509FindType.FindByThumbprint;
}
}
public string StoreName
{
get { return _args[0]; }
}
public string StoreLocation
{
get { return _args[1]; }
}
public string ComputeKey()
{
// insert a comma followed by a space for store.Certificates.Find(findType, key, false)
// to successful find the certificate
if (_args.Length < 4)
return string.Empty;
var keys = _args[3].Split(',');
var res = string.Empty;
for (var i = 0; i < keys.Length; i++)
{
res += keys[i];
if (i != keys.Length - 1)
res += ", ";
}
return res;
}
}
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,
}
}
ls Cert:\LocalMachine\my
# find your thumbprint from the output
# or wherever your System.Security.dll is (Reference Assemblies?)
$References = ([System.Reflection.AssemblyName]::GetAssemblyName('C:\inetpub\System.Security.dll').FullName)
$FindPrivateKeySource = @"
THE ABOVE FILE HERE
"@
Add-Type -ReferencedAssemblies $References -TypeDefinition $FindPrivateKeySource -Language CSharp
$keyPath = [PKI.PrivKey]::FindByThumbprint("My", "LocalMachine", "239848791283742983THUMBPRINT278872"
icacls $keyPath /grant "NetworkService:(R)"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment