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