aboutsummaryrefslogtreecommitdiffstats
path: root/libraries/spongycastle/mail/src/main/java/org/spongycastle/mail/smime/SMIMESignedGenerator.java
diff options
context:
space:
mode:
Diffstat (limited to 'libraries/spongycastle/mail/src/main/java/org/spongycastle/mail/smime/SMIMESignedGenerator.java')
-rw-r--r--libraries/spongycastle/mail/src/main/java/org/spongycastle/mail/smime/SMIMESignedGenerator.java606
1 files changed, 606 insertions, 0 deletions
diff --git a/libraries/spongycastle/mail/src/main/java/org/spongycastle/mail/smime/SMIMESignedGenerator.java b/libraries/spongycastle/mail/src/main/java/org/spongycastle/mail/smime/SMIMESignedGenerator.java
new file mode 100644
index 000000000..e2fd8c71d
--- /dev/null
+++ b/libraries/spongycastle/mail/src/main/java/org/spongycastle/mail/smime/SMIMESignedGenerator.java
@@ -0,0 +1,606 @@
+package org.bouncycastle.mail.smime;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+
+import javax.activation.CommandMap;
+import javax.activation.MailcapCommandMap;
+import javax.mail.MessagingException;
+import javax.mail.Multipart;
+import javax.mail.internet.ContentType;
+import javax.mail.internet.MimeBodyPart;
+import javax.mail.internet.MimeMessage;
+import javax.mail.internet.MimeMultipart;
+
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.cryptopro.CryptoProObjectIdentifiers;
+import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
+import org.bouncycastle.asn1.oiw.OIWObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.teletrust.TeleTrusTObjectIdentifiers;
+import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
+import org.bouncycastle.cms.CMSAlgorithm;
+import org.bouncycastle.cms.CMSException;
+import org.bouncycastle.cms.CMSSignedDataStreamGenerator;
+import org.bouncycastle.cms.SignerInfoGenerator;
+import org.bouncycastle.cms.SignerInformation;
+import org.bouncycastle.cms.SignerInformationStore;
+import org.bouncycastle.mail.smime.util.CRLFOutputStream;
+import org.bouncycastle.util.Store;
+
+/**
+ * general class for generating a pkcs7-signature message.
+ * <p>
+ * A simple example of usage.
+ *
+ * <pre>
+ * X509Certificate signCert = ...
+ * KeyPair signKP = ...
+ *
+ * List certList = new ArrayList();
+ *
+ * certList.add(signCert);
+ *
+ * Store certs = new JcaCertStore(certList);
+ *
+ * SMIMESignedGenerator gen = new SMIMESignedGenerator();
+ *
+ * gen.addSignerInfoGenerator(new JcaSimpleSignerInfoGeneratorBuilder().setProvider("BC").build("SHA1withRSA", signKP.getPrivate(), signCert));
+ *
+ * gen.addCertificates(certs);
+ *
+ * MimeMultipart smime = fact.generate(content);
+ * </pre>
+ * <p>
+ * Note 1: if you are using this class with AS2 or some other protocol
+ * that does not use "7bit" as the default content transfer encoding you
+ * will need to use the constructor that allows you to specify the default
+ * content transfer encoding, such as "binary".
+ * </p>
+ * <p>
+ * Note 2: between RFC 3851 and RFC 5751 the values used in the micalg parameter
+ * for signed messages changed. We will accept both, but the default is now to use
+ * RFC 5751. In the event you are dealing with an older style system you will also need
+ * to use a constructor that sets the micalgs table and call it with RFC3851_MICALGS.
+ * </p>
+ */
+public class SMIMESignedGenerator
+ extends SMIMEGenerator
+{
+ public static final String DIGEST_SHA1 = OIWObjectIdentifiers.idSHA1.getId();
+ public static final String DIGEST_MD5 = PKCSObjectIdentifiers.md5.getId();
+ public static final String DIGEST_SHA224 = NISTObjectIdentifiers.id_sha224.getId();
+ public static final String DIGEST_SHA256 = NISTObjectIdentifiers.id_sha256.getId();
+ public static final String DIGEST_SHA384 = NISTObjectIdentifiers.id_sha384.getId();
+ public static final String DIGEST_SHA512 = NISTObjectIdentifiers.id_sha512.getId();
+ public static final String DIGEST_GOST3411 = CryptoProObjectIdentifiers.gostR3411.getId();
+ public static final String DIGEST_RIPEMD128 = TeleTrusTObjectIdentifiers.ripemd128.getId();
+ public static final String DIGEST_RIPEMD160 = TeleTrusTObjectIdentifiers.ripemd160.getId();
+ public static final String DIGEST_RIPEMD256 = TeleTrusTObjectIdentifiers.ripemd256.getId();
+
+ public static final String ENCRYPTION_RSA = PKCSObjectIdentifiers.rsaEncryption.getId();
+ public static final String ENCRYPTION_DSA = X9ObjectIdentifiers.id_dsa_with_sha1.getId();
+ public static final String ENCRYPTION_ECDSA = X9ObjectIdentifiers.ecdsa_with_SHA1.getId();
+ public static final String ENCRYPTION_RSA_PSS = PKCSObjectIdentifiers.id_RSASSA_PSS.getId();
+ public static final String ENCRYPTION_GOST3410 = CryptoProObjectIdentifiers.gostR3410_94.getId();
+ public static final String ENCRYPTION_ECGOST3410 = CryptoProObjectIdentifiers.gostR3410_2001.getId();
+
+ private static final String CERTIFICATE_MANAGEMENT_CONTENT = "application/pkcs7-mime; name=smime.p7c; smime-type=certs-only";
+ private static final String DETACHED_SIGNATURE_TYPE = "application/pkcs7-signature; name=smime.p7s; smime-type=signed-data";
+ private static final String ENCAPSULATED_SIGNED_CONTENT_TYPE = "application/pkcs7-mime; name=smime.p7m; smime-type=signed-data";
+
+ public static final Map RFC3851_MICALGS;
+ public static final Map RFC5751_MICALGS;
+ public static final Map STANDARD_MICALGS;
+
+ private static MailcapCommandMap addCommands(CommandMap cm)
+ {
+ MailcapCommandMap mc = (MailcapCommandMap)cm;
+
+ mc.addMailcap("application/pkcs7-signature;; x-java-content-handler=org.bouncycastle.mail.smime.handlers.pkcs7_signature");
+ mc.addMailcap("application/pkcs7-mime;; x-java-content-handler=org.bouncycastle.mail.smime.handlers.pkcs7_mime");
+ mc.addMailcap("application/x-pkcs7-signature;; x-java-content-handler=org.bouncycastle.mail.smime.handlers.x_pkcs7_signature");
+ mc.addMailcap("application/x-pkcs7-mime;; x-java-content-handler=org.bouncycastle.mail.smime.handlers.x_pkcs7_mime");
+ mc.addMailcap("multipart/signed;; x-java-content-handler=org.bouncycastle.mail.smime.handlers.multipart_signed");
+
+ return mc;
+ }
+
+ static
+ {
+ CommandMap.setDefaultCommandMap(addCommands(CommandMap.getDefaultCommandMap()));
+
+ Map stdMicAlgs = new HashMap();
+
+ stdMicAlgs.put(CMSAlgorithm.MD5, "md5");
+ stdMicAlgs.put(CMSAlgorithm.SHA1, "sha-1");
+ stdMicAlgs.put(CMSAlgorithm.SHA224, "sha-224");
+ stdMicAlgs.put(CMSAlgorithm.SHA256, "sha-256");
+ stdMicAlgs.put(CMSAlgorithm.SHA384, "sha-384");
+ stdMicAlgs.put(CMSAlgorithm.SHA512, "sha-512");
+ stdMicAlgs.put(CMSAlgorithm.GOST3411, "gostr3411-94");
+
+ RFC5751_MICALGS = Collections.unmodifiableMap(stdMicAlgs);
+
+ Map oldMicAlgs = new HashMap();
+
+ oldMicAlgs.put(CMSAlgorithm.MD5, "md5");
+ oldMicAlgs.put(CMSAlgorithm.SHA1, "sha1");
+ oldMicAlgs.put(CMSAlgorithm.SHA224, "sha224");
+ oldMicAlgs.put(CMSAlgorithm.SHA256, "sha256");
+ oldMicAlgs.put(CMSAlgorithm.SHA384, "sha384");
+ oldMicAlgs.put(CMSAlgorithm.SHA512, "sha512");
+ oldMicAlgs.put(CMSAlgorithm.GOST3411, "gostr3411-94");
+
+ RFC3851_MICALGS = Collections.unmodifiableMap(oldMicAlgs);
+
+ STANDARD_MICALGS = RFC5751_MICALGS;
+ }
+
+ private final String defaultContentTransferEncoding;
+ private final Map micAlgs;
+
+ private List _certStores = new ArrayList();
+ private List certStores = new ArrayList();
+ private List crlStores = new ArrayList();
+ private List attrCertStores = new ArrayList();
+ private List signerInfoGens = new ArrayList();
+ private List _signers = new ArrayList();
+ private List _oldSigners = new ArrayList();
+ private List _attributeCerts = new ArrayList();
+ private Map _digests = new HashMap();
+
+ /**
+ * base constructor - default content transfer encoding 7bit
+ */
+ public SMIMESignedGenerator()
+ {
+ this("7bit", STANDARD_MICALGS);
+ }
+
+ /**
+ * base constructor - default content transfer encoding explicitly set
+ *
+ * @param defaultContentTransferEncoding new default to use.
+ */
+ public SMIMESignedGenerator(
+ String defaultContentTransferEncoding)
+ {
+ this(defaultContentTransferEncoding, STANDARD_MICALGS);
+ }
+
+ /**
+ * base constructor - default content transfer encoding explicitly set
+ *
+ * @param micAlgs a map of ANS1ObjectIdentifiers to strings hash algorithm names.
+ */
+ public SMIMESignedGenerator(
+ Map micAlgs)
+ {
+ this("7bit", micAlgs);
+ }
+
+ /**
+ * base constructor - default content transfer encoding explicitly set
+ *
+ * @param defaultContentTransferEncoding new default to use.
+ * @param micAlgs a map of ANS1ObjectIdentifiers to strings hash algorithm names.
+ */
+ public SMIMESignedGenerator(
+ String defaultContentTransferEncoding,
+ Map micAlgs)
+ {
+ this.defaultContentTransferEncoding = defaultContentTransferEncoding;
+ this.micAlgs = micAlgs;
+ }
+
+ /**
+ * Add a store of precalculated signers to the generator.
+ *
+ * @param signerStore store of signers
+ */
+ public void addSigners(
+ SignerInformationStore signerStore)
+ {
+ Iterator it = signerStore.getSigners().iterator();
+
+ while (it.hasNext())
+ {
+ _oldSigners.add(it.next());
+ }
+ }
+
+ /**
+ *
+ * @param sigInfoGen
+ */
+ public void addSignerInfoGenerator(SignerInfoGenerator sigInfoGen)
+ {
+ signerInfoGens.add(sigInfoGen);
+ }
+
+ public void addCertificates(
+ Store certStore)
+ {
+ certStores.add(certStore);
+ }
+
+ public void addCRLs(
+ Store crlStore)
+ {
+ crlStores.add(crlStore);
+ }
+
+ public void addAttributeCertificates(
+ Store certStore)
+ {
+ attrCertStores.add(certStore);
+ }
+
+ private void addHashHeader(
+ StringBuffer header,
+ List signers)
+ {
+ int count = 0;
+
+ //
+ // build the hash header
+ //
+ Iterator it = signers.iterator();
+ Set micAlgSet = new TreeSet();
+
+ while (it.hasNext())
+ {
+ Object signer = it.next();
+ ASN1ObjectIdentifier digestOID;
+
+ if (signer instanceof SignerInformation)
+ {
+ digestOID = ((SignerInformation)signer).getDigestAlgorithmID().getAlgorithm();
+ }
+ else
+ {
+ digestOID = ((SignerInfoGenerator)signer).getDigestAlgorithm().getAlgorithm();
+ }
+
+ String micAlg = (String)micAlgs.get(digestOID);
+
+ if (micAlg == null)
+ {
+ micAlgSet.add("unknown");
+ }
+ else
+ {
+ micAlgSet.add(micAlg);
+ }
+ }
+
+ it = micAlgSet.iterator();
+
+ while (it.hasNext())
+ {
+ String alg = (String)it.next();
+
+ if (count == 0)
+ {
+ if (micAlgSet.size() != 1)
+ {
+ header.append("; micalg=\"");
+ }
+ else
+ {
+ header.append("; micalg=");
+ }
+ }
+ else
+ {
+ header.append(',');
+ }
+
+ header.append(alg);
+
+ count++;
+ }
+
+ if (count != 0)
+ {
+ if (micAlgSet.size() != 1)
+ {
+ header.append('\"');
+ }
+ }
+ }
+
+ private MimeMultipart make(
+ MimeBodyPart content)
+ throws SMIMEException
+ {
+ try
+ {
+ MimeBodyPart sig = new MimeBodyPart();
+
+ sig.setContent(new ContentSigner(content, false), DETACHED_SIGNATURE_TYPE);
+ sig.addHeader("Content-Type", DETACHED_SIGNATURE_TYPE);
+ sig.addHeader("Content-Disposition", "attachment; filename=\"smime.p7s\"");
+ sig.addHeader("Content-Description", "S/MIME Cryptographic Signature");
+ sig.addHeader("Content-Transfer-Encoding", encoding);
+
+ //
+ // build the multipart header
+ //
+ StringBuffer header = new StringBuffer(
+ "signed; protocol=\"application/pkcs7-signature\"");
+
+ List allSigners = new ArrayList(_signers);
+
+ allSigners.addAll(_oldSigners);
+
+ allSigners.addAll(signerInfoGens);
+
+ addHashHeader(header, allSigners);
+
+ MimeMultipart mm = new MimeMultipart(header.toString());
+
+ mm.addBodyPart(content);
+ mm.addBodyPart(sig);
+
+ return mm;
+ }
+ catch (MessagingException e)
+ {
+ throw new SMIMEException("exception putting multi-part together.", e);
+ }
+ }
+
+ /*
+ * at this point we expect our body part to be well defined - generate with data in the signature
+ */
+ private MimeBodyPart makeEncapsulated(
+ MimeBodyPart content)
+ throws SMIMEException
+ {
+ try
+ {
+ MimeBodyPart sig = new MimeBodyPart();
+
+ sig.setContent(new ContentSigner(content, true), ENCAPSULATED_SIGNED_CONTENT_TYPE);
+ sig.addHeader("Content-Type", ENCAPSULATED_SIGNED_CONTENT_TYPE);
+ sig.addHeader("Content-Disposition", "attachment; filename=\"smime.p7m\"");
+ sig.addHeader("Content-Description", "S/MIME Cryptographic Signed Data");
+ sig.addHeader("Content-Transfer-Encoding", encoding);
+
+ return sig;
+ }
+ catch (MessagingException e)
+ {
+ throw new SMIMEException("exception putting body part together.", e);
+ }
+ }
+
+ /**
+ * Return a map of oids and byte arrays representing the digests calculated on the content during
+ * the last generate.
+ *
+ * @return a map of oids (as String objects) and byte[] representing digests.
+ */
+ public Map getGeneratedDigests()
+ {
+ return new HashMap(_digests);
+ }
+
+ public MimeMultipart generate(
+ MimeBodyPart content)
+ throws SMIMEException
+ {
+ return make(makeContentBodyPart(content));
+ }
+
+ public MimeMultipart generate(
+ MimeMessage message)
+ throws SMIMEException
+ {
+ try
+ {
+ message.saveChanges(); // make sure we're up to date.
+ }
+ catch (MessagingException e)
+ {
+ throw new SMIMEException("unable to save message", e);
+ }
+
+ return make(makeContentBodyPart(message));
+ }
+
+ /**
+ * generate a signed message with encapsulated content
+ * <p>
+ * Note: doing this is strongly <b>not</b> recommended as it means a
+ * recipient of the message will have to be able to read the signature to read the
+ * message.
+ */
+ public MimeBodyPart generateEncapsulated(
+ MimeBodyPart content)
+ throws SMIMEException
+ {
+ return makeEncapsulated(makeContentBodyPart(content));
+ }
+
+ public MimeBodyPart generateEncapsulated(
+ MimeMessage message)
+ throws SMIMEException
+ {
+ try
+ {
+ message.saveChanges(); // make sure we're up to date.
+ }
+ catch (MessagingException e)
+ {
+ throw new SMIMEException("unable to save message", e);
+ }
+
+ return makeEncapsulated(makeContentBodyPart(message));
+ }
+
+ /**
+ * Creates a certificate management message which is like a signed message with no content
+ * or signers but that still carries certificates and CRLs.
+ *
+ * @return a MimeBodyPart containing the certs and CRLs.
+ */
+ public MimeBodyPart generateCertificateManagement()
+ throws SMIMEException
+ {
+ try
+ {
+ MimeBodyPart sig = new MimeBodyPart();
+
+ sig.setContent(new ContentSigner(null, true), CERTIFICATE_MANAGEMENT_CONTENT);
+ sig.addHeader("Content-Type", CERTIFICATE_MANAGEMENT_CONTENT);
+ sig.addHeader("Content-Disposition", "attachment; filename=\"smime.p7c\"");
+ sig.addHeader("Content-Description", "S/MIME Certificate Management Message");
+ sig.addHeader("Content-Transfer-Encoding", encoding);
+
+ return sig;
+ }
+ catch (MessagingException e)
+ {
+ throw new SMIMEException("exception putting body part together.", e);
+ }
+ }
+
+ private class ContentSigner
+ implements SMIMEStreamingProcessor
+ {
+ private final MimeBodyPart content;
+ private final boolean encapsulate;
+ private final boolean noProvider;
+
+ ContentSigner(
+ MimeBodyPart content,
+ boolean encapsulate)
+ {
+ this.content = content;
+ this.encapsulate = encapsulate;
+ this.noProvider = true;
+ }
+
+ protected CMSSignedDataStreamGenerator getGenerator()
+ throws CMSException
+ {
+ CMSSignedDataStreamGenerator gen = new CMSSignedDataStreamGenerator();
+
+ for (Iterator it = certStores.iterator(); it.hasNext();)
+ {
+ gen.addCertificates((Store)it.next());
+ }
+
+ for (Iterator it = crlStores.iterator(); it.hasNext();)
+ {
+ gen.addCRLs((Store)it.next());
+ }
+
+ for (Iterator it = attrCertStores.iterator(); it.hasNext();)
+ {
+ gen.addAttributeCertificates((Store)it.next());
+ }
+
+ for (Iterator it = signerInfoGens.iterator(); it.hasNext();)
+ {
+ gen.addSignerInfoGenerator((SignerInfoGenerator)it.next());
+ }
+
+ gen.addSigners(new SignerInformationStore(_oldSigners));
+
+ return gen;
+ }
+
+ private void writeBodyPart(
+ OutputStream out,
+ MimeBodyPart bodyPart)
+ throws IOException, MessagingException
+ {
+ if (bodyPart.getContent() instanceof Multipart)
+ {
+ Multipart mp = (Multipart)bodyPart.getContent();
+ ContentType contentType = new ContentType(mp.getContentType());
+ String boundary = "--" + contentType.getParameter("boundary");
+
+ SMIMEUtil.LineOutputStream lOut = new SMIMEUtil.LineOutputStream(out);
+
+ Enumeration headers = bodyPart.getAllHeaderLines();
+ while (headers.hasMoreElements())
+ {
+ lOut.writeln((String)headers.nextElement());
+ }
+
+ lOut.writeln(); // CRLF separator
+
+ SMIMEUtil.outputPreamble(lOut, bodyPart, boundary);
+
+ for (int i = 0; i < mp.getCount(); i++)
+ {
+ lOut.writeln(boundary);
+ writeBodyPart(out, (MimeBodyPart)mp.getBodyPart(i));
+ lOut.writeln(); // CRLF terminator
+ }
+
+ lOut.writeln(boundary + "--");
+ }
+ else
+ {
+ if (SMIMEUtil.isCanonicalisationRequired(bodyPart, defaultContentTransferEncoding))
+ {
+ out = new CRLFOutputStream(out);
+ }
+
+ bodyPart.writeTo(out);
+ }
+ }
+
+ public void write(OutputStream out)
+ throws IOException
+ {
+ try
+ {
+ CMSSignedDataStreamGenerator gen = getGenerator();
+
+ OutputStream signingStream = gen.open(out, encapsulate);
+
+ if (content != null)
+ {
+ if (!encapsulate)
+ {
+ writeBodyPart(signingStream, content);
+ }
+ else
+ {
+ content.getDataHandler().setCommandMap(addCommands(CommandMap.getDefaultCommandMap()));
+
+ content.writeTo(signingStream);
+ }
+ }
+
+ signingStream.close();
+
+ _digests = gen.getGeneratedDigests();
+ }
+ catch (MessagingException e)
+ {
+ throw new IOException(e.toString());
+ }
+ catch (CMSException e)
+ {
+ throw new IOException(e.toString());
+ }
+ }
+ }
+}