/** * * Copyright 2014-2016 Florian Schmaus * * Licensed 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. */ package org.jivesoftware.smack.util; import java.security.KeyManagementException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.security.cert.Certificate; import java.security.cert.CertificateEncodingException; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.util.Arrays; import java.util.HashSet; import java.util.Set; import javax.net.ssl.HostnameVerifier; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLPeerUnverifiedException; import javax.net.ssl.SSLSession; import javax.net.ssl.SSLSocket; import javax.net.ssl.TrustManager; import javax.net.ssl.X509TrustManager; import org.jivesoftware.smack.ConnectionConfiguration; import org.jivesoftware.smack.SmackException.SecurityNotPossibleException; public class TLSUtils { public static final String SSL = "SSL"; public static final String TLS = "TLS"; public static final String PROTO_SSL3 = SSL + "v3"; public static final String PROTO_TLSV1 = TLS + "v1"; public static final String PROTO_TLSV1_1 = TLS + "v1.1"; public static final String PROTO_TLSV1_2 = TLS + "v1.2"; /** * Enable only TLS. Connections created with the given ConnectionConfiguration will only support TLS. *

* According to the Encrypted * XMPP Manifesto, TLSv1.2 shall be deployed, providing fallback support for SSLv3 and * TLSv1.1. This method goes one step boyond and upgrades the handshake to use TLSv1 or better. * This method requires the underlying OS to support all of TLSv1.2 , 1.1 and 1.0. *

* * @param builder the configuration builder to apply this setting to */ public static > B setTLSOnly(B builder) { builder.setEnabledSSLProtocols(new String[] { PROTO_TLSV1_2, PROTO_TLSV1_1, PROTO_TLSV1 }); return builder; } /** * Enable only TLS and SSLv3. Connections created with the given ConnectionConfiguration will * only support TLS and SSLv3. *

* According to the Encrypted * XMPP Manifesto, TLSv1.2 shall be deployed, providing fallback support for SSLv3 and * TLSv1.1. *

* * @param builder the configuration builder to apply this setting to */ public static > B setSSLv3AndTLSOnly(B builder) { builder.setEnabledSSLProtocols(new String[] { PROTO_TLSV1_2, PROTO_TLSV1_1, PROTO_TLSV1, PROTO_SSL3 }); return builder; } /** * Accept all TLS certificates. *

* Warning: Use with care. This method make the Connection use {@link AcceptAllTrustManager} and essentially * invalidates all security guarantees provided by TLS. Only use this method if you understand the * implications. *

* * @param builder a connection configuration builder. * @throws NoSuchAlgorithmException * @throws KeyManagementException * @return the given builder. */ public static > B acceptAllCertificates(B builder) throws NoSuchAlgorithmException, KeyManagementException { SSLContext context = SSLContext.getInstance(TLS); context.init(null, new TrustManager[] { new AcceptAllTrustManager() }, new SecureRandom()); builder.setCustomSSLContext(context); return builder; } private static final HostnameVerifier DOES_NOT_VERIFY_VERIFIER = new HostnameVerifier() { @Override public boolean verify(String hostname, SSLSession session) { // This verifier doesn't verify the hostname, it always returns true. return true; } }; /** * Disable the hostname verification of TLS certificates. *

* Warning: Use with care. This disables hostname verification of TLS certificates and essentially * invalidates all security guarantees provided by TLS. Only use this method if you understand the * implications. *

* * @param builder a connection configuration builder. * @return the given builder. */ public static > B disableHostnameVerificationForTlsCertificates(B builder) { builder.setHostnameVerifier(DOES_NOT_VERIFY_VERIFIER); return builder; } public static void setEnabledProtocolsAndCiphers(final SSLSocket sslSocket, String[] enabledProtocols, String[] enabledCiphers) throws SecurityNotPossibleException { if (enabledProtocols != null) { Set enabledProtocolsSet = new HashSet(Arrays.asList(enabledProtocols)); Set supportedProtocolsSet = new HashSet( Arrays.asList(sslSocket.getSupportedProtocols())); Set protocolsIntersection = new HashSet(supportedProtocolsSet); protocolsIntersection.retainAll(enabledProtocolsSet); if (protocolsIntersection.isEmpty()) { throw new SecurityNotPossibleException("Request to enable SSL/TLS protocols '" + StringUtils.collectionToString(enabledProtocolsSet) + "', but only '" + StringUtils.collectionToString(supportedProtocolsSet) + "' are supported."); } // Set the enabled protocols enabledProtocols = new String[protocolsIntersection.size()]; enabledProtocols = protocolsIntersection.toArray(enabledProtocols); sslSocket.setEnabledProtocols(enabledProtocols); } if (enabledCiphers != null) { Set enabledCiphersSet = new HashSet(Arrays.asList(enabledCiphers)); Set supportedCiphersSet = new HashSet( Arrays.asList(sslSocket.getEnabledCipherSuites())); Set ciphersIntersection = new HashSet(supportedCiphersSet); ciphersIntersection.retainAll(enabledCiphersSet); if (ciphersIntersection.isEmpty()) { throw new SecurityNotPossibleException("Request to enable SSL/TLS ciphers '" + StringUtils.collectionToString(enabledCiphersSet) + "', but only '" + StringUtils.collectionToString(supportedCiphersSet) + "' are supported."); } enabledCiphers = new String[ciphersIntersection.size()]; enabledCiphers = ciphersIntersection.toArray(enabledCiphers); sslSocket.setEnabledCipherSuites(enabledCiphers); } } /** * Get the channel binding data for the 'tls-server-end-point' channel binding type. This channel binding type is * defined in RFC 5929 § 4. * * @param sslSession the SSL/TLS session from which the data should be retrieved. * @return the channel binding data. * @throws SSLPeerUnverifiedException * @throws CertificateEncodingException * @throws NoSuchAlgorithmException * @see RFC 5929 § 4. */ public static byte[] getChannelBindingTlsServerEndPoint(final SSLSession sslSession) throws SSLPeerUnverifiedException, CertificateEncodingException, NoSuchAlgorithmException { final Certificate[] peerCertificates = sslSession.getPeerCertificates(); final Certificate certificate = peerCertificates[0]; final String certificateAlgorithm = certificate.getPublicKey().getAlgorithm(); // RFC 5929 § 4.1 hash function selection. String algorithm; switch (certificateAlgorithm) { case "MD5": case "SHA-1": algorithm = "SHA-256"; break; default: algorithm = certificateAlgorithm; break; } final MessageDigest messageDigest = MessageDigest.getInstance(algorithm); final byte[] certificateDerEncoded = certificate.getEncoded(); messageDigest.update(certificateDerEncoded); return messageDigest.digest(); } /** * A {@link X509TrustManager} that doesn't validate X.509 certificates. *

* Connections that use this TrustManager will just be encrypted, without any guarantee that the * counter part is actually the intended one. Man-in-the-Middle attacks will be possible, since * any certificate presented by the attacker will be considered valid. *

*/ public static class AcceptAllTrustManager implements X509TrustManager { @Override public void checkClientTrusted(X509Certificate[] arg0, String arg1) throws CertificateException { // Nothing to do here } @Override public void checkServerTrusted(X509Certificate[] arg0, String arg1) throws CertificateException { // Nothing to do here } @Override public X509Certificate[] getAcceptedIssuers() { return new X509Certificate[0]; } } }