NO-SOAP, No WebService Xml Signature with WSE and Certificates
By Peter A. Bromberg, Ph.D.
Printer - Friendly Version
Peter Bromberg

Recently I was contracted to help a vendor of a major health - related provider to negotiate a modified Xml-Signature log - on process to their site.

They had been working with a variety of techniques and implementations for a lengthy period of time with no success, and were quite frustrated. I was sent a specification for the Xml Signature handshake process, along with a sample certificate, and asked to come up with the code that would enable them to perform a modified Xml Signature on a user token, using the private key from their certificate, and POST the small Xml document to the provider's server, which would use the public key to verify the signature element.



This would then initiate the session process which allows the client, who is now verified to be who they are and who has been determined to possess a valid user token, to receive a session element that they can subsequently transmit over a secure connection to negotiate a series of transactional business processes over the wire.

I chose to use the .NET platform and especially, the new Web Services Enhancements (WSE) classes, to enable the client to retrieve a certificate from his local machine user store, use it to sign the specified Xml Element and insert it into the POST Xml document, and enable them to successfully initiate the process. Both the customer and I were surprised and pleased with the fact that the whole exercise took me less than two hours of coding work, and it worked correctly the very first time the client tested it.

I'm recreating the process in a generic way here to illustrate that the X509CertificateStore classes in WSE and the SignedXml class of the System.Security.Cryptography namespace don't have to be used only with Webservices. In this case, I used them to build a Winforms helper utility that can be used to retrieve a specific X.509 certificate from the local machine user store by subject search string, load an Xml template document containing a particular XmlElement that needs to be digitally signed, and use the certificate's private key to create the final document that will be used in the successful http POST action that negotiates the receipt of a valid session token, thereby allowing the user to conduct secure business conversations over an SSL connection without fear of being compromised.

In point of fact, you will notice that none of the code presented here has ANYTHING TO DO with SOAP or WebServices at all!

The recipient would decode and check the signature of the signed element that is received, compare it to the user token transmitted, and, if valid, this would be the basis for acceptance and initiation of of the secure communication process.

The other reason for this article, and certainly not the least important one, is that although you may find several pretty good sample implementation of Xml Signature, I simply couldn't find one that retrieves and uses a CERTIFICATE in order to perform the signature process (DUH? - isn't that what people would normally want to do anyway?). This includes both books and articles talking about Xml Security that I've searched. So, I learned something useful about Xml Security by being forced to "roll my own" and I'm passing it on.

We have several objectives in accomplishing this overall process:

1.) We need to be able to install the certificate on the client machine, in the correct store.
2) We need to be able to retrieve this particular certificate via some sort of search faciilty so that we can use it.
3) We need to be able to digitally sign a specific, single Xml element in a template XmlDocument that is to be POSTed to the server, and finally,
4) We need to be able to check the signature, if desired, to be sure it is valid before we send it.

Without further explanation, lets first talk about Step 1, how to install a certificate:

On the Windows OS, secure certificates can take many forms, and fortunately almost all of them are recognized by the Certmgr.exe utility program. If you are not familiar with CertMgr.exe, you might want to review my previous article, here.

To simplify things, here is the command line that I used to create the sample certificate for this article:

makecert -sk PAB -n "CN=PeterBromberg" -ss root -sr localmachine testPAB.cer

Typically, if you have a certificate with a .cer or a .P12 extension, simply double-clicking on this out of Windows Explorer will bring up the CertMgr wizard. In this particular case, we want to install the certificate in the PHYSICAL Trusted Root Local Computer store:

 

OK, now we have our certificate and we've installed it in the store. No more worries on that score, we can now focus on writing some cool code! Let's not forget, of course, that the people on the other end need the certificate too, or they won't be able to use its public key to decode the digitally - signed Xml Element we are going to send them (unless we include an Xml representation of the public key in our document).

In order to get the certificate out of the store and use it to for Xml Signing, we need some useful code. Guess what? The WSE has some neat classes to iterate, search and retrieve certificates from these stores. Here's an example of how you might populate a listbox on a form with certificate names:

store = X509CertificateStore.CurrentUserStore(
X509CertificateStore.RootStore.ToString());
store.OpenRead();
foreach(Microsoft.Web.Services.Security.X509.X509Certificate cert in store.Certificates)
{
listBox1.Items.Add(cert.GetName());
}

// or, you could use (as we will here:)
store =X509CertificateStore.LocalMachineStore(X509CertificateStore.RootStore.ToString());
store.OpenRead();
foreach(Microsoft.Web.Services.Security.X509.X509Certificate cert in store.Certificates)
{

listBox1.Items.Add(cert.GetName());

}

Now if we wanted to retrieve a certificate based on the SelectedIndexChanged event of our listbox, we could do so in the following manner:

private void listBox1_SelectedIndexChanged(object sender, System.EventArgs e)
{
string strItem=listBox1.Items[listBox1.SelectedIndex].ToString();
MessageBox.Show(strItem);
store =X509CertificateStore.LocalMachineStore(X509CertificateStore.RootStore.ToString());
store.OpenRead();
strItem = strItem.Substring(strItem.IndexOf("O=")+2);
strItem = strItem.Substring(0,strItem.IndexOf(","));

Microsoft.Web.Services.Security.X509.X509CertificateCollection cc=store.FindCertificateBySubjectString(strItem);
cert = cc[0]; // cert would be a class-level variable
}

Note that this code:

strItem = strItem.Substring(strItem.IndexOf("O=")+2);
strItem = strItem.Substring(0,strItem.IndexOf(","));

... is sufficient to isolate a string from the GetName() method of the Certificate that will allow us to use the FindCertificateBySubjectString method, which will work on a substring, to be successful.

OK, now we know how to iterate and select certificates from a store, thanks to WSE. All we need to do now is bring in our "template" Xml Document which will contain the element we need to digitally sign, and which also contains a blank Signature element, into which we'll insert the Base64 Encoded digital signature of the element using our certificate. This final XmlDocument would represent the stuff we need to send to the server via HTTP POST. And of course, we need to know how to actually perform the digital signature process on that Xml Element so we can populate our Signature element with the result.

In our code we have a public instance of an XmlDocument, "postDocument", into which we wll load our template POST Xml, which looks like the following:

<?xml version="1.0" encoding="UTF-8"?>
<Signature xmlns="http://yourserver.com/transaction">
<SignedInfo Id="userName">
<SignedValue>JoeBlow</SignedValue>
</SignedInfo>
<SignatureValue></SignatureValue>
</Signature>

Note that the above is not the full-blown W3C specification for an Xml Signature block; its simply a proprietary simplification using only the most necessary elements. In this case, that is the namespace declaration, the SIgnedValue element (whose InnerText we will actually sign), and the SignatureValue element which will hold the actual Base 64 encoded signature value. This value will be decoded using the public key portion of the certificate, compared against the "userName" value, and validated.

Our final step is to perform the actual Signature of the userName element:

private void signIt()
{
System.Security.Cryptography.Xml.SignedXml signedXml = new System.Security.Cryptography.Xml.SignedXml();
RSA key = cert.Key;
signedXml.SigningKey = key;

// Create a data object to hold the data to sign.
System.Security.Cryptography.Xml.DataObject dataObject = new System.Security.Cryptography.Xml.DataObject();
dataObject.Data = postDocument.SelectNodes("SignedValue");
dataObject.Id = "MyObjectId";

// Add the data object to the signature.
signedXml.AddObject(dataObject);

// Create a reference to be able to package everything into themessage.

System.Security.Cryptography.Xml.Reference reference = new System.Security.Cryptography.Xml.Reference();
reference.Uri = "#MyObjectId";

// Add it to the message.
signedXml.AddReference(reference);

// Add a KeyInfo.
KeyInfo keyInfo = new KeyInfo();
keyInfo.AddClause(new RSAKeyValue(key));
signedXml.KeyInfo = keyInfo;

// Compute the signature.
signedXml.ComputeSignature();

// Get the XML representation of the signature.
XmlElement xmlSignature = signedXml.GetXml();
string strSignatureValue=xmlSignature.GetElementsByTagName("SignatureValue").Item(0).InnerText;
// add it to the post document element
postDocument.GetElementsByTagName("SignatureValue").Item(0).InnerText=strSignatureValue;
// post the following XML:
textBox1.Text=postDocument.OuterXml;
} // end signIt method

The above code should be self-explanatory, and has some commenting I've put in that should be helpful in understanding the process. Our final POST document, complete with the SignatureValue element, looks like the following:

<?xml version="1.0" encoding="UTF-8"?>
<Signature xmlns="http://yourserver.com/transaction">
<SignedInfo Id="userName">
<SignedValue>JoeBlow</SignedValue>
</SignedInfo>
<SignatureValue>PiLtQttCLZdW8Ubctb1GSZQWyluIziQXLMKnFMSrs5bmINYEv/It3rJUipkqQ5fG
ugkqFmuwDtiHiOx/KP52x/kui82PGu50GbYwjy64TCHHg1x3ih+B/BPiLSCv/alioUcWaTovhF5PSe
GgPR+o4q73rlcJxN7n+T/6OEwTNEI=
</SignatureValue>
</Signature>

 

At this point, we have successfully created a digitally signed SIgnature Element and are ready to POST the document to the server for authentication. The recipient (server) would use their public key to decode and check this signature against the userName token, determine that it is valid, and initiate an exchange, probably supplying an encoded, digitally -signed session key which we would then decode and verify upon receipt.

Finally, in order to check a computed Signature element, you can use the signedXml.CheckSignature() method. The code in the solution below conains everything you need except your own certificate, which you can create for yourself using the information in the previous article referenced above.

 

Download the code that accompanies this article

 


Peter Bromberg is a C# MVP, MCP, and .NET consultant who has worked in the banking and financial industry for 20 years. He has architected and developed web - based corporate distributed application solutions since 1995, and focuses exclusively on the .NET Platform.