diff --git a/ext/puma_http11/extconf.rb b/ext/puma_http11/extconf.rb index 8e84e042..5b2676f4 100644 --- a/ext/puma_http11/extconf.rb +++ b/ext/puma_http11/extconf.rb @@ -2,4 +2,6 @@ require 'mkmf' dir_config("puma_http11") +$defs.push "-Wno-deprecated-declarations" + create_makefile("puma/puma_http11") diff --git a/ext/puma_http11/mini_ssl.c b/ext/puma_http11/mini_ssl.c new file mode 100644 index 00000000..b5657551 --- /dev/null +++ b/ext/puma_http11/mini_ssl.c @@ -0,0 +1,193 @@ +#include +#include +#include +#include + +typedef struct { + BIO* read; + BIO* write; + SSL* ssl; + SSL_CTX* ctx; +} ms_conn; + +void engine_free(ms_conn* conn) { + BIO_free(conn->read); + BIO_free(conn->write); + + free(conn); +} + +ms_conn* engine_alloc(VALUE klass, VALUE* obj) { + ms_conn* conn; + + *obj = Data_Make_Struct(klass, ms_conn, 0, engine_free, conn); + + conn->read = BIO_new(BIO_s_mem()); + BIO_set_nbio(conn->read, 1); + + conn->write = BIO_new(BIO_s_mem()); + BIO_set_nbio(conn->write, 1); + + conn->ssl = 0; + conn->ctx = 0; + + return conn; +} + +VALUE engine_init_server(VALUE self, VALUE key, VALUE cert) { + VALUE obj; + ms_conn* conn = engine_alloc(self, &obj); + + StringValue(key); + StringValue(cert); + + SSL_CTX* ctx = SSL_CTX_new(DTLSv1_method()); + conn->ctx = ctx; + + SSL_CTX_use_certificate_chain_file(ctx, RSTRING_PTR(cert)); + SSL_CTX_use_PrivateKey_file(ctx, RSTRING_PTR(key), SSL_FILETYPE_PEM); + SSL_CTX_use_certificate_file(ctx, RSTRING_PTR(cert), SSL_FILETYPE_PEM); + SSL_CTX_set_options(ctx, SSL_OP_SINGLE_DH_USE); + + SSL* ssl = SSL_new(ctx); + conn->ssl = ssl; + + SSL_set_verify(ssl, SSL_VERIFY_NONE, NULL); + + SSL_set_bio(ssl, conn->read, conn->write); + + SSL_set_accept_state(ssl); + return obj; +} + +VALUE engine_init_client(VALUE klass) { + VALUE obj; + ms_conn* conn = engine_alloc(klass, &obj); + + conn->ctx = SSL_CTX_new(DTLSv1_method()); + conn->ssl = SSL_new(conn->ctx); + SSL_set_verify(conn->ssl, SSL_VERIFY_NONE, NULL); + + SSL_set_bio(conn->ssl, conn->read, conn->write); + + SSL_set_connect_state(conn->ssl); + return obj; +} + +VALUE engine_input(VALUE self, VALUE str) { + ms_conn* conn; + long used; + + Data_Get_Struct(self, ms_conn, conn); + + StringValue(str); + + used = BIO_write(conn->read, RSTRING_PTR(str), RSTRING_LEN(str)); + + if(used == 0 || used == -1) { + return Qfalse; + } + + return INT2FIX(used); +} + +static VALUE eError; + +void raise_error(SSL* ssl, int result) { + int error = SSL_get_error(ssl, result); + char buffer[256]; + + if(error == SSL_ERROR_WANT_READ) { + printf("OpenSSL WANT READ!\n"); + } + + ERR_error_string_n(error, buffer, sizeof(buffer)); + + rb_raise(eError, "OpenSSL error: %s", buffer); +} + +VALUE engine_read(VALUE self) { + ms_conn* conn; + char buf[512]; + int bytes, n; + + Data_Get_Struct(self, ms_conn, conn); + printf("pre_read: %d\n", BIO_pending(conn->read)); + + if(!SSL_is_init_finished(conn->ssl)) { + n = SSL_accept(conn->ssl); + printf("SSL_accept: %d\n", n); + if(n < 0) { + if(SSL_want_read(conn->ssl)) return Qnil; + raise_error(conn->ssl, n); + } + } + + bytes = SSL_read(conn->ssl, (void*)buf, sizeof(buf)); + printf("ssl_read: %d => %d\n", BIO_pending(conn->read), bytes); + + if(bytes > 0) { + return rb_str_new(buf, bytes); + } + raise_error(conn->ssl, bytes); + + if(SSL_want_read(conn->ssl)) return Qnil; + + raise_error(conn->ssl, bytes); +} + +VALUE engine_write(VALUE self, VALUE str) { + ms_conn* conn; + char buf[512]; + int bytes; + + Data_Get_Struct(self, ms_conn, conn); + + StringValue(str); + + bytes = SSL_write(conn->ssl, (void*)RSTRING_PTR(str), RSTRING_LEN(str)); + if(bytes > 0) { + return INT2FIX(bytes); + } + + if(SSL_want_write(conn->ssl)) return Qnil; + + raise_error(conn->ssl, bytes); +} + +VALUE engine_output(VALUE self) { + ms_conn* conn; + int bytes; + size_t pending; + char buf[512]; + + Data_Get_Struct(self, ms_conn, conn); + + pending = BIO_pending(conn->write); + if(pending > 0) { + bytes = BIO_read(conn->write, buf, sizeof(buf)); + if(bytes > 0) { + return rb_str_new(buf, bytes); + } else if(!BIO_should_retry(conn->write)) { + raise_error(conn->ssl, bytes); + } + } + + return Qnil; +} + +void Init_mini_ssl() { + VALUE mod = rb_define_module("MiniSSL"); + VALUE eng = rb_define_class_under(mod, "Engine", rb_cObject); + + eError = rb_define_class_under(mod, "SSLError", rb_eStandardError); + + rb_define_singleton_method(eng, "server", engine_init_server, 2); + rb_define_singleton_method(eng, "client", engine_init_client, 0); + + rb_define_method(eng, "input", engine_input, 1); + rb_define_method(eng, "read", engine_read, 0); + + rb_define_method(eng, "write", engine_write, 1); + rb_define_method(eng, "output", engine_output, 0); +} diff --git a/ext/puma_http11/puma_http11.c b/ext/puma_http11/puma_http11.c index 82a4705c..54d0a014 100644 --- a/ext/puma_http11/puma_http11.c +++ b/ext/puma_http11/puma_http11.c @@ -456,6 +456,8 @@ VALUE HttpParser_body(VALUE self) { return http->body; } +void Init_mini_ssl(); + void Init_puma_http11() { @@ -482,4 +484,6 @@ void Init_puma_http11() rb_define_method(cHttpParser, "nread", HttpParser_nread, 0); rb_define_method(cHttpParser, "body", HttpParser_body, 0); init_common_fields(); + + Init_mini_ssl(); } diff --git a/lib/minissl.rb b/lib/minissl.rb new file mode 100644 index 00000000..96915ad6 --- /dev/null +++ b/lib/minissl.rb @@ -0,0 +1,93 @@ +module MiniSSL + class Socket + def initialize(socket, engine) + @socket = socket + @engine = engine + end + + def to_io + @socket + end + + def readpartial(size) + + p :start + p :a1 => @engine.read + + p :w1 => @engine.output + + data = @socket.readpartial(size) + + p :data => data + + @engine.input data + + p :a2 => @engine.read + p :w1 => @engine.output + + return + + while true + output = @engine.read + return output if output + + if IO.select([@socket], nil, nil, 1) + data = @socket.readpartial(size) + p :rp => [size, data.size, data] + p :in => @engine.input(data) + end + output = @engine.read + p :read => output + return output if output + + neg_data = @engine.output + p :neg => neg_data + + if neg_data + @socket.write neg_data + end + end + end + + def write(data) + need = data.size + + while true + wrote = @engine.write data + enc = @engine.output + + if enc + @socket.write enc + end + + need -= wrote + + return data.size if need == 0 + + data = data[need..-1] + end + end + + def flush + @socket.flush + end + end + + class Server + def initialize(socket, ctx) + @socket = socket + @ctx = ctx + end + + def to_io + @socket + end + + def accept + io = @socket.accept + engine = Engine.server @ctx[:key], @ctx[:cert] + + Socket.new io, engine + end + end +end diff --git a/lib/puma/cli.rb b/lib/puma/cli.rb index b8c3a591..84468fa5 100644 --- a/lib/puma/cli.rb +++ b/lib/puma/cli.rb @@ -349,22 +349,24 @@ module Puma @listeners << [str, io] when "ssl" params = Rack::Utils.parse_query uri.query - require 'openssl' + require 'minissl' - ctx = OpenSSL::SSL::SSLContext.new + # ctx = OpenSSL::SSL::SSLContext.new unless params['key'] error "Please specify the SSL key via 'key='" end - ctx.key = OpenSSL::PKey::RSA.new File.read(params['key']) + # ctx.key = OpenSSL::PKey::RSA.new File.read(params['key']) unless params['cert'] error "Please specify the SSL cert via 'cert='" end - ctx.cert = OpenSSL::X509::Certificate.new File.read(params['cert']) + # ctx.cert = OpenSSL::X509::Certificate.new File.read(params['cert']) - ctx.verify_mode = OpenSSL::SSL::VERIFY_NONE + # ctx.verify_mode = OpenSSL::SSL::VERIFY_NONE + # + ctx = { :key => params['key'], :cert => params['cert'] } if fd = @inherited_fds.delete(str) log "* Inherited #{str}" diff --git a/lib/puma/server.rb b/lib/puma/server.rb index 544abe8a..6bfe66e1 100644 --- a/lib/puma/server.rb +++ b/lib/puma/server.rb @@ -136,7 +136,8 @@ module Puma s.setsockopt(Socket::SOL_SOCKET,Socket::SO_REUSEADDR, true) s.listen backlog - ssl = OpenSSL::SSL::SSLServer.new(s, ctx) + # ssl = OpenSSL::SSL::SSLServer.new(s, ctx) + ssl = MiniSSL::Server.new s, ctx env = @proto_env.dup env[HTTPS_KEY] = HTTPS @envs[ssl] = env @@ -147,7 +148,8 @@ module Puma def inherited_ssl_listener(fd, ctx) s = TCPServer.for_fd(fd) - @ios << OpenSSL::SSL::SSLServer.new(s, ctx) + # @ios << OpenSSL::SSL::SSLServer.new(s, ctx) + @ios << MiniSSL::Server.new(s, ctx) s end