puma 5.6.5 → 6.4.2
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of puma might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/History.md +338 -14
- data/LICENSE +0 -0
- data/README.md +79 -29
- data/bin/puma-wild +1 -1
- data/docs/architecture.md +0 -0
- data/docs/compile_options.md +34 -0
- data/docs/deployment.md +0 -0
- data/docs/fork_worker.md +1 -3
- data/docs/images/puma-connection-flow-no-reactor.png +0 -0
- data/docs/images/puma-connection-flow.png +0 -0
- data/docs/images/puma-general-arch.png +0 -0
- data/docs/jungle/README.md +0 -0
- data/docs/jungle/rc.d/README.md +0 -0
- data/docs/jungle/rc.d/puma.conf +0 -0
- data/docs/kubernetes.md +12 -0
- data/docs/nginx.md +1 -1
- data/docs/plugins.md +0 -0
- data/docs/rails_dev_mode.md +0 -0
- data/docs/restart.md +1 -0
- data/docs/signals.md +0 -0
- data/docs/stats.md +0 -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/PumaHttp11Service.java +0 -0
- data/ext/puma_http11/ext_help.h +0 -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/no_ssl/PumaHttp11Service.java +0 -0
- data/ext/puma_http11/org/jruby/puma/Http11.java +3 -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 +100 -26
- 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 +129 -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/json_serialization.rb +0 -0
- 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/plugin.rb +0 -0
- 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 +365 -170
- 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 +12 -11
- data/lib/rack/handler/puma.rb +113 -86
- data/tools/Dockerfile +2 -2
- data/tools/trickletest.rb +0 -0
- metadata +11 -6
- data/lib/puma/queue_close.rb +0 -26
- data/lib/puma/systemd.rb +0 -46
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);
|
File without changes
|
@@ -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) {
|
@@ -153,9 +153,9 @@ public class Http11 extends RubyObject {
|
|
153
153
|
req.fastASet(RubyString.newStringShared(runtime, QUERY_STRING_BYTELIST),val);
|
154
154
|
}
|
155
155
|
|
156
|
-
public static void
|
156
|
+
public static void server_protocol(Ruby runtime, RubyHash req, ByteList buffer, int at, int length) {
|
157
157
|
RubyString val = RubyString.newString(runtime,new ByteList(buffer,at,length));
|
158
|
-
req.fastASet(RubyString.newStringShared(runtime,
|
158
|
+
req.fastASet(RubyString.newStringShared(runtime, SERVER_PROTOCOL_BYTELIST),val);
|
159
159
|
}
|
160
160
|
|
161
161
|
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
|
}
|