| 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. Pete's samples at GotDotNet.com have been downloaded over 41,000 times. You can read Peter's UnBlog Here. --><-- NOTE: Post QUESTIONS on FORUMS! |  |
Do you have a question or comment about this article? Have a programming problem you need to solve? Post it at eggheadcafe.com forums and receive immediate email notification of responses.
|