Created
November 2, 2012 11:38
-
-
Save roxlu/4000573 to your computer and use it in GitHub Desktop.
libuv + openssl + SSLBuffer
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <iostream> | |
#include <libgen.h> | |
#include <openssl/ssl.h> | |
#include <openssl/bio.h> | |
#include <openssl/err.h> | |
#include <openssl/pem.h> | |
#include <uv.h> | |
#include <vector> | |
#include <iterator> | |
#include <algorithm> | |
#include "SSLBuffer.h" | |
#define EXIT(msg) { printf(msg); ::exit(0); } | |
#define WHERE_INFO(ssl, w, flag, msg) { \ | |
if(w & flag) { \ | |
printf("\t"); \ | |
printf(msg); \ | |
printf(" - %s ", SSL_state_string(ssl)); \ | |
printf(" - %s ", SSL_state_string_long(ssl)); \ | |
printf("\n"); \ | |
}\ | |
} | |
// INFO CALLBACK | |
void dummy_ssl_info_callback(const SSL* ssl, int where, int ret) { | |
if(ret == 0) { | |
printf("dummy_ssl_info_callback, error occured.\n"); | |
return; | |
} | |
WHERE_INFO(ssl, where, SSL_CB_LOOP, "LOOP"); | |
WHERE_INFO(ssl, where, SSL_CB_EXIT, "EXIT"); | |
WHERE_INFO(ssl, where, SSL_CB_READ, "READ"); | |
WHERE_INFO(ssl, where, SSL_CB_WRITE, "WRITE"); | |
WHERE_INFO(ssl, where, SSL_CB_ALERT, "ALERT"); | |
WHERE_INFO(ssl, where, SSL_CB_HANDSHAKE_DONE, "HANDSHAKE DONE"); | |
} | |
// MSG CALLBACK | |
void dummy_ssl_msg_callback( | |
int writep | |
,int version | |
,int contentType | |
,const void* buf | |
,size_t len | |
,SSL* ssl | |
,void *arg | |
) | |
{ | |
printf("\tMessage callback with length: %zu\n", len); | |
} | |
// VERIFY | |
int dummy_ssl_verify_callback(int ok, X509_STORE_CTX* store) { | |
return 1; // We always return 1, so no verification actually | |
} | |
// LIBUV | |
struct Client { | |
void addAppData(const std::string str) { | |
std::copy(str.begin(), str.end(), std::back_inserter(buffer_out)); | |
} | |
uv_loop_t* loop; | |
uv_tcp_t socket; | |
uv_write_t write_req; | |
uv_connect_t connect_req; | |
char host[1024]; | |
char port[1024]; | |
char page[1024]; | |
std::vector<char> buffer_in; // app data in | |
std::vector<char> buffer_out; // app data out | |
SSL_CTX* ssl_ctx; | |
SSLBuffer ssl_buffer; | |
SSL* ssl; | |
}; | |
void write_to_socket_callback(const char* data, size_t len, void* client) { | |
if(len <= 0) { | |
return; | |
} | |
Client* c = static_cast<Client*>(client); | |
uv_buf_t uvbuf; | |
uvbuf.base = (char*)data; | |
uvbuf.len = len; | |
int r = uv_write(&c->write_req, (uv_stream_t*)&c->socket, &uvbuf, 1, NULL); | |
if(r < 0) { | |
printf("ERROR: write_to_socket error: %s\n", uv_err_name(uv_last_error(c->socket.loop))); | |
} | |
} | |
void read_decrypted_callback(const char* data, size_t len, void* client) { | |
std::copy(data, data+len, std::ostream_iterator<char>(std::cout, "")); | |
} | |
// HANDLE BUFFERS HERE! | |
uv_buf_t on_alloc_callback(uv_handle_t* con, size_t size) { | |
uv_buf_t buf; | |
buf.base = (char*)malloc(size); | |
buf.len = size; | |
return buf; | |
} | |
void on_read_callback(uv_stream_t* tcp, ssize_t nread, uv_buf_t buf) { | |
Client* c = static_cast<Client*>(tcp->data); | |
c->ssl_buffer.addEncryptedData(buf.base, nread); | |
c->ssl_buffer.update(); | |
} | |
void on_connect_callback(uv_connect_t* con, int status) { | |
Client* c = static_cast<Client*>(con->data); | |
if(status == -1) { | |
printf("ERROR: on_connect_callback %s\n", uv_err_name(uv_last_error(c->loop))); | |
::exit(0); | |
} | |
int r = uv_read_start((uv_stream_t*)&c->socket, on_alloc_callback, on_read_callback); | |
if(r == -1) { | |
printf("ERROR: uv_read_start error: %s\n", uv_err_name(uv_last_error(c->loop))); | |
::exit(0); | |
} | |
const char* http_request_tpl = "" \ | |
"GET %s HTTP/1.1\r\n" | |
"Host: %s\r\n" | |
"User-Agent: uv_www_client/0.1\r\n" | |
"Accept: */*\r\n" | |
"Connection: close\r\n" | |
"\r\n"; | |
char http_request[1024]; | |
sprintf(http_request, http_request_tpl, c->page, c->host); | |
c->ssl_buffer.addApplicationData(http_request); | |
c->ssl = SSL_new(c->ssl_ctx); | |
SSL_set_connect_state(c->ssl); | |
SSL_do_handshake(c->ssl); | |
c->ssl_buffer.init(c->ssl, write_to_socket_callback, c, read_decrypted_callback, c); | |
c->ssl_buffer.update(); | |
} | |
void on_resolved_callback(uv_getaddrinfo_t* resolver, int status, struct addrinfo * res) { | |
Client* c = static_cast<Client*>(resolver->data); | |
if(status == -1) { | |
printf("ERROR: getaddrinfo callback error: %s\n", uv_err_name(uv_last_error(c->loop))); | |
::exit(0); | |
} | |
char addr[17] = {'\0'}; | |
uv_ip4_name((struct sockaddr_in*) res->ai_addr, addr, 16); | |
printf("Found host: %s\n", addr); | |
uv_tcp_init(c->loop, &c->socket); | |
uv_tcp_connect(&c->connect_req, &c->socket, *(sockaddr_in*)res->ai_addr, on_connect_callback); | |
uv_freeaddrinfo(res); | |
} | |
int main() { | |
char client_key_file[1024]; | |
sprintf(client_key_file, "%s/%s", dirname(__FILE__), "client-key.pem"); | |
// Initialize SSL | |
SSL_library_init(); | |
SSL_load_error_strings(); | |
BIO* bio_err = BIO_new_fp(stderr, BIO_NOCLOSE); | |
SSL_CTX* ssl_ctx = SSL_CTX_new(SSLv23_client_method()); | |
int rc = SSL_CTX_use_PrivateKey_file(ssl_ctx, client_key_file, SSL_FILETYPE_PEM); | |
if(!rc) { | |
EXIT("Could not load client key file.\n"); | |
} | |
SSL_CTX_set_options(ssl_ctx, SSL_OP_NO_SSLv2); | |
SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER, dummy_ssl_verify_callback); // our callback always returns true, so no validation | |
SSL_CTX_set_info_callback(ssl_ctx, dummy_ssl_info_callback); // for dibugging | |
SSL_CTX_set_msg_callback(ssl_ctx, dummy_ssl_msg_callback); | |
uv_loop_t* loop = uv_loop_new(); | |
// Client context | |
Client c; | |
c.loop = loop; | |
c.connect_req.data = &c; | |
c.socket.data = &c; | |
c.ssl = NULL; | |
c.ssl_ctx = ssl_ctx; | |
sprintf(c.host, "%s", "test.localhost"); | |
sprintf(c.port, "%s", "443"); | |
sprintf(c.page, "%s", "/chunked.php"); | |
// Resolve host | |
struct addrinfo hints; | |
hints.ai_family = PF_INET; | |
hints.ai_socktype = SOCK_STREAM; | |
hints.ai_protocol = IPPROTO_TCP; | |
hints.ai_flags = 0; | |
uv_getaddrinfo_t resolver; | |
resolver.data = &c; | |
int r = uv_getaddrinfo(loop, &resolver, on_resolved_callback, c.host, c.port, &hints); | |
uv_run(loop); | |
return 0; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include "SSLBuffer.h" | |
SSLBuffer::SSLBuffer() | |
:ssl(NULL) | |
,read_bio(NULL) | |
,write_bio(NULL) | |
,write_to_socket_callback(NULL) | |
,write_to_socket_callback_data(NULL) | |
,read_decrypted_callback(NULL) | |
,read_decrypted_callback_data(NULL) | |
{ | |
} | |
SSLBuffer::~SSLBuffer() { | |
} | |
void SSLBuffer::init( | |
SSL* s, | |
cb_write_to_socket writeCB, | |
void* writeData, | |
cb_read_decrypted readCB, | |
void* readData | |
) | |
{ | |
ssl = s; | |
read_bio = BIO_new(BIO_s_mem()); | |
write_bio = BIO_new(BIO_s_mem()); | |
SSL_set_bio(ssl, read_bio, write_bio); | |
write_to_socket_callback = writeCB; | |
write_to_socket_callback_data = writeData; | |
read_decrypted_callback = readCB; | |
read_decrypted_callback_data = readData; | |
} | |
void SSLBuffer::addEncryptedData(const char* data, size_t len) { | |
BIO_write(read_bio, data, len); | |
update(); | |
} | |
void SSLBuffer::addApplicationData(const std::string& s) { | |
addApplicationData(s.c_str(), s.size()); | |
} | |
void SSLBuffer::addApplicationData(const char* data, size_t len) { | |
std::copy(data, data+len, std::back_inserter(buffer_out)); | |
} | |
void SSLBuffer::update() { | |
if(!SSL_is_init_finished(ssl)) { | |
int r = SSL_connect(ssl); | |
if(r < 0) { | |
handleError(r); | |
} | |
} | |
else { | |
char in_buf[1024 * 16]; | |
int bytes_read = 0; | |
while((bytes_read = SSL_read(ssl, in_buf, sizeof(in_buf))) > 0) { | |
if(read_decrypted_callback) { | |
read_decrypted_callback(in_buf, bytes_read, read_decrypted_callback_data); | |
} | |
} | |
} | |
checkOutgoingApplicationData(); | |
} | |
// flushes encrypted data | |
void SSLBuffer::flushWriteBIO() { | |
char buf[1024*16]; | |
int bytes_read = 0; | |
while((bytes_read = BIO_read(write_bio, buf, sizeof(buf))) > 0) { | |
if(write_to_socket_callback) { | |
write_to_socket_callback(buf, bytes_read, write_to_socket_callback_data); | |
} | |
} | |
} | |
// Any of the SSL_* functions can cause a error. We need to handle the SSL_ERROR_WANT_READ/WRITE | |
void SSLBuffer::handleError(int r) { | |
int error = SSL_get_error(ssl, r); | |
if(error == SSL_ERROR_WANT_READ) { | |
flushWriteBIO(); | |
} | |
} | |
// Is there data pending in the out buffer which should send? | |
void SSLBuffer::checkOutgoingApplicationData() { | |
if(SSL_is_init_finished(ssl)) { | |
if(buffer_out.size() > 0) { | |
int r = SSL_write(ssl, &buffer_out[0], buffer_out.size()); // causes the write_bio to fill up (which we need to flush) | |
buffer_out.clear(); | |
handleError(r); | |
flushWriteBIO(); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#ifndef ROXLU_OPENSSL_BUFFERH | |
#define ROXLU_OPENSSL_BUFFERH | |
#include <openssl/ssl.h> | |
#include <openssl/bio.h> | |
#include <openssl/err.h> | |
#include <string> | |
#include <vector> | |
/** | |
Wrapper for memory bios + non-blocking / async sockets | |
Usage, based on libuv (libevent or libev should be something similar): | |
1) resolve a host, connect with tcp | |
2) once connected: | |
- create a SSL* | |
- call: ssl_buffer.init(...) | |
- call: SSL_set_connect_state(ssl) | |
- call: SSL_do_handshake() | |
- call: ssl_buffer.update() | |
3) when you receive data from the socket, add this to the buffer: | |
- call: ssl_buffer.addEncryptedData() | |
- call: ssl_buffer.update() | |
- Whenever you need to write to the socket the 'write to socket callback' is called | |
- Whenever there is decrypted data available the 'read decrypted data callback' is called | |
- For an example see: https://gist.github.com/gists/4000573/edit | |
*/ | |
typedef void (*cb_write_to_socket)(const char* data, size_t len, void* userdata); | |
typedef void (*cb_read_decrypted)(const char* data, size_t len, void* userdata); | |
// SSL CLIENT BUFFER | |
class SSLBuffer { | |
public: | |
SSLBuffer(); | |
~SSLBuffer(); | |
void init( | |
SSL* ssl, | |
cb_write_to_socket writeCB, | |
void* writeData, | |
cb_read_decrypted readCB, | |
void* readData | |
); | |
void update(); | |
void addEncryptedData(const char* data, size_t len); | |
void addApplicationData(const std::string& data); | |
void addApplicationData(const char* data, size_t len); | |
private: | |
void handleError(int r); | |
void checkOutgoingApplicationData(); | |
void flushWriteBIO(); | |
private: | |
cb_write_to_socket write_to_socket_callback; | |
cb_read_decrypted read_decrypted_callback; | |
void* write_to_socket_callback_data; | |
void* read_decrypted_callback_data; | |
SSL* ssl; | |
BIO* read_bio; // SSL reads from this buffer (so we write encrypted data into this) | |
BIO* write_bio; // SSL writes into this buffer (so we need to send this to the server) | |
std::vector<char> buffer_out; // application data, what needs to be encrypted and send to server | |
}; | |
#endif |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Is this work licensed for use in other people's projects? I'm hacking with libuv right now and was wondering if I could use your code. Mostly for learning/experimentation, but maybe it'll become an X11 or APL2 licensed project one day.