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.

Files changed (85) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +338 -14
  3. data/LICENSE +0 -0
  4. data/README.md +79 -29
  5. data/bin/puma-wild +1 -1
  6. data/docs/architecture.md +0 -0
  7. data/docs/compile_options.md +34 -0
  8. data/docs/deployment.md +0 -0
  9. data/docs/fork_worker.md +1 -3
  10. data/docs/images/puma-connection-flow-no-reactor.png +0 -0
  11. data/docs/images/puma-connection-flow.png +0 -0
  12. data/docs/images/puma-general-arch.png +0 -0
  13. data/docs/jungle/README.md +0 -0
  14. data/docs/jungle/rc.d/README.md +0 -0
  15. data/docs/jungle/rc.d/puma.conf +0 -0
  16. data/docs/kubernetes.md +12 -0
  17. data/docs/nginx.md +1 -1
  18. data/docs/plugins.md +0 -0
  19. data/docs/rails_dev_mode.md +0 -0
  20. data/docs/restart.md +1 -0
  21. data/docs/signals.md +0 -0
  22. data/docs/stats.md +0 -0
  23. data/docs/systemd.md +3 -6
  24. data/docs/testing_benchmarks_local_files.md +150 -0
  25. data/docs/testing_test_rackup_ci_files.md +36 -0
  26. data/ext/puma_http11/PumaHttp11Service.java +0 -0
  27. data/ext/puma_http11/ext_help.h +0 -0
  28. data/ext/puma_http11/extconf.rb +16 -9
  29. data/ext/puma_http11/http11_parser.c +1 -1
  30. data/ext/puma_http11/http11_parser.h +1 -1
  31. data/ext/puma_http11/http11_parser.java.rl +2 -2
  32. data/ext/puma_http11/http11_parser.rl +2 -2
  33. data/ext/puma_http11/http11_parser_common.rl +2 -2
  34. data/ext/puma_http11/mini_ssl.c +127 -19
  35. data/ext/puma_http11/no_ssl/PumaHttp11Service.java +0 -0
  36. data/ext/puma_http11/org/jruby/puma/Http11.java +3 -3
  37. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +1 -1
  38. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +157 -53
  39. data/ext/puma_http11/puma_http11.c +17 -9
  40. data/lib/puma/app/status.rb +4 -4
  41. data/lib/puma/binder.rb +50 -53
  42. data/lib/puma/cli.rb +16 -18
  43. data/lib/puma/client.rb +100 -26
  44. data/lib/puma/cluster/worker.rb +18 -11
  45. data/lib/puma/cluster/worker_handle.rb +4 -1
  46. data/lib/puma/cluster.rb +102 -40
  47. data/lib/puma/commonlogger.rb +21 -14
  48. data/lib/puma/configuration.rb +77 -59
  49. data/lib/puma/const.rb +129 -92
  50. data/lib/puma/control_cli.rb +15 -11
  51. data/lib/puma/detect.rb +7 -4
  52. data/lib/puma/dsl.rb +250 -56
  53. data/lib/puma/error_logger.rb +18 -9
  54. data/lib/puma/events.rb +6 -126
  55. data/lib/puma/io_buffer.rb +39 -4
  56. data/lib/puma/jruby_restart.rb +2 -1
  57. data/lib/puma/json_serialization.rb +0 -0
  58. data/lib/puma/launcher/bundle_pruner.rb +104 -0
  59. data/lib/puma/launcher.rb +102 -175
  60. data/lib/puma/log_writer.rb +147 -0
  61. data/lib/puma/minissl/context_builder.rb +26 -12
  62. data/lib/puma/minissl.rb +104 -11
  63. data/lib/puma/null_io.rb +16 -2
  64. data/lib/puma/plugin/systemd.rb +90 -0
  65. data/lib/puma/plugin/tmp_restart.rb +1 -1
  66. data/lib/puma/plugin.rb +0 -0
  67. data/lib/puma/rack/builder.rb +6 -6
  68. data/lib/puma/rack/urlmap.rb +1 -1
  69. data/lib/puma/rack_default.rb +19 -4
  70. data/lib/puma/reactor.rb +19 -10
  71. data/lib/puma/request.rb +365 -170
  72. data/lib/puma/runner.rb +56 -20
  73. data/lib/puma/sd_notify.rb +149 -0
  74. data/lib/puma/server.rb +137 -89
  75. data/lib/puma/single.rb +13 -11
  76. data/lib/puma/state_file.rb +3 -6
  77. data/lib/puma/thread_pool.rb +57 -19
  78. data/lib/puma/util.rb +0 -11
  79. data/lib/puma.rb +12 -11
  80. data/lib/rack/handler/puma.rb +113 -86
  81. data/tools/Dockerfile +2 -2
  82. data/tools/trickletest.rb +0 -0
  83. metadata +11 -6
  84. data/lib/puma/queue_close.rb +0 -26
  85. data/lib/puma/systemd.rb +0 -46
@@ -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
- TypedData_Get_Struct(self, SSL_CTX, &sslctx_type, ctx);
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
- if (SSL_CTX_use_certificate(ctx, x509) != 1) {
273
- raise_file_error("SSL_CTX_use_certificate", RSTRING_PTR(cert_pem));
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, NULL, 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
- SSL_CTX_set_session_cache_mode(ctx, SSL_SESS_CACHE_OFF);
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[512];
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 HTTP_VERSION_BYTELIST = new ByteList(ByteList.plain("HTTP_VERSION"));
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 http_version(Ruby runtime, RubyHash req, ByteList buffer, int at, int length) {
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, HTTP_VERSION_BYTELIST),val);
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.http_version(runtime, parser.data, parser.buffer, parser.mark, p-parser.mark);
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
- mPuma.defineClassUnder("SSLError",
55
- runtime.getClass("IOError"),
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").convertToString().asJavaString();
145
- char[] password = miniSSLContext.callMethod(context, "keystore_pass").convertToString().asJavaString().toCharArray();
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(KeyStore.getDefaultType());
177
+ KeyStore ks = KeyStore.getInstance(keystoreType);
148
178
  InputStream is = new FileInputStream(keystoreFile);
149
179
  try {
150
- ks.load(is, password);
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, password);
185
+ kmf.init(ks, keystorePass);
156
186
  keyManagerFactoryMap.put(keystoreFile, kmf);
157
187
 
158
- KeyStore ts = KeyStore.getInstance(KeyStore.getDefaultType());
159
- is = new FileInputStream(keystoreFile);
160
- try {
161
- ts.load(is, password);
162
- } finally {
163
- is.close();
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
- new IRubyObject[] { miniSSLContext },
172
- Block.NULL_BLOCK);
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 threadContext, IRubyObject miniSSLContext)
215
+ public IRubyObject initialize(ThreadContext context, IRubyObject miniSSLContext)
177
216
  throws KeyStoreException, NoSuchAlgorithmException, KeyManagementException {
178
217
 
179
- String keystoreFile = miniSSLContext.callMethod(threadContext, "keystore").convertToString().asJavaString();
218
+ String keystoreFile = miniSSLContext.callMethod(context, "keystore").convertToString().asJavaString();
180
219
  KeyManagerFactory kmf = keyManagerFactoryMap.get(keystoreFile);
181
- TrustManagerFactory tmf = trustManagerFactoryMap.get(keystoreFile);
182
- if(kmf == null || tmf == null) {
183
- throw new KeyStoreException("Could not find KeyManagerFactory/TrustManagerFactory for keystore: " + keystoreFile);
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(), tmf.getTrustManagers(), null);
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[] protocols;
194
- if(miniSSLContext.callMethod(threadContext, "no_tlsv1").isTrue()) {
195
- protocols = new String[] { "TLSv1.1", "TLSv1.2" };
196
- } else {
197
- protocols = new String[] { "TLSv1", "TLSv1.1", "TLSv1.2" };
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
- if(miniSSLContext.callMethod(threadContext, "no_tlsv1_1").isTrue()) {
201
- protocols = new String[] { "TLSv1.2" };
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(threadContext, "verify_mode").convertToInteger("to_i").getLongValue();
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 sslCipherListObject = miniSSLContext.callMethod(threadContext, "ssl_cipher_list");
216
- if (!sslCipherListObject.isNil()) {
217
- String[] sslCipherList = sslCipherListObject.convertToString().asJavaString().split(",");
218
- engine.setEnabledCipherSuites(sslCipherList);
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 IllegalStateException("Unknown SSLOperation: " + sslOp);
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
- RaiseException re = getRuntime().newEOFError(e.getMessage());
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
- RaiseException ex = context.runtime.newRuntimeError(e.toString());
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
- return JavaEmbedUtils.javaToRuby(getRuntime(), engine.getSession().getPeerCertificates()[0].getEncoded());
471
+ peerCert = engine.getSession().getPeerCertificates()[0];
384
472
  } catch (SSLPeerUnverifiedException e) {
385
- return getRuntime().getNil();
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
  }