пятница, 25 марта 2016 г.

Работа с SSL/TLS сокетами на C++. Быстрый старт.

Пишем код (main.cpp)


#include <arpa/inet.h>
#include <iostream>
#include <map>
#include <openssl/err.h>
#include <openssl/ssl.h>
#include <sys/socket.h>
#include <unistd.h>
#include <vector>

void print_usage(const char* prog) {
    std::cout << "Usage: "
              << prog
              << " [client|server] <message>\n";
}

int create_socket(int port) {
    int s = socket(AF_INET, SOCK_STREAM, 0);
    if (s < 0) {
        perror("Unable to create socket");
        exit(EXIT_FAILURE);
    }
    return s;
}

int create_server_socket(int port) {
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(port);
    addr.sin_addr.s_addr = htonl(INADDR_ANY);
    int s = create_socket(port);
    if (bind(s, reinterpret_cast<struct sockaddr*>(&addr),
            sizeof(addr)) < 0) {
        perror("Unable to bind");
        exit(EXIT_FAILURE);
    }
    if (listen(s, 1) < 0) {
        perror("Unable to listen");
        exit(EXIT_FAILURE);
    }
    return s;
}

void connect_to_server(int socket, const std::string& ip, int port) {
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(port);
    addr.sin_addr.s_addr = inet_addr(ip.c_str());
    if (connect(socket,
            reinterpret_cast<const struct sockaddr*>(&addr),
            sizeof(addr)) == -1) {
        perror("Unable to connect");
        exit(EXIT_FAILURE);
    }
}

void do_read_write(SSL* ssl, const std::string& message) {
    std::cout << "Send: " << message << '\n';
    SSL_write(ssl, message.c_str(), message.length());
    std::vector<char> data(100, '\0');
    SSL_read(ssl, &data[0], data.size());
    std::cout << "Recv: " << &data[0] << '\n';
}

void init_openssl() {
    SSL_load_error_strings();
    OpenSSL_add_ssl_algorithms();
}

void cleanup_openssl() {
    EVP_cleanup();
}

SSL_CTX *create_context() {
    SSL_CTX* ctx = SSL_CTX_new(SSLv23_method());
    if (!ctx) {
        perror("Unable to create SSL context");
        ERR_print_errors_fp(stderr);
        exit(EXIT_FAILURE);
    }
    return ctx;
}

void configure_context(SSL_CTX *ctx) {
    SSL_CTX_set_ecdh_auto(ctx, 1);
    if (SSL_CTX_use_certificate_file(ctx, "cert.pem",
            SSL_FILETYPE_PEM) < 0) {
        ERR_print_errors_fp(stderr);
        exit(EXIT_FAILURE);
    }
    if (SSL_CTX_use_PrivateKey_file(ctx, "key.pem",
            SSL_FILETYPE_PEM) < 0) {
        ERR_print_errors_fp(stderr);
        exit(EXIT_FAILURE);
    }
}

void run_server(const std::string& message) {
    std::cout << "Run in server mode\n";
    init_openssl();
    int sock = create_server_socket(5872);
    SSL_CTX* ctx = create_context();
    configure_context(ctx);
    while (true) {
        struct sockaddr_in addr;
        uint len = sizeof(addr);
        int client = accept(sock,
                reinterpret_cast<struct sockaddr*>(&addr), &len);
        if (client < 0) {
            perror("Unable to accept");
            exit(EXIT_FAILURE);
        }
        SSL* ssl = SSL_new(ctx);
        SSL_set_fd(ssl, client);
        if (SSL_accept(ssl) <= 0)
            ERR_print_errors_fp(stderr);
        else
            do_read_write(ssl, message);
        SSL_free(ssl);
        close(client);
    }
    close(sock);
    SSL_CTX_free(ctx);
    cleanup_openssl();
}

void run_client(const std::string& message) {
    std::cout << "Run in client mode\n";
    init_openssl();
    int socket = create_socket(5872);
    SSL_CTX* ctx = create_context();
    configure_context(ctx);
    connect_to_server(socket, "127.0.0.1", 5872);
    SSL* ssl = SSL_new(ctx);
    SSL_set_fd(ssl, socket);
    if (SSL_connect(ssl) <= 0)
        ERR_print_errors_fp(stderr);
    else
        do_read_write(ssl, message);
    SSL_free(ssl);
    close(socket);
    SSL_CTX_free(ctx);
    cleanup_openssl();
}

typedef void (*Run)(const std::string&);
typedef std::map<std::string, Run> Runners;

int main(int argc, char* argv[]) {
    Runners runners;
    runners.insert(std::make_pair("server", run_server));
    runners.insert(std::make_pair("client", run_client));
    const std::string runner = 1 < argc ? argv[1] : "unknown";
    const std::string message = 2 < argc ? argv[2] :
            std::string("Hello stranger! I'm a ").append(runner);
    Runners::const_iterator r = runners.find(runner);
    runners.end() == r ? print_usage(argv[0]) : r->second(message);
    return 0;
}

Компилируем


$ g++ -lssl -lcrypto main.cpp -o main

Генерируем самоподписанный сертификат


$ openssl req -new > new.ssl.csr
$ openssl rsa -in privkey.pem -out key.pem
$ openssl x509 -in new.ssl.csr \
               -out cert.pem \
               -req \
               -signkey key.pem \
               -days 365

Наслаждаемся


$ ./main server "Hello client! I'm a server!" &
[1] 31382
Run in server mode
$ ./main client "Hello server! I'm a client!"
Run in client mode
Send: Hello client! I'm a server!
Send: Hello server! I'm a client!
Recv: Hello server! I'm a client!
Recv: Hello client! I'm a server!
$ kill 31382
[1]+  Terminated ./main server "Hello client! I'm a server!"