89608d9bc82d8cca6cbaf87fba7624fb8df75a43
[shibboleth/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 #include <algorithm>
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
36 using namespace saml;
37 using namespace shibboleth;
38 using namespace log4cpp;
39 using namespace std;
40
41 // OpenSSL password callback...
42 static int passwd_callback(char* buf, int len, int verify, void* passwd)
43 {
44     if(!verify)
45     {
46         if(passwd && len > strlen(reinterpret_cast<char*>(passwd)))
47         {
48             strcpy(buf,reinterpret_cast<char*>(passwd));
49             return strlen(buf);
50         }
51     }  
52     return 0;
53 }
54
55 // File-based resolver
56
57 class FileResolver : public ICredResolver
58 {
59 public:
60     FileResolver(const DOMElement* e);
61     ~FileResolver();
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;
66     
67 private:
68     enum format_t { PEM=SSL_FILETYPE_PEM, DER=SSL_FILETYPE_ASN1, _PKCS12, UNKNOWN };
69
70     format_t getEncodingFormat(BIO* in) const;
71     string formatToString(format_t format) const;
72     format_t xmlFormatToFormat(const XMLCh* format_xml) const;
73
74     format_t m_keyformat;
75     string m_keypath,m_keypass;
76     vector<X509*> m_certs;
77     vector<XSECCryptoX509*> m_xseccerts;
78 };
79
80 IPlugIn* FileCredResolverFactory(const DOMElement* e)
81 {
82     return new FileResolver(e);
83 }
84
85 FileResolver::FileResolver(const DOMElement* e)
86 {
87 #ifdef _DEBUG
88     saml::NDC ndc("FileResolver");
89 #endif
90     Category& log=Category::getInstance(XMLPROVIDERS_LOGCAT".CredResolvers");
91
92     format_t format;
93     BIO* in = NULL;
94     
95     // Move to Key
96     const DOMElement* root=e;
97     e=saml::XML::getFirstChildElement(root,::XML::CREDS_NS,SHIB_L(Key));
98     if (e) {
99
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));
103             
104         const XMLCh* password=e->getAttributeNS(NULL,SHIB_L(password));
105         if (password) {
106             auto_ptr_char kp(password);
107             m_keypass=kp.get();
108         }
109         
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);
114 #ifdef WIN32
115             struct _stat stat_buf;
116             if (_stat(kpath.get(), &stat_buf) != 0)
117 #else
118             struct stat stat_buf;
119             if (stat(kpath.get(), &stat_buf) != 0)
120 #endif
121             {
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()));
124             }
125             m_keypath=kpath.get();
126         }
127         else {
128             log.error("Path element missing inside Key element");
129             throw CredentialException("FileResolver can't access key file, no Path element specified.");
130         }
131
132         // Determine the key encoding format dynamically, if not explicitly specified
133         try {
134             if (format_xml && *format_xml) {
135                 format = xmlFormatToFormat(format_xml);
136                 if (format != UNKNOWN) {
137                     m_keyformat = format;
138                 }
139                 else {
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()));
143                 }
144             }
145             else {
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());
150                 }
151                 else {
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()));
154                 }
155                 if (in)
156                     BIO_free(in);
157                 in = NULL;    
158             }
159         }
160         catch (...) {
161             log.error("Error determining key encoding format");
162             throw;
163         }
164
165     }
166         
167     // Check for Certificate
168     e=saml::XML::getFirstChildElement(root,::XML::CREDS_NS,SHIB_L(Certificate));
169     if (!e)
170         return;
171     auto_ptr_char certpass(e->getAttributeNS(NULL,SHIB_L(password)));
172     
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.");
177     }
178     
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()));
187         }
188     }
189     
190     try {
191         X509* x=NULL;
192         PKCS12* p12=NULL;
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());
199             }
200
201             switch(format) {
202                 case PEM:
203                     while (x=PEM_read_bio_X509(in,NULL,passwd_callback,const_cast<char*>(certpass.get()))) {
204                            m_certs.push_back(x);
205                     }
206                     break;
207                                 
208                 case DER:
209                     x=d2i_X509_bio(in,NULL);
210                     if (x)
211                         m_certs.push_back(x);
212                     else {
213                         log_openssl();
214                         BIO_free(in);
215                         throw CredentialException("FileResolver unable to load DER certificate from file ($1)",params(1,certpath.get()));
216                     }
217                     break;
218
219                 case _PKCS12:
220                     p12=d2i_PKCS12_bio(in,NULL);
221                     if (p12) {
222                         PKCS12_parse(p12, certpass.get(), NULL, &x, NULL);
223                         PKCS12_free(p12);
224                     }
225                     if (x) {
226                         m_certs.push_back(x);
227                         x=NULL;
228                     } else {
229                         log_openssl();
230                         BIO_free(in);
231                         throw CredentialException("FileResolver unable to load PKCS12 certificate from file ($1)",params(1,certpath.get()));
232                     }
233                     break;
234             } // end switch
235
236         } else {
237             log_openssl();
238             if (in) {
239                 BIO_free(in);
240                 in=NULL;
241             }
242             throw CredentialException("FileResolver unable to load certificate(s) from file ($1)",params(1,certpath.get()));
243         }
244         if (in) {
245             BIO_free(in);
246             in=NULL;
247         }
248
249         if (m_certs.empty()) {
250             throw CredentialException("FileResolver unable to load any certificate(s)");
251         }
252
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())
257                 continue;
258             auto_ptr_char capath(static_cast<DOMElement*>(nlist->item(i))->getFirstChild()->getNodeValue());
259             x=NULL;
260             p12=NULL;
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());
267                 }
268
269                 switch (format)
270                 {
271                     case PEM:
272                         while (x=PEM_read_bio_X509(in,NULL,passwd_callback,const_cast<char*>(certpass.get()))) {
273                             m_certs.push_back(x);
274                         }
275                         break;
276
277                     case DER:
278                         x=d2i_X509_bio(in,NULL);
279                         if (x)
280                             m_certs.push_back(x);
281                         else {
282                             log_openssl();
283                             BIO_free(in);
284                             throw CredentialException("FileResolver unable to load DER CA certificate from file ($1)",params(1,capath.get()));
285                         }
286                         break;
287
288                     case _PKCS12:
289                         p12 = d2i_PKCS12_bio(in, NULL);
290                         if (p12) {
291                             PKCS12_parse(p12, certpass.get(), NULL, &x, NULL);
292                             PKCS12_free(p12);
293                         }
294                         if (x) {
295                             m_certs.push_back(x);
296                             x=NULL;
297                         } else {
298                             log_openssl();
299                             BIO_free(in);
300                             throw CredentialException("FileResolver unable to load PKCS12 CA certificate from file ($1)",params(1,capath.get()));
301                         }
302                         break;
303                 } //end switch
304
305                 BIO_free(in);
306
307             } else {
308                 if (in)
309                     BIO_free(in);
310                 log_openssl();
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()));
313             }
314         }
315     }
316     catch (...) {
317         for (vector<X509*>::iterator j=m_certs.begin(); j!=m_certs.end(); j++)
318             X509_free(*j);
319         throw;
320     }
321
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));
325 }
326
327 FileResolver::~FileResolver()
328 {
329     for_each(m_certs.begin(),m_certs.end(),X509_free);
330     for_each(m_xseccerts.begin(),m_xseccerts.end(),xmltooling::cleanup<XSECCryptoX509>());
331 }
332
333 void FileResolver::attach(void* ctx) const
334 {
335 #ifdef _DEBUG
336     saml::NDC ndc("attach");
337 #endif
338     
339     SSL_CTX* ssl_ctx=reinterpret_cast<SSL_CTX*>(ctx);
340
341     // Attach key.
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()));
344
345     int ret=0;
346     switch (m_keyformat) {
347         case PEM:
348             ret=SSL_CTX_use_PrivateKey_file(ssl_ctx, m_keypath.c_str(), m_keyformat);
349             break;
350             
351         case DER:
352             ret=SSL_CTX_use_RSAPrivateKey_file(ssl_ctx, m_keypath.c_str(), m_keyformat);
353             break;
354             
355         default: {
356             BIO* in=BIO_new(BIO_s_file_internal());
357             if (in && BIO_read_filename(in,m_keypath.c_str())>0) {
358                 EVP_PKEY* pkey=NULL;
359                 PKCS12* p12 = d2i_PKCS12_bio(in, NULL);
360                 if (p12) {
361                     PKCS12_parse(p12, const_cast<char*>(m_keypass.c_str()), &pkey, NULL, NULL);
362                     PKCS12_free(p12);
363                     if (pkey) {
364                         ret=SSL_CTX_use_PrivateKey(ssl_ctx, pkey);
365                         EVP_PKEY_free(pkey);
366                     }
367                 }
368             }
369             if (in)
370                 BIO_free(in);
371         }
372     }
373     
374     if (ret!=1) {
375         log_openssl();
376         throw CredentialException("Unable to attach private key to SSL context");
377     }
378
379     // Attach certs.
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) {
383                 log_openssl();
384                 throw CredentialException("Unable to attach SP client certificate to SSL context");
385             }
386         }
387         else {
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) {
391                 X509_free(dup);
392                 log_openssl();
393                 throw CredentialException("Unable to attach CA certificate to SSL context");
394             }
395         }
396     }
397 }
398
399 XSECCryptoKey* FileResolver::getKey() const
400 {
401 #ifdef _DEBUG
402     saml::NDC ndc("getKey");
403 #endif
404
405     // Get a EVP_PKEY.
406     EVP_PKEY* pkey=NULL;
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) {
410             case PEM:
411                 pkey=PEM_read_bio_PrivateKey(in, NULL, passwd_callback, const_cast<char*>(m_keypass.c_str()));
412                 break;
413             
414             case DER:
415                 pkey=d2i_PrivateKey_bio(in, NULL);
416                 break;
417                 
418             default: {
419                 PKCS12* p12 = d2i_PKCS12_bio(in, NULL);
420                 if (p12) {
421                     PKCS12_parse(p12, const_cast<char*>(m_keypass.c_str()), &pkey, NULL, NULL);
422                     PKCS12_free(p12);
423                 }
424             }
425         }
426     }
427     if (in)
428         BIO_free(in);
429     
430     // Now map it to an XSEC wrapper.
431     if (pkey) {
432         XSECCryptoKey* ret=NULL;
433         switch (pkey->type) {
434             case EVP_PKEY_RSA:
435                 ret=new OpenSSLCryptoKeyRSA(pkey);
436                 break;
437                 
438             case EVP_PKEY_DSA:
439                 ret=new OpenSSLCryptoKeyDSA(pkey);
440                 break;
441             
442             default:
443                 Category::getInstance(XMLPROVIDERS_LOGCAT".CredResolvers").error("unsupported private key type");
444         }
445         EVP_PKEY_free(pkey);
446         if (ret)
447             return ret;
448     }
449
450     log_openssl();
451     Category::getInstance(XMLPROVIDERS_LOGCAT".CredResolvers").error("FileResolver unable to load private key from file");
452     return NULL;
453 }
454
455 void FileResolver::dump(FILE* f) const
456 {
457     // Dump private key.
458     RSA* rsa=NULL;
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()));
465         else {
466             EVP_PKEY* pkey=NULL;
467             PKCS12* p12 = d2i_PKCS12_bio(in, NULL);
468             if (p12) {
469                 PKCS12_parse(p12, const_cast<char*>(m_keypass.c_str()), &pkey, NULL, NULL);
470                 PKCS12_free(p12);
471                 if (pkey) {
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);
477                     EVP_PKEY_free(pkey);
478                 }
479             }
480         }
481         if (rsa) {
482             fprintf(f,"----- PRIVATE KEY -----\n");
483             RSA_print_fp(f,rsa,0);
484             RSA_free(rsa);
485         }
486     }
487     if (in) {
488         BIO_free(in);
489         in=NULL;
490     }
491     
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);
497 #else
498         X509_print_fp(f,*i);
499 #endif
500     }
501 }
502
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
506 {
507     PKCS12* p12 = NULL;
508     format_t format;
509
510     const int READSIZE = 1;
511     char buf[READSIZE];
512     char b1;
513     int mark;
514
515     try {
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");
522     }
523     catch (...) {
524         log_openssl();
525         throw;
526     }
527
528     b1 = buf[0];
529
530     // This is a slight variation of the Java code by Chad La Joie.
531     //
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.
535     if (b1 !=  48) {
536         format = PEM;
537     } else {
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 ) {
542             format = DER;
543         } else {
544             format = _PKCS12;
545         }
546         if (p12)
547             PKCS12_free(p12);    
548         if ( BIO_seek(in, mark) < 0 ) {
549             log_openssl();
550             throw CredentialException("getEncodingFormat: BIO_seek() can't reset the file position");
551         }
552     }
553
554     return format;
555 }
556
557 // Convert key/cert format_t types to a human-meaningful string for debug output
558 string FileResolver::formatToString(format_t format) const
559 {
560     switch(format) {
561         case PEM:
562             return "PEM";
563         case DER:
564             return "DER";
565         case _PKCS12:
566             return "PKCS12";
567         default:
568             return "UNKNOWN";
569     }
570 }
571
572 // Convert key/cert raw XML format attribute (XMLCh[]) to format_t type
573 FileResolver::format_t FileResolver::xmlFormatToFormat(const XMLCh* format_xml) const
574 {
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 };
578     format_t format;
579
580     if (!XMLString::compareString(format_xml,cPEM))
581         format=PEM;
582     else if (!XMLString::compareString(format_xml,cDER))
583         format=DER;
584     else if (!XMLString::compareString(format_xml,cPKCS12))
585         format=_PKCS12;
586     else
587         format=UNKNOWN;
588
589     return format;
590 }