using System;
using System.Text;
using System.Diagnostics;
using CryptoSysPKI;

namespace MexicoSATcsharp
{
    class MexicoSAT
    {
        [STAThread]
        static void Main(string[] args)
        {
            Console.WriteLine("PKI Version={0}", General.Version());
            Test_Mex_Show_Encoding();
            Test_Mex_Make_Digest();
            Test_Mex_Sign_Data();
            Test_Mex_AuthCert();
            Test_Mex_Verify();
        }
        static void Test_Mex_Show_Encoding()
        {
            string strData;
            byte[] b;
            string digest;
            int n;
            Console.WriteLine("SHOW LATIN-1 AND UTF-8 ENCODINGS...");
            //
            // For SAT, the message data required for the MD5 digest function must be in
            // UTF-8 format. 
            // For Mexican, this only matters for accented characters like [óéíáñ].
            // The best way in C# is to convert directly to byte format using UTF-8
            // encoding and create the message digest directly from these bytes.

            // As an aside, let's do a simple test first.
            // Given a string that includes some Latin-1 accented characters:
            strData = "abcóéíáñ";
            Console.WriteLine("Original Latin-1 string={0}", strData);
            Console.WriteLine("# of characters in Latin-1 string  = {0}", strData.Length);
            
            // Check and see if this tests as valid UTF-8
            n = Cnv.CheckUTF8(strData);
            Console.WriteLine("Cnv.CheckUTF8(strData) returns {0} (expected 0 => not valid UTF-8)", n);

            // Convert directly to bytes using default encoding 
            // (NB results may differ depending on your default encoding setup)
            // We get: 
            // Latin-1 bytes: 61 62 63 f3 e9 ed e1 f1
            b = System.Text.Encoding.Default.GetBytes(strData);
            DisplayBytes("Latin-1 bytes: ", b);
            
            // Now convert directly to bytes using UTF-8 encoding
            // You should get:
            // UTF-8 bytes:   61 62 63 c3 b3 c3 a9 c3 ad c3 a1 c3 b1
            // THIS is the input we need when using a CryptoSys PKI Hash function for SAT purposes
            b = System.Text.Encoding.UTF8.GetBytes(strData);
            DisplayBytes("UTF-8 bytes:   ", b);
            Console.WriteLine("# of bytes when converted to UTF-8 = {0}", b.Length);
            // because this digest (from the string) is wrong for SAT purposes...
            digest = Hash.HexFromString(strData, HashAlgorithm.Md5);
            Console.WriteLine("MD5(string)={0} (wrong!)", digest);
            // Compare this to the hash of the bytes directly (which is correct)
            digest = Hash.HexFromBytes(b, HashAlgorithm.Md5);
            Console.WriteLine("MD5(bytes) ={0} (correct)", digest);
        }

        static void Test_Mex_Make_Digest()
        {
            // INPUT: message data in string format; digest algorithm
            // OUTPUT: message digest
            string strData;
            byte[] b;
            string digest;

            Console.WriteLine("\nMAKING MD5 DIGEST...");
            
            // Original SAT input string in Latin-1 format
            strData = "||2.0|A|1|2009-08-16T16:30:00|1|2009|ingreso|Una sola exhibición|350.00|5.25|397.25|ISP900909Q88|Industrias del Sur Poniente, S.A. de C.V.|Alvaro Obregón|37|3|Col. Roma Norte|México|Cuauhtémoc|Distrito Federal|México|06700|Pino Suarez|23|Centro|Monterrey|Monterrey|Nuevo Léon|México|95460|CAUR390312S87|Rosa María Calderón Uriegas|Topochico|52|Jardines del Valle|Monterrey|Monterrey|Nuevo León|México|95465|10|Caja|Vasos decorados|20.00|200|1|pieza|Charola metálica|150.00|150|IVA|15.00|52.50||";
            Console.WriteLine("Original Latin-1 string={0}", strData);
            
            // Convert directly to a byte array using the System.Text.Encoding function
            b = System.Text.Encoding.UTF8.GetBytes(strData);

            // Generate the MD5 message digest directly from these bytes
            digest = Hash.HexFromBytes(b, HashAlgorithm.Md5);
            Console.WriteLine("Digest={0}", digest);
        }

        static void Test_Mex_Sign_Data()
        {
            // INPUT: message data to be signed; RSA private key file; private key password.
            // OUTPUT: signature block
            // NOTE: Error testing is done here just using crude Debug.Assert statements
            // A proper implementation should wipe any sensitive data on error and exit gracefully.
            string strData;
            StringBuilder sbPassword;
            StringBuilder sbPrivateKey;
            byte[] b;
            byte[] block;
            int keyBytes;

            Console.WriteLine("\nSIGNING THE DATA...");

            // Read in the private key from encrypted .key file
            // (files need to be in the default working directory)
            string strKeyFile = "aaa010101aaa_CSD_01.key";
            // CAUTION: DO NOT hardcode production passwords!
            sbPassword = new StringBuilder("a0123456789");
            // Note that the key is read into a StringBuilder, not a string.
            sbPrivateKey = Rsa.ReadEncPrivateKey(strKeyFile, sbPassword.ToString());
            Debug.Assert(sbPrivateKey.Length > 0);
            
            // How big is this key?
            keyBytes = Rsa.KeyBytes(sbPrivateKey.ToString());
            Debug.Assert(keyBytes > 0);
            Console.WriteLine("Key length is {0} bits/{1} bytes", Rsa.KeyBits(sbPrivateKey.ToString()), keyBytes);

            // Original SAT input string in Latin-1 format
            strData = "||2.0|A|1|2009-08-16T16:30:00|1|2009|ingreso|Una sola exhibición|350.00|5.25|397.25|ISP900909Q88|Industrias del Sur Poniente, S.A. de C.V.|Alvaro Obregón|37|3|Col. Roma Norte|México|Cuauhtémoc|Distrito Federal|México|06700|Pino Suarez|23|Centro|Monterrey|Monterrey|Nuevo Léon|México|95460|CAUR390312S87|Rosa María Calderón Uriegas|Topochico|52|Jardines del Valle|Monterrey|Monterrey|Nuevo León|México|95465|10|Caja|Vasos decorados|20.00|200|1|pieza|Charola metálica|150.00|150|IVA|15.00|52.50||";
            //Console.WriteLine("Original Latin-1 string={0}", strData);
            Console.WriteLine("# of characters in Latin-1 string  = {0}", strData.Length);
            
            // Convert directly to a byte array using the System.Text.Encoding function
            b = System.Text.Encoding.UTF8.GetBytes(strData);
            Console.WriteLine("# of bytes when converted to UTF-8 = {0}", b.Length);

            // Encode this data ready for signing into an `Encoded Message For Signature' block
            // using PKCS#1 v1.5 method and the MD5 hash algorithm.
            block = Rsa.EncodeMsgForSignature(keyBytes, b, HashAlgorithm.Md5);
            Debug.Assert(block.Length > 0);
            Console.WriteLine("Encoded Block=\n{0}", Cnv.ToHex(block));
            
            // Now sign using the RSA private key
            block = Rsa.RawPrivate(block, sbPrivateKey.ToString());
            Debug.Assert(block.Length > 0);
            // Display in hex format
            Console.WriteLine("Signature in hex=\n{0}", Cnv.ToHex(block));
            // Display in base64 format
            Console.WriteLine("Signature in base64=\n{0}", System.Convert.ToBase64String(block));
            
            // Clean up sensitive data (NB should do on error, too, in a real program)
            Wipe.String(sbPassword);
            Wipe.String(sbPrivateKey);
            Wipe.Data(block);

        }
        static void Test_Mex_Verify()
        {
            // INPUT: signature in base64 format; signer's X.509 certificate; 
            // message data that has been signed (or its digest).
            // OUTPUT: "valid signature" or "invalid signature". 
            string strSello;
            string strCertificado;
            StringBuilder sbPublicKey;
            byte[] block;
            int keyBytes;
            byte[] digest;

            Console.WriteLine("\nVERIFYING A SIGNATURE...");
            // Given the signature in base64 format
            strSello = "UlUSwGNEicfigV6i4RhTy0eb2RYWFYyFatJFcM/u5Wlkb5XRxXiCizTGw5Yxz9oZNk8msAgO4C5Gevjh+S2TJPZueYhaQeZlo6k0rE3CQexkOGVRpHkvAoAgOM5kGKzYe24DKZbTgjNL+ai+tbhEHmRAFcpv2rDpehbL3w6BnYU=";
            // And the X.509 certificate (as a base64 string)
            strCertificado = "MIIDhDCCAmygAwIBAgIUMTAwMDEyMDAwMDAwMDAwMjI1MTcwDQYJKoZIhvcNAQEFBQAwgcMxGTAXBgNVBAcTEENpdWRhZCBkZSBNZXhpY28xFTATBgNVBAgTDE1leGljbywgRC5GLjELMAkGA1UEBhMCTVgxGjAYBgNVBAMTEUFDIGRlIFBydWViYXMgU0FUMTYwNAYDVQQLFC1BZG1pbmlzdHJhY2nzbiBkZSBTZWd1cmlkYWQgZGUgbGEgSW5" +
                "mb3JtYWNp824xLjAsBgNVBAoUJVNlcnZpY2lvIGRlIEFkbWluaXN0cmFjafNuIFRyaWJ1dGFyaWEwHhcNMDgwODIxMTUyMjA4WhcNMTAwODIxMTUyMjA4WjCBmDElMCMGA1UELRMcQUFBMDEwMTAxQUFBIC8gQUFBQTAxMDEwMUFBQTEeMBwGA1UEBRMVIC8gQUFBQTAxMDEwMUhERlJYWDAxMRIwEAYDVQQKEwlNYXRyaXogU0ExEzARBgNVBA" +
                "sTClVuaWRhZCAxMCAxEjAQBgNVBAMTCU1hdHJpeiBTQTESMBAGA1UEKRMJTWF0cml6IFNBMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDpmiW1q9gyzCFtMcbaFDJexk2IpLoTdNXg4ToGRZ/f+hIjmj3N6ODWX1ARNFGYocEHf113GpW5Oe/mj6UqhBpiH4JRTNR4Udb8myJTArIlODynVHuIUuyhKo7gbMbDdXjilTAYY2XWQuQ7aDtWw" +
                "ntUmNg4vAC/F3OtRz3+y9wM5QIDAQABox0wGzAMBgNVHRMBAf8EAjAAMAsGA1UdDwQEAwIGwDANBgkqhkiG9w0BAQUFAAOCAQEAafyD4gMsOvq7E3raPntmQlJTxpWwNySqskE7fe23HVL9UKFCUlWWx/W8gluxIX9S19y17iWnGbtmbNddHxG5PznPsy/a8PlwNHjDW0FOpia2LsvDrNcdPiJhzL/1OVagkenffFf8bLEetF3ktxZ7ifcH1yxV" +
                "xpZ7PS/pe8YIOpWRuMmTV4ypGdsw9TW3HVP5IJ/canuQGPTb3LQ8ojihW2dHnC6ojaWW4GHFSZAPhQJ/DaH/UgFjaQke/RBtoAketfROdG+1qYeA1q/is04O4AXNmMByGp7ZnvGNrO9LDBvs3eKN4ZYcQyjxFEbr1X/xUqHCRF1VEkkC5jJQ1ktC4g==";
            

            // Read in Public key from X.509 certificate
            sbPublicKey = Rsa.GetPublicKeyFromCert(strCertificado);
            Debug.Assert(sbPublicKey.Length > 0);
            keyBytes = Rsa.KeyBytes(sbPublicKey.ToString());
            Console.WriteLine("Key length is {0} bits/{1} bytes", Rsa.KeyBits(sbPublicKey.ToString()), keyBytes);
            
            // Convert the signature into an array of bytes
            block = System.Convert.FromBase64String(strSello);
            Console.WriteLine("Signature block is {0} bytes long", block.Length);

            // Check the sizes match. If not: "invalid signature" error
            if (block.Length != keyBytes)
            {
                Console.WriteLine("ERROR: Invalid signature");
                return;
            }

            // Decrypt using RSA public key
            block = Rsa.RawPublic(block, sbPublicKey.ToString());
            if (block.Length == 0)
            {
                Console.WriteLine("ERROR: Invalid signature");
                return;
            }
            Console.WriteLine("Decrypted block, EM=\n{0}", Cnv.ToHex(block));

            // METHOD 1. If we have an independently-generated hash value, we extract
            // the digest from the EMSA-PKCS1-V1_5 encoded block and compare 
            // the hash values directly.
            string newHash = "4cd8ed248d7a02314c50778a37d1522d";

            // Decode this block to extract the message digest
            digest = Rsa.DecodeDigestForSignature(block);
            if (digest.Length == 0)
            {
                Console.WriteLine("ERROR: Invalid signature");
                return;
            }
            Console.WriteLine("Digest={0}", Cnv.ToHex(digest));
            // Compare the hash value hex strings (careful with the case)
            if (String.Compare(newHash, Cnv.ToHex(digest), true) == 0)
                Console.WriteLine("VALID SIGNATURE");
            else
                Console.WriteLine("ERROR: Invalid signature");

            // METHOD 2. If we have the original message data, we create the MD5 message 
            // digest of that and then do as in method (1) above.
            // i.e. newHash = Hash.HexFromBytes(message, HashAlgorithm.Md5);

            // METHOD 3. Instead of decoding the block, we apply the message encoding
            // to the original message data (or its digest) and compare the encoded blocks
            // themselves.

            // Given a message digest (in bytes) create a new EMSA-PKCS1-V1_5 encoded block
            byte[] block2 = Rsa.EncodeDigestForSignature(keyBytes, Cnv.FromHex(newHash), HashAlgorithm.Md5);
            Console.WriteLine("EM'=\n{0}", Cnv.ToHex(block2));

            // Amazingly, .NET does not have a memcmp() function to compare byte arrays directly, 
            // so we compare the blocks as hex strings instead
            if (String.Compare(Cnv.ToHex(block), Cnv.ToHex(block2)) == 0)
                Console.WriteLine("VALID SIGNATURE");
            else
                Console.WriteLine("ERROR: Invalid signature");

        }

        static void Test_Mex_AuthCert()
        {
            // INPUT: X.509 certificate to be authenticated; issuer's certificate [; issuer's issuer's certificate;...].
            // OUTPUT: "certificate is authentic", "certificate is not authentic"
            Console.WriteLine("\nAUTHENTICATING A CERTIFICATE...");
            string strCertFile = "aaa010101aaa_CSD_01.cer";
            
            // Authenticate X509 certificate using X509_VerifyCert, if you have the signer's certificate
            string strIssuerCert = "AC_SAT2048.cer";
            if (X509.VerifyCert(strCertFile, strIssuerCert) == 0)
                Console.WriteLine("OK, certificate {0} was issued by holder of cert {1}", strCertFile, strIssuerCert);
            else
                Console.WriteLine("AUTHENTICATION ERROR: Certificate {0} was NOT issued by holder of cert {1}", strCertFile, strIssuerCert);
            // TODO: authenticate the AC_SAT2048 certificate as well (it was issued by the
            // Agencia Registradora Central de Pruebas), and so on up the chain
        }

        static private void DisplayBytes(string msg, byte[] b)
        {
            int i;
            Console.Write("{0}", msg);
            for (i = 0; i < b.Length; i++)
                Console.Write("{0:x2} ", b[i]);
            Console.Write("\n");
        }

    }
}