2012-04-24 14:02:15 +02:00
|
|
|
#include "i2-base.h"
|
|
|
|
|
|
|
|
using namespace icinga;
|
|
|
|
|
2012-04-24 14:54:05 +02:00
|
|
|
int I2_EXPORT TLSClient::m_SSLIndex;
|
|
|
|
bool I2_EXPORT TLSClient::m_SSLIndexInitialized = false;
|
|
|
|
|
2012-05-08 15:36:28 +02:00
|
|
|
/**
|
|
|
|
* TLSClient
|
|
|
|
*
|
|
|
|
* Constructor for the TLSClient class.
|
|
|
|
*
|
|
|
|
* @param role The role of the client.
|
|
|
|
* @param sslContext The SSL context for the client.
|
|
|
|
*/
|
2012-04-24 14:02:15 +02:00
|
|
|
TLSClient::TLSClient(TCPClientRole role, shared_ptr<SSL_CTX> sslContext) : TCPClient(role)
|
|
|
|
{
|
|
|
|
m_SSLContext = sslContext;
|
2012-04-27 14:15:22 +02:00
|
|
|
m_BlockRead = false;
|
|
|
|
m_BlockWrite = false;
|
2012-04-24 14:02:15 +02:00
|
|
|
}
|
|
|
|
|
2012-05-08 15:36:28 +02:00
|
|
|
/**
|
|
|
|
* NullCertificateDeleter
|
|
|
|
*
|
|
|
|
* Takes a certificate as an argument. Does nothing.
|
|
|
|
*
|
|
|
|
* @param certificate An X509 certificate.
|
|
|
|
*/
|
2012-04-24 15:56:48 +02:00
|
|
|
void TLSClient::NullCertificateDeleter(X509 *certificate)
|
2012-04-24 14:02:15 +02:00
|
|
|
{
|
2012-04-24 15:56:48 +02:00
|
|
|
/* Nothing to do here. */
|
2012-04-24 14:02:15 +02:00
|
|
|
}
|
|
|
|
|
2012-05-08 15:36:28 +02:00
|
|
|
/**
|
|
|
|
* GetClientCertificate
|
|
|
|
*
|
|
|
|
* Retrieves the X509 certficate for this client.
|
|
|
|
*
|
|
|
|
* @returns The X509 certificate.
|
|
|
|
*/
|
2012-04-24 15:56:48 +02:00
|
|
|
shared_ptr<X509> TLSClient::GetClientCertificate(void) const
|
2012-04-24 14:02:15 +02:00
|
|
|
{
|
2012-04-24 15:56:48 +02:00
|
|
|
return shared_ptr<X509>(SSL_get_certificate(m_SSL.get()), &TLSClient::NullCertificateDeleter);
|
|
|
|
}
|
|
|
|
|
2012-05-08 15:36:28 +02:00
|
|
|
/**
|
|
|
|
* GetPeerCertificate
|
|
|
|
*
|
|
|
|
* Retrieves the X509 certficate for the peer.
|
|
|
|
*
|
|
|
|
* @returns The X509 certificate.
|
|
|
|
*/
|
2012-04-24 15:56:48 +02:00
|
|
|
shared_ptr<X509> TLSClient::GetPeerCertificate(void) const
|
|
|
|
{
|
|
|
|
return shared_ptr<X509>(SSL_get_peer_certificate(m_SSL.get()), X509_free);
|
2012-04-24 14:02:15 +02:00
|
|
|
}
|
|
|
|
|
2012-05-08 15:36:28 +02:00
|
|
|
/**
|
|
|
|
* Start
|
|
|
|
*
|
|
|
|
* Registers the TLS socket and starts processing events for it.
|
|
|
|
*/
|
2012-04-24 14:02:15 +02:00
|
|
|
void TLSClient::Start(void)
|
|
|
|
{
|
|
|
|
TCPClient::Start();
|
|
|
|
|
|
|
|
m_SSL = shared_ptr<SSL>(SSL_new(m_SSLContext.get()), SSL_free);
|
|
|
|
|
|
|
|
if (!m_SSL)
|
2012-04-24 15:56:48 +02:00
|
|
|
throw OpenSSLException("SSL_new failed", ERR_get_error());
|
2012-04-24 14:02:15 +02:00
|
|
|
|
2012-04-24 14:54:05 +02:00
|
|
|
if (!GetClientCertificate())
|
|
|
|
throw InvalidArgumentException("No X509 client certificate was specified.");
|
|
|
|
|
|
|
|
if (!m_SSLIndexInitialized) {
|
|
|
|
m_SSLIndex = SSL_get_ex_new_index(0, (void *)"TLSClient", NULL, NULL, NULL);
|
|
|
|
m_SSLIndexInitialized = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
SSL_set_ex_data(m_SSL.get(), m_SSLIndex, this);
|
|
|
|
|
|
|
|
SSL_set_verify(m_SSL.get(), SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT,
|
|
|
|
&TLSClient::SSLVerifyCertificate);
|
|
|
|
|
2012-04-24 14:02:15 +02:00
|
|
|
BIO *bio = BIO_new_socket(GetFD(), 0);
|
|
|
|
SSL_set_bio(m_SSL.get(), bio, bio);
|
|
|
|
|
|
|
|
if (GetRole() == RoleInbound)
|
|
|
|
SSL_set_accept_state(m_SSL.get());
|
|
|
|
else
|
|
|
|
SSL_set_connect_state(m_SSL.get());
|
2012-05-07 11:41:23 +02:00
|
|
|
|
|
|
|
SSL_do_handshake(m_SSL.get());
|
2012-04-24 14:02:15 +02:00
|
|
|
}
|
|
|
|
|
2012-05-08 15:36:28 +02:00
|
|
|
/**
|
|
|
|
* ReadableEventHandler
|
|
|
|
*
|
|
|
|
* Processes data that is available for this socket.
|
|
|
|
*
|
|
|
|
* @param ea Event arguments.
|
|
|
|
* @returns 0
|
|
|
|
*/
|
2012-04-24 14:02:15 +02:00
|
|
|
int TLSClient::ReadableEventHandler(const EventArgs& ea)
|
|
|
|
{
|
|
|
|
int rc;
|
|
|
|
|
2012-04-27 14:15:22 +02:00
|
|
|
m_BlockRead = false;
|
|
|
|
m_BlockWrite = false;
|
|
|
|
|
2012-04-24 14:02:15 +02:00
|
|
|
size_t bufferSize = FIFO::BlockSize / 2;
|
|
|
|
char *buffer = (char *)GetRecvQueue()->GetWriteBuffer(&bufferSize);
|
|
|
|
rc = SSL_read(m_SSL.get(), buffer, bufferSize);
|
|
|
|
|
|
|
|
if (rc <= 0) {
|
|
|
|
switch (SSL_get_error(m_SSL.get(), rc)) {
|
|
|
|
case SSL_ERROR_WANT_WRITE:
|
2012-04-27 14:15:22 +02:00
|
|
|
m_BlockRead = true;
|
|
|
|
/* fall through */
|
2012-04-24 14:02:15 +02:00
|
|
|
case SSL_ERROR_WANT_READ:
|
|
|
|
return 0;
|
|
|
|
case SSL_ERROR_ZERO_RETURN:
|
|
|
|
Close();
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
default:
|
2012-04-24 14:54:05 +02:00
|
|
|
HandleSSLError();
|
2012-04-24 14:02:15 +02:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
GetRecvQueue()->Write(NULL, rc);
|
|
|
|
|
|
|
|
EventArgs dea;
|
|
|
|
dea.Source = shared_from_this();
|
|
|
|
OnDataAvailable(dea);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2012-05-08 15:36:28 +02:00
|
|
|
/**
|
|
|
|
* WritableEventHandler
|
|
|
|
*
|
|
|
|
* Processes data that can be written for this socket.
|
|
|
|
*
|
|
|
|
* @param ea Event arguments.
|
|
|
|
* @returns 0
|
|
|
|
*/
|
2012-04-24 14:02:15 +02:00
|
|
|
int TLSClient::WritableEventHandler(const EventArgs& ea)
|
|
|
|
{
|
|
|
|
int rc;
|
|
|
|
|
2012-04-27 14:15:22 +02:00
|
|
|
m_BlockRead = false;
|
|
|
|
m_BlockWrite = false;
|
|
|
|
|
2012-04-24 14:02:15 +02:00
|
|
|
rc = SSL_write(m_SSL.get(), (const char *)GetSendQueue()->GetReadBuffer(), GetSendQueue()->GetSize());
|
|
|
|
|
|
|
|
if (rc <= 0) {
|
|
|
|
switch (SSL_get_error(m_SSL.get(), rc)) {
|
|
|
|
case SSL_ERROR_WANT_READ:
|
2012-04-27 14:15:22 +02:00
|
|
|
m_BlockWrite = true;
|
|
|
|
/* fall through */
|
|
|
|
case SSL_ERROR_WANT_WRITE:
|
2012-04-24 14:02:15 +02:00
|
|
|
return 0;
|
|
|
|
case SSL_ERROR_ZERO_RETURN:
|
|
|
|
Close();
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
default:
|
2012-04-24 14:54:05 +02:00
|
|
|
HandleSSLError();
|
2012-04-24 14:02:15 +02:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
GetSendQueue()->Read(NULL, rc);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2012-05-08 15:36:28 +02:00
|
|
|
/**
|
|
|
|
* WantsToRead
|
|
|
|
*
|
|
|
|
* Checks whether data should be read for this socket.
|
|
|
|
*
|
|
|
|
* @returns true if data should be read, false otherwise.
|
|
|
|
*/
|
2012-04-24 16:27:23 +02:00
|
|
|
bool TLSClient::WantsToRead(void) const
|
|
|
|
{
|
|
|
|
if (SSL_want_read(m_SSL.get()))
|
|
|
|
return true;
|
|
|
|
|
2012-04-27 14:15:22 +02:00
|
|
|
if (m_BlockRead)
|
|
|
|
return false;
|
|
|
|
|
2012-04-24 19:53:47 +02:00
|
|
|
return TCPClient::WantsToRead();
|
2012-04-24 16:27:23 +02:00
|
|
|
}
|
|
|
|
|
2012-05-08 15:36:28 +02:00
|
|
|
/**
|
|
|
|
* WantsToWrite
|
|
|
|
*
|
|
|
|
* Checks whether data should be written for this socket.
|
|
|
|
*
|
|
|
|
* @returns true if data should be written, false otherwise.
|
|
|
|
*/
|
2012-04-24 14:02:15 +02:00
|
|
|
bool TLSClient::WantsToWrite(void) const
|
|
|
|
{
|
|
|
|
if (SSL_want_write(m_SSL.get()))
|
|
|
|
return true;
|
|
|
|
|
2012-04-27 14:15:22 +02:00
|
|
|
if (m_BlockWrite)
|
|
|
|
return false;
|
|
|
|
|
2012-04-24 14:02:15 +02:00
|
|
|
return TCPClient::WantsToWrite();
|
|
|
|
}
|
|
|
|
|
2012-05-08 15:36:28 +02:00
|
|
|
/**
|
|
|
|
* CloseInternal
|
|
|
|
*
|
|
|
|
* Closes the socket.
|
|
|
|
*
|
|
|
|
* @param from_dtor Whether this method was invoked from the destructor.
|
|
|
|
*/
|
2012-04-24 14:02:15 +02:00
|
|
|
void TLSClient::CloseInternal(bool from_dtor)
|
|
|
|
{
|
|
|
|
SSL_shutdown(m_SSL.get());
|
|
|
|
|
|
|
|
TCPClient::CloseInternal(from_dtor);
|
|
|
|
}
|
|
|
|
|
2012-05-08 15:36:28 +02:00
|
|
|
/**
|
|
|
|
* HandleSSLError
|
|
|
|
*
|
|
|
|
* Handles an OpenSSL error.
|
|
|
|
*/
|
2012-04-24 14:54:05 +02:00
|
|
|
void TLSClient::HandleSSLError(void)
|
|
|
|
{
|
|
|
|
int code = ERR_get_error();
|
|
|
|
|
|
|
|
if (code != 0) {
|
|
|
|
SocketErrorEventArgs sea;
|
|
|
|
sea.Code = code;
|
|
|
|
sea.Message = OpenSSLException::FormatErrorCode(sea.Code);
|
|
|
|
OnError(sea);
|
|
|
|
}
|
|
|
|
|
|
|
|
Close();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2012-05-08 15:36:28 +02:00
|
|
|
/**
|
|
|
|
* TLSClientFactory
|
|
|
|
*
|
|
|
|
* Factory function for the TLSClient class.
|
|
|
|
*
|
|
|
|
* @param role The role of the TLS socket.
|
|
|
|
* @param sslContext The SSL context for the socket.
|
|
|
|
* @returns A new TLS socket.
|
|
|
|
*/
|
2012-04-24 14:02:15 +02:00
|
|
|
TCPClient::Ptr icinga::TLSClientFactory(TCPClientRole role, shared_ptr<SSL_CTX> sslContext)
|
|
|
|
{
|
|
|
|
return make_shared<TLSClient>(role, sslContext);
|
|
|
|
}
|
2012-04-24 14:54:05 +02:00
|
|
|
|
2012-05-08 15:36:28 +02:00
|
|
|
/**
|
|
|
|
* SSLVerifyCertificate
|
|
|
|
*
|
|
|
|
* Callback function that verifies SSL certificates.
|
|
|
|
*
|
|
|
|
* @param ok Whether pre-checks for the SSL certificates were successful.
|
|
|
|
* @param x509Context X509 context for the certificate.
|
|
|
|
* @returns 1 if the verification was successful, 0 otherwise.
|
|
|
|
*/
|
2012-04-24 14:54:05 +02:00
|
|
|
int TLSClient::SSLVerifyCertificate(int ok, X509_STORE_CTX *x509Context)
|
|
|
|
{
|
|
|
|
SSL *ssl = (SSL *)X509_STORE_CTX_get_ex_data(x509Context, SSL_get_ex_data_X509_STORE_CTX_idx());
|
|
|
|
TLSClient *client = (TLSClient *)SSL_get_ex_data(ssl, m_SSLIndex);
|
|
|
|
|
|
|
|
if (client == NULL)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
VerifyCertificateEventArgs vcea;
|
|
|
|
vcea.Source = client->shared_from_this();
|
|
|
|
vcea.ValidCertificate = (ok != 0);
|
|
|
|
vcea.Context = x509Context;
|
2012-04-24 15:56:48 +02:00
|
|
|
vcea.Certificate = shared_ptr<X509>(x509Context->cert, &TLSClient::NullCertificateDeleter);
|
2012-04-24 14:54:05 +02:00
|
|
|
client->OnVerifyCertificate(vcea);
|
|
|
|
|
|
|
|
return (int)vcea.ValidCertificate;
|
|
|
|
}
|