001: package org.bouncycastle.cms;
002:
003: import org.bouncycastle.asn1.ASN1EncodableVector;
004: import org.bouncycastle.asn1.ASN1InputStream;
005: import org.bouncycastle.asn1.ASN1OctetString;
006: import org.bouncycastle.asn1.ASN1Set;
007: import org.bouncycastle.asn1.BERConstructedOctetString;
008: import org.bouncycastle.asn1.DERNull;
009: import org.bouncycastle.asn1.DERObject;
010: import org.bouncycastle.asn1.DERObjectIdentifier;
011: import org.bouncycastle.asn1.DEROctetString;
012: import org.bouncycastle.asn1.DEROutputStream;
013: import org.bouncycastle.asn1.DERSet;
014: import org.bouncycastle.asn1.cms.AttributeTable;
015: import org.bouncycastle.asn1.cms.ContentInfo;
016: import org.bouncycastle.asn1.cms.IssuerAndSerialNumber;
017: import org.bouncycastle.asn1.cms.SignedData;
018: import org.bouncycastle.asn1.cms.SignerIdentifier;
019: import org.bouncycastle.asn1.cms.SignerInfo;
020: import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
021: import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
022: import org.bouncycastle.asn1.x509.TBSCertificateStructure;
023:
024: import java.io.ByteArrayInputStream;
025: import java.io.ByteArrayOutputStream;
026: import java.io.IOException;
027: import java.io.OutputStream;
028: import java.security.InvalidKeyException;
029: import java.security.MessageDigest;
030: import java.security.NoSuchAlgorithmException;
031: import java.security.NoSuchProviderException;
032: import java.security.PrivateKey;
033: import java.security.Signature;
034: import java.security.SignatureException;
035: import java.security.cert.CertificateEncodingException;
036: import java.security.cert.X509Certificate;
037: import java.util.ArrayList;
038: import java.util.Collections;
039: import java.util.Iterator;
040: import java.util.List;
041: import java.util.Map;
042:
043: /**
044: * general class for generating a pkcs7-signature message.
045: * <p>
046: * A simple example of usage.
047: *
048: * <pre>
049: * CertStore certs...
050: * CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
051: *
052: * gen.addSigner(privKey, cert, CMSSignedGenerator.DIGEST_SHA1);
053: * gen.addCertificatesAndCRLs(certs);
054: *
055: * CMSSignedData data = gen.generate(content, "BC");
056: * </pre>
057: */
058: public class CMSSignedDataGenerator extends CMSSignedGenerator {
059: List signerInfs = new ArrayList();
060:
061: static class DigOutputStream extends OutputStream {
062: MessageDigest dig;
063:
064: public DigOutputStream(MessageDigest dig) {
065: this .dig = dig;
066: }
067:
068: public void write(byte[] b, int off, int len)
069: throws IOException {
070: dig.update(b, off, len);
071: }
072:
073: public void write(int b) throws IOException {
074: dig.update((byte) b);
075: }
076: }
077:
078: static class SigOutputStream extends OutputStream {
079: Signature sig;
080:
081: public SigOutputStream(Signature sig) {
082: this .sig = sig;
083: }
084:
085: public void write(byte[] b, int off, int len)
086: throws IOException {
087: try {
088: sig.update(b, off, len);
089: } catch (SignatureException e) {
090: throw new IOException("signature problem: " + e);
091: }
092: }
093:
094: public void write(int b) throws IOException {
095: try {
096: sig.update((byte) b);
097: } catch (SignatureException e) {
098: throw new IOException("signature problem: " + e);
099: }
100: }
101: }
102:
103: private class SignerInf {
104: PrivateKey key;
105: X509Certificate cert;
106: String digestOID;
107: String encOID;
108: CMSAttributeTableGenerator sAttr;
109: CMSAttributeTableGenerator unsAttr;
110: AttributeTable baseSignedTable;
111:
112: SignerInf(PrivateKey key, X509Certificate cert,
113: String digestOID, String encOID) {
114: this .key = key;
115: this .cert = cert;
116: this .digestOID = digestOID;
117: this .encOID = encOID;
118: }
119:
120: SignerInf(PrivateKey key, X509Certificate cert,
121: String digestOID, String encOID,
122: CMSAttributeTableGenerator sAttr,
123: CMSAttributeTableGenerator unsAttr,
124: AttributeTable baseSigneTable) {
125: this .key = key;
126: this .cert = cert;
127: this .digestOID = digestOID;
128: this .encOID = encOID;
129: this .sAttr = sAttr;
130: this .unsAttr = unsAttr;
131: this .baseSignedTable = baseSigneTable;
132: }
133:
134: PrivateKey getKey() {
135: return key;
136: }
137:
138: X509Certificate getCertificate() {
139: return cert;
140: }
141:
142: String getDigestAlgOID() {
143: return digestOID;
144: }
145:
146: byte[] getDigestAlgParams() {
147: return null;
148: }
149:
150: String getEncryptionAlgOID() {
151: return encOID;
152: }
153:
154: CMSAttributeTableGenerator getSignedAttributes() {
155: return sAttr;
156: }
157:
158: CMSAttributeTableGenerator getUnsignedAttributes() {
159: return unsAttr;
160: }
161:
162: SignerInfo toSignerInfo(DERObjectIdentifier contentType,
163: CMSProcessable content, String sigProvider,
164: boolean addDefaultAttributes) throws IOException,
165: SignatureException, InvalidKeyException,
166: NoSuchProviderException, NoSuchAlgorithmException,
167: CertificateEncodingException, CMSException {
168: AlgorithmIdentifier digAlgId = new AlgorithmIdentifier(
169: new DERObjectIdentifier(this .getDigestAlgOID()),
170: new DERNull());
171: AlgorithmIdentifier encAlgId = getEncAlgorithmIdentifier(this
172: .getEncryptionAlgOID());
173: String digestName = CMSSignedHelper.INSTANCE
174: .getDigestAlgName(digestOID);
175: String signatureName = digestName
176: + "with"
177: + CMSSignedHelper.INSTANCE
178: .getEncryptionAlgName(encOID);
179: Signature sig = CMSSignedHelper.INSTANCE
180: .getSignatureInstance(signatureName, sigProvider);
181: MessageDigest dig = CMSSignedHelper.INSTANCE
182: .getDigestInstance(digestName, sigProvider);
183:
184: byte[] hash = null;
185:
186: if (content != null) {
187: content.write(new DigOutputStream(dig));
188:
189: hash = dig.digest();
190:
191: _digests.put(digestOID, hash.clone());
192: }
193:
194: AttributeTable signed;
195:
196: if (addDefaultAttributes) {
197: Map parameters = getBaseParameters(contentType,
198: digAlgId, hash);
199: signed = (sAttr != null) ? sAttr
200: .getAttributes(Collections
201: .unmodifiableMap(parameters)) : null;
202: } else {
203: signed = baseSignedTable;
204: }
205:
206: ASN1Set signedAttr = getAttributeSet(signed);
207:
208: //
209: // sig must be composed from the DER encoding.
210: //
211: ByteArrayOutputStream bOut = new ByteArrayOutputStream();
212:
213: if (signedAttr != null) {
214: DEROutputStream dOut = new DEROutputStream(bOut);
215: dOut.writeObject(signedAttr);
216: } else {
217: content.write(bOut);
218: }
219:
220: sig.initSign(key);
221:
222: sig.update(bOut.toByteArray());
223:
224: ASN1OctetString encDigest = new DEROctetString(sig.sign());
225:
226: Map parameters = getBaseParameters(contentType, digAlgId,
227: hash);
228: parameters.put(CMSAttributeTableGenerator.SIGNATURE,
229: encDigest.getOctets().clone());
230:
231: AttributeTable unsigned = (unsAttr != null) ? unsAttr
232: .getAttributes(Collections
233: .unmodifiableMap(parameters)) : null;
234:
235: ASN1Set unsignedAttr = getAttributeSet(unsigned);
236:
237: X509Certificate cert = this .getCertificate();
238: ByteArrayInputStream bIn = new ByteArrayInputStream(cert
239: .getTBSCertificate());
240: ASN1InputStream aIn = new ASN1InputStream(bIn);
241: TBSCertificateStructure tbs = TBSCertificateStructure
242: .getInstance(aIn.readObject());
243: IssuerAndSerialNumber encSid = new IssuerAndSerialNumber(
244: tbs.getIssuer(), tbs.getSerialNumber().getValue());
245:
246: return new SignerInfo(new SignerIdentifier(encSid),
247: digAlgId, signedAttr, encAlgId, encDigest,
248: unsignedAttr);
249: }
250: }
251:
252: /**
253: * base constructor
254: */
255: public CMSSignedDataGenerator() {
256: }
257:
258: /**
259: * add a signer - no attributes other than the default ones will be
260: * provided here.
261: */
262: public void addSigner(PrivateKey key, X509Certificate cert,
263: String digestOID) throws IllegalArgumentException {
264: String encOID = getEncOID(key, digestOID);
265:
266: signerInfs
267: .add(new SignerInf(key, cert, digestOID, encOID,
268: new DefaultSignedAttributeTableGenerator(),
269: null, null));
270: }
271:
272: /**
273: * add a signer with extra signed/unsigned attributes.
274: */
275: public void addSigner(PrivateKey key, X509Certificate cert,
276: String digestOID, AttributeTable signedAttr,
277: AttributeTable unsignedAttr)
278: throws IllegalArgumentException {
279: String encOID = getEncOID(key, digestOID);
280:
281: signerInfs.add(new SignerInf(key, cert, digestOID, encOID,
282: new DefaultSignedAttributeTableGenerator(signedAttr),
283: new SimpleAttributeTableGenerator(unsignedAttr),
284: signedAttr));
285: }
286:
287: /**
288: * add a signer with extra signed/unsigned attributes based on generators.
289: */
290: public void addSigner(PrivateKey key, X509Certificate cert,
291: String digestOID, CMSAttributeTableGenerator signedAttrGen,
292: CMSAttributeTableGenerator unsignedAttrGen)
293: throws IllegalArgumentException {
294: String encOID = getEncOID(key, digestOID);
295:
296: signerInfs.add(new SignerInf(key, cert, digestOID, encOID,
297: signedAttrGen, unsignedAttrGen, null));
298: }
299:
300: private DERObject makeObj(byte[] encoding) throws IOException {
301: if (encoding == null) {
302: return null;
303: }
304:
305: ByteArrayInputStream bIn = new ByteArrayInputStream(encoding);
306: ASN1InputStream aIn = new ASN1InputStream(bIn);
307:
308: return aIn.readObject();
309: }
310:
311: private AlgorithmIdentifier makeAlgId(String oid, byte[] params)
312: throws IOException {
313: if (params != null) {
314: return new AlgorithmIdentifier(
315: new DERObjectIdentifier(oid), makeObj(params));
316: } else {
317: return new AlgorithmIdentifier(
318: new DERObjectIdentifier(oid), new DERNull());
319: }
320: }
321:
322: /**
323: * generate a signed object that for a CMS Signed Data
324: * object using the given provider.
325: */
326: public CMSSignedData generate(CMSProcessable content,
327: String sigProvider) throws NoSuchAlgorithmException,
328: NoSuchProviderException, CMSException {
329: return generate(content, false, sigProvider);
330: }
331:
332: /**
333: * generate a signed object that for a CMS Signed Data
334: * object using the given provider - if encapsulate is true a copy
335: * of the message will be included in the signature. The content type
336: * is set according to the OID represented by the string signedContentType.
337: */
338: public CMSSignedData generate(String signedContentType,
339: CMSProcessable content, boolean encapsulate,
340: String sigProvider) throws NoSuchAlgorithmException,
341: NoSuchProviderException, CMSException {
342: return generate(signedContentType, content, encapsulate,
343: sigProvider, true);
344: }
345:
346: /**
347: * Similar method to the other generate methods. The additional argument
348: * addDefaultAttributes indicates whether or not a default set of signed attributes
349: * need to be added automatically. If the argument is set to false, no
350: * attributes will get added at all.
351: */
352: public CMSSignedData generate(String signedContentType,
353: CMSProcessable content, boolean encapsulate,
354: String sigProvider, boolean addDefaultAttributes)
355: throws NoSuchAlgorithmException, NoSuchProviderException,
356: CMSException {
357: ASN1EncodableVector digestAlgs = new ASN1EncodableVector();
358: ASN1EncodableVector signerInfos = new ASN1EncodableVector();
359:
360: DERObjectIdentifier contentTypeOID = new DERObjectIdentifier(
361: signedContentType);
362:
363: _digests.clear(); // clear the current preserved digest state
364:
365: //
366: // add the precalculated SignerInfo objects.
367: //
368: Iterator it = _signers.iterator();
369:
370: while (it.hasNext()) {
371: SignerInformation signer = (SignerInformation) it.next();
372: AlgorithmIdentifier digAlgId;
373:
374: try {
375: digAlgId = makeAlgId(signer.getDigestAlgOID(), signer
376: .getDigestAlgParams());
377: } catch (IOException e) {
378: throw new CMSException("encoding error.", e);
379: }
380:
381: digestAlgs.add(digAlgId);
382:
383: signerInfos.add(signer.toSignerInfo());
384: }
385:
386: //
387: // add the SignerInfo objects
388: //
389: it = signerInfs.iterator();
390:
391: while (it.hasNext()) {
392: SignerInf signer = (SignerInf) it.next();
393: AlgorithmIdentifier digAlgId;
394:
395: try {
396: digAlgId = makeAlgId(signer.getDigestAlgOID(), signer
397: .getDigestAlgParams());
398:
399: digestAlgs.add(digAlgId);
400:
401: signerInfos.add(signer.toSignerInfo(contentTypeOID,
402: content, sigProvider, addDefaultAttributes));
403: } catch (IOException e) {
404: throw new CMSException("encoding error.", e);
405: } catch (InvalidKeyException e) {
406: throw new CMSException(
407: "key inappropriate for signature.", e);
408: } catch (SignatureException e) {
409: throw new CMSException("error creating signature.", e);
410: } catch (CertificateEncodingException e) {
411: throw new CMSException("error creating sid.", e);
412: }
413: }
414:
415: ASN1Set certificates = null;
416:
417: if (_certs.size() != 0) {
418: certificates = CMSUtils.createBerSetFromList(_certs);
419: }
420:
421: ASN1Set certrevlist = null;
422:
423: if (_crls.size() != 0) {
424: certrevlist = CMSUtils.createBerSetFromList(_crls);
425: }
426:
427: ContentInfo encInfo;
428:
429: if (encapsulate) {
430: ByteArrayOutputStream bOut = new ByteArrayOutputStream();
431:
432: try {
433: content.write(bOut);
434: } catch (IOException e) {
435: throw new CMSException("encapsulation error.", e);
436: }
437:
438: ASN1OctetString octs = new BERConstructedOctetString(bOut
439: .toByteArray());
440:
441: encInfo = new ContentInfo(contentTypeOID, octs);
442: } else {
443: encInfo = new ContentInfo(contentTypeOID, null);
444: }
445:
446: SignedData sd = new SignedData(new DERSet(digestAlgs), encInfo,
447: certificates, certrevlist, new DERSet(signerInfos));
448:
449: ContentInfo contentInfo = new ContentInfo(
450: PKCSObjectIdentifiers.signedData, sd);
451:
452: return new CMSSignedData(content, contentInfo);
453: }
454:
455: /**
456: * generate a signed object that for a CMS Signed Data
457: * object using the given provider - if encapsulate is true a copy
458: * of the message will be included in the signature with the
459: * default content type "data".
460: */
461: public CMSSignedData generate(CMSProcessable content,
462: boolean encapsulate, String sigProvider)
463: throws NoSuchAlgorithmException, NoSuchProviderException,
464: CMSException {
465: return this.generate(DATA, content, encapsulate, sigProvider);
466: }
467: }
|