org.codice.ddf.security.handler.pki.CrlChecker.java Source code

Java tutorial

Introduction

Here is the source code for org.codice.ddf.security.handler.pki.CrlChecker.java

Source

/**
 * Copyright (c) Codice Foundation
 *
 * <p>This is free software: you can redistribute it and/or modify it under the terms of the GNU
 * Lesser General Public License as published by the Free Software Foundation, either version 3 of
 * the License, or any later version.
 *
 * <p>This program 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 Lesser General Public License for more details. A copy of the GNU Lesser General Public
 * License is distributed along with this program and can be found at
 * <http://www.gnu.org/licenses/lgpl.html>.
 */
package org.codice.ddf.security.handler.pki;

import ddf.security.PropertiesLoader;
import ddf.security.common.audit.SecurityLogger;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.GeneralSecurityException;
import java.security.cert.CRL;
import java.security.cert.CertificateFactory;
import java.security.cert.X509CRL;
import java.security.cert.X509Certificate;
import java.util.Calendar;
import java.util.Properties;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import org.apache.commons.lang.time.DateUtils;
import org.apache.wss4j.common.crypto.Merlin;
import org.codice.ddf.configuration.AbsolutePathResolver;
import org.codice.ddf.platform.util.StandardThreadFactoryBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class CrlChecker {

    public static final String CRL_PROPERTY_KEY = Merlin.OLD_PREFIX + Merlin.X509_CRL_FILE;

    private static final Logger LOGGER = LoggerFactory.getLogger(CrlChecker.class);

    private static String encryptionPropertiesLocation = new AbsolutePathResolver(
            "etc/ws-security/server/encryption.properties").getPath();

    private static AtomicReference<CRL> crlCache = new AtomicReference<>();

    private static final CrlRefresh REFRESH = new CrlRefresh();

    static {
        Executors.newScheduledThreadPool(1, StandardThreadFactoryBuilder.newThreadFactory("crlCheckerThread"))
                .scheduleWithFixedDelay(REFRESH, 0, 1, TimeUnit.HOURS);
    }

    /**
     * Checks the given certs against the CRL. The CRL is configured in this class's constructor
     * method.
     *
     * @param certs certificates to be checked against the CRL
     * @return true if one of the certs passes the CRL or if CRL revocation is disabled, false if they
     *     are all revoked.
     */
    public boolean passesCrlCheck(X509Certificate[] certs) {
        if (crlCache.get() == null) {
            String errorMsg = "CRL is not set. Skipping CRL check";
            LOGGER.trace(errorMsg);
            return true;
        }
        LOGGER.trace("Checking request certs against CRL.");
        return passesCrl(certs);
    }

    /**
     * Checks if the provided cert is listed in the CRL.
     *
     * @param certs List of certs to be checked against the CRL
     * @return boolean value Whether or not the client presenting the certs should be let through
     */
    private boolean passesCrl(X509Certificate[] certs) {
        if (certs != null) {
            LOGGER.debug("Got {} certificate(s) in the incoming request", certs.length);
            for (X509Certificate curCert : certs) {
                if (crlCache.get() != null && crlCache.get().isRevoked(curCert)) {
                    SecurityLogger.audit("Denying access for subject DN: " + curCert.getSubjectDN()
                            + " due to certificate being revoked by CRL.");
                    return false;
                }
            }
        } else {
            LOGGER.debug("Allowing message through CRL check. There were no certificates sent by the client.");
            return true;
        }
        return true;
    }

    /**
     * Sets the location of the CRL. Enables CRL checking if property is set, disables it otherwise
     *
     * @param location Location of the DER-encoded CRL file that should be used to check certificate
     *     revocation.
     */
    public void setCrlLocation(String location) {
        REFRESH.setCrlLocation(location);
    }

    static URL urlFromPath(String location) {
        try {
            return new URL(location);
        } catch (MalformedURLException e) {
            return null;
        }
    }

    /**
     * Loads the properties from a given location.
     *
     * @param location location of properties file
     * @return Properties from the file
     */
    static Properties loadProperties(String location) {
        return PropertiesLoader.loadProperties(location);
    }

    /**
     * Runnable to refresh the CRL from the URL when either the nextUpdate time has elapsed or it has
     * rolled over to the next day.
     */
    private static class CrlRefresh implements Runnable {
        private Lock lock = new ReentrantLock();

        private Calendar start = Calendar.getInstance();

        public void run() {
            String crlLocation = loadProperties(encryptionPropertiesLocation).getProperty(CRL_PROPERTY_KEY);

            lock.lock();
            try {
                CRL crl = crlCache.get();
                if (crl == null) {
                    setCrlLocation(crlLocation);
                }

                if (crl instanceof X509CRL && checkCrlUpdate((X509CRL) crl)) {
                    setCrlLocation(crlLocation);
                }
            } finally {
                lock.unlock();
            }
        }

        /**
         * Sets the location of the CRL. Enables CRL checking if property is set, disables it otherwise
         *
         * @param location Location of the DER-encoded CRL file that should be used to check certificate
         *     revocation.
         */
        void setCrlLocation(String location) {
            lock.lock();
            try {
                if (location == null) {
                    LOGGER.info("CRL property in {} is not set. Certs will not be checked against a CRL",
                            encryptionPropertiesLocation);
                    crlCache.set(null);
                } else {
                    CRL crl = createCrl(location);
                    if (crl != null) {
                        crlCache.set(crl);
                        LOGGER.info("CRL has been updated from {}.", location);
                    }
                }
                start = Calendar.getInstance();
            } finally {
                lock.unlock();
            }
        }

        /**
         * Generates a new CRL object from the given location.
         *
         * @param location File Path or URL to the CRL file
         * @return A CRL object constructed from the given file path or URL. Null if an error occurred
         *     while attempting to read the file.
         */
        private CRL createCrl(String location) {
            URL url = urlFromPath(location);

            // If we get a URL, use URL, otherwise use as local file path
            try (InputStream is = url != null ? url.openStream() : new FileInputStream(new File(location))) {

                CertificateFactory cf = CertificateFactory.getInstance("X.509");
                return cf.generateCRL(is);
            } catch (IOException e) {
                LOGGER.warn("An error occurred while accessing {}", location, e);
                return null;
            } catch (GeneralSecurityException e) {
                LOGGER.warn(
                        "Encountered an error while generating CRL from file {}. CRL checking may not work correctly. Check the CRL file.",
                        location, e);
                return null;
            }
        }

        private boolean checkCrlUpdate(X509CRL x509Crl) {
            return x509Crl.getNextUpdate() == null
                    || Calendar.getInstance().getTime().after(x509Crl.getNextUpdate()) || rolledOverDay();
        }

        private boolean rolledOverDay() {
            return !DateUtils.isSameDay(Calendar.getInstance(), start);
        }
    }
}