org.wildfly.security.ssl.CipherSuiteSelector Maven / Gradle / Ivy
Go to download
This artifact provides a single jar that contains all classes required to use remote EJB and JMS, including
all dependencies. It is intended for use by those not using maven, maven users should just import the EJB and
JMS BOM's instead (shaded JAR's cause lots of problems with maven, as it is very easy to inadvertently end up
with different versions on classes on the class path).
/*
* 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");
}
}
}