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