/* * 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.Collections; import java.util.EnumMap; import java.util.EnumSet; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; /** * An immutable filter for SSL/TLS protocols. * * @author <a href="mailto:david.lloyd@redhat.com">David M. Lloyd</a> */ public abstract class ProtocolSelector { final ProtocolSelector prev; ProtocolSelector(final ProtocolSelector prev) { this.prev = prev; } /* -- predicates -- */ private static final ProtocolSelector EMPTY = new ProtocolSelector(null) { void applyFilter(final Set<Protocol> enabled, final EnumMap<Protocol, String> supported) { } void toString(final StringBuilder b) { b.append("(empty)"); } }; public final String toString() { final StringBuilder b = new StringBuilder(); toString(b); return b.toString(); } abstract void toString(final StringBuilder b); /** * Get the basic empty SSL protocol selector. * * @return the empty selector */ public static ProtocolSelector empty() { return EMPTY; } /** * Get the default SSL protocol selector. * * @return the default selector */ public static ProtocolSelector defaultProtocols() { return DEFAULT_SELECTOR; } /* -- Put this after the EMPTY selector for proper static ordering -- */ static final ProtocolSelector DEFAULT_SELECTOR = empty().add(Protocol.TLSv1, Protocol.TLSv1_1, Protocol.TLSv1_2, Protocol.TLSv1_3); /* -- delete -- */ /** * Permanently delete the given protocol. Matching protocols cannot * be re-added by a later rule (such rules will be ignored). * * @param protocolName the name of the protocol to remove * @return a new selector which includes the new rule */ public ProtocolSelector deleteFully(final String protocolName) { return deleteFully(Protocol.forName(protocolName)); } /** * Permanently delete the given protocol. Matching protocols cannot * be re-added by a later rule (such rules will be ignored). * * @param protocol the protocol to remove * @return a new selector which includes the new rule */ public ProtocolSelector deleteFully(final Protocol protocol) { return protocol == null ? this : new FullyDeletingProtocolSelector(this, EnumSet.of(protocol)); } /** * Permanently delete all of the given protocols. Matching protocols cannot * be re-added by a later rule (such rules will be ignored). * * @param protocols the protocols to remove * @return a new selector which includes the new rule */ public ProtocolSelector deleteFully(final Protocol... protocols) { return protocols == null || protocols.length == 0 ? this : new FullyDeletingProtocolSelector(this, EnumSet.of(protocols[0], protocols)); } /** * Permanently delete all of the given protocols. Matching protocols cannot * be re-added by a later rule (such rules will be ignored). * * @param protocols the protocols to remove * @return a new selector which includes the new rule */ public ProtocolSelector deleteFully(final EnumSet<Protocol> protocols) { return protocols == null || protocols.isEmpty() ? this : new FullyDeletingProtocolSelector(this, protocols); } /* -- remove -- */ /** * Remove the given protocol. Matching protocols may be re-added by a later rule. * * @param protocolName the name of the protocol to remove * @return a new selector which includes the new rule */ public ProtocolSelector remove(final String protocolName) { return remove(Protocol.forName(protocolName)); } /** * Remove the given protocol. Matching protocols may be re-added by a later rule. * * @param protocol the protocol to remove * @return a new selector which includes the new rule */ public ProtocolSelector remove(final Protocol protocol) { return protocol == null ? this : new RemovingProtocolSelector(this, EnumSet.of(protocol)); } /** * Remove the given protocols. Matching protocols may be re-added by a later rule. * * @param protocols the protocols to remove * @return a new selector which includes the new rule */ public ProtocolSelector remove(final Protocol... protocols) { return protocols == null || protocols.length == 0 ? this : new RemovingProtocolSelector(this, EnumSet.of(protocols[0], protocols)); } /** * Remove the given protocols. Matching protocols may be re-added by a later rule. * * @param protocols the protocols to remove * @return a new selector which includes the new rule */ public ProtocolSelector remove(final EnumSet<Protocol> protocols) { return protocols == null || protocols.isEmpty() ? this : new RemovingProtocolSelector(this, protocols); } /* -- add -- */ /** * Add the given protocol. * * @param protocolName the name of the protocol to add * @return a new selector which includes the new rule */ public ProtocolSelector add(final String protocolName) { return add(Protocol.forName(protocolName)); } /** * Add the given protocol. * * @param protocol the protocol to add * @return a new selector which includes the new rule */ public ProtocolSelector add(final Protocol protocol) { return protocol == null ? this : new AddingProtocolSelector(this, EnumSet.of(protocol)); } /** * Add the given protocols. * * @param protocols the protocols to add * @return a new selector which includes the new rule */ public ProtocolSelector add(final Protocol... protocols) { return protocols == null || protocols.length == 0 ? this : new AddingProtocolSelector(this, EnumSet.of(protocols[0], protocols)); } /** * Add the given protocols. * * @param protocols the protocols to add * @return a new selector which includes the new rule */ public ProtocolSelector add(final EnumSet<Protocol> protocols) { return protocols == null || protocols.isEmpty() ? this : new AddingProtocolSelector(this, protocols); } /* -- selector implementation -- */ abstract void applyFilter(Set<Protocol> enabled, EnumMap<Protocol, String> supported); private void doEvaluate(Set<Protocol> enabled, EnumMap<Protocol, String> supported) { if (prev != null) { prev.doEvaluate(enabled, supported); } applyFilter(enabled, supported); } /** * Evaluate this selector against the given list of JSSE supported protocols. * * @param supportedProtocols the supported protocols * @return the enabled protocols (not {@code null}) */ public final String[] evaluate(String[] supportedProtocols) { final EnumMap<Protocol, String> supported = new EnumMap<Protocol, String>(Protocol.class); for (String protocolName : supportedProtocols) { final Protocol protocol = Protocol.forName(protocolName); if (protocol != null) { supported.put(protocol, protocolName); } } final LinkedHashSet<Protocol> enabledSet = new LinkedHashSet<>(supported.size()); doEvaluate(enabledSet, supported); final ArrayList<String> list = new ArrayList<>(enabledSet.size()); for (Protocol protocol : enabledSet) { list.add(supported.get(protocol)); } return list.toArray(new String[enabledSet.size()]); } /* -- selector impls -- */ static final class AddingProtocolSelector extends ProtocolSelector { private final EnumSet<Protocol> protocols; AddingProtocolSelector(final ProtocolSelector prev, final EnumSet<Protocol> protocols) { super(prev); this.protocols = protocols; } void toString(final StringBuilder b) { if (prev != null && prev != EMPTY) { prev.toString(b); b.append(", then "); } b.append("add protocols ("); Iterator<Protocol> iterator = protocols.iterator(); Protocol protocol; if (iterator.hasNext()) { protocol = iterator.next(); b.append(protocol); while (iterator.hasNext()) { protocol = iterator.next(); b.append(", "); b.append(protocol); } } b.append(")"); } void applyFilter(final Set<Protocol> enabled, final EnumMap<Protocol, String> supported) { final List<Protocol> clone = new ArrayList<>(supported.keySet()); clone.retainAll(protocols); // it will be in reverse-preference order due to the ordering of the enum Collections.reverse(clone); enabled.addAll(clone); } } static final class RemovingProtocolSelector extends ProtocolSelector { private final EnumSet<Protocol> protocols; RemovingProtocolSelector(final ProtocolSelector prev, final EnumSet<Protocol> protocols) { super(prev); this.protocols = protocols; } void toString(final StringBuilder b) { if (prev != null && prev != EMPTY) { prev.toString(b); b.append(", then "); } b.append("remove protocols ("); Iterator<Protocol> iterator = protocols.iterator(); Protocol protocol; if (iterator.hasNext()) { protocol = iterator.next(); b.append(protocol); while (iterator.hasNext()) { protocol = iterator.next(); b.append(", "); b.append(protocol); } } b.append(")"); } void applyFilter(final Set<Protocol> enabled, final EnumMap<Protocol, String> supported) { enabled.removeAll(protocols); } } static class FullyDeletingProtocolSelector extends ProtocolSelector { private final EnumSet<Protocol> protocols; FullyDeletingProtocolSelector(final ProtocolSelector prev, final EnumSet<Protocol> protocols) { super(prev); this.protocols = protocols; } void toString(final StringBuilder b) { if (prev != null && prev != EMPTY) { prev.toString(b); b.append(", then "); } b.append("fully remove protocols ("); Iterator<Protocol> iterator = protocols.iterator(); Protocol protocol; if (iterator.hasNext()) { protocol = iterator.next(); b.append(protocol); while (iterator.hasNext()) { protocol = iterator.next(); b.append(", "); b.append(protocol); } } b.append(")"); } void applyFilter(final Set<Protocol> enabled, final EnumMap<Protocol, String> supported) { enabled.removeAll(protocols); for (Protocol protocol : protocols) { supported.remove(protocol); } } } }