001/** 002 * 003 * Copyright 2014 Florian Schmaus 004 * 005 * Licensed under the Apache License, Version 2.0 (the "License"); 006 * you may not use this file except in compliance with the License. 007 * You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.jivesoftware.smack.util; 018 019import java.security.KeyManagementException; 020import java.security.NoSuchAlgorithmException; 021import java.security.SecureRandom; 022import java.security.cert.CertificateException; 023import java.security.cert.X509Certificate; 024import java.util.Arrays; 025import java.util.HashSet; 026import java.util.Set; 027 028import javax.net.ssl.HostnameVerifier; 029import javax.net.ssl.SSLContext; 030import javax.net.ssl.SSLSession; 031import javax.net.ssl.SSLSocket; 032import javax.net.ssl.TrustManager; 033import javax.net.ssl.X509TrustManager; 034 035import org.jivesoftware.smack.ConnectionConfiguration; 036import org.jivesoftware.smack.SmackException.SecurityNotPossibleException; 037 038 039public class TLSUtils { 040 041 public static final String SSL = "SSL"; 042 public static final String TLS = "TLS"; 043 public static final String PROTO_SSL3 = SSL + "v3"; 044 public static final String PROTO_TLSV1 = TLS + "v1"; 045 public static final String PROTO_TLSV1_1 = TLS + "v1.1"; 046 public static final String PROTO_TLSV1_2 = TLS + "v1.2"; 047 048 /** 049 * Enable only TLS. Connections created with the given ConnectionConfiguration will only support TLS. 050 * <p> 051 * According to the <a 052 * href="https://raw.githubusercontent.com/stpeter/manifesto/master/manifesto.txt">Encrypted 053 * XMPP Manifesto</a>, TLSv1.2 shall be deployed, providing fallback support for SSLv3 and 054 * TLSv1.1. This method goes one step boyond and upgrades the handshake to use TLSv1 or better. 055 * This method requires the underlying OS to support all of TLSv1.2 , 1.1 and 1.0. 056 * </p> 057 * 058 * @param builder the configuration builder to apply this setting to 059 */ 060 public static <B extends ConnectionConfiguration.Builder<B,?>> B setTLSOnly(B builder) { 061 builder.setEnabledSSLProtocols(new String[] { PROTO_TLSV1_2, PROTO_TLSV1_1, PROTO_TLSV1 }); 062 return builder; 063 } 064 065 /** 066 * Enable only TLS and SSLv3. Connections created with the given ConnectionConfiguration will 067 * only support TLS and SSLv3. 068 * <p> 069 * According to the <a 070 * href="https://raw.githubusercontent.com/stpeter/manifesto/master/manifesto.txt">Encrypted 071 * XMPP Manifesto</a>, TLSv1.2 shall be deployed, providing fallback support for SSLv3 and 072 * TLSv1.1. 073 * </p> 074 * 075 * @param builder the configuration builder to apply this setting to 076 */ 077 public static <B extends ConnectionConfiguration.Builder<B,?>> B setSSLv3AndTLSOnly(B builder) { 078 builder.setEnabledSSLProtocols(new String[] { PROTO_TLSV1_2, PROTO_TLSV1_1, PROTO_TLSV1, PROTO_SSL3 }); 079 return builder; 080 } 081 082 /** 083 * Accept all TLS certificates. 084 * <p> 085 * <b>Warning:</b> Use with care. This method make the Connection use {@link AcceptAllTrustManager} and essentially 086 * <b>invalidates all security guarantees provided by TLS</b>. Only use this method if you understand the 087 * implications. 088 * </p> 089 * 090 * @param builder a connection configuration builder. 091 * @throws NoSuchAlgorithmException 092 * @throws KeyManagementException 093 * @return the given builder. 094 */ 095 public static <B extends ConnectionConfiguration.Builder<B,?>> B acceptAllCertificates(B builder) throws NoSuchAlgorithmException, KeyManagementException { 096 SSLContext context = SSLContext.getInstance(TLS); 097 context.init(null, new TrustManager[] { new AcceptAllTrustManager() }, new SecureRandom()); 098 builder.setCustomSSLContext(context); 099 return builder; 100 } 101 102 private static final HostnameVerifier DOES_NOT_VERIFY_VERIFIER = new HostnameVerifier() { 103 @Override 104 public boolean verify(String hostname, SSLSession session) { 105 // This verifier doesn't verify the hostname, it always returns true. 106 return true; 107 } 108 }; 109 110 /** 111 * Disable the hostname verification of TLS certificates. 112 * <p> 113 * <b>Warning:</b> Use with care. This disables hostname verification of TLS certificates and essentially 114 * <b>invalidates all security guarantees provided by TLS</b>. Only use this method if you understand the 115 * implications. 116 * </p> 117 * 118 * @param builder a connection configuration builder. 119 * @return the given builder. 120 */ 121 public static <B extends ConnectionConfiguration.Builder<B,?>> B disableHostnameVerificationForTlsCertificicates(B builder) { 122 builder.setHostnameVerifier(DOES_NOT_VERIFY_VERIFIER); 123 return builder; 124 } 125 126 public static void setEnabledProtocolsAndCiphers(final SSLSocket sslSocket, 127 String[] enabledProtocols, String[] enabledCiphers) 128 throws SecurityNotPossibleException { 129 if (enabledProtocols != null) { 130 Set<String> enabledProtocolsSet = new HashSet<String>(Arrays.asList(enabledProtocols)); 131 Set<String> supportedProtocolsSet = new HashSet<String>( 132 Arrays.asList(sslSocket.getSupportedProtocols())); 133 Set<String> protocolsIntersection = new HashSet<String>(supportedProtocolsSet); 134 protocolsIntersection.retainAll(enabledProtocolsSet); 135 if (protocolsIntersection.isEmpty()) { 136 throw new SecurityNotPossibleException("Request to enable SSL/TLS protocols '" 137 + StringUtils.collectionToString(enabledProtocolsSet) 138 + "', but only '" 139 + StringUtils.collectionToString(supportedProtocolsSet) 140 + "' are supported."); 141 } 142 143 // Set the enabled protocols 144 enabledProtocols = new String[protocolsIntersection.size()]; 145 enabledProtocols = protocolsIntersection.toArray(enabledProtocols); 146 sslSocket.setEnabledProtocols(enabledProtocols); 147 } 148 149 if (enabledCiphers != null) { 150 Set<String> enabledCiphersSet = new HashSet<String>(Arrays.asList(enabledCiphers)); 151 Set<String> supportedCiphersSet = new HashSet<String>( 152 Arrays.asList(sslSocket.getEnabledCipherSuites())); 153 Set<String> ciphersIntersection = new HashSet<String>(supportedCiphersSet); 154 ciphersIntersection.retainAll(enabledCiphersSet); 155 if (ciphersIntersection.isEmpty()) { 156 throw new SecurityNotPossibleException("Request to enable SSL/TLS ciphers '" 157 + StringUtils.collectionToString(enabledCiphersSet) 158 + "', but only '" 159 + StringUtils.collectionToString(supportedCiphersSet) 160 + "' are supported."); 161 } 162 163 enabledCiphers = new String[ciphersIntersection.size()]; 164 enabledCiphers = ciphersIntersection.toArray(enabledCiphers); 165 sslSocket.setEnabledCipherSuites(enabledCiphers); 166 } 167 } 168 169 /** 170 * A {@link X509TrustManager} that <b>doesn't validate</b> X.509 certificates. 171 * <p> 172 * Connections that use this TrustManager will just be encrypted, without any guarantee that the 173 * counter part is actually the intended one. Man-in-the-Middle attacks will be possible, since 174 * any certificate presented by the attacker will be considered valid. 175 * </p> 176 */ 177 public static class AcceptAllTrustManager implements X509TrustManager { 178 179 @Override 180 public void checkClientTrusted(X509Certificate[] arg0, String arg1) 181 throws CertificateException { 182 // Nothing to do here 183 } 184 185 @Override 186 public void checkServerTrusted(X509Certificate[] arg0, String arg1) 187 throws CertificateException { 188 // Nothing to do here 189 } 190 191 @Override 192 public X509Certificate[] getAcceptedIssuers() { 193 return new X509Certificate[0]; 194 } 195 } 196}