001: package org.bouncycastle.mail.smime;
002:
003: import org.bouncycastle.asn1.cms.AttributeTable;
004: import org.bouncycastle.asn1.cryptopro.CryptoProObjectIdentifiers;
005: import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
006: import org.bouncycastle.asn1.oiw.OIWObjectIdentifiers;
007: import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
008: import org.bouncycastle.asn1.teletrust.TeleTrusTObjectIdentifiers;
009: import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
010: import org.bouncycastle.cms.CMSException;
011: import org.bouncycastle.cms.CMSSignedDataStreamGenerator;
012: import org.bouncycastle.mail.smime.util.CRLFOutputStream;
013: import org.bouncycastle.x509.X509Store;
014:
015: import javax.activation.CommandMap;
016: import javax.activation.MailcapCommandMap;
017: import javax.mail.MessagingException;
018: import javax.mail.Multipart;
019: import javax.mail.internet.ContentType;
020: import javax.mail.internet.MimeBodyPart;
021: import javax.mail.internet.MimeMessage;
022: import javax.mail.internet.MimeMultipart;
023: import java.io.IOException;
024: import java.io.OutputStream;
025: import java.security.InvalidKeyException;
026: import java.security.NoSuchAlgorithmException;
027: import java.security.NoSuchProviderException;
028: import java.security.PrivateKey;
029: import java.security.cert.CertStore;
030: import java.security.cert.CertStoreException;
031: import java.security.cert.X509Certificate;
032: import java.util.ArrayList;
033: import java.util.Enumeration;
034: import java.util.HashMap;
035: import java.util.HashSet;
036: import java.util.Iterator;
037: import java.util.List;
038: import java.util.Map;
039: import java.util.Set;
040:
041: /**
042: * general class for generating a pkcs7-signature message.
043: * <p>
044: * A simple example of usage.
045: *
046: * <pre>
047: * CertStore certs...
048: * SMIMESignedGenerator fact = new SMIMESignedGenerator();
049: *
050: * fact.addSigner(privKey, cert, SMIMESignedGenerator.DIGEST_SHA1);
051: * fact.addCertificatesAndCRLs(certs);
052: *
053: * MimeMultipart smime = fact.generate(content, "BC");
054: * </pre>
055: * <p>
056: * Note: if you are using this class with AS2 or some other protocol
057: * that does not use "7bit" as the default content transfer encoding you
058: * will need to use the constructor that allows you to specify the default
059: * content transfer encoding, such as "binary".
060: * </p>
061: */
062: public class SMIMESignedGenerator extends SMIMEGenerator {
063: public static final String DIGEST_SHA1 = OIWObjectIdentifiers.idSHA1
064: .getId();
065: public static final String DIGEST_MD5 = PKCSObjectIdentifiers.md5
066: .getId();
067: public static final String DIGEST_SHA224 = NISTObjectIdentifiers.id_sha224
068: .getId();
069: public static final String DIGEST_SHA256 = NISTObjectIdentifiers.id_sha256
070: .getId();
071: public static final String DIGEST_SHA384 = NISTObjectIdentifiers.id_sha384
072: .getId();
073: public static final String DIGEST_SHA512 = NISTObjectIdentifiers.id_sha512
074: .getId();
075: public static final String DIGEST_GOST3411 = CryptoProObjectIdentifiers.gostR3411
076: .getId();
077: public static final String DIGEST_RIPEMD128 = TeleTrusTObjectIdentifiers.ripemd128
078: .getId();
079: public static final String DIGEST_RIPEMD160 = TeleTrusTObjectIdentifiers.ripemd160
080: .getId();
081: public static final String DIGEST_RIPEMD256 = TeleTrusTObjectIdentifiers.ripemd256
082: .getId();
083:
084: public static final String ENCRYPTION_RSA = PKCSObjectIdentifiers.rsaEncryption
085: .getId();
086: public static final String ENCRYPTION_DSA = X9ObjectIdentifiers.id_dsa_with_sha1
087: .getId();
088: public static final String ENCRYPTION_ECDSA = X9ObjectIdentifiers.ecdsa_with_SHA1
089: .getId();
090: public static final String ENCRYPTION_RSA_PSS = PKCSObjectIdentifiers.id_RSASSA_PSS
091: .getId();
092: public static final String ENCRYPTION_GOST3410 = CryptoProObjectIdentifiers.gostR3410_94
093: .getId();
094: public static final String ENCRYPTION_ECGOST3410 = CryptoProObjectIdentifiers.gostR3410_2001
095: .getId();
096:
097: private static final String CERTIFICATE_MANAGEMENT_CONTENT = "application/pkcs7-mime; name=smime.p7c; smime-type=certs-only";
098: private static final String DETACHED_SIGNATURE_TYPE = "application/pkcs7-signature; name=smime.p7s; smime-type=signed-data";
099: private static final String ENCAPSULATED_SIGNED_CONTENT_TYPE = "application/pkcs7-mime; name=smime.p7m; smime-type=signed-data";
100:
101: private final String _defaultContentTransferEncoding;
102:
103: private List _certStores = new ArrayList();
104: private List _signers = new ArrayList();
105: private List _attributeCerts = new ArrayList();
106: private Map _digests = new HashMap();
107:
108: static {
109: MailcapCommandMap mc = (MailcapCommandMap) CommandMap
110: .getDefaultCommandMap();
111:
112: mc
113: .addMailcap("application/pkcs7-signature;; x-java-content-handler=org.bouncycastle.mail.smime.handlers.pkcs7_signature");
114: mc
115: .addMailcap("application/pkcs7-mime;; x-java-content-handler=org.bouncycastle.mail.smime.handlers.pkcs7_mime");
116: mc
117: .addMailcap("application/x-pkcs7-signature;; x-java-content-handler=org.bouncycastle.mail.smime.handlers.x_pkcs7_signature");
118: mc
119: .addMailcap("application/x-pkcs7-mime;; x-java-content-handler=org.bouncycastle.mail.smime.handlers.x_pkcs7_mime");
120: mc
121: .addMailcap("multipart/signed;; x-java-content-handler=org.bouncycastle.mail.smime.handlers.multipart_signed");
122:
123: CommandMap.setDefaultCommandMap(mc);
124: }
125:
126: /**
127: * base constructor - default content transfer encoding 7bit
128: */
129: public SMIMESignedGenerator() {
130: _defaultContentTransferEncoding = "7bit";
131: }
132:
133: /**
134: * base constructor - default content transfer encoding explicitly set
135: *
136: * @param defaultContentTransferEncoding new default to use.
137: */
138: public SMIMESignedGenerator(String defaultContentTransferEncoding) {
139: _defaultContentTransferEncoding = defaultContentTransferEncoding;
140: }
141:
142: /**
143: * add a signer - no attributes other than the default ones will be
144: * provided here.
145: *
146: * @param key key to use to generate the signature
147: * @param cert the public key certificate associated with the signer's key.
148: * @param digestOID object ID of the digest algorithm to use.
149: * @exception IllegalArgumentException any of the arguments are inappropriate
150: */
151: public void addSigner(PrivateKey key, X509Certificate cert,
152: String digestOID) throws IllegalArgumentException {
153: _signers.add(new Signer(key, cert, digestOID, null, null));
154: }
155:
156: /**
157: * Add a signer with extra signed/unsigned attributes or overrides
158: * for the standard attributes. For example this method can be used to
159: * explictly set default attributes such as the signing time.
160: *
161: * @param key key to use to generate the signature
162: * @param cert the public key certificate associated with the signer's key.
163: * @param digestOID object ID of the digest algorithm to use.
164: * @param signedAttr signed attributes to be included in the signature.
165: * @param unsignedAttr unsigned attribitues to be included.
166: * @exception IllegalArgumentException any of the arguments are inappropriate
167: */
168: public void addSigner(PrivateKey key, X509Certificate cert,
169: String digestOID, AttributeTable signedAttr,
170: AttributeTable unsignedAttr)
171: throws IllegalArgumentException {
172: _signers.add(new Signer(key, cert, digestOID, signedAttr,
173: unsignedAttr));
174: }
175:
176: /**
177: * add the certificates and CRLs contained in the given CertStore
178: * to the pool that will be included in the encoded signature block.
179: * <p>
180: * Note: this assumes the CertStore will support null in the get
181: * methods.
182: * </p>
183: * @param certStore CertStore containing the certificates and CRLs to be added.
184: */
185: public void addCertificatesAndCRLs(CertStore certStore)
186: throws CertStoreException, SMIMEException {
187: _certStores.add(certStore);
188: }
189:
190: /**
191: * Add the attribute certificates contained in the passed in store to the
192: * generator.
193: *
194: * @param store a store of Version 2 attribute certificates
195: * @throws CMSException if an error occurse processing the store.
196: */
197: public void addAttributeCertificates(X509Store store)
198: throws CMSException {
199: _attributeCerts.add(store);
200: }
201:
202: private void addHashHeader(StringBuffer header, List signers) {
203: int count = 0;
204:
205: //
206: // build the hash header
207: //
208: Iterator it = signers.iterator();
209: Set micAlgs = new HashSet();
210:
211: while (it.hasNext()) {
212: Signer signer = (Signer) it.next();
213:
214: if (signer.getDigestOID().equals(DIGEST_SHA1)) {
215: micAlgs.add("sha1");
216: } else if (signer.getDigestOID().equals(DIGEST_MD5)) {
217: micAlgs.add("md5");
218: } else if (signer.getDigestOID().equals(DIGEST_SHA224)) {
219: micAlgs.add("sha224");
220: } else if (signer.getDigestOID().equals(DIGEST_SHA256)) {
221: micAlgs.add("sha256");
222: } else if (signer.getDigestOID().equals(DIGEST_SHA384)) {
223: micAlgs.add("sha384");
224: } else if (signer.getDigestOID().equals(DIGEST_SHA512)) {
225: micAlgs.add("sha512");
226: } else if (signer.getDigestOID().equals(DIGEST_GOST3411)) {
227: micAlgs.add("gostr3411-94");
228: } else {
229: micAlgs.add("unknown");
230: }
231: }
232:
233: it = micAlgs.iterator();
234:
235: while (it.hasNext()) {
236: String alg = (String) it.next();
237:
238: if (count == 0) {
239: if (micAlgs.size() != 1) {
240: header.append("; micalg=\"");
241: } else {
242: header.append("; micalg=");
243: }
244: } else {
245: header.append(',');
246: }
247:
248: header.append(alg);
249:
250: count++;
251: }
252:
253: if (count != 0) {
254: if (micAlgs.size() != 1) {
255: header.append('\"');
256: }
257: }
258: }
259:
260: /*
261: * at this point we expect our body part to be well defined.
262: */
263: private MimeMultipart make(MimeBodyPart content, String sigProvider)
264: throws NoSuchAlgorithmException, NoSuchProviderException,
265: SMIMEException {
266: try {
267: MimeBodyPart sig = new MimeBodyPart();
268:
269: sig.setContent(new ContentSigner(content, false,
270: sigProvider), DETACHED_SIGNATURE_TYPE);
271: sig.addHeader("Content-Type", DETACHED_SIGNATURE_TYPE);
272: sig.addHeader("Content-Disposition",
273: "attachment; filename=\"smime.p7s\"");
274: sig.addHeader("Content-Description",
275: "S/MIME Cryptographic Signature");
276: sig.addHeader("Content-Transfer-Encoding", encoding);
277:
278: //
279: // build the multipart header
280: //
281: StringBuffer header = new StringBuffer(
282: "signed; protocol=\"application/pkcs7-signature\"");
283:
284: addHashHeader(header, _signers);
285:
286: MimeMultipart mm = new MimeMultipart(header.toString());
287:
288: mm.addBodyPart(content);
289: mm.addBodyPart(sig);
290:
291: return mm;
292: } catch (MessagingException e) {
293: throw new SMIMEException(
294: "exception putting multi-part together.", e);
295: }
296: }
297:
298: /*
299: * at this point we expect our body part to be well defined - generate with data in the signature
300: */
301: private MimeBodyPart makeEncapsulated(MimeBodyPart content,
302: String sigProvider) throws NoSuchAlgorithmException,
303: NoSuchProviderException, SMIMEException {
304: try {
305: MimeBodyPart sig = new MimeBodyPart();
306:
307: sig.setContent(
308: new ContentSigner(content, true, sigProvider),
309: ENCAPSULATED_SIGNED_CONTENT_TYPE);
310: sig.addHeader("Content-Type",
311: ENCAPSULATED_SIGNED_CONTENT_TYPE);
312: sig.addHeader("Content-Disposition",
313: "attachment; filename=\"smime.p7m\"");
314: sig.addHeader("Content-Description",
315: "S/MIME Cryptographic Signed Data");
316: sig.addHeader("Content-Transfer-Encoding", encoding);
317:
318: return sig;
319: } catch (MessagingException e) {
320: throw new SMIMEException(
321: "exception putting body part together.", e);
322: }
323: }
324:
325: /**
326: * Return a map of oids and byte arrays representing the digests calculated on the content during
327: * the last generate.
328: *
329: * @return a map of oids (as String objects) and byte[] representing digests.
330: */
331: public Map getGeneratedDigests() {
332: return new HashMap(_digests);
333: }
334:
335: /**
336: * generate a signed object that contains an SMIME Signed Multipart
337: * object using the given provider.
338: * @param content the MimeBodyPart to be signed.
339: * @param sigProvider the provider to be used for the signature.
340: * @return a Multipart containing the content and signature.
341: * @throws NoSuchAlgorithmException if the required algorithms for the signature cannot be found.
342: * @throws NoSuchProviderException if no provider can be found.
343: * @throws SMIMEException if an exception occurs in processing the signature.
344: */
345: public MimeMultipart generate(MimeBodyPart content,
346: String sigProvider) throws NoSuchAlgorithmException,
347: NoSuchProviderException, SMIMEException {
348: return make(makeContentBodyPart(content), sigProvider);
349: }
350:
351: /**
352: * generate a signed object that contains an SMIME Signed Multipart
353: * object using the given provider from the given MimeMessage
354: *
355: * @throws NoSuchAlgorithmException if the required algorithms for the signature cannot be found.
356: * @throws NoSuchProviderException if no provider can be found.
357: * @throws SMIMEException if an exception occurs in processing the signature.
358: */
359: public MimeMultipart generate(MimeMessage message,
360: String sigProvider) throws NoSuchAlgorithmException,
361: NoSuchProviderException, SMIMEException {
362: try {
363: message.saveChanges(); // make sure we're up to date.
364: } catch (MessagingException e) {
365: throw new SMIMEException("unable to save message", e);
366: }
367:
368: return make(makeContentBodyPart(message), sigProvider);
369: }
370:
371: /**
372: * generate a signed message with encapsulated content
373: * <p>
374: * Note: doing this is strongly <b>not</b> recommended as it means a
375: * recipient of the message will have to be able to read the signature to read the
376: * message.
377: */
378: public MimeBodyPart generateEncapsulated(MimeBodyPart content,
379: String sigProvider) throws NoSuchAlgorithmException,
380: NoSuchProviderException, SMIMEException {
381: return makeEncapsulated(makeContentBodyPart(content),
382: sigProvider);
383: }
384:
385: /**
386: * generate a signed object that contains an SMIME Signed Multipart
387: * object using the given provider from the given MimeMessage.
388: * <p>
389: * Note: doing this is strongly <b>not</b> recommended as it means a
390: * recipient of the message will have to be able to read the signature to read the
391: * message.
392: */
393: public MimeBodyPart generateEncapsulated(MimeMessage message,
394: String sigProvider) throws NoSuchAlgorithmException,
395: NoSuchProviderException, SMIMEException {
396: try {
397: message.saveChanges(); // make sure we're up to date.
398: } catch (MessagingException e) {
399: throw new SMIMEException("unable to save message", e);
400: }
401:
402: return makeEncapsulated(makeContentBodyPart(message),
403: sigProvider);
404: }
405:
406: /**
407: * Creates a certificate management message which is like a signed message with no content
408: * or signers but that still carries certificates and CRLs.
409: *
410: * @return a MimeBodyPart containing the certs and CRLs.
411: */
412: public MimeBodyPart generateCertificateManagement(String provider)
413: throws SMIMEException, NoSuchProviderException {
414: try {
415: MimeBodyPart sig = new MimeBodyPart();
416:
417: sig.setContent(new ContentSigner(null, true, provider),
418: CERTIFICATE_MANAGEMENT_CONTENT);
419: sig.addHeader("Content-Type",
420: CERTIFICATE_MANAGEMENT_CONTENT);
421: sig.addHeader("Content-Disposition",
422: "attachment; filename=\"smime.p7c\"");
423: sig.addHeader("Content-Description",
424: "S/MIME Certificate Management Message");
425: sig.addHeader("Content-Transfer-Encoding", encoding);
426:
427: return sig;
428: } catch (MessagingException e) {
429: throw new SMIMEException(
430: "exception putting body part together.", e);
431: }
432: }
433:
434: private class Signer {
435: final PrivateKey key;
436: final X509Certificate cert;
437: final String digestOID;
438: final AttributeTable signedAttr;
439: final AttributeTable unsignedAttr;
440:
441: Signer(PrivateKey key, X509Certificate cert, String digestOID,
442: AttributeTable signedAttr, AttributeTable unsignedAttr) {
443: this .key = key;
444: this .cert = cert;
445: this .digestOID = digestOID;
446: this .signedAttr = signedAttr;
447: this .unsignedAttr = unsignedAttr;
448: }
449:
450: public X509Certificate getCert() {
451: return cert;
452: }
453:
454: public String getDigestOID() {
455: return digestOID;
456: }
457:
458: public PrivateKey getKey() {
459: return key;
460: }
461:
462: public AttributeTable getSignedAttr() {
463: return signedAttr;
464: }
465:
466: public AttributeTable getUnsignedAttr() {
467: return unsignedAttr;
468: }
469: }
470:
471: private class ContentSigner implements SMIMEStreamingProcessor {
472: private final MimeBodyPart _content;
473: private final boolean _encapsulate;
474: private final String _provider;
475:
476: ContentSigner(MimeBodyPart content, boolean encapsulate,
477: String provider) {
478: _content = content;
479: _encapsulate = encapsulate;
480: _provider = provider;
481: }
482:
483: protected CMSSignedDataStreamGenerator getGenerator()
484: throws CMSException, CertStoreException,
485: InvalidKeyException, NoSuchAlgorithmException,
486: NoSuchProviderException {
487: CMSSignedDataStreamGenerator gen = new CMSSignedDataStreamGenerator();
488:
489: for (Iterator it = _certStores.iterator(); it.hasNext();) {
490: gen.addCertificatesAndCRLs((CertStore) it.next());
491: }
492:
493: for (Iterator it = _attributeCerts.iterator(); it.hasNext();) {
494: gen.addAttributeCertificates((X509Store) it.next());
495: }
496:
497: for (Iterator it = _signers.iterator(); it.hasNext();) {
498: Signer signer = (Signer) it.next();
499:
500: gen.addSigner(signer.getKey(), signer.getCert(), signer
501: .getDigestOID(), signer.getSignedAttr(), signer
502: .getUnsignedAttr(), _provider);
503: }
504:
505: return gen;
506: }
507:
508: private void writeBodyPart(OutputStream out,
509: MimeBodyPart bodyPart) throws IOException,
510: MessagingException {
511: if (bodyPart.getContent() instanceof Multipart) {
512: Multipart mp = (Multipart) bodyPart.getContent();
513: ContentType contentType = new ContentType(mp
514: .getContentType());
515: String boundary = "--"
516: + contentType.getParameter("boundary");
517:
518: SMIMEUtil.LineOutputStream lOut = new SMIMEUtil.LineOutputStream(
519: out);
520:
521: Enumeration headers = bodyPart.getAllHeaderLines();
522: while (headers.hasMoreElements()) {
523: lOut.writeln((String) headers.nextElement());
524: }
525:
526: lOut.writeln(); // CRLF separator
527:
528: SMIMEUtil.outputPreamble(lOut, bodyPart, boundary);
529:
530: for (int i = 0; i < mp.getCount(); i++) {
531: lOut.writeln(boundary);
532: writeBodyPart(out, (MimeBodyPart) mp.getBodyPart(i));
533: lOut.writeln(); // CRLF terminator
534: }
535:
536: lOut.writeln(boundary + "--");
537: } else {
538: if (SMIMEUtil.isCanonicalisationRequired(bodyPart,
539: _defaultContentTransferEncoding)) {
540: out = new CRLFOutputStream(out);
541: }
542:
543: bodyPart.writeTo(out);
544: }
545: }
546:
547: public void write(OutputStream out) throws IOException {
548: try {
549: CMSSignedDataStreamGenerator gen = getGenerator();
550:
551: OutputStream signingStream = gen
552: .open(out, _encapsulate);
553:
554: if (_content != null) {
555: if (!_encapsulate) {
556: writeBodyPart(signingStream, _content);
557: } else {
558: _content.writeTo(signingStream);
559: }
560: }
561:
562: signingStream.close();
563:
564: _digests = gen.getGeneratedDigests();
565: } catch (MessagingException e) {
566: throw new IOException(e.toString());
567: } catch (NoSuchAlgorithmException e) {
568: throw new IOException(e.toString());
569: } catch (NoSuchProviderException e) {
570: throw new IOException(e.toString());
571: } catch (CMSException e) {
572: throw new IOException(e.toString());
573: } catch (InvalidKeyException e) {
574: throw new IOException(e.toString());
575: } catch (CertStoreException e) {
576: throw new IOException(e.toString());
577: }
578: }
579: }
580: }
|