Reduce catch all handlers, and make them optional.
[shibboleth/cpp-sp.git] / xmlproviders / CredResolvers.cpp
1 /*
2  *  Copyright 2001-2005 Internet2
3  * 
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
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16
17 /* CredResolvers.cpp - implementations of the ICredResolver interface
18
19    Scott Cantor
20    9/27/02
21
22    $History:$
23 */
24
25 #include "internal.h"
26
27 #include <sys/types.h>
28 #include <sys/stat.h>
29
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>
34
35 using namespace std;
36 using namespace saml;
37 using namespace shibboleth;
38 using namespace xmlproviders::logging;
39
40 // OpenSSL password callback...
41 static int passwd_callback(char* buf, int len, int verify, void* passwd)
42 {
43     if(!verify)
44     {
45         if(passwd && len > strlen(reinterpret_cast<char*>(passwd)))
46         {
47             strcpy(buf,reinterpret_cast<char*>(passwd));
48             return strlen(buf);
49         }
50     }  
51     return 0;
52 }
53
54 // File-based resolver
55
56 class FileResolver : public ICredResolver
57 {
58 public:
59     FileResolver(const DOMElement* e);
60     ~FileResolver();
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;
65     
66 private:
67     enum format_t { PEM=SSL_FILETYPE_PEM, DER=SSL_FILETYPE_ASN1, _PKCS12, UNKNOWN };
68
69     format_t getEncodingFormat(BIO* in) const;
70     string formatToString(format_t format) const;
71     format_t xmlFormatToFormat(const XMLCh* format_xml) const;
72
73     format_t m_keyformat;
74     string m_keypath,m_keypass;
75     vector<X509*> m_certs;
76     vector<XSECCryptoX509*> m_xseccerts;
77 };
78
79 IPlugIn* FileCredResolverFactory(const DOMElement* e)
80 {
81     return new FileResolver(e);
82 }
83
84 FileResolver::FileResolver(const DOMElement* e)
85 {
86 #ifdef _DEBUG
87     saml::NDC ndc("FileResolver");
88 #endif
89     Category& log=Category::getInstance(XMLPROVIDERS_LOGCAT".CredResolvers");
90
91     format_t format=UNKNOWN;
92     BIO* in = NULL;
93     
94     // Move to Key
95     const DOMElement* root=e;
96     e=saml::XML::getFirstChildElement(root,::XML::CREDS_NS,SHIB_L(Key));
97     if (e) {
98
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));
102             
103         const XMLCh* password=e->getAttributeNS(NULL,SHIB_L(password));
104         if (password) {
105             auto_ptr_char kp(password);
106             m_keypass=kp.get();
107         }
108         
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);
113 #ifdef WIN32
114             struct _stat stat_buf;
115             if (_stat(kpath.get(), &stat_buf) != 0)
116 #else
117             struct stat stat_buf;
118             if (stat(kpath.get(), &stat_buf) != 0)
119 #endif
120             {
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()));
123             }
124             m_keypath=kpath.get();
125         }
126         else {
127             log.error("Path element missing inside Key element");
128             throw CredentialException("FileResolver can't access key file, no Path element specified.");
129         }
130
131         // Determine the key encoding format dynamically, if not explicitly specified
132         try {
133             if (format_xml && *format_xml) {
134                 format = xmlFormatToFormat(format_xml);
135                 if (format != UNKNOWN) {
136                     m_keyformat = format;
137                 }
138                 else {
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()));
142                 }
143             }
144             else {
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());
149                 }
150                 else {
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()));
153                 }
154                 if (in)
155                     BIO_free(in);
156                 in = NULL;    
157             }
158         }
159         catch (...) {
160             log.error("Error determining key encoding format");
161             throw;
162         }
163
164     }
165     else {
166         throw CredentialException("FileResolver can't access key file, no Key element specified.");
167     }
168         
169     // Check for Certificate
170     e=saml::XML::getFirstChildElement(root,::XML::CREDS_NS,SHIB_L(Certificate));
171     if (!e)
172         return;
173     auto_ptr_char certpass(e->getAttributeNS(NULL,SHIB_L(password)));
174     
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.");
179     }
180     
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()));
189         }
190     }
191     
192     try {
193         X509* x=NULL;
194         PKCS12* p12=NULL;
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());
201             }
202
203             switch(format) {
204                 case PEM:
205                     while (x=PEM_read_bio_X509(in,NULL,passwd_callback,const_cast<char*>(certpass.get()))) {
206                            m_certs.push_back(x);
207                     }
208                     break;
209                                 
210                 case DER:
211                     x=d2i_X509_bio(in,NULL);
212                     if (x)
213                         m_certs.push_back(x);
214                     else {
215                         log_openssl();
216                         BIO_free(in);
217                         throw CredentialException("FileResolver unable to load DER certificate from file ($1)",params(1,certpath.get()));
218                     }
219                     break;
220
221                 case _PKCS12:
222                     p12=d2i_PKCS12_bio(in,NULL);
223                     if (p12) {
224                         PKCS12_parse(p12, certpass.get(), NULL, &x, NULL);
225                         PKCS12_free(p12);
226                     }
227                     if (x) {
228                         m_certs.push_back(x);
229                         x=NULL;
230                     } else {
231                         log_openssl();
232                         BIO_free(in);
233                         throw CredentialException("FileResolver unable to load PKCS12 certificate from file ($1)",params(1,certpath.get()));
234                     }
235                     break;
236                 case UNKNOWN:
237                     break;
238             } // end switch
239
240         } else {
241             log_openssl();
242             if (in) {
243                 BIO_free(in);
244                 in=NULL;
245             }
246             throw CredentialException("FileResolver unable to load certificate(s) from file ($1)",params(1,certpath.get()));
247         }
248         if (in) {
249             BIO_free(in);
250             in=NULL;
251         }
252
253         if (m_certs.empty()) {
254             throw CredentialException("FileResolver unable to load any certificate(s)");
255         }
256
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())
261                 continue;
262             auto_ptr_char capath(static_cast<DOMElement*>(nlist->item(i))->getFirstChild()->getNodeValue());
263             x=NULL;
264             p12=NULL;
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());
271                 }
272
273                 switch (format)
274                 {
275                     case PEM:
276                         while (x=PEM_read_bio_X509(in,NULL,passwd_callback,const_cast<char*>(certpass.get()))) {
277                             m_certs.push_back(x);
278                         }
279                         break;
280
281                     case DER:
282                         x=d2i_X509_bio(in,NULL);
283                         if (x)
284                             m_certs.push_back(x);
285                         else {
286                             log_openssl();
287                             BIO_free(in);
288                             throw CredentialException("FileResolver unable to load DER CA certificate from file ($1)",params(1,capath.get()));
289                         }
290                         break;
291
292                     case _PKCS12:
293                         p12 = d2i_PKCS12_bio(in, NULL);
294                         if (p12) {
295                             PKCS12_parse(p12, certpass.get(), NULL, &x, NULL);
296                             PKCS12_free(p12);
297                         }
298                         if (x) {
299                             m_certs.push_back(x);
300                             x=NULL;
301                         } else {
302                             log_openssl();
303                             BIO_free(in);
304                             throw CredentialException("FileResolver unable to load PKCS12 CA certificate from file ($1)",params(1,capath.get()));
305                         }
306                         break;
307                     case UNKNOWN:
308                         break;
309                 } //end switch
310
311                 BIO_free(in);
312
313             } else {
314                 if (in)
315                     BIO_free(in);
316                 log_openssl();
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()));
319             }
320         }
321     }
322     catch (...) {
323         for (vector<X509*>::iterator j=m_certs.begin(); j!=m_certs.end(); j++)
324             X509_free(*j);
325         throw;
326     }
327
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));
331 }
332
333 FileResolver::~FileResolver()
334 {
335     for (vector<X509*>::iterator i=m_certs.begin(); i!=m_certs.end(); i++)
336         X509_free(*i);
337     for (vector<XSECCryptoX509*>::iterator j=m_xseccerts.begin(); j!=m_xseccerts.end(); j++)
338         delete (*j);
339 }
340
341 void FileResolver::attach(void* ctx) const
342 {
343 #ifdef _DEBUG
344     saml::NDC ndc("attach");
345 #endif
346     
347     SSL_CTX* ssl_ctx=reinterpret_cast<SSL_CTX*>(ctx);
348
349     // Attach key.
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()));
352
353     int ret=0;
354     switch (m_keyformat) {
355         case PEM:
356             ret=SSL_CTX_use_PrivateKey_file(ssl_ctx, m_keypath.c_str(), m_keyformat);
357             break;
358             
359         case DER:
360             ret=SSL_CTX_use_RSAPrivateKey_file(ssl_ctx, m_keypath.c_str(), m_keyformat);
361             break;
362             
363         default: {
364             BIO* in=BIO_new(BIO_s_file_internal());
365             if (in && BIO_read_filename(in,m_keypath.c_str())>0) {
366                 EVP_PKEY* pkey=NULL;
367                 PKCS12* p12 = d2i_PKCS12_bio(in, NULL);
368                 if (p12) {
369                     PKCS12_parse(p12, const_cast<char*>(m_keypass.c_str()), &pkey, NULL, NULL);
370                     PKCS12_free(p12);
371                     if (pkey) {
372                         ret=SSL_CTX_use_PrivateKey(ssl_ctx, pkey);
373                         EVP_PKEY_free(pkey);
374                     }
375                 }
376             }
377             if (in)
378                 BIO_free(in);
379         }
380     }
381     
382     if (ret!=1) {
383         log_openssl();
384         throw CredentialException("Unable to attach private key to SSL context");
385     }
386
387     // Attach certs.
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) {
391                 log_openssl();
392                 throw CredentialException("Unable to attach SP client certificate to SSL context");
393             }
394         }
395         else {
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) {
399                 X509_free(dup);
400                 log_openssl();
401                 throw CredentialException("Unable to attach CA certificate to SSL context");
402             }
403         }
404     }
405 }
406
407 XSECCryptoKey* FileResolver::getKey() const
408 {
409 #ifdef _DEBUG
410     saml::NDC ndc("getKey");
411 #endif
412
413     // Get a EVP_PKEY.
414     EVP_PKEY* pkey=NULL;
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) {
418             case PEM:
419                 pkey=PEM_read_bio_PrivateKey(in, NULL, passwd_callback, const_cast<char*>(m_keypass.c_str()));
420                 break;
421             
422             case DER:
423                 pkey=d2i_PrivateKey_bio(in, NULL);
424                 break;
425                 
426             default: {
427                 PKCS12* p12 = d2i_PKCS12_bio(in, NULL);
428                 if (p12) {
429                     PKCS12_parse(p12, const_cast<char*>(m_keypass.c_str()), &pkey, NULL, NULL);
430                     PKCS12_free(p12);
431                 }
432             }
433         }
434     }
435     if (in)
436         BIO_free(in);
437     
438     // Now map it to an XSEC wrapper.
439     if (pkey) {
440         XSECCryptoKey* ret=NULL;
441         switch (pkey->type) {
442             case EVP_PKEY_RSA:
443                 ret=new OpenSSLCryptoKeyRSA(pkey);
444                 break;
445                 
446             case EVP_PKEY_DSA:
447                 ret=new OpenSSLCryptoKeyDSA(pkey);
448                 break;
449             
450             default:
451                 Category::getInstance(XMLPROVIDERS_LOGCAT".CredResolvers").error("unsupported private key type");
452         }
453         EVP_PKEY_free(pkey);
454         if (ret)
455             return ret;
456     }
457
458     log_openssl();
459     Category::getInstance(XMLPROVIDERS_LOGCAT".CredResolvers").error("FileResolver unable to load private key from file");
460     return NULL;
461 }
462
463 void FileResolver::dump(FILE* f) const
464 {
465     // Dump private key.
466     RSA* rsa=NULL;
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()));
473         else {
474             EVP_PKEY* pkey=NULL;
475             PKCS12* p12 = d2i_PKCS12_bio(in, NULL);
476             if (p12) {
477                 PKCS12_parse(p12, const_cast<char*>(m_keypass.c_str()), &pkey, NULL, NULL);
478                 PKCS12_free(p12);
479                 if (pkey) {
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);
485                     EVP_PKEY_free(pkey);
486                 }
487             }
488         }
489         if (rsa) {
490             fprintf(f,"----- PRIVATE KEY -----\n");
491             RSA_print_fp(f,rsa,0);
492             RSA_free(rsa);
493         }
494     }
495     if (in) {
496         BIO_free(in);
497         in=NULL;
498     }
499     
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);
505 #else
506         X509_print_fp(f,*i);
507 #endif
508     }
509 }
510
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
514 {
515     PKCS12* p12 = NULL;
516     format_t format;
517
518     const int READSIZE = 1;
519     char buf[READSIZE];
520     char b1;
521     int mark;
522
523     try {
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");
530     }
531     catch (...) {
532         log_openssl();
533         throw;
534     }
535
536     b1 = buf[0];
537
538     // This is a slight variation of the Java code by Chad La Joie.
539     //
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.
543     if (b1 !=  48) {
544         format = PEM;
545     } else {
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 ) {
550             format = DER;
551         } else {
552             format = _PKCS12;
553         }
554         if (p12)
555             PKCS12_free(p12);    
556         if ( BIO_seek(in, mark) < 0 ) {
557             log_openssl();
558             throw CredentialException("getEncodingFormat: BIO_seek() can't reset the file position");
559         }
560     }
561
562     return format;
563 }
564
565 // Convert key/cert format_t types to a human-meaningful string for debug output
566 string FileResolver::formatToString(format_t format) const
567 {
568     switch(format) {
569         case PEM:
570             return "PEM";
571         case DER:
572             return "DER";
573         case _PKCS12:
574             return "PKCS12";
575         default:
576             return "UNKNOWN";
577     }
578 }
579
580 // Convert key/cert raw XML format attribute (XMLCh[]) to format_t type
581 FileResolver::format_t FileResolver::xmlFormatToFormat(const XMLCh* format_xml) const
582 {
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 };
586     format_t format;
587
588     if (!XMLString::compareString(format_xml,cPEM))
589         format=PEM;
590     else if (!XMLString::compareString(format_xml,cDER))
591         format=DER;
592     else if (!XMLString::compareString(format_xml,cPKCS12))
593         format=_PKCS12;
594     else
595         format=UNKNOWN;
596
597     return format;
598 }