org.glite.security.util.FileCRLChecker.java Source code

Java tutorial

Introduction

Here is the source code for org.glite.security.util.FileCRLChecker.java

Source

/*
 * Copyright (c) Members of the EGEE Collaboration. 2004. See
 * http://www.eu-egee.org/partners/ for details on the copyright holders.
 * 
 * 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 org.glite.security.util;

import java.io.File;
import java.io.IOException;
import java.security.SignatureException;
import java.security.cert.CertificateException;
import java.security.cert.X509CRL;
import java.security.cert.X509CRLEntry;
import java.security.cert.X509Certificate;
import java.util.Date;
import java.util.Iterator;
import java.util.Set;
import java.util.Vector;

import org.apache.log4j.Logger;
import org.bouncycastle.asn1.ASN1Object;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.DEROctetString;
import org.bouncycastle.asn1.x509.IssuingDistributionPoint;
import org.bouncycastle.asn1.x509.X509Extensions;
import org.glite.security.trustmanager.ContextWrapper;

/**
 * The RevocationChecker implementation for checking the certificate revocation against file system stored CRLs with .r0
 * file ending and CA hash value as file prefix.
 * 
 * @author Joni Hahkala
 */
public class FileCRLChecker extends RevocationChecker {
    /** Logging facility */
    private static final Logger LOGGER = Logger.getLogger(FileCRLChecker.class);

    /** The CRL to use. */
    private X509CRL m_crl = null;

    /*
     * TODO: remove this, do not store ca cert as it's not updated, make new method checkCRL with cacert as input.
     */
    /** The CA certificate for checking the CRL signature and possible extension checks. */
    private X509Certificate m_caCert;

    /** The CA files base name. */
    private String m_caBaseFilename;

    /** The CRL file name. */
    private String m_crlFilename;

    /** The CA number. */
    private int m_caNumber;

    /** The time the CRL file was last modified. */
    private long m_crlModified = -255;

    /** Whether the CRLs are required or not for the CA. */
    private boolean m_crlRequired = true;

    /** Set if the creation of CRL failed, to be printed as the failure later when checking fails. */
    private String failureString = null;

    /** Certificate reader used by all instances of this class, to avoid creating new ones for each. */
    static FileCertReader s_certReader;

    static {
        try {
            s_certReader = new FileCertReader();
        } catch (CertificateException e) {
            throw new RuntimeException("Security provider initialization failed: " + e.getMessage(), e);
        }
    }

    /**
     * Generates a new instance of the checker.
     * 
     * @param caCert
     * @param caBaseFilename the base CA filename that is used to load the crl.
     * @param caNumber the number of ca with the same DN.
     * @param props the properties to use in case they need to be used for flags etc.
     */
    public FileCRLChecker(X509Certificate caCert, String caBaseFilename, int caNumber,
            CaseInsensitiveProperties props) {
        super(caCert, caBaseFilename, caNumber, props);
        m_caCert = caCert;
        m_caBaseFilename = caBaseFilename;
        m_caNumber = caNumber;
        m_crlFilename = m_caBaseFilename + FullTrustAnchor.CRL_FILE_ENDING_PREFIX + m_caNumber;

        String crlReqText = null;

        if (props != null) {
            crlReqText = props.getProperty(ContextWrapper.CRL_REQUIRED);
        }

        if (crlReqText != null) {
            crlReqText = crlReqText.trim().toLowerCase();
        } else {
            crlReqText = ContextWrapper.CRL_REQUIRED_DEFAULT;
        }

        if (crlReqText.startsWith("f") || crlReqText.startsWith("n")) {
            m_crlRequired = false;
        }
        checkUpdate(); // load the crl if possible
    }

    /**
     * Checks the CRL for validity.
     * 
     * @throws CertificateException if signature if wrong or the CRL has unsupported or invalid critical extensions.
     * @throws IOException in case CRL extension parsing fails.
     */
    private void checkCrl() throws CertificateException, IOException {
        if (m_crl == null) {
            throw new IllegalArgumentException();
        }

        // check that there are no unhandled critical extensions.
        Set<String> criticalOIDs = m_crl.getCriticalExtensionOIDs();
        if (criticalOIDs != null) {
            Iterator<String> iter = criticalOIDs.iterator();
            while (iter.hasNext()) {
                String oid = iter.next();

                checkExtension(oid);
            }
        }
        // verify that the CRL was signed by the CA.
        try {
            m_crl.verify(m_caCert.getPublicKey());
        } catch (SignatureException e) {
            LOGGER.debug("The crl " + m_crlFilename + " is not signed properly by CA "
                    + DNHandler.getSubject(m_caCert) + ".");
            throw new CertificateException("The crl " + m_crlFilename + " is not signed properly by CA "
                    + DNHandler.getSubject(m_caCert) + ".");
        } catch (Exception e) {
            LOGGER.debug("The verification of crl " + m_crlFilename + " failed: " + e.getMessage());
            throw new CertificateException(
                    "The verification of crl " + m_crlFilename + " failed: " + e.getMessage());
        }
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.glite.security.util.RevocationChecker#checkUpdate()
     */
    public void checkUpdate() {
        try {
            File crlFile = new File(m_crlFilename);
            long lastModified = crlFile.lastModified();
            if (lastModified != m_crlModified) {
                LOGGER.debug("CRL file changed, reloading it: " + crlFile.getName());
                loadCRL(crlFile.getAbsolutePath());
                // on successful update, remove failure flag
                failureString = null;
            }
        } catch (CertificateException e) {
            // only warn if CRLs are required and only first time, not every update
            if (m_crlRequired && failureString == null) {
                LOGGER.warn("CRL loading for CA file " + m_caBaseFilename + "." + m_caNumber
                        + " failed, the certificates from the CA " + DNHandler.getSubject(m_caCert).getRFCDN()
                        + " will be refused. Error was: " + e.getClass() + ": " + e.getMessage());
            }
            failureString = e.getMessage();
        } catch (IOException e) {
            // only warn if CRLs are required and only first time, not every update
            if (m_crlRequired && failureString == null) {
                LOGGER.warn("CRL loading for CA file " + m_caBaseFilename + "." + m_caNumber
                        + " failed, the certificates from the CA " + DNHandler.getSubject(m_caCert).getRFCDN()
                        + " will be refused. Error was: " + e.getClass() + ": " + e.getMessage());
            }
            failureString = e.getMessage();
        }
    }

    /**
     * Loads the CRL from given path. Also checks the CRL.
     * 
     * @param absolutePath The filename of the CRL to load.
     * @throws CertificateException thrown in case there is problems with the certificate handling.
     * @throws IOException thrown in case the extension parsing fails.
     */
    private void loadCRL(String absolutePath) throws CertificateException, IOException {
        Vector certs = s_certReader.readCRLs(absolutePath);
        if (certs == null || certs.isEmpty()) {
            throw new IOException("No CRL found in " + absolutePath + ".");
        }
        m_crl = (X509CRL) certs.get(0); // only support one CA cert on one file.
        File file = new File(absolutePath);
        m_crlModified = file.lastModified();

        checkCrl();
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.glite.security.util.CRLChecker#check(java.security.cert.X509Certificate)
     */
    public void check(X509Certificate cert) throws IOException, CertificateException, CertificateRevokedException {
        DN subDN = DNHandler.getSubject(cert);
        DN caDN = DNHandler.getIssuer(cert);

        // if crl loading had failed, throw exception
        if (failureString != null) {
            throw new CertificateException("CRL checking failed, CRL loading had failed: " + failureString);
        }

        Date now = new Date(System.currentTimeMillis());
        // Check that the CRLs are valid, and either fail or warn depending on the crl required flag.
        Date nextUpdate = m_crl.getNextUpdate();
        if (nextUpdate.before(now)) {
            if (m_crlRequired) {
                LOGGER.info("The certificate " + subDN + " is not in the CRL of " + caDN
                        + ", but the CRL has expired on " + nextUpdate + ", so rejecting this certificate.");
                throw new CertificateRevokedException("The certificate " + subDN + " is not in the CRL of " + caDN
                        + ", but the CRL has expired on " + nextUpdate + ", so rejecting this certificate.");
            }
            LOGGER.warn("The CRL of " + caDN + " has expired on " + nextUpdate
                    + ", but as CRLs are not required, the cert is not rejected.");
        }
        Date thisUpdate = m_crl.getThisUpdate();
        if (thisUpdate.after(now)) {
            if (m_crlRequired) {
                LOGGER.info("The certificate " + subDN + " is not in the CRL of " + caDN
                        + ", but the CRL is not yet valid (valid from " + thisUpdate
                        + "), so rejecting this certificate.");
                throw new CertificateRevokedException("The certificate " + subDN + " is not in the CRL of " + caDN
                        + ", but the CRL is not yet valid (valid from " + thisUpdate
                        + "), so rejecting this certificate.");
            }
            LOGGER.warn("The CRL of " + caDN + " is not yet valid (valid from " + thisUpdate
                    + "), but as CRLs are not required, the cert is not rejected.");
        }

        X509CRLEntry entry = m_crl.getRevokedCertificate(cert.getSerialNumber());

        if (entry != null) {
            throw new CertificateRevokedException("The certificate " + subDN + " is revoked by CA " + caDN + " on "
                    + entry.getRevocationDate() + ".");
        }
    }

    /**
     * Checks the extension with given OID.
     * 
     * @param oid The oid of the extension to check.
     * @throws CertificateException thrown in case there is problems with the certificate handling.
     * @throws IOException thrown in case the extension parsing fails.
     */
    private void checkExtension(String oid) throws CertificateException, IOException {
        if (oid.equals(X509Extensions.DeltaCRLIndicator.getId())) {
            LOGGER.debug("Found DeltaCRLIndicator extension that can't be supported.");
            throw new CertificateException("Usupported critical extension in CRL: DeltaCRLIndicator");
        }
        if (oid.equals(X509Extensions.IssuingDistributionPoint.toString())) {
            checkIssuinDistributionPoint();
            return;
        }

        throw new CertificateException("Unrecognized critical extension in CRL: " + oid);
    }

    /**
     * Checks the issuerDistributionPoint extension, whether it contains unsupported information.
     * 
     * @throws CertificateException thrown in case there is problems with the certificate handling.
     * @throws IOException thrown in case the extension parsing fails.
     */
    private void checkIssuinDistributionPoint() throws CertificateException, IOException {
        byte extensionBytes[] = m_crl.getExtensionValue(X509Extensions.IssuingDistributionPoint.toString());

        ASN1Object object = ASN1Object.fromByteArray(extensionBytes);
        if (!(object instanceof DEROctetString)) {
            throw new CertificateException(
                    "Invalid data in IssuingDistributionPoint extension, not DEROctetString");
        }
        DEROctetString string = (DEROctetString) object;

        object = ASN1Object.fromByteArray(string.getOctets());
        if (!(object instanceof ASN1Sequence)) {
            throw new CertificateException("Invalid data in IssuingDistributionPoint extension, not ASN1Sequence");
        }

        IssuingDistributionPoint issuingDistributionPoint = new IssuingDistributionPoint((ASN1Sequence) object);

        if (issuingDistributionPoint.onlyContainsAttributeCerts()) {
            throw new CertificateException("CRL only contains attribute certs, not useful for authentication.");
        }

        if (issuingDistributionPoint.getOnlySomeReasons() != null) {
            throw new CertificateException(
                    "CRL only contains some reasons of revocations, can't trust the certificates without other complementing CRL(s), which is not supported.");
        }
    }

}