puma 5.6.4 → 6.0.0

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 (57) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +136 -3
  3. data/README.md +21 -17
  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/testing_benchmarks_local_files.md +150 -0
  8. data/docs/testing_test_rackup_ci_files.md +36 -0
  9. data/ext/puma_http11/extconf.rb +18 -10
  10. data/ext/puma_http11/http11_parser.c +1 -1
  11. data/ext/puma_http11/http11_parser.h +1 -1
  12. data/ext/puma_http11/http11_parser.java.rl +2 -2
  13. data/ext/puma_http11/http11_parser.rl +2 -2
  14. data/ext/puma_http11/http11_parser_common.rl +2 -2
  15. data/ext/puma_http11/mini_ssl.c +63 -24
  16. data/ext/puma_http11/org/jruby/puma/Http11.java +3 -3
  17. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +1 -1
  18. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +166 -65
  19. data/ext/puma_http11/puma_http11.c +17 -9
  20. data/lib/puma/app/status.rb +6 -3
  21. data/lib/puma/binder.rb +37 -43
  22. data/lib/puma/cli.rb +11 -17
  23. data/lib/puma/client.rb +22 -12
  24. data/lib/puma/cluster/worker.rb +13 -11
  25. data/lib/puma/cluster/worker_handle.rb +4 -1
  26. data/lib/puma/cluster.rb +28 -25
  27. data/lib/puma/configuration.rb +74 -58
  28. data/lib/puma/const.rb +14 -18
  29. data/lib/puma/control_cli.rb +21 -18
  30. data/lib/puma/detect.rb +2 -0
  31. data/lib/puma/dsl.rb +94 -49
  32. data/lib/puma/error_logger.rb +17 -9
  33. data/lib/puma/events.rb +6 -126
  34. data/lib/puma/io_buffer.rb +29 -4
  35. data/lib/puma/jruby_restart.rb +2 -1
  36. data/lib/puma/launcher/bundle_pruner.rb +104 -0
  37. data/lib/puma/launcher.rb +107 -156
  38. data/lib/puma/log_writer.rb +137 -0
  39. data/lib/puma/minissl/context_builder.rb +23 -12
  40. data/lib/puma/minissl.rb +91 -15
  41. data/lib/puma/null_io.rb +5 -0
  42. data/lib/puma/plugin/tmp_restart.rb +1 -1
  43. data/lib/puma/rack/builder.rb +4 -4
  44. data/lib/puma/rack_default.rb +1 -1
  45. data/lib/puma/reactor.rb +3 -3
  46. data/lib/puma/request.rb +291 -156
  47. data/lib/puma/runner.rb +41 -20
  48. data/lib/puma/server.rb +53 -64
  49. data/lib/puma/single.rb +10 -10
  50. data/lib/puma/state_file.rb +2 -4
  51. data/lib/puma/systemd.rb +3 -2
  52. data/lib/puma/thread_pool.rb +16 -13
  53. data/lib/puma/util.rb +12 -14
  54. data/lib/puma.rb +11 -8
  55. data/lib/rack/handler/puma.rb +9 -9
  56. metadata +7 -3
  57. data/lib/puma/queue_close.rb +0 -26
@@ -62,8 +62,8 @@ static void snake_upcase_char(char *c)
62
62
  parser->query_string(parser, PTR_TO(query_start), LEN(query_start, fpc));
63
63
  }
64
64
 
65
- action http_version {
66
- parser->http_version(parser, PTR_TO(mark), LEN(mark, fpc));
65
+ action server_protocol {
66
+ parser->server_protocol(parser, PTR_TO(mark), LEN(mark, fpc));
67
67
  }
68
68
 
69
69
  action request_path {
@@ -38,8 +38,8 @@
38
38
  Method = ( upper | digit | safe ){1,20} >mark %request_method;
39
39
 
40
40
  http_number = ( digit+ "." digit+ ) ;
41
- HTTP_Version = ( "HTTP/" http_number ) >mark %http_version ;
42
- Request_Line = ( Method " " Request_URI ("#" Fragment){0,1} " " HTTP_Version CRLF ) ;
41
+ Server_Protocol = ( "HTTP/" http_number ) >mark %server_protocol ;
42
+ Request_Line = ( Method " " Request_URI ("#" Fragment){0,1} " " Server_Protocol CRLF ) ;
43
43
 
44
44
  field_name = ( token -- ":" )+ >start_field $snake_upcase_field %write_field;
45
45
 
@@ -30,6 +30,12 @@ typedef struct {
30
30
 
31
31
  VALUE eError;
32
32
 
33
+ NORETURN(void raise_file_error(const char* caller, const char *filename));
34
+
35
+ void raise_file_error(const char* caller, const char *filename) {
36
+ rb_raise(eError, "%s: error in file '%s': %s", caller, filename, ERR_error_string(ERR_get_error(), NULL));
37
+ }
38
+
33
39
  void engine_free(void *ptr) {
34
40
  ms_conn *conn = ptr;
35
41
  ms_cert_buf* cert_buf = (ms_cert_buf*)SSL_get_app_data(conn->ssl);
@@ -49,7 +55,7 @@ const rb_data_type_t engine_data_type = {
49
55
  0, 0, RUBY_TYPED_FREE_IMMEDIATELY,
50
56
  };
51
57
 
52
- #ifndef HAVE_SSL_GET1_PEER_CERTIFICATE
58
+ #ifndef HAVE_SSL_CTX_SET_DH_AUTO
53
59
  DH *get_dh2048(void) {
54
60
  /* `openssl dhparam -C 2048`
55
61
  * -----BEGIN DH PARAMETERS-----
@@ -92,13 +98,13 @@ DH *get_dh2048(void) {
92
98
  static unsigned char dh2048_g[] = { 0x02 };
93
99
 
94
100
  DH *dh;
95
- #if !(OPENSSL_VERSION_NUMBER < 0x10100005L || defined(LIBRESSL_VERSION_NUMBER))
101
+ #if !(OPENSSL_VERSION_NUMBER < 0x10100005L)
96
102
  BIGNUM *p, *g;
97
103
  #endif
98
104
 
99
105
  dh = DH_new();
100
106
 
101
- #if OPENSSL_VERSION_NUMBER < 0x10100005L || defined(LIBRESSL_VERSION_NUMBER)
107
+ #if OPENSSL_VERSION_NUMBER < 0x10100005L
102
108
  dh->p = BN_bin2bn(dh2048_p, sizeof(dh2048_p), NULL);
103
109
  dh->g = BN_bin2bn(dh2048_g, sizeof(dh2048_g), NULL);
104
110
 
@@ -204,25 +210,28 @@ sslctx_alloc(VALUE klass) {
204
210
  VALUE
205
211
  sslctx_initialize(VALUE self, VALUE mini_ssl_ctx) {
206
212
  SSL_CTX* ctx;
207
-
208
- #ifdef HAVE_SSL_CTX_SET_MIN_PROTO_VERSION
209
- int min;
210
- #endif
211
213
  int ssl_options;
212
214
  VALUE key, cert, ca, verify_mode, ssl_cipher_filter, no_tlsv1, no_tlsv1_1,
213
215
  verification_flags, session_id_bytes, cert_pem, key_pem;
214
- #ifndef HAVE_SSL_GET1_PEER_CERTIFICATE
215
- DH *dh;
216
- #endif
217
216
  BIO *bio;
218
217
  X509 *x509;
219
218
  EVP_PKEY *pkey;
220
-
219
+ #ifdef HAVE_SSL_CTX_SET_MIN_PROTO_VERSION
220
+ int min;
221
+ #endif
222
+ #ifndef HAVE_SSL_CTX_SET_DH_AUTO
223
+ DH *dh;
224
+ #endif
221
225
  #if OPENSSL_VERSION_NUMBER < 0x10002000L
222
226
  EC_KEY *ecdh;
223
227
  #endif
228
+ #ifdef HAVE_SSL_CTX_SET_SESSION_CACHE_MODE
229
+ VALUE reuse, reuse_cache_size, reuse_timeout;
224
230
 
225
- TypedData_Get_Struct(self, SSL_CTX, &sslctx_type, ctx);
231
+ reuse = rb_funcall(mini_ssl_ctx, rb_intern_const("reuse"), 0);
232
+ reuse_cache_size = rb_funcall(mini_ssl_ctx, rb_intern_const("reuse_cache_size"), 0);
233
+ reuse_timeout = rb_funcall(mini_ssl_ctx, rb_intern_const("reuse_timeout"), 0);
234
+ #endif
226
235
 
227
236
  key = rb_funcall(mini_ssl_ctx, rb_intern_const("key"), 0);
228
237
 
@@ -242,14 +251,22 @@ sslctx_initialize(VALUE self, VALUE mini_ssl_ctx) {
242
251
 
243
252
  no_tlsv1_1 = rb_funcall(mini_ssl_ctx, rb_intern_const("no_tlsv1_1"), 0);
244
253
 
254
+ TypedData_Get_Struct(self, SSL_CTX, &sslctx_type, ctx);
255
+
245
256
  if (!NIL_P(cert)) {
246
257
  StringValue(cert);
247
- SSL_CTX_use_certificate_chain_file(ctx, RSTRING_PTR(cert));
258
+
259
+ if (SSL_CTX_use_certificate_chain_file(ctx, RSTRING_PTR(cert)) != 1) {
260
+ raise_file_error("SSL_CTX_use_certificate_chain_file", RSTRING_PTR(cert));
261
+ }
248
262
  }
249
263
 
250
264
  if (!NIL_P(key)) {
251
265
  StringValue(key);
252
- SSL_CTX_use_PrivateKey_file(ctx, RSTRING_PTR(key), SSL_FILETYPE_PEM);
266
+
267
+ if (SSL_CTX_use_PrivateKey_file(ctx, RSTRING_PTR(key), SSL_FILETYPE_PEM) != 1) {
268
+ raise_file_error("SSL_CTX_use_PrivateKey_file", RSTRING_PTR(key));
269
+ }
253
270
  }
254
271
 
255
272
  if (!NIL_P(cert_pem)) {
@@ -257,7 +274,12 @@ sslctx_initialize(VALUE self, VALUE mini_ssl_ctx) {
257
274
  BIO_puts(bio, RSTRING_PTR(cert_pem));
258
275
  x509 = PEM_read_bio_X509(bio, NULL, NULL, NULL);
259
276
 
260
- SSL_CTX_use_certificate(ctx, x509);
277
+ if (SSL_CTX_use_certificate(ctx, x509) != 1) {
278
+ BIO_free(bio);
279
+ raise_file_error("SSL_CTX_use_certificate", RSTRING_PTR(cert_pem));
280
+ }
281
+ X509_free(x509);
282
+ BIO_free(bio);
261
283
  }
262
284
 
263
285
  if (!NIL_P(key_pem)) {
@@ -265,7 +287,12 @@ sslctx_initialize(VALUE self, VALUE mini_ssl_ctx) {
265
287
  BIO_puts(bio, RSTRING_PTR(key_pem));
266
288
  pkey = PEM_read_bio_PrivateKey(bio, NULL, NULL, NULL);
267
289
 
268
- SSL_CTX_use_PrivateKey(ctx, pkey);
290
+ if (SSL_CTX_use_PrivateKey(ctx, pkey) != 1) {
291
+ BIO_free(bio);
292
+ raise_file_error("SSL_CTX_use_PrivateKey", RSTRING_PTR(key_pem));
293
+ }
294
+ EVP_PKEY_free(pkey);
295
+ BIO_free(bio);
269
296
  }
270
297
 
271
298
  verification_flags = rb_funcall(mini_ssl_ctx, rb_intern_const("verification_flags"), 0);
@@ -278,7 +305,9 @@ sslctx_initialize(VALUE self, VALUE mini_ssl_ctx) {
278
305
 
279
306
  if (!NIL_P(ca)) {
280
307
  StringValue(ca);
281
- SSL_CTX_load_verify_locations(ctx, RSTRING_PTR(ca), NULL);
308
+ if (SSL_CTX_load_verify_locations(ctx, RSTRING_PTR(ca), NULL) != 1) {
309
+ raise_file_error("SSL_CTX_load_verify_locations", RSTRING_PTR(ca));
310
+ }
282
311
  }
283
312
 
284
313
  ssl_options = SSL_OP_CIPHER_SERVER_PREFERENCE | SSL_OP_SINGLE_ECDH_USE | SSL_OP_NO_COMPRESSION;
@@ -296,8 +325,6 @@ sslctx_initialize(VALUE self, VALUE mini_ssl_ctx) {
296
325
 
297
326
  SSL_CTX_set_min_proto_version(ctx, min);
298
327
 
299
- SSL_CTX_set_options(ctx, ssl_options);
300
-
301
328
  #else
302
329
  /* As of 1.0.2f, SSL_OP_SINGLE_DH_USE key use is always on */
303
330
  ssl_options |= SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_SINGLE_DH_USE;
@@ -308,10 +335,23 @@ sslctx_initialize(VALUE self, VALUE mini_ssl_ctx) {
308
335
  if(RTEST(no_tlsv1_1)) {
309
336
  ssl_options |= SSL_OP_NO_TLSv1 | SSL_OP_NO_TLSv1_1;
310
337
  }
311
- SSL_CTX_set_options(ctx, ssl_options);
312
338
  #endif
313
339
 
314
- SSL_CTX_set_session_cache_mode(ctx, SSL_SESS_CACHE_OFF);
340
+ #ifdef HAVE_SSL_CTX_SET_SESSION_CACHE_MODE
341
+ if (!NIL_P(reuse)) {
342
+ SSL_CTX_set_session_cache_mode(ctx, SSL_SESS_CACHE_SERVER);
343
+ if (!NIL_P(reuse_cache_size)) {
344
+ SSL_CTX_sess_set_cache_size(ctx, NUM2INT(reuse_cache_size));
345
+ }
346
+ if (!NIL_P(reuse_timeout)) {
347
+ SSL_CTX_set_timeout(ctx, NUM2INT(reuse_timeout));
348
+ }
349
+ } else {
350
+ SSL_CTX_set_session_cache_mode(ctx, SSL_SESS_CACHE_OFF);
351
+ }
352
+ #endif
353
+
354
+ SSL_CTX_set_options(ctx, ssl_options);
315
355
 
316
356
  if (!NIL_P(ssl_cipher_filter)) {
317
357
  StringValue(ssl_cipher_filter);
@@ -322,8 +362,7 @@ sslctx_initialize(VALUE self, VALUE mini_ssl_ctx) {
322
362
  }
323
363
 
324
364
  #if OPENSSL_VERSION_NUMBER < 0x10002000L
325
- // Remove this case if OpenSSL 1.0.1 (now EOL) support is no
326
- // longer needed.
365
+ // Remove this case if OpenSSL 1.0.1 (now EOL) support is no longer needed.
327
366
  ecdh = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1);
328
367
  if (ecdh) {
329
368
  SSL_CTX_set_tmp_ecdh(ctx, ecdh);
@@ -355,7 +394,7 @@ sslctx_initialize(VALUE self, VALUE mini_ssl_ctx) {
355
394
 
356
395
  // printf("\ninitialize end security_level %d\n", SSL_CTX_get_security_level(ctx));
357
396
 
358
- #ifdef HAVE_SSL_GET1_PEER_CERTIFICATE
397
+ #ifdef HAVE_SSL_CTX_SET_DH_AUTO
359
398
  // https://www.openssl.org/docs/man3.0/man3/SSL_CTX_set_dh_auto.html
360
399
  SSL_CTX_set_dh_auto(ctx, 1);
361
400
  #else
@@ -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,18 @@ 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
44
50
  private static ObjectAllocator ALLOCATOR = new ObjectAllocator() {
45
51
  public IRubyObject allocate(Ruby runtime, RubyClass klass) {
46
52
  return new MiniSSL(runtime, klass);
@@ -51,11 +57,10 @@ public class MiniSSL extends RubyObject {
51
57
  RubyModule mPuma = runtime.defineModule("Puma");
52
58
  RubyModule ssl = mPuma.defineModuleUnder("MiniSSL");
53
59
 
54
- mPuma.defineClassUnder("SSLError",
55
- runtime.getClass("IOError"),
56
- runtime.getClass("IOError").getAllocator());
60
+ // Puma::MiniSSL::SSLError
61
+ ssl.defineClassUnder("SSLError", runtime.getStandardError(), runtime.getStandardError().getAllocator());
57
62
 
58
- RubyClass eng = ssl.defineClassUnder("Engine",runtime.getObject(),ALLOCATOR);
63
+ RubyClass eng = ssl.defineClassUnder("Engine", runtime.getObject(), ALLOCATOR);
59
64
  eng.defineAnnotatedMethods(MiniSSL.class);
60
65
  }
61
66
 
@@ -137,74 +142,116 @@ public class MiniSSL extends RubyObject {
137
142
  private static Map<String, KeyManagerFactory> keyManagerFactoryMap = new ConcurrentHashMap<String, KeyManagerFactory>();
138
143
  private static Map<String, TrustManagerFactory> trustManagerFactoryMap = new ConcurrentHashMap<String, TrustManagerFactory>();
139
144
 
140
- @JRubyMethod(meta = true)
145
+ @JRubyMethod(meta = true) // Engine.server
141
146
  public static synchronized IRubyObject server(ThreadContext context, IRubyObject recv, IRubyObject miniSSLContext)
142
147
  throws KeyStoreException, IOException, CertificateException, NoSuchAlgorithmException, UnrecoverableKeyException {
143
148
  // 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();
149
+ String keystoreFile = asStringValue(miniSSLContext.callMethod(context, "keystore"), null);
150
+ char[] keystorePass = asStringValue(miniSSLContext.callMethod(context, "keystore_pass"), null).toCharArray();
151
+ String keystoreType = asStringValue(miniSSLContext.callMethod(context, "keystore_type"), KeyStore::getDefaultType);
152
+
153
+ String truststoreFile;
154
+ char[] truststorePass;
155
+ String truststoreType;
156
+ IRubyObject truststore = miniSSLContext.callMethod(context, "truststore");
157
+ if (truststore.isNil()) {
158
+ truststoreFile = keystoreFile;
159
+ truststorePass = keystorePass;
160
+ truststoreType = keystoreType;
161
+ } else if (!isDefaultSymbol(context, truststore)) {
162
+ truststoreFile = truststore.convertToString().asJavaString();
163
+ IRubyObject pass = miniSSLContext.callMethod(context, "truststore_pass");
164
+ if (pass.isNil()) {
165
+ truststorePass = null;
166
+ } else {
167
+ truststorePass = asStringValue(pass, null).toCharArray();
168
+ }
169
+ truststoreType = asStringValue(miniSSLContext.callMethod(context, "truststore_type"), KeyStore::getDefaultType);
170
+ } else { // self.truststore = :default
171
+ truststoreFile = null;
172
+ truststorePass = null;
173
+ truststoreType = null;
174
+ }
146
175
 
147
- KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
176
+ KeyStore ks = KeyStore.getInstance(keystoreType);
148
177
  InputStream is = new FileInputStream(keystoreFile);
149
178
  try {
150
- ks.load(is, password);
179
+ ks.load(is, keystorePass);
151
180
  } finally {
152
181
  is.close();
153
182
  }
154
183
  KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
155
- kmf.init(ks, password);
184
+ kmf.init(ks, keystorePass);
156
185
  keyManagerFactoryMap.put(keystoreFile, kmf);
157
186
 
158
- KeyStore ts = KeyStore.getInstance(KeyStore.getDefaultType());
159
- is = new FileInputStream(keystoreFile);
160
- try {
161
- ts.load(is, password);
162
- } finally {
163
- is.close();
187
+ if (truststoreFile != null) {
188
+ KeyStore ts = KeyStore.getInstance(truststoreType);
189
+ is = new FileInputStream(truststoreFile);
190
+ try {
191
+ ts.load(is, truststorePass);
192
+ } finally {
193
+ is.close();
194
+ }
195
+ TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
196
+ tmf.init(ts);
197
+ trustManagerFactoryMap.put(truststoreFile, tmf);
164
198
  }
165
- TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
166
- tmf.init(ts);
167
- trustManagerFactoryMap.put(keystoreFile, tmf);
168
199
 
169
200
  RubyClass klass = (RubyClass) recv;
170
- return klass.newInstance(context,
171
- new IRubyObject[] { miniSSLContext },
172
- Block.NULL_BLOCK);
201
+ return klass.newInstance(context, miniSSLContext, Block.NULL_BLOCK);
202
+ }
203
+
204
+ private static String asStringValue(IRubyObject value, Supplier<String> defaultValue) {
205
+ if (defaultValue != null && value.isNil()) return defaultValue.get();
206
+ return value.convertToString().asJavaString();
207
+ }
208
+
209
+ private static boolean isDefaultSymbol(ThreadContext context, IRubyObject truststore) {
210
+ return context.runtime.newSymbol("default").equals(truststore);
173
211
  }
174
212
 
175
213
  @JRubyMethod
176
- public IRubyObject initialize(ThreadContext threadContext, IRubyObject miniSSLContext)
214
+ public IRubyObject initialize(ThreadContext context, IRubyObject miniSSLContext)
177
215
  throws KeyStoreException, NoSuchAlgorithmException, KeyManagementException {
178
216
 
179
- String keystoreFile = miniSSLContext.callMethod(threadContext, "keystore").convertToString().asJavaString();
217
+ String keystoreFile = miniSSLContext.callMethod(context, "keystore").convertToString().asJavaString();
180
218
  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);
219
+ IRubyObject truststore = miniSSLContext.callMethod(context, "truststore");
220
+ String truststoreFile = isDefaultSymbol(context, truststore) ? "" : asStringValue(truststore, () -> keystoreFile);
221
+ TrustManagerFactory tmf = trustManagerFactoryMap.get(truststoreFile); // null if self.truststore = :default
222
+ if (kmf == null) {
223
+ throw new KeyStoreException("Could not find KeyManagerFactory for keystore: " + keystoreFile + " truststore: " + truststoreFile);
184
224
  }
185
225
 
186
226
  SSLContext sslCtx = SSLContext.getInstance("TLS");
187
227
 
188
- sslCtx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
228
+ sslCtx.init(kmf.getKeyManagers(), getTrustManagers(tmf), null);
189
229
  closed = false;
190
230
  handshake = false;
191
231
  engine = sslCtx.createSSLEngine();
192
232
 
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
- }
233
+ String[] enabledProtocols;
234
+ IRubyObject protocols = miniSSLContext.callMethod(context, "protocols");
235
+ if (protocols.isNil()) {
236
+ if (miniSSLContext.callMethod(context, "no_tlsv1").isTrue()) {
237
+ enabledProtocols = new String[] { "TLSv1.1", "TLSv1.2", "TLSv1.3" };
238
+ } else {
239
+ enabledProtocols = new String[] { "TLSv1", "TLSv1.1", "TLSv1.2", "TLSv1.3" };
240
+ }
199
241
 
200
- if(miniSSLContext.callMethod(threadContext, "no_tlsv1_1").isTrue()) {
201
- protocols = new String[] { "TLSv1.2" };
242
+ if (miniSSLContext.callMethod(context, "no_tlsv1_1").isTrue()) {
243
+ enabledProtocols = new String[] { "TLSv1.2", "TLSv1.3" };
244
+ }
245
+ } else if (protocols instanceof RubyArray) {
246
+ enabledProtocols = (String[]) ((RubyArray) protocols).toArray(new String[0]);
247
+ } else {
248
+ throw context.runtime.newTypeError(protocols, context.runtime.getArray());
202
249
  }
250
+ engine.setEnabledProtocols(enabledProtocols);
203
251
 
204
- engine.setEnabledProtocols(protocols);
205
252
  engine.setUseClientMode(false);
206
253
 
207
- long verify_mode = miniSSLContext.callMethod(threadContext, "verify_mode").convertToInteger("to_i").getLongValue();
254
+ long verify_mode = miniSSLContext.callMethod(context, "verify_mode").convertToInteger("to_i").getLongValue();
208
255
  if ((verify_mode & 0x1) != 0) { // 'peer'
209
256
  engine.setWantClientAuth(true);
210
257
  }
@@ -212,10 +259,11 @@ public class MiniSSL extends RubyObject {
212
259
  engine.setNeedClientAuth(true);
213
260
  }
214
261
 
215
- IRubyObject sslCipherListObject = miniSSLContext.callMethod(threadContext, "ssl_cipher_list");
216
- if (!sslCipherListObject.isNil()) {
217
- String[] sslCipherList = sslCipherListObject.convertToString().asJavaString().split(",");
218
- engine.setEnabledCipherSuites(sslCipherList);
262
+ IRubyObject cipher_suites = miniSSLContext.callMethod(context, "cipher_suites");
263
+ if (cipher_suites instanceof RubyArray) {
264
+ engine.setEnabledCipherSuites((String[]) ((RubyArray) cipher_suites).toArray(new String[0]));
265
+ } else if (!cipher_suites.isNil()) {
266
+ throw context.runtime.newTypeError(cipher_suites, context.runtime.getArray());
219
267
  }
220
268
 
221
269
  SSLSession session = engine.getSession();
@@ -227,6 +275,48 @@ public class MiniSSL extends RubyObject {
227
275
  return this;
228
276
  }
229
277
 
278
+ private TrustManager[] getTrustManagers(TrustManagerFactory factory) {
279
+ if (factory == null) return null; // use JDK trust defaults
280
+ final TrustManager[] tms = factory.getTrustManagers();
281
+ if (tms != null) {
282
+ for (int i=0; i<tms.length; i++) {
283
+ final TrustManager tm = tms[i];
284
+ if (tm instanceof X509TrustManager) {
285
+ tms[i] = new TrustManagerWrapper((X509TrustManager) tm);
286
+ }
287
+ }
288
+ }
289
+ return tms;
290
+ }
291
+
292
+ private volatile transient X509Certificate lastCheckedCert0;
293
+
294
+ private class TrustManagerWrapper implements X509TrustManager {
295
+
296
+ private final X509TrustManager delegate;
297
+
298
+ TrustManagerWrapper(X509TrustManager delegate) {
299
+ this.delegate = delegate;
300
+ }
301
+
302
+ @Override
303
+ public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
304
+ lastCheckedCert0 = chain.length > 0 ? chain[0] : null;
305
+ delegate.checkClientTrusted(chain, authType);
306
+ }
307
+
308
+ @Override
309
+ public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
310
+ delegate.checkServerTrusted(chain, authType);
311
+ }
312
+
313
+ @Override
314
+ public X509Certificate[] getAcceptedIssuers() {
315
+ return delegate.getAcceptedIssuers();
316
+ }
317
+
318
+ }
319
+
230
320
  @JRubyMethod
231
321
  public IRubyObject inject(IRubyObject arg) {
232
322
  ByteList bytes = arg.convertToString().getByteList();
@@ -251,7 +341,7 @@ public class MiniSSL extends RubyObject {
251
341
  res = engine.unwrap(src.getRawBuffer(), dst.getRawBuffer());
252
342
  break;
253
343
  default:
254
- throw new IllegalStateException("Unknown SSLOperation: " + sslOp);
344
+ throw new AssertionError("Unknown SSLOperation: " + sslOp);
255
345
  }
256
346
 
257
347
  switch (res.getStatus()) {
@@ -279,14 +369,6 @@ public class MiniSSL extends RubyObject {
279
369
  }
280
370
  }
281
371
 
282
- // after each op, run any delegated tasks if needed
283
- if(res.getHandshakeStatus() == HandshakeStatus.NEED_TASK) {
284
- Runnable runnable;
285
- while ((runnable = engine.getDelegatedTask()) != null) {
286
- runnable.run();
287
- }
288
- }
289
-
290
372
  return res;
291
373
  }
292
374
 
@@ -304,11 +386,12 @@ public class MiniSSL extends RubyObject {
304
386
 
305
387
  HandshakeStatus handshakeStatus = engine.getHandshakeStatus();
306
388
  boolean done = false;
307
- SSLEngineResult res = null;
308
389
  while (!done) {
390
+ SSLEngineResult res;
309
391
  switch (handshakeStatus) {
310
392
  case NEED_WRAP:
311
393
  res = doOp(SSLOperation.WRAP, inboundAppData, outboundNetData);
394
+ handshakeStatus = res.getHandshakeStatus();
312
395
  break;
313
396
  case NEED_UNWRAP:
314
397
  res = doOp(SSLOperation.UNWRAP, inboundNetData, inboundAppData);
@@ -316,13 +399,18 @@ public class MiniSSL extends RubyObject {
316
399
  // need more data before we can shake more hands
317
400
  done = true;
318
401
  }
402
+ handshakeStatus = res.getHandshakeStatus();
403
+ break;
404
+ case NEED_TASK:
405
+ Runnable runnable;
406
+ while ((runnable = engine.getDelegatedTask()) != null) {
407
+ runnable.run();
408
+ }
409
+ handshakeStatus = engine.getHandshakeStatus();
319
410
  break;
320
411
  default:
321
412
  done = true;
322
413
  }
323
- if (!done) {
324
- handshakeStatus = res.getHandshakeStatus();
325
- }
326
414
  }
327
415
 
328
416
  if (inboundNetData.hasRemaining()) {
@@ -338,9 +426,7 @@ public class MiniSSL extends RubyObject {
338
426
 
339
427
  return RubyString.newString(getRuntime(), appDataByteList);
340
428
  } catch (SSLException e) {
341
- RaiseException re = getRuntime().newEOFError(e.getMessage());
342
- re.initCause(e);
343
- throw re;
429
+ throw newSSLError(getRuntime(), e);
344
430
  }
345
431
  }
346
432
 
@@ -373,19 +459,19 @@ public class MiniSSL extends RubyObject {
373
459
 
374
460
  return RubyString.newString(context.runtime, dataByteList);
375
461
  } catch (SSLException e) {
376
- RaiseException ex = context.runtime.newRuntimeError(e.toString());
377
- ex.initCause(e);
378
- throw ex;
462
+ throw newSSLError(getRuntime(), e);
379
463
  }
380
464
  }
381
465
 
382
466
  @JRubyMethod
383
- public IRubyObject peercert() throws CertificateEncodingException {
467
+ public IRubyObject peercert(ThreadContext context) throws CertificateEncodingException {
468
+ Certificate peerCert;
384
469
  try {
385
- return JavaEmbedUtils.javaToRuby(getRuntime(), engine.getSession().getPeerCertificates()[0].getEncoded());
470
+ peerCert = engine.getSession().getPeerCertificates()[0];
386
471
  } catch (SSLPeerUnverifiedException e) {
387
- return getRuntime().getNil();
472
+ peerCert = lastCheckedCert0; // null if trust check did not happen
388
473
  }
474
+ return peerCert == null ? context.nil : JavaEmbedUtils.javaToRuby(context.runtime, peerCert.getEncoded());
389
475
  }
390
476
 
391
477
  @JRubyMethod(name = "init?")
@@ -404,4 +490,19 @@ public class MiniSSL extends RubyObject {
404
490
  return getRuntime().getFalse();
405
491
  }
406
492
  }
493
+
494
+ private static RubyClass getSSLError(Ruby runtime) {
495
+ return (RubyClass) ((RubyModule) runtime.getModule("Puma").getConstantAt("MiniSSL")).getConstantAt("SSLError");
496
+ }
497
+
498
+ private static RaiseException newSSLError(Ruby runtime, SSLException cause) {
499
+ return newError(runtime, getSSLError(runtime), cause.toString(), cause);
500
+ }
501
+
502
+ private static RaiseException newError(Ruby runtime, RubyClass errorClass, String message, Throwable cause) {
503
+ RaiseException ex = new RaiseException(runtime, errorClass, message, true);
504
+ ex.initCause(cause);
505
+ return ex;
506
+ }
507
+
407
508
  }