-/*
- * The Shibboleth License, Version 1.
- * Copyright (c) 2002
- * University Corporation for Advanced Internet Development, Inc.
- * All rights reserved
+/*
+ * Copyright 2001-2005 Internet2
*
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are met:
- *
- * Redistributions of source code must retain the above copyright notice, this
- * list of conditions and the following disclaimer.
- *
- * Redistributions in binary form must reproduce the above copyright notice,
- * this list of conditions and the following disclaimer in the documentation
- * and/or other materials provided with the distribution, if any, must include
- * the following acknowledgment: "This product includes software developed by
- * the University Corporation for Advanced Internet Development
- * <http://www.ucaid.edu>Internet2 Project. Alternately, this acknowledegement
- * may appear in the software itself, if and wherever such third-party
- * acknowledgments normally appear.
- *
- * Neither the name of Shibboleth nor the names of its contributors, nor
- * Internet2, nor the University Corporation for Advanced Internet Development,
- * Inc., nor UCAID may be used to endorse or promote products derived from this
- * software without specific prior written permission. For written permission,
- * please contact shibboleth@shibboleth.org
- *
- * Products derived from this software may not be called Shibboleth, Internet2,
- * UCAID, or the University Corporation for Advanced Internet Development, nor
- * may Shibboleth appear in their name, without prior written permission of the
- * University Corporation for Advanced Internet Development.
- *
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
- * AND WITH ALL FAULTS. ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
- * PARTICULAR PURPOSE, AND NON-INFRINGEMENT ARE DISCLAIMED AND THE ENTIRE RISK
- * OF SATISFACTORY QUALITY, PERFORMANCE, ACCURACY, AND EFFORT IS WITH LICENSEE.
- * IN NO EVENT SHALL THE COPYRIGHT OWNER, CONTRIBUTORS OR THE UNIVERSITY
- * CORPORATION FOR ADVANCED INTERNET DEVELOPMENT, INC. BE LIABLE FOR ANY DIRECT,
- * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
- * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
- * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
- * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * 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.
*/
/* CredResolvers.cpp - implementations of the ICredResolver interface
#include "internal.h"
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <algorithm>
#include <openssl/pkcs12.h>
#include <log4cpp/Category.hh>
#include <xsec/enc/OpenSSL/OpenSSLCryptoX509.hpp>
#include <xsec/enc/OpenSSL/OpenSSLCryptoKeyRSA.hpp>
#include <xsec/enc/OpenSSL/OpenSSLCryptoKeyDSA.hpp>
+#include <xmltooling/util/NDC.h>
-using namespace saml;
using namespace shibboleth;
+using namespace xmltooling;
using namespace log4cpp;
using namespace std;
// OpenSSL password callback...
-int passwd_callback(char* buf, int len, int verify, void* passwd)
+static int passwd_callback(char* buf, int len, int verify, void* passwd)
{
if(!verify)
{
virtual saml::Iterator<XSECCryptoX509*> getCertificates() const { return m_xseccerts; }
virtual void dump(FILE* f) const;
-protected:
- enum format_t { DER=SSL_FILETYPE_ASN1, PEM=SSL_FILETYPE_PEM, _PKCS12 };
+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;
};
-IPlugIn* FileCredResolverFactory(const DOMElement* e)
+saml::IPlugIn* FileCredResolverFactory(const DOMElement* e)
{
return new FileResolver(e);
}
FileResolver::FileResolver(const DOMElement* e)
{
- saml::NDC ndc("FileResolver");
- static const XMLCh cPEM[] = { chLatin_P, chLatin_E, chLatin_M, chNull };
- static const XMLCh cDER[] = { chLatin_D, chLatin_E, chLatin_R, chNull };
+#ifdef _DEBUG
+ xmltooling::NDC ndc("FileResolver");
+#endif
+ Category& log=Category::getInstance(XMLPROVIDERS_LOGCAT".CredResolvers");
+
+ format_t format;
+ BIO* in = NULL;
// Move to Key
- e=saml::XML::getFirstChildElement(e);
- const XMLCh* format=e->getAttributeNS(NULL,SHIB_L(format));
- if (!format || !*format || !XMLString::compareString(format,cPEM))
- m_keyformat=PEM;
- else if (!XMLString::compareString(format,cDER))
- m_keyformat=DER;
- else
- m_keyformat=_PKCS12;
+ const DOMElement* root=e;
+ e=saml::XML::getFirstChildElement(root,::XML::CREDS_NS,SHIB_L(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.
+ const XMLCh* format_xml=e->getAttributeNS(NULL,SHIB_L(format));
+
+ const XMLCh* password=e->getAttributeNS(NULL,SHIB_L(password));
+ if (password) {
+ auto_ptr_char kp(password);
+ m_keypass=kp.get();
+ }
- const XMLCh* password=e->getAttributeNS(NULL,SHIB_L(password));
- if (password) {
- auto_ptr_char kp(password);
- m_keypass=kp.get();
- }
-
- const XMLCh* s=saml::XML::getFirstChildElement(e,::XML::CREDS_NS,SHIB_L(Path))->getFirstChild()->getNodeValue();
- auto_ptr_char kpath(s);
-
+ e=saml::XML::getFirstChildElement(e,::XML::CREDS_NS,SHIB_L(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)
+ struct _stat stat_buf;
+ if (_stat(kpath.get(), &stat_buf) != 0)
#else
- struct stat stat_buf;
- if (stat(kpath.get(), &stat_buf) != 0)
+ struct stat stat_buf;
+ if (stat(kpath.get(), &stat_buf) != 0)
#endif
- {
- Category::getInstance(XMLPROVIDERS_LOGCAT".CredResolvers").error("key file '%s' can't be opened", kpath.get());
- throw CredentialException("FileResolver() can't access key file");
- }
- m_keypath=kpath.get();
-
- // Check for Certificate
- e=saml::XML::getNextSiblingElement(e);
- password=e->getAttributeNS(NULL,SHIB_L(password));
- auto_ptr_char certpass(password);
- s=saml::XML::getFirstChildElement(e,::XML::CREDS_NS,SHIB_L(Path))->getFirstChild()->getNodeValue();
- auto_ptr_char certpath(s);
+ {
+ log.error("key file (%s) can't be opened", kpath.get());
+ throw IOException("FileResolver can't access key file ($1)",params(1,kpath.get()));
+ }
+ m_keypath=kpath.get();
+ }
+ else {
+ log.error("Path element missing inside Key element");
+ throw IOException("FileResolver can't access key file, no Path element specified.");
+ }
- try {
- X509* x=NULL;
- BIO* in=BIO_new(BIO_s_file_internal());
- if (in && BIO_read_filename(in,certpath.get())>0) {
- format=e->getAttributeNS(NULL,SHIB_L(format));
- if (!format || !*format || !XMLString::compareString(format,cPEM)) {
- while (x=PEM_read_bio_X509(in,NULL,passwd_callback,const_cast<char*>(certpass.get()))) {
- m_certs.push_back(x);
+ // Determine the key encoding format dynamically, if not explicitly specified
+ try {
+ if (format_xml && *format_xml) {
+ format = xmlFormatToFormat(format_xml);
+ if (format != UNKNOWN) {
+ m_keyformat = format;
}
- }
- else if (!XMLString::compareString(format,cDER)) {
- x=d2i_X509_bio(in,NULL);
- if (x)
- m_certs.push_back(x);
else {
- log_openssl();
- BIO_free(in);
- throw CredentialException("FileResolver() unable to load DER certificate from file");
+ auto_ptr_char unknown(format_xml);
+ log.error("Configuration specifies unknown key encoding format (%s)", unknown.get());
+ throw IOException("FileResolver configuration contains unknown key encoding format ($1)",params(1,unknown.get()));
}
}
else {
- 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;
+ 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_openssl();
- BIO_free(in);
- throw CredentialException("FileResolver() unable to load PKCS12 certificate from file");
+ log.error("Key file (%s) can't be read to determine encoding format", m_keypath.c_str());
+ throw IOException("FileResolver can't read key file ($1) to determine encoding format",params(1,m_keypath.c_str()));
}
+ if (in)
+ BIO_free(in);
+ in = NULL;
}
}
- if (in) {
- BIO_free(in);
- in=NULL;
+ catch (...) {
+ log.error("Error determining key encoding format");
+ throw;
}
- // Load any extra CA files.
- DOMNodeList* nlist=e->getElementsByTagNameNS(::XML::CREDS_NS,SHIB_L(CAPath));
- for (int i=0; nlist && i<nlist->getLength(); i++) {
- s=static_cast<DOMElement*>(nlist->item(i))->getFirstChild()->getNodeValue();
- auto_ptr_char capath(s);
- x=NULL;
- in=BIO_new(BIO_s_file_internal());
- if (in && BIO_read_filename(in,capath.get())>0) {
- if (!format || !*format || !XMLString::compareString(format,cPEM)) {
+ }
+
+ // Check for Certificate
+ e=saml::XML::getFirstChildElement(root,::XML::CREDS_NS,SHIB_L(Certificate));
+ if (!e)
+ return;
+ auto_ptr_char certpass(e->getAttributeNS(NULL,SHIB_L(password)));
+
+ DOMElement* ep=saml::XML::getFirstChildElement(e,::XML::CREDS_NS,SHIB_L(Path));
+ if (!ep || !ep->hasChildNodes()) {
+ log.error("Path element missing inside Certificate element");
+ throw IOException("FileResolver can't access certificate file, missing Path element.");
+ }
+
+ auto_ptr_char certpath(ep->getFirstChild()->getNodeValue());
+ const XMLCh* format_xml=e->getAttributeNS(NULL,SHIB_L(format));
+ if (format_xml && *format_xml) {
+ format = xmlFormatToFormat(format_xml);
+ if (format == UNKNOWN) {
+ auto_ptr_char unknown(format_xml);
+ log.error("Configuration specifies unknown certificate encoding format (%s)", unknown.get());
+ throw IOException("FileResolver 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
+ format = getEncodingFormat(in);
+ log.debug("Cert encoding format for (%s) dynamically resolved as (%s)", certpath.get(), formatToString(format).c_str());
+ }
+
+ switch(format) {
+ case PEM:
while (x=PEM_read_bio_X509(in,NULL,passwd_callback,const_cast<char*>(certpass.get()))) {
- m_certs.push_back(x);
+ m_certs.push_back(x);
}
- }
- else if (!XMLString::compareString(format,cDER)) {
+ break;
+
+ case DER:
x=d2i_X509_bio(in,NULL);
if (x)
m_certs.push_back(x);
else {
log_openssl();
BIO_free(in);
- throw CredentialException("FileResolver() unable to load DER CA certificate from file");
+ throw IOException("FileResolver unable to load DER certificate from file ($1)",params(1,certpath.get()));
}
- }
- else {
- PKCS12* p12 = d2i_PKCS12_bio(in, NULL);
+ 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 {
+ } else {
log_openssl();
BIO_free(in);
- throw CredentialException("FileResolver() unable to load PKCS12 CA certificate from file");
+ throw IOException("FileResolver 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;
}
- else {
+ throw IOException("FileResolver unable to load certificate(s) from file ($1)",params(1,certpath.get()));
+ }
+ if (in) {
+ BIO_free(in);
+ in=NULL;
+ }
+
+ if (m_certs.empty()) {
+ throw IOException("FileResolver unable to load any certificate(s)");
+ }
+
+ // Load any extra CA files.
+ DOMNodeList* nlist=e->getElementsByTagNameNS(::XML::CREDS_NS,SHIB_L(CAPath));
+ for (unsigned int i=0; nlist && i<nlist->getLength(); i++) {
+ if (!nlist->item(i)->hasChildNodes())
+ continue;
+ auto_ptr_char capath(static_cast<DOMElement*>(nlist->item(i))->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
+ format = getEncodingFormat(in);
+ log.debug("Cert encoding format for (%s) dynamically resolved as (%s)", certpath.get(), formatToString(format).c_str());
+ }
+
+ switch (format)
+ {
+ 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 IOException("FileResolver 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 IOException("FileResolver 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();
- Category::getInstance(XMLPROVIDERS_LOGCAT".CredResolvers").error("CA file '%s' can't be opened", capath.get());
- throw CredentialException("FileResolver() can't open CA file");
+ log.error("CA file (%s) can't be opened", capath.get());
+ throw IOException("FileResolver can't open CA file ($1)",params(1,capath.get()));
}
}
}
FileResolver::~FileResolver()
{
- for (vector<X509*>::iterator i=m_certs.begin(); i!=m_certs.end(); i++)
- X509_free(*i);
- for (vector<XSECCryptoX509*>::iterator j=m_xseccerts.begin(); j!=m_xseccerts.end(); j++)
- delete (*j);
+ for_each(m_certs.begin(),m_certs.end(),X509_free);
+ for_each(m_xseccerts.begin(),m_xseccerts.end(),xmltooling::cleanup<XSECCryptoX509>());
}
void FileResolver::attach(void* ctx) const
{
- saml::NDC ndc("FileResolver");
+#ifdef _DEBUG
+ saml::NDC ndc("attach");
+#endif
SSL_CTX* ssl_ctx=reinterpret_cast<SSL_CTX*>(ctx);
SSL_CTX_set_default_passwd_cb_userdata(ssl_ctx, const_cast<char*>(m_keypass.c_str()));
int ret=0;
- switch (m_keyformat)
- {
+ switch (m_keyformat) {
case PEM:
ret=SSL_CTX_use_PrivateKey_file(ssl_ctx, m_keypath.c_str(), m_keyformat);
break;
if (ret!=1) {
log_openssl();
- throw CredentialException("FileResolver::attach() unable to set private key");
+ throw IOException("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(ssl_ctx, *i)!=1) {
+ if (SSL_CTX_use_certificate(ssl_ctx, *i) != 1) {
log_openssl();
- throw CredentialException("FileResolver::attach() unable to set EE certificate in context");
+ throw IOException("Unable to attach SP 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(ssl_ctx, dup) != 0) {
+ if (SSL_CTX_add_extra_chain_cert(ssl_ctx, dup) != 1) {
X509_free(dup);
log_openssl();
- throw CredentialException("FileResolver::attach() unable to add CA certificate to context");
+ throw IOException("Unable to attach CA certificate to SSL context");
}
}
}
XSECCryptoKey* FileResolver::getKey() const
{
+#ifdef _DEBUG
+ saml::NDC ndc("getKey");
+#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)
- {
+ switch (m_keyformat) {
case PEM:
pkey=PEM_read_bio_PrivateKey(in, NULL, passwd_callback, const_cast<char*>(m_keypass.c_str()));
break;
// Now map it to an XSEC wrapper.
if (pkey) {
XSECCryptoKey* ret=NULL;
- switch (pkey->type)
- {
+ switch (pkey->type) {
case EVP_PKEY_RSA:
ret=new OpenSSLCryptoKeyRSA(pkey);
break;
break;
default:
- saml::NDC ndc("FileResolver");
Category::getInstance(XMLPROVIDERS_LOGCAT".CredResolvers").error("unsupported private key type");
}
EVP_PKEY_free(pkey);
return ret;
}
- saml::NDC ndc("FileResolver");
log_openssl();
- Category::getInstance(XMLPROVIDERS_LOGCAT".CredResolvers").error("FileResolver::getKey() unable to load private key from file");
+ Category::getInstance(XMLPROVIDERS_LOGCAT".CredResolvers").error("FileResolver unable to load private key from file");
return NULL;
}
#endif
}
}
+
+// Used to determine the encoding format of credentials files
+// dynamically. Supports: PEM, DER, PKCS12.
+FileResolver::format_t FileResolver::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 IOException("getEncodingFormat: BIO_tell() can't get the file position");
+ if ( BIO_read(in, buf, READSIZE) <= 0 )
+ throw IOException("getEncodingFormat: BIO_read() can't read from the stream");
+ if ( BIO_seek(in, mark) < 0 )
+ throw IOException("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 IOException("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 FileResolver::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
+FileResolver::format_t FileResolver::xmlFormatToFormat(const XMLCh* format_xml) const
+{
+ static const XMLCh cPEM[] = { chLatin_P, chLatin_E, chLatin_M, chNull };
+ static const XMLCh cDER[] = { chLatin_D, chLatin_E, chLatin_R, chNull };
+ 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;
+}