Skip to content

WIP: ALPN negotiation #238

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion lib/jopenssl/version.rb
Original file line number Diff line number Diff line change
@@ -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
Expand Down
24 changes: 12 additions & 12 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -60,34 +60,34 @@ DO NOT MODIFIY - GENERATED CODE
</snapshotRepository>
</distributionManagement>
<properties>
<bc.versions>1.68</bc.versions>
<mavengem.wagon.version>1.0.3</mavengem.wagon.version>
<jruby.switches>-W0</jruby.switches>
<jruby.version>9.1.17.0</jruby.version>
<jruby.plugins.version>1.1.8</jruby.plugins.version>
<invoker.skip>${maven.test.skip}</invoker.skip>
<runit.dir>src/test/ruby/**/test_*.rb</runit.dir>
<mavengem-wagon.version>1.0.3</mavengem-wagon.version>
<jruby.versions>9.1.17.0</jruby.versions>
<polyglot.dump.readonly>false</polyglot.dump.readonly>
<polyglot.dump.pom>pom.xml</polyglot.dump.pom>
<bc.versions>1.69</bc.versions>
<invoker.test>${bc.versions}</invoker.test>
<mavengem-wagon.version>1.0.3</mavengem-wagon.version>
<polyglot.dump.pom>pom.xml</polyglot.dump.pom>
<mavengem.wagon.version>1.0.3</mavengem.wagon.version>
<jruby.version>9.2.9.0</jruby.version>
<jruby.plugins.version>1.1.8</jruby.plugins.version>
<invoker.skip>${maven.test.skip}</invoker.skip>
<jruby.switches>-W0</jruby.switches>
<jruby.versions>9.2.9.0</jruby.versions>
</properties>
<dependencies>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
<version>1.68</version>
<version>1.69</version>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcpkix-jdk15on</artifactId>
<version>1.68</version>
<version>1.69</version>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bctls-jdk15on</artifactId>
<version>1.68</version>
<version>1.69</version>
</dependency>
<dependency>
<groupId>org.jruby</groupId>
Expand Down
92 changes: 82 additions & 10 deletions src/main/java/org/jruby/ext/openssl/SSLContext.java
Original file line number Diff line number Diff line change
Expand Up @@ -53,13 +53,15 @@
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;
import org.jruby.RubyModule;
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;
Expand Down Expand Up @@ -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=");
Expand Down Expand Up @@ -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);
Expand All @@ -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);
Expand All @@ -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<CipherStrings.Def> cipherDefs =
CipherStrings.matchingCiphers(this.ciphers, supported, false);

Expand Down Expand Up @@ -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<SSLEngine>()
{
public String select(SSLEngine engine, List<String> 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;
}
Expand All @@ -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();
}

Expand Down Expand Up @@ -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<X509AuxCertificate> clientCert, final List<X509AuxCertificate> 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;
}
Expand All @@ -868,7 +934,9 @@ private class InternalContext {
final List<X509AuxCertificate> clientCert,
final List<X509AuxCertificate> 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();
Expand All @@ -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 :

Expand Down Expand Up @@ -932,6 +1002,8 @@ void initSSLContext(final ThreadContext context) throws KeyManagementException {
final List<X509AuxCertificate> extraChainCert; // empty assumed == null

private final int timeout;
private final String[] alpnProtocols;
private final RubyProc alpnSelectCb;

private final javax.net.ssl.SSLContext sslContext;

Expand Down
7 changes: 7 additions & 0 deletions src/main/java/org/jruby/ext/openssl/SSLSocket.java
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
19 changes: 19 additions & 0 deletions src/test/ruby/ssl/test_session.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down