//******************************************************************************** // // DecodeCertKey // X.509 Certificate to Public Key Extractor // // Copyright (C) 2006. JavaScience Consulting // //********************************************************************************* // // DecodeCertKey.cs // // This C# utility for .NET Framework 2.0 + // For any X.509 certificate, displays the ASN.1 encoded public key in formats: // RSA public key, X.509 SubjectPublicKeyInfo // and CryptoAPI PUBLICKEYBLOB and XML public key formats. // Allows saving all these public key formats to files. // Decodes the PUBLICKEYBLOB into fields and displays exponent and modulus bytes // in big-endian form suitable for RSAParameters fields. // // Handles either binary DER or BASE64 encoded X.509 v3 certificates. // //********************************************************************************** using System; using System.IO; using System.Text; using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using System.Runtime.InteropServices; namespace JavaScience { public class Win32 { [DllImport("crypt32.dll")] public static extern bool CryptDecodeObject( uint CertEncodingType, uint lpszStructType, byte[] pbEncoded, uint cbEncoded, uint flags, [In, Out] byte[] pvStructInfo, ref uint cbStructInfo); [DllImport("crypt32.dll")] public static extern bool CryptEncodeObject( uint CertEncodingType, uint lpszStructType, ref CERT_PUBLIC_KEY_INFO pvStructInfo, [In, Out] byte[] pbEncoded, ref uint cbEncoded); [DllImport("crypt32.dll", SetLastError=true)] public static extern IntPtr CertFindCertificateInStore( IntPtr hCertStore, uint dwCertEncodingType, uint dwFindFlags, uint dwFindType, [In, MarshalAs(UnmanagedType.LPWStr)]String pszFindString, IntPtr pPrevCertCntxt) ; [DllImport("crypt32.dll", SetLastError=true)] public static extern bool CertFreeCertificateContext( IntPtr hCertStore) ; [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 bool CertCloseStore( IntPtr hCertStore, uint dwFlags) ; } [StructLayout(LayoutKind.Sequential)] public struct PUBKEYBLOBHEADERS { public byte bType; //BLOBHEADER public byte bVersion; //BLOBHEADER public short reserved; //BLOBHEADER public uint aiKeyAlg; //BLOBHEADER public uint magic; //RSAPUBKEY public uint bitlen; //RSAPUBKEY public uint pubexp; //RSAPUBKEY } [StructLayout(LayoutKind.Sequential)] public struct CERT_PUBLIC_KEY_INFO { public String SubjPKIAlgpszObjId; public int SubjPKIAlgParameterscbData; public IntPtr SubjPKIAlgParameterspbData; public int PublicKeycbData; public IntPtr PublicKeypbData; public int PublicKeycUnusedBits; } public class DecodeCertKey { 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_SUBJECT_STR = 0x00080007; const String szOID_RSA_RSA ="1.2.840.113549.1.1.1" ; const uint X509_ASN_ENCODING = 0x00000001; const uint PKCS_7_ASN_ENCODING = 0x00010000; static uint ENCODING_TYPE = PKCS_7_ASN_ENCODING | X509_ASN_ENCODING ; const uint RSA_CSP_PUBLICKEYBLOB = 19; const uint X509_PUBLIC_KEY_INFO = 8; static string[] searchstores = {"MY", "ADDRESSBOOK", "ROOT"}; public static void Main(String[] args) { String basefname = null; X509Certificate cert =null; byte[] x509publickey = null; byte[] encodedpubkey = null; byte[] publickeyblob = null; uint storetype = CERT_SYSTEM_STORE_CURRENT_USER ; StoreLocation STORETYPE = StoreLocation.CurrentUser; if(args.Length<1 || args.Length>2){ usage(); return; } String CERTFILE_NAME = args[0] ; basefname = Path.GetFileNameWithoutExtension(CERTFILE_NAME); if(args.Length == 2 && args[1].ToUpper() == "M") // default is CurrentUser store { STORETYPE = StoreLocation.LocalMachine; storetype = CERT_SYSTEM_STORE_LOCAL_MACHINE; } //--------- If we have a file, try to decode as a valid X.509 certificate ------- if(File.Exists(CERTFILE_NAME)){ Console.WriteLine("File '{0}' found.\n", CERTFILE_NAME); // basefname = Path.GetFileNameWithoutExtension(CERTFILE_NAME); cert = getFilecert(CERTFILE_NAME) ; if(cert == null) { Console.WriteLine("\nFile '{0}' is not a valid X509 certificate", CERTFILE_NAME); return; } } //--------- If not a file, check if store name specified; if not try searching cert stores for matching SubjectName substring ------- if (!File.Exists(CERTFILE_NAME)){ Console.WriteLine("\nFile '{0}' not found. Checking certificate stores .. ", CERTFILE_NAME); if(CERTFILE_NAME.ToLower() == "personal") cert = selectStorecert("MY", STORETYPE); else if (CERTFILE_NAME.ToLower() == "others") cert = selectStorecert("ADDRESSBOOK", STORETYPE); else cert = selectStorecert(CERTFILE_NAME, STORETYPE) ; if(cert !=null) Console.WriteLine("Selected a certificate from '{0}' {1} certificate store\n", CERTFILE_NAME, STORETYPE); if(cert == null){ Console.WriteLine("Couldn't open '{0}' {1} store", CERTFILE_NAME, STORETYPE) ; cert = getStorecert(CERTFILE_NAME, storetype) ; if(cert == null) { Console.WriteLine("No cert in stores matching '{0}' substring", CERTFILE_NAME); return; } Console.WriteLine("Found {0} cert matching '{1}' substring\n", STORETYPE, CERTFILE_NAME); } } X509Certificate2UI.DisplayCertificate(new X509Certificate2(cert)) ; // Get the asn.1 encoded publickey bytes. encodedpubkey = cert.GetPublicKey(); x509publickey = EncodetoSubjectPublicKeyInfo(encodedpubkey); if(x509publickey == null) { Console.WriteLine("Couldn't decode to X509 public key"); return; } showBytes("X509 encoded Public Key", x509publickey); uint blobbytes=0; // Display the value to the console. Console.WriteLine(); showBytes("RSA encoded Public Key", encodedpubkey); Console.WriteLine(); if(Win32.CryptDecodeObject(ENCODING_TYPE, RSA_CSP_PUBLICKEYBLOB, encodedpubkey, (uint)encodedpubkey.Length, 0, null, ref blobbytes)) { publickeyblob = new byte[blobbytes]; if(Win32.CryptDecodeObject(ENCODING_TYPE, RSA_CSP_PUBLICKEYBLOB, encodedpubkey, (uint)encodedpubkey.Length, 0, publickeyblob, ref blobbytes)) showBytes("CryptoAPI publickeyblob", publickeyblob); } else{ Console.WriteLine("Couldn't decode publickeyblob from certificate publickey") ; return;} PUBKEYBLOBHEADERS pkheaders = new PUBKEYBLOBHEADERS() ; int headerslength = Marshal.SizeOf(pkheaders); IntPtr buffer = Marshal.AllocHGlobal( headerslength); Marshal.Copy( publickeyblob, 0, buffer, headerslength ); pkheaders = (PUBKEYBLOBHEADERS) Marshal.PtrToStructure( buffer, typeof(PUBKEYBLOBHEADERS) ); Marshal.FreeHGlobal( buffer ); Console.WriteLine("\n ---- PUBLICKEYBLOB headers ------"); Console.WriteLine(" bType {0}", pkheaders.bType); Console.WriteLine(" bVersion {0}", pkheaders.bVersion); Console.WriteLine(" reserved {0}", pkheaders.reserved); Console.WriteLine(" aiKeyAlg 0x{0:x8}", pkheaders.aiKeyAlg); String magicstring = (new ASCIIEncoding()).GetString(BitConverter.GetBytes(pkheaders.magic)) ; Console.WriteLine(" magic 0x{0:x8} '{1}'", pkheaders.magic, magicstring); Console.WriteLine(" bitlen {0}", pkheaders.bitlen); Console.WriteLine(" pubexp {0}", pkheaders.pubexp); Console.WriteLine(" --------------------------------"); //----- Get public exponent in big-endian byte array, suitable for RSAParameters.Exponent ------------- byte[] exponent = BitConverter.GetBytes(pkheaders.pubexp); //returns bytes in little-endian order Array.Reverse(exponent); //PUBLICKEYBLOB stores in LITTLE-endian order; convert to BIG-endian order showBytes("\nPublic key exponent (big-endian order):", exponent); //----- Get modulus in big-endian byte array, suitable for RSAParameters.Modulus ------------- int modulusbytes = (int)pkheaders.bitlen/8 ; byte[] modulus = new byte[modulusbytes]; try{ Array.Copy(publickeyblob, headerslength, modulus, 0, modulusbytes); Array.Reverse(modulus); //convert from little to big-endian ordering. showBytes("\nPublic key modulus (big-endian order):", modulus); } catch(Exception){ Console.WriteLine("Problem getting modulus from publickeyblob"); } //------- Try to instantiate an RSACryptoServiceProvider --------- //Create a new instance of RSACryptoServiceProvider. RSACryptoServiceProvider oRSA = new RSACryptoServiceProvider(); //Create a new instance of RSAParameters. RSAParameters RSAKeyInfo = new RSAParameters(); //Set RSAKeyInfo to the public key values. RSAKeyInfo.Modulus = modulus; RSAKeyInfo.Exponent = exponent; //Import key parameters into RSA. oRSA.ImportParameters(RSAKeyInfo); String xmlkey = oRSA.ToXmlString(false) ; Console.WriteLine("XML public key:\n{0}", xmlkey); Console.Write("\nWrite public key to Encoded Key, PUBLICKEYBLOB and XML key files? [y|n] "); string ans = Console.ReadLine(); if(ans.Trim().StartsWith("Y") || ans.Trim().StartsWith("y")){ WriteKeyBlob("X509pubkey_" + basefname, x509publickey); WriteKeyBlob("RSApubkey_" + basefname, encodedpubkey); WriteKeyBlob("PUBLICKEYBLOB_" + basefname, publickeyblob); WriteKeyBlob("XMLpubkey_" + basefname + ".txt", Encoding.ASCII.GetBytes(xmlkey)) ; } //------------------------------------------------------------ } // --- Encode from RSAPublicKey to SubjectPublicKeyInfo format ---- private static byte[] EncodetoSubjectPublicKeyInfo(byte[] keydata) { IntPtr p1 = Marshal.AllocHGlobal(2); Marshal.WriteInt16(p1, 0x0005) ; // write 2 byte BER asn.1 null sequence {05, 00} ; IntPtr p2 = Marshal.AllocHGlobal(keydata.Length); Marshal.Copy(keydata, 0, p2, keydata.Length) ; CERT_PUBLIC_KEY_INFO certpublickeyinfo = new CERT_PUBLIC_KEY_INFO() ; certpublickeyinfo.SubjPKIAlgpszObjId = szOID_RSA_RSA ; certpublickeyinfo.SubjPKIAlgParameterscbData = 2; certpublickeyinfo.SubjPKIAlgParameterspbData = p1; certpublickeyinfo.PublicKeycbData = keydata.Length; certpublickeyinfo.PublicKeypbData = p2; uint cbytes=0; if(Win32.CryptEncodeObject(ENCODING_TYPE, X509_PUBLIC_KEY_INFO, ref certpublickeyinfo, null, ref cbytes)) { byte [] encoded = new byte[cbytes]; Win32.CryptEncodeObject(ENCODING_TYPE, X509_PUBLIC_KEY_INFO, ref certpublickeyinfo, encoded, ref cbytes) ; Marshal.FreeHGlobal(p1) ; Marshal.FreeHGlobal(p2) ; return encoded; } else{ return null; } } private static X509Certificate getFilecert(String CERTFILE_NAME) { X509Certificate cert = null; try{ // Try loading certificate as binary DER into an X509Certificate object. cert = X509Certificate.CreateFromCertFile(CERTFILE_NAME); return cert; } catch(System.Security.Cryptography.CryptographicException) { //not binary DER; try BASE64 format StreamReader sr = File.OpenText(CERTFILE_NAME); String filestr = sr.ReadToEnd(); sr.Close(); StringBuilder sb = new StringBuilder(filestr) ; sb.Replace("-----BEGIN CERTIFICATE-----", "") ; sb.Replace("-----END CERTIFICATE-----", "") ; try{ //see if the file is a valid Base64 encoded cert byte[] certBytes = Convert.FromBase64String(sb.ToString()) ; cert = new X509Certificate(certBytes); return cert; } catch(System.FormatException) { Console.WriteLine("Not valid binary DER or Base64 X509 certificate format"); return null; } catch(System.Security.Cryptography.CryptographicException) { Console.WriteLine("Not valid binary DER or Base64 X509 certificate format"); return null; } } // end outer catch } private static X509Certificate selectStorecert(String storename, StoreLocation storetype) { X509Store store = new X509Store (storename, storetype); try{ store.Open (OpenFlags.ReadOnly | OpenFlags.OpenExistingOnly); } catch(Exception) { return null; } X509Certificate2Collection certscol = (X509Certificate2Collection)store.Certificates ; X509Certificate2Collection mycerts= X509Certificate2UI.SelectFromCollection(certscol, storename + " " + storetype + " store", "Select a certificate .. ", X509SelectionFlag.SingleSelection); if(mycerts.Count !=1) return null; X509Certificate cert = (X509Certificate) mycerts[0] ; store.Close(); return cert; } private static X509Certificate getStorecert(String searchstr, uint storetype) { X509Certificate cert = null; IntPtr hSysStore = IntPtr.Zero; IntPtr hCertCntxt = IntPtr.Zero; uint openflags = storetype | CERT_STORE_READONLY_FLAG | CERT_STORE_OPEN_EXISTING_FLAG; foreach(String store in searchstores) { hSysStore = Win32.CertOpenStore("System", ENCODING_TYPE, IntPtr.Zero, openflags, store ); if(hSysStore == IntPtr.Zero){ //Console.WriteLine("Failed to open system store {0}", store); continue; } hCertCntxt=Win32.CertFindCertificateInStore( hSysStore, ENCODING_TYPE, 0, CERT_FIND_SUBJECT_STR, searchstr , IntPtr.Zero) ; if(hCertCntxt != IntPtr.Zero){ //use certcontext from managed code // Console.WriteLine("CertContext:\t{0}", hCertCntxt) ; cert = new X509Certificate(hCertCntxt); //Console.WriteLine("\nFound certificate in store '{0}'\n",store); //Console.WriteLine("SubjectName:\t{0}", cert.GetName()); break; } } // end foreach //------- Clean Up ----------- if(hCertCntxt != IntPtr.Zero) Win32.CertFreeCertificateContext(hCertCntxt); if(hSysStore != IntPtr.Zero) Win32.CertCloseStore(hSysStore, 0) ; return cert; } private static void showBytes(String info, byte[] data){ 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 WriteKeyBlob(String keyblobfile, byte[] keydata) { FileStream fs = null; if (File.Exists(keyblobfile)) { Console.WriteLine("File '{0}' already exists!", keyblobfile); return; } try{ fs = new FileStream(keyblobfile, FileMode.CreateNew); fs.Write(keydata, 0, keydata.Length); Console.WriteLine("Wrote public key file '{0}'", keyblobfile) ; } catch(Exception e) { Console.WriteLine(e.Message) ; } finally { fs.Close(); } } private static void usage() { Console.WriteLine("\nUsage:\nDecodeCertKey.exe [ |