Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support AuthEnvelopedData in mail SMIME #1791

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package org.bouncycastle.mail.smime;

import org.bouncycastle.cms.CMSAuthEnvelopedData;
import org.bouncycastle.cms.CMSException;

import javax.mail.MessagingException;
import javax.mail.internet.MimeBodyPart;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimePart;

/**
* containing class for an S/MIME pkcs7-mime encrypted MimePart.
*/
public class SMIMEAuthEnveloped
extends CMSAuthEnvelopedData
{
MimePart message;

public SMIMEAuthEnveloped(
MimeBodyPart message)
throws MessagingException, CMSException
{
super(SMIMEUtil.getInputStream(message));

this.message = message;
}

public SMIMEAuthEnveloped(
MimeMessage message)
throws MessagingException, CMSException
{
super(SMIMEUtil.getInputStream(message));

this.message = message;
}

public MimePart getEncryptedContent()
{
return message;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
package org.bouncycastle.mail.smime;

import org.bouncycastle.asn1.ASN1EncodableVector;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.cms.*;
import org.bouncycastle.operator.OutputAEADEncryptor;
import org.bouncycastle.operator.OutputEncryptor;

import javax.activation.CommandMap;
import javax.activation.MailcapCommandMap;
import javax.mail.MessagingException;
import javax.mail.internet.MimeBodyPart;
import java.io.IOException;
import java.io.OutputStream;
import java.security.AccessController;
import java.security.PrivilegedAction;

/**
* General class for generating a pkcs7-mime message using AEAD algorithm.
*
* A simple example of usage.
*
* <pre>
* SMIMEAuthEnvelopedGenerator fact = new SMIMEAuthEnvelopedGenerator();
*
* fact.addRecipientInfoGenerator(new JceKeyTransRecipientInfoGenerator(recipientCert).setProvider("BC"));
*
* MimeBodyPart mp = fact.generate(content, new JceCMSContentEncryptorBuilder(CMSAlgorithm.AES256_GCM).setProvider("BC").build());
* </pre>
*
* <b>Note:</b> Most clients expect the MimeBodyPart to be in a MimeMultipart
* when it's sent.
*/
public class SMIMEAuthEnvelopedGenerator
extends SMIMEEnvelopedGenerator
{
public static final String AES128_GCM = CMSAuthEnvelopedDataGenerator.AES128_GCM;
public static final String AES192_GCM = CMSAuthEnvelopedDataGenerator.AES192_GCM;
public static final String AES256_GCM = CMSAuthEnvelopedDataGenerator.AES256_GCM;

private static final String AUTH_ENCRYPTED_CONTENT_TYPE = "application/pkcs7-mime; name=\"smime.p7m\"; smime-type=authEnveloped-data";

final private AuthEnvelopedGenerator authFact;

static
{
AccessController.doPrivileged(new PrivilegedAction()
{
public Object run()
{
CommandMap commandMap = CommandMap.getDefaultCommandMap();

if (commandMap instanceof MailcapCommandMap)
{
CommandMap.setDefaultCommandMap(MailcapUtil.addCommands((MailcapCommandMap)commandMap));
}

return null;
}
});
}

/**
* base constructor
*/
public SMIMEAuthEnvelopedGenerator()
{
authFact = new AuthEnvelopedGenerator();
}

/**
* add a recipientInfoGenerator.
*/
public void addRecipientInfoGenerator(
RecipientInfoGenerator recipientInfoGen)
throws IllegalArgumentException
{
authFact.addRecipientInfoGenerator(recipientInfoGen);
}

/**
* Use a BER Set to store the recipient information
*/
public void setBerEncodeRecipients(
boolean berEncodeRecipientSet)
{
authFact.setBEREncodeRecipients(berEncodeRecipientSet);
}

/**
* return encrypted content type for enveloped data.
*/
protected String getEncryptedContentType() {
return AUTH_ENCRYPTED_CONTENT_TYPE;
}

/**
* return content encryptor.
*/
protected SMIMEStreamingProcessor getContentEncryptor(
MimeBodyPart content,
OutputEncryptor encryptor)
throws SMIMEException
{
if (encryptor instanceof OutputAEADEncryptor) {
return new ContentEncryptor(content, (OutputAEADEncryptor)encryptor);
}
// this would happen if the encryption algorithm is not AEAD algorithm
throw new SMIMEException("encryptor is not AEAD encryptor");
}

private static class AuthEnvelopedGenerator
extends CMSAuthEnvelopedDataStreamGenerator
{
private ASN1ObjectIdentifier dataType;
private ASN1EncodableVector recipientInfos;

protected OutputStream open(
ASN1ObjectIdentifier dataType,
OutputStream out,
ASN1EncodableVector recipientInfos,
OutputAEADEncryptor encryptor)
throws IOException
{
this.dataType = dataType;
this.recipientInfos = recipientInfos;

return super.open(dataType, out, recipientInfos, encryptor);
}

OutputStream regenerate(
OutputStream out,
OutputAEADEncryptor encryptor)
throws IOException
{
return super.open(dataType, out, recipientInfos, encryptor);
}
}

private class ContentEncryptor
implements SMIMEStreamingProcessor
{
private final MimeBodyPart _content;
private OutputAEADEncryptor _encryptor;

private boolean _firstTime = true;

ContentEncryptor(
MimeBodyPart content,
OutputAEADEncryptor encryptor)
{
_content = content;
_encryptor = encryptor;
}

public void write(OutputStream out)
throws IOException
{
OutputStream encrypted;

try
{
if (_firstTime)
{
encrypted = authFact.open(out, _encryptor);

_firstTime = false;
}
else
{
encrypted = authFact.regenerate(out, _encryptor);
}

CommandMap commandMap = CommandMap.getDefaultCommandMap();

if (commandMap instanceof MailcapCommandMap)
{
_content.getDataHandler().setCommandMap(MailcapUtil.addCommands((MailcapCommandMap)commandMap));
}

_content.writeTo(encrypted);

encrypted.close();
}
catch (MessagingException | CMSException e)
{
throw new WrappingIOException(e.toString(), e);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package org.bouncycastle.mail.smime;

import org.bouncycastle.cms.CMSAuthEnvelopedDataParser;
import org.bouncycastle.cms.CMSException;

import javax.mail.MessagingException;
import javax.mail.internet.MimeBodyPart;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimePart;
import java.io.IOException;

/**
* Stream based containing class for an S/MIME pkcs7-mime encrypted MimePart using AEAD algorithm.
*/
public class SMIMEAuthEnvelopedParser
extends CMSAuthEnvelopedDataParser
{
private final MimePart message;

public SMIMEAuthEnvelopedParser(
MimeBodyPart message)
throws IOException, MessagingException, CMSException
{
this(message, 0);
}

public SMIMEAuthEnvelopedParser(
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 SMIMEAuthEnvelopedParser(
MimeBodyPart message,
int bufferSize)
throws IOException, MessagingException, CMSException
{
super(SMIMEUtil.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 bufferSize to be used.
*/
public SMIMEAuthEnvelopedParser(
MimeMessage message,
int bufferSize)
throws IOException, MessagingException, CMSException
{
super(SMIMEUtil.getInputStream(message, bufferSize));

this.message = message;
}

public MimePart getEncryptedContent()
{
return message;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,25 @@ public void setBerEncodeRecipients(
fact.setBEREncodeRecipients(berEncodeRecipientSet);
}

/**
/**
* return encrypted content type for enveloped data.
*/
protected String getEncryptedContentType() {
return ENCRYPTED_CONTENT_TYPE;
}

/**
* return content encryptor.
*/
protected SMIMEStreamingProcessor getContentEncryptor(
MimeBodyPart content,
OutputEncryptor encryptor)
throws SMIMEException
{
return new ContentEncryptor(content, encryptor);
}

/**
* if we get here we expect the Mime body part to be well defined.
*/
private MimeBodyPart make(
Expand All @@ -124,8 +142,8 @@ private MimeBodyPart make(
{
MimeBodyPart data = new MimeBodyPart();

data.setContent(new ContentEncryptor(content, encryptor), ENCRYPTED_CONTENT_TYPE);
data.addHeader("Content-Type", ENCRYPTED_CONTENT_TYPE);
data.setContent(getContentEncryptor(content, encryptor), getEncryptedContentType());
data.addHeader("Content-Type", getEncryptedContentType());
data.addHeader("Content-Disposition", "attachment; filename=\"smime.p7m\"");
data.addHeader("Content-Description", "S/MIME Encrypted Message");
data.addHeader("Content-Transfer-Encoding", encoding);
Expand Down Expand Up @@ -210,11 +228,7 @@ public void write(OutputStream out)

encrypted.close();
}
catch (MessagingException e)
{
throw new WrappingIOException(e.toString(), e);
}
catch (CMSException e)
catch (MessagingException | CMSException e)
{
throw new WrappingIOException(e.toString(), e);
}
Expand Down Expand Up @@ -249,7 +263,7 @@ OutputStream regenerate(
}
}

private static class WrappingIOException
protected static class WrappingIOException
extends IOException
{
private Throwable cause;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ public static Test suite()

suite.addTestSuite(NewSMIMESignedTest.class);
suite.addTestSuite(SignedMailValidatorTest.class);
suite.addTestSuite(NewSMIMEAuthEnvelopedTest.class);
suite.addTestSuite(NewSMIMEEnvelopedTest.class);
suite.addTestSuite(SMIMECompressedTest.class);
suite.addTestSuite(SMIMEMiscTest.class);
Expand Down
Loading