/* $Id: SignEnvSig.cs $
* Last updated:
* $Date: 2022-02-13 07:57:00 $
* $Version: 1.0.0 $
*/
/******************************* LICENSE ***********************************
* Copyright (C) 2022 David Ireland, DI Management Services Pty Limited.
* All rights reserved. <https://di-mgt.com.au> <https://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>
****************************************************************************
*/
using System;
using System.Text;
using System.Text.RegularExpressions;
using System.IO;
using System.Diagnostics;
/*
* Requires DI Management CryptoSys Libraries available from <https://cryptosys.net/>.
* 1. CryptoSys PKI Pro: https://cryptosys.net/pki/
* 2. SC14N, a straightforward XML canonicalization utility: https://cryptosys.net/sc14n/
* EITHER add references to
* `diCrSysPKINet.dll` (installed by default in `C:\Program Files (x86)\CryptoSysPKI\DotNet`)
* `diSc14nNet.dll` (installed by default in `C:\Program Files (x86)\Sc14n\DotNet`)
* OR add the C# source code files `CryptoSysPKI.cs` and `diSc14nNet.cs`
* directly to your project.
*/
using Pki = CryptoSysPKI;
using Sc14n;
namespace DIManagement.SignEnvSig
{
class SignEnvSig
{
static void Main(string[] args)
{
// Setup to display debugging info in console in Debug mode
// (Should be ignored in final Release mode)
TextWriterTraceListener[] listeners = new TextWriterTraceListener[] {
new TextWriterTraceListener(Console.Out)
};
Debug.Listeners.AddRange(listeners);
// Check required libraries
Debug.WriteLine("PKI Version=" + Pki.General.Version() + " " + Pki.General.ModuleName() + " [" + Pki.General.CompileTime() + "]");
Debug.WriteLine("SC14N Version=" + Sc14n.Gen.Version() + " " + Sc14n.Gen.ModuleName() + " [" + Sc14n.Gen.CompileTime() + "]");
// Require minimum PKI
if (Pki.General.Version() < 200000) {
Console.WriteLine("PKI version must be 20.0.0 or above");
return;
}
string signedDoc = SignEnvSigDoc("env-sig1-signed.xml", "env-sig1-base.xml", "AlicePrivRSASign.p8e", "password");
// Returns either the name of the file just created or an error message beginning with "**ERROR"
if (signedDoc.IndexOf("**ERROR") >= 0) {
Console.WriteLine(signedDoc);
} else {
Console.WriteLine("Created file '{0}'", signedDoc);
}
}
/// <summary>
/// Sign an enveloped-signature XML-DSIG document.
/// </summary>
/// <param name="outputXmlFile">Name of signed output file to be created</param>
/// <param name="baseXmlFile">Path to base XML document</param>
/// <param name="keyFile">Filename of private key to be used for signing (or PEM string)</param>
/// <param name="password">Password for encrypted private key ("" if not encrypted)</param>
/// <returns>String containing the name of the output document, or an error message beginning "**ERROR:"</returns>
/// <remarks>
/// The base XML document is expected to be a well-formed XML document with placeholders of the form <c>@!...!@</c>.
/// This code assumes specific hard-coded values for algorithms in the Signature template.
/// </remarks>
static string SignEnvSigDoc(string outputXmlFile, string baseXmlFile, string keyFile, string password)
{
// DEFAULT ALGORITHMS
// These match the algorithms hard-coded into the Signature template
// (change the template, remember to change these)
const Pki.SigAlgorithm sigAlg = Pki.SigAlgorithm.Rsa_Sha1; // http://www.w3.org/2000/09/xmldsig#rsa-sha1
const Sc14n.DigAlg sigDigAlg = Sc14n.DigAlg.Sha1; // To match digest alg in signature alg
const Sc14n.DigAlg digAlg = Sc14n.DigAlg.Sha1; // http://www.w3.org/2000/09/xmldsig#sha1
const Sc14n.TranMethod tranMethod = Sc14n.TranMethod.Inclusive; // http://www.w3.org/TR/2001/REC-xml-c14n-20010315
string xmlStr;
string strMsg;
byte[] b;
StringBuilder sbPriKey;
string s, sidigval;
// Read in base XML document
xmlStr = File.ReadAllText(baseXmlFile);
// Read in private key
sbPriKey = Pki.Rsa.ReadPrivateKey(keyFile, password);
if (sbPriKey.Length == 0) {
strMsg = String.Format("**ERROR: Failed to read private key file '{0}'", keyFile);
return strMsg;
}
// Get XML as a byte array
// Assumes base file is UTF-8 encoded
b = System.Text.Encoding.UTF8.GetBytes(xmlStr);
// Compute the digest value over the canonicalized data excluding the Signature element
s = Sc14n.C14n.ToDigest(b, "Signature", Sc14n.Tran.OmitByTag, digAlg, tranMethod);
Debug.WriteLine("DigestValue=" + s);
// Set the DigestValue in the original string
xmlStr = xmlStr.Replace("@!DIGVAL!@", s);
// Compute digest of (completed) SignedInfo
sidigval = Sc14n.C14n.ToDigest(Encoding.UTF8.GetBytes(xmlStr), "SignedInfo", Tran.SubsetByTag, sigDigAlg, tranMethod);
Debug.WriteLine("SignedInfo Digest=" + sidigval);
if (sidigval.Length == 0) {
strMsg = String.Format("**ERROR: Failed to compute C14N digest value: '{0}'", Sc14n.Err.LastError());
return strMsg;
}
// Compute signature value and insert in Signature string
s = Pki.Sig.SignDigest(Pki.Cnv.FromBase64(sidigval), sbPriKey.ToString(), "", sigAlg);
// Clean up private key
Pki.Wipe.String(sbPriKey);
Debug.WriteLine(String.Format("SignatureValue=" + s));
if (s.Length == 0) {
strMsg = String.Format("**ERROR: Failed to compute signature: '{0}'", Pki.General.LastError());
return strMsg;
}
xmlStr = xmlStr.Replace("@!SIGVAL!@", s);
// Final check for uncompleted '@!..!@'
int nret = xmlStr.IndexOf("@!");
if (nret >= 0) {
Debug.WriteLine("WARNING: uncompleted '@!..!@' items");
}
// Output XML should now be complete
// Final check (Note convert string to UTF-8 bytes before passing)
s = Sc14n.C14n.ToDigest(Encoding.UTF8.GetBytes(xmlStr), "", 0, 0, 0);
if (s.Length == 0) {
strMsg = String.Format("**ERROR: XML internal problem: '{0}'", Sc14n.Err.LastError());
return strMsg;
}
// Write final document string to output file
File.WriteAllText(outputXmlFile, xmlStr);
// Return full path name of output file created
FileInfo fi = new FileInfo(outputXmlFile);
return fi.FullName;
}
}
}