/**
* Copyright (C) 2010-2014 Leon Blakey <lord.quackstar at gmail.com>
*
* This file is part of PircBotX.
*
* PircBotX is free software: you can redistribute it and/or modify it under the
* terms of the GNU General Public License as published by the Free Software
* Foundation, either version 3 of the License, or (at your option) any later
* version.
*
* PircBotX is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
* A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with
* PircBotX. If not, see <http://www.gnu.org/licenses/>.
*/
package org.pircbotx;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.security.SecureRandom;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.LinkedList;
import java.util.List;
import javax.net.ssl.KeyManager;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.ToString;
import lombok.extern.slf4j.Slf4j;
/**
* Utility for doing various useful things to an SSL socket factory.
* <p>
* Most methods follow the builder pattern, meaning you can declare and setup
* this Socket Factory in one line
*
* @author Trusting all certificates code by <a
* href="http://www.howardism.org/Technical/Java/SelfSignedCerts.html">Howardism</a>
* <p>
* Disabling Diffie Hellman code by <a
* href="http://stackoverflow.com/questions/6851461/java-why-does-ssl-handshake-give-could-not-generate-dh-keypair-exception/6862383#6862383">Sam
* on StackOverflow</a>
* <p>
* Implemented and Maintained in PircBotX by: Leon Blakey <lord.quackstar at
* gmail.com>
*/
@EqualsAndHashCode(callSuper = false)
@ToString
@Slf4j
public class UtilSSLSocketFactory extends SSLSocketFactory {
protected SSLSocketFactory wrappedFactory = (SSLSocketFactory) SSLSocketFactory.getDefault();
@Getter
protected boolean trustingAllCertificates = false;
@Getter
protected boolean diffieHellmanDisabled = false;
protected boolean wrappedFactoryChanged = false;
/**
* By default, trust ALL certificates. <b>This is <i>very</i> insecure.</b>
* It also defeats one of the points of SSL: Making sure your connecting to
* the right server. Cannot be combined with {@link #disableDiffieHellman(javax.net.ssl.SSLSocketFactory)
* } since this overwrites the wrapped factory and will throw an exception!
*
* @return The current UtilSSLSocketFactory instance
*/
public UtilSSLSocketFactory trustAllCertificates() {
if (wrappedFactoryChanged)
throw new RuntimeException("Cannot combine trustAllCertificates() and disableDiffieHellman(SSLSocketFactory)");
if (trustingAllCertificates)
//Already doing this, no need to do it again
return this;
trustingAllCertificates = true;
try {
TrustManager[] tm = new TrustManager[]{new TrustingX509TrustManager()};
SSLContext context = SSLContext.getInstance("SSL");
context.init(new KeyManager[0], tm, new SecureRandom());
wrappedFactory = context.getSocketFactory();
} catch (Exception e) {
throw new RuntimeException("Can't recreate socket factory that trusts all certificates", e);
}
return this;
}
/**
* Disable the Diffie Hellman key exchange algorithm. This is useful to work
* around JDK bug #6521495 which throws an Exception when prime sizes are
* above 1024 bits.
* <p>
* Note that this requires that the server supports other key exchange
* algorithms. This socket factory (nor any other built in Socket Factory)
* cannot connect to a server that only supports Diffie Hellman key exchange
* with prime sizes larger than 1024 bits.
* <p>
* Also see PircBotX Issue #34
*
* @return The current UtilSSLSocketFactory instance
*/
public UtilSSLSocketFactory disableDiffieHellman() {
diffieHellmanDisabled = true;
return this;
}
/**
* Disable the Diffie Hellman key exchange algorithm using the provided SSL
* socket factory. Cannot be combined with {@link #disableDiffieHellman(javax.net.ssl.SSLSocketFactory)
* } since this overwrites the wrapped factory and will throw an exception!
*
* @see #disableDiffieHellman()
* @param sourceSocketFactory
* @return The current UtilSSLSocketFactory instance
*/
public UtilSSLSocketFactory disableDiffieHellman(SSLSocketFactory sourceSocketFactory) {
if (trustingAllCertificates)
throw new RuntimeException("Cannot combine trustAllCertificates() and disableDiffieHellman(SSLSocketFactory)");
wrappedFactory = sourceSocketFactory;
wrappedFactoryChanged = true;
return disableDiffieHellman();
}
protected SSLSocket prepare(Socket socket) {
SSLSocket sslSocket = (SSLSocket) socket;
if (diffieHellmanDisabled) {
List<String> limited = new LinkedList<String>();
for (String suite : sslSocket.getEnabledCipherSuites())
if (!suite.contains("_DHE_"))
limited.add(suite);
sslSocket.setEnabledCipherSuites(limited.toArray(new String[limited.size()]));
}
return sslSocket;
}
@Override
public SSLSocket createSocket(String host, int port) throws IOException, UnknownHostException {
return prepare(wrappedFactory.createSocket(host, port));
}
@Override
public SSLSocket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException, UnknownHostException {
return prepare(wrappedFactory.createSocket(host, port, localHost, localPort));
}
@Override
public SSLSocket createSocket(InetAddress address, int port) throws IOException {
return prepare(wrappedFactory.createSocket(address, port));
}
@Override
public SSLSocket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException {
return prepare(wrappedFactory.createSocket(address, port, localAddress, localPort));
}
@Override
public SSLSocket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException {
return prepare(wrappedFactory.createSocket(s, host, port, autoClose));
}
//@Override
public SSLSocket createSocket(Socket socket, InputStream in, boolean bln) throws IOException {
//This method is new in JDK 8, however PircBotX must compile on 7 and below
try {
return prepare((Socket) getClass().getMethod("createSocket", Socket.class, InputStream.class, boolean.class)
.invoke(this, socket, in, bln));
} catch (Exception e) {
throw new RuntimeException("Failed to create socket", e);
}
}
@Override
public SSLSocket createSocket() throws IOException {
return prepare(wrappedFactory.createSocket());
}
@Override
public String[] getDefaultCipherSuites() {
return wrappedFactory.getDefaultCipherSuites();
}
@Override
public String[] getSupportedCipherSuites() {
return wrappedFactory.getSupportedCipherSuites();
}
/**
* X509TrustManager that trusts all certificates. <b>This is very
* insecure</b>
*/
public static class TrustingX509TrustManager implements X509TrustManager {
/**
* Doesn't throw an exception, so this is how it approves a certificate.
*
* @see
* javax.net.ssl.X509TrustManager#checkClientTrusted(java.security.cert.X509Certificate[],
* String)
*
*/
public void checkClientTrusted(X509Certificate[] cert, String authType) throws CertificateException {
}
/**
* Doesn't throw an exception, so this is how it approves a certificate.
*
* @see
* javax.net.ssl.X509TrustManager#checkServerTrusted(java.security.cert.X509Certificate[],
* String)
*
*/
public void checkServerTrusted(X509Certificate[] cert, String authType) throws CertificateException {
}
/**
* @see javax.net.ssl.X509TrustManager#getAcceptedIssuers()
*
*/
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
}
}