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 <xsec/enc/OpenSSL/OpenSSLCryptoX509.hpp>
32 #include <xsec/enc/OpenSSL/OpenSSLCryptoKeyRSA.hpp>
33 #include <xsec/enc/OpenSSL/OpenSSLCryptoKeyDSA.hpp>
37 using namespace shibboleth;
38 using namespace xmlproviders::logging;
40 // OpenSSL password callback...
41 static int passwd_callback(char* buf, int len, int verify, void* passwd)
45 if(passwd && len > strlen(reinterpret_cast<char*>(passwd)))
47 strcpy(buf,reinterpret_cast<char*>(passwd));
54 // File-based resolver
56 class FileResolver : public ICredResolver
59 FileResolver(const DOMElement* e);
61 virtual void attach(void* ctx) const;
62 virtual XSECCryptoKey* getKey() const;
63 virtual saml::Iterator<XSECCryptoX509*> getCertificates() const { return m_xseccerts; }
64 virtual void dump(FILE* f) const;
67 enum format_t { PEM=SSL_FILETYPE_PEM, DER=SSL_FILETYPE_ASN1, _PKCS12, UNKNOWN };
69 format_t getEncodingFormat(BIO* in) const;
70 string formatToString(format_t format) const;
71 format_t xmlFormatToFormat(const XMLCh* format_xml) const;
74 string m_keypath,m_keypass;
75 vector<X509*> m_certs;
76 vector<XSECCryptoX509*> m_xseccerts;
79 IPlugIn* FileCredResolverFactory(const DOMElement* e)
81 return new FileResolver(e);
84 FileResolver::FileResolver(const DOMElement* e)
87 saml::NDC ndc("FileResolver");
89 Category& log=Category::getInstance(XMLPROVIDERS_LOGCAT".CredResolvers");
91 format_t format=UNKNOWN;
95 const DOMElement* root=e;
96 e=saml::XML::getFirstChildElement(root,::XML::CREDS_NS,SHIB_L(Key));
99 // Get raw format attrib value, but defer processing til later since may need to
100 // determine format dynamically, and we need the Path for that.
101 const XMLCh* format_xml=e->getAttributeNS(NULL,SHIB_L(format));
103 const XMLCh* password=e->getAttributeNS(NULL,SHIB_L(password));
105 auto_ptr_char kp(password);
109 e=saml::XML::getFirstChildElement(e,::XML::CREDS_NS,SHIB_L(Path));
110 if (e && e->hasChildNodes()) {
111 const XMLCh* s=e->getFirstChild()->getNodeValue();
112 auto_ptr_char kpath(s);
114 struct _stat stat_buf;
115 if (_stat(kpath.get(), &stat_buf) != 0)
117 struct stat stat_buf;
118 if (stat(kpath.get(), &stat_buf) != 0)
121 log.error("key file (%s) can't be opened", kpath.get());
122 throw CredentialException("FileResolver can't access key file ($1)",params(1,kpath.get()));
124 m_keypath=kpath.get();
127 log.error("Path element missing inside Key element");
128 throw CredentialException("FileResolver can't access key file, no Path element specified.");
131 // Determine the key encoding format dynamically, if not explicitly specified
133 if (format_xml && *format_xml) {
134 format = xmlFormatToFormat(format_xml);
135 if (format != UNKNOWN) {
136 m_keyformat = format;
139 auto_ptr_char unknown(format_xml);
140 log.error("Configuration specifies unknown key encoding format (%s)", unknown.get());
141 throw CredentialException("FileResolver configuration contains unknown key encoding format ($1)",params(1,unknown.get()));
145 in=BIO_new(BIO_s_file_internal());
146 if (in && BIO_read_filename(in,m_keypath.c_str())>0) {
147 m_keyformat = getEncodingFormat(in);
148 log.debug("Key encoding format for (%s) dynamically resolved as (%s)", m_keypath.c_str(), formatToString(m_keyformat).c_str());
151 log.error("Key file (%s) can't be read to determine encoding format", m_keypath.c_str());
152 throw CredentialException("FileResolver can't read key file ($1) to determine encoding format",params(1,m_keypath.c_str()));
160 log.error("Error determining key encoding format");
166 throw CredentialException("FileResolver can't access key file, no Key element specified.");
169 // Check for Certificate
170 e=saml::XML::getFirstChildElement(root,::XML::CREDS_NS,SHIB_L(Certificate));
173 auto_ptr_char certpass(e->getAttributeNS(NULL,SHIB_L(password)));
175 DOMElement* ep=saml::XML::getFirstChildElement(e,::XML::CREDS_NS,SHIB_L(Path));
176 if (!ep || !ep->hasChildNodes()) {
177 log.error("Path element missing inside Certificate element");
178 throw CredentialException("FileResolver can't access certificate file, missing Path element.");
181 auto_ptr_char certpath(ep->getFirstChild()->getNodeValue());
182 const XMLCh* format_xml=e->getAttributeNS(NULL,SHIB_L(format));
183 if (format_xml && *format_xml) {
184 format = xmlFormatToFormat(format_xml);
185 if (format == UNKNOWN) {
186 auto_ptr_char unknown(format_xml);
187 log.error("Configuration specifies unknown certificate encoding format (%s)", unknown.get());
188 throw CredentialException("FileResolver configuration contains unknown certificate encoding format ($1)",params(1,unknown.get()));
195 in=BIO_new(BIO_s_file_internal());
196 if (in && BIO_read_filename(in,certpath.get())>0) {
197 if (!format_xml || !*format_xml) {
198 // Determine the cert encoding format dynamically, if not explicitly specified
199 format = getEncodingFormat(in);
200 log.debug("Cert encoding format for (%s) dynamically resolved as (%s)", certpath.get(), formatToString(format).c_str());
205 while (x=PEM_read_bio_X509(in,NULL,passwd_callback,const_cast<char*>(certpass.get()))) {
206 m_certs.push_back(x);
211 x=d2i_X509_bio(in,NULL);
213 m_certs.push_back(x);
217 throw CredentialException("FileResolver unable to load DER certificate from file ($1)",params(1,certpath.get()));
222 p12=d2i_PKCS12_bio(in,NULL);
224 PKCS12_parse(p12, certpass.get(), NULL, &x, NULL);
228 m_certs.push_back(x);
233 throw CredentialException("FileResolver unable to load PKCS12 certificate from file ($1)",params(1,certpath.get()));
246 throw CredentialException("FileResolver unable to load certificate(s) from file ($1)",params(1,certpath.get()));
253 if (m_certs.empty()) {
254 throw CredentialException("FileResolver unable to load any certificate(s)");
257 // Load any extra CA files.
258 DOMNodeList* nlist=e->getElementsByTagNameNS(::XML::CREDS_NS,SHIB_L(CAPath));
259 for (XMLSize_t i=0; nlist && i<nlist->getLength(); i++) {
260 if (!nlist->item(i)->hasChildNodes())
262 auto_ptr_char capath(static_cast<DOMElement*>(nlist->item(i))->getFirstChild()->getNodeValue());
265 in=BIO_new(BIO_s_file_internal());
266 if (in && BIO_read_filename(in,capath.get())>0) {
267 if (!format_xml || !*format_xml) {
268 // Determine the cert encoding format dynamically, if not explicitly specified
269 format = getEncodingFormat(in);
270 log.debug("Cert encoding format for (%s) dynamically resolved as (%s)", certpath.get(), formatToString(format).c_str());
276 while (x=PEM_read_bio_X509(in,NULL,passwd_callback,const_cast<char*>(certpass.get()))) {
277 m_certs.push_back(x);
282 x=d2i_X509_bio(in,NULL);
284 m_certs.push_back(x);
288 throw CredentialException("FileResolver unable to load DER CA certificate from file ($1)",params(1,capath.get()));
293 p12 = d2i_PKCS12_bio(in, NULL);
295 PKCS12_parse(p12, certpass.get(), NULL, &x, NULL);
299 m_certs.push_back(x);
304 throw CredentialException("FileResolver unable to load PKCS12 CA certificate from file ($1)",params(1,capath.get()));
317 log.error("CA file (%s) can't be opened", capath.get());
318 throw CredentialException("FileResolver can't open CA file ($1)",params(1,capath.get()));
323 for (vector<X509*>::iterator j=m_certs.begin(); j!=m_certs.end(); j++)
328 // Reflect certs over to XSEC form.
329 for (vector<X509*>::iterator j=m_certs.begin(); j!=m_certs.end(); j++)
330 m_xseccerts.push_back(new OpenSSLCryptoX509(*j));
333 FileResolver::~FileResolver()
335 for (vector<X509*>::iterator i=m_certs.begin(); i!=m_certs.end(); i++)
337 for (vector<XSECCryptoX509*>::iterator j=m_xseccerts.begin(); j!=m_xseccerts.end(); j++)
341 void FileResolver::attach(void* ctx) const
344 saml::NDC ndc("attach");
347 SSL_CTX* ssl_ctx=reinterpret_cast<SSL_CTX*>(ctx);
350 SSL_CTX_set_default_passwd_cb(ssl_ctx, passwd_callback);
351 SSL_CTX_set_default_passwd_cb_userdata(ssl_ctx, const_cast<char*>(m_keypass.c_str()));
354 switch (m_keyformat) {
356 ret=SSL_CTX_use_PrivateKey_file(ssl_ctx, m_keypath.c_str(), m_keyformat);
360 ret=SSL_CTX_use_RSAPrivateKey_file(ssl_ctx, m_keypath.c_str(), m_keyformat);
364 BIO* in=BIO_new(BIO_s_file_internal());
365 if (in && BIO_read_filename(in,m_keypath.c_str())>0) {
367 PKCS12* p12 = d2i_PKCS12_bio(in, NULL);
369 PKCS12_parse(p12, const_cast<char*>(m_keypass.c_str()), &pkey, NULL, NULL);
372 ret=SSL_CTX_use_PrivateKey(ssl_ctx, pkey);
384 throw CredentialException("Unable to attach private key to SSL context");
388 for (vector<X509*>::const_iterator i=m_certs.begin(); i!=m_certs.end(); i++) {
389 if (i==m_certs.begin()) {
390 if (SSL_CTX_use_certificate(ssl_ctx, *i) != 1) {
392 throw CredentialException("Unable to attach SP client certificate to SSL context");
396 // When we add certs, they don't get ref counted, so we need to duplicate them.
397 X509* dup = X509_dup(*i);
398 if (SSL_CTX_add_extra_chain_cert(ssl_ctx, dup) != 1) {
401 throw CredentialException("Unable to attach CA certificate to SSL context");
407 XSECCryptoKey* FileResolver::getKey() const
410 saml::NDC ndc("getKey");
415 BIO* in=BIO_new(BIO_s_file_internal());
416 if (in && BIO_read_filename(in,m_keypath.c_str())>0) {
417 switch (m_keyformat) {
419 pkey=PEM_read_bio_PrivateKey(in, NULL, passwd_callback, const_cast<char*>(m_keypass.c_str()));
423 pkey=d2i_PrivateKey_bio(in, NULL);
427 PKCS12* p12 = d2i_PKCS12_bio(in, NULL);
429 PKCS12_parse(p12, const_cast<char*>(m_keypass.c_str()), &pkey, NULL, NULL);
438 // Now map it to an XSEC wrapper.
440 XSECCryptoKey* ret=NULL;
441 switch (pkey->type) {
443 ret=new OpenSSLCryptoKeyRSA(pkey);
447 ret=new OpenSSLCryptoKeyDSA(pkey);
451 Category::getInstance(XMLPROVIDERS_LOGCAT".CredResolvers").error("unsupported private key type");
459 Category::getInstance(XMLPROVIDERS_LOGCAT".CredResolvers").error("FileResolver unable to load private key from file");
463 void FileResolver::dump(FILE* f) const
467 BIO* in=BIO_new(BIO_s_file_internal());
468 if (in && BIO_read_filename(in,m_keypath.c_str())>0) {
469 if (m_keyformat==DER)
470 rsa=d2i_RSAPrivateKey_bio(in,NULL);
471 else if (m_keyformat==PEM)
472 rsa=PEM_read_bio_RSAPrivateKey(in,NULL,passwd_callback,const_cast<char*>(m_keypass.c_str()));
475 PKCS12* p12 = d2i_PKCS12_bio(in, NULL);
477 PKCS12_parse(p12, const_cast<char*>(m_keypass.c_str()), &pkey, NULL, NULL);
480 fprintf(f,"----- PRIVATE KEY -----\n");
481 if (pkey->type==EVP_PK_RSA)
482 RSA_print_fp(f,pkey->pkey.rsa,0);
483 else if (pkey->type==EVP_PK_DSA)
484 DSA_print_fp(f,pkey->pkey.dsa,0);
490 fprintf(f,"----- PRIVATE KEY -----\n");
491 RSA_print_fp(f,rsa,0);
500 // Dump certificates.
501 for (vector<X509*>::const_iterator i=m_certs.begin(); i!=m_certs.end(); i++) {
502 fprintf(f,"----- CERTIFICATE(S) -----\n");
503 #if (OPENSSL_VERSION_NUMBER > 0x009070000L)
504 X509_print_ex_fp(f,*i,XN_FLAG_SEP_MULTILINE,0);
511 // Used to determine the encoding format of credentials files
512 // dynamically. Supports: PEM, DER, PKCS12.
513 FileResolver::format_t FileResolver::getEncodingFormat(BIO* in) const
518 const int READSIZE = 1;
524 if ( (mark = BIO_tell(in)) < 0 )
525 throw CredentialException("getEncodingFormat: BIO_tell() can't get the file position");
526 if ( BIO_read(in, buf, READSIZE) <= 0 )
527 throw CredentialException("getEncodingFormat: BIO_read() can't read from the stream");
528 if ( BIO_seek(in, mark) < 0 )
529 throw CredentialException("getEncodingFormat: BIO_seek() can't reset the file position");
538 // This is a slight variation of the Java code by Chad La Joie.
540 // Check the first byte of the file. If it's some kind of
541 // DER-encoded structure (including PKCS12), it will begin with ASCII 048.
542 // Otherwise, assume it's PEM.
546 // Here we know it's DER-encoded, now try to parse it as a PKCS12
547 // ASN.1 structure. If it fails, must be another kind of DER-encoded
548 // key/cert structure. A little inefficient...but it works.
549 if ( (p12=d2i_PKCS12_bio(in,NULL)) == NULL ) {
556 if ( BIO_seek(in, mark) < 0 ) {
558 throw CredentialException("getEncodingFormat: BIO_seek() can't reset the file position");
565 // Convert key/cert format_t types to a human-meaningful string for debug output
566 string FileResolver::formatToString(format_t format) const
580 // Convert key/cert raw XML format attribute (XMLCh[]) to format_t type
581 FileResolver::format_t FileResolver::xmlFormatToFormat(const XMLCh* format_xml) const
583 static const XMLCh cPEM[] = { chLatin_P, chLatin_E, chLatin_M, chNull };
584 static const XMLCh cDER[] = { chLatin_D, chLatin_E, chLatin_R, chNull };
585 static const XMLCh cPKCS12[] = { chLatin_P, chLatin_K, chLatin_C, chLatin_S, chDigit_1, chDigit_2, chNull };
588 if (!XMLString::compareString(format_xml,cPEM))
590 else if (!XMLString::compareString(format_xml,cDER))
592 else if (!XMLString::compareString(format_xml,cPKCS12))