Digitally Sign an XML Doc with X509Certificate Solution :)

J

Joe Van Meer

Thanks to Martin Honnen and his suggestions I have modified my code to
digitally sign an XML Doc with an X509Certificate based on Friendly Name
passed on my WFE server and sends it via hidden form field to the expecting
server looking for a digitally signed XML doc.

Tonight I got logged in successully via Orbitz's SSO process :)

My code is in C# .NET 2.0, some adjustments may hadto be made, but of
course it is all there. In my particular case, I needed to digitally sign
an xml document from classic asp call and send that thru an form to a
receving form expecting a certian form field name.

So I made a .NET DLL that could be called from classic asp - I didn't want
to affect too much of the existing infrastructure/website so I just replaced
the existing DLL only with a .NET one making it Interop enabled to be
called from classic asp.

You will of course have to create the form, etc that uses this DLL code...
it's in C# .NET 2.0

Strong Name your DLL of course as well... so the guid in your's would be
differnet than example as seen below.

In my particular case, I had to call this from classic asp, so I made the
DLL com interop, etc...

Has a built in Encrypt fucntion if needed, I didn't in end of my case but
left it in

Thanks Martin man! I'm sharing what I learned, I appreciate all your help
mate :)



To install this DLL u need to use regasm.exe & then gacutil.exe this dll and
then referenced ones as well. I've copied my install.bat file with calls - u
need to find these dlls listed and include in regasm.exe call as below...


Bat file start...

Echo "Changing directory..."
cd "C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727"

Echo "Unregistering previously installed Tools.dll COM from registry..."
regasm /u "C:\installs\Tools.dll" /tlb:Tools.tlb


Echo "Changing directory..."
cd "C:\Program Files\Microsoft Visual Studio 8\SDK\v2.0\Bin"


Echo "Removing previously installed Tools.dll from GAC..."
gacutil.exe /u Tools


Echo "Changing directory..."
cd "C:\Program Files\Microsoft Visual Studio 8\SDK\v2.0\Bin\"


Echo "Registering..."
C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\regasm "C:\installs\Tools.dll"
/tlb:Tools.tlb

Echo "Registering..."
C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\regasm
"C:\installs\System.dll" /tlb:System.tlb

Echo "Registering..."
C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\regasm
"C:\installs\System.Security.dll" /tlb:Security.tlb

Echo "Registering..."
C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\regasm
"C:\installs\System.Web.dll" /tlb:Web.tlb

Echo "Registering..."
C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\regasm
"C:\installs\System.Xml.dll" /tlb:Xml.tlb


Echo "Executing gautil.exe on Tools.dll..."
gacutil.exe /if "C:\installs\Tools.dll"


Adjust above into your own bat file...






Below, the SignXml is the main method, which u can modify at will of course.
This grabs an X509Certificate installed from trusted root off your server
via friendly name - use WSE 3.0 to adjust this Friendly Name.


The SignXml() method digitally signs the xml passed in with the
ertifcate - it's found based on friendly name. In my case this code is in
a DLL (.NET 2.) with com interop enabled - so u can call it from classic ASP
:) U can call this from your classic asp form or wherever.

The GetPayLoadData() just returns a simple one node XML userinfo XMLNode.

Useful tools in my learning:

WSE3.0!!!!!!!!! very important! Use this to adjust your friendly names of
your certs.
MMC Certiifcates Console - install your certs from here, don't double click!
Then use WSE3.0 to adjust Permissions! In my case I had to add Network user
(not asp.net or anyone else) to have read & read/execute permissions.


Cheers & thanks Martin mate, Joey :)



My DLL code:

using System;
using System.Text;
using System.Xml;
using System.Security.Cryptography;
using System.Security.Cryptography.Xml;
using System.Security.Cryptography.X509Certificates;
using System.IO;
using System.Runtime.InteropServices;
using System.Diagnostics;
using System.Web;


[assembly: CLSCompliant(true)]
namespace Tools
{


[Guid("DB78948E-CBB6-4bdc-9CB4-33A551027610")]
[ComVisibleAttribute(true)]
public interface ToolInterface
{
string SignXml(string val1, string val2, string val3);
}



[Guid("8C9052DD-CE4A-4271-A30C-A2BE9FE04AFB")]
[ComVisibleAttribute(true)]
[ClassInterface(ClassInterfaceType.AutoDual)]
public class XMLSigningTool : ToolInterface
{

public const string USA_FF = @"Certname_sg_USA";
public const string CAN_FF = @"Certname_sg_CANADA";

string strSubject = string.Empty;

//default constructor
public XMLSigningTool()
{ }

[ComVisibleAttribute(true)]
[CLSCompliant(true)]
public string SignXml(string xmlstring, string Country, string
EncryptYesOrNo)
{

try
{
//Get the payload data
XmlDocument xmlDoc = new XmlDocument();
xmlDoc = GetPayLoadData(xmlstring);


//get CERTIFICATE based on CONST
if (Country == "CAN")
{
strSubject = CAN_FF;
}
else
{
strSubject = USA_FF;
}

//Get the certificate via FreindlyName
X509Certificate2 certificate = null;
certificate = GetCertificateBySubject(strSubject);

//ASSIGN to SignedXml obj
SignedXml signedXml = GetCertSignature(certificate, xmlDoc);


// Get the XML representation of the signature and save
// it to an XmlElement object.
XmlElement xmlDigitalSignature = signedXml.GetXml();

xmlDoc.DocumentElement.AppendChild(xmlDigitalSignature);

if (EncryptYesOrNo == "yes")
{
Encrypt(xmlDoc, "Envelope", certificate);
}


string retval = string.Empty;
retval = xmlDoc.InnerXml;

//important for return to HTML FORM if u intend to
post!!!!!!
retval = retval.Replace("\"", "\'");

return retval;

}
catch (CryptographicException ex)
{
//EventViewerLogEntry("There has been error in the CGI -
Tools DLL - in the SignXml() function. Error Message: " + ex.Message);
throw ex;
}
}

//Certificate get Signature method
private SignedXml GetCertSignature(X509Certificate2 cert,
XmlDocument xmlDoc)
{
//preserve ws - difference here I noticed - mine was set to true
xmlDoc.PreserveWhitespace = false;

// Create a SignedXml object.
SignedXml signedXml = new SignedXml(xmlDoc);

// Load the certificate into a KeyInfoX509Data object
// and add it to the KeyInfo object.
//// Add an RSAKeyValue KeyInfo (optional; helps recipient find
key to validate).
KeyInfo keyInfo = new KeyInfo();
keyInfo.AddClause(new KeyInfoX509Data(cert));

signedXml.KeyInfo = keyInfo;

//CANON method
signedXml.SignedInfo.CanonicalizationMethod =
SignedXml.XmlDsigCanonicalizationWithCommentsUrl;

// Set the rsaKey to the certificate's private key
RSACryptoServiceProvider rsaKey =
(RSACryptoServiceProvider)cert.PrivateKey;

// Add the key to the SignedXml document.
signedXml.SigningKey = rsaKey;

// Create a reference to be signed.
Reference reference = new Reference();
reference.Uri = "";

// Add an enveloped transformation to the reference.
XmlDsigEnvelopedSignatureTransform env = new
XmlDsigEnvelopedSignatureTransform();
reference.AddTransform(env);

// Add the reference to the SignedXml object.
signedXml.AddReference(reference);

// Now we can compute the signature.
signedXml.ComputeSignature();

return signedXml;

}

//gets payload data and returns xmn XMLDocument
private XmlDocument GetPayLoadData(string xmlstring)
{
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.LoadXml(xmlstring);
return xmlDoc;

}

//GET CERT BY FRIENDLYNAME
public X509Certificate2 GetCertificateBySubject(string subject)
{
X509Certificate2 cert = null;

try
{

X509Store xstore = new X509Store(StoreName.Root,
StoreLocation.LocalMachine);
xstore.Open(OpenFlags.IncludeArchived);


string strOutput = string.Empty;

foreach (X509Certificate2 cert2 in xstore.Certificates)
{

if (cert2.FriendlyName == subject.ToString())
{
cert = cert2;
break;
}

}
}
catch (CryptographicException ex)
{
//EventViewerLogEntry("There has been error in the CGI - Tools
DLL - in the GetCertificateBySubject() function. Error Message: " +
ex.Message);
throw ex;
}

return cert;
}

//XML ENCRYPTION
private static void Encrypt(XmlDocument Doc, string
ElementToEncrypt, X509Certificate2 Cert)
{
try
{

// Check the arguments.
if (Doc == null)
throw new ArgumentNullException("Doc");
if (ElementToEncrypt == null)
throw new ArgumentNullException("ElementToEncrypt");
if (Cert == null)
throw new ArgumentNullException("Cert");

////////////////////////////////////////////////
// Find the specified element in the XmlDocument
// object and create a new XmlElemnt object.
////////////////////////////////////////////////

XmlElement elementToEncrypt =
Doc.GetElementsByTagName(ElementToEncrypt)[0] as XmlElement;
// Throw an XmlException if the element was not found.
if (elementToEncrypt == null)
{
throw new XmlException("The specified element was not
found");
EventViewerLogEntry("The specified element was not
found");
}

//////////////////////////////////////////////////
// Create a new instance of the EncryptedXml class
// and use it to encrypt the XmlElement with the
// X.509 Certificate.
//////////////////////////////////////////////////

EncryptedXml eXml = new EncryptedXml();

// Encrypt the element.
EncryptedData edElement = eXml.Encrypt(elementToEncrypt,
Cert);

////////////////////////////////////////////////////
// Replace the element from the original XmlDocument
// object with the EncryptedData element.
////////////////////////////////////////////////////
EncryptedXml.ReplaceElement(elementToEncrypt, edElement,
false);
}
catch (Exception ex)
{
//EventViewerLogEntry("There has been error in the CGI -
Tools DLL - in the Encrypt() function. Error Message: " + ex.Message);
throw ex;
}
}

//method to put entry itno eventviewer
private static void EventViewerLogEntry(string Message)
{

string sSource;
string sLog;
string sMessage;

sSource = "CGI - Tools";
sLog = "Application";
sMessage = Message;

if (!EventLog.SourceExists(sSource))
{
EventLog.CreateEventSource(sSource, sLog);
}

EventLog.WriteEntry(sSource, sMessage);
//EventLog.WriteEntry(sSource, sMessage,
EventLogEntryType.Warning, 234);


}


}
}


Hope this helps, because I had a hard time finding/putting together all the
pieces to accomplish this, Joey :)
 
E

Eugene Mayevski

Thanks to Martin Honnen and his suggestions I have modified my code to
digitally sign an XML Doc with an X509Certificate based on Friendly Name
passed on my WFE server and sends it via hidden form field to the
expecting server looking for a digitally signed XML doc.

And you surely support timestamping of the signature(s), without which
signatures make little sense, don't you?
 
Joined
Aug 3, 2012
Messages
1
Reaction score
0
Hi,

Thanks for the cool code.

Can you post the code for verifying the signed xml as well?
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top