gcc const fix, converted linefeeds
[shibboleth/cpp-xmltooling.git] / xmltooling / signature / impl / FilesystemCredentialResolver.cpp
index 2389c14..f838142 100644 (file)
-/*\r
- *  Copyright 2001-2005 Internet2\r
- * \r
- * Licensed under the Apache License, Version 2.0 (the "License");\r
- * you may not use this file except in compliance with the License.\r
- * You may obtain a copy of the License at\r
- *\r
- *     http://www.apache.org/licenses/LICENSE-2.0\r
- *\r
- * Unless required by applicable law or agreed to in writing, software\r
- * distributed under the License is distributed on an "AS IS" BASIS,\r
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
- * See the License for the specific language governing permissions and\r
- * limitations under the License.\r
- */\r
-\r
-/**\r
- * FilesystemCredentialResolver.cpp\r
- * \r
- * Supplies credentials from local files\r
- */\r
-\r
-#include "internal.h"\r
-#include "signature/KeyResolver.h"\r
-#include "signature/OpenSSLCredentialResolver.h"\r
-#include "util/NDC.h"\r
-#include "util/XMLHelper.h"\r
-\r
-#include <sys/types.h>\r
-#include <sys/stat.h>\r
-#include <algorithm>\r
-#include <openssl/pkcs12.h>\r
-#include <log4cpp/Category.hh>\r
-#include <xercesc/util/XMLUniDefs.hpp>\r
-#include <xsec/enc/OpenSSL/OpenSSLCryptoX509.hpp>\r
-#include <xsec/enc/OpenSSL/OpenSSLCryptoKeyRSA.hpp>\r
-#include <xsec/enc/OpenSSL/OpenSSLCryptoKeyDSA.hpp>\r
-\r
-using namespace xmlsignature;\r
-using namespace xmltooling;\r
-using namespace log4cpp;\r
-using namespace std;\r
-\r
-// OpenSSL password callback...\r
-static int passwd_callback(char* buf, int len, int verify, void* passwd)\r
-{\r
-    if(!verify)\r
-    {\r
-        if(passwd && len > strlen(reinterpret_cast<char*>(passwd)))\r
-        {\r
-            strcpy(buf,reinterpret_cast<char*>(passwd));\r
-            return strlen(buf);\r
-        }\r
-    }  \r
-    return 0;\r
-}\r
-\r
-namespace xmlsignature {\r
-    class FilesystemCredentialResolver : public OpenSSLCredentialResolver, public KeyResolver\r
-    {\r
-    public:\r
-        FilesystemCredentialResolver(const DOMElement* e);\r
-        virtual ~FilesystemCredentialResolver();\r
-\r
-        Lockable* lock() { return this; }\r
-        void unlock() {}\r
-        \r
-        XSECCryptoKey* loadKey();\r
-        \r
-        XSECCryptoKey* getKey() const { return m_key ? m_key->clone() : NULL; }\r
-        const vector<XSECCryptoX509*>& getCertificates() const { return m_xseccerts; }\r
-        void attach(SSL_CTX* ctx) const;\r
-        \r
-        XSECCryptoKey* resolveKey(const KeyInfo* keyInfo) const { return m_key ? m_key->clone() : NULL; }\r
-        XSECCryptoKey* resolveKey(DSIGKeyInfoList* keyInfo) const { return m_key ? m_key->clone() : NULL; }\r
-        vector<XSECCryptoX509*>::size_type resolveCertificates(const KeyInfo* keyInfo, ResolvedCertificates& certs) const {\r
-            accessCertificates(certs).assign(m_xseccerts.begin(), m_xseccerts.end());\r
-            accessOwned(certs) = false;\r
-            return accessCertificates(certs).size();\r
-        }\r
-        vector<XSECCryptoX509*>::size_type resolveCertificates(DSIGKeyInfoList* keyInfo, ResolvedCertificates& certs) const {\r
-            accessCertificates(certs).assign(m_xseccerts.begin(), m_xseccerts.end());\r
-            accessOwned(certs) = false;\r
-            return accessCertificates(certs).size();\r
-        }\r
-        \r
-    private:\r
-        enum format_t { PEM=SSL_FILETYPE_PEM, DER=SSL_FILETYPE_ASN1, _PKCS12, UNKNOWN };\r
-    \r
-        format_t getEncodingFormat(BIO* in) const;\r
-        string formatToString(format_t format) const;\r
-        format_t xmlFormatToFormat(const XMLCh* format_xml) const;\r
-    \r
-        format_t m_keyformat;\r
-        string m_keypath,m_keypass;\r
-        vector<X509*> m_certs;\r
-        vector<XSECCryptoX509*> m_xseccerts;\r
-        XSECCryptoKey* m_key;\r
-    };\r
-\r
-    CredentialResolver* XMLTOOL_DLLLOCAL FilesystemCredentialResolverFactory(const DOMElement* const & e)\r
-    {\r
-        return new FilesystemCredentialResolver(e);\r
-    }\r
-\r
-    KeyResolver* XMLTOOL_DLLLOCAL FilesystemKeyResolverFactory(const DOMElement* const & e)\r
-    {\r
-        return new FilesystemCredentialResolver(e);\r
-    }\r
-};\r
-\r
-static const XMLCh CAPath[] =           UNICODE_LITERAL_6(C,A,P,a,t,h);\r
-static const XMLCh Certificate[] =      UNICODE_LITERAL_11(C,e,r,t,i,f,i,c,a,t,e);\r
-static const XMLCh format[] =           UNICODE_LITERAL_6(f,o,r,m,a,t);\r
-static const XMLCh Key[] =              UNICODE_LITERAL_3(K,e,y);\r
-static const XMLCh password[] =         UNICODE_LITERAL_8(p,a,s,s,w,o,r,d);\r
-static const XMLCh Path[] =             UNICODE_LITERAL_4(P,a,t,h);\r
-\r
-FilesystemCredentialResolver::FilesystemCredentialResolver(const DOMElement* e) : m_key(NULL)\r
-{\r
-#ifdef _DEBUG\r
-    NDC ndc("FilesystemCredentialResolver");\r
-#endif\r
-    Category& log=Category::getInstance(XMLTOOLING_LOGCAT".CredentialResolver");\r
-\r
-    format_t fformat;\r
-    const XMLCh* format_xml=NULL;\r
-    BIO* in = NULL;\r
-    \r
-    // Move to Key\r
-    const DOMElement* root=e;\r
-    e=XMLHelper::getFirstChildElement(root,Key);\r
-    if (e) {\r
-\r
-        // Get raw format attrib value, but defer processing til later since may need to \r
-        // determine format dynamically, and we need the Path for that.\r
-        format_xml=e->getAttributeNS(NULL,format);\r
-            \r
-        const XMLCh* password_xml=e->getAttributeNS(NULL,password);\r
-        if (password_xml) {\r
-            auto_ptr_char kp(password_xml);\r
-            m_keypass=kp.get();\r
-        }\r
-        \r
-        e=XMLHelper::getFirstChildElement(e,Path);\r
-        if (e && e->hasChildNodes()) {\r
-            const XMLCh* s=e->getFirstChild()->getNodeValue();\r
-            auto_ptr_char kpath(s);\r
-#ifdef WIN32\r
-            struct _stat stat_buf;\r
-            if (_stat(kpath.get(), &stat_buf) != 0)\r
-#else\r
-            struct stat stat_buf;\r
-            if (stat(kpath.get(), &stat_buf) != 0)\r
-#endif\r
-            {\r
-                log.error("key file (%s) can't be opened", kpath.get());\r
-                throw XMLSecurityException("FilesystemCredentialResolver can't access key file ($1)",params(1,kpath.get()));\r
-            }\r
-            m_keypath=kpath.get();\r
-        }\r
-        else {\r
-            log.error("Path element missing inside Key element");\r
-            throw XMLSecurityException("FilesystemCredentialResolver can't access key file, no Path element specified.");\r
-        }\r
-\r
-        // Determine the key encoding format dynamically, if not explicitly specified\r
-        if (format_xml && *format_xml) {\r
-            fformat = xmlFormatToFormat(format_xml);\r
-            if (fformat != UNKNOWN) {\r
-                m_keyformat = fformat;\r
-            }\r
-            else {\r
-                auto_ptr_char unknown(format_xml);\r
-                log.error("configuration specifies unknown key encoding format (%s)", unknown.get());\r
-                throw XMLSecurityException("FilesystemCredentialResolver configuration contains unknown key encoding format ($1)",params(1,unknown.get()));\r
-            }\r
-        }\r
-        else {\r
-            in=BIO_new(BIO_s_file_internal());\r
-            if (in && BIO_read_filename(in,m_keypath.c_str())>0) {\r
-                m_keyformat = getEncodingFormat(in);\r
-                log.debug("key encoding format for (%s) dynamically resolved as (%s)", m_keypath.c_str(), formatToString(m_keyformat).c_str());\r
-            }\r
-            else {\r
-                log.error("key file (%s) can't be read to determine encoding format", m_keypath.c_str());\r
-                throw XMLSecurityException("FilesystemCredentialResolver can't read key file ($1) to determine encoding format",params(1,m_keypath.c_str()));\r
-            }\r
-            if (in)\r
-                BIO_free(in);\r
-            in = NULL;    \r
-        }\r
-        \r
-        // Load the key.\r
-        m_key = loadKey();\r
-    }\r
-        \r
-    // Check for Certificate\r
-    e=XMLHelper::getFirstChildElement(root,Certificate);\r
-    if (!e)\r
-        return;\r
-    auto_ptr_char certpass(e->getAttributeNS(NULL,password));\r
-    \r
-    DOMElement* ep=XMLHelper::getFirstChildElement(e,Path);\r
-    if (!ep || !ep->hasChildNodes()) {\r
-        log.error("Path element missing inside Certificate element or is empty");\r
-        throw XMLSecurityException("FilesystemCredentialResolver can't access certificate file, missing or empty Path element.");\r
-    }\r
-    \r
-    auto_ptr_char certpath(ep->getFirstChild()->getNodeValue());\r
-    format_xml=e->getAttributeNS(NULL,format);\r
-    if (format_xml && *format_xml) {\r
-        fformat = xmlFormatToFormat(format_xml);\r
-        if (fformat == UNKNOWN) {\r
-            auto_ptr_char unknown(format_xml);\r
-            log.error("configuration specifies unknown certificate encoding format (%s)", unknown.get());\r
-            throw XMLSecurityException("FilesystemCredentialResolver configuration contains unknown certificate encoding format ($1)",params(1,unknown.get()));\r
-        }\r
-    }\r
-    \r
-    try {\r
-        X509* x=NULL;\r
-        PKCS12* p12=NULL;\r
-        in=BIO_new(BIO_s_file_internal());\r
-        if (in && BIO_read_filename(in,certpath.get())>0) {\r
-            if (!format_xml || !*format_xml) {\r
-                // Determine the cert encoding format dynamically, if not explicitly specified\r
-                fformat = getEncodingFormat(in);\r
-                log.debug("certificate encoding format for (%s) dynamically resolved as (%s)", certpath.get(), formatToString(fformat).c_str());\r
-            }\r
-\r
-            switch(fformat) {\r
-                case PEM:\r
-                    while (x=PEM_read_bio_X509(in,NULL,passwd_callback,const_cast<char*>(certpass.get())))\r
-                        m_certs.push_back(x);\r
-                    break;\r
-                                \r
-                case DER:\r
-                    x=d2i_X509_bio(in,NULL);\r
-                    if (x)\r
-                        m_certs.push_back(x);\r
-                    else {\r
-                        log_openssl();\r
-                        BIO_free(in);\r
-                        throw XMLSecurityException("FilesystemCredentialResolver unable to load DER certificate from file ($1)",params(1,certpath.get()));\r
-                    }\r
-                    break;\r
-\r
-                case _PKCS12:\r
-                    p12=d2i_PKCS12_bio(in,NULL);\r
-                    if (p12) {\r
-                        PKCS12_parse(p12, certpass.get(), NULL, &x, NULL);\r
-                        PKCS12_free(p12);\r
-                    }\r
-                    if (x) {\r
-                        m_certs.push_back(x);\r
-                        x=NULL;\r
-                    } else {\r
-                        log_openssl();\r
-                        BIO_free(in);\r
-                        throw XMLSecurityException("FilesystemCredentialResolver unable to load PKCS12 certificate from file ($1)",params(1,certpath.get()));\r
-                    }\r
-                    break;\r
-            } // end switch\r
-\r
-        } else {\r
-            log_openssl();\r
-            if (in) {\r
-                BIO_free(in);\r
-                in=NULL;\r
-            }\r
-            throw XMLSecurityException("FilesystemCredentialResolver unable to load certificate(s) from file ($1)",params(1,certpath.get()));\r
-        }\r
-        if (in) {\r
-            BIO_free(in);\r
-            in=NULL;\r
-        }\r
-\r
-        if (m_certs.empty()) {\r
-            throw XMLSecurityException("FilesystemCredentialResolver unable to load any certificate(s)");\r
-        }\r
-\r
-        // Load any extra CA files.\r
-        DOMElement* extra=XMLHelper::getFirstChildElement(e,CAPath);\r
-        while (extra) {\r
-            if (!extra->hasChildNodes()) {\r
-                log.warn("skipping empty CAPath element");\r
-                extra = XMLHelper::getNextSiblingElement(extra,CAPath);\r
-                continue;\r
-            }\r
-            auto_ptr_char capath(extra->getFirstChild()->getNodeValue());\r
-            x=NULL;\r
-            p12=NULL;\r
-            in=BIO_new(BIO_s_file_internal());\r
-            if (in && BIO_read_filename(in,capath.get())>0) {\r
-                if (!format_xml || !*format_xml) {\r
-                    // Determine the cert encoding format dynamically, if not explicitly specified\r
-                    fformat = getEncodingFormat(in);\r
-                    log.debug("CA certificate encoding format for (%s) dynamically resolved as (%s)", certpath.get(), formatToString(fformat).c_str());\r
-                }\r
-\r
-                switch (fformat) {\r
-                    case PEM:\r
-                        while (x=PEM_read_bio_X509(in,NULL,passwd_callback,const_cast<char*>(certpass.get())))\r
-                            m_certs.push_back(x);\r
-                        break;\r
-\r
-                    case DER:\r
-                        x=d2i_X509_bio(in,NULL);\r
-                        if (x)\r
-                            m_certs.push_back(x);\r
-                        else {\r
-                            log_openssl();\r
-                            BIO_free(in);\r
-                            throw XMLSecurityException("FilesystemCredentialResolver unable to load DER CA certificate from file ($1)",params(1,capath.get()));\r
-                        }\r
-                        break;\r
-\r
-                    case _PKCS12:\r
-                        p12 = d2i_PKCS12_bio(in, NULL);\r
-                        if (p12) {\r
-                            PKCS12_parse(p12, certpass.get(), NULL, &x, NULL);\r
-                            PKCS12_free(p12);\r
-                        }\r
-                        if (x) {\r
-                            m_certs.push_back(x);\r
-                            x=NULL;\r
-                        }\r
-                        else {\r
-                            log_openssl();\r
-                            BIO_free(in);\r
-                            throw XMLSecurityException("FilesystemCredentialResolver unable to load PKCS12 CA certificate from file ($1)",params(1,capath.get()));\r
-                        }\r
-                        break;\r
-                } //end switch\r
-\r
-                BIO_free(in);\r
-            }\r
-            else {\r
-                if (in)\r
-                    BIO_free(in);\r
-                log_openssl();\r
-                log.error("CA file (%s) can't be opened", capath.get());\r
-                throw XMLSecurityException("FilesystemCredentialResolver can't open CA file ($1)",params(1,capath.get()));\r
-            }\r
-            \r
-            extra = XMLHelper::getNextSiblingElement(extra,CAPath);\r
-        }\r
-    }\r
-    catch (XMLToolingException&) {\r
-        for (vector<X509*>::iterator j=m_certs.begin(); j!=m_certs.end(); j++)\r
-            X509_free(*j);\r
-        throw;\r
-    }\r
-\r
-    // Reflect certs over to XSEC form.\r
-    for (vector<X509*>::iterator j=m_certs.begin(); j!=m_certs.end(); j++)\r
-        m_xseccerts.push_back(new OpenSSLCryptoX509(*j));\r
-}\r
-\r
-XSECCryptoKey* FilesystemCredentialResolver::loadKey()\r
-{\r
-#ifdef _DEBUG\r
-    NDC ndc("loadKey");\r
-#endif\r
-\r
-    // Get a EVP_PKEY.\r
-    EVP_PKEY* pkey=NULL;\r
-    BIO* in=BIO_new(BIO_s_file_internal());\r
-    if (in && BIO_read_filename(in,m_keypath.c_str())>0) {\r
-        switch (m_keyformat) {\r
-            case PEM:\r
-                pkey=PEM_read_bio_PrivateKey(in, NULL, passwd_callback, const_cast<char*>(m_keypass.c_str()));\r
-                break;\r
-            \r
-            case DER:\r
-                pkey=d2i_PrivateKey_bio(in, NULL);\r
-                break;\r
-                \r
-            default: {\r
-                PKCS12* p12 = d2i_PKCS12_bio(in, NULL);\r
-                if (p12) {\r
-                    PKCS12_parse(p12, const_cast<char*>(m_keypass.c_str()), &pkey, NULL, NULL);\r
-                    PKCS12_free(p12);\r
-                }\r
-            }\r
-        }\r
-    }\r
-    if (in)\r
-        BIO_free(in);\r
-    \r
-    // Now map it to an XSEC wrapper.\r
-    if (pkey) {\r
-        XSECCryptoKey* ret=NULL;\r
-        switch (pkey->type) {\r
-            case EVP_PKEY_RSA:\r
-                ret=new OpenSSLCryptoKeyRSA(pkey);\r
-                break;\r
-                \r
-            case EVP_PKEY_DSA:\r
-                ret=new OpenSSLCryptoKeyDSA(pkey);\r
-                break;\r
-            \r
-            default:\r
-                Category::getInstance(XMLTOOLING_LOGCAT".CredentialResolver").error("unsupported private key type");\r
-        }\r
-        EVP_PKEY_free(pkey);\r
-        if (ret)\r
-            return ret;\r
-    }\r
-\r
-    log_openssl();\r
-    throw XMLSecurityException("FilesystemCredentialResolver unable to load private key from file."); \r
-}\r
-\r
-FilesystemCredentialResolver::~FilesystemCredentialResolver()\r
-{\r
-    delete m_key;\r
-    for_each(m_certs.begin(),m_certs.end(),X509_free);\r
-    for_each(m_xseccerts.begin(),m_xseccerts.end(),xmltooling::cleanup<XSECCryptoX509>());\r
-}\r
-\r
-void FilesystemCredentialResolver::attach(SSL_CTX* ctx) const\r
-{\r
-#ifdef _DEBUG\r
-    NDC ndc("attach");\r
-#endif\r
-    \r
-    // Attach key.\r
-    SSL_CTX_set_default_passwd_cb(ctx, passwd_callback);\r
-    SSL_CTX_set_default_passwd_cb_userdata(ctx, const_cast<char*>(m_keypass.c_str()));\r
-\r
-    int ret=0;\r
-    switch (m_keyformat) {\r
-        case PEM:\r
-            ret=SSL_CTX_use_PrivateKey_file(ctx, m_keypath.c_str(), m_keyformat);\r
-            break;\r
-            \r
-        case DER:\r
-            ret=SSL_CTX_use_RSAPrivateKey_file(ctx, m_keypath.c_str(), m_keyformat);\r
-            break;\r
-            \r
-        default: {\r
-            BIO* in=BIO_new(BIO_s_file_internal());\r
-            if (in && BIO_read_filename(in,m_keypath.c_str())>0) {\r
-                EVP_PKEY* pkey=NULL;\r
-                PKCS12* p12 = d2i_PKCS12_bio(in, NULL);\r
-                if (p12) {\r
-                    PKCS12_parse(p12, const_cast<char*>(m_keypass.c_str()), &pkey, NULL, NULL);\r
-                    PKCS12_free(p12);\r
-                    if (pkey) {\r
-                        ret=SSL_CTX_use_PrivateKey(ctx, pkey);\r
-                        EVP_PKEY_free(pkey);\r
-                    }\r
-                }\r
-            }\r
-            if (in)\r
-                BIO_free(in);\r
-        }\r
-    }\r
-    \r
-    if (ret!=1) {\r
-        log_openssl();\r
-        throw XMLSecurityException("Unable to attach private key to SSL context.");\r
-    }\r
-\r
-    // Attach certs.\r
-    for (vector<X509*>::const_iterator i=m_certs.begin(); i!=m_certs.end(); i++) {\r
-        if (i==m_certs.begin()) {\r
-            if (SSL_CTX_use_certificate(ctx, *i) != 1) {\r
-                log_openssl();\r
-                throw XMLSecurityException("Unable to attach client certificate to SSL context.");\r
-            }\r
-        }\r
-        else {\r
-            // When we add certs, they don't get ref counted, so we need to duplicate them.\r
-            X509* dup = X509_dup(*i);\r
-            if (SSL_CTX_add_extra_chain_cert(ctx, dup) != 1) {\r
-                X509_free(dup);\r
-                log_openssl();\r
-                throw XMLSecurityException("Unable to attach CA certificate to SSL context.");\r
-            }\r
-        }\r
-    }\r
-}\r
-\r
-// Used to determine the encoding format of credentials files\r
-// dynamically. Supports: PEM, DER, PKCS12.\r
-FilesystemCredentialResolver::format_t FilesystemCredentialResolver::getEncodingFormat(BIO* in) const\r
-{\r
-    PKCS12* p12 = NULL;\r
-    format_t format;\r
-\r
-    const int READSIZE = 1;\r
-    char buf[READSIZE];\r
-    char b1;\r
-    int mark;\r
-\r
-    try {\r
-        if ( (mark = BIO_tell(in)) < 0 ) \r
-            throw XMLSecurityException("getEncodingFormat: BIO_tell() can't get the file position");\r
-        if ( BIO_read(in, buf, READSIZE) <= 0 ) \r
-            throw XMLSecurityException("getEncodingFormat: BIO_read() can't read from the stream");\r
-        if ( BIO_seek(in, mark) < 0 ) \r
-            throw XMLSecurityException("getEncodingFormat: BIO_seek() can't reset the file position");\r
-    }\r
-    catch (...) {\r
-        log_openssl();\r
-        throw;\r
-    }\r
-\r
-    b1 = buf[0];\r
-\r
-    // This is a slight variation of the Java code by Chad La Joie.\r
-    //\r
-    // Check the first byte of the file.  If it's some kind of\r
-    // DER-encoded structure (including PKCS12), it will begin with ASCII 048.\r
-    // Otherwise, assume it's PEM.\r
-    if (b1 !=  48) {\r
-        format = PEM;\r
-    } else {\r
-        // Here we know it's DER-encoded, now try to parse it as a PKCS12\r
-        // ASN.1 structure.  If it fails, must be another kind of DER-encoded\r
-        // key/cert structure.  A little inefficient...but it works.\r
-        if ( (p12=d2i_PKCS12_bio(in,NULL)) == NULL ) {\r
-            format = DER;\r
-        } else {\r
-            format = _PKCS12;\r
-        }\r
-        if (p12)\r
-            PKCS12_free(p12);    \r
-        if ( BIO_seek(in, mark) < 0 ) {\r
-            log_openssl();\r
-            throw XMLSecurityException("getEncodingFormat: BIO_seek() can't reset the file position");\r
-        }\r
-    }\r
-\r
-    return format;\r
-}\r
-\r
-// Convert key/cert format_t types to a human-meaningful string for debug output\r
-string FilesystemCredentialResolver::formatToString(format_t format) const\r
-{\r
-    switch(format) {\r
-        case PEM:\r
-            return "PEM";\r
-        case DER:\r
-            return "DER";\r
-        case _PKCS12:\r
-            return "PKCS12";\r
-        default:\r
-            return "UNKNOWN";\r
-    }\r
-}\r
-\r
-// Convert key/cert raw XML format attribute (XMLCh[]) to format_t type\r
-FilesystemCredentialResolver::format_t FilesystemCredentialResolver::xmlFormatToFormat(const XMLCh* format_xml) const\r
-{\r
-    static const XMLCh cPEM[] = UNICODE_LITERAL_3(P,E,M);\r
-    static const XMLCh cDER[] = UNICODE_LITERAL_3(D,E,R);\r
-    static const XMLCh cPKCS12[] = { chLatin_P, chLatin_K, chLatin_C, chLatin_S, chDigit_1, chDigit_2, chNull };\r
-    format_t format;\r
-\r
-    if (!XMLString::compareString(format_xml,cPEM))\r
-        format=PEM;\r
-    else if (!XMLString::compareString(format_xml,cDER))\r
-        format=DER;\r
-    else if (!XMLString::compareString(format_xml,cPKCS12))\r
-        format=_PKCS12;\r
-    else\r
-        format=UNKNOWN;\r
-\r
-    return format;\r
-}\r
+/*
+ *  Copyright 2001-2005 Internet2
+ * 
+ * Licensed 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.
+ */
+
+/**
+ * FilesystemCredentialResolver.cpp
+ * 
+ * Supplies credentials from local files
+ */
+
+#include "internal.h"
+#include "signature/KeyResolver.h"
+#include "signature/OpenSSLCredentialResolver.h"
+#include "util/NDC.h"
+#include "util/XMLHelper.h"
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <algorithm>
+#include <openssl/pkcs12.h>
+#include <log4cpp/Category.hh>
+#include <xercesc/util/XMLUniDefs.hpp>
+#include <xsec/enc/OpenSSL/OpenSSLCryptoX509.hpp>
+#include <xsec/enc/OpenSSL/OpenSSLCryptoKeyRSA.hpp>
+#include <xsec/enc/OpenSSL/OpenSSLCryptoKeyDSA.hpp>
+
+using namespace xmlsignature;
+using namespace xmltooling;
+using namespace log4cpp;
+using namespace std;
+
+// OpenSSL password callback...
+static int passwd_callback(char* buf, int len, int verify, void* passwd)
+{
+    if(!verify)
+    {
+        if(passwd && len > strlen(reinterpret_cast<char*>(passwd)))
+        {
+            strcpy(buf,reinterpret_cast<char*>(passwd));
+            return strlen(buf);
+        }
+    }  
+    return 0;
+}
+
+namespace xmlsignature {
+    class FilesystemCredentialResolver : public OpenSSLCredentialResolver, public KeyResolver
+    {
+    public:
+        FilesystemCredentialResolver(const DOMElement* e);
+        virtual ~FilesystemCredentialResolver();
+
+        Lockable* lock() { return this; }
+        void unlock() {}
+        
+        XSECCryptoKey* loadKey();
+        
+        XSECCryptoKey* getKey() const { return m_key ? m_key->clone() : NULL; }
+        const vector<XSECCryptoX509*>& getCertificates() const { return m_xseccerts; }
+        void attach(SSL_CTX* ctx) const;
+        
+        XSECCryptoKey* resolveKey(const KeyInfo* keyInfo) const { return m_key ? m_key->clone() : NULL; }
+        XSECCryptoKey* resolveKey(DSIGKeyInfoList* keyInfo) const { return m_key ? m_key->clone() : NULL; }
+        vector<XSECCryptoX509*>::size_type resolveCertificates(const KeyInfo* keyInfo, ResolvedCertificates& certs) const {
+            accessCertificates(certs).assign(m_xseccerts.begin(), m_xseccerts.end());
+            accessOwned(certs) = false;
+            return accessCertificates(certs).size();
+        }
+        vector<XSECCryptoX509*>::size_type resolveCertificates(DSIGKeyInfoList* keyInfo, ResolvedCertificates& certs) const {
+            accessCertificates(certs).assign(m_xseccerts.begin(), m_xseccerts.end());
+            accessOwned(certs) = false;
+            return accessCertificates(certs).size();
+        }
+        
+    private:
+        enum format_t { PEM=SSL_FILETYPE_PEM, DER=SSL_FILETYPE_ASN1, _PKCS12, UNKNOWN };
+    
+        format_t getEncodingFormat(BIO* in) const;
+        string formatToString(format_t format) const;
+        format_t xmlFormatToFormat(const XMLCh* format_xml) const;
+    
+        format_t m_keyformat;
+        string m_keypath,m_keypass;
+        vector<X509*> m_certs;
+        vector<XSECCryptoX509*> m_xseccerts;
+        XSECCryptoKey* m_key;
+    };
+
+    CredentialResolver* XMLTOOL_DLLLOCAL FilesystemCredentialResolverFactory(const DOMElement* const & e)
+    {
+        return new FilesystemCredentialResolver(e);
+    }
+
+    KeyResolver* XMLTOOL_DLLLOCAL FilesystemKeyResolverFactory(const DOMElement* const & e)
+    {
+        return new FilesystemCredentialResolver(e);
+    }
+};
+
+static const XMLCh CAPath[] =           UNICODE_LITERAL_6(C,A,P,a,t,h);
+static const XMLCh Certificate[] =      UNICODE_LITERAL_11(C,e,r,t,i,f,i,c,a,t,e);
+static const XMLCh format[] =           UNICODE_LITERAL_6(f,o,r,m,a,t);
+static const XMLCh Key[] =              UNICODE_LITERAL_3(K,e,y);
+static const XMLCh password[] =         UNICODE_LITERAL_8(p,a,s,s,w,o,r,d);
+static const XMLCh Path[] =             UNICODE_LITERAL_4(P,a,t,h);
+
+FilesystemCredentialResolver::FilesystemCredentialResolver(const DOMElement* e) : m_key(NULL)
+{
+#ifdef _DEBUG
+    NDC ndc("FilesystemCredentialResolver");
+#endif
+    Category& log=Category::getInstance(XMLTOOLING_LOGCAT".CredentialResolver");
+
+    format_t fformat;
+    const XMLCh* format_xml=NULL;
+    BIO* in = NULL;
+    
+    // Move to Key
+    const DOMElement* root=e;
+    e=XMLHelper::getFirstChildElement(root,Key);
+    if (e) {
+
+        // Get raw format attrib value, but defer processing til later since may need to 
+        // determine format dynamically, and we need the Path for that.
+        format_xml=e->getAttributeNS(NULL,format);
+            
+        const XMLCh* password_xml=e->getAttributeNS(NULL,password);
+        if (password_xml) {
+            auto_ptr_char kp(password_xml);
+            m_keypass=kp.get();
+        }
+        
+        e=XMLHelper::getFirstChildElement(e,Path);
+        if (e && e->hasChildNodes()) {
+            const XMLCh* s=e->getFirstChild()->getNodeValue();
+            auto_ptr_char kpath(s);
+#ifdef WIN32
+            struct _stat stat_buf;
+            if (_stat(kpath.get(), &stat_buf) != 0)
+#else
+            struct stat stat_buf;
+            if (stat(kpath.get(), &stat_buf) != 0)
+#endif
+            {
+                log.error("key file (%s) can't be opened", kpath.get());
+                throw XMLSecurityException("FilesystemCredentialResolver can't access key file ($1)",params(1,kpath.get()));
+            }
+            m_keypath=kpath.get();
+        }
+        else {
+            log.error("Path element missing inside Key element");
+            throw XMLSecurityException("FilesystemCredentialResolver can't access key file, no Path element specified.");
+        }
+
+        // Determine the key encoding format dynamically, if not explicitly specified
+        if (format_xml && *format_xml) {
+            fformat = xmlFormatToFormat(format_xml);
+            if (fformat != UNKNOWN) {
+                m_keyformat = fformat;
+            }
+            else {
+                auto_ptr_char unknown(format_xml);
+                log.error("configuration specifies unknown key encoding format (%s)", unknown.get());
+                throw XMLSecurityException("FilesystemCredentialResolver configuration contains unknown key encoding format ($1)",params(1,unknown.get()));
+            }
+        }
+        else {
+            in=BIO_new(BIO_s_file_internal());
+            if (in && BIO_read_filename(in,m_keypath.c_str())>0) {
+                m_keyformat = getEncodingFormat(in);
+                log.debug("key encoding format for (%s) dynamically resolved as (%s)", m_keypath.c_str(), formatToString(m_keyformat).c_str());
+            }
+            else {
+                log.error("key file (%s) can't be read to determine encoding format", m_keypath.c_str());
+                throw XMLSecurityException("FilesystemCredentialResolver can't read key file ($1) to determine encoding format",params(1,m_keypath.c_str()));
+            }
+            if (in)
+                BIO_free(in);
+            in = NULL;    
+        }
+        
+        // Load the key.
+        m_key = loadKey();
+    }
+        
+    // Check for Certificate
+    e=XMLHelper::getFirstChildElement(root,Certificate);
+    if (!e)
+        return;
+    auto_ptr_char certpass(e->getAttributeNS(NULL,password));
+    
+    DOMElement* ep=XMLHelper::getFirstChildElement(e,Path);
+    if (!ep || !ep->hasChildNodes()) {
+        log.error("Path element missing inside Certificate element or is empty");
+        throw XMLSecurityException("FilesystemCredentialResolver can't access certificate file, missing or empty Path element.");
+    }
+    
+    auto_ptr_char certpath(ep->getFirstChild()->getNodeValue());
+    format_xml=e->getAttributeNS(NULL,format);
+    if (format_xml && *format_xml) {
+        fformat = xmlFormatToFormat(format_xml);
+        if (fformat == UNKNOWN) {
+            auto_ptr_char unknown(format_xml);
+            log.error("configuration specifies unknown certificate encoding format (%s)", unknown.get());
+            throw XMLSecurityException("FilesystemCredentialResolver configuration contains unknown certificate encoding format ($1)",params(1,unknown.get()));
+        }
+    }
+    
+    try {
+        X509* x=NULL;
+        PKCS12* p12=NULL;
+        in=BIO_new(BIO_s_file_internal());
+        if (in && BIO_read_filename(in,certpath.get())>0) {
+            if (!format_xml || !*format_xml) {
+                // Determine the cert encoding format dynamically, if not explicitly specified
+                fformat = getEncodingFormat(in);
+                log.debug("certificate encoding format for (%s) dynamically resolved as (%s)", certpath.get(), formatToString(fformat).c_str());
+            }
+
+            switch(fformat) {
+                case PEM:
+                    while (x=PEM_read_bio_X509(in,NULL,passwd_callback,const_cast<char*>(certpass.get())))
+                        m_certs.push_back(x);
+                    break;
+                                
+                case DER:
+                    x=d2i_X509_bio(in,NULL);
+                    if (x)
+                        m_certs.push_back(x);
+                    else {
+                        log_openssl();
+                        BIO_free(in);
+                        throw XMLSecurityException("FilesystemCredentialResolver unable to load DER certificate from file ($1)",params(1,certpath.get()));
+                    }
+                    break;
+
+                case _PKCS12:
+                    p12=d2i_PKCS12_bio(in,NULL);
+                    if (p12) {
+                        PKCS12_parse(p12, certpass.get(), NULL, &x, NULL);
+                        PKCS12_free(p12);
+                    }
+                    if (x) {
+                        m_certs.push_back(x);
+                        x=NULL;
+                    } else {
+                        log_openssl();
+                        BIO_free(in);
+                        throw XMLSecurityException("FilesystemCredentialResolver unable to load PKCS12 certificate from file ($1)",params(1,certpath.get()));
+                    }
+                    break;
+            } // end switch
+
+        } else {
+            log_openssl();
+            if (in) {
+                BIO_free(in);
+                in=NULL;
+            }
+            throw XMLSecurityException("FilesystemCredentialResolver unable to load certificate(s) from file ($1)",params(1,certpath.get()));
+        }
+        if (in) {
+            BIO_free(in);
+            in=NULL;
+        }
+
+        if (m_certs.empty()) {
+            throw XMLSecurityException("FilesystemCredentialResolver unable to load any certificate(s)");
+        }
+
+        // Load any extra CA files.
+        DOMElement* extra=XMLHelper::getFirstChildElement(e,CAPath);
+        while (extra) {
+            if (!extra->hasChildNodes()) {
+                log.warn("skipping empty CAPath element");
+                extra = XMLHelper::getNextSiblingElement(extra,CAPath);
+                continue;
+            }
+            auto_ptr_char capath(extra->getFirstChild()->getNodeValue());
+            x=NULL;
+            p12=NULL;
+            in=BIO_new(BIO_s_file_internal());
+            if (in && BIO_read_filename(in,capath.get())>0) {
+                if (!format_xml || !*format_xml) {
+                    // Determine the cert encoding format dynamically, if not explicitly specified
+                    fformat = getEncodingFormat(in);
+                    log.debug("CA certificate encoding format for (%s) dynamically resolved as (%s)", certpath.get(), formatToString(fformat).c_str());
+                }
+
+                switch (fformat) {
+                    case PEM:
+                        while (x=PEM_read_bio_X509(in,NULL,passwd_callback,const_cast<char*>(certpass.get())))
+                            m_certs.push_back(x);
+                        break;
+
+                    case DER:
+                        x=d2i_X509_bio(in,NULL);
+                        if (x)
+                            m_certs.push_back(x);
+                        else {
+                            log_openssl();
+                            BIO_free(in);
+                            throw XMLSecurityException("FilesystemCredentialResolver unable to load DER CA certificate from file ($1)",params(1,capath.get()));
+                        }
+                        break;
+
+                    case _PKCS12:
+                        p12 = d2i_PKCS12_bio(in, NULL);
+                        if (p12) {
+                            PKCS12_parse(p12, certpass.get(), NULL, &x, NULL);
+                            PKCS12_free(p12);
+                        }
+                        if (x) {
+                            m_certs.push_back(x);
+                            x=NULL;
+                        }
+                        else {
+                            log_openssl();
+                            BIO_free(in);
+                            throw XMLSecurityException("FilesystemCredentialResolver unable to load PKCS12 CA certificate from file ($1)",params(1,capath.get()));
+                        }
+                        break;
+                } //end switch
+
+                BIO_free(in);
+            }
+            else {
+                if (in)
+                    BIO_free(in);
+                log_openssl();
+                log.error("CA file (%s) can't be opened", capath.get());
+                throw XMLSecurityException("FilesystemCredentialResolver can't open CA file ($1)",params(1,capath.get()));
+            }
+            
+            extra = XMLHelper::getNextSiblingElement(extra,CAPath);
+        }
+    }
+    catch (XMLToolingException&) {
+        for (vector<X509*>::iterator j=m_certs.begin(); j!=m_certs.end(); j++)
+            X509_free(*j);
+        throw;
+    }
+
+    // Reflect certs over to XSEC form.
+    for (vector<X509*>::iterator j=m_certs.begin(); j!=m_certs.end(); j++)
+        m_xseccerts.push_back(new OpenSSLCryptoX509(*j));
+}
+
+XSECCryptoKey* FilesystemCredentialResolver::loadKey()
+{
+#ifdef _DEBUG
+    NDC ndc("loadKey");
+#endif
+
+    // Get a EVP_PKEY.
+    EVP_PKEY* pkey=NULL;
+    BIO* in=BIO_new(BIO_s_file_internal());
+    if (in && BIO_read_filename(in,m_keypath.c_str())>0) {
+        switch (m_keyformat) {
+            case PEM:
+                pkey=PEM_read_bio_PrivateKey(in, NULL, passwd_callback, const_cast<char*>(m_keypass.c_str()));
+                break;
+            
+            case DER:
+                pkey=d2i_PrivateKey_bio(in, NULL);
+                break;
+                
+            default: {
+                PKCS12* p12 = d2i_PKCS12_bio(in, NULL);
+                if (p12) {
+                    PKCS12_parse(p12, const_cast<char*>(m_keypass.c_str()), &pkey, NULL, NULL);
+                    PKCS12_free(p12);
+                }
+            }
+        }
+    }
+    if (in)
+        BIO_free(in);
+    
+    // Now map it to an XSEC wrapper.
+    if (pkey) {
+        XSECCryptoKey* ret=NULL;
+        switch (pkey->type) {
+            case EVP_PKEY_RSA:
+                ret=new OpenSSLCryptoKeyRSA(pkey);
+                break;
+                
+            case EVP_PKEY_DSA:
+                ret=new OpenSSLCryptoKeyDSA(pkey);
+                break;
+            
+            default:
+                Category::getInstance(XMLTOOLING_LOGCAT".CredentialResolver").error("unsupported private key type");
+        }
+        EVP_PKEY_free(pkey);
+        if (ret)
+            return ret;
+    }
+
+    log_openssl();
+    throw XMLSecurityException("FilesystemCredentialResolver unable to load private key from file."); 
+}
+
+FilesystemCredentialResolver::~FilesystemCredentialResolver()
+{
+    delete m_key;
+    for_each(m_certs.begin(),m_certs.end(),X509_free);
+    for_each(m_xseccerts.begin(),m_xseccerts.end(),xmltooling::cleanup<XSECCryptoX509>());
+}
+
+void FilesystemCredentialResolver::attach(SSL_CTX* ctx) const
+{
+#ifdef _DEBUG
+    NDC ndc("attach");
+#endif
+    
+    // Attach key.
+    SSL_CTX_set_default_passwd_cb(ctx, passwd_callback);
+    SSL_CTX_set_default_passwd_cb_userdata(ctx, const_cast<char*>(m_keypass.c_str()));
+
+    int ret=0;
+    switch (m_keyformat) {
+        case PEM:
+            ret=SSL_CTX_use_PrivateKey_file(ctx, m_keypath.c_str(), m_keyformat);
+            break;
+            
+        case DER:
+            ret=SSL_CTX_use_RSAPrivateKey_file(ctx, m_keypath.c_str(), m_keyformat);
+            break;
+            
+        default: {
+            BIO* in=BIO_new(BIO_s_file_internal());
+            if (in && BIO_read_filename(in,m_keypath.c_str())>0) {
+                EVP_PKEY* pkey=NULL;
+                PKCS12* p12 = d2i_PKCS12_bio(in, NULL);
+                if (p12) {
+                    PKCS12_parse(p12, const_cast<char*>(m_keypass.c_str()), &pkey, NULL, NULL);
+                    PKCS12_free(p12);
+                    if (pkey) {
+                        ret=SSL_CTX_use_PrivateKey(ctx, pkey);
+                        EVP_PKEY_free(pkey);
+                    }
+                }
+            }
+            if (in)
+                BIO_free(in);
+        }
+    }
+    
+    if (ret!=1) {
+        log_openssl();
+        throw XMLSecurityException("Unable to attach private key to SSL context.");
+    }
+
+    // Attach certs.
+    for (vector<X509*>::const_iterator i=m_certs.begin(); i!=m_certs.end(); i++) {
+        if (i==m_certs.begin()) {
+            if (SSL_CTX_use_certificate(ctx, *i) != 1) {
+                log_openssl();
+                throw XMLSecurityException("Unable to attach client certificate to SSL context.");
+            }
+        }
+        else {
+            // When we add certs, they don't get ref counted, so we need to duplicate them.
+            X509* dup = X509_dup(*i);
+            if (SSL_CTX_add_extra_chain_cert(ctx, dup) != 1) {
+                X509_free(dup);
+                log_openssl();
+                throw XMLSecurityException("Unable to attach CA certificate to SSL context.");
+            }
+        }
+    }
+}
+
+// Used to determine the encoding format of credentials files
+// dynamically. Supports: PEM, DER, PKCS12.
+FilesystemCredentialResolver::format_t FilesystemCredentialResolver::getEncodingFormat(BIO* in) const
+{
+    PKCS12* p12 = NULL;
+    format_t format;
+
+    const int READSIZE = 1;
+    char buf[READSIZE];
+    char b1;
+    int mark;
+
+    try {
+        if ( (mark = BIO_tell(in)) < 0 ) 
+            throw XMLSecurityException("getEncodingFormat: BIO_tell() can't get the file position");
+        if ( BIO_read(in, buf, READSIZE) <= 0 ) 
+            throw XMLSecurityException("getEncodingFormat: BIO_read() can't read from the stream");
+        if ( BIO_seek(in, mark) < 0 ) 
+            throw XMLSecurityException("getEncodingFormat: BIO_seek() can't reset the file position");
+    }
+    catch (...) {
+        log_openssl();
+        throw;
+    }
+
+    b1 = buf[0];
+
+    // This is a slight variation of the Java code by Chad La Joie.
+    //
+    // Check the first byte of the file.  If it's some kind of
+    // DER-encoded structure (including PKCS12), it will begin with ASCII 048.
+    // Otherwise, assume it's PEM.
+    if (b1 !=  48) {
+        format = PEM;
+    } else {
+        // Here we know it's DER-encoded, now try to parse it as a PKCS12
+        // ASN.1 structure.  If it fails, must be another kind of DER-encoded
+        // key/cert structure.  A little inefficient...but it works.
+        if ( (p12=d2i_PKCS12_bio(in,NULL)) == NULL ) {
+            format = DER;
+        } else {
+            format = _PKCS12;
+        }
+        if (p12)
+            PKCS12_free(p12);    
+        if ( BIO_seek(in, mark) < 0 ) {
+            log_openssl();
+            throw XMLSecurityException("getEncodingFormat: BIO_seek() can't reset the file position");
+        }
+    }
+
+    return format;
+}
+
+// Convert key/cert format_t types to a human-meaningful string for debug output
+string FilesystemCredentialResolver::formatToString(format_t format) const
+{
+    switch(format) {
+        case PEM:
+            return "PEM";
+        case DER:
+            return "DER";
+        case _PKCS12:
+            return "PKCS12";
+        default:
+            return "UNKNOWN";
+    }
+}
+
+// Convert key/cert raw XML format attribute (XMLCh[]) to format_t type
+FilesystemCredentialResolver::format_t FilesystemCredentialResolver::xmlFormatToFormat(const XMLCh* format_xml) const
+{
+    static const XMLCh cPEM[] = UNICODE_LITERAL_3(P,E,M);
+    static const XMLCh cDER[] = UNICODE_LITERAL_3(D,E,R);
+    static const XMLCh cPKCS12[] = { chLatin_P, chLatin_K, chLatin_C, chLatin_S, chDigit_1, chDigit_2, chNull };
+    format_t format;
+
+    if (!XMLString::compareString(format_xml,cPEM))
+        format=PEM;
+    else if (!XMLString::compareString(format_xml,cDER))
+        format=DER;
+    else if (!XMLString::compareString(format_xml,cPKCS12))
+        format=_PKCS12;
+    else
+        format=UNKNOWN;
+
+    return format;
+}