2 * Copyright 2001-2009 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 "io/HTTPResponse.h"
26 #include "security/OpenSSLCryptoX509CRL.h"
27 #include "security/SecurityHelper.h"
28 #include "security/X509Credential.h"
29 #include "soap/HTTPSOAPTransport.h"
33 #include <openssl/pem.h>
34 #include <openssl/pkcs12.h>
35 #include <xsec/enc/OpenSSL/OpenSSLCryptoX509.hpp>
36 #include <xsec/enc/OpenSSL/OpenSSLCryptoKeyRSA.hpp>
37 #include <xsec/enc/OpenSSL/OpenSSLCryptoKeyDSA.hpp>
39 using namespace xmltooling::logging;
40 using namespace xmltooling;
43 // OpenSSL password callback...
44 static int passwd_callback(char* buf, int len, int verify, void* passwd)
48 if(passwd && len > strlen(reinterpret_cast<char*>(passwd)))
50 strcpy(buf,reinterpret_cast<char*>(passwd));
57 const char* SecurityHelper::guessEncodingFormat(const char* pathname)
59 const char* format=NULL;
60 BIO* in=BIO_new(BIO_s_file_internal());
61 if (in && BIO_read_filename(in, pathname)>0) {
62 const int READSIZE = 1;
66 // Examine the first byte.
68 if ((mark = BIO_tell(in)) < 0)
69 throw XMLSecurityException("Error loading file: BIO_tell() can't get the file position.");
70 if (BIO_read(in, buf, READSIZE) <= 0)
71 throw XMLSecurityException("Error loading file: BIO_read() can't read from the stream.");
72 if (BIO_seek(in, mark) < 0)
73 throw XMLSecurityException("Error loading file: BIO_seek() can't reset the file position.");
81 // Check the first byte of the file. If it's some kind of DER-encoded structure
82 // (including PKCS12), it will begin with ASCII 048. Otherwise, assume it's PEM.
87 // Here we know it's DER-encoded, now try to parse it as a PKCS12 ASN.1 structure.
88 // If it fails, must be another kind of DER-encoded structure.
90 if ((p12=d2i_PKCS12_bio(in, NULL)) == NULL) {
103 throw XMLSecurityException("Unable to determine encoding for file ($1).", params(1,pathname));
106 XSECCryptoKey* SecurityHelper::loadKeyFromFile(const char* pathname, const char* format, const char* password)
109 NDC ndc("loadKeyFromFile");
111 Category& log = Category::getInstance(XMLTOOLING_LOGCAT".SecurityHelper");
112 log.info("loading private key from file (%s)", pathname);
118 BIO* in=BIO_new(BIO_s_file_internal());
119 if (in && BIO_read_filename(in, pathname)>0) {
120 // If the format isn't set, try and guess it.
121 if (!format || !*format) {
122 const int READSIZE = 1;
126 // Examine the first byte.
128 if ((mark = BIO_tell(in)) < 0)
129 throw XMLSecurityException("Error loading key: BIO_tell() can't get the file position.");
130 if (BIO_read(in, buf, READSIZE) <= 0)
131 throw XMLSecurityException("Error loading key: BIO_read() can't read from the stream.");
132 if (BIO_seek(in, mark) < 0)
133 throw XMLSecurityException("Error loading key: BIO_seek() can't reset the file position.");
141 // Check the first byte of the file. If it's some kind of DER-encoded structure
142 // (including PKCS12), it will begin with ASCII 048. Otherwise, assume it's PEM.
147 // Here we know it's DER-encoded, now try to parse it as a PKCS12 ASN.1 structure.
148 // If it fails, must be another kind of DER-encoded structure.
149 if ((p12=d2i_PKCS12_bio(in, NULL)) == NULL) {
151 if (BIO_seek(in, mark) < 0) {
154 throw XMLSecurityException("Error loading key: BIO_seek() can't reset the file position.");
161 log.debug("key encoding format for (%s) dynamically resolved as (%s)", pathname, format);
164 // The format should be known, so parse accordingly.
165 if (!strcmp(format, "PEM")) {
166 pkey = PEM_read_bio_PrivateKey(in, NULL, passwd_callback, const_cast<char*>(password));
168 else if (!strcmp(format, "DER")) {
169 pkey=d2i_PrivateKey_bio(in, NULL);
171 else if (!strcmp(format, "PKCS12")) {
173 p12 = d2i_PKCS12_bio(in, NULL);
176 PKCS12_parse(p12, const_cast<char*>(password), &pkey, &x, NULL);
182 log.error("unknown key encoding format (%s)", format);
188 // Now map it to an XSEC wrapper.
190 XSECCryptoKey* ret=NULL;
191 switch (pkey->type) {
193 ret=new OpenSSLCryptoKeyRSA(pkey);
197 ret=new OpenSSLCryptoKeyDSA(pkey);
201 log.error("unsupported private key type");
209 throw XMLSecurityException("Unable to load private key from file ($1).", params(1, pathname));
212 vector<XSECCryptoX509*>::size_type SecurityHelper::loadCertificatesFromFile(
213 vector<XSECCryptoX509*>& certs, const char* pathname, const char* format, const char* password
217 NDC ndc("loadCertificatesFromFile");
219 Category& log = Category::getInstance(XMLTOOLING_LOGCAT".SecurityHelper");
220 log.info("loading certificate(s) from file (%s)", pathname);
222 vector<XSECCryptoX509*>::size_type count = certs.size();
228 BIO* in=BIO_new(BIO_s_file_internal());
229 if (in && BIO_read_filename(in, pathname)>0) {
230 // If the format isn't set, try and guess it.
231 if (!format || !*format) {
232 const int READSIZE = 1;
236 // Examine the first byte.
238 if ((mark = BIO_tell(in)) < 0)
239 throw XMLSecurityException("Error loading certificate: BIO_tell() can't get the file position.");
240 if (BIO_read(in, buf, READSIZE) <= 0)
241 throw XMLSecurityException("Error loading certificate: BIO_read() can't read from the stream.");
242 if (BIO_seek(in, mark) < 0)
243 throw XMLSecurityException("Error loading certificate: BIO_seek() can't reset the file position.");
251 // Check the first byte of the file. If it's some kind of DER-encoded structure
252 // (including PKCS12), it will begin with ASCII 048. Otherwise, assume it's PEM.
257 // Here we know it's DER-encoded, now try to parse it as a PKCS12 ASN.1 structure.
258 // If it fails, must be another kind of DER-encoded structure.
259 if ((p12=d2i_PKCS12_bio(in, NULL)) == NULL) {
261 if (BIO_seek(in, mark) < 0) {
264 throw XMLSecurityException("Error loading certificate: BIO_seek() can't reset the file position.");
273 // The format should be known, so parse accordingly.
274 if (!strcmp(format, "PEM")) {
275 while (x=PEM_read_bio_X509(in, NULL, NULL, NULL)) {
276 certs.push_back(new OpenSSLCryptoX509(x));
280 else if (!strcmp(format, "DER")) {
281 x=d2i_X509_bio(in, NULL);
283 certs.push_back(new OpenSSLCryptoX509(x));
287 else if (!strcmp(format, "PKCS12")) {
289 p12 = d2i_PKCS12_bio(in, NULL);
292 STACK_OF(X509)* CAstack = sk_X509_new_null();
293 PKCS12_parse(p12, const_cast<char*>(password), &pkey, &x, &CAstack);
297 certs.push_back(new OpenSSLCryptoX509(x));
300 x = sk_X509_pop(CAstack);
302 certs.push_back(new OpenSSLCryptoX509(x));
304 x = sk_X509_pop(CAstack);
306 sk_X509_free(CAstack);
313 if (certs.size() == count) {
315 throw XMLSecurityException("Unable to load certificate(s) from file ($1).", params(1, pathname));
321 vector<XSECCryptoX509CRL*>::size_type SecurityHelper::loadCRLsFromFile(
322 vector<XSECCryptoX509CRL*>& crls, const char* pathname, const char* format
326 NDC ndc("loadCRLsFromFile");
328 Category& log = Category::getInstance(XMLTOOLING_LOGCAT".SecurityHelper");
329 log.info("loading CRL(s) from file (%s)", pathname);
331 vector<XSECCryptoX509CRL*>::size_type count = crls.size();
333 BIO* in=BIO_new(BIO_s_file_internal());
334 if (in && BIO_read_filename(in, pathname)>0) {
335 // If the format isn't set, try and guess it.
336 if (!format || !*format) {
337 const int READSIZE = 1;
341 // Examine the first byte.
343 if ((mark = BIO_tell(in)) < 0)
344 throw XMLSecurityException("Error loading CRL: BIO_tell() can't get the file position.");
345 if (BIO_read(in, buf, READSIZE) <= 0)
346 throw XMLSecurityException("Error loading CRL: BIO_read() can't read from the stream.");
347 if (BIO_seek(in, mark) < 0)
348 throw XMLSecurityException("Error loading CRL: BIO_seek() can't reset the file position.");
356 // Check the first byte of the file. If it's some kind of DER-encoded structure
357 // it will begin with ASCII 048. Otherwise, assume it's PEM.
364 log.debug("CRL encoding format for (%s) dynamically resolved as (%s)", pathname, format);
368 if (!strcmp(format, "PEM")) {
369 while (crl=PEM_read_bio_X509_CRL(in, NULL, NULL, NULL)) {
370 crls.push_back(new OpenSSLCryptoX509CRL(crl));
374 else if (!strcmp(format, "DER")) {
375 crl=d2i_X509_CRL_bio(in, NULL);
377 crls.push_back(new OpenSSLCryptoX509CRL(crl));
382 log.error("unknown CRL encoding format (%s)", format);
388 if (crls.size() == count) {
390 throw XMLSecurityException("Unable to load CRL(s) from file ($1).", params(1, pathname));
396 XSECCryptoKey* SecurityHelper::loadKeyFromURL(SOAPTransport& transport, const char* backing, const char* format, const char* password)
400 istream& msg = transport.receive();
402 // Check for "not modified" status.
403 if (dynamic_cast<HTTPSOAPTransport*>(&transport) && transport.getStatusCode() == HTTPResponse::XMLTOOLING_HTTP_STATUS_NOTMODIFIED)
404 throw (long)HTTPResponse::XMLTOOLING_HTTP_STATUS_NOTMODIFIED;
406 // Dump to output file.
407 ofstream out(backing, fstream::trunc|fstream::binary);
411 return loadKeyFromFile(backing, format, password);
414 vector<XSECCryptoX509*>::size_type SecurityHelper::loadCertificatesFromURL(
415 vector<XSECCryptoX509*>& certs, SOAPTransport& transport, const char* backing, const char* format, const char* password
419 istream& msg = transport.receive();
421 // Check for "not modified" status.
422 if (dynamic_cast<HTTPSOAPTransport*>(&transport) && transport.getStatusCode() == HTTPResponse::XMLTOOLING_HTTP_STATUS_NOTMODIFIED)
423 throw (long)HTTPResponse::XMLTOOLING_HTTP_STATUS_NOTMODIFIED;
425 // Dump to output file.
426 ofstream out(backing, fstream::trunc|fstream::binary);
430 return loadCertificatesFromFile(certs, backing, format, password);
433 vector<XSECCryptoX509CRL*>::size_type SecurityHelper::loadCRLsFromURL(
434 vector<XSECCryptoX509CRL*>& crls, SOAPTransport& transport, const char* backing, const char* format
439 istream& msg = transport.receive();
441 // Check for "not modified" status.
442 if (dynamic_cast<HTTPSOAPTransport*>(&transport) && transport.getStatusCode() == HTTPResponse::XMLTOOLING_HTTP_STATUS_NOTMODIFIED)
443 throw (long)HTTPResponse::XMLTOOLING_HTTP_STATUS_NOTMODIFIED;
445 // Dump to output file.
446 ofstream out(backing, fstream::trunc|fstream::binary);
450 return loadCRLsFromFile(crls, backing, format);
453 bool SecurityHelper::matches(const XSECCryptoKey& key1, const XSECCryptoKey& key2)
455 if (key1.getProviderName()!=DSIGConstants::s_unicodeStrPROVOpenSSL ||
456 key2.getProviderName()!=DSIGConstants::s_unicodeStrPROVOpenSSL) {
457 Category::getInstance(XMLTOOLING_LOGCAT".SecurityHelper").warn("comparison of non-OpenSSL keys not supported");
461 // If one key is public or both, just compare the public key half.
462 if (key1.getKeyType()==XSECCryptoKey::KEY_RSA_PUBLIC || key1.getKeyType()==XSECCryptoKey::KEY_RSA_PAIR) {
463 if (key2.getKeyType()!=XSECCryptoKey::KEY_RSA_PUBLIC && key2.getKeyType()!=XSECCryptoKey::KEY_RSA_PAIR)
465 const RSA* rsa1 = static_cast<const OpenSSLCryptoKeyRSA&>(key1).getOpenSSLRSA();
466 const RSA* rsa2 = static_cast<const OpenSSLCryptoKeyRSA&>(key2).getOpenSSLRSA();
467 return (rsa1 && rsa2 && BN_cmp(rsa1->n,rsa2->n) == 0 && BN_cmp(rsa1->e,rsa2->e) == 0);
470 // For a private key, compare the private half.
471 if (key1.getKeyType()==XSECCryptoKey::KEY_RSA_PRIVATE) {
472 if (key2.getKeyType()!=XSECCryptoKey::KEY_RSA_PRIVATE && key2.getKeyType()!=XSECCryptoKey::KEY_RSA_PAIR)
474 const RSA* rsa1 = static_cast<const OpenSSLCryptoKeyRSA&>(key1).getOpenSSLRSA();
475 const RSA* rsa2 = static_cast<const OpenSSLCryptoKeyRSA&>(key2).getOpenSSLRSA();
476 return (rsa1 && rsa2 && BN_cmp(rsa1->n,rsa2->n) == 0 && BN_cmp(rsa1->d,rsa2->d) == 0);
479 // If one key is public or both, just compare the public key half.
480 if (key1.getKeyType()==XSECCryptoKey::KEY_DSA_PUBLIC || key1.getKeyType()==XSECCryptoKey::KEY_DSA_PAIR) {
481 if (key2.getKeyType()!=XSECCryptoKey::KEY_DSA_PUBLIC && key2.getKeyType()!=XSECCryptoKey::KEY_DSA_PAIR)
483 const DSA* dsa1 = static_cast<const OpenSSLCryptoKeyDSA&>(key1).getOpenSSLDSA();
484 const DSA* dsa2 = static_cast<const OpenSSLCryptoKeyDSA&>(key2).getOpenSSLDSA();
485 return (dsa1 && dsa2 && BN_cmp(dsa1->pub_key,dsa2->pub_key) == 0);
488 // For a private key, compare the private half.
489 if (key1.getKeyType()==XSECCryptoKey::KEY_DSA_PRIVATE) {
490 if (key2.getKeyType()!=XSECCryptoKey::KEY_DSA_PRIVATE && key2.getKeyType()!=XSECCryptoKey::KEY_DSA_PAIR)
492 const DSA* dsa1 = static_cast<const OpenSSLCryptoKeyDSA&>(key1).getOpenSSLDSA();
493 const DSA* dsa2 = static_cast<const OpenSSLCryptoKeyDSA&>(key2).getOpenSSLDSA();
494 return (dsa1 && dsa2 && BN_cmp(dsa1->priv_key,dsa2->priv_key) == 0);
497 Category::getInstance(XMLTOOLING_LOGCAT".SecurityHelper").warn("unsupported key type for comparison");
501 string SecurityHelper::doHash(const char* hashAlg, const char* buf, unsigned long buflen, bool toHex)
503 static char DIGITS[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
506 const EVP_MD* md = EVP_get_digestbyname(hashAlg);
508 Category::getInstance(XMLTOOLING_LOGCAT".SecurityHelper").error("hash algorithm (%s) not available", hashAlg);
512 BIO* chain = BIO_new(BIO_s_mem());
513 BIO* b = BIO_new(BIO_f_md());
515 chain = BIO_push(b, chain);
516 BIO_write(chain, buf, buflen);
519 char digest[EVP_MAX_MD_SIZE];
520 int len = BIO_gets(chain, digest, EVP_MD_size(md));
522 if (len != EVP_MD_size(md)) {
523 Category::getInstance(XMLTOOLING_LOGCAT".SecurityHelper").error(
524 "hash result length (%d) did not match expected value (%d)", len, EVP_MD_size(md)
529 for (int i=0; i < len; ++i) {
530 ret += (DIGITS[((unsigned char)(0xF0 & digest[i])) >> 4 ]);
531 ret += (DIGITS[0x0F & digest[i]]);
535 for (int i=0; i < len; ++i) {
542 string SecurityHelper::getDEREncoding(const XSECCryptoKey& key, const char* hash, bool nowrap)
546 if (key.getProviderName()!=DSIGConstants::s_unicodeStrPROVOpenSSL) {
547 Category::getInstance(XMLTOOLING_LOGCAT".SecurityHelper").warn("encoding of non-OpenSSL keys not supported");
551 if (key.getKeyType() == XSECCryptoKey::KEY_RSA_PUBLIC || key.getKeyType() == XSECCryptoKey::KEY_RSA_PAIR) {
552 const RSA* rsa = static_cast<const OpenSSLCryptoKeyRSA&>(key).getOpenSSLRSA();
554 Category::getInstance(XMLTOOLING_LOGCAT".SecurityHelper").warn("key was not populated");
557 const EVP_MD* md=NULL;
559 md = EVP_get_digestbyname(hash);
561 Category::getInstance(XMLTOOLING_LOGCAT".SecurityHelper").error("hash algorithm (%s) not available", hash);
565 BIO* chain = BIO_new(BIO_s_mem());
566 BIO* b = BIO_new(BIO_f_base64());
568 BIO_set_flags(b, BIO_FLAGS_BASE64_NO_NL);
569 chain = BIO_push(b, chain);
571 b = BIO_new(BIO_f_md());
573 chain = BIO_push(b, chain);
575 i2d_RSA_PUBKEY_bio(chain, const_cast<RSA*>(rsa));
578 char digest[EVP_MAX_MD_SIZE];
579 int len = BIO_gets(chain, digest, EVP_MD_size(md));
580 if (len != EVP_MD_size(md)) {
588 BIO_write(chain, digest, len);
592 BIO_get_mem_ptr(chain, &bptr);
593 if (bptr && bptr->length > 0)
594 ret.append(bptr->data, bptr->length);
597 else if (key.getKeyType() == XSECCryptoKey::KEY_DSA_PUBLIC || key.getKeyType() == XSECCryptoKey::KEY_DSA_PAIR) {
598 const DSA* dsa = static_cast<const OpenSSLCryptoKeyDSA&>(key).getOpenSSLDSA();
600 Category::getInstance(XMLTOOLING_LOGCAT".SecurityHelper").warn("key was not populated");
603 const EVP_MD* md=NULL;
605 md = EVP_get_digestbyname(hash);
607 Category::getInstance(XMLTOOLING_LOGCAT".SecurityHelper").error("hash algorithm (%s) not available", hash);
611 BIO* chain = BIO_new(BIO_s_mem());
612 BIO* b = BIO_new(BIO_f_base64());
614 BIO_set_flags(b, BIO_FLAGS_BASE64_NO_NL);
615 chain = BIO_push(b, chain);
617 b = BIO_new(BIO_f_md());
619 chain = BIO_push(b, chain);
621 i2d_DSA_PUBKEY_bio(chain, const_cast<DSA*>(dsa));
624 char digest[EVP_MAX_MD_SIZE];
625 int len = BIO_gets(chain, digest, EVP_MD_size(md));
626 if (len != EVP_MD_size(md)) {
634 BIO_write(chain, digest, len);
638 BIO_get_mem_ptr(chain, &bptr);
639 if (bptr && bptr->length > 0)
640 ret.append(bptr->data, bptr->length);
644 Category::getInstance(XMLTOOLING_LOGCAT".SecurityHelper").warn("encoding of non-RSA/DSA public keys not supported");
649 string SecurityHelper::getDEREncoding(const XSECCryptoX509& cert, const char* hash, bool nowrap)
653 if (cert.getProviderName()!=DSIGConstants::s_unicodeStrPROVOpenSSL) {
654 Category::getInstance(XMLTOOLING_LOGCAT".SecurityHelper").warn("encoding of non-OpenSSL keys not supported");
658 const EVP_MD* md=NULL;
660 md = EVP_get_digestbyname(hash);
662 Category::getInstance(XMLTOOLING_LOGCAT".SecurityHelper").error("hash algorithm (%s) not available", hash);
667 const X509* x = static_cast<const OpenSSLCryptoX509&>(cert).getOpenSSLX509();
668 EVP_PKEY* key = X509_get_pubkey(const_cast<X509*>(x));
670 BIO* chain = BIO_new(BIO_s_mem());
671 BIO* b = BIO_new(BIO_f_base64());
673 BIO_set_flags(b, BIO_FLAGS_BASE64_NO_NL);
674 chain = BIO_push(b, chain);
676 b = BIO_new(BIO_f_md());
678 chain = BIO_push(b, chain);
680 i2d_PUBKEY_bio(chain, key);
684 char digest[EVP_MAX_MD_SIZE];
685 int len = BIO_gets(chain, digest, EVP_MD_size(md));
686 if (len != EVP_MD_size(md)) {
694 BIO_write(chain, digest, len);
698 BIO_get_mem_ptr(chain, &bptr);
699 if (bptr && bptr->length > 0)
700 ret.append(bptr->data, bptr->length);
705 string SecurityHelper::getDEREncoding(const Credential& cred, const char* hash, bool nowrap)
707 const X509Credential* x509 = dynamic_cast<const X509Credential*>(&cred);
708 if (x509 && !x509->getEntityCertificateChain().empty())
709 return getDEREncoding(*(x509->getEntityCertificateChain().front()), hash, nowrap);
710 else if (cred.getPublicKey())
711 return getDEREncoding(*(cred.getPublicKey()), hash, nowrap);
715 string SecurityHelper::getDEREncoding(const XSECCryptoKey& key, bool hash, bool nowrap)
717 return getDEREncoding(key, hash ? "SHA1" : NULL, nowrap);
720 string SecurityHelper::getDEREncoding(const XSECCryptoX509& cert, bool hash, bool nowrap)
722 return getDEREncoding(cert, hash ? "SHA1" : NULL, nowrap);
725 string SecurityHelper::getDEREncoding(const Credential& cred, bool hash, bool nowrap)
727 return getDEREncoding(cred, hash ? "SHA1" : NULL, nowrap);