org.cesecore.certificates.crl.CrlCreateSessionTest.java Source code

Java tutorial

Introduction

Here is the source code for org.cesecore.certificates.crl.CrlCreateSessionTest.java

Source

/*************************************************************************
 *                                                                       *
 *  EJBCA Community: The OpenSource Certificate Authority                *
 *                                                                       *
 *  This software 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 2.1 of the License, or any later version.                    *
 *                                                                       *
 *  See terms of license at gnu.org.                                     *
 *                                                                       *
 *************************************************************************/
package org.cesecore.certificates.crl;

import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.fail;

import java.io.ByteArrayInputStream;
import java.math.BigInteger;
import java.security.KeyPair;
import java.security.PublicKey;
import java.security.cert.CRLException;
import java.security.cert.Certificate;
import java.security.cert.X509CRL;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Random;

import javax.ejb.EJBException;

import org.apache.log4j.Logger;
import org.bouncycastle.asn1.ASN1InputStream;
import org.bouncycastle.asn1.ASN1Primitive;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.DEROctetString;
import org.bouncycastle.asn1.x509.AuthorityKeyIdentifier;
import org.bouncycastle.asn1.x509.BasicConstraints;
import org.bouncycastle.asn1.x509.Extension;
import org.bouncycastle.asn1.x509.SubjectKeyIdentifier;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.X509v3CertificateBuilder;
import org.bouncycastle.jce.X509KeyUsage;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.operator.BufferingContentSigner;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
import org.cesecore.CaTestUtils;
import org.cesecore.authentication.tokens.AlwaysAllowLocalAuthenticationToken;
import org.cesecore.authentication.tokens.AuthenticationToken;
import org.cesecore.authentication.tokens.UsernamePrincipal;
import org.cesecore.authorization.AuthorizationDeniedException;
import org.cesecore.certificates.ca.CA;
import org.cesecore.certificates.ca.CAConstants;
import org.cesecore.certificates.ca.CADoesntExistsException;
import org.cesecore.certificates.ca.CAInfo;
import org.cesecore.certificates.ca.CAOfflineException;
import org.cesecore.certificates.ca.CaSessionRemote;
import org.cesecore.certificates.ca.CaTestSessionRemote;
import org.cesecore.certificates.ca.X509CA;
import org.cesecore.certificates.ca.X509CAInfo;
import org.cesecore.certificates.ca.catoken.CAToken;
import org.cesecore.certificates.ca.catoken.CATokenConstants;
import org.cesecore.certificates.certificate.CertificateConstants;
import org.cesecore.certificates.certificate.CertificateStoreSessionRemote;
import org.cesecore.certificates.certificate.InternalCertificateStoreSessionRemote;
import org.cesecore.certificates.certificateprofile.CertificateProfileConstants;
import org.cesecore.certificates.util.AlgorithmConstants;
import org.cesecore.certificates.util.cert.CrlExtensions;
import org.cesecore.keys.token.CryptoTokenManagementSessionRemote;
import org.cesecore.keys.token.CryptoTokenOfflineException;
import org.cesecore.keys.token.CryptoTokenTestUtils;
import org.cesecore.keys.util.KeyTools;
import org.cesecore.mock.authentication.tokens.TestAlwaysAllowLocalAuthenticationToken;
import org.cesecore.util.CertTools;
import org.cesecore.util.EjbRemoteHelper;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;

/**
 * Basic vanilla tests for CrlCreateSession. Contains quite some code lifted from PublishingCrlSession that doesn't belong under the CESeCore
 * package. 
 * 
 * @version $Id: CrlCreateSessionTest.java 20728 2015-02-20 14:55:55Z mikekushner $
 *
 */
public class CrlCreateSessionTest {

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

    private final CaSessionRemote caSession = EjbRemoteHelper.INSTANCE.getRemoteSession(CaSessionRemote.class);
    private final CaTestSessionRemote caTestSessionRemote = EjbRemoteHelper.INSTANCE
            .getRemoteSession(CaTestSessionRemote.class, EjbRemoteHelper.MODULE_TEST);
    private final CrlCreateSessionRemote crlCreateSession = EjbRemoteHelper.INSTANCE
            .getRemoteSession(CrlCreateSessionRemote.class);
    private final CrlStoreSessionRemote crlStoreSession = EjbRemoteHelper.INSTANCE
            .getRemoteSession(CrlStoreSessionRemote.class);
    private final CryptoTokenManagementSessionRemote cryptoTokenMgmtSession = EjbRemoteHelper.INSTANCE
            .getRemoteSession(CryptoTokenManagementSessionRemote.class);
    private final CertificateStoreSessionRemote certificateStoreSession = EjbRemoteHelper.INSTANCE
            .getRemoteSession(CertificateStoreSessionRemote.class);
    private final InternalCertificateStoreSessionRemote internalCertificateStoreSession = EjbRemoteHelper.INSTANCE
            .getRemoteSession(InternalCertificateStoreSessionRemote.class, EjbRemoteHelper.MODULE_TEST);
    private static final String className = CrlCreateSessionTest.class.getSimpleName();
    private static final AuthenticationToken authenticationToken = new TestAlwaysAllowLocalAuthenticationToken(
            CrlCreateSessionTest.class.getSimpleName());

    private static final byte[] TEST_AKID = new byte[] { 1, 2, 3, 4 };

    @BeforeClass
    public static void beforeClass() throws Exception {
        CaTestUtils.createX509Ca(authenticationToken, className, className, "CN=" + className);
    }

    @AfterClass
    public static void afterClass() throws Exception {
        CaTestUtils.removeCA(authenticationToken, className, className);
    }

    @Test
    public void createCrl()
            throws CADoesntExistsException, AuthorizationDeniedException, CryptoTokenOfflineException {
        int caid = caSession.getCAInfo(authenticationToken, className).getCAId();
        CA ca = caTestSessionRemote.getCA(authenticationToken, caid);
        final String certSubjectDN = CertTools.getSubjectDN(ca.getCACertificate());
        Collection<RevokedCertInfo> revcerts = certificateStoreSession.listRevokedCertInfo(certSubjectDN, -1);
        int fullnumber = crlStoreSession.getLastCRLNumber(certSubjectDN, false);
        int deltanumber = crlStoreSession.getLastCRLNumber(certSubjectDN, true);
        // nextCrlNumber: The highest number of last CRL (full or delta) and increased by 1 (both full CRLs and deltaCRLs share the same series of CRL Number)
        int nextCrlNumber = ((fullnumber > deltanumber) ? fullnumber : deltanumber) + 1;

        crlCreateSession.generateAndStoreCRL(authenticationToken, ca, revcerts, -1, nextCrlNumber);
        // We should now have a CRL generated
        byte[] crl = crlStoreSession.getLastCRL(ca.getSubjectDN(), false);
        try {
            assertNotNull(crl);
            // Check that it is signed by the correct public key
            X509CRL xcrl = CertTools.getCRLfromByteArray(crl);
            PublicKey pubK = ca.getCACertificate().getPublicKey();
            xcrl.verify(pubK);
        } catch (Exception e) {
            log.error("Error: ", e);
            fail("Should not throw here");
        } finally {
            // Remove it to clean database
            internalCertificateStoreSession.removeCRL(authenticationToken, CertTools.getFingerprintAsString(crl));
        }
    }

    @Test
    public void testCreateNewDeltaCRL() throws Exception {
        int caid = caSession.getCAInfo(authenticationToken, className).getCAId();
        CA ca = caTestSessionRemote.getCA(authenticationToken, caid);
        X509CAInfo cainfo = (X509CAInfo) ca.getCAInfo();
        cainfo.setDeltaCRLPeriod(1); // Issue very often..
        caSession.editCA(authenticationToken, cainfo);
        forceCRL(authenticationToken, ca);
        forceDeltaCRL(authenticationToken, ca);

        // Get number of last Delta CRL
        int number = crlStoreSession.getLastCRLNumber(ca.getSubjectDN(), true);
        log.debug("Last CRLNumber = " + number);
        byte[] crl = crlStoreSession.getLastCRL(ca.getSubjectDN(), true);
        assertNotNull("Could not get CRL", crl);
        X509CRL x509crl = CertTools.getCRLfromByteArray(crl);
        BigInteger num = CrlExtensions.getCrlNumber(x509crl);
        assertEquals(number, num.intValue());
        // Create a new CRL again to see that the number increases
        forceDeltaCRL(authenticationToken, ca);
        int number1 = crlStoreSession.getLastCRLNumber(ca.getSubjectDN(), true);
        assertEquals(number + 1, number1);
        byte[] crl1 = crlStoreSession.getLastCRL(ca.getSubjectDN(), true);
        X509CRL x509crl1 = CertTools.getCRLfromByteArray(crl1);
        BigInteger num1 = CrlExtensions.getCrlNumber(x509crl1);
        assertEquals(number + 1, num1.intValue());
        // Now create a normal CRL and a deltaCRL again. CRLNUmber should now be
        // increased by two
        forceCRL(authenticationToken, ca);
        forceDeltaCRL(authenticationToken, ca);
        int number2 = crlStoreSession.getLastCRLNumber(ca.getSubjectDN(), true);
        assertEquals(number1 + 2, number2);
        byte[] crl2 = crlStoreSession.getLastCRL(ca.getSubjectDN(), true);
        X509CRL x509crl2 = CertTools.getCRLfromByteArray(crl2);
        BigInteger num2 = CrlExtensions.getCrlNumber(x509crl2);
        assertEquals(number1 + 2, num2.intValue());
    }

    /**
     * Tests issuing a CRL from a CA with a SKID that is not generated with SHA1.
     * The CRL is checked to contain the correct AKID value.
     */
    @Test
    public void testNonSHA1KeyId() throws Exception {
        final String subcaname = "CrlCSTestSub";
        final String subcadn = "CN=" + subcaname;
        try {
            // Create an external root ca certificate
            final KeyPair rootcakp = KeyTools.genKeys("1024", "RSA");
            final String rootcadn = "CN=CrlCSTestRoot";
            final X509Certificate rootcacert = CertTools.genSelfCert(rootcadn, 3650, null, rootcakp.getPrivate(),
                    rootcakp.getPublic(), AlgorithmConstants.SIGALG_SHA1_WITH_RSA, true, "BC", false);

            // Create sub ca
            final int cryptoTokenId = CryptoTokenTestUtils.createCryptoTokenForCA(authenticationToken, subcaname,
                    "1024");
            final CAToken catoken = CaTestUtils.createCaToken(cryptoTokenId,
                    AlgorithmConstants.SIGALG_SHA1_WITH_RSA, AlgorithmConstants.SIGALG_SHA1_WITH_RSA);
            X509CAInfo subcainfo = new X509CAInfo(subcadn, subcaname, CAConstants.CA_ACTIVE,
                    CertificateProfileConstants.CERTPROFILE_FIXED_SUBCA, 365, CAInfo.SIGNEDBYEXTERNALCA, null,
                    catoken);
            X509CA subca = new X509CA(subcainfo);
            subca.setCAToken(catoken);
            caSession.addCA(authenticationToken, subca);

            // Issue sub CA certificate with a non-standard SKID
            PublicKey subcapubkey = cryptoTokenMgmtSession.getPublicKey(authenticationToken, cryptoTokenId,
                    catoken.getAliasFromPurpose(CATokenConstants.CAKEYPURPOSE_CERTSIGN)).getPublicKey();
            Date firstDate = new Date();
            firstDate.setTime(firstDate.getTime() - (10 * 60 * 1000));
            Date lastDate = new Date();
            lastDate.setTime(lastDate.getTime() + 365 * 24 * 60 * 60 * 1000);
            final SubjectPublicKeyInfo subcaspki = new SubjectPublicKeyInfo(
                    (ASN1Sequence) ASN1Primitive.fromByteArray(subcapubkey.getEncoded()));
            final X509v3CertificateBuilder certbuilder = new X509v3CertificateBuilder(
                    CertTools.stringToBcX500Name(rootcadn, false),
                    new BigInteger(64, new Random(System.nanoTime())), firstDate, lastDate,
                    CertTools.stringToBcX500Name(subcadn, false), subcaspki);
            final AuthorityKeyIdentifier aki = new AuthorityKeyIdentifier(CertTools.getAuthorityKeyId(rootcacert));
            final SubjectKeyIdentifier ski = new SubjectKeyIdentifier(TEST_AKID); // Non-standard SKID. It should match the AKID in the CRL
            certbuilder.addExtension(Extension.authorityKeyIdentifier, true, aki);
            certbuilder.addExtension(Extension.subjectKeyIdentifier, false, ski);
            BasicConstraints bc = new BasicConstraints(true);
            certbuilder.addExtension(Extension.basicConstraints, true, bc);

            X509KeyUsage ku = new X509KeyUsage(X509KeyUsage.keyCertSign | X509KeyUsage.cRLSign);
            certbuilder.addExtension(Extension.keyUsage, true, ku);

            final ContentSigner signer = new BufferingContentSigner(
                    new JcaContentSignerBuilder(AlgorithmConstants.SIGALG_SHA1_WITH_RSA)
                            .setProvider(BouncyCastleProvider.PROVIDER_NAME).build(rootcakp.getPrivate()),
                    20480);
            final X509CertificateHolder certHolder = certbuilder.build(signer);
            final X509Certificate subcacert = (X509Certificate) CertTools
                    .getCertfromByteArray(certHolder.getEncoded(), "BC");

            // Replace sub CA certificate with a sub CA cert containing the test AKID
            subcainfo = (X509CAInfo) caSession.getCAInfo(authenticationToken, subcaname);
            List<Certificate> certificatechain = new ArrayList<Certificate>();
            certificatechain.add(subcacert);
            certificatechain.add(rootcacert);
            subcainfo.setCertificateChain(certificatechain);
            subcainfo.setExpireTime(CertTools.getNotAfter(subcacert));
            caSession.editCA(authenticationToken, subcainfo);
            subca = (X509CA) caTestSessionRemote.getCA(authenticationToken, subcaname);
            assertArrayEquals("Wrong SKID in test CA.", TEST_AKID,
                    CertTools.getSubjectKeyId(subca.getCACertificate()));

            // Create a base CRL and check the AKID
            int baseCrlNumber = crlStoreSession.getLastCRLNumber(subcadn, false) + 1;
            assertEquals("For a new CA, the next crl number should be 1.", 1, baseCrlNumber);
            crlCreateSession.generateAndStoreCRL(authenticationToken, subca, new ArrayList<RevokedCertInfo>(), -1,
                    baseCrlNumber);
            final byte[] crl = crlStoreSession.getLastCRL(subcadn, false);
            checkCrlAkid(subca, crl);

            // Create a delta CRL and check the AKID
            int deltaCrlNumber = crlStoreSession.getLastCRLNumber(subcadn, false) + 1;
            assertEquals("Next CRL number should be 2 at this point.", 2, deltaCrlNumber);
            crlCreateSession.generateAndStoreCRL(authenticationToken, subca, new ArrayList<RevokedCertInfo>(),
                    baseCrlNumber, deltaCrlNumber);
            final byte[] deltacrl = crlStoreSession.getLastCRL(subcadn, true); // true = get delta CRL
            checkCrlAkid(subca, deltacrl);
        } finally {
            // Remove everything created above to clean the database
            final Integer cryptoTokenId = cryptoTokenMgmtSession.getIdFromName(subcaname);
            if (cryptoTokenId != null) {
                CryptoTokenTestUtils.removeCryptoToken(authenticationToken, cryptoTokenId);
            }
            try {
                int caid = caSession.getCAInfo(authenticationToken, subcaname).getCAId();

                // Delete sub CA CRLs
                while (true) {
                    final byte[] crl = crlStoreSession.getLastCRL(subcadn, true); // delta CRLs
                    if (crl == null) {
                        break;
                    }
                    internalCertificateStoreSession.removeCRL(authenticationToken,
                            CertTools.getFingerprintAsString(crl));
                }

                while (true) {
                    final byte[] crl = crlStoreSession.getLastCRL(subcadn, false); // base CRLs
                    if (crl == null) {
                        break;
                    }
                    internalCertificateStoreSession.removeCRL(authenticationToken,
                            CertTools.getFingerprintAsString(crl));
                }

                // Delete sub CA
                caSession.removeCA(authenticationToken, caid);
            } catch (CADoesntExistsException cade) {
                // NOPMD ignore
            }
        }
    }

    private void checkCrlAkid(X509CA subca, final byte[] crl) throws Exception {
        assertNotNull(crl);

        // First, check that it is signed by the correct public key
        final X509CRL xcrl = CertTools.getCRLfromByteArray(crl);
        final PublicKey pubK = subca.getCACertificate().getPublicKey();
        xcrl.verify(pubK);

        // Check that the correct AKID is used
        final byte[] akidExtBytes = xcrl.getExtensionValue(Extension.authorityKeyIdentifier.getId());
        ASN1InputStream octAis = new ASN1InputStream(new ByteArrayInputStream(akidExtBytes));
        DEROctetString oct = (DEROctetString) (octAis.readObject());
        ASN1InputStream keyidAis = new ASN1InputStream(new ByteArrayInputStream(oct.getOctets()));
        AuthorityKeyIdentifier akid = AuthorityKeyIdentifier.getInstance((ASN1Sequence) keyidAis.readObject());
        keyidAis.close();
        octAis.close();
        assertArrayEquals("Incorrect Authority Key Id in CRL.", TEST_AKID, akid.getKeyIdentifier());
    }

    private void forceDeltaCRL(AuthenticationToken admin, CA ca) throws CADoesntExistsException,
            AuthorizationDeniedException, CryptoTokenOfflineException, CAOfflineException, CRLException {
        final CRLInfo crlInfo = crlStoreSession.getLastCRLInfo(ca.getSubjectDN(), false);
        // if no full CRL has been generated we can't create a delta CRL
        if (crlInfo != null) {
            CAInfo cainfo = ca.getCAInfo();
            if (cainfo.getDeltaCRLPeriod() > 0) {
                internalCreateDeltaCRL(admin, ca, crlInfo.getLastCRLNumber(), crlInfo.getCreateDate().getTime());
            }
        }
    }

    private String forceCRL(AuthenticationToken admin, CA ca)
            throws CAOfflineException, CryptoTokenOfflineException, AuthorizationDeniedException {
        if (ca == null) {
            throw new EJBException("No CA specified.");
        }
        CAInfo cainfo = ca.getCAInfo();
        String ret = null;

        final String caCertSubjectDN; // DN from the CA issuing the CRL to be used when searching for the CRL in the database.
        {
            final Collection<Certificate> certs = cainfo.getCertificateChain();
            final Certificate cacert = !certs.isEmpty() ? certs.iterator().next() : null;
            caCertSubjectDN = cacert != null ? CertTools.getSubjectDN(cacert) : null;
        }
        // We can not create a CRL for a CA that is waiting for certificate response
        if (caCertSubjectDN != null && cainfo.getStatus() == CAConstants.CA_ACTIVE) {
            long crlperiod = cainfo.getCRLPeriod();
            // Find all revoked certificates for a complete CRL

            Collection<RevokedCertInfo> revcerts = certificateStoreSession.listRevokedCertInfo(caCertSubjectDN, -1);
            Date now = new Date();
            Date check = new Date(now.getTime() - crlperiod);
            AuthenticationToken archiveAdmin = new AlwaysAllowLocalAuthenticationToken(
                    new UsernamePrincipal("CrlCreateSession.archive_expired"));
            for (RevokedCertInfo data : revcerts) {
                // We want to include certificates that was revoked after the last CRL was issued, but before this one
                // so the revoked certs are included in ONE CRL at least. See RFC5280 section 3.3.
                if (data.getExpireDate().before(check)) {
                    // Certificate has expired, set status to archived in the database
                    if (log.isDebugEnabled()) {
                        log.debug("Archiving certificate with fp=" + data.getCertificateFingerprint()
                                + ". Free memory=" + Runtime.getRuntime().freeMemory());
                    }
                    certificateStoreSession.setStatus(archiveAdmin, data.getCertificateFingerprint(),
                            CertificateConstants.CERT_ARCHIVED);
                }
            }
            // a full CRL
            final String certSubjectDN = CertTools.getSubjectDN(ca.getCACertificate());
            int fullnumber = crlStoreSession.getLastCRLNumber(certSubjectDN, false);
            int deltanumber = crlStoreSession.getLastCRLNumber(certSubjectDN, true);
            // nextCrlNumber: The highest number of last CRL (full or delta) and increased by 1 (both full CRLs and deltaCRLs share the same series of CRL Number)
            int nextCrlNumber = ((fullnumber > deltanumber) ? fullnumber : deltanumber) + 1;

            byte[] crlBytes = crlCreateSession.generateAndStoreCRL(admin, ca, revcerts, -1, nextCrlNumber);

            if (crlBytes != null) {
                ret = CertTools.getFingerprintAsString(crlBytes);
            }

        }

        return ret;
    }

    private byte[] internalCreateDeltaCRL(AuthenticationToken admin, CA ca, int baseCrlNumber,
            long baseCrlCreateTime)
            throws CryptoTokenOfflineException, CAOfflineException, AuthorizationDeniedException, CRLException {
        byte[] crlBytes = null;
        CAInfo cainfo = ca.getCAInfo();
        final String caCertSubjectDN;
        {
            final Collection<Certificate> certs = cainfo.getCertificateChain();
            final Certificate cacert = !certs.isEmpty() ? certs.iterator().next() : null;
            caCertSubjectDN = cacert != null ? CertTools.getSubjectDN(cacert) : null;
        }

        if ((baseCrlNumber == -1) && (baseCrlCreateTime == -1)) {
            CRLInfo basecrlinfo = crlStoreSession.getLastCRLInfo(caCertSubjectDN, false);
            baseCrlCreateTime = basecrlinfo.getCreateDate().getTime();
            baseCrlNumber = basecrlinfo.getLastCRLNumber();
        }
        // Find all revoked certificates
        Collection<RevokedCertInfo> revcertinfos = certificateStoreSession.listRevokedCertInfo(caCertSubjectDN,
                baseCrlCreateTime);
        if (log.isDebugEnabled()) {
            log.debug("Found " + revcertinfos.size() + " revoked certificates.");
        }
        // Go through them and create a CRL, at the same time archive expired certificates
        ArrayList<RevokedCertInfo> certs = new ArrayList<RevokedCertInfo>();
        Iterator<RevokedCertInfo> iter = revcertinfos.iterator();
        while (iter.hasNext()) {
            RevokedCertInfo ci = iter.next();
            if (ci.getRevocationDate() == null) {
                ci.setRevocationDate(new Date());
            }
            certs.add(ci);
        }
        // create a delta CRL
        final String certSubjectDN = CertTools.getSubjectDN(ca.getCACertificate());
        int fullnumber = crlStoreSession.getLastCRLNumber(certSubjectDN, false);
        int deltanumber = crlStoreSession.getLastCRLNumber(certSubjectDN, true);
        // nextCrlNumber: The highest number of last CRL (full or delta) and increased by 1 (both full CRLs and deltaCRLs share the same series of CRL Number)
        int nextCrlNumber = ((fullnumber > deltanumber) ? fullnumber : deltanumber) + 1;

        crlBytes = crlCreateSession.generateAndStoreCRL(admin, ca, certs, baseCrlNumber, nextCrlNumber);
        X509CRL crl = CertTools.getCRLfromByteArray(crlBytes);
        if (log.isDebugEnabled()) {
            log.debug("Created delta CRL with expire date: " + crl.getNextUpdate());
        }

        return crlBytes;
    }

}