be.fedict.trust.crl.CrlTrustLinker.java Source code

Java tutorial

Introduction

Here is the source code for be.fedict.trust.crl.CrlTrustLinker.java

Source

/*
 * Java Trust Project.
 * Copyright (C) 2009 FedICT.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License version
 * 3.0 as published by the Free Software Foundation.
 *
 * This software 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.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, see 
 * http://www.gnu.org/licenses/.
 */

package be.fedict.trust.crl;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.net.URI;
import java.net.URISyntaxException;
import java.security.InvalidParameterException;
import java.security.cert.CRLException;
import java.security.cert.X509CRL;
import java.security.cert.X509CRLEntry;
import java.security.cert.X509Certificate;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.bouncycastle.asn1.ASN1InputStream;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.DERIA5String;
import org.bouncycastle.asn1.DERInteger;
import org.bouncycastle.asn1.DEROctetString;
import org.bouncycastle.asn1.x509.CRLDistPoint;
import org.bouncycastle.asn1.x509.DistributionPoint;
import org.bouncycastle.asn1.x509.DistributionPointName;
import org.bouncycastle.asn1.x509.GeneralName;
import org.bouncycastle.asn1.x509.GeneralNames;
import org.bouncycastle.asn1.x509.IssuingDistributionPoint;
import org.bouncycastle.asn1.x509.X509Extensions;

import be.fedict.trust.CRLRevocationData;
import be.fedict.trust.RevocationData;
import be.fedict.trust.TrustLinker;
import be.fedict.trust.TrustLinkerResult;
import be.fedict.trust.TrustLinkerResultReason;
import be.fedict.trust.TrustValidator;

/**
 * Trust linker implementation based on CRL revocation information.
 * 
 * @author Frank Cornelis
 * 
 */
public class CrlTrustLinker implements TrustLinker {

    private static final Log LOG = LogFactory.getLog(CrlTrustLinker.class);

    private final CrlRepository crlRepository;

    /**
     * Main constructor.
     * 
     * @param crlRepository
     *            the CRL repository used by this CRL trust linker.
     */
    public CrlTrustLinker(CrlRepository crlRepository) {
        this.crlRepository = crlRepository;
    }

    public TrustLinkerResult hasTrustLink(X509Certificate childCertificate, X509Certificate certificate,
            Date validationDate, RevocationData revocationData) {

        URI crlUri = getCrlUri(childCertificate);
        if (null == crlUri) {
            LOG.debug("no CRL uri in certificate");
            return null;
        }

        return processCrl(crlUri, childCertificate, certificate, validationDate, revocationData, null);
    }

    private TrustLinkerResult processCrl(URI crlUri, X509Certificate childCertificate, X509Certificate certificate,
            Date validationDate, RevocationData revocationData, BigInteger baseCrlNumber) {

        LOG.debug("CRL URI: " + crlUri);
        X509CRL x509crl = this.crlRepository.findCrl(crlUri, certificate, validationDate);
        if (null == x509crl) {
            return null;
        }

        // check CRL integrity
        boolean crlIntegrityResult = checkCrlIntegrity(x509crl, certificate, validationDate);
        if (false == crlIntegrityResult) {
            return null;
        }

        // check CRL signature
        TrustLinkerResult trustResult = TrustValidator.checkSignatureAlgorithm(x509crl.getSigAlgName());
        if (!trustResult.isValid()) {
            return trustResult;
        }

        // we don't support indirect CRLs
        if (isIndirectCRL(x509crl)) {
            LOG.debug("indirect CRL detected");
            return null;
        }

        LOG.debug("CRL number: " + getCrlNumber(x509crl));
        // check delta CRL indicator against completeCrlNuber
        if (null != baseCrlNumber) {
            BigInteger crlNumber = getDeltaCrlIndicator(x509crl);
            if (!baseCrlNumber.equals(crlNumber)) {
                LOG.error("Delta CRL indicator (" + crlNumber + ") not equals base CRL number(" + baseCrlNumber
                        + ")");
                return null;
            }
        }

        // fill up revocation data if not null with this valid CRL
        if (null != revocationData) {
            try {
                revocationData.getCrlRevocationData().add(new CRLRevocationData(x509crl.getEncoded()));
            } catch (CRLException e) {
                LOG.error("CRLException: " + e.getMessage(), e);
                throw new RuntimeException("CRLException : " + e.getMessage(), e);
            }
        }

        boolean revoked = true;
        X509CRLEntry crlEntry = x509crl.getRevokedCertificate(childCertificate.getSerialNumber());
        if (null == crlEntry) {
            LOG.debug("CRL OK for: " + childCertificate.getSubjectX500Principal());
            revoked = false;
        } else if (crlEntry.getRevocationDate().after(validationDate)) {
            LOG.debug("CRL OK for: " + childCertificate.getSubjectX500Principal() + " at " + validationDate);
            revoked = false;
        }

        if (null != x509crl.getExtensionValue(X509Extensions.DeltaCRLIndicator.getId())) {
            // Delta CRL
            if (!revoked)
                return null;

        } else {
            // Base CRL, look for delta's
            List<URI> deltaCrlUris = getDeltaCrlUris(x509crl);
            if (null != deltaCrlUris) {
                for (URI deltaCrlUri : deltaCrlUris) {
                    LOG.debug("delta CRL: " + deltaCrlUri.toString());
                    TrustLinkerResult result = processCrl(deltaCrlUri, childCertificate, certificate,
                            validationDate, revocationData, getCrlNumber(x509crl));
                    if (null != result)
                        return result;
                }
            }
        }

        if (!revoked)
            return new TrustLinkerResult(true);

        return new TrustLinkerResult(false, TrustLinkerResultReason.INVALID_REVOCATION_STATUS,
                "certificate revoked by CRL=" + crlEntry.getSerialNumber());

    }

    /**
     * Checks the integrity of the given X509 CRL.
     * 
     * @param x509crl
     *            the X509 CRL to verify the integrity.
     * @param issuerCertificate
     *            the assumed issuer of the given X509 CRL.
     * @param validationDate
     *            the validate date.
     * @return <code>true</code> if integrity is OK, <code>false</code>
     *         otherwise.
     */
    public static boolean checkCrlIntegrity(X509CRL x509crl, X509Certificate issuerCertificate,
            Date validationDate) {
        if (false == x509crl.getIssuerX500Principal().equals(issuerCertificate.getSubjectX500Principal())) {
            return false;
        }
        try {
            x509crl.verify(issuerCertificate.getPublicKey());
        } catch (Exception e) {
            return false;
        }
        Date thisUpdate = x509crl.getThisUpdate();
        LOG.debug("validation date: " + validationDate);
        LOG.debug("CRL this update: " + thisUpdate);
        if (thisUpdate.after(validationDate)) {
            LOG.warn("CRL too young");
            return false;
        }
        LOG.debug("CRL next update: " + x509crl.getNextUpdate());
        if (validationDate.after(x509crl.getNextUpdate())) {
            LOG.debug("CRL too old");
            return false;
        }

        // assert cRLSign KeyUsage bit
        if (null == issuerCertificate.getKeyUsage()) {
            LOG.debug("No KeyUsage extension for CRL issuing certificate");
            return false;
        }

        if (false == issuerCertificate.getKeyUsage()[6]) {
            LOG.debug("cRLSign bit not set for CRL issuing certificate");
            return false;
        }

        return true;
    }

    /**
     * Gives back the CRL URI meta-data found within the given X509 certificate.
     * 
     * @param certificate
     *            the X509 certificate.
     * @return the CRL URI, or <code>null</code> if the extension is not
     *         present.
     */
    public static URI getCrlUri(X509Certificate certificate) {
        byte[] crlDistributionPointsValue = certificate
                .getExtensionValue(X509Extensions.CRLDistributionPoints.getId());
        if (null == crlDistributionPointsValue) {
            return null;
        }
        ASN1Sequence seq;
        try {
            DEROctetString oct;
            oct = (DEROctetString) (new ASN1InputStream(new ByteArrayInputStream(crlDistributionPointsValue))
                    .readObject());
            seq = (ASN1Sequence) new ASN1InputStream(oct.getOctets()).readObject();
        } catch (IOException e) {
            throw new RuntimeException("IO error: " + e.getMessage(), e);
        }
        CRLDistPoint distPoint = CRLDistPoint.getInstance(seq);
        DistributionPoint[] distributionPoints = distPoint.getDistributionPoints();
        for (DistributionPoint distributionPoint : distributionPoints) {
            DistributionPointName distributionPointName = distributionPoint.getDistributionPoint();
            if (DistributionPointName.FULL_NAME != distributionPointName.getType()) {
                continue;
            }
            GeneralNames generalNames = (GeneralNames) distributionPointName.getName();
            GeneralName[] names = generalNames.getNames();
            for (GeneralName name : names) {
                if (name.getTagNo() != GeneralName.uniformResourceIdentifier) {
                    LOG.debug("not a uniform resource identifier");
                    continue;
                }
                String str = ((DERIA5String) name.getName()).getString();
                URI uri = toURI(str);
                return uri;
            }
        }
        return null;
    }

    private List<URI> getDeltaCrlUris(X509CRL x509crl) {

        byte[] freshestCrlValue = x509crl.getExtensionValue(X509Extensions.FreshestCRL.getId());
        if (null == freshestCrlValue) {
            LOG.debug("no freshestCRL extension");
            return null;
        }
        ASN1Sequence seq;
        try {
            DEROctetString oct;
            oct = (DEROctetString) (new ASN1InputStream(new ByteArrayInputStream(freshestCrlValue)).readObject());
            seq = (ASN1Sequence) new ASN1InputStream(oct.getOctets()).readObject();
        } catch (IOException e) {
            throw new RuntimeException("IO error: " + e.getMessage(), e);
        }

        List<URI> deltaCrlUris = new LinkedList<URI>();
        CRLDistPoint distPoint = CRLDistPoint.getInstance(seq);
        DistributionPoint[] distributionPoints = distPoint.getDistributionPoints();
        for (DistributionPoint distributionPoint : distributionPoints) {
            DistributionPointName distributionPointName = distributionPoint.getDistributionPoint();
            if (DistributionPointName.FULL_NAME != distributionPointName.getType()) {
                continue;
            }
            GeneralNames generalNames = (GeneralNames) distributionPointName.getName();
            GeneralName[] names = generalNames.getNames();
            for (GeneralName name : names) {
                if (name.getTagNo() != GeneralName.uniformResourceIdentifier) {
                    LOG.debug("not a uniform resource identifier");
                    continue;
                }
                String str = ((DERIA5String) name.getName()).getString();
                URI uri = toURI(str);
                deltaCrlUris.add(uri);
            }
        }
        return deltaCrlUris;
    }

    private BigInteger getDeltaCrlIndicator(X509CRL deltaCrl) {

        byte[] deltaCrlIndicatorValue = deltaCrl.getExtensionValue(X509Extensions.DeltaCRLIndicator.getId());
        if (null == deltaCrlIndicatorValue)
            return null;

        try {
            DEROctetString octetString = (DEROctetString) (new ASN1InputStream(
                    new ByteArrayInputStream(deltaCrlIndicatorValue)).readObject());
            byte[] octets = octetString.getOctets();
            DERInteger integer = (DERInteger) new ASN1InputStream(octets).readObject();
            BigInteger crlNumber = integer.getPositiveValue();
            return crlNumber;
        } catch (IOException e) {
            throw new RuntimeException("IO error: " + e.getMessage(), e);
        }

    }

    private BigInteger getCrlNumber(X509CRL crl) {
        byte[] crlNumberExtensionValue = crl.getExtensionValue(X509Extensions.CRLNumber.getId());
        if (null == crlNumberExtensionValue) {
            return null;
        }
        try {
            DEROctetString octetString = (DEROctetString) (new ASN1InputStream(
                    new ByteArrayInputStream(crlNumberExtensionValue)).readObject());
            byte[] octets = octetString.getOctets();
            DERInteger integer = (DERInteger) new ASN1InputStream(octets).readObject();
            BigInteger crlNumber = integer.getPositiveValue();
            return crlNumber;
        } catch (IOException e) {
            throw new RuntimeException("IO error: " + e.getMessage(), e);
        }
    }

    private boolean isIndirectCRL(X509CRL crl) {
        byte[] idp = crl.getExtensionValue(X509Extensions.IssuingDistributionPoint.getId());
        boolean isIndirect = false;
        if (idp != null) {
            isIndirect = IssuingDistributionPoint.getInstance(idp).isIndirectCRL();
        }

        return isIndirect;
    }

    private static URI toURI(String str) {
        try {
            URI uri = new URI(str);
            return uri;
        } catch (URISyntaxException e) {
            throw new InvalidParameterException("CRL URI syntax error: " + e.getMessage());
        }
    }
}