summaryrefslogtreecommitdiff
path: root/cpp/src/IceSSL/Context.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'cpp/src/IceSSL/Context.cpp')
-rw-r--r--cpp/src/IceSSL/Context.cpp1325
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;
+}