diff --git a/lib/jopenssl/version.rb b/lib/jopenssl/version.rb
index 1771a983..bf193917 100644
--- a/lib/jopenssl/version.rb
+++ b/lib/jopenssl/version.rb
@@ -1,6 +1,6 @@
module JOpenSSL
VERSION = '0.10.7'
- BOUNCY_CASTLE_VERSION = '1.68'
+ BOUNCY_CASTLE_VERSION = '1.69'
end
Object.class_eval do
diff --git a/pom.xml b/pom.xml
index dda984ab..bcfed7f5 100644
--- a/pom.xml
+++ b/pom.xml
@@ -60,34 +60,34 @@ DO NOT MODIFIY - GENERATED CODE
- 1.68
- 1.0.3
- -W0
- 9.1.17.0
- 1.1.8
- ${maven.test.skip}
src/test/ruby/**/test_*.rb
- 1.0.3
- 9.1.17.0
false
- pom.xml
+ 1.69
${bc.versions}
+ 1.0.3
+ pom.xml
+ 1.0.3
+ 9.2.9.0
+ 1.1.8
+ ${maven.test.skip}
+ -W0
+ 9.2.9.0
org.bouncycastle
bcprov-jdk15on
- 1.68
+ 1.69
org.bouncycastle
bcpkix-jdk15on
- 1.68
+ 1.69
org.bouncycastle
bctls-jdk15on
- 1.68
+ 1.69
org.jruby
diff --git a/src/main/java/org/jruby/ext/openssl/SSLContext.java b/src/main/java/org/jruby/ext/openssl/SSLContext.java
index 7d1ac22c..afd17e8c 100644
--- a/src/main/java/org/jruby/ext/openssl/SSLContext.java
+++ b/src/main/java/org/jruby/ext/openssl/SSLContext.java
@@ -53,6 +53,7 @@
import org.jruby.Ruby;
import org.jruby.RubyArray;
import org.jruby.RubyClass;
+import org.jruby.RubyString;
import org.jruby.RubyFixnum;
import org.jruby.RubyHash;
import org.jruby.RubyInteger;
@@ -60,6 +61,7 @@
import org.jruby.RubyNumeric;
import org.jruby.RubyObject;
import org.jruby.RubySymbol;
+import org.jruby.RubyProc;
import org.jruby.anno.JRubyMethod;
import org.jruby.common.IRubyWarnings.ID;
import org.jruby.runtime.Arity;
@@ -192,6 +194,8 @@ public static void createSSLContext(final Ruby runtime, final RubyModule SSL) {
SSLContext.addReadWriteAttribute(context, "tmp_dh_callback");
SSLContext.addReadWriteAttribute(context, "servername_cb");
SSLContext.addReadWriteAttribute(context, "renegotiation_cb");
+ SSLContext.addReadWriteAttribute(context, "alpn_protocols");
+ SSLContext.addReadWriteAttribute(context, "alpn_select_cb");
SSLContext.defineAlias("ssl_timeout", "timeout");
SSLContext.defineAlias("ssl_timeout=", "timeout=");
@@ -444,6 +448,30 @@ public IRubyObject setup(final ThreadContext context) {
// SSL_CTX_set_tlsext_servername_callback(ctx, ssl_servername_cb);
}
+ final String[] alpnProtocols;
+
+ value = getInstanceVariable("@alpn_protocols");
+ if ( value != null && ! value.isNil() ) {
+ IRubyObject[] rArray = ((RubyArray) value).toJavaArray();
+ String[] protos = new String[rArray.length];
+ for(int i = 0; i < protos.length; i++) {
+ protos[i] = ((RubyString) rArray[i]).asJavaString();
+ }
+
+ alpnProtocols = protos;
+ } else {
+ alpnProtocols = null;
+ }
+
+ final RubyProc alpnSelectCb;
+ value = getInstanceVariable("@alpn_select_cb");
+ if ( value != null && ! value.isNil() ) {
+ alpnSelectCb = (RubyProc) value;
+ } else {
+ alpnSelectCb = null;
+ }
+
+
// NOTE: no API under javax.net to support session get/new/remove callbacks
/*
val = ossl_sslctx_get_sess_id_ctx(self);
@@ -470,7 +498,7 @@ public IRubyObject setup(final ThreadContext context) {
*/
try {
- internalContext = createInternalContext(context, cert, key, store, clientCert, extraChainCert, verifyMode, timeout);
+ internalContext = createInternalContext(context, cert, key, store, clientCert, extraChainCert, verifyMode, timeout, alpnProtocols, alpnSelectCb);
}
catch (GeneralSecurityException e) {
throw newSSLError(runtime, e);
@@ -487,7 +515,7 @@ public RubyArray ciphers(final ThreadContext context) {
private RubyArray matchedCiphers(final ThreadContext context) {
final Ruby runtime = context.runtime;
try {
- final String[] supported = getSupportedCipherSuites(protocol);
+ final String[] supported = getSupportedCipherSuites(runtime, protocol);
final Collection cipherDefs =
CipherStrings.matchingCiphers(this.ciphers, supported, false);
@@ -645,15 +673,52 @@ void setLastVerifyResult(int verifyResult) {
this.verifyResult = verifyResult;
}
+ void setApplicationProtocols(SSLEngine engine) {
+ if ( !(engine instanceof org.bouncycastle.jsse.BCSSLEngine) ) {
+ return;
+ }
+ if ( protocolForClient ) {
+ if ( internalContext.alpnProtocols != null ) {
+ org.bouncycastle.jsse.BCSSLParameters bcParams = new org.bouncycastle.jsse.BCSSLParameters();
+ bcParams.setApplicationProtocols(internalContext.alpnProtocols);
+ bcParams.setCipherSuites(getCipherSuites(engine.getSupportedCipherSuites()) );
+ ((org.bouncycastle.jsse.BCSSLEngine) engine).setParameters(bcParams);
+ }
+ }
+ if ( protocolForServer ) {
+ if ( internalContext.alpnSelectCb != null ) {
+ ((org.bouncycastle.jsse.BCSSLEngine) engine).setBCHandshakeApplicationProtocolSelector(
+ new org.bouncycastle.jsse.BCApplicationProtocolSelector()
+ {
+ public String select(SSLEngine engine, List protocols) {
+ Ruby runtime = internalContext.alpnSelectCb.getRuntime();
+ IRubyObject[] arr = new IRubyObject[protocols.size()];
+ for(int i = 0; i < arr.length; i++) {
+ arr[i] = runtime.newString(protocols.get(i));
+ }
+ RubyArray array = RubyArray.newArray(runtime, arr);
+
+ IRubyObject selectedProtocol = internalContext.alpnSelectCb.getBlock().call(runtime.getCurrentContext(), (IRubyObject) array);
+ if (selectedProtocol != null) {
+ return ((RubyString) selectedProtocol).toString();
+ }
+
+ return null;
+ }
+ });
+ }
+ }
+
+ }
private static String cachedProtocol = null;
private static String[] cachedSupportedCipherSuites;
- private static String[] getSupportedCipherSuites(final String protocol)
+ private static String[] getSupportedCipherSuites(Ruby runtime, final String protocol)
throws GeneralSecurityException {
if ( cachedProtocol == null ) {
synchronized(SSLContext.class) {
if ( cachedProtocol == null ) {
- cachedSupportedCipherSuites = dummySSLEngine(protocol).getSupportedCipherSuites();
+ cachedSupportedCipherSuites = dummySSLEngine(runtime, protocol).getSupportedCipherSuites();
cachedProtocol = protocol;
return cachedSupportedCipherSuites;
}
@@ -662,12 +727,12 @@ private static String[] getSupportedCipherSuites(final String protocol)
if ( protocol.equals(cachedProtocol) ) return cachedSupportedCipherSuites;
- return dummySSLEngine(protocol).getSupportedCipherSuites();
+ return dummySSLEngine(runtime, protocol).getSupportedCipherSuites();
}
- private static SSLEngine dummySSLEngine(final String protocol) throws GeneralSecurityException {
+ private static SSLEngine dummySSLEngine(Ruby runtime, final String protocol) throws GeneralSecurityException {
javax.net.ssl.SSLContext sslContext = SecurityHelper.getSSLContext(protocol);
- sslContext.init(null, null, null);
+ sslContext.init(null, null, OpenSSL.getSecureRandom(runtime));
return sslContext.createSSLEngine();
}
@@ -850,8 +915,9 @@ static RubyClass _SSLContext(final Ruby runtime) {
private InternalContext createInternalContext(ThreadContext context,
final X509Cert xCert, final PKey pKey, final Store store,
final List clientCert, final List extraChainCert,
- final int verifyMode, final int timeout) throws NoSuchAlgorithmException, KeyManagementException {
- InternalContext internalContext = new InternalContext(xCert, pKey, store, clientCert, extraChainCert, verifyMode, timeout);
+ final int verifyMode, final int timeout,
+ final String[] alpnProtocols, final RubyProc alpnSelectCb) throws NoSuchAlgorithmException, KeyManagementException {
+ InternalContext internalContext = new InternalContext(xCert, pKey, store, clientCert, extraChainCert, verifyMode, timeout, alpnProtocols, alpnSelectCb);
internalContext.initSSLContext(context);
return internalContext;
}
@@ -868,7 +934,9 @@ private class InternalContext {
final List clientCert,
final List extraChainCert,
final int verifyMode,
- final int timeout) throws NoSuchAlgorithmException {
+ final int timeout,
+ final String[] alpnProtocols,
+ final RubyProc alpnSelectCb) throws NoSuchAlgorithmException {
if ( pKey != null && xCert != null ) {
this.privateKey = pKey.getPrivateKey();
@@ -886,6 +954,8 @@ private class InternalContext {
this.extraChainCert = extraChainCert;
this.verifyMode = verifyMode;
this.timeout = timeout;
+ this.alpnProtocols = alpnProtocols;
+ this.alpnSelectCb = alpnSelectCb;
// initialize SSL context :
@@ -932,6 +1002,8 @@ void initSSLContext(final ThreadContext context) throws KeyManagementException {
final List extraChainCert; // empty assumed == null
private final int timeout;
+ private final String[] alpnProtocols;
+ private final RubyProc alpnSelectCb;
private final javax.net.ssl.SSLContext sslContext;
diff --git a/src/main/java/org/jruby/ext/openssl/SSLSocket.java b/src/main/java/org/jruby/ext/openssl/SSLSocket.java
index d55edb97..57dd5204 100644
--- a/src/main/java/org/jruby/ext/openssl/SSLSocket.java
+++ b/src/main/java/org/jruby/ext/openssl/SSLSocket.java
@@ -238,6 +238,11 @@ private SSLEngine ossl_ssl_setup(final ThreadContext context) {
@JRubyMethod(name = "context")
public final SSLContext context() { return this.sslContext; }
+ @JRubyMethod(name = "alpn_protocol")
+ public final RubyString alpn_protocol(final ThreadContext context) {
+ return RubyString.newString(context.runtime, this.engine.getApplicationProtocol());
+ }
+
@JRubyMethod(name = "sync")
public IRubyObject sync(final ThreadContext context) {
final CallSite[] sites = getMetaClass().getExtraCallSites();
@@ -285,6 +290,7 @@ private IRubyObject connectImpl(final ThreadContext context, final boolean block
if ( ! initialHandshake ) {
SSLEngine engine = ossl_ssl_setup(context);
engine.setUseClientMode(true);
+ sslContext.setApplicationProtocols(engine);
engine.beginHandshake();
handshakeStatus = engine.getHandshakeStatus();
initialHandshake = true;
@@ -359,6 +365,7 @@ private IRubyObject acceptImpl(final ThreadContext context, final boolean blocki
engine.setNeedClientAuth(true);
}
}
+ sslContext.setApplicationProtocols(engine);
engine.beginHandshake();
handshakeStatus = engine.getHandshakeStatus();
initialHandshake = true;
diff --git a/src/test/ruby/ssl/test_session.rb b/src/test/ruby/ssl/test_session.rb
index 14678fe9..e1798eb7 100644
--- a/src/test/ruby/ssl/test_session.rb
+++ b/src/test/ruby/ssl/test_session.rb
@@ -30,6 +30,25 @@ def test_session
end
end
+ def test_alpn_protocol_selection_ary
+ advertised = ["h2", "http/1.1"]
+ ctx_proc = Proc.new { |ctx|
+ ctx.alpn_select_cb = -> (protocols) {
+ protocols.first
+ }
+ }
+ start_server0(PORT, OpenSSL::SSL::VERIFY_NONE, true, ctx_proc: ctx_proc) do |server, port|
+ sock = TCPSocket.new("127.0.0.1", port)
+ ctx = OpenSSL::SSL::SSLContext.new("TLSv1_2")
+ ctx.alpn_protocols = advertised
+ ssl = OpenSSL::SSL::SSLSocket.new(sock, ctx)
+ ssl.sync_close = true
+ ssl.connect
+ assert_equal("h2", ssl.alpn_protocol)
+ ssl.puts "abc"; assert_equal "abc\n", ssl.gets
+ end
+ end
+
def test_exposes_session_error
OpenSSL::SSL::Session::SessionError
end