Error out when cert file is missing.
[shibboleth/cpp-sp.git] / xmlproviders / CredResolvers.cpp
1 /* 
2  * The Shibboleth License, Version 1. 
3  * Copyright (c) 2002 
4  * University Corporation for Advanced Internet Development, Inc. 
5  * All rights reserved
6  * 
7  * 
8  * Redistribution and use in source and binary forms, with or without 
9  * modification, are permitted provided that the following conditions are met:
10  * 
11  * Redistributions of source code must retain the above copyright notice, this 
12  * list of conditions and the following disclaimer.
13  * 
14  * Redistributions in binary form must reproduce the above copyright notice, 
15  * this list of conditions and the following disclaimer in the documentation 
16  * and/or other materials provided with the distribution, if any, must include 
17  * the following acknowledgment: "This product includes software developed by 
18  * the University Corporation for Advanced Internet Development 
19  * <http://www.ucaid.edu>Internet2 Project. Alternately, this acknowledegement 
20  * may appear in the software itself, if and wherever such third-party 
21  * acknowledgments normally appear.
22  * 
23  * Neither the name of Shibboleth nor the names of its contributors, nor 
24  * Internet2, nor the University Corporation for Advanced Internet Development, 
25  * Inc., nor UCAID may be used to endorse or promote products derived from this 
26  * software without specific prior written permission. For written permission, 
27  * please contact shibboleth@shibboleth.org
28  * 
29  * Products derived from this software may not be called Shibboleth, Internet2, 
30  * UCAID, or the University Corporation for Advanced Internet Development, nor 
31  * may Shibboleth appear in their name, without prior written permission of the 
32  * University Corporation for Advanced Internet Development.
33  * 
34  * 
35  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
36  * AND WITH ALL FAULTS. ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 
37  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 
38  * PARTICULAR PURPOSE, AND NON-INFRINGEMENT ARE DISCLAIMED AND THE ENTIRE RISK 
39  * OF SATISFACTORY QUALITY, PERFORMANCE, ACCURACY, AND EFFORT IS WITH LICENSEE. 
40  * IN NO EVENT SHALL THE COPYRIGHT OWNER, CONTRIBUTORS OR THE UNIVERSITY 
41  * CORPORATION FOR ADVANCED INTERNET DEVELOPMENT, INC. BE LIABLE FOR ANY DIRECT, 
42  * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 
43  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 
44  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 
45  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 
46  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 
47  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
48  */
49
50 /* CredResolvers.cpp - implementations of the ICredResolver interface
51
52    Scott Cantor
53    9/27/02
54
55    $History:$
56 */
57
58 #include "internal.h"
59
60 #include <sys/types.h>
61 #include <sys/stat.h>
62
63 #include <openssl/pkcs12.h>
64 #include <log4cpp/Category.hh>
65 #include <xsec/enc/OpenSSL/OpenSSLCryptoX509.hpp>
66 #include <xsec/enc/OpenSSL/OpenSSLCryptoKeyRSA.hpp>
67 #include <xsec/enc/OpenSSL/OpenSSLCryptoKeyDSA.hpp>
68
69 using namespace saml;
70 using namespace shibboleth;
71 using namespace log4cpp;
72 using namespace std;
73
74 // OpenSSL password callback...
75 int passwd_callback(char* buf, int len, int verify, void* passwd)
76 {
77     if(!verify)
78     {
79         if(passwd && len > strlen(reinterpret_cast<char*>(passwd)))
80         {
81             strcpy(buf,reinterpret_cast<char*>(passwd));
82             return strlen(buf);
83         }
84     }  
85     return 0;
86 }
87
88 // File-based resolver
89
90 class FileResolver : public ICredResolver
91 {
92 public:
93     FileResolver(const DOMElement* e);
94     ~FileResolver();
95     virtual void attach(void* ctx) const;
96     virtual XSECCryptoKey* getKey() const;
97     virtual saml::Iterator<XSECCryptoX509*> getCertificates() const { return m_xseccerts; }
98     virtual void dump(FILE* f) const;
99     
100 protected:
101     enum format_t { DER=SSL_FILETYPE_ASN1, PEM=SSL_FILETYPE_PEM, _PKCS12 };
102     format_t m_keyformat;
103     string m_keypath,m_keypass;
104     vector<X509*> m_certs;
105     vector<XSECCryptoX509*> m_xseccerts;
106 };
107
108 IPlugIn* FileCredResolverFactory(const DOMElement* e)
109 {
110     return new FileResolver(e);
111 }
112
113 FileResolver::FileResolver(const DOMElement* e)
114 {
115 #ifdef _DEBUG
116     saml::NDC ndc("FileResolver");
117 #endif
118     static const XMLCh cPEM[] = { chLatin_P, chLatin_E, chLatin_M, chNull };
119     static const XMLCh cDER[] = { chLatin_D, chLatin_E, chLatin_R, chNull };
120     
121     // Move to Key
122     e=saml::XML::getFirstChildElement(e);
123     const XMLCh* format=e->getAttributeNS(NULL,SHIB_L(format));
124     if (!format || !*format || !XMLString::compareString(format,cPEM))
125         m_keyformat=PEM;
126     else if (!XMLString::compareString(format,cDER))
127         m_keyformat=DER;
128     else
129         m_keyformat=_PKCS12;
130         
131     const XMLCh* password=e->getAttributeNS(NULL,SHIB_L(password));
132     if (password) {
133         auto_ptr_char kp(password);
134         m_keypass=kp.get();
135     }
136     
137     const XMLCh* s=saml::XML::getFirstChildElement(e,::XML::CREDS_NS,SHIB_L(Path))->getFirstChild()->getNodeValue();
138     auto_ptr_char kpath(s);
139     
140 #ifdef WIN32
141     struct _stat stat_buf;
142     if (_stat(kpath.get(), &stat_buf) != 0)
143 #else
144     struct stat stat_buf;
145     if (stat(kpath.get(), &stat_buf) != 0)
146 #endif
147     {
148         Category::getInstance(XMLPROVIDERS_LOGCAT".CredResolvers").error("key file '%s' can't be opened", kpath.get());
149         throw CredentialException("FileResolver() can't access key file");
150     }
151     m_keypath=kpath.get();
152     
153     // Check for Certificate
154     e=saml::XML::getNextSiblingElement(e);
155     password=e->getAttributeNS(NULL,SHIB_L(password));
156     auto_ptr_char certpass(password);
157     s=saml::XML::getFirstChildElement(e,::XML::CREDS_NS,SHIB_L(Path))->getFirstChild()->getNodeValue();
158     auto_ptr_char certpath(s);
159
160     try {
161         X509* x=NULL;
162         BIO* in=BIO_new(BIO_s_file_internal());
163         if (in && BIO_read_filename(in,certpath.get())>0) {
164             format=e->getAttributeNS(NULL,SHIB_L(format));
165             if (!format || !*format || !XMLString::compareString(format,cPEM)) {
166                 while (x=PEM_read_bio_X509(in,NULL,passwd_callback,const_cast<char*>(certpass.get()))) {
167                     m_certs.push_back(x);
168                 }
169             }
170             else if (!XMLString::compareString(format,cDER)) {
171                 x=d2i_X509_bio(in,NULL);
172                 if (x)
173                     m_certs.push_back(x);
174                 else {
175                     log_openssl();
176                     BIO_free(in);
177                     throw CredentialException("FileResolver() unable to load DER certificate from file");
178                 }
179             }
180             else {
181                 PKCS12* p12=d2i_PKCS12_bio(in,NULL);
182                 if (p12) {
183                     PKCS12_parse(p12, certpass.get(), NULL, &x, NULL);
184                     PKCS12_free(p12);
185                 }
186                 if (x) {
187                     m_certs.push_back(x);
188                     x=NULL;
189                 }
190                 else {
191                     log_openssl();
192                     BIO_free(in);
193                     throw CredentialException("FileResolver() unable to load PKCS12 certificate from file");
194                 }
195             }
196         }
197         else {
198             log_openssl();
199             if (in) {
200                 BIO_free(in);
201                 in=NULL;
202             }
203             throw CredentialException("FileResolver() unable to load PEM certificate(s) from file");
204         }
205         if (in) {
206             BIO_free(in);
207             in=NULL;
208         }
209
210         // Load any extra CA files.
211         DOMNodeList* nlist=e->getElementsByTagNameNS(::XML::CREDS_NS,SHIB_L(CAPath));
212         for (int i=0; nlist && i<nlist->getLength(); i++) {
213             s=static_cast<DOMElement*>(nlist->item(i))->getFirstChild()->getNodeValue();
214             auto_ptr_char capath(s);
215             x=NULL;
216             in=BIO_new(BIO_s_file_internal());
217             if (in && BIO_read_filename(in,capath.get())>0) {
218                 if (!format || !*format || !XMLString::compareString(format,cPEM)) {
219                     while (x=PEM_read_bio_X509(in,NULL,passwd_callback,const_cast<char*>(certpass.get()))) {
220                         m_certs.push_back(x);
221                     }
222                 }
223                 else if (!XMLString::compareString(format,cDER)) {
224                     x=d2i_X509_bio(in,NULL);
225                     if (x)
226                         m_certs.push_back(x);
227                     else {
228                         log_openssl();
229                         BIO_free(in);
230                         throw CredentialException("FileResolver() unable to load DER CA certificate from file");
231                     }
232                 }
233                 else {
234                     PKCS12* p12 = d2i_PKCS12_bio(in, NULL);
235                     if (p12) {
236                         PKCS12_parse(p12, certpass.get(), NULL, &x, NULL);
237                         PKCS12_free(p12);
238                     }
239                     if (x) {
240                         m_certs.push_back(x);
241                         x=NULL;
242                     }
243                     else {
244                         log_openssl();
245                         BIO_free(in);
246                         throw CredentialException("FileResolver() unable to load PKCS12 CA certificate from file");
247                     }
248                 }
249                 BIO_free(in);
250             }
251             else {
252                 if (in)
253                     BIO_free(in);
254                 log_openssl();
255                 Category::getInstance(XMLPROVIDERS_LOGCAT".CredResolvers").error("CA file '%s' can't be opened", capath.get());
256                 throw CredentialException("FileResolver() can't open CA file");
257             }
258         }
259     }
260     catch (...) {
261         for (vector<X509*>::iterator j=m_certs.begin(); j!=m_certs.end(); j++)
262             X509_free(*j);
263         throw;
264     }
265
266     // Reflect certs over to XSEC form.
267     for (vector<X509*>::iterator j=m_certs.begin(); j!=m_certs.end(); j++)
268         m_xseccerts.push_back(new OpenSSLCryptoX509(*j));
269 }
270
271 FileResolver::~FileResolver()
272 {
273     for (vector<X509*>::iterator i=m_certs.begin(); i!=m_certs.end(); i++)
274         X509_free(*i);
275     for (vector<XSECCryptoX509*>::iterator j=m_xseccerts.begin(); j!=m_xseccerts.end(); j++)
276         delete (*j);
277 }
278
279 void FileResolver::attach(void* ctx) const
280 {
281 #ifdef _DEBUG
282     saml::NDC ndc("FileResolver");
283 #endif
284     
285     SSL_CTX* ssl_ctx=reinterpret_cast<SSL_CTX*>(ctx);
286
287     // Attach key.
288     SSL_CTX_set_default_passwd_cb(ssl_ctx, passwd_callback);
289     SSL_CTX_set_default_passwd_cb_userdata(ssl_ctx, const_cast<char*>(m_keypass.c_str()));
290
291     int ret=0;
292     switch (m_keyformat)
293     {
294         case PEM:
295             ret=SSL_CTX_use_PrivateKey_file(ssl_ctx, m_keypath.c_str(), m_keyformat);
296             break;
297             
298         case DER:
299             ret=SSL_CTX_use_RSAPrivateKey_file(ssl_ctx, m_keypath.c_str(), m_keyformat);
300             break;
301             
302         default: {
303             BIO* in=BIO_new(BIO_s_file_internal());
304             if (in && BIO_read_filename(in,m_keypath.c_str())>0) {
305                 EVP_PKEY* pkey=NULL;
306                 PKCS12* p12 = d2i_PKCS12_bio(in, NULL);
307                 if (p12) {
308                     PKCS12_parse(p12, const_cast<char*>(m_keypass.c_str()), &pkey, NULL, NULL);
309                     PKCS12_free(p12);
310                     if (pkey) {
311                         ret=SSL_CTX_use_PrivateKey(ssl_ctx, pkey);
312                         EVP_PKEY_free(pkey);
313                     }
314                 }
315             }
316             if (in)
317                 BIO_free(in);
318         }
319     }
320     
321     if (ret!=1) {
322         log_openssl();
323         throw CredentialException("FileResolver::attach() unable to set private key");
324     }
325
326     // Attach certs.
327     for (vector<X509*>::const_iterator i=m_certs.begin(); i!=m_certs.end(); i++) {
328         if (i==m_certs.begin()) {
329             if (SSL_CTX_use_certificate(ssl_ctx, *i) != 1) {
330                 log_openssl();
331                 throw CredentialException("FileResolver::attach() unable to set EE certificate in context");
332             }
333         }
334         else {
335             // When we add certs, they don't get ref counted, so we need to duplicate them.
336             X509* dup = X509_dup(*i);
337             if (SSL_CTX_add_extra_chain_cert(ssl_ctx, dup) != 1) {
338                 X509_free(dup);
339                 log_openssl();
340                 throw CredentialException("FileResolver::attach() unable to add CA certificate to context");
341             }
342         }
343     }
344 }
345
346 XSECCryptoKey* FileResolver::getKey() const
347 {
348     // Get a EVP_PKEY.
349     EVP_PKEY* pkey=NULL;
350     BIO* in=BIO_new(BIO_s_file_internal());
351     if (in && BIO_read_filename(in,m_keypath.c_str())>0) {
352         switch (m_keyformat)
353         {
354             case PEM:
355                 pkey=PEM_read_bio_PrivateKey(in, NULL, passwd_callback, const_cast<char*>(m_keypass.c_str()));
356                 break;
357             
358             case DER:
359                 pkey=d2i_PrivateKey_bio(in, NULL);
360                 break;
361                 
362             default: {
363                 PKCS12* p12 = d2i_PKCS12_bio(in, NULL);
364                 if (p12) {
365                     PKCS12_parse(p12, const_cast<char*>(m_keypass.c_str()), &pkey, NULL, NULL);
366                     PKCS12_free(p12);
367                 }
368             }
369         }
370     }
371     if (in)
372         BIO_free(in);
373     
374     // Now map it to an XSEC wrapper.
375     if (pkey) {
376         XSECCryptoKey* ret=NULL;
377         switch (pkey->type)
378         {
379             case EVP_PKEY_RSA:
380                 ret=new OpenSSLCryptoKeyRSA(pkey);
381                 break;
382                 
383             case EVP_PKEY_DSA:
384                 ret=new OpenSSLCryptoKeyDSA(pkey);
385                 break;
386             
387             default:
388                 saml::NDC ndc("FileResolver");
389                 Category::getInstance(XMLPROVIDERS_LOGCAT".CredResolvers").error("unsupported private key type");
390         }
391         EVP_PKEY_free(pkey);
392         if (ret)
393             return ret;
394     }
395
396     saml::NDC ndc("FileResolver");
397     log_openssl();
398     Category::getInstance(XMLPROVIDERS_LOGCAT".CredResolvers").error("FileResolver::getKey() unable to load private key from file");
399     return NULL;
400 }
401
402 void FileResolver::dump(FILE* f) const
403 {
404     // Dump private key.
405     RSA* rsa=NULL;
406     BIO* in=BIO_new(BIO_s_file_internal());
407     if (in && BIO_read_filename(in,m_keypath.c_str())>0) {
408         if (m_keyformat==DER)
409             rsa=d2i_RSAPrivateKey_bio(in,NULL);
410         else if (m_keyformat==PEM)
411             rsa=PEM_read_bio_RSAPrivateKey(in,NULL,passwd_callback,const_cast<char*>(m_keypass.c_str()));
412         else {
413             EVP_PKEY* pkey=NULL;
414             PKCS12* p12 = d2i_PKCS12_bio(in, NULL);
415             if (p12) {
416                 PKCS12_parse(p12, const_cast<char*>(m_keypass.c_str()), &pkey, NULL, NULL);
417                 PKCS12_free(p12);
418                 if (pkey) {
419                     fprintf(f,"----- PRIVATE KEY -----\n");
420                     if (pkey->type==EVP_PK_RSA)
421                         RSA_print_fp(f,pkey->pkey.rsa,0);
422                     else if (pkey->type==EVP_PK_DSA)
423                         DSA_print_fp(f,pkey->pkey.dsa,0);
424                     EVP_PKEY_free(pkey);
425                 }
426             }
427         }
428         if (rsa) {
429             fprintf(f,"----- PRIVATE KEY -----\n");
430             RSA_print_fp(f,rsa,0);
431             RSA_free(rsa);
432         }
433     }
434     if (in) {
435         BIO_free(in);
436         in=NULL;
437     }
438     
439     // Dump certificates.
440     for (vector<X509*>::const_iterator i=m_certs.begin(); i!=m_certs.end(); i++) {
441         fprintf(f,"----- CERTIFICATE(S) -----\n");
442 #if (OPENSSL_VERSION_NUMBER > 0x009070000L)
443         X509_print_ex_fp(f,*i,XN_FLAG_SEP_MULTILINE,0);
444 #else
445         X509_print_fp(f,*i);
446 #endif
447     }
448 }