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     * <p>See Java Cryptography Architecture Standard Algorithm Name Documentation</p>
171     *
172     * @param protocols a string containing comma-separated JSSE secure transport
173     *                  protocol names
174     */
175    public void setIncludedProtocols(String protocols) {
176        this.includedProtocols = protocols;
177    }
178
179    /**
180     * Gets the JSSE secure transport protocols to exclude.
181     * 
182     * @return a string containing comma-separated JSSE secure transport protocol
183     *         names (e.g. {@code TLSv1})
184     */
185    public String getExcludedProtocols() {
186        return excludedProtocols;
187    }
188
189    /**
190     * Sets the JSSE secure transport protocols to exclude.
191     *
192     * <p>See Java Cryptography Architecture Standard Algorithm Name Documentation</p>
193     *
194     * @param protocols a string containing comma-separated JSSE secure transport
195     *                  protocol names
196     */
197    public void setExcludedProtocols(String protocols) {
198        this.excludedProtocols = protocols;
199    }
200
201    /**
202     * Gets the JSSE cipher suite names to include.
203     * 
204     * @return a string containing comma-separated JSSE cipher suite names (e.g.
205     *         {@code TLS_DHE_RSA_WITH_AES_256_CBC_SHA})
206     */
207    public String getIncludedCipherSuites() {
208        return includedCipherSuites;
209    }
210
211    /**
212     * Sets the JSSE cipher suite names to include.
213     *
214     * <p>See Java Cryptography Architecture Standard Algorithm Name Documentation</p>
215     *
216     * @param cipherSuites a string containing comma-separated JSSE cipher suite
217     *                     names
218     */
219    public void setIncludedCipherSuites(String cipherSuites) {
220        this.includedCipherSuites = cipherSuites;
221    }
222
223    /**
224     * Gets the JSSE cipher suite names to exclude.
225     * 
226     * @return a string containing comma-separated JSSE cipher suite names (e.g.
227     *         {@code TLS_DHE_RSA_WITH_AES_256_CBC_SHA})
228     */
229    public String getExcludedCipherSuites() {
230        return excludedCipherSuites;
231    }
232
233    /**
234     * Sets the JSSE cipher suite names to exclude.
235     *
236     * <p>See Java Cryptography Architecture Standard Algorithm Name Documentation</p>
237     *
238     * @param cipherSuites a string containing comma-separated JSSE cipher suite
239     *                     names
240     *
241     */
242    public void setExcludedCipherSuites(String cipherSuites) {
243        this.excludedCipherSuites = cipherSuites;
244    }
245
246    /**
247     * Gets a flag indicating whether client authentication is required.
248     * 
249     * @return flag state
250     */
251    public Boolean isNeedClientAuth() {
252        return needClientAuth;
253    }
254
255    /**
256     * Sets a flag indicating whether client authentication is required.
257     * 
258     * @param needClientAuth the flag state to set
259     */
260    public void setNeedClientAuth(Boolean needClientAuth) {
261        this.needClientAuth = needClientAuth;
262    }
263
264    /**
265     * Gets a flag indicating whether client authentication is desired.
266     * 
267     * @return flag state
268     */
269    public Boolean isWantClientAuth() {
270        return wantClientAuth;
271    }
272
273    /**
274     * Sets a flag indicating whether client authentication is desired.
275     * 
276     * @param wantClientAuth the flag state to set
277     */
278    public void setWantClientAuth(Boolean wantClientAuth) {
279        this.wantClientAuth = wantClientAuth;
280    }
281
282}