puma 5.6.8 → 6.4.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (67) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +332 -16
  3. data/README.md +79 -29
  4. data/bin/puma-wild +1 -1
  5. data/docs/compile_options.md +34 -0
  6. data/docs/fork_worker.md +1 -3
  7. data/docs/kubernetes.md +12 -0
  8. data/docs/nginx.md +1 -1
  9. data/docs/restart.md +1 -0
  10. data/docs/systemd.md +3 -6
  11. data/docs/testing_benchmarks_local_files.md +150 -0
  12. data/docs/testing_test_rackup_ci_files.md +36 -0
  13. data/ext/puma_http11/extconf.rb +16 -9
  14. data/ext/puma_http11/http11_parser.c +1 -1
  15. data/ext/puma_http11/http11_parser.h +1 -1
  16. data/ext/puma_http11/http11_parser.java.rl +2 -2
  17. data/ext/puma_http11/http11_parser.rl +2 -2
  18. data/ext/puma_http11/http11_parser_common.rl +2 -2
  19. data/ext/puma_http11/mini_ssl.c +127 -19
  20. data/ext/puma_http11/org/jruby/puma/Http11.java +5 -3
  21. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +1 -1
  22. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +157 -53
  23. data/ext/puma_http11/puma_http11.c +17 -9
  24. data/lib/puma/app/status.rb +4 -4
  25. data/lib/puma/binder.rb +50 -53
  26. data/lib/puma/cli.rb +16 -18
  27. data/lib/puma/client.rb +59 -19
  28. data/lib/puma/cluster/worker.rb +18 -11
  29. data/lib/puma/cluster/worker_handle.rb +4 -1
  30. data/lib/puma/cluster.rb +102 -40
  31. data/lib/puma/commonlogger.rb +21 -14
  32. data/lib/puma/configuration.rb +77 -59
  33. data/lib/puma/const.rb +137 -92
  34. data/lib/puma/control_cli.rb +15 -11
  35. data/lib/puma/detect.rb +7 -4
  36. data/lib/puma/dsl.rb +250 -56
  37. data/lib/puma/error_logger.rb +18 -9
  38. data/lib/puma/events.rb +6 -126
  39. data/lib/puma/io_buffer.rb +39 -4
  40. data/lib/puma/jruby_restart.rb +2 -1
  41. data/lib/puma/launcher/bundle_pruner.rb +104 -0
  42. data/lib/puma/launcher.rb +102 -175
  43. data/lib/puma/log_writer.rb +147 -0
  44. data/lib/puma/minissl/context_builder.rb +26 -12
  45. data/lib/puma/minissl.rb +104 -11
  46. data/lib/puma/null_io.rb +16 -2
  47. data/lib/puma/plugin/systemd.rb +90 -0
  48. data/lib/puma/plugin/tmp_restart.rb +1 -1
  49. data/lib/puma/rack/builder.rb +6 -6
  50. data/lib/puma/rack/urlmap.rb +1 -1
  51. data/lib/puma/rack_default.rb +19 -4
  52. data/lib/puma/reactor.rb +19 -10
  53. data/lib/puma/request.rb +380 -172
  54. data/lib/puma/runner.rb +56 -20
  55. data/lib/puma/sd_notify.rb +149 -0
  56. data/lib/puma/server.rb +137 -89
  57. data/lib/puma/single.rb +13 -11
  58. data/lib/puma/state_file.rb +3 -6
  59. data/lib/puma/thread_pool.rb +57 -19
  60. data/lib/puma/util.rb +0 -11
  61. data/lib/puma.rb +9 -10
  62. data/lib/rack/handler/puma.rb +113 -86
  63. data/tools/Dockerfile +2 -2
  64. metadata +11 -7
  65. data/lib/puma/queue_close.rb +0 -26
  66. data/lib/puma/systemd.rb +0 -46
  67. data/lib/rack/version_restriction.rb +0 -15
@@ -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);
@@ -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) {
@@ -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 http_version(Ruby runtime, RubyHash req, ByteList buffer, int at, int length) {
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, HTTP_VERSION_BYTELIST),val);
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.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
  }