001/*
002 * Logback: the reliable, generic, fast and flexible logging framework.
003 * Copyright (C) 1999-2026, QOS.ch. All rights reserved.
004 *
005 * This program and the accompanying materials are dual-licensed under
006 * either the terms of the Eclipse Public License v2.0 as published by
007 * the Eclipse Foundation
008 *
009 *   or (per the licensee's choosing)
010 *
011 * under the terms of the GNU Lesser General Public License version 2.1
012 * as published by the Free Software Foundation.
013 */
014package ch.qos.logback.core.net.ssl;
015
016import java.util.ArrayList;
017import java.util.Arrays;
018import java.util.List;
019
020import javax.net.ssl.SSLEngine;
021
022import ch.qos.logback.core.spi.ContextAwareBase;
023import ch.qos.logback.core.util.OptionHelper;
024import ch.qos.logback.core.util.StringCollectionUtil;
025
026/**
027 * A configuration of SSL parameters for an {@link SSLEngine}.
028 *
029 * @author Carl Harris
030 * @author Bruno Harbulot
031 */
032public class SSLParametersConfiguration extends ContextAwareBase {
033
034    private String includedProtocols;
035    private String excludedProtocols;
036    private String includedCipherSuites;
037    private String excludedCipherSuites;
038    private Boolean needClientAuth;
039    private Boolean wantClientAuth;
040    private String[] enabledProtocols;
041    private String[] enabledCipherSuites;
042    private boolean hostnameVerification = true;
043
044    /**
045     * Configures SSL parameters on an {@link SSLConfigurable}.
046     * 
047     * @param socket the subject configurable
048     */
049    public void configure(SSLConfigurable socket) {
050        socket.setEnabledProtocols(enabledProtocols(socket.getSupportedProtocols(), socket.getDefaultProtocols()));
051        socket.setEnabledCipherSuites(
052                enabledCipherSuites(socket.getSupportedCipherSuites(), socket.getDefaultCipherSuites()));
053        if (isNeedClientAuth() != null) {
054            socket.setNeedClientAuth(isNeedClientAuth());
055        }
056        if (isWantClientAuth() != null) {
057            socket.setWantClientAuth(isWantClientAuth());
058        }
059
060        addInfo("hostnameVerification=" + hostnameVerification);
061        socket.setHostnameVerification(hostnameVerification);
062    }
063
064    public boolean getHostnameVerification() {
065        return hostnameVerification;
066    }
067
068    public void setHostnameVerification(boolean hostnameVerification) {
069        this.hostnameVerification = hostnameVerification;
070    }
071
072    /**
073     * Gets the set of enabled protocols based on the configuration.
074     * 
075     * @param supportedProtocols protocols supported by the SSL engine
076     * @param defaultProtocols   default protocols enabled by the SSL engine
077     * @return enabled protocols
078     */
079    private String[] enabledProtocols(String[] supportedProtocols, String[] defaultProtocols) {
080        if (enabledProtocols == null) {
081            // we're assuming that the same engine is used for all configurables
082            // so once we determine the enabled set, we won't do it again
083            if (OptionHelper.isNullOrEmptyOrAllSpaces(getIncludedProtocols())
084                    && OptionHelper.isNullOrEmptyOrAllSpaces(getExcludedProtocols())) {
085                enabledProtocols = Arrays.copyOf(defaultProtocols, defaultProtocols.length);
086            } else {
087                enabledProtocols = includedStrings(supportedProtocols, getIncludedProtocols(), getExcludedProtocols());
088            }
089            for (String protocol : enabledProtocols) {
090                addInfo("enabled protocol: " + protocol);
091            }
092        }
093        return enabledProtocols;
094    }
095
096    /**
097     * Gets the set of enabled cipher suites based on the configuration.
098     * 
099     * @param supportedCipherSuites cipher suites supported by the SSL engine
100     * @param defaultCipherSuites   default cipher suites enabled by the SSL engine
101     * @return enabled cipher suites
102     */
103    private String[] enabledCipherSuites(String[] supportedCipherSuites, String[] defaultCipherSuites) {
104        if (enabledCipherSuites == null) {
105            // we're assuming that the same engine is used for all configurables
106            // so once we determine the enabled set, we won't do it again
107            if (OptionHelper.isNullOrEmptyOrAllSpaces(getIncludedCipherSuites())
108                    && OptionHelper.isNullOrEmptyOrAllSpaces(getExcludedCipherSuites())) {
109                enabledCipherSuites = Arrays.copyOf(defaultCipherSuites, defaultCipherSuites.length);
110            } else {
111                enabledCipherSuites = includedStrings(supportedCipherSuites, getIncludedCipherSuites(),
112                        getExcludedCipherSuites());
113            }
114            for (String cipherSuite : enabledCipherSuites) {
115                addInfo("enabled cipher suite: " + cipherSuite);
116            }
117        }
118        return enabledCipherSuites;
119    }
120
121    /**
122     * Applies include and exclude patterns to an array of default string values to
123     * produce an array of strings included by the patterns.
124     * 
125     * @param defaults default list of string values
126     * @param included comma-separated patterns that identity values to include
127     * @param excluded comma-separated patterns that identity string to exclude
128     * @return an array of strings containing those strings from {@code defaults}
129     *         that match at least one pattern in {@code included} that are not
130     *         matched by any pattern in {@code excluded}
131     */
132    private String[] includedStrings(String[] defaults, String included, String excluded) {
133        List<String> values = new ArrayList<String>(defaults.length);
134        values.addAll(Arrays.asList(defaults));
135        if (included != null) {
136            StringCollectionUtil.retainMatching(values, stringToArray(included));
137        }
138        if (excluded != null) {
139            StringCollectionUtil.removeMatching(values, stringToArray(excluded));
140        }
141        return values.toArray(new String[values.size()]);
142    }
143
144    /**
145     * Splits a string containing comma-separated values into an array.
146     * 
147     * @param s the subject string
148     * @return array of values contained in {@code s}
149     */
150    private String[] stringToArray(String s) {
151        return s.split("\\s*,\\s*");
152    }
153
154    /**
155     * Gets the JSSE secure transport protocols to include.
156     * 
157     * @return a string containing comma-separated JSSE secure transport protocol
158     *         names (e.g. {@code TLSv1})
159     */
160    public String getIncludedProtocols() {
161        return includedProtocols;
162    }
163
164    /**
165     * Sets the JSSE secure transport protocols to include.
166     *
167     * <p>See Java Cryptography Architecture Standard Algorithm Name Documentation</p>
168     *
169     * @param protocols a string containing comma-separated JSSE secure transport
170     *                  protocol names
171     */
172    public void setIncludedProtocols(String protocols) {
173        this.includedProtocols = protocols;
174    }
175
176    /**
177     * Gets the JSSE secure transport protocols to exclude.
178     * 
179     * @return a string containing comma-separated JSSE secure transport protocol
180     *         names (e.g. {@code TLSv1})
181     */
182    public String getExcludedProtocols() {
183        return excludedProtocols;
184    }
185
186    /**
187     * Sets the JSSE secure transport protocols to exclude.
188     *
189     * <p>See Java Cryptography Architecture Standard Algorithm Name Documentation</p>
190     *
191     * @param protocols a string containing comma-separated JSSE secure transport
192     *                  protocol names
193     */
194    public void setExcludedProtocols(String protocols) {
195        this.excludedProtocols = protocols;
196    }
197
198    /**
199     * Gets the JSSE cipher suite names to include.
200     * 
201     * @return a string containing comma-separated JSSE cipher suite names (e.g.
202     *         {@code TLS_DHE_RSA_WITH_AES_256_CBC_SHA})
203     */
204    public String getIncludedCipherSuites() {
205        return includedCipherSuites;
206    }
207
208    /**
209     * Sets the JSSE cipher suite names to include.
210     *
211     * <p>See Java Cryptography Architecture Standard Algorithm Name Documentation</p>
212     *
213     * @param cipherSuites a string containing comma-separated JSSE cipher suite
214     *                     names
215     */
216    public void setIncludedCipherSuites(String cipherSuites) {
217        this.includedCipherSuites = cipherSuites;
218    }
219
220    /**
221     * Gets the JSSE cipher suite names to exclude.
222     * 
223     * @return a string containing comma-separated JSSE cipher suite names (e.g.
224     *         {@code TLS_DHE_RSA_WITH_AES_256_CBC_SHA})
225     */
226    public String getExcludedCipherSuites() {
227        return excludedCipherSuites;
228    }
229
230    /**
231     * Sets the JSSE cipher suite names to exclude.
232     *
233     * <p>See Java Cryptography Architecture Standard Algorithm Name Documentation</p>
234     *
235     * @param cipherSuites a string containing comma-separated JSSE cipher suite
236     *                     names
237     *
238     */
239    public void setExcludedCipherSuites(String cipherSuites) {
240        this.excludedCipherSuites = cipherSuites;
241    }
242
243    /**
244     * Gets a flag indicating whether client authentication is required.
245     * 
246     * @return flag state
247     */
248    public Boolean isNeedClientAuth() {
249        return needClientAuth;
250    }
251
252    /**
253     * Sets a flag indicating whether client authentication is required.
254     * 
255     * @param needClientAuth the flag state to set
256     */
257    public void setNeedClientAuth(Boolean needClientAuth) {
258        this.needClientAuth = needClientAuth;
259    }
260
261    /**
262     * Gets a flag indicating whether client authentication is desired.
263     * 
264     * @return flag state
265     */
266    public Boolean isWantClientAuth() {
267        return wantClientAuth;
268    }
269
270    /**
271     * Sets a flag indicating whether client authentication is desired.
272     * 
273     * @param wantClientAuth the flag state to set
274     */
275    public void setWantClientAuth(Boolean wantClientAuth) {
276        this.wantClientAuth = wantClientAuth;
277    }
278
279}