package org.bouncycastle.cms; import java.io.IOException; import java.io.OutputStream; import java.security.SecureRandom; import java.util.Iterator; import java.util.List; import org.bouncycastle.asn1.ASN1EncodableVector; import org.bouncycastle.asn1.ASN1Integer; import org.bouncycastle.asn1.ASN1ObjectIdentifier; import org.bouncycastle.asn1.ASN1Set; import org.bouncycastle.asn1.ASN1TaggedObject; import org.bouncycastle.asn1.BERSequenceGenerator; import org.bouncycastle.asn1.BERTaggedObject; import org.bouncycastle.asn1.DERSet; import org.bouncycastle.asn1.cms.AttributeTable; import org.bouncycastle.asn1.cms.CMSObjectIdentifiers; import org.bouncycastle.asn1.cms.SignerInfo; import org.bouncycastle.operator.ContentSigner; import org.bouncycastle.operator.OperatorCreationException; /** * General class for generating a pkcs7-signature message stream. * <p> * A simple example of usage. * </p> * <pre> * X509Certificate signCert = ... * certList.add(signCert); * * Store certs = new JcaCertStore(certList); * ContentSigner sha1Signer = new JcaContentSignerBuilder("SHA1withRSA").setProvider("BC").build(signKP.getPrivate()); * * CMSSignedDataStreamGenerator gen = new CMSSignedDataStreamGenerator(); * * gen.addSignerInfoGenerator( * new JcaSignerInfoGeneratorBuilder( * new JcaDigestCalculatorProviderBuilder().setProvider("BC").build()) * .build(sha1Signer, signCert)); * * gen.addCertificates(certs); * * OutputStream sigOut = gen.open(bOut); * * sigOut.write("Hello World!".getBytes()); * * sigOut.close(); * </pre> */ public class CMSSignedDataStreamGenerator extends CMSSignedGenerator { private int _bufferSize; /** * base constructor */ public CMSSignedDataStreamGenerator() { } /** * Set the underlying string size for encapsulated data * * @param bufferSize length of octet strings to buffer the data. */ public void setBufferSize( int bufferSize) { _bufferSize = bufferSize; } /** * generate a signed object that for a CMS Signed Data * object using the given provider. */ public OutputStream open( OutputStream out) throws IOException { return open(out, false); } /** * generate a signed object that for a CMS Signed Data * object using the given provider - if encapsulate is true a copy * of the message will be included in the signature with the * default content type "data". */ public OutputStream open( OutputStream out, boolean encapsulate) throws IOException { return open(CMSObjectIdentifiers.data, out, encapsulate); } /** * generate a signed object that for a CMS Signed Data * object using the given provider - if encapsulate is true a copy * of the message will be included in the signature with the * default content type "data". If dataOutputStream is non null the data * being signed will be written to the stream as it is processed. * @param out stream the CMS object is to be written to. * @param encapsulate true if data should be encapsulated. * @param dataOutputStream output stream to copy the data being signed to. */ public OutputStream open( OutputStream out, boolean encapsulate, OutputStream dataOutputStream) throws IOException { return open(CMSObjectIdentifiers.data, out, encapsulate, dataOutputStream); } /** * generate a signed object that for a CMS Signed Data * object using the given provider - if encapsulate is true a copy * of the message will be included in the signature. The content type * is set according to the OID represented by the string signedContentType. */ public OutputStream open( ASN1ObjectIdentifier eContentType, OutputStream out, boolean encapsulate) throws IOException { return open(eContentType, out, encapsulate, null); } /** * generate a signed object that for a CMS Signed Data * object using the given provider - if encapsulate is true a copy * of the message will be included in the signature. The content type * is set according to the OID represented by the string signedContentType. * @param eContentType OID for data to be signed. * @param out stream the CMS object is to be written to. * @param encapsulate true if data should be encapsulated. * @param dataOutputStream output stream to copy the data being signed to. */ public OutputStream open( ASN1ObjectIdentifier eContentType, OutputStream out, boolean encapsulate, OutputStream dataOutputStream) throws IOException { // TODO // if (_signerInfs.isEmpty()) // { // /* RFC 3852 5.2 // * "In the degenerate case where there are no signers, the // * EncapsulatedContentInfo value being "signed" is irrelevant. In this // * case, the content type within the EncapsulatedContentInfo value being // * "signed" MUST be id-data (as defined in section 4), and the content // * field of the EncapsulatedContentInfo value MUST be omitted." // */ // if (encapsulate) // { // throw new IllegalArgumentException("no signers, encapsulate must be false"); // } // if (!DATA.equals(eContentType)) // { // throw new IllegalArgumentException("no signers, eContentType must be id-data"); // } // } // // if (!DATA.equals(eContentType)) // { // /* RFC 3852 5.3 // * [The 'signedAttrs']... // * field is optional, but it MUST be present if the content type of // * the EncapsulatedContentInfo value being signed is not id-data. // */ // // TODO signedAttrs must be present for all signers // } // // ContentInfo // BERSequenceGenerator sGen = new BERSequenceGenerator(out); sGen.addObject(CMSObjectIdentifiers.signedData); // // Signed Data // BERSequenceGenerator sigGen = new BERSequenceGenerator(sGen.getRawOutputStream(), 0, true); sigGen.addObject(calculateVersion(eContentType)); ASN1EncodableVector digestAlgs = new ASN1EncodableVector(); // // add the precalculated SignerInfo digest algorithms. // for (Iterator it = _signers.iterator(); it.hasNext();) { SignerInformation signer = (SignerInformation)it.next(); digestAlgs.add(CMSSignedHelper.INSTANCE.fixAlgID(signer.getDigestAlgorithmID())); } // // add the new digests // for (Iterator it = signerGens.iterator(); it.hasNext();) { SignerInfoGenerator signerGen = (SignerInfoGenerator)it.next(); digestAlgs.add(signerGen.getDigestAlgorithm()); } sigGen.getRawOutputStream().write(new DERSet(digestAlgs).getEncoded()); BERSequenceGenerator eiGen = new BERSequenceGenerator(sigGen.getRawOutputStream()); eiGen.addObject(eContentType); // If encapsulating, add the data as an octet string in the sequence OutputStream encapStream = encapsulate ? CMSUtils.createBEROctetOutputStream(eiGen.getRawOutputStream(), 0, true, _bufferSize) : null; // Also send the data to 'dataOutputStream' if necessary OutputStream contentStream = CMSUtils.getSafeTeeOutputStream(dataOutputStream, encapStream); // Let all the signers see the data as it is written OutputStream sigStream = CMSUtils.attachSignersToOutputStream(signerGens, contentStream); return new CmsSignedDataOutputStream(sigStream, eContentType, sGen, sigGen, eiGen); } // TODO Make public? void generate( OutputStream out, String eContentType, boolean encapsulate, OutputStream dataOutputStream, CMSProcessable content) throws CMSException, IOException { OutputStream signedOut = open(out, eContentType, encapsulate, dataOutputStream); if (content != null) { content.write(signedOut); } signedOut.close(); } // RFC3852, section 5.1: // IF ((certificates is present) AND // (any certificates with a type of other are present)) OR // ((crls is present) AND // (any crls with a type of other are present)) // THEN version MUST be 5 // ELSE // IF (certificates is present) AND // (any version 2 attribute certificates are present) // THEN version MUST be 4 // ELSE // IF ((certificates is present) AND // (any version 1 attribute certificates are present)) OR // (any SignerInfo structures are version 3) OR // (encapContentInfo eContentType is other than id-data) // THEN version MUST be 3 // ELSE version MUST be 1 // private ASN1Integer calculateVersion( ASN1ObjectIdentifier contentOid) { boolean otherCert = false; boolean otherCrl = false; boolean attrCertV1Found = false; boolean attrCertV2Found = false; if (certs != null) { for (Iterator it = certs.iterator(); it.hasNext();) { Object obj = it.next(); if (obj instanceof ASN1TaggedObject) { ASN1TaggedObject tagged = (ASN1TaggedObject)obj; if (tagged.getTagNo() == 1) { attrCertV1Found = true; } else if (tagged.getTagNo() == 2) { attrCertV2Found = true; } else if (tagged.getTagNo() == 3) { otherCert = true; } } } } if (otherCert) { return new ASN1Integer(5); } if (crls != null) // no need to check if otherCert is true { for (Iterator it = crls.iterator(); it.hasNext();) { Object obj = it.next(); if (obj instanceof ASN1TaggedObject) { otherCrl = true; } } } if (otherCrl) { return new ASN1Integer(5); } if (attrCertV2Found) { return new ASN1Integer(4); } if (attrCertV1Found) { return new ASN1Integer(3); } if (checkForVersion3(_signers, signerGens)) { return new ASN1Integer(3); } if (!CMSObjectIdentifiers.data.equals(contentOid)) { return new ASN1Integer(3); } return new ASN1Integer(1); } private boolean checkForVersion3(List signerInfos, List signerInfoGens) { for (Iterator it = signerInfos.iterator(); it.hasNext();) { SignerInfo s = SignerInfo.getInstance(((SignerInformation)it.next()).toASN1Structure()); if (s.getVersion().getValue().intValue() == 3) { return true; } } for (Iterator it = signerInfoGens.iterator(); it.hasNext();) { SignerInfoGenerator s = (SignerInfoGenerator)it.next(); if (s.getGeneratedVersion().getValue().intValue() == 3) { return true; } } return false; } private class CmsSignedDataOutputStream extends OutputStream { private OutputStream _out; private ASN1ObjectIdentifier _contentOID; private BERSequenceGenerator _sGen; private BERSequenceGenerator _sigGen; private BERSequenceGenerator _eiGen; public CmsSignedDataOutputStream( OutputStream out, ASN1ObjectIdentifier contentOID, BERSequenceGenerator sGen, BERSequenceGenerator sigGen, BERSequenceGenerator eiGen) { _out = out; _contentOID = contentOID; _sGen = sGen; _sigGen = sigGen; _eiGen = eiGen; } public void write( int b) throws IOException { _out.write(b); } public void write( byte[] bytes, int off, int len) throws IOException { _out.write(bytes, off, len); } public void write( byte[] bytes) throws IOException { _out.write(bytes); } public void close() throws IOException { _out.close(); _eiGen.close(); digests.clear(); // clear the current preserved digest state if (certs.size() != 0) { ASN1Set certSet = CMSUtils.createBerSetFromList(certs); _sigGen.getRawOutputStream().write(new BERTaggedObject(false, 0, certSet).getEncoded()); } if (crls.size() != 0) { ASN1Set crlSet = CMSUtils.createBerSetFromList(crls); _sigGen.getRawOutputStream().write(new BERTaggedObject(false, 1, crlSet).getEncoded()); } // // collect all the SignerInfo objects // ASN1EncodableVector signerInfos = new ASN1EncodableVector(); // // add the generated SignerInfo objects // for (Iterator it = signerGens.iterator(); it.hasNext();) { SignerInfoGenerator sigGen = (SignerInfoGenerator)it.next(); try { signerInfos.add(sigGen.generate(_contentOID)); byte[] calculatedDigest = sigGen.getCalculatedDigest(); digests.put(sigGen.getDigestAlgorithm().getAlgorithm().getId(), calculatedDigest); } catch (CMSException e) { throw new CMSStreamException("exception generating signers: " + e.getMessage(), e); } } // // add the precalculated SignerInfo objects // { Iterator it = _signers.iterator(); while (it.hasNext()) { SignerInformation signer = (SignerInformation)it.next(); // TODO Verify the content type and calculated digest match the precalculated SignerInfo // if (!signer.getContentType().equals(_contentOID)) // { // // TODO The precalculated content type did not match - error? // } // // byte[] calculatedDigest = (byte[])_digests.get(signer.getDigestAlgOID()); // if (calculatedDigest == null) // { // // TODO We can't confirm this digest because we didn't calculate it - error? // } // else // { // if (!Arrays.areEqual(signer.getContentDigest(), calculatedDigest)) // { // // TODO The precalculated digest did not match - error? // } // } signerInfos.add(signer.toASN1Structure()); } } _sigGen.getRawOutputStream().write(new DERSet(signerInfos).getEncoded()); _sigGen.close(); _sGen.close(); } } }