org.graylog.plugins.auth.tls.sso.CertificateTools.java Source code

Java tutorial

Introduction

Here is the source code for org.graylog.plugins.auth.tls.sso.CertificateTools.java

Source

/**
 * This file is part of Graylog Archive.
 *
 * Graylog Archive is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Graylog Archive is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Graylog Archive.  If not, see <http://www.gnu.org/licenses/>.
 */
package org.graylog.plugins.auth.tls.sso;

import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.io.StringReader;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.bouncycastle.asn1.ASN1Encodable;
import org.bouncycastle.asn1.x500.AttributeTypeAndValue;
import org.bouncycastle.asn1.x500.RDN;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x500.X500NameStyle;
import org.bouncycastle.asn1.x500.style.BCStyle;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
import org.bouncycastle.openssl.PEMParser;

public class CertificateTools {

    private static final Logger log = LoggerFactory.getLogger(CertificateTools.class);

    private static Map<String, String> extendedUsageOids;
    private static LoadingCache<String, UserInfo> userInfos;

    static {
        extendedUsageOids = new HashMap<>();
        extendedUsageOids.put("2.5.29.37.0", "All usages");
        extendedUsageOids.put("1.3.6.1.5.5.7.3.0", "All usages");
        extendedUsageOids.put("1.3.6.1.5.5.7.3.1", "Server authentication");
        extendedUsageOids.put("1.3.6.1.5.5.7.3.2", "Client authentication");
        extendedUsageOids.put("1.3.6.1.5.5.7.3.3", "Code signing");
        extendedUsageOids.put("1.3.6.1.5.5.7.3.4", "Email protection");
        extendedUsageOids.put("1.3.6.1.5.5.7.3.5", "IPSec end system");
        extendedUsageOids.put("1.3.6.1.5.5.7.3.6", "IPSec tunnel");
        extendedUsageOids.put("1.3.6.1.5.5.7.3.7", "IPSec user");
        extendedUsageOids.put("1.3.6.1.5.5.7.3.8", "Timestamping");
        extendedUsageOids.put("1.3.6.1.4.1.311.20.2.2", "Smartcard Logon");
        extendedUsageOids.put("1.3.6.1.5.5.7.3.9", "OCSP signer");

        userInfos = CacheBuilder.newBuilder().maximumSize(1000).expireAfterAccess(1, TimeUnit.HOURS)
                .build(new CacheLoader<String, UserInfo>() {
                    public UserInfo load(String key) throws CertificateException, IOException {
                        return getUserInfoFromHeader(key);
                    }
                });
    }

    private static UserInfo getUserInfoFromHeader(String header) throws IOException, CertificateException {
        final String normalizedHeader = normalizePEM(header);
        log.debug("Header: {}", normalizedHeader);

        try (PEMParser certReader = new PEMParser(new StringReader(normalizedHeader))) {
            // Parse the certificate from header
            Object certObj = certReader.readObject();

            if (!(certObj instanceof X509CertificateHolder)) {
                throw new CertificateException("Unable to parse trusted header");
            }

            X509CertificateHolder certificate = (X509CertificateHolder) certObj;

            // Inspect the certificate
            Map<String, String> certificateInformation = convertCertificateInformation(certificate);

            // Debug logging to assist in diagnostics
            for (Map.Entry<String, String> entry : certificateInformation.entrySet()) {
                log.debug("Parsed certificate information {} : {}", entry.getKey(), entry.getValue());
            }

            // Handle VRK CA for Qualified Certificates - G2
            // ... and everything that had compatible attributes, at least until more needs arise
            //if ("C=FI,O=Vaestorekisterikeskus CA,OU=Organisaatiovarmenteet,CN=VRK CA for Qualified Certificates - G2".equals(certificateInformation.getOrDefault("ISSUER", "unknown"))) {
            return new UserInfo(certificateInformation.getOrDefault("rfc822Name", "unknown@unknown"),
                    certificateInformation.getOrDefault("rfc822Name", "unknown@unknown"),
                    certificateInformation.getOrDefault("GIVENNAME", "unknown") + " "
                            + certificateInformation.getOrDefault("SURNAME", "unknown"));
            //}

            // Probably no handler available for this certificate
            //log.warn("No handler for certificate issued by {}", certificateInformation.getOrDefault("ISSUER", "unknown"));
        }

        //return null;
    }

    private static Map<String, String> convertCertificateInformation(X509CertificateHolder certificate)
            throws CertificateException {
        final X500NameStyle x500NameStyle = BCStyle.INSTANCE;
        final CertificateInfo<String, String> certInfo = new CertificateInfo<>();

        // Stores relative distinguished names of Subject
        X500Name subject = certificate.getSubject();
        for (RDN rdn : subject.getRDNs()) {
            if (rdn.getFirst() == null) {
                log.warn("Unable to get first RDN");
                continue;
            }
            AttributeTypeAndValue atav = rdn.getFirst();
            if (atav == null) {
                log.warn("Unable to get first AttributeTypeAndValue");
                continue;
            }
            String displayName = x500NameStyle.oidToDisplayName(atav.getType());
            ASN1Encodable value = atav.getValue();
            if (displayName != null && value != null) {
                certInfo.putLogString(displayName, value);
            }
        }

        certInfo.putLogString("CERT_SERIAL", certificate.getSerialNumber());
        certInfo.putLogString("ISSUER", certificate.getIssuer());

        // Convert to java.security.cert.X509Certificate
        X509Certificate jcert = new JcaX509CertificateConverter().setProvider("BC").getCertificate(certificate);

        // Set subject alternate names
        // There may be several of the same type in the certificate. This implementation will overwrite in collisions!
        Collection<List<?>> sans = jcert.getSubjectAlternativeNames();
        if (sans != null)
            for (List<?> san : sans) {
                Object[] sanArray = san.toArray();
                switch ((Integer) sanArray[0]) {
                // These are known to be Strings
                case 1:
                    if (sanArray[1] != null)
                        certInfo.putLogString("rfc822Name", sanArray[1]);
                    break;
                case 2:
                    if (sanArray[1] != null)
                        certInfo.putLogString("dNSName", sanArray[1]);
                    break;
                case 4:
                    if (sanArray[1] != null)
                        certInfo.putLogString("directoryName", sanArray[1]);
                    break;
                case 6:
                    if (sanArray[1] != null)
                        certInfo.putLogString("uniformResourceIdentifier", sanArray[1]);
                    break;
                case 7:
                    if (sanArray[1] != null)
                        certInfo.putLogString("iPAddress", sanArray[1]);
                    break;
                case 8:
                    if (sanArray[1] != null)
                        certInfo.putLogString("registeredID", sanArray[1]);
                    break;
                }
            }

        // Populate key usages
        boolean[] keyUsages = jcert.getKeyUsage();
        if (keyUsages != null && keyUsages.length == 9) {
            if (keyUsages[0])
                certInfo.putLogString("Usage digitalSignature", "true");
            if (keyUsages[1])
                certInfo.putLogString("Usage nonRepudiation", "true");
            if (keyUsages[2])
                certInfo.putLogString("Usage keyEncipherment", "true");
            if (keyUsages[3])
                certInfo.putLogString("Usage dataEncipherment", "true");
            if (keyUsages[4])
                certInfo.putLogString("Usage keyAgreement", "true");
            if (keyUsages[5])
                certInfo.putLogString("Usage keyCertSign", "true");
            if (keyUsages[6])
                certInfo.putLogString("Usage cRLSign", "true");
            if (keyUsages[7])
                certInfo.putLogString("Usage encipherOnly", "true");
            if (keyUsages[8])
                certInfo.putLogString("Usage decipherOnly", "true");
        }

        // Populate extended usages
        List<String> extendedUsage = jcert.getExtendedKeyUsage();
        if (extendedUsage != null)
            for (String s : extendedUsage) {
                if (extendedUsageOids.containsKey(s)) {
                    certInfo.putLogString("Usage " + extendedUsageOids.get(s), "true");
                } else {
                    log.warn("Unknown extended usage OID: {}", s);
                }
            }

        return certInfo;
    }

    public static UserInfo getUserinfoFromHeader(String header) {
        try {
            return userInfos.get(header);
        } catch (ExecutionException e) {
            log.error("Unable to get user information from cache", e);
        }
        return null;
    }

    private static final String PEM_MAIN_REGEX = "^---[-]+BEGIN[^-]+---[-]+([^-]*)--[-]+END [^-]+---[-]+$";
    private static final Pattern pemMainRegex = Pattern.compile(PEM_MAIN_REGEX);

    public static String normalizePEM(String header) {
        final Matcher matcher = pemMainRegex.matcher(header);
        if (matcher.matches()) {
            return "-----BEGIN CERTIFICATE-----\n" + matcher.group(1).trim().replace(" ", "\n")
                    + "\n-----END CERTIFICATE-----\n";
        }
        // Unable to clean this mess up
        return header;
    }

}