puma 2.2.0 → 8.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/History.md +3334 -0
- data/LICENSE +23 -20
- data/README.md +387 -100
- data/bin/puma-wild +25 -0
- data/bin/pumactl +1 -1
- data/docs/5.0-Upgrade.md +98 -0
- data/docs/6.0-Upgrade.md +56 -0
- data/docs/7.0-Upgrade.md +52 -0
- data/docs/8.0-Upgrade.md +100 -0
- data/docs/architecture.md +74 -0
- data/docs/compile_options.md +55 -0
- data/docs/deployment.md +137 -0
- data/docs/fork_worker.md +41 -0
- data/docs/grpc.md +62 -0
- data/docs/images/favicon.svg +1 -0
- 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/images/running-puma.svg +1 -0
- data/docs/images/standard-logo.svg +1 -0
- data/docs/java_options.md +54 -0
- data/docs/jungle/README.md +9 -0
- data/docs/jungle/rc.d/README.md +74 -0
- data/docs/jungle/rc.d/puma +61 -0
- data/docs/jungle/rc.d/puma.conf +10 -0
- data/docs/kubernetes.md +73 -0
- data/docs/nginx.md +5 -5
- data/docs/plugins.md +42 -0
- data/docs/rails_dev_mode.md +28 -0
- data/docs/restart.md +65 -0
- data/docs/signals.md +98 -0
- data/docs/stats.md +148 -0
- data/docs/systemd.md +253 -0
- 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 +2 -2
- data/ext/puma_http11/extconf.rb +59 -2
- data/ext/puma_http11/http11_parser.c +319 -487
- data/ext/puma_http11/http11_parser.h +15 -13
- data/ext/puma_http11/http11_parser.java.rl +64 -94
- data/ext/puma_http11/http11_parser.rl +27 -24
- data/ext/puma_http11/http11_parser_common.rl +8 -8
- data/ext/puma_http11/mini_ssl.c +696 -38
- data/ext/puma_http11/no_ssl/PumaHttp11Service.java +15 -0
- data/ext/puma_http11/org/jruby/puma/EnvKey.java +241 -0
- data/ext/puma_http11/org/jruby/puma/Http11.java +241 -145
- data/ext/puma_http11/org/jruby/puma/Http11Parser.java +174 -221
- data/ext/puma_http11/org/jruby/puma/MiniSSL.java +413 -193
- data/ext/puma_http11/puma_http11.c +183 -175
- data/lib/puma/app/status.rb +77 -29
- data/lib/puma/binder.rb +349 -119
- data/lib/puma/cli.rb +163 -801
- data/lib/puma/client.rb +627 -144
- data/lib/puma/client_env.rb +171 -0
- data/lib/puma/cluster/worker.rb +183 -0
- data/lib/puma/cluster/worker_handle.rb +127 -0
- data/lib/puma/cluster.rb +634 -0
- data/lib/puma/cluster_accept_loop_delay.rb +91 -0
- data/lib/puma/commonlogger.rb +115 -0
- data/lib/puma/configuration.rb +420 -198
- data/lib/puma/const.rb +266 -115
- data/lib/puma/control_cli.rb +219 -120
- data/lib/puma/detect.rb +56 -2
- data/lib/puma/dsl.rb +1562 -0
- data/lib/puma/error_logger.rb +115 -0
- data/lib/puma/events.rb +44 -60
- data/lib/puma/io_buffer.rb +48 -5
- data/lib/puma/jruby_restart.rb +2 -51
- data/lib/puma/json_serialization.rb +96 -0
- data/lib/puma/launcher/bundle_pruner.rb +102 -0
- data/lib/puma/launcher.rb +501 -0
- data/lib/puma/log_writer.rb +153 -0
- data/lib/puma/minissl/context_builder.rb +96 -0
- data/lib/puma/minissl.rb +355 -37
- data/lib/puma/null_io.rb +79 -12
- data/lib/puma/plugin/systemd.rb +90 -0
- data/lib/puma/plugin/tmp_restart.rb +36 -0
- data/lib/puma/plugin.rb +111 -0
- data/lib/puma/rack/builder.rb +297 -0
- data/lib/puma/rack/urlmap.rb +93 -0
- data/lib/puma/rack_default.rb +21 -4
- data/lib/puma/reactor.rb +102 -133
- data/lib/puma/response.rb +532 -0
- data/lib/puma/runner.rb +211 -0
- data/lib/puma/sd_notify.rb +146 -0
- data/lib/puma/server.rb +582 -445
- data/lib/puma/server_plugin_control.rb +32 -0
- data/lib/puma/single.rb +72 -0
- data/lib/puma/state_file.rb +69 -0
- data/lib/puma/thread_pool.rb +389 -57
- data/lib/puma/util.rb +125 -0
- data/lib/puma.rb +80 -6
- data/lib/rack/handler/puma.rb +125 -47
- data/tools/Dockerfile +26 -0
- data/tools/trickletest.rb +44 -0
- metadata +88 -148
- data/COPYING +0 -55
- data/Gemfile +0 -10
- data/History.txt +0 -302
- data/Manifest.txt +0 -60
- data/Rakefile +0 -165
- data/TODO +0 -5
- data/docs/config.md +0 -0
- data/ext/puma_http11/ext_help.h +0 -15
- data/ext/puma_http11/io_buffer.c +0 -154
- data/lib/puma/accept_nonblock.rb +0 -23
- data/lib/puma/capistrano.rb +0 -34
- data/lib/puma/compat.rb +0 -11
- data/lib/puma/daemon_ext.rb +0 -20
- data/lib/puma/delegation.rb +0 -11
- data/lib/puma/java_io_buffer.rb +0 -45
- data/lib/puma/rack_patch.rb +0 -25
- data/puma.gemspec +0 -45
- data/test/test_app_status.rb +0 -88
- data/test/test_cli.rb +0 -171
- data/test/test_config.rb +0 -16
- data/test/test_http10.rb +0 -27
- data/test/test_http11.rb +0 -126
- data/test/test_integration.rb +0 -154
- data/test/test_iobuffer.rb +0 -38
- data/test/test_minissl.rb +0 -25
- data/test/test_null_io.rb +0 -31
- data/test/test_persistent.rb +0 -238
- data/test/test_puma_server.rb +0 -224
- data/test/test_rack_handler.rb +0 -10
- data/test/test_rack_server.rb +0 -141
- data/test/test_thread_pool.rb +0 -146
- data/test/test_unix_socket.rb +0 -39
- data/test/test_ws.rb +0 -89
- data/tools/jungle/init.d/README.md +0 -54
- data/tools/jungle/init.d/puma +0 -332
- data/tools/jungle/init.d/run-puma +0 -3
- data/tools/jungle/upstart/README.md +0 -61
- data/tools/jungle/upstart/puma-manager.conf +0 -31
- data/tools/jungle/upstart/puma.conf +0 -52
|
@@ -1,32 +1,53 @@
|
|
|
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
|
-
import org.jruby.RubyHash;
|
|
6
6
|
import org.jruby.RubyModule;
|
|
7
|
-
import org.jruby.RubyNumeric;
|
|
8
7
|
import org.jruby.RubyObject;
|
|
9
8
|
import org.jruby.RubyString;
|
|
10
|
-
|
|
11
9
|
import org.jruby.anno.JRubyMethod;
|
|
12
|
-
|
|
10
|
+
import org.jruby.exceptions.RaiseException;
|
|
11
|
+
import org.jruby.javasupport.JavaEmbedUtils;
|
|
13
12
|
import org.jruby.runtime.Block;
|
|
14
13
|
import org.jruby.runtime.ObjectAllocator;
|
|
15
14
|
import org.jruby.runtime.ThreadContext;
|
|
16
15
|
import org.jruby.runtime.builtin.IRubyObject;
|
|
17
|
-
|
|
18
|
-
import org.jruby.exceptions.RaiseException;
|
|
19
|
-
|
|
20
16
|
import org.jruby.util.ByteList;
|
|
21
17
|
|
|
22
|
-
|
|
23
|
-
import javax.net.ssl
|
|
24
|
-
import javax.net.ssl.
|
|
25
|
-
import
|
|
26
|
-
import
|
|
27
|
-
import
|
|
28
|
-
|
|
29
|
-
|
|
18
|
+
import javax.net.ssl.KeyManagerFactory;
|
|
19
|
+
import javax.net.ssl.TrustManager;
|
|
20
|
+
import javax.net.ssl.TrustManagerFactory;
|
|
21
|
+
import javax.net.ssl.SSLContext;
|
|
22
|
+
import javax.net.ssl.SSLEngine;
|
|
23
|
+
import javax.net.ssl.SSLEngineResult;
|
|
24
|
+
import javax.net.ssl.SSLException;
|
|
25
|
+
import javax.net.ssl.SSLPeerUnverifiedException;
|
|
26
|
+
import javax.net.ssl.SSLSession;
|
|
27
|
+
import javax.net.ssl.X509TrustManager;
|
|
28
|
+
import java.io.FileInputStream;
|
|
29
|
+
import java.io.InputStream;
|
|
30
|
+
import java.io.IOException;
|
|
31
|
+
import java.nio.Buffer;
|
|
32
|
+
import java.nio.ByteBuffer;
|
|
33
|
+
import java.security.KeyManagementException;
|
|
34
|
+
import java.security.KeyStore;
|
|
35
|
+
import java.security.KeyStoreException;
|
|
36
|
+
import java.security.NoSuchAlgorithmException;
|
|
37
|
+
import java.security.UnrecoverableKeyException;
|
|
38
|
+
import java.security.cert.Certificate;
|
|
39
|
+
import java.security.cert.CertificateEncodingException;
|
|
40
|
+
import java.security.cert.CertificateException;
|
|
41
|
+
import java.security.cert.X509Certificate;
|
|
42
|
+
import java.util.concurrent.ConcurrentHashMap;
|
|
43
|
+
import java.util.Map;
|
|
44
|
+
import java.util.function.Supplier;
|
|
45
|
+
|
|
46
|
+
import static javax.net.ssl.SSLEngineResult.Status;
|
|
47
|
+
import static javax.net.ssl.SSLEngineResult.HandshakeStatus;
|
|
48
|
+
|
|
49
|
+
public class MiniSSL extends RubyObject { // MiniSSL::Engine
|
|
50
|
+
private static final long serialVersionUID = -6903439483039141234L;
|
|
30
51
|
private static ObjectAllocator ALLOCATOR = new ObjectAllocator() {
|
|
31
52
|
public IRubyObject allocate(Ruby runtime, RubyClass klass) {
|
|
32
53
|
return new MiniSSL(runtime, klass);
|
|
@@ -35,255 +56,454 @@ public class MiniSSL extends RubyObject {
|
|
|
35
56
|
|
|
36
57
|
public static void createMiniSSL(Ruby runtime) {
|
|
37
58
|
RubyModule mPuma = runtime.defineModule("Puma");
|
|
38
|
-
RubyModule ssl =
|
|
59
|
+
RubyModule ssl = mPuma.defineModuleUnder("MiniSSL");
|
|
39
60
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
runtime.getClass("IOError").getAllocator());
|
|
61
|
+
// Puma::MiniSSL::SSLError
|
|
62
|
+
ssl.defineClassUnder("SSLError", runtime.getStandardError(), runtime.getStandardError().getAllocator());
|
|
43
63
|
|
|
44
|
-
RubyClass eng = ssl.defineClassUnder("Engine",runtime.getObject(),ALLOCATOR);
|
|
64
|
+
RubyClass eng = ssl.defineClassUnder("Engine", runtime.getObject(), ALLOCATOR);
|
|
45
65
|
eng.defineAnnotatedMethods(MiniSSL.class);
|
|
46
66
|
}
|
|
47
67
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
private
|
|
52
|
-
|
|
53
|
-
private ByteBuffer peerAppData;
|
|
54
|
-
private ByteBuffer peerNetData;
|
|
55
|
-
private ByteBuffer netData;
|
|
56
|
-
private ByteBuffer dummy;
|
|
57
|
-
|
|
58
|
-
public MiniSSL(Ruby runtime, RubyClass klass) {
|
|
59
|
-
super(runtime, klass);
|
|
68
|
+
/**
|
|
69
|
+
* Fairly transparent wrapper around {@link java.nio.ByteBuffer} which adds the enhancements we need
|
|
70
|
+
*/
|
|
71
|
+
private static class MiniSSLBuffer {
|
|
72
|
+
ByteBuffer buffer;
|
|
60
73
|
|
|
61
|
-
|
|
62
|
-
|
|
74
|
+
private MiniSSLBuffer(int capacity) { buffer = ByteBuffer.allocate(capacity); }
|
|
75
|
+
private MiniSSLBuffer(byte[] initialContents) { buffer = ByteBuffer.wrap(initialContents); }
|
|
63
76
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
Block.NULL_BLOCK);
|
|
77
|
+
public void clear() { buffer.clear(); }
|
|
78
|
+
public void compact() { buffer.compact(); }
|
|
79
|
+
public void flip() { ((Buffer) buffer).flip(); }
|
|
80
|
+
public boolean hasRemaining() { return buffer.hasRemaining(); }
|
|
81
|
+
public int position() { return buffer.position(); }
|
|
70
82
|
|
|
71
|
-
|
|
72
|
-
|
|
83
|
+
public ByteBuffer getRawBuffer() {
|
|
84
|
+
return buffer;
|
|
85
|
+
}
|
|
73
86
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
java.security.UnrecoverableKeyException
|
|
84
|
-
{
|
|
85
|
-
KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
|
|
86
|
-
KeyStore ts = KeyStore.getInstance(KeyStore.getDefaultType());
|
|
87
|
-
|
|
88
|
-
char[] pass = "blahblah".toCharArray();
|
|
89
|
-
|
|
90
|
-
ks.load(new FileInputStream(key.convertToString().asJavaString()),
|
|
91
|
-
pass);
|
|
92
|
-
ts.load(new FileInputStream(cert.convertToString().asJavaString()),
|
|
93
|
-
pass);
|
|
87
|
+
/**
|
|
88
|
+
* Writes bytes to the buffer after ensuring there's room
|
|
89
|
+
*/
|
|
90
|
+
private void put(byte[] bytes, final int offset, final int length) {
|
|
91
|
+
if (buffer.remaining() < length) {
|
|
92
|
+
resize(buffer.limit() + length);
|
|
93
|
+
}
|
|
94
|
+
buffer.put(bytes, offset, length);
|
|
95
|
+
}
|
|
94
96
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
+
/**
|
|
98
|
+
* Ensures that newCapacity bytes can be written to this buffer, only re-allocating if necessary
|
|
99
|
+
*/
|
|
100
|
+
public void resize(int newCapacity) {
|
|
101
|
+
if (newCapacity > buffer.capacity()) {
|
|
102
|
+
ByteBuffer dstTmp = ByteBuffer.allocate(newCapacity);
|
|
103
|
+
flip();
|
|
104
|
+
dstTmp.put(buffer);
|
|
105
|
+
buffer = dstTmp;
|
|
106
|
+
} else {
|
|
107
|
+
buffer.limit(newCapacity);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
97
110
|
|
|
98
|
-
|
|
99
|
-
|
|
111
|
+
/**
|
|
112
|
+
* Drains the buffer to a ByteList, or returns null for an empty buffer
|
|
113
|
+
*/
|
|
114
|
+
public ByteList asByteList() {
|
|
115
|
+
flip();
|
|
116
|
+
if (!buffer.hasRemaining()) {
|
|
117
|
+
buffer.clear();
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
100
120
|
|
|
101
|
-
|
|
121
|
+
byte[] bss = new byte[buffer.limit()];
|
|
102
122
|
|
|
103
|
-
|
|
123
|
+
buffer.get(bss);
|
|
124
|
+
buffer.clear();
|
|
125
|
+
return new ByteList(bss, false);
|
|
126
|
+
}
|
|
104
127
|
|
|
105
|
-
|
|
128
|
+
@Override
|
|
129
|
+
public String toString() { return buffer.toString(); }
|
|
130
|
+
}
|
|
106
131
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
132
|
+
private SSLEngine engine;
|
|
133
|
+
private boolean closed;
|
|
134
|
+
private boolean handshake;
|
|
135
|
+
private MiniSSLBuffer inboundNetData;
|
|
136
|
+
private MiniSSLBuffer outboundAppData;
|
|
137
|
+
private MiniSSLBuffer outboundNetData;
|
|
110
138
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
peerAppData = ByteBuffer.allocate(session.getApplicationBufferSize());
|
|
114
|
-
netData = ByteBuffer.allocate(session.getPacketBufferSize());
|
|
115
|
-
peerNetData.limit(0);
|
|
116
|
-
peerAppData.limit(0);
|
|
117
|
-
netData.limit(0);
|
|
118
|
-
|
|
119
|
-
peerNetData.clear();
|
|
120
|
-
peerAppData.clear();
|
|
121
|
-
netData.clear();
|
|
122
|
-
|
|
123
|
-
dummy = ByteBuffer.allocate(0);
|
|
124
|
-
|
|
125
|
-
return this;
|
|
139
|
+
public MiniSSL(Ruby runtime, RubyClass klass) {
|
|
140
|
+
super(runtime, klass);
|
|
126
141
|
}
|
|
127
142
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
143
|
+
private static Map<String, KeyManagerFactory> keyManagerFactoryMap = new ConcurrentHashMap<String, KeyManagerFactory>();
|
|
144
|
+
private static Map<String, TrustManagerFactory> trustManagerFactoryMap = new ConcurrentHashMap<String, TrustManagerFactory>();
|
|
145
|
+
|
|
146
|
+
@JRubyMethod(meta = true) // Engine.server
|
|
147
|
+
public static synchronized IRubyObject server(ThreadContext context, IRubyObject recv, IRubyObject miniSSLContext)
|
|
148
|
+
throws KeyStoreException, IOException, CertificateException, NoSuchAlgorithmException, UnrecoverableKeyException {
|
|
149
|
+
// Create the KeyManagerFactory and TrustManagerFactory for this server
|
|
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
|
+
}
|
|
131
176
|
|
|
132
|
-
|
|
177
|
+
KeyStore ks = KeyStore.getInstance(keystoreType);
|
|
178
|
+
InputStream is = new FileInputStream(keystoreFile);
|
|
179
|
+
try {
|
|
180
|
+
ks.load(is, keystorePass);
|
|
181
|
+
} finally {
|
|
182
|
+
is.close();
|
|
183
|
+
}
|
|
184
|
+
KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
|
|
185
|
+
kmf.init(ks, keystorePass);
|
|
186
|
+
keyManagerFactoryMap.put(keystoreFile, kmf);
|
|
187
|
+
|
|
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);
|
|
199
|
+
}
|
|
133
200
|
|
|
134
|
-
|
|
201
|
+
RubyClass klass = (RubyClass) recv;
|
|
202
|
+
return klass.newInstance(context, miniSSLContext, Block.NULL_BLOCK);
|
|
203
|
+
}
|
|
135
204
|
|
|
136
|
-
|
|
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
|
+
}
|
|
137
209
|
|
|
138
|
-
|
|
139
|
-
return
|
|
210
|
+
private static boolean isDefaultSymbol(ThreadContext context, IRubyObject truststore) {
|
|
211
|
+
return context.runtime.newSymbol("default").equals(truststore);
|
|
140
212
|
}
|
|
141
213
|
|
|
142
214
|
@JRubyMethod
|
|
143
|
-
public IRubyObject
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
215
|
+
public IRubyObject initialize(ThreadContext context, IRubyObject miniSSLContext)
|
|
216
|
+
throws KeyStoreException, NoSuchAlgorithmException, KeyManagementException {
|
|
217
|
+
|
|
218
|
+
String keystoreFile = miniSSLContext.callMethod(context, "keystore").convertToString().asJavaString();
|
|
219
|
+
KeyManagerFactory kmf = keyManagerFactoryMap.get(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);
|
|
152
225
|
}
|
|
153
226
|
|
|
154
|
-
|
|
155
|
-
res = engine.unwrap(peerNetData, peerAppData);
|
|
156
|
-
} while(res.getStatus() == SSLEngineResult.Status.OK &&
|
|
157
|
-
res.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.NEED_UNWRAP &&
|
|
158
|
-
res.bytesProduced() == 0);
|
|
227
|
+
SSLContext sslCtx = SSLContext.getInstance("TLS");
|
|
159
228
|
|
|
160
|
-
|
|
229
|
+
sslCtx.init(kmf.getKeyManagers(), getTrustManagers(tmf), null);
|
|
230
|
+
closed = false;
|
|
231
|
+
handshake = false;
|
|
232
|
+
engine = sslCtx.createSSLEngine();
|
|
233
|
+
|
|
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
|
+
}
|
|
161
242
|
|
|
162
|
-
|
|
163
|
-
|
|
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());
|
|
164
250
|
}
|
|
251
|
+
engine.setEnabledProtocols(enabledProtocols);
|
|
165
252
|
|
|
166
|
-
|
|
167
|
-
peerNetData.limit(0);
|
|
253
|
+
engine.setUseClientMode(false);
|
|
168
254
|
|
|
169
|
-
|
|
255
|
+
long verify_mode = miniSSLContext.callMethod(context, "verify_mode").convertToInteger("to_i").getLongValue();
|
|
256
|
+
if ((verify_mode & 0x1) != 0) { // 'peer'
|
|
257
|
+
engine.setWantClientAuth(true);
|
|
258
|
+
}
|
|
259
|
+
if ((verify_mode & 0x2) != 0) { // 'force_peer'
|
|
260
|
+
engine.setNeedClientAuth(true);
|
|
261
|
+
}
|
|
170
262
|
|
|
171
|
-
|
|
172
|
-
|
|
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());
|
|
173
268
|
}
|
|
174
269
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
270
|
+
SSLSession session = engine.getSession();
|
|
271
|
+
inboundNetData = new MiniSSLBuffer(session.getPacketBufferSize());
|
|
272
|
+
outboundAppData = new MiniSSLBuffer(session.getApplicationBufferSize());
|
|
273
|
+
outboundAppData.flip();
|
|
274
|
+
outboundNetData = new MiniSSLBuffer(session.getPacketBufferSize());
|
|
275
|
+
|
|
276
|
+
return this;
|
|
277
|
+
}
|
|
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
|
+
}
|
|
180
289
|
}
|
|
290
|
+
return tms;
|
|
291
|
+
}
|
|
181
292
|
|
|
182
|
-
|
|
183
|
-
return getRuntime().getNil();
|
|
293
|
+
private volatile transient X509Certificate lastCheckedCert0;
|
|
184
294
|
|
|
185
|
-
|
|
186
|
-
// log("peerApp: " + peerAppData.position() + "/" + peerAppData.limit());
|
|
295
|
+
private class TrustManagerWrapper implements X509TrustManager {
|
|
187
296
|
|
|
188
|
-
|
|
297
|
+
private final X509TrustManager delegate;
|
|
189
298
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
// res = engine.unwrap(peerNetData, peerAppData);
|
|
193
|
-
// } while(res.getStatus() == SSLEngineResult.Status.OK &&
|
|
194
|
-
// res.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.NEED_UNWRAP &&
|
|
195
|
-
// res.bytesProduced() == 0);
|
|
196
|
-
// return getRuntime().getNil();
|
|
299
|
+
TrustManagerWrapper(X509TrustManager delegate) {
|
|
300
|
+
this.delegate = delegate;
|
|
197
301
|
}
|
|
198
302
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
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
|
+
}
|
|
204
308
|
|
|
205
|
-
|
|
309
|
+
@Override
|
|
310
|
+
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
|
|
311
|
+
delegate.checkServerTrusted(chain, authType);
|
|
312
|
+
}
|
|
206
313
|
|
|
207
|
-
|
|
314
|
+
@Override
|
|
315
|
+
public X509Certificate[] getAcceptedIssuers() {
|
|
316
|
+
return delegate.getAcceptedIssuers();
|
|
317
|
+
}
|
|
208
318
|
|
|
209
|
-
|
|
210
|
-
str.setValue(new ByteList(bss));
|
|
319
|
+
}
|
|
211
320
|
|
|
212
|
-
|
|
321
|
+
@JRubyMethod
|
|
322
|
+
public IRubyObject inject(IRubyObject arg) {
|
|
323
|
+
ByteList bytes = arg.convertToString().getByteList();
|
|
324
|
+
inboundNetData.put(bytes.unsafeBytes(), bytes.getBegin(), bytes.getRealSize());
|
|
325
|
+
return this;
|
|
213
326
|
}
|
|
214
327
|
|
|
215
|
-
private
|
|
216
|
-
|
|
328
|
+
private enum SSLOperation {
|
|
329
|
+
WRAP,
|
|
330
|
+
UNWRAP
|
|
331
|
+
}
|
|
217
332
|
|
|
218
|
-
|
|
333
|
+
private SSLEngineResult doOp(SSLOperation sslOp, MiniSSLBuffer src, MiniSSLBuffer dst) throws SSLException {
|
|
334
|
+
SSLEngineResult res = null;
|
|
335
|
+
boolean retryOp = true;
|
|
336
|
+
while (retryOp) {
|
|
337
|
+
switch (sslOp) {
|
|
338
|
+
case WRAP:
|
|
339
|
+
res = engine.wrap(src.getRawBuffer(), dst.getRawBuffer());
|
|
340
|
+
break;
|
|
341
|
+
case UNWRAP:
|
|
342
|
+
res = engine.unwrap(src.getRawBuffer(), dst.getRawBuffer());
|
|
343
|
+
break;
|
|
344
|
+
default:
|
|
345
|
+
throw new AssertionError("Unknown SSLOperation: " + sslOp);
|
|
346
|
+
}
|
|
219
347
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
348
|
+
switch (res.getStatus()) {
|
|
349
|
+
case BUFFER_OVERFLOW:
|
|
350
|
+
// increase the buffer size to accommodate the overflowing data
|
|
351
|
+
int newSize = Math.max(engine.getSession().getPacketBufferSize(), engine.getSession().getApplicationBufferSize());
|
|
352
|
+
dst.resize(newSize + dst.position());
|
|
353
|
+
// retry the operation
|
|
354
|
+
retryOp = true;
|
|
355
|
+
break;
|
|
356
|
+
case BUFFER_UNDERFLOW:
|
|
357
|
+
// need to wait for more data to come in before we retry
|
|
358
|
+
retryOp = false;
|
|
359
|
+
break;
|
|
360
|
+
case CLOSED:
|
|
361
|
+
closed = true;
|
|
362
|
+
retryOp = false;
|
|
363
|
+
break;
|
|
364
|
+
default:
|
|
365
|
+
// other case is OK. We're done here.
|
|
366
|
+
retryOp = false;
|
|
225
367
|
}
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
throw new Exception(
|
|
229
|
-
"handshake shouldn't need additional tasks");
|
|
368
|
+
if (res.getHandshakeStatus() == HandshakeStatus.FINISHED) {
|
|
369
|
+
handshake = true;
|
|
230
370
|
}
|
|
231
|
-
log("\tnew HandshakeStatus: " + hsStatus);
|
|
232
371
|
}
|
|
233
372
|
|
|
234
|
-
return
|
|
373
|
+
return res;
|
|
235
374
|
}
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
375
|
+
|
|
376
|
+
@JRubyMethod
|
|
377
|
+
public IRubyObject read() {
|
|
378
|
+
try {
|
|
379
|
+
inboundNetData.flip();
|
|
380
|
+
|
|
381
|
+
if(!inboundNetData.hasRemaining()) {
|
|
382
|
+
return getRuntime().getNil();
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
MiniSSLBuffer inboundAppData = new MiniSSLBuffer(engine.getSession().getApplicationBufferSize());
|
|
386
|
+
doOp(SSLOperation.UNWRAP, inboundNetData, inboundAppData);
|
|
387
|
+
|
|
388
|
+
HandshakeStatus handshakeStatus = engine.getHandshakeStatus();
|
|
389
|
+
boolean done = false;
|
|
390
|
+
while (!done) {
|
|
391
|
+
SSLEngineResult res;
|
|
392
|
+
switch (handshakeStatus) {
|
|
393
|
+
case NEED_WRAP:
|
|
394
|
+
res = doOp(SSLOperation.WRAP, inboundAppData, outboundNetData);
|
|
395
|
+
handshakeStatus = res.getHandshakeStatus();
|
|
396
|
+
break;
|
|
397
|
+
case NEED_UNWRAP:
|
|
398
|
+
res = doOp(SSLOperation.UNWRAP, inboundNetData, inboundAppData);
|
|
399
|
+
if (res.getStatus() == Status.BUFFER_UNDERFLOW) {
|
|
400
|
+
// need more data before we can shake more hands
|
|
401
|
+
done = true;
|
|
402
|
+
}
|
|
403
|
+
handshakeStatus = res.getHandshakeStatus();
|
|
404
|
+
break;
|
|
405
|
+
case NEED_TASK:
|
|
406
|
+
Runnable runnable;
|
|
407
|
+
while ((runnable = engine.getDelegatedTask()) != null) {
|
|
408
|
+
runnable.run();
|
|
409
|
+
}
|
|
410
|
+
handshakeStatus = engine.getHandshakeStatus();
|
|
411
|
+
break;
|
|
412
|
+
default:
|
|
413
|
+
done = true;
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
if (inboundNetData.hasRemaining()) {
|
|
418
|
+
inboundNetData.compact();
|
|
419
|
+
} else {
|
|
420
|
+
inboundNetData.clear();
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
ByteList appDataByteList = inboundAppData.asByteList();
|
|
424
|
+
if (appDataByteList == null) {
|
|
425
|
+
return getRuntime().getNil();
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
return RubyString.newString(getRuntime(), appDataByteList);
|
|
429
|
+
} catch (SSLException e) {
|
|
430
|
+
throw newSSLError(getRuntime(), e);
|
|
250
431
|
}
|
|
251
432
|
}
|
|
252
433
|
|
|
253
|
-
|
|
254
|
-
|
|
434
|
+
@JRubyMethod
|
|
435
|
+
public IRubyObject write(IRubyObject arg) {
|
|
436
|
+
byte[] bls = arg.convertToString().getBytes();
|
|
437
|
+
outboundAppData = new MiniSSLBuffer(bls);
|
|
438
|
+
|
|
439
|
+
return getRuntime().newFixnum(bls.length);
|
|
255
440
|
}
|
|
256
|
-
|
|
257
|
-
|
|
258
441
|
|
|
259
442
|
@JRubyMethod
|
|
260
|
-
public IRubyObject
|
|
261
|
-
|
|
443
|
+
public IRubyObject extract(ThreadContext context) {
|
|
444
|
+
try {
|
|
445
|
+
ByteList dataByteList = outboundNetData.asByteList();
|
|
446
|
+
if (dataByteList != null) {
|
|
447
|
+
return RubyString.newString(context.runtime, dataByteList);
|
|
448
|
+
}
|
|
262
449
|
|
|
263
|
-
|
|
264
|
-
|
|
450
|
+
if (!outboundAppData.hasRemaining()) {
|
|
451
|
+
return context.nil;
|
|
452
|
+
}
|
|
265
453
|
|
|
266
|
-
|
|
454
|
+
outboundNetData.clear();
|
|
455
|
+
doOp(SSLOperation.WRAP, outboundAppData, outboundNetData);
|
|
456
|
+
dataByteList = outboundNetData.asByteList();
|
|
457
|
+
if (dataByteList == null) {
|
|
458
|
+
return context.nil;
|
|
459
|
+
}
|
|
267
460
|
|
|
268
|
-
|
|
461
|
+
return RubyString.newString(context.runtime, dataByteList);
|
|
462
|
+
} catch (SSLException e) {
|
|
463
|
+
throw newSSLError(getRuntime(), e);
|
|
464
|
+
}
|
|
269
465
|
}
|
|
270
466
|
|
|
271
467
|
@JRubyMethod
|
|
272
|
-
public IRubyObject
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
468
|
+
public IRubyObject peercert(ThreadContext context) throws CertificateEncodingException {
|
|
469
|
+
Certificate peerCert;
|
|
470
|
+
try {
|
|
471
|
+
peerCert = engine.getSession().getPeerCertificates()[0];
|
|
472
|
+
} catch (SSLPeerUnverifiedException e) {
|
|
473
|
+
peerCert = lastCheckedCert0; // null if trust check did not happen
|
|
277
474
|
}
|
|
475
|
+
return peerCert == null ? context.nil : JavaEmbedUtils.javaToRuby(context.runtime, peerCert.getEncoded());
|
|
476
|
+
}
|
|
278
477
|
|
|
279
|
-
|
|
478
|
+
@JRubyMethod(name = "init?")
|
|
479
|
+
public IRubyObject isInit(ThreadContext context) {
|
|
480
|
+
return handshake ? getRuntime().getFalse() : getRuntime().getTrue();
|
|
481
|
+
}
|
|
280
482
|
|
|
281
|
-
|
|
282
|
-
|
|
483
|
+
@JRubyMethod
|
|
484
|
+
public IRubyObject shutdown() {
|
|
485
|
+
if (closed || engine.isInboundDone() && engine.isOutboundDone()) {
|
|
486
|
+
if (engine.isOutboundDone()) {
|
|
487
|
+
engine.closeOutbound();
|
|
488
|
+
}
|
|
489
|
+
return getRuntime().getTrue();
|
|
490
|
+
} else {
|
|
491
|
+
return getRuntime().getFalse();
|
|
492
|
+
}
|
|
493
|
+
}
|
|
283
494
|
|
|
284
|
-
|
|
285
|
-
|
|
495
|
+
private static RubyClass getSSLError(Ruby runtime) {
|
|
496
|
+
return (RubyClass) ((RubyModule) runtime.getModule("Puma").getConstantAt("MiniSSL")).getConstantAt("SSLError");
|
|
497
|
+
}
|
|
286
498
|
|
|
287
|
-
|
|
499
|
+
private static RaiseException newSSLError(Ruby runtime, SSLException cause) {
|
|
500
|
+
return newError(runtime, getSSLError(runtime), cause.toString(), cause);
|
|
288
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
|
+
|
|
289
509
|
}
|