1 package org.bouncycastle.cms; 2 3 import java.io.ByteArrayOutputStream; 4 import java.io.IOException; 5 import java.io.OutputStream; 6 import java.util.ArrayList; 7 import java.util.Iterator; 8 import java.util.List; 9 10 import org.bouncycastle.asn1.ASN1EncodableVector; 11 import org.bouncycastle.asn1.ASN1ObjectIdentifier; 12 import org.bouncycastle.asn1.ASN1OctetString; 13 import org.bouncycastle.asn1.ASN1Set; 14 import org.bouncycastle.asn1.BEROctetString; 15 import org.bouncycastle.asn1.DERSet; 16 import org.bouncycastle.asn1.cms.CMSObjectIdentifiers; 17 import org.bouncycastle.asn1.cms.ContentInfo; 18 import org.bouncycastle.asn1.cms.SignedData; 19 import org.bouncycastle.asn1.cms.SignerInfo; 20 21 /** 22 * general class for generating a pkcs7-signature message. 23 * <p> 24 * A simple example of usage, generating a detached signature. 25 * 26 * <pre> 27 * List certList = new ArrayList(); 28 * CMSTypedData msg = new CMSProcessableByteArray("Hello world!".getBytes()); 29 * 30 * certList.add(signCert); 31 * 32 * Store certs = new JcaCertStore(certList); 33 * 34 * CMSSignedDataGenerator gen = new CMSSignedDataGenerator(); 35 * ContentSigner sha1Signer = new JcaContentSignerBuilder("SHA1withRSA").setProvider("BC").build(signKP.getPrivate()); 36 * 37 * gen.addSignerInfoGenerator( 38 * new JcaSignerInfoGeneratorBuilder( 39 * new JcaDigestCalculatorProviderBuilder().setProvider("BC").build()) 40 * .build(sha1Signer, signCert)); 41 * 42 * gen.addCertificates(certs); 43 * 44 * CMSSignedData sigData = gen.generate(msg, false); 45 * </pre> 46 */ 47 public class CMSSignedDataGenerator 48 extends CMSSignedGenerator 49 { 50 private List signerInfs = new ArrayList(); 51 52 /** 53 * base constructor 54 */ CMSSignedDataGenerator()55 public CMSSignedDataGenerator() 56 { 57 } 58 59 /** 60 * Generate a CMS Signed Data object carrying a detached CMS signature. 61 * 62 * @param content the content to be signed. 63 */ generate( CMSTypedData content)64 public CMSSignedData generate( 65 CMSTypedData content) 66 throws CMSException 67 { 68 return generate(content, false); 69 } 70 71 /** 72 * Generate a CMS Signed Data object which can be carrying a detached CMS signature, or have encapsulated data, depending on the value 73 * of the encapsulated parameter. 74 * 75 * @param content the content to be signed. 76 * @param encapsulate true if the content should be encapsulated in the signature, false otherwise. 77 */ generate( CMSTypedData content, boolean encapsulate)78 public CMSSignedData generate( 79 // FIXME Avoid accessing more than once to support CMSProcessableInputStream 80 CMSTypedData content, 81 boolean encapsulate) 82 throws CMSException 83 { 84 if (!signerInfs.isEmpty()) 85 { 86 throw new IllegalStateException("this method can only be used with SignerInfoGenerator"); 87 } 88 89 // TODO 90 // if (signerInfs.isEmpty()) 91 // { 92 // /* RFC 3852 5.2 93 // * "In the degenerate case where there are no signers, the 94 // * EncapsulatedContentInfo value being "signed" is irrelevant. In this 95 // * case, the content type within the EncapsulatedContentInfo value being 96 // * "signed" MUST be id-data (as defined in section 4), and the content 97 // * field of the EncapsulatedContentInfo value MUST be omitted." 98 // */ 99 // if (encapsulate) 100 // { 101 // throw new IllegalArgumentException("no signers, encapsulate must be false"); 102 // } 103 // if (!DATA.equals(eContentType)) 104 // { 105 // throw new IllegalArgumentException("no signers, eContentType must be id-data"); 106 // } 107 // } 108 // 109 // if (!DATA.equals(eContentType)) 110 // { 111 // /* RFC 3852 5.3 112 // * [The 'signedAttrs']... 113 // * field is optional, but it MUST be present if the content type of 114 // * the EncapsulatedContentInfo value being signed is not id-data. 115 // */ 116 // // TODO signedAttrs must be present for all signers 117 // } 118 119 ASN1EncodableVector digestAlgs = new ASN1EncodableVector(); 120 ASN1EncodableVector signerInfos = new ASN1EncodableVector(); 121 122 digests.clear(); // clear the current preserved digest state 123 124 // 125 // add the precalculated SignerInfo objects. 126 // 127 for (Iterator it = _signers.iterator(); it.hasNext();) 128 { 129 SignerInformation signer = (SignerInformation)it.next(); 130 digestAlgs.add(CMSSignedHelper.INSTANCE.fixAlgID(signer.getDigestAlgorithmID())); 131 132 // TODO Verify the content type and calculated digest match the precalculated SignerInfo 133 signerInfos.add(signer.toASN1Structure()); 134 } 135 136 // 137 // add the SignerInfo objects 138 // 139 ASN1ObjectIdentifier contentTypeOID = content.getContentType(); 140 141 ASN1OctetString octs = null; 142 143 if (content.getContent() != null) 144 { 145 ByteArrayOutputStream bOut = null; 146 147 if (encapsulate) 148 { 149 bOut = new ByteArrayOutputStream(); 150 } 151 152 OutputStream cOut = CMSUtils.attachSignersToOutputStream(signerGens, bOut); 153 154 // Just in case it's unencapsulated and there are no signers! 155 cOut = CMSUtils.getSafeOutputStream(cOut); 156 157 try 158 { 159 content.write(cOut); 160 161 cOut.close(); 162 } 163 catch (IOException e) 164 { 165 throw new CMSException("data processing exception: " + e.getMessage(), e); 166 } 167 168 if (encapsulate) 169 { 170 octs = new BEROctetString(bOut.toByteArray()); 171 } 172 } 173 174 for (Iterator it = signerGens.iterator(); it.hasNext();) 175 { 176 SignerInfoGenerator sGen = (SignerInfoGenerator)it.next(); 177 SignerInfo inf = sGen.generate(contentTypeOID); 178 179 digestAlgs.add(inf.getDigestAlgorithm()); 180 signerInfos.add(inf); 181 182 byte[] calcDigest = sGen.getCalculatedDigest(); 183 184 if (calcDigest != null) 185 { 186 digests.put(inf.getDigestAlgorithm().getAlgorithm().getId(), calcDigest); 187 } 188 } 189 190 ASN1Set certificates = null; 191 192 if (certs.size() != 0) 193 { 194 certificates = CMSUtils.createBerSetFromList(certs); 195 } 196 197 ASN1Set certrevlist = null; 198 199 if (crls.size() != 0) 200 { 201 certrevlist = CMSUtils.createBerSetFromList(crls); 202 } 203 204 ContentInfo encInfo = new ContentInfo(contentTypeOID, octs); 205 206 SignedData sd = new SignedData( 207 new DERSet(digestAlgs), 208 encInfo, 209 certificates, 210 certrevlist, 211 new DERSet(signerInfos)); 212 213 ContentInfo contentInfo = new ContentInfo( 214 CMSObjectIdentifiers.signedData, sd); 215 216 return new CMSSignedData(content, contentInfo); 217 } 218 219 /** 220 * generate a set of one or more SignerInformation objects representing counter signatures on 221 * the passed in SignerInformation object. 222 * 223 * @param signer the signer to be countersigned 224 * @return a store containing the signers. 225 */ generateCounterSigners(SignerInformation signer)226 public SignerInformationStore generateCounterSigners(SignerInformation signer) 227 throws CMSException 228 { 229 return this.generate(new CMSProcessableByteArray(null, signer.getSignature()), false).getSignerInfos(); 230 } 231 } 232 233