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