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}