io.macgyver.core.util.CertChecker.java Source code

Java tutorial

Introduction

Here is the source code for io.macgyver.core.util.CertChecker.java

Source

/**
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package io.macgyver.core.util;

import io.macgyver.neorx.rest.impl.SslTrust;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.net.URL;
import java.security.GeneralSecurityException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Objects;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLSession;

import org.bouncycastle.asn1.x500.RDN;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x500.style.BCStyle;
import org.bouncycastle.asn1.x500.style.IETFUtils;
import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder;
import org.joda.time.DateTime;
import org.joda.time.Days;
import org.joda.time.format.ISODateTimeFormat;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.base.Strings;
import com.squareup.okhttp.OkHttpClient;
import com.squareup.okhttp.Request;
import com.squareup.okhttp.Response;

public class CertChecker {

    public static final String DESCRIPTION = "description";
    int certExpirationWarningDays = 30;

    public int getCertExpirationWarningDays() {
        return this.certExpirationWarningDays;
    }

    public void setCertExpirationWarningDays(int d) {
        this.certExpirationWarningDays = d;
    }

    public java.security.cert.X509Certificate convert(javax.security.cert.X509Certificate cert) {
        try {
            byte[] encoded = cert.getEncoded();
            ByteArrayInputStream bis = new ByteArrayInputStream(encoded);
            java.security.cert.CertificateFactory cf = java.security.cert.CertificateFactory.getInstance("X.509");
            return (java.security.cert.X509Certificate) cf.generateCertificate(bis);
        } catch (java.security.cert.CertificateEncodingException e) {
        } catch (javax.security.cert.CertificateEncodingException e) {
        } catch (java.security.cert.CertificateException e) {
        }
        return null;
    }

    public class CertExtractor implements HostnameVerifier {
        List<X509Certificate> certList = new ArrayList<X509Certificate>();
        String requestedHost = null;

        @Override
        public boolean verify(String hostname, SSLSession session) {
            requestedHost = hostname;

            try {
                javax.security.cert.X509Certificate[] certs = session.getPeerCertificateChain();
                for (javax.security.cert.X509Certificate cert : certs) {

                    X509Certificate xx = convert(cert);
                    certList.add(xx);

                }
            } catch (Exception e) {
                e.printStackTrace();
            }
            return true;
        }

    }

    public boolean doesCertificateHostnameMatch(List<X509Certificate> certList, String hostname) {
        try {
            X509Certificate cert = certList.get(0);
            String cn = extractCN(cert);

            if (Strings.isNullOrEmpty(cn)) {
                // fall through
            } else {
                if (hostname != null) {
                    if (hostname.equalsIgnoreCase(cn)) {
                        return true;
                    }
                    if (cn.startsWith("*")) {
                        return hostname.toLowerCase().endsWith(cn.substring(1).toLowerCase());
                    }
                }

            }
            for (List alternativeNames : cert.getSubjectAlternativeNames()) {
                for (Object val : alternativeNames) {
                    if (hostname.equalsIgnoreCase(Objects.toString(val, ""))) {
                        return true;
                    }

                }
            }

            return false;
        } catch (Exception e) {
            // ignore
        }
        return false;
    }

    public String extractCN(X509Certificate cert) throws GeneralSecurityException {
        X500Name x500name = new JcaX509CertificateHolder(cert).getSubject();
        RDN cn = x500name.getRDNs(BCStyle.CN)[0];

        String valx = IETFUtils.valueToString(cn.getFirst().getValue());
        return valx;
    }

    private List<ObjectNode> checkCertChain(String url) throws IOException {
        List<ObjectNode> problems = new ArrayList<ObjectNode>();
        try {
            OkHttpClient c = new OkHttpClient();
            Request.Builder b = new Request.Builder().url(url);
            c.setHostnameVerifier(SslTrust.withoutHostnameVerification());
            Response response = c.newCall(b.build()).execute();
            response.body().close();
        } catch (Exception e) {
            if (e.toString().contains("PKIX path building failed")) {
                ObjectNode n = new ObjectMapper().createObjectNode();
                n.put(DESCRIPTION, "invalid certficate chain");
                problems.add(n);
            }

        }
        return problems;

    }

    public ObjectNode checkCertificates(String url) throws GeneralSecurityException, IOException {
        return checkCertificates(url, null);
    }

    public ObjectNode checkCertificates(String url, String alternateHost)
            throws IOException, GeneralSecurityException {
        ObjectNode n = new ObjectMapper().createObjectNode();
        return checkCertificates(n, url, alternateHost);
    }

    protected ObjectNode checkCertificates(ObjectNode n, String url, String alternateHost)
            throws IOException, GeneralSecurityException {
        ObjectMapper mapper = new ObjectMapper();

        ArrayNode problems = mapper.createArrayNode();
        n.set("problems", problems);
        n.set("certs", mapper.createArrayNode());
        try {
            URL xurl = new URL(url);
            String host = xurl.getHost();
            n.put("host", alternateHost != null ? alternateHost : host);
            n.put("url", url);
            List<X509Certificate> certs = fetchCertificates(url);

            n = checkCertificates(n, certs, alternateHost != null ? alternateHost : host);

            try {

                List<ObjectNode> chainErrors = checkCertChain(url);
                ArrayNode errors = (ArrayNode) n.path("problems");
                for (ObjectNode x : chainErrors) {
                    errors.add(x);
                }
            } catch (Exception e) {
                ((ArrayNode) n.path("problems")).add(
                        new ObjectMapper().createObjectNode().put("type", "error").put(DESCRIPTION, e.toString()));
            }
        } catch (Exception e) {
            problems.add(mapper.createObjectNode().put("type", "error").put("description", e.toString()));
        }
        return n;

    }

    public ObjectNode checkCertificates(List<X509Certificate> certs) {
        return checkCertificates(certs, null);
    }

    public ObjectNode checkCertificates(List<X509Certificate> certs, String alternateHost) {
        ObjectMapper mapper = new ObjectMapper();
        ObjectNode results = mapper.createObjectNode();
        ArrayNode certArray = mapper.createArrayNode();
        ArrayNode problems = mapper.createArrayNode();
        results.set("problems", problems);
        results.set("certs", certArray);
        return checkCertificates(results, certs, alternateHost);
    }

    protected ObjectNode checkCertificates(ObjectNode nx, List<X509Certificate> certs, String alternateHost) {
        int fewestDaysToExpiration = Integer.MAX_VALUE;
        ObjectMapper mapper = new ObjectMapper();
        ArrayNode problems = (ArrayNode) nx.get("problems");
        ArrayNode certArray = (ArrayNode) nx.get("certs");

        try {

            for (X509Certificate cert : certs) {
                ObjectNode n = mapper.createObjectNode();
                String subjectDN = cert.getSubjectDN().getName();
                n.put("subjectDN", subjectDN);

                n.put("subjectCN", extractCN(cert));
                n.put("version", cert.getVersion());
                n.put("issuerDN", cert.getIssuerDN().getName());
                n.put("notValidAfter", ISODateTimeFormat.dateTime().print(cert.getNotAfter().getTime()));
                n.put("notValidAfterTimestamp", cert.getNotAfter().getTime());
                n.put("notValidBefore", ISODateTimeFormat.dateTime().print(cert.getNotBefore().getTime()));
                n.put("notValidBeforeTimestamp", cert.getNotBefore().getTime());
                n.put("serial", cert.getSerialNumber().toString());
                n.put("isDateValid", true);
                n.put("version", cert.getVersion());
                n.put("type", cert.getType());

                if (System.currentTimeMillis() < cert.getNotBefore().getTime()) {
                    problems.add(mapper.createObjectNode().put(DESCRIPTION, "certificate not yet valid").put("type",
                            "error"));

                    n.put("isDateValid", false);
                }
                if (System.currentTimeMillis() > cert.getNotAfter().getTime()) {

                    problems.add(
                            mapper.createObjectNode().put(DESCRIPTION, "certificate expired").put("type", "error"));

                    n.put("isDateValid", false);
                }
                int daysToExpiration = Math.max(0,
                        Days.daysBetween(new DateTime(System.currentTimeMillis()), new DateTime(cert.getNotAfter()))
                                .getDays());
                n.put("daysToExpiration", daysToExpiration);
                fewestDaysToExpiration = Math.min(fewestDaysToExpiration, daysToExpiration);

                certArray.add(n);

                Collection<List<?>> altNames = cert.getSubjectAlternativeNames();
                ArrayNode altNameArray = mapper.createArrayNode();
                n.set("subjectAlternativeNames", altNameArray);
                // http://stackoverflow.com/questions/18775206/extracting-subjectalternativename-from-x509-in-java
                if (altNames != null) {
                    for (List altList : altNames) {
                        if (altList.size() == 2) {
                            ObjectNode altData = mapper.createObjectNode();
                            altData.put("type", Integer.parseInt(altList.get(0).toString()));
                            altData.put("value", altList.get(1).toString());

                            altNameArray.add(altData);
                        }
                    }
                }

            }

        } catch (Exception e) {
            problems.add(mapper.createObjectNode().put("type", "error").put(DESCRIPTION, e.toString()));
        }

        if (!doesCertificateHostnameMatch(certs, alternateHost)) {
            problems.add(mapper.createObjectNode().put(DESCRIPTION, "host name does not match certificate")
                    .put("type", "error"));

        }
        if (fewestDaysToExpiration <= getCertExpirationWarningDays()) {
            problems.add(mapper.createObjectNode()
                    .put(DESCRIPTION, "certificate will expire in " + fewestDaysToExpiration + " days")
                    .put("type", "warning"));
        }

        nx.put("daysToExpiration", fewestDaysToExpiration);
        return nx;
    }

    public List<X509Certificate> fetchCertificates(String httpUrl) throws IOException {
        OkHttpClient c = new OkHttpClient();

        CertExtractor extractor = new CertExtractor();
        c.setHostnameVerifier(extractor);
        c.setSslSocketFactory(SslTrust.withoutCertificateValidation().getSocketFactory());

        Request r = new Request.Builder().url(httpUrl).build();

        Response response = c.newCall(r).execute();
        response.body().close();

        return extractor.certList;

    }

}