/**
* 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.bouncycastle.asn1.DERTags.*;
import static org.candlepin.util.DERUtil.*;
import org.apache.commons.io.IOUtils;
import org.bouncycastle.asn1.ASN1EncodableVector;
import org.bouncycastle.asn1.ASN1InputStream;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.DERBitString;
import org.bouncycastle.asn1.DEREnumerated;
import org.bouncycastle.asn1.DERGeneralizedTime;
import org.bouncycastle.asn1.DERInteger;
import org.bouncycastle.asn1.DERObject;
import org.bouncycastle.asn1.DERObjectIdentifier;
import org.bouncycastle.asn1.DEROctetString;
import org.bouncycastle.asn1.DERSequence;
import org.bouncycastle.asn1.DERTaggedObject;
import org.bouncycastle.asn1.DERTags;
import org.bouncycastle.asn1.DERUTCTime;
import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
import org.bouncycastle.asn1.oiw.OIWObjectIdentifiers;
import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import org.bouncycastle.asn1.x509.CRLReason;
import org.bouncycastle.asn1.x509.CertificateList;
import org.bouncycastle.asn1.x509.Time;
import org.bouncycastle.asn1.x509.X509Extension;
import org.bouncycastle.asn1.x509.X509Extensions;
import org.bouncycastle.cert.X509CRLHolder;
import org.bouncycastle.cert.X509v2CRLBuilder;
import org.bouncycastle.crypto.CryptoException;
import org.bouncycastle.crypto.DataLengthException;
import org.bouncycastle.crypto.Digest;
import org.bouncycastle.crypto.digests.MD4Digest;
import org.bouncycastle.crypto.digests.MD5Digest;
import org.bouncycastle.crypto.digests.SHA1Digest;
import org.bouncycastle.crypto.digests.SHA224Digest;
import org.bouncycastle.crypto.digests.SHA256Digest;
import org.bouncycastle.crypto.digests.SHA384Digest;
import org.bouncycastle.crypto.params.RSAKeyParameters;
import org.bouncycastle.crypto.signers.RSADigestSigner;
import org.bouncycastle.jce.provider.X509CRLEntryObject;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.DefaultDigestAlgorithmIdentifierFinder;
import org.bouncycastle.operator.OperatorCreationException;
import org.bouncycastle.operator.bc.BcRSAContentSignerBuilder;
import org.bouncycastle.x509.extension.AuthorityKeyIdentifierStructure;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.math.BigInteger;
import java.security.InvalidKeyException;
import java.security.cert.CRLException;
import java.security.cert.CertificateParsingException;
import java.security.cert.X509Certificate;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.util.Arrays;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.Vector;
import java.util.concurrent.atomic.AtomicInteger;
/**
* Class for adding entries to an X509 in a memory-efficient manner.
*
* The schema for an X509 CRL is described in
* <a href="https://tools.ietf.org/html/rfc5280#section-5">section 5 of RFC 5280</a>
*
* It is reproduced here for quick reference
*
* <pre>
* {@code
* CertificateList ::= SEQUENCE {
* tbsCertList TBSCertList,
* signatureAlgorithm AlgorithmIdentifier,
* signatureValue BIT STRING }
*
* TBSCertList ::= SEQUENCE {
* version Version OPTIONAL,
* -- if present, MUST be v2
* signature AlgorithmIdentifier,
* issuer Name,
* thisUpdate Time,
* nextUpdate Time OPTIONAL,
* revokedCertificates SEQUENCE OF SEQUENCE {
* userCertificate CertificateSerialNumber,
* revocationDate Time,
* crlEntryExtensions Extensions OPTIONAL
* -- if present, version MUST be v2
* } OPTIONAL,
* crlExtensions [0] EXPLICIT Extensions OPTIONAL
* -- if present, version MUST be v2
* }
*
* Version, Time, CertificateSerialNumber, and Extensions
* are all defined in the ASN.1 in Section 4.1
*
* AlgorithmIdentifier is defined in Section 4.1.1.2
* }
* </pre>
*/
public class X509CRLStreamWriter {
public static final Logger log = LoggerFactory.getLogger(X509CRLStreamWriter.class);
private boolean locked = false;
private boolean preScanned = false;
private List<DERSequence> newEntries;
private Set<BigInteger> deletedEntries;
private InputStream crlIn;
private Integer originalLength;
private AtomicInteger count;
private AlgorithmIdentifier signingAlg;
private AlgorithmIdentifier digestAlg;
private int deletedEntriesLength;
private RSADigestSigner signer;
private RSAPrivateKey key;
private AuthorityKeyIdentifierStructure akiStructure;
private int newSigLength;
private int oldSigLength;
private boolean emptyCrl;
private int extensionsDelta;
private byte[] newExtensions;
public X509CRLStreamWriter(File crlToChange, RSAPrivateKey key, X509Certificate ca)
throws CryptoException, IOException, CertificateParsingException {
this(new BufferedInputStream(new FileInputStream(crlToChange)), key, ca);
}
public X509CRLStreamWriter(InputStream crlToChange, RSAPrivateKey key, X509Certificate ca)
throws CryptoException, IOException, CertificateParsingException {
this(crlToChange, key, new AuthorityKeyIdentifierStructure(ca));
}
public X509CRLStreamWriter(File crlToChange, RSAPrivateKey key, RSAPublicKey pubKey)
throws CryptoException, IOException, InvalidKeyException {
this(new BufferedInputStream(new FileInputStream(crlToChange)), key, pubKey);
}
public X509CRLStreamWriter(InputStream crlToChange, RSAPrivateKey key, RSAPublicKey pubKey)
throws CryptoException, IOException, InvalidKeyException {
this(crlToChange, key, new AuthorityKeyIdentifierStructure(pubKey));
}
public X509CRLStreamWriter(InputStream crlToChange,
RSAPrivateKey key, AuthorityKeyIdentifierStructure akiStructure)
throws CryptoException, IOException {
this.deletedEntries = new HashSet<BigInteger>();
this.deletedEntriesLength = 0;
this.newEntries = new LinkedList<DERSequence>();
this.crlIn = crlToChange;
this.count = new AtomicInteger();
/* The length of an RSA signature is padded out to the length of the modulus
* in bytes. See http://stackoverflow.com/questions/6658728/rsa-signature-size
*
* If the original CRL was signed with a 2048 bit key and someone sends in a
* 4096 bit key, we need to account for the discrepancy.
*/
int newSigBytes = key.getModulus().bitLength() / 8;
/* Now we need a byte array to figure out how long the new signature will
* be when encoded.
*/
byte[] dummySig = new byte[newSigBytes];
Arrays.fill(dummySig, (byte) 0x00);
this.newSigLength = new DERBitString(dummySig).getDEREncoded().length;
this.key = key;
this.akiStructure = akiStructure;
}
public X509CRLStreamWriter preScan(File crlToChange) throws IOException {
return preScan(crlToChange, null);
}
public X509CRLStreamWriter preScan(File crlToChange, CRLEntryValidator validator)
throws IOException {
return preScan(new BufferedInputStream(new FileInputStream(crlToChange)), validator);
}
public X509CRLStreamWriter preScan(InputStream crlToChange) throws IOException {
return preScan(crlToChange, null);
}
public synchronized X509CRLStreamWriter preScan(InputStream crlToChange, CRLEntryValidator validator)
throws IOException {
if (locked) {
throw new IllegalStateException("Cannot modify a locked stream.");
}
if (preScanned) {
throw new IllegalStateException("preScan has already been run.");
}
X509CRLEntryStream reaperStream = null;
ASN1InputStream asn1In = null;
try {
reaperStream = new X509CRLEntryStream(crlToChange);
try {
if (!reaperStream.hasNext()) {
emptyCrl = true;
preScanned = true;
return this;
}
while (reaperStream.hasNext()) {
X509CRLEntryObject entry = reaperStream.next();
if (validator != null && validator.shouldDelete(entry)) {
deletedEntries.add(entry.getSerialNumber());
deletedEntriesLength += entry.getEncoded().length;
}
}
}
catch (CRLException e) {
throw new IOException("Could not read CRL entry", e);
}
/* At this point, crlToChange is at the point where the crlExtensions would
* be. RFC 5280 says that "Conforming CRL issuers are REQUIRED to include
* the authority key identifier (Section 5.2.1) and the CRL number (Section 5.2.3)
* extensions in all CRLs issued.
*/
byte[] oldExtensions = null;
DERObject o;
asn1In = new ASN1InputStream(crlToChange);
while ((o = asn1In.readObject()) != null) {
if (o instanceof DERSequence) {
// Now we are at the signatureAlgorithm
DERSequence seq = (DERSequence) o;
if (seq.getObjectAt(0) instanceof DERObjectIdentifier) {
signingAlg = new AlgorithmIdentifier(seq);
digestAlg = new DefaultDigestAlgorithmIdentifierFinder().find(signingAlg);
try {
// Build the signer
this.signer = new RSADigestSigner(createDigest(digestAlg));
signer.init(true, new RSAKeyParameters(
true, key.getModulus(), key.getPrivateExponent()));
}
catch (CryptoException e) {
throw new IOException(
"Could not create RSADigest signer for " + digestAlg.getAlgorithm());
}
}
}
else if (o instanceof DERBitString) {
oldSigLength = o.getDEREncoded().length;
}
else {
if (oldExtensions != null) {
throw new IllegalStateException("Already read in CRL extensions.");
}
oldExtensions = ((DERTaggedObject) o).getDEREncoded();
}
}
if (oldExtensions == null) {
/* v1 CRLs (defined in RFC 1422) don't require extensions but all new
* CRLs should be v2 (defined in RFC 5280). In the extremely unlikely
* event that someone is working with a v1 CRL, we handle it here although
* we print a warning.
*/
preScanned = true;
newExtensions = null;
extensionsDelta = 0;
log.warn("The CRL you are modifying is a version 1 CRL." +
" Please investigate moving to a version 2 CRL by adding the CRL Number" +
" and Authority Key Identifier extensions.");
return this;
}
newExtensions = updateExtensions(oldExtensions);
// newExtension and oldExtensions have already been converted to DER so any difference
// in the length of the L bytes will be accounted for in the overall difference between
// the length of the two byte arrays.
extensionsDelta = newExtensions.length - oldExtensions.length;
}
finally {
if (reaperStream != null) {
reaperStream.close();
}
IOUtils.closeQuietly(asn1In);
}
preScanned = true;
return this;
}
/**
* Create an entry to be added to the CRL.
*
* @param serial
* @param date
* @param reason
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
public void add(BigInteger serial, Date date, int reason) {
if (locked) {
throw new IllegalStateException("Cannot add to a locked stream.");
}
ASN1EncodableVector v = new ASN1EncodableVector();
v.add(new DERInteger(serial));
v.add(new Time(date));
CRLReason crlReason = new CRLReason(reason);
Vector extOids = new Vector();
Vector extValues = new Vector();
extOids.addElement(X509Extension.reasonCode);
extValues.addElement(new X509Extension(false, new DEROctetString(crlReason.getDEREncoded())));
v.add(new X509Extensions(extOids, extValues));
newEntries.add(new DERSequence(v));
}
/**
* Locks the stream to prepare it for writing.
*
* @return itself
*/
public synchronized X509CRLStreamWriter lock() {
if (locked) {
throw new IllegalStateException("This stream is already locked.");
}
locked = true;
return this;
}
public boolean hasChangesQueued() {
return this.newEntries.size() > 0 || this.deletedEntries.size() > 0;
}
protected void writeToEmptyCrl(OutputStream out) throws IOException {
ASN1InputStream asn1in = null;
try {
asn1in = new ASN1InputStream(crlIn);
DERSequence certListSeq = (DERSequence) asn1in.readObject();
CertificateList certList = new CertificateList(certListSeq);
X509CRLHolder oldCrl = new X509CRLHolder(certList);
X509v2CRLBuilder crlBuilder = new X509v2CRLBuilder(oldCrl.getIssuer(), new Date());
crlBuilder.addCRL(oldCrl);
Date now = new Date();
Date oldNextUpdate = certList.getNextUpdate().getDate();
Date oldThisUpdate = certList.getThisUpdate().getDate();
Date nextUpdate = new Date(now.getTime() + (oldNextUpdate.getTime() - oldThisUpdate.getTime()));
crlBuilder.setNextUpdate(nextUpdate);
for (Object o : oldCrl.getExtensionOIDs()) {
ASN1ObjectIdentifier oid = (ASN1ObjectIdentifier) o;
X509Extension ext = oldCrl.getExtension(oid);
if (oid.equals(X509Extension.cRLNumber)) {
DEROctetString octet = (DEROctetString) ext.getValue().getDERObject();
DERInteger currentNumber = (DERInteger) DERTaggedObject.fromByteArray(octet.getOctets());
DERInteger nextNumber = new DERInteger(currentNumber.getValue().add(BigInteger.ONE));
crlBuilder.addExtension(oid, ext.isCritical(), nextNumber);
}
else if (oid.equals(X509Extension.authorityKeyIdentifier)) {
crlBuilder.addExtension(oid, ext.isCritical(),
new AuthorityKeyIdentifierStructure(ext.getValue().getDEREncoded()));
}
}
for (DERSequence entry : newEntries) {
// XXX: This is all a bit messy considering the user already passed in the serial, date
// and reason.
BigInteger serial = ((DERInteger) entry.getObjectAt(0)).getValue();
Date revokeDate = ((Time) entry.getObjectAt(1)).getDate();
int reason = CRLReason.unspecified;
if (entry.size() == 3) {
X509Extensions extensions = (X509Extensions) entry.getObjectAt(2);
X509Extension reasonExt = extensions.getExtension(X509Extension.reasonCode);
if (reasonExt != null) {
reason = ((DEREnumerated) reasonExt.getParsedValue()).getValue().intValue();
}
}
crlBuilder.addCRLEntry(serial, revokeDate, reason);
}
RSAKeyParameters keyParams = new RSAKeyParameters(
true, key.getModulus(), key.getPrivateExponent());
signingAlg = oldCrl.toASN1Structure().getSignatureAlgorithm();
digestAlg = new DefaultDigestAlgorithmIdentifierFinder().find(signingAlg);
ContentSigner s;
try {
s = new BcRSAContentSignerBuilder(signingAlg, digestAlg).build(keyParams);
X509CRLHolder newCrl = crlBuilder.build(s);
out.write(newCrl.getEncoded());
}
catch (OperatorCreationException e) {
throw new IOException("Could not sign CRL", e);
}
}
finally {
IOUtils.closeQuietly(asn1in);
}
}
/**
* Write a modified CRL to the given output stream. This method will add each entry provided
* via the add() method.
*
* @param out OutputStream to write to
* @throws IOException if something goes wrong
*/
public void write(OutputStream out) throws IOException {
if (!locked || !preScanned) {
throw new IllegalStateException("The instance must be preScanned and locked before writing.");
}
if (emptyCrl) {
/* An empty CRL is going to be missing the revokedCertificates sequence
* and would require a lot of special casing during the streaming process.
* Instead, it is easier to construct the CRL in the normal fashion using
* BouncyCastle. Performance should be acceptable as long as the number of
* CRL entries being added are reasonable in number. Something less than a
* thousand or so should yield adequate performance.
*/
writeToEmptyCrl(out);
return;
}
originalLength = handleHeader(out);
int tag;
int tagNo;
int length;
while (originalLength > count.get()) {
tag = readTag(crlIn, count);
tagNo = readTagNumber(crlIn, tag, count);
length = readLength(crlIn, count);
byte[] entryBytes = new byte[length];
readFullyAndTrack(crlIn, entryBytes, count);
DERInteger serial = (DERInteger) DERInteger.fromByteArray(entryBytes);
if (deletedEntriesLength == 0 || !deletedEntries.contains(serial.getValue())) {
writeTag(out, tag, tagNo, signer);
writeLength(out, length, signer);
writeValue(out, entryBytes, signer);
}
}
// Write the new entries into the new CRL
for (DERSequence entry : newEntries) {
writeBytes(out, entry.getDEREncoded(), signer);
}
// Copy the old extensions over
if (newExtensions != null) {
out.write(newExtensions);
signer.update(newExtensions, 0, newExtensions.length);
}
out.write(signingAlg.getDEREncoded());
try {
byte[] signature = signer.generateSignature();
DERBitString signatureBits = new DERBitString(signature);
out.write(signatureBits.getDEREncoded());
}
catch (DataLengthException e) {
throw new IOException("Could not sign", e);
}
catch (CryptoException e) {
throw new IOException("Could not sign", e);
}
}
/**
* This method updates the crlNumber and authorityKeyIdentifier extensions. Any
* other extensions are copied over unchanged.
* @param extensions
* @return
* @throws IOException
*/
@SuppressWarnings("rawtypes")
protected byte[] updateExtensions(byte[] obj) throws IOException {
DERTaggedObject taggedExts = (DERTaggedObject) DERTaggedObject.fromByteArray(obj);
DERSequence seq = (DERSequence) taggedExts.getObject();
ASN1EncodableVector modifiedExts = new ASN1EncodableVector();
// Now we need to read the extensions and find the CRL number and increment it,
// and determine if its length changed.
Enumeration objs = seq.getObjects();
while (objs.hasMoreElements()) {
DERSequence ext = (DERSequence) objs.nextElement();
DERObjectIdentifier oid = (DERObjectIdentifier) ext.getObjectAt(0);
if (X509Extension.cRLNumber.equals(oid)) {
DEROctetString s = (DEROctetString) ext.getObjectAt(1);
DERInteger i = (DERInteger) DERTaggedObject.fromByteArray(s.getOctets());
DERInteger newCrlNumber = new DERInteger(i.getValue().add(BigInteger.ONE));
X509Extension newNumberExt =
new X509Extension(false, new DEROctetString(newCrlNumber.getDEREncoded()));
ASN1EncodableVector crlNumber = new ASN1EncodableVector();
crlNumber.add(X509Extension.cRLNumber);
crlNumber.add(newNumberExt.getValue());
modifiedExts.add(new DERSequence(crlNumber));
}
else if (X509Extension.authorityKeyIdentifier.equals(oid)) {
X509Extension newAuthorityKeyExt =
new X509Extension(false, new DEROctetString(akiStructure.getDEREncoded()));
ASN1EncodableVector aki = new ASN1EncodableVector();
aki.add(X509Extension.authorityKeyIdentifier);
aki.add(newAuthorityKeyExt.getValue());
modifiedExts.add(new DERSequence(aki));
}
else {
modifiedExts.add(ext);
}
}
DERSequence seqOut = new DERSequence(modifiedExts);
DERTaggedObject out = new DERTaggedObject(true, 0, seqOut);
return out.getDEREncoded();
}
protected int handleHeader(OutputStream out) throws IOException {
int addedEntriesLength = 0;
for (DERSequence s : newEntries) {
addedEntriesLength += s.getDEREncoded().length;
}
int topTag = readTag(crlIn, null);
int topTagNo = readTagNumber(crlIn, topTag, null);
int oldTotalLength = readLength(crlIn, null);
// Now we are in the TBSCertList
int tbsTag = readTag(crlIn, null);
int tbsTagNo = readTagNumber(crlIn, tbsTag, null);
int oldTbsLength = readLength(crlIn, null);
/* We may need to adjust the overall length of the tbsCertList
* based on changes in the revokedCertificates sequence, so we
* will cache the tbsCertList data in this temporary byte stream.
*/
ByteArrayOutputStream temp = new ByteArrayOutputStream();
int tagNo = DERTags.NULL;
Date oldThisUpdate = null;
while (true) {
int tag = readTag(crlIn, null);
tagNo = readTagNumber(crlIn, tag, null);
if (tagNo == GENERALIZED_TIME || tagNo == UTC_TIME) {
oldThisUpdate = readAndReplaceTime(temp, tagNo);
break;
}
else {
writeTag(temp, tag, tagNo);
int length = echoLength(temp);
echoValue(temp, length);
}
}
// Now we have to deal with the potential for an optional nextUpdate field
int tag = readTag(crlIn, null);
tagNo = readTagNumber(crlIn, tag, null);
if (tagNo == GENERALIZED_TIME || tagNo == UTC_TIME) {
/* It would be possible to take in a desired nextUpdate in the constructor
* but I'm not sure if the added complexity is worth it.
*/
offsetNextUpdate(temp, tagNo, oldThisUpdate);
echoTag(temp);
}
else {
writeTag(temp, tag, tagNo);
}
/* Much like throwing a stone into a pond, as one sequence increases in
* length the change can ripple out to parent sequences as more bytes are
* required to encode the length. For example, if we have a tbsCertList of
* size 250 and a revokedCertificates list of size 100, the revokedCertificates
* list size could increase by 6 with no change in the length bytes its sequence
* requires. However, 250 + 6 extra bytes equals a total length of 256 which
* requires 2 bytes to encode instead of 1, thus changing the total length
* of the CertificateList sequence.
*
* We account for these ripples with the xxxHeaderBytesDelta variables.
*/
int revokedCertsLengthDelta = addedEntriesLength - deletedEntriesLength;
int oldRevokedCertsLength = readLength(crlIn, null);
int newRevokedCertsLength = oldRevokedCertsLength + revokedCertsLengthDelta;
int revokedCertsHeaderBytesDelta = findHeaderBytesDelta(oldRevokedCertsLength, newRevokedCertsLength);
int tbsCertListLengthDelta = revokedCertsLengthDelta +
revokedCertsHeaderBytesDelta +
extensionsDelta;
int newTbsLength = oldTbsLength + tbsCertListLengthDelta;
int tbsHeaderBytesDelta = findHeaderBytesDelta(oldTbsLength, newTbsLength);
// newSigLength represents a DER encoded signature so it already contains the header bytes delta.
int sigLengthDelta = newSigLength - oldSigLength;
int totalLengthDelta = tbsCertListLengthDelta + tbsHeaderBytesDelta + sigLengthDelta;
int newTotalLength = oldTotalLength + totalLengthDelta;
/* NB: The top level sequence isn't part of the signature so its tag and
* length do not go through the signer.
*/
writeTag(out, topTag, topTagNo);
writeLength(out, newTotalLength);
writeTag(out, tbsTag, tbsTagNo, signer);
writeLength(out, newTbsLength, signer);
byte[] header = temp.toByteArray();
temp.close();
out.write(header);
signer.update(header, 0, header.length);
writeLength(out, newRevokedCertsLength, signer);
return oldRevokedCertsLength;
}
/**
* Write a new nextUpdate time that is the same amount of time ahead of the new thisUpdate
* time as the old nextUpdate was from the old thisUpdate.
*
* @param out
* @param tagNo
* @param oldThisUpdate
* @throws IOException
*/
protected void offsetNextUpdate(OutputStream out, int tagNo, Date oldThisUpdate)
throws IOException {
int originalLength = readLength(crlIn, null);
byte[] oldBytes = new byte[originalLength];
readFullyAndTrack(crlIn, oldBytes, null);
DERObject oldTime = null;
if (tagNo == UTC_TIME) {
DERTaggedObject t = new DERTaggedObject(UTC_TIME, new DEROctetString(oldBytes));
oldTime = DERUTCTime.getInstance(t, false);
}
else {
DERTaggedObject t = new DERTaggedObject(GENERALIZED_TIME, new DEROctetString(oldBytes));
oldTime = DERGeneralizedTime.getInstance(t, false);
}
/* Determine the time between the old thisUpdate and old nextUpdate and add it
/* to the new nextUpdate. */
Date oldNextUpdate = new Time(oldTime).getDate();
long delta = oldNextUpdate.getTime() - oldThisUpdate.getTime();
Date newNextUpdate = new Date(new Date().getTime() + delta);
DERObject newTime = null;
if (tagNo == UTC_TIME) {
newTime = new DERUTCTime(newNextUpdate);
}
else {
newTime = new DERGeneralizedTime(newNextUpdate);
}
writeNewTime(out, newTime, originalLength);
}
/**
* Replace a time in the ASN1 with the current time.
*
* @param out
* @param tagNo
* @return the time that was replaced
* @throws IOException
*/
protected Date readAndReplaceTime(OutputStream out, int tagNo) throws IOException {
int originalLength = readLength(crlIn, null);
byte[] oldBytes = new byte[originalLength];
readFullyAndTrack(crlIn, oldBytes, null);
DERObject oldTime = null;
DERObject newTime = null;
if (tagNo == UTC_TIME) {
DERTaggedObject t = new DERTaggedObject(UTC_TIME, new DEROctetString(oldBytes));
oldTime = DERUTCTime.getInstance(t, false);
newTime = new DERUTCTime(new Date());
}
else {
DERTaggedObject t = new DERTaggedObject(GENERALIZED_TIME, new DEROctetString(oldBytes));
oldTime = DERGeneralizedTime.getInstance(t, false);
newTime = new DERGeneralizedTime(new Date());
}
writeNewTime(out, newTime, originalLength);
return new Time(oldTime).getDate();
}
/**
* Write a UTCTime or GeneralizedTime to an output stream.
*
* @param out
* @param newTime
* @param originalLength
* @throws IOException
*/
protected void writeNewTime(OutputStream out, DERObject newTime, int originalLength)
throws IOException {
byte[] newEncodedTime = newTime.getDEREncoded();
InputStream timeIn = null;
try {
timeIn = new ByteArrayInputStream(newEncodedTime);
int newTag = readTag(timeIn, null);
readTagNumber(timeIn, newTag, null);
int newLength = readLength(timeIn, null);
/* If the length changes, it's going to create a discrepancy with the length
* reported in the TBSCertList sequence. The length could change with the addition
* or removal of time zone information for example. */
if (newLength != originalLength) {
throw new IllegalStateException("Length of generated time does not match " +
"the original length. Corruption would result.");
}
}
finally {
IOUtils.closeQuietly(timeIn);
}
writeBytes(out, newEncodedTime);
}
/**
* Echo tag without tracking and without signing.
*
* @param out
* @return tag value
* @throws IOException
*/
protected int echoTag(OutputStream out) throws IOException {
return echoTag(out, null, null);
}
/**
* Echo tag and sign with existing RSADigestSigner.
*
* @param out
* @param i optional value to increment by the number of bytes read
* @return tag value
* @throws IOException
*/
protected int echoTag(OutputStream out, AtomicInteger i) throws IOException {
return echoTag(out, i, signer);
}
protected int echoTag(OutputStream out, AtomicInteger i, RSADigestSigner s) throws IOException {
int tag = readTag(crlIn, i);
int tagNo = readTagNumber(crlIn, tag, i);
writeTag(out, tag, tagNo, s);
return tagNo;
}
/**
* Echo length without tracking and without signing.
*
* @param out
* @return length value
* @throws IOException
*/
protected int echoLength(OutputStream out) throws IOException {
return echoLength(out, null, null);
}
/**
* Echo length and sign with existing RSADigestSigner.
*
* @param out
* @param i optional value to increment by the number of bytes read
* @return length value
* @throws IOException
*/
protected int echoLength(OutputStream out, AtomicInteger i) throws IOException {
return echoLength(out, i, signer);
}
protected int echoLength(OutputStream out, AtomicInteger i, RSADigestSigner s) throws IOException {
int length = readLength(crlIn, i);
writeLength(out, length, s);
return length;
}
/**
* Echo value without tracking and without signing.
*
* @param out
* @param length
* @throws IOException
*/
protected void echoValue(OutputStream out, int length) throws IOException {
echoValue(out, length, null, null);
}
/**
* Echo value and sign with existing RSADigestSigner.
*
* @param out
* @param length
* @param i optional value to increment by the number of bytes read
* @throws IOException
*/
protected void echoValue(OutputStream out, int length, AtomicInteger i) throws IOException {
echoValue(out, length, i, signer);
}
protected void echoValue(OutputStream out, int length, AtomicInteger i, RSADigestSigner s)
throws IOException {
byte[] item = new byte[length];
readFullyAndTrack(crlIn, item, i);
writeValue(out, item, s);
}
protected static Digest createDigest(AlgorithmIdentifier digAlg) throws CryptoException {
Digest dig;
if (digAlg.getAlgorithm().equals(OIWObjectIdentifiers.idSHA1)) {
dig = new SHA1Digest();
}
else if (digAlg.getAlgorithm().equals(NISTObjectIdentifiers.id_sha224)) {
dig = new SHA224Digest();
}
else if (digAlg.getAlgorithm().equals(NISTObjectIdentifiers.id_sha256)) {
dig = new SHA256Digest();
}
else if (digAlg.getAlgorithm().equals(NISTObjectIdentifiers.id_sha384)) {
dig = new SHA384Digest();
}
else if (digAlg.getAlgorithm().equals(NISTObjectIdentifiers.id_sha512)) {
dig = new SHA384Digest();
}
else if (digAlg.getAlgorithm().equals(PKCSObjectIdentifiers.md5)) {
dig = new MD5Digest();
}
else if (digAlg.getAlgorithm().equals(PKCSObjectIdentifiers.md4)) {
dig = new MD4Digest();
}
else {
throw new CryptoException("Cannot recognize digest.");
}
return dig;
}
}