XML Signature Programming
There are two main signature modes of operation for the libraries. Signing and verifying. Verifying is the simplest operation, as it (generally) operates on a DOM <Signature> structure that has already been created.
Signing on the other hand can be more difficult, as there may be a requirement to create the DOM structure necessary for the signature prior to the actual signing operation.
The rest of this section provides a very high level overview on how to use the library for signing and verification of signatures.
Two samples are provided :
- Simple HMAC Signing
- Simple DSA Validation
The code snippets are taken directly from some of the sample code provided in the src/samples directory in the distribution. More information on the API can be found in the API Documentation.
A simple HMAC Signing example
The first example is based on the simpleHMAC.cpp code in samples. It creates an XML letter, the appends a dummy signature to the end, using an enveloped-signature transform.
The following code snippet initialises Xerces, Xalan and XSEC. Note that the enveloped transform is implemented using an XPath expression, so it is imperitive the Xalan libraries are initialised.
In the sample application, the call to createLetter(impl) simply creates a letter DOM structure with a to and from address and some text. This is done using standard DOM calls via Xerces.
Once the system is initialised and the DOM document is created, a DSIGSignature object is created via the XSECProvider interface class. The signature object is then used to create a blank signature DOM node structure which is then inserted at the end of the document.
The call to newSignature creates a signature object only. No DOM nodes are created at this point. The call to setDSIGNSPrefix tells the XSEC library what namespace prefix to use for the signature object when it starts to create DOM nodes (in this case "ds" will be used). By default, the library will use "dsig" as the prefix for the name space for Digital Signatures.
Finally, the call to sig->createBlankSignature sets up both the DOM structure and the XSEC objects for a new signature with no <Reference> elements. In this case, the signature will be made using Commented C14n canonicalisation, and a HMAC-SHA1 signature.
Warning: The XSECProvider class still "owns" the DSIGSignature object. To delete the object, the original provider.release(sig) call should be used. Never delete a DSIGSignature object directly.
Create a Reference and Sign
Now that the signature object is created, the signature is inserted into the document, and a reference is created and set for an enveloping transform.
The "" parameter to createReference sets the URI attribute for the reference to be "" - indicating the root element of the document in which the signature resides. The call to appendEnvelopedSignatureTransform adds a standard eneveloped-signature transform to the Reference node.
The macro MAKE_UNICODE_STRING is defined within the library header files and is used to transcode local code page strings. There is no need to insert the reference object into the DOM structure. This is done automatically by the createReference call.
Finally we create a signing key and sign the document.
The first two code lines create an OpenSSLCryptoKeyHMAC object, and set the key value to the string "secret". The OpenSSL... classes are the interface layer between XSEC and OpenSSL. More information can be found in the API documentation, but the main point of note is that the XSEC library never deals directly with OpenSSL - it works via the XSECCrypto abstract classes which are implemented in the OpenSSLCrypto code. This would allow another person to re-implement the XSECCrypto code to use any cryptographic provider required.
Once the key is passed to the signature it is owned by the signature. The signature object will delete the key when it is itself deleted, or a new key is passed in.
The call to sig->appendKeyName() is used to append a <KeyName> element into the <KeyInfo> block. The KeyInfo block was created as part of this call.
After the call to sig->sign() the DOM structure has the correct hash and signature values. The owner program can write, store or further manipulate the document as required. If a document manipulation might affect the signature (in this case almost anything would, as we are using an enveloping transform which effectively signs everything that is not part of the signature), then a further call to sig->sign() will re-sign the changes.
The last part of the code does some work to output the new DOM structure. The output should look something like the following:
Note that the DigestValue and SignatureValue elements have been filled in.
A simple validation example
The second example takes a pre-signed document and an associated certificate and verifies the embedded signature. The document in question is a simple purchase order, and changes are made to the value of the order to demonstrate a signature failing verification.
As in the first example, Initialisation of the libraries is performed, and Xerces is used to read in the document (which in this case is stored in a string in the source code).
In order to be able to modify the contents of the document later on, we also quickly find the string containing the value of the purchase order.
For the sake of brevity, the code relating to parsing the in-memory document has been removed from the snippet below.
Create the Signature and Key objects
Now that the document is in memory, an XSECProvider is created and used to create a new DSIGSignature object. In addition, the OpenSSL interface routines are used to read in a certificate and obtain the associated public key.
In this case, the signature is create with the newSignatureFromDOM method. This tells the library that the signature structure (although not necessarily a signed structure) already exists in the DOM nodes. The library attempts to find the <Signature> node so that the load will work. (The library will throw an XSECException if it cannot find the Element.)
The later call to sig->load() tells the library to read the DOM structure and create the appropriate DSIG elements.
In this case an OpenSSLCryptoX509 object is also created. It is used to read in the cert string and convert to an X509 structure. This could also be done using standard calls directly to OpenSSL, but this is a quick shortcut.
Find a key
As we already know the key, the following code snippet loads the key directly from the related X509. However prior to doing this, the code demonstrates using the DSIGKeyInfo structures to find the key name that was embedded in the certificate. In an application, this could be used to reference the correct key to be passed in. (Maybe via an XKMS call.)
the safeBuffer type is used extensively within the XSEC library to safely handle variable length strings and raw buffers. The call to rawCharBuffer() simply returns a (char *) type pointer to the buffer within the safeBuffer
The call to clonePublicKey() returns a copy of the public key embedded in the certificate. It is owned by the caller, so in this case it can safely be passed to the DSIGSignature object where it will be destroyed when another key is loaded or the object is released by the XSECProvider.
Validate the signature
Finally the signature is validated. In this case, we validate it three times. First with the original DOM structure, then with the price changed and finally with the price set back to the original value.
When run, the program outputs the following:
XML Encryption Programming
As with signatures, there are two main modes of operation for the library when performing encryption functions - Encryption and Decryption. Decryption is generally fairly simple, as the library will handle most of the work around de-referencing key material and re-creating a DOM document (or returning a byte stream).
Encryption is fairly simple if you are trying to encrypt a DOM structure. The library will encrypt the nodes and then replace them with the encrypted version. However if you want to embed an arbitrary encrypted object in the document, you will need to encrypt it first and then pass the encrypted text into the library.
The rest of this page looks at some simple examples around encrypting and decrypting nodes within an XML document
A simple encryption example
The next example encrypts an element (and all its children) from a pre-generated document. It uses a randomly generated key to handle the bulk encryption, and then encrypts this using an RSA public key. The resultant encrypted key is embedded in an <EncryptedKey> element.
This example can be found in the src/samples directory as simpleEncrypt.cpp.
The first step is initialisation of Xerces, Xalan (if used) and XML-Security. Once this is done, we create a document. For brevity, the details of the call to createLetter are not included on this page. The function is very simple - it creates an XML DOM document that represents a letter, and sets a global variable (g_toEncrypt) that will be used later on to determine what node to encrypt.
Setup for Encryption
Once the library is initialised, we create a XENCCipher object in a manner similar to the creation of a DSIGSignature object. The XENCCipher object is used to actually perform encryption/decryption functions and to manipulate the various encryption objects provided by the library.
As well as creating the XENCCipher object, the sample uses the RAND_bytes function within the OpenSSL library to create a random key that will be used during the encryption process.
Encryption of Element
The actual code to perform encryption is very small. Most of the complexity for standard encryption is hidden within the library.
The first two lines of code wrap the generated key bytes in an OpenSSL 3DES key. This is then passed into the cipher object with a call to setKey(key).
The last line in the following block performs the actual encryption. the first parameter to cipher->encryptElement is the node that will be encrypted. The second is the algorithm to be used. This is used to calcualte the Algorithm URI to be set in the <EncryptedData> element.
This call to EncryptElement will encrypt the provided element using the key set previously. The passed in element will be replaced with an <EncryptedData> element containing the encrypted version of the element and all its children.
If no further information is required to be embedded in the <EncryptedData> structure (such as <KeyInfo> nodes), the usage of the library could be terminated here.
Create an <EncryptedKey>
The following snippet of code uses the previously created XENCCipher object to encrypt the pseudo random key using an RSA key loaded from a X.509 certificate.
The first two lines load the certificate into an OpenSSLCryptoX509 structure, which is then used to extract the public key from the certificate and pass into the cipher.
A call to setKEK is used rather than setKey. This call is used to tell the cipher object that the key being used is a Key Encryption Key, and should be used for encrypting/decrypting <EncryptedKey> elements.
The final line actually performs the encryption and created the <EncryptedKey> structure. The first two parameters define the buffer and its length to be encrypted. The last defines the encryption algorithm to be used.
The encryptedKey method returns an XENCEncryptedKey object. This contains the DOM structure for the object, but it is not yet rooted in a particular document. (Although it is created using the DOMDocument that was passed in during the call to newCipher.)
Append <EncryptedKey> to <EncryptedData>
The final part (other than outputting the result) is to retrieve the <EncryptedData> element that was previously created and append the newly created <EncryptedKey> as a <KeyInfo> element.
The above code results in a document that contains the newly created <EncryptedData> as follows:
A simple decryption example
The final example shows how to use the library to decrypt an EncryptedData structure. A private key is loaded as a Key Encryption Key (KEK), and a call is made to the library which decrypts the encrypted data and inserts the resulting DOM nodes back into the original document.
This example can be found in the src/samples directory as simpleDecrypt.cpp.
The setup process is much the same as for simpleVerify. The document (which is the document created in simpleEncrypt) is parsed using Xerces and a DOMDocument is returned.
Load Private Key
The simpleDecrypt uses a preloaded RSA private key for the decryption. A key resolver (XSECKeyInfoResolver) can also be used to provide a callback mechanism such that applications can determine the correct key at run time.
The following code uses a XSECProvider to obtain a XENCCipheruses OpenSSL to load the private key from the s_privateKey char array.
The key is loaded using a call to setKEK. This method loads the key as a Key Encryption Key - which means it will be used to decrypt an <EncryptedKey> structure.
Now that the key is loaded, the actual decryption is performed using two lines of code. The first finds the node to be decrypted. In this case, the findXENCNode library function is used.
The second line, decryptElement actually performs the decryption. It performs the following steps :
- Load the <EncryptedData> structure into an XENCEncryptedData structure.
- if no decryption key is loaded (in this case, none is), search the <KeyInfo> list for an <EncryptedKey> element (one will be found in this case).
- Use the previously loaded KEK to decrypt the key found in the previous step.
- Use the decrypted key to decrypt the <EncryptedData> data
- Parse the decrypted data into DOM nodes
- Replace the <EncryptedData> with the DOM fragment returned in the previous step
The result of these steps is the decrypted letter.