New plugin implementations of Shibboleth APIs
[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 <openssl/pkcs12.h>
61 #include <log4cpp/Category.hh>
62 #include <xsec/enc/OpenSSL/OpenSSLCryptoX509.hpp>
63 #include <xsec/enc/OpenSSL/OpenSSLCryptoKeyRSA.hpp>
64 #include <xsec/enc/OpenSSL/OpenSSLCryptoKeyDSA.hpp>
65
66 using namespace saml;
67 using namespace shibboleth;
68 using namespace log4cpp;
69 using namespace std;
70
71 // OpenSSL password callback...
72 int passwd_callback(char* buf, int len, int verify, void* passwd)
73 {
74     if(!verify)
75     {
76         if(passwd && len > strlen(reinterpret_cast<char*>(passwd)))
77         {
78             strcpy(buf,reinterpret_cast<char*>(passwd));
79             return strlen(buf);
80         }
81     }  
82     return 0;
83 }
84
85 // File-based resolver
86
87 class FileResolver : public ICredResolver
88 {
89 public:
90     FileResolver(const DOMElement* e);
91     ~FileResolver();
92     virtual void attach(void* ctx) const;
93     virtual XSECCryptoKey* getKey() const;
94     virtual saml::Iterator<XSECCryptoX509*> getCertificates() const { return m_xseccerts; }
95     virtual void dump(FILE* f) const;
96     
97 protected:
98     enum format_t { DER=SSL_FILETYPE_ASN1, PEM=SSL_FILETYPE_PEM, _PKCS12 };
99     format_t m_keyformat;
100     string m_keypath,m_keypass;
101     vector<X509*> m_certs;
102     vector<XSECCryptoX509*> m_xseccerts;
103 };
104
105 extern "C" ICredResolver* FileCredResolverFactory(const DOMElement* e)
106 {
107     return new FileResolver(e);
108 }
109
110 FileResolver::FileResolver(const DOMElement* e)
111 {
112     saml::NDC ndc("FileResolver");
113     static const XMLCh cPEM[] = { chLatin_P, chLatin_E, chLatin_M, chNull };
114     static const XMLCh cDER[] = { chLatin_D, chLatin_E, chLatin_R, chNull };
115     
116     // Move to Key
117     e=saml::XML::getFirstChildElement(e);
118     const XMLCh* format=e->getAttributeNS(NULL,SHIB_L(format));
119     if (!format || !*format || !XMLString::compareString(format,cPEM))
120         m_keyformat=PEM;
121     else if (!XMLString::compareString(format,cDER))
122         m_keyformat=DER;
123     else
124         m_keyformat=_PKCS12;
125         
126     const XMLCh* password=e->getAttributeNS(NULL,SHIB_L(password));
127     if (password) {
128         auto_ptr_char kp(password);
129         m_keypass=kp.get();
130     }
131     
132     const XMLCh* s=saml::XML::getFirstChildElement(e,::XML::CREDS_NS,SHIB_L(Path))->getFirstChild()->getNodeValue();
133     auto_ptr_char kpath(s);
134     
135 #ifdef WIN32
136     struct _stat stat_buf;
137     if (_stat(kpath.get(), &stat_buf) != 0)
138 #else
139     struct stat stat_buf;
140     if (stat(kpath.get(), &stat_buf) != 0)
141 #endif
142     {
143         Category::getInstance(XMLPROVIDERS_LOGCAT".CredResolvers").error("key file '%s' can't be opened", kpath.get());
144         throw CredentialException("FileResolver() can't access key file");
145     }
146     m_keypath=kpath.get();
147     
148     // Check for Certificate
149     e=saml::XML::getNextSiblingElement(e);
150     password=e->getAttributeNS(NULL,SHIB_L(password));
151     auto_ptr_char certpass(password);
152     s=saml::XML::getFirstChildElement(e,::XML::CREDS_NS,SHIB_L(Path))->getFirstChild()->getNodeValue();
153     auto_ptr_char certpath(s);
154
155     try {
156         X509* x=NULL;
157         BIO* in=BIO_new(BIO_s_file_internal());
158         if (in && BIO_read_filename(in,certpath.get())>0) {
159             format=e->getAttributeNS(NULL,SHIB_L(format));
160             if (!format || !*format || !XMLString::compareString(format,cPEM)) {
161                 while (x=PEM_read_bio_X509(in,NULL,passwd_callback,const_cast<char*>(certpass.get()))) {
162                     m_certs.push_back(x);
163                 }
164             }
165             else if (!XMLString::compareString(format,cDER)) {
166                 x=d2i_X509_bio(in,NULL);
167                 if (x)
168                     m_certs.push_back(x);
169                 else {
170                     log_openssl();
171                     BIO_free(in);
172                     throw CredentialException("FileResolver() unable to load DER certificate from file");
173                 }
174             }
175             else {
176                 PKCS12* p12=d2i_PKCS12_bio(in,NULL);
177                 if (p12) {
178                     PKCS12_parse(p12, certpass.get(), NULL, &x, NULL);
179                     PKCS12_free(p12);
180                 }
181                 if (x) {
182                     m_certs.push_back(x);
183                     x=NULL;
184                 }
185                 else {
186                     log_openssl();
187                     BIO_free(in);
188                     throw CredentialException("FileResolver() unable to load PKCS12 certificate from file");
189                 }
190             }
191         }
192         if (in) {
193             BIO_free(in);
194             in=NULL;
195         }
196
197         // Load any extra CA files.
198         DOMNodeList* nlist=e->getElementsByTagNameNS(::XML::CREDS_NS,SHIB_L(CAPath));
199         for (int i=0; nlist && i<nlist->getLength(); i++) {
200             s=static_cast<DOMElement*>(nlist->item(i))->getFirstChild()->getNodeValue();
201             auto_ptr_char capath(s);
202             x=NULL;
203             in=BIO_new(BIO_s_file_internal());
204             if (in && BIO_read_filename(in,capath.get())>0) {
205                 if (!format || !*format || !XMLString::compareString(format,cPEM)) {
206                     while (x=PEM_read_bio_X509(in,NULL,passwd_callback,const_cast<char*>(certpass.get()))) {
207                         m_certs.push_back(x);
208                     }
209                 }
210                 else if (!XMLString::compareString(format,cDER)) {
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 CA certificate from file");
218                     }
219                 }
220                 else {
221                     PKCS12* 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                     }
230                     else {
231                         log_openssl();
232                         BIO_free(in);
233                         throw CredentialException("FileResolver() unable to load PKCS12 CA certificate from file");
234                     }
235                 }
236                 BIO_free(in);
237             }
238             else {
239                 if (in)
240                     BIO_free(in);
241                 log_openssl();
242                 Category::getInstance(XMLPROVIDERS_LOGCAT".CredResolvers").error("CA file '%s' can't be opened", capath.get());
243                 throw CredentialException("FileResolver() can't open CA file");
244             }
245         }
246     }
247     catch (...) {
248         for (vector<X509*>::iterator j=m_certs.begin(); j!=m_certs.end(); j++)
249             X509_free(*j);
250         throw;
251     }
252
253     // Reflect certs over to XSEC form.
254     for (vector<X509*>::iterator j=m_certs.begin(); j!=m_certs.end(); j++)
255         m_xseccerts.push_back(new OpenSSLCryptoX509(*j));
256 }
257
258 FileResolver::~FileResolver()
259 {
260     for (vector<X509*>::iterator i=m_certs.begin(); i!=m_certs.end(); i++)
261         X509_free(*i);
262     for (vector<XSECCryptoX509*>::iterator j=m_xseccerts.begin(); j!=m_xseccerts.end(); j++)
263         delete (*j);
264 }
265
266 void FileResolver::attach(void* ctx) const
267 {
268     saml::NDC ndc("FileResolver");
269     
270     SSL_CTX* ssl_ctx=reinterpret_cast<SSL_CTX*>(ctx);
271
272     // Attach key.
273     SSL_CTX_set_default_passwd_cb(ssl_ctx, passwd_callback);
274     SSL_CTX_set_default_passwd_cb_userdata(ssl_ctx, const_cast<char*>(m_keypass.c_str()));
275
276     int ret=0;
277     switch (m_keyformat)
278     {
279         case PEM:
280             ret=SSL_CTX_use_PrivateKey_file(ssl_ctx, m_keypath.c_str(), m_keyformat);
281             break;
282             
283         case DER:
284             ret=SSL_CTX_use_RSAPrivateKey_file(ssl_ctx, m_keypath.c_str(), m_keyformat);
285             break;
286             
287         default: {
288             BIO* in=BIO_new(BIO_s_file_internal());
289             if (in && BIO_read_filename(in,m_keypath.c_str())>0) {
290                 EVP_PKEY* pkey=NULL;
291                 PKCS12* p12 = d2i_PKCS12_bio(in, NULL);
292                 if (p12) {
293                     PKCS12_parse(p12, const_cast<char*>(m_keypass.c_str()), &pkey, NULL, NULL);
294                     PKCS12_free(p12);
295                     if (pkey) {
296                         ret=SSL_CTX_use_PrivateKey(ssl_ctx, pkey);
297                         EVP_PKEY_free(pkey);
298                     }
299                 }
300             }
301             if (in)
302                 BIO_free(in);
303         }
304     }
305     
306     if (ret!=1) {
307         log_openssl();
308         throw CredentialException("FileResolver::attach() unable to set private key");
309     }
310
311     // Attach certs.
312     for (vector<X509*>::const_iterator i=m_certs.begin(); i!=m_certs.end(); i++) {
313         if (i==m_certs.begin()) {
314             if (SSL_CTX_use_certificate(ssl_ctx, *i)!=1) {
315                 log_openssl();
316                 throw CredentialException("FileResolver::attach() unable to set EE certificate in context");
317             }
318         }
319         else {
320             // When we add certs, they don't get ref counted, so we need to duplicate them.
321             X509* dup = X509_dup(*i);
322             if (SSL_CTX_add_extra_chain_cert(ssl_ctx, dup) != 0) {
323                 X509_free(dup);
324                 log_openssl();
325                 throw CredentialException("FileResolver::attach() unable to add CA certificate to context");
326             }
327         }
328     }
329 }
330
331 XSECCryptoKey* FileResolver::getKey() const
332 {
333     // Get a EVP_PKEY.
334     EVP_PKEY* pkey=NULL;
335     BIO* in=BIO_new(BIO_s_file_internal());
336     if (in && BIO_read_filename(in,m_keypath.c_str())>0) {
337         switch (m_keyformat)
338         {
339             case PEM:
340                 pkey=PEM_read_bio_PrivateKey(in, NULL, passwd_callback, const_cast<char*>(m_keypass.c_str()));
341                 break;
342             
343             case DER:
344                 pkey=d2i_PrivateKey_bio(in, NULL);
345                 break;
346                 
347             default: {
348                 PKCS12* p12 = d2i_PKCS12_bio(in, NULL);
349                 if (p12) {
350                     PKCS12_parse(p12, const_cast<char*>(m_keypass.c_str()), &pkey, NULL, NULL);
351                     PKCS12_free(p12);
352                 }
353             }
354         }
355     }
356     if (in)
357         BIO_free(in);
358     
359     // Now map it to an XSEC wrapper.
360     if (pkey) {
361         XSECCryptoKey* ret=NULL;
362         switch (pkey->type)
363         {
364             case EVP_PKEY_RSA:
365                 ret=new OpenSSLCryptoKeyRSA(pkey);
366                 break;
367                 
368             case EVP_PKEY_DSA:
369                 ret=new OpenSSLCryptoKeyDSA(pkey);
370                 break;
371             
372             default:
373                 saml::NDC ndc("FileResolver");
374                 Category::getInstance(XMLPROVIDERS_LOGCAT".CredResolvers").error("unsupported private key type");
375         }
376         EVP_PKEY_free(pkey);
377         if (ret)
378             return ret;
379     }
380
381     saml::NDC ndc("FileResolver");
382     log_openssl();
383     Category::getInstance(XMLPROVIDERS_LOGCAT".CredResolvers").error("FileResolver::getKey() unable to load private key from file");
384     return NULL;
385 }
386
387 void FileResolver::dump(FILE* f) const
388 {
389     // Dump private key.
390     RSA* rsa=NULL;
391     BIO* in=BIO_new(BIO_s_file_internal());
392     if (in && BIO_read_filename(in,m_keypath.c_str())>0) {
393         if (m_keyformat==DER)
394             rsa=d2i_RSAPrivateKey_bio(in,NULL);
395         else if (m_keyformat==PEM)
396             rsa=PEM_read_bio_RSAPrivateKey(in,NULL,passwd_callback,const_cast<char*>(m_keypass.c_str()));
397         else {
398             EVP_PKEY* pkey=NULL;
399             PKCS12* p12 = d2i_PKCS12_bio(in, NULL);
400             if (p12) {
401                 PKCS12_parse(p12, const_cast<char*>(m_keypass.c_str()), &pkey, NULL, NULL);
402                 PKCS12_free(p12);
403                 if (pkey) {
404                     fprintf(f,"----- PRIVATE KEY -----\n");
405                     if (pkey->type==EVP_PK_RSA)
406                         RSA_print_fp(f,pkey->pkey.rsa,0);
407                     else if (pkey->type==EVP_PK_DSA)
408                         DSA_print_fp(f,pkey->pkey.dsa,0);
409                     EVP_PKEY_free(pkey);
410                 }
411             }
412         }
413         if (rsa) {
414             fprintf(f,"----- PRIVATE KEY -----\n");
415             RSA_print_fp(f,rsa,0);
416             RSA_free(rsa);
417         }
418     }
419     if (in) {
420         BIO_free(in);
421         in=NULL;
422     }
423     
424     // Dump certificates.
425     for (vector<X509*>::const_iterator i=m_certs.begin(); i!=m_certs.end(); i++) {
426         fprintf(f,"----- CERTIFICATE(S) -----\n");
427 #if (OPENSSL_VERSION_NUMBER > 0x009070000L)
428         X509_print_ex_fp(f,*i,XN_FLAG_SEP_MULTILINE,0);
429 #else
430         X509_print_fp(f,*i);
431 #endif
432     }
433 }