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