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 <https://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
// <https://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://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);
}
}
}