/**
 * 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
 *
 * XSECCryptoUtils:= Helper crypo utilities that make life easier
 *
 * Author(s): Berin Lautenbach
 *
 * $Id: XSECCryptoUtils.cpp 1833341 2018-06-11 16:25:41Z scantor $
 *
 */

#include <xsec/framework/XSECDefs.hpp>
#include <xsec/framework/XSECError.hpp>
#include <xsec/enc/XSECCryptoUtils.hpp>
#include <xsec/enc/XSECCryptoKeyHMAC.hpp>
#include <xsec/utils/XSECPlatformUtils.hpp>

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

#include <xercesc/util/Janitor.hpp>
#include <xercesc/util/XMLString.hpp>
#include <xercesc/util/XMLUniDefs.hpp>

XERCES_CPP_NAMESPACE_USE

#ifdef XSEC_XKMS_ENABLED

// --------------------------------------------------------------------------------
//           XKMS Limited-Use Shared Secret handling
// --------------------------------------------------------------------------------

int CleanXKMSPassPhrase(unsigned char * input, int inputLen, safeBuffer &output) {

    // Now obsolete - use SASLCleanXKMSPassPhrase instead
    int j = 0;
    unsigned char c;
    for (int i = 0; i < inputLen; ++i) {

        c = input[i];

        if (c >= 'A' && c <= 'Z') {
            output[j++] = c - 'A' + 'a';
        }
        else if (c != '\n' && c != '\r' && c != '\t' && c != ' ') {
            output[j++] = c;
        }

    }

    return j;

}

int SASLCleanXKMSPassPhrase(unsigned char * input, int inputLen, safeBuffer &output) {

    // For now - this does *not* implement the full SASLPrep algorithm.
    // THe NFKC form is not trivial to implement - so this is kept very simple.
    // TODO - Fix this - it can be an interoperability issue as pass phrases from
    // different implementations could be treated differently.
    // Currently we only check for prohibited unput for chars < 0xFFFF and drop any
    // chars over 0xFFFF

    unsigned char * inp = new unsigned char[inputLen + 1];
    ArrayJanitor<unsigned char> j_inp(inp);
    memcpy(inp, input, inputLen);
    inp[inputLen] = '\0';

    XSECAutoPtrXMLCh uinput((char *) inp);
    XMLSize_t l = XMLString::stringLen(uinput.get());
    XMLCh* uoutput = new XMLCh[l + 1];
    ArrayJanitor<XMLCh> j_uoutput(uoutput);

    XMLSize_t i, j;
    j = 0;

    XMLCh ch1;

    for (i = 0; i < l; ++i) {

        ch1 = uinput.get()[i];
        // Case one - char is < 0x10000
        if (ch1 < 0xD800 || ch1 > 0xDFFF) {

            // OK - ch1 is "real" value - let's see if it is legal
            // The following switch tables are derived from
            // RFC 3454 - see http://www.ietf.org/rfc/rfc3454.txt

            // Non-ASCII Spaces - C.1.2
            switch (ch1) {

            case 0x00A0:        // NO-BREAK SPACE
            case 0x1680:        // OGHAM SPACE MARK
            case 0x2000:        // EN QUAD
            case 0x2001:        // EM QUAD
            case 0x2002:        // EN SPACE
            case 0x2003:        // EM SPACE
            case 0x2004:        // THREE-PER-EM SPACE
            case 0x2005:        // FOUR-PER-EM SPACE
            case 0x2006:        // SIX-PER-EM SPACE
            case 0x2007:        // FIGURE SPACE
            case 0x2008:        // PUNCTUATION SPACE
            case 0x2009:        // THIN SPACE
            case 0x200A:        // HAIR SPACE
            case 0x200B:        // ZERO WIDTH SPACE
            case 0x202F:        // NARROW NO-BREAK SPACE
            case 0x205F:        // MEDIUM MATHEMATICAL SPACE
            case 0x3000:        // IDEOGRAPHIC SPACE

                throw XSECException(XSECException::XKMSError,
                    "SASLCleanXKMSPassPhrase - Non ASCII space character in XKMS pass phrase");
            default:

                break;

            }

            // ASCII Control characters
            // Note - us unsigned, so always >= 0)
            if ((ch1 <= 0x1F) || (ch1 == 0x7F)) {
                throw XSECException(XSECException::XKMSError,
                    "SASLCleanXKMSPassPhrase - ASCII control character in XKMS pass phrase");
            }

            // Non-ASCII Control Characters
            if ((ch1 >= 0x80 && ch1 <= 0x9F) ||
                (ch1 >= 0x206A && ch1 <= 0x206F) ||
                (ch1 >= 0xFFF9 && ch1 <= 0xFFFC)) {

                throw XSECException(XSECException::XKMSError,
                    "SASLCleanXKMSPassPhrase - NON ASCII control character in XKMS pass phrase");
            }

            switch (ch1) {


            case 0x06DD:        // ARABIC END OF AYAH
            case 0x070F:        // SYRIAC ABBREVIATION MARK
            case 0x180E:        // MONGOLIAN VOWEL SEPARATOR
            case 0x200C:        // ZERO WIDTH NON-JOINER
            case 0x200D:        // ZERO WIDTH JOINER
            case 0x2028:        // LINE SEPARATOR
            case 0x2029:        // PARAGRAPH SEPARATOR
            case 0x2060:        // WORD JOINER
            case 0x2061:        // FUNCTION APPLICATION
            case 0x2062:        // INVISIBLE TIMES
            case 0x2063:        // INVISIBLE SEPARATOR
            case 0xFEFF:        // ZERO WIDTH NO-BREAK SPACE
                throw XSECException(XSECException::XKMSError,
                    "SASLCleanXKMSPassPhrase - NON ASCII control character in XKMS pass phrase");
            default:
                break;            }
            // 1D173-1D17A; [MUSICAL CONTROL CHARACTERS] is not relevant as we are looking at
            // ch1 at the moment

            // Private Use characters
            if ((ch1 >= 0xE000 && ch1 <= 0xF8FF)) {

                throw XSECException(XSECException::XKMSError,
                    "SASLCleanXKMSPassPhrase - Private Use character in XKMS pass phrase");
            }

            // Non-character code points
            if ((ch1 >= 0xFDD0 && ch1 <= 0xFDEF) ||
                (ch1 >= 0xFFFE)) {

                throw XSECException(XSECException::XKMSError,
                    "SASLCleanXKMSPassPhrase - Non-character code points in XKMS pass phrase");
            }

            // Inappropriate for plain text characters
   
            switch (ch1) {
            case 0xFFF9:        // INTERLINEAR ANNOTATION ANCHOR
            case 0xFFFA:        // INTERLINEAR ANNOTATION SEPARATOR
            case 0xFFFB:        // INTERLINEAR ANNOTATION TERMINATOR
            case 0xFFFC:        // OBJECT REPLACEMENT CHARACTER
            case 0xFFFD:        // REPLACEMENT CHARACTER
                throw XSECException(XSECException::XKMSError,
                    "SASLCleanXKMSPassPhrase - Innappropriate for plain text chararcters in XKMS pass phrase");
            default:
                break;
            }

            // Inappripriate for canonical representation characters
            if (ch1 >= 0x2FF0 && ch1 <= 0x2FFB) {
                throw XSECException(XSECException::XKMSError,
                    "SASLCleanXKMSPassPhrase - Innappropriate for canonicalisation chararcters in XKMS pass phrase");
            }

            // Change display properties or are deprecated
            switch (ch1) {
            case 0x0340:        // COMBINING GRAVE TONE MARK
            case 0x0341:        // COMBINING ACUTE TONE MARK
            case 0x200E:        // LEFT-TO-RIGHT MARK
            case 0x200F:        // RIGHT-TO-LEFT MARK
            case 0x202A:        // LEFT-TO-RIGHT EMBEDDING
            case 0x202B:        // RIGHT-TO-LEFT EMBEDDING
            case 0x202C:        // POP DIRECTIONAL FORMATTING
            case 0x202D:        // LEFT-TO-RIGHT OVERRIDE
            case 0x202E:        // RIGHT-TO-LEFT OVERRIDE
            case 0x206A:        // INHIBIT SYMMETRIC SWAPPING
            case 0x206B:        // ACTIVATE SYMMETRIC SWAPPING
            case 0x206C:        // INHIBIT ARABIC FORM SHAPING
            case 0x206D:        // ACTIVATE ARABIC FORM SHAPING
            case 0x206E:        // NATIONAL DIGIT SHAPES
            case 0x206F:        // NOMINAL DIGIT SHAPES
                throw XSECException(XSECException::XKMSError,
                    "SASLCleanXKMSPassPhrase - change display or deprecated chararcters in XKMS pass phrase");
            default:
                break;
            }

            // We got this far = just run with it for now
            uoutput[j++] = ch1;
        }
        else {
            throw XSECException(XSECException::XKMSError,
                "SASLCleanXKMSPassPhrase - don't support XKMS pass phrase chars > 0xFFFF");
        }
    } /* for */
    uoutput[j++] = chNull;

    // Now transcode
    char * utf8output= transcodeToUTF8(uoutput);
    output.sbStrcpyIn(utf8output);

    int ret = (int)strlen(utf8output);
    XSEC_RELEASE_XMLCH(utf8output);
    return ret;
}

int XSEC_EXPORT CalculateXKMSAuthenticationKey(unsigned char * input, int inputLen, unsigned char * output, int maxOutputLen) {

    unsigned char keyVal[] = {XKMSAuthenticationValue};

    XSECCryptoKeyHMAC * k = XSECPlatformUtils::g_cryptoProvider->keyHMAC();
    Janitor<XSECCryptoKeyHMAC> j_k(k);
    k->setKey(keyVal, 1);

    XSECCryptoHash *h = XSECPlatformUtils::g_cryptoProvider->HMAC(XSECCryptoHash::HASH_SHA1);
    Janitor<XSECCryptoHash> j_h(h);
    h->setKey(k);

    // Clean the input
    safeBuffer sb;
    int l = SASLCleanXKMSPassPhrase(input, inputLen, sb);

    h->hash((unsigned char *) sb.rawBuffer(), l);
    return h->finish(output, maxOutputLen);

}


int XSEC_EXPORT CalculateXKMSRevocationCodeIdentifierEncoding1(unsigned char * input, int inputLen, unsigned char * output, int maxOutputLen) {

    unsigned char keyVal[] = {XKMSRevocationCodeIdenfitierEncoding1};

    XSECCryptoKeyHMAC * k = XSECPlatformUtils::g_cryptoProvider->keyHMAC();
    Janitor<XSECCryptoKeyHMAC> j_k(k);
    k->setKey(keyVal, 1);

    XSECCryptoHash *h = XSECPlatformUtils::g_cryptoProvider->HMAC(XSECCryptoHash::HASH_SHA1);
    Janitor<XSECCryptoHash> j_h(h);

    h->setKey(k);

    // Clean the input
    safeBuffer sb;
    int l = CleanXKMSPassPhrase(input, inputLen, sb);

    h->hash((unsigned char *) sb.rawBuffer(), l);
    return h->finish(output, maxOutputLen);

}

int XSEC_EXPORT CalculateXKMSRevocationCodeIdentifierEncoding2(unsigned char * input, int inputLen, unsigned char * output, int maxOutputLen) {


    unsigned char tmpBuf[XSEC_MAX_HASH_SIZE];
    int tmpLen = CalculateXKMSRevocationCodeIdentifierEncoding1(input, inputLen, tmpBuf, XSEC_MAX_HASH_SIZE);
    return CalculateXKMSRevocationCodeIdentifierEncoding2From1(tmpBuf, tmpLen, output, maxOutputLen);

}

int XSEC_EXPORT CalculateXKMSRevocationCodeIdentifierEncoding2From1(unsigned char * input, int inputLen, unsigned char * output, int maxOutputLen) {

    unsigned char keyVal[] = {XKMSRevocationCodeIdenfitierEncoding2};

    XSECCryptoKeyHMAC * k = XSECPlatformUtils::g_cryptoProvider->keyHMAC();
    Janitor<XSECCryptoKeyHMAC> j_k(k);
    k->setKey(keyVal, 1);

    XSECCryptoHash *h = XSECPlatformUtils::g_cryptoProvider->HMAC(XSECCryptoHash::HASH_SHA1);
    Janitor<XSECCryptoHash> j_h(h);

    h->setKey(k);

    h->hash(input, inputLen);
    return h->finish(output, maxOutputLen);

}

int XSEC_EXPORT CalculateXKMSKEK(unsigned char * input, int inputLen, unsigned char * output, int maxOutputLen) {

    unsigned char keyVal[] = {XKMSKeyEncryption};
    unsigned char shaOutput[22];    // SHA1 has 20 bytes of output

    // Clean the input
    safeBuffer sb;
    int l = SASLCleanXKMSPassPhrase(input, inputLen, sb);

    // Need to iterate through until we have enough data
    int bytesDone = 0, bytesToDo;;
    shaOutput[0] = keyVal[0];
    int keyLen = 1;
    while (bytesDone < maxOutputLen) {
        XSECCryptoKeyHMAC * k = XSECPlatformUtils::g_cryptoProvider->keyHMAC();
        k->setKey(shaOutput, keyLen);

        XSECCryptoHash *h = XSECPlatformUtils::g_cryptoProvider->HMAC(XSECCryptoHash::HASH_SHA1);
        Janitor<XSECCryptoHash> j_h(h);

        h->setKey(k);
        delete(k);

        // Now hash next round of data
        h->hash((unsigned char *) sb.rawBuffer(), l);
        keyLen = h->finish(shaOutput, 22);

        // Copy into the output buffer
        bytesToDo = maxOutputLen - bytesDone;
        bytesToDo = bytesToDo > 20 ? 20 : bytesToDo;
        memcpy(&output[bytesDone], shaOutput, bytesToDo);
        bytesDone += bytesToDo;

        // Set up for next key
        shaOutput[0] ^= keyVal[0];
        keyLen = 20;

        j_h.release();
        delete h;
    }

    return bytesDone;

}

#endif

// --------------------------------------------------------------------------------
//           Some Base64 helpers
// --------------------------------------------------------------------------------

XMLCh XSEC_EXPORT * EncodeToBase64XMLCh(unsigned char * input, int inputLen) {

    XSECCryptoBase64 * b64 = XSECPlatformUtils::g_cryptoProvider->base64();
    Janitor<XSECCryptoBase64> j_b64(b64);
    unsigned char * output;
    int outputLen = ((4 * inputLen) / 3) + 5;
    XSECnew(output, unsigned char[outputLen]);
    ArrayJanitor<unsigned char> j_output(output);

    b64->encodeInit();
    int j = b64->encode(input, inputLen, output, outputLen - 1);
    j += b64->encodeFinish(&output[j], outputLen - j - 1);

    // Strip any trailing \n\r
    while (j > 0 && (output[j-1] == '\n' || output[j-1] == '\r'))
        j--;

    // Now transcode and get out of here
    output[j] = '\0';
    return XMLString::transcode((char *) output);

}

unsigned int XSEC_EXPORT DecodeFromBase64XMLCh(const XMLCh * input, unsigned char * output, int maxOutputLen) {

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

    XSECAutoPtrChar tinput(input);

    b64->decodeInit();
    unsigned int j = b64->decode((unsigned char *) tinput.get(), (unsigned int) strlen(tinput.get()), output, maxOutputLen - 1);
    j += b64->decodeFinish(&output[j], maxOutputLen - j - 1);

    return j;
}

unsigned int XSEC_EXPORT DecodeFromBase64(const char * input, unsigned char * output, int maxOutputLen) {

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

    b64->decodeInit();
    unsigned int j = b64->decode((unsigned char *) input, (unsigned int) strlen(input), output, maxOutputLen - 1);
    j += b64->decodeFinish(&output[j], maxOutputLen - j - 1);

    return j;
}


// --------------------------------------------------------------------------------
//           Some stuff to help with wierd signatures
// --------------------------------------------------------------------------------

const unsigned char ASNDSAProlog[] = {0x30, 0x2c, 0x02, 0x14};
const unsigned char ASNDSAMiddle[] = {0x02, 0x14};

bool ASN2DSASig(const unsigned char * input, unsigned char * r, unsigned char * s) {

    if (memcmp(ASNDSAProlog, input, 4) != 0 ||
        memcmp(ASNDSAMiddle, &input[24], 2) != 0)

        return false;

    memcpy(r, &input[4], 20);
    memcpy(s, &input[26], 20);

    return true;

}


// --------------------------------------------------------------------------------
//           Calculate correct OIDs for an RSA sig
// --------------------------------------------------------------------------------

/* As per RSA's PKCS #1 v 2.1, Section 9.2 Note 1, the DER encodings for
 * the has types are as follows:
 *
 * MD2: (0x)30 20 30 0c 06 08 2a 86 48 86 f7 0d 02 02 05 00 04 10 || H.
 * MD5: (0x)30 20 30 0c 06 08 2a 86 48 86 f7 0d 02 05 05 00 04 10 || H.
 * SHA-1: (0x)30 21 30 09 06 05 2b 0e 03 02 1a 05 00 04 14 || H.
 * SHA-256: (0x)30 31 30 0d 06 09 60 86 48 01 65 03 04 02 01 05 00 04 20 || H.
 * SHA-384: (0x)30 41 30 0d 06 09 60 86 48 01 65 03 04 02 02 05 00 04 30 || H.
 * SHA-512: (0x)30 51 30 0d 06 09 60 86 48 01 65 03 04 02 03 05 00 04 40 || H.
 *
 * More recently the following has been provided for SHA-224
 *
 * SHA-224: 30 2d 30 0d 06 09 60 86 48 01 65 03 04 02 04 05 00 04 1c
 *
 */

int MD5OIDLen = 18;
unsigned char MD5OID[] = {
    0x30, 0x20, 0x30, 0x0c, 0x06, 0x08, 0x2a, 0x86,
    0x48, 0x86, 0xf7, 0x0d, 0x02, 0x05, 0x05, 0x00,
    0x04, 0x10
};


int sha1OIDLen = 15;
unsigned char sha1OID[] = {
    0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2B, 0x0E,
    0x03, 0x02, 0x1A, 0x05, 0x00, 0x04, 0x14
};

int sha224OIDLen = 19;
unsigned char sha224OID[] = {
    0x30, 0x2d, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86,
    0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x04, 0x05,
    0x00, 0x04, 0x1c
};

int sha256OIDLen = 19;
unsigned char sha256OID[] = {
    0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86,
    0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05,
    0x00, 0x04, 0x20
};

int sha384OIDLen = 19;
unsigned char sha384OID[] = {
    0x30, 0x41, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86,
    0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x02, 0x05,
    0x00, 0x04, 0x30
};

int sha512OIDLen = 19;
unsigned char sha512OID[] = {
    0x30, 0x51, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86,
    0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03, 0x05,
    0x00, 0x04, 0x40
};


unsigned char* getRSASigOID(XSECCryptoHash::HashType type, int& oidLen) {

    switch (type) {

    case (XSECCryptoHash::HASH_MD5):
        oidLen = MD5OIDLen;
        return MD5OID;
    case (XSECCryptoHash::HASH_SHA1):
        oidLen = sha1OIDLen;
        return sha1OID;
    case (XSECCryptoHash::HASH_SHA224):
        oidLen = sha224OIDLen;
        return sha224OID;
    case (XSECCryptoHash::HASH_SHA256):
        oidLen = sha256OIDLen;
        return sha256OID;
    case (XSECCryptoHash::HASH_SHA384):
        oidLen = sha384OIDLen;
        return sha384OID;
    case (XSECCryptoHash::HASH_SHA512):
        oidLen = sha512OIDLen;
        return sha512OID;
    default:
        oidLen = 0;
        return NULL;

    }
}

XSECCryptoHash* XSECCryptoProvider::hash(const XMLCh* uri) const {

    return hash(XSECAlgorithmSupport::getHashType(uri));
}

XSECCryptoHash* XSECCryptoProvider::HMAC(const XMLCh* uri) const {

    return HMAC(XSECAlgorithmSupport::getHashType(uri));
}
