5de29c7b079ed67cc3fd180064fe67795da83283
[shibboleth/cpp-xmltooling.git] / xmltooling / security / impl / SecurityHelper.cpp
1 /*
2  *  Copyright 2001-2008 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 /**
18  * SecurityHelper.cpp
19  *
20  * A helper class for working with keys, certificates, etc.
21  */
22
23 #include "internal.h"
24 #include "logging.h"
25 #include "security/OpenSSLCryptoX509CRL.h"
26 #include "security/SecurityHelper.h"
27 #include "util/NDC.h"
28
29 #include <fstream>
30 #include <openssl/pem.h>
31 #include <openssl/pkcs12.h>
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 xmltooling::logging;
37 using namespace xmltooling;
38 using namespace std;
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 const char* SecurityHelper::guessEncodingFormat(const char* pathname)
55 {
56     const char* format=NULL;
57     BIO* in=BIO_new(BIO_s_file_internal());
58     if (in && BIO_read_filename(in, pathname)>0) {
59         const int READSIZE = 1;
60         char buf[READSIZE];
61         int mark;
62
63         // Examine the first byte.
64         try {
65             if ((mark = BIO_tell(in)) < 0)
66                 throw XMLSecurityException("Error loading file: BIO_tell() can't get the file position.");
67             if (BIO_read(in, buf, READSIZE) <= 0)
68                 throw XMLSecurityException("Error loading file: BIO_read() can't read from the stream.");
69             if (BIO_seek(in, mark) < 0)
70                 throw XMLSecurityException("Error loading file: BIO_seek() can't reset the file position.");
71         }
72         catch (exception&) {
73             log_openssl();
74             BIO_free(in);
75             throw;
76         }
77
78         // Check the first byte of the file.  If it's some kind of DER-encoded structure
79         // (including PKCS12), it will begin with ASCII 048. Otherwise, assume it's PEM.
80         if (buf[0] != 48) {
81             format = "PEM";
82         }
83         else {
84             // Here we know it's DER-encoded, now try to parse it as a PKCS12 ASN.1 structure.
85             // If it fails, must be another kind of DER-encoded structure.
86             PKCS12* p12;
87             if ((p12=d2i_PKCS12_bio(in, NULL)) == NULL) {
88                 format = "DER";
89             }
90             else {
91                 format = "PKCS12";
92                 PKCS12_free(p12);
93             }
94         }
95     }
96     if (in)
97         BIO_free(in);
98     if (format)
99         return format;
100     throw XMLSecurityException("Unable to determine encoding for file ($1).", params(1,pathname));
101 }
102
103 XSECCryptoKey* SecurityHelper::loadKeyFromFile(const char* pathname, const char* format, const char* password)
104 {
105 #ifdef _DEBUG
106     NDC ndc("loadKeyFromFile");
107 #endif
108     Category& log = Category::getInstance(XMLTOOLING_LOGCAT".SecurityHelper");
109     log.info("loading private key from file (%s)", pathname);
110
111     // Native objects.
112     PKCS12* p12=NULL;
113     EVP_PKEY* pkey=NULL;
114
115     BIO* in=BIO_new(BIO_s_file_internal());
116     if (in && BIO_read_filename(in, pathname)>0) {
117         // If the format isn't set, try and guess it.
118         if (!format || !*format) {
119             const int READSIZE = 1;
120             char buf[READSIZE];
121             int mark;
122
123             // Examine the first byte.
124             try {
125                 if ((mark = BIO_tell(in)) < 0)
126                     throw XMLSecurityException("Error loading key: BIO_tell() can't get the file position.");
127                 if (BIO_read(in, buf, READSIZE) <= 0)
128                     throw XMLSecurityException("Error loading key: BIO_read() can't read from the stream.");
129                 if (BIO_seek(in, mark) < 0)
130                     throw XMLSecurityException("Error loading key: BIO_seek() can't reset the file position.");
131             }
132             catch (exception&) {
133                 log_openssl();
134                 BIO_free(in);
135                 throw;
136             }
137
138             // Check the first byte of the file.  If it's some kind of DER-encoded structure
139             // (including PKCS12), it will begin with ASCII 048. Otherwise, assume it's PEM.
140             if (buf[0] != 48) {
141                 format = "PEM";
142             }
143             else {
144                 // Here we know it's DER-encoded, now try to parse it as a PKCS12 ASN.1 structure.
145                 // If it fails, must be another kind of DER-encoded structure.
146                 if ((p12=d2i_PKCS12_bio(in, NULL)) == NULL) {
147                     format = "DER";
148                     if (BIO_seek(in, mark) < 0) {
149                         log_openssl();
150                         BIO_free(in);
151                         throw XMLSecurityException("Error loading key: BIO_seek() can't reset the file position.");
152                     }
153                 }
154                 else {
155                     format = "PKCS12";
156                 }
157             }
158             log.debug("key encoding format for (%s) dynamically resolved as (%s)", pathname, format);
159         }
160
161         // The format should be known, so parse accordingly.
162         if (!strcmp(format, "PEM")) {
163             pkey = PEM_read_bio_PrivateKey(in, NULL, passwd_callback, const_cast<char*>(password));
164         }
165         else if (!strcmp(format, "DER")) {
166             pkey=d2i_PrivateKey_bio(in, NULL);
167         }
168         else if (!strcmp(format, "PKCS12")) {
169             if (!p12)
170                 p12 = d2i_PKCS12_bio(in, NULL);
171             if (p12) {
172                 X509* x=NULL;
173                 PKCS12_parse(p12, const_cast<char*>(password), &pkey, &x, NULL);
174                 PKCS12_free(p12);
175                 X509_free(x);
176             }
177         }
178         else {
179             log.error("unknown key encoding format (%s)", format);
180         }
181     }
182     if (in)
183         BIO_free(in);
184
185     // Now map it to an XSEC wrapper.
186     if (pkey) {
187         XSECCryptoKey* ret=NULL;
188         switch (pkey->type) {
189             case EVP_PKEY_RSA:
190                 ret=new OpenSSLCryptoKeyRSA(pkey);
191                 break;
192
193             case EVP_PKEY_DSA:
194                 ret=new OpenSSLCryptoKeyDSA(pkey);
195                 break;
196
197             default:
198                 log.error("unsupported private key type");
199         }
200         EVP_PKEY_free(pkey);
201         if (ret)
202             return ret;
203     }
204
205     log_openssl();
206     throw XMLSecurityException("Unable to load private key from file ($1).", params(1, pathname));
207 }
208
209 vector<XSECCryptoX509*>::size_type SecurityHelper::loadCertificatesFromFile(
210     vector<XSECCryptoX509*>& certs, const char* pathname, const char* format, const char* password
211     )
212 {
213 #ifdef _DEBUG
214     NDC ndc("loadCertificatesFromFile");
215 #endif
216     Category& log = Category::getInstance(XMLTOOLING_LOGCAT".SecurityHelper");
217     log.info("loading certificate(s) from file (%s)", pathname);
218
219     vector<XSECCryptoX509*>::size_type count = certs.size();
220
221     // Native objects.
222     X509* x=NULL;
223     PKCS12* p12=NULL;
224
225     BIO* in=BIO_new(BIO_s_file_internal());
226     if (in && BIO_read_filename(in, pathname)>0) {
227         // If the format isn't set, try and guess it.
228         if (!format || !*format) {
229             const int READSIZE = 1;
230             char buf[READSIZE];
231             int mark;
232
233             // Examine the first byte.
234             try {
235                 if ((mark = BIO_tell(in)) < 0)
236                     throw XMLSecurityException("Error loading certificate: BIO_tell() can't get the file position.");
237                 if (BIO_read(in, buf, READSIZE) <= 0)
238                     throw XMLSecurityException("Error loading certificate: BIO_read() can't read from the stream.");
239                 if (BIO_seek(in, mark) < 0)
240                     throw XMLSecurityException("Error loading certificate: BIO_seek() can't reset the file position.");
241             }
242             catch (exception&) {
243                 log_openssl();
244                 BIO_free(in);
245                 throw;
246             }
247
248             // Check the first byte of the file.  If it's some kind of DER-encoded structure
249             // (including PKCS12), it will begin with ASCII 048. Otherwise, assume it's PEM.
250             if (buf[0] != 48) {
251                 format = "PEM";
252             }
253             else {
254                 // Here we know it's DER-encoded, now try to parse it as a PKCS12 ASN.1 structure.
255                 // If it fails, must be another kind of DER-encoded structure.
256                 if ((p12=d2i_PKCS12_bio(in, NULL)) == NULL) {
257                     format = "DER";
258                     if (BIO_seek(in, mark) < 0) {
259                         log_openssl();
260                         BIO_free(in);
261                         throw XMLSecurityException("Error loading certificate: BIO_seek() can't reset the file position.");
262                     }
263                 }
264                 else {
265                     format = "PKCS12";
266                 }
267             }
268         }
269
270         // The format should be known, so parse accordingly.
271         if (!strcmp(format, "PEM")) {
272             while (x=PEM_read_bio_X509(in, NULL, NULL, NULL)) {
273                 certs.push_back(new OpenSSLCryptoX509(x));
274                 X509_free(x);
275             }
276         }
277         else if (!strcmp(format, "DER")) {
278             x=d2i_X509_bio(in, NULL);
279             if (x) {
280                 certs.push_back(new OpenSSLCryptoX509(x));
281                 X509_free(x);
282             }
283         }
284         else if (!strcmp(format, "PKCS12")) {
285             if (!p12)
286                 p12 = d2i_PKCS12_bio(in, NULL);
287             if (p12) {
288                 EVP_PKEY* pkey=NULL;
289                 STACK_OF(X509)* CAstack = sk_X509_new_null();
290                 PKCS12_parse(p12, const_cast<char*>(password), &pkey, &x, &CAstack);
291                 PKCS12_free(p12);
292                 EVP_PKEY_free(pkey);
293                 if (x) {
294                     certs.push_back(new OpenSSLCryptoX509(x));
295                     X509_free(x);
296                 }
297                 x = sk_X509_pop(CAstack);
298                 while (x) {
299                     certs.push_back(new OpenSSLCryptoX509(x));
300                     X509_free(x);
301                     x = sk_X509_pop(CAstack);
302                 }
303                 sk_X509_free(CAstack);
304             }
305         }
306     }
307     if (in)
308         BIO_free(in);
309
310     if (certs.size() == count) {
311         log_openssl();
312         throw XMLSecurityException("Unable to load certificate(s) from file ($1).", params(1, pathname));
313     }
314
315     return certs.size();
316 }
317
318 vector<XSECCryptoX509CRL*>::size_type SecurityHelper::loadCRLsFromFile(
319     vector<XSECCryptoX509CRL*>& crls, const char* pathname, const char* format
320     )
321 {
322 #ifdef _DEBUG
323     NDC ndc("loadCRLsFromFile");
324 #endif
325     Category& log = Category::getInstance(XMLTOOLING_LOGCAT".SecurityHelper");
326     log.info("loading CRL(s) from file (%s)", pathname);
327
328     vector<XSECCryptoX509CRL*>::size_type count = crls.size();
329
330     BIO* in=BIO_new(BIO_s_file_internal());
331     if (in && BIO_read_filename(in, pathname)>0) {
332         // If the format isn't set, try and guess it.
333         if (!format || !*format) {
334             const int READSIZE = 1;
335             char buf[READSIZE];
336             int mark;
337
338             // Examine the first byte.
339             try {
340                 if ((mark = BIO_tell(in)) < 0)
341                     throw XMLSecurityException("Error loading CRL: BIO_tell() can't get the file position.");
342                 if (BIO_read(in, buf, READSIZE) <= 0)
343                     throw XMLSecurityException("Error loading CRL: BIO_read() can't read from the stream.");
344                 if (BIO_seek(in, mark) < 0)
345                     throw XMLSecurityException("Error loading CRL: BIO_seek() can't reset the file position.");
346             }
347             catch (exception&) {
348                 log_openssl();
349                 BIO_free(in);
350                 throw;
351             }
352
353             // Check the first byte of the file.  If it's some kind of DER-encoded structure
354             // it will begin with ASCII 048. Otherwise, assume it's PEM.
355             if (buf[0] != 48) {
356                 format = "PEM";
357             }
358             else {
359                 format = "DER";
360             }
361             log.debug("CRL encoding format for (%s) dynamically resolved as (%s)", pathname, format);
362         }
363
364         X509_CRL* crl=NULL;
365         if (!strcmp(format, "PEM")) {
366             while (crl=PEM_read_bio_X509_CRL(in, NULL, NULL, NULL)) {
367                 crls.push_back(new OpenSSLCryptoX509CRL(crl));
368                 X509_CRL_free(crl);
369             }
370         }
371         else if (!strcmp(format, "DER")) {
372             crl=d2i_X509_CRL_bio(in, NULL);
373             if (crl) {
374                 crls.push_back(new OpenSSLCryptoX509CRL(crl));
375                 X509_CRL_free(crl);
376             }
377         }
378         else {
379             log.error("unknown CRL encoding format (%s)", format);
380         }
381     }
382     if (in)
383         BIO_free(in);
384
385     if (crls.size() == count) {
386         log_openssl();
387         throw XMLSecurityException("Unable to load CRL(s) from file ($1).", params(1, pathname));
388     }
389
390     return crls.size();
391 }
392
393 XSECCryptoKey* SecurityHelper::loadKeyFromURL(SOAPTransport& transport, const char* backing, const char* format, const char* password)
394 {
395     // Fetch the data.
396     transport.send();
397     istream& msg = transport.receive();
398
399     // Dump to output file.
400     ofstream out(backing, fstream::trunc|fstream::binary);
401     out << msg.rdbuf();
402     out.close();
403
404     return loadKeyFromFile(backing, format, password);
405 }
406
407 vector<XSECCryptoX509*>::size_type SecurityHelper::loadCertificatesFromURL(
408     vector<XSECCryptoX509*>& certs, SOAPTransport& transport, const char* backing, const char* format, const char* password
409     )
410 {
411     transport.send();
412     istream& msg = transport.receive();
413
414     // Dump to output file.
415     ofstream out(backing, fstream::trunc|fstream::binary);
416     out << msg.rdbuf();
417     out.close();
418
419     return loadCertificatesFromFile(certs, backing, format, password);
420 }
421
422 vector<XSECCryptoX509CRL*>::size_type SecurityHelper::loadCRLsFromURL(
423     vector<XSECCryptoX509CRL*>& crls, SOAPTransport& transport, const char* backing, const char* format
424     )
425 {
426     // Fetch the data.
427     transport.send();
428     istream& msg = transport.receive();
429
430     // Dump to output file.
431     ofstream out(backing, fstream::trunc|fstream::binary);
432     out << msg.rdbuf();
433     out.close();
434
435     return loadCRLsFromFile(crls, backing, format);
436 }
437
438 bool SecurityHelper::matches(const XSECCryptoKey* key1, const XSECCryptoKey* key2)
439 {
440     if (key1->getProviderName()!=DSIGConstants::s_unicodeStrPROVOpenSSL ||
441         key2->getProviderName()!=DSIGConstants::s_unicodeStrPROVOpenSSL) {
442         Category::getInstance(XMLTOOLING_LOGCAT".SecurityHelper").warn("comparison of non-OpenSSL keys not supported");
443         return false;
444     }
445
446     // If one key is public or both, just compare the public key half.
447     if (key1->getKeyType()==XSECCryptoKey::KEY_RSA_PUBLIC || key1->getKeyType()==XSECCryptoKey::KEY_RSA_PAIR) {
448         if (key2->getKeyType()!=XSECCryptoKey::KEY_RSA_PUBLIC && key2->getKeyType()!=XSECCryptoKey::KEY_RSA_PAIR)
449             return false;
450         const RSA* rsa1 = static_cast<const OpenSSLCryptoKeyRSA*>(key1)->getOpenSSLRSA();
451         const RSA* rsa2 = static_cast<const OpenSSLCryptoKeyRSA*>(key2)->getOpenSSLRSA();
452         return (BN_cmp(rsa1->n,rsa2->n) == 0 && BN_cmp(rsa1->e,rsa2->e) == 0);
453     }
454
455     // For a private key, compare the private half.
456     if (key1->getKeyType()==XSECCryptoKey::KEY_RSA_PRIVATE) {
457         if (key2->getKeyType()!=XSECCryptoKey::KEY_RSA_PRIVATE && key2->getKeyType()!=XSECCryptoKey::KEY_RSA_PAIR)
458             return false;
459         const RSA* rsa1 = static_cast<const OpenSSLCryptoKeyRSA*>(key1)->getOpenSSLRSA();
460         const RSA* rsa2 = static_cast<const OpenSSLCryptoKeyRSA*>(key2)->getOpenSSLRSA();
461         return (BN_cmp(rsa1->n,rsa2->n) == 0 && BN_cmp(rsa1->d,rsa2->d) == 0);
462     }
463
464     // If one key is public or both, just compare the public key half.
465     if (key1->getKeyType()==XSECCryptoKey::KEY_DSA_PUBLIC || key1->getKeyType()==XSECCryptoKey::KEY_DSA_PAIR) {
466         if (key2->getKeyType()!=XSECCryptoKey::KEY_DSA_PUBLIC && key2->getKeyType()!=XSECCryptoKey::KEY_DSA_PAIR)
467             return false;
468         const DSA* dsa1 = static_cast<const OpenSSLCryptoKeyDSA*>(key1)->getOpenSSLDSA();
469         const DSA* dsa2 = static_cast<const OpenSSLCryptoKeyDSA*>(key2)->getOpenSSLDSA();
470         return (BN_cmp(dsa1->pub_key,dsa2->pub_key) == 0);
471     }
472
473     // For a private key, compare the private half.
474     if (key1->getKeyType()==XSECCryptoKey::KEY_DSA_PRIVATE) {
475         if (key2->getKeyType()!=XSECCryptoKey::KEY_DSA_PRIVATE && key2->getKeyType()!=XSECCryptoKey::KEY_DSA_PAIR)
476             return false;
477         const DSA* dsa1 = static_cast<const OpenSSLCryptoKeyDSA*>(key1)->getOpenSSLDSA();
478         const DSA* dsa2 = static_cast<const OpenSSLCryptoKeyDSA*>(key2)->getOpenSSLDSA();
479         return (BN_cmp(dsa1->priv_key,dsa2->priv_key) == 0);
480     }
481
482     Category::getInstance(XMLTOOLING_LOGCAT".SecurityHelper").warn("unsupported key type for comparison");
483     return false;
484 }