/**
 * 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
 *
 * XSECAlgorithmHandlerDefault := Interface class to define handling of
 *                                  default encryption algorithms
 *
 * $Id: XENCAlgorithmHandlerDefault.cpp 1833341 2018-06-11 16:25:41Z scantor $
 *
 */

// XSEC Includes

#include <xsec/dsig/DSIGConstants.hpp>
#include <xsec/enc/XSECCryptoKey.hpp>
#include <xsec/enc/XSECCryptoSymmetricKey.hpp>
#include <xsec/framework/XSECError.hpp>
#include <xsec/transformers/TXFMChain.hpp>
#include <xsec/transformers/TXFMCipher.hpp>
#include <xsec/transformers/TXFMBase64.hpp>
#include <xsec/transformers/TXFMSB.hpp>
#include <xsec/xenc/XENCEncryptionMethod.hpp>

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

#include "XENCAlgorithmHandlerDefault.hpp"

#include <xercesc/dom/DOM.hpp>
#include <xercesc/util/Janitor.hpp>

XERCES_CPP_NAMESPACE_USE

#define _MY_MAX_KEY_SIZE 2048

unsigned char s_3DES_CMS_IV [] = {
    0x4a,
    0xdd,
    0xa2,
    0x2c,
    0x79,
    0xe8,
    0x21,
    0x05
};

unsigned char s_AES_IV [] = {

    0xA6,
    0xA6,
    0xA6,
    0xA6,
    0xA6,
    0xA6,
    0xA6,
    0xA6

};

// --------------------------------------------------------------------------------
//            Compare URI to key type
// --------------------------------------------------------------------------------

void XENCAlgorithmHandlerDefault::mapURIToKey(const XMLCh* uri,
                                              const XSECCryptoKey* key,
                                              XSECCryptoKey::KeyType& kt,
                                              XSECCryptoSymmetricKey::SymmetricKeyType& skt,
                                              bool& isSymmetricKeyWrap,
                                              XSECCryptoSymmetricKey::SymmetricKeyMode& skm,
                                              unsigned int& taglen) const {

    if (key == NULL) {
        throw XSECException(XSECException::CipherError,
            "XENCAlgorithmHandlerDefault::mapURIToKey - trying to process a NULL key");
    }

    XSECCryptoSymmetricKey* keySymmetric;
    bool keyOK = false;

    kt = key->getKeyType();
    skt = XSECCryptoSymmetricKey::KEY_NONE;
    isSymmetricKeyWrap = false;
    skm = XSECCryptoSymmetricKey::MODE_NONE;
    taglen = 0;

    switch (kt) {

    case XSECCryptoKey::KEY_RSA_PUBLIC :
    case XSECCryptoKey::KEY_RSA_PAIR :
    case XSECCryptoKey::KEY_RSA_PRIVATE :
        keyOK = strEquals(uri, DSIGConstants::s_unicodeStrURIRSA_1_5) ||
            strEquals(uri, DSIGConstants::s_unicodeStrURIRSA_OAEP_MGFP1) ||
            strEquals(uri, DSIGConstants::s_unicodeStrURIRSA_OAEP);
        break;

    case XSECCryptoKey::KEY_SYMMETRIC :

        keySymmetric = (XSECCryptoSymmetricKey*) key;
        if (keySymmetric != NULL) {
            skt = keySymmetric->getSymmetricKeyType();

            switch (skt) {

            case XSECCryptoSymmetricKey::KEY_3DES_192 :
                if (strEquals(uri, DSIGConstants::s_unicodeStrURIKW_3DES)) {
                    keyOK = true;
                    isSymmetricKeyWrap = true;
                    skm = XSECCryptoSymmetricKey::MODE_CBC;
                }
                else if (strEquals(uri, DSIGConstants::s_unicodeStrURI3DES_CBC)) {
                    keyOK = true;
                    skm = XSECCryptoSymmetricKey::MODE_CBC;
                }
                break;

            case XSECCryptoSymmetricKey::KEY_AES_128 :
                if (strEquals(uri, DSIGConstants::s_unicodeStrURIKW_AES128) || strEquals(uri, DSIGConstants::s_unicodeStrURIKW_AES128_PAD)) {
                    keyOK = true;
                    isSymmetricKeyWrap = true;
                    skm = XSECCryptoSymmetricKey::MODE_ECB;
                }
                else if (strEquals(uri, DSIGConstants::s_unicodeStrURIAES128_CBC)) {
                    keyOK = true;
                    skm = XSECCryptoSymmetricKey::MODE_CBC;
                }
                else if (strEquals(uri, DSIGConstants::s_unicodeStrURIAES128_GCM)) {
                    keyOK = true;
                    skm = XSECCryptoSymmetricKey::MODE_GCM;
                    taglen = 16;
                }
                break;

            case XSECCryptoSymmetricKey::KEY_AES_192 :
                if (strEquals(uri, DSIGConstants::s_unicodeStrURIKW_AES192) || strEquals(uri, DSIGConstants::s_unicodeStrURIKW_AES192_PAD)) {
                    keyOK = true;
                    isSymmetricKeyWrap = true;
                    skm = XSECCryptoSymmetricKey::MODE_ECB;
                }
                else if (strEquals(uri, DSIGConstants::s_unicodeStrURIAES192_CBC)) {
                    keyOK = true;
                    skm = XSECCryptoSymmetricKey::MODE_CBC;
                }
                else if (strEquals(uri, DSIGConstants::s_unicodeStrURIAES192_GCM)) {
                    keyOK = true;
                    skm = XSECCryptoSymmetricKey::MODE_GCM;
                    taglen = 16;
                }
                break;

            case XSECCryptoSymmetricKey::KEY_AES_256 :
                if (strEquals(uri, DSIGConstants::s_unicodeStrURIKW_AES256) || strEquals(uri, DSIGConstants::s_unicodeStrURIKW_AES256_PAD)) {
                    keyOK = true;
                    isSymmetricKeyWrap = true;
                    skm = XSECCryptoSymmetricKey::MODE_ECB;
                }
                else if (strEquals(uri, DSIGConstants::s_unicodeStrURIAES256_CBC)) {
                    keyOK = true;
                    skm = XSECCryptoSymmetricKey::MODE_CBC;
                }
                else if (strEquals(uri, DSIGConstants::s_unicodeStrURIAES256_GCM)) {
                    keyOK = true;
                    skm = XSECCryptoSymmetricKey::MODE_GCM;
                    taglen = 16;
                }
                break;

            default:
                break;
            }
        }
        break;

    default:
         break;
    }

    if (keyOK == false) {
        throw XSECException(XSECException::CipherError,
            "XENCAlgorithmHandlerDefault::mapURIToKey - key inappropriate for URI");
    }
}

// --------------------------------------------------------------------------------
//            AES Key wrap/unwrap
// --------------------------------------------------------------------------------

unsigned int XENCAlgorithmHandlerDefault::unwrapKeyAES(
        TXFMChain* cipherText,
        const XSECCryptoKey* key,
        safeBuffer& result) const {

    // Cat the encrypted key
    XMLByte buf[_MY_MAX_KEY_SIZE];
    XMLByte aesBuf[16];
    XMLByte aesOutBuf[16];
    TXFMBase* b = cipherText->getLastTxfm();
    unsigned int sz = (unsigned int) b->readBytes(buf, _MY_MAX_KEY_SIZE);

    if (sz <= 0) {
        throw XSECException(XSECException::CipherError,
            "XENCAlgorithmHandlerDefault - AES Wrapped Key not found");
    }

    if (sz == _MY_MAX_KEY_SIZE) {
        throw XSECException(XSECException::CipherError,
            "XENCAlgorithmHandlerDefault - Key to decrypt too big!");
    }

    // Find number of blocks, and ensure we are a multiple of 64 bits
    if (sz % 8 != 0) {
        throw XSECException(XSECException::CipherError,
            "XENCAlgorithmHandlerDefault - AES wrapped key not a multiple of 64");
    }

    // Do the decrypt - this cast will throw if wrong, but we should
    // not have been able to get through algorithm checks otherwise
    XSECCryptoSymmetricKey* sk = (XSECCryptoSymmetricKey*) key;

    int blocks = sz / 8;
    int n = blocks - 1;
    for (int j = 5; j >= 0; j--) {
        for (int i = n ; i > 0 ; --i) {

            // Gather blocks to decrypt
            // A
            memcpy(aesBuf, buf, 8);
            // Ri
            memcpy(&aesBuf[8], &buf[8* i], 8);
            // A mod t
            aesBuf[7] ^= ((n * j) + i);

            // do decrypt
            sk->decryptInit(false, XSECCryptoSymmetricKey::MODE_ECB);        // No padding
            int sz = sk->decrypt(aesBuf, aesOutBuf, 16, 16);
            sz += sk->decryptFinish(&aesOutBuf[sz], 16 - sz);

            if (sz != 16) {
                throw XSECException(XSECException::CipherError,
                    "XENCAlgorithmHandlerDefault - Error performing decrypt in AES Unwrap");
            }

            // Copy back to where we are
            // A
            memcpy(buf, aesOutBuf, 8);
            // Ri
            memcpy(&buf[8 * i], &aesOutBuf[8], 8);
        }
    }

    // Check is valid
    if (memcmp(buf, s_AES_IV, 8) != 0) {
        throw XSECException(XSECException::CipherError,
            "XENCAlgorithmHandlerDefault - decrypt failed - AES IV is not correct");
    }

    // Copy to safebuffer
    result.sbMemcpyIn(&buf[8], n * 8);

    return n * 8;
}

bool XENCAlgorithmHandlerDefault::wrapKeyAES(
        TXFMChain* cipherText,
        const XSECCryptoKey* key,
        safeBuffer& result) const {

    // get the raw key
    XMLByte buf[_MY_MAX_KEY_SIZE + 8];
    memcpy(buf, s_AES_IV, 8);
    XMLByte aesBuf[16];
    XMLByte aesOutBuf[32];  // Give this an extra block for WinCAPI
    TXFMBase* b = cipherText->getLastTxfm();
    unsigned int sz = (unsigned int) b->readBytes(&buf[8], _MY_MAX_KEY_SIZE);

    if (sz <= 0) {
        throw XSECException(XSECException::CipherError,
            "XENCAlgorithmHandlerDefault - Key not found");
    }

    if (sz == _MY_MAX_KEY_SIZE) {
        throw XSECException(XSECException::CipherError,
            "XENCAlgorithmHandlerDefault - Key to encrypt too big!");
    }

    // Find number of blocks, and ensure we are a multiple of 64 bits
    if (sz % 8 != 0) {
        throw XSECException(XSECException::CipherError,
            "XENCAlgorithmHandlerDefault - AES wrapped key not a multiple of 64");
    }

    // Do the decrypt - this cast will throw if wrong, but we should
    // not have been able to get through algorithm checks otherwise
    XSECCryptoSymmetricKey* sk = (XSECCryptoSymmetricKey*) key;

    int n = sz / 8;

    for (int j = 0; j <= 5; ++j) {
        for (int i = 1 ; i <= n ; ++i) {

            // Gather blocks to decrypt
            // A
            memcpy(aesBuf, buf, 8);
            // Ri
            memcpy(&aesBuf[8], &buf[8 * i], 8);

            // do encrypt
            sk->encryptInit(false, XSECCryptoSymmetricKey::MODE_ECB);
            int sz = sk->encrypt(aesBuf, aesOutBuf, 16, 32);
            sz += sk->encryptFinish(&aesOutBuf[sz], 32 - sz);

            if (sz != 16) {
                throw XSECException(XSECException::CipherError,
                    "XENCAlgorithmHandlerDefault - Error performing encrypt in AES wrap");
            }

            // Copy back to where we are
            // A
            memcpy(buf, aesOutBuf, 8);
            // A mod t
            buf[7] ^= ((n * j) + i);
            // Ri
            memcpy(&buf[8 * i], &aesOutBuf[8], 8);
        }
    }

    // Now we have to base64 encode
    XSECCryptoBase64* b64 = XSECPlatformUtils::g_cryptoProvider->base64();

    if (!b64) {
        throw XSECException(XSECException::CryptoProviderError,
                "XENCAlgorithmHandlerDefault - Error getting base64 encoder in AES wrap");
    }

    Janitor<XSECCryptoBase64> j_b64(b64);
    unsigned char* b64Buffer;
    int bufLen = ((n + 1) * 8) * 3;
    XSECnew(b64Buffer, unsigned char[bufLen + 1]);// Overkill
    ArrayJanitor<unsigned char> j_b64Buffer(b64Buffer);

    b64->encodeInit();
    int outputLen = b64->encode (buf, (n+1) * 8, b64Buffer, bufLen);
    outputLen += b64->encodeFinish(&b64Buffer[outputLen], bufLen - outputLen);
    b64Buffer[outputLen] = '\0';

    // Copy to safebuffer
    result.sbStrcpyIn((const char*) b64Buffer);

    return true;
}

// --------------------------------------------------------------------------------
//            DES CMS Key wrap/unwrap
// --------------------------------------------------------------------------------

unsigned int XENCAlgorithmHandlerDefault::unwrapKey3DES(
        TXFMChain* cipherText,
        const XSECCryptoKey* key,
        safeBuffer& result) const {

    // Perform an unwrap on the key
    safeBuffer cipherSB;

    // Cat the encrypted key
    XMLByte buf[_MY_MAX_KEY_SIZE];
    TXFMBase* b = cipherText->getLastTxfm();
    unsigned int offset = 0;
    unsigned int sz = (unsigned int) b->readBytes(buf, _MY_MAX_KEY_SIZE);

    while (sz > 0) {
        cipherSB.sbMemcpyIn(offset, buf, sz);
        offset += sz;
        sz = (unsigned int) b->readBytes(buf, _MY_MAX_KEY_SIZE);
    }

    if (offset > _MY_MAX_KEY_SIZE) {
        throw XSECException(XSECException::CipherError,
            "XENCAlgorithmHandlerDefault - Key to decrypt too big!");
    }

    // Do the decrypt - this cast will throw if wrong, but we should
    // not have been able to get through algorithm checks otherwise
    XSECCryptoSymmetricKey* sk = (XSECCryptoSymmetricKey*) key;

    sk->decryptInit(false, XSECCryptoSymmetricKey::MODE_CBC, s_3DES_CMS_IV);
    // If key is bigger than this, then we have a problem
    sz = sk->decrypt(cipherSB.rawBuffer(), buf, offset, _MY_MAX_KEY_SIZE);

    sz += sk->decryptFinish(&buf[sz], _MY_MAX_KEY_SIZE - sz);

    if (sz <= 0) {
        throw XSECException(XSECException::CipherError,
            "XENCAlgorithmHandlerDefault - Error decrypting key!");
    }

    // We now have the first cut, reverse the cipher text
    XMLByte buf2[_MY_MAX_KEY_SIZE];
    for (unsigned int i = 0; i < sz; ++ i) {
        buf2[sz - i - 1] = buf[i];
    }

    // decrypt again
    sk->decryptInit(false);
    offset = sk->decrypt(buf2, buf, sz, _MY_MAX_KEY_SIZE);
    offset += sk->decryptFinish(&buf[offset], _MY_MAX_KEY_SIZE - offset);

    // Calculate the CMS Key Checksum
    XSECCryptoHash* sha1 = XSECPlatformUtils::g_cryptoProvider->hash(XSECCryptoHash::HASH_SHA1);
    if (!sha1) {
        throw XSECException(XSECException::CryptoProviderError,
                "XENCAlgorithmHandlerDefault - Error getting SHA-1 object in 3DES unwrap");
    }
    Janitor<XSECCryptoHash> j_sha1(sha1);
    sha1->reset();
    sha1->hash(buf, offset - 8);
    sha1->finish(buf2, _MY_MAX_KEY_SIZE);

    // Compare
    for (unsigned int j = 0; j < 8; ++j) {
        if (buf[offset - 8 + j] != buf2[j]) {
            throw XSECException(XSECException::CipherError,
                "XENCAlgorithmHandlerDefault::unwrapKey3DES - CMS Key Checksum does not match");
        }
    }

    result.sbMemcpyIn(buf, offset - 8);

    return offset - 8;
}

bool XENCAlgorithmHandlerDefault::wrapKey3DES(
        TXFMChain* cipherText,
        const XSECCryptoKey* key,
        safeBuffer& result) const {

    // Cat the plaintext key
    XMLByte buf[_MY_MAX_KEY_SIZE + 16];
    TXFMBase* b = cipherText->getLastTxfm();
    int offset = 0;
    unsigned int sz = (unsigned int) b->readBytes(buf, _MY_MAX_KEY_SIZE);

    if (sz <= 0) {
        throw XSECException(XSECException::CipherError,
            "XENCAlgorithmHandlerDefault::wrapKey3DES - Unable to read key");
    }

    if (sz >= _MY_MAX_KEY_SIZE) {
        throw XSECException(XSECException::CipherError,
            "XENCAlgorithmHandlerDefault::wrapKey3DES - Key to decrypt too big!");
    }

    if (sz % 8 != 0) {
        throw XSECException(XSECException::CipherError,
            "XENCAlgorithmHandlerDefault::wrapKey3DES - Key to encrypt not a multiple of 8 bytes");
    }

    // Calculate the CMS Key Checksum

    // Do the first encrypt
    XMLByte buf2[_MY_MAX_KEY_SIZE + 16];

    XSECCryptoHash* sha1 = XSECPlatformUtils::g_cryptoProvider->hash(XSECCryptoHash::HASH_SHA1);
    if (!sha1) {
        throw XSECException(XSECException::CryptoProviderError,
                "XENCAlgorithmHandlerDefault - Error getting SHA-1 object in 3DES wrap");
    }
    Janitor<XSECCryptoHash> j_sha1(sha1);
    sha1->reset();
    sha1->hash(buf, sz);
    sha1->finish(buf2, _MY_MAX_KEY_SIZE);

    for (int j = 0; j < 8 ; ++j)
        buf[sz++] = buf2[j];

    // Do the first encrypt - this cast will throw if wrong, but we should
    // not have been able to get through algorithm checks otherwise
    XSECCryptoSymmetricKey* sk = (XSECCryptoSymmetricKey*) key;

    sk->encryptInit(false);
    // If key is bigger than this, then we have a problem
    sz = sk->encrypt(buf, buf2, sz, _MY_MAX_KEY_SIZE);
    sz += sk->encryptFinish(&buf2[sz], _MY_MAX_KEY_SIZE - sz);

    if (sz <= 0) {
        throw XSECException(XSECException::CipherError,
            "XENCAlgorithmHandlerDefault::wrapKey3DES - Error encrypting key!");
    }

    // We now have the first cut, reverse the cipher text
    for (unsigned int i = 0; i < sz; ++ i) {
        buf[sz - i - 1] = buf2[i];
    }

    // encrypt again
    sk->encryptInit(false, XSECCryptoSymmetricKey::MODE_CBC, s_3DES_CMS_IV);
    offset = sk->encrypt(buf, buf2, sz, _MY_MAX_KEY_SIZE);
    offset += sk->encryptFinish(&buf2[offset], _MY_MAX_KEY_SIZE - offset);

    // Base64 encode
    XSECCryptoBase64* b64 = XSECPlatformUtils::g_cryptoProvider->base64();

    if (!b64) {
        throw XSECException(XSECException::CryptoProviderError,
                "XENCAlgorithmHandlerDefault - Error getting base64 encoder in 3DES wrap");
    }

    Janitor<XSECCryptoBase64> j_b64(b64);
    unsigned char* b64Buffer;
    int bufLen = (offset + 9) * 3;
    XSECnew(b64Buffer, unsigned char[bufLen + 1]);// Overkill
    ArrayJanitor<unsigned char> j_b64Buffer(b64Buffer);

    b64->encodeInit();
    int outputLen = b64->encode (&buf2[8], offset-8, b64Buffer, bufLen);
    outputLen += b64->encodeFinish(&b64Buffer[outputLen], bufLen - outputLen);
    b64Buffer[outputLen] = '\0';

    // Copy to safebuffer
    result.sbStrcpyIn((const char*) b64Buffer);

    return true;
}

// --------------------------------------------------------------------------------
//            InputStream decryption
// --------------------------------------------------------------------------------

bool XENCAlgorithmHandlerDefault::appendDecryptCipherTXFM(
        TXFMChain* cipherText,
        XENCEncryptionMethod* encryptionMethod,
        const XSECCryptoKey* key,
        XERCES_CPP_NAMESPACE_QUALIFIER DOMDocument* doc
        ) const {

    // We only support this for bulk Symmetric key algorithms

    XSECCryptoKey::KeyType kt;
    XSECCryptoSymmetricKey::SymmetricKeyType skt;
    bool isKeyWrap = false;
    XSECCryptoSymmetricKey::SymmetricKeyMode skm;
    unsigned int taglen;

    mapURIToKey(encryptionMethod->getAlgorithm(), key, kt, skt, isKeyWrap, skm, taglen);
    if (kt != XSECCryptoKey::KEY_SYMMETRIC || isKeyWrap == true) {
        throw XSECException(XSECException::CipherError,
            "XENCAlgorithmHandlerDefault::appendDecryptCipherTXFM - only supports bulk symmetric algorithms");
    }

    if (skm == XSECCryptoSymmetricKey::MODE_GCM) {

        // GCM doesn't fit the pipelined model of the existing code, so we have a custom
        // routine that decrypts to a safeBuffer directly.
        safeBuffer result;
        unsigned int sz = doGCMDecryptToSafeBuffer(cipherText, key, taglen, result);

        // Now we hijack the original tansform chain with a safeBuffer-sourced link.
        TXFMSB* tsb;
        XSECnew(tsb, TXFMSB(doc));
        tsb->setInput(result, sz);
        cipherText->appendTxfm(tsb);
        result.cleanseBuffer();
        return true;
    }


    // Add the decryption TXFM
    TXFMCipher* tcipher;
    XSECnew(tcipher, TXFMCipher(doc, key, false));
    cipherText->appendTxfm(tcipher);

    return true;
}


// --------------------------------------------------------------------------------
//            GCM SafeBuffer decryption
// --------------------------------------------------------------------------------

unsigned int XENCAlgorithmHandlerDefault::doGCMDecryptToSafeBuffer(
        TXFMChain* cipherText,
        const XSECCryptoKey* key,
        unsigned int taglen,
        safeBuffer& result) const {

    // Only works with symmetric key
    if (key->getKeyType() != XSECCryptoKey::KEY_SYMMETRIC) {
        throw XSECException(XSECException::CipherError,
            "XENCAlgorithmHandlerDefault - GCM Decrypt must use symmetric key");
    }

    // First read in all the data so we can get to the tag.
    safeBuffer cipherBuf("");
    XMLByte inbuf[3072];
    unsigned int szTotal = 0, sz = cipherText->getLastTxfm()->readBytes(inbuf, 3072);
    while (sz > 0) {
        cipherBuf.sbMemcpyIn(szTotal, inbuf, sz);
        szTotal += sz;
        sz = cipherText->getLastTxfm()->readBytes(inbuf, 3072);
    }

    if (szTotal <= taglen) {
        throw XSECException(XSECException::CipherError,
            "XENCAlgorithmHandlerDefault - GCM ciphertext size not large enough to include authentication tag");
    }

    const unsigned char* cipherPtr = cipherBuf.rawBuffer();

    // Init the cipher with the tag at the end of the cipher text and omit it from later decryption.
    szTotal -= taglen;
    ((XSECCryptoSymmetricKey*) key)->decryptInit(false, XSECCryptoSymmetricKey::MODE_GCM, NULL, cipherPtr + szTotal, taglen);

    unsigned int plainOffset = 0;
    do {
        // Feed the data in at most 2048-byte chunks.
        sz = ((XSECCryptoSymmetricKey*) key)->decrypt(cipherPtr, inbuf, (szTotal > 2048 ? 2048 : szTotal), 3072);
        cipherPtr += (szTotal > 2048 ? 2048 : szTotal);
        szTotal -= (szTotal > 2048 ? 2048 : szTotal);
        if (sz > 0) {
            result.sbMemcpyIn(plainOffset, inbuf, sz);
            plainOffset += sz;
        }
    } while (szTotal > 0);

    // Wrap it up.
    sz = ((XSECCryptoSymmetricKey*) key)->decryptFinish(inbuf, 3072);
    if (sz > 0) {
        result.sbMemcpyIn(plainOffset, inbuf, sz);
        plainOffset += sz;
    }

    // In case any plaintext is left lying around.
    memset(inbuf, 0, 3072);
    return plainOffset;
}

// --------------------------------------------------------------------------------
//            RSA SafeBuffer decryption
// --------------------------------------------------------------------------------

unsigned int XENCAlgorithmHandlerDefault::doRSADecryptToSafeBuffer(
        TXFMChain* cipherText,
        XENCEncryptionMethod* encryptionMethod,
        const XSECCryptoKey* key,
        DOMDocument* doc,
        safeBuffer& result) const {

    // Only works with RSA_PRIVATE or PAIR
    if (key->getKeyType() == XSECCryptoKey::KEY_RSA_PUBLIC) {
        throw XSECException(XSECException::CipherError,
            "XENCAlgorithmHandlerDefault - RSA Decrypt must use private key");
    }

    // Know this is an RSA key, so just cast

    XSECCryptoKeyRSA* rsa = (XSECCryptoKeyRSA*) key;

    // Allocate an output buffer
    unsigned char* decBuf;
    XSECnew(decBuf, unsigned char[rsa->getLength()]);
    ArrayJanitor<unsigned char> j_decBuf(decBuf);

    // Input
    TXFMBase* b = cipherText->getLastTxfm();
    safeBuffer cipherSB;
    XMLByte buf[1024];
    unsigned int offset = 0;

    unsigned int bytesRead = (unsigned int) b->readBytes(buf, 1024);
    while (bytesRead > 0) {
        cipherSB.sbMemcpyIn(offset, buf, bytesRead);
        offset += bytesRead;
        bytesRead = (unsigned int) b->readBytes(buf, 1024);
    }

    unsigned int decryptLen;

    // Now we find out what kind of padding
    if (strEquals(encryptionMethod->getAlgorithm(), DSIGConstants::s_unicodeStrURIRSA_1_5)) {

        // Do decrypt
        decryptLen = rsa->privateDecrypt(cipherSB.rawBuffer(),
                                                  decBuf,
                                                  offset,
                                                  rsa->getLength(),
                                                  XSECCryptoKeyRSA::PAD_PKCS_1_5);
    }
    else if (strEquals(encryptionMethod->getAlgorithm(), DSIGConstants::s_unicodeStrURIRSA_OAEP_MGFP1) ||
             strEquals(encryptionMethod->getAlgorithm(), DSIGConstants::s_unicodeStrURIRSA_OAEP)) {

        const XMLCh* digmeth = encryptionMethod->getDigestMethod();
        if (!digmeth|| !*digmeth) {
            digmeth = DSIGConstants::s_unicodeStrURISHA1;
        }

        const XMLCh* mgfalg = encryptionMethod->getMGF();
        if (!mgfalg || !*mgfalg) {
            mgfalg = DSIGConstants::s_unicodeStrURIMGF1_SHA1;
        }

        // Read out any OAEP params
        unsigned char* oaepParamsBuf = NULL;
        unsigned int oaepParamsLen = 0;

        const XMLCh* oaepParams = encryptionMethod->getOAEPparams();
        if (oaepParams != NULL) {
            XSECAutoPtrChar oaepParamsStr(oaepParams);

            unsigned int bufLen = (unsigned int) strlen(oaepParamsStr.get());
            oaepParamsBuf = new unsigned char[bufLen];

            XSECCryptoBase64* b64 = XSECPlatformUtils::g_cryptoProvider->base64();
            Janitor<XSECCryptoBase64> j_b64(b64);

            b64->decodeInit();
            oaepParamsLen = b64->decode((unsigned char*) oaepParamsStr.get(), bufLen, oaepParamsBuf, bufLen);
            oaepParamsLen += b64->decodeFinish(&oaepParamsBuf[oaepParamsLen], bufLen - oaepParamsLen);
        }

        ArrayJanitor<unsigned char> j_oaepParamsBuf(oaepParamsBuf);

        decryptLen = rsa->privateDecrypt(cipherSB.rawBuffer(),
                                                  decBuf,
                                                  offset,
                                                  rsa->getLength(),
                                                  XSECCryptoKeyRSA::PAD_OAEP,
                                                  digmeth,
                                                  mgfalg,
                                                  oaepParamsBuf,
                                                  oaepParamsLen);

    }

    else {
        throw XSECException(XSECException::CipherError,
            "XENCAlgorithmHandlerDefault::doRSADecryptToSafeBuffer - Unknown padding type");
    }
    // Copy to output
    result.sbMemcpyIn(decBuf, decryptLen);

    memset(decBuf, 0, decryptLen);

    return decryptLen;
}

// --------------------------------------------------------------------------------
//            SafeBuffer decryption
// --------------------------------------------------------------------------------

unsigned int XENCAlgorithmHandlerDefault::decryptToSafeBuffer(
        TXFMChain* cipherText,
        XENCEncryptionMethod* encryptionMethod,
        const XSECCryptoKey* key,
        DOMDocument* doc,
        safeBuffer& result
        ) const {

    XSECCryptoKey::KeyType kt;
    XSECCryptoSymmetricKey::SymmetricKeyType skt;
    bool isKeyWrap = false;
    XSECCryptoSymmetricKey::SymmetricKeyMode skm;
    unsigned int taglen;

    if (encryptionMethod == NULL) {
        throw XSECException(XSECException::CipherError,
            "XENCAlgorithmHandlerDefault::decryptToSafeBuffer - Cannot operate with NULL encryption Method");
    }


    // Check the uri against the key type
    mapURIToKey(encryptionMethod->getAlgorithm(), key, kt, skt, isKeyWrap, skm, taglen);

    // RSA?
    if (kt == XSECCryptoKey::KEY_RSA_PAIR ||
        kt == XSECCryptoKey::KEY_RSA_PUBLIC ||
        kt == XSECCryptoKey::KEY_RSA_PRIVATE) {

        return doRSADecryptToSafeBuffer(cipherText, encryptionMethod, key, doc, result);

    }

    // Ensure is symmetric before we continue
    if (kt != XSECCryptoKey::KEY_SYMMETRIC) {
        throw XSECException(XSECException::CipherError,
            "XENCAlgorithmHandlerDefault::decryptToSafeBuffer - Not an RSA key, but not symmetric");
    }

    // Key wrap?

    if (isKeyWrap == true) {

        if (skt == XSECCryptoSymmetricKey::KEY_AES_128 ||
            skt == XSECCryptoSymmetricKey::KEY_AES_192 ||
            skt == XSECCryptoSymmetricKey::KEY_AES_256) {

            return unwrapKeyAES(cipherText, key, result);
        }
        else if (skt == XSECCryptoSymmetricKey::KEY_3DES_192) {
            return unwrapKey3DES(cipherText, key, result);
        }
        else {
            throw XSECException(XSECException::CipherError,
                "XENCAlgorithmHandlerDefault::decryptToSafeBuffer - don't know how to do key wrap for algorithm");
        }
    }

    if (skm == XSECCryptoSymmetricKey::MODE_GCM) {
        // GCM doesn't fit the pipelined model of the existing code, so we have a custom
        // routine that decrypts to a safeBuffer directly.
        return doGCMDecryptToSafeBuffer(cipherText, key, taglen, result);
    }

    // It's symmetric and it's not a key wrap or GCM, so just treat as a block algorithm.

    TXFMCipher* tcipher;
    XSECnew(tcipher, TXFMCipher(doc, key, false));

    cipherText->appendTxfm(tcipher);

    // Do the decrypt to the safeBuffer.

    result.sbStrcpyIn("");
    unsigned int offset = 0;
    XMLByte buf[1024];
    TXFMBase* b = cipherText->getLastTxfm();

    unsigned int bytesRead = (unsigned int) b->readBytes(buf, 1024);
    while (bytesRead > 0) {
        result.sbMemcpyIn(offset, buf, bytesRead);
        offset += bytesRead;
        bytesRead = (unsigned int) b->readBytes(buf, 1024);
    }

    result[offset] = '\0';

    return offset;
}

// --------------------------------------------------------------------------------
//            RSA SafeBuffer encryption
// --------------------------------------------------------------------------------

bool XENCAlgorithmHandlerDefault::doRSAEncryptToSafeBuffer(
        TXFMChain* plainText,
        XENCEncryptionMethod* encryptionMethod,
        const XSECCryptoKey* key,
        XERCES_CPP_NAMESPACE_QUALIFIER DOMDocument* doc,
        safeBuffer& result
        ) const {

    // Only works with RSA_PRIVATE or PAIR
    if (key->getKeyType() == XSECCryptoKey::KEY_RSA_PRIVATE) {
        throw XSECException(XSECException::CipherError,
            "XENCAlgorithmHandlerDefault - RSA Encrypt must use public key");
    }

    XSECCryptoKeyRSA* rsa = (XSECCryptoKeyRSA*) key;

    // Allocate an output buffer
    unsigned char* encBuf;
    XSECnew(encBuf, unsigned char[rsa->getLength()]);
    ArrayJanitor<unsigned char> j_encBuf(encBuf);

    // Input
    TXFMBase* b = plainText->getLastTxfm();
    safeBuffer plainSB;
    plainSB.isSensitive();

    XMLByte buf[1024];
    unsigned int offset = 0;

    unsigned int bytesRead = (unsigned int) b->readBytes(buf, 1024);
    while (bytesRead > 0) {
        plainSB.sbMemcpyIn(offset, buf, bytesRead);
        offset += bytesRead;
        bytesRead = (unsigned int) b->readBytes(buf, 1024);
    }

    unsigned int encryptLen;

    // Do encrypt
    if (strEquals(encryptionMethod->getAlgorithm(), DSIGConstants::s_unicodeStrURIRSA_1_5)) {
        encryptLen = rsa->publicEncrypt(plainSB.rawBuffer(),
                                                  encBuf,
                                                  offset,
                                                  rsa->getLength(),
                                                  XSECCryptoKeyRSA::PAD_PKCS_1_5);
    }

    else if (strEquals(encryptionMethod->getAlgorithm(), DSIGConstants::s_unicodeStrURIRSA_OAEP_MGFP1) ||
            strEquals(encryptionMethod->getAlgorithm(), DSIGConstants::s_unicodeStrURIRSA_OAEP)) {

        const XMLCh* digmeth = encryptionMethod->getDigestMethod();
        if (!digmeth || !*digmeth) {
            digmeth = DSIGConstants::s_unicodeStrURISHA1;
        }

        const XMLCh* mgfalg = encryptionMethod->getMGF();
        if (!mgfalg || !*mgfalg) {
            mgfalg = DSIGConstants::s_unicodeStrURIMGF1_SHA1;
        }

        // Read out any OAEP params
        unsigned char* oaepParamsBuf = NULL;
        unsigned int oaepParamsLen = 0;

        const XMLCh* oaepParams = encryptionMethod->getOAEPparams();
        if (oaepParams != NULL) {
            XSECAutoPtrChar oaepParamsStr(oaepParams);

            unsigned int bufLen = (unsigned int) strlen(oaepParamsStr.get());
            oaepParamsBuf = new unsigned char[bufLen];

            XSECCryptoBase64* b64 = XSECPlatformUtils::g_cryptoProvider->base64();
            Janitor<XSECCryptoBase64> j_b64(b64);

            b64->decodeInit();
            oaepParamsLen = b64->decode((unsigned char*) oaepParamsStr.get(), bufLen, oaepParamsBuf, bufLen);
            oaepParamsLen += b64->decodeFinish(&oaepParamsBuf[oaepParamsLen], bufLen - oaepParamsLen);
        }

        ArrayJanitor<unsigned char> j_oaepParamsBuf(oaepParamsBuf);

        encryptLen = rsa->publicEncrypt(plainSB.rawBuffer(),
                                          encBuf,
                                          offset,
                                          rsa->getLength(),
                                          XSECCryptoKeyRSA::PAD_OAEP,
                                          digmeth,
                                          mgfalg,
                                          oaepParamsBuf,
                                          oaepParamsLen);
    }
    else {
        throw XSECException(XSECException::CipherError,
            "XENCAlgorithmHandlerDefault::doRSAEncryptToSafeBuffer - Unknown padding type");
    }

    // Now need to base64 encode
    XSECCryptoBase64* b64 =
        XSECPlatformUtils::g_cryptoProvider->base64();
    Janitor<XSECCryptoBase64> j_b64(b64);

    b64->encodeInit();
    encryptLen = b64->encode(encBuf, encryptLen, buf, 1024);
    result.sbMemcpyIn(buf, encryptLen);
    unsigned int finalLen = b64->encodeFinish(buf, 1024);
    result.sbMemcpyIn(encryptLen, buf, finalLen);
    result[encryptLen + finalLen] = '\0';

    // This is a string, so set the buffer correctly
    result.setBufferType(safeBuffer::BUFFER_CHAR);

    return true;
}


// --------------------------------------------------------------------------------
//            SafeBuffer encryption
// --------------------------------------------------------------------------------

bool XENCAlgorithmHandlerDefault::encryptToSafeBuffer(
        TXFMChain* plainText,
        XENCEncryptionMethod* encryptionMethod,
        const XSECCryptoKey* key,
        XERCES_CPP_NAMESPACE_QUALIFIER DOMDocument* doc,
        safeBuffer& result
        ) const {

    XSECCryptoKey::KeyType kt;
    XSECCryptoSymmetricKey::SymmetricKeyType skt;
    bool isKeyWrap = false;
    XSECCryptoSymmetricKey::SymmetricKeyMode skm;
    unsigned int taglen;

    if (encryptionMethod == NULL) {
        throw XSECException(XSECException::CipherError,
            "XENCAlgorithmHandlerDefault::encryptToSafeBuffer - Cannot operate with NULL encryption Method");
    }


    // Check the uri against the key type
    mapURIToKey(encryptionMethod->getAlgorithm(), key, kt, skt, isKeyWrap, skm, taglen);

    // RSA?
    if (kt == XSECCryptoKey::KEY_RSA_PRIVATE ||
        kt == XSECCryptoKey::KEY_RSA_PUBLIC ||
        kt == XSECCryptoKey::KEY_RSA_PAIR) {

        return doRSAEncryptToSafeBuffer(plainText, encryptionMethod, key, doc, result);
    }

    // Ensure is symmetric before we continue
    if (kt != XSECCryptoKey::KEY_SYMMETRIC) {
        throw XSECException(XSECException::CipherError,
            "XENCAlgorithmHandlerDefault::encryptToSafeBuffer - Not an RSA key, but not symmetric");
    }

    if (isKeyWrap == true) {

        if (skt == XSECCryptoSymmetricKey::KEY_AES_128 ||
            skt == XSECCryptoSymmetricKey::KEY_AES_192 ||
            skt == XSECCryptoSymmetricKey::KEY_AES_256) {

            return wrapKeyAES(plainText, key, result);
        }

        if (skt == XSECCryptoSymmetricKey::KEY_3DES_192) {
            return wrapKey3DES(plainText, key, result);
        }
        else {
            throw XSECException(XSECException::CipherError,
                "XENCAlgorithmHandlerDefault::decryptToSafeBuffer - don't know how to do key wrap for algorithm");
        }
    }

    // Must be bulk symmetric - do the encryption

    TXFMCipher* tcipher;
    XSECnew(tcipher, TXFMCipher(doc, key, true, skm, taglen));
    plainText->appendTxfm(tcipher);

    // Transform to Base64
    TXFMBase64* tb64;
    XSECnew(tb64, TXFMBase64(doc, false));
    plainText->appendTxfm(tb64);

    // Read into the safeBuffer
    result = "";

    result << plainText->getLastTxfm();

    return true;
}
// --------------------------------------------------------------------------------
//            Key Creation
// --------------------------------------------------------------------------------

XSECCryptoKey* XENCAlgorithmHandlerDefault::createKeyForURI(
        const XMLCh* uri,
        const unsigned char* keyBuffer,
        unsigned int keyLen
        ) const {

    XSECCryptoSymmetricKey* sk = NULL;

    if (strEquals(uri, DSIGConstants::s_unicodeStrURI3DES_CBC)) {
        if (keyLen < 192 / 8)
            throw XSECException(XSECException::CipherError, 
                "XENCAlgorithmHandlerDefault - key size was invalid");
        sk = XSECPlatformUtils::g_cryptoProvider->keySymmetric(XSECCryptoSymmetricKey::KEY_3DES_192);
    }
    else if (strEquals(uri, DSIGConstants::s_unicodeStrURIAES128_CBC) || strEquals(uri, DSIGConstants::s_unicodeStrURIAES128_GCM)) {
        if (keyLen < 128 / 8)
            throw XSECException(XSECException::CipherError, 
                "XENCAlgorithmHandlerDefault - key size was invalid");
        sk = XSECPlatformUtils::g_cryptoProvider->keySymmetric(XSECCryptoSymmetricKey::KEY_AES_128);
    }
    else if (strEquals(uri, DSIGConstants::s_unicodeStrURIAES192_CBC) || strEquals(uri, DSIGConstants::s_unicodeStrURIAES192_GCM)) {
        if (keyLen < 192 / 8)
            throw XSECException(XSECException::CipherError, 
                "XENCAlgorithmHandlerDefault - key size was invalid");
        sk = XSECPlatformUtils::g_cryptoProvider->keySymmetric(XSECCryptoSymmetricKey::KEY_AES_192);
    }
    else if (strEquals(uri, DSIGConstants::s_unicodeStrURIAES256_CBC) || strEquals(uri, DSIGConstants::s_unicodeStrURIAES256_GCM)) {
        if (keyLen < 256 / 8)
            throw XSECException(XSECException::CipherError, 
                "XENCAlgorithmHandlerDefault - key size was invalid");
        sk = XSECPlatformUtils::g_cryptoProvider->keySymmetric(XSECCryptoSymmetricKey::KEY_AES_256);
    }

    if (sk != NULL) {
        sk->setKey(keyBuffer, keyLen);
        return sk;
    }

    throw XSECException(XSECException::CipherError,
        "XENCAlgorithmHandlerDefault - URI Provided, but cannot create associated key");
}


// --------------------------------------------------------------------------------
//            Clone
// --------------------------------------------------------------------------------

XSECAlgorithmHandler* XENCAlgorithmHandlerDefault::clone(void) const {

    XENCAlgorithmHandlerDefault* ret;
    XSECnew(ret, XENCAlgorithmHandlerDefault);

    return ret;
}

// --------------------------------------------------------------------------------
//            Unsupported operations
// --------------------------------------------------------------------------------

unsigned int XENCAlgorithmHandlerDefault::signToSafeBuffer(
        TXFMChain* inputBytes,
        const XMLCh* URI,
        const XSECCryptoKey* key,
        unsigned int outputLength,
        safeBuffer& result) const {

    throw XSECException(XSECException::AlgorithmMapperError,
            "XENCAlgorithmHandlerDefault - Signature operations not supported");
}


bool XENCAlgorithmHandlerDefault::appendSignatureHashTxfm(
        TXFMChain* inputBytes,
        const XMLCh* URI,
        const XSECCryptoKey* key) const {

    throw XSECException(XSECException::AlgorithmMapperError,
            "XENCAlgorithmHandlerDefault - Signature operations not supported");
}

bool XENCAlgorithmHandlerDefault::verifyBase64Signature(
        TXFMChain* inputBytes,
        const XMLCh* URI,
        const char* sig,
        unsigned int outputLength,
        const XSECCryptoKey* key) const {

    throw XSECException(XSECException::AlgorithmMapperError,
            "XENCAlgorithmHandlerDefault - Signature operations not supported");
}

bool XENCAlgorithmHandlerDefault::appendHashTxfm(
        TXFMChain* inputBytes,
        const XMLCh* URI) const {

    throw XSECException(XSECException::AlgorithmMapperError,
            "XENCAlgorithmHandlerDefault - Hash operations not supported");
}
