001/** 002 * 003 * Copyright 2014-2020 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.io.DataInputStream; 020import java.io.File; 021import java.io.FileInputStream; 022import java.io.FileNotFoundException; 023import java.io.IOException; 024import java.io.InputStream; 025import java.security.MessageDigest; 026import java.security.NoSuchAlgorithmException; 027import java.security.cert.Certificate; 028import java.security.cert.CertificateEncodingException; 029import java.security.cert.CertificateException; 030import java.security.cert.X509Certificate; 031import java.util.Arrays; 032import java.util.HashSet; 033import java.util.Set; 034import java.util.logging.Level; 035import java.util.logging.Logger; 036 037import javax.net.ssl.SSLPeerUnverifiedException; 038import javax.net.ssl.SSLSession; 039import javax.net.ssl.SSLSocket; 040import javax.net.ssl.X509TrustManager; 041 042import org.jivesoftware.smack.ConnectionConfiguration; 043import org.jivesoftware.smack.SmackException.SecurityNotPossibleException; 044 045 046public class TLSUtils { 047 048 private static final Logger LOGGER = Logger.getLogger(TLSUtils.class.getName()); 049 050 public static final String SSL = "SSL"; 051 public static final String TLS = "TLS"; 052 public static final String PROTO_SSL3 = SSL + "v3"; 053 public static final String PROTO_TLSV1 = TLS + "v1"; 054 public static final String PROTO_TLSV1_1 = TLS + "v1.1"; 055 public static final String PROTO_TLSV1_2 = TLS + "v1.2"; 056 public static final String PROTO_TLSV1_3 = TLS + "v1.3"; 057 058 /** 059 * Enable the recommended TLS protocols. 060 * 061 * @param builder the configuration builder to apply this setting to 062 * @param <B> Type of the ConnectionConfiguration builder. 063 * 064 * @return the given builder 065 */ 066 public static <B extends ConnectionConfiguration.Builder<B, ?>> B setEnabledTlsProtocolsToRecommended(B builder) { 067 builder.setEnabledSSLProtocols(new String[] { PROTO_TLSV1_3, PROTO_TLSV1_2 }); 068 return builder; 069 } 070 071 /** 072 * Enable only TLS. Connections created with the given ConnectionConfiguration will only support TLS. 073 * <p> 074 * According to the <a 075 * href="https://raw.githubusercontent.com/stpeter/manifesto/master/manifesto.txt">Encrypted 076 * XMPP Manifesto</a>, TLSv1.2 shall be deployed, providing fallback support for SSLv3 and 077 * TLSv1.1. This method goes one step beyond and upgrades the handshake to use TLSv1 or better. 078 * This method requires the underlying OS to support all of TLSv1.2 , 1.1 and 1.0. 079 * </p> 080 * 081 * @param builder the configuration builder to apply this setting to 082 * @param <B> Type of the ConnectionConfiguration builder. 083 * 084 * @return the given builder 085 * @deprecated use {@link #setEnabledTlsProtocolsToRecommended(org.jivesoftware.smack.ConnectionConfiguration.Builder)} instead. 086 */ 087 // TODO: Remove in Smack 4.5. 088 @Deprecated 089 public static <B extends ConnectionConfiguration.Builder<B, ?>> B setTLSOnly(B builder) { 090 builder.setEnabledSSLProtocols(new String[] { PROTO_TLSV1_2, PROTO_TLSV1_1, PROTO_TLSV1 }); 091 return builder; 092 } 093 094 /** 095 * Enable only TLS and SSLv3. Connections created with the given ConnectionConfiguration will 096 * only support TLS and SSLv3. 097 * <p> 098 * According to the <a 099 * href="https://raw.githubusercontent.com/stpeter/manifesto/master/manifesto.txt">Encrypted 100 * XMPP Manifesto</a>, TLSv1.2 shall be deployed, providing fallback support for SSLv3 and 101 * TLSv1.1. 102 * </p> 103 * 104 * @param builder the configuration builder to apply this setting to 105 * @param <B> Type of the ConnectionConfiguration builder. 106 * 107 * @return the given builder 108 * @deprecated use {@link #setEnabledTlsProtocolsToRecommended(org.jivesoftware.smack.ConnectionConfiguration.Builder)} instead. 109 */ 110 // TODO: Remove in Smack 4.5. 111 @Deprecated 112 public static <B extends ConnectionConfiguration.Builder<B, ?>> B setSSLv3AndTLSOnly(B builder) { 113 builder.setEnabledSSLProtocols(new String[] { PROTO_TLSV1_2, PROTO_TLSV1_1, PROTO_TLSV1, PROTO_SSL3 }); 114 return builder; 115 } 116 117 /** 118 * Accept all TLS certificates. 119 * <p> 120 * <b>Warning:</b> Use with care. This method make the Connection use {@link AcceptAllTrustManager} and essentially 121 * <b>invalidates all security guarantees provided by TLS</b>. Only use this method if you understand the 122 * implications. 123 * </p> 124 * 125 * @param builder a connection configuration builder. 126 * @param <B> Type of the ConnectionConfiguration builder. 127 * @return the given builder. 128 */ 129 public static <B extends ConnectionConfiguration.Builder<B, ?>> B acceptAllCertificates(B builder) { 130 builder.setCustomX509TrustManager(new AcceptAllTrustManager()); 131 return builder; 132 } 133 134 /** 135 * Disable the hostname verification of TLS certificates. 136 * <p> 137 * <b>Warning:</b> Use with care. This disables hostname verification of TLS certificates and essentially 138 * <b>invalidates all security guarantees provided by TLS</b>. Only use this method if you understand the 139 * implications. 140 * </p> 141 * 142 * @param builder a connection configuration builder. 143 * @param <B> Type of the ConnectionConfiguration builder. 144 * @return the given builder. 145 */ 146 public static <B extends ConnectionConfiguration.Builder<B, ?>> B disableHostnameVerificationForTlsCertificates(B builder) { 147 builder.setHostnameVerifier((hostname, session) -> { 148 return true; 149 }); 150 return builder; 151 } 152 153 public static void setEnabledProtocolsAndCiphers(final SSLSocket sslSocket, 154 String[] enabledProtocols, String[] enabledCiphers) 155 throws SecurityNotPossibleException { 156 if (enabledProtocols != null) { 157 Set<String> enabledProtocolsSet = new HashSet<String>(Arrays.asList(enabledProtocols)); 158 Set<String> supportedProtocolsSet = new HashSet<String>( 159 Arrays.asList(sslSocket.getSupportedProtocols())); 160 Set<String> protocolsIntersection = new HashSet<String>(supportedProtocolsSet); 161 protocolsIntersection.retainAll(enabledProtocolsSet); 162 if (protocolsIntersection.isEmpty()) { 163 throw new SecurityNotPossibleException("Request to enable SSL/TLS protocols '" 164 + StringUtils.collectionToString(enabledProtocolsSet) 165 + "', but only '" 166 + StringUtils.collectionToString(supportedProtocolsSet) 167 + "' are supported."); 168 } 169 170 // Set the enabled protocols 171 enabledProtocols = new String[protocolsIntersection.size()]; 172 enabledProtocols = protocolsIntersection.toArray(enabledProtocols); 173 sslSocket.setEnabledProtocols(enabledProtocols); 174 } 175 176 if (enabledCiphers != null) { 177 Set<String> enabledCiphersSet = new HashSet<String>(Arrays.asList(enabledCiphers)); 178 Set<String> supportedCiphersSet = new HashSet<String>( 179 Arrays.asList(sslSocket.getEnabledCipherSuites())); 180 Set<String> ciphersIntersection = new HashSet<String>(supportedCiphersSet); 181 ciphersIntersection.retainAll(enabledCiphersSet); 182 if (ciphersIntersection.isEmpty()) { 183 throw new SecurityNotPossibleException("Request to enable SSL/TLS ciphers '" 184 + StringUtils.collectionToString(enabledCiphersSet) 185 + "', but only '" 186 + StringUtils.collectionToString(supportedCiphersSet) 187 + "' are supported."); 188 } 189 190 enabledCiphers = new String[ciphersIntersection.size()]; 191 enabledCiphers = ciphersIntersection.toArray(enabledCiphers); 192 sslSocket.setEnabledCipherSuites(enabledCiphers); 193 } 194 } 195 196 /** 197 * Get the channel binding data for the 'tls-server-end-point' channel binding type. This channel binding type is 198 * defined in RFC 5929 § 4. 199 * 200 * @param sslSession the SSL/TLS session from which the data should be retrieved. 201 * @return the channel binding data. 202 * @throws SSLPeerUnverifiedException if we TLS peer could not be verified. 203 * @throws CertificateEncodingException if there was an encoding error with the certificate. 204 * @throws NoSuchAlgorithmException if no such algorithm is available. 205 * @see <a href="https://tools.ietf.org/html/rfc5929#section-4">RFC 5929 § 4.</a> 206 */ 207 public static byte[] getChannelBindingTlsServerEndPoint(final SSLSession sslSession) 208 throws SSLPeerUnverifiedException, CertificateEncodingException, NoSuchAlgorithmException { 209 final Certificate[] peerCertificates = sslSession.getPeerCertificates(); 210 final Certificate certificate = peerCertificates[0]; 211 final String certificateAlgorithm = certificate.getPublicKey().getAlgorithm(); 212 213 // RFC 5929 § 4.1 hash function selection. 214 String algorithm; 215 switch (certificateAlgorithm) { 216 case "MD5": 217 case "SHA-1": 218 algorithm = "SHA-256"; 219 break; 220 default: 221 algorithm = certificateAlgorithm; 222 break; 223 } 224 225 final MessageDigest messageDigest = MessageDigest.getInstance(algorithm); 226 final byte[] certificateDerEncoded = certificate.getEncoded(); 227 messageDigest.update(certificateDerEncoded); 228 return messageDigest.digest(); 229 } 230 231 /** 232 * A {@link X509TrustManager} that <b>doesn't validate</b> X.509 certificates. 233 * <p> 234 * Connections that use this TrustManager will just be encrypted, without any guarantee that the 235 * counter part is actually the intended one. Man-in-the-Middle attacks will be possible, since 236 * any certificate presented by the attacker will be considered valid. 237 * </p> 238 */ 239 public static class AcceptAllTrustManager implements X509TrustManager { 240 241 @Override 242 public void checkClientTrusted(X509Certificate[] arg0, String arg1) 243 throws CertificateException { 244 // Nothing to do here 245 } 246 247 @Override 248 public void checkServerTrusted(X509Certificate[] arg0, String arg1) 249 throws CertificateException { 250 // Nothing to do here 251 } 252 253 @Override 254 public X509Certificate[] getAcceptedIssuers() { 255 return new X509Certificate[0]; 256 } 257 } 258 259 private static final File DEFAULT_TRUSTSTORE_PATH; 260 261 static { 262 String javaHome = System.getProperty("java.home"); 263 String defaultTruststorePath = javaHome + File.separator + "lib" + File.separator + "security" + File.separator + "cacerts"; 264 DEFAULT_TRUSTSTORE_PATH = new File(defaultTruststorePath); 265 } 266 267 public static FileInputStream getDefaultTruststoreStreamIfPossible() { 268 try { 269 return new FileInputStream(DEFAULT_TRUSTSTORE_PATH); 270 } catch (FileNotFoundException e) { 271 LOGGER.log(Level.WARNING, "Could not open default truststore at " + DEFAULT_TRUSTSTORE_PATH, e); 272 return null; 273 } 274 } 275 276 enum DefaultTrustStoreType { 277 jks, 278 unknown, 279 no_default, 280 } 281 282 private static final int JKS_MAGIC = 0xfeedfeed; 283 private static final int JKS_VERSION_1 = 1; 284 private static final int JKS_VERSION_2 = 2; 285 286 public static DefaultTrustStoreType getDefaultTruststoreType() throws IOException { 287 try (InputStream inputStream = getDefaultTruststoreStreamIfPossible()) { 288 if (inputStream == null) { 289 return DefaultTrustStoreType.no_default; 290 } 291 292 DataInputStream dis = new DataInputStream(inputStream); 293 int magic = dis.readInt(); 294 int version = dis.readInt(); 295 296 if (magic == JKS_MAGIC && (version == JKS_VERSION_1 || version == JKS_VERSION_2)) { 297 return DefaultTrustStoreType.jks; 298 } 299 } 300 301 return DefaultTrustStoreType.unknown; 302 } 303 304 /** 305 * Tries to determine if the default truststore type is of type jks and sets the javax.net.ssl.trustStoreType system 306 * property to 'JKS' if so. This is meant as workaround in situations where the default truststore type is (still) 307 * 'jks' but we run on a newer JRE/JDK which uses PKCS#12 as type. See for example <a href="https://bugs.gentoo.org/712290">Gentoo bug #712290</a>. 308 */ 309 public static void setDefaultTrustStoreTypeToJksIfRequired() { 310 DefaultTrustStoreType defaultTrustStoreType; 311 try { 312 defaultTrustStoreType = getDefaultTruststoreType(); 313 } catch (IOException e) { 314 LOGGER.log(Level.WARNING, "Could not set keystore type to jks if required", e); 315 return; 316 } 317 318 if (defaultTrustStoreType == DefaultTrustStoreType.jks) { 319 System.setProperty("javax.net.ssl.trustStoreType", "JKS"); 320 } 321 } 322}