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>
35 #include <xmltooling/util/NDC.h>
37 using namespace shibboleth;
38 using namespace xmltooling;
39 using namespace log4cpp;
42 // OpenSSL password callback...
43 static int passwd_callback(char* buf, int len, int verify, void* passwd)
47 if(passwd && len > strlen(reinterpret_cast<char*>(passwd)))
49 strcpy(buf,reinterpret_cast<char*>(passwd));
56 // File-based resolver
58 class FileResolver : public ICredResolver
61 FileResolver(const DOMElement* e);
63 virtual void attach(void* ctx) const;
64 virtual XSECCryptoKey* getKey() const;
65 virtual saml::Iterator<XSECCryptoX509*> getCertificates() const { return m_xseccerts; }
66 virtual void dump(FILE* f) const;
69 enum format_t { PEM=SSL_FILETYPE_PEM, DER=SSL_FILETYPE_ASN1, _PKCS12, UNKNOWN };
71 format_t getEncodingFormat(BIO* in) const;
72 string formatToString(format_t format) const;
73 format_t xmlFormatToFormat(const XMLCh* format_xml) const;
76 string m_keypath,m_keypass;
77 vector<X509*> m_certs;
78 vector<XSECCryptoX509*> m_xseccerts;
81 saml::IPlugIn* FileCredResolverFactory(const DOMElement* e)
83 return new FileResolver(e);
86 FileResolver::FileResolver(const DOMElement* e)
89 xmltooling::NDC ndc("FileResolver");
91 Category& log=Category::getInstance(XMLPROVIDERS_LOGCAT".CredResolvers");
97 const DOMElement* root=e;
98 e=saml::XML::getFirstChildElement(root,::XML::CREDS_NS,SHIB_L(Key));
101 // Get raw format attrib value, but defer processing til later since may need to
102 // determine format dynamically, and we need the Path for that.
103 const XMLCh* format_xml=e->getAttributeNS(NULL,SHIB_L(format));
105 const XMLCh* password=e->getAttributeNS(NULL,SHIB_L(password));
107 auto_ptr_char kp(password);
111 e=saml::XML::getFirstChildElement(e,::XML::CREDS_NS,SHIB_L(Path));
112 if (e && e->hasChildNodes()) {
113 const XMLCh* s=e->getFirstChild()->getNodeValue();
114 auto_ptr_char kpath(s);
116 struct _stat stat_buf;
117 if (_stat(kpath.get(), &stat_buf) != 0)
119 struct stat stat_buf;
120 if (stat(kpath.get(), &stat_buf) != 0)
123 log.error("key file (%s) can't be opened", kpath.get());
124 throw IOException("FileResolver can't access key file ($1)",params(1,kpath.get()));
126 m_keypath=kpath.get();
129 log.error("Path element missing inside Key element");
130 throw IOException("FileResolver can't access key file, no Path element specified.");
133 // Determine the key encoding format dynamically, if not explicitly specified
135 if (format_xml && *format_xml) {
136 format = xmlFormatToFormat(format_xml);
137 if (format != UNKNOWN) {
138 m_keyformat = format;
141 auto_ptr_char unknown(format_xml);
142 log.error("Configuration specifies unknown key encoding format (%s)", unknown.get());
143 throw IOException("FileResolver configuration contains unknown key encoding format ($1)",params(1,unknown.get()));
147 in=BIO_new(BIO_s_file_internal());
148 if (in && BIO_read_filename(in,m_keypath.c_str())>0) {
149 m_keyformat = getEncodingFormat(in);
150 log.debug("Key encoding format for (%s) dynamically resolved as (%s)", m_keypath.c_str(), formatToString(m_keyformat).c_str());
153 log.error("Key file (%s) can't be read to determine encoding format", m_keypath.c_str());
154 throw IOException("FileResolver can't read key file ($1) to determine encoding format",params(1,m_keypath.c_str()));
162 log.error("Error determining key encoding format");
168 // Check for Certificate
169 e=saml::XML::getFirstChildElement(root,::XML::CREDS_NS,SHIB_L(Certificate));
172 auto_ptr_char certpass(e->getAttributeNS(NULL,SHIB_L(password)));
174 DOMElement* ep=saml::XML::getFirstChildElement(e,::XML::CREDS_NS,SHIB_L(Path));
175 if (!ep || !ep->hasChildNodes()) {
176 log.error("Path element missing inside Certificate element");
177 throw IOException("FileResolver can't access certificate file, missing Path element.");
180 auto_ptr_char certpath(ep->getFirstChild()->getNodeValue());
181 const XMLCh* format_xml=e->getAttributeNS(NULL,SHIB_L(format));
182 if (format_xml && *format_xml) {
183 format = xmlFormatToFormat(format_xml);
184 if (format == UNKNOWN) {
185 auto_ptr_char unknown(format_xml);
186 log.error("Configuration specifies unknown certificate encoding format (%s)", unknown.get());
187 throw IOException("FileResolver configuration contains unknown certificate encoding format ($1)",params(1,unknown.get()));
194 in=BIO_new(BIO_s_file_internal());
195 if (in && BIO_read_filename(in,certpath.get())>0) {
196 if (!format_xml || !*format_xml) {
197 // Determine the cert encoding format dynamically, if not explicitly specified
198 format = getEncodingFormat(in);
199 log.debug("Cert encoding format for (%s) dynamically resolved as (%s)", certpath.get(), formatToString(format).c_str());
204 while (x=PEM_read_bio_X509(in,NULL,passwd_callback,const_cast<char*>(certpass.get()))) {
205 m_certs.push_back(x);
210 x=d2i_X509_bio(in,NULL);
212 m_certs.push_back(x);
216 throw IOException("FileResolver unable to load DER certificate from file ($1)",params(1,certpath.get()));
221 p12=d2i_PKCS12_bio(in,NULL);
223 PKCS12_parse(p12, certpass.get(), NULL, &x, NULL);
227 m_certs.push_back(x);
232 throw IOException("FileResolver unable to load PKCS12 certificate from file ($1)",params(1,certpath.get()));
243 throw IOException("FileResolver unable to load certificate(s) from file ($1)",params(1,certpath.get()));
250 if (m_certs.empty()) {
251 throw IOException("FileResolver unable to load any certificate(s)");
254 // Load any extra CA files.
255 DOMNodeList* nlist=e->getElementsByTagNameNS(::XML::CREDS_NS,SHIB_L(CAPath));
256 for (unsigned int i=0; nlist && i<nlist->getLength(); i++) {
257 if (!nlist->item(i)->hasChildNodes())
259 auto_ptr_char capath(static_cast<DOMElement*>(nlist->item(i))->getFirstChild()->getNodeValue());
262 in=BIO_new(BIO_s_file_internal());
263 if (in && BIO_read_filename(in,capath.get())>0) {
264 if (!format_xml || !*format_xml) {
265 // Determine the cert encoding format dynamically, if not explicitly specified
266 format = getEncodingFormat(in);
267 log.debug("Cert encoding format for (%s) dynamically resolved as (%s)", certpath.get(), formatToString(format).c_str());
273 while (x=PEM_read_bio_X509(in,NULL,passwd_callback,const_cast<char*>(certpass.get()))) {
274 m_certs.push_back(x);
279 x=d2i_X509_bio(in,NULL);
281 m_certs.push_back(x);
285 throw IOException("FileResolver unable to load DER CA certificate from file ($1)",params(1,capath.get()));
290 p12 = d2i_PKCS12_bio(in, NULL);
292 PKCS12_parse(p12, certpass.get(), NULL, &x, NULL);
296 m_certs.push_back(x);
301 throw IOException("FileResolver unable to load PKCS12 CA certificate from file ($1)",params(1,capath.get()));
312 log.error("CA file (%s) can't be opened", capath.get());
313 throw IOException("FileResolver can't open CA file ($1)",params(1,capath.get()));
318 for (vector<X509*>::iterator j=m_certs.begin(); j!=m_certs.end(); j++)
323 // Reflect certs over to XSEC form.
324 for (vector<X509*>::iterator j=m_certs.begin(); j!=m_certs.end(); j++)
325 m_xseccerts.push_back(new OpenSSLCryptoX509(*j));
328 FileResolver::~FileResolver()
330 for_each(m_certs.begin(),m_certs.end(),X509_free);
331 for_each(m_xseccerts.begin(),m_xseccerts.end(),xmltooling::cleanup<XSECCryptoX509>());
334 void FileResolver::attach(void* ctx) const
337 saml::NDC ndc("attach");
340 SSL_CTX* ssl_ctx=reinterpret_cast<SSL_CTX*>(ctx);
343 SSL_CTX_set_default_passwd_cb(ssl_ctx, passwd_callback);
344 SSL_CTX_set_default_passwd_cb_userdata(ssl_ctx, const_cast<char*>(m_keypass.c_str()));
347 switch (m_keyformat) {
349 ret=SSL_CTX_use_PrivateKey_file(ssl_ctx, m_keypath.c_str(), m_keyformat);
353 ret=SSL_CTX_use_RSAPrivateKey_file(ssl_ctx, m_keypath.c_str(), m_keyformat);
357 BIO* in=BIO_new(BIO_s_file_internal());
358 if (in && BIO_read_filename(in,m_keypath.c_str())>0) {
360 PKCS12* p12 = d2i_PKCS12_bio(in, NULL);
362 PKCS12_parse(p12, const_cast<char*>(m_keypass.c_str()), &pkey, NULL, NULL);
365 ret=SSL_CTX_use_PrivateKey(ssl_ctx, pkey);
377 throw IOException("Unable to attach private key to SSL context");
381 for (vector<X509*>::const_iterator i=m_certs.begin(); i!=m_certs.end(); i++) {
382 if (i==m_certs.begin()) {
383 if (SSL_CTX_use_certificate(ssl_ctx, *i) != 1) {
385 throw IOException("Unable to attach SP client certificate to SSL context");
389 // When we add certs, they don't get ref counted, so we need to duplicate them.
390 X509* dup = X509_dup(*i);
391 if (SSL_CTX_add_extra_chain_cert(ssl_ctx, dup) != 1) {
394 throw IOException("Unable to attach CA certificate to SSL context");
400 XSECCryptoKey* FileResolver::getKey() const
403 saml::NDC ndc("getKey");
408 BIO* in=BIO_new(BIO_s_file_internal());
409 if (in && BIO_read_filename(in,m_keypath.c_str())>0) {
410 switch (m_keyformat) {
412 pkey=PEM_read_bio_PrivateKey(in, NULL, passwd_callback, const_cast<char*>(m_keypass.c_str()));
416 pkey=d2i_PrivateKey_bio(in, NULL);
420 PKCS12* p12 = d2i_PKCS12_bio(in, NULL);
422 PKCS12_parse(p12, const_cast<char*>(m_keypass.c_str()), &pkey, NULL, NULL);
431 // Now map it to an XSEC wrapper.
433 XSECCryptoKey* ret=NULL;
434 switch (pkey->type) {
436 ret=new OpenSSLCryptoKeyRSA(pkey);
440 ret=new OpenSSLCryptoKeyDSA(pkey);
444 Category::getInstance(XMLPROVIDERS_LOGCAT".CredResolvers").error("unsupported private key type");
452 Category::getInstance(XMLPROVIDERS_LOGCAT".CredResolvers").error("FileResolver unable to load private key from file");
456 void FileResolver::dump(FILE* f) const
460 BIO* in=BIO_new(BIO_s_file_internal());
461 if (in && BIO_read_filename(in,m_keypath.c_str())>0) {
462 if (m_keyformat==DER)
463 rsa=d2i_RSAPrivateKey_bio(in,NULL);
464 else if (m_keyformat==PEM)
465 rsa=PEM_read_bio_RSAPrivateKey(in,NULL,passwd_callback,const_cast<char*>(m_keypass.c_str()));
468 PKCS12* p12 = d2i_PKCS12_bio(in, NULL);
470 PKCS12_parse(p12, const_cast<char*>(m_keypass.c_str()), &pkey, NULL, NULL);
473 fprintf(f,"----- PRIVATE KEY -----\n");
474 if (pkey->type==EVP_PK_RSA)
475 RSA_print_fp(f,pkey->pkey.rsa,0);
476 else if (pkey->type==EVP_PK_DSA)
477 DSA_print_fp(f,pkey->pkey.dsa,0);
483 fprintf(f,"----- PRIVATE KEY -----\n");
484 RSA_print_fp(f,rsa,0);
493 // Dump certificates.
494 for (vector<X509*>::const_iterator i=m_certs.begin(); i!=m_certs.end(); i++) {
495 fprintf(f,"----- CERTIFICATE(S) -----\n");
496 #if (OPENSSL_VERSION_NUMBER > 0x009070000L)
497 X509_print_ex_fp(f,*i,XN_FLAG_SEP_MULTILINE,0);
504 // Used to determine the encoding format of credentials files
505 // dynamically. Supports: PEM, DER, PKCS12.
506 FileResolver::format_t FileResolver::getEncodingFormat(BIO* in) const
511 const int READSIZE = 1;
517 if ( (mark = BIO_tell(in)) < 0 )
518 throw IOException("getEncodingFormat: BIO_tell() can't get the file position");
519 if ( BIO_read(in, buf, READSIZE) <= 0 )
520 throw IOException("getEncodingFormat: BIO_read() can't read from the stream");
521 if ( BIO_seek(in, mark) < 0 )
522 throw IOException("getEncodingFormat: BIO_seek() can't reset the file position");
531 // This is a slight variation of the Java code by Chad La Joie.
533 // Check the first byte of the file. If it's some kind of
534 // DER-encoded structure (including PKCS12), it will begin with ASCII 048.
535 // Otherwise, assume it's PEM.
539 // Here we know it's DER-encoded, now try to parse it as a PKCS12
540 // ASN.1 structure. If it fails, must be another kind of DER-encoded
541 // key/cert structure. A little inefficient...but it works.
542 if ( (p12=d2i_PKCS12_bio(in,NULL)) == NULL ) {
549 if ( BIO_seek(in, mark) < 0 ) {
551 throw IOException("getEncodingFormat: BIO_seek() can't reset the file position");
558 // Convert key/cert format_t types to a human-meaningful string for debug output
559 string FileResolver::formatToString(format_t format) const
573 // Convert key/cert raw XML format attribute (XMLCh[]) to format_t type
574 FileResolver::format_t FileResolver::xmlFormatToFormat(const XMLCh* format_xml) const
576 static const XMLCh cPEM[] = { chLatin_P, chLatin_E, chLatin_M, chNull };
577 static const XMLCh cDER[] = { chLatin_D, chLatin_E, chLatin_R, chNull };
578 static const XMLCh cPKCS12[] = { chLatin_P, chLatin_K, chLatin_C, chLatin_S, chDigit_1, chDigit_2, chNull };
581 if (!XMLString::compareString(format_xml,cPEM))
583 else if (!XMLString::compareString(format_xml,cDER))
585 else if (!XMLString::compareString(format_xml,cPKCS12))