Wednesday, July 4, 2018

Default HostnameVerifier

Even if the SSLContext can verify that a certificate is signed by a trusted Certificate Authority, there is still room for problems. What happens if the connection hostname doesn’t match the certificate hostname? Java provides the HostnameVerifier interface to give client code the option of providing a policy for handling this situations. AsyncHttpClient adopts this interface for this purpose as well. However, unlike the JDK, the default policy provided by AsyncHttpClient is to allow all connections regardless of hostname.
Unlike SSLContext, using the Java default (HttpsURLConnection.getDefaultHostnameVerifier) is not a viable option because the default HostnameVerifier expects to only be called in the case that there is a mismatch (and therefore always returns false) while some of the AsyncHttpClient providers (e.g. Netty, the default) call it on all connections. To make matters worse, the check is not trivial (consider SAN and wildcard matching) and is implemented insun.security.util.HostnameChecker (a Sun internal proprietary API). This leaves the developer in the position of either depending on an internal API or finding/copying/creating another implementation of this functionality. For the examples in this article, I have opted for the first option.
Unfortunately, as with SSLContext, there does not appear to be a way to set the default HostnameVerifier used by AsyncHttpClient. Instead, applications must set their preferred HostnameVerifier for each connection.

/* An example program using AsyncHttpClient with SSL certificate verification
 *
 * To the extent possible under law, Kevin Locke has waived all copyright and
 * related or neighboring rights to this work.
 * A legal description of this waiver is available in LICENSE.txt.
 */
import com.ning.http.client.AsyncHttpClient;
import com.ning.http.client.AsyncHttpClientConfig;
import com.ning.http.client.Response;

import java.io.IOException;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.Principal;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.concurrent.ExecutionException;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLPeerUnverifiedException;
import javax.net.ssl.SSLSession;
import javax.security.auth.kerberos.KerberosPrincipal;
import sun.security.util.HostnameChecker;

/** Implements the "MyDownloader" application */
public class MyDownloader {

    /** HostnameVerifier implementation which implements the same policy as the
     * Java built-in pre-HostnameVerifier policy.
     */
    private static class MyHostnameVerifier implements HostnameVerifier {
        /** Checks if a given hostname matches the certificate or principal of
         * a given session.
         */
        private boolean hostnameMatches(String hostname, SSLSession session) {
            HostnameChecker checker =
                HostnameChecker.getInstance(HostnameChecker.TYPE_TLS);

            boolean validCertificate = false, validPrincipal = false;
            try {
                Certificate[] peerCertificates = session.getPeerCertificates();

                if (peerCertificates.length > 0 &&
                        peerCertificates[0] instanceof X509Certificate) {
                    X509Certificate peerCertificate =
                            (X509Certificate)peerCertificates[0];

                    try {
                        checker.match(hostname, peerCertificate);
                        // Certificate matches hostname
                        validCertificate = true;
                    } catch (CertificateException ex) {
                        // Certificate does not match hostname
                    }
                } else {
                    // Peer does not have any certificates or they aren't X.509
                }
            } catch (SSLPeerUnverifiedException ex) {
                // Not using certificates for peers, try verifying the principal
                try {
                    Principal peerPrincipal = session.getPeerPrincipal();
                    if (peerPrincipal instanceof KerberosPrincipal) {
                        validPrincipal = HostnameChecker.match(hostname,
                                (KerberosPrincipal)peerPrincipal);
                    } else {
                        // Can't verify principal, not Kerberos
                    }
                } catch (SSLPeerUnverifiedException ex2) {
                    // Can't verify principal, no principal
                }
            }

            return validCertificate || validPrincipal;
        }

        public boolean verify(String hostname, SSLSession session) {
            if (hostnameMatches(hostname, session)) {
                return true;
            } else {
                // TODO: Add application-specific checks for
                // hostname/certificate match
                return false;
            }
        }
    }

    public static void main(String[] args) {
        if (args.length != 1) {
            System.err.println("Usage: myhttp <URL>");
        } else {
            String url = args[0];

            SSLContext context = null;
            try {
                context = SSLContext.getInstance("TLS");
            } catch (NoSuchAlgorithmException e) {
                e.printStackTrace();
                return;
            }

            try {
                context.init(null, null, null);
            } catch (KeyManagementException e) {
                e.printStackTrace();
                return;
            }

            AsyncHttpClient client = new AsyncHttpClient(
                    new AsyncHttpClientConfig.Builder()
                        .setSSLContext(context)
                        .setHostnameVerifier(new MyHostnameVerifier())
                        .build()
                );

            Response response = null;
            try {
                response = client.prepareGet(url).execute().get();
            } catch (InterruptedException e) {
                e.printStackTrace();
                return;
            } catch (ExecutionException e) {
                e.printStackTrace();
                return;
            } catch (IOException e) {
                e.printStackTrace();
                return;
            }

            if (response.getStatusCode() / 100 == 2) {
                try {
                    String responseBody = response.getResponseBody();
                    System.err.println("Successfully downloaded " + url);
                    System.out.println(responseBody);
                } catch (IOException e) {
                    e.printStackTrace();
                    return;
                }
            } else {
                System.err.println("Failure downloading " + url +
                        ": HTTP Status " + response.getStatusCode());
            }
        }
    }
}


Ref https://kevinlocke.name/bits/2012/10/03/ssl-certificate-verification-in-dispatch-and-asynchttpclient/

No comments:

Post a Comment

java-8-streams-map-examples

package com.mkyong.java8; import java.math.BigDecimal; import java.util.ArrayList; import java.util.Arrays; import java.util.List; im...