2 * Copyright 2001-2005 Internet2
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 /* CredResolvers.cpp - implementations of the ICredResolver interface
27 #include <sys/types.h>
30 #include <openssl/pkcs12.h>
31 #include <log4cpp/Category.hh>
32 #include <xsec/enc/OpenSSL/OpenSSLCryptoX509.hpp>
33 #include <xsec/enc/OpenSSL/OpenSSLCryptoKeyRSA.hpp>
34 #include <xsec/enc/OpenSSL/OpenSSLCryptoKeyDSA.hpp>
37 using namespace shibboleth;
38 using namespace log4cpp;
41 // OpenSSL password callback...
42 static int passwd_callback(char* buf, int len, int verify, void* passwd)
46 if(passwd && len > strlen(reinterpret_cast<char*>(passwd)))
48 strcpy(buf,reinterpret_cast<char*>(passwd));
55 // File-based resolver
57 class FileResolver : public ICredResolver
60 FileResolver(const DOMElement* e);
62 virtual void attach(void* ctx) const;
63 virtual XSECCryptoKey* getKey() const;
64 virtual saml::Iterator<XSECCryptoX509*> getCertificates() const { return m_xseccerts; }
65 virtual void dump(FILE* f) const;
68 enum format_t { PEM=SSL_FILETYPE_PEM, DER=SSL_FILETYPE_ASN1, _PKCS12, UNKNOWN };
70 format_t getEncodingFormat(BIO* in) const;
71 string formatToString(format_t format) const;
72 format_t xmlFormatToFormat(const XMLCh* format_xml) const;
75 string m_keypath,m_keypass;
76 vector<X509*> m_certs;
77 vector<XSECCryptoX509*> m_xseccerts;
80 IPlugIn* FileCredResolverFactory(const DOMElement* e)
82 return new FileResolver(e);
85 FileResolver::FileResolver(const DOMElement* e)
88 saml::NDC ndc("FileResolver");
90 Category& log=Category::getInstance(XMLPROVIDERS_LOGCAT".CredResolvers");
96 const DOMElement* root=e;
97 e=saml::XML::getFirstChildElement(root,::XML::CREDS_NS,SHIB_L(Key));
100 // Get raw format attrib value, but defer processing til later since may need to
101 // determine format dynamically, and we need the Path for that.
102 const XMLCh* format_xml=e->getAttributeNS(NULL,SHIB_L(format));
104 const XMLCh* password=e->getAttributeNS(NULL,SHIB_L(password));
106 auto_ptr_char kp(password);
110 e=saml::XML::getFirstChildElement(e,::XML::CREDS_NS,SHIB_L(Path));
111 if (e && e->hasChildNodes()) {
112 const XMLCh* s=e->getFirstChild()->getNodeValue();
113 auto_ptr_char kpath(s);
115 struct _stat stat_buf;
116 if (_stat(kpath.get(), &stat_buf) != 0)
118 struct stat stat_buf;
119 if (stat(kpath.get(), &stat_buf) != 0)
122 log.error("key file (%s) can't be opened", kpath.get());
123 throw CredentialException("FileResolver can't access key file ($1)",params(1,kpath.get()));
125 m_keypath=kpath.get();
128 log.error("Path element missing inside Key element");
129 throw CredentialException("FileResolver can't access key file, no Path element specified.");
132 // Determine the key encoding format dynamically, if not explicitly specified
134 if (format_xml && *format_xml) {
135 format = xmlFormatToFormat(format_xml);
136 if (format != UNKNOWN) {
137 m_keyformat = format;
140 auto_ptr_char unknown(format_xml);
141 log.error("Configuration specifies unknown key encoding format (%s)", unknown.get());
142 throw CredentialException("FileResolver configuration contains unknown key encoding format ($1)",params(1,unknown.get()));
146 in=BIO_new(BIO_s_file_internal());
147 if (in && BIO_read_filename(in,m_keypath.c_str())>0) {
148 m_keyformat = getEncodingFormat(in);
149 log.debug("Key encoding format for (%s) dynamically resolved as (%s)", m_keypath.c_str(), formatToString(m_keyformat).c_str());
152 log.error("Key file (%s) can't be read to determine encoding format", m_keypath.c_str());
153 throw CredentialException("FileResolver can't read key file ($1) to determine encoding format",params(1,m_keypath.c_str()));
161 log.error("Error determining key encoding format");
167 // Check for Certificate
168 e=saml::XML::getFirstChildElement(root,::XML::CREDS_NS,SHIB_L(Certificate));
171 auto_ptr_char certpass(e->getAttributeNS(NULL,SHIB_L(password)));
173 DOMElement* ep=saml::XML::getFirstChildElement(e,::XML::CREDS_NS,SHIB_L(Path));
174 if (!ep || !ep->hasChildNodes()) {
175 log.error("Path element missing inside Certificate element");
176 throw CredentialException("FileResolver can't access certificate file, missing Path element.");
179 auto_ptr_char certpath(ep->getFirstChild()->getNodeValue());
180 const XMLCh* format_xml=e->getAttributeNS(NULL,SHIB_L(format));
181 if (format_xml && *format_xml) {
182 format = xmlFormatToFormat(format_xml);
183 if (format == UNKNOWN) {
184 auto_ptr_char unknown(format_xml);
185 log.error("Configuration specifies unknown certificate encoding format (%s)", unknown.get());
186 throw CredentialException("FileResolver configuration contains unknown certificate encoding format ($1)",params(1,unknown.get()));
193 in=BIO_new(BIO_s_file_internal());
194 if (in && BIO_read_filename(in,certpath.get())>0) {
195 if (!format_xml || !*format_xml) {
196 // Determine the cert encoding format dynamically, if not explicitly specified
197 format = getEncodingFormat(in);
198 log.debug("Cert encoding format for (%s) dynamically resolved as (%s)", certpath.get(), formatToString(format).c_str());
203 while (x=PEM_read_bio_X509(in,NULL,passwd_callback,const_cast<char*>(certpass.get()))) {
204 m_certs.push_back(x);
209 x=d2i_X509_bio(in,NULL);
211 m_certs.push_back(x);
215 throw CredentialException("FileResolver unable to load DER certificate from file ($1)",params(1,certpath.get()));
220 p12=d2i_PKCS12_bio(in,NULL);
222 PKCS12_parse(p12, certpass.get(), NULL, &x, NULL);
226 m_certs.push_back(x);
231 throw CredentialException("FileResolver unable to load PKCS12 certificate from file ($1)",params(1,certpath.get()));
242 throw CredentialException("FileResolver unable to load certificate(s) from file ($1)",params(1,certpath.get()));
249 if (m_certs.empty()) {
250 throw CredentialException("FileResolver unable to load any certificate(s)");
253 // Load any extra CA files.
254 DOMNodeList* nlist=e->getElementsByTagNameNS(::XML::CREDS_NS,SHIB_L(CAPath));
255 for (unsigned int i=0; nlist && i<nlist->getLength(); i++) {
256 if (!nlist->item(i)->hasChildNodes())
258 auto_ptr_char capath(static_cast<DOMElement*>(nlist->item(i))->getFirstChild()->getNodeValue());
261 in=BIO_new(BIO_s_file_internal());
262 if (in && BIO_read_filename(in,capath.get())>0) {
263 if (!format_xml || !*format_xml) {
264 // Determine the cert encoding format dynamically, if not explicitly specified
265 format = getEncodingFormat(in);
266 log.debug("Cert encoding format for (%s) dynamically resolved as (%s)", certpath.get(), formatToString(format).c_str());
272 while (x=PEM_read_bio_X509(in,NULL,passwd_callback,const_cast<char*>(certpass.get()))) {
273 m_certs.push_back(x);
278 x=d2i_X509_bio(in,NULL);
280 m_certs.push_back(x);
284 throw CredentialException("FileResolver unable to load DER CA certificate from file ($1)",params(1,capath.get()));
289 p12 = d2i_PKCS12_bio(in, NULL);
291 PKCS12_parse(p12, certpass.get(), NULL, &x, NULL);
295 m_certs.push_back(x);
300 throw CredentialException("FileResolver unable to load PKCS12 CA certificate from file ($1)",params(1,capath.get()));
311 log.error("CA file (%s) can't be opened", capath.get());
312 throw CredentialException("FileResolver can't open CA file ($1)",params(1,capath.get()));
317 for (vector<X509*>::iterator j=m_certs.begin(); j!=m_certs.end(); j++)
322 // Reflect certs over to XSEC form.
323 for (vector<X509*>::iterator j=m_certs.begin(); j!=m_certs.end(); j++)
324 m_xseccerts.push_back(new OpenSSLCryptoX509(*j));
327 FileResolver::~FileResolver()
329 for_each(m_certs.begin(),m_certs.end(),X509_free);
330 for_each(m_xseccerts.begin(),m_xseccerts.end(),xmltooling::cleanup<XSECCryptoX509>());
333 void FileResolver::attach(void* ctx) const
336 saml::NDC ndc("attach");
339 SSL_CTX* ssl_ctx=reinterpret_cast<SSL_CTX*>(ctx);
342 SSL_CTX_set_default_passwd_cb(ssl_ctx, passwd_callback);
343 SSL_CTX_set_default_passwd_cb_userdata(ssl_ctx, const_cast<char*>(m_keypass.c_str()));
346 switch (m_keyformat) {
348 ret=SSL_CTX_use_PrivateKey_file(ssl_ctx, m_keypath.c_str(), m_keyformat);
352 ret=SSL_CTX_use_RSAPrivateKey_file(ssl_ctx, m_keypath.c_str(), m_keyformat);
356 BIO* in=BIO_new(BIO_s_file_internal());
357 if (in && BIO_read_filename(in,m_keypath.c_str())>0) {
359 PKCS12* p12 = d2i_PKCS12_bio(in, NULL);
361 PKCS12_parse(p12, const_cast<char*>(m_keypass.c_str()), &pkey, NULL, NULL);
364 ret=SSL_CTX_use_PrivateKey(ssl_ctx, pkey);
376 throw CredentialException("Unable to attach private key to SSL context");
380 for (vector<X509*>::const_iterator i=m_certs.begin(); i!=m_certs.end(); i++) {
381 if (i==m_certs.begin()) {
382 if (SSL_CTX_use_certificate(ssl_ctx, *i) != 1) {
384 throw CredentialException("Unable to attach SP client certificate to SSL context");
388 // When we add certs, they don't get ref counted, so we need to duplicate them.
389 X509* dup = X509_dup(*i);
390 if (SSL_CTX_add_extra_chain_cert(ssl_ctx, dup) != 1) {
393 throw CredentialException("Unable to attach CA certificate to SSL context");
399 XSECCryptoKey* FileResolver::getKey() const
402 saml::NDC ndc("getKey");
407 BIO* in=BIO_new(BIO_s_file_internal());
408 if (in && BIO_read_filename(in,m_keypath.c_str())>0) {
409 switch (m_keyformat) {
411 pkey=PEM_read_bio_PrivateKey(in, NULL, passwd_callback, const_cast<char*>(m_keypass.c_str()));
415 pkey=d2i_PrivateKey_bio(in, NULL);
419 PKCS12* p12 = d2i_PKCS12_bio(in, NULL);
421 PKCS12_parse(p12, const_cast<char*>(m_keypass.c_str()), &pkey, NULL, NULL);
430 // Now map it to an XSEC wrapper.
432 XSECCryptoKey* ret=NULL;
433 switch (pkey->type) {
435 ret=new OpenSSLCryptoKeyRSA(pkey);
439 ret=new OpenSSLCryptoKeyDSA(pkey);
443 Category::getInstance(XMLPROVIDERS_LOGCAT".CredResolvers").error("unsupported private key type");
451 Category::getInstance(XMLPROVIDERS_LOGCAT".CredResolvers").error("FileResolver unable to load private key from file");
455 void FileResolver::dump(FILE* f) const
459 BIO* in=BIO_new(BIO_s_file_internal());
460 if (in && BIO_read_filename(in,m_keypath.c_str())>0) {
461 if (m_keyformat==DER)
462 rsa=d2i_RSAPrivateKey_bio(in,NULL);
463 else if (m_keyformat==PEM)
464 rsa=PEM_read_bio_RSAPrivateKey(in,NULL,passwd_callback,const_cast<char*>(m_keypass.c_str()));
467 PKCS12* p12 = d2i_PKCS12_bio(in, NULL);
469 PKCS12_parse(p12, const_cast<char*>(m_keypass.c_str()), &pkey, NULL, NULL);
472 fprintf(f,"----- PRIVATE KEY -----\n");
473 if (pkey->type==EVP_PK_RSA)
474 RSA_print_fp(f,pkey->pkey.rsa,0);
475 else if (pkey->type==EVP_PK_DSA)
476 DSA_print_fp(f,pkey->pkey.dsa,0);
482 fprintf(f,"----- PRIVATE KEY -----\n");
483 RSA_print_fp(f,rsa,0);
492 // Dump certificates.
493 for (vector<X509*>::const_iterator i=m_certs.begin(); i!=m_certs.end(); i++) {
494 fprintf(f,"----- CERTIFICATE(S) -----\n");
495 #if (OPENSSL_VERSION_NUMBER > 0x009070000L)
496 X509_print_ex_fp(f,*i,XN_FLAG_SEP_MULTILINE,0);
503 // Used to determine the encoding format of credentials files
504 // dynamically. Supports: PEM, DER, PKCS12.
505 FileResolver::format_t FileResolver::getEncodingFormat(BIO* in) const
510 const int READSIZE = 1;
516 if ( (mark = BIO_tell(in)) < 0 )
517 throw CredentialException("getEncodingFormat: BIO_tell() can't get the file position");
518 if ( BIO_read(in, buf, READSIZE) <= 0 )
519 throw CredentialException("getEncodingFormat: BIO_read() can't read from the stream");
520 if ( BIO_seek(in, mark) < 0 )
521 throw CredentialException("getEncodingFormat: BIO_seek() can't reset the file position");
530 // This is a slight variation of the Java code by Chad La Joie.
532 // Check the first byte of the file. If it's some kind of
533 // DER-encoded structure (including PKCS12), it will begin with ASCII 048.
534 // Otherwise, assume it's PEM.
538 // Here we know it's DER-encoded, now try to parse it as a PKCS12
539 // ASN.1 structure. If it fails, must be another kind of DER-encoded
540 // key/cert structure. A little inefficient...but it works.
541 if ( (p12=d2i_PKCS12_bio(in,NULL)) == NULL ) {
548 if ( BIO_seek(in, mark) < 0 ) {
550 throw CredentialException("getEncodingFormat: BIO_seek() can't reset the file position");
557 // Convert key/cert format_t types to a human-meaningful string for debug output
558 string FileResolver::formatToString(format_t format) const
572 // Convert key/cert raw XML format attribute (XMLCh[]) to format_t type
573 FileResolver::format_t FileResolver::xmlFormatToFormat(const XMLCh* format_xml) const
575 static const XMLCh cPEM[] = { chLatin_P, chLatin_E, chLatin_M, chNull };
576 static const XMLCh cDER[] = { chLatin_D, chLatin_E, chLatin_R, chNull };
577 static const XMLCh cPKCS12[] = { chLatin_P, chLatin_K, chLatin_C, chLatin_S, chDigit_1, chDigit_2, chNull };
580 if (!XMLString::compareString(format_xml,cPEM))
582 else if (!XMLString::compareString(format_xml,cDER))
584 else if (!XMLString::compareString(format_xml,cPKCS12))