/** * 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; } }