package net.i2p.util;
/*
* Contains code adapted from:
* Jetty SslContextFactory.java
*
* =======================================================================
* Copyright (c) 1995-2012 Mort Bay Consulting Pty. Ltd.
* ------------------------------------------------------------------------
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* and Apache License v2.0 which accompanies this distribution.
*
* The Eclipse Public License is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* The Apache License v2.0 is available at
* http://www.opensource.org/licenses/apache2.0.php
*
* You may elect to redistribute this code under either of these licenses.
* ========================================================================
*/
/*
* Contains code adapted from:
* Apache httpcomponents PublicSuffixMatcherLoader.java
*
* ====================================================================
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation. For more
* information on the Apache Software Foundation, please see
* <http://www.apache.org/>.
*
*/
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.Arrays;
import java.util.Collections;
import java.util.Locale;
import java.net.InetAddress;
import java.net.Socket;
import java.security.KeyStore;
import java.security.GeneralSecurityException;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLHandshakeException;
import javax.net.ssl.SSLServerSocket;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManagerFactory;
import net.i2p.I2PAppContext;
import net.i2p.crypto.KeyStoreUtil;
import net.i2p.data.DataHelper;
import org.apache.http.conn.ssl.DefaultHostnameVerifier;
import org.apache.http.conn.util.PublicSuffixList;
import org.apache.http.conn.util.PublicSuffixListParser;
import org.apache.http.conn.util.PublicSuffixMatcher;
/**
* Loads trusted ASCII certs from ~/.i2p/certificates/ and $I2P/certificates/.
*
* TODO extend SSLSocketFactory
*
* @author zzz
* @since 0.9.9 moved from ../client, original since 0.8.3
*/
public class I2PSSLSocketFactory {
private static final String PROP_DISABLE = "i2p.disableSSLHostnameVerification";
private static final String PROP_GEOIP_DIR = "geoip.dir";
private static final String GEOIP_DIR_DEFAULT = "geoip";
private static final String GEOIP_FILE_DEFAULT = "geoip.txt";
private static final String COUNTRY_FILE_DEFAULT = "countries.txt";
private static final String PUBLIC_SUFFIX_LIST = "public-suffix-list.txt";
private static PublicSuffixMatcher DEFAULT_MATCHER;
private static boolean _matcherLoaded;
// not in countries.txt, but only the public ones, not the private ones
private static final String[] DEFAULT_TLDS = {
"arpa", "asia", "biz", "cat", "com", "coop",
"edu", "gov", "info", "int", "jobs", "mil",
"mobi", "museum", "name", "net", "org", "post",
"pro", "tel", "travel", "xxx"
};
// not in countries.txt or public-suffix-list.txt
private static final String[] ADDITIONAL_TLDS = {
"i2p", "mooo.com", "onion"
};
/**
* Unmodifiable.
* Public for RouterConsoleRunner.
* @since 0.9.16
*/
public static final List<String> EXCLUDE_PROTOCOLS = Collections.unmodifiableList(Arrays.asList(new String[] {
"SSLv2Hello", "SSLv3"
}));
/**
* Java 7 does not enable 1.1 or 1.2 by default on the client side.
* Java 8 does enable 1.1 and 1.2 by default on the client side.
* ref: http://docs.oracle.com/javase/7/docs/technotes/guides/security/SunProviders.html
* Unmodifiable.
* Public for RouterConsoleRunner.
* @since 0.9.16
*/
public static final List<String> INCLUDE_PROTOCOLS = Collections.unmodifiableList(Arrays.asList(new String[] {
"TLSv1", "TLSv1.1", "TLSv1.2"
}));
/**
* We exclude everything that Java 8 disables by default, plus some others.
* ref: http://docs.oracle.com/javase/8/docs/technotes/guides/security/SunProviders.html
* Unmodifiable.
* Public for RouterConsoleRunner.
* @since 0.9.16
*/
public static final List<String> EXCLUDE_CIPHERS = Collections.unmodifiableList(Arrays.asList(new String[] {
// following are disabled by default in Java 8
"SSL_DH_anon_EXPORT_WITH_DES40_CBC_SHA",
"SSL_DH_anon_EXPORT_WITH_RC4_40_MD5",
"SSL_DH_anon_WITH_3DES_EDE_CBC_SHA",
"SSL_DH_anon_WITH_DES_CBC_SHA",
"SSL_DH_anon_WITH_RC4_128_MD5",
"SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA",
"SSL_DHE_DSS_WITH_DES_CBC_SHA",
"SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA",
"SSL_DHE_RSA_WITH_DES_CBC_SHA",
"SSL_RSA_EXPORT_WITH_DES40_CBC_SHA",
"SSL_RSA_EXPORT_WITH_RC4_40_MD5",
"SSL_RSA_WITH_DES_CBC_SHA",
"SSL_RSA_WITH_NULL_MD5",
"SSL_RSA_WITH_NULL_SHA",
"TLS_DH_anon_WITH_AES_128_CBC_SHA",
"TLS_DH_anon_WITH_AES_128_CBC_SHA256",
"TLS_DH_anon_WITH_AES_128_GCM_SHA256",
"TLS_DH_anon_WITH_AES_256_CBC_SHA",
"TLS_DH_anon_WITH_AES_256_CBC_SHA256",
"TLS_DH_anon_WITH_AES_256_GCM_SHA384",
"TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA",
"TLS_ECDH_anon_WITH_AES_128_CBC_SHA",
"TLS_ECDH_anon_WITH_AES_256_CBC_SHA",
"TLS_ECDH_anon_WITH_NULL_SHA",
"TLS_ECDH_anon_WITH_RC4_128_SHA",
"TLS_ECDH_ECDSA_WITH_NULL_SHA",
"TLS_ECDHE_ECDSA_WITH_NULL_SHA",
"TLS_ECDHE_RSA_WITH_NULL_SHA",
"TLS_ECDH_RSA_WITH_NULL_SHA",
"TLS_KRB5_EXPORT_WITH_DES_CBC_40_MD5",
"TLS_KRB5_EXPORT_WITH_DES_CBC_40_SHA",
"TLS_KRB5_EXPORT_WITH_RC4_40_MD5",
"TLS_KRB5_EXPORT_WITH_RC4_40_SHA",
"TLS_KRB5_WITH_3DES_EDE_CBC_MD5",
"TLS_KRB5_WITH_3DES_EDE_CBC_SHA",
"TLS_KRB5_WITH_DES_CBC_MD5",
"TLS_KRB5_WITH_DES_CBC_SHA",
"TLS_KRB5_WITH_RC4_128_MD5",
"TLS_KRB5_WITH_RC4_128_SHA",
"TLS_RSA_WITH_NULL_SHA256",
// following are disabled because they are SSLv3
"SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA",
"SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA",
"SSL_RSA_WITH_3DES_EDE_CBC_SHA",
"SSL_RSA_WITH_RC4_128_MD5",
"SSL_RSA_WITH_RC4_128_SHA",
// following are disabled because they are RC4
"TLS_ECDH_ECDSA_WITH_RC4_128_SHA",
"TLS_ECDH_RSA_WITH_RC4_128_SHA",
"TLS_ECDHE_ECDSA_WITH_RC4_128_SHA",
"TLS_ECDHE_RSA_WITH_RC4_128_SHA",
// following are disabled because they are 3DES
"TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA",
"TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA",
"TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA",
"TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA",
// following is disabled because it is weak
// see e.g. https://bugzilla.mozilla.org/show_bug.cgi?id=1107787
"TLS_DHE_DSS_WITH_AES_128_CBC_SHA"
// ??? "TLS_DHE_DSS_WITH_AES_256_CBC_SHA"
//
// NOTE:
// If you add anything here, please also add to installer/resources/eepsite/jetty-ssl.xml
//
}));
/**
* Nothing for now.
* There's nothing disabled by default we would want to enable.
* Unmodifiable.
* Public for RouterConsoleRunner.
* @since 0.9.16
*/
public static final List<String> INCLUDE_CIPHERS = Collections.emptyList();
/** the "real" factory */
private final SSLSocketFactory _factory;
private final I2PAppContext _context;
/**
* @param relativeCertPath e.g. "certificates/i2cp"
* @since 0.9.9 was static
*/
public I2PSSLSocketFactory(I2PAppContext context, boolean loadSystemCerts, String relativeCertPath)
throws GeneralSecurityException {
_factory = initSSLContext(context, loadSystemCerts, relativeCertPath);
_context = context;
}
/**
* Returns a socket to the host.
*
* A host argument that's an IP address (instead of a host name)
* is not recommended, as this will probably fail
* SSL certificate validation.
*
* Hostname validation is skipped for localhost addresses, but you still
* must trust the certificate.
*
*/
public Socket createSocket(String host, int port) throws IOException {
SSLSocket rv = (SSLSocket) _factory.createSocket(host, port);
setProtocolsAndCiphers(rv);
verifyHostname(_context, rv, host);
return rv;
}
/**
* Returns a socket to the host.
*
* An InetAddress argument created with an IP address (instead of a host name)
* is not recommended, as this will perform a reverse DNS lookup to
* get the host name for certificate validation, which will probably then fail.
*
* Hostname validation is skipped for localhost addresses, but you still
* must trust the certificate.
*
* @since 0.9.9
*/
public Socket createSocket(InetAddress host, int port) throws IOException {
SSLSocket rv = (SSLSocket) _factory.createSocket(host, port);
setProtocolsAndCiphers(rv);
String name = host.getHostName();
verifyHostname(_context, rv, name);
return rv;
}
/**
* Validate the hostname
*
* ref: https://developer.android.com/training/articles/security-ssl.html
* ref: http://op-co.de/blog/posts/java_sslsocket_mitm/
* ref: http://kevinlocke.name/bits/2012/10/03/ssl-certificate-verification-in-dispatch-and-asynchttpclient/
*
* @throws SSLException on hostname verification failure
* @since 0.9.20
*/
public static void verifyHostname(I2PAppContext ctx, SSLSocket socket, String host) throws SSLException {
Log log = ctx.logManager().getLog(I2PSSLSocketFactory.class);
if (ctx.getBooleanProperty(PROP_DISABLE) ||
host.equals("localhost") ||
host.equals("127.0.0.1") ||
host.equals("::1") ||
host.equals("0:0:0:0:0:0:0:1")) {
if (log.shouldWarn())
log.warn("Skipping hostname validation for " + host);
return;
}
HostnameVerifier hv;
if (SystemVersion.isAndroid()) {
// https://developer.android.com/training/articles/security-ssl.html
hv = HttpsURLConnection.getDefaultHostnameVerifier();
} else {
// haha the above may work for Android but it doesn't in Oracle
//
// quote http://kevinlocke.name/bits/2012/10/03/ssl-certificate-verification-in-dispatch-and-asynchttpclient/ :
// Unlike SSLContext, using the Java default (HttpsURLConnection.getDefaultHostnameVerifier)
// is not a viable option because the default HostnameVerifier expects to only be called
// in the case that there is a mismatch (and therefore always returns false) while some
// of the AsyncHttpClient providers (e.g. Netty, the default) call it on all connections.
// To make matters worse, the check is not trivial (consider SAN and wildcard matching)
// and is implemented in sun.security.util.HostnameChecker (a Sun internal proprietary API).
// This leaves the developer in the position of either depending on an internal API or
// finding/copying/creating another implementation of this functionality.
//
hv = new DefaultHostnameVerifier(getDefaultMatcher(ctx));
}
SSLSession sess = socket.getSession();
// Verify that the certicate hostname is for mail.google.com
// This is due to lack of SNI support in the current SSLSocket.
if (!hv.verify(host, sess)) {
throw new SSLHandshakeException("SSL hostname verify failed, Expected " + host +
// throws SSLPeerUnverifiedException
//", found " + sess.getPeerPrincipal() +
// returns null
//", found " + sess.getPeerHost() +
// enable logging for DefaultHostnameVerifier to find out the CN and SANs
" - set " + PROP_DISABLE +
"=true to disable verification (dangerous!)");
}
// At this point SSLSocket performed certificate verificaiton and
// we have performed hostname verification, so it is safe to proceed.
}
/**
* From Apache PublicSuffixMatcherLoader.getDefault()
*
* https://publicsuffix.org/list/effective_tld_names.dat
* What does this get us?
* Deciding whether to issue or accept an SSL wildcard certificate for *.public.suffix.
*
* @return null on failure
* @since 0.9.20
*/
private static PublicSuffixMatcher getDefaultMatcher(I2PAppContext ctx) {
synchronized (I2PSSLSocketFactory.class) {
if (!_matcherLoaded) {
String geoDir = ctx.getProperty(PROP_GEOIP_DIR, GEOIP_DIR_DEFAULT);
File geoFile = new File(geoDir);
if (!geoFile.isAbsolute())
geoFile = new File(ctx.getBaseDir(), geoDir);
geoFile = new File(geoFile, PUBLIC_SUFFIX_LIST);
Log log = ctx.logManager().getLog(I2PSSLSocketFactory.class);
if (geoFile.exists()) {
try {
// we can't use PublicSuffixMatcherLoader.load() here because we
// want to add some of our own and a PublicSuffixMatcher's
// underlying PublicSuffixList is immutable and inaccessible
long begin = System.currentTimeMillis();
InputStream in = null;
PublicSuffixList list = new PublicSuffixList(Arrays.asList(ADDITIONAL_TLDS),
Collections.<String>emptyList());
try {
in = new FileInputStream(geoFile);
PublicSuffixList list2 = new PublicSuffixListParser().parse(
new InputStreamReader(in, "UTF-8"));
list = merge(list, list2);
} finally {
try { if (in != null) in.close(); } catch (IOException ioe) {}
}
DEFAULT_MATCHER = new PublicSuffixMatcher(list.getRules(), list.getExceptions());
if (log.shouldWarn())
log.warn("Loaded " + geoFile + " in " + (System.currentTimeMillis() - begin) +
" ms and created list with " + list.getRules().size() + " entries and " +
list.getExceptions().size() + " exceptions");
} catch (IOException ex) {
log.error("Failure loading public suffix list from " + geoFile, ex);
// DEFAULT_MATCHER remains null
}
} else {
List<String> list = new ArrayList<String>(320);
addCountries(ctx, list);
list.addAll(Arrays.asList(DEFAULT_TLDS));
list.addAll(Arrays.asList(ADDITIONAL_TLDS));
DEFAULT_MATCHER = new PublicSuffixMatcher(list, null);
if (log.shouldWarn())
log.warn("No public suffix list found at " + geoFile +
" - created default with " + list.size() + " entries");
}
}
_matcherLoaded = true;
}
return DEFAULT_MATCHER;
}
/**
* Merge two PublicSuffixLists
* Have to do this because they are unmodifiable
*
* @since 0.9.20
*/
private static PublicSuffixList merge(PublicSuffixList a, PublicSuffixList b) {
List<String> ar = a.getRules();
List<String> ae = a.getExceptions();
List<String> br = b.getRules();
List<String> be = b.getExceptions();
List<String> cr = new ArrayList<String>(ar.size() + br.size());
List<String> ce = new ArrayList<String>(ae.size() + be.size());
cr.addAll(ar);
cr.addAll(br);
ce.addAll(ae);
ce.addAll(be);
return new PublicSuffixList(cr, ce);
}
/**
* Read in the country file and add all TLDs to the list.
* It would almost be easier just to add all possible 26*26 two-letter codes.
*
* @param tlds out parameter
* @since 0.9.20 adapted from GeoIP.loadCountryFile()
*/
private static void addCountries(I2PAppContext ctx, List<String> tlds) {
Log log = ctx.logManager().getLog(I2PSSLSocketFactory.class);
String geoDir = ctx.getProperty(PROP_GEOIP_DIR, GEOIP_DIR_DEFAULT);
File geoFile = new File(geoDir);
if (!geoFile.isAbsolute())
geoFile = new File(ctx.getBaseDir(), geoDir);
geoFile = new File(geoFile, COUNTRY_FILE_DEFAULT);
if (!geoFile.exists()) {
if (log.shouldWarn())
log.warn("Country file not found: " + geoFile.getAbsolutePath());
return;
}
BufferedReader br = null;
try {
br = new BufferedReader(new InputStreamReader(
new FileInputStream(geoFile), "UTF-8"));
String line = null;
int i = 0;
while ( (line = br.readLine()) != null) {
try {
if (line.charAt(0) == '#')
continue;
String[] s = DataHelper.split(line, ",");
String lc = s[0].toLowerCase(Locale.US);
tlds.add(lc);
i++;
} catch (IndexOutOfBoundsException ioobe) {}
}
if (log.shouldInfo())
log.info("Loaded " + i + " TLDs from " + geoFile.getAbsolutePath());
} catch (IOException ioe) {
log.error("Error reading the Country File", ioe);
} finally {
if (br != null) try { br.close(); } catch (IOException ioe) {}
}
}
/**
* Loads certs from
* the ~/.i2p/certificates/ and $I2P/certificates/ directories.
*/
private static SSLSocketFactory initSSLContext(I2PAppContext context, boolean loadSystemCerts, String relativeCertPath)
throws GeneralSecurityException {
Log log = context.logManager().getLog(I2PSSLSocketFactory.class);
KeyStore ks;
if (loadSystemCerts) {
ks = KeyStoreUtil.loadSystemKeyStore();
if (ks == null)
throw new GeneralSecurityException("Key Store init error");
} else {
try {
ks = KeyStore.getInstance(KeyStore.getDefaultType());
ks.load(null, "".toCharArray());
} catch (IOException ioe) {
throw new GeneralSecurityException("Key Store init error", ioe);
}
}
File dir = new File(context.getConfigDir(), relativeCertPath);
int adds = KeyStoreUtil.addCerts(dir, ks);
int totalAdds = adds;
if (adds > 0) {
if (log.shouldLog(Log.INFO))
log.info("Loaded " + adds + " trusted certificates from " + dir.getAbsolutePath());
}
File dir2 = new File(context.getBaseDir(), relativeCertPath);
if (!dir.getAbsolutePath().equals(dir2.getAbsolutePath())) {
adds = KeyStoreUtil.addCerts(dir2, ks);
totalAdds += adds;
if (adds > 0) {
if (log.shouldLog(Log.INFO))
log.info("Loaded " + adds + " trusted certificates from " + dir.getAbsolutePath());
}
}
if (totalAdds > 0 || loadSystemCerts) {
if (log.shouldLog(Log.INFO))
log.info("Loaded total of " + totalAdds + " new trusted certificates");
} else {
String msg = "No trusted certificates loaded (looked in " +
dir.getAbsolutePath() + (dir.getAbsolutePath().equals(dir2.getAbsolutePath()) ? "" : (" and " + dir2.getAbsolutePath())) +
", SSL connections will fail. " +
"Copy the cert in " + relativeCertPath + " from the router to the directory.";
// don't continue, since we didn't load the system keystore, we have nothing.
throw new GeneralSecurityException(msg);
}
SSLContext sslc = SSLContext.getInstance("TLS");
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(ks);
sslc.init(null, tmf.getTrustManagers(), context.random());
return sslc.getSocketFactory();
}
/**
* Select protocols and cipher suites to be used
* based on configured inclusion and exclusion lists
* as well as enabled and supported protocols and cipher suites.
*
* Adapted from Jetty SslContextFactory.java
*
* @since 0.9.16
*/
public static void setProtocolsAndCiphers(SSLSocket socket) {
socket.setEnabledProtocols(selectProtocols(socket.getEnabledProtocols(),
socket.getSupportedProtocols()));
socket.setEnabledCipherSuites(selectCipherSuites(socket.getEnabledCipherSuites(),
socket.getSupportedCipherSuites()));
}
/**
* Select protocols and cipher suites to be used
* based on configured inclusion and exclusion lists
* as well as enabled and supported protocols and cipher suites.
*
* Adapted from Jetty SslContextFactory.java
*
* @since 0.9.16
*/
public static void setProtocolsAndCiphers(SSLServerSocket socket) {
String[] p = selectProtocols(socket.getEnabledProtocols(),
socket.getSupportedProtocols());
for (int i = 0; i < p.length; i++) {
// if we left SSLv3 in there, we don't support TLS,
// so we should't remove the SSL ciphers
if (p[i].equals("SSLv3"))
return;
}
socket.setEnabledProtocols(p);
socket.setEnabledCipherSuites(selectCipherSuites(socket.getEnabledCipherSuites(),
socket.getSupportedCipherSuites()));
}
/**
* Select protocols to be used
* based on configured inclusion and exclusion lists
* as well as enabled and supported protocols.
*
* Adapted from Jetty SslContextFactory.java
*
* @param enabledProtocols Array of enabled protocols
* @param supportedProtocols Array of supported protocols
* @return Array of protocols to enable
* @since 0.9.16
*/
private static String[] selectProtocols(String[] enabledProtocols, String[] supportedProtocols) {
return select(enabledProtocols, supportedProtocols, INCLUDE_PROTOCOLS, EXCLUDE_PROTOCOLS);
}
/**
* Select cipher suites to be used
* based on configured inclusion and exclusion lists
* as well as enabled and supported cipher suite lists.
*
* Adapted from Jetty SslContextFactory.java
*
* @param enabledCipherSuites Array of enabled cipher suites
* @param supportedCipherSuites Array of supported cipher suites
* @return Array of cipher suites to enable
* @since 0.9.16
*/
private static String[] selectCipherSuites(String[] enabledCipherSuites, String[] supportedCipherSuites) {
return select(enabledCipherSuites, supportedCipherSuites, INCLUDE_CIPHERS, EXCLUDE_CIPHERS);
}
/**
* Adapted from Jetty SslContextFactory.java
*
* @param toEnable Add all these to what is enabled, if supported
* @param toExclude Remove all these from what is enabled
* @since 0.9.16
*/
private static String[] select(String[] enabledArr, String[] supportedArr,
List<String> toEnable, List<String> toExclude) {
Log log = I2PAppContext.getGlobalContext().logManager().getLog(I2PSSLSocketFactory.class);
Set<String> selected = new HashSet<String>(enabledArr.length);
selected.addAll(Arrays.asList(enabledArr));
selected.removeAll(toExclude);
Set<String> supported = new HashSet<String>(supportedArr.length);
supported.addAll(Arrays.asList(supportedArr));
for (String s : toEnable) {
if (supported.contains(s)) {
if (selected.add(s)) {
if (log.shouldLog(Log.INFO))
log.info("Added, previously disabled: " + s);
}
} else if (log.shouldLog(Log.INFO)) {
log.info("Not supported in this JVM: " + s);
}
}
if (selected.isEmpty()) {
// shouldn't happen, Java 6 supports TLSv1
log.logAlways(Log.WARN, "No TLS support for SSLEepGet, falling back");
return enabledArr;
}
if (log.shouldLog(Log.DEBUG)) {
List<String> foo = new ArrayList<String>(selected);
Collections.sort(foo);
log.debug("Selected: " + foo);
}
return selected.toArray(new String[selected.size()]);
}
}