Created
November 2, 2012 09:46
-
-
Save roxlu/3999827 to your computer and use it in GitHub Desktop.
OpenSSL + LibUV: client https
This file contains hidden or 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> | |
#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) { | |
char buf[256]; | |
X509* err_cert; | |
err_cert = X509_STORE_CTX_get_current_cert(store); | |
int err = X509_STORE_CTX_get_error(store); | |
int depth = X509_STORE_CTX_get_error_depth(store); | |
X509_NAME_oneline(X509_get_subject_name(err_cert), buf, 256); | |
BIO* outbio = BIO_new_fp(stdout, BIO_NOCLOSE); | |
X509_NAME* cert_name = X509_get_subject_name(err_cert); | |
X509_NAME_print_ex(outbio, cert_name, 0, XN_FLAG_MULTILINE); | |
BIO_free_all(outbio); | |
printf("\tssl_verify_callback(), ok: %d, error: %d, depth: %d, name: %s\n", ok, err, depth, buf); | |
return 1; // We always return 1, so no verification actually | |
} | |
// LIBUV | |
// ------ | |
void on_written_callback(uv_write_t* req, int status); | |
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; | |
SSL* ssl; | |
BIO* read_bio; | |
BIO* write_bio; | |
}; | |
void write_to_socket(Client* c, char* buf, size_t len) { | |
if(len <= 0) { | |
return; | |
} | |
uv_buf_t uvbuf; | |
uvbuf.base = buf; | |
uvbuf.len = len; | |
int r = uv_write(&c->write_req, (uv_stream_t*)&c->socket, &uvbuf, 1, on_written_callback); | |
if(r < 0) { | |
printf("ERROR: write_to_socket error: %s\n", uv_err_name(uv_last_error(c->socket.loop))); | |
} | |
} | |
void flush_read_bio(Client* c) { | |
char buf[1024*16]; | |
int bytes_read = 0; | |
while((bytes_read = BIO_read(c->write_bio, buf, sizeof(buf))) > 0) { | |
write_to_socket(c, buf, bytes_read); | |
} | |
} | |
void handle_error(Client* c, int result) { | |
int error = SSL_get_error(c->ssl, result); | |
if(error == SSL_ERROR_WANT_READ) { // wants to read from bio | |
flush_read_bio(c); | |
} | |
} | |
void check_outgoing_application_data(Client* c) { | |
if(SSL_is_init_finished(c->ssl)) { | |
if(c->buffer_out.size() > 0) { | |
std::copy(c->buffer_out.begin(), c->buffer_out.end(), std::ostream_iterator<char>(std::cout,"")); | |
int r = SSL_write(c->ssl, &c->buffer_out[0], c->buffer_out.size()); | |
c->buffer_out.clear(); | |
handle_error(c, r); | |
flush_read_bio(c); | |
} | |
} | |
} | |
// HANDLE BUFFERS HERE! | |
// -------------------- | |
void on_event(Client* c) { // is called after each socket event | |
char buf[1024 * 10]; | |
int bytes_read = 0; | |
if(!SSL_is_init_finished(c->ssl)) { | |
int r = SSL_connect(c->ssl); | |
if(r < 0) { | |
handle_error(c, r); | |
} | |
check_outgoing_application_data(c); | |
} | |
else { | |
// connect, check if there is encrypted data, or we need to send app data | |
int r = SSL_read(c->ssl, buf, sizeof(buf)); | |
if(r < 0) { | |
handle_error(c, r); | |
} | |
else if(r > 0) { | |
std::copy(buf, buf+r, std::back_inserter(c->buffer_in)); | |
std::copy(c->buffer_in.begin(), c->buffer_in.end(), std::ostream_iterator<char>(std::cout)); | |
c->buffer_in.clear(); | |
} | |
check_outgoing_application_data(c); | |
} | |
} | |
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_written_callback(uv_write_t* req, int status) { } | |
void on_read_callback(uv_stream_t* tcp, ssize_t nread, uv_buf_t buf) { | |
Client* c = static_cast<Client*>(tcp->data); | |
if(nread == -1) { // disconnected (?) | |
char plain_buf[1024*10]; | |
int r = SSL_read(c->ssl, plain_buf, sizeof(plain_buf)); | |
if(r < 0) { | |
handle_error(c, r); | |
} | |
else if(r > 0) { | |
std::copy(plain_buf, plain_buf+r, std::back_inserter(c->buffer_in)); | |
} | |
std::copy(c->buffer_in.begin(), c->buffer_in.end(), std::ostream_iterator<char>(std::cout)); | |
::exit(0); | |
} | |
int written = BIO_write(c->read_bio, buf.base, nread); | |
on_event(c); | |
} | |
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->addAppData(http_request); | |
printf("APP DATA: %zu\n", c->buffer_out.size()); | |
c->ssl = SSL_new(c->ssl_ctx); | |
c->read_bio = BIO_new(BIO_s_mem()); | |
c->write_bio = BIO_new(BIO_s_mem()); | |
SSL_set_bio(c->ssl, c->read_bio, c->write_bio); | |
SSL_set_connect_state(c->ssl); | |
r = SSL_do_handshake(c->ssl); | |
on_event(c); | |
} | |
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; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment