//******************************************************************************** // // CertToKey // X.509 Certificate to Public Key Converter // // Copyright (C) 2006. JavaScience Consulting // //********************************************************************************* // // CertToKey.cs // // This C# utility for .NET Framework 2.0 + // For any X.509 certificate, displays the ASN.1 encoded public key as: // RSA public key. // Parses out the RSA publickey blob and extracts modulus, exponent // and returns both an XML public key string and an RSAParameters object // Implemented using managed code (no Platform Invoke). // // 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; namespace JavaScience { public class DecodeCertKey { public static void Main(String[] args) { String basefname = null; X509Certificate cert =null; byte[] encodedpubkey = null; 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; } //--------- 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); 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 (!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); else { Console.WriteLine("No certificate selected"); return; } } X509Certificate2UI.DisplayCertificate(new X509Certificate2(cert)) ; // Get the asn.1 encoded publickey bytes (RSA Public Key format) encodedpubkey = cert.GetPublicKey(); // Display the value to the console. Console.WriteLine(); showBytes("RSA encoded Public Key", encodedpubkey); Console.WriteLine(); String xmlkey = CertToXMLKey(cert); if(xmlkey == null) { Console.WriteLine("Problem getting public key from certificate"); return; } Console.WriteLine("XML publickey:\n{0}", xmlkey); Console.Write("\nWrite RSA public key and XML key files? [y|n] "); string ans = Console.ReadLine(); if(ans.Trim().StartsWith("Y") || ans.Trim().StartsWith("y")){ WriteKeyBlob("RSApubkey_" + basefname, encodedpubkey); WriteKeyBlob("XMLpubkey_" + basefname + ".txt", Encoding.ASCII.GetBytes(xmlkey)) ; } } // --- Extracts public key from X509Certificate and returns XML public key ------ // ---- Manually parses asn.1 of RSA public key ---- private static String CertToXMLKey(X509Certificate cert) { String xmlpublickey = null ; byte[] modulus, exponent; byte[] rsakey = cert.GetPublicKey(); // --------- Set up stream to read the asn.1 encoded SubjectPublicKeyInfo blob ------ MemoryStream mem = new MemoryStream(rsakey) ; BinaryReader binr = new BinaryReader(mem) ; ushort twobytes = 0; try{ twobytes = binr.ReadUInt16(); if(twobytes == 0x8130) //data read as little endian order (actual data order for Sequence is 30 81) binr.ReadByte(); //advance 1 byte else if(twobytes == 0x8230) binr.ReadInt16(); //advance 2 bytes else return null; twobytes = binr.ReadUInt16(); byte lowbyte = 0x00; byte highbyte = 0x00; if(twobytes == 0x8102) //data read as little endian order (actual data order for Integer is 02 81) lowbyte = binr.ReadByte(); // read next bytes which is bytes in modulus else if(twobytes == 0x8202) { highbyte = binr.ReadByte(); //advance 2 bytes lowbyte = binr.ReadByte(); } else return null; byte[] modint = {lowbyte, highbyte, 0x00, 0x00} ; //reverse byte order since asn.1 key uses big endian int modsize = BitConverter.ToInt32(modint, 0) ; int firstbyte = binr.PeekChar(); if(firstbyte == 0x00) { //if first byte (highest order) of modulus is zero, don't include it binr.ReadByte(); //skip this null byte modsize -=1 ; //reduce modulus buffer size by 1 } modulus = binr.ReadBytes(modsize); //read the modulus bytes if(binr.ReadByte() != 0x02) //expect an Integer for the exponent data return null; int expbytes = (int) binr.ReadByte() ; // should only need one byte for actual exponent data exponent = binr.ReadBytes(expbytes); if( binr.PeekChar() != -1 ) // if there is unexpected more data, then this is not a valid asn.1 RSAPublicKey return null; showBytes("\nExponent", exponent); showBytes("\nModulus", modulus) ; // ------- create RSACryptoServiceProvider instance and initialize with public key ----- RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(); RSAParameters RSAKeyInfo = new RSAParameters(); RSAKeyInfo.Modulus = modulus; RSAKeyInfo.Exponent = exponent; rsa.ImportParameters(RSAKeyInfo); xmlpublickey = rsa.ToXmlString(false) ; return xmlpublickey; } catch(Exception) { return null; } finally { binr.Close(); } } 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 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 [ |