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; } } }