//************************************************************************* // VerifySig // // Copyright (C) 2003. Michel I. Gallant // //************************************************************************* // // VerifySig.cs // // This C# utility for .NET Framework 1.0/1.1 verifies a PKCS#1 v1.5 // signature file against a content file, using public key from a user-specified // certificate file, or a SubjectName sub-string searched certificate from // MY or ADDRESSBOOK Current User certificate stores. // //************************************************************************** using System; using System.IO; using System.Text; using System.Security; using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using System.Runtime.InteropServices; namespace JavaScience { //--- P/Invoke CryptoAPI wrapper classes ----- 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", 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 } public class VerifySig{ const uint CERT_SYSTEM_STORE_CURRENT_USER = 0x00010000; const uint CERT_STORE_READONLY_FLAG = 0x00008000; const uint CERT_STORE_OPEN_EXISTING_FLAG = 0x00004000; const uint CERT_FIND_SUBJECT_STR = 0x00080007; const uint X509_ASN_ENCODING = 0x00000001; const uint PKCS_7_ASN_ENCODING = 0x00010000; const uint RSA_CSP_PUBLICKEYBLOB = 19; const int AT_KEYEXCHANGE = 1; //keyspec values const int AT_SIGNATURE = 2; static uint ENCODING_TYPE = PKCS_7_ASN_ENCODING | X509_ASN_ENCODING ; private X509Certificate recipcert; private byte[] certkeymodulus; private byte[] certkeyexponent; private uint certkeysize; private bool verbose = false; public static void Main(String[] args) { String signaturefile, contentfile; String subjectstr; VerifySig oVeri = new VerifySig(); if(args.Length<2 || args.Length>3){ VerifySig.usage(); return; } if(args.Length==3) //any 3th argument enables verbose mode oVeri.verbose = true; signaturefile = args[0] ; if (!File.Exists(signaturefile)){ Console.WriteLine("Signature file '{0}' not found.", signaturefile); return; } contentfile = args[1] ; if (!File.Exists(contentfile)){ Console.WriteLine("Content file '{0}' not found.", contentfile); return; } Console.Write("Enter SubjectName of recipient, or filename of recipient certificate: "); subjectstr = Console.ReadLine(); //----- Either a valid certificate file passed, or a cert store search string ---- if (File.Exists(subjectstr)) oVeri.recipcert = oVeri.GetRecipientFileCert(subjectstr); else oVeri.recipcert = oVeri.GetRecipientStoreCert(subjectstr); if(oVeri.recipcert == null){ Console.WriteLine("Couldn't find valid certificate in store or file matching '{0}'", subjectstr); return; } //----- get recipient certificate public key parameters ---- if(!oVeri.GetCertPublicKey(oVeri.recipcert)){ Console.WriteLine("Couldn't get recipient certificate public key"); return; } Console.WriteLine("Public key size: {0} bits", oVeri.certkeysize); byte[] sigbytes = GetFileBytes(signaturefile); //----- Get SHA-1 hash of content file ------- byte[] contentbytes = GetFileBytes(contentfile); SHA1 sha = new SHA1CryptoServiceProvider(); byte[] contenthash = sha.ComputeHash(contentbytes); String certinfo = oVeri.recipcert.GetName() ; if(oVeri.verbose){ showBytes("SHA1 Hash:", contenthash); certinfo = oVeri.recipcert.ToString(true); } if(oVeri.VerifyPKCS1Sig(contenthash, sigbytes, oVeri.certkeymodulus, oVeri.certkeyexponent)) Console.WriteLine("\n---- Signature '{0}' verified against content '{1}' ---- \nusing certificate:\n{2}", signaturefile, contentfile, certinfo) ; else Console.WriteLine("\n**** FAILED: Signature '{0}' NOT verified against content '{1}'****\nusing certificate\n{2}", signaturefile, contentfile, certinfo) ; } private bool VerifyPKCS1Sig(byte[] hash, byte[] sig, byte[] modulus, byte[] exp) { if(hash.Length !=20) return false; if(sig.Length == 0 || modulus.Length==0 || exp.Length==0) return false; RSAParameters RSAparms = new RSAParameters();; RSAparms.Modulus = modulus; RSAparms.Exponent = exp; RSACryptoServiceProvider RSA = new RSACryptoServiceProvider(); RSA.ImportParameters(RSAparms); RSAPKCS1SignatureDeformatter RSADeformatter = new RSAPKCS1SignatureDeformatter(RSA); RSADeformatter.SetHashAlgorithm("SHA1"); if(RSADeformatter.VerifySignature(hash, sig)) return true; else return false; } //--- Search for first matching certificate in CryptoAPI cert stores --- private X509Certificate GetRecipientStoreCert(String searchstr) { X509Certificate cert =null; IntPtr hSysStore = IntPtr.Zero; IntPtr hCertCntxt = IntPtr.Zero; string[] searchstores = {"ADDRESSBOOK", "MY"}; uint openflags = CERT_SYSTEM_STORE_CURRENT_USER | 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 cert = new X509Certificate(hCertCntxt); Console.WriteLine("\nFound certificate in {0} store with SubjectName string \"{1}\"", store, searchstr); 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; } //--- Get X509Certificate from binary DER or b64 cert file --- //--- try reading as binary DER first; if error, try b64 --- private X509Certificate GetRecipientFileCert(String certfile) { X509Certificate cert =null; try{ cert = X509Certificate.CreateFromCertFile(certfile); } catch(System.Security.Cryptography.CryptographicException) { StreamReader sr = File.OpenText(certfile); String filestr = sr.ReadToEnd(); sr.Close(); StringBuilder sb = new StringBuilder(filestr) ; sb.Replace("-----BEGIN CERTIFICATE-----", "") ; sb.Replace("-----END CERTIFICATE-----", "") ; //Decode try{ //see if the file is a valid Base64 encoded cert byte[] certBytes = Convert.FromBase64String(sb.ToString()) ; cert = new X509Certificate(certBytes); } catch(System.FormatException) { Console.WriteLine("Not valid binary DER or b64 X509 certificate"); } catch(System.Security.Cryptography.CryptographicException) { Console.WriteLine("Not valid binary DER or b64 X509 certificate"); } } if(cert !=null){ Console.WriteLine("{0} is a valid certificate file", certfile); Console.WriteLine("SubjectName:\t{0}", cert.GetName()); } return cert; } //----- decode public key and extract modulus and exponent ---- private bool GetCertPublicKey(X509Certificate cert) { byte[] publickeyblob ; byte[] encodedpubkey = cert.GetPublicKey(); //asn.1 encoded public key uint blobbytes=0; if(verbose) { Console.WriteLine(); showBytes("Encoded publickey", 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)) if(verbose) showBytes("CryptoAPI publickeyblob", publickeyblob); } else{ Console.WriteLine("Couldn't decode publickeyblob from certificate publickey") ; return false; } 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 ); if(verbose) { 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 key size in bits ------------- this.certkeysize = pkheaders.bitlen; //----- Get public exponent ------------- byte[] exponent = BitConverter.GetBytes(pkheaders.pubexp); //little-endian ordered Array.Reverse(exponent); //convert to big-endian order this.certkeyexponent = exponent; if(verbose) showBytes("\nPublic key exponent (big-endian order):", exponent); //----- Get 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. this.certkeymodulus = modulus; if(verbose) showBytes("\nPublic key modulus (big-endian order):", modulus); } catch(Exception){ Console.WriteLine("Problem getting modulus from publickeyblob"); return false; } return true; } private void PutFileBytes(String outfile, byte[] data, int bytes) { FileStream fs = null; if(bytes > data.Length) { Console.WriteLine("Too many bytes"); return; } try{ fs = new FileStream(outfile, FileMode.Create); fs.Write(data, 0, bytes); } catch(Exception e) { Console.WriteLine(e.Message) ; } finally { fs.Close(); } } 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){ 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 usage() { Console.WriteLine("\nUsage:\nVerifySig.exe [SignatureFile] [ContentFile]"); } } }