| package org.bouncycastle.cms; |
| |
| import java.io.IOException; |
| import java.io.OutputStream; |
| import java.util.ArrayList; |
| 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.CMSObjectIdentifiers; |
| import org.bouncycastle.asn1.cms.SignerInfo; |
| import org.bouncycastle.asn1.x509.AlgorithmIdentifier; |
| |
| /** |
| * 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(); |
| AlgorithmIdentifier digAlg = CMSSignedHelper.INSTANCE.fixAlgID(signer.getDigestAlgorithmID()); |
| |
| digestAlgs.add(digAlg); |
| } |
| |
| // |
| // 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); |
| } |
| |
| /** |
| * Return a list of the current Digest AlgorithmIdentifiers applying to the next signature. |
| * |
| * @return a list of the Digest AlgorithmIdentifiers |
| */ |
| public List<AlgorithmIdentifier> getDigestAlgorithms() |
| { |
| List digestAlorithms = new ArrayList(); |
| |
| // |
| // add the precalculated SignerInfo digest algorithms. |
| // |
| for (Iterator it = _signers.iterator(); it.hasNext();) |
| { |
| SignerInformation signer = (SignerInformation)it.next(); |
| AlgorithmIdentifier digAlg = CMSSignedHelper.INSTANCE.fixAlgID(signer.getDigestAlgorithmID()); |
| |
| digestAlorithms.add(digAlg); |
| } |
| |
| // |
| // add the new digests |
| // |
| |
| for (Iterator it = signerGens.iterator(); it.hasNext();) |
| { |
| SignerInfoGenerator signerGen = (SignerInfoGenerator)it.next(); |
| |
| digestAlorithms.add(signerGen.getDigestAlgorithm()); |
| } |
| |
| return digestAlorithms; |
| } |
| |
| // 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() == 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(); |
| } |
| } |
| } |