summaryrefslogtreecommitdiff
path: root/cpp/src/IceSSL/Instance.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'cpp/src/IceSSL/Instance.cpp')
-rw-r--r--cpp/src/IceSSL/Instance.cpp1125
1 files changed, 1125 insertions, 0 deletions
diff --git a/cpp/src/IceSSL/Instance.cpp b/cpp/src/IceSSL/Instance.cpp
new file mode 100644
index 00000000000..5eb4c58c731
--- /dev/null
+++ b/cpp/src/IceSSL/Instance.cpp
@@ -0,0 +1,1125 @@
+// **********************************************************************
+//
+// Copyright (c) 2003-2011 ZeroC, Inc. All rights reserved.
+//
+// This copy of Ice is licensed to you under the terms described in the
+// ICE_LICENSE file included in this distribution.
+//
+// **********************************************************************
+
+#include <IceUtil/Config.h>
+#ifdef _WIN32
+# include <winsock2.h>
+#endif
+
+#include <IceSSL/Instance.h>
+#include <IceSSL/EndpointI.h>
+#include <IceSSL/Util.h>
+#include <IceSSL/TrustManager.h>
+
+#include <Ice/Communicator.h>
+#include <Ice/LocalException.h>
+#include <Ice/Logger.h>
+#include <Ice/LoggerUtil.h>
+#include <Ice/Properties.h>
+#include <Ice/ProtocolPluginFacade.h>
+#include <Ice/StringConverter.h>
+
+#include <IceUtil/Mutex.h>
+#include <IceUtil/MutexPtrLock.h>
+#include <IceUtil/StringUtil.h>
+
+#include <openssl/rand.h>
+#include <openssl/err.h>
+
+#include <IceUtil/DisableWarnings.h>
+
+using namespace std;
+using namespace Ice;
+using namespace IceSSL;
+
+IceUtil::Shared* IceInternal::upCast(IceSSL::Instance* p) { return p; }
+
+namespace
+{
+
+IceUtil::Mutex* staticMutex = 0;
+int instanceCount = 0;
+IceUtil::Mutex* locks = 0;
+
+class Init
+{
+public:
+
+ Init()
+ {
+ staticMutex = new IceUtil::Mutex;
+ }
+
+ ~Init()
+ {
+ delete staticMutex;
+ staticMutex = 0;
+
+ if(locks)
+ {
+ delete[] locks;
+ locks = 0;
+ }
+ }
+};
+
+Init init;
+
+}
+
+extern "C"
+{
+
+//
+// OpenSSL mutex callback.
+//
+void
+IceSSL_opensslLockCallback(int mode, int n, const char* file, int line)
+{
+ assert(locks);
+ if(mode & CRYPTO_LOCK)
+ {
+ locks[n].lock();
+ }
+ else
+ {
+ locks[n].unlock();
+ }
+}
+
+//
+// OpenSSL thread id callback.
+//
+unsigned long
+IceSSL_opensslThreadIdCallback()
+{
+#if defined(_WIN32)
+ return static_cast<unsigned long>(GetCurrentThreadId());
+#elif defined(__FreeBSD__) || defined(__APPLE__) || defined(__osf1__)
+ //
+ // On some platforms, pthread_t is a pointer to a per-thread structure.
+ //
+ return reinterpret_cast<unsigned long>(pthread_self());
+#elif (defined(__linux) || defined(__sun) || defined(__hpux)) || defined(_AIX)
+ //
+ // On Linux, Solaris, HP-UX and AIX, pthread_t is an integer.
+ //
+ return static_cast<unsigned long>(pthread_self());
+#else
+# error "Unknown platform"
+#endif
+}
+
+int
+IceSSL_opensslPasswordCallback(char* buf, int size, int flag, void* userData)
+{
+ IceSSL::Instance* p = reinterpret_cast<IceSSL::Instance*>(userData);
+ string passwd = p->password(flag == 1);
+ int sz = static_cast<int>(passwd.size());
+ if(sz > size)
+ {
+ sz = size - 1;
+ }
+ strncpy(buf, passwd.c_str(), sz);
+ buf[sz] = '\0';
+
+ for(string::iterator i = passwd.begin(); i != passwd.end(); ++i)
+ {
+ *i = '\0';
+ }
+
+ return sz;
+}
+
+#ifndef OPENSSL_NO_DH
+DH*
+IceSSL_opensslDHCallback(SSL* ssl, int /*isExport*/, int keyLength)
+{
+ IceSSL::Instance* p = reinterpret_cast<IceSSL::Instance*>(SSL_CTX_get_ex_data(ssl->ctx, 0));
+ return p->dhParams(keyLength);
+}
+#endif
+
+int
+IceSSL_opensslVerifyCallback(int ok, X509_STORE_CTX* ctx)
+{
+ SSL* ssl = reinterpret_cast<SSL*>(X509_STORE_CTX_get_ex_data(ctx, SSL_get_ex_data_X509_STORE_CTX_idx()));
+ IceSSL::Instance* p = reinterpret_cast<IceSSL::Instance*>(SSL_CTX_get_ex_data(ssl->ctx, 0));
+ return p->verifyCallback(ok, ssl, ctx);
+}
+
+}
+
+static bool
+passwordError()
+{
+ 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);
+}
+
+IceSSL::Instance::Instance(const CommunicatorPtr& communicator) :
+ _logger(communicator->getLogger()),
+ _initialized(false),
+ _ctx(0)
+{
+ __setNoDelete(true);
+
+ //
+ // Initialize OpenSSL if necessary.
+ //
+ IceUtilInternal::MutexPtrLock<IceUtil::Mutex> sync(staticMutex);
+ instanceCount++;
+
+ if(instanceCount == 1)
+ {
+ PropertiesPtr properties = communicator->getProperties();
+
+ //
+ // Create the mutexes and set the callbacks.
+ //
+ if(!locks)
+ {
+ locks = new IceUtil::Mutex[CRYPTO_num_locks()];
+ CRYPTO_set_locking_callback(IceSSL_opensslLockCallback);
+ CRYPTO_set_id_callback(IceSSL_opensslThreadIdCallback);
+ }
+
+ //
+ // Load human-readable error messages.
+ //
+ SSL_load_error_strings();
+
+ //
+ // Initialize the SSL library.
+ //
+ SSL_library_init();
+
+ //
+ // This is necessary to allow programs that use OpenSSL 0.9.x to
+ // load private key files generated by OpenSSL 1.x.
+ //
+ OpenSSL_add_all_algorithms();
+
+ //
+ // Initialize the PRNG.
+ //
+#ifdef WINDOWS
+ RAND_screen(); // Uses data from the screen if possible.
+#endif
+ char randFile[1024];
+ if(RAND_file_name(randFile, sizeof(randFile))) // Gets the name of a default seed file.
+ {
+ RAND_load_file(randFile, 1024);
+ }
+ string randFiles = Ice::nativeToUTF8(communicator, properties->getProperty("IceSSL.Random"));
+
+ if(!randFiles.empty())
+ {
+ vector<string> files;
+#ifdef _WIN32
+ const string sep = ";";
+#else
+ const string sep = ":";
+#endif
+ string defaultDir = Ice::nativeToUTF8(communicator, properties->getProperty("IceSSL.DefaultDir"));
+
+ if(!IceUtilInternal::splitString(randFiles, sep, files))
+ {
+ PluginInitializationException ex(__FILE__, __LINE__);
+ ex.reason = "IceSSL: invalid value for IceSSL.Random:\n" + randFiles;
+ throw ex;
+ }
+ for(vector<string>::iterator p = files.begin(); p != files.end(); ++p)
+ {
+ string file = *p;
+ if(!checkPath(file, defaultDir, false))
+ {
+ PluginInitializationException ex(__FILE__, __LINE__);
+ ex.reason = "IceSSL: entropy data file not found:\n" + file;
+ throw ex;
+ }
+ if(!RAND_load_file(file.c_str(), 1024))
+ {
+ PluginInitializationException ex(__FILE__, __LINE__);
+ ex.reason = "IceSSL: unable to load entropy data from " + file;
+ throw ex;
+ }
+ }
+ }
+#ifndef _WIN32
+ //
+ // The Entropy Gathering Daemon (EGD) is not available on Windows.
+ // The file should be a Unix domain socket for the daemon.
+ //
+ string entropyDaemon = properties->getProperty("IceSSL.EntropyDaemon");
+ if(!entropyDaemon.empty())
+ {
+ if(RAND_egd(entropyDaemon.c_str()) <= 0)
+ {
+ PluginInitializationException ex(__FILE__, __LINE__);
+ ex.reason = "IceSSL: EGD failure using file " + entropyDaemon;
+ throw ex;
+ }
+ }
+#endif
+ if(!RAND_status())
+ {
+ communicator->getLogger()->warning("IceSSL: insufficient data to initialize PRNG");
+ }
+ }
+
+ _facade = IceInternal::getProtocolPluginFacade(communicator);
+ _securityTraceLevel = communicator->getProperties()->getPropertyAsInt("IceSSL.Trace.Security");
+ _securityTraceCategory = "Security";
+ _trustManager = new TrustManager(communicator);
+
+ //
+ // Register the endpoint factory. We have to do this now, rather than
+ // in initialize, because the communicator may need to interpret
+ // proxies before the plug-in is fully initialized.
+ //
+ _facade->addEndpointFactory(new EndpointFactoryI(this));
+
+ __setNoDelete(false);
+}
+
+IceSSL::Instance::~Instance()
+{
+ //
+ // Clean up OpenSSL resources.
+ //
+ IceUtilInternal::MutexPtrLock<IceUtil::Mutex> sync(staticMutex);
+
+ if(--instanceCount == 0)
+ {
+ //
+ // NOTE: We can't destroy the locks here: threads which might have called openssl methods
+ // might access openssl locks upon termination (from DllMain/THREAD_DETACHED). Instead,
+ // we release the locks in the ~Init() static destructor. See bug #4156.
+ //
+ //CRYPTO_set_locking_callback(0);
+ //CRYPTO_set_id_callback(0);
+ //delete[] locks;
+ //locks = 0;
+
+ CRYPTO_cleanup_all_ex_data();
+ RAND_cleanup();
+ ERR_free_strings();
+ EVP_cleanup();
+ }
+}
+
+void
+IceSSL::Instance::initialize()
+{
+ if(_initialized)
+ {
+ return;
+ }
+
+ try
+ {
+ const string propPrefix = "IceSSL.";
+ PropertiesPtr properties = communicator()->getProperties();
+
+ //
+ // CheckCertName determines whether we compare the name in a peer's
+ // certificate against its hostname.
+ //
+ _checkCertName = properties->getPropertyAsIntWithDefault(propPrefix + "CheckCertName", 0) > 0;
+
+ //
+ // VerifyDepthMax establishes the maximum length of a peer's certificate
+ // chain, including the peer's certificate. A value of 0 means there is
+ // no maximum.
+ //
+ _verifyDepthMax = properties->getPropertyAsIntWithDefault(propPrefix + "VerifyDepthMax", 2);
+
+ //
+ // VerifyPeer determines whether certificate validation failures abort a connection.
+ //
+ _verifyPeer = properties->getPropertyAsIntWithDefault(propPrefix + "VerifyPeer", 2);
+
+ //
+ // Create an SSL context if the application hasn't supplied one.
+ //
+ if(!_ctx)
+ {
+ _ctx = SSL_CTX_new(SSLv23_method());
+ if(!_ctx)
+ {
+ PluginInitializationException ex(__FILE__, __LINE__);
+ ex.reason = "IceSSL: unable to create SSL context:\n" + sslErrors();
+ throw ex;
+ }
+
+ //
+ // Check for a default directory. We look in this directory for
+ // files mentioned in the configuration.
+ //
+ string defaultDir = properties->getProperty(propPrefix + "DefaultDir");
+
+ //
+ // 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.
+ //
+ {
+ // TODO: Support quoted value?
+ string password = properties->getProperty(propPrefix + "Password");
+ if(!password.empty() || _prompt)
+ {
+ SSL_CTX_set_default_passwd_cb(_ctx, IceSSL_opensslPasswordCallback);
+ SSL_CTX_set_default_passwd_cb_userdata(_ctx, this);
+ _password = password;
+ }
+ }
+
+ int passwordRetryMax = properties->getPropertyAsIntWithDefault(propPrefix + "PasswordRetryMax", 3);
+
+ //
+ // Establish the location of CA certificates.
+ //
+ {
+ 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, defaultDir, false))
+ {
+ PluginInitializationException ex(__FILE__, __LINE__);
+ ex.reason = "IceSSL: CA certificate file not found:\n" + caFile;
+ throw ex;
+ }
+ file = caFile.c_str();
+ }
+ if(!caDir.empty())
+ {
+ if(!checkPath(caDir, defaultDir, true))
+ {
+ PluginInitializationException ex(__FILE__, __LINE__);
+ ex.reason = "IceSSL: CA certificate directory not found:\n" + caDir;
+ 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 = 0;
+ while(count < passwordRetryMax)
+ {
+ ERR_clear_error();
+ err = SSL_CTX_load_verify_locations(_ctx, file, dir);
+ if(err)
+ {
+ break;
+ }
+ ++count;
+ }
+ if(err == 0)
+ {
+ string msg = "IceSSL: unable to establish CA certificates";
+ if(passwordError())
+ {
+ msg += ":\ninvalid password";
+ }
+ else
+ {
+ string err = sslErrors();
+ if(!err.empty())
+ {
+ msg += ":\n" + err;
+ }
+ }
+ PluginInitializationException ex(__FILE__, __LINE__);
+ ex.reason = msg;
+ throw ex;
+ }
+ }
+ }
+
+ //
+ // Establish the certificate chains and private keys. One RSA certificate and
+ // one DSA certificate are allowed.
+ //
+ {
+#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(!IceUtilInternal::splitString(certFile, sep, files) || files.size() > 2)
+ {
+ PluginInitializationException ex(__FILE__, __LINE__);
+ ex.reason = "IceSSL: invalid value for " + propPrefix + "CertFile:\n" + certFile;
+ throw ex;
+ }
+ numCerts = files.size();
+ for(vector<string>::iterator p = files.begin(); p != files.end(); ++p)
+ {
+ string file = *p;
+ if(!checkPath(file, defaultDir, false))
+ {
+ PluginInitializationException ex(__FILE__, __LINE__);
+ ex.reason = "IceSSL: certificate file not found:\n" + file;
+ throw ex;
+ }
+ //
+ // The certificate may be stored in an encrypted file, so handle
+ // password retries.
+ //
+ int count = 0;
+ int err = 0;
+ while(count < passwordRetryMax)
+ {
+ ERR_clear_error();
+ err = SSL_CTX_use_certificate_chain_file(_ctx, file.c_str());
+ if(err)
+ {
+ break;
+ }
+ ++count;
+ }
+ if(err == 0)
+ {
+ string msg = "IceSSL: unable to load certificate chain from file " + file;
+ if(passwordError())
+ {
+ msg += ":\ninvalid password";
+ }
+ else
+ {
+ string err = sslErrors();
+ if(!err.empty())
+ {
+ msg += ":\n" + err;
+ }
+ }
+ 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(!IceUtilInternal::splitString(keyFile, sep, files) || files.size() > 2)
+ {
+ PluginInitializationException ex(__FILE__, __LINE__);
+ ex.reason = "IceSSL: invalid value for " + propPrefix + "KeyFile:\n" + keyFile;
+ throw ex;
+ }
+ if(files.size() != numCerts)
+ {
+ PluginInitializationException ex(__FILE__, __LINE__);
+ ex.reason = "IceSSL: " + propPrefix + "KeyFile does not agree with " + propPrefix + "CertFile";
+ throw ex;
+ }
+ for(vector<string>::iterator p = files.begin(); p != files.end(); ++p)
+ {
+ string file = *p;
+ if(!checkPath(file, defaultDir, false))
+ {
+ PluginInitializationException ex(__FILE__, __LINE__);
+ ex.reason = "IceSSL: key file not found:\n" + file;
+ throw ex;
+ }
+ //
+ // The private key may be stored in an encrypted file, so handle
+ // password retries.
+ //
+ int count = 0;
+ int err = 0;
+ while(count < passwordRetryMax)
+ {
+ ERR_clear_error();
+ err = SSL_CTX_use_PrivateKey_file(_ctx, file.c_str(), SSL_FILETYPE_PEM);
+ if(err)
+ {
+ break;
+ }
+ ++count;
+ }
+ if(err == 0)
+ {
+ string msg = "IceSSL: unable to load private key from file " + file;
+ if(passwordError())
+ {
+ msg += ":\ninvalid password";
+ }
+ else
+ {
+ string err = sslErrors();
+ if(!err.empty())
+ {
+ msg += ":\n" + err;
+ }
+ }
+ PluginInitializationException ex(__FILE__, __LINE__);
+ ex.reason = msg;
+ throw ex;
+ }
+ }
+ if(!SSL_CTX_check_private_key(_ctx))
+ {
+ PluginInitializationException ex(__FILE__, __LINE__);
+ ex.reason = "IceSSL: unable to validate private key(s):\n" + sslErrors();
+ throw ex;
+ }
+ }
+ }
+
+ //
+ // Diffie Hellman configuration.
+ //
+ {
+#ifndef OPENSSL_NO_DH
+ _dhParams = new DHParams;
+ SSL_CTX_set_options(_ctx, SSL_OP_SINGLE_DH_USE);
+ SSL_CTX_set_tmp_dh_callback(_ctx, IceSSL_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, defaultDir, false))
+ {
+ PluginInitializationException ex(__FILE__, __LINE__);
+ ex.reason = "IceSSL: DH parameter file not found:\n" + file;
+ throw ex;
+ }
+ if(!_dhParams->add(keyLength, file))
+ {
+ PluginInitializationException ex(__FILE__, __LINE__);
+ ex.reason = "IceSSL: unable to read DH parameter file " + file;
+ throw ex;
+ }
+ }
+ }
+#endif
+ }
+ }
+ }
+
+ //
+ // Store a pointer to ourself for use in OpenSSL callbacks.
+ //
+ SSL_CTX_set_ex_data(_ctx, 0, this);
+
+ //
+ // This is necessary for successful interop with Java. Without it, a Java
+ // client would fail to reestablish a connection: the server gets the
+ // error "session id context uninitialized" and the client receives
+ // "SSLHandshakeException: Remote host closed connection during handshake".
+ //
+ SSL_CTX_set_session_cache_mode(_ctx, SSL_SESS_CACHE_OFF);
+
+ //
+ // Select protocols.
+ //
+ StringSeq protocols = properties->getPropertyAsList(propPrefix + "Protocols");
+ if(!protocols.empty())
+ {
+ parseProtocols(protocols);
+ }
+
+ //
+ // Establish the cipher list.
+ //
+ string ciphers = properties->getProperty(propPrefix + "Ciphers");
+ if(!ciphers.empty())
+ {
+ if(!SSL_CTX_set_cipher_list(_ctx, ciphers.c_str()))
+ {
+ PluginInitializationException ex(__FILE__, __LINE__);
+ ex.reason = "IceSSL: unable to set ciphers using `" + ciphers + "':\n" + sslErrors();
+ throw ex;
+ }
+ }
+
+ //
+ // Determine whether a certificate is required from the peer.
+ //
+ {
+ 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:
+ {
+ PluginInitializationException ex(__FILE__, __LINE__);
+ ex.reason = "IceSSL: invalid value for " + propPrefix + "VerifyPeer";
+ throw ex;
+ }
+ }
+ SSL_CTX_set_verify(_ctx, sslVerifyMode, IceSSL_opensslVerifyCallback);
+ }
+ }
+ catch(...)
+ {
+ //
+ // We free the SSL context regardless of whether the plugin created it
+ // or the application supplied it.
+ //
+ SSL_CTX_free(_ctx);
+ _ctx = 0;
+ throw;
+ }
+
+ _initialized = true;
+}
+
+void
+IceSSL::Instance::context(SSL_CTX* context)
+{
+ if(_initialized)
+ {
+ PluginInitializationException ex(__FILE__, __LINE__);
+ ex.reason = "IceSSL: plug-in is already initialized";
+ throw ex;
+ }
+
+ assert(!_ctx);
+ _ctx = context;
+}
+
+SSL_CTX*
+IceSSL::Instance::context() const
+{
+ return _ctx;
+}
+
+void
+IceSSL::Instance::setCertificateVerifier(const CertificateVerifierPtr& verifier)
+{
+ _verifier = verifier;
+}
+
+void
+IceSSL::Instance::setPasswordPrompt(const PasswordPromptPtr& prompt)
+{
+ _prompt = prompt;
+}
+
+CommunicatorPtr
+IceSSL::Instance::communicator() const
+{
+ return _facade->getCommunicator();
+}
+
+IceInternal::EndpointHostResolverPtr
+IceSSL::Instance::endpointHostResolver() const
+{
+ return _facade->getEndpointHostResolver();
+}
+
+IceInternal::ProtocolSupport
+IceSSL::Instance::protocolSupport() const
+{
+ return _facade->getProtocolSupport();
+}
+
+string
+IceSSL::Instance::defaultHost() const
+{
+ return _facade->getDefaultHost();
+}
+
+int
+IceSSL::Instance::networkTraceLevel() const
+{
+ return _facade->getNetworkTraceLevel();
+}
+
+string
+IceSSL::Instance::networkTraceCategory() const
+{
+ return _facade->getNetworkTraceCategory();
+}
+
+int
+IceSSL::Instance::securityTraceLevel() const
+{
+ return _securityTraceLevel;
+}
+
+string
+IceSSL::Instance::securityTraceCategory() const
+{
+ return _securityTraceCategory;
+}
+
+void
+IceSSL::Instance::verifyPeer(SSL* ssl, SOCKET fd, const string& address, const NativeConnectionInfoPtr& info)
+{
+ long result = SSL_get_verify_result(ssl);
+ if(result != X509_V_OK)
+ {
+ if(_verifyPeer == 0)
+ {
+ if(_securityTraceLevel >= 1)
+ {
+ ostringstream ostr;
+ ostr << "IceSSL: ignoring certificate verification failure:\n" << X509_verify_cert_error_string(result);
+ _logger->trace(_securityTraceCategory, ostr.str());
+ }
+ }
+ else
+ {
+ ostringstream ostr;
+ ostr << "IceSSL: certificate verification failed:\n" << X509_verify_cert_error_string(result);
+ string msg = ostr.str();
+ if(_securityTraceLevel >= 1)
+ {
+ _logger->trace(_securityTraceCategory, msg);
+ }
+ SecurityException ex(__FILE__, __LINE__);
+ ex.reason = msg;
+ throw ex;
+ }
+ }
+
+ X509* rawCert = SSL_get_peer_certificate(ssl);
+ CertificatePtr cert;
+ if(rawCert != 0)
+ {
+ cert = new Certificate(rawCert);
+ }
+
+ //
+ // For an outgoing connection, we compare the proxy address (if any) against
+ // fields in the server's certificate (if any).
+ //
+ if(cert && !address.empty())
+ {
+ //
+ // Extract the IP addresses and the DNS names from the subject
+ // alternative names.
+ //
+ vector<pair<int, string> > subjectAltNames = cert->getSubjectAlternativeNames();
+ vector<string> ipAddresses;
+ vector<string> dnsNames;
+ for(vector<pair<int, string> >::const_iterator p = subjectAltNames.begin(); p != subjectAltNames.end(); ++p)
+ {
+ if(p->first == 7)
+ {
+ ipAddresses.push_back(IceUtilInternal::toLower(p->second));
+ }
+ else if(p->first == 2)
+ {
+ dnsNames.push_back(IceUtilInternal::toLower(p->second));
+ }
+ }
+
+ //
+ // Compare the peer's address against the common name.
+ //
+ bool certNameOK = false;
+ string dn;
+ string addrLower = IceUtilInternal::toLower(address);
+ {
+ DistinguishedName d = cert->getSubjectDN();
+ dn = IceUtilInternal::toLower(string(d));
+ string cn = "cn=" + addrLower;
+ string::size_type pos = dn.find(cn);
+ if(pos != string::npos)
+ {
+ //
+ // Ensure we match the entire common name.
+ //
+ certNameOK = (pos + cn.size() == dn.size()) || (dn[pos + cn.size()] == ',');
+ }
+ }
+
+ //
+ // Compare the peer's address against the the dnsName and ipAddress
+ // values in the subject alternative name.
+ //
+ if(!certNameOK)
+ {
+ certNameOK = find(ipAddresses.begin(), ipAddresses.end(), addrLower) != ipAddresses.end();
+ }
+ if(!certNameOK)
+ {
+ certNameOK = find(dnsNames.begin(), dnsNames.end(), addrLower) != dnsNames.end();
+ }
+
+ //
+ // 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 || (_securityTraceLevel >= 1 && !_verifier)))
+ {
+ ostringstream ostr;
+ ostr << "IceSSL: ";
+ if(!_checkCertName)
+ {
+ ostr << "ignoring ";
+ }
+ ostr << "certificate validation failure:\npeer certificate does not have `" << address
+ << "' as its commonName or in its subjectAltName extension";
+ if(!dn.empty())
+ {
+ ostr << "\nSubject DN: " << dn;
+ }
+ if(!dnsNames.empty())
+ {
+ ostr << "\nDNS names found in certificate: ";
+ for(vector<string>::const_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>::const_iterator p = ipAddresses.begin(); p != ipAddresses.end(); ++p)
+ {
+ if(p != ipAddresses.begin())
+ {
+ ostr << ", ";
+ }
+ ostr << *p;
+ }
+ }
+ string msg = ostr.str();
+ if(_securityTraceLevel >= 1)
+ {
+ Trace out(_logger, _securityTraceCategory);
+ out << msg;
+ }
+ if(_checkCertName)
+ {
+ SecurityException ex(__FILE__, __LINE__);
+ ex.reason = msg;
+ throw ex;
+ }
+ }
+ }
+
+ if(_verifyDepthMax > 0 && static_cast<int>(info->certs.size()) > _verifyDepthMax)
+ {
+ ostringstream ostr;
+ ostr << (info->incoming ? "incoming" : "outgoing") << " connection rejected:\n"
+ << "length of peer's certificate chain (" << info->certs.size() << ") exceeds maximum of "
+ << _verifyDepthMax;
+ string msg = ostr.str();
+ if(_securityTraceLevel >= 1)
+ {
+ _logger->trace(_securityTraceCategory, msg + "\n" + IceInternal::fdToString(fd));
+ }
+ SecurityException ex(__FILE__, __LINE__);
+ ex.reason = msg;
+ throw ex;
+ }
+
+ if(!_trustManager->verify(info))
+ {
+ string msg = string(info->incoming ? "incoming" : "outgoing") + " connection rejected by trust manager";
+ if(_securityTraceLevel >= 1)
+ {
+ _logger->trace(_securityTraceCategory, msg + "\n" + IceInternal::fdToString(fd));
+ }
+ SecurityException ex(__FILE__, __LINE__);
+ ex.reason = msg;
+ throw ex;
+ }
+
+ if(_verifier && !_verifier->verify(info))
+ {
+ string msg = string(info->incoming ? "incoming" : "outgoing") + " connection rejected by certificate verifier";
+ if(_securityTraceLevel >= 1)
+ {
+ _logger->trace(_securityTraceCategory, msg + "\n" + IceInternal::fdToString(fd));
+ }
+ SecurityException ex(__FILE__, __LINE__);
+ ex.reason = msg;
+ throw ex;
+ }
+}
+
+string
+IceSSL::Instance::sslErrors() const
+{
+ return getSslErrors(_securityTraceLevel >= 1);
+}
+
+void
+IceSSL::Instance::destroy()
+{
+ _facade = 0;
+
+ if(_ctx)
+ {
+ SSL_CTX_free(_ctx);
+ }
+}
+
+string
+IceSSL::Instance::password(bool /*encrypting*/)
+{
+ if(_prompt)
+ {
+ try
+ {
+ return _prompt->getPassword();
+ }
+ catch(...)
+ {
+ //
+ // Don't allow exceptions to cross an OpenSSL boundary.
+ //
+ return string();
+ }
+ }
+ else
+ {
+ return _password;
+ }
+}
+
+int
+IceSSL::Instance::verifyCallback(int ok, SSL* ssl, X509_STORE_CTX* c)
+{
+ if(!ok && _securityTraceLevel >= 1)
+ {
+ X509* cert = X509_STORE_CTX_get_current_cert(c);
+ int err = X509_STORE_CTX_get_error(c);
+ char buf[256];
+
+ Trace out(_logger, _securityTraceCategory);
+ out << "certificate verification failure\n";
+
+ X509_NAME_oneline(X509_get_issuer_name(cert), buf, static_cast<int>(sizeof(buf)));
+ out << "issuer = " << buf << '\n';
+ X509_NAME_oneline(X509_get_subject_name(cert), buf, static_cast<int>(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;
+}
+
+#ifndef OPENSSL_NO_DH
+DH*
+IceSSL::Instance::dhParams(int keyLength)
+{
+ return _dhParams->get(keyLength);
+}
+#endif
+
+void
+IceSSL::Instance::traceConnection(SSL* ssl, bool incoming)
+{
+ Trace out(_logger, _securityTraceCategory);
+ out << "SSL summary for " << (incoming ? "incoming" : "outgoing") << " connection\n";
+
+ //
+ // The const_cast is necesary because Solaris still uses OpenSSL 0.9.7.
+ //
+ //const SSL_CIPHER *cipher = SSL_get_current_cipher(ssl);
+ SSL_CIPHER *cipher = const_cast<SSL_CIPHER*>(SSL_get_current_cipher(ssl));
+ if(!cipher)
+ {
+ out << "unknown cipher\n";
+ }
+ else
+ {
+ out << "cipher = " << SSL_CIPHER_get_name(cipher) << "\n";
+ out << "bits = " << SSL_CIPHER_get_bits(cipher, 0) << "\n";
+ out << "protocol = " << SSL_get_version(ssl) << "\n";
+ }
+ out << IceInternal::fdToString(SSL_get_fd(ssl));
+}
+
+void
+IceSSL::Instance::parseProtocols(const StringSeq& protocols)
+{
+ bool sslv3 = false, tlsv1 = false;
+ for(Ice::StringSeq::const_iterator p = protocols.begin(); p != protocols.end(); ++p)
+ {
+ string prot = *p;
+
+ if(prot == "ssl3" || prot == "sslv3")
+ {
+ sslv3 = true;
+ }
+ else if(prot == "tls" || prot == "tls1" || prot == "tlsv1")
+ {
+ tlsv1 = true;
+ }
+ else
+ {
+ PluginInitializationException ex(__FILE__, __LINE__);
+ ex.reason = "IceSSL: unrecognized protocol `" + prot + "'";
+ throw ex;
+ }
+ }
+
+ long opts = SSL_OP_NO_SSLv2; // SSLv2 is not supported.
+ if(!sslv3)
+ {
+ opts |= SSL_OP_NO_SSLv3;
+ }
+ if(!tlsv1)
+ {
+ opts |= SSL_OP_NO_TLSv1;
+ }
+ SSL_CTX_set_options(_ctx, opts);
+}