/**
 * 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: DSIGAlgorithmHandlerDefault.cpp 1833341 2018-06-11 16:25:41Z scantor $
 *
 */

// XSEC Includes


#include <xsec/dsig/DSIGAlgorithmHandlerDefault.hpp>
#include <xsec/enc/XSECCryptoKey.hpp>
#include <xsec/framework/XSECDefs.hpp>
#include <xsec/framework/XSECError.hpp>
#include <xsec/transformers/TXFMChain.hpp>
#include <xsec/transformers/TXFMBase64.hpp>
#include <xsec/transformers/TXFMHash.hpp>

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

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

XERCES_CPP_NAMESPACE_USE

#define MAXB64BUFSIZE 2048

// --------------------------------------------------------------------------------
//           Some useful utility functions
// --------------------------------------------------------------------------------


bool compareBase64StringToRaw(const char* b64Str,
                              unsigned char* raw,
                              unsigned int rawLen,
                              unsigned int maxCompare = 0) {
    // Decode a base64 buffer and then compare the result to a raw buffer
    // Compare at most maxCompare bits (if maxCompare > 0)
    // Note - whilst the other parameters are bytes, maxCompare is bits

    // The div function below takes signed int, so make sure the value
    // is safe to cast.
    if ((int) maxCompare < 0) {

        throw XSECException(XSECException::CryptoProviderError,
                "Comparison length was unsafe");

    }

    unsigned char outputStr[MAXB64BUFSIZE];
    unsigned int outputLen = 0;

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

    if (!b64) {

        throw XSECException(XSECException::CryptoProviderError,
                "Error requesting Base64 object from Crypto Provider");

    }

    Janitor<XSECCryptoBase64> j_b64(b64);

    b64->decodeInit();
    outputLen = b64->decode((unsigned char *) b64Str, (unsigned int) strlen((char *) b64Str), outputStr, MAXB64BUFSIZE);
    outputLen += b64->decodeFinish(&outputStr[outputLen], MAXB64BUFSIZE - outputLen);

    // Compare

    div_t d;
    d.rem = 0;
    d.quot = 0;

    unsigned int maxCompareBytes;

    unsigned int size;

    if (maxCompare > 0) {
        d = div(maxCompare, 8);
        maxCompareBytes = d.quot;
        if (d.rem != 0)
            maxCompareBytes++;

        if (rawLen < maxCompareBytes && outputLen < maxCompareBytes) {
            if (rawLen != outputLen)
                return false;
            size = rawLen;
        }
        else if (rawLen < maxCompareBytes || outputLen < maxCompareBytes) {
            return false;
        }
        else
            size = maxCompareBytes;
    }
    else {

        if (rawLen != outputLen)
            return false;

        size = rawLen;

    }

    // Compare bytes
    unsigned int i, j;
    for (i = 0; i < size; ++ i) {
        if (raw[i] != outputStr[i])
            return false;
    }

    // Compare bits

    char mask = 0x01;
    if (maxCompare != 0) {
        for (j = 0 ; j < (unsigned int) d.rem; ++j) {

            if ((raw[i] & mask) != (outputStr[i] & mask))
                return false;

            mask = mask << 1;
        }
    }

    return true;
}


void convertRawToBase64String(safeBuffer& b64SB,
                              unsigned char* raw,
                              unsigned int rawLen,
                              unsigned int maxBits = 0) {

    // Translate the rawbuffer (at most maxBits or rawLen - whichever is smaller)
    // to a base64 string

    unsigned char b64Str[MAXB64BUFSIZE];
    unsigned int outputLen = 0;

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

    if (!b64) {
        throw XSECException(XSECException::CryptoProviderError,
                "Error requesting Base64 object from Crypto Provider");
    }

    Janitor<XSECCryptoBase64> j_b64(b64);

    // Determine length to translate
    unsigned int size;

    if (maxBits > 0) {
        div_t d = div(maxBits, 8);
        size = d.quot;
        if (d.rem != 0)
            ++size;

        if (size > rawLen)
            size = rawLen;
    }

    else
        size = rawLen;

    b64->encodeInit();
    outputLen = b64->encode((unsigned char *) raw, rawLen, b64Str, MAXB64BUFSIZE - 1);
    outputLen += b64->encodeFinish(&b64Str[outputLen], MAXB64BUFSIZE - outputLen - 1);
    b64Str[outputLen] = '\0';

    // Copy out

    b64SB.sbStrcpyIn((char *) b64Str);
}

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

XSECAlgorithmHandler* DSIGAlgorithmHandlerDefault::clone() const {

    DSIGAlgorithmHandlerDefault* ret;
    XSECnew(ret, DSIGAlgorithmHandlerDefault);

    return ret;
}

// --------------------------------------------------------------------------------
//            Add a hash txfm
// --------------------------------------------------------------------------------

TXFMBase* addHashTxfm(XSECCryptoHash::HashType hashType, const XSECCryptoKey* key, DOMDocument* doc) {

    // Given a hash method and signature method, create an appropriate TXFM

    TXFMBase* txfm;

    XSECnew(txfm, TXFMHash(doc, hashType, key));

    return txfm;
}

// --------------------------------------------------------------------------------
//            Map a Signature hash
// --------------------------------------------------------------------------------

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

    XSECCryptoHash::HashType hashType;

    // Map to internal constants

    if (!XSECAlgorithmSupport::evalSignatureMethod(URI, key, hashType)) {
        safeBuffer sb;
        sb.sbTranscodeIn("DSIGAlgorithmHandlerDefault - Unknown or key-incompatible URI : ");
        sb.sbXMLChCat(URI);

        throw XSECException(XSECException::AlgorithmMapperError,
            sb.rawXMLChBuffer());
    }

    // Now append the appropriate hash transform onto the end of the chain
    // If this is an HMAC of some kind - this function will add the appropriate key

    TXFMBase* htxfm = addHashTxfm(
            hashType,
            (key->getKeyType() == XSECCryptoKey::KEY_HMAC ? key : NULL),
            inputBytes->getLastTxfm()->getDocument());
    inputBytes->appendTxfm(htxfm);

    return true;
}


// --------------------------------------------------------------------------------
//            Sign
// --------------------------------------------------------------------------------

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

    XSECCryptoHash::HashType hashType;

    // Map to internal constants

    if (!XSECAlgorithmSupport::evalSignatureMethod(URI, key, hashType)) {
        safeBuffer sb;
        sb.sbTranscodeIn("DSIGAlgorithmHandlerDefault - Unknown or key-incompatible URI : ");
        sb.sbXMLChCat(URI);

        throw XSECException(XSECException::AlgorithmMapperError,
            sb.rawXMLChBuffer());
    }

    // Now append the appropriate hash transform onto the end of the chain
    // If this is an HMAC of some kind - this function will add the appropriate key

    TXFMBase * htxfm = addHashTxfm(
            hashType,
            (key->getKeyType() == XSECCryptoKey::KEY_HMAC ? key : NULL),
            inputBytes->getLastTxfm()->getDocument());
    inputBytes->appendTxfm(htxfm);

    unsigned char hash[4096];

    int hashLen = inputBytes->getLastTxfm()->readBytes((XMLByte *) hash, 4096);

    // Now check the calculated hash

    // For now, use a fixed length buffer, but expand it,
    // and detect if the signature size exceeds what we can
    // handle.
    char b64Buf[MAXB64BUFSIZE];
    unsigned int b64Len;
    safeBuffer b64SB;

    switch (key->getKeyType()) {

    case (XSECCryptoKey::KEY_DSA_PRIVATE) :
    case (XSECCryptoKey::KEY_DSA_PAIR) :

        b64Len = ((XSECCryptoKeyDSA *) key)->signBase64Signature(
            hash,
            hashLen,
            (char *) b64Buf,
            MAXB64BUFSIZE);

        if (b64Len <= 0) {
            throw XSECException(XSECException::AlgorithmMapperError,
                "Unknown error occurred during a DSA Signing operation");
        }
        else if (b64Len >= MAXB64BUFSIZE) {
            throw XSECException(XSECException::AlgorithmMapperError,
                "DSA Signing operation exceeded size of buffer");
        }

        if (b64Buf[b64Len-1] == '\n')
            b64Buf[b64Len-1] = '\0';
        else
            b64Buf[b64Len] = '\0';

        break;

    case (XSECCryptoKey::KEY_RSA_PRIVATE) :
    case (XSECCryptoKey::KEY_RSA_PAIR) :

        b64Len = ((XSECCryptoKeyRSA *) key)->signSHA1PKCS1Base64Signature(
            hash,
            hashLen,
            (char *) b64Buf,
            MAXB64BUFSIZE,
            hashType);

        if (b64Len <= 0) {
            throw XSECException(XSECException::AlgorithmMapperError,
                "Unknown error occurred during a RSA Signing operation");
        }
        else if (b64Len >= MAXB64BUFSIZE) {
            throw XSECException(XSECException::AlgorithmMapperError,
                "RSA Signing operation exceeded size of buffer");
        }

        // Clean up some "funnies" and make sure the string is NULL terminated

        if (b64Buf[b64Len-1] == '\n')
            b64Buf[b64Len-1] = '\0';
        else
            b64Buf[b64Len] = '\0';

        break;

    case (XSECCryptoKey::KEY_EC_PRIVATE) :
    case (XSECCryptoKey::KEY_EC_PAIR) :

        b64Len = ((XSECCryptoKeyEC *) key)->signBase64SignatureDSA(
            hash,
            hashLen,
            (char *) b64Buf,
            MAXB64BUFSIZE);

        if (b64Len <= 0) {
            throw XSECException(XSECException::AlgorithmMapperError,
                "Unknown error occurred during an ECDSA Signing operation");
        }
        else if (b64Len >= MAXB64BUFSIZE) {
            throw XSECException(XSECException::AlgorithmMapperError,
                "ECDSA Signing operation exceeded size of buffer");
        }

        if (b64Buf[b64Len-1] == '\n')
            b64Buf[b64Len-1] = '\0';
        else
            b64Buf[b64Len] = '\0';

        break;

    case (XSECCryptoKey::KEY_HMAC) :

        // Signature already created, so just translate to base 64 and enter string

        // FIX: CVE-2009-0217
        if (outputLength > 0 && (outputLength > (unsigned int)hashLen || outputLength < 80 || outputLength < (unsigned int)hashLen / 2)) {
            throw XSECException(XSECException::AlgorithmMapperError,
                "HMACOutputLength set to unsafe value.");
        }

        convertRawToBase64String(b64SB,
                                hash,
                                hashLen,
                                outputLength);

        strncpy(b64Buf, (char *) b64SB.rawBuffer(), MAXB64BUFSIZE);
        break;

    default :
        throw XSECException(XSECException::AlgorithmMapperError,
            "Key found, but don't know how to sign the document using it");

    }

    result = b64Buf;

    return (unsigned int) strlen(b64Buf);
}

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

    XSECCryptoHash::HashType hashType;

    // Map to internal constants

    if (!XSECAlgorithmSupport::evalSignatureMethod(URI, key, hashType)) {
        safeBuffer sb;
        sb.sbTranscodeIn("DSIGAlgorithmHandlerDefault - Unknown or key-incompatible URI : ");
        sb.sbXMLChCat(URI);

        throw XSECException(XSECException::AlgorithmMapperError,
            sb.rawXMLChBuffer());
    }

    // Now append the appropriate hash transform onto the end of the chain
    // If this is an HMAC of some kind - this function will add the appropriate key

    TXFMBase * htxfm = addHashTxfm(
            hashType,
            (key->getKeyType() == XSECCryptoKey::KEY_HMAC ? key : NULL),
            inputBytes->getLastTxfm()->getDocument());
    inputBytes->appendTxfm(htxfm);

    unsigned char hash[4096];

    int hashLen = inputBytes->getLastTxfm()->readBytes((XMLByte *) hash, 4096);

    // Now check the calculated hash
    bool sigVfyRet = false;

    switch (key->getKeyType()) {

    case (XSECCryptoKey::KEY_DSA_PUBLIC) :
    case (XSECCryptoKey::KEY_DSA_PAIR) :

        sigVfyRet = ((XSECCryptoKeyDSA *) key)->verifyBase64Signature(
            hash,
            hashLen,
            (char *) sig,
            (unsigned int) strlen(sig));

        break;

    case (XSECCryptoKey::KEY_RSA_PUBLIC) :
    case (XSECCryptoKey::KEY_RSA_PAIR) :

        sigVfyRet = ((XSECCryptoKeyRSA *) key)->verifySHA1PKCS1Base64Signature(
            hash,
            hashLen,
            sig,
            (unsigned int) strlen(sig),
            hashType);

        break;

    case (XSECCryptoKey::KEY_EC_PUBLIC) :
    case (XSECCryptoKey::KEY_EC_PAIR) :

        sigVfyRet = ((XSECCryptoKeyEC *) key)->verifyBase64SignatureDSA(
            hash,
            hashLen,
            (char *) sig,
            (unsigned int) strlen(sig));

        break;

    case (XSECCryptoKey::KEY_HMAC) :

        // Already done - just compare calculated value with read value

        // FIX: CVE-2009-0217
        if (outputLength > 0 && (outputLength > (unsigned int)hashLen || outputLength < 80 || outputLength < (unsigned int)hashLen / 2)) {
            throw XSECException(XSECException::AlgorithmMapperError,
                "HMACOutputLength set to unsafe value.");
        }

        sigVfyRet = compareBase64StringToRaw(sig,
            hash,
            hashLen,
            outputLength);

        break;

    default :
        throw XSECException(XSECException::AlgorithmMapperError,
            "Key found, but don't know how to verify the signature using it");

    }

    return sigVfyRet;
}

// --------------------------------------------------------------------------------
//            Hash TXFM appenders
// --------------------------------------------------------------------------------

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

    // Is this a URI we recognize?

    XSECCryptoHash::HashType hashType = XSECAlgorithmSupport::getHashType(URI);

    if (hashType == XSECCryptoHash::HASH_NONE) {
        safeBuffer sb;
        sb.sbTranscodeIn("DSIGAlgorithmHandlerDefault - Unknown Hash URI : ");
        sb.sbXMLChCat(URI);

        throw XSECException(XSECException::AlgorithmMapperError, sb.rawXMLChBuffer());
    }

    TXFMBase* txfm;
    DOMDocument* d = inputBytes->getLastTxfm()->getDocument();
    XSECnew(txfm, TXFMHash(d, hashType));

    inputBytes->appendTxfm(txfm);

    return true;
}

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

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

    throw XSECException(XSECException::AlgorithmMapperError,
            "DSIGAlgorithmHandlerDefault - Encryption operations not supported");
}

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

    throw XSECException(XSECException::AlgorithmMapperError,
            "DSIGAlgorithmHandlerDefault - Encryption operations not supported");
}


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

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

    throw XSECException(XSECException::AlgorithmMapperError,
            "DSIGAlgorithmHandlerDefault - Encryption operations not supported");
}

// --------------------------------------------------------------------------------
//            Key Creation
// --------------------------------------------------------------------------------

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

    throw XSECException(XSECException::AlgorithmMapperError,
            "DSIGAlgorithmHandlerDefault - Key creation operations not supported");
}
