diff --git a/src/main/java/org/jruby/ext/openssl/CipherStrings.java b/src/main/java/org/jruby/ext/openssl/CipherStrings.java index 6f69517b..073d1eb2 100644 --- a/src/main/java/org/jruby/ext/openssl/CipherStrings.java +++ b/src/main/java/org/jruby/ext/openssl/CipherStrings.java @@ -494,6 +494,7 @@ static final class Def implements Comparable, Cloneable { private final long mask; private final long algStrengthMask; private final long requiredAlgorithms; + private final int requiredStrengthBits; // 0 matches any // OpenSSL 1.1.1 private long algorithm_mkey; @@ -518,9 +519,14 @@ static final class Def implements Comparable, Cloneable { this.mask = mask; this.algStrengthMask = maskStrength; this.requiredAlgorithms = 0; + this.requiredStrengthBits = 0; } Def(String name, long algorithms, long mask, long requiredAlgorithms) { + this(name, algorithms, mask, requiredAlgorithms, 0); + } + + Def(String name, long algorithms, long mask, long requiredAlgorithms, int requiredStrengthBits) { this.valid = false; this.name = name; this.id = 0; @@ -531,6 +537,7 @@ static final class Def implements Comparable, Cloneable { this.mask = mask; this.algStrengthMask = 0; this.requiredAlgorithms = requiredAlgorithms; + this.requiredStrengthBits = requiredStrengthBits; } Def(String name, long algorithms, long algo_strength, int strength_bits, int alg_bits, long mask, long maskStrength) { @@ -544,6 +551,7 @@ static final class Def implements Comparable, Cloneable { this.mask = mask; this.algStrengthMask = maskStrength; this.requiredAlgorithms = 0; + this.requiredStrengthBits = 0; } Def(int valid, String name, String stdname, /* RFC name */ @@ -578,6 +586,7 @@ static final class Def implements Comparable, Cloneable { this.mask = 0; this.algStrengthMask = 0; this.requiredAlgorithms = 0; + this.requiredStrengthBits = 0; } @@ -636,6 +645,7 @@ public String toString() { // from ssl_cipher_apply_rule public boolean matches(Def current) { if ( (current.algorithms & requiredAlgorithms) != requiredAlgorithms ) return false; + if ( requiredStrengthBits != 0 && current.algStrengthBits != requiredStrengthBits ) return false; // ma = mask & cp->algorithms; // ma_s = mask_strength & cp->algo_strength; // @@ -740,11 +750,13 @@ private static Collection matchingExact(final String name, final String[] a return matchingPattern(pattern, all, true, setSuite); } - final Def def = CipherNames.get(name); + // an OpenSSL cipher name, or an RFC (Java) suite name + final String ossl = CipherNames.containsKey(name) ? name : SuiteToOSSL.get(name); + final Def def = ossl == null ? null : CipherNames.get(ossl); if (def != null) { if (setSuite) { for (final String entry : all) { - if (name.equals(SuiteToOSSL.get(entry))) { + if (ossl.equals(SuiteToOSSL.get(entry))) { return Collections.singleton(def.setCipherSuite(entry)); } } @@ -759,14 +771,13 @@ private static Collection matching(final String[] defs, final String[] all, final boolean setSuite) { Collection matching = null; for ( final String name : defs ) { - final Def pattern = Definitions.get(name); - if ( pattern != null ) { - if ( matching == null ) { - matching = matchingPattern(pattern, all, true, setSuite); - } - else { - matching.retainAll( matchingPattern(pattern, all, false, setSuite) ); - } + final Collection found = matchingExact(name, all, setSuite); + if ( found == null ) return null; // unknown name drops the rule (libssl ssl_cipher_process_rulestr) + if ( matching == null ) { + matching = new LinkedHashSet(found); + } + else { + matching.retainAll(found); } } return matching; @@ -847,6 +858,9 @@ static Def suiteToDef(final String cipherSuite) { Definitions.put(SSL_TXT_eNULL,new Def(0,SSL_TXT_eNULL,0,SSL_eNULL,0,0,0,0,SSL_ENC_MASK,0)); Definitions.put(SSL_TXT_eFZA,new Def(0,SSL_TXT_eFZA,0,SSL_eFZA, 0,0,0,0,SSL_ENC_MASK,0)); Definitions.put(SSL_TXT_AES,new Def(0,SSL_TXT_AES, 0,SSL_AES, 0,0,0,0,SSL_ENC_MASK,0)); + Definitions.put(SSL_TXT_AES128,new Def(SSL_TXT_AES128,SSL_AES,SSL_ENC_MASK,SSL_AES,128)); + Definitions.put(SSL_TXT_AES256,new Def(SSL_TXT_AES256,SSL_AES,SSL_ENC_MASK,SSL_AES,256)); + Definitions.put(SSL_TXT_CHACHA20,new Def(0,SSL_TXT_CHACHA20,0,SSL_CHACHA20,0,0,0,0,SSL_ENC_MASK,0)); Definitions.put(SSL_TXT_MD5,new Def(0,SSL_TXT_MD5, 0,SSL_MD5, 0,0,0,0,SSL_MAC_MASK,0)); Definitions.put(SSL_TXT_SHA1,new Def(0,SSL_TXT_SHA1,0,SSL_SHA1, 0,0,0,0,SSL_MAC_MASK,0)); Definitions.put(SSL_TXT_SHA,new Def(0,SSL_TXT_SHA, 0,SSL_SHA, 0,0,0,0,SSL_MAC_MASK,0)); diff --git a/src/test/java/org/jruby/ext/openssl/CipherStringsTest.java b/src/test/java/org/jruby/ext/openssl/CipherStringsTest.java index a6ca67d2..d17ccb35 100644 --- a/src/test/java/org/jruby/ext/openssl/CipherStringsTest.java +++ b/src/test/java/org/jruby/ext/openssl/CipherStringsTest.java @@ -54,8 +54,49 @@ public void ecdheSelectorsMatchOpenSSLAuthenticationRules() { ), javaNames); } + private static final String[] ENC_SUITES = { + "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", + "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", + "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256", + "TLS_DHE_RSA_WITH_AES_128_CBC_SHA", + "TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256" + }; + + @Test + public void encryptionSelectorsMatchKeySize() { + assertEquals(Arrays.asList("ECDHE-RSA-AES128-GCM-SHA256", "DHE-RSA-AES128-SHA"), names("AES128", ENC_SUITES)); + assertEquals(Arrays.asList("ECDHE-RSA-AES256-GCM-SHA384"), names("AES256", ENC_SUITES)); + assertEquals(Arrays.asList("ECDHE-RSA-CHACHA20-POLY1305", "DHE-RSA-CHACHA20-POLY1305"), names("CHACHA20", ENC_SUITES)); + assertEquals(Arrays.asList("ECDHE-RSA-CHACHA20-POLY1305"), names("EECDH+CHACHA20", ENC_SUITES)); + assertEquals(Arrays.asList("ECDHE-RSA-AES128-GCM-SHA256"), names("EECDH+AES128", ENC_SUITES)); + } + + @Test + public void unknownAliasVoidsCombinedRule() { + assertEquals(Arrays.asList(), names("EECDH+FOO", ENC_SUITES)); + assertEquals(Arrays.asList(), names("FOO+EECDH", ENC_SUITES)); + assertEquals(Arrays.asList("ECDHE-RSA-AES256-GCM-SHA384"), names("EECDH+FOO:EECDH+AES256", ENC_SUITES)); + } + + @Test + public void combinedRuleAcceptsCipherNames() { + assertEquals(Arrays.asList("ECDHE-RSA-AES128-GCM-SHA256"), names("AES128+ECDHE-RSA-AES128-GCM-SHA256", ENC_SUITES)); + assertEquals(Arrays.asList("ECDHE-RSA-AES128-GCM-SHA256"), names("ECDHE-RSA-AES128-GCM-SHA256+AES128", ENC_SUITES)); + assertEquals(Arrays.asList("ECDHE-RSA-AES128-GCM-SHA256"), names("AES128+TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", ENC_SUITES)); + assertEquals(Arrays.asList(), names("AES256+ECDHE-RSA-AES128-GCM-SHA256", ENC_SUITES)); + } + + @Test + public void rfcCipherName() { + assertEquals(Arrays.asList("ECDHE-RSA-AES128-GCM-SHA256"), names("TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", ENC_SUITES)); + } + private static List names(final String selector) { - final Collection ciphers = CipherStrings.matchingCiphers(selector, SUITES, false); + return names(selector, SUITES); + } + + private static List names(final String selector, final String[] suites) { + final Collection ciphers = CipherStrings.matchingCiphers(selector, suites, false); final List names = new ArrayList<>(ciphers.size()); for (final CipherStrings.Def cipher : ciphers) names.add(cipher.name); return names; diff --git a/test/ssl/test_context.rb b/test/ssl/test_context.rb index cd4cb2a8..481db529 100644 --- a/test/ssl/test_context.rb +++ b/test/ssl/test_context.rb @@ -200,6 +200,39 @@ def test_set_ciphers_by_ecdh_group_name end end + def test_set_ciphers_by_encryption_alias + context = OpenSSL::SSL::SSLContext.new + context.ciphers = 'EECDH+CHACHA20:EECDH+AES128' + selected = context.ciphers.map(&:first) + assert_include selected, 'ECDHE-RSA-CHACHA20-POLY1305' + assert_include selected, 'ECDHE-RSA-AES128-GCM-SHA256' + assert_not_include selected, 'ECDHE-RSA-AES256-GCM-SHA384' + + context.ciphers = 'AES256' + selected = context.ciphers.map(&:first) + assert_include selected, 'ECDHE-RSA-AES256-GCM-SHA384' + assert_not_include selected, 'ECDHE-RSA-AES128-GCM-SHA256' + + assert_raise(OpenSSL::SSL::SSLError) { context.ciphers = 'EECDH+FOO' } + end + + def test_set_ciphers_by_cipher_name_in_combined_rule + context = OpenSSL::SSL::SSLContext.new + ['AES128+ECDHE-RSA-AES128-GCM-SHA256', 'AES128+TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256'].each do |rule| + context.ciphers = rule + selected = context.ciphers.reject { |_, version, *| version == 'TLSv1.3' }.map(&:first) + assert_equal ['ECDHE-RSA-AES128-GCM-SHA256'], selected, rule + end + + assert_raise(OpenSSL::SSL::SSLError) { context.ciphers = 'AES256+ECDHE-RSA-AES128-GCM-SHA256' } + end + + def test_set_ciphers_by_rfc_name + context = OpenSSL::SSL::SSLContext.new + context.ciphers = 'TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256' + assert_equal ['ECDHE-RSA-AES128-GCM-SHA256'], context.ciphers.reject { |_, version, *| version == 'TLSv1.3' }.map(&:first) + end + def test_set_ciphers_by_cipher_name context = OpenSSL::SSL::SSLContext.new context.ciphers = "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384"