001/**
002 * Logback: the reliable, generic, fast and flexible logging framework.
003 * Copyright (C) 1999-2015, 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 v1.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;
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        if (hostnameVerification != null) {
060            addInfo("hostnameVerification=" + hostnameVerification);
061            socket.setHostnameVerification(hostnameVerification);
062        }
063    }
064
065    public boolean getHostnameVerification() {
066        if (hostnameVerification == null)
067            return false;
068        return hostnameVerification;
069    }
070
071    public void setHostnameVerification(boolean hostnameVerification) {
072        this.hostnameVerification = hostnameVerification;
073    }
074
075    /**
076     * Gets the set of enabled protocols based on the configuration.
077     * 
078     * @param supportedProtocols protocols supported by the SSL engine
079     * @param defaultProtocols   default protocols enabled by the SSL engine
080     * @return enabled protocols
081     */
082    private String[] enabledProtocols(String[] supportedProtocols, String[] defaultProtocols) {
083        if (enabledProtocols == null) {
084            // we're assuming that the same engine is used for all configurables
085            // so once we determine the enabled set, we won't do it again
086            if (OptionHelper.isNullOrEmptyOrAllSpaces(getIncludedProtocols())
087                    && OptionHelper.isNullOrEmptyOrAllSpaces(getExcludedProtocols())) {
088                enabledProtocols = Arrays.copyOf(defaultProtocols, defaultProtocols.length);
089            } else {
090                enabledProtocols = includedStrings(supportedProtocols, getIncludedProtocols(), getExcludedProtocols());
091            }
092            for (String protocol : enabledProtocols) {
093                addInfo("enabled protocol: " + protocol);
094            }
095        }
096        return enabledProtocols;
097    }
098
099    /**
100     * Gets the set of enabled cipher suites based on the configuration.
101     * 
102     * @param supportedCipherSuites cipher suites supported by the SSL engine
103     * @param defaultCipherSuites   default cipher suites enabled by the SSL engine
104     * @return enabled cipher suites
105     */
106    private String[] enabledCipherSuites(String[] supportedCipherSuites, String[] defaultCipherSuites) {
107        if (enabledCipherSuites == null) {
108            // we're assuming that the same engine is used for all configurables
109            // so once we determine the enabled set, we won't do it again
110            if (OptionHelper.isNullOrEmptyOrAllSpaces(getIncludedCipherSuites())
111                    && OptionHelper.isNullOrEmptyOrAllSpaces(getExcludedCipherSuites())) {
112                enabledCipherSuites = Arrays.copyOf(defaultCipherSuites, defaultCipherSuites.length);
113            } else {
114                enabledCipherSuites = includedStrings(supportedCipherSuites, getIncludedCipherSuites(),
115                        getExcludedCipherSuites());
116            }
117            for (String cipherSuite : enabledCipherSuites) {
118                addInfo("enabled cipher suite: " + cipherSuite);
119            }
120        }
121        return enabledCipherSuites;
122    }
123
124    /**
125     * Applies include and exclude patterns to an array of default string values to
126     * produce an array of strings included by the patterns.
127     * 
128     * @param defaults default list of string values
129     * @param included comma-separated patterns that identity values to include
130     * @param excluded comma-separated patterns that identity string to exclude
131     * @return an array of strings containing those strings from {@code defaults}
132     *         that match at least one pattern in {@code included} that are not
133     *         matched by any pattern in {@code excluded}
134     */
135    private String[] includedStrings(String[] defaults, String included, String excluded) {
136        List<String> values = new ArrayList<String>(defaults.length);
137        values.addAll(Arrays.asList(defaults));
138        if (included != null) {
139            StringCollectionUtil.retainMatching(values, stringToArray(included));
140        }
141        if (excluded != null) {
142            StringCollectionUtil.removeMatching(values, stringToArray(excluded));
143        }
144        return values.toArray(new String[values.size()]);
145    }
146
147    /**
148     * Splits a string containing comma-separated values into an array.
149     * 
150     * @param s the subject string
151     * @return array of values contained in {@code s}
152     */
153    private String[] stringToArray(String s) {
154        return s.split("\\s*,\\s*");
155    }
156
157    /**
158     * Gets the JSSE secure transport protocols to include.
159     * 
160     * @return a string containing comma-separated JSSE secure transport protocol
161     *         names (e.g. {@code TLSv1})
162     */
163    public String getIncludedProtocols() {
164        return includedProtocols;
165    }
166
167    /**
168     * Sets the JSSE secure transport protocols to include.
169     * 
170     * @param protocols a string containing comma-separated JSSE secure transport
171     *                  protocol names
172     * @see Java Cryptography Architecture Standard Algorithm Name Documentation
173     */
174    public void setIncludedProtocols(String protocols) {
175        this.includedProtocols = protocols;
176    }
177
178    /**
179     * Gets the JSSE secure transport protocols to exclude.
180     * 
181     * @return a string containing comma-separated JSSE secure transport protocol
182     *         names (e.g. {@code TLSv1})
183     */
184    public String getExcludedProtocols() {
185        return excludedProtocols;
186    }
187
188    /**
189     * Sets the JSSE secure transport protocols to exclude.
190     * 
191     * @param protocols a string containing comma-separated JSSE secure transport
192     *                  protocol names
193     * @see Java Cryptography Architecture Standard Algorithm Name Documentation
194     */
195    public void setExcludedProtocols(String protocols) {
196        this.excludedProtocols = protocols;
197    }
198
199    /**
200     * Gets the JSSE cipher suite names to include.
201     * 
202     * @return a string containing comma-separated JSSE cipher suite names (e.g.
203     *         {@code TLS_DHE_RSA_WITH_AES_256_CBC_SHA})
204     */
205    public String getIncludedCipherSuites() {
206        return includedCipherSuites;
207    }
208
209    /**
210     * Sets the JSSE cipher suite names to include.
211     * 
212     * @param cipherSuites a string containing comma-separated JSSE cipher suite
213     *                     names
214     * @see Java Cryptography Architecture Standard Algorithm Name Documentation
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     * @param cipherSuites a string containing comma-separated JSSE cipher suite
234     *                     names
235     * @see Java Cryptography Architecture Standard Algorithm Name Documentation
236     */
237    public void setExcludedCipherSuites(String cipherSuites) {
238        this.excludedCipherSuites = cipherSuites;
239    }
240
241    /**
242     * Gets a flag indicating whether client authentication is required.
243     * 
244     * @return flag state
245     */
246    public Boolean isNeedClientAuth() {
247        return needClientAuth;
248    }
249
250    /**
251     * Sets a flag indicating whether client authentication is required.
252     * 
253     * @param needClientAuth the flag state to set
254     */
255    public void setNeedClientAuth(Boolean needClientAuth) {
256        this.needClientAuth = needClientAuth;
257    }
258
259    /**
260     * Gets a flag indicating whether client authentication is desired.
261     * 
262     * @return flag state
263     */
264    public Boolean isWantClientAuth() {
265        return wantClientAuth;
266    }
267
268    /**
269     * Sets a flag indicating whether client authentication is desired.
270     * 
271     * @param wantClientAuth the flag state to set
272     */
273    public void setWantClientAuth(Boolean wantClientAuth) {
274        this.wantClientAuth = wantClientAuth;
275    }
276
277}