Anatol Zolotusky
2013-01-04 00:30:14 UTC
I'm trying to read some data over a TCP connection, and based on the contents of the data, either establish an SSL connection over the same socket, or close the socket. We're using boost::asio 1.48 on Ubuntu. (tls instead of ssl in the following source is due to a modification by our engineer, which allows PSK authentication. He has submitted it to the library's author for review, and it will apparently be included in the next release of the library. In this case no modifications are used, the authentication is done using certs)
The doc says (http://www.boost.org/doc/libs/1_48_0/doc/html/boost_asio/overview/ssl.html):
-----------------
In some use cases the underlying stream object will need to have a longer lifetime than the SSL stream, in which case the template parameter should be a reference to the stream type:
ip::tcp::socket sock(my_io_service); ssl::stream<ip::tcp::socket&> ssl_sock(sock, ctx);
---------------
So the connection class has two asio constructs TCP socket, and TLS stream, the latter constructed at runtime from the socket:
struct ContextContainer
{
boost::asio::tls::context tlsContext;
ContextContainer(boost::asio::io_service& service, boost::asio::tls::context::method method);
};
class UserConnection: public boost::enable_shared_from_this<UserConnection>
{
private:
boost::asio::io_service& io_service;
ContextContainer tlsContext;
boost::asio::ip::tcp::socket tcpSocket; // used to read cleartext token
// the TLS stream is constructed from the TCP socket object above,
// after a check on the token - hence the use of a pointer
boost::asio::tls::stream<boost::asio::ip::tcp::socket>* socket;
..........
};
void UserConnection::accepted()
{
// this method is called right after TCP connection is established
assert(buffer.getLength() >= sizeof(token));
boost::asio::async_read(tcpSocket,
boost::asio::buffer(buffer.asRawPtr<uint8_t>(), sizeof(token)),
boost::bind(&UserConnection::readToken,
shared_from_this(),
_1));
}
void UserConnection::readToken(const boost::system::error_code& error)
{
if (error)
{
ERROR << "Failed to read cleartext token: " << error;
// If an error occurs, or the token doesn't check out (see below), then no new asynchronous operations are started. This
//
means, according to ASIO docs, that all shared_ptr references to the connection object will
// disappear and the object will be destroyed automatically after this
// handler returns. The UserConnection class's destructor closes the TCP socket, and the TLS stream (socket) isn't constructed.
return;
}
Memory::copy(token, *buffer.asRawPtr<uint8_t[32]>());
DEBUG << "Received a token=" << token;
if(server.checkToken(shared_from_this()))
{
DEBUG << "Token" << token<< " has checked out, proceeding to TLS handshake";
socket = new boost::asio::tls::stream<boost::asio::ip::tcp::socket> (io_service,
tlsContext.tlsContext);
socket->async_handshake(boost::asio::tls::stream_base::server,
boost::bind(&UserConnection::tlsHandshake, shared_from_this(), _1));
}
else
{
ERROR << "Token" << token<< "hasn't checked out, closing connection";
close();
return;
}
}
void UserConnection::tlsHandshake(const boost::system::error_code& error)
{
if (error)
{
ERROR << "Failed to perform SSL handshake: " <<
error;
close();
return;
}
.........
===========================
This code fails at SSL handshake, with the error: system:9 Same code without reading a token in cleartext (i.e. when TLS stream is constructed immediately with the constructor, and the token is read after authentication) works fine, the TLS connection is authenticated.
What am I doing wrong?
The doc says (http://www.boost.org/doc/libs/1_48_0/doc/html/boost_asio/overview/ssl.html):
-----------------
In some use cases the underlying stream object will need to have a longer lifetime than the SSL stream, in which case the template parameter should be a reference to the stream type:
ip::tcp::socket sock(my_io_service); ssl::stream<ip::tcp::socket&> ssl_sock(sock, ctx);
---------------
So the connection class has two asio constructs TCP socket, and TLS stream, the latter constructed at runtime from the socket:
struct ContextContainer
{
boost::asio::tls::context tlsContext;
ContextContainer(boost::asio::io_service& service, boost::asio::tls::context::method method);
};
class UserConnection: public boost::enable_shared_from_this<UserConnection>
{
private:
boost::asio::io_service& io_service;
ContextContainer tlsContext;
boost::asio::ip::tcp::socket tcpSocket; // used to read cleartext token
// the TLS stream is constructed from the TCP socket object above,
// after a check on the token - hence the use of a pointer
boost::asio::tls::stream<boost::asio::ip::tcp::socket>* socket;
..........
};
void UserConnection::accepted()
{
// this method is called right after TCP connection is established
assert(buffer.getLength() >= sizeof(token));
boost::asio::async_read(tcpSocket,
boost::asio::buffer(buffer.asRawPtr<uint8_t>(), sizeof(token)),
boost::bind(&UserConnection::readToken,
shared_from_this(),
_1));
}
void UserConnection::readToken(const boost::system::error_code& error)
{
if (error)
{
ERROR << "Failed to read cleartext token: " << error;
// If an error occurs, or the token doesn't check out (see below), then no new asynchronous operations are started. This
//
means, according to ASIO docs, that all shared_ptr references to the connection object will
// disappear and the object will be destroyed automatically after this
// handler returns. The UserConnection class's destructor closes the TCP socket, and the TLS stream (socket) isn't constructed.
return;
}
Memory::copy(token, *buffer.asRawPtr<uint8_t[32]>());
DEBUG << "Received a token=" << token;
if(server.checkToken(shared_from_this()))
{
DEBUG << "Token" << token<< " has checked out, proceeding to TLS handshake";
socket = new boost::asio::tls::stream<boost::asio::ip::tcp::socket> (io_service,
tlsContext.tlsContext);
socket->async_handshake(boost::asio::tls::stream_base::server,
boost::bind(&UserConnection::tlsHandshake, shared_from_this(), _1));
}
else
{
ERROR << "Token" << token<< "hasn't checked out, closing connection";
close();
return;
}
}
void UserConnection::tlsHandshake(const boost::system::error_code& error)
{
if (error)
{
ERROR << "Failed to perform SSL handshake: " <<
error;
close();
return;
}
.........
===========================
This code fails at SSL handshake, with the error: system:9 Same code without reading a token in cleartext (i.e. when TLS stream is constructed immediately with the constructor, and the token is read after authentication) works fine, the TLS connection is authenticated.
What am I doing wrong?