using System; using System.Diagnostics; using System.Text; using System.IO; using CryptoSysPKI; namespace PortugalTax_cs { class PortugalTax_cs { // $Id: PortugalTax2.cs $ // $Date: 2010-11-26 06:15Z $ // $Revision: 2.0 $ // $Author: dai $ // *************************** COPYRIGHT NOTICE ****************************** // This code was originally written by David Ireland and is copyright // (C) 2010 DI Management Services Pty Ltd <www.di-mgt.com.au>. // Provided "as is". No warranties. Use at your own risk. You must make your // own assessment of its accuracy and suitability for your own purposes. // It is not to be altered or distributed, except as part of an application. // You are free to use it in any application, provided this copyright notice // is left unchanged. // ************************ END OF COPYRIGHT NOTICE ************************** // This module uses functions from the CryptoSys (tm) PKI Toolkit available from // <www.cryptosys.net/pki/>. // Include the module `basCrPKI` in your project. // NOTES: // (1) The key files in these tests are expected to exist in the current working directory. // (2) The word "signature" or "signature value" == the <Hash> field of the specification. // REFERENCES: // [ESPECIF-2010] "Especificação das Regras Técnicas para Certificação de Software // Portaria n.º 363/2010, de 23 de Junho", Direcção Geral dos Impostos (DGCI), Especificacao_regras_tecnicas_Certificacao_Softwar.pdf // <http://info.portaldasfinancas.gov.pt/NR/rdonlyres/BF3D4A62-3243-404F-8F94-DCB2B19547C3/44379/Especificacao_regras_tecnicas_Certificacao_Softwar.pdf> // (accessed 7 August 2010). // // [ADIT-2010] "- 1º Aditamento - Especificação das Regras Técnicas para Certificação de Software // Portaria n.º 363/2010, de 23 de Junho", Direcção Geral dos Impostos (DGCI), 1_Aditamento_Especificaca_regras_tecnicas.pdf // <http://info.portaldasfinancas.gov.pt/NR/rdonlyres/84B18C77-577B-4581-A846-2DB0201B0FB4/0/1_Aditamento_Especificaca_regras_tecnicas.pdf> // (accessed 21 November 2010). // ************************************************************************************ // NOTES ON THE C# CODE // This C# code was converted from VB.NET using SharpDevelop by icsharpcode.net, // which in turn had been converted from a VB6 original. // We've taken out the obvious VB bits which used Microsoft.VisualBasic, // but it's still not representative of good C# coding practice. Sorry. // We're sure you'll get the idea. // -- DI Management. // ************************************************************************************ public static void Main() { Console.WriteLine("PKI Version={0}", General.Version()); Pt_CreateSignature_Especificacao(); Pt_CreateSignature_SAFT_IDEMO(); Pt_VerifySignature(); Pt_ExtractDigest(); Pt_CreateSignatureWithKeyAsString(); Pt_Create_Keys(); Pt_Test_DoKeyPairFilesMatch(); Pt_SavePrivateKeyAsEncrypted(); Pt_Make_X509_CertSelfSigned(); Pt_QueryCert(); Pt_GetPublicKeyFromFileAndCert(); } // ******************* // GENERIC FUNCTIONS * // ******************* // Use overloads for optional parameter... public static string rsaCreateSignatureInBase64(string strMessage, string strKeyFile, bool ShowDebug) { return m_rsaCreateSignatureInBase64(strMessage, strKeyFile, ShowDebug); } public static string rsaCreateSignatureInBase64(string strMessage, string strKeyFile) { return m_rsaCreateSignatureInBase64(strMessage, strKeyFile, false); } private static string m_rsaCreateSignatureInBase64(string strMessage, string strKeyFile, bool ShowDebug) { // $GENERIC-FUNCTION$ // INPUT: Message string to be signed; filename of private RSA key file (unencrypted OpenSSL format) // OUTPUT: Signature in base64 format string strPrivateKey = null; byte[] abMessage = null; int nMsgLen = 0; byte[] abBlock = null; int nBlkLen = 0; string strSigBase64 = null; // 1. Convert message into unambigous array of bytes and compute length abMessage = System.Text.Encoding.Default.GetBytes(strMessage); nMsgLen = abMessage.Length; if (ShowDebug) Console.WriteLine("Message length = " + nMsgLen + " bytes."); // 1a. While we're here, compute the digest of the input. (We don't need it but it's a check for later) string strDigest = null; strDigest = Hash.HexFromBytes(abMessage, HashAlgorithm.Sha1); if (ShowDebug) Console.WriteLine("DIGEST=" + strDigest); // 2. Read the private key file into our internal string format // (Note that strPrivateKey is a one-off, ephemeral, internal string we made when reading the key file. // You can't save it to use again.) strPrivateKey = Rsa.ReadPrivateKeyInfo(strKeyFile).ToString(); if (strPrivateKey.Length <= 0) { return "**ERROR: cannot read private key file"; } if (ShowDebug) Console.WriteLine("Private key size is " + Rsa.KeyBits(strPrivateKey) + " bits."); // 3. Encode (i.e. digest and pad) the message into format required for PKCS#1v1.5 signature // Required block length is key size in bytes nBlkLen = Rsa.KeyBytes(strPrivateKey); if (ShowDebug) Console.WriteLine("Key/block size is " + nBlkLen + " bytes."); abBlock = Rsa.EncodeMsgForSignature(nBlkLen, abMessage, HashAlgorithm.Sha1); // Show the encoded block in hex format (should be 0001FFFF...ending with the 20-byte digest) if (ShowDebug) Console.WriteLine(Cnv.ToHex(abBlock)); // 4. Create the signature block using the private key abBlock = Rsa.RawPrivate(abBlock, strPrivateKey); // Show the signature block in hex format if (ShowDebug) Console.WriteLine(Cnv.ToHex(abBlock)); // 5. Convert to base64 format strSigBase64 = Cnv.ToBase64(abBlock); if (ShowDebug) Console.WriteLine(strSigBase64); // Return base64 signature return strSigBase64; } public static string rsaVerifySignature(string strSigBase64, string strPublicKeyFileOrCert, string strTextToSign, bool ShowDebug) { return m_rsaVerifySignature(strSigBase64, strPublicKeyFileOrCert, strTextToSign, ShowDebug); } public static string rsaVerifySignature(string strSigBase64, string strPublicKeyFileOrCert, string strTextToSign) { return m_rsaVerifySignature(strSigBase64, strPublicKeyFileOrCert, strTextToSign, false); } private static string m_rsaVerifySignature(string strSigBase64, string strPublicKeyFileOrCert, string strTextToSign, bool ShowDebug) { string functionReturnValue = null; // $GENERIC-FUNCTION$ // INPUT: Signature value in base64 format (the <Hash> field); // filename of RSA public key file or X.509 certificate containing the same public key; // text that was signed. // OUTPUT: "OK" if signature is valid or error message beginning "**ERROR" if not. string strPublicKey = null; byte[] abBlock = null; byte[] abDigest = null; string strDigest = null; string strDigest1 = null; // 1. Read the public key file into our internal string format strPublicKey = Rsa.ReadPublicKey(strPublicKeyFileOrCert).ToString(); if (strPublicKey.Length <= 0) { // Was not a public key file, so try reading an X.509 certificate instead strPublicKey = Rsa.GetPublicKeyFromCert(strPublicKeyFileOrCert).ToString(); if (strPublicKey.Length <= 0) { return "**ERROR: cannot read public key file"; } } if (ShowDebug) Console.WriteLine("Public key size is " + Rsa.KeyBits(strPublicKey) + " bits."); // 2. Convert base64 signature to byte array abBlock = Cnv.FromBase64(strSigBase64); if (ShowDebug) Console.WriteLine("Signature block length = " + abBlock.Length + " bytes"); if (ShowDebug) Console.WriteLine(Cnv.ToHex(abBlock)); // 3. Decrypt the signature block using the RSA public key // (Note that strPublicKey is a one-off, ephemeral, internal string we made when reading the key file. // You can't save it to use again.) abBlock = Rsa.RawPublic(abBlock, strPublicKey); if (abBlock.Length <= 0) { return "**ERROR: invalid signature"; } // Show the decrypted signature block in hex format if (ShowDebug) Console.WriteLine(Cnv.ToHex(abBlock)); // 4. Extract the message digest from the block (presumed SHA-1) abDigest = Rsa.DecodeDigestForSignature(abBlock); if (abDigest.Length <= 0) { return "**ERROR: invalid signature"; } if (ShowDebug) Console.WriteLine("Message digest is " + abDigest.Length + " bytes long"); strDigest = Cnv.ToHex(abDigest); if (ShowDebug) Console.WriteLine("EXTRACTED DIGEST=" + strDigest); // 5. Compute the SHA-1 message digest of the text that was signed strDigest1 = Hash.HexFromString(strTextToSign, HashAlgorithm.Sha1); if (ShowDebug) Console.WriteLine("COMPUTED DIGEST =" + strDigest1); // 6. Compare these two digest values and return OK only if they match if (strDigest.ToUpper() == strDigest1.ToUpper()) { functionReturnValue = "OK"; } else { functionReturnValue = "**ERROR: invalid signature"; } return functionReturnValue; } public static string hashHexFromString_SHA1(string strMessage) { // $GENERIC-FUNCTION$ // INPUT: Message to be hashed in a string of ANSI characters // OUTPUT: SHA-1 digest in hex-encoded format // REMARK: Hardly worth it as a function in .NET! return Hash.HexFromString(strMessage, HashAlgorithm.Sha1); } // Use overloads for optional parameters... public static string rsaGetDigestFromBase64Signature(string strSigBase64, string strKeyFile, bool ShowDebug) { return m_rsaGetDigestFromBase64Signature(strSigBase64, strKeyFile, ShowDebug); } public static string rsaGetDigestFromBase64Signature(string strSigBase64, string strKeyFile) { return m_rsaGetDigestFromBase64Signature(strSigBase64, strKeyFile, false); } private static string m_rsaGetDigestFromBase64Signature(string strSigBase64, string strKeyFile, bool ShowDebug) { // $GENERIC-FUNCTION$ // INPUT: Signature value in base64 format; filename of public key file. // OUTPUT: SHA-1 digest of signed message in hex-encoded format string strPublicKey = null; byte[] abBlock = null; byte[] abDigest = null; string strDigest = null; // 1. Convert to byte array abBlock = Cnv.FromBase64(strSigBase64); if (ShowDebug) Console.WriteLine("Signature block length = " + abBlock.Length + " bytes"); if (ShowDebug) Console.WriteLine(Cnv.ToHex(abBlock)); // 2. Read the public key file into our internal string format strPublicKey = Rsa.ReadPublicKey(strKeyFile).ToString(); if (strPublicKey.Length <= 0) { return "**ERROR: cannot read public key file"; } if (ShowDebug) Console.WriteLine("Public key size is " + Rsa.KeyBits(strPublicKey) + " bits."); // 3. Decrypt the signature block using the public key abBlock = Rsa.RawPublic(abBlock, strPublicKey); // Show the decrypted signature block in hex format if (ShowDebug) Console.WriteLine(Cnv.ToHex(abBlock)); // 4. Extract the SHA-1 message digest from the block abDigest = Rsa.DecodeDigestForSignature(abBlock); if (abDigest.Length < 0) { return "**ERROR: Decryption Error"; } if (ShowDebug) Console.WriteLine("Message digest is " + abDigest.Length + " bytes long"); strDigest = Cnv.ToHex(abDigest); if (ShowDebug) Console.WriteLine("DIGEST=" + strDigest); // Return extracted digest in hex form return strDigest; } // ******* // TESTS * // ******* public static void Pt_CreateSignature_Especificacao() { // Compute the correct signature values for the examples given in [ESPECIF-2010] string strMessage = null; string strKeyFile = null; string strSigBase64 = null; // Private key file: sample provided by DGCI strKeyFile = "Chave_Privada.txt"; // Registo 1 // Message string to be signed as per [REF] specifications // (not including quotes; no intermediate spaces or CR-LF chars) strMessage = "2010-05-18;2010-05-18T11:22:19;FAC 001/14;3.12;"; strSigBase64 = rsaCreateSignatureInBase64(strMessage, strKeyFile, false); Console.WriteLine("1: " + strSigBase64); // Original value = "Am1K5+CP4LDNVDZYvcL...UnuJrca+7emgb/kpU=" // Correct value = "OpE9IFpK5cJO8SwC5BUy3XTCkjVK5JsjHo3TvWjM9D09aw9wabH+sGNOs7hx4iEoOP9UY6DGsR6PgIkAZSTYInhbgs2x9sxWkr417aCKoSGY4awDIVB9aUlQ91SseH3Hk5S24PfjXFDn44acWhQL4INp9Re+dC51YNC7MrpAmP4=" // Registo 2 strMessage = "2010-05-18;2010-05-18T15:43:25;FAC 001/15;25.62;" + "Am1K5+CP4LDNVDZYvcLYGpnu8/1b+WWkzgoe8sbZhvk6QFzFvNN77Zsq+cHNm52jCVS" + "EDgWLGHgPS1wcT8ZG7w6KgVq+2/VgOU+xKNt0lcC3gouyarZvcZpZclIReDgLh6m3nv8D" + "YYHKAOQc+eCi/BQ4LqUnuJrca+7emgb/kpU="; strSigBase64 = rsaCreateSignatureInBase64(strMessage, strKeyFile, false); Console.WriteLine("2: " + strSigBase64); // Original value = "Jh7/rmIILVwbrPLTdk...RG8JS1Uos78=" // Correct value = "hsR2TYJtl0mad+zVAhGxNLxs6matD+T8Y8IpEo12I3szSohdwwWVOfPclnu6D23pZ0w8g/Eh0TOMzYNsdkkUJpM68/nKH2d8ehI8HT85NUyLgrGhC8msXHK+ASCCOU0RN4mr04249IG+MuOAlnW8EcMJNZA+lTf94MbpJNqRYUw=" } public static void Pt_CreateSignature_SAFT_IDEMO() { // Reproduce the signatures (<Hash>) values in SAFT_IDEMO599999999.XML string strMessage = null; string strKeyFile = null; string strSigBase64 = null; // Sample XML data file and private key file provided by DGCI at: // <http://info.portaldasfinancas.gov.pt/pt/apoio_contribuinte/certificacaosoftware.htm> // <http://info.portaldasfinancas.gov.pt/NR/rdonlyres/371795DE-D83B-4B0E-B673-010C0F523EFB/0/SAFT_IDEMO599999999.XML> // Private key file: // <http://info.portaldasfinancas.gov.pt/NR/rdonlyres/70FDBA7F-1C48-496C-B9C3-4F45B4FAA55F/0/Chave_Privada.txt> strKeyFile = "Chave_Privada.txt"; // Message string from 1st record in SAFT_IDEMO599999999.XML (starting at line 9492) // This is the first record of the series, so the "Hash" field is empty strMessage = "2008-03-10;2008-03-10T15:58:00;FT 1/1;28.07;"; strSigBase64 = rsaCreateSignatureInBase64(strMessage, strKeyFile); Console.WriteLine("FT 1/1: " + strSigBase64); // Expected signature = "F8952fjEClltx2tF9m6/...jsablpR6A4=" // Record 2: carry forward the Hash field from record 1 strMessage = "2008-09-16;2008-09-16T15:58:00;FT 1/2;235.15;" + "F8952fjEClltx2tF9m6/QTFynFjSuiboMslNZ1ag9oR5iIivgYYa0cNa0wJeWXlsf8QQVHUol303hp7XmIy5/kFOiV0Cv8QH6SF0Q5zNsDtpeFh2ZJ256y0DkJMSQqCq3oSka+9zIXXRkXgEsSv6VScCYv8VTlIcGjsablpR6A4="; strSigBase64 = rsaCreateSignatureInBase64(strMessage, strKeyFile); Console.WriteLine("FT 1/2: " + strSigBase64); // Expected signature = "wh0uUgI/fLTt9Kpb/hFw.../bU651c3va0=" // Record 3: carry forward the Hash field from record 2 strMessage = "2008-09-16;2008-09-16T15:58:00;FT 1/3;679.61;" + "wh0uUgI/fLTt9Kpb/hFwN6VIkjWZWI8R2TxtHUMyRL0a7hyQLIvoxuqGzKfzUfvAV3E1gxpKZtai5qli6Nx7unqzC4vIoc6rtb3ObuxifXiBAUD95BMh31T73O6cgcwhGR0YhiV/E6jfCbihJL2B/2s+/qsaL7OY/bU651c3va0="; strSigBase64 = rsaCreateSignatureInBase64(strMessage, strKeyFile); Console.WriteLine("FT 1/3: " + strSigBase64); // Expected signature = "iVYbEDuefMedP5DHBfl+...Z4+0oX3qdxY=" // ...etc, etc, ... Console.WriteLine("..."); // Record 6: carry forward the Hash field from record 5 strMessage = "2008-10-21;2008-10-21T15:32:00;FT 1/6;3600.00;" + "nv2NKxZ5c/1aC/D6RgCL0Z1EmvkELlxQ0qUQwu/5C+5fvDwb5+nigoN8G5NZjebQTJefCK3nT7DxYjfuTLaVwkDHsHDqW+WzNJ7r2VlGeeBV/TKpgYwy45Vb9dlpx3pwDftlfV44yLJN/uO6RIQnTU4o9+r0DtoPibhm8zEAaA4="; strSigBase64 = rsaCreateSignatureInBase64(strMessage, strKeyFile); Console.WriteLine("FT 1/6: " + strSigBase64); // Expected signature = "V5HNew6rKFxmSeNTSmp5...AqTsAdmi9WU=" // Record NC 1/1: We start a new series, so leave hash field empty strMessage = "2008-09-16;2008-09-16T15:58:00;NC 1/1;235.15;" + ""; strSigBase64 = rsaCreateSignatureInBase64(strMessage, strKeyFile); Console.WriteLine("NC 1/1: " + strSigBase64); // Expected signature = "jTCuqNUzz+QDJiHeOGwk...DpQ3kO770ko=" // Record NC 1/2: carry forward the Hash field from record 1 strMessage = "2008-09-16;2008-09-16T15:58:00;NC 1/2;2261.34;" + "jTCuqNUzz+QDJiHeOGwkJzBoJwqNOLRMs0ISI7TXddv5RrH8KmKtaMgzaZxWY9QO4U5aoasqHRieqof+7oXq0fALKcROyVxU/PQRsh7eKani46ENkrkQNXREjAdz1nvoCSAKphd21nfMJupWlYTAJV2H0A7I+MGcDpQ3kO770ko="; strSigBase64 = rsaCreateSignatureInBase64(strMessage, strKeyFile); Console.WriteLine("NC 1/2: " + strSigBase64); // Expected signature = "YIt8KKn+0m9HpK2BpsnY...vfxhM7re2SU=" } public static void Pt_VerifySignature() { string strMessage = null; string strKeyFile = null; string strSigBase64 = null; string strStatus = null; // Public key file: // <http://info.portaldasfinancas.gov.pt/NR/rdonlyres/547D8EFD-4B88-4072-8CD8-17DF08FE847A/0/Chave_Publica.txt> strKeyFile = "Chave_Publica.txt"; // Message string and "Hash" from 1st record in SAFT_IDEMO599999999.XML (starting at line 9492) strMessage = "2008-03-10;2008-03-10T15:58:00;FT 1/1;28.07;"; strSigBase64 = "F8952fjEClltx2tF9m6/QTFynFjSuiboMslNZ1ag9oR5iIivgYYa0cNa0wJeWXlsf8QQVHUol303hp7XmIy5/kFOiV0Cv8QH6SF0Q5zNsDtpeFh2ZJ256y0DkJMSQqCq3oSka+9zIXXRkXgEsSv6VScCYv8VTlIcGjsablpR6A4="; strStatus = rsaVerifySignature(strSigBase64, strKeyFile, strMessage, true); Console.WriteLine("Result=" + strStatus); // Corrected version of Registo 2 in Especificacao [RE-1] strMessage = "2010-05-18;2010-05-18T15:43:25;FAC 001/15;25.62;" + "Am1K5+CP4LDNVDZYvcLYGpnu8/1b+WWkzgoe8sbZhvk6QFzFvNN77Zsq+cHNm52jCVS" + "EDgWLGHgPS1wcT8ZG7w6KgVq+2/VgOU+xKNt0lcC3gouyarZvcZpZclIReDgLh6m3nv8D" + "YYHKAOQc+eCi/BQ4LqUnuJrca+7emgb/kpU="; strSigBase64 = "hsR2TYJtl0mad+zVAhGxNLxs6matD+T8Y8IpEo12I3szSohdwwWVOfPclnu6D23pZ0w8g/Eh0TOMzYNsdkkUJpM68/nKH2d8ehI8HT85NUyLgrGhC8msXHK+ASCCOU0RN4mr04249IG+MuOAlnW8EcMJNZA+lTf94MbpJNqRYUw="; strStatus = rsaVerifySignature(strSigBase64, strKeyFile, strMessage, true); Console.WriteLine("Result=" + strStatus); } // Extract the message digest from a given signature. // (Use this in your debugging) public static void Pt_ExtractDigest() { string strKeyFile = null; string strSigBase64 = null; string strDigest = null; // Public key file we created ourselves strKeyFile = "Chave_Publica.txt"; // Signature in base64 form. strSigBase64 = "F8952fjEClltx2tF9m6/QTFynFjSuiboMslNZ1ag9oR5iIivgYYa0cNa0wJeWXlsf8QQVHUol303hp7XmIy5/kFOiV0Cv8QH6SF0Q5zNsDtpeFh2ZJ256y0DkJMSQqCq3oSka+9zIXXRkXgEsSv6VScCYv8VTlIcGjsablpR6A4="; strDigest = rsaGetDigestFromBase64Signature(strSigBase64, strKeyFile); Console.WriteLine("DIGEST FOUND=" + strDigest); Console.WriteLine("EXPECTED =" + "BB5C0F8FF294016FA4F0A3265410249D275B0986"); } // Example to read in key files directly as a string... // Use this as an alternative to passing filenames. // The CryptoSys RSA_Read* functions will accept a string containing the file contents. // You must still use the RSA_Read* functions to obtain the ephemeral "internal" key strings to use with the RSA_Raw* functions. public static void Pt_CreateSignatureWithKeyAsString() { string strMessage = null; string strKeyFile = null; string strSigBase64 = null; string strDigest = null; string strStatus = null; // As an alternative to passing a filename, you may instead pass the key data directly as a "PEM" string strKeyFile = "-----BEGIN RSA PRIVATE KEY-----" + "MIICXgIBAAKBgQDWDX9wVqj6ZqNZU1ojwBpyKKkuzHTCmfK39xx/T9vWkqpcV7h3sx++ZOv2KhhNkIe/1I4OCWDPCXRE4g0uIQr0NS29vMlP3aHHayy76+lbBCNVcHFxM0ggjre1acnD0qUpZ6Vza7F+PpCyuypD2V/pkL1nX9Z6z5uYyqc0XaSFdwIDAQABAoGBAJCA7j6Vkl/w+GeuOJUX9AK" + "LZqN8TXquWUhOX4OnEt9Jhg7u/U55s31iPlWh12RNpQcg5IGfXSaH2GFEReeVUQGMrb89kkfbeY5HSRHh3/sBSyJTMn2cjsqfUnUJhywJPxT8NFIcS2pRBJe/QN/pL+M2jk+Fl40wyVXRhnog+4fhAkEA//Tijl5SA7a/uCyfOQkJ6yop13dfN4EHEWYMzI6SlnYWuJfdIOz4wkzBWgD0r/btFA" + "ths1zElmRWINjWsB84ZwJBANYWywqsZA4FShXkDEWfG1GbrEIXiOnPJay2p7en3DQ+lx4GfE10iO52f54QRu13SZp06050YkrWcRfBGCXaYHECQQCU8vMsmmLr2ltzWDRIQqRM/7pdsw/sAuAUFej42Tcg7BOI1IdQc9bHa1dRgyDhjbalZYIzmJamVjlw3/7/ewudAkB/ipatpiP5YldPkUtqU" + "q5QwOAvg5vSRtEYAr0KIZuDGGKoxY5aCnnlLn06qlHG+JDFzq+8ToOcOAKp9yQusNlRAkEA+0DarosTmn2I7+fj2/3ojVKdW/eIisz547U3bGbW/hBCZRi+y+cQnPlZ7Cr4LcGInhdxR+fSWptMNwrDCUiYHA==" + "-----END RSA PRIVATE KEY-----"; // Exact message string to be signed: strMessage = "2008-03-10;2008-03-10T15:58:00;FT 1/1;28.07;"; strSigBase64 = rsaCreateSignatureInBase64(strMessage, strKeyFile); Console.WriteLine("<Hash>=" + strSigBase64); // Expected signature = "F8952fjEClltx2tF9m6/...jsablpR6A4=" // Similarly, we can pass the public key data as a "PEM" string strKeyFile = "-----BEGIN PUBLIC KEY-----" + "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDWDX9wVqj6ZqNZU1ojwBpyKKkuzHTCmfK39xx/T9vWkqpcV7h3sx++ZOv2KhhNkIe/1I4OCWDPCXRE4g0uIQr0NS29vMlP3aHHayy76+lbBCNVcHFxM0ggjre1acnD0qUpZ6Vza7F+PpCyuypD2V/pkL1nX9Z6z5uYyqc0XaSFdwIDAQAB" + "-----END PUBLIC KEY-----"; strDigest = rsaGetDigestFromBase64Signature(strSigBase64, strKeyFile); Console.WriteLine("DIGEST EXTRACTED=" + strDigest); strStatus = rsaVerifySignature(strSigBase64, strKeyFile, strMessage); Console.WriteLine("Result=" + strStatus); } //**************************************************************************************************************** // THE FOLLOWING SHOWS HOW TO CREATE A PAIR OF RSA PUBLIC AND PRIVATE KEYS COMPATIBLE WITH THE OPENSSL PEM FORMAT. // AND HOW TO CREATE AN X.509 CERTIFICATE FROM THE KEYS AND HOW TO VERIFY THAT A PAIR OF KEYS MATCH. //**************************************************************************************************************** public static void Pt_Create_Keys() { // Create an RSA key pair int nRet = 0; string strPublicKeyFile = null; string strEncPrivateKeyFile = null; string strPemPrivateKeyFile = null; StringBuilder sbIntPrivateKey = null; string strPassword = null; strPassword = "password"; // Password for encrypted private key file (please pick something stronger!) // OpenSSL commands to create an RSA key pair: // cmd> openssl genrsa -out PrivateKey.PEM 1024 // cmd> openssl rsa -in PrivateKey.PEM -out PublicKey.PEM -outform PEM –pubout // strPublicKeyFile = "Pt_PublicKey.PEM"; // This is created in OpenSSL PEM format strEncPrivateKeyFile = "Pt_PrivateKey.EPK"; // This is in encrypted form strPemPrivateKeyFile = "Pt_PrivateKey.PEM"; // This is in OpenSSL PEM format // RSA_MakeKeys creates the key pair with an encrypted private key. Console.WriteLine("Creating keys. This may take a few seconds..."); nRet = Rsa.MakeKeys(strPublicKeyFile, strEncPrivateKeyFile, 1024, Rsa.PublicExponent.Exp_EQ_65537, 2048, strPassword, 0, HashAlgorithm.Sha1, Rsa.Format.SSL, false ); Console.WriteLine("RSA_MakeKeys returns " + nRet + " (expected 0)"); // Adjust the white space in the public key file created here to suit the requirements of the DGCI. // The file is a valid PEM format either way, but DGCI insists it should be exactly 272 bytes long. // See <http://www.cryptosys.net/pki/portugal_DGCI_billing_software.html#key278vs272>. FixFileDosToUnix(strPublicKeyFile); // To save the encrypted private key in unencrypted OpenSSL format, we read the key into an internal key string // and then save to a file in the correct format. // Note that the "internal" key string is ephemeral. // CAUTION: saving a production private key in unencrypted form is a huge security risk! sbIntPrivateKey = Rsa.ReadEncPrivateKey(strEncPrivateKeyFile, strPassword); if (sbIntPrivateKey.Length == 0) { Console.WriteLine("Error reading encrypted private key file"); return; } // Now save in correct form nRet = Rsa.SavePrivateKeyInfo(strPemPrivateKeyFile, sbIntPrivateKey.ToString(), Rsa.Format.SSL); Console.WriteLine("RSA_SavePrivateKeyInfo returns " + nRet + " (expected 0)"); // Clear the private key Wipe.String(sbIntPrivateKey); // Do some checks that the OpenSSL keys match if (!Pt_DoKeyPairFilesMatch(strPemPrivateKeyFile, strPublicKeyFile)) { Console.WriteLine("Error: keys do not match"); return; } } public static bool FixFileDosToUnix(string fileName) { // Converts the line endings in a file from CR-LF pairs to single LF characters string strBuffer = null; FileInfo finfo = new FileInfo(fileName); // Check if file exists if (finfo.Exists) { // Read in the file to a string FileStream fsi = finfo.OpenRead(); StreamReader sr = new StreamReader(fsi); strBuffer = sr.ReadToEnd(); //'Console.WriteLine(strBuffer) sr.Close(); fsi.Close(); } else { return false; } // Edit the string Console.WriteLine("Input is {0} bytes long", strBuffer.Length); strBuffer = strBuffer.Replace("\r\n", "\n"); Console.WriteLine("Output is {0} bytes long", strBuffer.Length); // Re-write the file FileStream fs = null; StreamWriter sw = null; fs = new FileStream(fileName, FileMode.Create, FileAccess.Write); sw = new StreamWriter(fs); sw.Write(strBuffer); sw.Close(); fs.Close(); return true; // Success } public static void Pt_Test_DoKeyPairFilesMatch() { // Check that the two OpenSSL-format key files match... string strPublicKeyFile = null; string strPemPrivateKeyFile = null; strPublicKeyFile = "Pt_PublicKey.PEM"; // This is in OpenSSL PEM format strPemPrivateKeyFile = "Pt_PrivateKey.PEM"; // This is in OpenSSL PEM format if (!Pt_DoKeyPairFilesMatch(strPemPrivateKeyFile, strPublicKeyFile)) { Console.WriteLine("Error: keys do not match"); } else { Console.WriteLine("OK, keys match."); } } public static bool Pt_DoKeyPairFilesMatch(string strPemPrivateKeyFile, string strPublicKeyFile) { // Returns TRUE if the public and private keys in the given files match or FALSE if they do not. int nRet = 0; StringBuilder sbIntPrivateKey = null; string strIntPublicKey = null; int nHashCodePub = 0; int nHashCodePri = 0; // Read in the keys from the files to internal key strings sbIntPrivateKey = Rsa.ReadPrivateKeyInfo(strPemPrivateKeyFile); if (sbIntPrivateKey.Length == 0) { Console.WriteLine("Error reading PEM private key file"); return false; } strIntPublicKey = Rsa.ReadPublicKey(strPublicKeyFile).ToString(); if (strIntPublicKey.Length == 0) { Console.WriteLine("Error reading PEM private key file"); return false; } // Display the key lengths Console.WriteLine("Private key is " + Rsa.KeyBits(sbIntPrivateKey.ToString()) + " bits"); Console.WriteLine("Public key is " + Rsa.KeyBits(strIntPublicKey) + " bits"); // Display the "hashcode" (this is an internal hash code which should be equal for matching keys) nHashCodePri = Rsa.KeyHashCode(sbIntPrivateKey.ToString()); nHashCodePub = Rsa.KeyHashCode(strIntPublicKey); Console.WriteLine("Hashcodes are {0,8:X} and {0,8:X}", nHashCodePri, nHashCodePub); // Verify that a pair of "internal" RSA private and public key strings are matched nRet = Rsa.KeyMatch(sbIntPrivateKey.ToString(), strIntPublicKey); Console.WriteLine("RSA_KeyMatch returns " + nRet + " (0 => keys match)"); // Clear the private key Wipe.String(sbIntPrivateKey); return (nRet == 0); } public static void Pt_SavePrivateKeyAsEncrypted() { // Save an unencrypted OpenSSL private key in PKCS-8 encrypted form int nRet = 0; string strEncPrivateKeyFile = null; string strOpenSSLPrivateKeyFile = null; StringBuilder sbIntPrivateKey = null; string strPassword = null; strOpenSSLPrivateKeyFile = "Pt_PrivateKey.pem"; // This was created in unencrypted OpenSSL PEM format strEncPrivateKeyFile = "Pt_PrivateKeyEncrypted.pem"; // This is in encrypted form strPassword = "password"; // CAUTION: Pick something better than this! // Read private key info into ephemeral internal string sbIntPrivateKey = Rsa.ReadPrivateKeyInfo(strOpenSSLPrivateKeyFile); // Save in encrypted file form (set nCount to 2000 or so) nRet = Rsa.SaveEncPrivateKey(strEncPrivateKeyFile, sbIntPrivateKey.ToString(), 2000, strPassword, Rsa.PbeOptions.Default, Rsa.Format.PEM); Console.WriteLine("RSA_SaveEncPrivateKey returns " + nRet + " (expected 0)"); // Clear the private key Wipe.String(sbIntPrivateKey); } public static void Pt_Make_X509_CertSelfSigned() { // Use the RSA key file to make a self-signed X.509 certificate containing the public key // Requirements from [ESPECIF-2010] 5.2.2: // Formato = x.509 // Charset = UTF-8 // Encoding = Base-64 // Endianess = Little Endian [COMMENT: this is not relevant for X.509!] // OAEP Padding = PKCS1 v1.5 padding [COMMENT: This is NOT "OAEP" padding] // Tamanho da chave privada = 1024 bytes // Formato do Hash da mensagem = SHA-1 int nRet = 0; string strEncPrivateKeyFile = null; string strPassword = null; X509.KeyUsageOptions keyUsage = default(X509.KeyUsageOptions); X509.Options options = default(X509.Options); string strCertFile = null; string strDN = null; int nYearsValid = 0; int nCertNum = 0; // With CryptoSys PKI we need to use the encrypted private key file we created with RSA_MakeKeys, not the OpenSSL one. strEncPrivateKeyFile = "Pt_PrivateKey.EPK"; // This is in encrypted form strPassword = "password"; // The password for the encrypted private key strCertFile = "Pt_SelfSigned.cer"; // The certificate file we are going to create nYearsValid = 10; // Make this as long as you want. nCertNum = 0x101; // Pick a number. Change this if you issue another certificate with a different key. // The distinguished name of both the subject and the issuer of the certificate... strDN = "C=PT;O=Exemplo Organização;CN=Certificado auto-assinado"; // Options... keyUsage = X509.KeyUsageOptions.DigitalSignature | X509.KeyUsageOptions.KeyCertSign | X509.KeyUsageOptions.CrlSign; // We want UTF-8 text and the output in PEM format... options = X509.Options.UTF8String | X509.Options.FormatPem; // Create the certificate file Console.WriteLine("Creating self-signed X.509 certificate serial number 0x{0,8:X}" + nCertNum + " for subject '" + strDN + "'"); nRet = X509.MakeCertSelf(strCertFile, strEncPrivateKeyFile, nCertNum, nYearsValid, strDN, "", keyUsage, strPassword, options); Console.WriteLine("X509.MakeCertSelf returns " + nRet + " (expected 0)"); } public static void Pt_QueryCert() { // Query an X.509 certificate for selected information string strOutput = null; string strQuery = null; string strCertFile = null; strCertFile = "Pt_SelfSigned.cer"; Console.WriteLine("For certificate file " + strCertFile); strQuery = "serialNumber"; strOutput = X509.QueryCert(strCertFile, strQuery); if (strOutput.Length <= 0) return; // catch error Console.WriteLine(strQuery + "=" + strOutput); strQuery = "subjectName"; // NB use of option to obtain UTF-8-encoded name in Latin-1 format strOutput = X509.QueryCert(strCertFile, strQuery, X509.Options.Latin1); if (strOutput.Length <= 0) return; // catch error Console.WriteLine(strQuery + "=" + strOutput); strQuery = "notAfter"; strOutput = X509.QueryCert(strCertFile, strQuery); if (strOutput.Length <= 0) return; // catch error Console.WriteLine(strQuery + "=" + strOutput); } public static void Pt_GetPublicKeyFromFileAndCert() { // Read the public key from both the original public key file and from the X.509 certificate we created. // Display some info about the key. string strPublicKeyFile = null; string strCertFile = null; string strIntPublicKey = null; strPublicKeyFile = "Pt_PublicKey.PEM"; strCertFile = "Pt_SelfSigned.cer"; // NOTE: // The internal key string is ephemeral and encrypted: it will be different each time you read it, // although it will contain the same underlying key data.. // It is only intended for "internal" use by CryptoSys PKI functions like RSA_RawPublic in the same process. // But the HashCode will always be the same for the same key value. // Read in public key from file created by RSA_MakeKeys strIntPublicKey = Rsa.ReadPublicKey(strPublicKeyFile).ToString(); Console.WriteLine("Public key is " + Rsa.KeyBits(strIntPublicKey) + " bits"); Console.WriteLine("HashCode=0x{0,8:X}", Rsa.KeyHashCode(strIntPublicKey)); Console.WriteLine(Pt_GetPublicKeyAsXml(strIntPublicKey)); // Read in (the same) public key from certificate file strIntPublicKey = Rsa.GetPublicKeyFromCert(strCertFile).ToString(); Console.WriteLine("Public key is " + Rsa.KeyBits(strIntPublicKey) + " bits"); Console.WriteLine("HashCode=0x{0,8:X}", Rsa.KeyHashCode(strIntPublicKey)); Console.WriteLine(Pt_GetPublicKeyAsXml(strIntPublicKey)); } public static string Pt_GetPublicKeyAsXml(string strIntPublicKey) { // Get the public key in <RSAKeyValue> XML form from an "internal" key string return Rsa.ToXMLString(strIntPublicKey, 0); } } }