Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/quite/humla-spongycastle.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'mail/src/main/java/org/spongycastle/mail/smime')
-rw-r--r--mail/src/main/java/org/spongycastle/mail/smime/CMSProcessableBodyPart.java44
-rw-r--r--mail/src/main/java/org/spongycastle/mail/smime/CMSProcessableBodyPartInbound.java66
-rw-r--r--mail/src/main/java/org/spongycastle/mail/smime/CMSProcessableBodyPartOutbound.java73
-rw-r--r--mail/src/main/java/org/spongycastle/mail/smime/SMIMECompressed.java59
-rw-r--r--mail/src/main/java/org/spongycastle/mail/smime/SMIMECompressedGenerator.java152
-rw-r--r--mail/src/main/java/org/spongycastle/mail/smime/SMIMECompressedParser.java100
-rw-r--r--mail/src/main/java/org/spongycastle/mail/smime/SMIMEEnveloped.java59
-rw-r--r--mail/src/main/java/org/spongycastle/mail/smime/SMIMEEnvelopedGenerator.java282
-rw-r--r--mail/src/main/java/org/spongycastle/mail/smime/SMIMEEnvelopedParser.java100
-rw-r--r--mail/src/main/java/org/spongycastle/mail/smime/SMIMEException.java32
-rw-r--r--mail/src/main/java/org/spongycastle/mail/smime/SMIMEGenerator.java223
-rw-r--r--mail/src/main/java/org/spongycastle/mail/smime/SMIMESigned.java241
-rw-r--r--mail/src/main/java/org/spongycastle/mail/smime/SMIMESignedGenerator.java616
-rw-r--r--mail/src/main/java/org/spongycastle/mail/smime/SMIMESignedParser.java368
-rw-r--r--mail/src/main/java/org/spongycastle/mail/smime/SMIMEStreamingProcessor.java10
-rw-r--r--mail/src/main/java/org/spongycastle/mail/smime/SMIMEUtil.java623
-rw-r--r--mail/src/main/java/org/spongycastle/mail/smime/examples/CreateCompressedMail.java57
-rw-r--r--mail/src/main/java/org/spongycastle/mail/smime/examples/CreateEncryptedMail.java128
-rw-r--r--mail/src/main/java/org/spongycastle/mail/smime/examples/CreateLargeCompressedMail.java63
-rw-r--r--mail/src/main/java/org/spongycastle/mail/smime/examples/CreateLargeEncryptedMail.java105
-rw-r--r--mail/src/main/java/org/spongycastle/mail/smime/examples/CreateLargeSignedMail.java198
-rw-r--r--mail/src/main/java/org/spongycastle/mail/smime/examples/CreateSignedMail.java221
-rw-r--r--mail/src/main/java/org/spongycastle/mail/smime/examples/CreateSignedMultipartMail.java213
-rw-r--r--mail/src/main/java/org/spongycastle/mail/smime/examples/ExampleUtils.java77
-rw-r--r--mail/src/main/java/org/spongycastle/mail/smime/examples/ReadCompressedMail.java41
-rw-r--r--mail/src/main/java/org/spongycastle/mail/smime/examples/ReadEncryptedMail.java94
-rw-r--r--mail/src/main/java/org/spongycastle/mail/smime/examples/ReadLargeCompressedMail.java38
-rw-r--r--mail/src/main/java/org/spongycastle/mail/smime/examples/ReadLargeEncryptedMail.java71
-rw-r--r--mail/src/main/java/org/spongycastle/mail/smime/examples/ReadLargeSignedMail.java125
-rw-r--r--mail/src/main/java/org/spongycastle/mail/smime/examples/ReadSignedMail.java176
-rw-r--r--mail/src/main/java/org/spongycastle/mail/smime/examples/SendSignedAndEncryptedMail.java192
-rw-r--r--mail/src/main/java/org/spongycastle/mail/smime/examples/ValidateSignedMail.java352
-rw-r--r--mail/src/main/java/org/spongycastle/mail/smime/handlers/PKCS7ContentHandler.java110
-rw-r--r--mail/src/main/java/org/spongycastle/mail/smime/handlers/multipart_signed.java280
-rw-r--r--mail/src/main/java/org/spongycastle/mail/smime/handlers/pkcs7_mime.java18
-rw-r--r--mail/src/main/java/org/spongycastle/mail/smime/handlers/pkcs7_signature.java18
-rw-r--r--mail/src/main/java/org/spongycastle/mail/smime/handlers/x_pkcs7_mime.java18
-rw-r--r--mail/src/main/java/org/spongycastle/mail/smime/handlers/x_pkcs7_signature.java90
-rw-r--r--mail/src/main/java/org/spongycastle/mail/smime/util/CRLFOutputStream.java67
-rw-r--r--mail/src/main/java/org/spongycastle/mail/smime/util/FileBackedMimeBodyPart.java162
-rw-r--r--mail/src/main/java/org/spongycastle/mail/smime/util/SharedFileInputStream.java241
-rw-r--r--mail/src/main/java/org/spongycastle/mail/smime/validator/SignedMailValidator.java957
-rw-r--r--mail/src/main/java/org/spongycastle/mail/smime/validator/SignedMailValidatorException.java19
43 files changed, 7179 insertions, 0 deletions
diff --git a/mail/src/main/java/org/spongycastle/mail/smime/CMSProcessableBodyPart.java b/mail/src/main/java/org/spongycastle/mail/smime/CMSProcessableBodyPart.java
new file mode 100644
index 00000000..040def41
--- /dev/null
+++ b/mail/src/main/java/org/spongycastle/mail/smime/CMSProcessableBodyPart.java
@@ -0,0 +1,44 @@
+package org.bouncycastle.mail.smime;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+import javax.mail.BodyPart;
+import javax.mail.MessagingException;
+
+import org.bouncycastle.cms.CMSException;
+import org.bouncycastle.cms.CMSProcessable;
+
+/**
+ * a holding class for a BodyPart to be processed.
+ */
+public class CMSProcessableBodyPart
+ implements CMSProcessable
+{
+ private BodyPart bodyPart;
+
+ public CMSProcessableBodyPart(
+ BodyPart bodyPart)
+ {
+ this.bodyPart = bodyPart;
+ }
+
+ public void write(
+ OutputStream out)
+ throws IOException, CMSException
+ {
+ try
+ {
+ bodyPart.writeTo(out);
+ }
+ catch (MessagingException e)
+ {
+ throw new CMSException("can't write BodyPart to stream.", e);
+ }
+ }
+
+ public Object getContent()
+ {
+ return bodyPart;
+ }
+}
diff --git a/mail/src/main/java/org/spongycastle/mail/smime/CMSProcessableBodyPartInbound.java b/mail/src/main/java/org/spongycastle/mail/smime/CMSProcessableBodyPartInbound.java
new file mode 100644
index 00000000..1497590d
--- /dev/null
+++ b/mail/src/main/java/org/spongycastle/mail/smime/CMSProcessableBodyPartInbound.java
@@ -0,0 +1,66 @@
+package org.bouncycastle.mail.smime;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+import javax.mail.BodyPart;
+import javax.mail.MessagingException;
+
+import org.bouncycastle.cms.CMSException;
+import org.bouncycastle.cms.CMSProcessable;
+
+/**
+ * a holding class for a BodyPart to be processed which does CRLF canonicalisation if
+ * dealing with non-binary data.
+ */
+public class CMSProcessableBodyPartInbound
+ implements CMSProcessable
+{
+ private final BodyPart bodyPart;
+ private final String defaultContentTransferEncoding;
+
+ /**
+ * Create a processable with the default transfer encoding of 7bit
+ *
+ * @param bodyPart body part to be processed
+ */
+ public CMSProcessableBodyPartInbound(
+ BodyPart bodyPart)
+ {
+ this(bodyPart, "7bit");
+ }
+
+ /**
+ * Create a processable with the a default transfer encoding of
+ * the passed in value.
+ *
+ * @param bodyPart body part to be processed
+ * @param defaultContentTransferEncoding the new default to use.
+ */
+ public CMSProcessableBodyPartInbound(
+ BodyPart bodyPart,
+ String defaultContentTransferEncoding)
+ {
+ this.bodyPart = bodyPart;
+ this.defaultContentTransferEncoding = defaultContentTransferEncoding;
+ }
+
+ public void write(
+ OutputStream out)
+ throws IOException, CMSException
+ {
+ try
+ {
+ SMIMEUtil.outputBodyPart(out, bodyPart, defaultContentTransferEncoding);
+ }
+ catch (MessagingException e)
+ {
+ throw new CMSException("can't write BodyPart to stream: " + e, e);
+ }
+ }
+
+ public Object getContent()
+ {
+ return bodyPart;
+ }
+}
diff --git a/mail/src/main/java/org/spongycastle/mail/smime/CMSProcessableBodyPartOutbound.java b/mail/src/main/java/org/spongycastle/mail/smime/CMSProcessableBodyPartOutbound.java
new file mode 100644
index 00000000..4c4b3b11
--- /dev/null
+++ b/mail/src/main/java/org/spongycastle/mail/smime/CMSProcessableBodyPartOutbound.java
@@ -0,0 +1,73 @@
+package org.bouncycastle.mail.smime;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+import javax.mail.BodyPart;
+import javax.mail.MessagingException;
+import javax.mail.internet.MimeBodyPart;
+
+import org.bouncycastle.cms.CMSException;
+import org.bouncycastle.cms.CMSProcessable;
+import org.bouncycastle.mail.smime.util.CRLFOutputStream;
+
+/**
+ * a holding class for a BodyPart to be processed which does CRLF canocicalisation if
+ * dealing with non-binary data.
+ */
+public class CMSProcessableBodyPartOutbound
+ implements CMSProcessable
+{
+ private BodyPart bodyPart;
+ private String defaultContentTransferEncoding;
+
+ /**
+ * Create a processable with the default transfer encoding of 7bit
+ *
+ * @param bodyPart body part to be processed
+ */
+ public CMSProcessableBodyPartOutbound(
+ BodyPart bodyPart)
+ {
+ this.bodyPart = bodyPart;
+ }
+
+ /**
+ * Create a processable with the a default transfer encoding of
+ * the passed in value.
+ *
+ * @param bodyPart body part to be processed
+ * @param defaultContentTransferEncoding the new default to use.
+ */
+ public CMSProcessableBodyPartOutbound(
+ BodyPart bodyPart,
+ String defaultContentTransferEncoding)
+ {
+ this.bodyPart = bodyPart;
+ this.defaultContentTransferEncoding = defaultContentTransferEncoding;
+ }
+
+ public void write(
+ OutputStream out)
+ throws IOException, CMSException
+ {
+ try
+ {
+ if (SMIMEUtil.isCanonicalisationRequired((MimeBodyPart)bodyPart, defaultContentTransferEncoding))
+ {
+ out = new CRLFOutputStream(out);
+ }
+
+ bodyPart.writeTo(out);
+ }
+ catch (MessagingException e)
+ {
+ throw new CMSException("can't write BodyPart to stream.", e);
+ }
+ }
+
+ public Object getContent()
+ {
+ return bodyPart;
+ }
+}
diff --git a/mail/src/main/java/org/spongycastle/mail/smime/SMIMECompressed.java b/mail/src/main/java/org/spongycastle/mail/smime/SMIMECompressed.java
new file mode 100644
index 00000000..2fca9366
--- /dev/null
+++ b/mail/src/main/java/org/spongycastle/mail/smime/SMIMECompressed.java
@@ -0,0 +1,59 @@
+package org.bouncycastle.mail.smime;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import javax.mail.MessagingException;
+import javax.mail.Part;
+import javax.mail.internet.MimeBodyPart;
+import javax.mail.internet.MimeMessage;
+import javax.mail.internet.MimePart;
+
+import org.bouncycastle.cms.CMSCompressedData;
+import org.bouncycastle.cms.CMSException;
+
+/**
+ * containing class for an S/MIME pkcs7-mime MimePart.
+ */
+public class SMIMECompressed
+ extends CMSCompressedData
+{
+ MimePart message;
+
+ private static InputStream getInputStream(
+ Part bodyPart)
+ throws MessagingException
+ {
+ try
+ {
+ return bodyPart.getInputStream();
+ }
+ catch (IOException e)
+ {
+ throw new MessagingException("can't extract input stream: " + e);
+ }
+ }
+
+ public SMIMECompressed(
+ MimeBodyPart message)
+ throws MessagingException, CMSException
+ {
+ super(getInputStream(message));
+
+ this.message = message;
+ }
+
+ public SMIMECompressed(
+ MimeMessage message)
+ throws MessagingException, CMSException
+ {
+ super(getInputStream(message));
+
+ this.message = message;
+ }
+
+ public MimePart getCompressedContent()
+ {
+ return message;
+ }
+}
diff --git a/mail/src/main/java/org/spongycastle/mail/smime/SMIMECompressedGenerator.java b/mail/src/main/java/org/spongycastle/mail/smime/SMIMECompressedGenerator.java
new file mode 100644
index 00000000..2701e9d0
--- /dev/null
+++ b/mail/src/main/java/org/spongycastle/mail/smime/SMIMECompressedGenerator.java
@@ -0,0 +1,152 @@
+package org.bouncycastle.mail.smime;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+
+import javax.activation.CommandMap;
+import javax.activation.MailcapCommandMap;
+import javax.mail.MessagingException;
+import javax.mail.internet.MimeBodyPart;
+import javax.mail.internet.MimeMessage;
+
+import org.bouncycastle.cms.CMSCompressedDataGenerator;
+import org.bouncycastle.cms.CMSCompressedDataStreamGenerator;
+import org.bouncycastle.operator.OutputCompressor;
+
+/**
+ * General class for generating a pkcs7-mime compressed message.
+ *
+ * A simple example of usage.
+ *
+ * <pre>
+ * SMIMECompressedGenerator fact = new SMIMECompressedGenerator();
+ *
+ * MimeBodyPart smime = fact.generate(content, algorithm);
+ * </pre>
+ *
+ * <b>Note:<b> Most clients expect the MimeBodyPart to be in a MimeMultipart
+ * when it's sent.
+ */
+public class SMIMECompressedGenerator
+ extends SMIMEGenerator
+{
+ public static final String ZLIB = CMSCompressedDataGenerator.ZLIB;
+
+ private static final String COMPRESSED_CONTENT_TYPE = "application/pkcs7-mime; name=\"smime.p7z\"; smime-type=compressed-data";
+
+ static
+ {
+ final MailcapCommandMap mc = (MailcapCommandMap)CommandMap.getDefaultCommandMap();
+
+ mc.addMailcap("application/pkcs7-mime;; x-java-content-handler=org.bouncycastle.mail.smime.handlers.pkcs7_mime");
+ mc.addMailcap("application/x-pkcs7-mime;; x-java-content-handler=org.bouncycastle.mail.smime.handlers.x_pkcs7_mime");
+
+ AccessController.doPrivileged(new PrivilegedAction()
+ {
+ public Object run()
+ {
+ CommandMap.setDefaultCommandMap(mc);
+
+ return null;
+ }
+ });
+ }
+
+ /**
+ * generate an compressed object that contains an SMIME Compressed
+ * object using the given compression algorithm.
+ */
+ private MimeBodyPart make(
+ MimeBodyPart content,
+ OutputCompressor compressor)
+ throws SMIMEException
+ {
+ try
+ {
+ MimeBodyPart data = new MimeBodyPart();
+
+ data.setContent(new ContentCompressor(content, compressor), COMPRESSED_CONTENT_TYPE);
+ data.addHeader("Content-Type", COMPRESSED_CONTENT_TYPE);
+ data.addHeader("Content-Disposition", "attachment; filename=\"smime.p7z\"");
+ data.addHeader("Content-Description", "S/MIME Compressed Message");
+ data.addHeader("Content-Transfer-Encoding", encoding);
+
+ return data;
+ }
+ catch (MessagingException e)
+ {
+ throw new SMIMEException("exception putting multi-part together.", e);
+ }
+ }
+
+ /**
+ * generate an compressed object that contains an SMIME Compressed
+ * object using the given provider from the contents of the passed in
+ * message
+ */
+ public MimeBodyPart generate(
+ MimeBodyPart content,
+ OutputCompressor compressor)
+ throws SMIMEException
+ {
+ return make(makeContentBodyPart(content), compressor);
+ }
+
+ /**
+ * generate an compressed object that contains an SMIME Compressed
+ * object using the given provider from the contents of the passed in
+ * message
+ */
+ public MimeBodyPart generate(
+ MimeMessage message,
+ OutputCompressor compressor)
+ 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), compressor);
+ }
+
+ private class ContentCompressor
+ implements SMIMEStreamingProcessor
+ {
+ private final MimeBodyPart content;
+ private final OutputCompressor compressor;
+
+ ContentCompressor(
+ MimeBodyPart content,
+ OutputCompressor compressor)
+ {
+ this.content = content;
+ this.compressor = compressor;
+ }
+
+ public void write(OutputStream out)
+ throws IOException
+ {
+ CMSCompressedDataStreamGenerator cGen = new CMSCompressedDataStreamGenerator();
+
+ OutputStream compressed = cGen.open(out, compressor);
+
+ try
+ {
+ content.writeTo(compressed);
+
+ compressed.close();
+ }
+ catch (MessagingException e)
+ {
+ throw new IOException(e.toString());
+ }
+ }
+ }
+}
diff --git a/mail/src/main/java/org/spongycastle/mail/smime/SMIMECompressedParser.java b/mail/src/main/java/org/spongycastle/mail/smime/SMIMECompressedParser.java
new file mode 100644
index 00000000..23214a4d
--- /dev/null
+++ b/mail/src/main/java/org/spongycastle/mail/smime/SMIMECompressedParser.java
@@ -0,0 +1,100 @@
+package org.bouncycastle.mail.smime;
+
+import java.io.BufferedInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+import javax.mail.MessagingException;
+import javax.mail.Part;
+import javax.mail.internet.MimeBodyPart;
+import javax.mail.internet.MimeMessage;
+import javax.mail.internet.MimePart;
+
+import org.bouncycastle.cms.CMSCompressedDataParser;
+import org.bouncycastle.cms.CMSException;
+
+/**
+ * Stream based containing class for an S/MIME pkcs7-mime compressed MimePart.
+ */
+public class SMIMECompressedParser
+ extends CMSCompressedDataParser
+{
+ private final MimePart message;
+
+ private static InputStream getInputStream(
+ Part bodyPart,
+ int bufferSize)
+ throws MessagingException
+ {
+ try
+ {
+ InputStream in = bodyPart.getInputStream();
+
+ if (bufferSize == 0)
+ {
+ return new BufferedInputStream(in);
+ }
+ else
+ {
+ return new BufferedInputStream(in, bufferSize);
+ }
+ }
+ catch (IOException e)
+ {
+ throw new MessagingException("can't extract input stream: " + e);
+ }
+ }
+
+ public SMIMECompressedParser(
+ MimeBodyPart message)
+ throws MessagingException, CMSException
+ {
+ this(message, 0);
+ }
+
+ public SMIMECompressedParser(
+ MimeMessage message)
+ throws MessagingException, CMSException
+ {
+ this(message, 0);
+ }
+
+ /**
+ * Create a parser from a MimeBodyPart using the passed in buffer size
+ * for reading it.
+ *
+ * @param message body part to be parsed.
+ * @param bufferSize bufferSoze to be used.
+ */
+ public SMIMECompressedParser(
+ MimeBodyPart message,
+ int bufferSize)
+ throws MessagingException, CMSException
+ {
+ super(getInputStream(message, bufferSize));
+
+ this.message = message;
+ }
+
+ /**
+ * Create a parser from a MimeMessage using the passed in buffer size
+ * for reading it.
+ *
+ * @param message message to be parsed.
+ * @param bufferSize bufferSoze to be used.
+ */
+ public SMIMECompressedParser(
+ MimeMessage message,
+ int bufferSize)
+ throws MessagingException, CMSException
+ {
+ super(getInputStream(message, bufferSize));
+
+ this.message = message;
+ }
+
+ public MimePart getCompressedContent()
+ {
+ return message;
+ }
+}
diff --git a/mail/src/main/java/org/spongycastle/mail/smime/SMIMEEnveloped.java b/mail/src/main/java/org/spongycastle/mail/smime/SMIMEEnveloped.java
new file mode 100644
index 00000000..bf7a7ff4
--- /dev/null
+++ b/mail/src/main/java/org/spongycastle/mail/smime/SMIMEEnveloped.java
@@ -0,0 +1,59 @@
+package org.bouncycastle.mail.smime;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import javax.mail.MessagingException;
+import javax.mail.Part;
+import javax.mail.internet.MimeBodyPart;
+import javax.mail.internet.MimeMessage;
+import javax.mail.internet.MimePart;
+
+import org.bouncycastle.cms.CMSEnvelopedData;
+import org.bouncycastle.cms.CMSException;
+
+/**
+ * containing class for an S/MIME pkcs7-mime encrypted MimePart.
+ */
+public class SMIMEEnveloped
+ extends CMSEnvelopedData
+{
+ MimePart message;
+
+ private static InputStream getInputStream(
+ Part bodyPart)
+ throws MessagingException
+ {
+ try
+ {
+ return bodyPart.getInputStream();
+ }
+ catch (IOException e)
+ {
+ throw new MessagingException("can't extract input stream: " + e);
+ }
+ }
+
+ public SMIMEEnveloped(
+ MimeBodyPart message)
+ throws MessagingException, CMSException
+ {
+ super(getInputStream(message));
+
+ this.message = message;
+ }
+
+ public SMIMEEnveloped(
+ MimeMessage message)
+ throws MessagingException, CMSException
+ {
+ super(getInputStream(message));
+
+ this.message = message;
+ }
+
+ public MimePart getEncryptedContent()
+ {
+ return message;
+ }
+}
diff --git a/mail/src/main/java/org/spongycastle/mail/smime/SMIMEEnvelopedGenerator.java b/mail/src/main/java/org/spongycastle/mail/smime/SMIMEEnvelopedGenerator.java
new file mode 100644
index 00000000..dd547d35
--- /dev/null
+++ b/mail/src/main/java/org/spongycastle/mail/smime/SMIMEEnvelopedGenerator.java
@@ -0,0 +1,282 @@
+package org.bouncycastle.mail.smime;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.activation.CommandMap;
+import javax.activation.MailcapCommandMap;
+import javax.mail.MessagingException;
+import javax.mail.internet.MimeBodyPart;
+import javax.mail.internet.MimeMessage;
+
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.cms.CMSEnvelopedDataGenerator;
+import org.bouncycastle.cms.CMSEnvelopedDataStreamGenerator;
+import org.bouncycastle.cms.CMSException;
+import org.bouncycastle.cms.RecipientInfoGenerator;
+import org.bouncycastle.operator.OutputEncryptor;
+
+/**
+ * General class for generating a pkcs7-mime message.
+ *
+ * A simple example of usage.
+ *
+ * <pre>
+ * SMIMEEnvelopedGenerator fact = new SMIMEEnvelopedGenerator();
+ *
+ * fact.addRecipientInfoGenerator(new JceKeyTransRecipientInfoGenerator(recipientCert).setProvider("BC"));
+ *
+ * MimeBodyPart mp = fact.generate(content, new JceCMSContentEncryptorBuilder(CMSAlgorithm.RC2_CBC, 40).setProvider("BC").build());
+ * </pre>
+ *
+ * <b>Note:<b> Most clients expect the MimeBodyPart to be in a MimeMultipart
+ * when it's sent.
+ */
+public class SMIMEEnvelopedGenerator
+ extends SMIMEGenerator
+{
+ public static final String DES_EDE3_CBC = CMSEnvelopedDataGenerator.DES_EDE3_CBC;
+ public static final String RC2_CBC = CMSEnvelopedDataGenerator.RC2_CBC;
+ public static final String IDEA_CBC = CMSEnvelopedDataGenerator.IDEA_CBC;
+ public static final String CAST5_CBC = CMSEnvelopedDataGenerator.CAST5_CBC;
+
+ public static final String AES128_CBC = CMSEnvelopedDataGenerator.AES128_CBC;
+ public static final String AES192_CBC = CMSEnvelopedDataGenerator.AES192_CBC;
+ public static final String AES256_CBC = CMSEnvelopedDataGenerator.AES256_CBC;
+
+ public static final String CAMELLIA128_CBC = CMSEnvelopedDataGenerator.CAMELLIA128_CBC;
+ public static final String CAMELLIA192_CBC = CMSEnvelopedDataGenerator.CAMELLIA192_CBC;
+ public static final String CAMELLIA256_CBC = CMSEnvelopedDataGenerator.CAMELLIA256_CBC;
+
+ public static final String SEED_CBC = CMSEnvelopedDataGenerator.SEED_CBC;
+
+ public static final String DES_EDE3_WRAP = CMSEnvelopedDataGenerator.DES_EDE3_WRAP;
+ public static final String AES128_WRAP = CMSEnvelopedDataGenerator.AES128_WRAP;
+ public static final String AES256_WRAP = CMSEnvelopedDataGenerator.AES256_WRAP;
+ public static final String CAMELLIA128_WRAP = CMSEnvelopedDataGenerator.CAMELLIA128_WRAP;
+ public static final String CAMELLIA192_WRAP = CMSEnvelopedDataGenerator.CAMELLIA192_WRAP;
+ public static final String CAMELLIA256_WRAP = CMSEnvelopedDataGenerator.CAMELLIA256_WRAP;
+ public static final String SEED_WRAP = CMSEnvelopedDataGenerator.SEED_WRAP;
+
+ public static final String ECDH_SHA1KDF = CMSEnvelopedDataGenerator.ECDH_SHA1KDF;
+
+ private static final String ENCRYPTED_CONTENT_TYPE = "application/pkcs7-mime; name=\"smime.p7m\"; smime-type=enveloped-data";
+
+ private EnvelopedGenerator fact;
+ private List recipients = new ArrayList();
+
+ static
+ {
+ AccessController.doPrivileged(new PrivilegedAction()
+ {
+ public Object run()
+ {
+ CommandMap.setDefaultCommandMap(addCommands(CommandMap.getDefaultCommandMap()));
+
+ return null;
+ }
+ });
+ }
+
+ 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;
+ }
+
+ /**
+ * base constructor
+ */
+ public SMIMEEnvelopedGenerator()
+ {
+ fact = new EnvelopedGenerator();
+ }
+
+ /**
+ * add a recipientInfoGenerator.
+ */
+ public void addRecipientInfoGenerator(
+ RecipientInfoGenerator recipientInfoGen)
+ throws IllegalArgumentException
+ {
+ fact.addRecipientInfoGenerator(recipientInfoGen);
+ }
+
+ /**
+ * Use a BER Set to store the recipient information
+ */
+ public void setBerEncodeRecipients(
+ boolean berEncodeRecipientSet)
+ {
+ fact.setBEREncodeRecipients(berEncodeRecipientSet);
+ }
+
+ /**
+ * if we get here we expect the Mime body part to be well defined.
+ */
+ private MimeBodyPart make(
+ MimeBodyPart content,
+ OutputEncryptor encryptor)
+ throws SMIMEException
+ {
+ try
+ {
+ MimeBodyPart data = new MimeBodyPart();
+
+ data.setContent(new ContentEncryptor(content, encryptor), ENCRYPTED_CONTENT_TYPE);
+ data.addHeader("Content-Type", ENCRYPTED_CONTENT_TYPE);
+ data.addHeader("Content-Disposition", "attachment; filename=\"smime.p7m\"");
+ data.addHeader("Content-Description", "S/MIME Encrypted Message");
+ data.addHeader("Content-Transfer-Encoding", encoding);
+
+ return data;
+ }
+ catch (MessagingException e)
+ {
+ throw new SMIMEException("exception putting multi-part together.", e);
+ }
+ }
+
+ /**
+ * generate an enveloped object that contains an SMIME Enveloped
+ * object using the given content encryptor
+ */
+ public MimeBodyPart generate(
+ MimeBodyPart content,
+ OutputEncryptor encryptor)
+ throws SMIMEException
+ {
+ return make(makeContentBodyPart(content), encryptor);
+ }
+
+ /**
+ * generate an enveloped object that contains an SMIME Enveloped
+ * object using the given provider from the contents of the passed in
+ * message
+ */
+ public MimeBodyPart generate(
+ MimeMessage message,
+ OutputEncryptor encryptor)
+ 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), encryptor);
+ }
+
+ private class ContentEncryptor
+ implements SMIMEStreamingProcessor
+ {
+ private final MimeBodyPart _content;
+ private OutputEncryptor _encryptor;
+
+ private boolean _firstTime = true;
+
+ ContentEncryptor(
+ MimeBodyPart content,
+ OutputEncryptor encryptor)
+ {
+ _content = content;
+ _encryptor = encryptor;
+ }
+
+ public void write(OutputStream out)
+ throws IOException
+ {
+ OutputStream encrypted;
+
+ try
+ {
+ if (_firstTime)
+ {
+ encrypted = fact.open(out, _encryptor);
+
+ _firstTime = false;
+ }
+ else
+ {
+ encrypted = fact.regenerate(out, _encryptor);
+ }
+
+ _content.getDataHandler().setCommandMap(addCommands(CommandMap.getDefaultCommandMap()));
+
+ _content.writeTo(encrypted);
+
+ encrypted.close();
+ }
+ catch (MessagingException e)
+ {
+ throw new WrappingIOException(e.toString(), e);
+ }
+ catch (CMSException e)
+ {
+ throw new WrappingIOException(e.toString(), e);
+ }
+ }
+ }
+
+ private class EnvelopedGenerator
+ extends CMSEnvelopedDataStreamGenerator
+ {
+ private ASN1ObjectIdentifier dataType;
+ private ASN1EncodableVector recipientInfos;
+
+ protected OutputStream open(
+ ASN1ObjectIdentifier dataType,
+ OutputStream out,
+ ASN1EncodableVector recipientInfos,
+ OutputEncryptor encryptor)
+ throws IOException
+ {
+ this.dataType = dataType;
+ this.recipientInfos = recipientInfos;
+
+ return super.open(dataType, out, recipientInfos, encryptor);
+ }
+
+ OutputStream regenerate(
+ OutputStream out,
+ OutputEncryptor encryptor)
+ throws IOException
+ {
+ return super.open(dataType, out, recipientInfos, encryptor);
+ }
+ }
+
+ private static class WrappingIOException
+ extends IOException
+ {
+ private Throwable cause;
+
+ WrappingIOException(String msg, Throwable cause)
+ {
+ super(msg);
+
+ this.cause = cause;
+ }
+
+ public Throwable getCause()
+ {
+ return cause;
+ }
+ }
+}
diff --git a/mail/src/main/java/org/spongycastle/mail/smime/SMIMEEnvelopedParser.java b/mail/src/main/java/org/spongycastle/mail/smime/SMIMEEnvelopedParser.java
new file mode 100644
index 00000000..95849472
--- /dev/null
+++ b/mail/src/main/java/org/spongycastle/mail/smime/SMIMEEnvelopedParser.java
@@ -0,0 +1,100 @@
+package org.bouncycastle.mail.smime;
+
+import java.io.BufferedInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+import javax.mail.MessagingException;
+import javax.mail.Part;
+import javax.mail.internet.MimeBodyPart;
+import javax.mail.internet.MimeMessage;
+import javax.mail.internet.MimePart;
+
+import org.bouncycastle.cms.CMSEnvelopedDataParser;
+import org.bouncycastle.cms.CMSException;
+
+/**
+ * Stream based containing class for an S/MIME pkcs7-mime encrypted MimePart.
+ */
+public class SMIMEEnvelopedParser
+ extends CMSEnvelopedDataParser
+{
+ private final MimePart message;
+
+ private static InputStream getInputStream(
+ Part bodyPart,
+ int bufferSize)
+ throws MessagingException
+ {
+ try
+ {
+ InputStream in = bodyPart.getInputStream();
+
+ if (bufferSize == 0)
+ {
+ return new BufferedInputStream(in);
+ }
+ else
+ {
+ return new BufferedInputStream(in, bufferSize);
+ }
+ }
+ catch (IOException e)
+ {
+ throw new MessagingException("can't extract input stream: " + e);
+ }
+ }
+
+ public SMIMEEnvelopedParser(
+ MimeBodyPart message)
+ throws IOException, MessagingException, CMSException
+ {
+ this(message, 0);
+ }
+
+ public SMIMEEnvelopedParser(
+ MimeMessage message)
+ throws IOException, MessagingException, CMSException
+ {
+ this(message, 0);
+ }
+
+ /**
+ * Create a parser from a MimeBodyPart using the passed in buffer size
+ * for reading it.
+ *
+ * @param message body part to be parsed.
+ * @param bufferSize bufferSoze to be used.
+ */
+ public SMIMEEnvelopedParser(
+ MimeBodyPart message,
+ int bufferSize)
+ throws IOException, MessagingException, CMSException
+ {
+ super(getInputStream(message, bufferSize));
+
+ this.message = message;
+ }
+
+ /**
+ * Create a parser from a MimeMessage using the passed in buffer size
+ * for reading it.
+ *
+ * @param message message to be parsed.
+ * @param bufferSize bufferSoze to be used.
+ */
+ public SMIMEEnvelopedParser(
+ MimeMessage message,
+ int bufferSize)
+ throws IOException, MessagingException, CMSException
+ {
+ super(getInputStream(message, bufferSize));
+
+ this.message = message;
+ }
+
+ public MimePart getEncryptedContent()
+ {
+ return message;
+ }
+}
diff --git a/mail/src/main/java/org/spongycastle/mail/smime/SMIMEException.java b/mail/src/main/java/org/spongycastle/mail/smime/SMIMEException.java
new file mode 100644
index 00000000..fe7499cf
--- /dev/null
+++ b/mail/src/main/java/org/spongycastle/mail/smime/SMIMEException.java
@@ -0,0 +1,32 @@
+package org.bouncycastle.mail.smime;
+
+public class SMIMEException
+ extends Exception
+{
+ Exception e;
+
+ public SMIMEException(
+ String name)
+ {
+ super(name);
+ }
+
+ public SMIMEException(
+ String name,
+ Exception e)
+ {
+ super(name);
+
+ this.e = e;
+ }
+
+ public Exception getUnderlyingException()
+ {
+ return e;
+ }
+
+ public Throwable getCause()
+ {
+ return e;
+ }
+}
diff --git a/mail/src/main/java/org/spongycastle/mail/smime/SMIMEGenerator.java b/mail/src/main/java/org/spongycastle/mail/smime/SMIMEGenerator.java
new file mode 100644
index 00000000..168cc4a4
--- /dev/null
+++ b/mail/src/main/java/org/spongycastle/mail/smime/SMIMEGenerator.java
@@ -0,0 +1,223 @@
+package org.bouncycastle.mail.smime;
+
+import java.io.IOException;
+import java.security.NoSuchAlgorithmException;
+import java.security.Provider;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.crypto.KeyGenerator;
+import javax.mail.Header;
+import javax.mail.MessagingException;
+import javax.mail.Multipart;
+import javax.mail.Session;
+import javax.mail.internet.MimeBodyPart;
+import javax.mail.internet.MimeMessage;
+
+import org.bouncycastle.cms.CMSEnvelopedGenerator;
+import org.bouncycastle.util.Strings;
+
+/**
+ * super class of the various generators.
+ */
+public class SMIMEGenerator
+{
+ private static Map BASE_CIPHER_NAMES = new HashMap();
+
+ static
+ {
+ BASE_CIPHER_NAMES.put(CMSEnvelopedGenerator.DES_EDE3_CBC, "DESEDE");
+ BASE_CIPHER_NAMES.put(CMSEnvelopedGenerator.AES128_CBC, "AES");
+ BASE_CIPHER_NAMES.put(CMSEnvelopedGenerator.AES192_CBC, "AES");
+ BASE_CIPHER_NAMES.put(CMSEnvelopedGenerator.AES256_CBC, "AES");
+ }
+
+ protected boolean useBase64 = true;
+ protected String encoding = "base64"; // default sets base64
+
+ /**
+ * base constructor
+ */
+ protected SMIMEGenerator()
+ {
+ }
+
+ /**
+ * set the content-transfer-encoding for the CMS block (enveloped data, signature, etc...) in the message.
+ *
+ * @param encoding the encoding to use, default "base64", use "binary" for a binary encoding.
+ */
+ public void setContentTransferEncoding(
+ String encoding)
+ {
+ this.encoding = encoding;
+ this.useBase64 = Strings.toLowerCase(encoding).equals("base64");
+ }
+
+ /**
+ * Make sure we have a valid content body part - setting the headers
+ * with defaults if neccessary.
+ */
+ protected MimeBodyPart makeContentBodyPart(
+ MimeBodyPart content)
+ throws SMIMEException
+ {
+ //
+ // add the headers to the body part - if they are missing, in
+ // the event they have already been set the content settings override
+ // any defaults that might be set.
+ //
+ try
+ {
+ MimeMessage msg = new MimeMessage((Session)null);
+
+ Enumeration e = content.getAllHeaders();
+
+ msg.setDataHandler(content.getDataHandler());
+
+ while (e.hasMoreElements())
+ {
+ Header hdr =(Header)e.nextElement();
+
+ msg.setHeader(hdr.getName(), hdr.getValue());
+ }
+
+ msg.saveChanges();
+
+ //
+ // we do this to make sure at least the default headers are
+ // set in the body part.
+ //
+ e = msg.getAllHeaders();
+
+ while (e.hasMoreElements())
+ {
+ Header hdr =(Header)e.nextElement();
+
+ if (Strings.toLowerCase(hdr.getName()).startsWith("content-"))
+ {
+ content.setHeader(hdr.getName(), hdr.getValue());
+ }
+ }
+ }
+ catch (MessagingException e)
+ {
+ throw new SMIMEException("exception saving message state.", e);
+ }
+
+ return content;
+ }
+
+ /**
+ * extract an appropriate body part from the passed in MimeMessage
+ */
+ protected MimeBodyPart makeContentBodyPart(
+ MimeMessage message)
+ throws SMIMEException
+ {
+ MimeBodyPart content = new MimeBodyPart();
+
+ //
+ // add the headers to the body part.
+ //
+ try
+ {
+ message.removeHeader("Message-Id");
+ message.removeHeader("Mime-Version");
+
+ // JavaMail has a habit of reparsing some content types, if the bodypart is
+ // a multipart it might be signed, we rebuild the body part using the raw input stream for the message.
+ try
+ {
+ if (message.getContent() instanceof Multipart)
+ {
+ content.setContent(message.getRawInputStream(), message.getContentType());
+
+ extractHeaders(content, message);
+
+ return content;
+ }
+ }
+ catch (MessagingException e)
+ {
+ // fall back to usual method below
+ }
+
+ content.setContent(message.getContent(), message.getContentType());
+
+ content.setDataHandler(message.getDataHandler());
+
+ extractHeaders(content, message);
+ }
+ catch (MessagingException e)
+ {
+ throw new SMIMEException("exception saving message state.", e);
+ }
+ catch (IOException e)
+ {
+ throw new SMIMEException("exception getting message content.", e);
+ }
+
+ return content;
+ }
+
+ private void extractHeaders(MimeBodyPart content, MimeMessage message)
+ throws MessagingException
+ {
+ Enumeration e = message.getAllHeaders();
+
+ while (e.hasMoreElements())
+ {
+ Header hdr =(Header)e.nextElement();
+
+ content.addHeader(hdr.getName(), hdr.getValue());
+ }
+ }
+
+ protected KeyGenerator createSymmetricKeyGenerator(
+ String encryptionOID,
+ Provider provider)
+ throws NoSuchAlgorithmException
+ {
+ try
+ {
+ return createKeyGenerator(encryptionOID, provider);
+ }
+ catch (NoSuchAlgorithmException e)
+ {
+ try
+ {
+ String algName = (String)BASE_CIPHER_NAMES.get(encryptionOID);
+ if (algName != null)
+ {
+ return createKeyGenerator(algName, provider);
+ }
+ }
+ catch (NoSuchAlgorithmException ex)
+ {
+ // ignore
+ }
+ if (provider != null)
+ {
+ return createSymmetricKeyGenerator(encryptionOID, null);
+ }
+ throw e;
+ }
+ }
+
+ private KeyGenerator createKeyGenerator(
+ String algName,
+ Provider provider)
+ throws NoSuchAlgorithmException
+ {
+ if (provider != null)
+ {
+ return KeyGenerator.getInstance(algName, provider);
+ }
+ else
+ {
+ return KeyGenerator.getInstance(algName);
+ }
+ }
+}
diff --git a/mail/src/main/java/org/spongycastle/mail/smime/SMIMESigned.java b/mail/src/main/java/org/spongycastle/mail/smime/SMIMESigned.java
new file mode 100644
index 00000000..9f279398
--- /dev/null
+++ b/mail/src/main/java/org/spongycastle/mail/smime/SMIMESigned.java
@@ -0,0 +1,241 @@
+package org.bouncycastle.mail.smime;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+
+import javax.activation.CommandMap;
+import javax.activation.MailcapCommandMap;
+import javax.mail.MessagingException;
+import javax.mail.Part;
+import javax.mail.Session;
+import javax.mail.internet.MimeBodyPart;
+import javax.mail.internet.MimeMessage;
+import javax.mail.internet.MimeMultipart;
+import javax.mail.internet.MimePart;
+
+import org.bouncycastle.cms.CMSException;
+import org.bouncycastle.cms.CMSProcessable;
+import org.bouncycastle.cms.CMSSignedData;
+
+/**
+ * general class for handling a pkcs7-signature message.
+ * <p>
+ * A simple example of usage - note, in the example below the validity of
+ * the certificate isn't verified, just the fact that one of the certs
+ * matches the given signer...
+ * <p>
+ * <pre>
+ * CertStore certs = s.getCertificates("Collection", "BC");
+ * SignerInformationStore signers = s.getSignerInfos();
+ * Collection c = signers.getSigners();
+ * Iterator it = c.iterator();
+ *
+ * while (it.hasNext())
+ * {
+ * SignerInformation signer = (SignerInformation)it.next();
+ * Collection certCollection = certs.getCertificates(signer.getSID());
+ *
+ * Iterator certIt = certCollection.iterator();
+ * X509Certificate cert = (X509Certificate)certIt.next();
+ *
+ * if (signer.verify(cert.getPublicKey()))
+ * {
+ * verified++;
+ * }
+ * }
+ * </pre>
+ * <p>
+ * Note: 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>
+ */
+public class SMIMESigned
+ extends CMSSignedData
+{
+ Object message;
+ MimeBodyPart content;
+
+ private static InputStream getInputStream(
+ Part bodyPart)
+ throws MessagingException
+ {
+ try
+ {
+ if (bodyPart.isMimeType("multipart/signed"))
+ {
+ throw new MessagingException("attempt to create signed data object from multipart content - use MimeMultipart constructor.");
+ }
+
+ return bodyPart.getInputStream();
+ }
+ catch (IOException e)
+ {
+ throw new MessagingException("can't extract input stream: " + e);
+ }
+ }
+
+ static
+ {
+ final MailcapCommandMap mc = (MailcapCommandMap)CommandMap.getDefaultCommandMap();
+
+ 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");
+
+ AccessController.doPrivileged(new PrivilegedAction()
+ {
+ public Object run()
+ {
+ CommandMap.setDefaultCommandMap(mc);
+
+ return null;
+ }
+ });
+ }
+
+ /**
+ * base constructor using a defaultContentTransferEncoding of 7bit
+ *
+ * @exception MessagingException on an error extracting the signature or
+ * otherwise processing the message.
+ * @exception CMSException if some other problem occurs.
+ */
+ public SMIMESigned(
+ MimeMultipart message)
+ throws MessagingException, CMSException
+ {
+ super(new CMSProcessableBodyPartInbound(message.getBodyPart(0)), getInputStream(message.getBodyPart(1)));
+
+ this.message = message;
+ this.content = (MimeBodyPart)message.getBodyPart(0);
+ }
+
+ /**
+ * base constructor with settable contentTransferEncoding
+ *
+ * @param message the signed message
+ * @param defaultContentTransferEncoding new default to use
+ * @exception MessagingException on an error extracting the signature or
+ * otherwise processing the message.
+ * @exception CMSException if some other problem occurs.
+ */
+ public SMIMESigned(
+ MimeMultipart message,
+ String defaultContentTransferEncoding)
+ throws MessagingException, CMSException
+ {
+ super(new CMSProcessableBodyPartInbound(message.getBodyPart(0), defaultContentTransferEncoding), getInputStream(message.getBodyPart(1)));
+
+ this.message = message;
+ this.content = (MimeBodyPart)message.getBodyPart(0);
+ }
+
+ /**
+ * base constructor for a signed message with encapsulated content.
+ *
+ * @exception MessagingException on an error extracting the signature or
+ * otherwise processing the message.
+ * @exception SMIMEException if the body part encapsulated in the message cannot be extracted.
+ * @exception CMSException if some other problem occurs.
+ */
+ public SMIMESigned(
+ Part message)
+ throws MessagingException, CMSException, SMIMEException
+ {
+ super(getInputStream(message));
+
+ this.message = message;
+
+ CMSProcessable cont = this.getSignedContent();
+
+ if (cont != null)
+ {
+ byte[] contBytes = (byte[])cont.getContent();
+
+ this.content = SMIMEUtil.toMimeBodyPart(contBytes);
+ }
+ }
+
+ /**
+ * return the content that was signed.
+ */
+ public MimeBodyPart getContent()
+ {
+ return content;
+ }
+
+ /**
+ * Return the content that was signed as a mime message.
+ *
+ * @param session
+ * @return a MimeMessage holding the content.
+ * @throws MessagingException
+ */
+ public MimeMessage getContentAsMimeMessage(Session session)
+ throws MessagingException, IOException
+ {
+ Object content = getSignedContent().getContent();
+ byte[] contentBytes = null;
+
+ if (content instanceof byte[])
+ {
+ contentBytes = (byte[])content;
+ }
+ else if (content instanceof MimePart)
+ {
+ MimePart part = (MimePart)content;
+ ByteArrayOutputStream out;
+
+ if (part.getSize() > 0)
+ {
+ out = new ByteArrayOutputStream(part.getSize());
+ }
+ else
+ {
+ out = new ByteArrayOutputStream();
+ }
+
+ part.writeTo(out);
+ contentBytes = out.toByteArray();
+ }
+ else
+ {
+ String type = "<null>";
+ if (content != null)
+ {
+ type = content.getClass().getName();
+ }
+
+ throw new MessagingException(
+ "Could not transfrom content of type "
+ + type
+ + " into MimeMessage.");
+ }
+
+ if (contentBytes != null)
+ {
+ ByteArrayInputStream in = new ByteArrayInputStream(contentBytes);
+
+ return new MimeMessage(session, in);
+ }
+
+ return null;
+ }
+
+ /**
+ * return the content that was signed - depending on whether this was
+ * unencapsulated or not it will return a MimeMultipart or a MimeBodyPart
+ */
+ public Object getContentWithSignature()
+ {
+ return message;
+ }
+}
diff --git a/mail/src/main/java/org/spongycastle/mail/smime/SMIMESignedGenerator.java b/mail/src/main/java/org/spongycastle/mail/smime/SMIMESignedGenerator.java
new file mode 100644
index 00000000..e4c96e86
--- /dev/null
+++ b/mail/src/main/java/org/spongycastle/mail/smime/SMIMESignedGenerator.java
@@ -0,0 +1,616 @@
+package org.bouncycastle.mail.smime;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+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
+ {
+ AccessController.doPrivileged(new PrivilegedAction()
+ {
+ public Object run()
+ {
+ CommandMap.setDefaultCommandMap(addCommands(CommandMap.getDefaultCommandMap()));
+
+ return null;
+ }
+ });
+
+ 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());
+ }
+ }
+ }
+}
diff --git a/mail/src/main/java/org/spongycastle/mail/smime/SMIMESignedParser.java b/mail/src/main/java/org/spongycastle/mail/smime/SMIMESignedParser.java
new file mode 100644
index 00000000..0f497697
--- /dev/null
+++ b/mail/src/main/java/org/spongycastle/mail/smime/SMIMESignedParser.java
@@ -0,0 +1,368 @@
+package org.bouncycastle.mail.smime;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+
+import javax.activation.CommandMap;
+import javax.activation.MailcapCommandMap;
+import javax.mail.BodyPart;
+import javax.mail.MessagingException;
+import javax.mail.Part;
+import javax.mail.Session;
+import javax.mail.internet.MimeBodyPart;
+import javax.mail.internet.MimeMessage;
+import javax.mail.internet.MimeMultipart;
+
+import org.bouncycastle.cms.CMSException;
+import org.bouncycastle.cms.CMSSignedDataParser;
+import org.bouncycastle.cms.CMSTypedStream;
+import org.bouncycastle.operator.DigestCalculatorProvider;
+
+/**
+ * general class for handling a pkcs7-signature message.
+ * <p>
+ * A simple example of usage - note, in the example below the validity of
+ * the certificate isn't verified, just the fact that one of the certs
+ * matches the given signer...
+ * <p>
+ * <pre>
+ * CertStore certs = s.getCertificates("Collection", "BC");
+ * SignerInformationStore signers = s.getSignerInfos();
+ * Collection c = signers.getSigners();
+ * Iterator it = c.iterator();
+ *
+ * while (it.hasNext())
+ * {
+ * SignerInformation signer = (SignerInformation)it.next();
+ * Collection certCollection = certs.getCertificates(signer.getSID());
+ *
+ * Iterator certIt = certCollection.iterator();
+ * X509Certificate cert = (X509Certificate)certIt.next();
+ *
+ * if (signer.verify(cert.getPublicKey()))
+ * {
+ * verified++;
+ * }
+ * }
+ * </pre>
+ * <p>
+ * Note: 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>
+ */
+public class SMIMESignedParser
+ extends CMSSignedDataParser
+{
+ Object message;
+ MimeBodyPart content;
+
+ private static InputStream getInputStream(
+ Part bodyPart)
+ throws MessagingException
+ {
+ try
+ {
+ if (bodyPart.isMimeType("multipart/signed"))
+ {
+ throw new MessagingException("attempt to create signed data object from multipart content - use MimeMultipart constructor.");
+ }
+
+ return bodyPart.getInputStream();
+ }
+ catch (IOException e)
+ {
+ throw new MessagingException("can't extract input stream: " + e);
+ }
+ }
+
+ private static File getTmpFile()
+ throws MessagingException
+ {
+ try
+ {
+ return File.createTempFile("bcMail", ".mime");
+ }
+ catch (IOException e)
+ {
+ throw new MessagingException("can't extract input stream: " + e);
+ }
+ }
+
+ private static CMSTypedStream getSignedInputStream(
+ BodyPart bodyPart,
+ String defaultContentTransferEncoding,
+ File backingFile)
+ throws MessagingException
+ {
+ try
+ {
+ OutputStream out = new BufferedOutputStream(new FileOutputStream(backingFile));
+
+ SMIMEUtil.outputBodyPart(out, bodyPart, defaultContentTransferEncoding);
+
+ out.close();
+
+ InputStream in = new TemporaryFileInputStream(backingFile);
+
+ return new CMSTypedStream(in);
+ }
+ catch (IOException e)
+ {
+ throw new MessagingException("can't extract input stream: " + e);
+ }
+ }
+
+ static
+ {
+ final MailcapCommandMap mc = (MailcapCommandMap)CommandMap.getDefaultCommandMap();
+
+ 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");
+
+ AccessController.doPrivileged(new PrivilegedAction()
+ {
+ public Object run()
+ {
+ CommandMap.setDefaultCommandMap(mc);
+
+ return null;
+ }
+ });
+ }
+
+ /**
+ * base constructor using a defaultContentTransferEncoding of 7bit. A temporary backing file
+ * will be created for the signed data.
+ *
+ * @param digCalcProvider provider for digest calculators.
+ * @param message signed message with signature.
+ * @exception MessagingException on an error extracting the signature or
+ * otherwise processing the message.
+ * @exception CMSException if some other problem occurs.
+ */
+ public SMIMESignedParser(
+ DigestCalculatorProvider digCalcProvider,
+ MimeMultipart message)
+ throws MessagingException, CMSException
+ {
+ this(digCalcProvider, message, getTmpFile());
+ }
+
+ /**
+ * base constructor using a defaultContentTransferEncoding of 7bit and a specified backing file.
+ *
+ * @param digCalcProvider provider for digest calculators.
+ * @param message signed message with signature.
+ * @param backingFile the temporary file to use to back the signed data.
+ * @exception MessagingException on an error extracting the signature or
+ * otherwise processing the message.
+ * @exception CMSException if some other problem occurs.
+ */
+ public SMIMESignedParser(
+ DigestCalculatorProvider digCalcProvider,
+ MimeMultipart message,
+ File backingFile)
+ throws MessagingException, CMSException
+ {
+ this(digCalcProvider, message, "7bit", backingFile);
+ }
+
+ /**
+ * base constructor with settable contentTransferEncoding. A temporary backing file will be created
+ * to contain the signed data.
+ *
+ * @param digCalcProvider provider for digest calculators.
+ * @param message the signed message with signature.
+ * @param defaultContentTransferEncoding new default to use.
+ * @exception MessagingException on an error extracting the signature or
+ * otherwise processing the message.
+ * @exception CMSException if some other problem occurs.r
+ */
+ public SMIMESignedParser(
+ DigestCalculatorProvider digCalcProvider,
+ MimeMultipart message,
+ String defaultContentTransferEncoding)
+ throws MessagingException, CMSException
+ {
+ this(digCalcProvider, message, defaultContentTransferEncoding, getTmpFile());
+ }
+
+ /**
+ * base constructor with settable contentTransferEncoding and a specified backing file.
+ *
+ * @param digCalcProvider provider for digest calculators.
+ * @param message the signed message with signature.
+ * @param defaultContentTransferEncoding new default to use.
+ * @param backingFile the temporary file to use to back the signed data.
+ * @exception MessagingException on an error extracting the signature or
+ * otherwise processing the message.
+ * @exception CMSException if some other problem occurs.
+ */
+ public SMIMESignedParser(
+ DigestCalculatorProvider digCalcProvider,
+ MimeMultipart message,
+ String defaultContentTransferEncoding,
+ File backingFile)
+ throws MessagingException, CMSException
+ {
+ super(digCalcProvider, getSignedInputStream(message.getBodyPart(0), defaultContentTransferEncoding, backingFile), getInputStream(message.getBodyPart(1)));
+
+ this.message = message;
+ this.content = (MimeBodyPart)message.getBodyPart(0);
+
+ drainContent();
+ }
+
+ /**
+ * base constructor for a signed message with encapsulated content.
+ * <p>
+ * Note: in this case the encapsulated MimeBody part will only be suitable for a single
+ * writeTo - once writeTo has been called the file containing the body part will be deleted. If writeTo is not
+ * called the file will be left in the temp directory.
+ * </p>
+ * @param digCalcProvider provider for digest calculators.
+ * @param message the message containing the encapsulated signed data.
+ * @exception MessagingException on an error extracting the signature or
+ * otherwise processing the message.
+ * @exception SMIMEException if the body part encapsulated in the message cannot be extracted.
+ * @exception CMSException if some other problem occurs.
+ */
+ public SMIMESignedParser(
+ DigestCalculatorProvider digCalcProvider,
+ Part message)
+ throws MessagingException, CMSException, SMIMEException
+ {
+ super(digCalcProvider, getInputStream(message));
+
+ this.message = message;
+
+ CMSTypedStream cont = this.getSignedContent();
+
+ if (cont != null)
+ {
+ this.content = SMIMEUtil.toWriteOnceBodyPart(cont);
+ }
+ }
+
+ /**
+ * Constructor for a signed message with encapsulated content. The encapsulated
+ * content, if it exists, is written to the file represented by the File object
+ * passed in.
+ *
+ * @param digCalcProvider provider for digest calculators.
+ * @param message the Part containing the signed content.
+ * @param file the file the encapsulated part is to be written to after it has been decoded.
+ *
+ * @exception MessagingException on an error extracting the signature or
+ * otherwise processing the message.
+ * @exception SMIMEException if the body part encapsulated in the message cannot be extracted.
+ * @exception CMSException if some other problem occurs.
+ */
+ public SMIMESignedParser(
+ DigestCalculatorProvider digCalcProvider,
+ Part message,
+ File file)
+ throws MessagingException, CMSException, SMIMEException
+ {
+ super(digCalcProvider, getInputStream(message));
+
+ this.message = message;
+
+ CMSTypedStream cont = this.getSignedContent();
+
+ if (cont != null)
+ {
+ this.content = SMIMEUtil.toMimeBodyPart(cont, file);
+ }
+ }
+
+ /**
+ * return the content that was signed.
+ * @return the signed body part in this message.
+ */
+ public MimeBodyPart getContent()
+ {
+ return content;
+ }
+
+ /**
+ * Return the content that was signed as a mime message.
+ *
+ * @param session the session to base the MimeMessage around.
+ * @return a MimeMessage holding the content.
+ * @throws MessagingException if there is an issue creating the MimeMessage.
+ * @throws IOException if there is an issue reading the content.
+ */
+ public MimeMessage getContentAsMimeMessage(Session session)
+ throws MessagingException, IOException
+ {
+ if (message instanceof MimeMultipart)
+ {
+ BodyPart bp = ((MimeMultipart)message).getBodyPart(0);
+ return new MimeMessage(session, bp.getInputStream());
+ }
+ else
+ {
+ return new MimeMessage(session, getSignedContent().getContentStream());
+ }
+ }
+
+ /**
+ * return the content that was signed with its signature attached.
+ * @return depending on whether this was unencapsulated or not it will return a MimeMultipart
+ * or a MimeBodyPart
+ */
+ public Object getContentWithSignature()
+ {
+ return message;
+ }
+
+ private void drainContent()
+ throws CMSException
+ {
+ try
+ {
+ this.getSignedContent().drain();
+ }
+ catch (IOException e)
+ {
+ throw new CMSException("unable to read content for verification: " + e, e);
+ }
+ }
+
+ private static class TemporaryFileInputStream
+ extends BufferedInputStream
+ {
+ private final File _file;
+
+ TemporaryFileInputStream(File file)
+ throws FileNotFoundException
+ {
+ super(new FileInputStream(file));
+
+ _file = file;
+ }
+
+ public void close()
+ throws IOException
+ {
+ super.close();
+
+ _file.delete();
+ }
+ }
+}
diff --git a/mail/src/main/java/org/spongycastle/mail/smime/SMIMEStreamingProcessor.java b/mail/src/main/java/org/spongycastle/mail/smime/SMIMEStreamingProcessor.java
new file mode 100644
index 00000000..e773232d
--- /dev/null
+++ b/mail/src/main/java/org/spongycastle/mail/smime/SMIMEStreamingProcessor.java
@@ -0,0 +1,10 @@
+package org.bouncycastle.mail.smime;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+public interface SMIMEStreamingProcessor
+{
+ public void write(OutputStream out)
+ throws IOException;
+}
diff --git a/mail/src/main/java/org/spongycastle/mail/smime/SMIMEUtil.java b/mail/src/main/java/org/spongycastle/mail/smime/SMIMEUtil.java
new file mode 100644
index 00000000..b2f36e6f
--- /dev/null
+++ b/mail/src/main/java/org/spongycastle/mail/smime/SMIMEUtil.java
@@ -0,0 +1,623 @@
+package org.bouncycastle.mail.smime;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FilterOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.security.NoSuchProviderException;
+import java.security.Provider;
+import java.security.Security;
+import java.security.cert.CertificateParsingException;
+import java.security.cert.X509Certificate;
+import java.util.Enumeration;
+
+import javax.mail.BodyPart;
+import javax.mail.MessagingException;
+import javax.mail.internet.ContentType;
+import javax.mail.internet.MimeBodyPart;
+import javax.mail.internet.MimeMultipart;
+
+import org.bouncycastle.asn1.cms.IssuerAndSerialNumber;
+import org.bouncycastle.cms.CMSTypedStream;
+import org.bouncycastle.jce.PrincipalUtil;
+import org.bouncycastle.mail.smime.util.CRLFOutputStream;
+import org.bouncycastle.mail.smime.util.FileBackedMimeBodyPart;
+
+public class SMIMEUtil
+{
+ private static final int BUF_SIZE = 32760;
+
+ static boolean isCanonicalisationRequired(
+ MimeBodyPart bodyPart,
+ String defaultContentTransferEncoding)
+ throws MessagingException
+ {
+ String[] cte = bodyPart.getHeader("Content-Transfer-Encoding");
+ String contentTransferEncoding;
+
+ if (cte == null)
+ {
+ contentTransferEncoding = defaultContentTransferEncoding;
+ }
+ else
+ {
+ contentTransferEncoding = cte[0];
+ }
+
+ return !contentTransferEncoding.equalsIgnoreCase("binary");
+ }
+
+ public static Provider getProvider(String providerName)
+ throws NoSuchProviderException
+ {
+ if (providerName != null)
+ {
+ Provider prov = Security.getProvider(providerName);
+
+ if (prov != null)
+ {
+ return prov;
+ }
+
+ throw new NoSuchProviderException("provider " + providerName + " not found.");
+ }
+
+ return null;
+ }
+
+ static class LineOutputStream extends FilterOutputStream
+ {
+ private static byte newline[];
+
+ public LineOutputStream(OutputStream outputstream)
+ {
+ super(outputstream);
+ }
+
+ public void writeln(String s)
+ throws MessagingException
+ {
+ try
+ {
+ byte abyte0[] = getBytes(s);
+ super.out.write(abyte0);
+ super.out.write(newline);
+ }
+ catch(Exception exception)
+ {
+ throw new MessagingException("IOException", exception);
+ }
+ }
+
+ public void writeln()
+ throws MessagingException
+ {
+ try
+ {
+ super.out.write(newline);
+ }
+ catch(Exception exception)
+ {
+ throw new MessagingException("IOException", exception);
+ }
+ }
+
+ static
+ {
+ newline = new byte[2];
+ newline[0] = 13;
+ newline[1] = 10;
+ }
+
+ private static byte[] getBytes(String s)
+ {
+ char ac[] = s.toCharArray();
+ int i = ac.length;
+ byte abyte0[] = new byte[i];
+ int j = 0;
+
+ while (j < i)
+ {
+ abyte0[j] = (byte)ac[j++];
+ }
+
+ return abyte0;
+ }
+ }
+
+ /**
+ * internal preamble is generally included in signatures, while this is technically wrong,
+ * if we find internal preamble we include it by default.
+ */
+ static void outputPreamble(LineOutputStream lOut, MimeBodyPart part, String boundary)
+ throws MessagingException, IOException
+ {
+ InputStream in;
+
+ try
+ {
+ in = part.getRawInputStream();
+ }
+ catch (MessagingException e)
+ {
+ return; // no underlying content rely on default generation
+ }
+
+ String line;
+
+ while ((line = readLine(in)) != null)
+ {
+ if (line.equals(boundary))
+ {
+ break;
+ }
+
+ lOut.writeln(line);
+ }
+
+ in.close();
+
+ if (line == null)
+ {
+ throw new MessagingException("no boundary found");
+ }
+ }
+
+ /**
+ * internal postamble is generally included in signatures, while this is technically wrong,
+ * if we find internal postamble we include it by default.
+ */
+ static void outputPostamble(LineOutputStream lOut, MimeBodyPart part, int count, String boundary)
+ throws MessagingException, IOException
+ {
+ InputStream in;
+
+ try
+ {
+ in = part.getRawInputStream();
+ }
+ catch (MessagingException e)
+ {
+ return; // no underlying content rely on default generation
+ }
+
+ String line;
+ int boundaries = count + 1;
+
+ while ((line = readLine(in)) != null)
+ {
+ if (line.startsWith(boundary))
+ {
+ boundaries--;
+
+ if (boundaries == 0)
+ {
+ break;
+ }
+ }
+ }
+
+ while ((line = readLine(in)) != null)
+ {
+ lOut.writeln(line);
+ }
+
+ in.close();
+
+ if (boundaries != 0)
+ {
+ throw new MessagingException("all boundaries not found for: " + boundary);
+ }
+ }
+
+ static void outputPostamble(LineOutputStream lOut, BodyPart parent, String parentBoundary, BodyPart part)
+ throws MessagingException, IOException
+ {
+ InputStream in;
+
+ try
+ {
+ in = ((MimeBodyPart)parent).getRawInputStream();
+ }
+ catch (MessagingException e)
+ {
+ return; // no underlying content rely on default generation
+ }
+
+
+ MimeMultipart multipart = (MimeMultipart)part.getContent();
+ ContentType contentType = new ContentType(multipart.getContentType());
+ String boundary = "--" + contentType.getParameter("boundary");
+ int count = multipart.getCount() + 1;
+ String line;
+ while (count != 0 && (line = readLine(in)) != null)
+ {
+ if (line.startsWith(boundary))
+ {
+ count--;
+ }
+ }
+
+ while ((line = readLine(in)) != null)
+ {
+ if (line.startsWith(parentBoundary))
+ {
+ break;
+ }
+ lOut.writeln(line);
+ }
+
+ in.close();
+ }
+
+ /*
+ * read a line of input stripping of the tailing \r\n
+ */
+ private static String readLine(InputStream in)
+ throws IOException
+ {
+ StringBuffer b = new StringBuffer();
+
+ int ch;
+ while ((ch = in.read()) >= 0 && ch != '\n')
+ {
+ if (ch != '\r')
+ {
+ b.append((char)ch);
+ }
+ }
+
+ if (ch < 0 && b.length() == 0)
+ {
+ return null;
+ }
+
+ return b.toString();
+ }
+
+ static void outputBodyPart(
+ OutputStream out,
+ BodyPart bodyPart,
+ String defaultContentTransferEncoding)
+ throws MessagingException, IOException
+ {
+ if (bodyPart instanceof MimeBodyPart)
+ {
+ MimeBodyPart mimePart = (MimeBodyPart)bodyPart;
+ String[] cte = mimePart.getHeader("Content-Transfer-Encoding");
+ String contentTransferEncoding;
+
+ if (mimePart.getContent() instanceof MimeMultipart)
+ {
+ MimeMultipart mp = (MimeMultipart)bodyPart.getContent();
+ ContentType contentType = new ContentType(mp.getContentType());
+ String boundary = "--" + contentType.getParameter("boundary");
+
+ SMIMEUtil.LineOutputStream lOut = new SMIMEUtil.LineOutputStream(out);
+
+ Enumeration headers = mimePart.getAllHeaderLines();
+ while (headers.hasMoreElements())
+ {
+ String header = (String)headers.nextElement();
+ lOut.writeln(header);
+ }
+
+ lOut.writeln(); // CRLF separator
+
+ outputPreamble(lOut, mimePart, boundary);
+
+ for (int i = 0; i < mp.getCount(); i++)
+ {
+ lOut.writeln(boundary);
+ BodyPart part = mp.getBodyPart(i);
+ outputBodyPart(out, part, defaultContentTransferEncoding);
+ if (!(part.getContent() instanceof MimeMultipart))
+ {
+ lOut.writeln(); // CRLF terminator needed
+ }
+ else
+ {
+ outputPostamble(lOut, mimePart, boundary, part);
+ }
+ }
+
+ lOut.writeln(boundary + "--");
+
+ outputPostamble(lOut, mimePart, mp.getCount(), boundary);
+
+ return;
+ }
+
+ if (cte == null)
+ {
+ contentTransferEncoding = defaultContentTransferEncoding;
+ }
+ else
+ {
+ contentTransferEncoding = cte[0];
+ }
+
+ if (!contentTransferEncoding.equalsIgnoreCase("base64")
+ && !contentTransferEncoding.equalsIgnoreCase("quoted-printable"))
+ {
+ if (!contentTransferEncoding.equalsIgnoreCase("binary"))
+ {
+ out = new CRLFOutputStream(out);
+ }
+ bodyPart.writeTo(out);
+ out.flush();
+ return;
+ }
+
+ boolean base64 = contentTransferEncoding.equalsIgnoreCase("base64");
+
+ //
+ // Write raw content, performing canonicalization
+ //
+ InputStream inRaw;
+
+ try
+ {
+ inRaw = mimePart.getRawInputStream();
+ }
+ catch (MessagingException e)
+ {
+ // this is less than ideal, but if the raw output stream is unavailable it's the
+ // best option we've got.
+ out = new CRLFOutputStream(out);
+ bodyPart.writeTo(out);
+ out.flush();
+ return;
+ }
+
+ //
+ // Write headers
+ //
+ LineOutputStream outLine = new LineOutputStream(out);
+ for (Enumeration e = mimePart.getAllHeaderLines(); e.hasMoreElements();)
+ {
+ String header = (String)e.nextElement();
+
+ outLine.writeln(header);
+ }
+
+ outLine.writeln();
+ outLine.flush();
+
+
+ OutputStream outCRLF;
+
+ if (base64)
+ {
+ outCRLF = new Base64CRLFOutputStream(out);
+ }
+ else
+ {
+ outCRLF = new CRLFOutputStream(out);
+ }
+
+ byte[] buf = new byte[BUF_SIZE];
+
+ int len;
+ while ((len = inRaw.read(buf, 0, buf.length)) > 0)
+ {
+
+ outCRLF.write(buf, 0, len);
+ }
+
+ outCRLF.flush();
+ }
+ else
+ {
+ if (!defaultContentTransferEncoding.equalsIgnoreCase("binary"))
+ {
+ out = new CRLFOutputStream(out);
+ }
+
+ bodyPart.writeTo(out);
+
+ out.flush();
+ }
+ }
+
+ /**
+ * return the MimeBodyPart described in the raw bytes provided in content
+ */
+ public static MimeBodyPart toMimeBodyPart(
+ byte[] content)
+ throws SMIMEException
+ {
+ return toMimeBodyPart(new ByteArrayInputStream(content));
+ }
+
+ /**
+ * return the MimeBodyPart described in the input stream content
+ */
+ public static MimeBodyPart toMimeBodyPart(
+ InputStream content)
+ throws SMIMEException
+ {
+ try
+ {
+ return new MimeBodyPart(content);
+ }
+ catch (MessagingException e)
+ {
+ throw new SMIMEException("exception creating body part.", e);
+ }
+ }
+
+ static FileBackedMimeBodyPart toWriteOnceBodyPart(
+ CMSTypedStream content)
+ throws SMIMEException
+ {
+ try
+ {
+ return new WriteOnceFileBackedMimeBodyPart(content.getContentStream(), File.createTempFile("bcMail", ".mime"));
+ }
+ catch (IOException e)
+ {
+ throw new SMIMEException("IOException creating tmp file:" + e.getMessage(), e);
+ }
+ catch (MessagingException e)
+ {
+ throw new SMIMEException("can't create part: " + e, e);
+ }
+ }
+
+ /**
+ * return a file backed MimeBodyPart described in {@link CMSTypedStream} content.
+ * </p>
+ */
+ public static FileBackedMimeBodyPart toMimeBodyPart(
+ CMSTypedStream content)
+ throws SMIMEException
+ {
+ try
+ {
+ return toMimeBodyPart(content, File.createTempFile("bcMail", ".mime"));
+ }
+ catch (IOException e)
+ {
+ throw new SMIMEException("IOException creating tmp file:" + e.getMessage(), e);
+ }
+ }
+
+ /**
+ * Return a file based MimeBodyPart represented by content and backed
+ * by the file represented by file.
+ *
+ * @param content content stream containing body part.
+ * @param file file to store the decoded body part in.
+ * @return the decoded body part.
+ * @throws SMIMEException
+ */
+ public static FileBackedMimeBodyPart toMimeBodyPart(
+ CMSTypedStream content,
+ File file)
+ throws SMIMEException
+ {
+ try
+ {
+ return new FileBackedMimeBodyPart(content.getContentStream(), file);
+ }
+ catch (IOException e)
+ {
+ throw new SMIMEException("can't save content to file: " + e, e);
+ }
+ catch (MessagingException e)
+ {
+ throw new SMIMEException("can't create part: " + e, e);
+ }
+ }
+
+ /**
+ * Return a CMS IssuerAndSerialNumber structure for the passed in X.509 certificate.
+ *
+ * @param cert the X.509 certificate to get the issuer and serial number for.
+ * @return an IssuerAndSerialNumber structure representing the certificate.
+ */
+ public static IssuerAndSerialNumber createIssuerAndSerialNumberFor(
+ X509Certificate cert)
+ throws CertificateParsingException
+ {
+ try
+ {
+ return new IssuerAndSerialNumber(PrincipalUtil.getIssuerX509Principal(cert), cert.getSerialNumber());
+ }
+ catch (Exception e)
+ {
+ throw new CertificateParsingException("exception extracting issuer and serial number: " + e);
+ }
+ }
+
+ private static class WriteOnceFileBackedMimeBodyPart
+ extends FileBackedMimeBodyPart
+ {
+ public WriteOnceFileBackedMimeBodyPart(InputStream content, File file)
+ throws MessagingException, IOException
+ {
+ super(content, file);
+ }
+
+ public void writeTo(OutputStream out)
+ throws MessagingException, IOException
+ {
+ super.writeTo(out);
+
+ this.dispose();
+ }
+ }
+
+ static class Base64CRLFOutputStream extends FilterOutputStream
+ {
+ protected int lastb;
+ protected static byte newline[];
+ private boolean isCrlfStream;
+
+ public Base64CRLFOutputStream(OutputStream outputstream)
+ {
+ super(outputstream);
+ lastb = -1;
+ }
+
+ public void write(int i)
+ throws IOException
+ {
+ if (i == '\r')
+ {
+ out.write(newline);
+ }
+ else if (i == '\n')
+ {
+ if (lastb != '\r')
+ { // imagine my joy...
+ if (!(isCrlfStream && lastb == '\n'))
+ {
+ out.write(newline);
+ }
+ }
+ else
+ {
+ isCrlfStream = true;
+ }
+ }
+ else
+ {
+ out.write(i);
+ }
+
+ lastb = i;
+ }
+
+ public void write(byte[] buf)
+ throws IOException
+ {
+ this.write(buf, 0, buf.length);
+ }
+
+ public void write(byte buf[], int off, int len)
+ throws IOException
+ {
+ for (int i = off; i != off + len; i++)
+ {
+ this.write(buf[i]);
+ }
+ }
+
+ public void writeln()
+ throws IOException
+ {
+ super.out.write(newline);
+ }
+
+ static
+ {
+ newline = new byte[2];
+ newline[0] = '\r';
+ newline[1] = '\n';
+ }
+ }
+}
diff --git a/mail/src/main/java/org/spongycastle/mail/smime/examples/CreateCompressedMail.java b/mail/src/main/java/org/spongycastle/mail/smime/examples/CreateCompressedMail.java
new file mode 100644
index 00000000..5d1df695
--- /dev/null
+++ b/mail/src/main/java/org/spongycastle/mail/smime/examples/CreateCompressedMail.java
@@ -0,0 +1,57 @@
+package org.bouncycastle.mail.smime.examples;
+
+import java.io.FileOutputStream;
+import java.util.Properties;
+
+import javax.mail.Address;
+import javax.mail.Message;
+import javax.mail.Session;
+import javax.mail.internet.InternetAddress;
+import javax.mail.internet.MimeBodyPart;
+import javax.mail.internet.MimeMessage;
+
+import org.bouncycastle.cms.jcajce.ZlibCompressor;
+import org.bouncycastle.mail.smime.SMIMECompressedGenerator;
+
+/**
+ * a simple example that creates a single compressed mail message.
+ */
+public class CreateCompressedMail
+{
+ public static void main(
+ String args[])
+ throws Exception
+ {
+ //
+ // create the generator for creating an smime/compressed message
+ //
+ SMIMECompressedGenerator gen = new SMIMECompressedGenerator();
+
+ //
+ // create the base for our message
+ //
+ MimeBodyPart msg = new MimeBodyPart();
+
+ msg.setText("Hello world!");
+
+ MimeBodyPart mp = gen.generate(msg, new ZlibCompressor());
+
+ //
+ // Get a Session object and create the mail message
+ //
+ Properties props = System.getProperties();
+ Session session = Session.getDefaultInstance(props, null);
+
+ Address fromUser = new InternetAddress("\"Eric H. Echidna\"<eric@bouncycastle.org>");
+ Address toUser = new InternetAddress("example@bouncycastle.org");
+
+ MimeMessage body = new MimeMessage(session);
+ body.setFrom(fromUser);
+ body.setRecipient(Message.RecipientType.TO, toUser);
+ body.setSubject("example compressed message");
+ body.setContent(mp.getContent(), mp.getContentType());
+ body.saveChanges();
+
+ body.writeTo(new FileOutputStream("compressed.message"));
+ }
+}
diff --git a/mail/src/main/java/org/spongycastle/mail/smime/examples/CreateEncryptedMail.java b/mail/src/main/java/org/spongycastle/mail/smime/examples/CreateEncryptedMail.java
new file mode 100644
index 00000000..6ef42d9d
--- /dev/null
+++ b/mail/src/main/java/org/spongycastle/mail/smime/examples/CreateEncryptedMail.java
@@ -0,0 +1,128 @@
+package org.bouncycastle.mail.smime.examples;
+
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.security.KeyStore;
+import java.security.Security;
+import java.security.cert.Certificate;
+import java.security.cert.X509Certificate;
+import java.util.Enumeration;
+import java.util.Properties;
+
+import javax.mail.Address;
+import javax.mail.Message;
+import javax.mail.Session;
+import javax.mail.internet.InternetAddress;
+import javax.mail.internet.MimeBodyPart;
+import javax.mail.internet.MimeMessage;
+
+import org.bouncycastle.cms.CMSAlgorithm;
+import org.bouncycastle.cms.jcajce.JceCMSContentEncryptorBuilder;
+import org.bouncycastle.cms.jcajce.JceKeyTransRecipientInfoGenerator;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.mail.smime.SMIMEEnvelopedGenerator;
+
+/**
+ * a simple example that creates a single encrypted mail message.
+ * <p>
+ * The key store can be created using the class in
+ * org.bouncycastle.jce.examples.PKCS12Example - the program expects only one
+ * key to be present in the key file.
+ * <p>
+ * Note: while this means that both the private key is available to
+ * the program, the private key is retrieved from the keystore only for
+ * the purposes of locating the corresponding public key, in normal circumstances
+ * you would only be doing this with a certificate available.
+ */
+public class CreateEncryptedMail
+{
+ public static void main(
+ String args[])
+ throws Exception
+ {
+ if (args.length != 2)
+ {
+ System.err.println("usage: CreateEncryptedMail pkcs12Keystore password");
+ System.exit(0);
+ }
+
+ if (Security.getProvider("BC") == null)
+ {
+ Security.addProvider(new BouncyCastleProvider());
+ }
+
+ //
+ // Open the key store
+ //
+ KeyStore ks = KeyStore.getInstance("PKCS12", "BC");
+
+ ks.load(new FileInputStream(args[0]), args[1].toCharArray());
+
+ Enumeration e = ks.aliases();
+ String keyAlias = null;
+
+ while (e.hasMoreElements())
+ {
+ String alias = (String)e.nextElement();
+
+ if (ks.isKeyEntry(alias))
+ {
+ keyAlias = alias;
+ }
+ }
+
+ if (keyAlias == null)
+ {
+ System.err.println("can't find a private key!");
+ System.exit(0);
+ }
+
+ Certificate[] chain = ks.getCertificateChain(keyAlias);
+
+ //
+ // create the generator for creating an smime/encrypted message
+ //
+ SMIMEEnvelopedGenerator gen = new SMIMEEnvelopedGenerator();
+
+ gen.addRecipientInfoGenerator(new JceKeyTransRecipientInfoGenerator((X509Certificate)chain[0]).setProvider("BC"));
+
+ //
+ // create a subject key id - this has to be done the same way as
+ // it is done in the certificate associated with the private key
+ // version 3 only.
+ //
+ /*
+ MessageDigest dig = MessageDigest.getInstance("SHA1", "BC");
+
+ dig.update(cert.getPublicKey().getEncoded());
+
+ gen.addKeyTransRecipient(cert.getPublicKey(), dig.digest());
+ */
+
+ //
+ // create the base for our message
+ //
+ MimeBodyPart msg = new MimeBodyPart();
+
+ msg.setText("Hello world!");
+
+ MimeBodyPart mp = gen.generate(msg, new JceCMSContentEncryptorBuilder(CMSAlgorithm.RC2_CBC).setProvider("BC").build());
+ //
+ // Get a Session object and create the mail message
+ //
+ Properties props = System.getProperties();
+ Session session = Session.getDefaultInstance(props, null);
+
+ Address fromUser = new InternetAddress("\"Eric H. Echidna\"<eric@bouncycastle.org>");
+ Address toUser = new InternetAddress("example@bouncycastle.org");
+
+ MimeMessage body = new MimeMessage(session);
+ body.setFrom(fromUser);
+ body.setRecipient(Message.RecipientType.TO, toUser);
+ body.setSubject("example encrypted message");
+ body.setContent(mp.getContent(), mp.getContentType());
+ body.saveChanges();
+
+ body.writeTo(new FileOutputStream("encrypted.message"));
+ }
+}
diff --git a/mail/src/main/java/org/spongycastle/mail/smime/examples/CreateLargeCompressedMail.java b/mail/src/main/java/org/spongycastle/mail/smime/examples/CreateLargeCompressedMail.java
new file mode 100644
index 00000000..63c5125d
--- /dev/null
+++ b/mail/src/main/java/org/spongycastle/mail/smime/examples/CreateLargeCompressedMail.java
@@ -0,0 +1,63 @@
+package org.bouncycastle.mail.smime.examples;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.util.Properties;
+
+import javax.activation.DataHandler;
+import javax.activation.FileDataSource;
+import javax.mail.Address;
+import javax.mail.Message;
+import javax.mail.Session;
+import javax.mail.internet.InternetAddress;
+import javax.mail.internet.MimeBodyPart;
+import javax.mail.internet.MimeMessage;
+
+import org.bouncycastle.cms.jcajce.ZlibCompressor;
+import org.bouncycastle.mail.smime.SMIMECompressedGenerator;
+
+/**
+ * a simple example that creates a single compressed mail message using the large
+ * file model.
+ */
+public class CreateLargeCompressedMail
+{
+ public static void main(
+ String args[])
+ throws Exception
+ {
+ //
+ // create the generator for creating an smime/compressed message
+ //
+ SMIMECompressedGenerator gen = new SMIMECompressedGenerator();
+
+ //
+ // create the base for our message
+ //
+ MimeBodyPart msg = new MimeBodyPart();
+
+ msg.setDataHandler(new DataHandler(new FileDataSource(new File(args[0]))));
+ msg.setHeader("Content-Type", "application/octet-stream");
+ msg.setHeader("Content-Transfer-Encoding", "binary");
+
+ MimeBodyPart mp = gen.generate(msg, new ZlibCompressor());
+
+ //
+ // Get a Session object and create the mail message
+ //
+ Properties props = System.getProperties();
+ Session session = Session.getDefaultInstance(props, null);
+
+ Address fromUser = new InternetAddress("\"Eric H. Echidna\"<eric@bouncycastle.org>");
+ Address toUser = new InternetAddress("example@bouncycastle.org");
+
+ MimeMessage body = new MimeMessage(session);
+ body.setFrom(fromUser);
+ body.setRecipient(Message.RecipientType.TO, toUser);
+ body.setSubject("example compressed message");
+ body.setContent(mp.getContent(), mp.getContentType());
+ body.saveChanges();
+
+ body.writeTo(new FileOutputStream("compressed.message"));
+ }
+}
diff --git a/mail/src/main/java/org/spongycastle/mail/smime/examples/CreateLargeEncryptedMail.java b/mail/src/main/java/org/spongycastle/mail/smime/examples/CreateLargeEncryptedMail.java
new file mode 100644
index 00000000..5fc7663a
--- /dev/null
+++ b/mail/src/main/java/org/spongycastle/mail/smime/examples/CreateLargeEncryptedMail.java
@@ -0,0 +1,105 @@
+package org.bouncycastle.mail.smime.examples;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.security.KeyStore;
+import java.security.cert.Certificate;
+import java.security.cert.X509Certificate;
+import java.util.Properties;
+
+import javax.activation.DataHandler;
+import javax.activation.FileDataSource;
+import javax.mail.Address;
+import javax.mail.Message;
+import javax.mail.Session;
+import javax.mail.internet.InternetAddress;
+import javax.mail.internet.MimeBodyPart;
+import javax.mail.internet.MimeMessage;
+
+import org.bouncycastle.cms.CMSAlgorithm;
+import org.bouncycastle.cms.jcajce.JceCMSContentEncryptorBuilder;
+import org.bouncycastle.cms.jcajce.JceKeyTransRecipientInfoGenerator;
+import org.bouncycastle.mail.smime.SMIMEEnvelopedGenerator;
+
+/**
+ * a simple example that creates a single encrypted mail message.
+ * <p>
+ * The key store can be created using the class in
+ * org.bouncycastle.jce.examples.PKCS12Example - the program expects only one
+ * key to be present in the key file.
+ * <p>
+ * Note: while this means that both the private key is available to
+ * the program, the private key is retrieved from the keystore only for
+ * the purposes of locating the corresponding public key, in normal circumstances
+ * you would only be doing this with a certificate available.
+ */
+public class CreateLargeEncryptedMail
+{
+ public static void main(
+ String args[])
+ throws Exception
+ {
+ if (args.length != 3)
+ {
+ System.err.println("usage: CreateLargeEncryptedMail pkcs12Keystore password inputFile");
+ System.exit(0);
+ }
+
+ //
+ // Open the key store
+ //
+ KeyStore ks = KeyStore.getInstance("PKCS12", "BC");
+ String keyAlias = ExampleUtils.findKeyAlias(ks, args[0], args[1].toCharArray());
+
+ Certificate[] chain = ks.getCertificateChain(keyAlias);
+
+ //
+ // create the generator for creating an smime/encrypted message
+ //
+ SMIMEEnvelopedGenerator gen = new SMIMEEnvelopedGenerator();
+
+ gen.addRecipientInfoGenerator(new JceKeyTransRecipientInfoGenerator((X509Certificate)chain[0]).setProvider("BC"));
+
+ //
+ // create a subject key id - this has to be done the same way as
+ // it is done in the certificate associated with the private key
+ // version 3 only.
+ //
+ /*
+ MessageDigest dig = MessageDigest.getInstance("SHA1", "BC");
+
+ dig.update(cert.getPublicKey().getEncoded());
+
+ gen.addKeyTransRecipient(cert.getPublicKey(), dig.digest());
+ */
+
+ //
+ // create the base for our message
+ //
+ MimeBodyPart msg = new MimeBodyPart();
+
+ msg.setDataHandler(new DataHandler(new FileDataSource(new File(args[2]))));
+ msg.setHeader("Content-Type", "application/octet-stream");
+ msg.setHeader("Content-Transfer-Encoding", "binary");
+
+ MimeBodyPart mp = gen.generate(msg, new JceCMSContentEncryptorBuilder(CMSAlgorithm.RC2_CBC).setProvider("BC").build());
+
+ //
+ // Get a Session object and create the mail message
+ //
+ Properties props = System.getProperties();
+ Session session = Session.getDefaultInstance(props, null);
+
+ Address fromUser = new InternetAddress("\"Eric H. Echidna\"<eric@bouncycastle.org>");
+ Address toUser = new InternetAddress("example@bouncycastle.org");
+
+ MimeMessage body = new MimeMessage(session);
+ body.setFrom(fromUser);
+ body.setRecipient(Message.RecipientType.TO, toUser);
+ body.setSubject("example encrypted message");
+ body.setContent(mp.getContent(), mp.getContentType());
+ body.saveChanges();
+
+ body.writeTo(new FileOutputStream("encrypted.message"));
+ }
+}
diff --git a/mail/src/main/java/org/spongycastle/mail/smime/examples/CreateLargeSignedMail.java b/mail/src/main/java/org/spongycastle/mail/smime/examples/CreateLargeSignedMail.java
new file mode 100644
index 00000000..ffb092ab
--- /dev/null
+++ b/mail/src/main/java/org/spongycastle/mail/smime/examples/CreateLargeSignedMail.java
@@ -0,0 +1,198 @@
+package org.bouncycastle.mail.smime.examples;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.math.BigInteger;
+import java.security.GeneralSecurityException;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.SecureRandom;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.Properties;
+
+import javax.activation.DataHandler;
+import javax.activation.FileDataSource;
+import javax.mail.Address;
+import javax.mail.Message;
+import javax.mail.Session;
+import javax.mail.internet.InternetAddress;
+import javax.mail.internet.MimeBodyPart;
+import javax.mail.internet.MimeMessage;
+import javax.mail.internet.MimeMultipart;
+
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.cms.AttributeTable;
+import org.bouncycastle.asn1.cms.IssuerAndSerialNumber;
+import org.bouncycastle.asn1.smime.SMIMECapabilitiesAttribute;
+import org.bouncycastle.asn1.smime.SMIMECapability;
+import org.bouncycastle.asn1.smime.SMIMECapabilityVector;
+import org.bouncycastle.asn1.smime.SMIMEEncryptionKeyPreferenceAttribute;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.asn1.x509.X509Extension;
+import org.bouncycastle.cert.X509v3CertificateBuilder;
+import org.bouncycastle.cert.jcajce.JcaCertStore;
+import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
+import org.bouncycastle.cert.jcajce.JcaX509ExtensionUtils;
+import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
+import org.bouncycastle.cms.jcajce.JcaSimpleSignerInfoGeneratorBuilder;
+import org.bouncycastle.mail.smime.SMIMESignedGenerator;
+import org.bouncycastle.operator.OperatorCreationException;
+import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
+import org.bouncycastle.util.Store;
+
+/**
+ * a simple example that creates a single signed mail message.
+ */
+public class CreateLargeSignedMail
+{
+ //
+ // certificate serial number seed.
+ //
+ static int serialNo = 1;
+
+ /**
+ * create a basic X509 certificate from the given keys
+ */
+ static X509Certificate makeCertificate(
+ KeyPair subKP,
+ String subDN,
+ KeyPair issKP,
+ String issDN)
+ throws GeneralSecurityException, IOException, OperatorCreationException
+ {
+ PublicKey subPub = subKP.getPublic();
+ PrivateKey issPriv = issKP.getPrivate();
+ PublicKey issPub = issKP.getPublic();
+
+ JcaX509ExtensionUtils extUtils = new JcaX509ExtensionUtils();
+ X509v3CertificateBuilder v3CertGen = new JcaX509v3CertificateBuilder(new X500Name(issDN), BigInteger.valueOf(serialNo++), new Date(System.currentTimeMillis()), new Date(System.currentTimeMillis() + (1000L * 60 * 60 * 24 * 100)), new X500Name(subDN), subPub);
+
+ v3CertGen.addExtension(
+ X509Extension.subjectKeyIdentifier,
+ false,
+ extUtils.createSubjectKeyIdentifier(subPub));
+
+ v3CertGen.addExtension(
+ X509Extension.authorityKeyIdentifier,
+ false,
+ extUtils.createAuthorityKeyIdentifier(issPub));
+
+ return new JcaX509CertificateConverter().setProvider("BC").getCertificate(v3CertGen.build(new JcaContentSignerBuilder("MD5withRSA").setProvider("BC").build(issPriv)));
+ }
+
+ public static void main(
+ String args[])
+ throws Exception
+ {
+ //
+ // set up our certs
+ //
+ KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA", "BC");
+
+ kpg.initialize(1024, new SecureRandom());
+
+ //
+ // cert that issued the signing certificate
+ //
+ String signDN = "O=Bouncy Castle, C=AU";
+ KeyPair signKP = kpg.generateKeyPair();
+ X509Certificate signCert = makeCertificate(
+ signKP, signDN, signKP, signDN);
+
+ //
+ // cert we sign against
+ //
+ String origDN = "CN=Eric H. Echidna, E=eric@bouncycastle.org, O=Bouncy Castle, C=AU";
+ KeyPair origKP = kpg.generateKeyPair();
+ X509Certificate origCert = makeCertificate(
+ origKP, origDN, signKP, signDN);
+
+ List certList = new ArrayList();
+
+ certList.add(origCert);
+ certList.add(signCert);
+
+ //
+ // create a CertStore containing the certificates we want carried
+ // in the signature
+ //
+ Store certs = new JcaCertStore(certList);
+
+ //
+ // create some smime capabilities in case someone wants to respond
+ //
+ ASN1EncodableVector signedAttrs = new ASN1EncodableVector();
+ SMIMECapabilityVector caps = new SMIMECapabilityVector();
+
+ caps.addCapability(SMIMECapability.dES_EDE3_CBC);
+ caps.addCapability(SMIMECapability.rC2_CBC, 128);
+ caps.addCapability(SMIMECapability.dES_CBC);
+
+ signedAttrs.add(new SMIMECapabilitiesAttribute(caps));
+
+ //
+ // add an encryption key preference for encrypted responses -
+ // normally this would be different from the signing certificate...
+ //
+ IssuerAndSerialNumber issAndSer = new IssuerAndSerialNumber(
+ new X500Name(signDN), origCert.getSerialNumber());
+
+ signedAttrs.add(new SMIMEEncryptionKeyPreferenceAttribute(issAndSer));
+
+ //
+ // create the generator for creating an smime/signed message
+ //
+ SMIMESignedGenerator gen = new SMIMESignedGenerator();
+
+ //
+ // add a signer to the generator - this specifies we are using SHA1 and
+ // adding the smime attributes above to the signed attributes that
+ // will be generated as part of the signature. The encryption algorithm
+ // used is taken from the key - in this RSA with PKCS1Padding
+ //
+ gen.addSignerInfoGenerator(new JcaSimpleSignerInfoGeneratorBuilder().setProvider("BC").setSignedAttributeGenerator(new AttributeTable(signedAttrs)).build("SHA1withRSA", origKP.getPrivate(), origCert));
+
+ //
+ // add our pool of certs and cerls (if any) to go with the signature
+ //
+ gen.addCertificates(certs);
+
+ //
+ // create the base for our message
+ //
+ MimeBodyPart msg = new MimeBodyPart();
+
+ msg.setDataHandler(new DataHandler(new FileDataSource(new File(args[0]))));
+ msg.setHeader("Content-Type", "application/octet-stream");
+ msg.setHeader("Content-Transfer-Encoding", "base64");
+
+ //
+ // extract the multipart object from the SMIMESigned object.
+ //
+ MimeMultipart mm = gen.generate(msg);
+
+ //
+ // Get a Session object and create the mail message
+ //
+ Properties props = System.getProperties();
+ Session session = Session.getDefaultInstance(props, null);
+
+ Address fromUser = new InternetAddress("\"Eric H. Echidna\"<eric@bouncycastle.org>");
+ Address toUser = new InternetAddress("example@bouncycastle.org");
+
+ MimeMessage body = new MimeMessage(session);
+ body.setFrom(fromUser);
+ body.setRecipient(Message.RecipientType.TO, toUser);
+ body.setSubject("example signed message");
+ body.setContent(mm, mm.getContentType());
+ body.saveChanges();
+
+ body.writeTo(new FileOutputStream("signed.message"));
+ }
+}
diff --git a/mail/src/main/java/org/spongycastle/mail/smime/examples/CreateSignedMail.java b/mail/src/main/java/org/spongycastle/mail/smime/examples/CreateSignedMail.java
new file mode 100644
index 00000000..8f3fb787
--- /dev/null
+++ b/mail/src/main/java/org/spongycastle/mail/smime/examples/CreateSignedMail.java
@@ -0,0 +1,221 @@
+package org.bouncycastle.mail.smime.examples;
+
+import java.io.ByteArrayInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.math.BigInteger;
+import java.security.GeneralSecurityException;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.SecureRandom;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.Properties;
+
+import javax.mail.Address;
+import javax.mail.Message;
+import javax.mail.Session;
+import javax.mail.internet.InternetAddress;
+import javax.mail.internet.MimeBodyPart;
+import javax.mail.internet.MimeMessage;
+import javax.mail.internet.MimeMultipart;
+
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.cms.AttributeTable;
+import org.bouncycastle.asn1.cms.IssuerAndSerialNumber;
+import org.bouncycastle.asn1.smime.SMIMECapabilitiesAttribute;
+import org.bouncycastle.asn1.smime.SMIMECapability;
+import org.bouncycastle.asn1.smime.SMIMECapabilityVector;
+import org.bouncycastle.asn1.smime.SMIMEEncryptionKeyPreferenceAttribute;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.asn1.x509.AuthorityKeyIdentifier;
+import org.bouncycastle.asn1.x509.SubjectKeyIdentifier;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.asn1.x509.X509Extension;
+import org.bouncycastle.cert.X509v3CertificateBuilder;
+import org.bouncycastle.cert.bc.BcX509ExtensionUtils;
+import org.bouncycastle.cert.jcajce.JcaCertStore;
+import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
+import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
+import org.bouncycastle.cms.jcajce.JcaSimpleSignerInfoGeneratorBuilder;
+import org.bouncycastle.mail.smime.SMIMESignedGenerator;
+import org.bouncycastle.operator.OperatorCreationException;
+import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
+import org.bouncycastle.util.Store;
+
+/**
+ * a simple example that creates a single signed mail message.
+ */
+public class CreateSignedMail
+{
+ //
+ // certificate serial number seed.
+ //
+ static int serialNo = 1;
+
+ static AuthorityKeyIdentifier createAuthorityKeyId(
+ PublicKey pub)
+ throws IOException
+ {
+ ByteArrayInputStream bIn = new ByteArrayInputStream(pub.getEncoded());
+ SubjectPublicKeyInfo info = new SubjectPublicKeyInfo(
+ (ASN1Sequence)new ASN1InputStream(bIn).readObject());
+
+ return new AuthorityKeyIdentifier(info);
+ }
+
+ static SubjectKeyIdentifier createSubjectKeyId(
+ PublicKey pub)
+ throws IOException
+ {
+ ByteArrayInputStream bIn = new ByteArrayInputStream(pub.getEncoded());
+
+ SubjectPublicKeyInfo info = new SubjectPublicKeyInfo(
+ (ASN1Sequence)new ASN1InputStream(bIn).readObject());
+
+ return new BcX509ExtensionUtils().createSubjectKeyIdentifier(info);
+ }
+
+ /**
+ * create a basic X509 certificate from the given keys
+ */
+ static X509Certificate makeCertificate(
+ KeyPair subKP,
+ String subDN,
+ KeyPair issKP,
+ String issDN)
+ throws GeneralSecurityException, IOException, OperatorCreationException
+ {
+ PublicKey subPub = subKP.getPublic();
+ PrivateKey issPriv = issKP.getPrivate();
+ PublicKey issPub = issKP.getPublic();
+
+ X509v3CertificateBuilder v3CertGen = new JcaX509v3CertificateBuilder(new X500Name(issDN), BigInteger.valueOf(serialNo++), new Date(System.currentTimeMillis()), new Date(System.currentTimeMillis() + (1000L * 60 * 60 * 24 * 100)), new X500Name(subDN), subPub);
+
+ v3CertGen.addExtension(
+ X509Extension.subjectKeyIdentifier,
+ false,
+ createSubjectKeyId(subPub));
+
+ v3CertGen.addExtension(
+ X509Extension.authorityKeyIdentifier,
+ false,
+ createAuthorityKeyId(issPub));
+
+ return new JcaX509CertificateConverter().setProvider("BC").getCertificate(v3CertGen.build(new JcaContentSignerBuilder("MD5withRSA").setProvider("BC").build(issPriv)));
+ }
+
+ public static void main(
+ String args[])
+ throws Exception
+ {
+ //
+ // set up our certs
+ //
+ KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA", "BC");
+
+ kpg.initialize(1024, new SecureRandom());
+
+ //
+ // cert that issued the signing certificate
+ //
+ String signDN = "O=Bouncy Castle, C=AU";
+ KeyPair signKP = kpg.generateKeyPair();
+ X509Certificate signCert = makeCertificate(
+ signKP, signDN, signKP, signDN);
+
+ //
+ // cert we sign against
+ //
+ String origDN = "CN=Eric H. Echidna, E=eric@bouncycastle.org, O=Bouncy Castle, C=AU";
+ KeyPair origKP = kpg.generateKeyPair();
+ X509Certificate origCert = makeCertificate(
+ origKP, origDN, signKP, signDN);
+
+ List certList = new ArrayList();
+
+ certList.add(origCert);
+ certList.add(signCert);
+
+ //
+ // create a CertStore containing the certificates we want carried
+ // in the signature
+ //
+ Store certs = new JcaCertStore(certList);
+
+ //
+ // create some smime capabilities in case someone wants to respond
+ //
+ ASN1EncodableVector signedAttrs = new ASN1EncodableVector();
+ SMIMECapabilityVector caps = new SMIMECapabilityVector();
+
+ caps.addCapability(SMIMECapability.dES_EDE3_CBC);
+ caps.addCapability(SMIMECapability.rC2_CBC, 128);
+ caps.addCapability(SMIMECapability.dES_CBC);
+
+ signedAttrs.add(new SMIMECapabilitiesAttribute(caps));
+
+ //
+ // add an encryption key preference for encrypted responses -
+ // normally this would be different from the signing certificate...
+ //
+ IssuerAndSerialNumber issAndSer = new IssuerAndSerialNumber(
+ new X500Name(signDN), origCert.getSerialNumber());
+
+ signedAttrs.add(new SMIMEEncryptionKeyPreferenceAttribute(issAndSer));
+
+ //
+ // create the generator for creating an smime/signed message
+ //
+ SMIMESignedGenerator gen = new SMIMESignedGenerator();
+
+ //
+ // add a signer to the generator - this specifies we are using SHA1 and
+ // adding the smime attributes above to the signed attributes that
+ // will be generated as part of the signature. The encryption algorithm
+ // used is taken from the key - in this RSA with PKCS1Padding
+ //
+ gen.addSignerInfoGenerator(new JcaSimpleSignerInfoGeneratorBuilder().setProvider("BC").setSignedAttributeGenerator(new AttributeTable(signedAttrs)).build("SHA1withRSA", origKP.getPrivate(), origCert));
+
+ //
+ // add our pool of certs and cerls (if any) to go with the signature
+ //
+ gen.addCertificates(certs);
+
+ //
+ // create the base for our message
+ //
+ MimeBodyPart msg = new MimeBodyPart();
+
+ msg.setText("Hello world!");
+
+ //
+ // extract the multipart object from the SMIMESigned object.
+ //
+ MimeMultipart mm = gen.generate(msg);
+
+ //
+ // Get a Session object and create the mail message
+ //
+ Properties props = System.getProperties();
+ Session session = Session.getDefaultInstance(props, null);
+
+ Address fromUser = new InternetAddress("\"Eric H. Echidna\"<eric@bouncycastle.org>");
+ Address toUser = new InternetAddress("example@bouncycastle.org");
+
+ MimeMessage body = new MimeMessage(session);
+ body.setFrom(fromUser);
+ body.setRecipient(Message.RecipientType.TO, toUser);
+ body.setSubject("example signed message");
+ body.setContent(mm, mm.getContentType());
+ body.saveChanges();
+
+ body.writeTo(new FileOutputStream("signed.message"));
+ }
+}
diff --git a/mail/src/main/java/org/spongycastle/mail/smime/examples/CreateSignedMultipartMail.java b/mail/src/main/java/org/spongycastle/mail/smime/examples/CreateSignedMultipartMail.java
new file mode 100644
index 00000000..20d1b3ea
--- /dev/null
+++ b/mail/src/main/java/org/spongycastle/mail/smime/examples/CreateSignedMultipartMail.java
@@ -0,0 +1,213 @@
+package org.bouncycastle.mail.smime.examples;
+
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.math.BigInteger;
+import java.security.GeneralSecurityException;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.SecureRandom;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.Properties;
+
+import javax.mail.Address;
+import javax.mail.Message;
+import javax.mail.Session;
+import javax.mail.internet.InternetAddress;
+import javax.mail.internet.MimeBodyPart;
+import javax.mail.internet.MimeMessage;
+import javax.mail.internet.MimeMultipart;
+
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.cms.AttributeTable;
+import org.bouncycastle.asn1.cms.IssuerAndSerialNumber;
+import org.bouncycastle.asn1.smime.SMIMECapabilitiesAttribute;
+import org.bouncycastle.asn1.smime.SMIMECapability;
+import org.bouncycastle.asn1.smime.SMIMECapabilityVector;
+import org.bouncycastle.asn1.smime.SMIMEEncryptionKeyPreferenceAttribute;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.asn1.x509.X509Extension;
+import org.bouncycastle.cert.X509v3CertificateBuilder;
+import org.bouncycastle.cert.jcajce.JcaCertStore;
+import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
+import org.bouncycastle.cert.jcajce.JcaX509ExtensionUtils;
+import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
+import org.bouncycastle.cms.jcajce.JcaSimpleSignerInfoGeneratorBuilder;
+import org.bouncycastle.mail.smime.SMIMESignedGenerator;
+import org.bouncycastle.operator.OperatorCreationException;
+import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
+import org.bouncycastle.util.Store;
+
+/**
+ * a simple example that creates a single signed multipart mail message.
+ */
+public class CreateSignedMultipartMail
+{
+ //
+ // certificate serial number seed.
+ //
+ static int serialNo = 1;
+
+ /**
+ * create a basic X509 certificate from the given keys
+ */
+ static X509Certificate makeCertificate(
+ KeyPair subKP,
+ String subDN,
+ KeyPair issKP,
+ String issDN)
+ throws GeneralSecurityException, IOException, OperatorCreationException
+ {
+ PublicKey subPub = subKP.getPublic();
+ PrivateKey issPriv = issKP.getPrivate();
+ PublicKey issPub = issKP.getPublic();
+
+ JcaX509ExtensionUtils extUtils = new JcaX509ExtensionUtils();
+ X509v3CertificateBuilder v3CertGen = new JcaX509v3CertificateBuilder(new X500Name(issDN), BigInteger.valueOf(serialNo++), new Date(System.currentTimeMillis()), new Date(System.currentTimeMillis() + (1000L * 60 * 60 * 24 * 100)), new X500Name(subDN), subPub);
+
+ v3CertGen.addExtension(
+ X509Extension.subjectKeyIdentifier,
+ false,
+ extUtils.createSubjectKeyIdentifier(subPub));
+
+ v3CertGen.addExtension(
+ X509Extension.authorityKeyIdentifier,
+ false,
+ extUtils.createAuthorityKeyIdentifier(issPub));
+
+ return new JcaX509CertificateConverter().setProvider("BC").getCertificate(v3CertGen.build(new JcaContentSignerBuilder("MD5withRSA").setProvider("BC").build(issPriv)));
+ }
+
+ public static void main(
+ String args[])
+ throws Exception
+ {
+ //
+ // set up our certs
+ //
+ KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA", "BC");
+
+ kpg.initialize(1024, new SecureRandom());
+
+ //
+ // cert that issued the signing certificate
+ //
+ String signDN = "O=Bouncy Castle, C=AU";
+ KeyPair signKP = kpg.generateKeyPair();
+ X509Certificate signCert = makeCertificate(
+ signKP, signDN, signKP, signDN);
+
+ //
+ // cert we sign against
+ //
+ String origDN = "CN=Eric H. Echidna, E=eric@bouncycastle.org, O=Bouncy Castle, C=AU";
+ KeyPair origKP = kpg.generateKeyPair();
+ X509Certificate origCert = makeCertificate(
+ origKP, origDN, signKP, signDN);
+
+ List certList = new ArrayList();
+
+ certList.add(origCert);
+ certList.add(signCert);
+
+ //
+ // create a CertStore containing the certificates we want carried
+ // in the signature
+ //
+ Store certs = new JcaCertStore(certList);
+
+ //
+ // create some smime capabilities in case someone wants to respond
+ //
+ ASN1EncodableVector signedAttrs = new ASN1EncodableVector();
+ SMIMECapabilityVector caps = new SMIMECapabilityVector();
+
+ caps.addCapability(SMIMECapability.dES_EDE3_CBC);
+ caps.addCapability(SMIMECapability.rC2_CBC, 128);
+ caps.addCapability(SMIMECapability.dES_CBC);
+
+ signedAttrs.add(new SMIMECapabilitiesAttribute(caps));
+
+ //
+ // add an encryption key preference for encrypted responses -
+ // normally this would be different from the signing certificate...
+ //
+ IssuerAndSerialNumber issAndSer = new IssuerAndSerialNumber(
+ new X500Name(signDN), origCert.getSerialNumber());
+
+ signedAttrs.add(new SMIMEEncryptionKeyPreferenceAttribute(issAndSer));
+
+ //
+ // create the generator for creating an smime/signed message
+ //
+ SMIMESignedGenerator gen = new SMIMESignedGenerator();
+
+ //
+ // add a signer to the generator - this specifies we are using SHA1 and
+ // adding the smime attributes above to the signed attributes that
+ // will be generated as part of the signature. The encryption algorithm
+ // used is taken from the key - in this RSA with PKCS1Padding
+ //
+ gen.addSignerInfoGenerator(new JcaSimpleSignerInfoGeneratorBuilder().setProvider("BC").setSignedAttributeGenerator(new AttributeTable(signedAttrs)).build("SHA1withRSA", origKP.getPrivate(), origCert));
+
+ //
+ // add our pool of certs and cerls (if any) to go with the signature
+ //
+ gen.addCertificates(certs);
+
+ //
+ // create the base for our message
+ //
+ MimeBodyPart msg1 = new MimeBodyPart();
+
+ msg1.setText("Hello part 1!");
+
+ MimeBodyPart msg2 = new MimeBodyPart();
+
+ msg2.setText("Hello part 2!");
+
+ MimeMultipart mp = new MimeMultipart();
+
+ mp.addBodyPart(msg1);
+ mp.addBodyPart(msg2);
+
+ MimeBodyPart m = new MimeBodyPart();
+
+ //
+ // be careful about setting extra headers here. Some mail clients
+ // ignore the To and From fields (for example) in the body part
+ // that contains the multipart. The result of this will be that the
+ // signature fails to verify... Outlook Express is an example of
+ // a client that exhibits this behaviour.
+ //
+ m.setContent(mp);
+
+ //
+ // extract the multipart object from the SMIMESigned object.
+ //
+ MimeMultipart mm = gen.generate(m);
+
+ //
+ // Get a Session object and create the mail message
+ //
+ Properties props = System.getProperties();
+ Session session = Session.getDefaultInstance(props, null);
+
+ Address fromUser = new InternetAddress("\"Eric H. Echidna\"<eric@bouncycastle.org>");
+ Address toUser = new InternetAddress("example@bouncycastle.org");
+
+ MimeMessage body = new MimeMessage(session);
+ body.setFrom(fromUser);
+ body.setRecipient(Message.RecipientType.TO, toUser);
+ body.setSubject("example signed message");
+ body.setContent(mm, mm.getContentType());
+ body.saveChanges();
+
+ body.writeTo(new FileOutputStream("signed.message"));
+ }
+}
diff --git a/mail/src/main/java/org/spongycastle/mail/smime/examples/ExampleUtils.java b/mail/src/main/java/org/spongycastle/mail/smime/examples/ExampleUtils.java
new file mode 100644
index 00000000..10c0f06c
--- /dev/null
+++ b/mail/src/main/java/org/spongycastle/mail/smime/examples/ExampleUtils.java
@@ -0,0 +1,77 @@
+package org.bouncycastle.mail.smime.examples;
+
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.security.KeyStore;
+import java.util.Enumeration;
+
+import javax.mail.MessagingException;
+import javax.mail.internet.MimeBodyPart;
+
+public class ExampleUtils
+{
+ /**
+ * Dump the content of the passed in BodyPart to the file fileName.
+ *
+ * @throws MessagingException
+ * @throws IOException
+ */
+ public static void dumpContent(
+ MimeBodyPart bodyPart,
+ String fileName)
+ throws MessagingException, IOException
+ {
+ //
+ // print mime type of compressed content
+ //
+ System.out.println("content type: " + bodyPart.getContentType());
+
+ //
+ // recover the compressed content
+ //
+ OutputStream out = new FileOutputStream(fileName);
+ InputStream in = bodyPart.getInputStream();
+
+ byte[] buf = new byte[10000];
+ int len;
+
+ while ((len = in.read(buf, 0, buf.length)) > 0)
+ {
+ out.write(buf, 0, len);
+ }
+
+ out.close();
+ }
+
+ public static String findKeyAlias(
+ KeyStore store,
+ String storeName,
+ char[] password)
+ throws Exception
+ {
+ store.load(new FileInputStream(storeName), password);
+
+ Enumeration e = store.aliases();
+ String keyAlias = null;
+
+ while (e.hasMoreElements())
+ {
+ String alias = (String)e.nextElement();
+
+ if (store.isKeyEntry(alias))
+ {
+ keyAlias = alias;
+ }
+ }
+
+ if (keyAlias == null)
+ {
+ throw new IllegalArgumentException("can't find a private key in keyStore: " + storeName);
+ }
+
+ return keyAlias;
+ }
+}
diff --git a/mail/src/main/java/org/spongycastle/mail/smime/examples/ReadCompressedMail.java b/mail/src/main/java/org/spongycastle/mail/smime/examples/ReadCompressedMail.java
new file mode 100644
index 00000000..b462b336
--- /dev/null
+++ b/mail/src/main/java/org/spongycastle/mail/smime/examples/ReadCompressedMail.java
@@ -0,0 +1,41 @@
+package org.bouncycastle.mail.smime.examples;
+
+import java.io.FileInputStream;
+import java.util.Properties;
+
+import javax.mail.Session;
+import javax.mail.internet.MimeBodyPart;
+import javax.mail.internet.MimeMessage;
+
+import org.bouncycastle.cms.jcajce.ZlibExpanderProvider;
+import org.bouncycastle.mail.smime.SMIMECompressed;
+import org.bouncycastle.mail.smime.SMIMEUtil;
+
+/**
+ * a simple example that reads a compressed email.
+ * <p>
+ */
+public class ReadCompressedMail
+{
+ public static void main(
+ String args[])
+ throws Exception
+ {
+ //
+ // Get a Session object with the default properties.
+ //
+ Properties props = System.getProperties();
+
+ Session session = Session.getDefaultInstance(props, null);
+
+ MimeMessage msg = new MimeMessage(session, new FileInputStream("compressed.message"));
+
+ SMIMECompressed m = new SMIMECompressed(msg);
+
+ MimeBodyPart res = SMIMEUtil.toMimeBodyPart(m.getContent(new ZlibExpanderProvider()));
+
+ System.out.println("Message Contents");
+ System.out.println("----------------");
+ System.out.println(res.getContent());
+ }
+}
diff --git a/mail/src/main/java/org/spongycastle/mail/smime/examples/ReadEncryptedMail.java b/mail/src/main/java/org/spongycastle/mail/smime/examples/ReadEncryptedMail.java
new file mode 100644
index 00000000..a180994f
--- /dev/null
+++ b/mail/src/main/java/org/spongycastle/mail/smime/examples/ReadEncryptedMail.java
@@ -0,0 +1,94 @@
+package org.bouncycastle.mail.smime.examples;
+
+import java.io.FileInputStream;
+import java.security.KeyStore;
+import java.security.PrivateKey;
+import java.security.cert.X509Certificate;
+import java.util.Enumeration;
+import java.util.Properties;
+
+import javax.mail.Session;
+import javax.mail.internet.MimeBodyPart;
+import javax.mail.internet.MimeMessage;
+
+import org.bouncycastle.cms.RecipientId;
+import org.bouncycastle.cms.RecipientInformation;
+import org.bouncycastle.cms.RecipientInformationStore;
+import org.bouncycastle.cms.jcajce.JceKeyTransEnvelopedRecipient;
+import org.bouncycastle.cms.jcajce.JceKeyTransRecipientId;
+import org.bouncycastle.mail.smime.SMIMEEnveloped;
+import org.bouncycastle.mail.smime.SMIMEUtil;
+
+/**
+ * a simple example that reads an encrypted email.
+ * <p>
+ * The key store can be created using the class in
+ * org.bouncycastle.jce.examples.PKCS12Example - the program expects only one
+ * key to be present.
+ */
+public class ReadEncryptedMail
+{
+ public static void main(
+ String args[])
+ throws Exception
+ {
+ if (args.length != 2)
+ {
+ System.err.println("usage: ReadEncryptedMail pkcs12Keystore password");
+ System.exit(0);
+ }
+
+ //
+ // Open the key store
+ //
+ KeyStore ks = KeyStore.getInstance("PKCS12", "BC");
+
+ ks.load(new FileInputStream(args[0]), args[1].toCharArray());
+
+ Enumeration e = ks.aliases();
+ String keyAlias = null;
+
+ while (e.hasMoreElements())
+ {
+ String alias = (String)e.nextElement();
+
+ if (ks.isKeyEntry(alias))
+ {
+ keyAlias = alias;
+ }
+ }
+
+ if (keyAlias == null)
+ {
+ System.err.println("can't find a private key!");
+ System.exit(0);
+ }
+
+ //
+ // find the certificate for the private key and generate a
+ // suitable recipient identifier.
+ //
+ X509Certificate cert = (X509Certificate)ks.getCertificate(keyAlias);
+ RecipientId recId = new JceKeyTransRecipientId(cert);
+
+ //
+ // Get a Session object with the default properties.
+ //
+ Properties props = System.getProperties();
+
+ Session session = Session.getDefaultInstance(props, null);
+
+ MimeMessage msg = new MimeMessage(session, new FileInputStream("encrypted.message"));
+
+ SMIMEEnveloped m = new SMIMEEnveloped(msg);
+
+ RecipientInformationStore recipients = m.getRecipientInfos();
+ RecipientInformation recipient = recipients.get(recId);
+
+ MimeBodyPart res = SMIMEUtil.toMimeBodyPart(recipient.getContent(new JceKeyTransEnvelopedRecipient((PrivateKey)ks.getKey(keyAlias, null)).setProvider("BC")));
+
+ System.out.println("Message Contents");
+ System.out.println("----------------");
+ System.out.println(res.getContent());
+ }
+}
diff --git a/mail/src/main/java/org/spongycastle/mail/smime/examples/ReadLargeCompressedMail.java b/mail/src/main/java/org/spongycastle/mail/smime/examples/ReadLargeCompressedMail.java
new file mode 100644
index 00000000..795d0497
--- /dev/null
+++ b/mail/src/main/java/org/spongycastle/mail/smime/examples/ReadLargeCompressedMail.java
@@ -0,0 +1,38 @@
+package org.bouncycastle.mail.smime.examples;
+
+import java.util.Properties;
+
+import javax.mail.Session;
+import javax.mail.internet.MimeBodyPart;
+import javax.mail.internet.MimeMessage;
+
+import org.bouncycastle.cms.jcajce.ZlibExpanderProvider;
+import org.bouncycastle.mail.smime.SMIMECompressedParser;
+import org.bouncycastle.mail.smime.SMIMEUtil;
+import org.bouncycastle.mail.smime.util.SharedFileInputStream;
+
+/**
+ * a simple example that reads an oversize compressed email and writes data contained
+ * in the compressed part into a file.
+ */
+public class ReadLargeCompressedMail
+{
+ public static void main(
+ String args[])
+ throws Exception
+ {
+ //
+ // Get a Session object with the default properties.
+ //
+ Properties props = System.getProperties();
+
+ Session session = Session.getDefaultInstance(props, null);
+
+ MimeMessage msg = new MimeMessage(session, new SharedFileInputStream("compressed.message"));
+
+ SMIMECompressedParser m = new SMIMECompressedParser(msg);
+ MimeBodyPart res = SMIMEUtil.toMimeBodyPart(m.getContent(new ZlibExpanderProvider()));
+
+ ExampleUtils.dumpContent(res, args[0]);
+ }
+}
diff --git a/mail/src/main/java/org/spongycastle/mail/smime/examples/ReadLargeEncryptedMail.java b/mail/src/main/java/org/spongycastle/mail/smime/examples/ReadLargeEncryptedMail.java
new file mode 100644
index 00000000..8389b443
--- /dev/null
+++ b/mail/src/main/java/org/spongycastle/mail/smime/examples/ReadLargeEncryptedMail.java
@@ -0,0 +1,71 @@
+package org.bouncycastle.mail.smime.examples;
+
+import java.security.KeyStore;
+import java.security.PrivateKey;
+import java.security.cert.X509Certificate;
+import java.util.Properties;
+
+import javax.mail.Session;
+import javax.mail.internet.MimeBodyPart;
+import javax.mail.internet.MimeMessage;
+
+import org.bouncycastle.cms.RecipientId;
+import org.bouncycastle.cms.RecipientInformation;
+import org.bouncycastle.cms.RecipientInformationStore;
+import org.bouncycastle.cms.jcajce.JceKeyTransEnvelopedRecipient;
+import org.bouncycastle.cms.jcajce.JceKeyTransRecipientId;
+import org.bouncycastle.mail.smime.SMIMEEnvelopedParser;
+import org.bouncycastle.mail.smime.SMIMEUtil;
+import org.bouncycastle.mail.smime.util.SharedFileInputStream;
+
+/**
+ * a simple example that reads an encrypted email using the large file model.
+ * <p>
+ * The key store can be created using the class in
+ * org.bouncycastle.jce.examples.PKCS12Example - the program expects only one
+ * key to be present.
+ */
+public class ReadLargeEncryptedMail
+{
+ public static void main(
+ String args[])
+ throws Exception
+ {
+ if (args.length != 3)
+ {
+ System.err.println("usage: ReadLargeEncryptedMail pkcs12Keystore password outputFile");
+ System.exit(0);
+ }
+
+ //
+ // Open the key store
+ //
+ KeyStore ks = KeyStore.getInstance("PKCS12", "BC");
+ String keyAlias = ExampleUtils.findKeyAlias(ks, args[0], args[1].toCharArray());
+
+ //
+ // find the certificate for the private key and generate a
+ // suitable recipient identifier.
+ //
+ X509Certificate cert = (X509Certificate)ks.getCertificate(keyAlias);
+ RecipientId recId = new JceKeyTransRecipientId(cert);
+
+ //
+ // Get a Session object with the default properties.
+ //
+ Properties props = System.getProperties();
+
+ Session session = Session.getDefaultInstance(props, null);
+
+ MimeMessage msg = new MimeMessage(session, new SharedFileInputStream("encrypted.message"));
+
+ SMIMEEnvelopedParser m = new SMIMEEnvelopedParser(msg);
+
+ RecipientInformationStore recipients = m.getRecipientInfos();
+ RecipientInformation recipient = recipients.get(recId);
+
+ MimeBodyPart res = SMIMEUtil.toMimeBodyPart(recipient.getContentStream(new JceKeyTransEnvelopedRecipient((PrivateKey)ks.getKey(keyAlias, null)).setProvider("BC")));
+
+ ExampleUtils.dumpContent(res, args[2]);
+ }
+}
diff --git a/mail/src/main/java/org/spongycastle/mail/smime/examples/ReadLargeSignedMail.java b/mail/src/main/java/org/spongycastle/mail/smime/examples/ReadLargeSignedMail.java
new file mode 100644
index 00000000..91074337
--- /dev/null
+++ b/mail/src/main/java/org/spongycastle/mail/smime/examples/ReadLargeSignedMail.java
@@ -0,0 +1,125 @@
+package org.bouncycastle.mail.smime.examples;
+
+import java.security.cert.X509Certificate;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.Properties;
+
+import javax.mail.Session;
+import javax.mail.internet.MimeMessage;
+import javax.mail.internet.MimeMultipart;
+
+import org.bouncycastle.cert.X509CertificateHolder;
+import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
+import org.bouncycastle.cms.SignerInformation;
+import org.bouncycastle.cms.SignerInformationStore;
+import org.bouncycastle.cms.jcajce.JcaSimpleSignerInfoVerifierBuilder;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.mail.smime.SMIMESignedParser;
+import org.bouncycastle.mail.smime.util.SharedFileInputStream;
+import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder;
+import org.bouncycastle.util.Store;
+
+/**
+ * a simple example that reads a basic SMIME signed mail file.
+ */
+public class ReadLargeSignedMail
+{
+ private static final String BC = BouncyCastleProvider.PROVIDER_NAME;
+
+ /**
+ * verify the signature (assuming the cert is contained in the message)
+ */
+ private static void verify(
+ SMIMESignedParser s)
+ throws Exception
+ {
+ //
+ // extract the information to verify the signatures.
+ //
+
+ //
+ // certificates and crls passed in the signature - this must happen before
+ // s.getSignerInfos()
+ //
+ Store certs = s.getCertificates();
+
+ //
+ // SignerInfo blocks which contain the signatures
+ //
+ SignerInformationStore signers = s.getSignerInfos();
+
+ Collection c = signers.getSigners();
+ Iterator it = c.iterator();
+
+ //
+ // check each signer
+ //
+ while (it.hasNext())
+ {
+ SignerInformation signer = (SignerInformation)it.next();
+ Collection certCollection = certs.getMatches(signer.getSID());
+
+ Iterator certIt = certCollection.iterator();
+ X509Certificate cert = new JcaX509CertificateConverter().setProvider(BC).getCertificate((X509CertificateHolder)certIt.next());
+
+
+ //
+ // verify that the sig is correct and that it was generated
+ // when the certificate was current
+ //
+ if (signer.verify(new JcaSimpleSignerInfoVerifierBuilder().setProvider(BC).build(cert)))
+ {
+ System.out.println("signature verified");
+ }
+ else
+ {
+ System.out.println("signature failed!");
+ }
+ }
+ }
+
+ public static void main(
+ String[] args)
+ throws Exception
+ {
+ //
+ // Get a Session object with the default properties.
+ //
+ Properties props = System.getProperties();
+
+ Session session = Session.getDefaultInstance(props, null);
+
+ MimeMessage msg = new MimeMessage(session, new SharedFileInputStream("signed.message"));
+
+ //
+ // make sure this was a multipart/signed message - there should be
+ // two parts as we have one part for the content that was signed and
+ // one part for the actual signature.
+ //
+ if (msg.isMimeType("multipart/signed"))
+ {
+ SMIMESignedParser s = new SMIMESignedParser(new JcaDigestCalculatorProviderBuilder().build(),
+ (MimeMultipart)msg.getContent());
+
+ System.out.println("Status:");
+
+ verify(s);
+ }
+ else if (msg.isMimeType("application/pkcs7-mime"))
+ {
+ //
+ // in this case the content is wrapped in the signature block.
+ //
+ SMIMESignedParser s = new SMIMESignedParser(new JcaDigestCalculatorProviderBuilder().build(), msg);
+
+ System.out.println("Status:");
+
+ verify(s);
+ }
+ else
+ {
+ System.err.println("Not a signed message!");
+ }
+ }
+}
diff --git a/mail/src/main/java/org/spongycastle/mail/smime/examples/ReadSignedMail.java b/mail/src/main/java/org/spongycastle/mail/smime/examples/ReadSignedMail.java
new file mode 100644
index 00000000..370106d9
--- /dev/null
+++ b/mail/src/main/java/org/spongycastle/mail/smime/examples/ReadSignedMail.java
@@ -0,0 +1,176 @@
+package org.bouncycastle.mail.smime.examples;
+
+import java.io.FileInputStream;
+import java.security.cert.X509Certificate;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.Properties;
+
+import javax.mail.BodyPart;
+import javax.mail.Multipart;
+import javax.mail.Session;
+import javax.mail.internet.MimeBodyPart;
+import javax.mail.internet.MimeMessage;
+import javax.mail.internet.MimeMultipart;
+
+import org.bouncycastle.cert.X509CertificateHolder;
+import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
+import org.bouncycastle.cms.SignerInformation;
+import org.bouncycastle.cms.SignerInformationStore;
+import org.bouncycastle.cms.jcajce.JcaSimpleSignerInfoVerifierBuilder;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.mail.smime.SMIMESigned;
+import org.bouncycastle.util.Store;
+
+/**
+ * a simple example that reads a basic SMIME signed mail file.
+ */
+public class ReadSignedMail
+{
+ private static final String BC = BouncyCastleProvider.PROVIDER_NAME;
+
+ /**
+ * verify the signature (assuming the cert is contained in the message)
+ */
+ private static void verify(
+ SMIMESigned s)
+ throws Exception
+ {
+ //
+ // extract the information to verify the signatures.
+ //
+
+ //
+ // certificates and crls passed in the signature
+ //
+ Store certs = s.getCertificates();
+
+ //
+ // SignerInfo blocks which contain the signatures
+ //
+ SignerInformationStore signers = s.getSignerInfos();
+
+ Collection c = signers.getSigners();
+ Iterator it = c.iterator();
+
+ //
+ // check each signer
+ //
+ while (it.hasNext())
+ {
+ SignerInformation signer = (SignerInformation)it.next();
+ Collection certCollection = certs.getMatches(signer.getSID());
+
+ Iterator certIt = certCollection.iterator();
+ X509Certificate cert = new JcaX509CertificateConverter().setProvider(BC).getCertificate((X509CertificateHolder)certIt.next());
+
+ //
+ // verify that the sig is correct and that it was generated
+ // when the certificate was current
+ //
+ if (signer.verify(new JcaSimpleSignerInfoVerifierBuilder().setProvider(BC).build(cert)))
+ {
+ System.out.println("signature verified");
+ }
+ else
+ {
+ System.out.println("signature failed!");
+ }
+ }
+ }
+
+ public static void main(
+ String[] args)
+ throws Exception
+ {
+ //
+ // Get a Session object with the default properties.
+ //
+ Properties props = System.getProperties();
+
+ Session session = Session.getDefaultInstance(props, null);
+
+ MimeMessage msg = new MimeMessage(session, new FileInputStream("signed.message"));
+
+ //
+ // make sure this was a multipart/signed message - there should be
+ // two parts as we have one part for the content that was signed and
+ // one part for the actual signature.
+ //
+ if (msg.isMimeType("multipart/signed"))
+ {
+ SMIMESigned s = new SMIMESigned(
+ (MimeMultipart)msg.getContent());
+
+ //
+ // extract the content
+ //
+ MimeBodyPart content = s.getContent();
+
+ System.out.println("Content:");
+
+ Object cont = content.getContent();
+
+ if (cont instanceof String)
+ {
+ System.out.println((String)cont);
+ }
+ else if (cont instanceof Multipart)
+ {
+ Multipart mp = (Multipart)cont;
+ int count = mp.getCount();
+ for (int i = 0; i < count; i++)
+ {
+ BodyPart m = mp.getBodyPart(i);
+ Object part = m.getContent();
+
+ System.out.println("Part " + i);
+ System.out.println("---------------------------");
+
+ if (part instanceof String)
+ {
+ System.out.println((String)part);
+ }
+ else
+ {
+ System.out.println("can't print...");
+ }
+ }
+ }
+
+ System.out.println("Status:");
+
+ verify(s);
+ }
+ else if (msg.isMimeType("application/pkcs7-mime")
+ || msg.isMimeType("application/x-pkcs7-mime"))
+ {
+ //
+ // in this case the content is wrapped in the signature block.
+ //
+ SMIMESigned s = new SMIMESigned(msg);
+
+ //
+ // extract the content
+ //
+ MimeBodyPart content = s.getContent();
+
+ System.out.println("Content:");
+
+ Object cont = content.getContent();
+
+ if (cont instanceof String)
+ {
+ System.out.println((String)cont);
+ }
+
+ System.out.println("Status:");
+
+ verify(s);
+ }
+ else
+ {
+ System.err.println("Not a signed message!");
+ }
+ }
+}
diff --git a/mail/src/main/java/org/spongycastle/mail/smime/examples/SendSignedAndEncryptedMail.java b/mail/src/main/java/org/spongycastle/mail/smime/examples/SendSignedAndEncryptedMail.java
new file mode 100644
index 00000000..8861152e
--- /dev/null
+++ b/mail/src/main/java/org/spongycastle/mail/smime/examples/SendSignedAndEncryptedMail.java
@@ -0,0 +1,192 @@
+package org.bouncycastle.mail.smime.examples;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.FileInputStream;
+import java.security.KeyStore;
+import java.security.PrivateKey;
+import java.security.Security;
+import java.security.cert.Certificate;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.Properties;
+
+import javax.activation.CommandMap;
+import javax.activation.MailcapCommandMap;
+import javax.mail.Message;
+import javax.mail.Session;
+import javax.mail.Transport;
+import javax.mail.internet.InternetAddress;
+import javax.mail.internet.MimeBodyPart;
+import javax.mail.internet.MimeMessage;
+import javax.mail.internet.MimeMultipart;
+
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.cms.AttributeTable;
+import org.bouncycastle.asn1.cms.IssuerAndSerialNumber;
+import org.bouncycastle.asn1.smime.SMIMECapabilitiesAttribute;
+import org.bouncycastle.asn1.smime.SMIMECapability;
+import org.bouncycastle.asn1.smime.SMIMECapabilityVector;
+import org.bouncycastle.asn1.smime.SMIMEEncryptionKeyPreferenceAttribute;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.cert.jcajce.JcaCertStore;
+import org.bouncycastle.cms.CMSAlgorithm;
+import org.bouncycastle.cms.jcajce.JcaSimpleSignerInfoGeneratorBuilder;
+import org.bouncycastle.cms.jcajce.JceCMSContentEncryptorBuilder;
+import org.bouncycastle.cms.jcajce.JceKeyTransRecipientInfoGenerator;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.mail.smime.SMIMEEnvelopedGenerator;
+import org.bouncycastle.mail.smime.SMIMEException;
+import org.bouncycastle.mail.smime.SMIMESignedGenerator;
+import org.bouncycastle.util.Store;
+import org.bouncycastle.util.Strings;
+
+/**
+ * Example that sends a signed and encrypted mail message.
+ */
+public class SendSignedAndEncryptedMail
+{
+ public static void main(String args[])
+ {
+ if (args.length != 5)
+ {
+ System.err
+ .println("usage: SendSignedAndEncryptedMail <pkcs12Keystore> <password> <keyalias> <smtp server> <email address>");
+ System.exit(0);
+ }
+
+ try
+ {
+ MailcapCommandMap mailcap = (MailcapCommandMap)CommandMap
+ .getDefaultCommandMap();
+
+ mailcap
+ .addMailcap("application/pkcs7-signature;; x-java-content-handler=org.bouncycastle.mail.smime.handlers.pkcs7_signature");
+ mailcap
+ .addMailcap("application/pkcs7-mime;; x-java-content-handler=org.bouncycastle.mail.smime.handlers.pkcs7_mime");
+ mailcap
+ .addMailcap("application/x-pkcs7-signature;; x-java-content-handler=org.bouncycastle.mail.smime.handlers.x_pkcs7_signature");
+ mailcap
+ .addMailcap("application/x-pkcs7-mime;; x-java-content-handler=org.bouncycastle.mail.smime.handlers.x_pkcs7_mime");
+ mailcap
+ .addMailcap("multipart/signed;; x-java-content-handler=org.bouncycastle.mail.smime.handlers.multipart_signed");
+
+ CommandMap.setDefaultCommandMap(mailcap);
+
+ /* Add BC */
+ Security.addProvider(new BouncyCastleProvider());
+
+ /* Open the keystore */
+ KeyStore keystore = KeyStore.getInstance("PKCS12", "BC");
+ keystore.load(new FileInputStream(args[0]), args[1].toCharArray());
+ Certificate[] chain = keystore.getCertificateChain(args[2]);
+
+ /* Get the private key to sign the message with */
+ PrivateKey privateKey = (PrivateKey)keystore.getKey(args[2],
+ args[1].toCharArray());
+ if (privateKey == null)
+ {
+ throw new Exception("cannot find private key for alias: "
+ + args[2]);
+ }
+
+ /* Create the message to sign and encrypt */
+ Properties props = System.getProperties();
+ props.put("mail.smtp.host", args[3]);
+ Session session = Session.getDefaultInstance(props, null);
+
+ MimeMessage body = new MimeMessage(session);
+ body.setFrom(new InternetAddress(args[4]));
+ body.setRecipient(Message.RecipientType.TO, new InternetAddress(
+ args[4]));
+ body.setSubject("example encrypted message");
+ body.setContent("example encrypted message", "text/plain");
+ body.saveChanges();
+
+ /* Create the SMIMESignedGenerator */
+ SMIMECapabilityVector capabilities = new SMIMECapabilityVector();
+ capabilities.addCapability(SMIMECapability.dES_EDE3_CBC);
+ capabilities.addCapability(SMIMECapability.rC2_CBC, 128);
+ capabilities.addCapability(SMIMECapability.dES_CBC);
+
+ ASN1EncodableVector attributes = new ASN1EncodableVector();
+ attributes.add(new SMIMEEncryptionKeyPreferenceAttribute(
+ new IssuerAndSerialNumber(
+ new X500Name(((X509Certificate)chain[0])
+ .getIssuerDN().getName()),
+ ((X509Certificate)chain[0]).getSerialNumber())));
+ attributes.add(new SMIMECapabilitiesAttribute(capabilities));
+
+ SMIMESignedGenerator signer = new SMIMESignedGenerator();
+ signer.addSignerInfoGenerator(new JcaSimpleSignerInfoGeneratorBuilder().setProvider("BC").setSignedAttributeGenerator(new AttributeTable(attributes)).build("DSA".equals(privateKey.getAlgorithm()) ? "SHA1withDSA" : "MD5withRSA", privateKey, (X509Certificate)chain[0]));
+
+
+ /* Add the list of certs to the generator */
+ List certList = new ArrayList();
+ certList.add(chain[0]);
+ Store certs = new JcaCertStore(certList);
+ signer.addCertificates(certs);
+
+ /* Sign the message */
+ MimeMultipart mm = signer.generate(body);
+ MimeMessage signedMessage = new MimeMessage(session);
+
+ /* Set all original MIME headers in the signed message */
+ Enumeration headers = body.getAllHeaderLines();
+ while (headers.hasMoreElements())
+ {
+ signedMessage.addHeaderLine((String)headers.nextElement());
+ }
+
+ /* Set the content of the signed message */
+ signedMessage.setContent(mm);
+ signedMessage.saveChanges();
+
+ /* Create the encrypter */
+ SMIMEEnvelopedGenerator encrypter = new SMIMEEnvelopedGenerator();
+ encrypter.addRecipientInfoGenerator(new JceKeyTransRecipientInfoGenerator((X509Certificate)chain[0]).setProvider("BC"));
+
+ /* Encrypt the message */
+ MimeBodyPart encryptedPart = encrypter.generate(signedMessage,
+ new JceCMSContentEncryptorBuilder(CMSAlgorithm.RC2_CBC).setProvider("BC").build());
+
+ /*
+ * Create a new MimeMessage that contains the encrypted and signed
+ * content
+ */
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ encryptedPart.writeTo(out);
+
+ MimeMessage encryptedMessage = new MimeMessage(session,
+ new ByteArrayInputStream(out.toByteArray()));
+
+ /* Set all original MIME headers in the encrypted message */
+ headers = body.getAllHeaderLines();
+ while (headers.hasMoreElements())
+ {
+ String headerLine = (String)headers.nextElement();
+ /*
+ * Make sure not to override any content-* headers from the
+ * original message
+ */
+ if (!Strings.toLowerCase(headerLine).startsWith("content-"))
+ {
+ encryptedMessage.addHeaderLine(headerLine);
+ }
+ }
+
+ Transport.send(encryptedMessage);
+ }
+ catch (SMIMEException ex)
+ {
+ ex.getUnderlyingException().printStackTrace(System.err);
+ ex.printStackTrace(System.err);
+ }
+ catch (Exception ex)
+ {
+ ex.printStackTrace(System.err);
+ }
+ }
+}
diff --git a/mail/src/main/java/org/spongycastle/mail/smime/examples/ValidateSignedMail.java b/mail/src/main/java/org/spongycastle/mail/smime/examples/ValidateSignedMail.java
new file mode 100644
index 00000000..31961f1e
--- /dev/null
+++ b/mail/src/main/java/org/spongycastle/mail/smime/examples/ValidateSignedMail.java
@@ -0,0 +1,352 @@
+package org.bouncycastle.mail.smime.examples;
+
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.security.KeyPairGenerator;
+import java.security.KeyStore;
+import java.security.PublicKey;
+import java.security.SecureRandom;
+import java.security.Security;
+import java.security.cert.CertStore;
+import java.security.cert.CertificateFactory;
+import java.security.cert.CollectionCertStoreParameters;
+import java.security.cert.PKIXParameters;
+import java.security.cert.TrustAnchor;
+import java.security.cert.X509CRL;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Locale;
+import java.util.Properties;
+import java.util.Set;
+
+import javax.mail.Session;
+import javax.mail.internet.MimeMessage;
+import javax.security.auth.x500.X500Principal;
+
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1Encoding;
+import org.bouncycastle.asn1.x509.X509Extension;
+import org.bouncycastle.cms.SignerInformation;
+import org.bouncycastle.i18n.ErrorBundle;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.mail.smime.validator.SignedMailValidator;
+import org.bouncycastle.x509.PKIXCertPathReviewer;
+import org.bouncycastle.x509.extension.X509ExtensionUtil;
+
+/**
+ * An Example that reads a signed mail and validates its signature. Also
+ * validating the certificate path from the signers key to a trusted entity
+ */
+public class ValidateSignedMail
+{
+
+ /*
+ * Use trusted certificates from $JAVA_HOME/lib/security/cacerts as
+ * trustanchors
+ */
+ public static final boolean useCaCerts = false;
+
+ public static void main(String[] args) throws Exception
+ {
+
+ Security.addProvider(new BouncyCastleProvider());
+
+ //
+ // Get a Session object with the default properties.
+ //
+ Properties props = System.getProperties();
+
+ Session session = Session.getDefaultInstance(props, null);
+
+ // read message
+ MimeMessage msg = new MimeMessage(session, new FileInputStream(
+ "signed.message"));
+
+ // create PKIXparameters
+ PKIXParameters param;
+
+ if (useCaCerts)
+ {
+ KeyStore caCerts = KeyStore.getInstance("JKS");
+ String javaHome = System.getProperty("java.home");
+ caCerts.load(
+ new FileInputStream(javaHome + "/lib/security/cacerts"),
+ "changeit".toCharArray());
+
+ param = new PKIXParameters(caCerts);
+ }
+ else
+ {
+ // load trustanchors from files (here we only load one)
+ Set trustanchors = new HashSet();
+ TrustAnchor trust = getTrustAnchor("trustanchor");
+
+ // create a dummy trustanchor if we can not find any trustanchor. so
+ // we can still try to validate the message
+ if (trust == null)
+ {
+ System.out
+ .println("no trustanchor file found, using a dummy trustanchor");
+ trust = getDummyTrustAnchor();
+ }
+ trustanchors.add(trust);
+
+ param = new PKIXParameters(trustanchors);
+ }
+
+ // load one ore more crls from files (here we only load one crl)
+ List crls = new ArrayList();
+ X509CRL crl = loadCRL("crl.file");
+ if (crl != null)
+ {
+ crls.add(crl);
+ }
+ CertStore certStore = CertStore.getInstance("Collection",
+ new CollectionCertStoreParameters(crls), "BC");
+
+ // add crls and enable revocation checking
+ param.addCertStore(certStore);
+ param.setRevocationEnabled(true);
+
+ // or disable revocation checking
+ // param.setRevocationEnabled(false);
+
+ verifySignedMail(msg, param);
+ }
+
+ public static final int TITLE = 0;
+ public static final int TEXT = 1;
+ public static final int SUMMARY = 2;
+ public static final int DETAIL = 3;
+
+ static int dbgLvl = DETAIL;
+
+ private static final String RESOURCE_NAME = "org.bouncycastle.mail.smime.validator.SignedMailValidatorMessages";
+
+ public static void verifySignedMail(MimeMessage msg, PKIXParameters param)
+ throws Exception
+ {
+ // set locale for the output
+ Locale loc = Locale.ENGLISH;
+ // Locale loc = Locale.GERMAN;
+
+ // validate signatures
+ SignedMailValidator validator = new SignedMailValidator(msg, param);
+
+ // iterate over all signatures and print results
+ Iterator it = validator.getSignerInformationStore().getSigners()
+ .iterator();
+ while (it.hasNext())
+ {
+ SignerInformation signer = (SignerInformation) it.next();
+ SignedMailValidator.ValidationResult result = validator
+ .getValidationResult(signer);
+ if (result.isValidSignature())
+ {
+ ErrorBundle errMsg = new ErrorBundle(RESOURCE_NAME,
+ "SignedMailValidator.sigValid");
+ System.out.println(errMsg.getText(loc));
+ }
+ else
+ {
+ ErrorBundle errMsg = new ErrorBundle(RESOURCE_NAME,
+ "SignedMailValidator.sigInvalid");
+ System.out.println(errMsg.getText(loc));
+ // print errors
+ System.out.println("Errors:");
+ Iterator errorsIt = result.getErrors().iterator();
+ while (errorsIt.hasNext())
+ {
+ ErrorBundle errorMsg = (ErrorBundle) errorsIt.next();
+ if (dbgLvl == DETAIL)
+ {
+ System.out.println("\t\t" + errorMsg.getDetail(loc));
+ }
+ else
+ {
+ System.out.println("\t\t" + errorMsg.getText(loc));
+ }
+ }
+ }
+ if (!result.getNotifications().isEmpty())
+ {
+ System.out.println("Notifications:");
+ Iterator notIt = result.getNotifications().iterator();
+ while (notIt.hasNext())
+ {
+ ErrorBundle notMsg = (ErrorBundle) notIt.next();
+ if (dbgLvl == DETAIL)
+ {
+ System.out.println("\t\t" + notMsg.getDetail(loc));
+ }
+ else
+ {
+ System.out.println("\t\t" + notMsg.getText(loc));
+ }
+ }
+ }
+ PKIXCertPathReviewer review = result.getCertPathReview();
+ if (review != null)
+ {
+ if (review.isValidCertPath())
+ {
+ System.out.println("Certificate path valid");
+ }
+ else
+ {
+ System.out.println("Certificate path invalid");
+ }
+
+ System.out.println("\nCertificate path validation results:");
+ // global errors
+ System.out.println("Errors:");
+ Iterator errorsIt = review.getErrors(-1).iterator();
+ while (errorsIt.hasNext())
+ {
+ ErrorBundle errorMsg = (ErrorBundle) errorsIt.next();
+ if (dbgLvl == DETAIL)
+ {
+ System.out.println("\t\t" + errorMsg.getDetail(loc));
+ }
+ else
+ {
+ System.out.println("\t\t" + errorMsg.getText(loc));
+ }
+ }
+
+ System.out.println("Notifications:");
+ Iterator notificationsIt = review.getNotifications(-1)
+ .iterator();
+ while (notificationsIt.hasNext())
+ {
+ ErrorBundle noteMsg = (ErrorBundle) notificationsIt.next();
+ System.out.println("\t" + noteMsg.getText(loc));
+ }
+
+ // per certificate errors and notifications
+ Iterator certIt = review.getCertPath().getCertificates()
+ .iterator();
+ int i = 0;
+ while (certIt.hasNext())
+ {
+ X509Certificate cert = (X509Certificate) certIt.next();
+ System.out.println("\nCertificate " + i + "\n========");
+ System.out.println("Issuer: "
+ + cert.getIssuerDN().getName());
+ System.out.println("Subject: "
+ + cert.getSubjectDN().getName());
+
+ // errors
+ System.out.println("\tErrors:");
+ errorsIt = review.getErrors(i).iterator();
+ while (errorsIt.hasNext())
+ {
+ ErrorBundle errorMsg = (ErrorBundle) errorsIt.next();
+ if (dbgLvl == DETAIL)
+ {
+ System.out
+ .println("\t\t" + errorMsg.getDetail(loc));
+ }
+ else
+ {
+ System.out.println("\t\t" + errorMsg.getText(loc));
+ }
+ }
+
+ // notifications
+ System.out.println("\tNotifications:");
+ notificationsIt = review.getNotifications(i).iterator();
+ while (notificationsIt.hasNext())
+ {
+ ErrorBundle noteMsg = (ErrorBundle) notificationsIt
+ .next();
+ if (dbgLvl == DETAIL)
+ {
+ System.out.println("\t\t" + noteMsg.getDetail(loc));
+ }
+ else
+ {
+ System.out.println("\t\t" + noteMsg.getText(loc));
+ }
+ }
+
+ i++;
+ }
+ }
+ }
+
+ }
+
+ protected static TrustAnchor getTrustAnchor(String trustcert)
+ throws Exception
+ {
+ X509Certificate cert = loadCert(trustcert);
+ if (cert != null)
+ {
+ byte[] ncBytes = cert
+ .getExtensionValue(X509Extension.nameConstraints.getId());
+
+ if (ncBytes != null)
+ {
+ ASN1Encodable extValue = X509ExtensionUtil
+ .fromExtensionValue(ncBytes);
+ return new TrustAnchor(cert, extValue.toASN1Primitive().getEncoded(ASN1Encoding.DER));
+ }
+ return new TrustAnchor(cert, null);
+ }
+ return null;
+ }
+
+ protected static X509Certificate loadCert(String certfile)
+ {
+ X509Certificate cert = null;
+ try
+ {
+ InputStream in = new FileInputStream(certfile);
+
+ CertificateFactory cf = CertificateFactory.getInstance("X.509",
+ "BC");
+ cert = (X509Certificate) cf.generateCertificate(in);
+ }
+ catch (Exception e)
+ {
+ System.out.println("certfile \"" + certfile
+ + "\" not found - classpath is "
+ + System.getProperty("java.class.path"));
+ }
+ return cert;
+ }
+
+ protected static X509CRL loadCRL(String crlfile)
+ {
+ X509CRL crl = null;
+ try
+ {
+ InputStream in = new FileInputStream(crlfile);
+
+ CertificateFactory cf = CertificateFactory.getInstance("X.509",
+ "BC");
+ crl = (X509CRL) cf.generateCRL(in);
+ }
+ catch (Exception e)
+ {
+ System.out.println("crlfile \"" + crlfile
+ + "\" not found - classpath is "
+ + System.getProperty("java.class.path"));
+ }
+ return crl;
+ }
+
+ private static TrustAnchor getDummyTrustAnchor() throws Exception
+ {
+ X500Principal principal = new X500Principal("CN=Dummy Trust Anchor");
+ KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA", "BC");
+ kpg.initialize(1024, new SecureRandom());
+ PublicKey trustPubKey = kpg.generateKeyPair().getPublic();
+ return new TrustAnchor(principal, trustPubKey, null);
+ }
+
+}
diff --git a/mail/src/main/java/org/spongycastle/mail/smime/handlers/PKCS7ContentHandler.java b/mail/src/main/java/org/spongycastle/mail/smime/handlers/PKCS7ContentHandler.java
new file mode 100644
index 00000000..d3db7fd6
--- /dev/null
+++ b/mail/src/main/java/org/spongycastle/mail/smime/handlers/PKCS7ContentHandler.java
@@ -0,0 +1,110 @@
+package org.bouncycastle.mail.smime.handlers;
+
+import java.awt.datatransfer.DataFlavor;
+import java.io.BufferedInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import javax.activation.ActivationDataFlavor;
+import javax.activation.DataContentHandler;
+import javax.activation.DataSource;
+import javax.mail.MessagingException;
+import javax.mail.internet.MimeBodyPart;
+
+import org.bouncycastle.mail.smime.SMIMEStreamingProcessor;
+
+public class PKCS7ContentHandler
+ implements DataContentHandler
+{
+ private final ActivationDataFlavor _adf;
+ private final DataFlavor[] _dfs;
+
+ PKCS7ContentHandler(
+ ActivationDataFlavor adf,
+ DataFlavor[] dfs)
+ {
+ _adf = adf;
+ _dfs = dfs;
+ }
+
+ public Object getContent(
+ DataSource ds)
+ throws IOException
+ {
+ return ds.getInputStream();
+ }
+
+ public Object getTransferData(
+ DataFlavor df,
+ DataSource ds)
+ throws IOException
+ {
+ if (_adf.equals(df))
+ {
+ return getContent(ds);
+ }
+ else
+ {
+ return null;
+ }
+ }
+
+ public DataFlavor[] getTransferDataFlavors()
+ {
+ return _dfs;
+ }
+
+ public void writeTo(
+ Object obj,
+ String mimeType,
+ OutputStream os)
+ throws IOException
+ {
+ if (obj instanceof MimeBodyPart)
+ {
+ try
+ {
+ ((MimeBodyPart)obj).writeTo(os);
+ }
+ catch (MessagingException ex)
+ {
+ throw new IOException(ex.getMessage());
+ }
+ }
+ else if (obj instanceof byte[])
+ {
+ os.write((byte[])obj);
+ }
+ else if (obj instanceof InputStream)
+ {
+ int b;
+ InputStream in = (InputStream)obj;
+
+ if (!(in instanceof BufferedInputStream))
+ {
+ in = new BufferedInputStream(in);
+ }
+
+ while ((b = in.read()) >= 0)
+ {
+ os.write(b);
+ }
+ }
+ else if (obj instanceof SMIMEStreamingProcessor)
+ {
+ SMIMEStreamingProcessor processor = (SMIMEStreamingProcessor)obj;
+
+ processor.write(os);
+ }
+ else
+ {
+ // TODO it would be even nicer if we could attach the object to the exception
+ // as well since in deeply nested messages, it is not always clear which
+ // part caused the problem. Thus I guess we would have to subclass the
+ // IOException
+
+ throw new IOException("unknown object in writeTo " + obj);
+ }
+ }
+}
diff --git a/mail/src/main/java/org/spongycastle/mail/smime/handlers/multipart_signed.java b/mail/src/main/java/org/spongycastle/mail/smime/handlers/multipart_signed.java
new file mode 100644
index 00000000..dd5ef193
--- /dev/null
+++ b/mail/src/main/java/org/spongycastle/mail/smime/handlers/multipart_signed.java
@@ -0,0 +1,280 @@
+package org.bouncycastle.mail.smime.handlers;
+
+import org.bouncycastle.mail.smime.SMIMEStreamingProcessor;
+
+import javax.activation.ActivationDataFlavor;
+import javax.activation.DataContentHandler;
+import javax.activation.DataSource;
+import javax.mail.MessagingException;
+import javax.mail.Multipart;
+import javax.mail.internet.ContentType;
+import javax.mail.internet.MimeBodyPart;
+import javax.mail.internet.MimeMultipart;
+import java.awt.datatransfer.DataFlavor;
+import java.io.BufferedInputStream;
+import java.io.FilterOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Enumeration;
+
+public class multipart_signed
+ implements DataContentHandler
+{
+ private static final ActivationDataFlavor ADF = new ActivationDataFlavor(MimeMultipart.class, "multipart/signed", "Multipart Signed");
+ private static final DataFlavor[] DFS = new DataFlavor[] { ADF };
+
+ public Object getContent(DataSource ds)
+ throws IOException
+ {
+ try
+ {
+ return new MimeMultipart(ds);
+ }
+ catch (MessagingException ex)
+ {
+ return null;
+ }
+ }
+
+ public Object getTransferData(DataFlavor df, DataSource ds)
+ throws IOException
+ {
+ if (ADF.equals(df))
+ {
+ return getContent(ds);
+ }
+ else
+ {
+ return null;
+ }
+ }
+
+ public DataFlavor[] getTransferDataFlavors()
+ {
+ return DFS;
+ }
+
+ public void writeTo(Object obj, String _mimeType, OutputStream os)
+ throws IOException
+ {
+
+ if (obj instanceof MimeMultipart)
+ {
+ try
+ {
+ outputBodyPart(os, obj);
+ }
+ catch (MessagingException ex)
+ {
+ throw new IOException(ex.getMessage());
+ }
+ }
+ else if(obj instanceof byte[])
+ {
+ os.write((byte[])obj);
+ }
+ else if (obj instanceof InputStream)
+ {
+ int b;
+ InputStream in = (InputStream)obj;
+
+ if (!(in instanceof BufferedInputStream))
+ {
+ in = new BufferedInputStream(in);
+ }
+
+ while ((b = in.read()) >= 0)
+ {
+ os.write(b);
+ }
+ }
+ else if (obj instanceof SMIMEStreamingProcessor)
+ {
+ SMIMEStreamingProcessor processor = (SMIMEStreamingProcessor)obj;
+
+ processor.write(os);
+ }
+ else
+ {
+ throw new IOException("unknown object in writeTo " + obj);
+ }
+ }
+
+ /*
+ * Output the mulitpart as a collection of leaves to make sure preamble text is not included.
+ */
+ private void outputBodyPart(
+ OutputStream out,
+ Object bodyPart)
+ throws MessagingException, IOException
+ {
+ if (bodyPart instanceof Multipart)
+ {
+ Multipart mp = (Multipart)bodyPart;
+ ContentType contentType = new ContentType(mp.getContentType());
+ String boundary = "--" + contentType.getParameter("boundary");
+
+ LineOutputStream lOut = new LineOutputStream(out);
+
+ for (int i = 0; i < mp.getCount(); i++)
+ {
+ lOut.writeln(boundary);
+ outputBodyPart(out, mp.getBodyPart(i));
+ lOut.writeln(); // CRLF terminator
+ }
+
+ lOut.writeln(boundary + "--");
+ return;
+ }
+
+ MimeBodyPart mimePart = (MimeBodyPart)bodyPart;
+
+ if (mimePart.getContent() instanceof Multipart)
+ {
+ Multipart mp = (Multipart)mimePart.getContent();
+ ContentType contentType = new ContentType(mp.getContentType());
+ String boundary = "--" + contentType.getParameter("boundary");
+
+ LineOutputStream lOut = new LineOutputStream(out);
+
+ Enumeration headers = mimePart.getAllHeaderLines();
+ while (headers.hasMoreElements())
+ {
+ lOut.writeln((String)headers.nextElement());
+ }
+
+ lOut.writeln(); // CRLF separator
+
+ outputPreamble(lOut, mimePart, boundary);
+
+ outputBodyPart(out, mp);
+ return;
+ }
+
+ mimePart.writeTo(out);
+ }
+
+ /**
+ * internal preamble is generally included in signatures, while this is technically wrong,
+ * if we find internal preamble we include it by default.
+ */
+ static void outputPreamble(LineOutputStream lOut, MimeBodyPart part, String boundary)
+ throws MessagingException, IOException
+ {
+ InputStream in;
+
+ try
+ {
+ in = part.getRawInputStream();
+ }
+ catch (MessagingException e)
+ {
+ return; // no underlying content, rely on default generation
+ }
+
+ String line;
+
+ while ((line = readLine(in)) != null)
+ {
+ if (line.equals(boundary))
+ {
+ break;
+ }
+
+ lOut.writeln(line);
+ }
+
+ in.close();
+
+ if (line == null)
+ {
+ throw new MessagingException("no boundary found");
+ }
+ }
+
+ /*
+ * read a line of input stripping of the tailing \r\n
+ */
+ private static String readLine(InputStream in)
+ throws IOException
+ {
+ StringBuffer b = new StringBuffer();
+
+ int ch;
+ while ((ch = in.read()) >= 0 && ch != '\n')
+ {
+ if (ch != '\r')
+ {
+ b.append((char)ch);
+ }
+ }
+
+ if (ch < 0)
+ {
+ return null;
+ }
+
+ return b.toString();
+ }
+
+ private static class LineOutputStream extends FilterOutputStream
+ {
+ private static byte newline[];
+
+ public LineOutputStream(OutputStream outputstream)
+ {
+ super(outputstream);
+ }
+
+ public void writeln(String s)
+ throws MessagingException
+ {
+ try
+ {
+ byte abyte0[] = getBytes(s);
+ super.out.write(abyte0);
+ super.out.write(newline);
+ }
+ catch(Exception exception)
+ {
+ throw new MessagingException("IOException", exception);
+ }
+ }
+
+ public void writeln()
+ throws MessagingException
+ {
+ try
+ {
+ super.out.write(newline);
+ }
+ catch(Exception exception)
+ {
+ throw new MessagingException("IOException", exception);
+ }
+ }
+
+ static
+ {
+ newline = new byte[2];
+ newline[0] = 13;
+ newline[1] = 10;
+ }
+
+ private static byte[] getBytes(String s)
+ {
+ char ac[] = s.toCharArray();
+ int i = ac.length;
+ byte abyte0[] = new byte[i];
+ int j = 0;
+
+ while (j < i)
+ {
+ abyte0[j] = (byte)ac[j++];
+ }
+
+ return abyte0;
+ }
+ }
+}
diff --git a/mail/src/main/java/org/spongycastle/mail/smime/handlers/pkcs7_mime.java b/mail/src/main/java/org/spongycastle/mail/smime/handlers/pkcs7_mime.java
new file mode 100644
index 00000000..abdf1251
--- /dev/null
+++ b/mail/src/main/java/org/spongycastle/mail/smime/handlers/pkcs7_mime.java
@@ -0,0 +1,18 @@
+package org.bouncycastle.mail.smime.handlers;
+
+import java.awt.datatransfer.DataFlavor;
+
+import javax.activation.ActivationDataFlavor;
+import javax.mail.internet.MimeBodyPart;
+
+public class pkcs7_mime
+ extends PKCS7ContentHandler
+{
+ private static final ActivationDataFlavor ADF = new ActivationDataFlavor(MimeBodyPart.class, "application/pkcs7-mime", "Encrypted Data");
+ private static final DataFlavor[] DFS = new DataFlavor[] { ADF };
+
+ public pkcs7_mime()
+ {
+ super(ADF, DFS);
+ }
+}
diff --git a/mail/src/main/java/org/spongycastle/mail/smime/handlers/pkcs7_signature.java b/mail/src/main/java/org/spongycastle/mail/smime/handlers/pkcs7_signature.java
new file mode 100644
index 00000000..0c669508
--- /dev/null
+++ b/mail/src/main/java/org/spongycastle/mail/smime/handlers/pkcs7_signature.java
@@ -0,0 +1,18 @@
+package org.bouncycastle.mail.smime.handlers;
+
+import java.awt.datatransfer.DataFlavor;
+
+import javax.activation.ActivationDataFlavor;
+import javax.mail.internet.MimeBodyPart;
+
+public class pkcs7_signature
+ extends PKCS7ContentHandler
+{
+ private static final ActivationDataFlavor ADF = new ActivationDataFlavor(MimeBodyPart.class, "application/pkcs7-signature", "Signature");
+ private static final DataFlavor[] DFS = new DataFlavor[] { ADF };
+
+ public pkcs7_signature()
+ {
+ super(ADF, DFS);
+ }
+}
diff --git a/mail/src/main/java/org/spongycastle/mail/smime/handlers/x_pkcs7_mime.java b/mail/src/main/java/org/spongycastle/mail/smime/handlers/x_pkcs7_mime.java
new file mode 100644
index 00000000..7e28f281
--- /dev/null
+++ b/mail/src/main/java/org/spongycastle/mail/smime/handlers/x_pkcs7_mime.java
@@ -0,0 +1,18 @@
+package org.bouncycastle.mail.smime.handlers;
+
+import java.awt.datatransfer.DataFlavor;
+
+import javax.activation.ActivationDataFlavor;
+import javax.mail.internet.MimeBodyPart;
+
+public class x_pkcs7_mime
+ extends PKCS7ContentHandler
+{
+ private static final ActivationDataFlavor ADF = new ActivationDataFlavor(MimeBodyPart.class, "application/x-pkcs7-mime", "Encrypted Data");
+ private static final DataFlavor[] DFS = new DataFlavor[] { ADF };
+
+ public x_pkcs7_mime()
+ {
+ super(ADF, DFS);
+ }
+}
diff --git a/mail/src/main/java/org/spongycastle/mail/smime/handlers/x_pkcs7_signature.java b/mail/src/main/java/org/spongycastle/mail/smime/handlers/x_pkcs7_signature.java
new file mode 100644
index 00000000..a58fd531
--- /dev/null
+++ b/mail/src/main/java/org/spongycastle/mail/smime/handlers/x_pkcs7_signature.java
@@ -0,0 +1,90 @@
+package org.bouncycastle.mail.smime.handlers;
+
+import java.awt.datatransfer.DataFlavor;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import javax.activation.ActivationDataFlavor;
+import javax.activation.DataContentHandler;
+import javax.activation.DataSource;
+import javax.mail.MessagingException;
+import javax.mail.internet.MimeBodyPart;
+
+public class x_pkcs7_signature
+ implements DataContentHandler
+{
+
+ /*
+ *
+ * VARIABLES
+ *
+ */
+
+ private static final ActivationDataFlavor ADF;
+ private static final DataFlavor[] ADFs;
+
+ static
+ {
+ ADF = new ActivationDataFlavor(MimeBodyPart.class, "application/x-pkcs7-signature", "Signature");
+ ADFs = new DataFlavor[] { ADF };
+ }
+
+ public Object getContent(DataSource _ds)
+ throws IOException
+ {
+ return _ds.getInputStream();
+ }
+
+ public Object getTransferData(DataFlavor _df, DataSource _ds)
+ throws IOException
+ {
+ if (ADF.equals(_df))
+ {
+ return getContent(_ds);
+ }
+ else
+ {
+ return null;
+ }
+ }
+
+ public DataFlavor[] getTransferDataFlavors()
+ {
+ return ADFs;
+ }
+
+ public void writeTo(Object _obj, String _mimeType, OutputStream _os)
+ throws IOException
+ {
+ if (_obj instanceof MimeBodyPart)
+ {
+ try
+ {
+ ((MimeBodyPart)_obj).writeTo(_os);
+ }
+ catch (MessagingException ex)
+ {
+ throw new IOException(ex.getMessage());
+ }
+ }
+ else if (_obj instanceof byte[])
+ {
+ _os.write((byte[])_obj);
+ }
+ else if (_obj instanceof InputStream)
+ {
+ int b;
+ InputStream in = (InputStream)_obj;
+
+ while ((b = in.read()) >= 0)
+ {
+ _os.write(b);
+ }
+ }
+ else
+ {
+ throw new IOException("unknown object in writeTo " + _obj);
+ }
+ }
+}
diff --git a/mail/src/main/java/org/spongycastle/mail/smime/util/CRLFOutputStream.java b/mail/src/main/java/org/spongycastle/mail/smime/util/CRLFOutputStream.java
new file mode 100644
index 00000000..b11583d0
--- /dev/null
+++ b/mail/src/main/java/org/spongycastle/mail/smime/util/CRLFOutputStream.java
@@ -0,0 +1,67 @@
+package org.bouncycastle.mail.smime.util;
+
+import java.io.FilterOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+public class CRLFOutputStream extends FilterOutputStream
+{
+ protected int lastb;
+ protected static byte newline[];
+
+ public CRLFOutputStream(OutputStream outputstream)
+ {
+ super(outputstream);
+ lastb = -1;
+ }
+
+ public void write(int i)
+ throws IOException
+ {
+ if (i == '\r')
+ {
+ out.write(newline);
+ }
+ else if (i == '\n')
+ {
+ if (lastb != '\r')
+ {
+ out.write(newline);
+ }
+ }
+ else
+ {
+ out.write(i);
+ }
+
+ lastb = i;
+ }
+
+ public void write(byte[] buf)
+ throws IOException
+ {
+ this.write(buf, 0, buf.length);
+ }
+
+ public void write(byte buf[], int off, int len)
+ throws IOException
+ {
+ for (int i = off; i != off + len; i++)
+ {
+ this.write(buf[i]);
+ }
+ }
+
+ public void writeln()
+ throws IOException
+ {
+ super.out.write(newline);
+ }
+
+ static
+ {
+ newline = new byte[2];
+ newline[0] = '\r';
+ newline[1] = '\n';
+ }
+}
diff --git a/mail/src/main/java/org/spongycastle/mail/smime/util/FileBackedMimeBodyPart.java b/mail/src/main/java/org/spongycastle/mail/smime/util/FileBackedMimeBodyPart.java
new file mode 100644
index 00000000..6bae91c9
--- /dev/null
+++ b/mail/src/main/java/org/spongycastle/mail/smime/util/FileBackedMimeBodyPart.java
@@ -0,0 +1,162 @@
+package org.bouncycastle.mail.smime.util;
+
+import javax.mail.MessagingException;
+import javax.mail.internet.InternetHeaders;
+import javax.mail.internet.MimeBodyPart;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Enumeration;
+
+public class FileBackedMimeBodyPart
+ extends MimeBodyPart
+{
+ private static final int BUF_SIZE = 32760;
+
+ private final File _file;
+
+ /**
+ * Create a MimeBodyPart backed by the data in file.
+ *
+ * @param file file containing the body part.
+ * @throws MessagingException an exception occurs parsing file.
+ * @throws IOException an exception occurs accessing file.
+ */
+ public FileBackedMimeBodyPart(
+ File file)
+ throws MessagingException, IOException
+ {
+ super(new SharedFileInputStream(file));
+
+ _file = file;
+ }
+
+ /**
+ * Create a MimeBodyPart backed by file based on the headers and
+ * content data in content.
+ *
+ * @param content an inputstream containing the body part.
+ * @param file a handle to the backing file to use for storage.
+ * @throws MessagingException an exception occurs parsing the resulting body part in file.
+ * @throws IOException an exception occurs accessing file or content.
+ */
+ public FileBackedMimeBodyPart(
+ InputStream content,
+ File file)
+ throws MessagingException, IOException
+ {
+ this(saveStreamToFile(content, file));
+ }
+
+ /**
+ * Create a MimeBodyPart backed by file, with the headers
+ * given in headers and body content taken from the stream body.
+ *
+ * @param headers headers for the body part.
+ * @param body internal content for the body part.
+ * @param file backing file to use.
+ *
+ * @throws MessagingException if the body part can't be produced.
+ * @throws IOException if there is an issue reading stream or writing to file.
+ */
+ public FileBackedMimeBodyPart(
+ InternetHeaders headers,
+ InputStream body,
+ File file)
+ throws MessagingException, IOException
+ {
+ this(saveStreamToFile(headers, body, file));
+ }
+
+ public void writeTo(
+ OutputStream out)
+ throws IOException, MessagingException
+ {
+ if (!_file.exists())
+ {
+ throw new IOException("file " + _file.getCanonicalPath() + " no longer exists.");
+ }
+
+ super.writeTo(out);
+ }
+
+ /**
+ * Close off the underlying shared streams and remove the backing file.
+ *
+ * @throws IOException if streams cannot be closed or the file cannot be deleted.
+ */
+ public void dispose()
+ throws IOException
+ {
+ ((SharedFileInputStream)contentStream).getRoot().dispose();
+
+ if (_file.exists() && !_file.delete())
+ {
+ throw new IOException("deletion of underlying file <" + _file.getCanonicalPath() + "> failed.");
+ }
+ }
+
+ private static File saveStreamToFile(InputStream content, File tempFile)
+ throws IOException
+ {
+ saveContentToStream(new FileOutputStream(tempFile), content);
+
+ return tempFile;
+ }
+
+ private static File saveStreamToFile(InternetHeaders headers, InputStream content, File tempFile)
+ throws IOException
+ {
+ OutputStream out = new FileOutputStream(tempFile);
+ Enumeration en = headers.getAllHeaderLines();
+
+ while (en.hasMoreElements())
+ {
+ writeHeader(out, (String)en.nextElement());
+ }
+
+ writeSeperator(out);
+
+ saveContentToStream(out, content);
+
+ return tempFile;
+ }
+
+
+ private static void writeHeader(OutputStream out, String header)
+ throws IOException
+ {
+ for (int i = 0; i != header.length(); i++)
+ {
+ out.write(header.charAt(i));
+ }
+
+ writeSeperator(out);
+ }
+
+ private static void writeSeperator(OutputStream out)
+ throws IOException
+ {
+ out.write('\r');
+ out.write('\n');
+ }
+
+ private static void saveContentToStream(
+ OutputStream out,
+ InputStream content)
+ throws IOException
+ {
+ byte[] buf = new byte[BUF_SIZE];
+ int len;
+
+ while ((len = content.read(buf, 0, buf.length)) > 0)
+ {
+ out.write(buf, 0, len);
+ }
+
+ out.close();
+ content.close();
+ }
+ }
diff --git a/mail/src/main/java/org/spongycastle/mail/smime/util/SharedFileInputStream.java b/mail/src/main/java/org/spongycastle/mail/smime/util/SharedFileInputStream.java
new file mode 100644
index 00000000..97cfd9c0
--- /dev/null
+++ b/mail/src/main/java/org/spongycastle/mail/smime/util/SharedFileInputStream.java
@@ -0,0 +1,241 @@
+package org.bouncycastle.mail.smime.util;
+
+import javax.mail.internet.SharedInputStream;
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+
+public class SharedFileInputStream
+ extends FilterInputStream
+ implements SharedInputStream
+{
+ private final SharedFileInputStream _parent;
+ private final File _file;
+ private final long _start;
+ private final long _length;
+
+ private long _position;
+ private long _markedPosition;
+
+ private List _subStreams = new LinkedList();
+
+ public SharedFileInputStream(
+ String fileName)
+ throws IOException
+ {
+ this(new File(fileName));
+ }
+
+ public SharedFileInputStream(
+ File file)
+ throws IOException
+ {
+ this(file, 0, file.length());
+ }
+
+ private SharedFileInputStream(
+ File file,
+ long start,
+ long length)
+ throws IOException
+ {
+ super(new BufferedInputStream(new FileInputStream(file)));
+
+ _parent = null;
+ _file = file;
+ _start = start;
+ _length = length;
+
+ in.skip(start);
+ }
+
+ private SharedFileInputStream(
+ SharedFileInputStream parent,
+ long start,
+ long length)
+ throws IOException
+ {
+ super(new BufferedInputStream(new FileInputStream(parent._file)));
+
+ _parent = parent;
+ _file = parent._file;
+ _start = start;
+ _length = length;
+
+ in.skip(start);
+ }
+
+ public long getPosition()
+ {
+ return _position;
+ }
+
+ public InputStream newStream(long start, long finish)
+ {
+ try
+ {
+ SharedFileInputStream stream;
+
+ if (finish < 0)
+ {
+ if (_length > 0)
+ {
+ stream = new SharedFileInputStream(this, _start + start, _length - start);
+ }
+ else if (_length == 0)
+ {
+ stream = new SharedFileInputStream(this, _start + start, 0);
+ }
+ else
+ {
+ stream = new SharedFileInputStream(this, _start + start, -1);
+ }
+ }
+ else
+ {
+ stream = new SharedFileInputStream(this, _start + start, finish - start);
+ }
+
+ _subStreams.add(stream);
+
+ return stream;
+ }
+ catch (IOException e)
+ {
+ throw new IllegalStateException("unable to create shared stream: " + e);
+ }
+ }
+
+ public int read(
+ byte[] buf)
+ throws IOException
+ {
+ return this.read(buf, 0, buf.length);
+ }
+
+ public int read(
+ byte[] buf,
+ int off,
+ int len)
+ throws IOException
+ {
+ int count = 0;
+
+ if (len == 0)
+ {
+ return 0;
+ }
+
+ while (count < len)
+ {
+ int ch = this.read();
+
+ if (ch < 0)
+ {
+ break;
+ }
+
+ buf[off + count] = (byte)ch;
+ count++;
+ }
+
+ if (count == 0)
+ {
+ return -1; // EOF
+ }
+
+ return count;
+ }
+
+ public int read()
+ throws IOException
+ {
+ if (_position == _length)
+ {
+ return -1;
+ }
+
+ _position++;
+ return in.read();
+ }
+
+ public boolean markSupported()
+ {
+ return true;
+ }
+
+ public long skip(long n)
+ throws IOException
+ {
+ long count;
+
+ for (count = 0; count != n; count++)
+ {
+ if (this.read() < 0)
+ {
+ break;
+ }
+ }
+
+ return count;
+ }
+
+ public void mark(
+ int readLimit)
+ {
+ _markedPosition = _position;
+ in.mark(readLimit);
+ }
+
+ public void reset()
+ throws IOException
+ {
+ _position = _markedPosition;
+ in.reset();
+ }
+
+ /**
+ * Return the shared stream that represents the top most stream that
+ * this stream inherits from.
+ * @return the base of the shared stream tree.
+ */
+ public SharedFileInputStream getRoot()
+ {
+ if (_parent != null)
+ {
+ return _parent.getRoot();
+ }
+
+ return this;
+ }
+
+ /**
+ * Close of this stream and any substreams that have been created from it.
+ * @throws IOException on problem closing the main stream.
+ */
+ public void dispose()
+ throws IOException
+ {
+ Iterator it = _subStreams.iterator();
+
+ while (it.hasNext())
+ {
+ try
+ {
+ ((SharedFileInputStream)it.next()).dispose();
+ }
+ catch (IOException e)
+ {
+ // ignore
+ }
+ }
+
+ in.close();
+ }
+}
diff --git a/mail/src/main/java/org/spongycastle/mail/smime/validator/SignedMailValidator.java b/mail/src/main/java/org/spongycastle/mail/smime/validator/SignedMailValidator.java
new file mode 100644
index 00000000..01c1c514
--- /dev/null
+++ b/mail/src/main/java/org/spongycastle/mail/smime/validator/SignedMailValidator.java
@@ -0,0 +1,957 @@
+package org.bouncycastle.mail.smime.validator;
+
+import java.io.IOException;
+import java.security.GeneralSecurityException;
+import java.security.PublicKey;
+import java.security.cert.CertPath;
+import java.security.cert.CertStore;
+import java.security.cert.CertStoreException;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.CertificateExpiredException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.CertificateNotYetValidException;
+import java.security.cert.PKIXParameters;
+import java.security.cert.TrustAnchor;
+import java.security.cert.X509CertSelector;
+import java.security.cert.X509Certificate;
+import java.security.interfaces.DSAPublicKey;
+import java.security.interfaces.RSAPublicKey;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.Vector;
+
+import javax.mail.Address;
+import javax.mail.MessagingException;
+import javax.mail.internet.InternetAddress;
+import javax.mail.internet.MimeMessage;
+import javax.mail.internet.MimeMultipart;
+
+import org.bouncycastle.asn1.ASN1Encoding;
+import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.ASN1TaggedObject;
+import org.bouncycastle.asn1.DERIA5String;
+import org.bouncycastle.asn1.DEROctetString;
+import org.bouncycastle.asn1.cms.Attribute;
+import org.bouncycastle.asn1.cms.AttributeTable;
+import org.bouncycastle.asn1.cms.CMSAttributes;
+import org.bouncycastle.asn1.cms.Time;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.x509.AuthorityKeyIdentifier;
+import org.bouncycastle.asn1.x509.ExtendedKeyUsage;
+import org.bouncycastle.asn1.x509.KeyPurposeId;
+import org.bouncycastle.asn1.x509.X509Extensions;
+import org.bouncycastle.cert.jcajce.JcaCertStoreBuilder;
+import org.bouncycastle.cms.SignerInformation;
+import org.bouncycastle.cms.SignerInformationStore;
+import org.bouncycastle.cms.jcajce.JcaSimpleSignerInfoVerifierBuilder;
+import org.bouncycastle.cms.jcajce.JcaX509CertSelectorConverter;
+import org.bouncycastle.i18n.ErrorBundle;
+import org.bouncycastle.i18n.filter.TrustedInput;
+import org.bouncycastle.i18n.filter.UntrustedInput;
+import org.bouncycastle.jce.PrincipalUtil;
+import org.bouncycastle.jce.X509Principal;
+import org.bouncycastle.mail.smime.SMIMESigned;
+import org.bouncycastle.util.Integers;
+import org.bouncycastle.x509.CertPathReviewerException;
+import org.bouncycastle.x509.PKIXCertPathReviewer;
+
+public class SignedMailValidator
+{
+ private static final String RESOURCE_NAME = "org.bouncycastle.mail.smime.validator.SignedMailValidatorMessages";
+
+ private static final Class DEFAULT_CERT_PATH_REVIEWER = PKIXCertPathReviewer.class;
+
+ private static final String EXT_KEY_USAGE = X509Extensions.ExtendedKeyUsage
+ .getId();
+
+ private static final String SUBJECT_ALTERNATIVE_NAME = X509Extensions.SubjectAlternativeName
+ .getId();
+
+ private static final int shortKeyLength = 512;
+
+ // (365.25*30)*24*3600*1000
+ private static final long THIRTY_YEARS_IN_MILLI_SEC = 21915l * 12l * 3600l * 1000l;
+
+ private static final JcaX509CertSelectorConverter selectorConverter = new JcaX509CertSelectorConverter();
+
+ private CertStore certs;
+
+ private SignerInformationStore signers;
+
+ private Map results;
+
+ private String[] fromAddresses;
+
+ private Class certPathReviewerClass;
+
+ /**
+ * Validates the signed {@link MimeMessage} message. The
+ * {@link PKIXParameters} from param are used for the certificate path
+ * validation. The actual PKIXParameters used for the certificate path
+ * validation is a copy of param with the followin changes: <br> - The
+ * validation date is changed to the signature time <br> - A CertStore with
+ * certificates and crls from the mail message is added to the CertStores.<br>
+ * <br>
+ * In <code>param</code> it's also possible to add additional CertStores
+ * with intermediate Certificates and/or CRLs which then are also used for
+ * the validation.
+ *
+ * @param message the signed MimeMessage
+ * @param param the parameters for the certificate path validation
+ * @throws SignedMailValidatorException if the message is no signed message or if an exception occurs
+ * reading the message
+ */
+ public SignedMailValidator(MimeMessage message, PKIXParameters param)
+ throws SignedMailValidatorException
+ {
+ this(message, param, DEFAULT_CERT_PATH_REVIEWER);
+ }
+
+ /**
+ * Validates the signed {@link MimeMessage} message. The
+ * {@link PKIXParameters} from param are used for the certificate path
+ * validation. The actual PKIXParameters used for the certificate path
+ * validation is a copy of param with the followin changes: <br> - The
+ * validation date is changed to the signature time <br> - A CertStore with
+ * certificates and crls from the mail message is added to the CertStores.<br>
+ * <br>
+ * In <code>param</code> it's also possible to add additional CertStores
+ * with intermediate Certificates and/or CRLs which then are also used for
+ * the validation.
+ *
+ * @param message the signed MimeMessage
+ * @param param the parameters for the certificate path validation
+ * @param certPathReviewerClass a subclass of {@link PKIXCertPathReviewer}. The SignedMailValidator
+ * uses objects of this type for the cert path vailidation. The class must
+ * have an empty constructor.
+ * @throws SignedMailValidatorException if the message is no signed message or if an exception occurs
+ * reading the message
+ * @throws IllegalArgumentException if the certPathReviewerClass is not a
+ * subclass of {@link PKIXCertPathReviewer} or objects of
+ * certPathReviewerClass can not be instantiated
+ */
+ public SignedMailValidator(MimeMessage message, PKIXParameters param, Class certPathReviewerClass)
+ throws SignedMailValidatorException
+ {
+ this.certPathReviewerClass = certPathReviewerClass;
+ boolean isSubclass = DEFAULT_CERT_PATH_REVIEWER.isAssignableFrom(certPathReviewerClass);
+ if (!isSubclass)
+ {
+ throw new IllegalArgumentException("certPathReviewerClass is not a subclass of " + DEFAULT_CERT_PATH_REVIEWER.getName());
+ }
+
+ SMIMESigned s;
+
+ try
+ {
+ // check if message is multipart signed
+ if (message.isMimeType("multipart/signed"))
+ {
+ MimeMultipart mimemp = (MimeMultipart)message.getContent();
+ s = new SMIMESigned(mimemp);
+ }
+ else if (message.isMimeType("application/pkcs7-mime")
+ || message.isMimeType("application/x-pkcs7-mime"))
+ {
+ s = new SMIMESigned(message);
+ }
+ else
+ {
+ ErrorBundle msg = new ErrorBundle(RESOURCE_NAME,
+ "SignedMailValidator.noSignedMessage");
+ throw new SignedMailValidatorException(msg);
+ }
+
+ // save certstore and signerInformationStore
+ certs = new JcaCertStoreBuilder().addCertificates(s.getCertificates()).addCRLs(s.getCRLs()).setProvider("BC").build();
+ signers = s.getSignerInfos();
+
+ // save "from" addresses from message
+ Address[] froms = message.getFrom();
+ InternetAddress sender = null;
+ try
+ {
+ if (message.getHeader("Sender") != null)
+ {
+ sender = new InternetAddress(message.getHeader("Sender")[0]);
+ }
+ }
+ catch (MessagingException ex)
+ {
+ //ignore garbage in Sender: header
+ }
+
+ int fromsLength = (froms != null) ? froms.length : 0;
+ fromAddresses = new String[fromsLength + ((sender != null) ? 1 : 0)];
+ for (int i = 0; i < froms.length; i++)
+ {
+ InternetAddress inetAddr = (InternetAddress)froms[i];
+ fromAddresses[i] = inetAddr.getAddress();
+ }
+ if (sender != null)
+ {
+ fromAddresses[froms.length] = sender.getAddress();
+ }
+
+ // initialize results
+ results = new HashMap();
+ }
+ catch (Exception e)
+ {
+ if (e instanceof SignedMailValidatorException)
+ {
+ throw (SignedMailValidatorException)e;
+ }
+ // exception reading message
+ ErrorBundle msg = new ErrorBundle(RESOURCE_NAME,
+ "SignedMailValidator.exceptionReadingMessage",
+ new Object[]{e.getMessage(), e, e.getClass().getName()});
+ throw new SignedMailValidatorException(msg, e);
+ }
+
+ // validate signatues
+ validateSignatures(param);
+ }
+
+ protected void validateSignatures(PKIXParameters pkixParam)
+ {
+ PKIXParameters usedParameters = (PKIXParameters)pkixParam.clone();
+
+ // add crls and certs from mail
+ usedParameters.addCertStore(certs);
+
+ Collection c = signers.getSigners();
+ Iterator it = c.iterator();
+
+ // check each signer
+ while (it.hasNext())
+ {
+ List errors = new ArrayList();
+ List notifications = new ArrayList();
+
+ SignerInformation signer = (SignerInformation)it.next();
+ // signer certificate
+ X509Certificate cert = null;
+
+ try
+ {
+ Collection certCollection = findCerts(usedParameters
+ .getCertStores(), selectorConverter.getCertSelector(signer.getSID()));
+
+ Iterator certIt = certCollection.iterator();
+ if (certIt.hasNext())
+ {
+ cert = (X509Certificate)certIt.next();
+ }
+ }
+ catch (CertStoreException cse)
+ {
+ ErrorBundle msg = new ErrorBundle(RESOURCE_NAME,
+ "SignedMailValidator.exceptionRetrievingSignerCert",
+ new Object[]{cse.getMessage(), cse, cse.getClass().getName()});
+ errors.add(msg);
+ }
+
+ if (cert != null)
+ {
+ // check signature
+ boolean validSignature = false;
+ try
+ {
+ validSignature = signer.verify(new JcaSimpleSignerInfoVerifierBuilder().setProvider("BC").build(cert.getPublicKey()));
+ if (!validSignature)
+ {
+ ErrorBundle msg = new ErrorBundle(RESOURCE_NAME,
+ "SignedMailValidator.signatureNotVerified");
+ errors.add(msg);
+ }
+ }
+ catch (Exception e)
+ {
+ ErrorBundle msg = new ErrorBundle(RESOURCE_NAME,
+ "SignedMailValidator.exceptionVerifyingSignature",
+ new Object[]{e.getMessage(), e, e.getClass().getName()});
+ errors.add(msg);
+ }
+
+ // check signer certificate (mail address, key usage, etc)
+ checkSignerCert(cert, errors, notifications);
+
+ // notify if a signed receip request is in the message
+ AttributeTable atab = signer.getSignedAttributes();
+ if (atab != null)
+ {
+ Attribute attr = atab.get(PKCSObjectIdentifiers.id_aa_receiptRequest);
+ if (attr != null)
+ {
+ ErrorBundle msg = new ErrorBundle(RESOURCE_NAME,
+ "SignedMailValidator.signedReceiptRequest");
+ notifications.add(msg);
+ }
+ }
+
+ // check certificate path
+
+ // get signing time if possible, otherwise use current time as
+ // signing time
+ Date signTime = getSignatureTime(signer);
+ if (signTime == null) // no signing time was found
+ {
+ ErrorBundle msg = new ErrorBundle(RESOURCE_NAME,
+ "SignedMailValidator.noSigningTime");
+ errors.add(msg);
+ signTime = new Date();
+ }
+ else
+ {
+ // check if certificate was valid at signing time
+ try
+ {
+ cert.checkValidity(signTime);
+ }
+ catch (CertificateExpiredException e)
+ {
+ ErrorBundle msg = new ErrorBundle(RESOURCE_NAME,
+ "SignedMailValidator.certExpired",
+ new Object[]{new TrustedInput(signTime), new TrustedInput(cert.getNotAfter())});
+ errors.add(msg);
+ }
+ catch (CertificateNotYetValidException e)
+ {
+ ErrorBundle msg = new ErrorBundle(RESOURCE_NAME,
+ "SignedMailValidator.certNotYetValid",
+ new Object[]{new TrustedInput(signTime), new TrustedInput(cert.getNotBefore())});
+ errors.add(msg);
+ }
+ }
+ usedParameters.setDate(signTime);
+
+ try
+ {
+ // construct cert chain
+ CertPath certPath;
+ List userProvidedList;
+
+ List userCertStores = new ArrayList();
+ userCertStores.add(certs);
+ Object[] cpres = createCertPath(cert, usedParameters.getTrustAnchors(), pkixParam.getCertStores(), userCertStores);
+ certPath = (CertPath)cpres[0];
+ userProvidedList = (List)cpres[1];
+
+ // validate cert chain
+ PKIXCertPathReviewer review;
+ try
+ {
+ review = (PKIXCertPathReviewer)certPathReviewerClass.newInstance();
+ }
+ catch (IllegalAccessException e)
+ {
+ throw new IllegalArgumentException("Cannot instantiate object of type " +
+ certPathReviewerClass.getName() + ": " + e.getMessage());
+ }
+ catch (InstantiationException e)
+ {
+ throw new IllegalArgumentException("Cannot instantiate object of type " +
+ certPathReviewerClass.getName() + ": " + e.getMessage());
+ }
+ review.init(certPath, usedParameters);
+ if (!review.isValidCertPath())
+ {
+ ErrorBundle msg = new ErrorBundle(RESOURCE_NAME,
+ "SignedMailValidator.certPathInvalid");
+ errors.add(msg);
+ }
+ results.put(signer, new ValidationResult(review,
+ validSignature, errors, notifications, userProvidedList));
+ }
+ catch (GeneralSecurityException gse)
+ {
+ // cannot create cert path
+ ErrorBundle msg = new ErrorBundle(RESOURCE_NAME,
+ "SignedMailValidator.exceptionCreateCertPath",
+ new Object[]{gse.getMessage(), gse, gse.getClass().getName()});
+ errors.add(msg);
+ results.put(signer, new ValidationResult(null,
+ validSignature, errors, notifications, null));
+ }
+ catch (CertPathReviewerException cpre)
+ {
+ // cannot initialize certpathreviewer - wrong parameters
+ errors.add(cpre.getErrorMessage());
+ results.put(signer, new ValidationResult(null,
+ validSignature, errors, notifications, null));
+ }
+ }
+ else
+ // no signer certificate found
+ {
+ ErrorBundle msg = new ErrorBundle(RESOURCE_NAME,
+ "SignedMailValidator.noSignerCert");
+ errors.add(msg);
+ results.put(signer, new ValidationResult(null, false, errors,
+ notifications, null));
+ }
+ }
+ }
+
+ public static Set getEmailAddresses(X509Certificate cert)
+ throws IOException, CertificateEncodingException
+ {
+ Set addresses = new HashSet();
+
+ X509Principal name = PrincipalUtil.getSubjectX509Principal(cert);
+ Vector oids = name.getOIDs();
+ Vector names = name.getValues();
+ for (int i = 0; i < oids.size(); i++)
+ {
+ if (oids.get(i).equals(X509Principal.EmailAddress))
+ {
+ String email = ((String)names.get(i)).toLowerCase();
+ addresses.add(email);
+ break;
+ }
+ }
+
+ byte[] ext = cert.getExtensionValue(SUBJECT_ALTERNATIVE_NAME);
+ if (ext != null)
+ {
+ ASN1Sequence altNames = ASN1Sequence.getInstance(getObject(ext));
+ for (int j = 0; j < altNames.size(); j++)
+ {
+ ASN1TaggedObject o = (ASN1TaggedObject)altNames
+ .getObjectAt(j);
+
+ if (o.getTagNo() == 1)
+ {
+ String email = DERIA5String.getInstance(o, false)
+ .getString().toLowerCase();
+ addresses.add(email);
+ }
+ }
+ }
+
+ return addresses;
+ }
+
+ private static ASN1Primitive getObject(byte[] ext)
+ throws IOException
+ {
+ ASN1InputStream aIn = new ASN1InputStream(ext);
+ ASN1OctetString octs = (ASN1OctetString)aIn.readObject();
+
+ aIn = new ASN1InputStream(octs.getOctets());
+ return aIn.readObject();
+ }
+
+ protected void checkSignerCert(X509Certificate cert, List errors,
+ List notifications)
+ {
+ // get key length
+ PublicKey key = cert.getPublicKey();
+ int keyLenght = -1;
+ if (key instanceof RSAPublicKey)
+ {
+ keyLenght = ((RSAPublicKey)key).getModulus().bitLength();
+ }
+ else if (key instanceof DSAPublicKey)
+ {
+ keyLenght = ((DSAPublicKey)key).getParams().getP().bitLength();
+ }
+ if (keyLenght != -1 && keyLenght <= shortKeyLength)
+ {
+ ErrorBundle msg = new ErrorBundle(RESOURCE_NAME,
+ "SignedMailValidator.shortSigningKey",
+ new Object[]{Integers.valueOf(keyLenght)});
+ notifications.add(msg);
+ }
+
+ // warn if certificate has very long validity period
+ long validityPeriod = cert.getNotAfter().getTime() - cert.getNotBefore().getTime();
+ if (validityPeriod > THIRTY_YEARS_IN_MILLI_SEC)
+ {
+ ErrorBundle msg = new ErrorBundle(RESOURCE_NAME,
+ "SignedMailValidator.longValidity",
+ new Object[]{new TrustedInput(cert.getNotBefore()), new TrustedInput(cert.getNotAfter())});
+ notifications.add(msg);
+ }
+
+ // check key usage if digitalSignature or nonRepudiation is set
+ boolean[] keyUsage = cert.getKeyUsage();
+ if (keyUsage != null && !keyUsage[0] && !keyUsage[1])
+ {
+ ErrorBundle msg = new ErrorBundle(RESOURCE_NAME,
+ "SignedMailValidator.signingNotPermitted");
+ errors.add(msg);
+ }
+
+ // check extended key usage
+ try
+ {
+ byte[] ext = cert.getExtensionValue(EXT_KEY_USAGE);
+ if (ext != null)
+ {
+ ExtendedKeyUsage extKeyUsage = ExtendedKeyUsage
+ .getInstance(getObject(ext));
+ if (!extKeyUsage
+ .hasKeyPurposeId(KeyPurposeId.anyExtendedKeyUsage)
+ && !extKeyUsage
+ .hasKeyPurposeId(KeyPurposeId.id_kp_emailProtection))
+ {
+ ErrorBundle msg = new ErrorBundle(RESOURCE_NAME,
+ "SignedMailValidator.extKeyUsageNotPermitted");
+ errors.add(msg);
+ }
+ }
+ }
+ catch (Exception e)
+ {
+ ErrorBundle msg = new ErrorBundle(RESOURCE_NAME,
+ "SignedMailValidator.extKeyUsageError", new Object[]{
+ e.getMessage(), e, e.getClass().getName()}
+ );
+ errors.add(msg);
+ }
+
+ // cert has an email address
+ try
+ {
+ Set certEmails = getEmailAddresses(cert);
+ if (certEmails.isEmpty())
+ {
+ // error no email address in signing certificate
+ ErrorBundle msg = new ErrorBundle(RESOURCE_NAME,
+ "SignedMailValidator.noEmailInCert");
+ errors.add(msg);
+ }
+ else
+ {
+ // check if email in cert is equal to the from address in the
+ // message
+ boolean equalsFrom = false;
+ for (int i = 0; i < fromAddresses.length; i++)
+ {
+ if (certEmails.contains(fromAddresses[i].toLowerCase()))
+ {
+ equalsFrom = true;
+ break;
+ }
+ }
+ if (!equalsFrom)
+ {
+ ErrorBundle msg = new ErrorBundle(RESOURCE_NAME,
+ "SignedMailValidator.emailFromCertMismatch",
+ new Object[]{
+ new UntrustedInput(
+ addressesToString(fromAddresses)),
+ new UntrustedInput(certEmails)}
+ );
+ errors.add(msg);
+ }
+ }
+ }
+ catch (Exception e)
+ {
+ ErrorBundle msg = new ErrorBundle(RESOURCE_NAME,
+ "SignedMailValidator.certGetEmailError", new Object[]{
+ e.getMessage(), e, e.getClass().getName()}
+ );
+ errors.add(msg);
+ }
+ }
+
+ static String addressesToString(Object[] a)
+ {
+ if (a == null)
+ {
+ return "null";
+ }
+
+ StringBuffer b = new StringBuffer();
+ b.append('[');
+
+ for (int i = 0; i != a.length; i++)
+ {
+ if (i > 0)
+ {
+ b.append(", ");
+ }
+ b.append(String.valueOf(a[i]));
+ }
+
+ return b.append(']').toString();
+ }
+
+ public static Date getSignatureTime(SignerInformation signer)
+ {
+ AttributeTable atab = signer.getSignedAttributes();
+ Date result = null;
+ if (atab != null)
+ {
+ Attribute attr = atab.get(CMSAttributes.signingTime);
+ if (attr != null)
+ {
+ Time t = Time.getInstance(attr.getAttrValues().getObjectAt(0)
+ .toASN1Primitive());
+ result = t.getDate();
+ }
+ }
+ return result;
+ }
+
+ private static List findCerts(List certStores, X509CertSelector selector)
+ throws CertStoreException
+ {
+ List result = new ArrayList();
+ Iterator it = certStores.iterator();
+ while (it.hasNext())
+ {
+ CertStore store = (CertStore)it.next();
+ Collection coll = store.getCertificates(selector);
+ result.addAll(coll);
+ }
+ return result;
+ }
+
+ private static X509Certificate findNextCert(List certStores, X509CertSelector selector, Set certSet)
+ throws CertStoreException
+ {
+ Iterator certIt = findCerts(certStores, selector).iterator();
+
+ boolean certFound = false;
+ X509Certificate nextCert = null;
+ while (certIt.hasNext())
+ {
+ nextCert = (X509Certificate)certIt.next();
+ if (!certSet.contains(nextCert))
+ {
+ certFound = true;
+ break;
+ }
+ }
+
+ return certFound ? nextCert : null;
+ }
+
+ /**
+ * @param signerCert the end of the path
+ * @param trustanchors trust anchors for the path
+ * @param certStores
+ * @return the resulting certificate path.
+ * @throws GeneralSecurityException
+ */
+ public static CertPath createCertPath(X509Certificate signerCert,
+ Set trustanchors, List certStores)
+ throws GeneralSecurityException
+ {
+ Object[] results = createCertPath(signerCert, trustanchors, certStores, null);
+ return (CertPath)results[0];
+ }
+
+ /**
+ * Returns an Object array containing a CertPath and a List of Booleans. The list contains the value <code>true</code>
+ * if the corresponding certificate in the CertPath was taken from the user provided CertStores.
+ *
+ * @param signerCert the end of the path
+ * @param trustanchors trust anchors for the path
+ * @param systemCertStores list of {@link CertStore} provided by the system
+ * @param userCertStores list of {@link CertStore} provided by the user
+ * @return a CertPath and a List of booleans.
+ * @throws GeneralSecurityException
+ */
+ public static Object[] createCertPath(X509Certificate signerCert,
+ Set trustanchors, List systemCertStores, List userCertStores)
+ throws GeneralSecurityException
+ {
+ Set certSet = new LinkedHashSet();
+ List userProvidedList = new ArrayList();
+
+ // add signer certificate
+
+ X509Certificate cert = signerCert;
+ certSet.add(cert);
+ userProvidedList.add(new Boolean(true));
+
+ boolean trustAnchorFound = false;
+
+ X509Certificate taCert = null;
+
+ // add other certs to the cert path
+ while (cert != null && !trustAnchorFound)
+ {
+ // check if cert Issuer is Trustanchor
+ Iterator trustIt = trustanchors.iterator();
+ while (trustIt.hasNext())
+ {
+ TrustAnchor anchor = (TrustAnchor)trustIt.next();
+ X509Certificate anchorCert = anchor.getTrustedCert();
+ if (anchorCert != null)
+ {
+ if (anchorCert.getSubjectX500Principal().equals(
+ cert.getIssuerX500Principal()))
+ {
+ try
+ {
+ cert.verify(anchorCert.getPublicKey(), "BC");
+ trustAnchorFound = true;
+ taCert = anchorCert;
+ break;
+ }
+ catch (Exception e)
+ {
+ // trustanchor not found
+ }
+ }
+ }
+ else
+ {
+ if (anchor.getCAName().equals(
+ cert.getIssuerX500Principal().getName()))
+ {
+ try
+ {
+ cert.verify(anchor.getCAPublicKey(), "BC");
+ trustAnchorFound = true;
+ break;
+ }
+ catch (Exception e)
+ {
+ // trustanchor not found
+ }
+ }
+ }
+ }
+
+ if (!trustAnchorFound)
+ {
+ // add next cert to path
+ X509CertSelector select = new X509CertSelector();
+ try
+ {
+ select.setSubject(cert.getIssuerX500Principal().getEncoded());
+ }
+ catch (IOException e)
+ {
+ throw new IllegalStateException(e.toString());
+ }
+ byte[] authKeyIdentBytes = cert.getExtensionValue(X509Extensions.AuthorityKeyIdentifier.getId());
+ if (authKeyIdentBytes != null)
+ {
+ try
+ {
+ AuthorityKeyIdentifier kid = AuthorityKeyIdentifier.getInstance(getObject(authKeyIdentBytes));
+ if (kid.getKeyIdentifier() != null)
+ {
+ select.setSubjectKeyIdentifier(new DEROctetString(kid.getKeyIdentifier()).getEncoded(ASN1Encoding.DER));
+ }
+ }
+ catch (IOException ioe)
+ {
+ // ignore
+ }
+ }
+ boolean userProvided = false;
+
+ cert = findNextCert(systemCertStores, select, certSet);
+ if (cert == null && userCertStores != null)
+ {
+ userProvided = true;
+ cert = findNextCert(userCertStores, select, certSet);
+ }
+
+ if (cert != null)
+ {
+ // cert found
+ certSet.add(cert);
+ userProvidedList.add(new Boolean(userProvided));
+ }
+ }
+ }
+
+ // if a trustanchor was found - try to find a selfsigned certificate of
+ // the trustanchor
+ if (trustAnchorFound)
+ {
+ if (taCert != null && taCert.getSubjectX500Principal().equals(taCert.getIssuerX500Principal()))
+ {
+ certSet.add(taCert);
+ userProvidedList.add(new Boolean(false));
+ }
+ else
+ {
+ X509CertSelector select = new X509CertSelector();
+
+ try
+ {
+ select.setSubject(cert.getIssuerX500Principal().getEncoded());
+ select.setIssuer(cert.getIssuerX500Principal().getEncoded());
+ }
+ catch (IOException e)
+ {
+ throw new IllegalStateException(e.toString());
+ }
+
+ boolean userProvided = false;
+
+ taCert = findNextCert(systemCertStores, select, certSet);
+ if (taCert == null && userCertStores != null)
+ {
+ userProvided = true;
+ taCert = findNextCert(userCertStores, select, certSet);
+ }
+ if (taCert != null)
+ {
+ try
+ {
+ cert.verify(taCert.getPublicKey(), "BC");
+ certSet.add(taCert);
+ userProvidedList.add(new Boolean(userProvided));
+ }
+ catch (GeneralSecurityException gse)
+ {
+ // wrong cert
+ }
+ }
+ }
+ }
+
+ CertPath certPath = CertificateFactory.getInstance("X.509", "BC").generateCertPath(new ArrayList(certSet));
+ return new Object[]{certPath, userProvidedList};
+ }
+
+ public CertStore getCertsAndCRLs()
+ {
+ return certs;
+ }
+
+ public SignerInformationStore getSignerInformationStore()
+ {
+ return signers;
+ }
+
+ public ValidationResult getValidationResult(SignerInformation signer)
+ throws SignedMailValidatorException
+ {
+ if (signers.getSigners(signer.getSID()).isEmpty())
+ {
+ // the signer is not part of the SignerInformationStore
+ // he has not signed the message
+ ErrorBundle msg = new ErrorBundle(RESOURCE_NAME,
+ "SignedMailValidator.wrongSigner");
+ throw new SignedMailValidatorException(msg);
+ }
+ else
+ {
+ return (ValidationResult)results.get(signer);
+ }
+ }
+
+ public class ValidationResult
+ {
+
+ private PKIXCertPathReviewer review;
+
+ private List errors;
+
+ private List notifications;
+
+ private List userProvidedCerts;
+
+ private boolean signVerified;
+
+ ValidationResult(PKIXCertPathReviewer review, boolean verified,
+ List errors, List notifications, List userProvidedCerts)
+ {
+ this.review = review;
+ this.errors = errors;
+ this.notifications = notifications;
+ signVerified = verified;
+ this.userProvidedCerts = userProvidedCerts;
+ }
+
+ /**
+ * Returns a list of error messages of type {@link ErrorBundle}.
+ *
+ * @return List of error messages
+ */
+ public List getErrors()
+ {
+ return errors;
+ }
+
+ /**
+ * Returns a list of notification messages of type {@link ErrorBundle}.
+ *
+ * @return List of notification messages
+ */
+ public List getNotifications()
+ {
+ return notifications;
+ }
+
+ /**
+ * @return the PKIXCertPathReviewer for the CertPath of this signature
+ * or null if an Exception occured.
+ */
+ public PKIXCertPathReviewer getCertPathReview()
+ {
+ return review;
+ }
+
+ /**
+ * @return the CertPath for this signature
+ * or null if an Exception occured.
+ */
+ public CertPath getCertPath()
+ {
+ return review != null ? review.getCertPath() : null;
+ }
+
+ /**
+ * @return a List of Booleans that are true if the corresponding certificate in the CertPath was taken from
+ * the CertStore of the SMIME message
+ */
+ public List getUserProvidedCerts()
+ {
+ return userProvidedCerts;
+ }
+
+ /**
+ * @return true if the signature corresponds to the public key of the
+ * signer
+ */
+ public boolean isVerifiedSignature()
+ {
+ return signVerified;
+ }
+
+ /**
+ * @return true if the signature is valid (ie. if it corresponds to the
+ * public key of the signer and the cert path for the signers
+ * certificate is also valid)
+ */
+ public boolean isValidSignature()
+ {
+ if (review != null)
+ {
+ return signVerified && review.isValidCertPath()
+ && errors.isEmpty();
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ }
+}
diff --git a/mail/src/main/java/org/spongycastle/mail/smime/validator/SignedMailValidatorException.java b/mail/src/main/java/org/spongycastle/mail/smime/validator/SignedMailValidatorException.java
new file mode 100644
index 00000000..06f14618
--- /dev/null
+++ b/mail/src/main/java/org/spongycastle/mail/smime/validator/SignedMailValidatorException.java
@@ -0,0 +1,19 @@
+package org.bouncycastle.mail.smime.validator;
+
+import org.bouncycastle.i18n.ErrorBundle;
+import org.bouncycastle.i18n.LocalizedException;
+
+public class SignedMailValidatorException extends LocalizedException
+{
+
+ public SignedMailValidatorException(ErrorBundle errorMessage, Throwable throwable)
+ {
+ super(errorMessage, throwable);
+ }
+
+ public SignedMailValidatorException(ErrorBundle errorMessage)
+ {
+ super(errorMessage);
+ }
+
+}