001/** 002 * 003 * Copyright 2014-2024 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 * Accept all TLS certificates. 073 * <p> 074 * <b>Warning:</b> Use with care. This method make the Connection use {@link AcceptAllTrustManager} and essentially 075 * <b>invalidates all security guarantees provided by TLS</b>. Only use this method if you understand the 076 * implications. 077 * </p> 078 * 079 * @param builder a connection configuration builder. 080 * @param <B> Type of the ConnectionConfiguration builder. 081 * @return the given builder. 082 */ 083 public static <B extends ConnectionConfiguration.Builder<B, ?>> B acceptAllCertificates(B builder) { 084 builder.setCustomX509TrustManager(new AcceptAllTrustManager()); 085 return builder; 086 } 087 088 /** 089 * Disable the hostname verification of TLS certificates. 090 * <p> 091 * <b>Warning:</b> Use with care. This disables hostname verification of TLS certificates and essentially 092 * <b>invalidates all security guarantees provided by TLS</b>. Only use this method if you understand the 093 * implications. 094 * </p> 095 * 096 * @param builder a connection configuration builder. 097 * @param <B> Type of the ConnectionConfiguration builder. 098 * @return the given builder. 099 */ 100 public static <B extends ConnectionConfiguration.Builder<B, ?>> B disableHostnameVerificationForTlsCertificates(B builder) { 101 builder.setHostnameVerifier((hostname, session) -> { 102 return true; 103 }); 104 return builder; 105 } 106 107 public static void setEnabledProtocolsAndCiphers(final SSLSocket sslSocket, 108 String[] enabledProtocols, String[] enabledCiphers) 109 throws SecurityNotPossibleException { 110 if (enabledProtocols != null) { 111 Set<String> enabledProtocolsSet = new HashSet<String>(Arrays.asList(enabledProtocols)); 112 Set<String> supportedProtocolsSet = new HashSet<String>( 113 Arrays.asList(sslSocket.getSupportedProtocols())); 114 Set<String> protocolsIntersection = new HashSet<String>(supportedProtocolsSet); 115 protocolsIntersection.retainAll(enabledProtocolsSet); 116 if (protocolsIntersection.isEmpty()) { 117 throw new SecurityNotPossibleException("Request to enable SSL/TLS protocols '" 118 + StringUtils.collectionToString(enabledProtocolsSet) 119 + "', but only '" 120 + StringUtils.collectionToString(supportedProtocolsSet) 121 + "' are supported."); 122 } 123 124 // Set the enabled protocols 125 enabledProtocols = new String[protocolsIntersection.size()]; 126 enabledProtocols = protocolsIntersection.toArray(enabledProtocols); 127 sslSocket.setEnabledProtocols(enabledProtocols); 128 } 129 130 if (enabledCiphers != null) { 131 Set<String> enabledCiphersSet = new HashSet<String>(Arrays.asList(enabledCiphers)); 132 Set<String> supportedCiphersSet = new HashSet<String>( 133 Arrays.asList(sslSocket.getEnabledCipherSuites())); 134 Set<String> ciphersIntersection = new HashSet<String>(supportedCiphersSet); 135 ciphersIntersection.retainAll(enabledCiphersSet); 136 if (ciphersIntersection.isEmpty()) { 137 throw new SecurityNotPossibleException("Request to enable SSL/TLS ciphers '" 138 + StringUtils.collectionToString(enabledCiphersSet) 139 + "', but only '" 140 + StringUtils.collectionToString(supportedCiphersSet) 141 + "' are supported."); 142 } 143 144 enabledCiphers = new String[ciphersIntersection.size()]; 145 enabledCiphers = ciphersIntersection.toArray(enabledCiphers); 146 sslSocket.setEnabledCipherSuites(enabledCiphers); 147 } 148 } 149 150 /** 151 * Get the channel binding data for the 'tls-server-end-point' channel binding type. This channel binding type is 152 * defined in RFC 5929 § 4. 153 * 154 * @param sslSession the SSL/TLS session from which the data should be retrieved. 155 * @return the channel binding data. 156 * @throws SSLPeerUnverifiedException if we TLS peer could not be verified. 157 * @throws CertificateEncodingException if there was an encoding error with the certificate. 158 * @throws NoSuchAlgorithmException if no such algorithm is available. 159 * @see <a href="https://tools.ietf.org/html/rfc5929#section-4">RFC 5929 § 4.</a> 160 */ 161 public static byte[] getChannelBindingTlsServerEndPoint(final SSLSession sslSession) 162 throws SSLPeerUnverifiedException, CertificateEncodingException, NoSuchAlgorithmException { 163 final Certificate[] peerCertificates = sslSession.getPeerCertificates(); 164 final Certificate certificate = peerCertificates[0]; 165 final String certificateAlgorithm = certificate.getPublicKey().getAlgorithm(); 166 167 // RFC 5929 § 4.1 hash function selection. 168 String algorithm; 169 switch (certificateAlgorithm) { 170 case "MD5": 171 case "SHA-1": 172 algorithm = "SHA-256"; 173 break; 174 default: 175 algorithm = certificateAlgorithm; 176 break; 177 } 178 179 final MessageDigest messageDigest = MessageDigest.getInstance(algorithm); 180 final byte[] certificateDerEncoded = certificate.getEncoded(); 181 messageDigest.update(certificateDerEncoded); 182 return messageDigest.digest(); 183 } 184 185 /** 186 * A {@link X509TrustManager} that <b>doesn't validate</b> X.509 certificates. 187 * <p> 188 * Connections that use this TrustManager will just be encrypted, without any guarantee that the 189 * counter part is actually the intended one. Man-in-the-Middle attacks will be possible, since 190 * any certificate presented by the attacker will be considered valid. 191 * </p> 192 */ 193 public static class AcceptAllTrustManager implements X509TrustManager { 194 195 @Override 196 public void checkClientTrusted(X509Certificate[] arg0, String arg1) 197 throws CertificateException { 198 // Nothing to do here 199 } 200 201 @Override 202 public void checkServerTrusted(X509Certificate[] arg0, String arg1) 203 throws CertificateException { 204 // Nothing to do here 205 } 206 207 @Override 208 public X509Certificate[] getAcceptedIssuers() { 209 return new X509Certificate[0]; 210 } 211 } 212 213 private static final File DEFAULT_TRUSTSTORE_PATH; 214 215 static { 216 String javaHome = System.getProperty("java.home"); 217 String defaultTruststorePath = javaHome + File.separator + "lib" + File.separator + "security" + File.separator + "cacerts"; 218 DEFAULT_TRUSTSTORE_PATH = new File(defaultTruststorePath); 219 } 220 221 public static FileInputStream getDefaultTruststoreStreamIfPossible() { 222 try { 223 return new FileInputStream(DEFAULT_TRUSTSTORE_PATH); 224 } catch (FileNotFoundException e) { 225 LOGGER.log(Level.WARNING, "Could not open default truststore at " + DEFAULT_TRUSTSTORE_PATH, e); 226 return null; 227 } 228 } 229 230 enum DefaultTrustStoreType { 231 jks, 232 unknown, 233 no_default, 234 } 235 236 private static final int JKS_MAGIC = 0xfeedfeed; 237 private static final int JKS_VERSION_1 = 1; 238 private static final int JKS_VERSION_2 = 2; 239 240 public static DefaultTrustStoreType getDefaultTruststoreType() throws IOException { 241 try (InputStream inputStream = getDefaultTruststoreStreamIfPossible()) { 242 if (inputStream == null) { 243 return DefaultTrustStoreType.no_default; 244 } 245 246 DataInputStream dis = new DataInputStream(inputStream); 247 int magic = dis.readInt(); 248 int version = dis.readInt(); 249 250 if (magic == JKS_MAGIC && (version == JKS_VERSION_1 || version == JKS_VERSION_2)) { 251 return DefaultTrustStoreType.jks; 252 } 253 } 254 255 return DefaultTrustStoreType.unknown; 256 } 257 258 /** 259 * Tries to determine if the default truststore type is of type jks and sets the javax.net.ssl.trustStoreType system 260 * property to 'JKS' if so. This is meant as workaround in situations where the default truststore type is (still) 261 * '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>. 262 */ 263 public static void setDefaultTrustStoreTypeToJksIfRequired() { 264 DefaultTrustStoreType defaultTrustStoreType; 265 try { 266 defaultTrustStoreType = getDefaultTruststoreType(); 267 } catch (IOException e) { 268 LOGGER.log(Level.WARNING, "Could not set keystore type to jks if required", e); 269 return; 270 } 271 272 if (defaultTrustStoreType == DefaultTrustStoreType.jks) { 273 System.setProperty("javax.net.ssl.trustStoreType", "JKS"); 274 } 275 } 276}