/**
* Copyright (c) 2009 - 2012 Red Hat, Inc.
*
* This software is licensed to you under the GNU General Public License,
* version 2 (GPLv2). There is NO WARRANTY for this software, express or
* implied, including the implied warranties of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. You should have received a copy of GPLv2
* along with this software; if not, see
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt.
*
* Red Hat trademarks are not licensed under GPLv2. No permission is
* granted to use or replicate Red Hat trademarks that are incorporated
* in this software or its documentation.
*/
package org.candlepin.util;
import static org.candlepin.test.MatchesPattern.matchesPattern;
import static org.junit.Assert.*;
import org.apache.commons.codec.binary.Base64InputStream;
import org.apache.commons.io.FileUtils;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x509.CRLNumber;
import org.bouncycastle.asn1.x509.CRLReason;
import org.bouncycastle.asn1.x509.X509Extension;
import org.bouncycastle.cert.X509CRLHolder;
import org.bouncycastle.cert.X509v2CRLBuilder;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
import org.bouncycastle.util.io.Streams;
import org.bouncycastle.x509.extension.AuthorityKeyIdentifierStructure;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.rules.TemporaryFolder;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.math.BigInteger;
import java.net.URL;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.cert.CertificateFactory;
import java.security.cert.X509CRL;
import java.security.cert.X509CRLEntry;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;
public class X509CRLEntryStreamTest {
private static final BouncyCastleProvider BC = new BouncyCastleProvider();
@Rule
public TemporaryFolder folder = new TemporaryFolder();
@Rule
public ExpectedException thrown = ExpectedException.none();
private File derFile;
private File pemFile;
private X500Name issuer;
private ContentSigner signer;
private KeyPair keyPair;
@Before
public void setUp() throws Exception {
URL url = X509CRLEntryStreamTest.class.getClassLoader().getResource("crl.der");
derFile = new File(url.getFile());
url = X509CRLEntryStreamTest.class.getClassLoader().getResource("crl.pem");
pemFile = new File(url.getFile());
issuer = new X500Name("CN=Test Issuer");
KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");
generator.initialize(2048);
keyPair = generator.generateKeyPair();
signer = new JcaContentSignerBuilder("SHA256WithRSAEncryption")
.setProvider(BC)
.build(keyPair.getPrivate());
}
@Test
public void testIterateOverSerials() throws Exception {
InputStream referenceStream = new FileInputStream(derFile);
CertificateFactory cf = CertificateFactory.getInstance("X.509");
X509CRL referenceCrl = (X509CRL) cf.generateCRL(referenceStream);
Set<BigInteger> referenceSerials = new HashSet<BigInteger>();
for (X509CRLEntry entry : referenceCrl.getRevokedCertificates()) {
referenceSerials.add(entry.getSerialNumber());
}
X509CRLEntryStream stream = new X509CRLEntryStream(derFile);
try {
Set<BigInteger> streamedSerials = new HashSet<BigInteger>();
while (stream.hasNext()) {
streamedSerials.add(stream.next().getSerialNumber());
}
assertEquals(referenceSerials, streamedSerials);
}
finally {
referenceStream.close();
stream.close();
}
}
@Test
public void testIterateOverEmptyCrl() throws Exception {
X509v2CRLBuilder crlBuilder = new X509v2CRLBuilder(issuer, new Date());
crlBuilder.addExtension(X509Extension.authorityKeyIdentifier, false,
new AuthorityKeyIdentifierStructure(keyPair.getPublic()));
crlBuilder.addExtension(X509Extension.cRLNumber, false, new CRLNumber(new BigInteger("127")));
X509CRLHolder holder = crlBuilder.build(signer);
File noUpdateTimeCrl = new File(folder.getRoot(), "test.crl");
FileUtils.writeByteArrayToFile(noUpdateTimeCrl, holder.getEncoded());
X509CRLEntryStream stream = new X509CRLEntryStream(noUpdateTimeCrl);
try {
Set<BigInteger> streamedSerials = new HashSet<BigInteger>();
while (stream.hasNext()) {
streamedSerials.add(stream.next().getSerialNumber());
}
assertEquals(0, streamedSerials.size());
}
finally {
stream.close();
}
}
@Test
public void testIterateOverEmptyCrlWithNoExtensions() throws Exception {
X509v2CRLBuilder crlBuilder = new X509v2CRLBuilder(issuer, new Date());
X509CRLHolder holder = crlBuilder.build(signer);
File noUpdateTimeCrl = new File(folder.getRoot(), "test.crl");
FileUtils.writeByteArrayToFile(noUpdateTimeCrl, holder.getEncoded());
X509CRLEntryStream stream = new X509CRLEntryStream(noUpdateTimeCrl);
thrown.expect(IllegalStateException.class);
thrown.expectMessage(matchesPattern("v1.*"));
try {
while (stream.hasNext()) {
stream.next();
}
}
finally {
stream.close();
}
}
@Test
public void testCRLwithoutUpdateTime() throws Exception {
X509v2CRLBuilder crlBuilder = new X509v2CRLBuilder(issuer, new Date());
crlBuilder.addExtension(X509Extension.authorityKeyIdentifier, false,
new AuthorityKeyIdentifierStructure(keyPair.getPublic()));
crlBuilder.addExtension(X509Extension.cRLNumber, false, new CRLNumber(new BigInteger("127")));
crlBuilder.addCRLEntry(new BigInteger("100"), new Date(), CRLReason.unspecified);
X509CRLHolder holder = crlBuilder.build(signer);
File noUpdateTimeCrl = new File(folder.getRoot(), "test.crl");
FileUtils.writeByteArrayToFile(noUpdateTimeCrl, holder.getEncoded());
X509CRLEntryStream stream = new X509CRLEntryStream(noUpdateTimeCrl);
try {
Set<BigInteger> streamedSerials = new HashSet<BigInteger>();
while (stream.hasNext()) {
streamedSerials.add(stream.next().getSerialNumber());
}
assertEquals(1, streamedSerials.size());
assertTrue(streamedSerials.contains(new BigInteger("100")));
}
finally {
stream.close();
}
}
@Test
public void testPemReadThroughBase64Stream() throws Exception {
/* NB: Base64InputStream only takes base64. The "-----BEGIN X509 CRL-----" and
* corresponding footer must be removed. Luckily in Base64InputStream stops the
* minute it sees a padding character and our test file has some padding. Thus,
* we don't need to worry about removing the footer. If the Base64 file didn't
* require padding, I'm not sure what happens so the footer should be removed
* somehow for real uses */
InputStream referenceStream = new BufferedInputStream(new FileInputStream(pemFile));
byte[] header = "-----BEGIN X509 CRL-----".getBytes("ASCII");
Streams.readFully(referenceStream, header);
referenceStream = new Base64InputStream(referenceStream);
CertificateFactory cf = CertificateFactory.getInstance("X.509");
X509CRL referenceCrl = (X509CRL) cf.generateCRL(referenceStream);
Set<BigInteger> referenceSerials = new HashSet<BigInteger>();
for (X509CRLEntry entry : referenceCrl.getRevokedCertificates()) {
referenceSerials.add(entry.getSerialNumber());
}
X509CRLEntryStream stream = new X509CRLEntryStream(derFile);
try {
Set<BigInteger> streamedSerials = new HashSet<BigInteger>();
while (stream.hasNext()) {
streamedSerials.add(stream.next().getSerialNumber());
}
assertEquals(referenceSerials, streamedSerials);
}
finally {
referenceStream.close();
stream.close();
}
}
}