org.wildfly.security.ssl.CipherSuiteSelector Maven / Gradle / Ivy
The newest version!
/*
* JBoss, Home of Professional Open Source.
* Copyright 2014 Red Hat, Inc., and individual contributors
* as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.wildfly.security.ssl;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.wildfly.common.iteration.CodePointIterator;
/**
* An immutable filter for SSL/TLS cipher suites.
*
* @author David M. Lloyd
*/
public abstract class CipherSuiteSelector {
final CipherSuiteSelector prev;
CipherSuiteSelector(final CipherSuiteSelector prev) {
this.prev = prev;
}
/* -- predicates -- */
private static final CipherSuiteSelector EMPTY = new CipherSuiteSelector(null) {
void applyFilter(final Set enabled, final Map supported) {
}
void toString(final StringBuilder b) {
b.append("(empty)");
}
};
private static final CipherSuiteSelector TLS13_EMPTY = new CipherSuiteSelector(null) {
void applyFilter(final Set enabled, final Map supported) {
}
void toString(final StringBuilder b) {
b.append("(empty)");
}
MechanismDatabase getMechanismDatabase() {
return MechanismDatabase.getTLS13Instance();
}
};
/**
* Get the basic empty SSL cipher suite selector.
*
* @return the empty selector
*/
public static CipherSuiteSelector empty() {
return EMPTY;
}
/**
* Get the basic empty SSL cipher suite selector.
*
* @param useTLS13 {@code true} if the TLSv1.3 mechanism database should be used by this selector and {@code false} otherwise
* @return the empty selector
*/
public static CipherSuiteSelector empty(final boolean useTLS13) {
return useTLS13 ? TLS13_EMPTY : EMPTY;
}
/**
* OpenSSL default cipher suites for TLSv1.3.
*/
public static final String OPENSSL_DEFAULT_CIPHER_SUITE_NAMES = "TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256";
static final CipherSuiteSelector OPENSSL_ALL = empty().add(CipherSuitePredicate.matchOpenSslAll());
static final CipherSuiteSelector OPENSSL_DEFAULT = openSslAll().deleteFully(CipherSuitePredicate.matchOpenSslDefaultDeletes());
// OpenSSL default cipher suites for TLSv1.3
static final CipherSuiteSelector OPENSSL_DEFAULT_CIPHER_SUITES = CipherSuiteSelector.fromNamesString(OPENSSL_DEFAULT_CIPHER_SUITE_NAMES);
static final CipherSuiteSelector OPENSSL_COMBINED_DEFAULT = aggregate(OPENSSL_DEFAULT_CIPHER_SUITES, OPENSSL_DEFAULT);
/**
* Get the cipher selector which OpenSSL defines as {@code DEFAULT}.
*
* @return the selector
* @see CipherSuitePredicate#matchOpenSslDefaultDeletes()
*/
public static CipherSuiteSelector openSslDefault() {
return OPENSSL_DEFAULT;
}
/**
* Get the cipher selector which OpenSSL defines as {@code ALL}.
*
* @return the selector
* @see CipherSuitePredicate#matchOpenSslAll()
*/
public static CipherSuiteSelector openSslAll() {
return OPENSSL_ALL;
}
/**
* Get the cipher selector which OpenSSL defines as the default cipher suites for TLSv1.3.
*
* @return the selector
*/
public static CipherSuiteSelector openSslDefaultCipherSuites() {
return OPENSSL_DEFAULT_CIPHER_SUITES;
}
/**
* Get the cipher selector which OpenSSL defines as {@code DEFAULT} combined with the
* cipher suites which OpenSSL defines as the default cipher suites for TLSv1.3.
*
* @return the selector
*/
public static CipherSuiteSelector openSslCombinedDefault() {
return OPENSSL_COMBINED_DEFAULT;
}
/* -- delete -- */
/**
* Permanently delete all cipher suites which are matched by the given predicate. Matching ciphers cannot
* be re-added by a later rule (such rules will be ignored).
*
* @param predicate the predicate to match
* @return a new selector which includes the new rule
*/
public CipherSuiteSelector deleteFully(final CipherSuitePredicate predicate) {
return predicate == null ? this : new FullyDeletingCipherSuiteSelector(this, predicate);
}
/**
* A convenience method to permanently delete a cipher suite by name. This is a shortcut for calling
* {@code deleteFully(Predicate.matchName(cipherSuiteName))}. The cipher suite name must be a standard or OpenSSL-style
* mechanism name identifying a single mechanism.
*
* @param cipherSuiteName the cipher suite name
* @return a new selector which includes the new rule
*/
public CipherSuiteSelector deleteFully(final String cipherSuiteName) {
return deleteFully(CipherSuitePredicate.matchName(cipherSuiteName));
}
/* -- remove -- */
/**
* Remove all cipher suites which are matched by the given predicate. Matching ciphers may be re-added by a later
* rule.
*
* @param predicate the predicate to match
* @return a new selector which includes the new rule
*/
public CipherSuiteSelector remove(final CipherSuitePredicate predicate) {
return predicate == null || predicate.isAlwaysFalse() ? this : new RemovingCipherSuiteSelector(this, predicate);
}
/**
* A convenience method to remove a cipher suite by name. This is a shortcut for calling
* {@code remove(Predicate.matchName(cipherSuiteName))}. The cipher suite name must be a standard or OpenSSL-style
* mechanism name identifying a single mechanism.
*
* @param cipherSuiteName the cipher suite name
* @return a new selector which includes the new rule
*/
public CipherSuiteSelector remove(final String cipherSuiteName) {
return remove(CipherSuitePredicate.matchName(cipherSuiteName));
}
/* -- add -- */
/**
* Add all cipher suites which are matched by the given predicate. The cipher suites are added in a reasonably
* logical order. Any suites which are not supported by the underlying socket layer will not be added.
*
* @param predicate the predicate to match
* @return a new selector which includes the new rule
*/
public CipherSuiteSelector add(final CipherSuitePredicate predicate) {
return predicate == null || predicate.isAlwaysFalse() ? this : new AddingCipherSuiteSelector(this, predicate);
}
/**
* A convenience method to add a cipher suite by name. If the underlying socket layer does not support the named
* cipher suite, or if the cipher suite is invalid, it will not be added. This is a shortcut for calling
* {@code add(Predicate.matchName(cipherSuiteName))}. The cipher suite name must be a standard or OpenSSL-style
* mechanism name identifying a single mechanism.
*
* @param cipherSuiteName the cipher suite name
* @return a new selector which includes the new rule
*/
public CipherSuiteSelector add(final String cipherSuiteName) {
return add(CipherSuitePredicate.matchName(cipherSuiteName));
}
/* -- push to end -- */
/**
* Push all cipher suites which are matched by the given predicate to the end of the enabled ciphers list. Only
* cipher suites which are already enabled will be moved; no cipher suites are added by this transformation.
*
* @param predicate the predicate to match
* @return a new selector which includes the new rule
*/
public CipherSuiteSelector pushToEnd(final CipherSuitePredicate predicate) {
return predicate == null || predicate.isAlwaysFalse() || predicate.isAlwaysTrue() ? this : new PushToEndCipherSuiteSelector(this, predicate);
}
/**
* A convenience method to push a cipher suite by name to the end of the enabled ciphers list. This is a shortcut
* for calling {@code pushToEnd(Predicate.matchName(cipherSuiteName))}. In particular, no cipher suites are added
* by this transformation. The cipher suite name must be a standard or OpenSSL-style mechanism name identifying a
* single mechanism.
*
* @param cipherSuiteName the cipher suite name
* @return a new selector which includes the new rule
*/
public CipherSuiteSelector pushToEnd(final String cipherSuiteName) {
return pushToEnd(CipherSuitePredicate.matchName(cipherSuiteName));
}
/**
* Sort all of the enabled ciphers by decreasing key length. Only the ciphers which were added by previous rules
* will be sorted.
*
* @return a new selector which includes the sort
*/
public CipherSuiteSelector sortByAlgorithmKeyLength() {
return new SortByAlgorithmKeyLengthCipherSuiteSelector(this);
}
public final String toString() {
StringBuilder b = new StringBuilder();
toString(b);
return b.toString();
}
abstract void toString(StringBuilder b);
/* -- selector implementation -- */
abstract void applyFilter(Set enabled, Map supported);
private void doEvaluate(Set enabled, Map supported) {
if (prev != null) {
prev.doEvaluate(enabled, supported);
}
applyFilter(enabled, supported);
}
MechanismDatabase getMechanismDatabase() {
if (prev != null) {
return prev.getMechanismDatabase();
}
return MechanismDatabase.getInstance();
}
/**
* Evaluate this selector against the given list of JSSE supported mechanisms.
*
* @param supportedMechanisms the supported mechanisms
* @return the enabled mechanisms (not {@code null})
*/
public String[] evaluate(String[] supportedMechanisms) {
if (ElytronMessages.tls.isTraceEnabled()) {
StringBuilder b = new StringBuilder(supportedMechanisms.length * 16);
b.append("Evaluating filter \"").append(this).append("\" on supported mechanisms:");
for (String s : supportedMechanisms) {
b.append("\n ").append(s);
}
ElytronMessages.tls.trace(b);
}
final MechanismDatabase database = getMechanismDatabase();
final LinkedHashMap supportedMap = new LinkedHashMap<>(supportedMechanisms.length);
for (String supportedMechanism : supportedMechanisms) {
final MechanismDatabase.Entry entry = database.getCipherSuite(supportedMechanism);
if (entry != null) {
ElytronMessages.tls.tracef("Found supported mechanism %s", supportedMechanism);
supportedMap.put(entry, supportedMechanism);
} else {
ElytronMessages.tls.tracef("Dropping unknown mechanism %s", supportedMechanism);
}
}
final LinkedHashSet enabledSet = new LinkedHashSet(supportedMap.size());
doEvaluate(enabledSet, supportedMap);
return enabledSet.toArray(new String[enabledSet.size()]);
}
/**
* Create a cipher suite selector from the given OpenSSL-style cipher list string. The rules of the string are as
* follows:
*
* - Each item is separated from the other items by a colon (":"), though for compatibility, commas (",") or
* spaces (" ") are allowed delimiters as well.
* - The items are evaluated in order from left to right.
* -
* Each item may consist of one of the following:
*
* - An OpenSSL-style cipher suite name like {@code DH-RSA-AES256-SHA256}, which adds the named cipher suite to the end of the list (if it is supported and not already present).
* - A standard SSL/TLS cipher suite name like {@code TLS_DH_RSA_WITH_AES_256_CBC_SHA256}, which adds the named cipher suite to the end of the list (if it is supported and not already present).
* -
* Any of the following special keywords:
*
* - {@code HIGH}, which matches all supported cipher suites with "high" encryption, presently defined
* as all cipher suites with key lengths larger than 128 bits, and some with key lengths
* of exactly 128 bits (see {@link SecurityLevel#HIGH}).
* - {@code MEDIUM}, which matches all supported cipher suites with "medium" encryption, presently
* defined as some cipher suites with 128 bit keys (see {@link SecurityLevel#MEDIUM}).
* - {@code LOW}, which matches all supported cipher suites with "low" encryption, presently defined
* as cipher suites which use 64- or 56-bit encryption but excluding export cipher suites
* (see {@link SecurityLevel#LOW}).
* - {@code EXP} or {@code EXPORT}, which matches supported cipher suites using export algorithms, presently defined as
* cipher suites which include those that use 56- or 40-bit encryption algorithms (see
* {@link SecurityLevel#EXP40} and {@link SecurityLevel#EXP56})
.
* - {@code EXPORT40}, which matches supported cipher suites using export algorithms with 40-bit encryption (see {@link SecurityLevel#EXP40}).
* - {@code EXPORT56}, which matches supported cipher suites using export algorithms with 56-bit encryption (see {@link SecurityLevel#EXP56}).
* - {@code eNULL} or {@code NULL}, which matches supported cipher suites without encryption (see {@link Encryption#NULL}).
* - {@code aNULL}, which matches supported cipher suites without authentication (i.e. they are anonymous) (see {@link Authentication#NULL}).
* - {@code kRSA}, which matches supported cipher suites using RSA key exchange (see {@link KeyAgreement#RSA}).
* - {@code aRSA}, which matches supported cipher suites using RSA authentication (see {@link Authentication#RSA}).
* - {@code RSA}, which matches supported cipher suites using either RSA key exchange or RSA authentication.
* - {@code kDHr}, which matches supported cipher suites using DH key agreement with DH certificates signed with a RSA key (see {@link KeyAgreement#DHr}).
* - {@code kDHd}, which matches supported cipher suites using DH key agreement with DH certificates signed with a DSS key (see {@link KeyAgreement#DHd}).
* - {@code kDH}, which matches any supported cipher suite using DH key agreement.
* - {@code kDHE} or {@code kEDH}, which matches supported cipher suites using ephemeral DH key agreement (including anonymous cipher suites; see {@link KeyAgreement#DHE}).
* - {@code DHE} or {@code EDH}, which matches supported cipher suites using non-anonymous ephemeral DH key agreement.
* - {@code ADH}, which matches supported cipher suites using anonymous DH, not including anonymous elliptic curve suites.
* - {@code DH}, which matches any supported cipher suite using DH.
* - {@code kECDHr}, which matches supported cipher suites using fixed ECDH key agreement signed using RSA keys (see {@link KeyAgreement#ECDHr}).
* - {@code kECDHe}, which matches supported cipher suites using fixed ECDH key agreement signed by ECDSA keys (see {@link KeyAgreement#ECDHe}).
* - {@code kECDH}, which matches supported cipher suites using fixed ECDH key agreement.
* - {@code kEECDH} or {@code kECDHE}, which matches supported cipher suites using ephemeral ECDH key agreement (including anonymous cipher suites; see {@link KeyAgreement#ECDHE}).
* - {@code ECDHE} or {@code EECDHE}, which matches supported cipher suites using authenticated (non-anonymous) ephemeral ECDH key agreement.
* - {@code AECDH}, which matches supported cipher suites using anonymous ephemeral ECDH key agreement.
* - {@code ECDH}, which matches all supported cipher suites using ECDH key agreement.
* - {@code aDSS} or {@code DSS}, which matches supported cipher suites using DSS authentication (see {@link Authentication#DSS}).
* - {@code aDH}, which matches supported cipher suites using DH authentication (see {@link Authentication#DH}).
* - {@code aECDH}, which matches supported cipher suites using ECDH authentication (see {@link Authentication#ECDH}).
* - {@code aECDSA} or {@code ECDSA}, which matches supported cipher suites using ECDSA authentication (see {@link Authentication#ECDSA}).
* - {@code kFZA}, which matches supported cipher suites using Fortezza key agreement (see {@link KeyAgreement#FZA}).
* - {@code aFZA}, which matches supported cipher suites using Fortezza authentication (see {@link Authentication#FZA}).
* - {@code eFZA}, which matches supported cipher suites using Fortezza encryption (see {@link Encryption#FZA}).
* - {@code FZA}, which matches all supported cipher suites using any Fortezza algorithm.
* - {@code TLSv1.2}, which matches supported cipher suites defined in TLS v1.2 (see {@link Protocol#TLSv1_2}).
* - {@code TLSv1}, which matches supported cipher suites defined in TLS v1 (see {@link Protocol#TLSv1}).
* - {@code SSLv3}, which matches supported cipher suites defined in SSL v3.0 (see {@link Protocol#SSLv3}).
* - {@code SSLv2}, which matches supported cipher suites defined in SSL v2.0 (see {@link Protocol#SSLv2}).
* - {@code AES256}, which matches supported cipher suites using 256-bit AES (see {@link Encryption#AES256}).
* - {@code AES128}, which matches supported cipher suites using 128-bit AES (see {@link Encryption#AES128}).
* - {@code AES}, which matches all supported cipher suites using AES.
* - {@code AESGCM}, which matches supported cipher suites using AES in Galois Counter Mode (GCM) (see {@link Encryption#AES256GCM} and {@link Encryption#AES128GCM}).
* - {@code CAMELLIA256}, which matches supported cipher suites using 256-bit Camellia encryption (see {@link Encryption#CAMELLIA256}).
* - {@code CAMELLIA128}, which matches supported cipher suites using 128-bit Camellia encryption (see {@link Encryption#CAMELLIA128}).
* - {@code CAMELLIA}, which matches all supported cipher suites using any Camellia encryption.
* - {@code 3DES}, which matches supported cipher suites using triple DES encryption (see {@link Encryption#_3DES}).
* - {@code DES}, which matches supported cipher suites using plain DES encryption (see {@link Encryption#DES}).
* - {@code RC4}, which matches supported cipher suites using RC4 encryption (see {@link Encryption#RC4}).
* - {@code RC2}, which matches supported cipher suites using RC2 encryption (see {@link Encryption#RC2}).
* - {@code IDEA}, which matches supported cipher suites using IDEA encryption (see {@link Encryption#IDEA}).
* - {@code SEED}, which matches supported cipher suites using SEED encryption (see {@link Encryption#SEED}).
* - {@code MD5}, which matches supported cipher suites using the MD5 digest algorithm (see {@link Digest#MD5}).
* - {@code SHA1} or {@code SHA}, which matches supported cipher suites using the SHA-1 digest algorithm (see {@link Digest#SHA1}).
* - {@code SHA256}, which matches supported cipher suites using the SHA-256 digest algorithm (see {@link Digest#SHA256}).
* - {@code SHA384}, which matches supported cipher suites using the SHA-384 digest algorithm (see {@link Digest#SHA384}).
* - {@code aGOST}, which matches supported cipher suites using GOST authentication.
* - {@code aGOST01}, which matches supported cipher suites using GOST R 34.10-2001 authentication (see {@link Authentication#GOST01}).
* - {@code aGOST94}, which matches supported cipher suites using GOST R 34.10-94 authentication (see {@link Authentication#GOST94}).
* - {@code kGOST}, which matches supported cipher suites using VKO 34.10 key exchange (see {@link KeyAgreement#GOST}).
* - {@code GOST94}, which matches supported cipher suites using GOST R 34.11-94 based HMAC (see {@link Digest#GOST94}).
* - {@code GOST89MAC}, which matches supported cipher suites using GOST 28147-89 based MAC (not HMAC) (see {@link Digest#GOST89MAC}).
* - {@code kPSK}, which matches supported cipher suites using pre-shared keys key agreement (see {@link KeyAgreement#PSK}).
* - {@code aPSK}, which matches supported cipher suites using pre-shared keys authentication (see {@link Authentication#PSK}).
* - {@code PSK}, which matches supported cipher suites using pre-shared keys (see {@link Authentication#PSK} and {@link KeyAgreement#PSK}).
* - {@code RSAPSK} or {@code kRSAPSK}, which matches supported cipher suites using RSA-based pre-shared keys (see {@link KeyAgreement#RSAPSK}).
* - {@code kEDHPSK}, {@code kDHEPSK}, {@code EDHPSK} or {@code DHEPSK}, which matches supported cipher suites using ephemeral DH-based pre-shared keys (see {@link KeyAgreement#DHEPSK}).
* - {@code kEECDHPSK}, {@code EECDHPSK}, {@code kECDHEPSK} or {@code ECDHEPSK}, which matches supported cipher suites using ephemeral elliptic-curve DH-based pre-shared keys (see {@link KeyAgreement#ECDHEPSK}).
*
*
* - More than one of any of the above keywords or cipher suite names joined by {@code +} symbols, which
* indicates that all of the items must be matched (i.e. a logical "and" operation).
* - The special unary {@code !} operator followed by any of the above keywords or cipher
* names, which removes the matching cipher suite(s) from the enabled list and also deletes it from the
* supported list, preventing any matching cipher suites from being re-added by a later rule.
* - The special unary {@code -} operator followed by any of the above keywords or cipher
* names, which removes the matching cipher suite(s) from the enabled list (though they may still
* be re-added).
* - The special unary {@code +} operator followed by any of the above keywords or cipher
* names, which causes any of the matching cipher suite(s) to be moved to the end of the list
* of enabled cipher suites.
* - The special {@code ALL} keyword, which includes all cipher suites (except for encryptionless
* suites; in other words, this keyword implies {@code -eNULL}).
* - The special {@code COMPLEMENTOFALL} keyword, which is presently equivalent to {@code eNULL}.
* - The special {@code DEFAULT} keyword, which is equivalent to {@code ALL:!EXPORT:!LOW:!aNULL:!eNULL:!SSLv2}.
* - The special {@code COMPLEMENTOFDEFAULT} keyword, which presently includes any anonymous cipher
* suites (but excludes those without encryption, which must always be enabled manually).
* - The special {@code @STRENGTH} keyword, which causes all the mechanisms enabled thus far to be
* automatically sorted by encryption algorithm key length in descending order.
*
*
*
*
* @param string the string to parse
* @return the parsed cipher suite selector
* @throws IllegalArgumentException if the given string is not valid
*/
public static CipherSuiteSelector fromString(String string) throws IllegalArgumentException {
final CodePointIterator i = CodePointIterator.ofString(string);
CipherSuiteSelector current = empty();
CipherSuitePredicate predicate;
String name;
int cp;
while (i.hasNext()) {
cp = i.next();
switch (cp) {
case '+': {
current = parseMoveToEnd(current, i);
break;
}
case '-': {
current = parseRemove(current, i);
break;
}
case '!': {
current = parseDelete(current, i);
break;
}
case '@': {
current = parseSpecial(current, i);
break;
}
case '=': {
throw ElytronMessages.log.mechSelectorTokenNotAllowed("=", i.getIndex(), string);
}
case ',':
case ':': {
// skip empty
break;
}
default: {
if (Character.isWhitespace(cp)) {
// skip whitespace
break;
}
if (Character.isLetterOrDigit(cp)) {
i.previous();
name = i.delimitedBy('+', ':', ',', ' ').drainToString();
predicate = parsePredicate(i, name);
if (predicate != null) {
current = current.add(predicate);
} else {
switch (name) {
/* -- openssl special -- */
case "DEFAULT": current = current.add(CipherSuitePredicate.matchOpenSslAll())
.deleteFully(CipherSuitePredicate.matchOpenSslDefaultDeletes()); break;
case "COMPLEMENTOFDEFAULT": current = current.add(CipherSuitePredicate.matchAnonDH()); break;
case "ALL": current = current.add(CipherSuitePredicate.matchOpenSslAll()); break;
case "COMPLEMENTOFALL": current = current.add(CipherSuitePredicate.matchOpenSslComplementOfAll()); break;
// SUITEB not yet supported
// case "SUITEB128": return null;
// case "SUITEB128ONLY": return null;
// case "SUITEB192": return null;
default: {
throw ElytronMessages.log.mechSelectorUnknownToken(name, string);
}
}
}
break;
}
throw ElytronMessages.log.mechSelectorUnexpectedChar(cp, i.getIndex(), string);
}
}
// current character should be : or EOS after parse* methods
}
return current;
}
/**
* Create a cipher suite selector from the given OpenSSL-style TLSv1.3 cipher suites string. The format for this string
* is a simple colon (":") separated list of TLSv1.3 cipher suite names.
*
* @param names the string to parse
* @return the parsed cipher suite selector
* @throws IllegalArgumentException if the given string is not valid
*/
public static CipherSuiteSelector fromNamesString(String names) throws IllegalArgumentException {
final CodePointIterator cpi = CodePointIterator.ofString(names);
final CodePointIterator di = cpi.delimitedBy(':');
CipherSuiteSelector current = empty(true);
while (cpi.hasNext()) {
if (di.hasNext()) {
String name = di.drainToString();
final MechanismDatabase database = MechanismDatabase.getTLS13Instance();
MechanismDatabase.Entry entry = database.getCipherSuiteOpenSSLName(name);
if (entry == null) {
throw ElytronMessages.log.unknownCipherSuiteName(name, names);
}
current = current.add(name);
} else {
cpi.next(); // skip the colon
}
}
return current;
}
/**
* Create an aggregate {@link CipherSuiteSelector}. Each cipher suite selector is executed in order.
*
* @param cipherSuiteSelector1 the first cipher suite selector
* @param cipherSuiteSelector2 the second cipher suite selector
* @return the aggregate cipher suite selector (not {@code null})
*/
public static CipherSuiteSelector aggregate(final CipherSuiteSelector cipherSuiteSelector1, final CipherSuiteSelector cipherSuiteSelector2) {
return new CipherSuiteSelector(null) {
void toString(StringBuilder b) {
if (cipherSuiteSelector1 != null && cipherSuiteSelector1 != EMPTY) {
cipherSuiteSelector1.toString(b);
}
if (cipherSuiteSelector2 != null && cipherSuiteSelector2 != EMPTY) {
cipherSuiteSelector2.toString(b);
}
}
void applyFilter(Set enabled, Map supported) {
if (cipherSuiteSelector1 != null) {
cipherSuiteSelector1.applyFilter(enabled, supported);
}
if (cipherSuiteSelector2 != null) {
cipherSuiteSelector2.applyFilter(enabled, supported);
}
}
public String[] evaluate(String[] supportedMechanisms) {
ArrayList enabledList = new ArrayList<>();
if (cipherSuiteSelector1 != null) {
enabledList.addAll(Arrays.asList(cipherSuiteSelector1.evaluate(supportedMechanisms)));
}
if (cipherSuiteSelector2 != null) {
enabledList.addAll(Arrays.asList(cipherSuiteSelector2.evaluate(supportedMechanisms)));
}
return enabledList.toArray(new String[enabledList.size()]);
}
};
}
private static CipherSuiteSelector parseMoveToEnd(final CipherSuiteSelector current, final CodePointIterator i) {
return current.pushToEnd(parsePredicate(i));
}
private static CipherSuiteSelector parseRemove(final CipherSuiteSelector current, final CodePointIterator i) {
return current.remove(parsePredicate(i));
}
private static CipherSuiteSelector parseDelete(final CipherSuiteSelector current, final CodePointIterator i) {
return current.deleteFully(parsePredicate(i));
}
private static CipherSuiteSelector parseSpecial(final CipherSuiteSelector current, final CodePointIterator i) {
String word = i.delimitedBy('=', ':').drainToString();
switch (word) {
case "STRENGTH": {
if (i.hasNext() && i.next() == '=') {
throw ElytronMessages.log.mechSelectorTokenNotAllowed("=", i.getIndex(), i.drainToString());
}
return current.sortByAlgorithmKeyLength();
}
default: {
throw ElytronMessages.log.mechSelectorUnknownToken(word, i.drainToString());
}
}
}
private static CipherSuitePredicate parsePredicate(final CodePointIterator i) {
return parsePredicate(i, i.delimitedBy('+', ':', ',', ' ').drainToString());
}
private static CipherSuitePredicate parsePredicate(final CodePointIterator i, final String word) {
CipherSuitePredicate item = getSimplePredicateByName(word);
if (i.hasNext() && i.next() == '+') {
if (item == null) {
throw ElytronMessages.log.mechSelectorTokenNotAllowed("+", i.getIndex(), i.drainToString());
}
return parseAndPredicate(item, i);
} else {
return item;
}
}
private static CipherSuitePredicate parseAndPredicate(CipherSuitePredicate item, final CodePointIterator i) {
final ArrayList list = new ArrayList<>();
list.add(item);
do {
list.add(getSimplePredicateByName(i.delimitedBy('+', ':', ',', ' ').drainToString()));
} while (i.hasNext() && i.next() == '+');
return CipherSuitePredicate.matchAll(list.toArray(new CipherSuitePredicate[list.size()]));
}
private static CipherSuitePredicate getSimplePredicateByName(final String word) {
switch (word) {
/* -- openssl standard -- */
case "HIGH": return CipherSuitePredicate.matchLevel(SecurityLevel.HIGH);
case "MEDIUM": return CipherSuitePredicate.matchLevel(SecurityLevel.MEDIUM);
case "LOW": return CipherSuitePredicate.matchLevel(SecurityLevel.LOW);
case "EXP": // synonym
case "EXPORT": return CipherSuitePredicate.matchLevel(SecurityLevel.EXP40, SecurityLevel.EXP56);
case "EXPORT40": return CipherSuitePredicate.matchLevel(SecurityLevel.EXP40);
case "EXPORT56": return CipherSuitePredicate.matchLevel(SecurityLevel.EXP56);
case "NULL": // synonym
case "eNULL": return CipherSuitePredicate.matchEncryption(Encryption.NULL);
case "aNULL": return CipherSuitePredicate.matchAuthentication(Authentication.NULL);
case "kRSA": return CipherSuitePredicate.matchKeyAgreement(KeyAgreement.RSA);
case "aRSA": return CipherSuitePredicate.matchAuthentication(Authentication.RSA);
case "RSA": return CipherSuitePredicate.matchAny(CipherSuitePredicate.matchKeyAgreement(KeyAgreement.RSA), CipherSuitePredicate.matchAuthentication(Authentication.RSA));
case "kDHr": return CipherSuitePredicate.matchKeyAgreement(KeyAgreement.DHr);
case "kDHd": return CipherSuitePredicate.matchKeyAgreement(KeyAgreement.DHd);
case "kDH": return CipherSuitePredicate.matchKeyExchange(KeyAgreement.DHr, KeyAgreement.DHd);
case "kDHE": // synonym
case "kEDH": return CipherSuitePredicate.matchKeyAgreement(KeyAgreement.DHE);
case "DHE": // synonym
case "EDH": return CipherSuitePredicate.matchAll(CipherSuitePredicate.matchKeyAgreement(KeyAgreement.DHE), CipherSuitePredicate.matchNot(CipherSuitePredicate.matchAuthentication(Authentication.NULL)));
case "ADH": return CipherSuitePredicate.matchAnonDH();
case "DH": return CipherSuitePredicate.matchAll(CipherSuitePredicate.matchKeyExchange(KeyAgreement.DHE, KeyAgreement.DHd, KeyAgreement.DHr, KeyAgreement.ECDHe, KeyAgreement.ECDHr, KeyAgreement.ECDHE), CipherSuitePredicate.matchAuthentication(Authentication.DH, Authentication.ECDH, Authentication.NULL));
case "kECDHr": return CipherSuitePredicate.matchKeyAgreement(KeyAgreement.ECDHr);
case "kECDHe": return CipherSuitePredicate.matchKeyAgreement(KeyAgreement.ECDHe);
case "kECDH": return CipherSuitePredicate.matchKeyExchange(KeyAgreement.ECDHe, KeyAgreement.ECDHr);
case "kEECDH": // synonym
case "kECDHE": return CipherSuitePredicate.matchKeyAgreement(KeyAgreement.ECDHE);
case "ECDHE": // synonym
case "EECDHE": return CipherSuitePredicate.matchAll(CipherSuitePredicate.matchKeyAgreement(KeyAgreement.ECDHE), CipherSuitePredicate.matchNot(CipherSuitePredicate.matchAuthentication(Authentication.NULL)));
case "AECDH": return CipherSuitePredicate.matchAll(CipherSuitePredicate.matchKeyExchange(KeyAgreement.ECDHe, KeyAgreement.ECDHr, KeyAgreement.ECDHE), CipherSuitePredicate.matchAuthentication(Authentication.NULL));
case "ECDH": return CipherSuitePredicate.matchKeyExchange(KeyAgreement.ECDHe, KeyAgreement.ECDHr, KeyAgreement.ECDHE);
case "DSS": // synonym
case "aDSS": return CipherSuitePredicate.matchAuthentication(Authentication.DSS);
case "aDH": return CipherSuitePredicate.matchAuthentication(Authentication.DH);
case "aECDH": return CipherSuitePredicate.matchAuthentication(Authentication.ECDH);
case "ECDSA": // synonym
case "aECDSA": return CipherSuitePredicate.matchAuthentication(Authentication.ECDSA);
case "kFZA": return CipherSuitePredicate.matchKeyAgreement(KeyAgreement.FZA);
case "aFZA": return CipherSuitePredicate.matchAuthentication(Authentication.FZA);
case "eFZA": return CipherSuitePredicate.matchEncryption(Encryption.FZA);
case "FZA": return CipherSuitePredicate.matchAny(CipherSuitePredicate.matchKeyAgreement(KeyAgreement.FZA), CipherSuitePredicate.matchAuthentication(Authentication.FZA), CipherSuitePredicate.matchEncryption(Encryption.FZA));
case "TLSv1.2": return CipherSuitePredicate.matchProtocol(Protocol.TLSv1_2);
case "TLSv1": return CipherSuitePredicate.matchProtocol(Protocol.TLSv1);
case "SSLv3": return CipherSuitePredicate.matchProtocol(Protocol.SSLv3);
case "SSLv2": return CipherSuitePredicate.matchProtocol(Protocol.SSLv2);
case "AES128": return CipherSuitePredicate.matchEncryption(Encryption.AES128, Encryption.AES128GCM);
case "AES256": return CipherSuitePredicate.matchEncryption(Encryption.AES256, Encryption.AES256GCM);
case "AES": return CipherSuitePredicate.matchEncryption(Encryption.AES128, Encryption.AES128GCM, Encryption.AES256, Encryption.AES256GCM);
case "AESGCM": return CipherSuitePredicate.matchEncryption(Encryption.AES128GCM, Encryption.AES256GCM);
case "ARIA": return CipherSuitePredicate.matchEncryption(Encryption.ARIA128, Encryption.ARIA128GCM, Encryption.ARIA256, Encryption.ARIA256GCM);
case "ARIA128": return CipherSuitePredicate.matchEncryption(Encryption.ARIA128, Encryption.ARIA128GCM);
case "ARIA256": return CipherSuitePredicate.matchEncryption(Encryption.ARIA256, Encryption.ARIA256GCM);
case "ARIAGCM": return CipherSuitePredicate.matchEncryption(Encryption.ARIA128GCM, Encryption.ARIA256GCM);
case "CAMELLIA128": return CipherSuitePredicate.matchEncryption(Encryption.CAMELLIA128);
case "CAMELLIA256": return CipherSuitePredicate.matchEncryption(Encryption.CAMELLIA256);
case "CAMELLIA": return CipherSuitePredicate.matchEncryption(Encryption.CAMELLIA128, Encryption.CAMELLIA256);
case "3DES": return CipherSuitePredicate.matchEncryption(Encryption._3DES);
case "DES": return CipherSuitePredicate.matchEncryption(Encryption.DES);
case "RC4": return CipherSuitePredicate.matchEncryption(Encryption.RC4);
case "RC2": return CipherSuitePredicate.matchEncryption(Encryption.RC2);
case "IDEA": return CipherSuitePredicate.matchEncryption(Encryption.IDEA);
case "SEED": return CipherSuitePredicate.matchEncryption(Encryption.SEED);
case "MD5": return CipherSuitePredicate.matchDigest(Digest.MD5);
case "SHA": // synonym
case "SHA1": return CipherSuitePredicate.matchDigest(Digest.SHA1);
case "SHA256": return CipherSuitePredicate.matchDigest(Digest.SHA256);
case "SHA384": return CipherSuitePredicate.matchDigest(Digest.SHA384);
case "aGOST": return CipherSuitePredicate.matchAuthentication(Authentication.GOST01, Authentication.GOST94);
case "aGOST01": return CipherSuitePredicate.matchAuthentication(Authentication.GOST01);
case "aGOST94": return CipherSuitePredicate.matchAuthentication(Authentication.GOST94);
case "kGOST": return CipherSuitePredicate.matchKeyAgreement(KeyAgreement.GOST);
case "GOST94": return CipherSuitePredicate.matchDigest(Digest.GOST94);
case "GOST89MAC": return CipherSuitePredicate.matchDigest(Digest.GOST89MAC);
case "kPSK": return CipherSuitePredicate.matchKeyAgreement(KeyAgreement.PSK);
case "aPSK": return CipherSuitePredicate.matchAuthentication(Authentication.PSK);
case "PSK": return CipherSuitePredicate.matchAny(CipherSuitePredicate.matchAuthentication(Authentication.PSK), CipherSuitePredicate.matchKeyAgreement(KeyAgreement.PSK));
case "kRSAPSK": // synonym
case "RSAPSK": return CipherSuitePredicate.matchKeyAgreement(KeyAgreement.RSAPSK);
case "DHEPSK": // synonym
case "EDHPSK": // synonym
case "kDHEPSK": // synonym
case "kEDHPSK": return CipherSuitePredicate.matchKeyAgreement(KeyAgreement.DHEPSK);
case "EECDHPSK": // synonym
case "kEECDHPSK": // synonym
case "ECDHEPSK": // synonym
case "kECDEHPSK": return CipherSuitePredicate.matchKeyAgreement(KeyAgreement.ECDHEPSK);
default: {
final MechanismDatabase database = MechanismDatabase.getInstance();
MechanismDatabase.Entry entry = database.getCipherSuiteOpenSSLName(word);
if (entry == null) {
entry = database.getCipherSuite(word);
}
if (entry == null) {
return null;
}
return CipherSuitePredicate.matchName(word);
}
}
}
/* -- selector impls -- */
static final class AddingCipherSuiteSelector extends CipherSuiteSelector {
private final CipherSuitePredicate predicate;
AddingCipherSuiteSelector(final CipherSuiteSelector next, final CipherSuitePredicate predicate) {
super(next);
this.predicate = predicate;
}
void applyFilter(final Set enabled, final Map supported) {
for (Map.Entry item : supported.entrySet()) {
final MechanismDatabase.Entry entry = item.getKey();
if (predicate.test(entry)) {
if (enabled.add(item.getValue())) {
ElytronMessages.tls.tracef("Adding cipher suite %s due to add rule", entry);
} else {
ElytronMessages.tls.tracef("Would have added cipher suite %s due to add rule, but it was already added previously", entry);
}
}
}
}
void toString(final StringBuilder b) {
if (prev != null && prev != EMPTY) {
prev.toString(b);
b.append(", then ");
}
b.append("add ");
predicate.toString(b);
}
}
static final class RemovingCipherSuiteSelector extends CipherSuiteSelector {
private final CipherSuitePredicate predicate;
RemovingCipherSuiteSelector(final CipherSuiteSelector next, final CipherSuitePredicate predicate) {
super(next);
this.predicate = predicate;
}
void applyFilter(final Set enabled, final Map supported) {
for (Map.Entry item : supported.entrySet()) {
final MechanismDatabase.Entry entry = item.getKey();
if (predicate.test(entry)) {
if (enabled.remove(item.getValue())) {
ElytronMessages.tls.tracef("Removing cipher suite %s due to remove rule", entry);
} else {
ElytronMessages.tls.tracef("Would have removed cipher suite %s due to remove rule, but it already wasn't present", entry);
}
}
}
}
void toString(final StringBuilder b) {
if (prev != null && prev != EMPTY) {
prev.toString(b);
b.append(", then ");
}
b.append("remove ");
predicate.toString(b);
}
}
static final class FullyDeletingCipherSuiteSelector extends CipherSuiteSelector {
private final CipherSuitePredicate predicate;
FullyDeletingCipherSuiteSelector(final CipherSuiteSelector next, final CipherSuitePredicate predicate) {
super(next);
this.predicate = predicate;
}
void applyFilter(final Set enabled, final Map supported) {
Iterator> iterator = supported.entrySet().iterator();
while (iterator.hasNext()) {
final Map.Entry item = iterator.next();
final MechanismDatabase.Entry entry = item.getKey();
if (predicate.test(entry)) {
iterator.remove();
enabled.remove(item.getValue());
ElytronMessages.tls.tracef("Fully removing cipher suite %s due to full remove rule", entry);
}
}
}
void toString(final StringBuilder b) {
if (prev != null && prev != EMPTY) {
prev.toString(b);
b.append(", then ");
}
b.append("remove fully ");
predicate.toString(b);
}
}
static final class PushToEndCipherSuiteSelector extends CipherSuiteSelector {
private final CipherSuitePredicate predicate;
PushToEndCipherSuiteSelector(final CipherSuiteSelector next, final CipherSuitePredicate predicate) {
super(next);
this.predicate = predicate;
}
void applyFilter(final Set enabled, final Map supported) {
final MechanismDatabase database = MechanismDatabase.getInstance();
final Iterator iterator = enabled.iterator();
List pushed = null;
while (iterator.hasNext()) {
final String name = iterator.next();
final MechanismDatabase.Entry entry = database.getCipherSuite(name);
if (predicate.test(entry)) {
if (pushed == null) pushed = new ArrayList<>();
pushed.add(name);
iterator.remove();
ElytronMessages.tls.tracef("Pushing cipher suite %s to end due to push rule", entry);
}
}
if (pushed != null) {
// add back in order
enabled.addAll(pushed);
}
}
void toString(final StringBuilder b) {
if (prev != null && prev != EMPTY) {
prev.toString(b);
b.append(", then ");
}
b.append("push to end ");
predicate.toString(b);
}
}
static final class SortByAlgorithmKeyLengthCipherSuiteSelector extends CipherSuiteSelector {
SortByAlgorithmKeyLengthCipherSuiteSelector(final CipherSuiteSelector prev) {
super(prev);
}
void applyFilter(final Set enabled, final Map supported) {
if (! enabled.isEmpty()) {
final ArrayList list = new ArrayList<>(enabled);
// stable sort
Collections.sort(list, (o1, o2) -> {
final MechanismDatabase database = MechanismDatabase.getInstance();
final MechanismDatabase.Entry e1 = database.getCipherSuite(o1);
final MechanismDatabase.Entry e2 = database.getCipherSuite(o2);
return Integer.signum(e2.getAlgorithmBits() - e1.getAlgorithmBits());
});
enabled.clear();
enabled.addAll(list);
if (ElytronMessages.tls.isTraceEnabled()) {
StringBuilder b = new StringBuilder(list.size() * 16);
b.append("Sorted ciphers by algorithm key length, result is:");
for (String s : list) {
b.append("\n ").append(s);
}
ElytronMessages.tls.trace(b);
}
}
}
void toString(final StringBuilder b) {
if (prev != null && prev != EMPTY) {
prev.toString(b);
b.append(", then ");
}
b.append("sort by key length");
}
}
}