c_cpp libuv + libopenssl

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了c_cpp libuv + libopenssl相关的知识,希望对你有一定的参考价值。

#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;
}
#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
#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();
    }
  }
}

以上是关于c_cpp libuv + libopenssl的主要内容,如果未能解决你的问题,请参考以下文章

hiwifi里更新了openwrt的刷机包,刷完了怎样安装goagent等

什么是libuv?

libuv之二:libuv基础

libuv之一:libuv介绍

libuv 中文编程指南

编译Libuv