Add spongy castle sources to libraries folder
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
package org.bouncycastle.mail.smime;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
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
|
||||
{
|
||||
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");
|
||||
|
||||
CommandMap.setDefaultCommandMap(mc);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,272 @@
|
||||
package org.bouncycastle.mail.smime;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
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
|
||||
{
|
||||
CommandMap.setDefaultCommandMap(addCommands(CommandMap.getDefaultCommandMap()));
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,230 @@
|
||||
package org.bouncycastle.mail.smime;
|
||||
|
||||
import org.bouncycastle.cms.CMSException;
|
||||
import org.bouncycastle.cms.CMSProcessable;
|
||||
import org.bouncycastle.cms.CMSSignedData;
|
||||
|
||||
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 java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* 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
|
||||
{
|
||||
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");
|
||||
|
||||
CommandMap.setDefaultCommandMap(mc);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,606 @@
|
||||
package org.bouncycastle.mail.smime;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Enumeration;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
|
||||
import javax.activation.CommandMap;
|
||||
import javax.activation.MailcapCommandMap;
|
||||
import javax.mail.MessagingException;
|
||||
import javax.mail.Multipart;
|
||||
import javax.mail.internet.ContentType;
|
||||
import javax.mail.internet.MimeBodyPart;
|
||||
import javax.mail.internet.MimeMessage;
|
||||
import javax.mail.internet.MimeMultipart;
|
||||
|
||||
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
|
||||
import org.bouncycastle.asn1.cryptopro.CryptoProObjectIdentifiers;
|
||||
import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
|
||||
import org.bouncycastle.asn1.oiw.OIWObjectIdentifiers;
|
||||
import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
|
||||
import org.bouncycastle.asn1.teletrust.TeleTrusTObjectIdentifiers;
|
||||
import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
|
||||
import org.bouncycastle.cms.CMSAlgorithm;
|
||||
import org.bouncycastle.cms.CMSException;
|
||||
import org.bouncycastle.cms.CMSSignedDataStreamGenerator;
|
||||
import org.bouncycastle.cms.SignerInfoGenerator;
|
||||
import org.bouncycastle.cms.SignerInformation;
|
||||
import org.bouncycastle.cms.SignerInformationStore;
|
||||
import org.bouncycastle.mail.smime.util.CRLFOutputStream;
|
||||
import org.bouncycastle.util.Store;
|
||||
|
||||
/**
|
||||
* general class for generating a pkcs7-signature message.
|
||||
* <p>
|
||||
* A simple example of usage.
|
||||
*
|
||||
* <pre>
|
||||
* X509Certificate signCert = ...
|
||||
* KeyPair signKP = ...
|
||||
*
|
||||
* List certList = new ArrayList();
|
||||
*
|
||||
* certList.add(signCert);
|
||||
*
|
||||
* Store certs = new JcaCertStore(certList);
|
||||
*
|
||||
* SMIMESignedGenerator gen = new SMIMESignedGenerator();
|
||||
*
|
||||
* gen.addSignerInfoGenerator(new JcaSimpleSignerInfoGeneratorBuilder().setProvider("BC").build("SHA1withRSA", signKP.getPrivate(), signCert));
|
||||
*
|
||||
* gen.addCertificates(certs);
|
||||
*
|
||||
* MimeMultipart smime = fact.generate(content);
|
||||
* </pre>
|
||||
* <p>
|
||||
* Note 1: if you are using this class with AS2 or some other protocol
|
||||
* that does not use "7bit" as the default content transfer encoding you
|
||||
* will need to use the constructor that allows you to specify the default
|
||||
* content transfer encoding, such as "binary".
|
||||
* </p>
|
||||
* <p>
|
||||
* Note 2: between RFC 3851 and RFC 5751 the values used in the micalg parameter
|
||||
* for signed messages changed. We will accept both, but the default is now to use
|
||||
* RFC 5751. In the event you are dealing with an older style system you will also need
|
||||
* to use a constructor that sets the micalgs table and call it with RFC3851_MICALGS.
|
||||
* </p>
|
||||
*/
|
||||
public class SMIMESignedGenerator
|
||||
extends SMIMEGenerator
|
||||
{
|
||||
public static final String DIGEST_SHA1 = OIWObjectIdentifiers.idSHA1.getId();
|
||||
public static final String DIGEST_MD5 = PKCSObjectIdentifiers.md5.getId();
|
||||
public static final String DIGEST_SHA224 = NISTObjectIdentifiers.id_sha224.getId();
|
||||
public static final String DIGEST_SHA256 = NISTObjectIdentifiers.id_sha256.getId();
|
||||
public static final String DIGEST_SHA384 = NISTObjectIdentifiers.id_sha384.getId();
|
||||
public static final String DIGEST_SHA512 = NISTObjectIdentifiers.id_sha512.getId();
|
||||
public static final String DIGEST_GOST3411 = CryptoProObjectIdentifiers.gostR3411.getId();
|
||||
public static final String DIGEST_RIPEMD128 = TeleTrusTObjectIdentifiers.ripemd128.getId();
|
||||
public static final String DIGEST_RIPEMD160 = TeleTrusTObjectIdentifiers.ripemd160.getId();
|
||||
public static final String DIGEST_RIPEMD256 = TeleTrusTObjectIdentifiers.ripemd256.getId();
|
||||
|
||||
public static final String ENCRYPTION_RSA = PKCSObjectIdentifiers.rsaEncryption.getId();
|
||||
public static final String ENCRYPTION_DSA = X9ObjectIdentifiers.id_dsa_with_sha1.getId();
|
||||
public static final String ENCRYPTION_ECDSA = X9ObjectIdentifiers.ecdsa_with_SHA1.getId();
|
||||
public static final String ENCRYPTION_RSA_PSS = PKCSObjectIdentifiers.id_RSASSA_PSS.getId();
|
||||
public static final String ENCRYPTION_GOST3410 = CryptoProObjectIdentifiers.gostR3410_94.getId();
|
||||
public static final String ENCRYPTION_ECGOST3410 = CryptoProObjectIdentifiers.gostR3410_2001.getId();
|
||||
|
||||
private static final String CERTIFICATE_MANAGEMENT_CONTENT = "application/pkcs7-mime; name=smime.p7c; smime-type=certs-only";
|
||||
private static final String DETACHED_SIGNATURE_TYPE = "application/pkcs7-signature; name=smime.p7s; smime-type=signed-data";
|
||||
private static final String ENCAPSULATED_SIGNED_CONTENT_TYPE = "application/pkcs7-mime; name=smime.p7m; smime-type=signed-data";
|
||||
|
||||
public static final Map RFC3851_MICALGS;
|
||||
public static final Map RFC5751_MICALGS;
|
||||
public static final Map STANDARD_MICALGS;
|
||||
|
||||
private static MailcapCommandMap addCommands(CommandMap cm)
|
||||
{
|
||||
MailcapCommandMap mc = (MailcapCommandMap)cm;
|
||||
|
||||
mc.addMailcap("application/pkcs7-signature;; x-java-content-handler=org.bouncycastle.mail.smime.handlers.pkcs7_signature");
|
||||
mc.addMailcap("application/pkcs7-mime;; x-java-content-handler=org.bouncycastle.mail.smime.handlers.pkcs7_mime");
|
||||
mc.addMailcap("application/x-pkcs7-signature;; x-java-content-handler=org.bouncycastle.mail.smime.handlers.x_pkcs7_signature");
|
||||
mc.addMailcap("application/x-pkcs7-mime;; x-java-content-handler=org.bouncycastle.mail.smime.handlers.x_pkcs7_mime");
|
||||
mc.addMailcap("multipart/signed;; x-java-content-handler=org.bouncycastle.mail.smime.handlers.multipart_signed");
|
||||
|
||||
return mc;
|
||||
}
|
||||
|
||||
static
|
||||
{
|
||||
CommandMap.setDefaultCommandMap(addCommands(CommandMap.getDefaultCommandMap()));
|
||||
|
||||
Map stdMicAlgs = new HashMap();
|
||||
|
||||
stdMicAlgs.put(CMSAlgorithm.MD5, "md5");
|
||||
stdMicAlgs.put(CMSAlgorithm.SHA1, "sha-1");
|
||||
stdMicAlgs.put(CMSAlgorithm.SHA224, "sha-224");
|
||||
stdMicAlgs.put(CMSAlgorithm.SHA256, "sha-256");
|
||||
stdMicAlgs.put(CMSAlgorithm.SHA384, "sha-384");
|
||||
stdMicAlgs.put(CMSAlgorithm.SHA512, "sha-512");
|
||||
stdMicAlgs.put(CMSAlgorithm.GOST3411, "gostr3411-94");
|
||||
|
||||
RFC5751_MICALGS = Collections.unmodifiableMap(stdMicAlgs);
|
||||
|
||||
Map oldMicAlgs = new HashMap();
|
||||
|
||||
oldMicAlgs.put(CMSAlgorithm.MD5, "md5");
|
||||
oldMicAlgs.put(CMSAlgorithm.SHA1, "sha1");
|
||||
oldMicAlgs.put(CMSAlgorithm.SHA224, "sha224");
|
||||
oldMicAlgs.put(CMSAlgorithm.SHA256, "sha256");
|
||||
oldMicAlgs.put(CMSAlgorithm.SHA384, "sha384");
|
||||
oldMicAlgs.put(CMSAlgorithm.SHA512, "sha512");
|
||||
oldMicAlgs.put(CMSAlgorithm.GOST3411, "gostr3411-94");
|
||||
|
||||
RFC3851_MICALGS = Collections.unmodifiableMap(oldMicAlgs);
|
||||
|
||||
STANDARD_MICALGS = RFC5751_MICALGS;
|
||||
}
|
||||
|
||||
private final String defaultContentTransferEncoding;
|
||||
private final Map micAlgs;
|
||||
|
||||
private List _certStores = new ArrayList();
|
||||
private List certStores = new ArrayList();
|
||||
private List crlStores = new ArrayList();
|
||||
private List attrCertStores = new ArrayList();
|
||||
private List signerInfoGens = new ArrayList();
|
||||
private List _signers = new ArrayList();
|
||||
private List _oldSigners = new ArrayList();
|
||||
private List _attributeCerts = new ArrayList();
|
||||
private Map _digests = new HashMap();
|
||||
|
||||
/**
|
||||
* base constructor - default content transfer encoding 7bit
|
||||
*/
|
||||
public SMIMESignedGenerator()
|
||||
{
|
||||
this("7bit", STANDARD_MICALGS);
|
||||
}
|
||||
|
||||
/**
|
||||
* base constructor - default content transfer encoding explicitly set
|
||||
*
|
||||
* @param defaultContentTransferEncoding new default to use.
|
||||
*/
|
||||
public SMIMESignedGenerator(
|
||||
String defaultContentTransferEncoding)
|
||||
{
|
||||
this(defaultContentTransferEncoding, STANDARD_MICALGS);
|
||||
}
|
||||
|
||||
/**
|
||||
* base constructor - default content transfer encoding explicitly set
|
||||
*
|
||||
* @param micAlgs a map of ANS1ObjectIdentifiers to strings hash algorithm names.
|
||||
*/
|
||||
public SMIMESignedGenerator(
|
||||
Map micAlgs)
|
||||
{
|
||||
this("7bit", micAlgs);
|
||||
}
|
||||
|
||||
/**
|
||||
* base constructor - default content transfer encoding explicitly set
|
||||
*
|
||||
* @param defaultContentTransferEncoding new default to use.
|
||||
* @param micAlgs a map of ANS1ObjectIdentifiers to strings hash algorithm names.
|
||||
*/
|
||||
public SMIMESignedGenerator(
|
||||
String defaultContentTransferEncoding,
|
||||
Map micAlgs)
|
||||
{
|
||||
this.defaultContentTransferEncoding = defaultContentTransferEncoding;
|
||||
this.micAlgs = micAlgs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a store of precalculated signers to the generator.
|
||||
*
|
||||
* @param signerStore store of signers
|
||||
*/
|
||||
public void addSigners(
|
||||
SignerInformationStore signerStore)
|
||||
{
|
||||
Iterator it = signerStore.getSigners().iterator();
|
||||
|
||||
while (it.hasNext())
|
||||
{
|
||||
_oldSigners.add(it.next());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param sigInfoGen
|
||||
*/
|
||||
public void addSignerInfoGenerator(SignerInfoGenerator sigInfoGen)
|
||||
{
|
||||
signerInfoGens.add(sigInfoGen);
|
||||
}
|
||||
|
||||
public void addCertificates(
|
||||
Store certStore)
|
||||
{
|
||||
certStores.add(certStore);
|
||||
}
|
||||
|
||||
public void addCRLs(
|
||||
Store crlStore)
|
||||
{
|
||||
crlStores.add(crlStore);
|
||||
}
|
||||
|
||||
public void addAttributeCertificates(
|
||||
Store certStore)
|
||||
{
|
||||
attrCertStores.add(certStore);
|
||||
}
|
||||
|
||||
private void addHashHeader(
|
||||
StringBuffer header,
|
||||
List signers)
|
||||
{
|
||||
int count = 0;
|
||||
|
||||
//
|
||||
// build the hash header
|
||||
//
|
||||
Iterator it = signers.iterator();
|
||||
Set micAlgSet = new TreeSet();
|
||||
|
||||
while (it.hasNext())
|
||||
{
|
||||
Object signer = it.next();
|
||||
ASN1ObjectIdentifier digestOID;
|
||||
|
||||
if (signer instanceof SignerInformation)
|
||||
{
|
||||
digestOID = ((SignerInformation)signer).getDigestAlgorithmID().getAlgorithm();
|
||||
}
|
||||
else
|
||||
{
|
||||
digestOID = ((SignerInfoGenerator)signer).getDigestAlgorithm().getAlgorithm();
|
||||
}
|
||||
|
||||
String micAlg = (String)micAlgs.get(digestOID);
|
||||
|
||||
if (micAlg == null)
|
||||
{
|
||||
micAlgSet.add("unknown");
|
||||
}
|
||||
else
|
||||
{
|
||||
micAlgSet.add(micAlg);
|
||||
}
|
||||
}
|
||||
|
||||
it = micAlgSet.iterator();
|
||||
|
||||
while (it.hasNext())
|
||||
{
|
||||
String alg = (String)it.next();
|
||||
|
||||
if (count == 0)
|
||||
{
|
||||
if (micAlgSet.size() != 1)
|
||||
{
|
||||
header.append("; micalg=\"");
|
||||
}
|
||||
else
|
||||
{
|
||||
header.append("; micalg=");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
header.append(',');
|
||||
}
|
||||
|
||||
header.append(alg);
|
||||
|
||||
count++;
|
||||
}
|
||||
|
||||
if (count != 0)
|
||||
{
|
||||
if (micAlgSet.size() != 1)
|
||||
{
|
||||
header.append('\"');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private MimeMultipart make(
|
||||
MimeBodyPart content)
|
||||
throws SMIMEException
|
||||
{
|
||||
try
|
||||
{
|
||||
MimeBodyPart sig = new MimeBodyPart();
|
||||
|
||||
sig.setContent(new ContentSigner(content, false), DETACHED_SIGNATURE_TYPE);
|
||||
sig.addHeader("Content-Type", DETACHED_SIGNATURE_TYPE);
|
||||
sig.addHeader("Content-Disposition", "attachment; filename=\"smime.p7s\"");
|
||||
sig.addHeader("Content-Description", "S/MIME Cryptographic Signature");
|
||||
sig.addHeader("Content-Transfer-Encoding", encoding);
|
||||
|
||||
//
|
||||
// build the multipart header
|
||||
//
|
||||
StringBuffer header = new StringBuffer(
|
||||
"signed; protocol=\"application/pkcs7-signature\"");
|
||||
|
||||
List allSigners = new ArrayList(_signers);
|
||||
|
||||
allSigners.addAll(_oldSigners);
|
||||
|
||||
allSigners.addAll(signerInfoGens);
|
||||
|
||||
addHashHeader(header, allSigners);
|
||||
|
||||
MimeMultipart mm = new MimeMultipart(header.toString());
|
||||
|
||||
mm.addBodyPart(content);
|
||||
mm.addBodyPart(sig);
|
||||
|
||||
return mm;
|
||||
}
|
||||
catch (MessagingException e)
|
||||
{
|
||||
throw new SMIMEException("exception putting multi-part together.", e);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* at this point we expect our body part to be well defined - generate with data in the signature
|
||||
*/
|
||||
private MimeBodyPart makeEncapsulated(
|
||||
MimeBodyPart content)
|
||||
throws SMIMEException
|
||||
{
|
||||
try
|
||||
{
|
||||
MimeBodyPart sig = new MimeBodyPart();
|
||||
|
||||
sig.setContent(new ContentSigner(content, true), ENCAPSULATED_SIGNED_CONTENT_TYPE);
|
||||
sig.addHeader("Content-Type", ENCAPSULATED_SIGNED_CONTENT_TYPE);
|
||||
sig.addHeader("Content-Disposition", "attachment; filename=\"smime.p7m\"");
|
||||
sig.addHeader("Content-Description", "S/MIME Cryptographic Signed Data");
|
||||
sig.addHeader("Content-Transfer-Encoding", encoding);
|
||||
|
||||
return sig;
|
||||
}
|
||||
catch (MessagingException e)
|
||||
{
|
||||
throw new SMIMEException("exception putting body part together.", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a map of oids and byte arrays representing the digests calculated on the content during
|
||||
* the last generate.
|
||||
*
|
||||
* @return a map of oids (as String objects) and byte[] representing digests.
|
||||
*/
|
||||
public Map getGeneratedDigests()
|
||||
{
|
||||
return new HashMap(_digests);
|
||||
}
|
||||
|
||||
public MimeMultipart generate(
|
||||
MimeBodyPart content)
|
||||
throws SMIMEException
|
||||
{
|
||||
return make(makeContentBodyPart(content));
|
||||
}
|
||||
|
||||
public MimeMultipart generate(
|
||||
MimeMessage message)
|
||||
throws SMIMEException
|
||||
{
|
||||
try
|
||||
{
|
||||
message.saveChanges(); // make sure we're up to date.
|
||||
}
|
||||
catch (MessagingException e)
|
||||
{
|
||||
throw new SMIMEException("unable to save message", e);
|
||||
}
|
||||
|
||||
return make(makeContentBodyPart(message));
|
||||
}
|
||||
|
||||
/**
|
||||
* generate a signed message with encapsulated content
|
||||
* <p>
|
||||
* Note: doing this is strongly <b>not</b> recommended as it means a
|
||||
* recipient of the message will have to be able to read the signature to read the
|
||||
* message.
|
||||
*/
|
||||
public MimeBodyPart generateEncapsulated(
|
||||
MimeBodyPart content)
|
||||
throws SMIMEException
|
||||
{
|
||||
return makeEncapsulated(makeContentBodyPart(content));
|
||||
}
|
||||
|
||||
public MimeBodyPart generateEncapsulated(
|
||||
MimeMessage message)
|
||||
throws SMIMEException
|
||||
{
|
||||
try
|
||||
{
|
||||
message.saveChanges(); // make sure we're up to date.
|
||||
}
|
||||
catch (MessagingException e)
|
||||
{
|
||||
throw new SMIMEException("unable to save message", e);
|
||||
}
|
||||
|
||||
return makeEncapsulated(makeContentBodyPart(message));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a certificate management message which is like a signed message with no content
|
||||
* or signers but that still carries certificates and CRLs.
|
||||
*
|
||||
* @return a MimeBodyPart containing the certs and CRLs.
|
||||
*/
|
||||
public MimeBodyPart generateCertificateManagement()
|
||||
throws SMIMEException
|
||||
{
|
||||
try
|
||||
{
|
||||
MimeBodyPart sig = new MimeBodyPart();
|
||||
|
||||
sig.setContent(new ContentSigner(null, true), CERTIFICATE_MANAGEMENT_CONTENT);
|
||||
sig.addHeader("Content-Type", CERTIFICATE_MANAGEMENT_CONTENT);
|
||||
sig.addHeader("Content-Disposition", "attachment; filename=\"smime.p7c\"");
|
||||
sig.addHeader("Content-Description", "S/MIME Certificate Management Message");
|
||||
sig.addHeader("Content-Transfer-Encoding", encoding);
|
||||
|
||||
return sig;
|
||||
}
|
||||
catch (MessagingException e)
|
||||
{
|
||||
throw new SMIMEException("exception putting body part together.", e);
|
||||
}
|
||||
}
|
||||
|
||||
private class ContentSigner
|
||||
implements SMIMEStreamingProcessor
|
||||
{
|
||||
private final MimeBodyPart content;
|
||||
private final boolean encapsulate;
|
||||
private final boolean noProvider;
|
||||
|
||||
ContentSigner(
|
||||
MimeBodyPart content,
|
||||
boolean encapsulate)
|
||||
{
|
||||
this.content = content;
|
||||
this.encapsulate = encapsulate;
|
||||
this.noProvider = true;
|
||||
}
|
||||
|
||||
protected CMSSignedDataStreamGenerator getGenerator()
|
||||
throws CMSException
|
||||
{
|
||||
CMSSignedDataStreamGenerator gen = new CMSSignedDataStreamGenerator();
|
||||
|
||||
for (Iterator it = certStores.iterator(); it.hasNext();)
|
||||
{
|
||||
gen.addCertificates((Store)it.next());
|
||||
}
|
||||
|
||||
for (Iterator it = crlStores.iterator(); it.hasNext();)
|
||||
{
|
||||
gen.addCRLs((Store)it.next());
|
||||
}
|
||||
|
||||
for (Iterator it = attrCertStores.iterator(); it.hasNext();)
|
||||
{
|
||||
gen.addAttributeCertificates((Store)it.next());
|
||||
}
|
||||
|
||||
for (Iterator it = signerInfoGens.iterator(); it.hasNext();)
|
||||
{
|
||||
gen.addSignerInfoGenerator((SignerInfoGenerator)it.next());
|
||||
}
|
||||
|
||||
gen.addSigners(new SignerInformationStore(_oldSigners));
|
||||
|
||||
return gen;
|
||||
}
|
||||
|
||||
private void writeBodyPart(
|
||||
OutputStream out,
|
||||
MimeBodyPart bodyPart)
|
||||
throws IOException, MessagingException
|
||||
{
|
||||
if (bodyPart.getContent() instanceof Multipart)
|
||||
{
|
||||
Multipart mp = (Multipart)bodyPart.getContent();
|
||||
ContentType contentType = new ContentType(mp.getContentType());
|
||||
String boundary = "--" + contentType.getParameter("boundary");
|
||||
|
||||
SMIMEUtil.LineOutputStream lOut = new SMIMEUtil.LineOutputStream(out);
|
||||
|
||||
Enumeration headers = bodyPart.getAllHeaderLines();
|
||||
while (headers.hasMoreElements())
|
||||
{
|
||||
lOut.writeln((String)headers.nextElement());
|
||||
}
|
||||
|
||||
lOut.writeln(); // CRLF separator
|
||||
|
||||
SMIMEUtil.outputPreamble(lOut, bodyPart, boundary);
|
||||
|
||||
for (int i = 0; i < mp.getCount(); i++)
|
||||
{
|
||||
lOut.writeln(boundary);
|
||||
writeBodyPart(out, (MimeBodyPart)mp.getBodyPart(i));
|
||||
lOut.writeln(); // CRLF terminator
|
||||
}
|
||||
|
||||
lOut.writeln(boundary + "--");
|
||||
}
|
||||
else
|
||||
{
|
||||
if (SMIMEUtil.isCanonicalisationRequired(bodyPart, defaultContentTransferEncoding))
|
||||
{
|
||||
out = new CRLFOutputStream(out);
|
||||
}
|
||||
|
||||
bodyPart.writeTo(out);
|
||||
}
|
||||
}
|
||||
|
||||
public void write(OutputStream out)
|
||||
throws IOException
|
||||
{
|
||||
try
|
||||
{
|
||||
CMSSignedDataStreamGenerator gen = getGenerator();
|
||||
|
||||
OutputStream signingStream = gen.open(out, encapsulate);
|
||||
|
||||
if (content != null)
|
||||
{
|
||||
if (!encapsulate)
|
||||
{
|
||||
writeBodyPart(signingStream, content);
|
||||
}
|
||||
else
|
||||
{
|
||||
content.getDataHandler().setCommandMap(addCommands(CommandMap.getDefaultCommandMap()));
|
||||
|
||||
content.writeTo(signingStream);
|
||||
}
|
||||
}
|
||||
|
||||
signingStream.close();
|
||||
|
||||
_digests = gen.getGeneratedDigests();
|
||||
}
|
||||
catch (MessagingException e)
|
||||
{
|
||||
throw new IOException(e.toString());
|
||||
}
|
||||
catch (CMSException e)
|
||||
{
|
||||
throw new IOException(e.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,358 @@
|
||||
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 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
|
||||
{
|
||||
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");
|
||||
|
||||
CommandMap.setDefaultCommandMap(mc);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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';
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
package org.bouncycastle.mail.smime.examples;
|
||||
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.security.KeyStore;
|
||||
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.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);
|
||||
}
|
||||
|
||||
//
|
||||
// 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"));
|
||||
}
|
||||
}
|
||||
@@ -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"));
|
||||
}
|
||||
}
|
||||
@@ -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"));
|
||||
}
|
||||
}
|
||||
@@ -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"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,220 @@
|
||||
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.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 SubjectKeyIdentifier(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"));
|
||||
}
|
||||
}
|
||||
@@ -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"));
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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]);
|
||||
}
|
||||
}
|
||||
@@ -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]);
|
||||
}
|
||||
}
|
||||
@@ -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!");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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!");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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';
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,960 @@
|
||||
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
|
||||
}
|
||||
fromAddresses = new String[froms.length + (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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
<html>
|
||||
<body bgcolor="#ffffff">
|
||||
Example code demonstrating the use of the S/MIME package for a variety of uses.
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,5 @@
|
||||
<html>
|
||||
<body bgcolor="#ffffff">
|
||||
S/MIME handlers for the JavaMail API.
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,11 @@
|
||||
<html>
|
||||
<body bgcolor="#ffffff">
|
||||
High level classes for dealing with S/MIME objects (RFC 3851).
|
||||
<p>
|
||||
There is one thing that is worth commenting about with these. If you're using
|
||||
AS2 on some other standard which specifies a different default content transfer encoding from RFC 2405, make
|
||||
sure you use the constructors on SMIMESigned and SMIMESignedGenerator that allow you to
|
||||
set the default ("binary" in the case of AS2 as opposed to "bit7" which is the default).
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,172 @@
|
||||
## constructor exception messages
|
||||
|
||||
# Signature valid
|
||||
SignedMailValidator.sigValid.title = Signature valid
|
||||
SignedMailValidator.sigValid.text = Signature valid
|
||||
SignedMailValidator.sigValid.summary = Signature valid
|
||||
SignedMailValidator.sigValid.details = Signature valid
|
||||
|
||||
# Signature invalid
|
||||
SignedMailValidator.sigInvalid.title = Signature invalid
|
||||
SignedMailValidator.sigInvalid.text = Signature invalid
|
||||
SignedMailValidator.sigInvalid.summary = Signature invalid
|
||||
SignedMailValidator.sigInvalid.details = Signature invalid
|
||||
|
||||
# message is not signed
|
||||
SignedMailValidator.noSignedMessage.title = Message is not signed
|
||||
SignedMailValidator.noSignedMessage.text = SignedMailValidator: MimeMessage message is not a signed message.
|
||||
SignedMailValidator.noSignedMessage.summary = SignedMailValidator: MimeMessage message is not a signed message.
|
||||
SignedMailValidator.noSignedMessage.details = SignedMailValidator: MimeMessage message is not a signed message.
|
||||
|
||||
# exception reading the Mime message
|
||||
# {0} message of the underlying exception
|
||||
# {1} the underlying exception
|
||||
# {2} the name of the exception
|
||||
SignedMailValidator.exceptionReadingMessage.title = Exception reading the MimeMessage
|
||||
SignedMailValidator.exceptionReadingMessage.text = SignedMailValidator: there was a {2} reading the MimeMessage: {0}.
|
||||
SignedMailValidator.exceptionReadingMessage.summary = SignedMailValidator: there was a {2} reading the MimeMessage: {0}.
|
||||
SignedMailValidator.exceptionReadingMessage.details = SignedMailValidator: there was a {2} reading the MimeMessage: {0}.
|
||||
|
||||
## exception messages
|
||||
|
||||
# signer has not signed the mail message
|
||||
SignedMailValidator.wrongSigner.title = Signer has not signed the message
|
||||
SignedMailValidator.wrongSigner.text = The given signer did not sign the mail message.
|
||||
SignedMailValidator.wrongSigner.summary = The given signer did not sign the mail message.
|
||||
SignedMailValidator.wrongSigner.details = The given signer did not sign the mail message.
|
||||
|
||||
## notifications messages
|
||||
|
||||
# short signing key
|
||||
# {0} the key lenght as Integer
|
||||
SignedMailValidator.shortSigningKey.title = Careless short signing key
|
||||
SignedMailValidator.shortSigningKey.text = Warning: The signing key is only {0} bits long.
|
||||
SignedMailValidator.shortSigningKey.summary = Warning: The signing key is only {0} bits long.
|
||||
SignedMailValidator.shortSigningKey.details = Warning: The signing key is only {0} bits long.
|
||||
|
||||
# signing certificate has very long validity period
|
||||
# {0} notBefore date
|
||||
# {1} notAfter date
|
||||
SignedMailValidator.longValidity.title = Very long validity period
|
||||
SignedMailValidator.longValidity.text = Warning: The signing certificate has a very long validity period: from {0,date} {0,time,full} until {1,date} {1,time,full}.
|
||||
SignedMailValidator.longValidity.summary = Warning: The signing certificate has a very long validity period: from {0,date} {0,time,full} until {1,date} {1,time,full}.
|
||||
SignedMailValidator.longValidity.details = Warning: The signing certificate has a very long validity period: from {0,date} {0,time,full} until {1,date} {1,time,full}.
|
||||
|
||||
# signed receipt requested
|
||||
SignedMailValidator.signedReceiptRequest.title = Signed Receipt Request
|
||||
SignedMailValidator.signedReceiptRequest.text = The signature contains a signed receipt request.
|
||||
SignedMailValidator.signedReceiptRequest.summary = The signature contains a signed receipt request.
|
||||
SignedMailValidator.signedReceiptRequest.details = The signature contains a signed receipt request as per RFC 2634.
|
||||
|
||||
## error messages
|
||||
|
||||
# no signer certificate found
|
||||
SignedMailValidator.noSignerCert.title = No signer certificate found
|
||||
SignedMailValidator.noSignerCert.text = Signature Validation failed: No signer certificate found.
|
||||
SignedMailValidator.noSignerCert.summary = Signature Validation failed: No signer certificate found.
|
||||
SignedMailValidator.noSignerCert.details = Signature Validation failed: No signer certificate found.
|
||||
|
||||
# certificate contains no email address
|
||||
SignedMailValidator.noEmailInCert.title = Certificate not usable for email signatures
|
||||
SignedMailValidator.noEmailInCert.text = The signer certificate is not usable for email signatures: it contains no email address.
|
||||
SignedMailValidator.noEmailInCert.summary = The signer certificate is not usable for email signatures: it contains no email address.
|
||||
SignedMailValidator.noEmailInCert.details = The signer certificate is not usable for email signatures: it contains no email address.
|
||||
|
||||
# certificate email address does not match from email address
|
||||
# {0} from email addresses in the message
|
||||
# {1} email addresses in the certificate
|
||||
SignedMailValidator.emailFromCertMismatch.title = Email address mismatch
|
||||
SignedMailValidator.emailFromCertMismatch.text = Email address in singer certificate does not match the sender address. Signer email: {1}. Sender email: {0}.
|
||||
SignedMailValidator.emailFromCertMismatch.summary = Email address in singer certificate does not match the sender address. Signer email: {1}. Sender email: {0}.
|
||||
SignedMailValidator.emailFromCertMismatch.details = Email address in singer certificate does not match the sender address. Signer email: {1}. Sender email: {0}.
|
||||
|
||||
# exception extracting email addresses from certificate
|
||||
# {0} message of the underlying exception
|
||||
# {1} the underlying exception
|
||||
# {2} the name of the exception
|
||||
SignedMailValidator.certGetEmailError.title = Exception extracting email addresses from certificate
|
||||
SignedMailValidator.certGetEmailError.text = There was a {2} extracting the email addresses from the certificate. Cause: {0}.
|
||||
SignedMailValidator.certGetEmailError.summary = There was a {2} extracting the email addresses from the certificate.
|
||||
SignedMailValidator.certGetEmailError.details = There was a {2} extracting the email addresses from the certificate. Cause: {0}.
|
||||
|
||||
# no signing time found
|
||||
SignedMailValidator.noSigningTime.title = No signing time
|
||||
SignedMailValidator.noSigningTime.text = The signature contains no signing time. Using the current time for validating the certificate path.
|
||||
SignedMailValidator.noSigningTime.summary = The signature contains no signing time.
|
||||
SignedMailValidator.noSigningTime.details = The signature contains no signing time. Using the current time for validating the certificate path.
|
||||
|
||||
# expired at signing time
|
||||
# {0} signing time
|
||||
# {1} not after date
|
||||
SignedMailValidator.certExpired.title = Certificate expired at signing time
|
||||
SignedMailValidator.certExpired.text = The message was signed at {0,date} {0,time,full}. But the certificate expired at {1,date} {1,time,full}.
|
||||
SignedMailValidator.certExpired.summary = The message was signed at {0,date} {0,time,full}. But the certificate expired at {1,date} {1,time,full}.
|
||||
SignedMailValidator.certExpired.details = The message was signed at {0,date} {0,time,full}. But the certificate expired at {1,date} {1,time,full}.
|
||||
|
||||
# not yet valid at signing time
|
||||
# {0} signing time
|
||||
# {1} notBefore date
|
||||
SignedMailValidator.certNotYetValid.title = Certificate not yet valid at signing time
|
||||
SignedMailValidator.certNotYetValid.text = The message was signed at {0,date} {0,time,full}. But the certificate is not valid before {1,date} {1,time,full}.
|
||||
SignedMailValidator.certNotYetValid.summary = The message was signed at {0,date} {0,time,full}. But the certificate is not valid before {1,date} {1,time,full}.
|
||||
SignedMailValidator.certNotYetValid.details = The message was signed at {0,date} {0,time,full}. But the certificate is not valid before {1,date} {1,time,full}.
|
||||
|
||||
# exception retrieving the signer certificate
|
||||
# {0} message of the underlying exception
|
||||
# {1} the underlying exception
|
||||
# {2} the name of the exception
|
||||
SignedMailValidator.exceptionRetrievingSignerCert.title = Exception retrieving the signer certificate
|
||||
SignedMailValidator.exceptionRetrievingSignerCert.text = Signature Validation failed. There was a {2} retrieving the signer certificate: {0}.
|
||||
SignedMailValidator.exceptionRetrievingSignerCert.summary = Signature Validation failed. There was a {2} retrieving the signer certificate.
|
||||
SignedMailValidator.exceptionRetrievingSignerCert.details = Signature Validation failed There was a {2} retrieving the signer certificate: {0}.
|
||||
|
||||
# exception verifying the signature
|
||||
# {0} message of the underlying exception
|
||||
# {1} the underlying exception
|
||||
# {2} the name of the exception
|
||||
SignedMailValidator.exceptionVerifyingSignature.title = Signature not verified
|
||||
SignedMailValidator.exceptionVerifyingSignature.text = Signature not verified. There was a {2}. Cause: {0}.
|
||||
SignedMailValidator.exceptionVerifyingSignature.summary = Signature not verified. There was a {2}.
|
||||
SignedMailValidator.exceptionVerifyingSignature.details = Signature not verified. There was a {2}. Cause: {0}.
|
||||
|
||||
# signature not verified
|
||||
SignedMailValidator.signatureNotVerified.title = Signature not verified
|
||||
SignedMailValidator.signatureNotVerified.text = Signature not verified. The public key of the signer does not correspond to the signature.
|
||||
SignedMailValidator.signatureNotVerified.summary = Signature not verified. The public key of the signer does not correspond to the signature.
|
||||
SignedMailValidator.signatureNotVerified.details = Signature not verified. The public key of the signer does not correspond to the signature.
|
||||
|
||||
# certificate key usage does not permit digitalSignature or nonRepudiation
|
||||
SignedMailValidator.signingNotPermitted.title = Key not usable for email signatures
|
||||
SignedMailValidator.signingNotPermitted.text = The key usage extension of signer certificate does not permit using the key for email signatures.
|
||||
SignedMailValidator.signingNotPermitted.summary = The signer key is not usable for email signatures.
|
||||
SignedMailValidator.signingNotPermitted.details = The key usage extension of signer certificate does not permit using the key for email signatures.
|
||||
|
||||
# certificate extended key usage does not permit emailProtection or anyExtendedKeyUsage
|
||||
SignedMailValidator.extKeyUsageNotPermitted.title = Key not usable for email signatures
|
||||
SignedMailValidator.extKeyUsageNotPermitted.text = The extended key usage extension of the signer certificate does not permit using the key for email signatures.
|
||||
SignedMailValidator.extKeyUsageNotPermitted.summary = The signer key is not usable for email signatures.
|
||||
SignedMailValidator.extKeyUsageNotPermitted.details = The extended key usage extension of the signer certificate does not permit using the key for email signatures.
|
||||
|
||||
# exception processing the extended key usage extension
|
||||
# {0} message of the underlying exception
|
||||
# {1} the underlying exception
|
||||
# {2} the name of the exception
|
||||
SignedMailValidator.extKeyUsageError.title = Exception processing the extended key usage extension
|
||||
SignedMailValidator.extKeyUsageError.text = There was a {2} processing the extended key usage extension. Cause: {0}.
|
||||
SignedMailValidator.extKeyUsageError.summary = There was a {2} processing the extended key usage extension.
|
||||
SignedMailValidator.extKeyUsageError.details = There was a {2} processing the extended key usage extension. Cause: {0}.
|
||||
|
||||
# cannot create certificate path (exception)
|
||||
# {0} message of the underlying exception
|
||||
# {1} the underlying exception
|
||||
# {2} the name of the exception
|
||||
SignedMailValidator.exceptionCreateCertPath.title = Certificate path validation failed
|
||||
SignedMailValidator.exceptionCreateCertPath.text = Certificate path validation failed. There was a {2} creating the CertPath: {0}.
|
||||
SignedMailValidator.exceptionCreateCertPath.summary = Certificate path validation failed. There was a {2} creating the CertPath: {0}.
|
||||
SignedMailValidator.exceptionCreateCertPath.details = Certificate path validation failed. There was a {2} creating the CertPath: {0}.
|
||||
|
||||
# certificate path is invalid
|
||||
SignedMailValidator.certPathInvalid.title = Certificate path invalid
|
||||
SignedMailValidator.certPathInvalid.text = The certificate path is invalid.
|
||||
SignedMailValidator.certPathInvalid.summary = The certificate path is invalid.
|
||||
SignedMailValidator.certPathInvalid.details = The certificate path is invalid.
|
||||
@@ -0,0 +1,172 @@
|
||||
## constructor exception messages
|
||||
|
||||
# Signatur gültig
|
||||
SignedMailValidator.sigValid.title = Signatur gültig
|
||||
SignedMailValidator.sigValid.text = Signatur gültig
|
||||
SignedMailValidator.sigValid.summary = Signatur gültig
|
||||
SignedMailValidator.sigValid.details = Signatur gültig
|
||||
|
||||
# Signatur ungültig
|
||||
SignedMailValidator.sigInvalid.title = Signatur ungültig
|
||||
SignedMailValidator.sigInvalid.text = Signatur ungültig
|
||||
SignedMailValidator.sigInvalid.summary = Signatur ungültig
|
||||
SignedMailValidator.sigInvalid.details = Signatur ungültig
|
||||
|
||||
# message is not signed
|
||||
SignedMailValidator.noSignedMessage.title = Die Nachricht ist nicht signiert
|
||||
SignedMailValidator.noSignedMessage.text = SignedMailValidator: Die MimeMessage message ist nicht signiert.
|
||||
SignedMailValidator.noSignedMessage.summary = SignedMailValidator: Die MimeMessage message ist nicht signiert.
|
||||
SignedMailValidator.noSignedMessage.details = SignedMailValidator: Die MimeMessage message ist nicht signiert.
|
||||
|
||||
# exception reading the Mime message
|
||||
# {0} message of the underlying exception
|
||||
# {1} the underlying exception
|
||||
# {2} the name of the exception
|
||||
SignedMailValidator.exceptionReadingMessage.title = Fehler beim lesen der MimeMessage
|
||||
SignedMailValidator.exceptionReadingMessage.text = SignedMailValidator: Es gab eine {2} beim lesen der MimeMessage: {0}.
|
||||
SignedMailValidator.exceptionReadingMessage.summary = SignedMailValidator: Es gab eine {2} beim lesen der MimeMessage.
|
||||
SignedMailValidator.exceptionReadingMessage.details = SignedMailValidator: Es gab eine {2} beim lesen der MimeMessage: {0}.
|
||||
|
||||
## exception messages
|
||||
|
||||
# signer has not signed the mail message
|
||||
SignedMailValidator.wrongSigner.title = Falscher Unterzeichner
|
||||
SignedMailValidator.wrongSigner.text = Die Email enhält keine Signatur vom gegebenen Unterzeichner.
|
||||
SignedMailValidator.wrongSigner.summary = Die Email enhält keine Signatur vom gegebenen Unterzeichner.
|
||||
SignedMailValidator.wrongSigner.details = Die Email enhält keine Signatur vom gegebenen Unterzeichner.
|
||||
|
||||
## notifications messages
|
||||
|
||||
# short signing key
|
||||
# {0} the key lenght as Integer
|
||||
SignedMailValidator.shortSigningKey.title = Fahrlässig kurzer Signaturschlüssel
|
||||
SignedMailValidator.shortSigningKey.text = Warnung: Der Signaturschlüssel ist nur {0} bit lang.
|
||||
SignedMailValidator.shortSigningKey.summary = Warnung: Der Signaturschlüssel ist nur {0} bit lang.
|
||||
SignedMailValidator.shortSigningKey.details = Warnung: Der Signaturschlüssel ist nur {0} bit lang.
|
||||
|
||||
# signing certificate has very long validity period
|
||||
# {0} notBefore date
|
||||
# {1} notAfter date
|
||||
SignedMailValidator.longValidity.title = Sehr lange Gültigkeitsdauer
|
||||
SignedMailValidator.longValidity.text = Warnung: Das Signierzertifikat hat eine sehr lange Gültigkeitsdauer: von {0,date} {0,time,full} bis {1,date} {1,time,full}.
|
||||
SignedMailValidator.longValidity.summary = Warnung: Das Signierzertifikat hat eine sehr lange Gültigkeitsdauer: von {0,date} {0,time,full} bis {1,date} {1,time,full}.
|
||||
SignedMailValidator.longValidity.details = Warnung: Das Signierzertifikat hat eine sehr lange Gültigkeitsdauer: von {0,date} {0,time,full} bis {1,date} {1,time,full}.
|
||||
|
||||
# signed receipt requested
|
||||
SignedMailValidator.signedReceiptRequest.title = Signed Receipt Request
|
||||
SignedMailValidator.signedReceiptRequest.text = Die Signatur enthält einen signed receipt request.
|
||||
SignedMailValidator.signedReceiptRequest.summary = Die Signatur enthält einen signed receipt request.
|
||||
SignedMailValidator.signedReceiptRequest.details = Die Signatur enthält einen signed receipt request gemäss RFC 2634.
|
||||
|
||||
## error messages
|
||||
|
||||
# no signer certificate found
|
||||
SignedMailValidator.noSignerCert.title = Kein Unterzeichner Zertifikat gefunden
|
||||
SignedMailValidator.noSignerCert.text = Signatur Validierung fehlgeschlagen: Es wurde kein Unterzeichner Zertifikat gefunden.
|
||||
SignedMailValidator.noSignerCert.summary = Signatur Validierung fehlgeschlagen: Es wurde kein Unterzeichner Zertifikat gefunden.
|
||||
SignedMailValidator.noSignerCert.details = Signatur Validierung fehlgeschlagen: Es wurde kein Unterzeichner Zertifikat gefunden.
|
||||
|
||||
# certificate contains no email address
|
||||
SignedMailValidator.noEmailInCert.title = Zertifikat nicht für Email Signaturen verwendbar
|
||||
SignedMailValidator.noEmailInCert.text = Das Unterzeichner Zertifikat kann nicht für Email Signaturen verwendet werden: Es enthält keine Email Addresse.
|
||||
SignedMailValidator.noEmailInCert.summary = Das Unterzeichner Zertifikat kann nicht für Email Signaturen verwendet werden: Es enthält keine Email Addresse.
|
||||
SignedMailValidator.noEmailInCert.details = Das Unterzeichner Zertifikat kann nicht für Email Signaturen verwendet werden: Es enthält keine Email Addresse.
|
||||
|
||||
# certificate email address does not match from email address
|
||||
# {0} from email addresses in the message
|
||||
# {1} email addresses in the certificate
|
||||
SignedMailValidator.emailFromCertMismatch.title = Email Addressen stimmen nicht überein
|
||||
SignedMailValidator.emailFromCertMismatch.text = Die Email Addresse im Unterzeichner Zertifikat stimmt nicht mit der Sender Addresse überein. Unterzeichner: {1}. Sender: {0}.
|
||||
SignedMailValidator.emailFromCertMismatch.summary = Die Email Addresse im Unterzeichner Zertifikat stimmt nicht mit der Sender Addresse überein. Unterzeichner: {1}. Sender: {0}.
|
||||
SignedMailValidator.emailFromCertMismatch.details = Die Email Addresse im Unterzeichner Zertifikat stimmt nicht mit der Sender Addresse überein. Unterzeichner: {1}. Sender: {0}.
|
||||
|
||||
# exception extracting email addresses from certificate
|
||||
# {0} message of the underlying exception
|
||||
# {1} the underlying exception
|
||||
# {2} the name of the exception
|
||||
SignedMailValidator.certGetEmailError.title = Fehler bei extrahieren der Email Addresse vom Zertifikat
|
||||
SignedMailValidator.certGetEmailError.text = Es gab eine {2} beim Extrahieren der Email Addresse vom Zertifikat. Grund: {0}.
|
||||
SignedMailValidator.certGetEmailError.summary = Es gab eine {2} beim Extrahieren der Email Addresse vom Zertifikat.
|
||||
SignedMailValidator.certGetEmailError.details = Es gab eine {2} beim Extrahieren der Email Addresse vom Zertifikat. Grund: {0}.
|
||||
|
||||
# no signing time found
|
||||
SignedMailValidator.noSigningTime.title = Keine Signierzeit
|
||||
SignedMailValidator.noSigningTime.text = Die Signatur enthält keine Signier Zeit. Benutze die aktuelle Zeit zur Zertifikationpfad Validierung.
|
||||
SignedMailValidator.noSigningTime.summary = Die Signatur enthält keine Signier Zeit.
|
||||
SignedMailValidator.noSigningTime.details = Die Signatur enthält keine Signier Zeit. Benutze die aktuelle Zeit zur Zertifikationpfad Validierung.
|
||||
|
||||
# expired at signing time
|
||||
# {0} signing time
|
||||
# {1} not after date
|
||||
SignedMailValidator.certExpired.title = Zertifikat zur Signierzeit abgelaufen
|
||||
SignedMailValidator.certExpired.text = Die Nachricht wurde am {0,date} {0,time,full} signiert. Aber das Zertifikat ist am {1,date} {1,time,full} abgelaufen.
|
||||
SignedMailValidator.certExpired.summary = Die Nachricht wurde am {0,date} {0,time,full} signiert. Aber das Zertifikat ist am {1,date} {1,time,full} abgelaufen.
|
||||
SignedMailValidator.certExpired.details = Die Nachricht wurde am {0,date} {0,time,full} signiert. Aber das Zertifikat ist am {1,date} {1,time,full} abgelaufen.
|
||||
|
||||
# not yet valid at signing time
|
||||
# {0} signing time
|
||||
# {1} notBefore date
|
||||
SignedMailValidator.certNotYetValid.title = Zertifikat noch nicht gültig zur Signierzeit
|
||||
SignedMailValidator.certNotYetValid.text = Die Nachricht wurde am {0,date} {0,time,full} signiert. Aber das Zertifikat ist erst gültig ab {1,date} {1,time,full}.
|
||||
SignedMailValidator.certNotYetValid.summary = Die Nachricht wurde am {0,date} {0,time,full} signiert. Aber das Zertifikat ist erst gültig ab {1,date} {1,time,full}.
|
||||
SignedMailValidator.certNotYetValid.details = Die Nachricht wurde am {0,date} {0,time,full} signiert. Aber das Zertifikat ist erst gültig ab {1,date} {1,time,full}.
|
||||
|
||||
# exception retrieving the signer certificate
|
||||
# {0} message of the underlying exception
|
||||
# {1} the underlying exception
|
||||
# {2} the name of the exception
|
||||
SignedMailValidator.exceptionRetrievingSignerCert.title = Fehler beim Lesen des Signaturzertifikats
|
||||
SignedMailValidator.exceptionRetrievingSignerCert.text = Signatur Validierung fehlgeschlagen. Es gab eine {2} beim Lesen des Signaturzertifikats: {0}.
|
||||
SignedMailValidator.exceptionRetrievingSignerCert.summary = Signatur Validierung fehlgeschlagen. Es gab eine {2} beim Lesen des Signaturzertifikats.
|
||||
SignedMailValidator.exceptionRetrievingSignerCert.details = Signatur Validierung fehlgeschlagen. Es gab eine {2} beim Lesen des Signaturzertifikats: {0}.
|
||||
|
||||
# exception verifying the signature
|
||||
# {0} message of the underlying exception
|
||||
# {1} the underlying exception
|
||||
# {2} the name of the exception
|
||||
SignedMailValidator.exceptionVerifyingSignature.title = Signatur nicht verifiziert
|
||||
SignedMailValidator.exceptionVerifyingSignature.text = Signatur nicht verifiziert. Es gab eine {2}. Grund: {0}.
|
||||
SignedMailValidator.exceptionVerifyingSignature.summary = Signatur nicht verifiziert. Es gab eine {2}.
|
||||
SignedMailValidator.exceptionVerifyingSignature.details = Signatur nicht verifiziert. Es gab eine {2}. Grund: {0}.
|
||||
|
||||
# signature not verified
|
||||
SignedMailValidator.signatureNotVerified.title = Signatur nicht verifiziert
|
||||
SignedMailValidator.signatureNotVerified.text = Signatur nicht verifiziert. Der öffentliche Schlüssel des Unterzeichners passt nicht zur Signatur.
|
||||
SignedMailValidator.signatureNotVerified.summary = Signatur nicht verifiziert. Der öffentliche Schlüssel des Unterzeichners passt nicht zur Signatur.
|
||||
SignedMailValidator.signatureNotVerified.details = Signatur nicht verifiziert. Der öffentliche Schlüssel des Unterzeichners passt nicht zur Signatur.
|
||||
|
||||
# certificate key usage does not permit digitalSignature or nonRepudiation
|
||||
SignedMailValidator.signingNotPermitted.title = Schlüssel nicht verwendbar für Email Signaturen
|
||||
SignedMailValidator.signingNotPermitted.text = Der Schlüssel des Unterzeichners darf nicht für Email Signaturen verwendet werden.
|
||||
SignedMailValidator.signingNotPermitted.summary = Der Schlüssel des Unterzeichners darf nicht für Email Signaturen verwendet werden.
|
||||
SignedMailValidator.signingNotPermitted.details = Die Schlüsselverwendung des Unterzeichner Zertifikats erlaubt keine Verwendung für Email Signaturen.
|
||||
|
||||
# certificate extended key usage does not permit emailProtection or anyExtendedKeyUsage
|
||||
SignedMailValidator.extKeyUsageNotPermitted.title = Schlüssel nicht verwendbar für Email Signaturen
|
||||
SignedMailValidator.extKeyUsageNotPermitted.text = Der Schlüssel des Unterzeichners darf nicht für Email Signaturen verwendet werden.
|
||||
SignedMailValidator.extKeyUsageNotPermitted.summary = Der Schlüssel des Unterzeichners darf nicht für Email Signaturen verwendet werden.
|
||||
SignedMailValidator.extKeyUsageNotPermitted.details = Die erweiterte Schlüsselverwendung des Unterzeichner Zertifikats erlaubt keine Verwendung für Email Signaturen.
|
||||
|
||||
# exception processing the extended key usage extension
|
||||
# {0} message of the underlying exception
|
||||
# {1} the underlying exception
|
||||
# {2} the name of the exception
|
||||
SignedMailValidator.extKeyUsageError.title = Fehler bei der Verarbeitung der Extended key usage Erweiterung
|
||||
SignedMailValidator.extKeyUsageError.text = Es gab eine {2} bei der Verarbeitung der Extended key usage Erweiterung. Grund: {0}.
|
||||
SignedMailValidator.extKeyUsageError.summary = Es gab eine {2} bei der Verarbeitung der Extended key usage Erweiterung.
|
||||
SignedMailValidator.extKeyUsageError.details = Es gab eine {2} bei der Verarbeitung der Extended key usage Erweiterung. Grund: {0}.
|
||||
|
||||
# cannot create certificate path (exception)
|
||||
# {0} message of the underlying exception
|
||||
# {1} the underlying exception
|
||||
# {2} the name of the exception
|
||||
SignedMailValidator.exceptionCreateCertPath.title = Zertifizierungspfad Validierung fehlgeschlagen
|
||||
SignedMailValidator.exceptionCreateCertPath.text = Die Zertifizierungspfad Validierung ist fehlgeschlagen. Es gab eine {2} beim erstellen des Zertifizierungspfad: {0}.
|
||||
SignedMailValidator.exceptionCreateCertPath.summary = Die Zertifizierungspfad Validierung ist fehlgeschlagen. Es gab eine {2} beim erstellen des Zertifizierungspfad: {0}.
|
||||
SignedMailValidator.exceptionCreateCertPath.details = Die Zertifizierungspfad Validierung ist fehlgeschlagen. Es gab eine {2} beim erstellen des Zertifizierungspfad: {0}.
|
||||
|
||||
# certificate path is invalid
|
||||
SignedMailValidator.certPathInvalid.title = Zertifikats-Pfad ungültig
|
||||
SignedMailValidator.certPathInvalid.text = Der Zertifikats-Pfad ist ungültig.
|
||||
SignedMailValidator.certPathInvalid.summary = Der Zertifikats-Pfad ist ungültig.
|
||||
SignedMailValidator.certPathInvalid.details = Der Zertifikats-Pfad ist ungültig.
|
||||
@@ -0,0 +1,26 @@
|
||||
package org.bouncycastle.mail.smime.test;
|
||||
|
||||
import junit.framework.Test;
|
||||
import junit.framework.TestSuite;
|
||||
|
||||
public class AllTests
|
||||
{
|
||||
public static void main (String[] args)
|
||||
throws Exception
|
||||
{
|
||||
junit.textui.TestRunner.run (suite());
|
||||
}
|
||||
|
||||
public static Test suite()
|
||||
throws Exception
|
||||
{
|
||||
TestSuite suite= new TestSuite("SMIME tests");
|
||||
|
||||
suite.addTest(NewSMIMESignedTest.suite());
|
||||
suite.addTest(SignedMailValidatorTest.suite());
|
||||
suite.addTest(NewSMIMEEnvelopedTest.suite());
|
||||
suite.addTest(SMIMECompressedTest.suite());
|
||||
suite.addTest(SMIMEMiscTest.suite());
|
||||
return suite;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package org.bouncycastle.mail.smime.test;
|
||||
|
||||
import org.bouncycastle.x509.PKIXCertPathReviewer;
|
||||
|
||||
public class DummyCertPathReviewer extends PKIXCertPathReviewer
|
||||
{
|
||||
|
||||
}
|
||||
@@ -0,0 +1,511 @@
|
||||
package org.bouncycastle.mail.smime.test;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.security.KeyPair;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.cert.CertificateEncodingException;
|
||||
import java.security.cert.CertificateFactory;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.Arrays;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.mail.MessagingException;
|
||||
import javax.mail.Session;
|
||||
import javax.mail.internet.MimeBodyPart;
|
||||
import javax.mail.internet.MimeMessage;
|
||||
|
||||
import junit.framework.Test;
|
||||
import junit.framework.TestCase;
|
||||
import junit.framework.TestSuite;
|
||||
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
|
||||
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
|
||||
import org.bouncycastle.cms.CMSAlgorithm;
|
||||
import org.bouncycastle.cms.KeyTransRecipientId;
|
||||
import org.bouncycastle.cms.RecipientId;
|
||||
import org.bouncycastle.cms.RecipientInformation;
|
||||
import org.bouncycastle.cms.RecipientInformationStore;
|
||||
import org.bouncycastle.cms.jcajce.JceCMSContentEncryptorBuilder;
|
||||
import org.bouncycastle.cms.jcajce.JceKeyTransEnvelopedRecipient;
|
||||
import org.bouncycastle.cms.jcajce.JceKeyTransRecipientId;
|
||||
import org.bouncycastle.cms.jcajce.JceKeyTransRecipientInfoGenerator;
|
||||
import org.bouncycastle.cms.test.CMSTestUtil;
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||
import org.bouncycastle.mail.smime.SMIMEEnveloped;
|
||||
import org.bouncycastle.mail.smime.SMIMEEnvelopedGenerator;
|
||||
import org.bouncycastle.mail.smime.SMIMEEnvelopedParser;
|
||||
import org.bouncycastle.mail.smime.SMIMEUtil;
|
||||
import org.bouncycastle.mail.smime.util.FileBackedMimeBodyPart;
|
||||
import org.bouncycastle.openssl.PEMKeyPair;
|
||||
import org.bouncycastle.openssl.PEMParser;
|
||||
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
|
||||
import org.bouncycastle.util.encoders.Base64;
|
||||
|
||||
public class NewSMIMEEnvelopedTest
|
||||
extends TestCase
|
||||
{
|
||||
private static final String BC = BouncyCastleProvider.PROVIDER_NAME;
|
||||
|
||||
private static String _signDN;
|
||||
private static KeyPair _signKP;
|
||||
|
||||
private static String _reciDN;
|
||||
private static KeyPair _reciKP;
|
||||
private static X509Certificate _reciCert;
|
||||
|
||||
private static String _reciDN2;
|
||||
private static KeyPair _reciKP2;
|
||||
private static X509Certificate _reciCert2;
|
||||
|
||||
private static boolean _initialised = false;
|
||||
|
||||
private static final byte[] testMessage = Base64.decode(
|
||||
"TUlNRS1WZXJzaW9uOiAxLjANCkNvbnRlbnQtVHlwZTogbXVsdGlwYXJ0L21peGVkOyANCglib3VuZGFye" +
|
||||
"T0iLS0tLT1fUGFydF8wXzI2MDM5NjM4Ni4xMzUyOTA0NzUwMTMyIg0KQ29udGVudC1MYW5ndWFnZTogZW" +
|
||||
"4NCkNvbnRlbnQtRGVzY3JpcHRpb246IEEgbWFpbCBmb2xsb3dpbmcgdGhlIERJUkVDVCBwcm9qZWN0IHN" +
|
||||
"wZWNpZmljYXRpb25zDQoNCi0tLS0tLT1fUGFydF8wXzI2MDM5NjM4Ni4xMzUyOTA0NzUwMTMyDQpDb250" +
|
||||
"ZW50LVR5cGU6IHRleHQvcGxhaW47IG5hbWU9bnVsbDsgY2hhcnNldD11cy1hc2NpaQ0KQ29udGVudC1Uc" +
|
||||
"mFuc2Zlci1FbmNvZGluZzogN2JpdA0KQ29udGVudC1EaXNwb3NpdGlvbjogaW5saW5lOyBmaWxlbmFtZT" +
|
||||
"1udWxsDQoNCkNpYW8gZnJvbSB2aWVubmENCi0tLS0tLT1fUGFydF8wXzI2MDM5NjM4Ni4xMzUyOTA0NzU" +
|
||||
"wMTMyLS0NCg==");
|
||||
|
||||
private static void init()
|
||||
throws Exception
|
||||
{
|
||||
if (!_initialised)
|
||||
{
|
||||
_initialised = true;
|
||||
|
||||
_signDN = "O=Bouncy Castle, C=AU";
|
||||
_signKP = CMSTestUtil.makeKeyPair();
|
||||
|
||||
_reciDN = "CN=Doug, OU=Sales, O=Bouncy Castle, C=AU";
|
||||
_reciKP = CMSTestUtil.makeKeyPair();
|
||||
_reciCert = CMSTestUtil.makeCertificate(_reciKP, _reciDN, _signKP, _signDN);
|
||||
|
||||
_reciDN2 = "CN=Fred, OU=Sales, O=Bouncy Castle, C=AU";
|
||||
_reciKP2 = CMSTestUtil.makeKeyPair();
|
||||
_reciCert2 = CMSTestUtil.makeCertificate(_reciKP2, _reciDN2, _signKP, _signDN);
|
||||
}
|
||||
}
|
||||
|
||||
public NewSMIMEEnvelopedTest(
|
||||
String name)
|
||||
{
|
||||
super(name);
|
||||
}
|
||||
|
||||
public static void main(
|
||||
String args[])
|
||||
{
|
||||
junit.textui.TestRunner.run(NewSMIMEEnvelopedTest.class);
|
||||
}
|
||||
|
||||
public static Test suite()
|
||||
throws Exception
|
||||
{
|
||||
return new SMIMETestSetup(new TestSuite(NewSMIMEEnvelopedTest.class));
|
||||
}
|
||||
|
||||
public void setUp()
|
||||
throws Exception
|
||||
{
|
||||
init();
|
||||
}
|
||||
|
||||
private MimeMessage loadMessage(String name)
|
||||
throws MessagingException, FileNotFoundException
|
||||
{
|
||||
Session session = Session.getDefaultInstance(System.getProperties(), null);
|
||||
|
||||
return new MimeMessage(session, getClass().getResourceAsStream(name));
|
||||
}
|
||||
|
||||
private X509Certificate loadCert(String name)
|
||||
throws Exception
|
||||
{
|
||||
return (X509Certificate)CertificateFactory.getInstance("X.509", BC).generateCertificate(getClass().getResourceAsStream(name));
|
||||
}
|
||||
|
||||
private PrivateKey loadKey(String name)
|
||||
throws Exception
|
||||
{
|
||||
return new JcaPEMKeyConverter().setProvider("BC").getKeyPair((PEMKeyPair)(new PEMParser(new InputStreamReader(getClass().getResourceAsStream(name)))).readObject()).getPrivate();
|
||||
}
|
||||
|
||||
public void testHeaders()
|
||||
throws Exception
|
||||
{
|
||||
MimeBodyPart msg = SMIMETestUtil.makeMimeBodyPart("WallaWallaWashington");
|
||||
|
||||
SMIMEEnvelopedGenerator gen = new SMIMEEnvelopedGenerator();
|
||||
|
||||
gen.addRecipientInfoGenerator(new JceKeyTransRecipientInfoGenerator(_reciCert).setProvider(BC));
|
||||
|
||||
//
|
||||
// generate a MimeBodyPart object which encapsulates the content
|
||||
// we want encrypted.
|
||||
//
|
||||
|
||||
MimeBodyPart mp = gen.generate(msg, new JceCMSContentEncryptorBuilder(CMSAlgorithm.DES_EDE3_CBC).setProvider(BC).build());
|
||||
|
||||
assertEquals("application/pkcs7-mime; name=\"smime.p7m\"; smime-type=enveloped-data", mp.getHeader("Content-Type")[0]);
|
||||
assertEquals("attachment; filename=\"smime.p7m\"", mp.getHeader("Content-Disposition")[0]);
|
||||
assertEquals("S/MIME Encrypted Message", mp.getHeader("Content-Description")[0]);
|
||||
}
|
||||
|
||||
public void testDESEDE3Encrypted()
|
||||
throws Exception
|
||||
{
|
||||
MimeBodyPart msg = SMIMETestUtil.makeMimeBodyPart("WallaWallaWashington");
|
||||
String algorithm = SMIMEEnvelopedGenerator.DES_EDE3_CBC;
|
||||
|
||||
verifyAlgorithm(algorithm, msg);
|
||||
}
|
||||
|
||||
public void testParserDESEDE3Encrypted()
|
||||
throws Exception
|
||||
{
|
||||
MimeBodyPart msg = SMIMETestUtil.makeMimeBodyPart("WallaWallaWashington");
|
||||
String algorithm = SMIMEEnvelopedGenerator.DES_EDE3_CBC;
|
||||
|
||||
verifyParserAlgorithm(algorithm, msg);
|
||||
}
|
||||
|
||||
public void testIDEAEncrypted()
|
||||
throws Exception
|
||||
{
|
||||
if (isPresent("IDEA"))
|
||||
{
|
||||
MimeBodyPart msg = SMIMETestUtil.makeMimeBodyPart("WallaWallaWashington");
|
||||
String algorithm = SMIMEEnvelopedGenerator.IDEA_CBC;
|
||||
|
||||
verifyAlgorithm(algorithm, msg);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isPresent(String algorithm)
|
||||
throws Exception
|
||||
{
|
||||
try
|
||||
{
|
||||
Cipher.getInstance(algorithm, BC);
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (NoSuchAlgorithmException e)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public void testRC2Encrypted()
|
||||
throws Exception
|
||||
{
|
||||
MimeBodyPart msg = SMIMETestUtil.makeMimeBodyPart("WallaWallaWashington");
|
||||
String algorithm = SMIMEEnvelopedGenerator.RC2_CBC;
|
||||
|
||||
verifyAlgorithm(algorithm, msg);
|
||||
}
|
||||
|
||||
public void testCASTEncrypted()
|
||||
throws Exception
|
||||
{
|
||||
MimeBodyPart msg = SMIMETestUtil.makeMimeBodyPart("WallaWallaWashington");
|
||||
String algorithm = SMIMEEnvelopedGenerator.CAST5_CBC;
|
||||
|
||||
verifyAlgorithm(algorithm, msg);
|
||||
}
|
||||
|
||||
public void testAES128Encrypted()
|
||||
throws Exception
|
||||
{
|
||||
MimeBodyPart msg = SMIMETestUtil.makeMimeBodyPart("WallaWallaWashington");
|
||||
String algorithm = SMIMEEnvelopedGenerator.AES128_CBC;
|
||||
|
||||
verifyAlgorithm(algorithm, msg);
|
||||
}
|
||||
|
||||
public void testAES192Encrypted()
|
||||
throws Exception
|
||||
{
|
||||
MimeBodyPart msg = SMIMETestUtil.makeMimeBodyPart("WallaWallaWashington");
|
||||
String algorithm = SMIMEEnvelopedGenerator.AES192_CBC;
|
||||
|
||||
verifyAlgorithm(algorithm, msg);
|
||||
}
|
||||
|
||||
public void testAES256Encrypted()
|
||||
throws Exception
|
||||
{
|
||||
MimeBodyPart msg = SMIMETestUtil.makeMimeBodyPart("WallaWallaWashington");
|
||||
String algorithm = SMIMEEnvelopedGenerator.AES256_CBC;
|
||||
|
||||
verifyAlgorithm(algorithm, msg);
|
||||
}
|
||||
|
||||
public void testSubKeyId()
|
||||
throws Exception
|
||||
{
|
||||
MimeBodyPart msg = SMIMETestUtil.makeMimeBodyPart("WallaWallaWashington");
|
||||
|
||||
SMIMEEnvelopedGenerator gen = new SMIMEEnvelopedGenerator();
|
||||
|
||||
//
|
||||
// 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
|
||||
//
|
||||
MessageDigest dig = MessageDigest.getInstance("SHA1", BC);
|
||||
dig.update(SubjectPublicKeyInfo.getInstance(_reciCert.getPublicKey().getEncoded()).getPublicKeyData().getBytes());
|
||||
|
||||
|
||||
gen.addRecipientInfoGenerator(new JceKeyTransRecipientInfoGenerator(dig.digest(), _reciCert.getPublicKey()).setProvider(BC));
|
||||
|
||||
//
|
||||
// generate a MimeBodyPart object which encapsulates the content
|
||||
// we want encrypted.
|
||||
//
|
||||
|
||||
MimeBodyPart mp = gen.generate(msg, new JceCMSContentEncryptorBuilder(CMSAlgorithm.DES_EDE3_CBC).setProvider(BC).build());
|
||||
|
||||
SMIMEEnveloped m = new SMIMEEnveloped(mp);
|
||||
|
||||
dig.update(SubjectPublicKeyInfo.getInstance(_reciCert.getPublicKey().getEncoded()).getPublicKeyData().getBytes());
|
||||
|
||||
RecipientId recId = new KeyTransRecipientId(dig.digest());
|
||||
|
||||
RecipientInformationStore recipients = m.getRecipientInfos();
|
||||
RecipientInformation recipient = recipients.get(recId);
|
||||
|
||||
MimeBodyPart res = SMIMEUtil.toMimeBodyPart(recipient.getContent(new JceKeyTransEnvelopedRecipient(_reciKP.getPrivate()).setProvider(BC)));
|
||||
|
||||
verifyMessageBytes(msg, res);
|
||||
}
|
||||
|
||||
public void testDotNetEncMailMatch()
|
||||
throws Exception
|
||||
{
|
||||
MimeMessage message = loadMessage("dotnet_encrypted_mail.eml");
|
||||
|
||||
SMIMEEnveloped env = new SMIMEEnveloped(message);
|
||||
|
||||
RecipientInformationStore store = env.getRecipientInfos();
|
||||
|
||||
assertNotNull(store.get(new JceKeyTransRecipientId(loadCert("dotnet_enc_cert.pem"))));
|
||||
}
|
||||
|
||||
public void testAES128()
|
||||
throws Exception
|
||||
{
|
||||
MimeMessage message = loadMessage("test128.message");
|
||||
|
||||
SMIMEEnveloped env = new SMIMEEnveloped(message);
|
||||
|
||||
RecipientInformationStore store = env.getRecipientInfos();
|
||||
|
||||
RecipientInformation recipInfo = store.get(new JceKeyTransRecipientId(loadCert("cert.pem")));
|
||||
|
||||
assertNotNull(recipInfo);
|
||||
|
||||
byte[] content = recipInfo.getContent(new JceKeyTransEnvelopedRecipient(loadKey("key.pem")));
|
||||
|
||||
assertTrue(org.bouncycastle.util.Arrays.areEqual(testMessage, content));
|
||||
}
|
||||
|
||||
public void testAES192()
|
||||
throws Exception
|
||||
{
|
||||
MimeMessage message = loadMessage("test192.message");
|
||||
|
||||
SMIMEEnveloped env = new SMIMEEnveloped(message);
|
||||
|
||||
RecipientInformationStore store = env.getRecipientInfos();
|
||||
|
||||
RecipientInformation recipInfo = store.get(new JceKeyTransRecipientId(loadCert("cert.pem")));
|
||||
|
||||
assertNotNull(recipInfo);
|
||||
|
||||
byte[] content = recipInfo.getContent(new JceKeyTransEnvelopedRecipient(loadKey("key.pem")));
|
||||
|
||||
assertTrue(org.bouncycastle.util.Arrays.areEqual(testMessage, content));
|
||||
}
|
||||
|
||||
public void testAES256()
|
||||
throws Exception
|
||||
{
|
||||
MimeMessage message = loadMessage("test256.message");
|
||||
|
||||
SMIMEEnveloped env = new SMIMEEnveloped(message);
|
||||
|
||||
RecipientInformationStore store = env.getRecipientInfos();
|
||||
|
||||
RecipientInformation recipInfo = store.get(new JceKeyTransRecipientId(loadCert("cert.pem")));
|
||||
|
||||
assertNotNull(recipInfo);
|
||||
|
||||
byte[] content = recipInfo.getContent(new JceKeyTransEnvelopedRecipient(loadKey("key.pem")));
|
||||
|
||||
assertTrue(org.bouncycastle.util.Arrays.areEqual(testMessage, content));
|
||||
}
|
||||
|
||||
public void testCapEncrypt()
|
||||
throws Exception
|
||||
{
|
||||
MimeBodyPart msg = SMIMETestUtil.makeMimeBodyPart("WallaWallaWashington");
|
||||
|
||||
SMIMEEnvelopedGenerator gen = new SMIMEEnvelopedGenerator();
|
||||
|
||||
//
|
||||
// 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
|
||||
//
|
||||
MessageDigest dig = MessageDigest.getInstance("SHA1", BC);
|
||||
|
||||
dig.update(_reciCert.getPublicKey().getEncoded());
|
||||
|
||||
gen.addRecipientInfoGenerator(new JceKeyTransRecipientInfoGenerator(dig.digest(), _reciCert.getPublicKey()).setProvider(BC));
|
||||
|
||||
//
|
||||
// generate a MimeBodyPart object which encapsulates the content
|
||||
// we want encrypted.
|
||||
//
|
||||
MimeBodyPart mp = gen.generate(msg, new JceCMSContentEncryptorBuilder(CMSAlgorithm.RC2_CBC, 40).setProvider(BC).build());
|
||||
|
||||
SMIMEEnveloped m = new SMIMEEnveloped(mp);
|
||||
|
||||
dig.update(_reciCert.getPublicKey().getEncoded());
|
||||
|
||||
RecipientId recId = new KeyTransRecipientId(dig.digest());
|
||||
|
||||
RecipientInformationStore recipients = m.getRecipientInfos();
|
||||
RecipientInformation recipient = recipients.get(recId);
|
||||
|
||||
MimeBodyPart res = SMIMEUtil.toMimeBodyPart(recipient.getContent(new JceKeyTransEnvelopedRecipient(_reciKP.getPrivate()).setProvider(BC)));
|
||||
|
||||
verifyMessageBytes(msg, res);
|
||||
}
|
||||
|
||||
public void testTwoRecipients()
|
||||
throws Exception
|
||||
{
|
||||
MimeBodyPart _msg = SMIMETestUtil.makeMimeBodyPart("WallaWallaWashington");
|
||||
|
||||
SMIMEEnvelopedGenerator gen = new SMIMEEnvelopedGenerator();
|
||||
|
||||
gen.addRecipientInfoGenerator(new JceKeyTransRecipientInfoGenerator(_reciCert).setProvider(BC));
|
||||
gen.addRecipientInfoGenerator(new JceKeyTransRecipientInfoGenerator(_reciCert2).setProvider(BC));
|
||||
|
||||
//
|
||||
// generate a MimeBodyPart object which encapsulates the content
|
||||
// we want encrypted.
|
||||
//
|
||||
MimeBodyPart mp = gen.generate(_msg, new JceCMSContentEncryptorBuilder(CMSAlgorithm.RC2_CBC, 40).setProvider(BC).build());
|
||||
|
||||
SMIMEEnvelopedParser m = new SMIMEEnvelopedParser(mp);
|
||||
|
||||
RecipientId recId = getRecipientId(_reciCert2);
|
||||
|
||||
RecipientInformationStore recipients = m.getRecipientInfos();
|
||||
RecipientInformation recipient = recipients.get(recId);
|
||||
|
||||
FileBackedMimeBodyPart res = SMIMEUtil.toMimeBodyPart(recipient.getContentStream(new JceKeyTransEnvelopedRecipient(_reciKP2.getPrivate()).setProvider(BC)));
|
||||
|
||||
verifyMessageBytes(_msg, res);
|
||||
|
||||
m = new SMIMEEnvelopedParser(mp);
|
||||
|
||||
res.dispose();
|
||||
|
||||
recId = getRecipientId(_reciCert);
|
||||
|
||||
recipients = m.getRecipientInfos();
|
||||
recipient = recipients.get(recId);
|
||||
|
||||
res = SMIMEUtil.toMimeBodyPart(recipient.getContentStream(new JceKeyTransEnvelopedRecipient(_reciKP.getPrivate()).setProvider(BC)));
|
||||
|
||||
verifyMessageBytes(_msg, res);
|
||||
|
||||
res.dispose();
|
||||
}
|
||||
|
||||
private void verifyAlgorithm(
|
||||
String algorithmOid,
|
||||
MimeBodyPart msg)
|
||||
throws Exception
|
||||
{
|
||||
SMIMEEnvelopedGenerator gen = new SMIMEEnvelopedGenerator();
|
||||
|
||||
gen.addRecipientInfoGenerator(new JceKeyTransRecipientInfoGenerator(_reciCert).setProvider(BC));
|
||||
|
||||
//
|
||||
// generate a MimeBodyPart object which encapsulates the content
|
||||
// we want encrypted.
|
||||
//
|
||||
|
||||
MimeBodyPart mp = gen.generate(msg, new JceCMSContentEncryptorBuilder(new ASN1ObjectIdentifier(algorithmOid)).setProvider(BC).build());
|
||||
SMIMEEnveloped m = new SMIMEEnveloped(mp);
|
||||
RecipientId recId = getRecipientId(_reciCert);
|
||||
|
||||
RecipientInformationStore recipients = m.getRecipientInfos();
|
||||
RecipientInformation recipient = recipients.get(recId);
|
||||
|
||||
MimeBodyPart res = SMIMEUtil.toMimeBodyPart(recipient.getContent(new JceKeyTransEnvelopedRecipient(_reciKP.getPrivate()).setProvider(BC)));
|
||||
|
||||
verifyMessageBytes(msg, res);
|
||||
}
|
||||
|
||||
private void verifyParserAlgorithm(
|
||||
String algorithmOid,
|
||||
MimeBodyPart msg)
|
||||
throws Exception
|
||||
{
|
||||
SMIMEEnvelopedGenerator gen = new SMIMEEnvelopedGenerator();
|
||||
|
||||
gen.addRecipientInfoGenerator(new JceKeyTransRecipientInfoGenerator(_reciCert).setProvider(BC));
|
||||
|
||||
//
|
||||
// generate a MimeBodyPart object which encapsulates the content
|
||||
// we want encrypted.
|
||||
//
|
||||
|
||||
MimeBodyPart mp = gen.generate(msg, new JceCMSContentEncryptorBuilder(new ASN1ObjectIdentifier(algorithmOid)).setProvider(BC).build());
|
||||
SMIMEEnvelopedParser m = new SMIMEEnvelopedParser(mp);
|
||||
RecipientId recId = getRecipientId(_reciCert);
|
||||
|
||||
RecipientInformationStore recipients = m.getRecipientInfos();
|
||||
RecipientInformation recipient = recipients.get(recId);
|
||||
|
||||
MimeBodyPart res = SMIMEUtil.toMimeBodyPart(recipient.getContent(new JceKeyTransEnvelopedRecipient(_reciKP.getPrivate()).setProvider(BC)));
|
||||
|
||||
verifyMessageBytes(msg, res);
|
||||
}
|
||||
|
||||
private RecipientId getRecipientId(
|
||||
X509Certificate cert)
|
||||
throws IOException, CertificateEncodingException
|
||||
{
|
||||
RecipientId recId = new JceKeyTransRecipientId(cert);
|
||||
|
||||
return recId;
|
||||
}
|
||||
|
||||
|
||||
private void verifyMessageBytes(MimeBodyPart a, MimeBodyPart b)
|
||||
throws IOException, MessagingException
|
||||
{
|
||||
ByteArrayOutputStream _baos = new ByteArrayOutputStream();
|
||||
a.writeTo(_baos);
|
||||
_baos.close();
|
||||
byte[] _msgBytes = _baos.toByteArray();
|
||||
_baos = new ByteArrayOutputStream();
|
||||
b.writeTo(_baos);
|
||||
_baos.close();
|
||||
byte[] _resBytes = _baos.toByteArray();
|
||||
|
||||
assertEquals(true, Arrays.equals(_msgBytes, _resBytes));
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,228 @@
|
||||
package org.bouncycastle.mail.smime.test;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.InputStream;
|
||||
import java.security.KeyPair;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import javax.mail.Session;
|
||||
import javax.mail.internet.MimeBodyPart;
|
||||
import javax.mail.internet.MimeMessage;
|
||||
import javax.mail.internet.MimeMultipart;
|
||||
|
||||
import junit.framework.Test;
|
||||
import junit.framework.TestCase;
|
||||
import junit.framework.TestSuite;
|
||||
import org.bouncycastle.asn1.ASN1EncodableVector;
|
||||
import org.bouncycastle.asn1.cms.AttributeTable;
|
||||
import org.bouncycastle.asn1.smime.SMIMECapabilitiesAttribute;
|
||||
import org.bouncycastle.asn1.smime.SMIMECapability;
|
||||
import org.bouncycastle.asn1.smime.SMIMECapabilityVector;
|
||||
import org.bouncycastle.cert.X509CertificateHolder;
|
||||
import org.bouncycastle.cert.jcajce.JcaCertStore;
|
||||
import org.bouncycastle.cms.SignerInformation;
|
||||
import org.bouncycastle.cms.SignerInformationStore;
|
||||
import org.bouncycastle.cms.jcajce.JcaSimpleSignerInfoGeneratorBuilder;
|
||||
import org.bouncycastle.cms.jcajce.JcaSimpleSignerInfoVerifierBuilder;
|
||||
import org.bouncycastle.cms.jcajce.JcaX509CertSelectorConverter;
|
||||
import org.bouncycastle.cms.jcajce.ZlibCompressor;
|
||||
import org.bouncycastle.cms.jcajce.ZlibExpanderProvider;
|
||||
import org.bouncycastle.cms.test.CMSTestUtil;
|
||||
import org.bouncycastle.mail.smime.SMIMECompressed;
|
||||
import org.bouncycastle.mail.smime.SMIMECompressedGenerator;
|
||||
import org.bouncycastle.mail.smime.SMIMECompressedParser;
|
||||
import org.bouncycastle.mail.smime.SMIMESigned;
|
||||
import org.bouncycastle.mail.smime.SMIMESignedGenerator;
|
||||
import org.bouncycastle.mail.smime.SMIMEUtil;
|
||||
import org.bouncycastle.util.Arrays;
|
||||
import org.bouncycastle.util.Store;
|
||||
|
||||
public class SMIMECompressedTest
|
||||
extends TestCase
|
||||
{
|
||||
private static final String COMPRESSED_CONTENT_TYPE = "application/pkcs7-mime; name=\"smime.p7z\"; smime-type=compressed-data";
|
||||
|
||||
private static final JcaX509CertSelectorConverter selectorConverter = new JcaX509CertSelectorConverter();
|
||||
|
||||
boolean DEBUG = true;
|
||||
|
||||
MimeBodyPart msg;
|
||||
|
||||
String signDN;
|
||||
KeyPair signKP;
|
||||
X509Certificate signCert;
|
||||
|
||||
String origDN;
|
||||
KeyPair origKP;
|
||||
X509Certificate origCert;
|
||||
|
||||
String reciDN;
|
||||
KeyPair reciKP;
|
||||
X509Certificate reciCert;
|
||||
|
||||
KeyPair dsaSignKP;
|
||||
X509Certificate dsaSignCert;
|
||||
|
||||
KeyPair dsaOrigKP;
|
||||
X509Certificate dsaOrigCert;
|
||||
|
||||
/*
|
||||
*
|
||||
* INFRASTRUCTURE
|
||||
*
|
||||
*/
|
||||
|
||||
public SMIMECompressedTest(
|
||||
String name)
|
||||
throws Exception
|
||||
{
|
||||
super(name);
|
||||
|
||||
msg = SMIMETestUtil.makeMimeBodyPart("Hello world!");
|
||||
|
||||
signDN = "O=Bouncy Castle, C=AU";
|
||||
signKP = CMSTestUtil.makeKeyPair();
|
||||
signCert = CMSTestUtil.makeCertificate(signKP, signDN, signKP, signDN);
|
||||
|
||||
origDN = "CN=Eric H. Echidna, E=eric@bouncycastle.org, O=Bouncy Castle, C=AU";
|
||||
origKP = CMSTestUtil.makeKeyPair();
|
||||
origCert = CMSTestUtil.makeCertificate(origKP, origDN, signKP, signDN);
|
||||
}
|
||||
|
||||
public static void main(String args[])
|
||||
{
|
||||
junit.textui.TestRunner.run(SMIMECompressedTest.class);
|
||||
}
|
||||
|
||||
public static Test suite()
|
||||
{
|
||||
return new SMIMETestSetup(new TestSuite(SMIMECompressedTest.class));
|
||||
}
|
||||
|
||||
public void testHeaders()
|
||||
throws Exception
|
||||
{
|
||||
SMIMECompressedGenerator cgen = new SMIMECompressedGenerator();
|
||||
|
||||
MimeBodyPart cbp = cgen.generate(msg, new ZlibCompressor());
|
||||
|
||||
assertEquals(COMPRESSED_CONTENT_TYPE, cbp.getHeader("Content-Type")[0]);
|
||||
assertEquals("attachment; filename=\"smime.p7z\"", cbp.getHeader("Content-Disposition")[0]);
|
||||
assertEquals("S/MIME Compressed Message", cbp.getHeader("Content-Description")[0]);
|
||||
}
|
||||
|
||||
public void testBasic()
|
||||
throws Exception
|
||||
{
|
||||
SMIMECompressedGenerator cgen = new SMIMECompressedGenerator();
|
||||
ByteArrayOutputStream bOut = new ByteArrayOutputStream();
|
||||
MimeBodyPart cbp = cgen.generate(msg, new ZlibCompressor());
|
||||
|
||||
SMIMECompressed sc = new SMIMECompressed(cbp);
|
||||
|
||||
msg.writeTo(bOut);
|
||||
|
||||
assertTrue(Arrays.areEqual(bOut.toByteArray(), sc.getContent(new ZlibExpanderProvider())));
|
||||
}
|
||||
|
||||
public void testParser()
|
||||
throws Exception
|
||||
{
|
||||
SMIMECompressedGenerator cgen = new SMIMECompressedGenerator();
|
||||
ByteArrayOutputStream bOut1 = new ByteArrayOutputStream();
|
||||
ByteArrayOutputStream bOut2 = new ByteArrayOutputStream();
|
||||
MimeBodyPart cbp = cgen.generate(msg, new ZlibCompressor());
|
||||
SMIMECompressedParser sc = new SMIMECompressedParser(cbp);
|
||||
|
||||
msg.writeTo(bOut1);
|
||||
|
||||
InputStream in = sc.getContent(new ZlibExpanderProvider()).getContentStream();
|
||||
int ch;
|
||||
|
||||
while ((ch = in.read()) >= 0)
|
||||
{
|
||||
bOut2.write(ch);
|
||||
}
|
||||
|
||||
assertTrue(Arrays.areEqual(bOut1.toByteArray(), bOut2.toByteArray()));
|
||||
}
|
||||
|
||||
/*
|
||||
* test compressing and uncompressing of a multipart-signed message.
|
||||
*/
|
||||
public void testCompressedSHA1WithRSA()
|
||||
throws Exception
|
||||
{
|
||||
List certList = new ArrayList();
|
||||
|
||||
certList.add(origCert);
|
||||
certList.add(signCert);
|
||||
|
||||
Store certs = new JcaCertStore(certList);
|
||||
|
||||
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));
|
||||
|
||||
SMIMESignedGenerator gen = new SMIMESignedGenerator();
|
||||
|
||||
gen.addSignerInfoGenerator(new JcaSimpleSignerInfoGeneratorBuilder().setProvider("BC").setSignedAttributeGenerator(new AttributeTable(signedAttrs)).build("SHA1withRSA", origKP.getPrivate(), origCert));
|
||||
|
||||
gen.addCertificates(certs);
|
||||
|
||||
MimeMultipart smp = gen.generate(msg);
|
||||
|
||||
MimeMessage bp2 = new MimeMessage((Session)null);
|
||||
|
||||
bp2.setContent(smp);
|
||||
|
||||
bp2.saveChanges();
|
||||
|
||||
SMIMECompressedGenerator cgen = new SMIMECompressedGenerator();
|
||||
|
||||
MimeBodyPart cbp = cgen.generate(bp2, new ZlibCompressor());
|
||||
|
||||
SMIMECompressed cm = new SMIMECompressed(cbp);
|
||||
|
||||
MimeMultipart mm = (MimeMultipart)SMIMEUtil.toMimeBodyPart(cm.getContent(new ZlibExpanderProvider())).getContent();
|
||||
|
||||
SMIMESigned s = new SMIMESigned(mm);
|
||||
|
||||
ByteArrayOutputStream _baos = new ByteArrayOutputStream();
|
||||
msg.writeTo(_baos);
|
||||
_baos.close();
|
||||
byte[] _msgBytes = _baos.toByteArray();
|
||||
_baos = new ByteArrayOutputStream();
|
||||
s.getContent().writeTo(_baos);
|
||||
_baos.close();
|
||||
byte[] _resBytes = _baos.toByteArray();
|
||||
|
||||
assertEquals(true, Arrays.areEqual(_msgBytes, _resBytes));
|
||||
|
||||
certs = s.getCertificates();
|
||||
|
||||
SignerInformationStore signers = s.getSignerInfos();
|
||||
Collection c = signers.getSigners();
|
||||
Iterator it = c.iterator();
|
||||
|
||||
while (it.hasNext())
|
||||
{
|
||||
SignerInformation signer = (SignerInformation)it.next();
|
||||
Collection certCollection = certs.getMatches(signer.getSID());
|
||||
|
||||
Iterator certIt = certCollection.iterator();
|
||||
X509CertificateHolder cert = (X509CertificateHolder)certIt.next();
|
||||
|
||||
assertEquals(true, signer.verify(new JcaSimpleSignerInfoVerifierBuilder().setProvider("BC").build(cert)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,367 @@
|
||||
package org.bouncycastle.mail.smime.test;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.security.KeyPair;
|
||||
import java.security.Security;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
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 junit.framework.Test;
|
||||
import junit.framework.TestCase;
|
||||
import junit.framework.TestSuite;
|
||||
import org.bouncycastle.asn1.ASN1EncodableVector;
|
||||
import org.bouncycastle.asn1.cms.AttributeTable;
|
||||
import org.bouncycastle.asn1.smime.SMIMECapabilitiesAttribute;
|
||||
import org.bouncycastle.asn1.smime.SMIMECapability;
|
||||
import org.bouncycastle.asn1.smime.SMIMECapabilityVector;
|
||||
import org.bouncycastle.cert.X509CertificateHolder;
|
||||
import org.bouncycastle.cert.jcajce.JcaCertStore;
|
||||
import org.bouncycastle.cms.CMSAlgorithm;
|
||||
import org.bouncycastle.cms.CMSException;
|
||||
import org.bouncycastle.cms.RecipientInformation;
|
||||
import org.bouncycastle.cms.SignerInformation;
|
||||
import org.bouncycastle.cms.SignerInformationStore;
|
||||
import org.bouncycastle.cms.jcajce.JcaSimpleSignerInfoGeneratorBuilder;
|
||||
import org.bouncycastle.cms.jcajce.JcaSimpleSignerInfoVerifierBuilder;
|
||||
import org.bouncycastle.cms.jcajce.JcaX509CertSelectorConverter;
|
||||
import org.bouncycastle.cms.jcajce.JceCMSContentEncryptorBuilder;
|
||||
import org.bouncycastle.cms.jcajce.JceKeyTransEnvelopedRecipient;
|
||||
import org.bouncycastle.cms.jcajce.JceKeyTransRecipientInfoGenerator;
|
||||
import org.bouncycastle.cms.jcajce.ZlibCompressor;
|
||||
import org.bouncycastle.cms.test.CMSTestUtil;
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||
import org.bouncycastle.mail.smime.SMIMECompressedGenerator;
|
||||
import org.bouncycastle.mail.smime.SMIMEEnveloped;
|
||||
import org.bouncycastle.mail.smime.SMIMEEnvelopedGenerator;
|
||||
import org.bouncycastle.mail.smime.SMIMESigned;
|
||||
import org.bouncycastle.mail.smime.SMIMESignedGenerator;
|
||||
import org.bouncycastle.mail.smime.SMIMESignedParser;
|
||||
import org.bouncycastle.mail.smime.SMIMEUtil;
|
||||
import org.bouncycastle.mail.smime.util.FileBackedMimeBodyPart;
|
||||
import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder;
|
||||
import org.bouncycastle.util.Store;
|
||||
|
||||
public class SMIMEMiscTest
|
||||
extends TestCase
|
||||
{
|
||||
static MimeBodyPart msg;
|
||||
|
||||
static String signDN;
|
||||
static KeyPair signKP;
|
||||
static X509Certificate signCert;
|
||||
|
||||
static String origDN;
|
||||
static KeyPair origKP;
|
||||
static X509Certificate origCert;
|
||||
|
||||
static String reciDN;
|
||||
static KeyPair reciKP;
|
||||
static X509Certificate reciCert;
|
||||
|
||||
private static final JcaX509CertSelectorConverter selectorConverter = new JcaX509CertSelectorConverter();
|
||||
|
||||
KeyPair dsaSignKP;
|
||||
X509Certificate dsaSignCert;
|
||||
|
||||
KeyPair dsaOrigKP;
|
||||
X509Certificate dsaOrigCert;
|
||||
|
||||
static
|
||||
{
|
||||
try
|
||||
{
|
||||
msg = SMIMETestUtil.makeMimeBodyPart("Hello world!\n");
|
||||
|
||||
signDN = "O=Bouncy Castle, C=AU";
|
||||
signKP = CMSTestUtil.makeKeyPair();
|
||||
signCert = CMSTestUtil.makeCertificate(signKP, signDN, signKP, signDN);
|
||||
|
||||
origDN = "CN=Eric H. Echidna, E=eric@bouncycastle.org, O=Bouncy Castle, C=AU";
|
||||
origKP = CMSTestUtil.makeKeyPair();
|
||||
origCert = CMSTestUtil.makeCertificate(origKP, origDN, signKP, signDN);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new RuntimeException("problem setting up signed test class: " + e);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
*
|
||||
* INFRASTRUCTURE
|
||||
*
|
||||
*/
|
||||
|
||||
public SMIMEMiscTest(String name)
|
||||
{
|
||||
super(name);
|
||||
}
|
||||
|
||||
public static void main(String args[])
|
||||
{
|
||||
Security.addProvider(new BouncyCastleProvider());
|
||||
|
||||
junit.textui.TestRunner.run(SMIMEMiscTest.class);
|
||||
}
|
||||
|
||||
public static Test suite()
|
||||
{
|
||||
return new SMIMETestSetup(new TestSuite(SMIMEMiscTest.class));
|
||||
}
|
||||
|
||||
public void testSHA256WithRSAParserEncryptedWithAES()
|
||||
throws Exception
|
||||
{
|
||||
List certList = new ArrayList();
|
||||
|
||||
certList.add(origCert);
|
||||
certList.add(signCert);
|
||||
|
||||
Store certs = new JcaCertStore(certList);
|
||||
|
||||
SMIMEEnvelopedGenerator encGen = new SMIMEEnvelopedGenerator();
|
||||
|
||||
encGen.addRecipientInfoGenerator(new JceKeyTransRecipientInfoGenerator(origCert).setProvider("BC"));
|
||||
|
||||
MimeBodyPart mp = encGen.generate(msg, new JceCMSContentEncryptorBuilder(CMSAlgorithm.AES128_CBC).setProvider("BC").build());
|
||||
ASN1EncodableVector signedAttrs = generateSignedAttributes();
|
||||
|
||||
SMIMESignedGenerator gen = new SMIMESignedGenerator();
|
||||
|
||||
gen.addSignerInfoGenerator(new JcaSimpleSignerInfoGeneratorBuilder().setProvider("BC").setSignedAttributeGenerator(new AttributeTable(signedAttrs)).build("SHA256withRSA", origKP.getPrivate(), origCert));
|
||||
gen.addCertificates(certs);
|
||||
|
||||
MimeMultipart smm = gen.generate(mp);
|
||||
File tmpFile = File.createTempFile("bcTest", ".mime");
|
||||
|
||||
MimeMessage msg = createMimeMessage(tmpFile, smm);
|
||||
|
||||
SMIMESignedParser s = new SMIMESignedParser(new JcaDigestCalculatorProviderBuilder().setProvider("BC").build(), (MimeMultipart)msg.getContent());
|
||||
|
||||
certs = s.getCertificates();
|
||||
|
||||
verifyMessageBytes(mp, s.getContent());
|
||||
|
||||
verifySigners(certs, s.getSignerInfos());
|
||||
|
||||
tmpFile.delete();
|
||||
}
|
||||
|
||||
public void testSHA256WithRSACompressed()
|
||||
throws Exception
|
||||
{
|
||||
List certList = new ArrayList();
|
||||
|
||||
certList.add(origCert);
|
||||
certList.add(signCert);
|
||||
|
||||
Store certs = new JcaCertStore(certList);
|
||||
|
||||
SMIMECompressedGenerator cGen = new SMIMECompressedGenerator();
|
||||
|
||||
MimeBodyPart mp = cGen.generate(msg, new ZlibCompressor());
|
||||
|
||||
ASN1EncodableVector signedAttrs = generateSignedAttributes();
|
||||
|
||||
SMIMESignedGenerator gen = new SMIMESignedGenerator();
|
||||
|
||||
gen.addSignerInfoGenerator(new JcaSimpleSignerInfoGeneratorBuilder().setProvider("BC").setSignedAttributeGenerator(new AttributeTable(signedAttrs)).build("SHA256withRSA", origKP.getPrivate(), origCert));
|
||||
gen.addCertificates(certs);
|
||||
|
||||
MimeMultipart smm = gen.generate(mp);
|
||||
File tmpFile = File.createTempFile("bcTest", ".mime");
|
||||
|
||||
MimeMessage msg = createMimeMessage(tmpFile, smm);
|
||||
|
||||
SMIMESigned s = new SMIMESigned((MimeMultipart)msg.getContent());
|
||||
|
||||
certs = s.getCertificates();
|
||||
|
||||
verifyMessageBytes(mp, s.getContent());
|
||||
|
||||
verifySigners(certs, s.getSignerInfos());
|
||||
|
||||
tmpFile.delete();
|
||||
}
|
||||
|
||||
public void testQuotePrintableSigPreservation()
|
||||
throws Exception
|
||||
{
|
||||
MimeMessage msg = new MimeMessage((Session)null, getClass().getResourceAsStream("qp-soft-break.eml"));
|
||||
|
||||
SMIMEEnvelopedGenerator encGen = new SMIMEEnvelopedGenerator();
|
||||
|
||||
encGen.addRecipientInfoGenerator(new JceKeyTransRecipientInfoGenerator(origCert).setProvider("BC"));
|
||||
|
||||
MimeBodyPart mp = encGen.generate(msg, new JceCMSContentEncryptorBuilder(CMSAlgorithm.AES128_CBC).setProvider("BC").build());
|
||||
|
||||
SMIMEEnveloped env = new SMIMEEnveloped(mp);
|
||||
RecipientInformation ri = (RecipientInformation)env.getRecipientInfos().getRecipients().iterator().next();
|
||||
MimeBodyPart mm = SMIMEUtil.toMimeBodyPart(ri.getContentStream(new JceKeyTransEnvelopedRecipient(origKP.getPrivate()).setProvider("BC")));
|
||||
SMIMESigned s = new SMIMESigned((MimeMultipart)mm.getContent());
|
||||
Collection c = s.getSignerInfos().getSigners();
|
||||
Iterator it = c.iterator();
|
||||
Store certs = s.getCertificates();
|
||||
|
||||
while (it.hasNext())
|
||||
{
|
||||
SignerInformation signer = (SignerInformation)it.next();
|
||||
Collection certCollection = certs.getMatches(signer.getSID());
|
||||
|
||||
Iterator certIt = certCollection.iterator();
|
||||
X509CertificateHolder cert = (X509CertificateHolder)certIt.next();
|
||||
|
||||
assertEquals(true, signer.verify(new JcaSimpleSignerInfoVerifierBuilder().setProvider("BC").build(cert)));
|
||||
}
|
||||
|
||||
((FileBackedMimeBodyPart)mm).dispose();
|
||||
}
|
||||
|
||||
public void testSHA256WithRSAParserCompressed()
|
||||
throws Exception
|
||||
{
|
||||
List certList = new ArrayList();
|
||||
|
||||
certList.add(origCert);
|
||||
certList.add(signCert);
|
||||
|
||||
Store certs = new JcaCertStore(certList);
|
||||
|
||||
SMIMECompressedGenerator cGen = new SMIMECompressedGenerator();
|
||||
|
||||
MimeBodyPart mp = cGen.generate(msg, new ZlibCompressor());
|
||||
|
||||
ASN1EncodableVector signedAttrs = generateSignedAttributes();
|
||||
|
||||
SMIMESignedGenerator gen = new SMIMESignedGenerator();
|
||||
|
||||
gen.addSignerInfoGenerator(new JcaSimpleSignerInfoGeneratorBuilder().setProvider("BC").setSignedAttributeGenerator(new AttributeTable(signedAttrs)).build("SHA256withRSA", origKP.getPrivate(), origCert));
|
||||
gen.addCertificates(certs);
|
||||
|
||||
MimeMultipart smm = gen.generate(mp);
|
||||
File tmpFile = File.createTempFile("bcTest", ".mime");
|
||||
|
||||
MimeMessage msg = createMimeMessage(tmpFile, smm);
|
||||
|
||||
SMIMESignedParser s = new SMIMESignedParser(new JcaDigestCalculatorProviderBuilder().setProvider("BC").build(), (MimeMultipart)msg.getContent());
|
||||
|
||||
certs = s.getCertificates();
|
||||
|
||||
verifyMessageBytes(mp, s.getContent());
|
||||
|
||||
verifySigners(certs, s.getSignerInfos());
|
||||
|
||||
tmpFile.delete();
|
||||
}
|
||||
|
||||
public void testBrokenEnvelope()
|
||||
throws Exception
|
||||
{
|
||||
Session session = Session.getDefaultInstance(System.getProperties(), null);
|
||||
MimeMessage msg = new MimeMessage(session, getClass().getResourceAsStream("brokenEnv.message"));
|
||||
|
||||
try
|
||||
{
|
||||
new SMIMEEnveloped(msg);
|
||||
}
|
||||
catch (CMSException e)
|
||||
{
|
||||
if (!e.getMessage().equals("Malformed content."))
|
||||
{
|
||||
fail("wrong exception on bogus envelope");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void verifySigners(Store certs, SignerInformationStore signers)
|
||||
throws Exception
|
||||
{
|
||||
Collection c = signers.getSigners();
|
||||
Iterator it = c.iterator();
|
||||
|
||||
while (it.hasNext())
|
||||
{
|
||||
SignerInformation signer = (SignerInformation)it.next();
|
||||
Collection certCollection = certs.getMatches(signer.getSID());
|
||||
|
||||
Iterator certIt = certCollection.iterator();
|
||||
X509CertificateHolder cert = (X509CertificateHolder)certIt.next();
|
||||
|
||||
assertEquals(true, signer.verify(new JcaSimpleSignerInfoVerifierBuilder().setProvider("BC").build(cert)));
|
||||
}
|
||||
}
|
||||
|
||||
private void verifyMessageBytes(MimeBodyPart a, MimeBodyPart b)
|
||||
throws Exception
|
||||
{
|
||||
ByteArrayOutputStream bOut1 = new ByteArrayOutputStream();
|
||||
|
||||
a.writeTo(bOut1);
|
||||
bOut1.close();
|
||||
|
||||
ByteArrayOutputStream bOut2 = new ByteArrayOutputStream();
|
||||
|
||||
b.writeTo(bOut2);
|
||||
bOut2.close();
|
||||
|
||||
assertEquals(true, Arrays.equals(bOut1.toByteArray(), bOut2.toByteArray()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a mime message representing the multipart. We need to do
|
||||
* this as otherwise no raw content stream for the message will exist.
|
||||
*/
|
||||
private MimeMessage createMimeMessage(File tmpFile, MimeMultipart smm)
|
||||
throws Exception
|
||||
{
|
||||
FileOutputStream fOut = new FileOutputStream(tmpFile);
|
||||
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(smm, smm.getContentType());
|
||||
body.saveChanges();
|
||||
|
||||
body.writeTo(fOut);
|
||||
|
||||
fOut.close();
|
||||
|
||||
return new MimeMessage(session, new FileInputStream(tmpFile));
|
||||
}
|
||||
|
||||
private ASN1EncodableVector generateSignedAttributes()
|
||||
{
|
||||
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));
|
||||
|
||||
return signedAttrs;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
// Copyright (c) 2005 The Legion Of The Bouncy Castle (http://www.bouncycastle.org)
|
||||
package org.bouncycastle.mail.smime.test;
|
||||
|
||||
import junit.extensions.TestSetup;
|
||||
import junit.framework.Test;
|
||||
|
||||
import javax.activation.CommandMap;
|
||||
import javax.activation.MailcapCommandMap;
|
||||
import java.security.Security;
|
||||
|
||||
class SMIMETestSetup extends TestSetup
|
||||
{
|
||||
private CommandMap originalMap = null;
|
||||
|
||||
public SMIMETestSetup(Test test)
|
||||
{
|
||||
super(test);
|
||||
}
|
||||
|
||||
protected void setUp()
|
||||
{
|
||||
Security
|
||||
.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
|
||||
|
||||
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");
|
||||
|
||||
originalMap = CommandMap.getDefaultCommandMap();
|
||||
CommandMap.setDefaultCommandMap(_mailcap);
|
||||
}
|
||||
|
||||
protected void tearDown()
|
||||
{
|
||||
CommandMap.setDefaultCommandMap(originalMap);
|
||||
originalMap = null;
|
||||
Security.removeProvider("BC");
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package org.bouncycastle.mail.smime.test;
|
||||
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||
|
||||
import javax.mail.MessagingException;
|
||||
import javax.mail.internet.MimeBodyPart;
|
||||
import javax.mail.internet.MimeMultipart;
|
||||
import java.security.Security;
|
||||
|
||||
public class SMIMETestUtil
|
||||
{
|
||||
public static final boolean DEBUG = true;
|
||||
|
||||
static
|
||||
{
|
||||
Security.addProvider(new BouncyCastleProvider());
|
||||
}
|
||||
|
||||
/*
|
||||
*
|
||||
* MAIL
|
||||
*
|
||||
*/
|
||||
|
||||
public static MimeBodyPart makeMimeBodyPart(String msg)
|
||||
throws MessagingException
|
||||
{
|
||||
|
||||
MimeBodyPart _mbp = new MimeBodyPart();
|
||||
_mbp.setText(msg);
|
||||
return _mbp;
|
||||
}
|
||||
|
||||
public static MimeBodyPart makeMimeBodyPart(MimeMultipart mm)
|
||||
throws MessagingException
|
||||
{
|
||||
|
||||
MimeBodyPart _mbp = new MimeBodyPart();
|
||||
_mbp.setContent(mm, mm.getContentType());
|
||||
return _mbp;
|
||||
}
|
||||
|
||||
public static MimeMultipart makeMimeMultipart(String msg1, String msg2)
|
||||
throws MessagingException
|
||||
{
|
||||
|
||||
MimeMultipart _mm = new MimeMultipart();
|
||||
_mm.addBodyPart(makeMimeBodyPart(msg1));
|
||||
_mm.addBodyPart(makeMimeBodyPart(msg2));
|
||||
|
||||
return _mm;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,474 @@
|
||||
package org.bouncycastle.mail.smime.test;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.security.KeyPair;
|
||||
import java.security.Security;
|
||||
import java.security.cert.CertPath;
|
||||
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 java.util.TimeZone;
|
||||
|
||||
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 junit.framework.Test;
|
||||
import junit.framework.TestCase;
|
||||
import junit.framework.TestSuite;
|
||||
import org.bouncycastle.asn1.ASN1Encodable;
|
||||
import org.bouncycastle.asn1.ASN1Encoding;
|
||||
import org.bouncycastle.asn1.x509.X509Extension;
|
||||
import org.bouncycastle.cert.jcajce.JcaCertStore;
|
||||
import org.bouncycastle.cms.SignerInformation;
|
||||
import org.bouncycastle.cms.jcajce.JcaSimpleSignerInfoGeneratorBuilder;
|
||||
import org.bouncycastle.cms.test.CMSTestUtil;
|
||||
import org.bouncycastle.i18n.ErrorBundle;
|
||||
import org.bouncycastle.mail.smime.SMIMESignedGenerator;
|
||||
import org.bouncycastle.mail.smime.validator.SignedMailValidator;
|
||||
import org.bouncycastle.util.Store;
|
||||
import org.bouncycastle.x509.PKIXCertPathReviewer;
|
||||
import org.bouncycastle.x509.extension.X509ExtensionUtil;
|
||||
|
||||
public class SignedMailValidatorTest extends TestCase
|
||||
{
|
||||
static String TEST_TRUST_ACHOR = "validator.root.crt";
|
||||
|
||||
public void testShortKey() throws Exception
|
||||
{
|
||||
String message = "validator.shortKey.eml";
|
||||
PKIXParameters params = createDefaultParams();
|
||||
SignedMailValidator.ValidationResult result = doTest(message, params);
|
||||
|
||||
assertTrue(result.isValidSignature());
|
||||
assertContainsMessage(result.getNotifications(),
|
||||
"SignedMailValidator.shortSigningKey",
|
||||
"Warning: The signing key is only 512 bits long.");
|
||||
}
|
||||
|
||||
public void testKeyUsage() throws Exception
|
||||
{
|
||||
String message = "validator.keyUsage.eml";
|
||||
PKIXParameters params = createDefaultParams();
|
||||
SignedMailValidator.ValidationResult result = doTest(message, params);
|
||||
|
||||
assertTrue(result.isVerifiedSignature());
|
||||
assertTrue(result.getCertPathReview().isValidCertPath());
|
||||
assertFalse(result.isValidSignature());
|
||||
|
||||
assertContainsMessage(
|
||||
result.getErrors(),
|
||||
"SignedMailValidator.signingNotPermitted",
|
||||
"The key usage extension of signer certificate does not permit using the key for email signatures.");
|
||||
}
|
||||
|
||||
public void testExtKeyUsage() throws Exception
|
||||
{
|
||||
String message = "validator.extKeyUsage.eml";
|
||||
PKIXParameters params = createDefaultParams();
|
||||
SignedMailValidator.ValidationResult result = doTest(message, params);
|
||||
|
||||
assertTrue(result.isVerifiedSignature());
|
||||
assertTrue(result.getCertPathReview().isValidCertPath());
|
||||
assertFalse(result.isValidSignature());
|
||||
|
||||
assertContainsMessage(
|
||||
result.getErrors(),
|
||||
"SignedMailValidator.extKeyUsageNotPermitted",
|
||||
"The extended key usage extension of the signer certificate does not permit using the key for email signatures.");
|
||||
}
|
||||
|
||||
public void testNoEmail() throws Exception
|
||||
{
|
||||
String message = "validator.noEmail.eml";
|
||||
PKIXParameters params = createDefaultParams();
|
||||
SignedMailValidator.ValidationResult result = doTest(message, params);
|
||||
|
||||
assertTrue(result.isVerifiedSignature());
|
||||
assertTrue(result.getCertPathReview().isValidCertPath());
|
||||
assertFalse(result.isValidSignature());
|
||||
|
||||
assertContainsMessage(
|
||||
result.getErrors(),
|
||||
"SignedMailValidator.noEmailInCert",
|
||||
"The signer certificate is not usable for email signatures: it contains no email address.");
|
||||
}
|
||||
|
||||
public void testNotYetValid() throws Exception
|
||||
{
|
||||
String message = "validator.notYetValid.eml";
|
||||
PKIXParameters params = createDefaultParams();
|
||||
SignedMailValidator.ValidationResult result = doTest(message, params);
|
||||
|
||||
assertTrue(result.isVerifiedSignature());
|
||||
assertFalse(result.isValidSignature());
|
||||
assertContainsMessage(result.getErrors(),
|
||||
"SignedMailValidator.certNotYetValid",
|
||||
"The message was signed at Aug 28, 2006 3:04:01 PM GMT. But the certificate is not valid before Dec 28, 2006 2:19:31 PM GMT.");
|
||||
|
||||
PKIXCertPathReviewer review = result.getCertPathReview();
|
||||
assertFalse(review.isValidCertPath());
|
||||
assertContainsMessage(
|
||||
review.getErrors(0),
|
||||
"CertPathReviewer.certificateNotYetValid",
|
||||
"Could not validate the certificate. Certificate is not valid until Dec 28, 2006 2:19:31 PM GMT.");
|
||||
}
|
||||
|
||||
public void testExpired() throws Exception
|
||||
{
|
||||
String message = "validator.expired.eml";
|
||||
PKIXParameters params = createDefaultParams();
|
||||
SignedMailValidator.ValidationResult result = doTest(message, params);
|
||||
|
||||
assertTrue(result.isVerifiedSignature());
|
||||
assertFalse(result.isValidSignature());
|
||||
assertContainsMessage(result.getErrors(),
|
||||
"SignedMailValidator.certExpired",
|
||||
"The message was signed at Sep 1, 2006 9:08:35 AM GMT. But the certificate expired at Sep 1, 2006 8:39:20 AM GMT.");
|
||||
|
||||
PKIXCertPathReviewer review = result.getCertPathReview();
|
||||
assertFalse(review.isValidCertPath());
|
||||
assertContainsMessage(
|
||||
review.getErrors(0),
|
||||
"CertPathReviewer.certificateExpired",
|
||||
"Could not validate the certificate. Certificate expired on Sep 1, 2006 8:39:20 AM GMT.");
|
||||
}
|
||||
|
||||
public void testRevoked() throws Exception
|
||||
{
|
||||
String message = "validator.revoked.eml";
|
||||
PKIXParameters params = createDefaultParams();
|
||||
List crlList = new ArrayList();
|
||||
crlList.add(loadCRL("validator.revoked.crl"));
|
||||
CertStore crls = CertStore.getInstance("Collection",new CollectionCertStoreParameters(crlList));
|
||||
params.addCertStore(crls);
|
||||
params.setRevocationEnabled(true);
|
||||
|
||||
SignedMailValidator.ValidationResult result = doTest(message, params);
|
||||
|
||||
assertTrue(result.isVerifiedSignature());
|
||||
assertFalse(result.isValidSignature());
|
||||
|
||||
PKIXCertPathReviewer review = result.getCertPathReview();
|
||||
assertFalse(review.isValidCertPath());
|
||||
assertContainsMessage(
|
||||
review.getErrors(0),
|
||||
"CertPathReviewer.certRevoked",
|
||||
"The certificate was revoked at Sep 1, 2006 9:30:00 AM GMT. Reason: Key Compromise.");
|
||||
}
|
||||
|
||||
public void testLongValidity() throws Exception
|
||||
{
|
||||
String message = "validator.longValidity.eml";
|
||||
PKIXParameters params = createDefaultParams();
|
||||
|
||||
SignedMailValidator.ValidationResult result = doTest(message, params);
|
||||
|
||||
assertTrue(result.isVerifiedSignature());
|
||||
assertTrue(result.isValidSignature());
|
||||
|
||||
assertContainsMessage(result.getNotifications(),
|
||||
"SignedMailValidator.longValidity",
|
||||
"Warning: The signing certificate has a very long validity period: from Sep 1, 2006 11:00:00 AM GMT until Aug 8, 2106 11:00:00 AM GMT.");
|
||||
}
|
||||
|
||||
public void testSelfSignedCert()
|
||||
throws Exception
|
||||
{
|
||||
MimeBodyPart baseMsg = SMIMETestUtil.makeMimeBodyPart("Hello world!\n");
|
||||
String signDN = "CN=Eric H. Echidna, E=eric@bouncycastle.org, O=Bouncy Castle, C=AU";
|
||||
KeyPair signKP = CMSTestUtil.makeKeyPair();
|
||||
X509Certificate signCert = CMSTestUtil.makeV1Certificate(signKP, signDN, signKP, signDN);
|
||||
|
||||
// check basic path validation
|
||||
Set trustanchors = new HashSet();
|
||||
TrustAnchor ta = new TrustAnchor(signCert, null);
|
||||
trustanchors.add(ta);
|
||||
|
||||
X509Certificate rootCert = ta.getTrustedCert();
|
||||
|
||||
// init cert stores
|
||||
List certStores = new ArrayList();
|
||||
List certList = new ArrayList();
|
||||
certList.add(rootCert);
|
||||
CertStore store = CertStore.getInstance("Collection", new CollectionCertStoreParameters(certList));
|
||||
certStores.add(store);
|
||||
|
||||
// first path
|
||||
CertPath path = SignedMailValidator.createCertPath(rootCert, trustanchors, certStores);
|
||||
|
||||
assertTrue("path size is not 1", path.getCertificates().size() == 1);
|
||||
|
||||
// check message validation
|
||||
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 signedMsg = gen.generate(baseMsg);
|
||||
|
||||
Properties props = System.getProperties();
|
||||
Session session = Session.getDefaultInstance(props, null);
|
||||
|
||||
// read message
|
||||
MimeMessage msg = new MimeMessage(session);
|
||||
|
||||
Address fromUser = new InternetAddress("\"Eric H. Echidna\"<eric@bouncycastle.org>");
|
||||
Address toUser = new InternetAddress("example@bouncycastle.org");
|
||||
|
||||
msg.setFrom(fromUser);
|
||||
msg.setRecipient(Message.RecipientType.TO, toUser);
|
||||
msg.setContent(signedMsg, signedMsg.getContentType());
|
||||
|
||||
msg.saveChanges();
|
||||
|
||||
PKIXParameters params = new PKIXParameters(trustanchors);
|
||||
params.setRevocationEnabled(false);
|
||||
|
||||
SignedMailValidator validator = new SignedMailValidator(msg, params);
|
||||
SignerInformation signer = (SignerInformation) validator
|
||||
.getSignerInformationStore().getSigners().iterator().next();
|
||||
|
||||
SignedMailValidator.ValidationResult res = validator.getValidationResult(signer);
|
||||
|
||||
assertTrue(res.isVerifiedSignature());
|
||||
assertTrue(res.isValidSignature());
|
||||
}
|
||||
|
||||
// TODO: this test needs to be replaced, unfortunately it was working due to a bug in
|
||||
// trust anchor extension handling
|
||||
// public void testCorruptRootStore() throws Exception
|
||||
// {
|
||||
// String message = "validator.validMail.eml";
|
||||
// Set trustanchors = new HashSet();
|
||||
// trustanchors.add(getTrustAnchor(TEST_TRUST_ACHOR));
|
||||
// trustanchors.add(getTrustAnchor("validator.fakeRoot.crt"));
|
||||
// PKIXParameters params = new PKIXParameters(trustanchors);
|
||||
// params.setRevocationEnabled(false);
|
||||
//
|
||||
// SignedMailValidator.ValidationResult result = doTest(message, params);
|
||||
//
|
||||
// assertTrue(result.isVerifiedSignature());
|
||||
// assertFalse(result.isValidSignature());
|
||||
//
|
||||
// PKIXCertPathReviewer review = result.getCertPathReview();
|
||||
//
|
||||
// assertFalse(review.isValidCertPath());
|
||||
// assertContainsMessage(review.getErrors(-1),
|
||||
// "CertPathReviewer.conflictingTrustAnchors",
|
||||
// "Warning: corrupt trust root store: There are 2 trusted public keys for the CA \"CN=SignedMailValidatorTest Root, C=CH\" - please ensure with CA which is the correct key.");
|
||||
// }
|
||||
|
||||
public void testCircular() throws Exception
|
||||
{
|
||||
String message = "circular.eml";
|
||||
PKIXParameters params = createDefaultParams();
|
||||
SignedMailValidator.ValidationResult result = doTest(message, params);
|
||||
|
||||
assertTrue(result.isVerifiedSignature());
|
||||
assertFalse(result.isValidSignature());
|
||||
assertFalse(result.getCertPathReview().isValidCertPath());
|
||||
assertTrue("cert path size", result.getCertPathReview().getCertPathSize() > 2);
|
||||
}
|
||||
|
||||
public void testExtendedReviewer() throws Exception
|
||||
{
|
||||
try
|
||||
{
|
||||
// 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, getClass().getResourceAsStream("validator.shortKey.eml"));
|
||||
|
||||
SignedMailValidator validator = new SignedMailValidator(msg, createDefaultParams(), String.class);
|
||||
fail();
|
||||
}
|
||||
catch (IllegalArgumentException e)
|
||||
{
|
||||
assertTrue(e.getMessage().startsWith("certPathReviewerClass is not a subclass of"));
|
||||
}
|
||||
|
||||
// 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, getClass().getResourceAsStream("validator.shortKey.eml"));
|
||||
|
||||
SignedMailValidator validator = new SignedMailValidator(msg, createDefaultParams(), DummyCertPathReviewer.class);
|
||||
SignerInformation sInfo = (SignerInformation) validator.getSignerInformationStore().getSigners().iterator().next();
|
||||
SignedMailValidator.ValidationResult result = validator.getValidationResult(sInfo);
|
||||
|
||||
assertTrue(result.isValidSignature());
|
||||
assertContainsMessage(result.getNotifications(),
|
||||
"SignedMailValidator.shortSigningKey",
|
||||
"Warning: The signing key is only 512 bits long.");
|
||||
}
|
||||
|
||||
public void testCreateCertPath() throws Exception
|
||||
{
|
||||
// load trust anchor
|
||||
Set trustanchors = new HashSet();
|
||||
TrustAnchor ta = getTrustAnchor("certpath_root.crt");
|
||||
trustanchors.add(ta);
|
||||
|
||||
X509Certificate rootCert = ta.getTrustedCert();
|
||||
X509Certificate interCert1 = loadCert("certpath_inter1.crt");
|
||||
X509Certificate interCert2 = loadCert("certpath_inter2.crt");
|
||||
X509Certificate endCert1 = loadCert("certpath_end1.crt");
|
||||
X509Certificate endCert2 = loadCert("certpath_end2.crt");
|
||||
|
||||
// init cert stores
|
||||
List certStores = new ArrayList();
|
||||
List certList = new ArrayList();
|
||||
certList.add(interCert1);
|
||||
certList.add(interCert2);
|
||||
CertStore store = CertStore.getInstance("Collection", new CollectionCertStoreParameters(certList));
|
||||
certStores.add(store);
|
||||
|
||||
// first path
|
||||
CertPath path = SignedMailValidator.createCertPath(endCert1, trustanchors, certStores);
|
||||
assertTrue("path size is not 3", path.getCertificates().size() == 3);
|
||||
assertEquals("different end certificate", path.getCertificates().get(0), endCert1);
|
||||
assertEquals("different intermediate certificate", path.getCertificates().get(1), interCert1);
|
||||
assertEquals("different root certificate", path.getCertificates().get(2), rootCert);
|
||||
|
||||
// second path
|
||||
path = SignedMailValidator.createCertPath(endCert2, trustanchors, certStores);
|
||||
assertTrue("path size is not 3", path.getCertificates().size() == 3);
|
||||
assertEquals("different end certificate", path.getCertificates().get(0), endCert2);
|
||||
assertEquals("different intermediate certificate", path.getCertificates().get(1), interCert2);
|
||||
assertEquals("different root certificate", path.getCertificates().get(2), rootCert);
|
||||
}
|
||||
|
||||
private SignedMailValidator.ValidationResult doTest(String message,
|
||||
PKIXParameters params) throws Exception
|
||||
{
|
||||
// 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, getClass().getResourceAsStream(message));
|
||||
|
||||
SignedMailValidator validator = new SignedMailValidator(msg, params);
|
||||
SignerInformation signer = (SignerInformation) validator
|
||||
.getSignerInformationStore().getSigners().iterator().next();
|
||||
return validator.getValidationResult(signer);
|
||||
}
|
||||
|
||||
private void assertContainsMessage(List msgList, String messageId,
|
||||
String text) throws Exception
|
||||
{
|
||||
Iterator it = msgList.iterator();
|
||||
boolean found = false;
|
||||
while (it.hasNext())
|
||||
{
|
||||
ErrorBundle message = (ErrorBundle) it.next();
|
||||
if (message.getId().equals(messageId))
|
||||
{
|
||||
found = true;
|
||||
assertEquals(text, message.getText(Locale.ENGLISH, TimeZone
|
||||
.getTimeZone("GMT")));
|
||||
break;
|
||||
}
|
||||
}
|
||||
assertTrue("Expected message not found!", found);
|
||||
}
|
||||
|
||||
private PKIXParameters createDefaultParams() throws Exception
|
||||
{
|
||||
Set trustanchors = new HashSet();
|
||||
trustanchors.add(getTrustAnchor(TEST_TRUST_ACHOR));
|
||||
PKIXParameters defParams = new PKIXParameters(trustanchors);
|
||||
defParams.setRevocationEnabled(false);
|
||||
|
||||
return defParams;
|
||||
}
|
||||
|
||||
private 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;
|
||||
}
|
||||
|
||||
private X509Certificate loadCert(String certfile) throws Exception
|
||||
{
|
||||
X509Certificate cert = null;
|
||||
InputStream in = getClass().getResourceAsStream(certfile);
|
||||
|
||||
CertificateFactory cf = CertificateFactory.getInstance("X.509", "BC");
|
||||
cert = (X509Certificate) cf.generateCertificate(in);
|
||||
return cert;
|
||||
}
|
||||
|
||||
private X509CRL loadCRL(String crlfile) throws Exception
|
||||
{
|
||||
X509CRL crl = null;
|
||||
InputStream in = this.getClass().getResourceAsStream(crlfile);
|
||||
|
||||
CertificateFactory cf = CertificateFactory.getInstance("x.509", "BC");
|
||||
crl = (X509CRL) cf.generateCRL(in);
|
||||
return crl;
|
||||
}
|
||||
|
||||
public void setUp()
|
||||
{
|
||||
if (Security.getProvider("BC") == null)
|
||||
{
|
||||
Security
|
||||
.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws Exception
|
||||
{
|
||||
junit.textui.TestRunner.run(suite());
|
||||
}
|
||||
|
||||
public static Test suite() throws Exception
|
||||
{
|
||||
TestSuite suite = new TestSuite("SignedMailValidator Tests");
|
||||
|
||||
suite.addTestSuite(SignedMailValidatorTest.class);
|
||||
|
||||
return suite;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,224 @@
|
||||
package org.bouncycastle.mail.smime.test;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.InputStream;
|
||||
import java.security.KeyPair;
|
||||
import org.bouncycastle.jce.cert.CertStore;
|
||||
import org.bouncycastle.jce.cert.CollectionCertStoreParameters;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import javax.mail.Session;
|
||||
import javax.mail.internet.MimeBodyPart;
|
||||
import javax.mail.internet.MimeMessage;
|
||||
import javax.mail.internet.MimeMultipart;
|
||||
|
||||
import junit.framework.Test;
|
||||
import junit.framework.TestCase;
|
||||
import junit.framework.TestSuite;
|
||||
import org.bouncycastle.asn1.ASN1EncodableVector;
|
||||
import org.bouncycastle.asn1.cms.AttributeTable;
|
||||
import org.bouncycastle.asn1.smime.SMIMECapabilitiesAttribute;
|
||||
import org.bouncycastle.asn1.smime.SMIMECapability;
|
||||
import org.bouncycastle.asn1.smime.SMIMECapabilityVector;
|
||||
import org.bouncycastle.cms.SignerInformation;
|
||||
import org.bouncycastle.cms.SignerInformationStore;
|
||||
import org.bouncycastle.cms.jcajce.JcaX509CertSelectorConverter;
|
||||
import org.bouncycastle.cms.test.CMSTestUtil;
|
||||
import org.bouncycastle.mail.smime.SMIMECompressed;
|
||||
import org.bouncycastle.mail.smime.SMIMECompressedGenerator;
|
||||
import org.bouncycastle.mail.smime.SMIMECompressedParser;
|
||||
import org.bouncycastle.mail.smime.SMIMESigned;
|
||||
import org.bouncycastle.mail.smime.SMIMESignedGenerator;
|
||||
import org.bouncycastle.mail.smime.SMIMEUtil;
|
||||
import org.bouncycastle.util.Arrays;
|
||||
|
||||
public class SMIMECompressedTest
|
||||
extends TestCase
|
||||
{
|
||||
private static final String COMPRESSED_CONTENT_TYPE = "application/pkcs7-mime; name=\"smime.p7z\"; smime-type=compressed-data";
|
||||
|
||||
private static final JcaX509CertSelectorConverter selectorConverter = new JcaX509CertSelectorConverter();
|
||||
|
||||
boolean DEBUG = true;
|
||||
|
||||
MimeBodyPart msg;
|
||||
|
||||
String signDN;
|
||||
KeyPair signKP;
|
||||
X509Certificate signCert;
|
||||
|
||||
String origDN;
|
||||
KeyPair origKP;
|
||||
X509Certificate origCert;
|
||||
|
||||
String reciDN;
|
||||
KeyPair reciKP;
|
||||
X509Certificate reciCert;
|
||||
|
||||
KeyPair dsaSignKP;
|
||||
X509Certificate dsaSignCert;
|
||||
|
||||
KeyPair dsaOrigKP;
|
||||
X509Certificate dsaOrigCert;
|
||||
|
||||
/*
|
||||
*
|
||||
* INFRASTRUCTURE
|
||||
*
|
||||
*/
|
||||
|
||||
public SMIMECompressedTest(
|
||||
String name)
|
||||
throws Exception
|
||||
{
|
||||
super(name);
|
||||
|
||||
msg = SMIMETestUtil.makeMimeBodyPart("Hello world!");
|
||||
|
||||
signDN = "O=Bouncy Castle, C=AU";
|
||||
signKP = CMSTestUtil.makeKeyPair();
|
||||
signCert = CMSTestUtil.makeCertificate(signKP, signDN, signKP, signDN);
|
||||
|
||||
origDN = "CN=Eric H. Echidna, E=eric@bouncycastle.org, O=Bouncy Castle, C=AU";
|
||||
origKP = CMSTestUtil.makeKeyPair();
|
||||
origCert = CMSTestUtil.makeCertificate(origKP, origDN, signKP, signDN);
|
||||
}
|
||||
|
||||
public static void main(String args[])
|
||||
{
|
||||
junit.textui.TestRunner.run(SMIMECompressedTest.class);
|
||||
}
|
||||
|
||||
public static Test suite()
|
||||
{
|
||||
return new SMIMETestSetup(new TestSuite(SMIMECompressedTest.class));
|
||||
}
|
||||
|
||||
public void testHeaders()
|
||||
throws Exception
|
||||
{
|
||||
SMIMECompressedGenerator cgen = new SMIMECompressedGenerator();
|
||||
|
||||
MimeBodyPart cbp = cgen.generate(msg, SMIMECompressedGenerator.ZLIB);
|
||||
|
||||
assertEquals(COMPRESSED_CONTENT_TYPE, cbp.getHeader("Content-Type")[0]);
|
||||
assertEquals("attachment; filename=\"smime.p7z\"", cbp.getHeader("Content-Disposition")[0]);
|
||||
assertEquals("S/MIME Compressed Message", cbp.getHeader("Content-Description")[0]);
|
||||
}
|
||||
|
||||
public void testBasic()
|
||||
throws Exception
|
||||
{
|
||||
SMIMECompressedGenerator cgen = new SMIMECompressedGenerator();
|
||||
ByteArrayOutputStream bOut = new ByteArrayOutputStream();
|
||||
MimeBodyPart cbp = cgen.generate(msg, SMIMECompressedGenerator.ZLIB);
|
||||
|
||||
SMIMECompressed sc = new SMIMECompressed(cbp);
|
||||
|
||||
msg.writeTo(bOut);
|
||||
|
||||
assertTrue(Arrays.areEqual(bOut.toByteArray(), sc.getContent()));
|
||||
}
|
||||
|
||||
public void testParser()
|
||||
throws Exception
|
||||
{
|
||||
SMIMECompressedGenerator cgen = new SMIMECompressedGenerator();
|
||||
ByteArrayOutputStream bOut1 = new ByteArrayOutputStream();
|
||||
ByteArrayOutputStream bOut2 = new ByteArrayOutputStream();
|
||||
MimeBodyPart cbp = cgen.generate(msg, SMIMECompressedGenerator.ZLIB);
|
||||
SMIMECompressedParser sc = new SMIMECompressedParser(cbp);
|
||||
|
||||
msg.writeTo(bOut1);
|
||||
|
||||
InputStream in = sc.getContent().getContentStream();
|
||||
int ch;
|
||||
|
||||
while ((ch = in.read()) >= 0)
|
||||
{
|
||||
bOut2.write(ch);
|
||||
}
|
||||
|
||||
assertTrue(Arrays.areEqual(bOut1.toByteArray(), bOut2.toByteArray()));
|
||||
}
|
||||
|
||||
/*
|
||||
* test compressing and uncompressing of a multipart-signed message.
|
||||
*/
|
||||
public void testCompressedSHA1WithRSA()
|
||||
throws Exception
|
||||
{
|
||||
List certList = new ArrayList();
|
||||
|
||||
certList.add(origCert);
|
||||
certList.add(signCert);
|
||||
|
||||
CertStore certs = CertStore.getInstance("Collection",
|
||||
new CollectionCertStoreParameters(certList), "BC");
|
||||
|
||||
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));
|
||||
|
||||
SMIMESignedGenerator gen = new SMIMESignedGenerator();
|
||||
|
||||
gen.addSigner(origKP.getPrivate(), origCert, SMIMESignedGenerator.DIGEST_SHA1, new AttributeTable(signedAttrs), null);
|
||||
|
||||
gen.addCertificatesAndCRLs(certs);
|
||||
|
||||
MimeMultipart smp = gen.generate(msg, "BC");
|
||||
|
||||
MimeMessage bp2 = new MimeMessage((Session)null);
|
||||
|
||||
bp2.setContent(smp);
|
||||
|
||||
bp2.saveChanges();
|
||||
|
||||
SMIMECompressedGenerator cgen = new SMIMECompressedGenerator();
|
||||
|
||||
MimeBodyPart cbp = cgen.generate(bp2, SMIMECompressedGenerator.ZLIB);
|
||||
|
||||
SMIMECompressed cm = new SMIMECompressed(cbp);
|
||||
|
||||
MimeMultipart mm = (MimeMultipart)SMIMEUtil.toMimeBodyPart(cm.getContent()).getContent();
|
||||
|
||||
SMIMESigned s = new SMIMESigned(mm);
|
||||
|
||||
ByteArrayOutputStream _baos = new ByteArrayOutputStream();
|
||||
msg.writeTo(_baos);
|
||||
_baos.close();
|
||||
byte[] _msgBytes = _baos.toByteArray();
|
||||
_baos = new ByteArrayOutputStream();
|
||||
s.getContent().writeTo(_baos);
|
||||
_baos.close();
|
||||
byte[] _resBytes = _baos.toByteArray();
|
||||
|
||||
assertEquals(true, Arrays.areEqual(_msgBytes, _resBytes));
|
||||
|
||||
certs = s.getCertificatesAndCRLs("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(selectorConverter.getCertSelector(signer.getSID()));
|
||||
|
||||
Iterator certIt = certCollection.iterator();
|
||||
X509Certificate cert = (X509Certificate)certIt.next();
|
||||
|
||||
assertEquals(true, signer.verify(cert, "BC"));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,361 @@
|
||||
package org.bouncycastle.mail.smime.test;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.security.KeyPair;
|
||||
import java.security.Security;
|
||||
import org.bouncycastle.jce.cert.CertStore;
|
||||
import org.bouncycastle.jce.cert.CollectionCertStoreParameters;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
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 junit.framework.Test;
|
||||
import junit.framework.TestCase;
|
||||
import junit.framework.TestSuite;
|
||||
import org.bouncycastle.asn1.ASN1EncodableVector;
|
||||
import org.bouncycastle.asn1.cms.AttributeTable;
|
||||
import org.bouncycastle.asn1.smime.SMIMECapabilitiesAttribute;
|
||||
import org.bouncycastle.asn1.smime.SMIMECapability;
|
||||
import org.bouncycastle.asn1.smime.SMIMECapabilityVector;
|
||||
import org.bouncycastle.cms.CMSException;
|
||||
import org.bouncycastle.cms.RecipientInformation;
|
||||
import org.bouncycastle.cms.SignerInformation;
|
||||
import org.bouncycastle.cms.SignerInformationStore;
|
||||
import org.bouncycastle.cms.jcajce.JcaX509CertSelectorConverter;
|
||||
import org.bouncycastle.cms.test.CMSTestUtil;
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||
import org.bouncycastle.mail.smime.SMIMECompressedGenerator;
|
||||
import org.bouncycastle.mail.smime.SMIMEEnveloped;
|
||||
import org.bouncycastle.mail.smime.SMIMEEnvelopedGenerator;
|
||||
import org.bouncycastle.mail.smime.SMIMESigned;
|
||||
import org.bouncycastle.mail.smime.SMIMESignedGenerator;
|
||||
import org.bouncycastle.mail.smime.SMIMESignedParser;
|
||||
import org.bouncycastle.mail.smime.SMIMEUtil;
|
||||
import org.bouncycastle.mail.smime.util.FileBackedMimeBodyPart;
|
||||
|
||||
public class SMIMEMiscTest
|
||||
extends TestCase
|
||||
{
|
||||
static MimeBodyPart msg;
|
||||
|
||||
static String signDN;
|
||||
static KeyPair signKP;
|
||||
static X509Certificate signCert;
|
||||
|
||||
static String origDN;
|
||||
static KeyPair origKP;
|
||||
static X509Certificate origCert;
|
||||
|
||||
static String reciDN;
|
||||
static KeyPair reciKP;
|
||||
static X509Certificate reciCert;
|
||||
|
||||
private static final JcaX509CertSelectorConverter selectorConverter = new JcaX509CertSelectorConverter();
|
||||
|
||||
KeyPair dsaSignKP;
|
||||
X509Certificate dsaSignCert;
|
||||
|
||||
KeyPair dsaOrigKP;
|
||||
X509Certificate dsaOrigCert;
|
||||
|
||||
static
|
||||
{
|
||||
try
|
||||
{
|
||||
msg = SMIMETestUtil.makeMimeBodyPart("Hello world!\n");
|
||||
|
||||
signDN = "O=Bouncy Castle, C=AU";
|
||||
signKP = CMSTestUtil.makeKeyPair();
|
||||
signCert = CMSTestUtil.makeCertificate(signKP, signDN, signKP, signDN);
|
||||
|
||||
origDN = "CN=Eric H. Echidna, E=eric@bouncycastle.org, O=Bouncy Castle, C=AU";
|
||||
origKP = CMSTestUtil.makeKeyPair();
|
||||
origCert = CMSTestUtil.makeCertificate(origKP, origDN, signKP, signDN);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new RuntimeException("problem setting up signed test class: " + e);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
*
|
||||
* INFRASTRUCTURE
|
||||
*
|
||||
*/
|
||||
|
||||
public SMIMEMiscTest(String name)
|
||||
{
|
||||
super(name);
|
||||
}
|
||||
|
||||
public static void main(String args[])
|
||||
{
|
||||
Security.addProvider(new BouncyCastleProvider());
|
||||
|
||||
junit.textui.TestRunner.run(SMIMEMiscTest.class);
|
||||
}
|
||||
|
||||
public static Test suite()
|
||||
{
|
||||
return new SMIMETestSetup(new TestSuite(SMIMEMiscTest.class));
|
||||
}
|
||||
|
||||
public void testSHA256WithRSAParserEncryptedWithAES()
|
||||
throws Exception
|
||||
{
|
||||
List certList = new ArrayList();
|
||||
|
||||
certList.add(origCert);
|
||||
certList.add(signCert);
|
||||
|
||||
CertStore certs = CertStore.getInstance("Collection",
|
||||
new CollectionCertStoreParameters(certList), "BC");
|
||||
|
||||
SMIMEEnvelopedGenerator encGen = new SMIMEEnvelopedGenerator();
|
||||
|
||||
encGen.addKeyTransRecipient(origCert);
|
||||
|
||||
MimeBodyPart mp = encGen.generate(msg, SMIMEEnvelopedGenerator.AES128_CBC, "BC");
|
||||
ASN1EncodableVector signedAttrs = generateSignedAttributes();
|
||||
|
||||
SMIMESignedGenerator gen = new SMIMESignedGenerator();
|
||||
|
||||
gen.addSigner(origKP.getPrivate(), origCert, SMIMESignedGenerator.DIGEST_SHA256, new AttributeTable(signedAttrs), null);
|
||||
gen.addCertificatesAndCRLs(certs);
|
||||
|
||||
MimeMultipart smm = gen.generate(mp, "BC");
|
||||
File tmpFile = File.createTempFile("bcTest", ".mime");
|
||||
|
||||
MimeMessage msg = createMimeMessage(tmpFile, smm);
|
||||
|
||||
SMIMESignedParser s = new SMIMESignedParser((MimeMultipart)msg.getContent());
|
||||
|
||||
certs = s.getCertificatesAndCRLs("Collection", "BC");
|
||||
|
||||
verifyMessageBytes(mp, s.getContent());
|
||||
|
||||
verifySigners(certs, s.getSignerInfos());
|
||||
|
||||
tmpFile.delete();
|
||||
}
|
||||
|
||||
public void testSHA256WithRSACompressed()
|
||||
throws Exception
|
||||
{
|
||||
List certList = new ArrayList();
|
||||
|
||||
certList.add(origCert);
|
||||
certList.add(signCert);
|
||||
|
||||
CertStore certs = CertStore.getInstance("Collection",
|
||||
new CollectionCertStoreParameters(certList), "BC");
|
||||
|
||||
SMIMECompressedGenerator cGen = new SMIMECompressedGenerator();
|
||||
|
||||
MimeBodyPart mp = cGen.generate(msg, SMIMECompressedGenerator.ZLIB);
|
||||
|
||||
ASN1EncodableVector signedAttrs = generateSignedAttributes();
|
||||
|
||||
SMIMESignedGenerator gen = new SMIMESignedGenerator();
|
||||
|
||||
gen.addSigner(origKP.getPrivate(), origCert, SMIMESignedGenerator.DIGEST_SHA256, new AttributeTable(signedAttrs), null);
|
||||
gen.addCertificatesAndCRLs(certs);
|
||||
|
||||
MimeMultipart smm = gen.generate(mp, "BC");
|
||||
File tmpFile = File.createTempFile("bcTest", ".mime");
|
||||
|
||||
MimeMessage msg = createMimeMessage(tmpFile, smm);
|
||||
|
||||
SMIMESigned s = new SMIMESigned((MimeMultipart)msg.getContent());
|
||||
|
||||
certs = s.getCertificatesAndCRLs("Collection", "BC");
|
||||
|
||||
verifyMessageBytes(mp, s.getContent());
|
||||
|
||||
verifySigners(certs, s.getSignerInfos());
|
||||
|
||||
tmpFile.delete();
|
||||
}
|
||||
|
||||
public void testQuotePrintableSigPreservation()
|
||||
throws Exception
|
||||
{
|
||||
MimeMessage msg = new MimeMessage((Session)null, getClass().getResourceAsStream("qp-soft-break.eml"));
|
||||
|
||||
SMIMEEnvelopedGenerator encGen = new SMIMEEnvelopedGenerator();
|
||||
|
||||
encGen.addKeyTransRecipient(origCert);
|
||||
|
||||
MimeBodyPart mp = encGen.generate(msg, SMIMEEnvelopedGenerator.AES128_CBC, "BC");
|
||||
|
||||
SMIMEEnveloped env = new SMIMEEnveloped(mp);
|
||||
RecipientInformation ri = (RecipientInformation)env.getRecipientInfos().getRecipients().iterator().next();
|
||||
MimeBodyPart mm = SMIMEUtil.toMimeBodyPart(ri.getContentStream(origKP.getPrivate(), "BC"));
|
||||
SMIMESigned s = new SMIMESigned((MimeMultipart)mm.getContent());
|
||||
Collection c = s.getSignerInfos().getSigners();
|
||||
Iterator it = c.iterator();
|
||||
CertStore certs = s.getCertificatesAndCRLs("Collection", "BC");
|
||||
|
||||
while (it.hasNext())
|
||||
{
|
||||
SignerInformation signer = (SignerInformation)it.next();
|
||||
Collection certCollection = certs.getCertificates(selectorConverter.getCertSelector(signer.getSID()));
|
||||
|
||||
Iterator certIt = certCollection.iterator();
|
||||
X509Certificate cert = (X509Certificate)certIt.next();
|
||||
|
||||
assertEquals(true, signer.verify(cert, "BC"));
|
||||
}
|
||||
|
||||
((FileBackedMimeBodyPart)mm).dispose();
|
||||
}
|
||||
|
||||
public void testSHA256WithRSAParserCompressed()
|
||||
throws Exception
|
||||
{
|
||||
List certList = new ArrayList();
|
||||
|
||||
certList.add(origCert);
|
||||
certList.add(signCert);
|
||||
|
||||
CertStore certs = CertStore.getInstance("Collection",
|
||||
new CollectionCertStoreParameters(certList), "BC");
|
||||
|
||||
SMIMECompressedGenerator cGen = new SMIMECompressedGenerator();
|
||||
|
||||
MimeBodyPart mp = cGen.generate(msg, SMIMECompressedGenerator.ZLIB);
|
||||
|
||||
ASN1EncodableVector signedAttrs = generateSignedAttributes();
|
||||
|
||||
SMIMESignedGenerator gen = new SMIMESignedGenerator();
|
||||
|
||||
gen.addSigner(origKP.getPrivate(), origCert, SMIMESignedGenerator.DIGEST_SHA256, new AttributeTable(signedAttrs), null);
|
||||
gen.addCertificatesAndCRLs(certs);
|
||||
|
||||
MimeMultipart smm = gen.generate(mp, "BC");
|
||||
File tmpFile = File.createTempFile("bcTest", ".mime");
|
||||
|
||||
MimeMessage msg = createMimeMessage(tmpFile, smm);
|
||||
|
||||
SMIMESignedParser s = new SMIMESignedParser((MimeMultipart)msg.getContent());
|
||||
|
||||
certs = s.getCertificatesAndCRLs("Collection", "BC");
|
||||
|
||||
verifyMessageBytes(mp, s.getContent());
|
||||
|
||||
verifySigners(certs, s.getSignerInfos());
|
||||
|
||||
tmpFile.delete();
|
||||
}
|
||||
|
||||
public void testBrokenEnvelope()
|
||||
throws Exception
|
||||
{
|
||||
Session session = Session.getDefaultInstance(System.getProperties(), null);
|
||||
MimeMessage msg = new MimeMessage(session, getClass().getResourceAsStream("brokenEnv.message"));
|
||||
|
||||
try
|
||||
{
|
||||
new SMIMEEnveloped(msg);
|
||||
}
|
||||
catch (CMSException e)
|
||||
{
|
||||
if (!e.getMessage().equals("Malformed content."))
|
||||
{
|
||||
fail("wrong exception on bogus envelope");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void verifySigners(CertStore certs, SignerInformationStore signers)
|
||||
throws Exception
|
||||
{
|
||||
Collection c = signers.getSigners();
|
||||
Iterator it = c.iterator();
|
||||
|
||||
while (it.hasNext())
|
||||
{
|
||||
SignerInformation signer = (SignerInformation)it.next();
|
||||
Collection certCollection = certs.getCertificates(selectorConverter.getCertSelector(signer.getSID()));
|
||||
|
||||
Iterator certIt = certCollection.iterator();
|
||||
X509Certificate cert = (X509Certificate)certIt.next();
|
||||
|
||||
assertEquals(true, signer.verify(cert, "BC"));
|
||||
}
|
||||
}
|
||||
|
||||
private void verifyMessageBytes(MimeBodyPart a, MimeBodyPart b)
|
||||
throws Exception
|
||||
{
|
||||
ByteArrayOutputStream bOut1 = new ByteArrayOutputStream();
|
||||
|
||||
a.writeTo(bOut1);
|
||||
bOut1.close();
|
||||
|
||||
ByteArrayOutputStream bOut2 = new ByteArrayOutputStream();
|
||||
|
||||
b.writeTo(bOut2);
|
||||
bOut2.close();
|
||||
|
||||
assertEquals(true, Arrays.equals(bOut1.toByteArray(), bOut2.toByteArray()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a mime message representing the multipart. We need to do
|
||||
* this as otherwise no raw content stream for the message will exist.
|
||||
*/
|
||||
private MimeMessage createMimeMessage(File tmpFile, MimeMultipart smm)
|
||||
throws Exception
|
||||
{
|
||||
FileOutputStream fOut = new FileOutputStream(tmpFile);
|
||||
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(smm, smm.getContentType());
|
||||
body.saveChanges();
|
||||
|
||||
body.writeTo(fOut);
|
||||
|
||||
fOut.close();
|
||||
|
||||
return new MimeMessage(session, new FileInputStream(tmpFile));
|
||||
}
|
||||
|
||||
private ASN1EncodableVector generateSignedAttributes()
|
||||
{
|
||||
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));
|
||||
|
||||
return signedAttrs;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,25 @@
|
||||
package org.bouncycastle.mail.smime.test;
|
||||
|
||||
import junit.framework.Test;
|
||||
import junit.framework.TestSuite;
|
||||
|
||||
public class AllTests
|
||||
{
|
||||
public static void main (String[] args)
|
||||
throws Exception
|
||||
{
|
||||
junit.textui.TestRunner.run (suite());
|
||||
}
|
||||
|
||||
public static Test suite()
|
||||
throws Exception
|
||||
{
|
||||
TestSuite suite= new TestSuite("SMIME tests");
|
||||
|
||||
suite.addTest(NewSMIMESignedTest.suite());
|
||||
suite.addTest(NewSMIMEEnvelopedTest.suite());
|
||||
suite.addTest(SMIMECompressedTest.suite());
|
||||
suite.addTest(SMIMEMiscTest.suite());
|
||||
return suite;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user