mirror of
https://github.com/puma/puma.git
synced 2022-11-09 13:48:40 -05:00
Merge branch 'minissl' into just-dash-w
Conflicts: ext/puma_http11/puma_http11.c lib/puma/cli.rb lib/puma/server.rb
This commit is contained in:
commit
faf7f9916c
11 changed files with 639 additions and 18 deletions
6
Rakefile
6
Rakefile
|
@ -60,6 +60,8 @@ file 'ext/puma_http11/org/jruby/puma/Http11Parser.java' => ['ext/puma_http11/htt
|
||||||
end
|
end
|
||||||
task :ragel => ['ext/puma_http11/org/jruby/puma/Http11Parser.java']
|
task :ragel => ['ext/puma_http11/org/jruby/puma/Http11Parser.java']
|
||||||
|
|
||||||
|
if !IS_JRUBY
|
||||||
|
|
||||||
# compile extensions using rake-compiler
|
# compile extensions using rake-compiler
|
||||||
# C (MRI, Rubinius)
|
# C (MRI, Rubinius)
|
||||||
Rake::ExtensionTask.new("puma_http11", HOE.spec) do |ext|
|
Rake::ExtensionTask.new("puma_http11", HOE.spec) do |ext|
|
||||||
|
@ -77,11 +79,15 @@ Rake::ExtensionTask.new("puma_http11", HOE.spec) do |ext|
|
||||||
CLEAN.include "lib/puma/puma_http11.rb"
|
CLEAN.include "lib/puma/puma_http11.rb"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
else
|
||||||
|
|
||||||
# Java (JRuby)
|
# Java (JRuby)
|
||||||
Rake::JavaExtensionTask.new("puma_http11", HOE.spec) do |ext|
|
Rake::JavaExtensionTask.new("puma_http11", HOE.spec) do |ext|
|
||||||
ext.lib_dir = "lib/puma"
|
ext.lib_dir = "lib/puma"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
# the following is a fat-binary stub that will be used when
|
# the following is a fat-binary stub that will be used when
|
||||||
# require 'puma/puma_http11' and will use either 1.8 or 1.9 version depending
|
# require 'puma/puma_http11' and will use either 1.8 or 1.9 version depending
|
||||||
# on RUBY_VERSION
|
# on RUBY_VERSION
|
||||||
|
|
BIN
examples/puma/keystore.jks
Normal file
BIN
examples/puma/keystore.jks
Normal file
Binary file not shown.
|
@ -6,10 +6,12 @@ import org.jruby.Ruby;
|
||||||
import org.jruby.runtime.load.BasicLibraryService;
|
import org.jruby.runtime.load.BasicLibraryService;
|
||||||
|
|
||||||
import org.jruby.puma.Http11;
|
import org.jruby.puma.Http11;
|
||||||
|
import org.jruby.puma.MiniSSL;
|
||||||
|
|
||||||
public class PumaHttp11Service implements BasicLibraryService {
|
public class PumaHttp11Service implements BasicLibraryService {
|
||||||
public boolean basicLoad(final Ruby runtime) throws IOException {
|
public boolean basicLoad(final Ruby runtime) throws IOException {
|
||||||
Http11.createHttp11(runtime);
|
Http11.createHttp11(runtime);
|
||||||
|
MiniSSL.createMiniSSL(runtime);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,4 +2,7 @@ require 'mkmf'
|
||||||
|
|
||||||
dir_config("puma_http11")
|
dir_config("puma_http11")
|
||||||
|
|
||||||
|
$defs.push "-Wno-deprecated-declarations"
|
||||||
|
$libs += " -lssl -lcrypto "
|
||||||
|
|
||||||
create_makefile("puma/puma_http11")
|
create_makefile("puma/puma_http11")
|
||||||
|
|
189
ext/puma_http11/mini_ssl.c
Normal file
189
ext/puma_http11/mini_ssl.c
Normal file
|
@ -0,0 +1,189 @@
|
||||||
|
#include <ruby.h>
|
||||||
|
#include <rubyio.h>
|
||||||
|
#include <openssl/bio.h>
|
||||||
|
#include <openssl/ssl.h>
|
||||||
|
#include <openssl/err.h>
|
||||||
|
|
||||||
|
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(SSLv23_server_method());
|
||||||
|
conn->ctx = ctx;
|
||||||
|
|
||||||
|
SSL_CTX_use_certificate_file(ctx, RSTRING_PTR(cert), SSL_FILETYPE_PEM);
|
||||||
|
SSL_CTX_use_PrivateKey_file(ctx, RSTRING_PTR(key), 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_inject(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* msg = ERR_error_string(error, NULL);
|
||||||
|
|
||||||
|
ERR_clear_error();
|
||||||
|
rb_raise(eError, "OpenSSL error: %s - %d", msg, error);
|
||||||
|
}
|
||||||
|
|
||||||
|
VALUE engine_read(VALUE self) {
|
||||||
|
ms_conn* conn;
|
||||||
|
char buf[512];
|
||||||
|
int bytes, n;
|
||||||
|
|
||||||
|
Data_Get_Struct(self, ms_conn, conn);
|
||||||
|
|
||||||
|
bytes = SSL_read(conn->ssl, (void*)buf, sizeof(buf));
|
||||||
|
|
||||||
|
if(bytes > 0) {
|
||||||
|
return rb_str_new(buf, bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(SSL_want_read(conn->ssl)) return Qnil;
|
||||||
|
|
||||||
|
if(SSL_get_error(conn->ssl, bytes) == SSL_ERROR_ZERO_RETURN) {
|
||||||
|
rb_eof_error();
|
||||||
|
}
|
||||||
|
|
||||||
|
raise_error(conn->ssl, bytes);
|
||||||
|
|
||||||
|
return Qnil;
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
return Qnil;
|
||||||
|
}
|
||||||
|
|
||||||
|
VALUE engine_extract(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 puma) {
|
||||||
|
SSL_library_init();
|
||||||
|
OpenSSL_add_ssl_algorithms();
|
||||||
|
SSL_load_error_strings();
|
||||||
|
ERR_load_crypto_strings();
|
||||||
|
|
||||||
|
VALUE mod = rb_define_module_under(puma, "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, "inject", engine_inject, 1);
|
||||||
|
rb_define_method(eng, "read", engine_read, 0);
|
||||||
|
|
||||||
|
rb_define_method(eng, "write", engine_write, 1);
|
||||||
|
rb_define_method(eng, "extract", engine_extract, 0);
|
||||||
|
}
|
289
ext/puma_http11/org/jruby/puma/MiniSSL.java
Normal file
289
ext/puma_http11/org/jruby/puma/MiniSSL.java
Normal file
|
@ -0,0 +1,289 @@
|
||||||
|
package org.jruby.puma;
|
||||||
|
|
||||||
|
import org.jruby.Ruby;
|
||||||
|
import org.jruby.RubyClass;
|
||||||
|
import org.jruby.RubyHash;
|
||||||
|
import org.jruby.RubyModule;
|
||||||
|
import org.jruby.RubyNumeric;
|
||||||
|
import org.jruby.RubyObject;
|
||||||
|
import org.jruby.RubyString;
|
||||||
|
|
||||||
|
import org.jruby.anno.JRubyMethod;
|
||||||
|
|
||||||
|
import org.jruby.runtime.Block;
|
||||||
|
import org.jruby.runtime.ObjectAllocator;
|
||||||
|
import org.jruby.runtime.ThreadContext;
|
||||||
|
import org.jruby.runtime.builtin.IRubyObject;
|
||||||
|
|
||||||
|
import org.jruby.exceptions.RaiseException;
|
||||||
|
|
||||||
|
import org.jruby.util.ByteList;
|
||||||
|
|
||||||
|
|
||||||
|
import javax.net.ssl.*;
|
||||||
|
import javax.net.ssl.SSLEngineResult.*;
|
||||||
|
import java.io.*;
|
||||||
|
import java.security.*;
|
||||||
|
import java.nio.*;
|
||||||
|
|
||||||
|
public class MiniSSL extends RubyObject {
|
||||||
|
private static ObjectAllocator ALLOCATOR = new ObjectAllocator() {
|
||||||
|
public IRubyObject allocate(Ruby runtime, RubyClass klass) {
|
||||||
|
return new MiniSSL(runtime, klass);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public static void createMiniSSL(Ruby runtime) {
|
||||||
|
RubyModule mPuma = runtime.defineModule("Puma");
|
||||||
|
RubyModule ssl = mPuma.defineModuleUnder("MiniSSL");
|
||||||
|
|
||||||
|
mPuma.defineClassUnder("SSLError",
|
||||||
|
runtime.getClass("IOError"),
|
||||||
|
runtime.getClass("IOError").getAllocator());
|
||||||
|
|
||||||
|
RubyClass eng = ssl.defineClassUnder("Engine",runtime.getObject(),ALLOCATOR);
|
||||||
|
eng.defineAnnotatedMethods(MiniSSL.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Ruby runtime;
|
||||||
|
private SSLContext sslc;
|
||||||
|
|
||||||
|
private SSLEngine engine;
|
||||||
|
|
||||||
|
private ByteBuffer peerAppData;
|
||||||
|
private ByteBuffer peerNetData;
|
||||||
|
private ByteBuffer netData;
|
||||||
|
private ByteBuffer dummy;
|
||||||
|
|
||||||
|
public MiniSSL(Ruby runtime, RubyClass klass) {
|
||||||
|
super(runtime, klass);
|
||||||
|
|
||||||
|
this.runtime = runtime;
|
||||||
|
}
|
||||||
|
|
||||||
|
@JRubyMethod(meta = true)
|
||||||
|
public static IRubyObject server(ThreadContext context, IRubyObject recv, IRubyObject key, IRubyObject cert) {
|
||||||
|
RubyClass klass = (RubyClass) recv;
|
||||||
|
IRubyObject newInstance = klass.newInstance(context,
|
||||||
|
new IRubyObject[] { key, cert },
|
||||||
|
Block.NULL_BLOCK);
|
||||||
|
|
||||||
|
return newInstance;
|
||||||
|
}
|
||||||
|
|
||||||
|
@JRubyMethod
|
||||||
|
public IRubyObject initialize(IRubyObject key, IRubyObject cert)
|
||||||
|
throws java.security.KeyStoreException,
|
||||||
|
java.io.FileNotFoundException,
|
||||||
|
java.io.IOException,
|
||||||
|
java.io.FileNotFoundException,
|
||||||
|
java.security.NoSuchAlgorithmException,
|
||||||
|
java.security.KeyManagementException,
|
||||||
|
java.security.cert.CertificateException,
|
||||||
|
java.security.UnrecoverableKeyException
|
||||||
|
{
|
||||||
|
KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
|
||||||
|
KeyStore ts = KeyStore.getInstance(KeyStore.getDefaultType());
|
||||||
|
|
||||||
|
char[] pass = "blahblah".toCharArray();
|
||||||
|
|
||||||
|
ks.load(new FileInputStream(key.convertToString().asJavaString()),
|
||||||
|
pass);
|
||||||
|
ts.load(new FileInputStream(cert.convertToString().asJavaString()),
|
||||||
|
pass);
|
||||||
|
|
||||||
|
KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
|
||||||
|
kmf.init(ks, pass);
|
||||||
|
|
||||||
|
TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
|
||||||
|
tmf.init(ts);
|
||||||
|
|
||||||
|
SSLContext sslCtx = SSLContext.getInstance("TLS");
|
||||||
|
|
||||||
|
sslCtx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
|
||||||
|
|
||||||
|
sslc = sslCtx;
|
||||||
|
|
||||||
|
engine = sslc.createSSLEngine();
|
||||||
|
engine.setUseClientMode(false);
|
||||||
|
// engine.setNeedClientAuth(true);
|
||||||
|
|
||||||
|
SSLSession session = engine.getSession();
|
||||||
|
peerNetData = ByteBuffer.allocate(session.getPacketBufferSize());
|
||||||
|
peerAppData = ByteBuffer.allocate(session.getApplicationBufferSize());
|
||||||
|
netData = ByteBuffer.allocate(session.getPacketBufferSize());
|
||||||
|
peerNetData.limit(0);
|
||||||
|
peerAppData.limit(0);
|
||||||
|
netData.limit(0);
|
||||||
|
|
||||||
|
peerNetData.clear();
|
||||||
|
peerAppData.clear();
|
||||||
|
netData.clear();
|
||||||
|
|
||||||
|
dummy = ByteBuffer.allocate(0);
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@JRubyMethod
|
||||||
|
public IRubyObject inject(IRubyObject arg) {
|
||||||
|
byte[] bytes = arg.convertToString().getBytes();
|
||||||
|
|
||||||
|
peerNetData.limit(peerNetData.limit() + bytes.length);
|
||||||
|
|
||||||
|
log("capacity: " + peerNetData.capacity() + " limit: " + peerNetData.limit());
|
||||||
|
|
||||||
|
peerNetData.put(bytes);
|
||||||
|
|
||||||
|
log("netData: " + peerNetData.position() + "/" + peerAppData.limit());
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@JRubyMethod
|
||||||
|
public IRubyObject read() throws javax.net.ssl.SSLException, Exception {
|
||||||
|
peerAppData.clear();
|
||||||
|
peerNetData.flip();
|
||||||
|
SSLEngineResult res;
|
||||||
|
|
||||||
|
log("available read: " + peerNetData.position() + "/ " + peerNetData.limit());
|
||||||
|
|
||||||
|
if(!peerNetData.hasRemaining()) {
|
||||||
|
return getRuntime().getNil();
|
||||||
|
}
|
||||||
|
|
||||||
|
do {
|
||||||
|
res = engine.unwrap(peerNetData, peerAppData);
|
||||||
|
} while(res.getStatus() == SSLEngineResult.Status.OK &&
|
||||||
|
res.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.NEED_UNWRAP &&
|
||||||
|
res.bytesProduced() == 0);
|
||||||
|
|
||||||
|
log("read: ", res);
|
||||||
|
|
||||||
|
if(peerNetData.hasRemaining()) {
|
||||||
|
log("STILL HAD peerNetData!");
|
||||||
|
}
|
||||||
|
|
||||||
|
peerNetData.position(0);
|
||||||
|
peerNetData.limit(0);
|
||||||
|
|
||||||
|
HandshakeStatus hsStatus = runDelegatedTasks(res, engine);
|
||||||
|
|
||||||
|
if(res.getStatus() == SSLEngineResult.Status.BUFFER_UNDERFLOW) {
|
||||||
|
return getRuntime().getNil();
|
||||||
|
}
|
||||||
|
|
||||||
|
if(hsStatus == HandshakeStatus.NEED_WRAP) {
|
||||||
|
netData.clear();
|
||||||
|
log("netData: " + netData.limit());
|
||||||
|
engine.wrap(dummy, netData);
|
||||||
|
return getRuntime().getNil();
|
||||||
|
}
|
||||||
|
|
||||||
|
if(hsStatus == HandshakeStatus.NEED_UNWRAP) {
|
||||||
|
return getRuntime().getNil();
|
||||||
|
|
||||||
|
// log("peerNet: " + peerNetData.position() + "/" + peerNetData.limit());
|
||||||
|
// log("peerApp: " + peerAppData.position() + "/" + peerAppData.limit());
|
||||||
|
|
||||||
|
// peerNetData.compact();
|
||||||
|
|
||||||
|
// log("peerNet: " + peerNetData.position() + "/" + peerNetData.limit());
|
||||||
|
// do {
|
||||||
|
// res = engine.unwrap(peerNetData, peerAppData);
|
||||||
|
// } while(res.getStatus() == SSLEngineResult.Status.OK &&
|
||||||
|
// res.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.NEED_UNWRAP &&
|
||||||
|
// res.bytesProduced() == 0);
|
||||||
|
// return getRuntime().getNil();
|
||||||
|
}
|
||||||
|
|
||||||
|
// if(peerAppData.position() == 0 &&
|
||||||
|
// res.getStatus() == SSLEngineResult.Status.OK &&
|
||||||
|
// peerNetData.hasRemaining()) {
|
||||||
|
// res = engine.unwrap(peerNetData, peerAppData);
|
||||||
|
// }
|
||||||
|
|
||||||
|
byte[] bss = new byte[peerAppData.limit()];
|
||||||
|
|
||||||
|
peerAppData.get(bss);
|
||||||
|
|
||||||
|
RubyString str = getRuntime().newString("");
|
||||||
|
str.setValue(new ByteList(bss));
|
||||||
|
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static HandshakeStatus runDelegatedTasks(SSLEngineResult result,
|
||||||
|
SSLEngine engine) throws Exception {
|
||||||
|
|
||||||
|
HandshakeStatus hsStatus = result.getHandshakeStatus();
|
||||||
|
|
||||||
|
if(hsStatus == HandshakeStatus.NEED_TASK) {
|
||||||
|
Runnable runnable;
|
||||||
|
while ((runnable = engine.getDelegatedTask()) != null) {
|
||||||
|
log("\trunning delegated task...");
|
||||||
|
runnable.run();
|
||||||
|
}
|
||||||
|
hsStatus = engine.getHandshakeStatus();
|
||||||
|
if (hsStatus == HandshakeStatus.NEED_TASK) {
|
||||||
|
throw new Exception(
|
||||||
|
"handshake shouldn't need additional tasks");
|
||||||
|
}
|
||||||
|
log("\tnew HandshakeStatus: " + hsStatus);
|
||||||
|
}
|
||||||
|
|
||||||
|
return hsStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static void log(String str, SSLEngineResult result) {
|
||||||
|
System.out.println("The format of the SSLEngineResult is: \n" +
|
||||||
|
"\t\"getStatus() / getHandshakeStatus()\" +\n" +
|
||||||
|
"\t\"bytesConsumed() / bytesProduced()\"\n");
|
||||||
|
|
||||||
|
HandshakeStatus hsStatus = result.getHandshakeStatus();
|
||||||
|
log(str +
|
||||||
|
result.getStatus() + "/" + hsStatus + ", " +
|
||||||
|
result.bytesConsumed() + "/" + result.bytesProduced() +
|
||||||
|
" bytes");
|
||||||
|
if (hsStatus == HandshakeStatus.FINISHED) {
|
||||||
|
log("\t...ready for application data");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void log(String str) {
|
||||||
|
System.out.println(str);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@JRubyMethod
|
||||||
|
public IRubyObject write(IRubyObject arg) throws javax.net.ssl.SSLException {
|
||||||
|
log("write from: " + netData.position());
|
||||||
|
|
||||||
|
byte[] bls = arg.convertToString().getBytes();
|
||||||
|
ByteBuffer src = ByteBuffer.wrap(bls);
|
||||||
|
|
||||||
|
SSLEngineResult res = engine.wrap(src, netData);
|
||||||
|
|
||||||
|
return getRuntime().newFixnum(res.bytesConsumed());
|
||||||
|
}
|
||||||
|
|
||||||
|
@JRubyMethod
|
||||||
|
public IRubyObject extract() {
|
||||||
|
netData.flip();
|
||||||
|
|
||||||
|
if(!netData.hasRemaining()) {
|
||||||
|
return getRuntime().getNil();
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] bss = new byte[netData.limit()];
|
||||||
|
|
||||||
|
netData.get(bss);
|
||||||
|
netData.clear();
|
||||||
|
|
||||||
|
RubyString str = getRuntime().newString("");
|
||||||
|
str.setValue(new ByteList(bss));
|
||||||
|
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
}
|
|
@ -457,6 +457,7 @@ VALUE HttpParser_body(VALUE self) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Init_io_buffer(VALUE puma);
|
void Init_io_buffer(VALUE puma);
|
||||||
|
void Init_mini_ssl(VALUE mod);
|
||||||
|
|
||||||
void Init_puma_http11()
|
void Init_puma_http11()
|
||||||
{
|
{
|
||||||
|
@ -486,4 +487,5 @@ void Init_puma_http11()
|
||||||
init_common_fields();
|
init_common_fields();
|
||||||
|
|
||||||
Init_io_buffer(mPuma);
|
Init_io_buffer(mPuma);
|
||||||
|
Init_mini_ssl(mPuma);
|
||||||
}
|
}
|
||||||
|
|
|
@ -99,22 +99,22 @@ module Puma
|
||||||
@listeners << [str, io]
|
@listeners << [str, io]
|
||||||
when "ssl"
|
when "ssl"
|
||||||
params = Rack::Utils.parse_query uri.query
|
params = Rack::Utils.parse_query uri.query
|
||||||
require 'openssl'
|
require 'puma/minissl'
|
||||||
|
|
||||||
ctx = OpenSSL::SSL::SSLContext.new
|
ctx = MiniSSL::Context.new
|
||||||
unless params['key']
|
unless params['key']
|
||||||
logger.error "Please specify the SSL key via 'key='"
|
error "Please specify the SSL key via 'key='"
|
||||||
end
|
end
|
||||||
|
|
||||||
ctx.key = OpenSSL::PKey::RSA.new File.read(params['key'])
|
ctx.key = params['key']
|
||||||
|
|
||||||
unless params['cert']
|
unless params['cert']
|
||||||
logger.error "Please specify the SSL cert via 'cert='"
|
error "Please specify the SSL cert via 'cert='"
|
||||||
end
|
end
|
||||||
|
|
||||||
ctx.cert = OpenSSL::X509::Certificate.new File.read(params['cert'])
|
ctx.cert = params['cert']
|
||||||
|
|
||||||
ctx.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
ctx.verify_mode = MiniSSL::VERIFY_NONE
|
||||||
|
|
||||||
if fd = @inherited_fds.delete(str)
|
if fd = @inherited_fds.delete(str)
|
||||||
logger.log "* Inherited #{str}"
|
logger.log "* Inherited #{str}"
|
||||||
|
@ -181,6 +181,8 @@ module Puma
|
||||||
|
|
||||||
def add_ssl_listener(host, port, ctx,
|
def add_ssl_listener(host, port, ctx,
|
||||||
optimize_for_latency=true, backlog=1024)
|
optimize_for_latency=true, backlog=1024)
|
||||||
|
require 'puma/minissl'
|
||||||
|
|
||||||
s = TCPServer.new(host, port)
|
s = TCPServer.new(host, port)
|
||||||
if optimize_for_latency
|
if optimize_for_latency
|
||||||
s.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
|
s.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
|
||||||
|
@ -188,7 +190,7 @@ module Puma
|
||||||
s.setsockopt(Socket::SOL_SOCKET,Socket::SO_REUSEADDR, true)
|
s.setsockopt(Socket::SOL_SOCKET,Socket::SO_REUSEADDR, true)
|
||||||
s.listen backlog
|
s.listen backlog
|
||||||
|
|
||||||
ssl = OpenSSL::SSL::SSLServer.new(s, ctx)
|
ssl = MiniSSL::Server.new s, ctx
|
||||||
env = @proto_env.dup
|
env = @proto_env.dup
|
||||||
env[HTTPS_KEY] = HTTPS
|
env[HTTPS_KEY] = HTTPS
|
||||||
@envs[ssl] = env
|
@envs[ssl] = env
|
||||||
|
@ -199,7 +201,7 @@ module Puma
|
||||||
|
|
||||||
def inherited_ssl_listener(fd, ctx)
|
def inherited_ssl_listener(fd, ctx)
|
||||||
s = TCPServer.for_fd(fd)
|
s = TCPServer.for_fd(fd)
|
||||||
@ios << OpenSSL::SSL::SSLServer.new(s, ctx)
|
@ios << MiniSSL::Server.new(s, ctx)
|
||||||
s
|
s
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -127,7 +127,11 @@ module Puma
|
||||||
def try_to_finish
|
def try_to_finish
|
||||||
return read_body unless @read_header
|
return read_body unless @read_header
|
||||||
|
|
||||||
data = @io.readpartial(CHUNK_SIZE)
|
begin
|
||||||
|
data = @io.read_nonblock(CHUNK_SIZE)
|
||||||
|
rescue Errno::EAGAIN
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
if @buffer
|
if @buffer
|
||||||
@buffer << data
|
@buffer << data
|
||||||
|
@ -207,7 +211,11 @@ module Puma
|
||||||
want = remain
|
want = remain
|
||||||
end
|
end
|
||||||
|
|
||||||
chunk = @io.readpartial(want)
|
begin
|
||||||
|
chunk = @io.read_nonblock(want)
|
||||||
|
rescue Errno::EAGAIN
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
# No chunk means a closed socket
|
# No chunk means a closed socket
|
||||||
unless chunk
|
unless chunk
|
||||||
|
|
115
lib/puma/minissl.rb
Normal file
115
lib/puma/minissl.rb
Normal file
|
@ -0,0 +1,115 @@
|
||||||
|
module Puma::MiniSSL
|
||||||
|
class Socket
|
||||||
|
def initialize(socket, engine)
|
||||||
|
@socket = socket
|
||||||
|
@engine = engine
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_io
|
||||||
|
@socket
|
||||||
|
end
|
||||||
|
|
||||||
|
def readpartial(size)
|
||||||
|
while true
|
||||||
|
output = @engine.read
|
||||||
|
return output if output
|
||||||
|
|
||||||
|
data = @socket.readpartial(size)
|
||||||
|
@engine.inject(data)
|
||||||
|
output = @engine.read
|
||||||
|
|
||||||
|
return output if output
|
||||||
|
|
||||||
|
while neg_data = @engine.extract
|
||||||
|
@socket.write neg_data
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def read_nonblock(size)
|
||||||
|
while true
|
||||||
|
output = @engine.read
|
||||||
|
return output if output
|
||||||
|
|
||||||
|
data = @socket.read_nonblock(size)
|
||||||
|
|
||||||
|
@engine.inject(data)
|
||||||
|
output = @engine.read
|
||||||
|
|
||||||
|
return output if output
|
||||||
|
|
||||||
|
while neg_data = @engine.extract
|
||||||
|
@socket.write neg_data
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def write(data)
|
||||||
|
need = data.size
|
||||||
|
|
||||||
|
while true
|
||||||
|
wrote = @engine.write data
|
||||||
|
enc = @engine.extract
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
def close
|
||||||
|
@socket.close
|
||||||
|
end
|
||||||
|
|
||||||
|
def peeraddr
|
||||||
|
@socket.peeraddr
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class Context
|
||||||
|
attr_accessor :key, :cert, :verify_mode
|
||||||
|
end
|
||||||
|
|
||||||
|
VERIFY_NONE = 0
|
||||||
|
VERIFY_PEER = 1
|
||||||
|
|
||||||
|
#if defined?(JRUBY_VERSION)
|
||||||
|
#class Engine
|
||||||
|
#def self.server(key, cert)
|
||||||
|
#new(key, cert)
|
||||||
|
#end
|
||||||
|
#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
|
||||||
|
|
||||||
|
def close
|
||||||
|
@socket.close
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -3,6 +3,7 @@ require 'test/unit'
|
||||||
require 'socket'
|
require 'socket'
|
||||||
require 'openssl'
|
require 'openssl'
|
||||||
|
|
||||||
|
require 'puma/minissl'
|
||||||
require 'puma/server'
|
require 'puma/server'
|
||||||
|
|
||||||
require 'net/https'
|
require 'net/https'
|
||||||
|
@ -18,22 +19,26 @@ class TestPumaServer < Test::Unit::TestCase
|
||||||
@events = Puma::Events.new STDOUT, STDERR
|
@events = Puma::Events.new STDOUT, STDERR
|
||||||
@server = Puma::Server.new @app, @events
|
@server = Puma::Server.new @app, @events
|
||||||
|
|
||||||
|
if defined?(JRUBY_VERSION)
|
||||||
|
@ssl_key = File.expand_path "../../examples/puma/keystore.jks", __FILE__
|
||||||
|
@ssl_cert = @ssl_key
|
||||||
|
else
|
||||||
@ssl_key = File.expand_path "../../examples/puma/puma_keypair.pem", __FILE__
|
@ssl_key = File.expand_path "../../examples/puma/puma_keypair.pem", __FILE__
|
||||||
@ssl_cert = File.expand_path "../../examples/puma/cert_puma.pem", __FILE__
|
@ssl_cert = File.expand_path "../../examples/puma/cert_puma.pem", __FILE__
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def teardown
|
def teardown
|
||||||
@server.stop(true)
|
@server.stop(true)
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_url_scheme_for_https
|
def test_url_scheme_for_https
|
||||||
ctx = OpenSSL::SSL::SSLContext.new
|
ctx = Puma::MiniSSL::Context.new
|
||||||
|
|
||||||
ctx.key = OpenSSL::PKey::RSA.new File.read(@ssl_key)
|
ctx.key = @ssl_key
|
||||||
|
ctx.cert = @ssl_cert
|
||||||
|
|
||||||
ctx.cert = OpenSSL::X509::Certificate.new File.read(@ssl_cert)
|
ctx.verify_mode = Puma::MiniSSL::VERIFY_NONE
|
||||||
|
|
||||||
ctx.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
|
||||||
|
|
||||||
@server.add_ssl_listener @host, @port, ctx
|
@server.add_ssl_listener @host, @port, ctx
|
||||||
@server.run
|
@server.run
|
||||||
|
|
Loading…
Add table
Reference in a new issue