From: Scott Cantor Date: Mon, 24 Jul 2006 04:05:07 +0000 (+0000) Subject: Credential resolver plugin X-Git-Tag: 1.0-alpha1~206 X-Git-Url: http://www.project-moonshot.org/gitweb/?p=shibboleth%2Fcpp-xmltooling.git;a=commitdiff_plain;h=9edc32bbe3f1e2b16ba6694bec8fe7a079eb1d16 Credential resolver plugin --- diff --git a/configure.ac b/configure.ac index d6c5eb2..8fdc692 100644 --- a/configure.ac +++ b/configure.ac @@ -73,7 +73,7 @@ fi AC_ARG_WITH(openssl, AC_HELP_STRING([--with-openssl=PATH], [where openssl is installed]), [if test x_$with_openssl != x_/usr; then - SSLLIBS="-L${with_openssl}/lib -lcrypto" + SSLLIBS="-L${with_openssl}/lib -lcrypto -lssl" SSLFLAGS="-I${with_openssl}/include" fi]) @@ -90,7 +90,7 @@ if test "x$SSLLIBS" = "x" ; then fi if test "x$SSLLIBS" = "x" ; then - SSLLIBS="-lcrypto" + SSLLIBS="-lcrypto -lssl" fi AC_MSG_CHECKING(for OpenSSL libraries) diff --git a/xmltooling/Makefile.am b/xmltooling/Makefile.am index 71dd110..cdcd8b6 100644 --- a/xmltooling/Makefile.am +++ b/xmltooling/Makefile.am @@ -56,8 +56,10 @@ ioinclude_HEADERS = \ siginclude_HEADERS = \ signature/ContentReference.h \ + signature/CredentialResolver.h \ signature/KeyInfo.h \ signature/KeyResolver.h \ + signature/OpenSSLCredentialResolver.h \ signature/Signature.h \ signature/SignatureValidator.h @@ -81,6 +83,8 @@ if BUILD_XMLSEC xmlsec_sources = \ encryption/impl/Decrypter.cpp \ encryption/impl/Encrypter.cpp \ + signature/impl/CredentialResolver.cpp \ + signature/impl/FilesystemCredentialResolver.cpp \ signature/impl/SignatureValidator.cpp \ signature/impl/XMLSecSignatureImpl.cpp else diff --git a/xmltooling/PluginManager.h b/xmltooling/PluginManager.h index e1add0e..67323c8 100644 --- a/xmltooling/PluginManager.h +++ b/xmltooling/PluginManager.h @@ -69,9 +69,15 @@ namespace xmltooling { * @param type the name of the plugin type */ void deregisterFactory(const char* type) { - if (type) { + if (type) m_map.erase(type); - } + } + + /** + * Unregisters all registered factories. + */ + void deregisterFactories() { + m_map.clear(); } /** diff --git a/xmltooling/XMLToolingConfig.cpp b/xmltooling/XMLToolingConfig.cpp index b22b664..a5a4e3b 100644 --- a/xmltooling/XMLToolingConfig.cpp +++ b/xmltooling/XMLToolingConfig.cpp @@ -25,6 +25,7 @@ #include "XMLToolingConfig.h" #include "encryption/Encryption.h" #include "impl/UnknownElement.h" +#include "signature/CredentialResolver.h" #include "signature/KeyInfo.h" #include "signature/Signature.h" #include "util/NDC.h" @@ -35,16 +36,16 @@ # include #endif +#include #include #include #include #include #ifndef XMLTOOLING_NO_XMLSEC #include + #include #endif -#include - using namespace xmlencryption; using namespace xmlsignature; using namespace xmltooling; @@ -176,6 +177,7 @@ bool XMLToolingInternalConfig::init() #ifndef XMLTOOLING_NO_XMLSEC XMLObjectBuilder::registerBuilder(QName(XMLConstants::XMLSIG_NS,Signature::LOCAL_NAME),new SignatureBuilder()); REGISTER_EXCEPTION_FACTORY(SignatureException,xmlsignature); + registerCredentialResolvers(); #endif } catch (const xercesc::XMLException&) { @@ -194,6 +196,11 @@ void XMLToolingInternalConfig::term() EncryptionSchemaValidators.destroyValidators(); XMLToolingException::deregisterFactories(); +#ifndef XMLTOOLING_NO_XMLSEC + CredentialResolverManager.deregisterFactories(); + KeyResolverManager.deregisterFactories(); +#endif + for (vector::reverse_iterator i=m_libhandles.rbegin(); i!=m_libhandles.rend(); i++) { #if defined(WIN32) FARPROC fn=GetProcAddress(static_cast(*i),"xmltooling_extension_term"); @@ -318,3 +325,21 @@ bool XMLToolingInternalConfig::load_library(const char* path, void* context) log.info("loaded extension: %s", path); return true; } + +#ifndef XMLTOOLING_NO_XMLSEC +void xmltooling::log_openssl() +{ + const char* file; + const char* data; + int flags,line; + + unsigned long code=ERR_get_error_line_data(&file,&line,&data,&flags); + while (code) { + Category& log=Category::getInstance("OpenSSL"); + log.errorStream() << "error code: " << code << " in " << file << ", line " << line << CategoryStream::ENDLINE; + if (data && (flags & ERR_TXT_STRING)) + log.errorStream() << "error data: " << data << CategoryStream::ENDLINE; + code=ERR_get_error_line_data(&file,&line,&data,&flags); + } +} +#endif diff --git a/xmltooling/XMLToolingConfig.h b/xmltooling/XMLToolingConfig.h index 5241fe4..e2845b8 100644 --- a/xmltooling/XMLToolingConfig.h +++ b/xmltooling/XMLToolingConfig.h @@ -24,8 +24,21 @@ #define __xmltooling_config_h__ #include +#include #include +#ifndef XMLTOOLING_NO_XMLSEC +namespace xmlsignature { + class XMLTOOL_API CredentialResolver; + class XMLTOOL_API KeyResolver; +}; +#endif + +#if defined (_MSC_VER) + #pragma warning( push ) + #pragma warning( disable : 4251 ) +#endif + namespace xmltooling { /** @@ -110,10 +123,26 @@ namespace xmltooling { */ virtual ParserPool& getValidatingParser() const=0; +#ifndef XMLTOOLING_NO_XMLSEC + /** + * Manages factories for KeyResolver plugins. + */ + xmltooling::PluginManager KeyResolverManager; + + /** + * Manages factories for CredentialResolver plugins. + */ + xmltooling::PluginManager CredentialResolverManager; +#endif + protected: XMLToolingConfig() {} }; }; +#if defined (_MSC_VER) + #pragma warning( pop ) +#endif + #endif /* __xmltooling_config_h__ */ diff --git a/xmltooling/internal.h b/xmltooling/internal.h index 49b1a35..1ddd193 100644 --- a/xmltooling/internal.h +++ b/xmltooling/internal.h @@ -92,6 +92,11 @@ namespace xmltooling { ParserPool* m_parserPool; ParserPool* m_validatingPool; }; + +#ifndef XMLTOOLING_NO_XMLSEC + void log_openssl(); +#endif + /// @endcond }; diff --git a/xmltooling/signature/CredentialResolver.h b/xmltooling/signature/CredentialResolver.h new file mode 100644 index 0000000..f955f37 --- /dev/null +++ b/xmltooling/signature/CredentialResolver.h @@ -0,0 +1,72 @@ +/* + * Copyright 2001-2006 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. + */ + +/** + * @file xmltooling/signature/CredentialResolver.h + * + * Resolves keys and certificates "owned" by an entity + */ + +#if !defined(__xmltooling_credres_h__) && !defined(XMLTOOLING_NO_XMLSEC) +#define __xmltooling_credres_h__ + +#include + +#include +#include +#include + +namespace xmlsignature { + + /** + * An API for resolving local/owned keys and certificates + */ + class XMLTOOL_API CredentialResolver : public xmltooling::Lockable + { + MAKE_NONCOPYABLE(CredentialResolver); + protected: + CredentialResolver() {} + + public: + virtual ~CredentialResolver() {} + + /** + * Returns a secret or private key to use for signing operations. + * The caller is responsible for deleting the key when finished with it. + * + * @return a secret or private key + */ + virtual XSECCryptoKey* getKey() const=0; + + /** + * Returns a set of certificates to publish during signing operations. + * The certificates must be cloned if kept beyond the scope of a lock. + * + * @return a set of certificates + */ + virtual const std::vector& getCertificates() const=0; + }; + + /** + * Registers CredentialResolver classes into the runtime. + */ + void XMLTOOL_API registerCredentialResolvers(); + + /** CredentialResolver based on local files */ + #define FILESYSTEM_CREDENTIAL_RESOLVER "org.opensaml.xmlooling.FilesystemCredentialResolver" +}; + +#endif /* __xmltooling_credres_h__ */ diff --git a/xmltooling/signature/KeyResolver.h b/xmltooling/signature/KeyResolver.h index 7d060c6..2c88250 100644 --- a/xmltooling/signature/KeyResolver.h +++ b/xmltooling/signature/KeyResolver.h @@ -34,6 +34,7 @@ namespace xmlsignature { * An API for resolving keys. */ class XMLTOOL_API KeyResolver { + MAKE_NONCOPYABLE(KeyResolver); public: /** * Constructor based on a single externally supplied key. @@ -69,15 +70,6 @@ namespace xmlsignature { return m_key ? m_key->clone() : NULL; } - /** - * Creates a copy of the resolver. - * - * @return the cloned resolver - */ - virtual KeyResolver* clone() const { - return new KeyResolver(m_key ? m_key->clone() : NULL); - } - protected: XSECCryptoKey* m_key; }; diff --git a/xmltooling/signature/OpenSSLCredentialResolver.h b/xmltooling/signature/OpenSSLCredentialResolver.h new file mode 100644 index 0000000..e119380 --- /dev/null +++ b/xmltooling/signature/OpenSSLCredentialResolver.h @@ -0,0 +1,54 @@ +/* + * Copyright 2001-2006 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. + */ + +/** + * @file xmltooling/signature/OpenSSLCredentialResolver.h + * + * OpenSSL-specific credential resolver + */ + +#if !defined(__xmltooling_opensslcredres_h__) && !defined(XMLTOOLING_NO_XMLSEC) +#define __xmltooling_opensslcredres_h__ + +#include + +#include + +namespace xmlsignature { + + /** + * An OpenSSL-specific API for resolving local/owned keys and certificates + */ + class XMLTOOL_API OpenSSLCredentialResolver : public CredentialResolver + { + protected: + OpenSSLCredentialResolver() {} + + public: + virtual ~OpenSSLCredentialResolver() {} + + /** + * Attaches credentials to an OpenSSL SSL context object. + * The resolver is unlockable after attachment. + * + * @param ctx an SSL context + */ + virtual void attach(SSL_CTX* ctx) const=0; + }; + +}; + +#endif /* __xmltooling_opensslcredres_h__ */ diff --git a/xmltooling/signature/impl/CredentialResolver.cpp b/xmltooling/signature/impl/CredentialResolver.cpp new file mode 100644 index 0000000..9da33d3 --- /dev/null +++ b/xmltooling/signature/impl/CredentialResolver.cpp @@ -0,0 +1,38 @@ +/* + * Copyright 2001-2006 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. + */ + +/** + * CredentialResolver.cpp + * + * Registration of factories for built-in resolvers + */ + +#include "internal.h" +#include "signature/CredentialResolver.h" + +using namespace xmlsignature; +using namespace xmltooling; + +namespace xmlsignature { + XMLTOOL_DLLLOCAL PluginManager::Factory FilesystemCredentialResolverFactory; +}; + +void XMLTOOL_API xmlsignature::registerCredentialResolvers() +{ + XMLToolingConfig& conf=XMLToolingConfig::getConfig(); + conf.CredentialResolverManager.registerFactory(FILESYSTEM_CREDENTIAL_RESOLVER, FilesystemCredentialResolverFactory); + conf.CredentialResolverManager.registerFactory("edu.internet2.middleware.shibboleth.common.Credentials.FileCredentialResolver", FilesystemCredentialResolverFactory); +} diff --git a/xmltooling/signature/impl/FilesystemCredentialResolver.cpp b/xmltooling/signature/impl/FilesystemCredentialResolver.cpp new file mode 100644 index 0000000..86e70b1 --- /dev/null +++ b/xmltooling/signature/impl/FilesystemCredentialResolver.cpp @@ -0,0 +1,557 @@ +/* + * 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/OpenSSLCredentialResolver.h" +#include "util/NDC.h" +#include "util/XMLHelper.h" + +using namespace xmlsignature; +using namespace xmltooling; + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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(passwd))) + { + strcpy(buf,reinterpret_cast(passwd)); + return strlen(buf); + } + } + return 0; +} + +namespace xmlsignature { + class FilesystemCredentialResolver : public CredentialResolver + { + public: + FilesystemCredentialResolver(const DOMElement* e); + ~FilesystemCredentialResolver(); + + Lockable* lock() { return this; } + void unlock() {} + + XSECCryptoKey* loadKey(); + + void attach(SSL_CTX* ctx) const; + XSECCryptoKey* getKey() const { return m_key->clone(); } + const vector& getCertificates() const { return m_xseccerts; } + + 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 m_certs; + vector m_xseccerts; + XSECCryptoKey* m_key; + }; + + CredentialResolver* XMLTOOL_DLLLOCAL FilesystemCredentialResolverFactory(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) +{ +#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(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(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::iterator j=m_certs.begin(); j!=m_certs.end(); j++) + X509_free(*j); + throw; + } + + // Reflect certs over to XSEC form. + for (vector::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(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(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() +{ + for_each(m_certs.begin(),m_certs.end(),X509_free); + for_each(m_xseccerts.begin(),m_xseccerts.end(),xmltooling::cleanup()); +} + +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(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(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::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; +} diff --git a/xmltooling/util/XMLHelper.cpp b/xmltooling/util/XMLHelper.cpp index 7d61fdc..1bf8d5d 100644 --- a/xmltooling/util/XMLHelper.cpp +++ b/xmltooling/util/XMLHelper.cpp @@ -133,75 +133,83 @@ const XMLCh* XMLHelper::getTextContent(const DOMElement* e) return NULL; } -DOMElement* XMLHelper::getFirstChildElement(const DOMNode* n) +DOMElement* XMLHelper::getFirstChildElement(const DOMNode* n, const XMLCh* localName) { DOMNode* child = n->getFirstChild(); while (child && child->getNodeType() != DOMNode::ELEMENT_NODE) child = child->getNextSibling(); - if (child) - return static_cast(child); - return NULL; + if (child && localName) { + if (!XMLString::equals(localName,child->getLocalName())) + return getNextSiblingElement(child, localName); + } + return static_cast(child); } -DOMElement* XMLHelper::getLastChildElement(const DOMNode* n) +DOMElement* XMLHelper::getLastChildElement(const DOMNode* n, const XMLCh* localName) { DOMNode* child = n->getLastChild(); while (child && child->getNodeType() != DOMNode::ELEMENT_NODE) child = child->getPreviousSibling(); - if (child) - return static_cast(child); - return NULL; + if (child && localName) { + if (!XMLString::equals(localName,child->getLocalName())) + return getPreviousSiblingElement(child, localName); + } + return static_cast(child); } DOMElement* XMLHelper::getFirstChildElement(const DOMNode* n, const XMLCh* ns, const XMLCh* localName) { - DOMElement* e = getFirstChildElement(n); - while (e && !isNodeNamed(e, ns, localName)) - e = getNextSiblingElement(e); + DOMElement* e = getFirstChildElement(n, localName); + while (e && !XMLString::equals(e->getNamespaceURI(),ns)) + e = getNextSiblingElement(e, localName); return e; } DOMElement* XMLHelper::getLastChildElement(const DOMNode* n, const XMLCh* ns, const XMLCh* localName) { - DOMElement* e = getLastChildElement(n); - while (e && !isNodeNamed(e, ns, localName)) - e = getPreviousSiblingElement(e); + DOMElement* e = getLastChildElement(n, localName); + while (e && !XMLString::equals(e->getNamespaceURI(),ns)) + e = getPreviousSiblingElement(e, localName); return e; } -DOMElement* XMLHelper::getNextSiblingElement(const DOMNode* n) +DOMElement* XMLHelper::getNextSiblingElement(const DOMNode* n, const XMLCh* localName) { DOMNode* sib = n->getNextSibling(); while (sib && sib->getNodeType() != DOMNode::ELEMENT_NODE) sib = sib->getNextSibling(); - if (sib) - return static_cast(sib); - return NULL; + if (sib && localName) { + if (!XMLString::equals(localName,sib->getLocalName())) + return getNextSiblingElement(sib, localName); + } + return static_cast(sib); } -DOMElement* XMLHelper::getPreviousSiblingElement(const DOMNode* n) +DOMElement* XMLHelper::getPreviousSiblingElement(const DOMNode* n, const XMLCh* localName) { DOMNode* sib = n->getPreviousSibling(); while (sib && sib->getNodeType() != DOMNode::ELEMENT_NODE) sib = sib->getPreviousSibling(); - if (sib) - return static_cast(sib); - return NULL; + if (sib && localName) { + if (!XMLString::equals(localName,sib->getLocalName())) + return getPreviousSiblingElement(sib, localName); + } + return static_cast(sib); } DOMElement* XMLHelper::getNextSiblingElement(const DOMNode* n, const XMLCh* ns, const XMLCh* localName) { - DOMElement* e = getNextSiblingElement(n); - while (e && !isNodeNamed(e, ns, localName)) - e = getNextSiblingElement(e); + DOMElement* e = getNextSiblingElement(n, localName); + while (e && !XMLString::equals(e->getNamespaceURI(),ns)) + e = getNextSiblingElement(e, localName); return e; } DOMElement* XMLHelper::getPreviousSiblingElement(const DOMNode* n, const XMLCh* ns, const XMLCh* localName) { - DOMElement* e = getPreviousSiblingElement(n); - while (e && !isNodeNamed(e, ns, localName)) - e = getPreviousSiblingElement(e); + DOMElement* e = getPreviousSiblingElement(n, localName); + while (e && !XMLString::equals(e->getNamespaceURI(),ns)) + e = getPreviousSiblingElement(e, localName); return e; } diff --git a/xmltooling/util/XMLHelper.h b/xmltooling/util/XMLHelper.h index 912c208..777ce13 100644 --- a/xmltooling/util/XMLHelper.h +++ b/xmltooling/util/XMLHelper.h @@ -20,7 +20,7 @@ * A helper class for working with W3C DOM objects. */ -#if !defined(__xmltooling_xmlhelper_h__) +#ifndef __xmltooling_xmlhelper_h__ #define __xmltooling_xmlhelper_h__ #include @@ -126,36 +126,40 @@ namespace xmltooling { } /** - * Returns the first child element of the node if any. + * Returns the first matching child element of the node if any. * - * @param n node to check - * @return the first child node of type Element, or NULL + * @param n node to check + * @param localName local name to compare with or NULL for any match + * @return the first matching child node of type Element, or NULL */ - static DOMElement* getFirstChildElement(const DOMNode* n); + static DOMElement* getFirstChildElement(const DOMNode* n, const XMLCh* localName=NULL); /** - * Returns the last child element of the node if any. + * Returns the last matching child element of the node if any. * * @param n node to check - * @return the last child node of type Element, or NULL + * @param localName local name to compare with or NULL for any match + * @return the last matching child node of type Element, or NULL */ - static DOMElement* getLastChildElement(const DOMNode* n); + static DOMElement* getLastChildElement(const DOMNode* n, const XMLCh* localName=NULL); /** - * Returns the next sibling element of the node if any. + * Returns the next matching sibling element of the node if any. * * @param n node to check - * @return the next sibling node of type Element, or NULL + * @param localName local name to compare with or NULL for any match + * @return the next matching sibling node of type Element, or NULL */ - static DOMElement* getNextSiblingElement(const DOMNode* n); + static DOMElement* getNextSiblingElement(const DOMNode* n, const XMLCh* localName=NULL); /** - * Returns the previous sibling element of the node if any. + * Returns the previous matching sibling element of the node if any. * * @param n node to check - * @return the previous sibling node of type Element, or NULL + * @param localName local name to compare with or NULL for any match + * @return the previous matching sibling node of type Element, or NULL */ - static DOMElement* getPreviousSiblingElement(const DOMNode* n); + static DOMElement* getPreviousSiblingElement(const DOMNode* n, const XMLCh* localName=NULL); /** * Returns the first matching child element of the node if any. diff --git a/xmltooling/xmltooling.vcproj b/xmltooling/xmltooling.vcproj index dd64694..51492df 100644 --- a/xmltooling/xmltooling.vcproj +++ b/xmltooling/xmltooling.vcproj @@ -62,7 +62,7 @@ /> + + + + @@ -507,6 +515,10 @@ > + + @@ -515,6 +527,10 @@ > + + diff --git a/xmltoolingtest/FilesystemCredentialResolverTest.h b/xmltoolingtest/FilesystemCredentialResolverTest.h new file mode 100644 index 0000000..92e720a --- /dev/null +++ b/xmltoolingtest/FilesystemCredentialResolverTest.h @@ -0,0 +1,48 @@ +/* + * Copyright 2001-2006 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. + */ + +#include "XMLObjectBaseTestCase.h" + +#include + +#include + +class FilesystemCredentialResolverTest : public CxxTest::TestSuite { +public: + void setUp() { + } + + void tearDown() { + } + + void testFilesystemProvider() { + string config = data_path + "FilesystemCredentialResolver.xml"; + ifstream in(config.c_str()); + DOMDocument* doc=XMLToolingConfig::getConfig().getParser().parse(in); + XercesJanitor janitor(doc); + + auto_ptr credResolver( + XMLToolingConfig::getConfig().CredentialResolverManager.newPlugin( + FILESYSTEM_CREDENTIAL_RESOLVER,doc->getDocumentElement() + ) + ); + + Locker locker(credResolver.get()); + auto_ptr key(credResolver->getKey()); + TSM_ASSERT("Retrieved key was null", key.get()!=NULL); + TSM_ASSERT_EQUALS("Unexpected number of certificates", 1, credResolver->getCertificates().size()); + } +}; diff --git a/xmltoolingtest/Makefile.am b/xmltoolingtest/Makefile.am index 69538ec..4525b08 100644 --- a/xmltoolingtest/Makefile.am +++ b/xmltoolingtest/Makefile.am @@ -10,6 +10,7 @@ endif if BUILD_XMLSEC xmlsec_sources = \ EncryptionTest.h \ + FilesystemCredentialResolverTest.h \ SignatureTest.h else xmlsec_sources = diff --git a/xmltoolingtest/data/FilesystemCredentialResolver.xml b/xmltoolingtest/data/FilesystemCredentialResolver.xml new file mode 100644 index 0000000..32be243 --- /dev/null +++ b/xmltoolingtest/data/FilesystemCredentialResolver.xml @@ -0,0 +1,9 @@ + + + + ../xmltoolingtest/data/key.pem + + + ../xmltoolingtest/data/cert.pem + + diff --git a/xmltoolingtest/xmltoolingtest.vcproj b/xmltoolingtest/xmltoolingtest.vcproj index 5e838d9..4502fe7 100644 --- a/xmltoolingtest/xmltoolingtest.vcproj +++ b/xmltoolingtest/xmltoolingtest.vcproj @@ -192,6 +192,10 @@ > + + @@ -284,6 +288,28 @@ + + + + + + + +