After discussion with the Santuario PMC, it has been decided to address the long term lack of support for the C++ library by formally retiring the code here at Apache. The Java code of course remains well supported and will continue to be developed.

As of now, the C++ code is frozen here. The current sole maintainer will be transferring the source code to the Shibboleth Project and it will be maintained by that team for some period of time because it is a dependency of that software, but it will not be supported for any third-party use. It is estimated that the code will be fully retired some time before 2030. The code will be publically hosted and accessible after the transition, and the license is not changing.

Once the code transition occurs, which may not be for some time yet, we will update more of the site as is appropriate to reflect the transition. In the event a significant issue arises with the library prior to the transition, we will endeavor to address it here.

Much of this information may be slightly inaccurate as it has not been updated for V2.0.0 of the library.

XML Signature Programming

Overview

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.

Setup

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.

int main (int argc, char **argv) {

    try {
        XMLPlatformUtils::Initialize();
#ifndef XSEC_NO_XALAN
        XalanTransformer::initialize();
#endif
        XSECPlatformUtils::Initialise();
    }
    catch (const XMLException &e) {

        cerr << "Error during initialisation of Xerces" << endl;
        cerr << "Error Message = : "
		     << e.getMessage() << endl;

    }

    // Create a blank Document

    DOMImplementation *impl = 
        DOMImplementationRegistry::getDOMImplementation(MAKE_UNICODE_STRING("Core"));
	
    // Create a letter
    DOMDocument *doc = createLetter(impl);
    DOMElement *rootElem = doc->getDocumentElement();

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.

    XSECProvider prov;
    DSIGSignature *sig;
    DOMElement *sigNode;

    try {
		
        // Create a signature object

        sig = prov.newSignature();
        sig->setDSIGNSPrefix("ds");

        // Use it to create a blank signature DOM structure from the doc

        sigNode = sig->createBlankSignature(doc, 
                                    CANON_C14N_COM, 
                                    SIGNATURE_HMAC, 
                                    HASH_SHA1);

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.

        // Insert the signature DOM nodes into the doc

        rootElem->appendChild(doc->createTextNode(MAKE_UNICODE_STRING("\n")));
        rootElem->appendChild(sigNode);
        rootElem->appendChild(doc->createTextNode(MAKE_UNICODE_STRING("\n")));

        // Create an envelope reference for the text to be signed
        DSIGReference * ref = sig->createReference("");
        ref->appendEnvelopedSignatureTransform();

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.

        // Set the HMAC Key to be the string "secret"

        OpenSSLCryptoKeyHMAC * hmacKey = new OpenSSLCryptoKeyHMAC();
        hmacKey->setKey((unsigned char *) "secret", strlen("secret"));
        sig->setSigningKey(hmacKey);

        // Add a KeyInfo element
        sig->appendKeyName("The secret key is \"secret\"");

        // Sign

        sig->sign();
    }

    catch (XSECException &e)
    {
        cerr << "An error occured during a signature load\n   Message: "
             << e.getMsg() << endl;
        exit(1);
                
    }

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:

<Letter>
<ToAddress>The address of the Recipient</ToAddress>
<FromAddress>The address of the Sender</FromAddress>
<Text>
To whom it may concern

...
</Text>
<ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:SignedInfo>
<ds:CanonicalizationMethod Algorithm=
"http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments"/>
<ds:SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#hmac-sha1"/>
<ds:Reference URI="">
<ds:Transforms>
<ds:Transform Algorithm=
"http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
</ds:Transforms>
<ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
<ds:DigestValue>askxS/A3BaLCjFjZ/ttU9c12kA4=</ds:DigestValue>
</ds:Reference>
</ds:SignedInfo>
<ds:SignatureValue>oYEdQYG1IHzbkR1UcJ9Q5VriRPs=
</ds:SignatureValue>
<ds:KeyInfo>
<ds:KeyName>The secret key is "secret"</ds:KeyName>
</ds:KeyInfo>
</ds:Signature>
</Letter>

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.

Setup

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.

int main (int argc, char **argv) {

    try {
        XMLPlatformUtils::Initialize();
#ifndef XSEC_NO_XALAN
        XalanTransformer::initialize();
#endif
        XSECPlatformUtils::Initialise();
    }
    catch (const XMLException &e) {

        cerr << "Error during initialisation of Xerces" << endl;
        cerr << "Error Message = : "
             << DOMString(e.getMessage()) << endl;

    }

    ...
    
    Xerces is used to parse the document here
    
   

    DOM_Document doc = parser->getDocument();

    // Find the Amount node
    DOMNode *amt = doc->getDocumentElement();

    if (amt != NULL)
        amt = amt->getFirstChild();

    while (amt != NULL && 
           (amt->getNodeType() != DOMNode::ELEMENT_NODE || 
           !strEquals(amt->getNodeName(), "Amount")))
        amt = amt->getNextSibling();

    if (amt != NULL)
        amt = amt->getFirstChild();

    if (amt == NULL || amt->getNodeType() != DOMNode::TEXT_NODE) {
        cerr << "Error finding amount in purchase order" << endl;
        exit (1);
    }
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.

    XSECProvider prov;

    DSIGSignature * sig = prov.newSignatureFromDOM(doc);


    try {
        // Use the OpenSSL interface objects to get a signing key

        OpenSSLCryptoX509 * x509 = new OpenSSLCryptoX509();
        x509->loadX509Base64Bin(cert, strlen(cert));
                
        sig->load();

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.

        DSIGKeyInfoList * kinfList = sig->getKeyInfoList();
                
        // See if we can find a Key Name
        safeBuffer kname;
        DSIGKeyInfo * kinf = kinfList->getFirstKeyInfo();
        while (kinf != NULL) {
            kname = kinf->getKeyName();
            if (kname.sbStrcmp("")) {
                cout << "Key Name = " 
                     << kname.rawCharBuffer() << endl;
            }
            kinf = kinfList->getNextKeyInfo();
        }

        sig->setSigningKey(x509->clonePublicKey());
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.

        cout << "Amount = " << amt << " -> ";

        if (sig->verify()) {
            cout << "Signature Valid\n";
        }
        else {
            cout << "Incorrect Signature\n";
        }

        amt.setNodeValue("$0.50");
                
        cout << "Amount = " << amt << " -> ";

        if (sig->verify()) {
            cout << "Signature Valid\n";
        }
        else {
            cout << "Incorrect Signature\n";
        }

        amt.setNodeValue("$16.50");
                
        cout << "Amount = " << amt << " -> ";

        if (sig->verify()) {
            cout << "Signature Valid\n";
        }
        else {
            cout << "Incorrect Signature\n";
        }

When run, the program outputs the following:

Key Name = C=AU, ST=Vic, O=XML-Security-C Project, 
CN=Samples Demo Certificate
Amount = $16.50 -> Signature Valid
Amount = $0.50 -> Incorrect Signature
Amount = $16.50 -> Signature Valid

XML Encryption Programming

Overview

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.

Setup

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.

int main (int argc, char **argv) {

    try {
        XMLPlatformUtils::Initialize();
#ifndef XSEC_NO_XALAN
        XalanTransformer::initialize();
#endif
        XSECPlatformUtils::Initialise();
    }
    catch (const XMLException &e) {

        cerr << "Error during initialisation of Xerces" << endl;
        cerr << "Error Message = : "
             << e.getMessage() << endl;

    }

    // Create a blank Document

    DOMImplementation *impl = 
        DOMImplementationRegistry::getDOMImplementation(MAKE_UNICODE_STRING("Core"));
	
    // Create a letter
    DOMDocument *doc = createLetter(impl);
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.

    try {
		
        /* Create the cipher object that we need */

        XSECProvider prov;
        XENCCipher *cipher;

        cipher = prov.newCipher(doc);

        /* Now generate a random key that we can use to encrypt the element
         *
         * First check the status of the random generation in OpenSSL
         */

        if (RAND_status() != 1) {

            cerr << "OpenSSL random generation not properly initialised" << endl;
            exit(1);

        }

        unsigned char keyBuf[24];
        if (RAND_bytes(keyBuf, 24) == 0) {

            cerr << "Error obtaining 24 bytes of random from OpenSSL" << endl;
            exit(1);

        }
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.

        /* Wrap this in a Symmetric 3DES key */

        OpenSSLCryptoSymmetricKey * key = 
            new OpenSSLCryptoSymmetricKey(XSECCryptoSymmetricKey::KEY_3DES_192);
        key->setKey(keyBuf, 24);
        cipher->setKey(key);

        /* Encrypt the element that needs to be hidden */
        cipher->encryptElement(g_toEncrypt, ENCRYPT_3DES_CBC);
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.)

        /* Now lets create an EncryptedKey element to hold the generated key */

        /* First lets load the public key in the certificate */
        OpenSSLCryptoX509 * x509 = new OpenSSLCryptoX509();
        x509->loadX509Base64Bin(cert, strlen(cert));
	
        /* Now set the Key Encrypting Key (NOTE: Not the normal key) */
        cipher->setKEK(x509->clonePublicKey());
		

        /* Now do the encrypt, using RSA with PKCS 1.5 padding */

        XENCEncryptedKey * encryptedKey = 
            cipher->encryptKey(keyBuf, 24, ENCRYPT_RSA_15);
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.

        /*
         * Add the encrypted Key to the previously created EncryptedData, which
         * we first retrieve from the cipher object.  This will automatically create
         * the appropriate <KeyInfo> element within the EncryptedData
         */

        XENCEncryptedData * encryptedData = cipher->getEncryptedData();
        encryptedData->appendEncryptedKey(encryptedKey);

The above code results in a document that contains the newly created <EncryptedData> as follows:

<Letter>
<ToAddress>The address of the Recipient</ToAddress>
<FromAddress>The address of the Sender</FromAddress>
<xenc:EncryptedData Type="http://www.w3.org/2001/04/xmlenc#Element" 
xmlns:xenc="http://www.w3.org/2001/04/xmlenc#">
<xenc:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#tripledes-cbc"/>
<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<xenc:EncryptedKey xmlns:xenc="http://www.w3.org/2001/04/xmlenc#">
<xenc:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#rsa-1_5"/>
<xenc:CipherData>
<xenc:CipherValue>Wh8pAkDsQceHiktGxnlhXGfEMPDOLB6FwWp8PLedFEB3L3F6xHUoCOerIvA7Pgvv
VYzVqLv4a5x5YdnCqikkFBLE/fruAUe2Z8ZTEn/CaPYmpzU6qYHALCl7Q61LcbqH
R87TzroBYsYwfHmXmrKHL9K9sB6zmuec1TjVzm2c/Xs=
</xenc:CipherValue>
</xenc:CipherData>
</xenc:EncryptedKey>
</ds:KeyInfo>
<xenc:CipherData>
<xenc:CipherValue>YhqQciiFkLG1z0I1TJC6Pewnzw/gmVuGqcTvHtWpgak/b3NQDRAlv07lJOmBLoHX
23LQ1CdPSxvnyerlJGwkY6xJ0M5tjpDregTVcECXo/bd+x8eIsF2kaawoZGCqD1K
96T36Fx9rHek9bY/Hp1OiQ==
</xenc:CipherValue>
</xenc:CipherData>
</xenc:EncryptedData></Letter>

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.

Setup

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.

        XSECProvider prov;
        XENCCipher *cipher;

        cipher = prov.newCipher(doc);

        /* Load the private key via OpenSSL and then wrap in an OpenSSLCrypto construct */
        BIO * bioMem = BIO_new(BIO_s_mem());
        BIO_puts(bioMem, s_privateKey);
        EVP_PKEY * pk = PEM_read_bio_PrivateKey(bioMem, NULL, NULL, NULL);

        /* NOTE : For simplicity - no error checking here */

        OpenSSLCryptoKeyRSA * k = new OpenSSLCryptoKeyRSA(pk);
        cipher->setKEK(k);
Perform Decryption

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
	
        /* Find the EncryptedData node */
        DOMNode * encryptedNode = findXENCNode(doc, "EncryptedData");

        /* Do the decrypt */
        cipher->decryptElement((DOMElement *) encryptedNode);

The result of these steps is the decrypted letter.

<Letter>
<ToAddress>The address of the Recipient</ToAddress>
<FromAddress>The address of the Sender</FromAddress>
<Text>
To whom it may concern, my secret credit card number is : 
  0123 4567 89ab cdef

...
</Text></Letter>