4545
4646import javax .net .ssl .KeyManager ;
4747import javax .net .ssl .SSLEngine ;
48+ import javax .net .ssl .SSLParameters ;
4849import javax .net .ssl .SSLSessionContext ;
4950import javax .net .ssl .TrustManager ;
5051import javax .net .ssl .X509ExtendedKeyManager ;
5354import org .jruby .Ruby ;
5455import org .jruby .RubyArray ;
5556import org .jruby .RubyClass ;
57+ import org .jruby .RubyString ;
5658import org .jruby .RubyFixnum ;
5759import org .jruby .RubyHash ;
5860import org .jruby .RubyInteger ;
5961import org .jruby .RubyModule ;
6062import org .jruby .RubyNumeric ;
6163import org .jruby .RubyObject ;
6264import org .jruby .RubySymbol ;
65+ import org .jruby .RubyProc ;
6366import org .jruby .anno .JRubyMethod ;
6467import org .jruby .common .IRubyWarnings .ID ;
6568import org .jruby .runtime .Arity ;
@@ -242,6 +245,8 @@ public static void createSSLContext(final Ruby runtime, final RubyModule SSL) {
242245 SSLContext .addReadWriteAttribute (context , "tmp_dh_callback" );
243246 SSLContext .addReadWriteAttribute (context , "servername_cb" );
244247 SSLContext .addReadWriteAttribute (context , "renegotiation_cb" );
248+ SSLContext .addReadWriteAttribute (context , "alpn_protocols" );
249+ SSLContext .addReadWriteAttribute (context , "alpn_select_cb" );
245250
246251 SSLContext .defineAlias ("ssl_timeout" , "timeout" );
247252 SSLContext .defineAlias ("ssl_timeout=" , "timeout=" );
@@ -451,6 +456,29 @@ public IRubyObject setup(final ThreadContext context) {
451456 // SSL_CTX_set_tlsext_servername_callback(ctx, ssl_servername_cb);
452457 }
453458
459+ final String [] alpnProtocols ;
460+
461+ value = getInstanceVariable ("@alpn_protocols" );
462+ if ( value != null && ! value .isNil () ) {
463+ IRubyObject [] alpn_protocols = ((RubyArray ) value ).toJavaArrayMaybeUnsafe ();
464+ String [] protocols = new String [alpn_protocols .length ];
465+ for (int i = 0 ; i < protocols .length ; i ++) {
466+ protocols [i ] = alpn_protocols [i ].convertToString ().asJavaString ();
467+ }
468+ alpnProtocols = protocols ;
469+ } else {
470+ alpnProtocols = null ;
471+ }
472+
473+ final RubyProc alpnSelectCb ;
474+ value = getInstanceVariable ("@alpn_select_cb" );
475+ if ( value != null && ! value .isNil () ) {
476+ alpnSelectCb = (RubyProc ) value ;
477+ } else {
478+ alpnSelectCb = null ;
479+ }
480+
481+
454482 // NOTE: no API under javax.net to support session get/new/remove callbacks
455483 /*
456484 val = ossl_sslctx_get_sess_id_ctx(self);
@@ -477,7 +505,8 @@ public IRubyObject setup(final ThreadContext context) {
477505 */
478506
479507 try {
480- internalContext = createInternalContext (context , cert , key , store , clientCert , extraChainCert , verifyMode , timeout );
508+ internalContext = createInternalContext (context , cert , key , store , clientCert , extraChainCert ,
509+ verifyMode , timeout , alpnProtocols , alpnSelectCb );
481510 }
482511 catch (GeneralSecurityException e ) {
483512 throw newSSLError (runtime , e );
@@ -505,7 +534,7 @@ private RubyArray matchedCiphersWithCache(final ThreadContext context) {
505534 private RubyArray matchedCiphers (final ThreadContext context ) {
506535 final Ruby runtime = context .runtime ;
507536 try {
508- final String [] supported = getSupportedCipherSuites (protocol );
537+ final String [] supported = getSupportedCipherSuites (runtime , protocol );
509538 final Collection <CipherStrings .Def > cipherDefs =
510539 CipherStrings .matchingCiphers (this .ciphers , supported , false );
511540
@@ -688,14 +717,48 @@ private static class CipherListCache {
688717 }
689718 }
690719
691- private static String [] getSupportedCipherSuites (final String protocol )
720+ void setApplicationProtocolsOrSelector (final SSLEngine engine ) {
721+ setApplicationProtocolSelector (engine );
722+ setApplicationProtocols (engine );
723+ }
724+
725+ private void setApplicationProtocolSelector (final SSLEngine engine ) {
726+ final RubyProc alpn_select_cb = internalContext .alpnSelectCallback ;
727+ if (alpn_select_cb != null ) {
728+ engine .setHandshakeApplicationProtocolSelector ((_engine , protocols ) -> {
729+ final Ruby runtime = getRuntime ();
730+ IRubyObject [] rubyProtocols = new IRubyObject [protocols .size ()];
731+ int i = 0 ; for (String protocol : protocols ) {
732+ rubyProtocols [i ++] = runtime .newString (protocol );
733+ }
734+
735+ IRubyObject [] args = new IRubyObject [] { RubyArray .newArray (runtime , rubyProtocols ) };
736+ IRubyObject selected_protocol = alpn_select_cb .call (runtime .getCurrentContext (), args );
737+ if (selected_protocol != null && !selected_protocol .isNil ()) {
738+ return ((RubyString ) selected_protocol ).asJavaString ();
739+ }
740+ return null ; // callback returned nil - none of the advertised names are acceptable
741+ });
742+ }
743+ }
744+
745+ private void setApplicationProtocols (final SSLEngine engine ) {
746+ final String [] alpn_protocols = internalContext .alpnProtocols ;
747+ if (alpn_protocols != null ) {
748+ SSLParameters params = engine .getSSLParameters ();
749+ params .setApplicationProtocols (alpn_protocols );
750+ engine .setSSLParameters (params );
751+ }
752+ }
753+
754+ private static String [] getSupportedCipherSuites (Ruby runtime , final String protocol )
692755 throws GeneralSecurityException {
693- return dummySSLEngine (protocol ).getSupportedCipherSuites ();
756+ return dummySSLEngine (runtime , protocol ).getSupportedCipherSuites ();
694757 }
695758
696- private static SSLEngine dummySSLEngine (final String protocol ) throws GeneralSecurityException {
759+ private static SSLEngine dummySSLEngine (Ruby runtime , final String protocol ) throws GeneralSecurityException {
697760 javax .net .ssl .SSLContext sslContext = SecurityHelper .getSSLContext (protocol );
698- sslContext .init (null , null , null );
761+ sslContext .init (null , null , OpenSSL . getSecureRandom ( runtime ) );
699762 return sslContext .createSSLEngine ();
700763 }
701764
@@ -899,8 +962,9 @@ static RubyClass _SSLContext(final Ruby runtime) {
899962 private InternalContext createInternalContext (ThreadContext context ,
900963 final X509Cert xCert , final PKey pKey , final Store store ,
901964 final List <X509AuxCertificate > clientCert , final List <X509AuxCertificate > extraChainCert ,
902- final int verifyMode , final int timeout ) throws NoSuchAlgorithmException , KeyManagementException {
903- InternalContext internalContext = new InternalContext (xCert , pKey , store , clientCert , extraChainCert , verifyMode , timeout );
965+ final int verifyMode , final int timeout ,
966+ final String [] alpnProtocols , final RubyProc alpnSelectCb ) throws NoSuchAlgorithmException , KeyManagementException {
967+ InternalContext internalContext = new InternalContext (xCert , pKey , store , clientCert , extraChainCert , verifyMode , timeout , alpnProtocols , alpnSelectCb );
904968 internalContext .initSSLContext (context );
905969 return internalContext ;
906970 }
@@ -917,7 +981,9 @@ private class InternalContext {
917981 final List <X509AuxCertificate > clientCert ,
918982 final List <X509AuxCertificate > extraChainCert ,
919983 final int verifyMode ,
920- final int timeout ) throws NoSuchAlgorithmException {
984+ final int timeout ,
985+ final String [] alpnProtocols ,
986+ final RubyProc alpnSelectCallback ) throws NoSuchAlgorithmException {
921987
922988 if ( pKey != null && xCert != null ) {
923989 this .privateKey = pKey .getPrivateKey ();
@@ -935,6 +1001,8 @@ private class InternalContext {
9351001 this .extraChainCert = extraChainCert ;
9361002 this .verifyMode = verifyMode ;
9371003 this .timeout = timeout ;
1004+ this .alpnProtocols = alpnProtocols ;
1005+ this .alpnSelectCallback = alpnSelectCallback ;
9381006
9391007 // initialize SSL context :
9401008
@@ -982,6 +1050,9 @@ void initSSLContext(final ThreadContext context) throws KeyManagementException {
9821050
9831051 private final int timeout ;
9841052
1053+ private final String [] alpnProtocols ;
1054+ private final RubyProc alpnSelectCallback ;
1055+
9851056 private final javax .net .ssl .SSLContext sslContext ;
9861057
9871058 // part of ssl_verify_cert_chain
0 commit comments