puma 5.6.8 → 6.4.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/History.md +332 -16
- data/README.md +79 -29
- data/bin/puma-wild +1 -1
- data/docs/compile_options.md +34 -0
- data/docs/fork_worker.md +1 -3
- data/docs/kubernetes.md +12 -0
- data/docs/nginx.md +1 -1
- data/docs/restart.md +1 -0
- data/docs/systemd.md +3 -6
- data/docs/testing_benchmarks_local_files.md +150 -0
- data/docs/testing_test_rackup_ci_files.md +36 -0
- data/ext/puma_http11/extconf.rb +16 -9
- data/ext/puma_http11/http11_parser.c +1 -1
- data/ext/puma_http11/http11_parser.h +1 -1
- data/ext/puma_http11/http11_parser.java.rl +2 -2
- data/ext/puma_http11/http11_parser.rl +2 -2
- data/ext/puma_http11/http11_parser_common.rl +2 -2
- data/ext/puma_http11/mini_ssl.c +127 -19
- data/ext/puma_http11/org/jruby/puma/Http11.java +5 -3
- data/ext/puma_http11/org/jruby/puma/Http11Parser.java +1 -1
- data/ext/puma_http11/org/jruby/puma/MiniSSL.java +157 -53
- data/ext/puma_http11/puma_http11.c +17 -9
- data/lib/puma/app/status.rb +4 -4
- data/lib/puma/binder.rb +50 -53
- data/lib/puma/cli.rb +16 -18
- data/lib/puma/client.rb +59 -19
- data/lib/puma/cluster/worker.rb +18 -11
- data/lib/puma/cluster/worker_handle.rb +4 -1
- data/lib/puma/cluster.rb +102 -40
- data/lib/puma/commonlogger.rb +21 -14
- data/lib/puma/configuration.rb +77 -59
- data/lib/puma/const.rb +137 -92
- data/lib/puma/control_cli.rb +15 -11
- data/lib/puma/detect.rb +7 -4
- data/lib/puma/dsl.rb +250 -56
- data/lib/puma/error_logger.rb +18 -9
- data/lib/puma/events.rb +6 -126
- data/lib/puma/io_buffer.rb +39 -4
- data/lib/puma/jruby_restart.rb +2 -1
- data/lib/puma/launcher/bundle_pruner.rb +104 -0
- data/lib/puma/launcher.rb +102 -175
- data/lib/puma/log_writer.rb +147 -0
- data/lib/puma/minissl/context_builder.rb +26 -12
- data/lib/puma/minissl.rb +104 -11
- data/lib/puma/null_io.rb +16 -2
- data/lib/puma/plugin/systemd.rb +90 -0
- data/lib/puma/plugin/tmp_restart.rb +1 -1
- data/lib/puma/rack/builder.rb +6 -6
- data/lib/puma/rack/urlmap.rb +1 -1
- data/lib/puma/rack_default.rb +19 -4
- data/lib/puma/reactor.rb +19 -10
- data/lib/puma/request.rb +380 -172
- data/lib/puma/runner.rb +56 -20
- data/lib/puma/sd_notify.rb +149 -0
- data/lib/puma/server.rb +137 -89
- data/lib/puma/single.rb +13 -11
- data/lib/puma/state_file.rb +3 -6
- data/lib/puma/thread_pool.rb +57 -19
- data/lib/puma/util.rb +0 -11
- data/lib/puma.rb +9 -10
- data/lib/rack/handler/puma.rb +113 -86
- data/tools/Dockerfile +2 -2
- metadata +11 -7
- data/lib/puma/queue_close.rb +0 -26
- data/lib/puma/systemd.rb +0 -46
- data/lib/rack/version_restriction.rb +0 -15
data/ext/puma_http11/mini_ssl.c
CHANGED
@@ -36,6 +36,12 @@ void raise_file_error(const char* caller, const char *filename) {
|
|
36
36
|
rb_raise(eError, "%s: error in file '%s': %s", caller, filename, ERR_error_string(ERR_get_error(), NULL));
|
37
37
|
}
|
38
38
|
|
39
|
+
NORETURN(void raise_param_error(const char* caller, const char *param));
|
40
|
+
|
41
|
+
void raise_param_error(const char* caller, const char *param) {
|
42
|
+
rb_raise(eError, "%s: error with parameter '%s': %s", caller, param, ERR_error_string(ERR_get_error(), NULL));
|
43
|
+
}
|
44
|
+
|
39
45
|
void engine_free(void *ptr) {
|
40
46
|
ms_conn *conn = ptr;
|
41
47
|
ms_cert_buf* cert_buf = (ms_cert_buf*)SSL_get_app_data(conn->ssl);
|
@@ -185,6 +191,18 @@ static int engine_verify_callback(int preverify_ok, X509_STORE_CTX* ctx) {
|
|
185
191
|
return preverify_ok;
|
186
192
|
}
|
187
193
|
|
194
|
+
static int password_callback(char *buf, int size, int rwflag, void *userdata) {
|
195
|
+
const char *password = (const char *) userdata;
|
196
|
+
size_t len = strlen(password);
|
197
|
+
|
198
|
+
if (len > (size_t) size) {
|
199
|
+
return 0;
|
200
|
+
}
|
201
|
+
|
202
|
+
memcpy(buf, password, len);
|
203
|
+
return (int) len;
|
204
|
+
}
|
205
|
+
|
188
206
|
static VALUE
|
189
207
|
sslctx_alloc(VALUE klass) {
|
190
208
|
SSL_CTX *ctx;
|
@@ -210,28 +228,35 @@ sslctx_alloc(VALUE klass) {
|
|
210
228
|
VALUE
|
211
229
|
sslctx_initialize(VALUE self, VALUE mini_ssl_ctx) {
|
212
230
|
SSL_CTX* ctx;
|
213
|
-
|
231
|
+
int ssl_options;
|
232
|
+
VALUE key, cert, ca, verify_mode, ssl_cipher_filter, no_tlsv1, no_tlsv1_1,
|
233
|
+
verification_flags, session_id_bytes, cert_pem, key_pem, key_password_command, key_password;
|
234
|
+
BIO *bio;
|
235
|
+
X509 *x509 = NULL;
|
236
|
+
EVP_PKEY *pkey;
|
237
|
+
pem_password_cb *password_cb = NULL;
|
238
|
+
const char *password = NULL;
|
214
239
|
#ifdef HAVE_SSL_CTX_SET_MIN_PROTO_VERSION
|
215
240
|
int min;
|
216
241
|
#endif
|
217
|
-
int ssl_options;
|
218
|
-
VALUE key, cert, ca, verify_mode, ssl_cipher_filter, no_tlsv1, no_tlsv1_1,
|
219
|
-
verification_flags, session_id_bytes, cert_pem, key_pem;
|
220
242
|
#ifndef HAVE_SSL_CTX_SET_DH_AUTO
|
221
243
|
DH *dh;
|
222
244
|
#endif
|
223
|
-
BIO *bio;
|
224
|
-
X509 *x509;
|
225
|
-
EVP_PKEY *pkey;
|
226
|
-
|
227
245
|
#if OPENSSL_VERSION_NUMBER < 0x10002000L
|
228
246
|
EC_KEY *ecdh;
|
229
247
|
#endif
|
248
|
+
#ifdef HAVE_SSL_CTX_SET_SESSION_CACHE_MODE
|
249
|
+
VALUE reuse, reuse_cache_size, reuse_timeout;
|
230
250
|
|
231
|
-
|
251
|
+
reuse = rb_funcall(mini_ssl_ctx, rb_intern_const("reuse"), 0);
|
252
|
+
reuse_cache_size = rb_funcall(mini_ssl_ctx, rb_intern_const("reuse_cache_size"), 0);
|
253
|
+
reuse_timeout = rb_funcall(mini_ssl_ctx, rb_intern_const("reuse_timeout"), 0);
|
254
|
+
#endif
|
232
255
|
|
233
256
|
key = rb_funcall(mini_ssl_ctx, rb_intern_const("key"), 0);
|
234
257
|
|
258
|
+
key_password_command = rb_funcall(mini_ssl_ctx, rb_intern_const("key_password_command"), 0);
|
259
|
+
|
235
260
|
cert = rb_funcall(mini_ssl_ctx, rb_intern_const("cert"), 0);
|
236
261
|
|
237
262
|
ca = rb_funcall(mini_ssl_ctx, rb_intern_const("ca"), 0);
|
@@ -248,6 +273,8 @@ sslctx_initialize(VALUE self, VALUE mini_ssl_ctx) {
|
|
248
273
|
|
249
274
|
no_tlsv1_1 = rb_funcall(mini_ssl_ctx, rb_intern_const("no_tlsv1_1"), 0);
|
250
275
|
|
276
|
+
TypedData_Get_Struct(self, SSL_CTX, &sslctx_type, ctx);
|
277
|
+
|
251
278
|
if (!NIL_P(cert)) {
|
252
279
|
StringValue(cert);
|
253
280
|
|
@@ -256,6 +283,18 @@ sslctx_initialize(VALUE self, VALUE mini_ssl_ctx) {
|
|
256
283
|
}
|
257
284
|
}
|
258
285
|
|
286
|
+
if (!NIL_P(key_password_command)) {
|
287
|
+
key_password = rb_funcall(mini_ssl_ctx, rb_intern_const("key_password"), 0);
|
288
|
+
|
289
|
+
if (!NIL_P(key_password)) {
|
290
|
+
StringValue(key_password);
|
291
|
+
password_cb = password_callback;
|
292
|
+
password = RSTRING_PTR(key_password);
|
293
|
+
SSL_CTX_set_default_passwd_cb(ctx, password_cb);
|
294
|
+
SSL_CTX_set_default_passwd_cb_userdata(ctx, (void *) password);
|
295
|
+
}
|
296
|
+
}
|
297
|
+
|
259
298
|
if (!NIL_P(key)) {
|
260
299
|
StringValue(key);
|
261
300
|
|
@@ -265,23 +304,78 @@ sslctx_initialize(VALUE self, VALUE mini_ssl_ctx) {
|
|
265
304
|
}
|
266
305
|
|
267
306
|
if (!NIL_P(cert_pem)) {
|
307
|
+
X509 *ca = NULL;
|
308
|
+
unsigned long err;
|
309
|
+
|
268
310
|
bio = BIO_new(BIO_s_mem());
|
269
311
|
BIO_puts(bio, RSTRING_PTR(cert_pem));
|
312
|
+
|
313
|
+
/**
|
314
|
+
* Much of this pulled as a simplified version of the `use_certificate_chain_file` method
|
315
|
+
* from openssl's `ssl_rsa.c` file.
|
316
|
+
*/
|
317
|
+
|
318
|
+
/* first read the cert as the first item in the pem file */
|
270
319
|
x509 = PEM_read_bio_X509(bio, NULL, NULL, NULL);
|
320
|
+
if (NULL == x509) {
|
321
|
+
BIO_free_all(bio);
|
322
|
+
raise_param_error("PEM_read_bio_X509", "cert_pem");
|
323
|
+
}
|
324
|
+
|
325
|
+
/* Add the cert to the context */
|
326
|
+
/* 1 is success - otherwise check the error codes */
|
327
|
+
if (1 != SSL_CTX_use_certificate(ctx, x509)) {
|
328
|
+
BIO_free_all(bio);
|
329
|
+
raise_param_error("SSL_CTX_use_certificate", "cert_pem");
|
330
|
+
}
|
331
|
+
|
332
|
+
X509_free(x509); /* no longer need our reference */
|
333
|
+
|
334
|
+
/* Now lets load up the rest of the certificate chain */
|
335
|
+
/* 1 is success 0 is error */
|
336
|
+
if (0 == SSL_CTX_clear_chain_certs(ctx)) {
|
337
|
+
BIO_free_all(bio);
|
338
|
+
raise_param_error("SSL_CTX_clear_chain_certs","cert_pem");
|
339
|
+
}
|
340
|
+
|
341
|
+
while (1) {
|
342
|
+
ca = PEM_read_bio_X509(bio, NULL, NULL, NULL);
|
343
|
+
|
344
|
+
if (NULL == ca) {
|
345
|
+
break;
|
346
|
+
}
|
347
|
+
|
348
|
+
if (0 == SSL_CTX_add0_chain_cert(ctx, ca)) {
|
349
|
+
BIO_free_all(bio);
|
350
|
+
raise_param_error("SSL_CTX_add0_chain_cert","cert_pem");
|
351
|
+
}
|
352
|
+
/* don't free ca - its now owned by the context */
|
353
|
+
}
|
271
354
|
|
272
|
-
|
273
|
-
|
355
|
+
/* ca is NULL - so its either the end of the file or an error */
|
356
|
+
err = ERR_peek_last_error();
|
357
|
+
|
358
|
+
/* If its the end of the file - then we are done, in any case free the bio */
|
359
|
+
BIO_free_all(bio);
|
360
|
+
|
361
|
+
if ((ERR_GET_LIB(err) == ERR_LIB_PEM) && (ERR_GET_REASON(err) == PEM_R_NO_START_LINE)) {
|
362
|
+
ERR_clear_error();
|
363
|
+
} else {
|
364
|
+
raise_param_error("PEM_read_bio_X509","cert_pem");
|
274
365
|
}
|
275
366
|
}
|
276
367
|
|
277
368
|
if (!NIL_P(key_pem)) {
|
278
369
|
bio = BIO_new(BIO_s_mem());
|
279
370
|
BIO_puts(bio, RSTRING_PTR(key_pem));
|
280
|
-
pkey = PEM_read_bio_PrivateKey(bio, NULL,
|
371
|
+
pkey = PEM_read_bio_PrivateKey(bio, NULL, password_cb, (void *) password);
|
281
372
|
|
282
373
|
if (SSL_CTX_use_PrivateKey(ctx, pkey) != 1) {
|
374
|
+
BIO_free(bio);
|
283
375
|
raise_file_error("SSL_CTX_use_PrivateKey", RSTRING_PTR(key_pem));
|
284
376
|
}
|
377
|
+
EVP_PKEY_free(pkey);
|
378
|
+
BIO_free(bio);
|
285
379
|
}
|
286
380
|
|
287
381
|
verification_flags = rb_funcall(mini_ssl_ctx, rb_intern_const("verification_flags"), 0);
|
@@ -314,8 +408,6 @@ sslctx_initialize(VALUE self, VALUE mini_ssl_ctx) {
|
|
314
408
|
|
315
409
|
SSL_CTX_set_min_proto_version(ctx, min);
|
316
410
|
|
317
|
-
SSL_CTX_set_options(ctx, ssl_options);
|
318
|
-
|
319
411
|
#else
|
320
412
|
/* As of 1.0.2f, SSL_OP_SINGLE_DH_USE key use is always on */
|
321
413
|
ssl_options |= SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_SINGLE_DH_USE;
|
@@ -326,10 +418,23 @@ sslctx_initialize(VALUE self, VALUE mini_ssl_ctx) {
|
|
326
418
|
if(RTEST(no_tlsv1_1)) {
|
327
419
|
ssl_options |= SSL_OP_NO_TLSv1 | SSL_OP_NO_TLSv1_1;
|
328
420
|
}
|
329
|
-
SSL_CTX_set_options(ctx, ssl_options);
|
330
421
|
#endif
|
331
422
|
|
332
|
-
|
423
|
+
#ifdef HAVE_SSL_CTX_SET_SESSION_CACHE_MODE
|
424
|
+
if (!NIL_P(reuse)) {
|
425
|
+
SSL_CTX_set_session_cache_mode(ctx, SSL_SESS_CACHE_SERVER);
|
426
|
+
if (!NIL_P(reuse_cache_size)) {
|
427
|
+
SSL_CTX_sess_set_cache_size(ctx, NUM2INT(reuse_cache_size));
|
428
|
+
}
|
429
|
+
if (!NIL_P(reuse_timeout)) {
|
430
|
+
SSL_CTX_set_timeout(ctx, NUM2INT(reuse_timeout));
|
431
|
+
}
|
432
|
+
} else {
|
433
|
+
SSL_CTX_set_session_cache_mode(ctx, SSL_SESS_CACHE_OFF);
|
434
|
+
}
|
435
|
+
#endif
|
436
|
+
|
437
|
+
SSL_CTX_set_options(ctx, ssl_options);
|
333
438
|
|
334
439
|
if (!NIL_P(ssl_cipher_filter)) {
|
335
440
|
StringValue(ssl_cipher_filter);
|
@@ -340,8 +445,7 @@ sslctx_initialize(VALUE self, VALUE mini_ssl_ctx) {
|
|
340
445
|
}
|
341
446
|
|
342
447
|
#if OPENSSL_VERSION_NUMBER < 0x10002000L
|
343
|
-
// Remove this case if OpenSSL 1.0.1 (now EOL) support is no
|
344
|
-
// longer needed.
|
448
|
+
// Remove this case if OpenSSL 1.0.1 (now EOL) support is no longer needed.
|
345
449
|
ecdh = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1);
|
346
450
|
if (ecdh) {
|
347
451
|
SSL_CTX_set_tmp_ecdh(ctx, ecdh);
|
@@ -442,7 +546,7 @@ NORETURN(void raise_error(SSL* ssl, int result));
|
|
442
546
|
|
443
547
|
void raise_error(SSL* ssl, int result) {
|
444
548
|
char buf[512];
|
445
|
-
char msg[
|
549
|
+
char msg[768];
|
446
550
|
const char* err_str;
|
447
551
|
int err = errno;
|
448
552
|
int mask = 4095;
|
@@ -700,6 +804,10 @@ void Init_mini_ssl(VALUE puma) {
|
|
700
804
|
|
701
805
|
rb_define_method(eng, "init?", engine_init, 0);
|
702
806
|
|
807
|
+
/* @!attribute [r] peercert
|
808
|
+
* Returns `nil` when `MiniSSL::Context#verify_mode` is set to `VERIFY_NONE`.
|
809
|
+
* @return [String, nil] DER encoded cert
|
810
|
+
*/
|
703
811
|
rb_define_method(eng, "peercert", engine_peercert, 0);
|
704
812
|
|
705
813
|
rb_define_method(eng, "ssl_vers_st", engine_ssl_vers_st, 0);
|
@@ -46,7 +46,7 @@ public class Http11 extends RubyObject {
|
|
46
46
|
public static final ByteList FRAGMENT_BYTELIST = new ByteList(ByteList.plain("FRAGMENT"));
|
47
47
|
public static final ByteList REQUEST_PATH_BYTELIST = new ByteList(ByteList.plain("REQUEST_PATH"));
|
48
48
|
public static final ByteList QUERY_STRING_BYTELIST = new ByteList(ByteList.plain("QUERY_STRING"));
|
49
|
-
public static final ByteList
|
49
|
+
public static final ByteList SERVER_PROTOCOL_BYTELIST = new ByteList(ByteList.plain("SERVER_PROTOCOL"));
|
50
50
|
|
51
51
|
private static ObjectAllocator ALLOCATOR = new ObjectAllocator() {
|
52
52
|
public IRubyObject allocate(Ruby runtime, RubyClass klass) {
|
@@ -99,6 +99,8 @@ public class Http11 extends RubyObject {
|
|
99
99
|
int bite = b.get(i) & 0xFF;
|
100
100
|
if(bite == '-') {
|
101
101
|
b.set(i, (byte)'_');
|
102
|
+
} else if(bite == '_') {
|
103
|
+
b.set(i, (byte)',');
|
102
104
|
} else {
|
103
105
|
b.set(i, (byte)Character.toUpperCase(bite));
|
104
106
|
}
|
@@ -153,9 +155,9 @@ public class Http11 extends RubyObject {
|
|
153
155
|
req.fastASet(RubyString.newStringShared(runtime, QUERY_STRING_BYTELIST),val);
|
154
156
|
}
|
155
157
|
|
156
|
-
public static void
|
158
|
+
public static void server_protocol(Ruby runtime, RubyHash req, ByteList buffer, int at, int length) {
|
157
159
|
RubyString val = RubyString.newString(runtime,new ByteList(buffer,at,length));
|
158
|
-
req.fastASet(RubyString.newStringShared(runtime,
|
160
|
+
req.fastASet(RubyString.newStringShared(runtime, SERVER_PROTOCOL_BYTELIST),val);
|
159
161
|
}
|
160
162
|
|
161
163
|
public void header_done(Ruby runtime, RubyHash req, ByteList buffer, int at, int length) {
|
@@ -383,7 +383,7 @@ case 1:
|
|
383
383
|
case 11:
|
384
384
|
// line 42 "ext/puma_http11/http11_parser.java.rl"
|
385
385
|
{
|
386
|
-
Http11.
|
386
|
+
Http11.server_protocol(runtime, parser.data, parser.buffer, parser.mark, p-parser.mark);
|
387
387
|
}
|
388
388
|
break;
|
389
389
|
case 12:
|
@@ -1,6 +1,7 @@
|
|
1
1
|
package org.jruby.puma;
|
2
2
|
|
3
3
|
import org.jruby.Ruby;
|
4
|
+
import org.jruby.RubyArray;
|
4
5
|
import org.jruby.RubyClass;
|
5
6
|
import org.jruby.RubyModule;
|
6
7
|
import org.jruby.RubyObject;
|
@@ -15,6 +16,7 @@ import org.jruby.runtime.builtin.IRubyObject;
|
|
15
16
|
import org.jruby.util.ByteList;
|
16
17
|
|
17
18
|
import javax.net.ssl.KeyManagerFactory;
|
19
|
+
import javax.net.ssl.TrustManager;
|
18
20
|
import javax.net.ssl.TrustManagerFactory;
|
19
21
|
import javax.net.ssl.SSLContext;
|
20
22
|
import javax.net.ssl.SSLEngine;
|
@@ -22,6 +24,7 @@ import javax.net.ssl.SSLEngineResult;
|
|
22
24
|
import javax.net.ssl.SSLException;
|
23
25
|
import javax.net.ssl.SSLPeerUnverifiedException;
|
24
26
|
import javax.net.ssl.SSLSession;
|
27
|
+
import javax.net.ssl.X509TrustManager;
|
25
28
|
import java.io.FileInputStream;
|
26
29
|
import java.io.InputStream;
|
27
30
|
import java.io.IOException;
|
@@ -32,15 +35,19 @@ import java.security.KeyStore;
|
|
32
35
|
import java.security.KeyStoreException;
|
33
36
|
import java.security.NoSuchAlgorithmException;
|
34
37
|
import java.security.UnrecoverableKeyException;
|
38
|
+
import java.security.cert.Certificate;
|
35
39
|
import java.security.cert.CertificateEncodingException;
|
36
40
|
import java.security.cert.CertificateException;
|
41
|
+
import java.security.cert.X509Certificate;
|
37
42
|
import java.util.concurrent.ConcurrentHashMap;
|
38
43
|
import java.util.Map;
|
44
|
+
import java.util.function.Supplier;
|
39
45
|
|
40
46
|
import static javax.net.ssl.SSLEngineResult.Status;
|
41
47
|
import static javax.net.ssl.SSLEngineResult.HandshakeStatus;
|
42
48
|
|
43
|
-
public class MiniSSL extends RubyObject {
|
49
|
+
public class MiniSSL extends RubyObject { // MiniSSL::Engine
|
50
|
+
private static final long serialVersionUID = -6903439483039141234L;
|
44
51
|
private static ObjectAllocator ALLOCATOR = new ObjectAllocator() {
|
45
52
|
public IRubyObject allocate(Ruby runtime, RubyClass klass) {
|
46
53
|
return new MiniSSL(runtime, klass);
|
@@ -51,11 +58,10 @@ public class MiniSSL extends RubyObject {
|
|
51
58
|
RubyModule mPuma = runtime.defineModule("Puma");
|
52
59
|
RubyModule ssl = mPuma.defineModuleUnder("MiniSSL");
|
53
60
|
|
54
|
-
|
55
|
-
|
56
|
-
runtime.getClass("IOError").getAllocator());
|
61
|
+
// Puma::MiniSSL::SSLError
|
62
|
+
ssl.defineClassUnder("SSLError", runtime.getStandardError(), runtime.getStandardError().getAllocator());
|
57
63
|
|
58
|
-
RubyClass eng = ssl.defineClassUnder("Engine",runtime.getObject(),ALLOCATOR);
|
64
|
+
RubyClass eng = ssl.defineClassUnder("Engine", runtime.getObject(), ALLOCATOR);
|
59
65
|
eng.defineAnnotatedMethods(MiniSSL.class);
|
60
66
|
}
|
61
67
|
|
@@ -137,74 +143,116 @@ public class MiniSSL extends RubyObject {
|
|
137
143
|
private static Map<String, KeyManagerFactory> keyManagerFactoryMap = new ConcurrentHashMap<String, KeyManagerFactory>();
|
138
144
|
private static Map<String, TrustManagerFactory> trustManagerFactoryMap = new ConcurrentHashMap<String, TrustManagerFactory>();
|
139
145
|
|
140
|
-
@JRubyMethod(meta = true)
|
146
|
+
@JRubyMethod(meta = true) // Engine.server
|
141
147
|
public static synchronized IRubyObject server(ThreadContext context, IRubyObject recv, IRubyObject miniSSLContext)
|
142
148
|
throws KeyStoreException, IOException, CertificateException, NoSuchAlgorithmException, UnrecoverableKeyException {
|
143
149
|
// Create the KeyManagerFactory and TrustManagerFactory for this server
|
144
|
-
String keystoreFile = miniSSLContext.callMethod(context, "keystore")
|
145
|
-
char[]
|
150
|
+
String keystoreFile = asStringValue(miniSSLContext.callMethod(context, "keystore"), null);
|
151
|
+
char[] keystorePass = asStringValue(miniSSLContext.callMethod(context, "keystore_pass"), null).toCharArray();
|
152
|
+
String keystoreType = asStringValue(miniSSLContext.callMethod(context, "keystore_type"), KeyStore::getDefaultType);
|
153
|
+
|
154
|
+
String truststoreFile;
|
155
|
+
char[] truststorePass;
|
156
|
+
String truststoreType;
|
157
|
+
IRubyObject truststore = miniSSLContext.callMethod(context, "truststore");
|
158
|
+
if (truststore.isNil()) {
|
159
|
+
truststoreFile = keystoreFile;
|
160
|
+
truststorePass = keystorePass;
|
161
|
+
truststoreType = keystoreType;
|
162
|
+
} else if (!isDefaultSymbol(context, truststore)) {
|
163
|
+
truststoreFile = truststore.convertToString().asJavaString();
|
164
|
+
IRubyObject pass = miniSSLContext.callMethod(context, "truststore_pass");
|
165
|
+
if (pass.isNil()) {
|
166
|
+
truststorePass = null;
|
167
|
+
} else {
|
168
|
+
truststorePass = asStringValue(pass, null).toCharArray();
|
169
|
+
}
|
170
|
+
truststoreType = asStringValue(miniSSLContext.callMethod(context, "truststore_type"), KeyStore::getDefaultType);
|
171
|
+
} else { // self.truststore = :default
|
172
|
+
truststoreFile = null;
|
173
|
+
truststorePass = null;
|
174
|
+
truststoreType = null;
|
175
|
+
}
|
146
176
|
|
147
|
-
KeyStore ks = KeyStore.getInstance(
|
177
|
+
KeyStore ks = KeyStore.getInstance(keystoreType);
|
148
178
|
InputStream is = new FileInputStream(keystoreFile);
|
149
179
|
try {
|
150
|
-
ks.load(is,
|
180
|
+
ks.load(is, keystorePass);
|
151
181
|
} finally {
|
152
182
|
is.close();
|
153
183
|
}
|
154
184
|
KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
|
155
|
-
kmf.init(ks,
|
185
|
+
kmf.init(ks, keystorePass);
|
156
186
|
keyManagerFactoryMap.put(keystoreFile, kmf);
|
157
187
|
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
188
|
+
if (truststoreFile != null) {
|
189
|
+
KeyStore ts = KeyStore.getInstance(truststoreType);
|
190
|
+
is = new FileInputStream(truststoreFile);
|
191
|
+
try {
|
192
|
+
ts.load(is, truststorePass);
|
193
|
+
} finally {
|
194
|
+
is.close();
|
195
|
+
}
|
196
|
+
TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
|
197
|
+
tmf.init(ts);
|
198
|
+
trustManagerFactoryMap.put(truststoreFile, tmf);
|
164
199
|
}
|
165
|
-
TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
|
166
|
-
tmf.init(ts);
|
167
|
-
trustManagerFactoryMap.put(keystoreFile, tmf);
|
168
200
|
|
169
201
|
RubyClass klass = (RubyClass) recv;
|
170
|
-
return klass.newInstance(context,
|
171
|
-
|
172
|
-
|
202
|
+
return klass.newInstance(context, miniSSLContext, Block.NULL_BLOCK);
|
203
|
+
}
|
204
|
+
|
205
|
+
private static String asStringValue(IRubyObject value, Supplier<String> defaultValue) {
|
206
|
+
if (defaultValue != null && value.isNil()) return defaultValue.get();
|
207
|
+
return value.convertToString().asJavaString();
|
208
|
+
}
|
209
|
+
|
210
|
+
private static boolean isDefaultSymbol(ThreadContext context, IRubyObject truststore) {
|
211
|
+
return context.runtime.newSymbol("default").equals(truststore);
|
173
212
|
}
|
174
213
|
|
175
214
|
@JRubyMethod
|
176
|
-
public IRubyObject initialize(ThreadContext
|
215
|
+
public IRubyObject initialize(ThreadContext context, IRubyObject miniSSLContext)
|
177
216
|
throws KeyStoreException, NoSuchAlgorithmException, KeyManagementException {
|
178
217
|
|
179
|
-
String keystoreFile = miniSSLContext.callMethod(
|
218
|
+
String keystoreFile = miniSSLContext.callMethod(context, "keystore").convertToString().asJavaString();
|
180
219
|
KeyManagerFactory kmf = keyManagerFactoryMap.get(keystoreFile);
|
181
|
-
|
182
|
-
|
183
|
-
|
220
|
+
IRubyObject truststore = miniSSLContext.callMethod(context, "truststore");
|
221
|
+
String truststoreFile = isDefaultSymbol(context, truststore) ? "" : asStringValue(truststore, () -> keystoreFile);
|
222
|
+
TrustManagerFactory tmf = trustManagerFactoryMap.get(truststoreFile); // null if self.truststore = :default
|
223
|
+
if (kmf == null) {
|
224
|
+
throw new KeyStoreException("Could not find KeyManagerFactory for keystore: " + keystoreFile + " truststore: " + truststoreFile);
|
184
225
|
}
|
185
226
|
|
186
227
|
SSLContext sslCtx = SSLContext.getInstance("TLS");
|
187
228
|
|
188
|
-
sslCtx.init(kmf.getKeyManagers(),
|
229
|
+
sslCtx.init(kmf.getKeyManagers(), getTrustManagers(tmf), null);
|
189
230
|
closed = false;
|
190
231
|
handshake = false;
|
191
232
|
engine = sslCtx.createSSLEngine();
|
192
233
|
|
193
|
-
String[]
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
234
|
+
String[] enabledProtocols;
|
235
|
+
IRubyObject protocols = miniSSLContext.callMethod(context, "protocols");
|
236
|
+
if (protocols.isNil()) {
|
237
|
+
if (miniSSLContext.callMethod(context, "no_tlsv1").isTrue()) {
|
238
|
+
enabledProtocols = new String[] { "TLSv1.1", "TLSv1.2", "TLSv1.3" };
|
239
|
+
} else {
|
240
|
+
enabledProtocols = new String[] { "TLSv1", "TLSv1.1", "TLSv1.2", "TLSv1.3" };
|
241
|
+
}
|
199
242
|
|
200
|
-
|
201
|
-
|
243
|
+
if (miniSSLContext.callMethod(context, "no_tlsv1_1").isTrue()) {
|
244
|
+
enabledProtocols = new String[] { "TLSv1.2", "TLSv1.3" };
|
245
|
+
}
|
246
|
+
} else if (protocols instanceof RubyArray) {
|
247
|
+
enabledProtocols = (String[]) ((RubyArray) protocols).toArray(new String[0]);
|
248
|
+
} else {
|
249
|
+
throw context.runtime.newTypeError(protocols, context.runtime.getArray());
|
202
250
|
}
|
251
|
+
engine.setEnabledProtocols(enabledProtocols);
|
203
252
|
|
204
|
-
engine.setEnabledProtocols(protocols);
|
205
253
|
engine.setUseClientMode(false);
|
206
254
|
|
207
|
-
long verify_mode = miniSSLContext.callMethod(
|
255
|
+
long verify_mode = miniSSLContext.callMethod(context, "verify_mode").convertToInteger("to_i").getLongValue();
|
208
256
|
if ((verify_mode & 0x1) != 0) { // 'peer'
|
209
257
|
engine.setWantClientAuth(true);
|
210
258
|
}
|
@@ -212,10 +260,11 @@ public class MiniSSL extends RubyObject {
|
|
212
260
|
engine.setNeedClientAuth(true);
|
213
261
|
}
|
214
262
|
|
215
|
-
IRubyObject
|
216
|
-
if (
|
217
|
-
String[]
|
218
|
-
|
263
|
+
IRubyObject cipher_suites = miniSSLContext.callMethod(context, "cipher_suites");
|
264
|
+
if (cipher_suites instanceof RubyArray) {
|
265
|
+
engine.setEnabledCipherSuites((String[]) ((RubyArray) cipher_suites).toArray(new String[0]));
|
266
|
+
} else if (!cipher_suites.isNil()) {
|
267
|
+
throw context.runtime.newTypeError(cipher_suites, context.runtime.getArray());
|
219
268
|
}
|
220
269
|
|
221
270
|
SSLSession session = engine.getSession();
|
@@ -227,6 +276,48 @@ public class MiniSSL extends RubyObject {
|
|
227
276
|
return this;
|
228
277
|
}
|
229
278
|
|
279
|
+
private TrustManager[] getTrustManagers(TrustManagerFactory factory) {
|
280
|
+
if (factory == null) return null; // use JDK trust defaults
|
281
|
+
final TrustManager[] tms = factory.getTrustManagers();
|
282
|
+
if (tms != null) {
|
283
|
+
for (int i=0; i<tms.length; i++) {
|
284
|
+
final TrustManager tm = tms[i];
|
285
|
+
if (tm instanceof X509TrustManager) {
|
286
|
+
tms[i] = new TrustManagerWrapper((X509TrustManager) tm);
|
287
|
+
}
|
288
|
+
}
|
289
|
+
}
|
290
|
+
return tms;
|
291
|
+
}
|
292
|
+
|
293
|
+
private volatile transient X509Certificate lastCheckedCert0;
|
294
|
+
|
295
|
+
private class TrustManagerWrapper implements X509TrustManager {
|
296
|
+
|
297
|
+
private final X509TrustManager delegate;
|
298
|
+
|
299
|
+
TrustManagerWrapper(X509TrustManager delegate) {
|
300
|
+
this.delegate = delegate;
|
301
|
+
}
|
302
|
+
|
303
|
+
@Override
|
304
|
+
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
|
305
|
+
lastCheckedCert0 = chain.length > 0 ? chain[0] : null;
|
306
|
+
delegate.checkClientTrusted(chain, authType);
|
307
|
+
}
|
308
|
+
|
309
|
+
@Override
|
310
|
+
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
|
311
|
+
delegate.checkServerTrusted(chain, authType);
|
312
|
+
}
|
313
|
+
|
314
|
+
@Override
|
315
|
+
public X509Certificate[] getAcceptedIssuers() {
|
316
|
+
return delegate.getAcceptedIssuers();
|
317
|
+
}
|
318
|
+
|
319
|
+
}
|
320
|
+
|
230
321
|
@JRubyMethod
|
231
322
|
public IRubyObject inject(IRubyObject arg) {
|
232
323
|
ByteList bytes = arg.convertToString().getByteList();
|
@@ -251,7 +342,7 @@ public class MiniSSL extends RubyObject {
|
|
251
342
|
res = engine.unwrap(src.getRawBuffer(), dst.getRawBuffer());
|
252
343
|
break;
|
253
344
|
default:
|
254
|
-
throw new
|
345
|
+
throw new AssertionError("Unknown SSLOperation: " + sslOp);
|
255
346
|
}
|
256
347
|
|
257
348
|
switch (res.getStatus()) {
|
@@ -336,9 +427,7 @@ public class MiniSSL extends RubyObject {
|
|
336
427
|
|
337
428
|
return RubyString.newString(getRuntime(), appDataByteList);
|
338
429
|
} catch (SSLException e) {
|
339
|
-
|
340
|
-
re.initCause(e);
|
341
|
-
throw re;
|
430
|
+
throw newSSLError(getRuntime(), e);
|
342
431
|
}
|
343
432
|
}
|
344
433
|
|
@@ -371,19 +460,19 @@ public class MiniSSL extends RubyObject {
|
|
371
460
|
|
372
461
|
return RubyString.newString(context.runtime, dataByteList);
|
373
462
|
} catch (SSLException e) {
|
374
|
-
|
375
|
-
ex.initCause(e);
|
376
|
-
throw ex;
|
463
|
+
throw newSSLError(getRuntime(), e);
|
377
464
|
}
|
378
465
|
}
|
379
466
|
|
380
467
|
@JRubyMethod
|
381
|
-
public IRubyObject peercert() throws CertificateEncodingException {
|
468
|
+
public IRubyObject peercert(ThreadContext context) throws CertificateEncodingException {
|
469
|
+
Certificate peerCert;
|
382
470
|
try {
|
383
|
-
|
471
|
+
peerCert = engine.getSession().getPeerCertificates()[0];
|
384
472
|
} catch (SSLPeerUnverifiedException e) {
|
385
|
-
|
473
|
+
peerCert = lastCheckedCert0; // null if trust check did not happen
|
386
474
|
}
|
475
|
+
return peerCert == null ? context.nil : JavaEmbedUtils.javaToRuby(context.runtime, peerCert.getEncoded());
|
387
476
|
}
|
388
477
|
|
389
478
|
@JRubyMethod(name = "init?")
|
@@ -402,4 +491,19 @@ public class MiniSSL extends RubyObject {
|
|
402
491
|
return getRuntime().getFalse();
|
403
492
|
}
|
404
493
|
}
|
494
|
+
|
495
|
+
private static RubyClass getSSLError(Ruby runtime) {
|
496
|
+
return (RubyClass) ((RubyModule) runtime.getModule("Puma").getConstantAt("MiniSSL")).getConstantAt("SSLError");
|
497
|
+
}
|
498
|
+
|
499
|
+
private static RaiseException newSSLError(Ruby runtime, SSLException cause) {
|
500
|
+
return newError(runtime, getSSLError(runtime), cause.toString(), cause);
|
501
|
+
}
|
502
|
+
|
503
|
+
private static RaiseException newError(Ruby runtime, RubyClass errorClass, String message, Throwable cause) {
|
504
|
+
RaiseException ex = RaiseException.from(runtime, errorClass, message);
|
505
|
+
ex.initCause(cause);
|
506
|
+
return ex;
|
507
|
+
}
|
508
|
+
|
405
509
|
}
|