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 org.codehaus.janino.Java;
023
024import ch.qos.logback.core.spi.ContextAwareBase;
025import ch.qos.logback.core.util.OptionHelper;
026import ch.qos.logback.core.util.StringCollectionUtil;
027
028/**
029 * A configuration of SSL parameters for an {@link SSLEngine}.
030 *
031 * @author Carl Harris
032 * @author Bruno Harbulot
033 */
034public class SSLParametersConfiguration extends ContextAwareBase {
035
036    private String includedProtocols;
037    private String excludedProtocols;
038    private String includedCipherSuites;
039    private String excludedCipherSuites;
040    private Boolean needClientAuth;
041    private Boolean wantClientAuth;
042    private String[] enabledProtocols;
043    private String[] enabledCipherSuites;
044    private Boolean hostnameVerification;
045
046    /**
047     * Configures SSL parameters on an {@link SSLConfigurable}.
048     * 
049     * @param socket the subject configurable
050     */
051    public void configure(SSLConfigurable socket) {
052        socket.setEnabledProtocols(enabledProtocols(socket.getSupportedProtocols(), socket.getDefaultProtocols()));
053        socket.setEnabledCipherSuites(
054                enabledCipherSuites(socket.getSupportedCipherSuites(), socket.getDefaultCipherSuites()));
055        if (isNeedClientAuth() != null) {
056            socket.setNeedClientAuth(isNeedClientAuth());
057        }
058        if (isWantClientAuth() != null) {
059            socket.setWantClientAuth(isWantClientAuth());
060        }
061        if (hostnameVerification != null) {
062            addInfo("hostnameVerification=" + hostnameVerification);
063            socket.setHostnameVerification(hostnameVerification);
064        }
065    }
066
067    public boolean getHostnameVerification() {
068        if (hostnameVerification == null)
069            return false;
070        return hostnameVerification;
071    }
072
073    public void setHostnameVerification(boolean hostnameVerification) {
074        this.hostnameVerification = hostnameVerification;
075    }
076
077    /**
078     * Gets the set of enabled protocols based on the configuration.
079     * 
080     * @param supportedProtocols protocols supported by the SSL engine
081     * @param defaultProtocols   default protocols enabled by the SSL engine
082     * @return enabled protocols
083     */
084    private String[] enabledProtocols(String[] supportedProtocols, String[] defaultProtocols) {
085        if (enabledProtocols == null) {
086            // we're assuming that the same engine is used for all configurables
087            // so once we determine the enabled set, we won't do it again
088            if (OptionHelper.isNullOrEmpty(getIncludedProtocols())
089                    && OptionHelper.isNullOrEmpty(getExcludedProtocols())) {
090                enabledProtocols = Arrays.copyOf(defaultProtocols, defaultProtocols.length);
091            } else {
092                enabledProtocols = includedStrings(supportedProtocols, getIncludedProtocols(), getExcludedProtocols());
093            }
094            for (String protocol : enabledProtocols) {
095                addInfo("enabled protocol: " + protocol);
096            }
097        }
098        return enabledProtocols;
099    }
100
101    /**
102     * Gets the set of enabled cipher suites based on the configuration.
103     * 
104     * @param supportedCipherSuites cipher suites supported by the SSL engine
105     * @param defaultCipherSuites   default cipher suites enabled by the SSL engine
106     * @return enabled cipher suites
107     */
108    private String[] enabledCipherSuites(String[] supportedCipherSuites, String[] defaultCipherSuites) {
109        if (enabledCipherSuites == null) {
110            // we're assuming that the same engine is used for all configurables
111            // so once we determine the enabled set, we won't do it again
112            if (OptionHelper.isNullOrEmpty(getIncludedCipherSuites())
113                    && OptionHelper.isNullOrEmpty(getExcludedCipherSuites())) {
114                enabledCipherSuites = Arrays.copyOf(defaultCipherSuites, defaultCipherSuites.length);
115            } else {
116                enabledCipherSuites = includedStrings(supportedCipherSuites, getIncludedCipherSuites(),
117                        getExcludedCipherSuites());
118            }
119            for (String cipherSuite : enabledCipherSuites) {
120                addInfo("enabled cipher suite: " + cipherSuite);
121            }
122        }
123        return enabledCipherSuites;
124    }
125
126    /**
127     * Applies include and exclude patterns to an array of default string values to
128     * produce an array of strings included by the patterns.
129     * 
130     * @param defaults default list of string values
131     * @param included comma-separated patterns that identity values to include
132     * @param excluded comma-separated patterns that identity string to exclude
133     * @return an array of strings containing those strings from {@code defaults}
134     *         that match at least one pattern in {@code included} that are not
135     *         matched by any pattern in {@code excluded}
136     */
137    private String[] includedStrings(String[] defaults, String included, String excluded) {
138        List<String> values = new ArrayList<String>(defaults.length);
139        values.addAll(Arrays.asList(defaults));
140        if (included != null) {
141            StringCollectionUtil.retainMatching(values, stringToArray(included));
142        }
143        if (excluded != null) {
144            StringCollectionUtil.removeMatching(values, stringToArray(excluded));
145        }
146        return values.toArray(new String[values.size()]);
147    }
148
149    /**
150     * Splits a string containing comma-separated values into an array.
151     * 
152     * @param s the subject string
153     * @return array of values contained in {@code s}
154     */
155    private String[] stringToArray(String s) {
156        return s.split("\\s*,\\s*");
157    }
158
159    /**
160     * Gets the JSSE secure transport protocols to include.
161     * 
162     * @return a string containing comma-separated JSSE secure transport protocol
163     *         names (e.g. {@code TLSv1})
164     */
165    public String getIncludedProtocols() {
166        return includedProtocols;
167    }
168
169    /**
170     * Sets the JSSE secure transport protocols to include.
171     * 
172     * @param protocols a string containing comma-separated JSSE secure transport
173     *                  protocol names
174     * @see Java Cryptography Architecture Standard Algorithm Name Documentation
175     */
176    public void setIncludedProtocols(String protocols) {
177        this.includedProtocols = protocols;
178    }
179
180    /**
181     * Gets the JSSE secure transport protocols to exclude.
182     * 
183     * @return a string containing comma-separated JSSE secure transport protocol
184     *         names (e.g. {@code TLSv1})
185     */
186    public String getExcludedProtocols() {
187        return excludedProtocols;
188    }
189
190    /**
191     * Sets the JSSE secure transport protocols to exclude.
192     * 
193     * @param protocols a string containing comma-separated JSSE secure transport
194     *                  protocol names
195     * @see Java Cryptography Architecture Standard Algorithm Name Documentation
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     * @param cipherSuites a string containing comma-separated JSSE cipher suite
215     *                     names
216     * @see Java Cryptography Architecture Standard Algorithm Name Documentation
217     */
218    public void setIncludedCipherSuites(String cipherSuites) {
219        this.includedCipherSuites = cipherSuites;
220    }
221
222    /**
223     * Gets the JSSE cipher suite names to exclude.
224     * 
225     * @return a string containing comma-separated JSSE cipher suite names (e.g.
226     *         {@code TLS_DHE_RSA_WITH_AES_256_CBC_SHA})
227     */
228    public String getExcludedCipherSuites() {
229        return excludedCipherSuites;
230    }
231
232    /**
233     * Sets the JSSE cipher suite names to exclude.
234     * 
235     * @param cipherSuites a string containing comma-separated JSSE cipher suite
236     *                     names
237     * @see Java Cryptography Architecture Standard Algorithm Name Documentation
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}