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