From fdbedf748f4c2654fe85f613216a2dacc407c0a7 Mon Sep 17 00:00:00 2001 From: Felix Fietkau Date: Tue, 23 Oct 2012 04:01:09 +0200 Subject: [PATCH 1/1] Initial import --- .gitignore | 9 ++ CMakeLists.txt | 27 +++++ example.crt | 9 ++ example.key | 9 ++ ustream-example.c | 198 +++++++++++++++++++++++++++++++++++ ustream-io.c | 176 +++++++++++++++++++++++++++++++ ustream-io.h | 11 ++ ustream-ssl.c | 259 ++++++++++++++++++++++++++++++++++++++++++++++ ustream-ssl.h | 27 +++++ 9 files changed, 725 insertions(+) create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 example.crt create mode 100644 example.key create mode 100644 ustream-example.c create mode 100644 ustream-io.c create mode 100644 ustream-io.h create mode 100644 ustream-ssl.c create mode 100644 ustream-ssl.h diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7be0618 --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +Makefile +CMakeCache.txt +CMakeFiles +*.cmake +*.a +*.so +*.dylib +install_manifest.txt +*-example diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..bd8ce3f --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,27 @@ +cmake_minimum_required(VERSION 2.6) + +PROJECT(ustream-ssl C) +ADD_DEFINITIONS(-Os -Wall -Werror --std=gnu99 -g3 -Wmissing-declarations) + +SET(CMAKE_SHARED_LIBRARY_LINK_C_FLAGS "") + +#INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}/include/cyassl) +#LINK_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}/lib) +IF(APPLE) + INCLUDE_DIRECTORIES(/opt/local/include) + LINK_DIRECTORIES(/opt/local/lib) +ENDIF() + +IF (CYASSL) + SET(SSL_LIB cyassl) +ELSE() + SET(SSL_LIB crypto ssl) +ENDIF() + +ADD_LIBRARY(ustream-ssl SHARED ustream-ssl.c ustream-io.c) +TARGET_LINK_LIBRARIES(ustream-ssl ubox ${SSL_LIB}) + +ADD_EXECUTABLE(ustream-example ustream-example.c) +TARGET_LINK_LIBRARIES(ustream-example ustream-ssl) + +SET(CMAKE_INSTALL_PREFIX /usr) diff --git a/example.crt b/example.crt new file mode 100644 index 0000000..9ac235f --- /dev/null +++ b/example.crt @@ -0,0 +1,9 @@ +-----BEGIN CERTIFICATE----- +MIIBHTCByKADAgECAgRo1CQZMA0GCSqGSIb3DQEBBQUAMBYxFDASBgNVBAMTC2Zv +b0BiYXIuY29tMB4XDTEyMTAyMjIyNDg1NFoXDTc2MDgyMjE2MjAzOFowFjEUMBIG +A1UEAxMLZm9vQGJhci5jb20wXDANBgkqhkiG9w0BAQEFAANLADBIAkEAqU6orvsW +TP0Q/bV3m41+HSaSA5YhggSSx/w8OYR0/owDz2vhpUfVHKaRGpg+H+Q2M7uPVxms +LyUQNTaA8mNxKwIDAQABMA0GCSqGSIb3DQEBBQUAA0EAKcXv7EsiNYV/5dakVvic +Rg2Rme5PFK2jkLFOhm/jnhNfNiXcMHx5hhtmrLnTugqyAzIkV14r9n63xYErj59M +lQ== +-----END CERTIFICATE----- diff --git a/example.key b/example.key new file mode 100644 index 0000000..da69d0c --- /dev/null +++ b/example.key @@ -0,0 +1,9 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIBOQIBAAJBAKlOqK77Fkz9EP21d5uNfh0mkgOWIYIEksf8PDmEdP6MA89r4aVH +1RymkRqYPh/kNjO7j1cZrC8lEDU2gPJjcSsCAwEAAQJAAiwxO/Wa5qgEtMzEWSmq +qaMaEpO1oF6Ap7JT74UEn1OVOgnCZVdzUDu0vgWKc16vKfS+vIWU24A1VvHCqOyZ +aQIhAPXuu2zAY1e1C0L8DWXl2BjPiz3Qr0nIMWsd4kE48WidAiEAsDztNFSnaRsy +S3Zten+2uHCyZAMfYvxkUj96uUabomcCIAbAOebfVRrIPnnlP1zntUnhEJpuyxEE +bM7a8CYIMSBFAiAcmrbxUHAfmh9uqhkY0dPJWdlKbEtS2J47zzvPCIvILwIgbtEL +g29/1Fs/KyVa/sE9/oKpIBwux8UHQxg6epUKeuw= +-----END RSA PRIVATE KEY----- diff --git a/ustream-example.c b/ustream-example.c new file mode 100644 index 0000000..01e3aab --- /dev/null +++ b/ustream-example.c @@ -0,0 +1,198 @@ +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include "ustream-ssl.h" + +static void *ctx; + +static struct uloop_fd server; +static const char *port = "10000"; +static struct client *next_client = NULL; + +struct client { + struct sockaddr_in sin; + + struct ustream_fd s; + struct ustream_ssl ssl; + int ctr; + + int state; +}; + +enum { + STATE_INITIAL, + STATE_HEADERS, + STATE_DONE, +}; + +static void client_read_cb(struct ustream *s, int bytes) +{ + struct client *cl = container_of(s, struct client, ssl.stream); + struct ustream_buf *buf = s->r.head; + char *newline, *str; + + do { + str = ustream_get_read_buf(s, NULL); + if (!str) + break; + + newline = strchr(buf->data, '\n'); + if (!newline) + break; + + *newline = 0; + switch (cl->state) { + case STATE_INITIAL: + ustream_printf(s, "HTTP/1.1 200 OK\nContent-Type:text/plain\n\n"); + ustream_printf(s, "Got request header: %s\n", str); + cl->state++; + break; + case STATE_HEADERS: + switch(str[0]) { + case '\r': + case '\n': + s->eof = true; + ustream_state_change(s); + cl->state++; + break; + default: + ustream_printf(s, "%s\n", str); + break; + } + break; + default: + break; + } + ustream_consume(s, newline + 1 - str); + cl->ctr += newline + 1 - str; + } while(1); + + if (s->w.data_bytes > 256 && ustream_read_blocked(s)) { + fprintf(stderr, "Block read, bytes: %d\n", s->w.data_bytes); + ustream_set_read_blocked(s, true); + } +} + +static void client_close(struct ustream *s) +{ + struct client *cl = container_of(s, struct client, ssl.stream); + + fprintf(stderr, "Connection closed\n"); + ustream_free(s); + ustream_free(&cl->s.stream); + close(cl->s.fd.fd); + free(cl); +} + +static void client_notify_write(struct ustream *s, int bytes) +{ + fprintf(stderr, "Wrote %d bytes, pending: %d\n", bytes, s->w.data_bytes); + + if (s->w.data_bytes < 128 && ustream_read_blocked(s)) { + fprintf(stderr, "Unblock read\n"); + ustream_set_read_blocked(s, false); + } +} + +static void client_notify_state(struct ustream *s) +{ + struct client *cl = container_of(s, struct client, ssl.stream); + + if (!s->eof) + return; + + fprintf(stderr, "eof!, pending: %d, total: %d\n", s->w.data_bytes, cl->ctr); + if (!s->w.data_bytes) + return client_close(s); +} + +static void client_notify_connected(struct ustream_ssl *ssl) +{ + fprintf(stderr, "SSL connection established\n"); +} + +static void client_notify_error(struct ustream_ssl *ssl, int error, const char *str) +{ + fprintf(stderr, "SSL connection error(%d): %s\n", error, str); +} + +static void server_cb(struct uloop_fd *fd, unsigned int events) +{ + struct client *cl; + unsigned int sl = sizeof(struct sockaddr_in); + int sfd; + + if (!next_client) + next_client = calloc(1, sizeof(*next_client)); + + cl = next_client; + sfd = accept(server.fd, (struct sockaddr *) &cl->sin, &sl); + if (sfd < 0) { + fprintf(stderr, "Accept failed\n"); + return; + } + + cl->ssl.stream.string_data = true; + cl->ssl.stream.notify_read = client_read_cb; + cl->ssl.stream.notify_state = client_notify_state; + cl->ssl.stream.notify_write = client_notify_write; + cl->ssl.notify_connected = client_notify_connected; + cl->ssl.notify_error = client_notify_error; + + ustream_fd_init(&cl->s, sfd); + ustream_ssl_init(&cl->ssl, &cl->s.stream, ctx, true); + next_client = NULL; + fprintf(stderr, "New connection\n"); +} + +static int run_server(void) +{ + + server.cb = server_cb; + server.fd = usock(USOCK_TCP | USOCK_SERVER | USOCK_IPV4ONLY | USOCK_NUMERIC, "127.0.0.1", port); + if (server.fd < 0) { + perror("usock"); + return 1; + } + + uloop_init(); + uloop_fd_add(&server, ULOOP_READ); + uloop_run(); + + return 0; +} + +static int usage(const char *name) +{ + fprintf(stderr, "Usage: %s -p \n", name); + return 1; +} + +int main(int argc, char **argv) +{ + int ch; + + ctx = ustream_ssl_context_new(true); + ustream_ssl_context_set_crt_file(ctx, "example.crt"); + ustream_ssl_context_set_key_file(ctx, "example.key"); + + while ((ch = getopt(argc, argv, "p:")) != -1) { + switch(ch) { + case 'p': + port = optarg; + break; + default: + return usage(argv[0]); + } + } + + return run_server(); +} diff --git a/ustream-io.c b/ustream-io.c new file mode 100644 index 0000000..487e4ef --- /dev/null +++ b/ustream-io.c @@ -0,0 +1,176 @@ +#include + +#include + +#include "ustream-io.h" +#include "ustream-ssl.h" + +#ifdef CYASSL_OPENSSL_H_ + +/* not defined in the header file */ +typedef int (*CallbackIORecv)(char *buf, int sz, void *ctx); +typedef int (*CallbackIOSend)(char *buf, int sz, void *ctx); + +void SetCallbackIORecv_Ctx(SSL_CTX*, CallbackIORecv); +void SetCallbackIOSend_Ctx(SSL_CTX*, CallbackIOSend); +void SetCallbackIO_ReadCtx(SSL* ssl, void *rctx); +void SetCallbackIO_WriteCtx(SSL* ssl, void *wctx); + +static int s_ustream_read(char *buf, int len, void *ctx) +{ + struct ustream *s = ctx; + char *sbuf; + int slen; + + if (s->eof) + return -3; + + sbuf = ustream_get_read_buf(s, &slen); + if (slen > len) + slen = len; + + if (!slen) + return -2; + + memcpy(buf, sbuf, slen); + ustream_consume(s, slen); + + return slen; +} + +static int s_ustream_write(char *buf, int len, void *ctx) +{ + struct ustream *s = ctx; + + return ustream_write(s, buf, len, false); +} + +void ustream_set_io(SSL_CTX *ctx, SSL *ssl, struct ustream *conn) +{ + SetCallbackIO_ReadCtx(ssl, conn); + SetCallbackIO_WriteCtx(ssl, conn); + SetCallbackIORecv_Ctx(ctx, s_ustream_read); + SetCallbackIOSend_Ctx(ctx, s_ustream_write); +} + +#else + +static int +s_ustream_new(BIO *b) +{ + b->init = 1; + b->num = 0; + b->ptr = NULL; + b->flags = 0; + return 1; +} + +static int +s_ustream_free(BIO *b) +{ + if (!b) + return 0; + + b->ptr = NULL; + b->init = 0; + b->flags = 0; + return 1; +} + +static int +s_ustream_read(BIO *b, char *buf, int len) +{ + struct ustream *s; + char *sbuf; + int slen; + + if (!buf || len <= 0) + return 0; + + s = (struct ustream *)b->ptr; + if (!s) + return 0; + + sbuf = ustream_get_read_buf(s, &slen); + + BIO_clear_retry_flags(b); + if (!slen) { + BIO_set_retry_read(b); + return -1; + } + + if (slen > len) + slen = len; + + memcpy(buf, sbuf, slen); + ustream_consume(s, slen); + + return slen; +} + +static int +s_ustream_write(BIO *b, const char *buf, int len) +{ + struct ustream *s; + + if (!buf || len <= 0) + return 0; + + s = (struct ustream *)b->ptr; + if (!s) + return 0; + + return ustream_write(s, buf, len, false); +} + +static int +s_ustream_gets(BIO *b, char *buf, int len) +{ + return -1; +} + +static int +s_ustream_puts(BIO *b, const char *str) +{ + return s_ustream_write(b, str, strlen(str)); +} + +static long s_ustream_ctrl(BIO *b, int cmd, long num, void *ptr) +{ + switch (cmd) { + case BIO_CTRL_FLUSH: + return 1; + default: + return 0; + }; +} + +static BIO_METHOD methods_ustream = { + 100 | BIO_TYPE_SOURCE_SINK, + "ustream", + s_ustream_write, + s_ustream_read, + s_ustream_puts, + s_ustream_gets, + s_ustream_ctrl, + s_ustream_new, + s_ustream_free, + NULL, +}; + +static BIO *ustream_bio_new(struct ustream *s) +{ + BIO *bio; + + bio = BIO_new(&methods_ustream); + bio->ptr = s; + return bio; +} + +void ustream_set_io(SSL_CTX *ctx, SSL *ssl, struct ustream *conn) +{ + BIO *bio = ustream_bio_new(conn); + SSL_set_bio(ssl, bio, bio); +} + +#endif diff --git a/ustream-io.h b/ustream-io.h new file mode 100644 index 0000000..7aa886a --- /dev/null +++ b/ustream-io.h @@ -0,0 +1,11 @@ +#ifndef __USTREAM_BIO_H +#define __USTREAM_BIO_H + +#include +#include + +#include "ustream-ssl.h" + +void ustream_set_io(SSL_CTX *ctx, SSL *ssl, struct ustream *s); + +#endif diff --git a/ustream-ssl.c b/ustream-ssl.c new file mode 100644 index 0000000..6a337be --- /dev/null +++ b/ustream-ssl.c @@ -0,0 +1,259 @@ +#include + +#include +#include + +#include +#include "ustream-io.h" +#include "ustream-ssl.h" + +static void ssl_init(void) +{ + static bool _init = false; + + if (_init) + return; + + SSL_load_error_strings(); + SSL_library_init(); + + _init = true; +} + +static void ustream_ssl_error_cb(struct uloop_timeout *t) +{ + struct ustream_ssl *us = container_of(t, struct ustream_ssl, error_timer); + static char buffer[128]; + int error = us->error; + + if (us->notify_error) + us->notify_error(us, error, ERR_error_string(us->error, buffer)); +} + +static void ustream_ssl_error(struct ustream_ssl *us, int error) +{ + us->error = error; + uloop_timeout_set(&us->error_timer, 0); +} + +static void ustream_ssl_check_conn(struct ustream_ssl *us) +{ + int ret; + + if (us->connected || us->error) + return; + + if (us->server) + ret = SSL_accept(us->ssl); + else + ret = SSL_connect(us->ssl); + + if (ret == 1) { + us->connected = true; + if (us->notify_connected) + us->notify_connected(us); + return; + } + + ret = SSL_get_error(us->ssl, ret); + if (ret == SSL_ERROR_WANT_READ || ret == SSL_ERROR_WANT_WRITE) + return; + + ustream_ssl_error(us, ret); +} + +static void ustream_ssl_notify_read(struct ustream *s, int bytes) +{ + struct ustream_ssl *us = container_of(s->next, struct ustream_ssl, stream); + char *buf; + int wr = 0, len, ret; + + ustream_ssl_check_conn(us); + if (!us->connected || us->error) + return; + + buf = ustream_reserve(&us->stream, 1, &len); + if (!len) + return; + + do { + ret = SSL_read(us->ssl, buf, len); + if (ret < 0) { + ret = SSL_get_error(us->ssl, ret); + + if (ret == SSL_ERROR_WANT_READ) + break; + + ustream_ssl_error(us, ret); + break; + } + if (ret == 0) { + us->stream.eof = true; + ustream_state_change(&us->stream); + break; + } + + buf += ret; + len -= ret; + wr += ret; + } while (1); + + if (wr) + ustream_fill_read(&us->stream, wr); +} + +static void ustream_ssl_notify_write(struct ustream *s, int bytes) +{ + struct ustream_ssl *us = container_of(s->next, struct ustream_ssl, stream); + + ustream_ssl_check_conn(us); + ustream_write_pending(s->next); +} + +static void ustream_ssl_notify_state(struct ustream *s) +{ + s->next->write_error = true; + ustream_state_change(s->next); +} + +static int ustream_ssl_write(struct ustream *s, const char *buf, int len, bool more) +{ + struct ustream_ssl *us = container_of(s, struct ustream_ssl, stream); + int ret; + + if (!us->connected || us->error) + return 0; + + if (us->conn->w.data_bytes) + return 0; + + ret = SSL_write(us->ssl, buf, len); + if (ret < 0) { + int err = SSL_get_error(us->ssl, ret); + if (err == SSL_ERROR_WANT_WRITE) + return 0; + } + + return ret; +} + +static void ustream_ssl_set_read_blocked(struct ustream *s) +{ + struct ustream_ssl *us = container_of(s, struct ustream_ssl, stream); + + ustream_set_read_blocked(us->conn, !!s->read_blocked); +} + +static void ustream_ssl_free(struct ustream *s) +{ + struct ustream_ssl *us = container_of(s, struct ustream_ssl, stream); + + if (us->conn) { + us->conn->next = NULL; + us->conn->notify_read = NULL; + us->conn->notify_write = NULL; + us->conn->notify_state = NULL; + } + + uloop_timeout_cancel(&us->error_timer); + SSL_shutdown(us->ssl); + SSL_free(us->ssl); + us->ctx = NULL; + us->ssl = NULL; + us->conn = NULL; + us->connected = false; + us->error = false; +} + +static void ustream_ssl_stream_init(struct ustream_ssl *us) +{ + struct ustream *conn = us->conn; + struct ustream *s = &us->stream; + + conn->notify_read = ustream_ssl_notify_read; + conn->notify_write = ustream_ssl_notify_write; + conn->notify_state = ustream_ssl_notify_state; + + s->free = ustream_ssl_free; + s->write = ustream_ssl_write; + s->set_read_blocked = ustream_ssl_set_read_blocked; + ustream_init_defaults(s); +} + +void *ustream_ssl_context_new(bool server) +{ +#ifdef CYASSL_OPENSSL_H_ + SSL_METHOD *m; +#else + const SSL_METHOD *m; +#endif + SSL_CTX *c; + + ssl_init(); + +#ifdef CYASSL_OPENSSL_H_ + if (server) + m = SSLv23_server_method(); + else + m = SSLv23_client_method(); +#else + if (server) + m = TLSv1_server_method(); + else + m = TLSv1_client_method(); +#endif + + c = SSL_CTX_new(m); + if (!c) + return NULL; + + if (server) + SSL_CTX_set_verify(c, SSL_VERIFY_NONE, NULL); + + return c; +} + +int ustream_ssl_context_set_crt_file(void *ctx, const char *file) +{ + int ret; + + ret = SSL_CTX_use_certificate_file(ctx, file, SSL_FILETYPE_PEM); + if (ret < 1) + ret = SSL_CTX_use_certificate_file(ctx, file, SSL_FILETYPE_ASN1); + + return ret; +} + +int ustream_ssl_context_set_key_file(void *ctx, const char *file) +{ + int ret; + + ret = SSL_CTX_use_PrivateKey_file(ctx, file, SSL_FILETYPE_PEM); + if (ret < 1) + ret = SSL_CTX_use_PrivateKey_file(ctx, file, SSL_FILETYPE_ASN1); + + return ret; +} + +void ustream_ssl_context_free(void *ctx) +{ + SSL_CTX_free(ctx); +} + +int ustream_ssl_init(struct ustream_ssl *us, struct ustream *conn, void *ctx, bool server) +{ + us->error_timer.cb = ustream_ssl_error_cb; + us->server = server; + us->conn = conn; + us->ctx = ctx; + + us->ssl = SSL_new(us->ctx); + if (!us->ssl) + return -ENOMEM; + + conn->next = &us->stream; + ustream_set_io(ctx, us->ssl, conn); + ustream_ssl_stream_init(us); + + return 0; +} diff --git a/ustream-ssl.h b/ustream-ssl.h new file mode 100644 index 0000000..5148843 --- /dev/null +++ b/ustream-ssl.h @@ -0,0 +1,27 @@ +#ifndef __USTREAM_SSL_H +#define __USTREAM_SSL_H + +struct ustream_ssl { + struct ustream stream; + struct ustream *conn; + struct uloop_timeout error_timer; + + void (*notify_connected)(struct ustream_ssl *us); + void (*notify_error)(struct ustream_ssl *us, int error, const char *str); + + void *ctx; + void *ssl; + + int error; + bool connected; + bool server; +}; + +void *ustream_ssl_context_new(bool server); +int ustream_ssl_context_set_crt_file(void *ctx, const char *file); +int ustream_ssl_context_set_key_file(void *ctx, const char *file); +void ustream_ssl_context_free(void *ctx); + +int ustream_ssl_init(struct ustream_ssl *us, struct ustream *conn, void *ctx, bool server); + +#endif -- 2.25.1