2 * Copyright 2001-2008 Internet2
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
20 * A helper class for working with keys, certificates, etc.
25 #include "security/OpenSSLCryptoX509CRL.h"
26 #include "security/SecurityHelper.h"
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>
36 using namespace xmltooling::logging;
37 using namespace xmltooling;
40 // OpenSSL password callback...
41 static int passwd_callback(char* buf, int len, int verify, void* passwd)
45 if(passwd && len > strlen(reinterpret_cast<char*>(passwd)))
47 strcpy(buf,reinterpret_cast<char*>(passwd));
54 const char* SecurityHelper::guessEncodingFormat(const char* pathname)
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;
63 // Examine the first byte.
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.");
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.
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.
87 if ((p12=d2i_PKCS12_bio(in, NULL)) == NULL) {
100 throw XMLSecurityException("Unable to determine encoding for file ($1).", params(1,pathname));
103 XSECCryptoKey* SecurityHelper::loadKeyFromFile(const char* pathname, const char* format, const char* password)
106 NDC ndc("loadKeyFromFile");
108 Category& log = Category::getInstance(XMLTOOLING_LOGCAT".SecurityHelper");
109 log.info("loading private key from file (%s)", pathname);
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;
123 // Examine the first byte.
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.");
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.
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) {
148 if (BIO_seek(in, mark) < 0) {
151 throw XMLSecurityException("Error loading key: BIO_seek() can't reset the file position.");
158 log.debug("key encoding format for (%s) dynamically resolved as (%s)", pathname, format);
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));
165 else if (!strcmp(format, "DER")) {
166 pkey=d2i_PrivateKey_bio(in, NULL);
168 else if (!strcmp(format, "PKCS12")) {
170 p12 = d2i_PKCS12_bio(in, NULL);
173 PKCS12_parse(p12, const_cast<char*>(password), &pkey, &x, NULL);
179 log.error("unknown key encoding format (%s)", format);
185 // Now map it to an XSEC wrapper.
187 XSECCryptoKey* ret=NULL;
188 switch (pkey->type) {
190 ret=new OpenSSLCryptoKeyRSA(pkey);
194 ret=new OpenSSLCryptoKeyDSA(pkey);
198 log.error("unsupported private key type");
206 throw XMLSecurityException("Unable to load private key from file ($1).", params(1, pathname));
209 vector<XSECCryptoX509*>::size_type SecurityHelper::loadCertificatesFromFile(
210 vector<XSECCryptoX509*>& certs, const char* pathname, const char* format, const char* password
214 NDC ndc("loadCertificatesFromFile");
216 Category& log = Category::getInstance(XMLTOOLING_LOGCAT".SecurityHelper");
217 log.info("loading certificate(s) from file (%s)", pathname);
219 vector<XSECCryptoX509*>::size_type count = certs.size();
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;
233 // Examine the first byte.
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.");
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.
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) {
258 if (BIO_seek(in, mark) < 0) {
261 throw XMLSecurityException("Error loading certificate: BIO_seek() can't reset the file position.");
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));
277 else if (!strcmp(format, "DER")) {
278 x=d2i_X509_bio(in, NULL);
280 certs.push_back(new OpenSSLCryptoX509(x));
284 else if (!strcmp(format, "PKCS12")) {
286 p12 = d2i_PKCS12_bio(in, NULL);
289 STACK_OF(X509)* CAstack = sk_X509_new_null();
290 PKCS12_parse(p12, const_cast<char*>(password), &pkey, &x, &CAstack);
294 certs.push_back(new OpenSSLCryptoX509(x));
297 x = sk_X509_pop(CAstack);
299 certs.push_back(new OpenSSLCryptoX509(x));
301 x = sk_X509_pop(CAstack);
303 sk_X509_free(CAstack);
310 if (certs.size() == count) {
312 throw XMLSecurityException("Unable to load certificate(s) from file ($1).", params(1, pathname));
318 vector<XSECCryptoX509CRL*>::size_type SecurityHelper::loadCRLsFromFile(
319 vector<XSECCryptoX509CRL*>& crls, const char* pathname, const char* format
323 NDC ndc("loadCRLsFromFile");
325 Category& log = Category::getInstance(XMLTOOLING_LOGCAT".SecurityHelper");
326 log.info("loading CRL(s) from file (%s)", pathname);
328 vector<XSECCryptoX509CRL*>::size_type count = crls.size();
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;
338 // Examine the first byte.
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.");
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.
361 log.debug("CRL encoding format for (%s) dynamically resolved as (%s)", pathname, format);
365 if (!strcmp(format, "PEM")) {
366 while (crl=PEM_read_bio_X509_CRL(in, NULL, NULL, NULL)) {
367 crls.push_back(new OpenSSLCryptoX509CRL(crl));
371 else if (!strcmp(format, "DER")) {
372 crl=d2i_X509_CRL_bio(in, NULL);
374 crls.push_back(new OpenSSLCryptoX509CRL(crl));
379 log.error("unknown CRL encoding format (%s)", format);
385 if (crls.size() == count) {
387 throw XMLSecurityException("Unable to load CRL(s) from file ($1).", params(1, pathname));
393 XSECCryptoKey* SecurityHelper::loadKeyFromURL(SOAPTransport& transport, const char* backing, const char* format, const char* password)
397 istream& msg = transport.receive();
399 // Dump to output file.
400 ofstream out(backing, fstream::trunc|fstream::binary);
404 return loadKeyFromFile(backing, format, password);
407 vector<XSECCryptoX509*>::size_type SecurityHelper::loadCertificatesFromURL(
408 vector<XSECCryptoX509*>& certs, SOAPTransport& transport, const char* backing, const char* format, const char* password
412 istream& msg = transport.receive();
414 // Dump to output file.
415 ofstream out(backing, fstream::trunc|fstream::binary);
419 return loadCertificatesFromFile(certs, backing, format, password);
422 vector<XSECCryptoX509CRL*>::size_type SecurityHelper::loadCRLsFromURL(
423 vector<XSECCryptoX509CRL*>& crls, SOAPTransport& transport, const char* backing, const char* format
428 istream& msg = transport.receive();
430 // Dump to output file.
431 ofstream out(backing, fstream::trunc|fstream::binary);
435 return loadCRLsFromFile(crls, backing, format);
438 bool SecurityHelper::matches(const XSECCryptoKey* key1, const XSECCryptoKey* key2)
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");
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)
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);
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)
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);
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)
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);
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)
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);
482 Category::getInstance(XMLTOOLING_LOGCAT".SecurityHelper").warn("unsupported key type for comparison");