summaryrefslogtreecommitdiff
path: root/cpp/src/IceSSL/SslConnectionOpenSSL.cpp
diff options
context:
space:
mode:
authorMark Spruiell <mes@zeroc.com>2002-04-24 21:13:00 +0000
committerMark Spruiell <mes@zeroc.com>2002-04-24 21:13:00 +0000
commit5409c1ecef0f226dedc77721c0d2fc8dfe9e85de (patch)
tree97ba75bc47a143726d6d8382be3a462e51716700 /cpp/src/IceSSL/SslConnectionOpenSSL.cpp
parentcleaning up sample impls (diff)
downloadice-5409c1ecef0f226dedc77721c0d2fc8dfe9e85de.tar.bz2
ice-5409c1ecef0f226dedc77721c0d2fc8dfe9e85de.tar.xz
ice-5409c1ecef0f226dedc77721c0d2fc8dfe9e85de.zip
merging from plugins branch
Diffstat (limited to 'cpp/src/IceSSL/SslConnectionOpenSSL.cpp')
-rw-r--r--cpp/src/IceSSL/SslConnectionOpenSSL.cpp755
1 files changed, 755 insertions, 0 deletions
diff --git a/cpp/src/IceSSL/SslConnectionOpenSSL.cpp b/cpp/src/IceSSL/SslConnectionOpenSSL.cpp
new file mode 100644
index 00000000000..b06f6cc95a2
--- /dev/null
+++ b/cpp/src/IceSSL/SslConnectionOpenSSL.cpp
@@ -0,0 +1,755 @@
+// **********************************************************************
+//
+// Copyright (c) 2001
+// MutableRealms, Inc.
+// Huntsville, AL, USA
+//
+// All Rights Reserved
+//
+// **********************************************************************
+
+// Note: This pragma is used to disable spurious warning messages having
+// to do with the length of debug symbols exceeding 255 characters.
+// This is due to STL template identifiers expansion.
+// The MSDN Library recommends that you put this pragma directive
+// in place to avoid the warnings.
+#ifdef _WIN32
+#pragma warning(disable:4786)
+#endif
+
+#include <Ice/Network.h>
+#include <Ice/TraceLevels.h>
+#include <Ice/Logger.h>
+#include <Ice/LocalException.h>
+
+#include <IceSSL/OpenSSL.h>
+#include <IceSSL/Exception.h>
+#include <IceSSL/SslConnection.h>
+#include <IceSSL/SslConnectionOpenSSL.h>
+#include <IceSSL/OpenSSLPluginI.h>
+#include <IceSSL/CertificateVerifierOpenSSL.h>
+#include <IceSSL/OpenSSLUtils.h>
+
+#include <openssl/err.h>
+
+#include <sstream>
+
+using namespace std;
+using namespace Ice;
+using namespace IceInternal;
+
+////////////////////////////////
+////////// Connection //////////
+////////////////////////////////
+
+//
+// Static Member Initialization
+//
+IceSSL::OpenSSL::SslConnectionMap IceSSL::OpenSSL::Connection::_connectionMap;
+IceUtil::Mutex IceSSL::OpenSSL::Connection::_connectionRepositoryMutex;
+
+//
+// Public Methods
+//
+
+void IceInternal::incRef(IceSSL::OpenSSL::Connection* p) { p->__incRef(); }
+void IceInternal::decRef(IceSSL::OpenSSL::Connection* p) { p->__decRef(); }
+
+// Note: I would use a using directive of the form:
+// using IceSSL::CertificateVerifierPtr;
+// but unfortunately, it appears that this is not properly picked up.
+//
+
+IceSSL::OpenSSL::Connection::Connection(const TraceLevelsPtr& traceLevels,
+ const LoggerPtr& logger,
+ const IceSSL::CertificateVerifierPtr& certificateVerifier,
+ SSL* sslConnection,
+ const PluginBaseIPtr& plugin) :
+ IceSSL::Connection(traceLevels, logger, certificateVerifier),
+ _sslConnection(sslConnection)
+{
+ assert(_sslConnection != 0);
+ assert(system != 0);
+
+ SSL_set_ex_data(sslConnection, 0, static_cast<void*>(plugin.get()));
+
+ _lastError = SSL_ERROR_NONE;
+
+ _initWantRead = 0;
+ _initWantWrite = 0;
+
+ // None configured, default to indicated timeout
+ _handshakeReadTimeout = 0;
+
+ // Set up the SSL to be able to refer back to our connection object.
+ addConnection(_sslConnection, this);
+}
+
+IceSSL::OpenSSL::Connection::~Connection()
+{
+ if (_sslConnection != 0)
+ {
+ removeConnection(_sslConnection);
+ SSL_set_ex_data(_sslConnection, 0, 0);
+ SSL_free(_sslConnection);
+ _sslConnection = 0;
+ }
+}
+
+void
+IceSSL::OpenSSL::Connection::shutdown()
+{
+ if (_sslConnection == 0)
+ {
+ return;
+ }
+
+ if (_traceLevels->security >= IceSSL::SECURITY_WARNINGS)
+ {
+ _logger->trace(_traceLevels->securityCat, "WRN " +
+ string("shutting down ssl connection\n") +
+ fdToString(SSL_get_fd(_sslConnection)));
+ }
+
+ int shutdown = 0;
+ int numRetries = 100;
+ int retries = -numRetries;
+
+ do
+ {
+ shutdown = SSL_shutdown(_sslConnection);
+ retries++;
+ }
+ while ((shutdown == 0) && (retries < 0));
+
+ if ((_traceLevels->security >= IceSSL::SECURITY_PROTOCOL) && (shutdown <= 0))
+ {
+ ostringstream s;
+ s << "ssl shutdown failure encountered: code[" << shutdown << "] retries[";
+ s << (retries + numRetries) << "]\n" << fdToString(SSL_get_fd(_sslConnection));
+ _logger->trace(_traceLevels->securityCat, s.str());
+ }
+}
+
+void
+IceSSL::OpenSSL::Connection::setHandshakeReadTimeout(int timeout)
+{
+ _handshakeReadTimeout = timeout;
+}
+
+IceSSL::OpenSSL::ConnectionPtr
+IceSSL::OpenSSL::Connection::getConnection(SSL* sslPtr)
+{
+ IceUtil::Mutex::Lock sync(_connectionRepositoryMutex);
+
+ assert(sslPtr);
+
+ Connection* connection = _connectionMap[sslPtr];
+
+ assert(connection);
+
+ return ConnectionPtr(connection);
+}
+
+//
+// Note: Do not throw exceptions from verifyCertificate - it would rip through the OpenSSL system,
+// interfering with the usual handling and alert system of the handshake. Exceptions should
+// be caught here (if they can be generated), logged and then a fail return code (0) should
+// returned.
+//
+int
+IceSSL::OpenSSL::Connection::verifyCertificate(int preVerifyOkay, X509_STORE_CTX* x509StoreContext)
+{
+ // Should NEVER be able to happen.
+ assert(_certificateVerifier.get() != 0);
+
+ // Get the verifier, make sure it is for OpenSSL connections
+ IceSSL::OpenSSL::CertificateVerifierPtr verifier;
+ verifier = dynamic_cast<IceSSL::OpenSSL::CertificateVerifier*>(_certificateVerifier.get());
+
+ // Check to make sure we have a proper verifier for the operation.
+ if (verifier)
+ {
+ // Use the verifier to verify the certificate
+ try
+ {
+ preVerifyOkay = verifier->verify(preVerifyOkay, x509StoreContext, _sslConnection);
+ }
+ catch (const Ice::LocalException& localEx)
+ {
+ if (_traceLevels->security >= IceSSL::SECURITY_WARNINGS)
+ {
+ ostringstream s;
+
+ s << "WRN exception during certificate verification: " << std::endl;
+ s << localEx << flush;
+
+ _logger->trace(_traceLevels->securityCat, s.str());
+ }
+
+ preVerifyOkay = 0;
+ }
+ }
+ else
+ {
+ // Note: This code should NEVER be able to be reached, as we check each
+ // CertificateVerifier as it is added to the System.
+
+ if (_traceLevels->security >= IceSSL::SECURITY_WARNINGS)
+ {
+ string errorString;
+
+ if (_certificateVerifier.get())
+ {
+ errorString = "WRN improper CertificateVerifier type";
+ }
+ else
+ {
+ // NOTE: This should NEVER be able to happen, but just in case.
+ errorString = "WRN CertificateVerifier not set";
+ }
+
+ _logger->trace(_traceLevels->securityCat, errorString);
+ }
+ }
+
+ return preVerifyOkay;
+}
+
+//
+// Protected Methods
+//
+
+int
+IceSSL::OpenSSL::Connection::connect()
+{
+ assert(_sslConnection != 0);
+
+ int result = SSL_connect(_sslConnection);
+
+ setLastError(result);
+
+ return result;
+}
+
+int
+IceSSL::OpenSSL::Connection::accept()
+{
+ assert(_sslConnection != 0);
+
+ int result = SSL_accept(_sslConnection);
+
+ setLastError(result);
+
+ return result;
+}
+
+// NOTE: Currently not used, maybe later.
+int
+IceSSL::OpenSSL::Connection::renegotiate()
+{
+ assert(_sslConnection != 0);
+ return SSL_renegotiate(_sslConnection);
+}
+
+int
+IceSSL::OpenSSL::Connection::initialize(int timeout)
+{
+ int retCode = 0;
+
+ while (true)
+ {
+ // One lucky thread will get the honor of carrying out the hanshake,
+ // if there is one to perform. The HandshakeSentinel effectively
+ // establishes a first-come, first-serve policy. One thread will own
+ // the handshake, and the others will either return rejected to the
+ // caller (who will figure out what to do with them) OR wait until
+ // our lead thread is done. Then, the shuffle begins again.
+ // Eventually, all threads will filter through.
+
+ HandshakeSentinel handshakeSentinel(_handshakeFlag);
+
+ if (!handshakeSentinel.ownHandshake())
+ {
+ if (timeout >= 0)
+ {
+ // We should return immediately here - do not block,
+ // leave it to the caller to figure this out.
+ retCode = -1;
+ break;
+ }
+ else
+ {
+ // We will wait here - blocking IO is being used.
+ IceUtil::Mutex::Lock sync(_handshakeWaitMutex);
+ }
+ }
+ else
+ {
+ // Perform our init(), then leave.
+ IceUtil::Mutex::Lock sync(_handshakeWaitMutex);
+ retCode = init(timeout);
+ break;
+ }
+ }
+
+ return retCode;
+}
+
+int
+IceSSL::OpenSSL::Connection::pending()
+{
+ assert(_sslConnection != 0);
+ return SSL_pending(_sslConnection);
+}
+
+int
+IceSSL::OpenSSL::Connection::getLastError() const
+{
+ assert(_sslConnection != 0);
+ return SSL_get_error(_sslConnection, _lastError);
+}
+
+int
+IceSSL::OpenSSL::Connection::sslRead(char* buffer, int bufferSize)
+{
+ assert(_sslConnection != 0);
+
+ int bytesRead = SSL_read(_sslConnection, buffer, bufferSize);
+
+ setLastError(bytesRead);
+
+ return bytesRead;
+}
+
+int
+IceSSL::OpenSSL::Connection::sslWrite(char* buffer, int bufferSize)
+{
+ assert(_sslConnection != 0);
+
+ int bytesWritten = SSL_write(_sslConnection, buffer, bufferSize);
+
+ setLastError(bytesWritten);
+
+ return bytesWritten;
+}
+
+int
+IceSSL::OpenSSL::Connection::select(int timeout, bool write)
+{
+ int ret;
+
+ assert(_sslConnection != 0);
+ SOCKET fd = SSL_get_fd(_sslConnection);
+
+ fd_set rwFdSet;
+ struct timeval tv;
+
+ if (timeout >= 0)
+ {
+ tv.tv_sec = timeout / 1000;
+ tv.tv_usec = (timeout - tv.tv_sec * 1000) * 1000;
+ }
+
+ do
+ {
+ FD_ZERO(&rwFdSet);
+ FD_SET(fd, &rwFdSet);
+
+ if (timeout >= 0)
+ {
+ if (write)
+ {
+ ret = ::select(fd + 1, 0, &rwFdSet, 0, &tv);
+ }
+ else
+ {
+ ret = ::select(fd + 1, &rwFdSet, 0, 0, &tv);
+ }
+ }
+ else
+ {
+ if (write)
+ {
+ ret = ::select(fd + 1, 0, &rwFdSet, 0, 0);
+ }
+ else
+ {
+ ret = ::select(fd + 1, &rwFdSet, 0, 0, 0);
+ }
+ }
+ }
+ while (ret == SOCKET_ERROR && interrupted());
+
+ if (ret == SOCKET_ERROR)
+ {
+ SocketException ex(__FILE__, __LINE__);
+ ex.error = getSocketErrno();
+ throw ex;
+ }
+
+ if (ret == 0)
+ {
+ throw TimeoutException(__FILE__, __LINE__);
+ }
+
+ return FD_ISSET(fd, &rwFdSet);
+}
+
+int
+IceSSL::OpenSSL::Connection::readSelect(int timeout)
+{
+ return select(timeout, false);
+}
+
+int
+IceSSL::OpenSSL::Connection::writeSelect(int timeout)
+{
+ return select(timeout, true);
+}
+
+int
+IceSSL::OpenSSL::Connection::read(Buffer& buf, int timeout)
+{
+ int packetSize = buf.b.end() - buf.i;
+ int totalBytesRead = 0;
+ int bytesRead;
+
+ int initReturn = 0;
+
+ // We keep reading until we're done.
+ while (buf.i != buf.b.end())
+ {
+ // Ensure we're initialized.
+ initReturn = initialize(timeout);
+
+ if (initReturn == -1)
+ {
+ // Handshake underway, timeout immediately, easy way to deal with this.
+ throw TimeoutException(__FILE__, __LINE__);
+ }
+
+ if (initReturn == 0)
+ {
+ // Retry the initialize call
+ continue;
+ }
+
+ // initReturn must be > 0, so we're okay to try a write
+
+ if (!pending() && !readSelect(_readTimeout))
+ {
+ // Nothing is left to read (according to SSL).
+ if (_traceLevels->security >= IceSSL::SECURITY_PROTOCOL)
+ {
+ _logger->trace(_traceLevels->securityCat, "no pending application-level bytes");
+ }
+
+ // We're done here.
+ break;
+ }
+
+ _readTimeout = timeout;
+
+ bytesRead = sslRead((char *)buf.i, packetSize);
+
+ switch (getLastError())
+ {
+ case SSL_ERROR_NONE:
+ {
+ if (bytesRead > 0)
+ {
+ if (_traceLevels->network >= 3)
+ {
+ ostringstream s;
+ s << "received " << bytesRead << " of " << packetSize;
+ s << " bytes via ssl\n" << fdToString(SSL_get_fd(_sslConnection));
+ _logger->trace(_traceLevels->networkCat, s.str());
+ }
+
+ totalBytesRead += bytesRead;
+
+ buf.i += bytesRead;
+
+ if (packetSize > buf.b.end() - buf.i)
+ {
+ packetSize = buf.b.end() - buf.i;
+ }
+ }
+ continue;
+ }
+
+ case SSL_ERROR_WANT_READ:
+ {
+ if (!readSelect(timeout))
+ {
+ // Timeout and wait for them to arrive.
+ throw TimeoutException(__FILE__, __LINE__);
+ }
+ continue;
+ }
+
+ case SSL_ERROR_WANT_WRITE:
+ case SSL_ERROR_WANT_X509_LOOKUP:
+ {
+ // Perform another read. The read should take care of this.
+ continue;
+ }
+
+ case SSL_ERROR_SYSCALL:
+ {
+ if(bytesRead == -1)
+ {
+ // IO Error in underlying BIO
+
+ if (interrupted())
+ {
+ break;
+ }
+
+ if (wouldBlock())
+ {
+ break;
+ }
+
+ if (connectionLost())
+ {
+ ConnectionLostException ex(__FILE__, __LINE__);
+ ex.error = getSocketErrno();
+ throw ex;
+ }
+ else
+ {
+ SocketException ex(__FILE__, __LINE__);
+ ex.error = getSocketErrno();
+ throw ex;
+ }
+ }
+ else // (bytesRead == 0)
+ {
+ ConnectionLostException ex(__FILE__, __LINE__);
+ ex.error = 0;
+ throw ex;
+ }
+ }
+
+ case SSL_ERROR_SSL:
+ {
+ ProtocolException protocolEx(__FILE__, __LINE__);
+
+ protocolEx._message = "encountered a violation of the ssl protocol\n";
+ protocolEx._message += sslGetErrors();
+
+ throw protocolEx;
+ }
+
+ case SSL_ERROR_ZERO_RETURN:
+ {
+ // Indicates that that the SSL Connection has been closed.
+ // But does not necessarily indicate that the underlying transport
+ // has been closed (in the case of Ice, it definitely hasn't yet).
+
+ ConnectionLostException ex(__FILE__, __LINE__);
+ ex.error = getSocketErrno();
+ throw ex;
+ }
+ }
+ }
+
+ return totalBytesRead;
+}
+
+void
+IceSSL::OpenSSL::Connection::addConnection(SSL* sslPtr, Connection* connection)
+{
+ assert(sslPtr);
+ assert(connection);
+ IceUtil::Mutex::Lock sync(_connectionRepositoryMutex);
+ _connectionMap[sslPtr] = connection;
+}
+
+void
+IceSSL::OpenSSL::Connection::removeConnection(SSL* sslPtr)
+{
+ assert(sslPtr);
+ IceUtil::Mutex::Lock sync(_connectionRepositoryMutex);
+ _connectionMap.erase(sslPtr);
+}
+
+void
+IceSSL::OpenSSL::Connection::showCertificateChain(BIO* bio)
+{
+ assert(_sslConnection != 0);
+ assert(bio != 0);
+
+ STACK_OF(X509)* sk;
+
+ // Big nasty buffer
+ char buffer[4096];
+
+ if ((sk = SSL_get_peer_cert_chain(_sslConnection)) != 0)
+ {
+ BIO_printf(bio,"---\nCertificate chain\n");
+
+ for (int i = 0; i < sk_X509_num(sk); i++)
+ {
+ X509_NAME_oneline(X509_get_subject_name(sk_X509_value(sk,i)), buffer, sizeof(buffer));
+ BIO_printf(bio, "%2d s:%s\n", i, buffer);
+
+ X509_NAME_oneline(X509_get_issuer_name(sk_X509_value(sk,i)), buffer, sizeof(buffer));
+ BIO_printf(bio, " i:%s\n", buffer);
+
+ PEM_write_bio_X509(bio, sk_X509_value(sk, i));
+ }
+ }
+ else
+ {
+ BIO_printf(bio, "---\nNo peer certificate chain available.\n");
+ }
+}
+
+void
+IceSSL::OpenSSL::Connection::showPeerCertificate(BIO* bio, const char* connType)
+{
+ assert(_sslConnection != 0);
+ assert(bio != 0);
+
+ X509* peerCert = 0;
+ char buffer[4096];
+
+ if ((peerCert = SSL_get_peer_certificate(_sslConnection)) != 0)
+ {
+ BIO_printf(bio, "%s Certificate\n", connType);
+ PEM_write_bio_X509(bio, peerCert);
+
+ X509_NAME_oneline(X509_get_subject_name(peerCert), buffer, sizeof(buffer));
+ BIO_printf(bio, "subject=%s\n", buffer);
+
+ X509_NAME_oneline(X509_get_issuer_name(peerCert), buffer, sizeof(buffer));
+ BIO_printf(bio, "issuer=%s\n", buffer);
+
+ EVP_PKEY *pktmp;
+ pktmp = X509_get_pubkey(peerCert);
+ BIO_printf(bio,"%s public key is %d bit\n", connType, EVP_PKEY_bits(pktmp));
+ EVP_PKEY_free(pktmp);
+
+ X509_free(peerCert);
+ }
+ else
+ {
+ BIO_printf(bio, "No %s certificate available.\n", connType);
+ }
+}
+
+void
+IceSSL::OpenSSL::Connection::showSharedCiphers(BIO* bio)
+{
+ assert(_sslConnection != 0);
+ assert(bio != 0);
+
+ char buffer[4096];
+ char* strPointer = 0;
+
+ if ((strPointer = SSL_get_shared_ciphers(_sslConnection, buffer, sizeof(buffer))) != 0)
+ {
+ // This works only for SSL 2. In later protocol versions, the client does not know
+ // what other ciphers (in addition to the one to be used in the current connection)
+ // the server supports.
+
+ BIO_printf(bio, "---\nShared Ciphers:\n");
+
+ int j = 0;
+ int i = 0;
+
+ while (*strPointer)
+ {
+ if (*strPointer == ':')
+ {
+ BIO_write(bio, " ", (15-j%25));
+ i++;
+ j=0;
+ BIO_write(bio, ((i%3)?" ":"\n"), 1);
+ }
+ else
+ {
+ BIO_write(bio, strPointer, 1);
+ j++;
+ }
+
+ strPointer++;
+ }
+
+ BIO_write(bio,"\n",1);
+ }
+}
+
+void
+IceSSL::OpenSSL::Connection::showSessionInfo(BIO* bio)
+{
+ assert(_sslConnection != 0);
+ assert(bio != 0);
+
+ if (_sslConnection->hit)
+ {
+ BIO_printf(bio, "Reused session-id\n");
+ }
+
+ PEM_write_bio_SSL_SESSION(bio, SSL_get_session(_sslConnection));
+}
+
+void
+IceSSL::OpenSSL::Connection::showSelectedCipherInfo(BIO* bio)
+{
+ assert(_sslConnection != 0);
+ assert(bio != 0);
+
+ const char* str;
+ SSL_CIPHER* cipher;
+
+ // Show the cipher that was finally selected.
+ cipher = SSL_get_current_cipher(_sslConnection);
+
+ str = SSL_CIPHER_get_name(cipher);
+ BIO_printf(bio, "Cipher Version: %s\n", ((str != 0) ? str : "(NONE)"));
+
+ str = SSL_CIPHER_get_version(cipher);
+ BIO_printf(bio, "Cipher Name: %s\n", ((str != 0) ? str : "(NONE)"));
+}
+
+void
+IceSSL::OpenSSL::Connection::showHandshakeStats(BIO* bio)
+{
+ assert(_sslConnection != 0);
+ assert(bio != 0);
+
+ BIO_printf(bio, "---\nSSL handshake has read %ld bytes and written %ld bytes\n",
+ BIO_number_read(SSL_get_rbio(_sslConnection)),
+ BIO_number_written(SSL_get_wbio(_sslConnection)));
+}
+
+void
+IceSSL::OpenSSL::Connection::showClientCAList(BIO* bio, const char* connType)
+{
+ assert(_sslConnection != 0);
+ assert(bio != 0);
+ assert(connType != 0);
+
+ char buffer[4096];
+ STACK_OF(X509_NAME)* sk = SSL_get_client_CA_list(_sslConnection);
+
+ if ((sk != 0) && (sk_X509_NAME_num(sk) > 0))
+ {
+ BIO_printf(bio,"---\nAcceptable %s certificate CA names\n", connType);
+
+ for (int i = 0; i < sk_X509_NAME_num(sk); i++)
+ {
+ X509_NAME_oneline(sk_X509_NAME_value(sk, i), buffer, sizeof(buffer));
+ BIO_write(bio, buffer, strlen(buffer));
+ BIO_write(bio,"\n", 1);
+ }
+ }
+ else
+ {
+ BIO_printf(bio,"---\nNo %s certificate CA names sent\n", connType);
+ }
+}