/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

/*
 * XSEC
 *
 * cipher := Tool to handle basic encryption/decryption of XML documents
 *
 * Author(s): Berin Lautenbach
 *
 * $Id: cipher.cpp 1894293 2021-10-15 14:14:50Z scantor $
 *
 */

// XSEC

#include <xsec/utils/XSECPlatformUtils.hpp>
#include <xsec/framework/XSECProvider.hpp>
#include <xsec/framework/XSECException.hpp>
#include <xsec/enc/XSECCryptoException.hpp>
#include <xsec/enc/XSECCryptoUtils.hpp>
#include <xsec/enc/OpenSSL/OpenSSLCryptoSymmetricKey.hpp>
#include <xsec/utils/XSECBinTXFMInputStream.hpp>
#include <xsec/xenc/XENCEncryptedData.hpp>
#include <xsec/xenc/XENCEncryptedKey.hpp>

#include "XencInteropResolver.hpp"

#include "../../utils/XSECDOMUtils.hpp"

// General

#include <memory.h>
#include <string.h>
#include <iostream>
#include <fstream>
#include <stdlib.h>

#if defined(HAVE_UNISTD_H)
# include <unistd.h>
#else
# if defined(HAVE_DIRECT_H)
#  include <direct.h>
# endif
#endif

#if defined (_DEBUG) && defined (_MSC_VER)
#include <crtdbg.h>
#endif


#include <xercesc/util/PlatformUtils.hpp>
#include <xercesc/util/XMLString.hpp>

#include <xercesc/dom/DOM.hpp>
#include <xercesc/parsers/XercesDOMParser.hpp>
#include <xercesc/util/XMLException.hpp>
#include <xercesc/util/XMLUri.hpp>
#include <xercesc/util/XMLUni.hpp>
#include <xercesc/util/Janitor.hpp>
#include <xercesc/util/BinFileInputStream.hpp>
#include <xercesc/framework/XMLFormatter.hpp>
#include <xercesc/framework/StdOutFormatTarget.hpp>
#include <xercesc/framework/LocalFileFormatTarget.hpp>

XERCES_CPP_NAMESPACE_USE

using std::cerr;
using std::cout;
using std::endl;
using std::ostream;

#ifdef XSEC_HAVE_XALAN

// XALAN

#include <xalanc/XPath/XPathEvaluator.hpp>
#include <xalanc/XalanTransformer/XalanTransformer.hpp>

// If this isn't defined, we're on Xalan 1.12+ and require modern C++
#ifndef XALAN_USING_XALAN
# define XALAN_USING_XALAN(NAME) using xalanc :: NAME;
#endif

XALAN_USING_XALAN(XPathEvaluator)
XALAN_USING_XALAN(XalanTransformer)

#endif

#if defined (XSEC_HAVE_OPENSSL)
// OpenSSL

#   include <xsec/enc/OpenSSL/OpenSSLCryptoKeyHMAC.hpp>
#   include <xsec/enc/OpenSSL/OpenSSLCryptoKeyRSA.hpp>
#   include <openssl/err.h>
#   include <openssl/bio.h>
#   include <openssl/evp.h>
#   include <openssl/pem.h>

#endif

#if defined (XSEC_HAVE_WINCAPI)

#   include <xsec/enc/WinCAPI/WinCAPICryptoProvider.hpp>
#   include <xsec/enc/WinCAPI/WinCAPICryptoSymmetricKey.hpp>
#   include <xsec/enc/WinCAPI/WinCAPICryptoKeyHMAC.hpp>

#endif

#if defined (XSEC_HAVE_NSS)

#   include <xsec/enc/NSS/NSSCryptoProvider.hpp>
#   include <xsec/enc/NSS/NSSCryptoSymmetricKey.hpp>
#   include <xsec/enc/NSS/NSSCryptoKeyHMAC.hpp>

#endif

#include <time.h>

#ifndef XSEC_HAVE_XALAN

std::ostream& operator<< (std::ostream& target, const XMLCh * s)
{
    char *p = XMLString::transcode(s);
    target << p;
    XSEC_RELEASE_XMLCH(p);
    return target;
}

#endif

// ----------------------------------------------------------------------------
//           Checksig
// ----------------------------------------------------------------------------


void printUsage(void) {

    cerr << "\nUsage: cipher [options] <input file name>\n\n";
    cerr << "     Where options are :\n\n";
    cerr << "     --decrypt/-d\n";
    cerr << "         Operate in decrypt mode (default) - outputs the decrypted octet stream\n";
    cerr << "         Reads in the input file as an XML file, searches for an EncryptedData node\n";
    cerr << "         and decrypts the content\n";
    cerr << "     --decrypt-element/-de\n";
    cerr << "         Operate in decrypt and XML mode.\n";
    cerr << "         This will output the original XML document with the first encrypted\n";
    cerr << "         element decrypted.\n";
    cerr << "     --encrypt-file/-ef\n";
    cerr << "         Encrypt the contents of the input file as raw data and create an\n";
    cerr << "         XML Encrypted Data outpu\n";
    cerr << "     --encrypt-xml/-ex\n";
    cerr << "         Parse the input file and encrypt the doc element down, storing the\n";
    cerr << "         output as a XML Encrypted Data\n";
    cerr << "     --key/-k [kek] <KEY_TYPE> [options]\n";
    cerr << "         Set the key to use.\n";
    cerr << "             If the first parameter is \"kek\", the key arguments will be used\n";
    cerr << "                  as a Key EncryptionKey\n";
    cerr << "             KEY_TYPE defines what the key is.  Can be one of :\n";
    cerr << "                  X509, RSA, AES128, AES192, AES256, AES128-GCM, AES192-GCM, AES256-GCM or 3DES\n";
    cerr << "             options are :\n";
    cerr << "                  <filename> - for X509 PEM files (must be an RSA KEK certificate\n";
    cerr << "                  <filename> <password> - for RSA private key files (MUST be a KEK)\n";
    cerr << "                  <key-string> - For a string to use as the key for AES or DES keys\n";
#ifdef XSEC_XKMS_ENABLED
    cerr << "     --xkms/-x\n";
    cerr << "         The key that follows on the command line is to be interpreted as\n";
    cerr << "         an XKMS RSAKeyPair encryption key\n";
#endif
    cerr << "     --interop/-i\n";
    cerr << "         Use the interop resolver for Baltimore interop examples\n";
    cerr << "     --out-file/-o\n";
    cerr << "         Output the result to the indicated file (rather than stdout)\n";
#ifdef XSEC_HAVE_WINCAPI
    cerr << "     --wincapi/-w\n";
    cerr << "         Force use of Windows Crypto API\n";
#endif
#ifdef XSEC_HAVE_NSS
    cerr << "     --nss/-n\n";
    cerr << "         Force use of NSS Crypto API\n";
#endif

    cerr << "\n     Exits with codes :\n";
    cerr << "         0 = Decrypt/Encrypt OK\n";
    cerr << "         1 = Decrypt/Encrypt failed\n";
    cerr << "         2 = Processing error\n";

}

int evaluate(int argc, char ** argv) {
    
    char                    * filename = NULL;
    char                    * outfile = NULL;
    unsigned char           * keyStr = NULL;
    bool                    doDecrypt = true;
    bool                    errorsOccured = false;
    bool                    doDecryptElement = false;
    bool                    useInteropResolver = false;
    bool                    encryptFileAsData = false;
    bool                    parseXMLInput = true;
    bool                    doXMLOutput = false;
#ifdef XSEC_XKMS_ENABLED
    bool                    isXKMSKey = false;
#endif
    XSECCryptoKey           * kek = NULL;
    XSECCryptoKey           * key = NULL;
    int                     keyLen = 0;
    const XMLCh*            kekAlg = NULL;
    const XMLCh*            keyAlg = NULL;
    DOMDocument             *doc;
    unsigned char           keyBuf[24];
    XMLFormatTarget         *formatTarget ;

#if defined(_WIN32) && defined (XSEC_HAVE_WINCAPI)
    HCRYPTPROV              win32DSSCSP = 0;        // Crypto Providers
    HCRYPTPROV              win32RSACSP = 0;

    CryptAcquireContext(&win32DSSCSP, NULL, NULL, PROV_DSS, CRYPT_VERIFYCONTEXT);
    CryptAcquireContext(&win32RSACSP, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT);

#endif

    if (argc < 2) {

        printUsage();
        return 2;
    }

    // Run through parameters
    int paramCount = 1;

    while (paramCount < argc - 1) {

        if (_stricmp(argv[paramCount], "--decrypt-element") == 0 || _stricmp(argv[paramCount], "-de") == 0) {
            paramCount++;
            doDecrypt = true;
            doDecryptElement = true;
            doXMLOutput = true;
            parseXMLInput = true;
        }
        else if (_stricmp(argv[paramCount], "--interop") == 0 || _stricmp(argv[paramCount], "-i") == 0) {
            // Use the interop key resolver
            useInteropResolver = true;
            paramCount++;
        }
        else if (_stricmp(argv[paramCount], "--encrypt-file") == 0 || _stricmp(argv[paramCount], "-ef") == 0) {
            // Use this file as the input
            doDecrypt = false;
            encryptFileAsData = true;
            doXMLOutput = true;
            parseXMLInput = false;
            paramCount++;
        }
        else if (_stricmp(argv[paramCount], "--encrypt-xml") == 0 || _stricmp(argv[paramCount], "-ex") == 0) {
            // Us this file as an XML input file
            doDecrypt = false;
            encryptFileAsData = false;
            doXMLOutput = true;
            parseXMLInput = true;
            paramCount++;
        }
        else if (_stricmp(argv[paramCount], "--out-file") == 0 || _stricmp(argv[paramCount], "-o") == 0) {
            if (paramCount +2 >= argc) {
                printUsage();
                return 1;
            }
            paramCount++;
            outfile = argv[paramCount];
            paramCount++;
        }
#ifdef XSEC_XKMS_ENABLED
        else if (_stricmp(argv[paramCount], "--xkms") == 0 || _stricmp(argv[paramCount], "-x") == 0) {
            paramCount++;
            isXKMSKey = true;
        }
#endif
#ifdef XSEC_HAVE_WINCAPI
        else if (_stricmp(argv[paramCount], "--wincapi") == 0 || _stricmp(argv[paramCount], "-w") == 0) {
            // Use the interop key resolver
            WinCAPICryptoProvider * cp = new WinCAPICryptoProvider();
            XSECPlatformUtils::SetCryptoProvider(cp);
            paramCount++;
        }
#endif
#ifdef XSEC_HAVE_NSS
        else if (_stricmp(argv[paramCount], "--nss") == 0 || _stricmp(argv[paramCount], "-n") == 0) {
            // NSS Crypto Provider
            NSSCryptoProvider * cp = new NSSCryptoProvider();
            XSECPlatformUtils::SetCryptoProvider(cp);
            paramCount++;
        }
#endif
        else if (_stricmp(argv[paramCount], "--key") == 0 || _stricmp(argv[paramCount], "-k") == 0) {

            // Have a key!
            paramCount++;
            bool isKEK = false;
            XSECCryptoSymmetricKey::SymmetricKeyType loadKeyAs =
                XSECCryptoSymmetricKey::KEY_NONE;

            if (_stricmp(argv[paramCount], "kek") == 0) {
                isKEK = true;
                paramCount++;
                if (paramCount >= argc) {
                    printUsage();
                    return 2;
                }
            }

            if (_stricmp(argv[paramCount], "3DES") == 0 ||
                _stricmp(argv[paramCount], "AES128") == 0 ||
                _stricmp(argv[paramCount], "AES192") == 0 ||
                _stricmp(argv[paramCount], "AES256") == 0 ||
                _stricmp(argv[paramCount], "AES128-GCM") == 0 ||
                _stricmp(argv[paramCount], "AES192-GCM") == 0 ||
                _stricmp(argv[paramCount], "AES256-GCM") == 0) {
                
                if (paramCount +2 >= argc) {
                    printUsage();
                    return 2;
                }

                switch(argv[paramCount][4]) {
                case '\0' :
                    keyLen = 24;
                    loadKeyAs = XSECCryptoSymmetricKey::KEY_3DES_192;
                    keyAlg = DSIGConstants::s_unicodeStrURI3DES_CBC;
                    break;
                case '2' :
                    keyLen = 16;
                    loadKeyAs = XSECCryptoSymmetricKey::KEY_AES_128;
                    if (isKEK) {
                        kekAlg = DSIGConstants::s_unicodeStrURIKW_AES128;
                    }
                    else if (strlen(argv[paramCount]) == 6) {
                        keyAlg = DSIGConstants::s_unicodeStrURIAES128_CBC;
                    }
                    else {
                        keyAlg = DSIGConstants::s_unicodeStrURIAES128_GCM;
                    }
                    break;
                case '9' :
                    keyLen = 24;
                    loadKeyAs = XSECCryptoSymmetricKey::KEY_AES_192;
                    if (isKEK) {
                        kekAlg = DSIGConstants::s_unicodeStrURIKW_AES192;
                    }
                    else if (strlen(argv[paramCount]) == 6) {
                        keyAlg = DSIGConstants::s_unicodeStrURIAES192_CBC;
                    }
                    else {
                        keyAlg = DSIGConstants::s_unicodeStrURIAES192_GCM;
                    }
                    break;
                case '5' :
                    keyLen = 32;
                    loadKeyAs = XSECCryptoSymmetricKey::KEY_AES_256;
                    if (isKEK) {
                        kekAlg = DSIGConstants::s_unicodeStrURIKW_AES256;
                    }
                    else if (strlen(argv[paramCount]) == 6) {
                        keyAlg = DSIGConstants::s_unicodeStrURIAES256_CBC;
                    }
                    else {
                        keyAlg = DSIGConstants::s_unicodeStrURIAES256_GCM;
                    }
                    break;
                }

                paramCount++;
                unsigned char keyStr[64];
                if (strlen(argv[paramCount]) > 64) {
                    cerr << "Key string too long\n";
                    return 2;
                }
                XSECCryptoSymmetricKey * sk = 
                    XSECPlatformUtils::g_cryptoProvider->keySymmetric(loadKeyAs);

#ifdef XSEC_XKMS_ENABLED
                if (isXKMSKey) {
                    unsigned char kbuf[XSEC_MAX_HASH_SIZE];
                    CalculateXKMSKEK((unsigned char *) argv[paramCount], (int) strlen(argv[paramCount]), kbuf, XSEC_MAX_HASH_SIZE);
                    sk->setKey(kbuf, keyLen);
                }
                else {
#endif
                    memset(keyStr, 0, 64);
                    strcpy((char *) keyStr, argv[paramCount]);
                    sk->setKey(keyStr, keyLen);
#ifdef XSEC_XKMS_ENABLED
                }
#endif
                paramCount++;
                if (isKEK)
                    kek = sk;
                else
                    key = sk;
            }


#ifdef XSEC_HAVE_OPENSSL

            else if (_stricmp(argv[paramCount], "RSA") == 0) {
                // RSA private key file

                if (paramCount + 3 >= argc) {

                    printUsage();
                    return 2;

                }

                if (!isKEK) {
                    cerr << "RSA private keys may only be KEKs\n";
                    return 2;
                }

                BIO * bioKey;
                if ((bioKey = BIO_new(BIO_s_file())) == NULL) {

                    cerr << "Error opening private key file\n\n";
                    return 1;

                }

                if (BIO_read_filename(bioKey, argv[paramCount + 1]) <= 0) {

                    cerr << "Error opening private key file\n\n";
                    return 1;

                }

                EVP_PKEY * pkey;
                pkey = PEM_read_bio_PrivateKey(bioKey,NULL,NULL,argv[paramCount + 2]);

                if (pkey == NULL) {

                    cerr << "Error loading private key\n\n";
                    return 1;

                }

                kek = new OpenSSLCryptoKeyRSA(pkey);
                kekAlg = DSIGConstants::s_unicodeStrURIRSA_OAEP_MGFP1;
                EVP_PKEY_free(pkey);
                BIO_free(bioKey);
                paramCount += 3;
            }

            else if (_stricmp(argv[paramCount], "X509") == 0) {

                // X509 cert used to load an encrypting key

                if (paramCount + 2 >= argc) {

                    printUsage();
                    exit (1);

                }

                if (!isKEK) {
                    cerr << "X509 private keys may only be KEKs\n";
                    return 2;
                }

                // Load the encrypting key
                // For now just read a particular file

                BIO * bioX509;

                if ((bioX509 = BIO_new(BIO_s_file())) == NULL) {

                    cerr << "Error opening file\n\n";
                    exit (1);

                }

                if (BIO_read_filename(bioX509, argv[paramCount + 1]) <= 0) {

                    cerr << "Error opening X509 Certificate " << argv[paramCount + 1] << "\n\n";
                    exit (1);

                }

                X509 * x
                    ;
                x = PEM_read_bio_X509_AUX(bioX509,NULL,NULL,NULL);

                if (x == NULL) {

                    BIO * bio_err;
                    
                    if ((bio_err=BIO_new(BIO_s_file())) != NULL)
                        BIO_set_fp(bio_err,stderr,BIO_NOCLOSE|BIO_FP_TEXT);

                    cerr << "Error loading certificate key\n\n";
                    ERR_print_errors(bio_err);
                    BIO_free(bio_err);
                    exit (1);

                }

                // Now load the key
                EVP_PKEY *pkey;

                pkey = X509_get_pubkey(x);
#ifdef XSEC_OPENSSL_HAVE_EVP_PKEY_ID
                if (pkey == NULL || EVP_PKEY_id(pkey) != EVP_PKEY_RSA)
#else
                if (pkey == NULL || pkey->type != EVP_PKEY_RSA)
#endif
                {
                    cerr << "Error extracting RSA key from certificate" << endl;
                }

                kek = new OpenSSLCryptoKeyRSA(pkey);
                kekAlg = DSIGConstants::s_unicodeStrURIRSA_OAEP_MGFP1;

                // Clean up

                EVP_PKEY_free (pkey);
                X509_free(x);
                BIO_free(bioX509);

                paramCount += 2;
                
            } /* argv[1] = "--x509cert" */
#endif /* XSEC_HAVE_OPENSSL */
            else {
                printUsage();
                return 2;
            }
        }

        else {
            cerr << "Unknown option: " << argv[paramCount] << endl;
            printUsage();
            return 2;
        }
    }

    if (paramCount >= argc) {
        printUsage();
        return 2;
    }

    if (outfile != NULL) {
        formatTarget = new LocalFileFormatTarget(outfile);
    }
    else {
        formatTarget = new StdOutFormatTarget();
    }

    filename = argv[paramCount];

    if (parseXMLInput) {

        XercesDOMParser * parser = new XercesDOMParser;
        Janitor<XercesDOMParser> j_parser(parser);
        
        parser->setDoNamespaces(true);
        parser->setCreateEntityReferenceNodes(true);

        // Now parse out file

        XMLSize_t errorCount = 0;
        try
        {
            parser->parse(filename);
            errorCount = parser->getErrorCount();
            if (errorCount > 0)
                errorsOccured = true;
        }

        catch (const XMLException& e)
        {
            cerr << "An error occurred during parsing\n   Message: "
                 << e.getMessage() << endl;
            errorsOccured = true;
        }


        catch (const DOMException& e)
        {
           cerr << "A DOM error occurred during parsing\n   DOMException code: "
                 << e.code << endl;
            errorsOccured = true;
        }

        if (errorsOccured) {

            cout << "Errors during parse" << endl;
            return (2);

        }

        /*

            Now that we have the parsed file, get the DOM document and start looking at it

        */
        
        doc = parser->adoptDocument();
    }

    else {
        // Create an empty document
        XMLCh tempStr[100];
        XMLString::transcode("Core", tempStr, 99);    
        DOMImplementation *impl = DOMImplementationRegistry::getDOMImplementation(tempStr);
        doc = impl->createDocument(
            0,                    // root element namespace URI.
            MAKE_UNICODE_STRING("ADoc"),            // root element name
            NULL);// DOMDocumentType());  // document type object (DTD).
    }


    XSECProvider prov;
    XENCCipher * cipher = prov.newCipher(doc);

    if (kek != NULL)
        cipher->setKEK(kek);
    if (key != NULL)
        cipher->setKey(key);

    try {

        if (doDecrypt) {

            if (useInteropResolver == true) {

                // Map out base path of the file
#ifdef XSEC_HAVE_GETCWD_DYN
                char *path = getcwd(NULL, 0);
                char *baseURI = (char*)malloc(strlen(path) + 8 + 1 + strlen(filename) + 1);
#else
                char path[PATH_MAX];
                char baseURI[(PATH_MAX * 2) + 10];
                getcwd(path, PATH_MAX);
#endif
                strcpy(baseURI, "file:///");        

                // Ugly and nasty but quick
                if (filename[0] != '\\' && filename[0] != '/' && filename[1] != ':') {
                    strcat(baseURI, path);
                    strcat(baseURI, "/");
                } else if (path[1] == ':') {
                    path[2] = '\0';
                    strcat(baseURI, path);
                }

                strcat(baseURI, filename);

                // Find any ':' and "\" characters
                int lastSlash = 0;
                for (unsigned int i = 8; i < strlen(baseURI); ++i) {
                    if (baseURI[i] == '\\') {
                        lastSlash = i;
                        baseURI[i] = '/';
                    }
                    else if (baseURI[i] == '/')
                        lastSlash = i;
                }

                // The last "\\" must prefix the filename
                baseURI[lastSlash + 1] = '\0';

                XMLCh * uriT = XMLString::transcode(baseURI);
#ifdef XSEC_HAVE_GETCWD_DYN
                free(path);
                free(baseURI);
#endif

                XencInteropResolver ires(doc, &(uriT[8]));
                XSEC_RELEASE_XMLCH(uriT);
                cipher->setKeyInfoResolver(&ires);

            }
            // Find the EncryptedData node
            DOMNode * n = findXENCNode(doc, "EncryptedData");

            if (doDecryptElement) {
                while (n != NULL) {

                    // decrypt
                    cipher->decryptElement(static_cast<DOMElement *>(n));

                    // Find the next EncryptedData node
                    n = findXENCNode(doc, "EncryptedData");
                }

            }
            else {
                XSECBinTXFMInputStream * bis = cipher->decryptToBinInputStream(static_cast<DOMElement *>(n));
                Janitor<XSECBinTXFMInputStream> j_bis(bis);
    
                XMLByte buf[1024];          
                XMLSize_t read = bis->readBytes(buf, 1023);
                while (read > 0) {
                    formatTarget->writeChars(buf, read, NULL);
                    read = bis->readBytes(buf, 1023);
                }
            }
        }
        else {

            XENCEncryptedData *xenc = NULL;
            // Encrypting
            if (kek != NULL && key == NULL) {
                XSECPlatformUtils::g_cryptoProvider->getRandom(keyBuf, 24);
                XSECCryptoSymmetricKey * k = 
                    XSECPlatformUtils::g_cryptoProvider->keySymmetric(XSECCryptoSymmetricKey::KEY_3DES_192);
                k->setKey(keyBuf, 24);
                cipher->setKey(k);
                keyAlg = DSIGConstants::s_unicodeStrURI3DES_CBC;
                keyStr = keyBuf;
                keyLen = 24;
            }

            if (encryptFileAsData) {

                // Create a BinInputStream
                BinFileInputStream * is = new BinFileInputStream(filename, XMLPlatformUtils::fgMemoryManager);
                xenc = cipher->encryptBinInputStream(is, keyAlg);

                // Replace the document element
                DOMElement * elt = doc->getDocumentElement();
                doc->replaceChild(xenc->getElement(), elt);
                elt->release();
            }
            else {
                // Document encryption
                cipher->encryptElement(doc->getDocumentElement(), keyAlg);
            }

            // Do we encrypt a created key?
            if (kek != NULL && xenc != NULL) {
                XENCEncryptedKey *xkey = cipher->encryptKey(keyStr, keyLen, kekAlg);
                // Add to the EncryptedData
                xenc->appendEncryptedKey(xkey);
            }
        }

        if (doXMLOutput) {
            // Output the result

            XMLCh core[] = {
                XERCES_CPP_NAMESPACE_QUALIFIER chLatin_C,
                XERCES_CPP_NAMESPACE_QUALIFIER chLatin_o,
                XERCES_CPP_NAMESPACE_QUALIFIER chLatin_r,
                XERCES_CPP_NAMESPACE_QUALIFIER chLatin_e,
                XERCES_CPP_NAMESPACE_QUALIFIER chNull
            };

            DOMImplementation *impl = DOMImplementationRegistry::getDOMImplementation(core);

            DOMLSSerializer   *theSerializer = ((DOMImplementationLS*)impl)->createLSSerializer();
            Janitor<DOMLSSerializer> j_theSerializer(theSerializer);
            
            // Get the config so we can set up pretty printing
            DOMConfiguration *dc = theSerializer->getDomConfig();
            dc->setParameter(XMLUni::fgDOMWRTFormatPrettyPrint, false);

            // Now create an output object to format to UTF-8
            DOMLSOutput *theOutput = ((DOMImplementationLS*)impl)->createLSOutput();
            Janitor<DOMLSOutput> j_theOutput(theOutput);

            theOutput->setEncoding(MAKE_UNICODE_STRING("UTF-8"));
            theOutput->setByteStream(formatTarget);

            theSerializer->write(doc, theOutput);

            cout << endl;

        }
    }

    catch (const XSECException &e) {
        char * msg = XMLString::transcode(e.getMsg());
        cerr << "An error occurred during encryption/decryption operation\n   Message: "
        << msg << endl;
        XSEC_RELEASE_XMLCH(msg);
        errorsOccured = true;
        if (formatTarget != NULL)
            delete formatTarget;
        doc->release();
        return 2;
    }
    catch (const XSECCryptoException &e) {
        cerr << "An error occurred during encryption/decryption operation\n   Message: "
        << e.getMsg() << endl;
        errorsOccured = true;
        if (formatTarget != NULL)
            delete formatTarget;
        doc->release();

#ifdef XSEC_HAVE_OPENSSL
        ERR_load_crypto_strings();
        BIO * bio_err;
        if ((bio_err=BIO_new(BIO_s_file())) != NULL)
            BIO_set_fp(bio_err,stderr,BIO_NOCLOSE|BIO_FP_TEXT);

        ERR_print_errors(bio_err);
#endif
        return 2;
    }
    
    if (formatTarget != NULL)
        delete formatTarget;

    doc->release();
    return 0;
}


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

    int retResult;

#if defined (_DEBUG) && defined (_MSC_VER)

    // Do some memory debugging under Visual C++

    _CrtMemState s1, s2, s3;

    // At this point we are about to start really using XSEC, so
    // Take a "before" checkpoing

    _CrtMemCheckpoint( &s1 );

#endif

    // Initialise the XML system

    try {

        XMLPlatformUtils::Initialize();
#ifdef XSEC_HAVE_XALAN
        XPathEvaluator::initialize();
        XalanTransformer::initialize();
#endif
        XSECPlatformUtils::Initialise();

    }
    catch (const XMLException &e) {

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

    }

    retResult = evaluate(argc, argv);

    XSECPlatformUtils::Terminate();
#ifdef XSEC_HAVE_XALAN
    XalanTransformer::terminate();
    XPathEvaluator::terminate();
#endif
    XMLPlatformUtils::Terminate();

#if defined (_DEBUG) && defined (_MSC_VER)

    _CrtMemCheckpoint( &s2 );

    if ( _CrtMemDifference( &s3, &s1, &s2 ) && (
        s3.lCounts[0] > 0 ||
        s3.lCounts[1] > 1 ||
        // s3.lCounts[2] > 2 ||  We don't worry about C Runtime
        s3.lCounts[3] > 0 ||
        s3.lCounts[4] > 0)) {

        // Note that there is generally 1 Normal and 1 CRT block
        // still taken.  1 is from Xalan and 1 from stdio

        // Send all reports to STDOUT
        _CrtSetReportMode( _CRT_WARN, _CRTDBG_MODE_FILE );
        _CrtSetReportFile( _CRT_WARN, _CRTDBG_FILE_STDOUT );
        _CrtSetReportMode( _CRT_ERROR, _CRTDBG_MODE_FILE );
        _CrtSetReportFile( _CRT_ERROR, _CRTDBG_FILE_STDOUT );
        _CrtSetReportMode( _CRT_ASSERT, _CRTDBG_MODE_FILE );
        _CrtSetReportFile( _CRT_ASSERT, _CRTDBG_FILE_STDOUT );

        // Dumpy memory stats

        _CrtMemDumpAllObjectsSince( &s3 );
        _CrtMemDumpStatistics( &s3 );
    }

    // Now turn off memory leak checking and end as there are some 
    // Globals that are allocated that get seen as leaks (Xalan?)

    int dbgFlag = _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG);
    dbgFlag &= ~(_CRTDBG_LEAK_CHECK_DF);
    _CrtSetDbgFlag( dbgFlag );

#endif

    return retResult;
}
