using System;
using System.Text;
using System.Diagnostics;
using System.IO;
using Sc14n;

/*  
 * $Id: TestSc14n.cs $
 * Last updated:
 *   $Date: 2019-12-11 09:01 $
 *   $Version: 2.1.0 $
 */
/* Some tests using the SC14N .NET interface.
 * 
 * Requires `Sc14n` to be installed on your system: available from <http://cryptosys.net/sc14n/>
 * Add a reference to `diSc14nNet.dll`
 * 
 * Test files, e.g. `olamundo.xml`, are in `sc14n-testfiles.zip`. These must be in the CWD.
 * 
 * This is a Console Application written for target .NET Framework 4.0 and above 
 * Please report any bugs to <https://cryptosys.net/contact>
 */
/******************************* LICENSE ***********************************
 * Copyright (C) 2017-19 David Ireland, DI Management Services Pty Limited.
 * All rights reserved. <www.di-mgt.com.au> <www.cryptosys.net>
 * The code in this module is licensed under the terms of the MIT license.  
 * For a copy, see <http://opensource.org/licenses/MIT>
****************************************************************************
*/

namespace TestSc14nNet
{
    class TestSc14nNet
    {
        static void Main(string[] args)
        {
            int n, r;
            string s, fname, oname, digval, digval1, digok;
            string dig1, dig2, dig3, dig4, dig5, dig6;
            byte[] b;

            // SHOW GENERAL INFO ABOUT THE CORE LIBRARY DLL
            n = Gen.Version();
            Console.WriteLine("Version={0}", n);
            Console.WriteLine("Platform={0}", Gen.Platform());
            Console.WriteLine("ModuleName={0}", Gen.ModuleName());
            Console.WriteLine("CompileTime={0}", Gen.CompileTime());
            Console.WriteLine("LicenceType={0}", Gen.LicenceType());


            // DO TESTS ON INPUT.XML (these are from the manual)

            Console.WriteLine("FILE: {0}", "input.xml");

            // Example 1. Excludes the first element with the tag name <Signature>
            r = C14n.ToFile("c14nfile1.txt", "input.xml", "Signature", Tran.OmitByTag);
            Debug.Assert(0 == r, "C14n.ToFile failed");

            // Example 2. Finds and transforms the first element with the tag name <SignedInfo>
            r = C14n.ToFile("c14nfile2.txt", "input.xml", "SignedInfo", Tran.SubsetByTag);
            Debug.Assert(0 == r, "C14n.ToFile failed");

            // Example 3. Finds and transforms the third element with the tag name <Data>
            r = C14n.ToFile("c14nfile3.txt", "input.xml", "Data[3]", Tran.SubsetByTag);
            Debug.Assert(0 == r, "C14n.ToFile failed");

            // Example 4. Finds and transforms the element with attribute Id="foo"
            r = C14n.ToFile("c14nfile4.txt", "input.xml", "foo", Tran.SubsetById);
            Debug.Assert(0 == r, "C14n.ToFile failed");

            // Example 5. Finds and transforms the element with attribute ID="bar"
            r = C14n.ToFile("c14nfile5.txt", "input.xml", "ID=bar", Tran.SubsetById);
            Debug.Assert(0 == r, "C14n.ToFile failed");

            // Example 6. Excludes element with attribute Id="thesig"
            r = C14n.ToFile("c14nfile6.txt", "input.xml", "thesig", Tran.OmitById);
            Debug.Assert(0 == r, "C14n.ToFile failed");


            // REDO LAST TESTS BUT COMPUTE DIGEST INSTEAD OF CREATING NEW FILE

            // Example 1. Excludes the first element with the tag name <Signature>
            digval = C14n.ToDigest("input.xml", "Signature", Tran.OmitByTag, DigAlg.Sha1);
            Console.WriteLine("DIG1={0}", digval);
            Debug.Assert(digval.Length > 0, "C14n.ToDigest failed");
            dig1 = digval;

            // Example 2. Finds and transforms the first element with the tag name <SignedInfo>
            digval = C14n.ToDigest("input.xml", "SignedInfo", Tran.SubsetByTag, DigAlg.Sha1);
            Console.WriteLine("DIG2={0}", digval);
            Debug.Assert(digval.Length > 0, "C14n.ToDigest failed");
            dig2 = digval;

            // Example 3. Finds and transforms the third element with the tag name <Data>
            digval = C14n.ToDigest("input.xml", "Data[3]", Tran.SubsetByTag, DigAlg.Sha1);
            Console.WriteLine("DIG3={0}", digval);
            Debug.Assert(digval.Length > 0, "C14n.ToDigest failed");
            dig3 = digval;

            // Example 4. Finds and transforms the element with attribute Id="foo"
            digval = C14n.ToDigest("input.xml", "foo", Tran.SubsetById, DigAlg.Sha1);
            Console.WriteLine("DIG4={0}", digval);
            Debug.Assert(digval.Length > 0, "C14n.ToDigest failed");
            dig4 = digval;

            // Example 5. Finds and transforms the element with attribute ID="bar"
            digval = C14n.ToDigest("input.xml", "ID=bar", Tran.SubsetById, DigAlg.Sha1);
            Console.WriteLine("DIG5={0}", digval);
            Debug.Assert(digval.Length > 0, "C14n.ToDigest failed");
            dig5 = digval;

            // Example 6. Excludes element with attribute Id="thesig"
            digval = C14n.ToDigest("input.xml", "thesig", Tran.OmitById, DigAlg.Sha1);
            Console.WriteLine("DIG6={0}", digval);
            Debug.Assert(digval.Length > 0, "C14n.ToDigest failed");
            dig6 = digval;

            Console.WriteLine("NOTE: Should have DIG1==DIG6 and DIG3==DIG4 above.");
            Debug.Assert(dig1 == dig6 && dig3 == dig4, "Digests do not match expected values");

            // Transform entire file
            fname = "olamundo.xml";
            oname = "olamundo-out.xml";
            Console.WriteLine("FILE: {0}", fname);
            n = C14n.ToFile(oname, fname);
            Console.WriteLine("C14n.ToFile()->{0} returns {1} (expected 0 => OK)", oname, n);
            Debug.Assert(0 == n, "C14n.ToFile failed");

            // Compute digest of entire file
            digval = C14n.ToDigest(fname, 0);
            Console.WriteLine("C14n.ToDigest({0})={1}", fname, digval);
            // and do the same to the transformed file we made above
            digval1 = C14n.ToDigest(oname, 0);
            Console.WriteLine("C14n.ToDigest({0})={1}", oname, digval1);
            Debug.Assert(digval == digval1);

            // Same data, different encoding plus UTF-8 Byte Order Mark
            fname = "olamundo-utf8bom.xml";
            oname = "olamundo-utf8bom-out.xml";
            Console.WriteLine("FILE: {0}", fname);
            digval = C14n.ToDigest(fname, 0);
            Console.WriteLine("C14n.ToDigest({0})={1}", fname, digval);

            // -- we can transform (X)HTML files, too. 
            // This example is used in a detached signature: see `detached.xml`
            fname = "abc.html";
            oname = "abc-c14n-html.txt";
            Console.WriteLine("FILE: {0}", fname);
            n = C14n.ToFile(oname, fname);
            Console.WriteLine("C14n.ToFile()->{0} returns {1} (expected 0 => OK)", oname, n);
            if (n != 0) Console.WriteLine(displayError(n));
            Debug.Assert(0 == n, "C14n.ToFile failed");
            // Display entire file after transforming...
            s = System.Text.Encoding.UTF8.GetString(C14n.ToBytes(fname));
            Console.WriteLine("C14N('{0}'):", fname);
            Console.WriteLine(s);

            // Compute SHA-1 digest of entire file
            digval = C14n.ToDigest(fname, 0);
            Console.WriteLine("C14n.ToDigest({0})={1}", fname, digval);
            // and do the same to the transformed file we made above
            digval1 = C14n.ToDigest(oname, 0);
            Console.WriteLine("C14n.ToDigest({0})={1}", oname, digval1);
            Debug.Assert(digval == digval1);

            // Same again but using SHA-256
            digval = C14n.ToDigest(fname, DigAlg.Sha256);
            Console.WriteLine("SHA256('{0}'): {1}", fname, digval);

            // Extract and transform the body element
            // Output to a byte array
            fname = "olamundo-base.xml";
            Console.WriteLine("FILE: {0}", fname);
            b = C14n.ToBytes(fname, "Body", Tran.SubsetByTag);
            Debug.Assert(b.Length > 0, "C14n.ToBytes failed");
            Console.WriteLine("C14n.ToBytes(SubsetByTag) returns {0} bytes.", b.Length);
            // Note this a byte array containing UTF-8-encoded text, so be careful printing
            Console.WriteLine("C14N(Body):");
            Console.WriteLine(System.Text.Encoding.UTF8.GetString(b));

            // Compute SHA-1 digest value of the XML document excluding the <Signature> element
            // ... this is the value to be inserted into <SignedInfo>
            digval = C14n.ToDigest(fname, "Signature", Tran.OmitByTag, DigAlg.Sha1);
            Console.WriteLine("DIGVAL:");
            Console.WriteLine(digval);

            // Extract the SignedInfo element into memory
            // Note %digval% parameter to be completed
            b = C14n.ToBytes(fname, "SignedInfo", Tran.SubsetByTag);
            Debug.Assert(b.Length > 0, "C14n.ToBytes failed");
            Console.WriteLine("SIGNEDINFO (BASE):");
            Console.WriteLine(System.Text.Encoding.UTF8.GetString(b));

            // Insert the required DigestValue we prepared earlier
            // Note the SignedInfo element is *always* US-ASCII encoded,
            // so we can use the more convenient String.Replace function
            s = System.Text.Encoding.UTF8.GetString(b).Replace("%digval%", digval);
            Console.WriteLine("SIGNEDINFO (COMPLETED):");
            Console.WriteLine(s);
            // Now compute the digest value of this string
            // We could use this to compute the required signature value.
            // See the module `TestScn14PKI` for a full example using a cryptographic library.
            digval1 = C14n.ToDigest(System.Text.Encoding.UTF8.GetBytes(s), DigAlg.Sha1);
            Console.WriteLine("SHA1(signedinfo)= {0}", digval1);

            // USE EXCLUSIVE C14N

            // Examples from Section 2.2 of Exclusive XML Canonicalization Version 1.0 [RFC 3741] 

            // Use inclusive method
            fname = "example1.xml";
            oname = "example1-incl-out.xml";
            Console.WriteLine();
            Console.WriteLine("FILE: {0}", fname);
            Console.WriteLine("Using inclusive c14n:");
            r = C14n.ToFile(oname, fname, "n1:elem2", Tran.SubsetByTag);
            Debug.Assert(0 == r, "C14n.ToFile failed");
            b = C14n.ToBytes(fname, "n1:elem2", Tran.SubsetByTag);
            Debug.Assert(b.Length > 0, "C14n.ToBytes failed");
            Console.WriteLine(System.Text.Encoding.UTF8.GetString(b));
            digval = C14n.ToDigest(fname, "n1:elem2", Tran.SubsetByTag, DigAlg.Sha1);
            Debug.Assert(digval.Length > 0, "C14n.ToDigest failed");
            digok = "RSTxYngjk7kroYxpMtbJP2g7Q3s=";
            Console.WriteLine("SHA1(subset)= {0}", digval);
            Console.WriteLine("Correct SHA1= {0}", digok);
            Debug.Assert(digval == digok, "Digests do not match");

            fname = "example2.xml";
            oname = "example2-incl-out.xml";
            Console.WriteLine();
            Console.WriteLine("FILE: {0}", fname);
            Console.WriteLine("Using inclusive c14n:");
            r = C14n.ToFile(oname, fname, "n1:elem2", Tran.SubsetByTag);
            Debug.Assert(0 == r, "C14n.ToFile failed");
            b = C14n.ToBytes(fname, "n1:elem2", Tran.SubsetByTag);
            Debug.Assert(b.Length > 0, "C14n.ToBytes failed");
            Console.WriteLine(System.Text.Encoding.UTF8.GetString(b));
            digval = C14n.ToDigest(fname, "n1:elem2", Tran.SubsetByTag, DigAlg.Sha1);
            Debug.Assert(digval.Length > 0, "C14n.ToDigest failed");
            digok = "x9seNaaK3lTVs9n2WIIrIgDDU1E=";
            Console.WriteLine("SHA1(subset)= {0}", digval);
            Console.WriteLine("Correct SHA1= {0}", digok);
            Debug.Assert(digval == digok, "Digests do not match");

            // Use exclusive method - outputs should be identical
            fname = "example1.xml";
            oname = "example1-excl-out.xml";
            Console.WriteLine();
            Console.WriteLine("FILE: {0}", fname);
            Console.WriteLine("Using exclusive c14n:");
            r = C14n.ToFile(oname, fname, "n1:elem2", Tran.SubsetByTag, TranMethod.Exclusive);
            Debug.Assert(0 == r, "C14n.ToFile failed");
            b = C14n.ToBytes(fname, "n1:elem2", Tran.SubsetByTag, TranMethod.Exclusive);
            Debug.Assert(b.Length > 0, "C14n.ToBytes failed");
            Console.WriteLine(System.Text.Encoding.UTF8.GetString(b));
            digval = C14n.ToDigest(fname, "n1:elem2", Tran.SubsetByTag, DigAlg.Sha1, TranMethod.Exclusive);
            Debug.Assert(digval.Length > 0, "C14n.ToDigest failed");
            digok = "qYwgpdgV1/b3PQ3aSpMx9wKGtqY=";
            Console.WriteLine("SHA1(subset)= {0}", digval);
            Console.WriteLine("Correct SHA1= {0}", digok);
            // Correct SHA1=qYwgpdgV1/b3PQ3aSpMx9wKGtqY=
            Debug.Assert(digval == digok, "Digests do not match");

            fname = "example2.xml";
            oname = "example2-excl-out.xml";
            Console.WriteLine();
            Console.WriteLine("FILE: {0}", fname);
            Console.WriteLine("Using exclusive c14n:");
            r = C14n.ToFile(oname, fname, "n1:elem2", Tran.SubsetByTag, TranMethod.Exclusive);
            Debug.Assert(0 == r, "C14n.ToFile failed");
            b = C14n.ToBytes(fname, "n1:elem2", Tran.SubsetByTag, TranMethod.Exclusive);
            Debug.Assert(b.Length > 0, "C14n.ToBytes failed");
            Console.WriteLine(System.Text.Encoding.UTF8.GetString(b));
            digval = C14n.ToDigest(fname, "n1:elem2", Tran.SubsetByTag, DigAlg.Sha1, TranMethod.Exclusive);
            Debug.Assert(digval.Length > 0, "C14n.ToDigest failed");
            digok = "qYwgpdgV1/b3PQ3aSpMx9wKGtqY=";
            Console.WriteLine("SHA1(subset)= {0}", digval);
            Console.WriteLine("Correct SHA1= {0}", digok);
            // Correct SHA1=qYwgpdgV1/b3PQ3aSpMx9wKGtqY=
            Debug.Assert(digval == digok, "Digests do not match");

            // Show use of PrefixList with excl-c14n
            fname = "soap-ts3-signed-by-alice.xml";
            Console.WriteLine();
            Console.WriteLine("FILE: {0}", fname);
            Console.WriteLine("Using exclusive c14n with PrefixList...");
            /*
            <ds:Reference URI="#TS-3">
            <ds:Transforms>
            <ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#">
            <ec:InclusiveNamespaces PrefixList="wsse SOAP-ENV" xmlns:ec="http://www.w3.org/2001/10/xml-exc-c14n#" />
            </ds:Transform>
            </ds:Transforms>
            <ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256" />
            */
            // Transform element with wsu:Id="TS-3" using excl-c14n with PrefixList="wsse SOAP-ENV"
            b = C14n.ToBytes(fname, "wsu:Id=TS-3", Tran.SubsetById, TranMethod.Exclusive, "wsse SOAP-ENV");
            Debug.Assert(b.Length > 0, "C14n.ToBytes failed");
            Console.WriteLine(System.Text.Encoding.UTF8.GetString(b));

            // Compute SHA-256 digest (to be inserted into <DigestValue> element)
            digok = "a4cojI7ZDOI1lKvGD7OHNus7qy1DQgpqNdGZ/YEDJQo=";
            digval = C14n.ToDigest(fname, "wsu:Id=TS-3", Tran.SubsetById, DigAlg.Sha256, TranMethod.Exclusive, "wsse SOAP-ENV");
            Console.WriteLine("SHA256(#TS-3)  = {0}", digval);
            Console.WriteLine("Correct SHA256 = {0}", digok);
            Debug.Assert(digval == digok, "Digests do not match");

            // Transform SignedInfo using excl-c14n with PrefixList="SOAP-ENV"
            /*
            <ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#">
            <ec:InclusiveNamespaces PrefixList="SOAP-ENV" xmlns:ec="http://www.w3.org/2001/10/xml-exc-c14n#" />
            </ds:CanonicalizationMethod>
            */
            // Compute SHA-256 digest (to be used to compute the SignatureValue using crypto toolkit)
            digok = "VenfIoZjs3/LxtvQdQuHIizR3vKi7TViE1ZF7Ddnn8I=";
            digval = C14n.ToDigest(fname, "ds:SignedInfo", Tran.SubsetByTag, DigAlg.Sha256, TranMethod.Exclusive, "SOAP-ENV");
            Console.WriteLine("SHA256(ds:SignedInfo)= {0}", digval);
            Console.WriteLine("Correct SHA256       = {0}", digok);
            Debug.Assert(digval == digok, "Digests do not match");


            // TEST "FLATTEN" OPTION ADDED IN V2.1

            fname = "ignorable_ws.xml";
            Console.WriteLine();
            Console.WriteLine("FILE: {0}", fname);
            Console.WriteLine("Default without flatten:");
            b = C14n.ToBytes(fname, "", Tran.Entire, advOpts: AdvOptions.Default);
            Debug.Assert(0 == r, "C14n.ToBytes failed");
            Console.WriteLine(System.Text.Encoding.UTF8.GetString(b));
            digok = "JNluoz+Z+MbLrTX8W//wEEgeFpo=";
            digval = C14n.ToDigest(fname, "", Tran.Entire, DigAlg.Sha1, advOpts: AdvOptions.Default);
            Console.WriteLine("SHA1(NO-FLATTEN)= {0}", digval);
            Console.WriteLine("Correct SHA1    = {0}", digok);

            Console.WriteLine("With flatten option:");
            b = C14n.ToBytes(fname, "", Tran.Entire, advOpts: AdvOptions.Flatten);
            Debug.Assert(0 == r, "C14n.ToBytes failed");
            Console.WriteLine(System.Text.Encoding.UTF8.GetString(b));
            digok = "4ZKWJnP7dUperStlOKrq7athzxw=";
            digval = C14n.ToDigest(fname, "", Tran.Entire, DigAlg.Sha1, advOpts: AdvOptions.Flatten);
            Console.WriteLine("SHA1(FLATTEN)= {0}", digval);
            Console.WriteLine("Correct SHA1 = {0}", digok);

            Console.WriteLine("\nALL DONE.");

        }

        /// <summary>
        /// Display last error status
        /// </summary>
        /// <param name="errCode">Error code (+ve or -ve)</param>
        /// <returns>String containing error status</returns>
        private static string displayError(int errCode)
        {
            string se = Err.LastError();
            string s = string.Format("ERROR {0}: ", (errCode < 0 ? -errCode : errCode)); 
            s += Err.ErrorLookup(errCode);
            if (se.Length > 0) {
                s += ": " + se;
            }
            return s;
        }
    }
}