/**
 * 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
 *
 * InteropResolver := Class to resolve key elements into certificates for
 *                      merlin-18 interop test
 *
 * $Id: InteropResolver.cpp 1833341 2018-06-11 16:25:41Z scantor $
 *
 */

// XSEC

#include "InteropResolver.hpp"

#include <xsec/framework/XSECDefs.hpp>
#include <xsec/enc/XSECKeyInfoResolver.hpp>
#include <xsec/dsig/DSIGKeyInfoX509.hpp>
#include <xsec/dsig/DSIGKeyInfoName.hpp>
#include <xsec/enc/OpenSSL/OpenSSLCryptoX509.hpp>

#include <xsec/enc/OpenSSL/OpenSSLCryptoBase64.hpp>
#include <xsec/enc/OpenSSL/OpenSSLSupport.hpp>

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

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

XERCES_CPP_NAMESPACE_USE

#include <iostream>

#if defined (XSEC_HAVE_OPENSSL)

InteropResolver::InteropResolver(const XMLCh * baseURI) {

    if (baseURI != NULL)
        mp_baseURI = XMLString::replicate(baseURI);
    else
        mp_baseURI = NULL;

    m_searchStarted = false;
    m_searchFinished = false;

#if !defined(_WIN32)
    m_fcount = 0;
#endif

}


InteropResolver::~InteropResolver() {

    if (mp_baseURI != NULL)
        XSEC_RELEASE_XMLCH(mp_baseURI);

}

#if defined(_WIN32)

void reverseSlash(safeBuffer &path) {

    for (int i = 0; i < (int) strlen(path.rawCharBuffer()); ++i) {

        if (path[i] == '/')
            path[i] = '\\';

    }

}

#endif


X509 * InteropResolver::nextFile2Cert(void) const {


    if (m_searchFinished)
        return NULL;

    int res;

    if (!m_searchStarted) {

        char * base = XMLString::transcode(mp_baseURI);
        safeBuffer path = base;
        XSEC_RELEASE_XMLCH(base);

        path.sbStrcatIn("/certs/*.crt");

#if defined(_WIN32)

        // Reverse the "backslash" characters

        reverseSlash(path);

        m_handle = (long) _findfirst(path.rawCharBuffer(), &m_finder);
        res = m_handle;
#else
        if (glob(path.rawCharBuffer(), 0, NULL, &m_globbuf) != 0)
            res = -1;
        else
            res = 0;
#endif

        m_searchStarted = true;

    }
    else {

#if defined(_WIN32)
        res = _findnext(m_handle, &m_finder);
#else
        if (m_fcount == (int) m_globbuf.gl_pathc)
            res = -1;
        else
            res = 0;
#endif

    }

    if (res == -1) {

        m_searchFinished = true;
        return NULL;

    }

    /* 
     * Create the OpenSSL BIOs necessary to read in the X509 cert
     */

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

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

    }

    // Create the filename
    safeBuffer fname;
#if defined(_WIN32)
    fname.sbTranscodeIn(mp_baseURI);
    fname.sbStrcatIn("/certs/");
    fname.sbStrcatIn(m_finder.name);
    reverseSlash(fname);
#else
    fname.sbStrcpyIn(m_globbuf.gl_pathv[m_fcount++]);
#endif

    if (BIO_read_filename(bioCert, fname.rawCharBuffer()) <= 0) {

        std::cerr << "Error opening certificate file\n" << fname.rawCharBuffer() << std::endl;
        return NULL;

    }

    X509 * x509 = d2i_X509_bio(bioCert, NULL);
    BIO_free(bioCert);

    return x509;


}

X509_NAME * X509_NAME_create_from_txt(const char * n) {

    // I'm sure there must be a function to do this in OpenSSL, but I'm
    // darned if I can find it.

    int idx = 0;
    int j;
    bool ok = true;

    X509_NAME_ENTRY * entries[10];
    int entCount = 0;

    char * name = new char[strlen(n)];
    char * value = new char[strlen(n)];
    while (true) {

        while (n[idx] == ' ' ||
               n[idx] == '\t' ||
               n[idx] == '\n' ||
               n[idx] == '\r')
               idx++;
        
        if (n[idx] == 0)
            break;

        j = 0;
        while (n[idx] != 0 && n[idx] != '=') {
            name[j++] = n[idx++];
        }

        if (j == 0 || n[idx] == 0) {
            ok = false;
            break;
        }

        name[j] = '\0';
        idx++;

        // Now the value
        j = 0;
        while (n[idx] != 0 && n[idx] != '\n' && n[idx] != '\r') {

            if (n[idx] == ',') {

                // find out if this is a marker for end of RDN
                int jdx = idx + 1;
                while (n[jdx] != '\0' && n[jdx] != '=' && n[jdx] != ',')
                    ++jdx;

                if (n[jdx] != ',')
                    break;

            }

            value[j++] = n[idx++];

        }

        if (j == 0) {
            ok = false;
            break;
        }

        if (n[idx] != 0)
            idx++;

        value[j] = '\0';
        X509_NAME_ENTRY * xne;

        xne = X509_NAME_ENTRY_create_by_txt(NULL, name, MBSTRING_ASC, (unsigned char *) value, -1);

        if (xne != NULL) {
            entries[entCount++] = xne;
            if (entCount == 10) {
                ok = false;
                break;
            }
        }
        else {
            ok = false;
            break;
        }

    }

    delete[] name;
    delete[] value;

    X509_NAME *ret = NULL;
    int i;

    if (ok) {

        // Create the return value
        ret = X509_NAME_new();
        for (i = entCount - 1; i >= 0; --i) {

            if (!X509_NAME_add_entry(ret, entries[i], -1, 0)) {
                X509_NAME_free(ret);
                ret = NULL;
                break;
            }
        }

    }
    
    // Clean out the entries
    for (i = 0; i < entCount; ++i) {
        X509_NAME_ENTRY_free(entries[i]);
    }

    return ret;
}

bool InteropResolver::checkMatch(const DSIGKeyInfoList * lst, X509 * x) const {

    // Check if the parameters in x match the required certificate


    int sz = (int) lst->getSize();
    const DSIGKeyInfo* k;
    
    for (int i = 0; i < sz; ++i) {

        k = lst->item(i);

        if (k->getKeyInfoType() == DSIGKeyInfo::KEYINFO_X509) {

            const DSIGKeyInfoX509 * kx = static_cast<const DSIGKeyInfoX509 *>(k);
            
            const XMLCh * serial = kx->getX509IssuerSerialNumber();

            if (serial != NULL) {

                char * cserial = XMLString::transcode(serial);
                char * xserial;

                BIGNUM * bnserial = ASN1_INTEGER_to_BN(X509_get_serialNumber(x), NULL);
                xserial = BN_bn2dec(bnserial);
                BN_free(bnserial);

                if (strcmp(xserial, cserial) == 0) {
                    
                    OPENSSL_free(xserial);
                    XSEC_RELEASE_XMLCH(cserial);
                    return true;

                }
                //delete[] xserial;
                XSEC_RELEASE_XMLCH(cserial);
                OPENSSL_free(xserial);

            }

            /* 
             * Either it's not a serial number, or we didn't pass, so lets
             * look at the next option.
             */
            
            const XMLCh * ski = kx->getX509SKI();
            
            if (ski != NULL) {

                char * cski = XMLString::transcode(ski);
                int clen = (int) strlen(cski);
                unsigned char * xski = new unsigned char[clen];
                ArrayJanitor <unsigned char> j_xski(xski);

                // Decode

                OpenSSLCryptoBase64 b64;
                b64.decodeInit();
                int xlen = b64.decode((unsigned char *) cski, clen, xski, clen);
                xlen += b64.decodeFinish(&xski[xlen], clen - xlen);
                XSEC_RELEASE_XMLCH(cski);

                if (xlen != 0) {

                    // Have a buffer with a number in it
                    const STACK_OF(X509_EXTENSION) *exts;
                    exts = X509_get0_extensions(x);

                    if (exts != NULL) {

                        // Find the Subject Key Identifier OID
                        X509_EXTENSION * ext;
                        ASN1_OBJECT * objski = OBJ_nid2obj(NID_subject_key_identifier);
                        int extn = X509v3_get_ext_by_OBJ(exts, objski, -1);
                        if (extn != -1) {
                            // Dummy up an OCTET_STRING from the xski
                            unsigned char * octxski = new unsigned char[xlen + 2];
                            ArrayJanitor<unsigned char> j_octxski(octxski);

                            octxski[0] = 4;
                            octxski[1] = xlen;
                            memcpy(&octxski[2], xski, xlen);
                            
                            ext = sk_X509_EXTENSION_value(exts,extn);
                            ASN1_OCTET_STRING *skid = X509_EXTENSION_get_data(ext);
                            ASN1_OCTET_STRING * xskid = ASN1_OCTET_STRING_new();

                            ASN1_STRING_set(xskid, octxski, xlen+2);
                            
                            if (ASN1_OCTET_STRING_cmp(xskid, skid) == 0) {
                                ASN1_OCTET_STRING_free(xskid);
                                return true;
                            }
                        }

                    }

                }

            }

            /* Not a subject key identifier */
            const XMLCh *sn = kx->getX509SubjectName();

            if (sn != NULL) {

                char * csn = XMLString::transcode(sn);

                X509_NAME * x509name = X509_get_subject_name(x);
                X509_NAME * snname = X509_NAME_create_from_txt(csn);
                XSEC_RELEASE_XMLCH(csn);

                if (snname != NULL) {

                    if (!X509_NAME_cmp(x509name, snname)) {
                        X509_NAME_free(snname);
                        return true;
                    }
                
                    X509_NAME_free(snname);
                }
            }
        }
    }

    return false;

}


XSECCryptoKey * InteropResolver::openCertURI(const XMLCh * uri) const {

    // Open a certificate from a file URI relative to the signature file
    BIO * bioCert;
    if ((bioCert = BIO_new(BIO_s_file())) == NULL) {
        
        return NULL;

    }

    safeBuffer fname;
    char * u = XMLString::transcode(uri);
    fname.sbTranscodeIn(mp_baseURI);
    fname.sbStrcatIn("/");
    fname.sbStrcatIn(u);
    XSEC_RELEASE_XMLCH(u);

#if defined(_WIN32)
    reverseSlash(fname);
#endif

    if (BIO_read_filename(bioCert, fname.rawCharBuffer()) <= 0) {
        
        return NULL;

    }

    X509 * x509 = d2i_X509_bio(bioCert, NULL);
    BIO_free(bioCert);

    OpenSSLCryptoX509 oX509(x509);
    X509_free(x509);

    return oX509.clonePublicKey();

}

XSECCryptoKey * InteropResolver::resolveKey(const DSIGKeyInfoList * lst) const {


    // First check if this has an X509 cert + an X509 CRL
    const XMLCh * b64cert = NULL;
    const XMLCh * b64crl = NULL;

    int lstSize = (int) lst->getSize();

    for (int i = 0; i < lstSize; ++i) {

        const DSIGKeyInfo * ki;
        ki = lst->item(i);
        const XMLCh * rawuri;

        if (ki->getKeyInfoType() == DSIGKeyInfo::KEYINFO_X509) {
            
            const DSIGKeyInfoX509 * kix509 = static_cast<const DSIGKeyInfoX509 *>(ki);

            if ((rawuri = kix509->getRawRetrievalURI()) != NULL) {

                // We have a raw certificate by de-reference
                // Assume it is just a file dereference and open the cert

                return openCertURI(rawuri);

            }
            
            if (kix509->getCertificateListSize() == 1) {

                b64cert = kix509->getCertificateItem(0);

            }

            if (b64crl == NULL) {

                b64crl = kix509->getX509CRL();

            }
        }

        else if (ki->getKeyInfoType() == DSIGKeyInfo::KEYINFO_NAME) {

            const DSIGKeyInfoName * kn = static_cast<const DSIGKeyInfoName *>(ki);

            if (kn->getKeyName() != NULL) {

                static XMLCh certStr[] = {
                    XERCES_CPP_NAMESPACE_QUALIFIER chLatin_c,
                    XERCES_CPP_NAMESPACE_QUALIFIER chLatin_e,
                    XERCES_CPP_NAMESPACE_QUALIFIER chLatin_r,
                    XERCES_CPP_NAMESPACE_QUALIFIER chLatin_t,
                    XERCES_CPP_NAMESPACE_QUALIFIER chLatin_s,
                    XERCES_CPP_NAMESPACE_QUALIFIER chForwardSlash,
                    XERCES_CPP_NAMESPACE_QUALIFIER chNull
                };
                static XMLCh extStr[] = {
                    XERCES_CPP_NAMESPACE_QUALIFIER chPeriod,
                    XERCES_CPP_NAMESPACE_QUALIFIER chLatin_c,
                    XERCES_CPP_NAMESPACE_QUALIFIER chLatin_r,
                    XERCES_CPP_NAMESPACE_QUALIFIER chLatin_t,
                    XERCES_CPP_NAMESPACE_QUALIFIER chNull
                };

                safeBuffer fname;
                fname = certStr;
                fname.sbXMLChCat(kn->getKeyName());
                fname.sbXMLChCat(extStr);

                fname.sbStrlwr();

                return openCertURI(fname.rawXMLChBuffer());

            }

        }

    }

    if (b64cert != NULL && b64crl != NULL) {

        // We have a certificate and a crl, lets get the cert and check in the crl

        OpenSSLCryptoBase64 b64;
        char * transb64cert = XMLString::transcode(b64cert);
        unsigned char * x509buf = new unsigned char[strlen(transb64cert)];
        ArrayJanitor<unsigned char> j_x509buf(x509buf);

        int x509bufLen;

        X509 *x;

        b64.decodeInit();
        x509bufLen = b64.decode((unsigned char *) transb64cert, (unsigned int) strlen(transb64cert), x509buf, (unsigned int) strlen(transb64cert));
        x509bufLen += b64.decodeFinish(&x509buf[x509bufLen], (unsigned int) strlen(transb64cert) - x509bufLen);
        XSEC_RELEASE_XMLCH(transb64cert);

        if (x509bufLen > 0) {
#if defined(XSEC_OPENSSL_D2IX509_CONST_BUFFER)
            x =  d2i_X509(NULL, (const unsigned char **) (&x509buf), x509bufLen
);
#else
            x =  d2i_X509(NULL, &x509buf, x509bufLen);
#endif
        }
        else 
            return NULL;        // Something has gone wrong

        if (x == NULL)
            return NULL;

        // Now the CRL
        char * transb64crl = XMLString::transcode(b64crl);
        unsigned char * crlbuf = new unsigned char[strlen(transb64crl)];
        ArrayJanitor<unsigned char> j_crlbuf(crlbuf);

        int crlbufLen;

        X509_CRL * c;

        b64.decodeInit();
        crlbufLen = b64.decode((unsigned char*) transb64crl, (unsigned int) strlen(transb64crl), crlbuf, (unsigned int) strlen(transb64crl));
        crlbufLen += b64.decodeFinish(&crlbuf[crlbufLen], (unsigned int) strlen(transb64crl) - crlbufLen);
        XSEC_RELEASE_XMLCH(transb64crl);

        if (crlbufLen > 0) {
#if defined(XSEC_OPENSSL_D2IX509_CONST_BUFFER)
            c =  d2i_X509_CRL(NULL, (const unsigned char **) (&crlbuf), crlbufLen);
#else
    c =  d2i_X509_CRL(NULL, &crlbuf, crlbufLen);
#endif
        }
        else 
            return NULL;        // Something has gone wrong

        if (c == NULL)
            return NULL;

        // Now check if the cert is in the CRL (code lifted from OpenSSL x509_vfy.c

        int idx;
        X509_REVOKED *rtmp = X509_REVOKED_new();

        /* Look for serial number of certificate in CRL */
        X509_REVOKED_set_serialNumber(rtmp, X509_get_serialNumber(x));
        idx = sk_X509_REVOKED_find(X509_CRL_get_REVOKED(c), rtmp);

        X509_REVOKED_free(rtmp);
        
        /* Not found: OK */
        
        if(idx != -1) {

            std::cerr << "Warning - certificate revoked in attached CRL" << std::endl;

        }

        OpenSSLCryptoX509 ox(x);
        X509_free(x);
        X509_CRL_free(c);
        return ox.clonePublicKey();

    }

    // Do a run through each match in the directory

    while (m_searchFinished == false) {

        X509 * x = nextFile2Cert();

        if (x != NULL) {

            if (checkMatch(lst, x)) {

                OpenSSLCryptoX509 ox(x);
                X509_free(x);
                return ox.clonePublicKey();

            }

        }
        X509_free(x);

    }

    return NULL;

}



XSECKeyInfoResolver * InteropResolver::clone(void) const {

    return new InteropResolver(mp_baseURI);

}



#endif /* XSEC_HAVE_OPENSSL */
