/**
 * 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
 *
 * WinCAPICryptoProvider := Provider to support Windows Crypto API
 *
 * Author(s): Berin Lautenbach
 *
 * $Id: WinCAPICryptoProvider.cpp 1817863 2017-12-11 22:47:43Z scantor $
 *
 */

#include <xsec/framework/XSECError.hpp>


#include <xsec/enc/WinCAPI/WinCAPICryptoProvider.hpp>
#include <xsec/enc/WinCAPI/WinCAPICryptoX509.hpp>
#include <xsec/enc/WinCAPI/WinCAPICryptoKeyDSA.hpp>
#include <xsec/enc/WinCAPI/WinCAPICryptoKeyHMAC.hpp>
#include <xsec/enc/WinCAPI/WinCAPICryptoKeyRSA.hpp>
#include <xsec/enc/WinCAPI/WinCAPICryptoHash.hpp>
#include <xsec/enc/WinCAPI/WinCAPICryptoHashHMAC.hpp>
#include <xsec/enc/WinCAPI/WinCAPICryptoSymmetricKey.hpp>
#include <xsec/enc/XSCrypt/XSCryptCryptoBase64.hpp>
#include <xsec/enc/XSECCryptoException.hpp>

#if defined (XSEC_HAVE_WINCAPI)

#include <xercesc/util/Janitor.hpp>

XSEC_USING_XERCES(ArrayJanitor);

static char s_xsecKeyStoreName[] = "ApacheXML-SecurityKeyStore";

WinCAPICryptoProvider::WinCAPICryptoProvider(
                        LPCSTR provDSSName,
                        LPCSTR provRSAName,
                        DWORD dwFlags) {

    if (!CryptAcquireContext(&m_provDSS,
        NULL,
        provDSSName,
        PROV_DSS,
        CRYPT_VERIFYCONTEXT))
    {
        throw XSECException(XSECException::InternalError,
            "WinCAPICryptoProvider() - Error obtaining default PROV_DSS");
    }

    if (!CryptAcquireContext(&m_provRSA,
        NULL,
        provRSAName,
        PROV_RSA_AES,
        CRYPT_VERIFYCONTEXT))
    {
        // Check if we maybe don't understand AES

        DWORD error = GetLastError();
        if (error == NTE_PROV_TYPE_NOT_DEF || error == 0) {

            // This system does not have AES!
            m_haveAES = false;
            m_provRSAType = PROV_RSA_FULL;

            if (!CryptAcquireContext(&m_provRSA,
                NULL,
                provRSAName,
                PROV_RSA_FULL,
                CRYPT_VERIFYCONTEXT))
            {

                throw XSECException(XSECException::InternalError,
                    "WinCAPICryptoProvider() - Error obtaining default PROV_RSA_FULL");
            }

        }

        else {

            throw XSECException(XSECException::InternalError,
                "WinCAPICryptoProvider() - Error obtaining default PROV_RSA_AES");
        }
    }

    else {
        m_haveAES = true;
        m_provRSAType = PROV_RSA_AES;
    }

    // Now obtain our internal (library) key store

    if (!CryptAcquireContext(&m_provApacheKeyStore,
        s_xsecKeyStoreName,
        provRSAName,
        m_provRSAType,
        dwFlags))
    {
        CryptAcquireContext(&m_provApacheKeyStore,
                                s_xsecKeyStoreName,
                                provRSAName,
                                m_provRSAType,
                                CRYPT_DELETEKEYSET);

        // Try to create
        if (!CryptAcquireContext(&m_provApacheKeyStore,
            s_xsecKeyStoreName,
            provRSAName,
            m_provRSAType,
            dwFlags | CRYPT_NEWKEYSET)) {

            // Prevents failure on mandatory profiles, see SANTUARIO-378.
            if (GetLastError() != NTE_TEMPORARY_PROFILE) {
                throw XSECException(XSECException::InternalError,
                    "WinCAPICryptoProvider() - Error obtaining generating internal key store for PROV_RSA_FULL");
            } else {
                m_provApacheKeyStore = NULL;
            }
        }
        else {
            HCRYPTKEY k;
            if (!CryptGenKey(m_provApacheKeyStore, AT_KEYEXCHANGE, CRYPT_EXPORTABLE, &k)) {
                throw XSECException(XSECException::InternalError,
                    "WinCAPICryptoProvider() - Error generating internal key set for PROV_RSA_FULL");
            }
            CryptDestroyKey(k);
        }
    }

    // Copy parameters for later use

    if (provDSSName != NULL)
        m_provDSSName = strdup(provDSSName);
    else
        m_provDSSName = NULL;

    if (provRSAName != NULL)
        m_provRSAName = strdup(provRSAName);
    else
        m_provRSAName = NULL;
}

WinCAPICryptoProvider::~WinCAPICryptoProvider() {
    CryptReleaseContext(m_provRSA, 0);
    CryptReleaseContext(m_provDSS, 0);
    if (m_provApacheKeyStore) {
        CryptReleaseContext(m_provApacheKeyStore, 0);
    }
}

const XMLCh* WinCAPICryptoProvider::getProviderName() const {

    return DSIGConstants::s_unicodeStrPROVWinCAPI;

}


// Hashing classes

unsigned int WinCAPICryptoProvider::getMaxHashSize() const {
    return WINCAPI_MAX_HASH_SIZE;
}


XSECCryptoHash* WinCAPICryptoProvider::hash(XSECCryptoHash::HashType type) const {

    WinCAPICryptoHash* ret = NULL;

    switch(type) {
    case XSECCryptoHash::HASH_SHA1:
    case XSECCryptoHash::HASH_MD5:
        XSECnew(ret, WinCAPICryptoHash(m_provDSS, type));
        break;
    }

    return ret;
}

XSECCryptoHash* WinCAPICryptoProvider::HMAC(XSECCryptoHash::HashType type) const {

    WinCAPICryptoHashHMAC* ret = NULL;

    switch(type) {
    case XSECCryptoHash::HASH_SHA1:
    case XSECCryptoHash::HASH_MD5:
        XSECnew(ret, WinCAPICryptoHashHMAC(m_provDSS, type));
        break;
    }

    return ret;
}

XSECCryptoKeyHMAC* WinCAPICryptoProvider::keyHMAC(void) const {
    WinCAPICryptoKeyHMAC * ret;

    XSECnew(ret, WinCAPICryptoKeyHMAC(m_provDSS));

    return ret;
}

XSECCryptoKeyDSA* WinCAPICryptoProvider::keyDSA() const {
    WinCAPICryptoKeyDSA * ret;

    XSECnew(ret, WinCAPICryptoKeyDSA(m_provDSS));

    return ret;
}

XSECCryptoKeyRSA* WinCAPICryptoProvider::keyRSA() const {
    WinCAPICryptoKeyRSA * ret;

    XSECnew(ret, WinCAPICryptoKeyRSA(m_provRSA));

    return ret;
}

XSECCryptoKeyEC* WinCAPICryptoProvider::keyEC() const {

    throw XSECCryptoException(XSECCryptoException::UnsupportedError,
        "WinCAPICryptoProvider::keyEC - EC support not available");
}

XSECCryptoKey* WinCAPICryptoProvider::keyDER(const char* buf, unsigned long len, bool base64) const {

    throw XSECCryptoException(XSECCryptoException::UnsupportedError,
        "WinCAPICryptoProvider::keyDER - DER decoding support not available");
}

XSECCryptoX509* WinCAPICryptoProvider::X509() const {
    WinCAPICryptoX509 * ret;

    XSECnew(ret, WinCAPICryptoX509(m_provRSA, m_provDSS));

    return ret;
}

XSECCryptoBase64* WinCAPICryptoProvider::base64() const {

    // The Windows CAPI does not provide a Base64 decoder/encoder.
    // Use the internal implementation.

    XSCryptCryptoBase64 * ret;

    XSECnew(ret, XSCryptCryptoBase64());

    return ret;
}

bool WinCAPICryptoProvider::algorithmSupported(XSECCryptoSymmetricKey::SymmetricKeyType alg) const {

    switch (alg) {

    case (XSECCryptoSymmetricKey::KEY_AES_128) :
    case (XSECCryptoSymmetricKey::KEY_AES_192) :
    case (XSECCryptoSymmetricKey::KEY_AES_256) :

        return m_haveAES;

    case (XSECCryptoSymmetricKey::KEY_3DES_192) :

        return true;

    default:

        return false;

    }

    return false;
}

bool WinCAPICryptoProvider::algorithmSupported(XSECCryptoHash::HashType alg) const {
    switch (alg) {

    case (XSECCryptoHash::HASH_SHA1) :
    case (XSECCryptoHash::HASH_MD5) :

        return true;

    case (XSECCryptoHash::HASH_SHA224) :
    case (XSECCryptoHash::HASH_SHA256) :
    case (XSECCryptoHash::HASH_SHA384) :
    case (XSECCryptoHash::HASH_SHA512) :

        return false;

    default:
        return false;
    }

    return false;
}

XSECCryptoSymmetricKey* WinCAPICryptoProvider::keySymmetric(XSECCryptoSymmetricKey::SymmetricKeyType alg) const {
    // Only temporary

    WinCAPICryptoSymmetricKey * ret;

    XSECnew(ret, WinCAPICryptoSymmetricKey(m_provApacheKeyStore, alg));

    return ret;
}

unsigned int WinCAPICryptoProvider::getRandom(unsigned char* buffer, unsigned int numOctets) const {

    if (!CryptGenRandom(m_provApacheKeyStore, numOctets, buffer)) {
        throw XSECException(XSECException::InternalError,
            "WinCAPICryptoProvider() - Error generating Random data");
    }

    return numOctets;
}



// --------------------------------------------------------------------------------
//     Translate a Base64 number to a Windows (little endian) integer
// --------------------------------------------------------------------------------

BYTE* WinCAPICryptoProvider::b642WinBN(const char* b64, unsigned int b64Len, unsigned int& retLen) {

    BYTE * os;
    XSECnew(os, BYTE[b64Len]);
    ArrayJanitor<BYTE> j_os(os);

    // Decode
    XSCryptCryptoBase64 b;

    b.decodeInit();
    retLen = b.decode((unsigned char *) b64, b64Len, os, b64Len);
    retLen += b.decodeFinish(&os[retLen], b64Len - retLen);

    BYTE * ret;
    XSECnew(ret, BYTE[retLen]);

    BYTE * j = os;
    BYTE * k = ret + retLen - 1;

    for (unsigned int i = 0; i < retLen ; ++i)
        *k-- = *j++;

    return ret;
}

// --------------------------------------------------------------------------------
//     Translate a Windows integer to a Base64 I2OSP number 
// --------------------------------------------------------------------------------

unsigned char * WinCAPICryptoProvider::WinBN2b64(BYTE * n, DWORD nLen, unsigned int &retLen) {


    // First reverse
    BYTE * rev;;
    XSECnew(rev, BYTE[nLen]);
    ArrayJanitor<BYTE> j_rev(rev);

    BYTE * j = n;
    BYTE * k = rev + nLen - 1;

    for (unsigned int i = 0; i < nLen ; ++i)
        *k-- = *j++;


    unsigned char * b64;
    // Naieve length calculation
    unsigned int bufLen = nLen * 2 + 4;

    XSECnew(b64, unsigned char[bufLen]);
    ArrayJanitor<unsigned char> j_b64(b64);

    XSCryptCryptoBase64 b;

    b.encodeInit();
    retLen = b.encode(rev, (unsigned int) nLen, b64, bufLen);
    retLen += b.encodeFinish(&b64[retLen], bufLen - retLen);

    j_b64.release();
    return b64;
}

#endif /* XSEC_HAVE_WINCAPI */

