diff options
Diffstat (limited to 'cpp/src/IceSSL/Context.cpp')
-rw-r--r-- | cpp/src/IceSSL/Context.cpp | 1325 |
1 files changed, 795 insertions, 530 deletions
diff --git a/cpp/src/IceSSL/Context.cpp b/cpp/src/IceSSL/Context.cpp index 099eaa17bed..ffa4d6a8a11 100644 --- a/cpp/src/IceSSL/Context.cpp +++ b/cpp/src/IceSSL/Context.cpp @@ -7,650 +7,915 @@ // // ********************************************************************** +#include <Context.h> +#include <Instance.h> +#include <Util.h> #include <Ice/Communicator.h> +#include <Ice/LocalException.h> +#include <Ice/Logger.h> #include <Ice/LoggerUtil.h> #include <Ice/Properties.h> -#include <IceSSL/DefaultCertificateVerifier.h> -#include <IceSSL/Exception.h> -#include <IceSSL/RSAKeyPair.h> -#include <IceSSL/CertificateDesc.h> -#include <IceSSL/SslTransceiver.h> -#include <IceSSL/Context.h> -#include <IceSSL/OpenSSLJanitors.h> -#include <IceSSL/OpenSSLUtils.h> -#include <IceSSL/TraceLevels.h> - +#ifdef _WIN32 +# include <direct.h> +# include <sys/types.h> +# include <sys/stat.h> +# define S_ISDIR(mode) ((mode) & _S_IFDIR) +# define S_ISREG(mode) ((mode) & _S_IFREG) +#else +# include <sys/stat.h> +#endif + +#include <openssl/x509v3.h> #include <openssl/err.h> using namespace std; using namespace Ice; -using namespace IceInternal; - -void IceInternal::incRef(::IceSSL::Context* p) { p->__incRef(); } -void IceInternal::decRef(::IceSSL::Context* p) { p->__decRef(); } - -IceSSL::Context::~Context() -{ - cleanUp(); -} +using namespace IceSSL; -bool -IceSSL::Context::isConfigured() +static int +opensslPasswordCallback(char* buf, int size, int flag, void* userData) { - return (_sslContext != 0 ? true : false); -} - -void -IceSSL::Context::cleanUp() -{ - if(_sslContext != 0) + IceSSL::Context* c = reinterpret_cast<IceSSL::Context*>(userData); + string passwd = c->password(flag == 1); + int sz = static_cast<int>(passwd.size()); + if(sz > size) { - SSL_CTX_free(_sslContext); - - _sslContext = 0; + sz = size - 1; } + strncpy(buf, passwd.c_str(), sz); + buf[sz] = '\0'; + return sz; } -void -IceSSL::Context::setCertificateVerifier(const CertificateVerifierPtr& verifier) +#ifndef OPENSSL_NO_DH +static DH* +opensslDHCallback(SSL* ssl, int /*isExport*/, int keyLength) { - _certificateVerifier = verifier; - _certificateVerifier->setContext(_contextType); + IceSSL::Context* c = reinterpret_cast<IceSSL::Context*>(SSL_CTX_get_ex_data(ssl->ctx, 0)); + return c->dhParams(keyLength); } +#endif -void -IceSSL::Context::addTrustedCertificateBase64(const string& trustedCertString) +static int +opensslVerifyCallback(int ok, X509_STORE_CTX* ctx) { - RSAPublicKey pubKey(trustedCertString); - - addTrustedCertificate(pubKey); + SSL* ssl = reinterpret_cast<SSL*>(X509_STORE_CTX_get_ex_data(ctx, SSL_get_ex_data_X509_STORE_CTX_idx())); + IceSSL::Context* c = reinterpret_cast<IceSSL::Context*>(SSL_CTX_get_ex_data(ssl->ctx, 0)); + return c->verifyCallback(ok, ssl, ctx); } -void -IceSSL::Context::addTrustedCertificate(const Ice::ByteSeq& trustedCert) +static bool +passwordError() { - RSAPublicKey pubKey(trustedCert); - - addTrustedCertificate(pubKey); + int reason = ERR_GET_REASON(ERR_peek_error()); + return (reason == PEM_R_BAD_BASE64_DECODE || + reason == PEM_R_BAD_DECRYPT || + reason == PEM_R_BAD_PASSWORD_READ || + reason == PEM_R_PROBLEMS_GETTING_PASSWORD); } -void -IceSSL::Context::setRSAKeysBase64(const string& privateKey, const string& publicKey) +// +// Context. +// +IceSSL::Context::Context(const InstancePtr& instance, const string& propPrefix, SSL_CTX* ctx) : + _instance(instance), + _logger(instance->communicator()->getLogger()), + _ctx(ctx) { - if(privateKey.empty()) + if(_ctx) { - IceSSL::PrivateKeyException privateKeyEx(__FILE__, __LINE__); - - privateKeyEx.message = "Empty private key supplied."; - - throw privateKeyEx; + return; } - addKeyCert(privateKey, publicKey); -} - -void -IceSSL::Context::setRSAKeys(const Ice::ByteSeq& privateKey, const Ice::ByteSeq& publicKey) -{ - if(privateKey.empty()) + _ctx = SSL_CTX_new(SSLv23_method()); + if(!_ctx) { - IceSSL::PrivateKeyException privateKeyEx(__FILE__, __LINE__); - - privateKeyEx.message = "Empty private key supplied."; - - throw privateKeyEx; + string err = _instance->sslErrors(); + string msg = "IceSSL: unable to create SSL context:\n" + err; + if(_instance->securityTraceLevel() >= 1) + { + _logger->trace(_instance->securityTraceCategory(), msg); + } + PluginInitializationException ex(__FILE__, __LINE__); + ex.reason = msg; + throw ex; } - addKeyCert(privateKey, publicKey); -} - -void -IceSSL::Context::configure(const GeneralConfig& generalConfig, - const CertificateAuthority& certificateAuthority, - const BaseCertificates& baseCertificates) -{ - // Create an SSL Context based on the context params. - createContext(generalConfig.getProtocol()); - - // Enable workarounds and disable SSLv2. - SSL_CTX_set_options(_sslContext, SSL_OP_ALL|SSL_OP_NO_SSLv2); - - // Get the cipherlist and set it in the context. - setCipherList(generalConfig.getCipherList()); - - // Set the certificate verification mode. - SSL_CTX_set_verify(_sslContext, generalConfig.getVerifyMode(), verifyCallback); - - // Set the certificate verify depth - SSL_CTX_set_verify_depth(_sslContext, generalConfig.getVerifyDepth()); - - // Determine the number of retries the user gets on passphrase entry. - string passphraseRetries = _communicator->getProperties()->getPropertyWithDefault(_passphraseRetriesProperty, - _maxPassphraseRetriesDefault); - int retries = atoi(passphraseRetries.c_str()); - retries = (retries < 0 ? 0 : retries); - _maxPassphraseTries = retries + 1; + // + // Store a pointer to ourself for use in OpenSSL callbacks. + // + SSL_CTX_set_ex_data(_ctx, 0, this); - // Process the RSA Certificate - setKeyCert(baseCertificates.getRSACert(), _rsaPrivateKeyProperty, _rsaPublicKeyProperty); + PropertiesPtr properties = _instance->communicator()->getProperties(); - // Process the DSA Certificate - setKeyCert(baseCertificates.getDSACert(), _dsaPrivateKeyProperty, _dsaPublicKeyProperty); - - // Set the DH key agreement parameters. - if(baseCertificates.getDHParams().getKeySize() != 0) + // + // Check for a default directory. We look in this directory for + // files mentioned in the configuration. + // { - setDHParams(baseCertificates); + _defaultDir = properties->getProperty(propPrefix + "DefaultDir"); } -} - -// -// Protected -// - -IceSSL::Context::Context(const TraceLevelsPtr& traceLevels, const CommunicatorPtr& communicator, - const ContextType& type) : - _traceLevels(traceLevels), - _communicator(communicator), - _contextType(type) -{ - _certificateVerifier = new DefaultCertificateVerifier(traceLevels, communicator); - _certificateVerifier->setContext(_contextType); - _sslContext = 0; - - _maxPassphraseRetriesDefault = "4"; -} -SSL_METHOD* -IceSSL::Context::getSslMethod(SslProtocol sslVersion) -{ - SSL_METHOD* sslMethod = 0; - - switch(sslVersion) + // + // Select protocols. + // { - case SSL_V23 : - { - sslMethod = SSLv23_method(); - break; - } - - case SSL_V3 : - { - sslMethod = SSLv3_method(); - break; - } - - case TLS_V1 : - { - sslMethod = TLSv1_method(); - break; - } - - default : - { - if(_traceLevels->security >= IceSSL::SECURITY_WARNINGS) - { - Trace out(_communicator->getLogger(), _traceLevels->securityCat); - out << "WRN ssl version " << sslVersion; - out << " not supported (defaulting to SSL_V23)"; - } - - sslMethod = SSLv23_method(); - } + string protocols = properties->getProperty(propPrefix + "Protocols"); + if(!protocols.empty()) + { + parseProtocols(protocols); + } } - return sslMethod; -} - -void -IceSSL::Context::createContext(SslProtocol sslProtocol) -{ - if(_sslContext != 0) + // + // Determine whether a certificate is required from the peer. + // { - SSL_CTX_free(_sslContext); - _sslContext = 0; + int verifyPeer = properties->getPropertyAsIntWithDefault(propPrefix + "VerifyPeer", 2); + int sslVerifyMode; + switch(verifyPeer) + { + case 0: + sslVerifyMode = SSL_VERIFY_NONE; + break; + case 1: + sslVerifyMode = SSL_VERIFY_PEER; + break; + case 2: + sslVerifyMode = SSL_VERIFY_PEER|SSL_VERIFY_FAIL_IF_NO_PEER_CERT; + break; + default: + { + string msg = "IceSSL: invalid value for " + propPrefix + "VerifyPeer"; + if(_instance->securityTraceLevel() >= 1) + { + _logger->trace(_instance->securityTraceCategory(), msg); + } + PluginInitializationException ex(__FILE__, __LINE__); + ex.reason = msg; + throw ex; + } + } + SSL_CTX_set_verify(_ctx, sslVerifyMode, opensslVerifyCallback); } - _sslContext = SSL_CTX_new(getSslMethod(sslProtocol)); - - if(_sslContext == 0) + // + // If the configuration defines a password, or the application has supplied + // a password prompt object, then register a password callback. Otherwise, + // let OpenSSL use its default behavior. + // { - ContextInitializationException contextInitEx(__FILE__, __LINE__); - - contextInitEx.message = "unable to create ssl context\n" + sslGetErrors(); - - throw contextInitEx; + // TODO: Support quoted value? + string password = properties->getProperty(propPrefix + "Password"); + if(!password.empty() || _instance->passwordPrompt()) + { + SSL_CTX_set_default_passwd_cb(_ctx, opensslPasswordCallback); + SSL_CTX_set_default_passwd_cb_userdata(_ctx, this); + _password = password; + } } - // Turn off session caching, supposedly fixes a problem with multithreading. - SSL_CTX_set_session_cache_mode(_sslContext, SSL_SESS_CACHE_OFF); -} - -void -IceSSL::Context::loadCertificateAuthority(const CertificateAuthority& certAuth) -{ - assert(_sslContext != 0); - - string fileName = certAuth.getCAFileName(); - string certPath = certAuth.getCAPath(); + int passwordRetryMax = properties->getPropertyAsIntWithDefault(propPrefix + "PasswordRetryMax", 3); - const char* caFile = 0; - const char* caPath = 0; - - // The following checks are required to send the expected values to the OpenSSL library. - // It does not like receiving "", but prefers NULLs. - - if(!fileName.empty()) + // + // Establish the location of CA certificates. + // { - caFile = fileName.c_str(); + string caFile = properties->getProperty(propPrefix + "CertAuthFile"); + string caDir = properties->getPropertyWithDefault(propPrefix + "CertAuthDir", _defaultDir); + const char* file = 0; + const char* dir = 0; + if(!caFile.empty()) + { + if(!checkPath(caFile, false)) + { + string msg = "IceSSL: CA certificate file not found:\n" + caFile; + if(_instance->securityTraceLevel() >= 1) + { + _logger->trace(_instance->securityTraceCategory(), msg); + } + PluginInitializationException ex(__FILE__, __LINE__); + ex.reason = msg; + throw ex; + } + file = caFile.c_str(); + } + if(!caDir.empty()) + { + if(!checkPath(caDir, true)) + { + string msg = "IceSSL: CA certificate directory not found:\n" + caDir; + if(_instance->securityTraceLevel() >= 1) + { + _logger->trace(_instance->securityTraceCategory(), msg); + } + PluginInitializationException ex(__FILE__, __LINE__); + ex.reason = msg; + throw ex; + } + dir = caDir.c_str(); + } + if(file || dir) + { + // + // The certificate may be stored in an encrypted file, so handle + // password retries. + // + int count = 0; + int err; + while(count < passwordRetryMax) + { + ERR_clear_error(); + err = SSL_CTX_load_verify_locations(_ctx, file, dir); + if(err || !passwordError()) + { + break; + } + ++count; + } + if(err == 0) + { + string msg = "IceSSL: unable to establish CA certificates"; + if(passwordError()) + { + msg += ":\ninvalid password"; + } + else + { + string err = _instance->sslErrors(); + if(!err.empty()) + { + msg += ":\n" + err; + } + } + if(_instance->securityTraceLevel() >= 1) + { + _logger->trace(_instance->securityTraceCategory(), msg); + } + PluginInitializationException ex(__FILE__, __LINE__); + ex.reason = msg; + throw ex; + } + } } - if(!certPath.empty()) + // + // Establish the certificate chains and private keys. One RSA certificate and + // one DSA certificate are allowed. + // { - caPath = certPath.c_str(); +#ifdef _WIN32 + const string sep = ";"; +#else + const string sep = ":"; +#endif + string certFile = properties->getProperty(propPrefix + "CertFile"); + string keyFile = properties->getProperty(propPrefix + "KeyFile"); + vector<string>::size_type numCerts = 0; + if(!certFile.empty()) + { + vector<string> files; + if(!splitString(certFile, sep, false, files) || files.size() > 2) + { + string msg = "IceSSL: invalid value for " + propPrefix + "CertFile:\n" + certFile; + if(_instance->securityTraceLevel() >= 1) + { + _logger->trace(_instance->securityTraceCategory(), msg); + } + PluginInitializationException ex(__FILE__, __LINE__); + ex.reason = msg; + throw ex; + } + numCerts = files.size(); + for(vector<string>::iterator p = files.begin(); p != files.end(); ++p) + { + string file = *p; + if(!checkPath(file, false)) + { + string msg = "IceSSL: certificate file not found:\n" + file; + if(_instance->securityTraceLevel() >= 1) + { + _logger->trace(_instance->securityTraceCategory(), msg); + } + PluginInitializationException ex(__FILE__, __LINE__); + ex.reason = msg; + throw ex; + } + // + // The certificate may be stored in an encrypted file, so handle + // password retries. + // + int count = 0; + int err; + while(count < passwordRetryMax) + { + ERR_clear_error(); + err = SSL_CTX_use_certificate_chain_file(_ctx, file.c_str()); + if(err || !passwordError()) + { + break; + } + ++count; + } + if(err == 0) + { + string msg = "IceSSL: unable to load certificate chain from file " + file; + if(passwordError()) + { + msg += ":\ninvalid password"; + } + else + { + string err = _instance->sslErrors(); + if(!err.empty()) + { + msg += ":\n" + err; + } + } + if(_instance->securityTraceLevel() >= 1) + { + _logger->trace(_instance->securityTraceCategory(), msg); + } + PluginInitializationException ex(__FILE__, __LINE__); + ex.reason = msg; + throw ex; + } + } + } + if(keyFile.empty()) + { + keyFile = certFile; // Assume the certificate file also contains the private key. + } + if(!keyFile.empty()) + { + vector<string> files; + if(!splitString(keyFile, sep, false, files) || files.size() > 2) + { + string msg = "IceSSL: invalid value for " + propPrefix + "KeyFile:\n" + keyFile; + if(_instance->securityTraceLevel() >= 1) + { + _logger->trace(_instance->securityTraceCategory(), msg); + } + PluginInitializationException ex(__FILE__, __LINE__); + ex.reason = msg; + throw ex; + } + if(files.size() != numCerts) + { + string msg = "IceSSL: " + propPrefix + "KeyFile does not agree with " + propPrefix + "CertFile"; + if(_instance->securityTraceLevel() >= 1) + { + _logger->trace(_instance->securityTraceCategory(), msg); + } + PluginInitializationException ex(__FILE__, __LINE__); + ex.reason = msg; + throw ex; + } + for(vector<string>::iterator p = files.begin(); p != files.end(); ++p) + { + string file = *p; + if(!checkPath(file, false)) + { + string msg = "IceSSL: key file not found:\n" + file; + if(_instance->securityTraceLevel() >= 1) + { + _logger->trace(_instance->securityTraceCategory(), msg); + } + PluginInitializationException ex(__FILE__, __LINE__); + ex.reason = msg; + throw ex; + } + // + // The private key may be stored in an encrypted file, so handle + // password retries. + // + int count = 0; + int err; + while(count < passwordRetryMax) + { + ERR_clear_error(); + err = SSL_CTX_use_PrivateKey_file(_ctx, file.c_str(), SSL_FILETYPE_PEM); + if(err || !passwordError()) + { + break; + } + ++count; + } + if(err == 0) + { + string msg = "IceSSL: unable to load private key from file " + file; + if(passwordError()) + { + msg += ":\ninvalid password"; + } + else + { + string err = _instance->sslErrors(); + if(!err.empty()) + { + msg += ":\n" + err; + } + } + if(_instance->securityTraceLevel() >= 1) + { + _logger->trace(_instance->securityTraceCategory(), msg); + } + PluginInitializationException ex(__FILE__, __LINE__); + ex.reason = msg; + throw ex; + } + } + if(!SSL_CTX_check_private_key(_ctx)) + { + string err = _instance->sslErrors(); + string msg = "IceSSL: unable to validate private key(s):\n" + err; + if(_instance->securityTraceLevel() >= 1) + { + _logger->trace(_instance->securityTraceCategory(), msg); + } + PluginInitializationException ex(__FILE__, __LINE__); + ex.reason = msg; + throw ex; + } + } } - // SSL_CTX_set_default_passwd_cb(sslContext, passwordCallback); - - // Check the Certificate Authority file(s). - int loadVerifyRet = SSL_CTX_load_verify_locations(_sslContext, caFile, caPath); - - if(!loadVerifyRet) - { - if(_traceLevels->security >= IceSSL::SECURITY_WARNINGS) - { - Trace out(_communicator->getLogger(), _traceLevels->securityCat); - out << "WRN unable to load certificate authorities."; - } - } - else + // + // Establish the cipher list. + // { - int setDefaultVerifyPathsRet = SSL_CTX_set_default_verify_paths(_sslContext); + string ciphers = properties->getProperty(propPrefix + "Ciphers"); + if(!ciphers.empty()) + { + if(!SSL_CTX_set_cipher_list(_ctx, ciphers.c_str())) + { + string err = _instance->sslErrors(); + string msg = "IceSSL: unable to set ciphers using `" + ciphers + "':\n" + err; + if(_instance->securityTraceLevel() >= 1) + { + _logger->trace(_instance->securityTraceCategory(), msg); + } + PluginInitializationException ex(__FILE__, __LINE__); + ex.reason = msg; + throw ex; + } + } + } - if(!setDefaultVerifyPathsRet && (_traceLevels->security >= IceSSL::SECURITY_WARNINGS)) - { - Trace out(_communicator->getLogger(), _traceLevels->securityCat); - out << "WRN unable to verify certificate authorities."; - } + // + // Establish the maximum verify depth. + // + { + int depth = properties->getPropertyAsIntWithDefault(propPrefix + "VerifyDepthMax", -1); + if(depth >= 0) + { + SSL_CTX_set_verify_depth(_ctx, depth); + } } - // Now we add whatever override/addition that we wish to put into the trusted certificates list - string caCertBase64 = _communicator->getProperties()->getProperty(_caCertificateProperty); - if(!caCertBase64.empty()) + // + // Diffie Hellman configuration. + // { - addTrustedCertificateBase64(caCertBase64); +#ifndef OPENSSL_NO_DH + _dhParams = new DHParams; + SSL_CTX_set_options(_ctx, SSL_OP_SINGLE_DH_USE); + SSL_CTX_set_tmp_dh_callback(_ctx, opensslDHCallback); +#endif + // + // Properties have the following form: + // + // ...DH.<keyLength>=file + // + const string dhPrefix = propPrefix + "DH."; + PropertyDict d = properties->getPropertiesForPrefix(dhPrefix); + if(!d.empty()) + { +#ifdef OPENSSL_NO_DH + _logger->warning("IceSSL: OpenSSL is not configured for Diffie Hellman"); +#else + for(PropertyDict::iterator p = d.begin(); p != d.end(); ++p) + { + string s = p->first.substr(dhPrefix.size()); + int keyLength = atoi(s.c_str()); + if(keyLength > 0) + { + string file = p->second; + if(!checkPath(file, false)) + { + string msg = "IceSSL: DH parameter file not found:\n" + file; + if(_instance->securityTraceLevel() >= 1) + { + _logger->trace(_instance->securityTraceCategory(), msg); + } + PluginInitializationException ex(__FILE__, __LINE__); + ex.reason = msg; + throw ex; + } + if(!_dhParams->add(keyLength, file)) + { + string msg = "IceSSL: unable to read DH parameter file " + file; + if(_instance->securityTraceLevel() >= 1) + { + _logger->trace(_instance->securityTraceCategory(), msg); + } + PluginInitializationException ex(__FILE__, __LINE__); + ex.reason = msg; + throw ex; + } + } + } +#endif + } } } -void -IceSSL::Context::setKeyCert(const CertificateDesc& certDesc, - const string& privateProperty, - const string& publicProperty) +IceSSL::Context::~Context() { - string privateKey; - string publicKey; - - if(!privateProperty.empty()) + if(_ctx) { - privateKey = _communicator->getProperties()->getProperty(privateProperty); + SSL_CTX_free(_ctx); } +} - if(!publicProperty.empty()) +SSL_CTX* +IceSSL::Context::ctx() const +{ + return _ctx; +} + +void +IceSSL::Context::validatePeer(SSL* ssl, const string& address, bool incoming) +{ + long result = SSL_get_verify_result(ssl); + if(result != X509_V_OK) { - publicKey = _communicator->getProperties()->getProperty(publicProperty); + ostringstream ostr; + ostr << "IceSSL: certificate verification failed:\n" << X509_verify_cert_error_string(result); + string msg = ostr.str(); + if(_instance->securityTraceLevel() >= 1) + { + _logger->trace(_instance->securityTraceCategory(), msg); + } + SecurityException ex(__FILE__, __LINE__); + ex.reason = msg; + throw ex; } - if(!privateKey.empty() && !publicKey.empty()) + X509* cert = SSL_get_peer_certificate(ssl); + try { - addKeyCert(privateKey, publicKey); + // + // Collect the dnsName and ipAddress values that appear in the peer's subjectAltName + // certificate extension. + // + vector<string> dnsNames, ipAddresses; + if(cert) + { + GENERAL_NAMES* gens = reinterpret_cast<GENERAL_NAMES*>(X509_get_ext_d2i(cert, NID_subject_alt_name, 0, 0)); + int i; + for(i = 0; i < sk_GENERAL_NAME_num(gens); ++i) + { + GENERAL_NAME* gen = sk_GENERAL_NAME_value(gens, i); + if(gen->type == GEN_DNS) + { + ASN1_IA5STRING* str = gen->d.dNSName; + if(str && str->type == V_ASN1_IA5STRING && str->data && str->length > 0) + { + string s = reinterpret_cast<const char*>(str->data); + dnsNames.push_back(s); + } + } + else if(gen->type == GEN_IPADD) + { + ASN1_OCTET_STRING* addr = gen->d.iPAddress; + // TODO: Support IPv6 someday. + if(addr && addr->type == V_ASN1_OCTET_STRING && addr->data && addr->length == 4) + { + ostringstream ostr; + for(int j = 0; j < 4; ++j) + { + if(j > 0) + { + ostr << '.'; + } + ostr << static_cast<int>(addr->data[j]); + } + ipAddresses.push_back(ostr.str()); + } + } + } + sk_GENERAL_NAME_pop_free(gens, GENERAL_NAME_free); + } + + CertificateVerifierPtr verifier = _instance->certificateVerifier(); + + // + // Compare the peer's address against the dnsName and ipAddress values. + // + if(!address.empty()) + { + bool certNameOK = false; + + for(vector<string>::iterator p = ipAddresses.begin(); p != ipAddresses.end() && !certNameOK; ++p) + { + if(address == *p) + { + certNameOK = true; + } + } + + if(!certNameOK && !dnsNames.empty()) + { + string host = address; + transform(host.begin(), host.end(), host.begin(), ::tolower); + for(vector<string>::iterator p = dnsNames.begin(); p != dnsNames.end() && !certNameOK; ++p) + { + string s = *p; + transform(s.begin(), s.end(), s.begin(), ::tolower); + if(host == s) + { + certNameOK = true; + } + } + } + + // + // Log a message if the name comparison fails. If CheckCertName is defined, + // we also raise an exception to abort the connection. Don't log a message + // if CheckCertName is not defined and a verifier is present. + // + if(!certNameOK && (_checkCertName || (_instance->securityTraceLevel() >= 1 && !verifier))) + { + ostringstream ostr; + ostr << "IceSSL: "; + if(!_checkCertName) + { + ostr << "ignoring "; + } + ostr << "certificate validation failure:\npeer certificate does not contain `" + << address << "' in its subjectAltName extension"; + if(!dnsNames.empty()) + { + ostr << "\nDNS names found in certificate: "; + for(vector<string>::iterator p = dnsNames.begin(); p != dnsNames.end(); ++p) + { + if(p != dnsNames.begin()) + { + ostr << ", "; + } + ostr << *p; + } + } + if(!ipAddresses.empty()) + { + ostr << "\nIP addresses found in certificate: "; + for(vector<string>::iterator p = ipAddresses.begin(); p != ipAddresses.end(); ++p) + { + if(p != ipAddresses.begin()) + { + ostr << ", "; + } + ostr << *p; + } + } + string msg = ostr.str(); + if(_instance->securityTraceLevel() >= 1) + { + Trace out(_logger, _instance->securityTraceCategory()); + out << msg; + } + if(_checkCertName) + { + SecurityException ex(__FILE__, __LINE__); + ex.reason = msg; + throw ex; + } + } + } + + if(verifier) + { + VerifyInfo info; + const_cast<bool&>(info.incoming) = incoming; + info.cert = cert; + info.ssl = ssl; + const_cast<string&>(info.address) = address; + const_cast<vector<string>&>(info.dnsNames) = dnsNames; + const_cast<vector<string>&>(info.ipAddresses) = ipAddresses; + verifier->verify(info); + } } - else if(certDesc.getKeySize() != 0) + catch(...) { - addKeyCert(certDesc.getPrivate(), certDesc.getPublic()); + if(cert) + { + X509_free(cert); + } + throw; } -} - -void -IceSSL::Context::checkKeyCert() -{ - assert(_sslContext != 0); - - // Check to see if the Private and Public keys that have been - // set against the SSL context match up. - if(!SSL_CTX_check_private_key(_sslContext)) + if(cert) { - CertificateKeyMatchException certKeyMatchEx(__FILE__, __LINE__); - - certKeyMatchEx.message = "private key does not match the certificate public key"; - string sslError = sslGetErrors(); - - if(!sslError.empty()) - { - certKeyMatchEx.message += "\n"; - certKeyMatchEx.message += sslError; - } - - throw certKeyMatchEx; + X509_free(cert); } } -void -IceSSL::Context::addTrustedCertificate(const RSAPublicKey& trustedCertificate) +string +IceSSL::Context::password(bool /*encrypting*/) { - if(_sslContext == 0) + PasswordPromptPtr prompt = _instance->passwordPrompt(); + if(prompt) { - ContextNotConfiguredException contextConfigEx(__FILE__, __LINE__); - - contextConfigEx.message = "ssl context not configured"; - - throw contextConfigEx; + try + { + return prompt->getPassword(); + } + catch(...) + { + // + // Don't allow exceptions to cross an OpenSSL boundary. + // + return string(); + } } - - X509_STORE* certStore = SSL_CTX_get_cert_store(_sslContext); - - assert(certStore != 0); - - if(X509_STORE_add_cert(certStore, trustedCertificate.getX509PublicKey()) == 0) + else { - TrustedCertificateAddException trustEx(__FILE__, __LINE__); - - trustEx.message = sslGetErrors(); - - throw trustEx; + return _password; } } -void -IceSSL::Context::addKeyCert(const CertificateFile& privateKey, const CertificateFile& publicCert) +#ifndef OPENSSL_NO_DH +DH* +IceSSL::Context::dhParams(int keyLength) { - assert(_sslContext != 0); + return _dhParams->get(keyLength); +} +#endif - if(!publicCert.getFileName().empty()) +int +IceSSL::Context::verifyCallback(int ok, SSL* ssl, X509_STORE_CTX* c) +{ + if(!ok && _instance->securityTraceLevel() >= 1) { - string publicCertFile = publicCert.getFileName(); - const char* publicFile = publicCertFile.c_str(); - int publicEncoding = publicCert.getEncoding(); - - string privCertFile = privateKey.getFileName(); - const char* privKeyFile = privCertFile.c_str(); - int privKeyFileType = privateKey.getEncoding(); - - // Set which Public Key file to use. - if(SSL_CTX_use_certificate_file(_sslContext, publicFile, publicEncoding) <= 0) - { - CertificateLoadException certLoadEx(__FILE__, __LINE__); - - certLoadEx.message = "unable to load certificate from '"; - certLoadEx.message += publicFile; - certLoadEx.message += "'\n"; - certLoadEx.message += sslGetErrors(); - - throw certLoadEx; - } - - if(privateKey.getFileName().empty()) - { - if(_traceLevels->security >= IceSSL::SECURITY_WARNINGS) - { - Trace out(_communicator->getLogger(), _traceLevels->securityCat); - out << "WRN no private key specified -- using the certificate"; - } - - privKeyFile = publicFile; - privKeyFileType = publicEncoding; - } - - int retryCount = 0; - int pkLoadResult = 0; - int errCode = 0; - - while(retryCount != _maxPassphraseTries) - { - // We ignore the errors and remove them from the stack. - string errorString = sslGetErrors(); - - // Set which Private Key file to use. - pkLoadResult = SSL_CTX_use_PrivateKey_file(_sslContext, privKeyFile, privKeyFileType); - - if(pkLoadResult <= 0) - { - errCode = ERR_GET_REASON(ERR_peek_error()); - } - else - { - // The load went fine - continue on. - break; - } - - // PEM errors, most likely related to a bad passphrase. - if(errCode != PEM_R_BAD_PASSWORD_READ && - errCode != PEM_R_BAD_DECRYPT && - errCode != PEM_R_BAD_BASE64_DECODE) - { - // Other errors get dealt with below. - break; - } - - cout << "Passphrase error!" << endl; - - retryCount++; - } - - if(pkLoadResult <= 0) - { - errCode = ERR_GET_REASON(ERR_peek_error()); - - // Note: Because OpenSSL currently (V0.9.6b) performs a check to see if the - // key matches the private key when calling SSL_CTX_use_PrivateKey_file(). - if(errCode == X509_R_KEY_VALUES_MISMATCH || errCode == X509_R_KEY_TYPE_MISMATCH) - { - CertificateKeyMatchException certKeyMatchEx(__FILE__, __LINE__); - - certKeyMatchEx.message = "private key does not match the certificate public key"; - string sslError = sslGetErrors(); - - if(!sslError.empty()) - { - certKeyMatchEx.message += "\n"; - certKeyMatchEx.message += sslError; - } - - throw certKeyMatchEx; - } - else - { - PrivateKeyLoadException pklEx(__FILE__, __LINE__); - - pklEx.message = "unable to load private key from '"; - pklEx.message += privKeyFile; - pklEx.message += "'\n"; - pklEx.message += sslGetErrors(); - - throw pklEx; - } - } - - checkKeyCert(); + X509* cert = X509_STORE_CTX_get_current_cert(c); + int err = X509_STORE_CTX_get_error(c); + char buf[256]; + + Trace out(_logger, _instance->securityTraceCategory()); + out << "certificate verification failure\n"; + + X509_NAME_oneline(X509_get_issuer_name(cert), buf, sizeof(buf)); + out << "issuer = " << buf << '\n'; + X509_NAME_oneline(X509_get_subject_name(cert), buf, sizeof(buf)); + out << "subject = " << buf << '\n'; + out << "depth = " << X509_STORE_CTX_get_error_depth(c) << '\n'; + out << "error = " << X509_verify_cert_error_string(err) << '\n'; + out << IceInternal::fdToString(SSL_get_fd(ssl)); } + return ok; } void -IceSSL::Context::addKeyCert(const RSAKeyPair& keyPair) +IceSSL::Context::traceConnection(SSL* ssl, bool incoming) { - if(_sslContext == 0) - { - ContextNotConfiguredException contextConfigEx(__FILE__, __LINE__); - - contextConfigEx.message = "ssl context not configured"; - - throw contextConfigEx; - } - - // Note: Normally I would use an X509Janitor and RSAJanitor to ensure that - // memory was being freed properly when exceptions are thrown, but - // both SSL_CTX_use_certificate and SSL_CTX_use_RSAPrivateKey free - // certificate/key memory regardless if the call succeeded. - - // Set which Public Key file to use. - if(SSL_CTX_use_certificate(_sslContext, keyPair.getX509PublicKey()) <= 0) + Trace out(_logger, _instance->securityTraceCategory()); + out << "SSL summary for " << (incoming ? "incoming" : "outgoing") << " connection\n"; + SSL_CIPHER* cipher = SSL_get_current_cipher(ssl); + if(!cipher) { - CertificateLoadException certLoadEx(__FILE__, __LINE__); - - certLoadEx.message = "unable to set certificate from memory"; - string sslError = sslGetErrors(); - - if(!sslError.empty()) - { - certLoadEx.message += "\n"; - certLoadEx.message += sslError; - } - - throw certLoadEx; + out << "unknown cipher\n"; } - - // Set which Private Key file to use. - if(SSL_CTX_use_RSAPrivateKey(_sslContext, keyPair.getRSAPrivateKey()) <= 0) + else { - int errCode = ERR_GET_REASON(ERR_peek_error()); - - // Note: Because OpenSSL currently (V0.9.6b) performs a check to see if the - // key matches the private key when calling SSL_CTX_use_PrivateKey_file(). - if(errCode == X509_R_KEY_VALUES_MISMATCH || errCode == X509_R_KEY_TYPE_MISMATCH) - { - CertificateKeyMatchException certKeyMatchEx(__FILE__, __LINE__); - - certKeyMatchEx.message = "private key does not match the certificate public key"; - string sslError = sslGetErrors(); - - if(!sslError.empty()) - { - certKeyMatchEx.message += "\n"; - certKeyMatchEx.message += sslError; - } - - throw certKeyMatchEx; - } - else - { - PrivateKeyLoadException pklEx(__FILE__, __LINE__); - - pklEx.message = "unable to set private key from memory"; - string sslError = sslGetErrors(); - - if(!sslError.empty()) - { - pklEx.message += "\n"; - pklEx.message += sslError; - } - - throw pklEx; - } + out << "cipher = " << SSL_CIPHER_get_name(cipher) << "\n"; + out << "bits = " << SSL_CIPHER_get_bits(cipher, 0) << "\n"; + out << "protocol = " << SSL_get_version(ssl) << "\n"; } - - checkKeyCert(); + out << IceInternal::fdToString(SSL_get_fd(ssl)); } -void -IceSSL::Context::addKeyCert(const Ice::ByteSeq& privateKey, const Ice::ByteSeq& publicKey) +bool +IceSSL::Context::checkPath(string& path, bool dir) { - Ice::ByteSeq privKey = privateKey; - - if(privKey.empty()) + // + // Check if file exists. If not, try prepending the default + // directory and check again. If the file is found, the + // string argument is modified and true is returned. Otherwise + // false is returned. + // +#ifdef _WIN32 + struct _stat st; + int err = ::_stat(path.c_str(), &st); +#else + struct stat st; + int err = ::stat(path.c_str(), &st); +#endif + if(err == 0) { - if(_traceLevels->security >= IceSSL::SECURITY_WARNINGS) - { - Trace out(_communicator->getLogger(), _traceLevels->securityCat); - out << "WRN no private key specified -- using the certificate"; - } - - privKey = publicKey; + return dir ? S_ISDIR(st.st_mode) != 0 : S_ISREG(st.st_mode) != 0; } - // Make a key pair based on the DER encoded byte sequences. - RSAKeyPair rsaKeyPair(privKey, publicKey); - addKeyCert(rsaKeyPair); -} - -void -IceSSL::Context::addKeyCert(const string& privateKey, const string& publicKey) -{ - string privKey = privateKey; - - if(privKey.empty()) + if(!_defaultDir.empty()) { - if(_traceLevels->security >= IceSSL::SECURITY_WARNINGS) - { - Trace out(_communicator->getLogger(), _traceLevels->securityCat); - out << "WRN no private key specified -- using the certificate"; - } - - privKey = publicKey; +#ifdef _WIN32 + string s = _defaultDir + "\\" + path; + err = ::_stat(s.c_str(), &st); +#else + string s = _defaultDir + "/" + path; + err = ::stat(s.c_str(), &st); +#endif + if(err == 0 && ((!dir && S_ISREG(st.st_mode)) || (dir && S_ISDIR(st.st_mode)))) + { + path = s; + return true; + } } - // Make a key pair based on the Base64 encoded strings. - RSAKeyPair rsaKeyPair(privKey, publicKey); - addKeyCert(rsaKeyPair); -} - -SSL* -IceSSL::Context::createSSLConnection(int socket) -{ - assert(_sslContext != 0); - - SSL* sslConnection = SSL_new(_sslContext); - assert(sslConnection != 0); - - SSL_clear(sslConnection); - - SSL_set_fd(sslConnection, socket); - - return sslConnection; + return false; } void -IceSSL::Context::setCipherList(const string& cipherList) +IceSSL::Context::parseProtocols(const string& val) { - assert(_sslContext != 0); - - if(!cipherList.empty() && (!SSL_CTX_set_cipher_list(_sslContext, cipherList.c_str())) && - (_traceLevels->security >= IceSSL::SECURITY_WARNINGS)) + const string delim = ", "; + bool sslv3 = false, tlsv1 = false; + string::size_type pos = 0; + while(pos != string::npos) { - Trace out(_communicator->getLogger(), _traceLevels->securityCat); - out << "WRN error setting cipher list " << cipherList << " -- using default list" << "\n"; - out << sslGetErrors(); + pos = val.find_first_not_of(delim, pos); + if(pos == string::npos) + { + break; + } + + string prot; + string::size_type end = val.find_first_of(delim, pos); + if(end == string::npos) + { + prot = val.substr(pos); + } + else + { + prot = val.substr(pos, end - pos); + } + pos = end; + + if(prot == "ssl3" || prot == "sslv3") + { + sslv3 = true; + } + else if(prot == "tls1" || prot == "tlsv1") + { + tlsv1 = true; + } + else + { + string msg = "IceSSL: unrecognized protocol `" + prot + "'"; + if(_instance->securityTraceLevel() >= 1) + { + _logger->trace(_instance->securityTraceCategory(), msg); + } + PluginInitializationException ex(__FILE__, __LINE__); + ex.reason = msg; + throw ex; + } } -} - -void -IceSSL::Context::setDHParams(const BaseCertificates& baseCerts) -{ - DH* dh = 0; - - string dhFile = baseCerts.getDHParams().getFileName(); - int encoding = baseCerts.getDHParams().getEncoding(); - // File type must be PEM - that's the only way we can load DH Params, apparently. - if((!dhFile.empty()) && (encoding == SSL_FILETYPE_PEM)) + long opts = SSL_OP_NO_SSLv2; // SSLv2 is not supported. + if(!sslv3) { - dh = loadDHParam(dhFile.c_str()); + opts |= SSL_OP_NO_SSLv3; } - - if(dh == 0) + if(!tlsv1) { - if(_traceLevels->security >= IceSSL::SECURITY_WARNINGS) - { - Trace out(_communicator->getLogger(), _traceLevels->securityCat); - out << "WRN Could not load Diffie-Hellman params, generating a temporary 512bit key."; - } - - dh = getTempDH512(); + opts |= SSL_OP_NO_TLSv1; } + SSL_CTX_set_options(_ctx, opts); +} - if(dh != 0) - { - SSL_CTX_set_tmp_dh(_sslContext, dh); +// +// ClientContext. +// +IceSSL::ClientContext::ClientContext(const InstancePtr& instance, SSL_CTX* ctx) : + Context(instance, "IceSSL.Client.", ctx) +{ + PropertiesPtr properties = _instance->communicator()->getProperties(); - DH_free(dh); + // + // CheckCertName determines whether we compare the name in a peer's + // certificate against its hostname. + // + { + _checkCertName = properties->getPropertyAsIntWithDefault("IceSSL.Client.CheckCertName", 0) > 0; } } + +// +// ServerContext. +// +IceSSL::ServerContext::ServerContext(const InstancePtr& instance, SSL_CTX* ctx) : + Context(instance, "IceSSL.Server.", ctx) +{ + _checkCertName = false; +} |