X-Git-Url: http://www.project-moonshot.org/gitweb/?a=blobdiff_plain;f=xmltooling%2Fsecurity%2Fimpl%2FSecurityHelper.cpp;h=a4ac4f2099c451986e010f6768495d818ef3ada1;hb=dbcc5e98b26eeeb7c44ad339b9a59edbf0a2e62f;hp=6370079e2f6532ce4ee767d107df8893341ef1a9;hpb=a6ec5ba5d5a9ce4f3e127fdc455eea31051390c2;p=shibboleth%2Fcpp-xmltooling.git diff --git a/xmltooling/security/impl/SecurityHelper.cpp b/xmltooling/security/impl/SecurityHelper.cpp index 6370079..a4ac4f2 100644 --- a/xmltooling/security/impl/SecurityHelper.cpp +++ b/xmltooling/security/impl/SecurityHelper.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2001-2009 Internet2 + * Copyright 2001-2010 Internet2 * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,17 +22,31 @@ #include "internal.h" #include "logging.h" +#include "io/HTTPResponse.h" #include "security/OpenSSLCryptoX509CRL.h" #include "security/SecurityHelper.h" #include "security/X509Credential.h" +#include "soap/HTTPSOAPTransport.h" #include "util/NDC.h" #include +#include #include #include +#include #include #include #include +#if defined(XMLTOOLING_XMLSEC_ECC) && defined(XMLTOOLING_OPENSSL_HAVE_EC) +# include +#endif +#include + +#ifdef WIN32 +# if (OPENSSL_VERSION_NUMBER >= 0x00907000) +# define XMLTOOLING_OPENSSL_HAVE_EC 1 +# endif +#endif using namespace xmltooling::logging; using namespace xmltooling; @@ -54,7 +68,7 @@ static int passwd_callback(char* buf, int len, int verify, void* passwd) const char* SecurityHelper::guessEncodingFormat(const char* pathname) { - const char* format=NULL; + const char* format=nullptr; BIO* in=BIO_new(BIO_s_file_internal()); if (in && BIO_read_filename(in, pathname)>0) { const int READSIZE = 1; @@ -85,7 +99,7 @@ const char* SecurityHelper::guessEncodingFormat(const char* pathname) // Here we know it's DER-encoded, now try to parse it as a PKCS12 ASN.1 structure. // If it fails, must be another kind of DER-encoded structure. PKCS12* p12; - if ((p12=d2i_PKCS12_bio(in, NULL)) == NULL) { + if ((p12=d2i_PKCS12_bio(in, nullptr)) == nullptr) { format = "DER"; } else { @@ -110,8 +124,8 @@ XSECCryptoKey* SecurityHelper::loadKeyFromFile(const char* pathname, const char* log.info("loading private key from file (%s)", pathname); // Native objects. - PKCS12* p12=NULL; - EVP_PKEY* pkey=NULL; + PKCS12* p12=nullptr; + EVP_PKEY* pkey=nullptr; BIO* in=BIO_new(BIO_s_file_internal()); if (in && BIO_read_filename(in, pathname)>0) { @@ -144,7 +158,7 @@ XSECCryptoKey* SecurityHelper::loadKeyFromFile(const char* pathname, const char* else { // Here we know it's DER-encoded, now try to parse it as a PKCS12 ASN.1 structure. // If it fails, must be another kind of DER-encoded structure. - if ((p12=d2i_PKCS12_bio(in, NULL)) == NULL) { + if ((p12=d2i_PKCS12_bio(in, nullptr)) == nullptr) { format = "DER"; if (BIO_seek(in, mark) < 0) { log_openssl(); @@ -161,17 +175,17 @@ XSECCryptoKey* SecurityHelper::loadKeyFromFile(const char* pathname, const char* // The format should be known, so parse accordingly. if (!strcmp(format, "PEM")) { - pkey = PEM_read_bio_PrivateKey(in, NULL, passwd_callback, const_cast(password)); + pkey = PEM_read_bio_PrivateKey(in, nullptr, passwd_callback, const_cast(password)); } else if (!strcmp(format, "DER")) { - pkey=d2i_PrivateKey_bio(in, NULL); + pkey=d2i_PrivateKey_bio(in, nullptr); } else if (!strcmp(format, "PKCS12")) { if (!p12) - p12 = d2i_PKCS12_bio(in, NULL); + p12 = d2i_PKCS12_bio(in, nullptr); if (p12) { - X509* x=NULL; - PKCS12_parse(p12, const_cast(password), &pkey, &x, NULL); + X509* x=nullptr; + PKCS12_parse(p12, const_cast(password), &pkey, &x, nullptr); PKCS12_free(p12); X509_free(x); } @@ -185,7 +199,7 @@ XSECCryptoKey* SecurityHelper::loadKeyFromFile(const char* pathname, const char* // Now map it to an XSEC wrapper. if (pkey) { - XSECCryptoKey* ret=NULL; + XSECCryptoKey* ret=nullptr; switch (pkey->type) { case EVP_PKEY_RSA: ret=new OpenSSLCryptoKeyRSA(pkey); @@ -195,6 +209,11 @@ XSECCryptoKey* SecurityHelper::loadKeyFromFile(const char* pathname, const char* ret=new OpenSSLCryptoKeyDSA(pkey); break; +#if defined(XMLTOOLING_XMLSEC_ECC) && defined(XMLTOOLING_OPENSSL_HAVE_EC) + case EVP_PKEY_EC: + ret=new OpenSSLCryptoKeyEC(pkey); + break; +#endif default: log.error("unsupported private key type"); } @@ -220,8 +239,8 @@ vector::size_type SecurityHelper::loadCertificatesFromFile( vector::size_type count = certs.size(); // Native objects. - X509* x=NULL; - PKCS12* p12=NULL; + X509* x=nullptr; + PKCS12* p12=nullptr; BIO* in=BIO_new(BIO_s_file_internal()); if (in && BIO_read_filename(in, pathname)>0) { @@ -254,7 +273,7 @@ vector::size_type SecurityHelper::loadCertificatesFromFile( else { // Here we know it's DER-encoded, now try to parse it as a PKCS12 ASN.1 structure. // If it fails, must be another kind of DER-encoded structure. - if ((p12=d2i_PKCS12_bio(in, NULL)) == NULL) { + if ((p12=d2i_PKCS12_bio(in, nullptr)) == nullptr) { format = "DER"; if (BIO_seek(in, mark) < 0) { log_openssl(); @@ -270,13 +289,13 @@ vector::size_type SecurityHelper::loadCertificatesFromFile( // The format should be known, so parse accordingly. if (!strcmp(format, "PEM")) { - while (x=PEM_read_bio_X509(in, NULL, NULL, NULL)) { + while (x=PEM_read_bio_X509(in, nullptr, nullptr, nullptr)) { certs.push_back(new OpenSSLCryptoX509(x)); X509_free(x); } } else if (!strcmp(format, "DER")) { - x=d2i_X509_bio(in, NULL); + x=d2i_X509_bio(in, nullptr); if (x) { certs.push_back(new OpenSSLCryptoX509(x)); X509_free(x); @@ -284,9 +303,9 @@ vector::size_type SecurityHelper::loadCertificatesFromFile( } else if (!strcmp(format, "PKCS12")) { if (!p12) - p12 = d2i_PKCS12_bio(in, NULL); + p12 = d2i_PKCS12_bio(in, nullptr); if (p12) { - EVP_PKEY* pkey=NULL; + EVP_PKEY* pkey=nullptr; STACK_OF(X509)* CAstack = sk_X509_new_null(); PKCS12_parse(p12, const_cast(password), &pkey, &x, &CAstack); PKCS12_free(p12); @@ -362,15 +381,15 @@ vector::size_type SecurityHelper::loadCRLsFromFile( log.debug("CRL encoding format for (%s) dynamically resolved as (%s)", pathname, format); } - X509_CRL* crl=NULL; + X509_CRL* crl=nullptr; if (!strcmp(format, "PEM")) { - while (crl=PEM_read_bio_X509_CRL(in, NULL, NULL, NULL)) { + while (crl=PEM_read_bio_X509_CRL(in, nullptr, nullptr, nullptr)) { crls.push_back(new OpenSSLCryptoX509CRL(crl)); X509_CRL_free(crl); } } else if (!strcmp(format, "DER")) { - crl=d2i_X509_CRL_bio(in, NULL); + crl=d2i_X509_CRL_bio(in, nullptr); if (crl) { crls.push_back(new OpenSSLCryptoX509CRL(crl)); X509_CRL_free(crl); @@ -397,6 +416,10 @@ XSECCryptoKey* SecurityHelper::loadKeyFromURL(SOAPTransport& transport, const ch transport.send(); istream& msg = transport.receive(); + // Check for "not modified" status. + if (dynamic_cast(&transport) && transport.getStatusCode() == HTTPResponse::XMLTOOLING_HTTP_STATUS_NOTMODIFIED) + throw (long)HTTPResponse::XMLTOOLING_HTTP_STATUS_NOTMODIFIED; + // Dump to output file. ofstream out(backing, fstream::trunc|fstream::binary); out << msg.rdbuf(); @@ -412,6 +435,10 @@ vector::size_type SecurityHelper::loadCertificatesFromURL( transport.send(); istream& msg = transport.receive(); + // Check for "not modified" status. + if (dynamic_cast(&transport) && transport.getStatusCode() == HTTPResponse::XMLTOOLING_HTTP_STATUS_NOTMODIFIED) + throw (long)HTTPResponse::XMLTOOLING_HTTP_STATUS_NOTMODIFIED; + // Dump to output file. ofstream out(backing, fstream::trunc|fstream::binary); out << msg.rdbuf(); @@ -428,6 +455,10 @@ vector::size_type SecurityHelper::loadCRLsFromURL( transport.send(); istream& msg = transport.receive(); + // Check for "not modified" status. + if (dynamic_cast(&transport) && transport.getStatusCode() == HTTPResponse::XMLTOOLING_HTTP_STATUS_NOTMODIFIED) + throw (long)HTTPResponse::XMLTOOLING_HTTP_STATUS_NOTMODIFIED; + // Dump to output file. ofstream out(backing, fstream::trunc|fstream::binary); out << msg.rdbuf(); @@ -480,11 +511,76 @@ bool SecurityHelper::matches(const XSECCryptoKey& key1, const XSECCryptoKey& key return (dsa1 && dsa2 && BN_cmp(dsa1->priv_key,dsa2->priv_key) == 0); } +#if defined(XMLTOOLING_XMLSEC_ECC) && defined(XMLTOOLING_OPENSSL_HAVE_EC) + // If one key is public or both, just compare the public key half. + if (key1.getKeyType()==XSECCryptoKey::KEY_EC_PUBLIC || key1.getKeyType()==XSECCryptoKey::KEY_EC_PAIR) { + if (key2.getKeyType()!=XSECCryptoKey::KEY_EC_PUBLIC && key2.getKeyType()!=XSECCryptoKey::KEY_EC_PAIR) + return false; + const EC_KEY* ec1 = static_cast(key1).getOpenSSLEC(); + const EC_KEY* ec2 = static_cast(key2).getOpenSSLEC(); + if (!ec1 || !ec2) + return false; + if (EC_GROUP_cmp(EC_KEY_get0_group(ec1), EC_KEY_get0_group(ec2), nullptr) != 0) + return false; + return (EC_POINT_cmp(EC_KEY_get0_group(ec1), EC_KEY_get0_public_key(ec1), EC_KEY_get0_public_key(ec2), nullptr) == 0); + } + + // For a private key, compare the private half. + if (key1.getKeyType()==XSECCryptoKey::KEY_EC_PRIVATE) { + if (key2.getKeyType()!=XSECCryptoKey::KEY_EC_PRIVATE && key2.getKeyType()!=XSECCryptoKey::KEY_EC_PAIR) + return false; + const EC_KEY* ec1 = static_cast(key1).getOpenSSLEC(); + const EC_KEY* ec2 = static_cast(key2).getOpenSSLEC(); + return (ec1 && ec2 && BN_cmp(EC_KEY_get0_private_key(ec1), EC_KEY_get0_private_key(ec2)) == 0); + } +#endif + Category::getInstance(XMLTOOLING_LOGCAT".SecurityHelper").warn("unsupported key type for comparison"); return false; } -string SecurityHelper::getDEREncoding(const XSECCryptoKey& key, bool nowrap) +string SecurityHelper::doHash(const char* hashAlg, const char* buf, unsigned long buflen, bool toHex) +{ + static char DIGITS[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; + string ret; + + const EVP_MD* md = EVP_get_digestbyname(hashAlg); + if (!md) { + Category::getInstance(XMLTOOLING_LOGCAT".SecurityHelper").error("hash algorithm (%s) not available", hashAlg); + return ret; + } + + BIO* chain = BIO_new(BIO_s_mem()); + BIO* b = BIO_new(BIO_f_md()); + BIO_set_md(b, md); + chain = BIO_push(b, chain); + BIO_write(chain, buf, buflen); + BIO_flush(chain); + + char digest[EVP_MAX_MD_SIZE]; + int len = BIO_gets(chain, digest, EVP_MD_size(md)); + BIO_free_all(chain); + if (len != EVP_MD_size(md)) { + Category::getInstance(XMLTOOLING_LOGCAT".SecurityHelper").error( + "hash result length (%d) did not match expected value (%d)", len, EVP_MD_size(md) + ); + return ret; + } + if (toHex) { + for (int i=0; i < len; ++i) { + ret += (DIGITS[((unsigned char)(0xF0 & digest[i])) >> 4 ]); + ret += (DIGITS[0x0F & digest[i]]); + } + } + else { + for (int i=0; i < len; ++i) { + ret += digest[i]; + } + } + return ret; +} + +string SecurityHelper::getDEREncoding(const XSECCryptoKey& key, const char* hash, bool nowrap) { string ret; @@ -493,51 +589,94 @@ string SecurityHelper::getDEREncoding(const XSECCryptoKey& key, bool nowrap) return ret; } + const RSA* rsa = nullptr; + const DSA* dsa = nullptr; +#if defined(XMLTOOLING_XMLSEC_ECC) && defined(XMLTOOLING_OPENSSL_HAVE_EC) + const EC_KEY* ec = nullptr; +#endif + if (key.getKeyType() == XSECCryptoKey::KEY_RSA_PUBLIC || key.getKeyType() == XSECCryptoKey::KEY_RSA_PAIR) { - const RSA* rsa = static_cast(key).getOpenSSLRSA(); + rsa = static_cast(key).getOpenSSLRSA(); if (!rsa) { Category::getInstance(XMLTOOLING_LOGCAT".SecurityHelper").warn("key was not populated"); return ret; } - BIO* base64 = BIO_new(BIO_f_base64()); - if (nowrap) - BIO_set_flags(base64, BIO_FLAGS_BASE64_NO_NL); - BIO* mem = BIO_new(BIO_s_mem()); - BIO_push(base64, mem); - i2d_RSA_PUBKEY_bio(base64, const_cast(rsa)); - BIO_flush(base64); - BUF_MEM* bptr=NULL; - BIO_get_mem_ptr(base64, &bptr); - if (bptr && bptr->length > 0) - ret.append(bptr->data, bptr->length); - BIO_free_all(base64); } else if (key.getKeyType() == XSECCryptoKey::KEY_DSA_PUBLIC || key.getKeyType() == XSECCryptoKey::KEY_DSA_PAIR) { - const DSA* dsa = static_cast(key).getOpenSSLDSA(); + dsa = static_cast(key).getOpenSSLDSA(); if (!dsa) { Category::getInstance(XMLTOOLING_LOGCAT".SecurityHelper").warn("key was not populated"); return ret; } - BIO* base64 = BIO_new(BIO_f_base64()); - if (nowrap) - BIO_set_flags(base64, BIO_FLAGS_BASE64_NO_NL); - BIO* mem = BIO_new(BIO_s_mem()); - BIO_push(base64, mem); - i2d_DSA_PUBKEY_bio(base64, const_cast(dsa)); - BIO_flush(base64); - BUF_MEM* bptr=NULL; - BIO_get_mem_ptr(base64, &bptr); - if (bptr && bptr->length > 0) - ret.append(bptr->data, bptr->length); - BIO_free_all(base64); } +#if defined(XMLTOOLING_XMLSEC_ECC) && defined(XMLTOOLING_OPENSSL_HAVE_EC) + else if (key.getKeyType() == XSECCryptoKey::KEY_EC_PUBLIC || key.getKeyType() == XSECCryptoKey::KEY_EC_PAIR) { + ec = static_cast(key).getOpenSSLEC(); + if (!ec) { + Category::getInstance(XMLTOOLING_LOGCAT".SecurityHelper").warn("key was not populated"); + return ret; + } + } +#endif else { - Category::getInstance(XMLTOOLING_LOGCAT".SecurityHelper").warn("encoding of non-RSA/DSA public keys not supported"); + Category::getInstance(XMLTOOLING_LOGCAT".SecurityHelper").warn("public key type not supported"); + return ret; + } + + const EVP_MD* md=nullptr; + if (hash) { + md = EVP_get_digestbyname(hash); + if (!md) { + Category::getInstance(XMLTOOLING_LOGCAT".SecurityHelper").error("hash algorithm (%s) not available", hash); + return ret; + } + } + + BIO* chain = BIO_new(BIO_s_mem()); + BIO* b = BIO_new(BIO_f_base64()); + if (nowrap) + BIO_set_flags(b, BIO_FLAGS_BASE64_NO_NL); + chain = BIO_push(b, chain); + if (md) { + b = BIO_new(BIO_f_md()); + BIO_set_md(b, md); + chain = BIO_push(b, chain); } + + if (rsa) + i2d_RSA_PUBKEY_bio(chain, const_cast(rsa)); + else if (dsa) + i2d_DSA_PUBKEY_bio(chain, const_cast(dsa)); +#if defined(XMLTOOLING_XMLSEC_ECC) && defined(XMLTOOLING_OPENSSL_HAVE_EC) + else + i2d_EC_PUBKEY_bio(chain, const_cast(ec)); +#endif + + BIO_flush(chain); + if (md) { + char digest[EVP_MAX_MD_SIZE]; + int len = BIO_gets(chain, digest, EVP_MD_size(md)); + if (len != EVP_MD_size(md)) { + BIO_free_all(chain); + return ret; + } + b = BIO_pop(chain); + BIO_free(chain); + chain = b; + BIO_reset(chain); + BIO_write(chain, digest, len); + BIO_flush(chain); + } + BUF_MEM* bptr=nullptr; + BIO_get_mem_ptr(chain, &bptr); + if (bptr && bptr->length > 0) + ret.append(bptr->data, bptr->length); + BIO_free_all(chain); + return ret; } -string SecurityHelper::getDEREncoding(const XSECCryptoX509& cert, bool nowrap) +string SecurityHelper::getDEREncoding(const XSECCryptoX509& cert, const char* hash, bool nowrap) { string ret; @@ -546,31 +685,146 @@ string SecurityHelper::getDEREncoding(const XSECCryptoX509& cert, bool nowrap) return ret; } + const EVP_MD* md=nullptr; + if (hash) { + md = EVP_get_digestbyname(hash); + if (!md) { + Category::getInstance(XMLTOOLING_LOGCAT".SecurityHelper").error("hash algorithm (%s) not available", hash); + return ret; + } + } + const X509* x = static_cast(cert).getOpenSSLX509(); EVP_PKEY* key = X509_get_pubkey(const_cast(x)); - BIO* base64 = BIO_new(BIO_f_base64()); + BIO* chain = BIO_new(BIO_s_mem()); + BIO* b = BIO_new(BIO_f_base64()); if (nowrap) - BIO_set_flags(base64, BIO_FLAGS_BASE64_NO_NL); - BIO* mem = BIO_new(BIO_s_mem()); - BIO_push(base64, mem); - i2d_PUBKEY_bio(base64, key); + BIO_set_flags(b, BIO_FLAGS_BASE64_NO_NL); + chain = BIO_push(b, chain); + if (md) { + b = BIO_new(BIO_f_md()); + BIO_set_md(b, md); + chain = BIO_push(b, chain); + } + i2d_PUBKEY_bio(chain, key); EVP_PKEY_free(key); - BIO_flush(base64); - BUF_MEM* bptr=NULL; - BIO_get_mem_ptr(base64, &bptr); + BIO_flush(chain); + if (md) { + char digest[EVP_MAX_MD_SIZE]; + int len = BIO_gets(chain, digest, EVP_MD_size(md)); + if (len != EVP_MD_size(md)) { + BIO_free_all(chain); + return ret; + } + b = BIO_pop(chain); + BIO_free(chain); + chain = b; + BIO_reset(chain); + BIO_write(chain, digest, len); + BIO_flush(chain); + } + BUF_MEM* bptr=nullptr; + BIO_get_mem_ptr(chain, &bptr); if (bptr && bptr->length > 0) ret.append(bptr->data, bptr->length); - BIO_free_all(base64); + BIO_free_all(chain); return ret; } -string SecurityHelper::getDEREncoding(const Credential& cred, bool nowrap) +string SecurityHelper::getDEREncoding(const Credential& cred, const char* hash, bool nowrap) { const X509Credential* x509 = dynamic_cast(&cred); if (x509 && !x509->getEntityCertificateChain().empty()) - return getDEREncoding(*(x509->getEntityCertificateChain().front()), nowrap); + return getDEREncoding(*(x509->getEntityCertificateChain().front()), hash, nowrap); else if (cred.getPublicKey()) - return getDEREncoding(*(cred.getPublicKey()), nowrap); + return getDEREncoding(*(cred.getPublicKey()), hash, nowrap); return ""; } + +string SecurityHelper::getDEREncoding(const XSECCryptoKey& key, bool hash, bool nowrap) +{ + return getDEREncoding(key, hash ? "SHA1" : nullptr, nowrap); +} + +string SecurityHelper::getDEREncoding(const XSECCryptoX509& cert, bool hash, bool nowrap) +{ + return getDEREncoding(cert, hash ? "SHA1" : nullptr, nowrap); +} + +string SecurityHelper::getDEREncoding(const Credential& cred, bool hash, bool nowrap) +{ + return getDEREncoding(cred, hash ? "SHA1" : nullptr, nowrap); +} + +XSECCryptoKey* SecurityHelper::fromDEREncoding(const char* buf, unsigned long buflen, bool base64) +{ + xsecsize_t x; + XMLByte* decoded=nullptr; + if (base64) { + decoded = xercesc::Base64::decode(reinterpret_cast(buf), &x); + if (!decoded) { + Category::getInstance(XMLTOOLING_LOGCAT".SecurityHelper").error("base64 decode failed"); + return nullptr; + } + } + + BIO* b = BIO_new_mem_buf((void*)(base64 ? (char*)decoded : buf), (base64 ? x : buflen)); + EVP_PKEY* pkey = d2i_PUBKEY_bio(b, nullptr); + BIO_free(b); + if (base64) { +#ifdef XMLTOOLING_XERCESC_HAS_XMLBYTE_RELEASE + XMLString::release(&decoded); +#else + XMLString::release((char**)&decoded); +#endif + } + + if (pkey) { + // Now map it to an XSEC wrapper. + XSECCryptoKey* ret = nullptr; + try { + switch (pkey->type) { + case EVP_PKEY_RSA: + ret = new OpenSSLCryptoKeyRSA(pkey); + break; + + case EVP_PKEY_DSA: + ret = new OpenSSLCryptoKeyDSA(pkey); + break; + +#if defined(XMLTOOLING_XMLSEC_ECC) && defined(XMLTOOLING_OPENSSL_HAVE_EC) + case EVP_PKEY_EC: + ret = new OpenSSLCryptoKeyEC(pkey); + break; +#endif + default: + Category::getInstance(XMLTOOLING_LOGCAT".SecurityHelper").error("unsupported public key type"); + } + } + catch (XSECCryptoException& ex) { + Category::getInstance(XMLTOOLING_LOGCAT".SecurityHelper").error(ex.getMsg()); + } + EVP_PKEY_free(pkey); + return ret; + } + + return nullptr; +} + +XSECCryptoKey* SecurityHelper::fromDEREncoding(const XMLCh* buf) +{ + xsecsize_t x; + XMLByte* decoded = xercesc::Base64::decodeToXMLByte(buf, &x); + if (!decoded) { + Category::getInstance(XMLTOOLING_LOGCAT".SecurityHelper").error("base64 decode failed"); + return nullptr; + } + XSECCryptoKey* ret = fromDEREncoding((const char*)decoded, x, false); +#ifdef XMLTOOLING_XERCESC_HAS_XMLBYTE_RELEASE + XMLString::release(&decoded); +#else + XMLString::release((char**)&decoded); +#endif + return ret; +}