+/*\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/OpenSSLCredentialResolver.h"\r
+#include "util/NDC.h"\r
+#include "util/XMLHelper.h"\r
+\r
+using namespace xmlsignature;\r
+using namespace xmltooling;\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 CredentialResolver\r
+ {\r
+ public:\r
+ FilesystemCredentialResolver(const DOMElement* e);\r
+ ~FilesystemCredentialResolver();\r
+\r
+ Lockable* lock() { return this; }\r
+ void unlock() {}\r
+ \r
+ XSECCryptoKey* loadKey();\r
+ \r
+ void attach(SSL_CTX* ctx) const;\r
+ XSECCryptoKey* getKey() const { return m_key->clone(); }\r
+ const vector<XSECCryptoX509*>& getCertificates() const { return m_xseccerts; }\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
+\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)\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
+ 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