001/** 002 * 003 * Copyright 2014-2016 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.MessageDigest; 021import java.security.NoSuchAlgorithmException; 022import java.security.SecureRandom; 023import java.security.cert.Certificate; 024import java.security.cert.CertificateEncodingException; 025import java.security.cert.CertificateException; 026import java.security.cert.X509Certificate; 027import java.util.Arrays; 028import java.util.HashSet; 029import java.util.Set; 030 031import javax.net.ssl.HostnameVerifier; 032import javax.net.ssl.SSLContext; 033import javax.net.ssl.SSLPeerUnverifiedException; 034import javax.net.ssl.SSLSession; 035import javax.net.ssl.SSLSocket; 036import javax.net.ssl.TrustManager; 037import javax.net.ssl.X509TrustManager; 038 039import org.jivesoftware.smack.ConnectionConfiguration; 040import org.jivesoftware.smack.SmackException.SecurityNotPossibleException; 041 042 043public class TLSUtils { 044 045 public static final String SSL = "SSL"; 046 public static final String TLS = "TLS"; 047 public static final String PROTO_SSL3 = SSL + "v3"; 048 public static final String PROTO_TLSV1 = TLS + "v1"; 049 public static final String PROTO_TLSV1_1 = TLS + "v1.1"; 050 public static final String PROTO_TLSV1_2 = TLS + "v1.2"; 051 052 /** 053 * Enable only TLS. Connections created with the given ConnectionConfiguration will only support TLS. 054 * <p> 055 * According to the <a 056 * href="https://raw.githubusercontent.com/stpeter/manifesto/master/manifesto.txt">Encrypted 057 * XMPP Manifesto</a>, TLSv1.2 shall be deployed, providing fallback support for SSLv3 and 058 * TLSv1.1. This method goes one step beyond and upgrades the handshake to use TLSv1 or better. 059 * This method requires the underlying OS to support all of TLSv1.2 , 1.1 and 1.0. 060 * </p> 061 * 062 * @param builder the configuration builder to apply this setting to 063 * @param <B> Type of the ConnectionConfiguration builder. 064 * 065 * @return the given builder 066 */ 067 public static <B extends ConnectionConfiguration.Builder<B,?>> B setTLSOnly(B builder) { 068 builder.setEnabledSSLProtocols(new String[] { PROTO_TLSV1_2, PROTO_TLSV1_1, PROTO_TLSV1 }); 069 return builder; 070 } 071 072 /** 073 * Enable only TLS and SSLv3. Connections created with the given ConnectionConfiguration will 074 * only support TLS and SSLv3. 075 * <p> 076 * According to the <a 077 * href="https://raw.githubusercontent.com/stpeter/manifesto/master/manifesto.txt">Encrypted 078 * XMPP Manifesto</a>, TLSv1.2 shall be deployed, providing fallback support for SSLv3 and 079 * TLSv1.1. 080 * </p> 081 * 082 * @param builder the configuration builder to apply this setting to 083 * @param <B> Type of the ConnectionConfiguration builder. 084 * 085 * @return the given builder 086 */ 087 public static <B extends ConnectionConfiguration.Builder<B,?>> B setSSLv3AndTLSOnly(B builder) { 088 builder.setEnabledSSLProtocols(new String[] { PROTO_TLSV1_2, PROTO_TLSV1_1, PROTO_TLSV1, PROTO_SSL3 }); 089 return builder; 090 } 091 092 /** 093 * Accept all TLS certificates. 094 * <p> 095 * <b>Warning:</b> Use with care. This method make the Connection use {@link AcceptAllTrustManager} and essentially 096 * <b>invalidates all security guarantees provided by TLS</b>. Only use this method if you understand the 097 * implications. 098 * </p> 099 * 100 * @param builder a connection configuration builder. 101 * @param <B> Type of the ConnectionConfiguration builder. 102 * @throws NoSuchAlgorithmException 103 * @throws KeyManagementException 104 * @return the given builder. 105 */ 106 public static <B extends ConnectionConfiguration.Builder<B,?>> B acceptAllCertificates(B builder) throws NoSuchAlgorithmException, KeyManagementException { 107 SSLContext context = SSLContext.getInstance(TLS); 108 context.init(null, new TrustManager[] { new AcceptAllTrustManager() }, new SecureRandom()); 109 builder.setCustomSSLContext(context); 110 return builder; 111 } 112 113 private static final HostnameVerifier DOES_NOT_VERIFY_VERIFIER = new HostnameVerifier() { 114 @Override 115 public boolean verify(String hostname, SSLSession session) { 116 // This verifier doesn't verify the hostname, it always returns true. 117 return true; 118 } 119 }; 120 121 /** 122 * Disable the hostname verification of TLS certificates. 123 * <p> 124 * <b>Warning:</b> Use with care. This disables hostname verification of TLS certificates and essentially 125 * <b>invalidates all security guarantees provided by TLS</b>. Only use this method if you understand the 126 * implications. 127 * </p> 128 * 129 * @param builder a connection configuration builder. 130 * @param <B> Type of the ConnectionConfiguration builder. 131 * @return the given builder. 132 */ 133 public static <B extends ConnectionConfiguration.Builder<B,?>> B disableHostnameVerificationForTlsCertificates(B builder) { 134 builder.setHostnameVerifier(DOES_NOT_VERIFY_VERIFIER); 135 return builder; 136 } 137 138 public static void setEnabledProtocolsAndCiphers(final SSLSocket sslSocket, 139 String[] enabledProtocols, String[] enabledCiphers) 140 throws SecurityNotPossibleException { 141 if (enabledProtocols != null) { 142 Set<String> enabledProtocolsSet = new HashSet<String>(Arrays.asList(enabledProtocols)); 143 Set<String> supportedProtocolsSet = new HashSet<String>( 144 Arrays.asList(sslSocket.getSupportedProtocols())); 145 Set<String> protocolsIntersection = new HashSet<String>(supportedProtocolsSet); 146 protocolsIntersection.retainAll(enabledProtocolsSet); 147 if (protocolsIntersection.isEmpty()) { 148 throw new SecurityNotPossibleException("Request to enable SSL/TLS protocols '" 149 + StringUtils.collectionToString(enabledProtocolsSet) 150 + "', but only '" 151 + StringUtils.collectionToString(supportedProtocolsSet) 152 + "' are supported."); 153 } 154 155 // Set the enabled protocols 156 enabledProtocols = new String[protocolsIntersection.size()]; 157 enabledProtocols = protocolsIntersection.toArray(enabledProtocols); 158 sslSocket.setEnabledProtocols(enabledProtocols); 159 } 160 161 if (enabledCiphers != null) { 162 Set<String> enabledCiphersSet = new HashSet<String>(Arrays.asList(enabledCiphers)); 163 Set<String> supportedCiphersSet = new HashSet<String>( 164 Arrays.asList(sslSocket.getEnabledCipherSuites())); 165 Set<String> ciphersIntersection = new HashSet<String>(supportedCiphersSet); 166 ciphersIntersection.retainAll(enabledCiphersSet); 167 if (ciphersIntersection.isEmpty()) { 168 throw new SecurityNotPossibleException("Request to enable SSL/TLS ciphers '" 169 + StringUtils.collectionToString(enabledCiphersSet) 170 + "', but only '" 171 + StringUtils.collectionToString(supportedCiphersSet) 172 + "' are supported."); 173 } 174 175 enabledCiphers = new String[ciphersIntersection.size()]; 176 enabledCiphers = ciphersIntersection.toArray(enabledCiphers); 177 sslSocket.setEnabledCipherSuites(enabledCiphers); 178 } 179 } 180 181 /** 182 * Get the channel binding data for the 'tls-server-end-point' channel binding type. This channel binding type is 183 * defined in RFC 5929 § 4. 184 * 185 * @param sslSession the SSL/TLS session from which the data should be retrieved. 186 * @return the channel binding data. 187 * @throws SSLPeerUnverifiedException 188 * @throws CertificateEncodingException 189 * @throws NoSuchAlgorithmException 190 * @see <a href="https://tools.ietf.org/html/rfc5929#section-4">RFC 5929 § 4.</a> 191 */ 192 public static byte[] getChannelBindingTlsServerEndPoint(final SSLSession sslSession) 193 throws SSLPeerUnverifiedException, CertificateEncodingException, NoSuchAlgorithmException { 194 final Certificate[] peerCertificates = sslSession.getPeerCertificates(); 195 final Certificate certificate = peerCertificates[0]; 196 final String certificateAlgorithm = certificate.getPublicKey().getAlgorithm(); 197 198 // RFC 5929 § 4.1 hash function selection. 199 String algorithm; 200 switch (certificateAlgorithm) { 201 case "MD5": 202 case "SHA-1": 203 algorithm = "SHA-256"; 204 break; 205 default: 206 algorithm = certificateAlgorithm; 207 break; 208 } 209 210 final MessageDigest messageDigest = MessageDigest.getInstance(algorithm); 211 final byte[] certificateDerEncoded = certificate.getEncoded(); 212 messageDigest.update(certificateDerEncoded); 213 return messageDigest.digest(); 214 } 215 216 /** 217 * A {@link X509TrustManager} that <b>doesn't validate</b> X.509 certificates. 218 * <p> 219 * Connections that use this TrustManager will just be encrypted, without any guarantee that the 220 * counter part is actually the intended one. Man-in-the-Middle attacks will be possible, since 221 * any certificate presented by the attacker will be considered valid. 222 * </p> 223 */ 224 public static class AcceptAllTrustManager implements X509TrustManager { 225 226 @Override 227 public void checkClientTrusted(X509Certificate[] arg0, String arg1) 228 throws CertificateException { 229 // Nothing to do here 230 } 231 232 @Override 233 public void checkServerTrusted(X509Certificate[] arg0, String arg1) 234 throws CertificateException { 235 // Nothing to do here 236 } 237 238 @Override 239 public X509Certificate[] getAcceptedIssuers() { 240 return new X509Certificate[0]; 241 } 242 } 243}