From 05f8fd39dc3df75c287db598b2fce922f8ebda0d Mon Sep 17 00:00:00 2001 From: Ulrich Schuster Date: Tue, 17 May 2022 15:37:12 +0200 Subject: [PATCH 1/3] feat: Make cryptographic algorithms configurable --- pom.xml | 58 +- .../mail/smime/KeyEncapsulationAlgorithm.java | 13 + .../utils/mail/smime/SmimeKey.java | 2 +- .../utils/mail/smime/SmimeKeyStore.java | 18 +- .../utils/mail/smime/SmimeUtil.java | 1270 +++++++++-------- .../utils/mail/smime/SmimeUtilTest.java | 125 ++ src/test/resources/alice-certgen-rsa.sh | 27 + src/test/resources/alice.cnf | 35 + src/test/resources/alice.p12 | Bin 0 -> 4472 bytes src/test/resources/bob-certgen-rsa.sh | 27 + src/test/resources/bob.cnf | 35 + src/test/resources/bob.p12 | Bin 0 -> 4452 bytes 12 files changed, 981 insertions(+), 629 deletions(-) create mode 100644 src/main/java/org/simplejavamail/utils/mail/smime/KeyEncapsulationAlgorithm.java create mode 100644 src/test/java/org/simplejavamail/utils/mail/smime/SmimeUtilTest.java create mode 100755 src/test/resources/alice-certgen-rsa.sh create mode 100644 src/test/resources/alice.cnf create mode 100644 src/test/resources/alice.p12 create mode 100755 src/test/resources/bob-certgen-rsa.sh create mode 100644 src/test/resources/bob.cnf create mode 100644 src/test/resources/bob.p12 diff --git a/pom.xml b/pom.xml index a065631..d148b48 100644 --- a/pom.xml +++ b/pom.xml @@ -3,28 +3,33 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> 4.0.0 - - com.github.bbottema - standard-project-parent - 1.0.24 - + + + + + org.simplejavamail utils-mail-smime jar utils-mail-smime - 2.0.1 - A S/MIME library for JavaMail + 2.1.0-SNAPSHOT + An S/MIME library for JavaMail http:///github.com/bbottema/java-utils-mail-smime 2021 + UTF-8 + UTF-8 com.github.bbottema.java-utils-mail-smime com/mycila/maven/plugin/license/templates/APACHE-2.txt Benny Bottema benny@bennybottema.com + 5.8.2 + 17 + 17 @@ -72,6 +77,18 @@ https://github.com/bbottema/java-utils-mail-smime/issues + + + + org.junit + junit-bom + ${junit.version} + pom + import + + + + org.jetbrains @@ -85,10 +102,37 @@ bcjmail-jdk15to18 1.70 + + jakarta.activation + jakarta.activation-api + 2.0.1 + + + com.sun.activation + jakarta.activation + 2.0.1 + + + jakarta.mail + jakarta.mail-api + 2.0.1 + com.sun.mail jakarta.mail 2.0.1 + + + org.junit.jupiter + junit-jupiter-api + test + + + org.junit.jupiter + junit-jupiter-engine + test + + \ No newline at end of file diff --git a/src/main/java/org/simplejavamail/utils/mail/smime/KeyEncapsulationAlgorithm.java b/src/main/java/org/simplejavamail/utils/mail/smime/KeyEncapsulationAlgorithm.java new file mode 100644 index 0000000..fe93fc0 --- /dev/null +++ b/src/main/java/org/simplejavamail/utils/mail/smime/KeyEncapsulationAlgorithm.java @@ -0,0 +1,13 @@ +/* + * Copyright (c) 2022. Ulrich Schuster, 10827 Berlin, Germany + */ + +package org.simplejavamail.utils.mail.smime; + +public enum KeyEncapsulationAlgorithm { + RSA, + RSA_OAEP_SHA224, + RSA_OAEP_SHA256, + RSA_OAEP_SHA384, + RSA_OAEP_SHA512, +} \ No newline at end of file diff --git a/src/main/java/org/simplejavamail/utils/mail/smime/SmimeKey.java b/src/main/java/org/simplejavamail/utils/mail/smime/SmimeKey.java index 6aeb848..81f5a7c 100644 --- a/src/main/java/org/simplejavamail/utils/mail/smime/SmimeKey.java +++ b/src/main/java/org/simplejavamail/utils/mail/smime/SmimeKey.java @@ -89,7 +89,7 @@ private void extractAssociatedAddresses() { try { X509Certificate certificate = getCertificate(); if (null != certificate) { - Principal principal = certificate.getSubjectDN(); + Principal principal = certificate.getSubjectX500Principal(); if (null != principal) { String name = principal.getName(); StringTokenizer tokenizer = new StringTokenizer(name, ","); diff --git a/src/main/java/org/simplejavamail/utils/mail/smime/SmimeKeyStore.java b/src/main/java/org/simplejavamail/utils/mail/smime/SmimeKeyStore.java index 8189cad..ba0f689 100644 --- a/src/main/java/org/simplejavamail/utils/mail/smime/SmimeKeyStore.java +++ b/src/main/java/org/simplejavamail/utils/mail/smime/SmimeKeyStore.java @@ -14,7 +14,7 @@ /** * A wrapper around a {@link KeyStore} that can be initialized with a PKCS12 * keystore and is used to obtain {@link SmimeKey SmimeKeys}. - * + * * @author Allen Petersen (akp at sourceforge dot net) * @author Torsten Krause (tk at markenwerk dot net) * @since 1.0.0 @@ -26,11 +26,11 @@ public class SmimeKeyStore { /** * Creates a new {@code SmimeKeyStore} by loading a PKCS12 keystore from * the given input stream. - * + * *

* The character array holding the password is overwritten with {@code 0s} * after it has been used. - * + * * @param stream * The {@link InputStream} to read the PKCS12 keystore from. * @param password @@ -43,12 +43,12 @@ public SmimeKeyStore(InputStream stream, char[] password) { /** * Creates a new {@code SmimeKeyStore} by loading a PKCS12 keystore from * the given input stream. - * + * *

* If {@code discardPassword} is set to {@code true}, the character array * holding the password is overwritten with {@code 0s} after it has been * used. - * + * * @param stream * The {@link InputStream} to read the PKCS12 keystore from. * @param password @@ -95,11 +95,11 @@ public int size() { /** * Returns the S/MIME key associated with the given alias, using the given * password to recover it. - * + * *

* The character array holding the password is overwritten with {@code 0s} * after it has been used. - * + * * @param alias * The alias. * @param password @@ -115,12 +115,12 @@ public SmimeKey getPrivateKey(String alias, char[] password) { /** * Returns the S/MIME key associated with the given alias, using the given * password to recover it. - * + * *

* If {@code discardPassword} is set to {@code true}, the character array * holding the password is overwritten with {@code 0s} after it has been * used. - * + * * @param alias * The alias. * @param password diff --git a/src/main/java/org/simplejavamail/utils/mail/smime/SmimeUtil.java b/src/main/java/org/simplejavamail/utils/mail/smime/SmimeUtil.java index 7340ae4..03ba41d 100644 --- a/src/main/java/org/simplejavamail/utils/mail/smime/SmimeUtil.java +++ b/src/main/java/org/simplejavamail/utils/mail/smime/SmimeUtil.java @@ -1,9 +1,18 @@ package org.simplejavamail.utils.mail.smime; import com.sun.mail.smtp.SMTPMessage; +import jakarta.activation.CommandMap; +import jakarta.activation.MailcapCommandMap; +import jakarta.mail.Header; +import jakarta.mail.MessagingException; +import jakarta.mail.Multipart; +import jakarta.mail.Session; +import jakarta.mail.internet.*; import org.bouncycastle.asn1.ASN1EncodableVector; +import org.bouncycastle.asn1.ASN1ObjectIdentifier; import org.bouncycastle.asn1.cms.AttributeTable; import org.bouncycastle.asn1.cms.IssuerAndSerialNumber; +import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers; import org.bouncycastle.asn1.smime.SMIMECapabilitiesAttribute; import org.bouncycastle.asn1.smime.SMIMECapability; import org.bouncycastle.asn1.smime.SMIMECapabilityVector; @@ -12,47 +21,24 @@ import org.bouncycastle.asn1.x500.X500Name; import org.bouncycastle.asn1.x500.style.BCStyle; import org.bouncycastle.asn1.x500.style.IETFUtils; +import org.bouncycastle.asn1.x509.AlgorithmIdentifier; import org.bouncycastle.cert.X509CertificateHolder; import org.bouncycastle.cert.jcajce.JcaCertStore; import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; -import org.bouncycastle.cms.CMSAlgorithm; -import org.bouncycastle.cms.CMSException; -import org.bouncycastle.cms.RecipientInformation; -import org.bouncycastle.cms.RecipientInformationStore; -import org.bouncycastle.cms.SignerId; -import org.bouncycastle.cms.SignerInfoGenerator; -import org.bouncycastle.cms.SignerInformation; -import org.bouncycastle.cms.SignerInformationVerifier; -import org.bouncycastle.cms.jcajce.JcaSimpleSignerInfoGeneratorBuilder; -import org.bouncycastle.cms.jcajce.JcaSimpleSignerInfoVerifierBuilder; -import org.bouncycastle.cms.jcajce.JceCMSContentEncryptorBuilder; -import org.bouncycastle.cms.jcajce.JceKeyTransEnvelopedRecipient; -import org.bouncycastle.cms.jcajce.JceKeyTransRecipient; -import org.bouncycastle.cms.jcajce.JceKeyTransRecipientId; -import org.bouncycastle.cms.jcajce.JceKeyTransRecipientInfoGenerator; +import org.bouncycastle.cms.*; +import org.bouncycastle.cms.jcajce.*; import org.bouncycastle.jce.provider.BouncyCastleProvider; -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.SMIMEUtil; +import org.bouncycastle.mail.smime.*; import org.bouncycastle.operator.OperatorCreationException; import org.bouncycastle.operator.OutputEncryptor; +import org.bouncycastle.operator.jcajce.JcaAlgorithmParametersConverter; import org.bouncycastle.util.Store; -import jakarta.activation.CommandMap; -import jakarta.activation.MailcapCommandMap; -import jakarta.mail.Header; -import jakarta.mail.MessagingException; -import jakarta.mail.Multipart; -import jakarta.mail.Session; -import jakarta.mail.internet.ContentType; -import jakarta.mail.internet.MimeBodyPart; -import jakarta.mail.internet.MimeMessage; -import jakarta.mail.internet.MimeMultipart; -import jakarta.mail.internet.MimePart; +import javax.crypto.spec.OAEPParameterSpec; +import javax.crypto.spec.PSource; import java.io.IOException; import java.math.BigInteger; +import java.security.InvalidAlgorithmParameterException; import java.security.PrivateKey; import java.security.PublicKey; import java.security.Security; @@ -60,596 +46,656 @@ import java.security.cert.CertificateEncodingException; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Enumeration; -import java.util.Iterator; -import java.util.List; +import java.security.spec.MGF1ParameterSpec; +import java.util.*; /** * Utilities for handling S/MIME specific operations on MIME messages from * JavaMail. - * + * * @author Allen Petersen (akp at sourceforge dot net) * @author Torsten Krause (tk at markenwerk dot net) * @since 1.0.0 */ public final class SmimeUtil { - static { - if (null == Security.getProvider(BouncyCastleProvider.PROVIDER_NAME)) { - Security.addProvider(new BouncyCastleProvider()); - updateMailcapCommandMap(); - } - } - - @SuppressWarnings("unused") - private SmimeUtil() { - } - - private static void updateMailcapCommandMap() { - MailcapCommandMap map = (MailcapCommandMap) CommandMap.getDefaultCommandMap(); - map.addMailcap("application/pkcs7-signature;;x-java-content-handler=org.bouncycastle.mail.smime.handlers.pkcs7_signature"); - map.addMailcap("application/pkcs7-mime;;x-java-content-handler=org.bouncycastle.mail.smime.handlers.pkcs7_mime"); - map.addMailcap("application/x-pkcs7-signature;;x-java-content-handler=org.bouncycastle.mail.smime.handlers.x_pkcs7_signature"); - map.addMailcap("application/x-pkcs7-mime;;x-java-content-handler=org.bouncycastle.mail.smime.handlers.x_pkcs7_mime"); - map.addMailcap("multipart/signed;;x-java-content-handler=org.bouncycastle.mail.smime.handlers.multipart_signed"); - CommandMap.setDefaultCommandMap(map); - } - - /** - * Encrypts a MIME message and yields a new S/MIME encrypted MIME message. - * - * @param session - * The {@link Session} that is used in conjunction with the - * original {@link MimeMessage}. - * @param mimeMessage - * The original {@link MimeMessage} to be encrypted. - * @param certificate - * The {@link X509Certificate} used to obtain the - * {@link PublicKey} to encrypt the original message with. - * @return The new S/MIME encrypted {@link MimeMessage}. - */ - public static MimeMessage encrypt(Session session, MimeMessage mimeMessage, X509Certificate certificate) { - try { - MimeMessage encryptedMimeMessage = new MimeMessage(session); - copyHeaders(mimeMessage, encryptedMimeMessage); - - SMIMEEnvelopedGenerator generator = prepareGenerator(certificate); - OutputEncryptor encryptor = prepareEncryptor(); - - MimeBodyPart encryptedMimeBodyPart = generator.generate(mimeMessage, encryptor); - copyContent(encryptedMimeBodyPart, encryptedMimeMessage); - copyHeaders(encryptedMimeBodyPart, encryptedMimeMessage); - encryptedMimeMessage.saveChanges(); - return encryptedMimeMessage; - } catch (Exception e) { - throw handledException(e); - } - } - - /** - * Encrypts a MIME body part and yields a new S/MIME encrypted MIME body - * part. - * - * @param mimeBodyPart - * The original {@link MimeBodyPart} to be encrypted. - * @param certificate - * The {@link X509Certificate} used to obtain the - * {@link PublicKey} to encrypt the original body part with. - * @return The new S/MIME encrypted {@link MimeBodyPart}. - */ - public static MimeBodyPart encrypt(MimeBodyPart mimeBodyPart, X509Certificate certificate) { - try { - SMIMEEnvelopedGenerator generator = prepareGenerator(certificate); - OutputEncryptor encryptor = prepareEncryptor(); - - return generator.generate(mimeBodyPart, encryptor); - - } catch (Exception e) { - throw handledException(e); - } - } - - private static void copyHeaders(MimeBodyPart fromBodyPart, MimeMessage toMessage) throws MessagingException { - Enumeration

headers = fromBodyPart.getAllHeaders(); - copyHeaders(headers, toMessage); - } - - private static void copyHeaders(MimeMessage fromMessage, MimeMessage toMessage) throws MessagingException { - Enumeration
headers = fromMessage.getAllHeaders(); - copyHeaders(headers, toMessage); - } - - private static void copyHeaders(Enumeration
headers, MimeMessage toMessage) throws MessagingException { - while (headers.hasMoreElements()) { - Header header = headers.nextElement(); - toMessage.setHeader(header.getName(), header.getValue()); - } - } - - private static SMIMEEnvelopedGenerator prepareGenerator(X509Certificate certificate) - throws CertificateEncodingException { - JceKeyTransRecipientInfoGenerator infoGenerator = new JceKeyTransRecipientInfoGenerator(certificate); - infoGenerator.setProvider(BouncyCastleProvider.PROVIDER_NAME); - SMIMEEnvelopedGenerator generator = new SMIMEEnvelopedGenerator(); - generator.addRecipientInfoGenerator(infoGenerator); - return generator; - } - - private static OutputEncryptor prepareEncryptor() throws CMSException { - return new JceCMSContentEncryptorBuilder(CMSAlgorithm.DES_EDE3_CBC).setProvider( - BouncyCastleProvider.PROVIDER_NAME).build(); - } - - /** - * Decrypts an S/MIME encrypted MIME message and yields a new MIME message. - * - * @param session - * The {@link Session} that is used in conjunction with the - * encrypted {@link MimeMessage}. - * @param mimeMessage - * The encrypted {@link MimeMessage} to be decrypted. - * @param smimeKey - * The {@link SmimeKey} used to obtain the {@link PrivateKey} to - * decrypt the encrypted message with. - * @return The new S/MIME decrypted {@link MimeMessage}. - */ - public static MimeMessage decrypt(Session session, MimeMessage mimeMessage, SmimeKey smimeKey) { - try { - byte[] content = decryptContent(new SMIMEEnveloped(mimeMessage), smimeKey); - MimeBodyPart mimeBodyPart = SMIMEUtil.toMimeBodyPart(content); - - MimeMessage decryptedMessage = new MimeMessage(session); - copyHeaderLines(mimeMessage, decryptedMessage); - copyContent(mimeBodyPart, decryptedMessage); - decryptedMessage.setHeader("Content-Type", mimeBodyPart.getContentType()); - return decryptedMessage; - - } catch (Exception e) { - throw handledException(e); - } - } - - /** - * Decrypts an S/MIME encrypted MIME body part and yields a new MIME body - * part. - * - * @param mimeBodyPart - * The encrypted {@link MimeBodyPart} to be decrypted. - * @param smimeKey - * The {@link SmimeKey} used to obtain the {@link PrivateKey} to - * decrypt the encrypted body part with. - * @return The new S/MIME decrypted {@link MimeBodyPart}. - */ - public static MimeBodyPart decrypt(MimeBodyPart mimeBodyPart, SmimeKey smimeKey) { - try { - return SMIMEUtil.toMimeBodyPart(decryptContent(new SMIMEEnveloped(mimeBodyPart), smimeKey)); - } catch (Exception e) { - throw handledException(e); - } - - } - - /** - * Decrypts an S/MIME encrypted MIME multipart and yields a new MIME body - * part. - * - * @param mimeMultipart - * The encrypted {@link MimeMultipart} to be decrypted. - * @param smimeKey - * The {@link SmimeKey} used to obtain the {@link PrivateKey} to - * decrypt the encrypted multipart with. - * @return The new S/MIME decrypted {@link MimeBodyPart}. - */ - public static MimeBodyPart decrypt(MimeMultipart mimeMultipart, SmimeKey smimeKey) { - try { - MimeBodyPart mimeBodyPart = new MimeBodyPart(); - mimeBodyPart.setContent(mimeMultipart); - mimeBodyPart.setHeader("Content-Type", mimeMultipart.getContentType()); - return decrypt(mimeBodyPart, smimeKey); - } catch (Exception e) { - throw handledException(e); - } - } - - private static byte[] decryptContent(SMIMEEnveloped smimeEnveloped, SmimeKey smimeKey) throws MessagingException, - CMSException { - X509Certificate certificate = smimeKey.getCertificate(); - PrivateKey privateKey = smimeKey.getPrivateKey(); - - RecipientInformationStore recipients = smimeEnveloped.getRecipientInfos(); - RecipientInformation recipient = recipients.get(new JceKeyTransRecipientId(certificate)); - - if (null == recipient) { - throw new MessagingException("no recipient"); - } - - JceKeyTransRecipient transportRecipient = new JceKeyTransEnvelopedRecipient(privateKey); - transportRecipient.setProvider(BouncyCastleProvider.PROVIDER_NAME); - return recipient.getContent(transportRecipient); - } - - private static void copyHeaderLines(MimeMessage fromMessage, MimeMessage toMessage) throws MessagingException { - Enumeration headerLines = fromMessage.getAllHeaderLines(); - while (headerLines.hasMoreElements()) { - String nextElement = headerLines.nextElement(); - toMessage.addHeaderLine(nextElement); - } - } - - private static void copyContent(MimeBodyPart fromBodyPart, MimeMessage toMessage) throws MessagingException, - IOException { - toMessage.setContent(fromBodyPart.getContent(), fromBodyPart.getContentType()); - } - - /** - * Signs a MIME body part and yields a new S/MIME signed MIME body part. - * - * @param mimeBodyPart - * The original {@link MimeBodyPart} to be signed. - * @param smimeKey - * The {@link SmimeKey} used to obtain the {@link PrivateKey} to - * sign the original body part with. - * @return The new S/MIME signed {@link MimeBodyPart}. - */ - public static MimeBodyPart sign(MimeBodyPart mimeBodyPart, SmimeKey smimeKey) { - try { - SMIMESignedGenerator generator = getGenerator(smimeKey); - MimeMultipart signedMimeMultipart = generator.generate(MimeUtil.canonicalize(mimeBodyPart)); - MimeBodyPart signedMimeBodyPart = new MimeBodyPart(); - signedMimeBodyPart.setContent(signedMimeMultipart); - return signedMimeBodyPart; - - } catch (Exception e) { - throw handledException(e); - } - - } - - private static SMIMESignedGenerator getGenerator(SmimeKey smimeKey) throws CertificateEncodingException, - OperatorCreationException { - SMIMESignedGenerator generator = new SMIMESignedGenerator(); - generator.addCertificates(getCertificateStore(smimeKey)); - generator.addSignerInfoGenerator(getInfoGenerator(smimeKey)); - return generator; - } - - private static SignerInfoGenerator getInfoGenerator(SmimeKey smimeKey) throws OperatorCreationException, - CertificateEncodingException { - JcaSimpleSignerInfoGeneratorBuilder builder = new JcaSimpleSignerInfoGeneratorBuilder(); - builder.setSignedAttributeGenerator(new AttributeTable(getSignedAttributes(smimeKey))); - builder.setProvider(BouncyCastleProvider.PROVIDER_NAME); - - PrivateKey privateKey = smimeKey.getPrivateKey(); - X509Certificate certificate = smimeKey.getCertificate(); - return builder.build("SHA256withRSA", privateKey, certificate); - } - - private static ASN1EncodableVector getSignedAttributes(SmimeKey smimeKey) { - ASN1EncodableVector signedAttributes = new ASN1EncodableVector(); - IssuerAndSerialNumber issuerAndSerialNumber = getIssuerAndSerialNumber(smimeKey); - signedAttributes.add(new SMIMEEncryptionKeyPreferenceAttribute(issuerAndSerialNumber)); - signedAttributes.add(new SMIMECapabilitiesAttribute(getCapabilityVector())); - return signedAttributes; - } - - private static SMIMECapabilityVector getCapabilityVector() { - SMIMECapabilityVector capabilityVector = new SMIMECapabilityVector(); - capabilityVector.addCapability(SMIMECapability.dES_EDE3_CBC); - capabilityVector.addCapability(SMIMECapability.rC2_CBC, 128); - capabilityVector.addCapability(SMIMECapability.dES_CBC); - return capabilityVector; - } - - private static IssuerAndSerialNumber getIssuerAndSerialNumber(SmimeKey smimeKey) { - X509Certificate certificate = smimeKey.getCertificate(); - BigInteger serialNumber = certificate.getSerialNumber(); - X500Name issuerName = new X500Name(certificate.getIssuerDN().getName()); - return new IssuerAndSerialNumber(issuerName, serialNumber); - } - - private static JcaCertStore getCertificateStore(SmimeKey smimeKey) throws CertificateEncodingException { - Certificate[] certificateChain = smimeKey.getCertificateChain(); - X509Certificate certificate = smimeKey.getCertificate(); - - final List certificateList; - if (certificateChain != null && certificateChain.length > 0) { - certificateList = Arrays.asList(certificateChain); - } else { - certificateList = new ArrayList<>(); - certificateList.add(certificate); - } - return new JcaCertStore(certificateList); - } - - /** - * Signs a MIME message and yields a new S/MIME signed MIME message. - * - * @param session - * The {@link Session} that is used in conjunction with the - * original {@link MimeMessage}. - * @param mimeMessage - * The original {@link MimeMessage} or {@link SMTPMessage} to be signed. - * @param smimeKey - * The {@link SmimeKey} used to obtain the {@link PrivateKey} to - * sign the original message with. - * @return The new S/MIME signed {@link MimeMessage} or {@link SMTPMessage}. - */ - public static T sign(Session session, T mimeMessage, SmimeKey smimeKey) { - //noinspection unchecked - return (mimeMessage instanceof SMTPMessage) - ? sign(mimeMessage, (T) new SMTPMessage(session), smimeKey) - : sign(mimeMessage, (T) new MimeMessage(session), smimeKey); - } - - private static T sign(T mimeMessage, T signedMessage, SmimeKey smimeKey) { - try { - copyHeaderLines(mimeMessage, signedMessage); - copyContent(sign(extractMimeBodyPart(mimeMessage), smimeKey), signedMessage); - return signedMessage; - } catch (Exception e) { - throw handledException(e); - } - } - - private static MimeBodyPart extractMimeBodyPart(MimeMessage mimeMessage) throws IOException, MessagingException { - Object content = mimeMessage.getContent(); - UpdatableMimeBodyPart updateableMimeBodyPart = new UpdatableMimeBodyPart(); - if (content instanceof Multipart) { - updateableMimeBodyPart.setContent((Multipart) content); - } else { - updateableMimeBodyPart.setContent(content, mimeMessage.getDataHandler().getContentType()); - } - updateableMimeBodyPart.updateHeaders(); - return updateableMimeBodyPart; - } - - /** - * Checks the signature on an S/MIME signed MIME multipart. - * - * @param mimeMultipart - * The {@link MimeMultipart} to be checked. - * @return {@code true} if the multipart is correctly signed, {@code false} - * otherwise. - */ - public static boolean checkSignature(MimeMultipart mimeMultipart) { - try { - return checkSignature(new SMIMESigned(mimeMultipart)); - } catch (Exception e) { - throw handledException(e); - } - } - - /** - * Checks the signature on an S/MIME signed MIME part (i.e. MIME message). - * - * @param mimePart - * The {@link MimePart} to be checked. - * @return {@code true} if the part is correctly signed, {@code false} - * otherwise. - */ - public static boolean checkSignature(MimePart mimePart) { - try { - if (mimePart.isMimeType("multipart/signed")) { - return checkSignature(new SMIMESigned((MimeMultipart) mimePart.getContent())); - } else if (mimePart.isMimeType("application/pkcs7-mime") || mimePart.isMimeType("application/x-pkcs7-mime")) { - return checkSignature(new SMIMESigned(mimePart)); - } else { - throw new SmimeException("Message not signed"); - } - } catch (Exception e) { - throw handledException(e); - } - } - - /** - * Checks a SMIMESigned to make sure that the signature matches. - */ - private static boolean checkSignature(SMIMESigned smimeSigned) { - try { - boolean returnValue = true; - - @SuppressWarnings("rawtypes") - Store certificates = smimeSigned.getCertificates(); - Iterator signerInformations = smimeSigned.getSignerInfos().getSigners().iterator(); - - while (returnValue && signerInformations.hasNext()) { - SignerInformation signerInformation = signerInformations.next(); - X509Certificate certificate = getCertificate(certificates, signerInformation.getSID()); - SignerInformationVerifier verifier = getVerifier(certificate); - if (!signerInformation.verify(verifier)) { - returnValue = false; - } - } - return returnValue; - - } catch (Exception e) { - throw handledException(e); - } - } - - /** - * @param mimeMultipart - * The {@link MimeMultipart} to be checked. - * @return The subject / address to which the certificate was issued to. Email clients may use this to show - * {@code "Signed by: "} - */ - public static String getSignedByAddress(MimeMultipart mimeMultipart) { - try { - return getSignedByAddress(new SMIMESigned(mimeMultipart)); - } catch (Exception e) { - throw handledException(e); - } - } - - /** - * @param mimePart - * The {@link MimePart} to be checked. - * @return The subject / address to which the certificate was issued to. Email clients may use this to show - * {@code "Signed by: "} - */ - public static String getSignedByAddress(MimePart mimePart) { - try { - if (mimePart.isMimeType("multipart/signed")) { - return getSignedByAddress(new SMIMESigned((MimeMultipart) mimePart.getContent())); - } else if (mimePart.isMimeType("application/pkcs7-mime") || mimePart.isMimeType("application/x-pkcs7-mime")) { - return getSignedByAddress(new SMIMESigned(mimePart)); - } else { - throw new SmimeException("Message not signed"); - } - } catch (Exception e) { - throw handledException(e); - } - } - - /** - * Returns the subject / address to which the certificate was issued to. Email clients may use this to show - * {@code "Signed by: "} - */ - private static String getSignedByAddress(SMIMESigned smimeSigned) { - try { - @SuppressWarnings("rawtypes") - Store certificates = smimeSigned.getCertificates(); - - SignerInformation signerInformation = smimeSigned.getSignerInfos().getSigners().iterator().next(); - X509Certificate certificate = getCertificate(certificates, signerInformation.getSID()); - SignerInformationVerifier verifier = getVerifier(certificate); - X500Name x500name = verifier.getAssociatedCertificate().getSubject(); - RDN cn = x500name.getRDNs(BCStyle.CN)[0]; - return IETFUtils.valueToString(cn.getFirst().getValue()); - - } catch (Exception e) { - throw handledException(e); - } - } - - private static X509Certificate getCertificate(@SuppressWarnings("rawtypes") Store certificates, SignerId signerId) - throws CertificateException { - @SuppressWarnings({ "unchecked" }) - X509CertificateHolder certificateHolder = (X509CertificateHolder) certificates.getMatches(signerId).iterator() - .next(); - JcaX509CertificateConverter certificateConverter = new JcaX509CertificateConverter(); - certificateConverter.setProvider(BouncyCastleProvider.PROVIDER_NAME); - return certificateConverter.getCertificate(certificateHolder); - } - - private static SignerInformationVerifier getVerifier(X509Certificate certificate) throws OperatorCreationException { - JcaSimpleSignerInfoVerifierBuilder builder = new JcaSimpleSignerInfoVerifierBuilder(); - builder.setProvider(BouncyCastleProvider.PROVIDER_NAME); - return builder.build(certificate); - } - - /** - * Returns the signed MIME body part of an S/MIME signed MIME multipart. - * - * @param mimeMultipart - * The {@link MimeMultipart} to be stripped off. - * @return The signed {@link MimeBodyPart} contained in the - * {@link MimeMultipart}. - */ - public static MimeBodyPart getSignedContent(MimeMultipart mimeMultipart) { - try { - return new SMIMESigned(mimeMultipart).getContent(); - } catch (Exception e) { - throw handledException(e); - } - } - - /** - * Returns the signed MIME body part of an S/MIME signed MIME part (i.e. MIME - * message). - * - * @param mimePart - * The {@link MimePart} to be stripped off. - * @return The signed {@link MimeBodyPart} contained in the {@link MimePart} - * . - */ - public static MimeBodyPart getSignedContent(MimePart mimePart) { - try { - if (mimePart.isMimeType("multipart/signed")) { - return new SMIMESigned((MimeMultipart) mimePart.getContent()).getContent(); - } else if (mimePart.isMimeType("application/pkcs7-mime") || mimePart.isMimeType("application/x-pkcs7-mime")) { - return new SMIMESigned(mimePart).getContent(); - } else { - throw new SmimeException("Message not signed"); - } - } catch (Exception e) { - throw handledException(e); - } - } - - /** - * Returns the S/MIME state of a MIME multipart. - * - * @param mimeMultipart - * The {@link MimeMultipart} to be checked. - * @return the {@link SmimeState} of the {@link MimeMultipart}. - */ - public static SmimeState getStatus(MimeMultipart mimeMultipart) { - try { - return getStatus(new ContentType(mimeMultipart.getContentType())); - } catch (Exception e) { - throw handledException(e); - } - } - - /** - * Returns the S/MIME state of a MIME part (i.e. MIME message). - * - * @param mimePart - * The {@link MimePart} to be checked. - * @return the {@link SmimeState} of the {@link MimePart}. - */ - public static SmimeState getStatus(MimePart mimePart) { - try { - return getStatus(new ContentType(mimePart.getContentType())); - } catch (Exception e) { - throw handledException(e); - } - } - - private static SmimeState getStatus(ContentType contentType) { - if (isSmimeSignatureContentType(contentType)) { - return SmimeState.SIGNED; - } else if (isSignatureSmimeType(contentType)) { - return SmimeState.SIGNED_ENVELOPED; - } else if (isSmimeEncryptionContenttype(contentType)) { - return SmimeState.ENCRYPTED; - } else { - return SmimeState.NEITHER; - } - } - - private static boolean isSmimeEncryptionContenttype(ContentType contentType) { - String baseContentType = contentType.getBaseType(); - return baseContentType.equalsIgnoreCase("application/pkcs7-mime") - || baseContentType.equalsIgnoreCase("application/x-pkcs7-mime"); - } - - private static boolean isSmimeSignatureContentType(ContentType contentType) { - String baseContentType = contentType.getBaseType(); - String protocol = contentType.getParameter("protocol"); - return baseContentType.equalsIgnoreCase("multipart/signed") - && protocol != null && isSmimeSignatureProtocoll(protocol); - } - - private static boolean isSignatureSmimeType(ContentType contentType) { - String baseContentType = contentType.getBaseType(); - return baseContentType.equalsIgnoreCase("application/x-pkcs7-mime") - && "signed-data".equals(contentType.getParameter("smime-type")); - } - - private static boolean isSmimeSignatureProtocoll(String protocol) { - return protocol.equalsIgnoreCase("application/pkcs7-signature") - || protocol.equalsIgnoreCase("application/x-pkcs7-signature"); - } - - private static SmimeException handledException(Exception e) { - if (e instanceof SmimeException) { - return (SmimeException) e; - } - return new SmimeException(e.getMessage(), e); - } + private static final String defaultSignatureAlgorithmName = "SHA256withRSA"; + private static final KeyEncapsulationAlgorithm defaultKeyEncapsulationAlgorithm = KeyEncapsulationAlgorithm.RSA; + private static final ASN1ObjectIdentifier defaultCipher = CMSAlgorithm.DES_EDE3_CBC; + + static { + if (null == Security.getProvider(BouncyCastleProvider.PROVIDER_NAME)) { + Security.addProvider(new BouncyCastleProvider()); + updateMailcapCommandMap(); + } + } + + @SuppressWarnings("unused") + private SmimeUtil() { + } + + private static void updateMailcapCommandMap() { + MailcapCommandMap map = (MailcapCommandMap) CommandMap.getDefaultCommandMap(); + map.addMailcap("application/pkcs7-signature;;x-java-content-handler=org.bouncycastle.mail.smime.handlers.pkcs7_signature"); + map.addMailcap("application/pkcs7-mime;;x-java-content-handler=org.bouncycastle.mail.smime.handlers.pkcs7_mime"); + map.addMailcap("application/x-pkcs7-signature;;x-java-content-handler=org.bouncycastle.mail.smime.handlers.x_pkcs7_signature"); + map.addMailcap("application/x-pkcs7-mime;;x-java-content-handler=org.bouncycastle.mail.smime.handlers.x_pkcs7_mime"); + map.addMailcap("multipart/signed;;x-java-content-handler=org.bouncycastle.mail.smime.handlers.multipart_signed"); + CommandMap.setDefaultCommandMap(map); + } + + /** + * Encrypts a MIME message and yields a new S/MIME encrypted MIME message. + * + * @param session The {@link Session} that is used in conjunction with the + * original {@link MimeMessage}. + * @param mimeMessage The original {@link MimeMessage} to be encrypted. + * @param certificate The {@link X509Certificate} used to obtain the + * {@link PublicKey} to encrypt the original message with. + * @return The new S/MIME encrypted {@link MimeMessage}. + */ + public static MimeMessage encrypt(Session session, MimeMessage mimeMessage, X509Certificate certificate) { + return encrypt(session, mimeMessage, certificate, defaultKeyEncapsulationAlgorithm, defaultCipher); + } + + /** + * Encrypts a MIME message and yields a new S/MIME encrypted MIME message. + * + * @param session The {@link Session} that is used in conjunction with the + * original {@link MimeMessage}. + * @param mimeMessage The original {@link MimeMessage} to be encrypted. + * @param certificate The {@link X509Certificate} used to obtain the + * {@link PublicKey} to encrypt the original message with. + * @param keyEncapsulationAlgorithm Algorithm used to encapsulate the symmetric encryption key. + * Currently, RSA RSA-OAEP with various SHA digest lengths are supported. + * @param cmsAlgorithm Encryption algorithm for symmetric content encryption. + * @return The new S/MIME encrypted {@link MimeMessage}. + */ + public static MimeMessage encrypt(Session session, MimeMessage mimeMessage, X509Certificate certificate, KeyEncapsulationAlgorithm keyEncapsulationAlgorithm, ASN1ObjectIdentifier cmsAlgorithm) { + try { + MimeMessage encryptedMimeMessage = new MimeMessage(session); + copyHeaders(mimeMessage, encryptedMimeMessage); + + SMIMEEnvelopedGenerator generator = prepareGenerator(certificate, keyEncapsulationAlgorithm); + OutputEncryptor encryptor = prepareEncryptor(cmsAlgorithm); + + MimeBodyPart encryptedMimeBodyPart = generator.generate(mimeMessage, encryptor); + copyContent(encryptedMimeBodyPart, encryptedMimeMessage); + copyHeaders(encryptedMimeBodyPart, encryptedMimeMessage); + encryptedMimeMessage.saveChanges(); + return encryptedMimeMessage; + } catch (Exception e) { + throw handledException(e); + } + } + + /** + * Encrypts a MIME body part and yields a new S/MIME encrypted MIME body + * part. + * + * @param mimeBodyPart The original {@link MimeBodyPart} to be encrypted. + * @param certificate The {@link X509Certificate} used to obtain the + * {@link PublicKey} to encrypt the original body part with. + * @return The new S/MIME encrypted {@link MimeBodyPart}. + */ + public static MimeBodyPart encrypt(MimeBodyPart mimeBodyPart, X509Certificate certificate) { + return encrypt(mimeBodyPart, certificate, defaultKeyEncapsulationAlgorithm, defaultCipher); + } + + /** + * Encrypts a MIME body part and yields a new S/MIME encrypted MIME body + * part. + * + * @param mimeBodyPart The original {@link MimeBodyPart} to be encrypted. + * @param certificate The {@link X509Certificate} used to obtain the + * {@link PublicKey} to encrypt the original body part with. + * @param keyEncapsulationAlgorithm Algorithm used to encapsulate the symmetric encryption key. + * Currently, RSA RSA-OAEP with various SHA digest lengths are supported. + * @param cmsAlgorithm Encryption algorithm for symmetric content encryption. + * @return The new S/MIME encrypted {@link MimeBodyPart}. + */ + public static MimeBodyPart encrypt(MimeBodyPart mimeBodyPart, + X509Certificate certificate, + KeyEncapsulationAlgorithm keyEncapsulationAlgorithm, + ASN1ObjectIdentifier cmsAlgorithm) { + try { + SMIMEEnvelopedGenerator generator = prepareGenerator(certificate, keyEncapsulationAlgorithm); + OutputEncryptor encryptor = prepareEncryptor(cmsAlgorithm); + + return generator.generate(mimeBodyPart, encryptor); + + } catch (Exception e) { + throw handledException(e); + } + } + + private static void copyHeaders(MimeBodyPart fromBodyPart, MimeMessage toMessage) throws MessagingException { + Enumeration
headers = fromBodyPart.getAllHeaders(); + copyHeaders(headers, toMessage); + } + + private static void copyHeaders(MimeMessage fromMessage, MimeMessage toMessage) throws MessagingException { + Enumeration
headers = fromMessage.getAllHeaders(); + copyHeaders(headers, toMessage); + } + + private static void copyHeaders(Enumeration
headers, MimeMessage toMessage) throws MessagingException { + while (headers.hasMoreElements()) { + Header header = headers.nextElement(); + toMessage.setHeader(header.getName(), header.getValue()); + } + } + + private static SMIMEEnvelopedGenerator prepareGenerator(X509Certificate certificate, + KeyEncapsulationAlgorithm keyEncapsulationAlgorithm) + throws CertificateEncodingException, InvalidAlgorithmParameterException { + JceKeyTransRecipientInfoGenerator infoGenerator; + if (keyEncapsulationAlgorithm == KeyEncapsulationAlgorithm.RSA) { + infoGenerator = new JceKeyTransRecipientInfoGenerator(certificate); + } else { + String digestName; + if (keyEncapsulationAlgorithm == KeyEncapsulationAlgorithm.RSA_OAEP_SHA224) { + digestName = "SHA-234"; + } else if (keyEncapsulationAlgorithm == KeyEncapsulationAlgorithm.RSA_OAEP_SHA256) { + digestName = "SHA-256"; + } else if (keyEncapsulationAlgorithm == KeyEncapsulationAlgorithm.RSA_OAEP_SHA384) { + digestName = "SHA-384"; + } else if (keyEncapsulationAlgorithm == KeyEncapsulationAlgorithm.RSA_OAEP_SHA512) { + digestName = "SHA-512"; + } else { + throw new InvalidAlgorithmParameterException("Unknown S/MIME key encapsulation algorithm: " + + keyEncapsulationAlgorithm.name()); + } + JcaAlgorithmParametersConverter paramsConverter = new JcaAlgorithmParametersConverter(); + AlgorithmIdentifier oaepParams = paramsConverter.getAlgorithmIdentifier( + PKCSObjectIdentifiers.id_RSAES_OAEP, new OAEPParameterSpec( + digestName, "MGF1", new MGF1ParameterSpec(digestName), PSource.PSpecified.DEFAULT)); + infoGenerator = new JceKeyTransRecipientInfoGenerator(certificate, oaepParams); + } + infoGenerator.setProvider(BouncyCastleProvider.PROVIDER_NAME); + SMIMEEnvelopedGenerator generator = new SMIMEEnvelopedGenerator(); + generator.addRecipientInfoGenerator(infoGenerator); + return generator; + } + + private static OutputEncryptor prepareEncryptor(ASN1ObjectIdentifier cmsAlgorithm) throws CMSException { + return new JceCMSContentEncryptorBuilder(cmsAlgorithm).setProvider(BouncyCastleProvider.PROVIDER_NAME).build(); + } + + /** + * Decrypts an S/MIME encrypted MIME message and yields a new MIME message. + * + * @param session The {@link Session} that is used in conjunction with the + * encrypted {@link MimeMessage}. + * @param mimeMessage The encrypted {@link MimeMessage} to be decrypted. + * @param smimeKey The {@link SmimeKey} used to obtain the {@link PrivateKey} to + * decrypt the encrypted message with. + * @return The new S/MIME decrypted {@link MimeMessage}. + */ + public static MimeMessage decrypt(Session session, MimeMessage mimeMessage, SmimeKey smimeKey) { + try { + byte[] content = decryptContent(new SMIMEEnveloped(mimeMessage), smimeKey); + MimeBodyPart mimeBodyPart = SMIMEUtil.toMimeBodyPart(content); + + MimeMessage decryptedMessage = new MimeMessage(session); + copyHeaderLines(mimeMessage, decryptedMessage); + copyContent(mimeBodyPart, decryptedMessage); + decryptedMessage.setHeader("Content-Type", mimeBodyPart.getContentType()); + return decryptedMessage; + + } catch (Exception e) { + throw handledException(e); + } + } + + /** + * Decrypts an S/MIME encrypted MIME body part and yields a new MIME body + * part. + * + * @param mimeBodyPart The encrypted {@link MimeBodyPart} to be decrypted. + * @param smimeKey The {@link SmimeKey} used to obtain the {@link PrivateKey} to + * decrypt the encrypted body part with. + * @return The new S/MIME decrypted {@link MimeBodyPart}. + */ + public static MimeBodyPart decrypt(MimeBodyPart mimeBodyPart, SmimeKey smimeKey) { + try { + return SMIMEUtil.toMimeBodyPart(decryptContent(new SMIMEEnveloped(mimeBodyPart), smimeKey)); + } catch (Exception e) { + throw handledException(e); + } + } + + /** + * Decrypts an S/MIME encrypted MIME multipart and yields a new MIME body + * part. + * + * @param mimeMultipart The encrypted {@link MimeMultipart} to be decrypted. + * @param smimeKey The {@link SmimeKey} used to obtain the {@link PrivateKey} to + * decrypt the encrypted multipart with. + * @return The new S/MIME decrypted {@link MimeBodyPart}. + */ + public static MimeBodyPart decrypt(MimeMultipart mimeMultipart, SmimeKey smimeKey) { + try { + MimeBodyPart mimeBodyPart = new MimeBodyPart(); + mimeBodyPart.setContent(mimeMultipart); + mimeBodyPart.setHeader("Content-Type", mimeMultipart.getContentType()); + return decrypt(mimeBodyPart, smimeKey); + } catch (Exception e) { + throw handledException(e); + } + } + + private static byte[] decryptContent(SMIMEEnveloped smimeEnveloped, SmimeKey smimeKey) throws MessagingException, CMSException { + X509Certificate certificate = smimeKey.getCertificate(); + PrivateKey privateKey = smimeKey.getPrivateKey(); + + RecipientInformationStore recipients = smimeEnveloped.getRecipientInfos(); + RecipientInformation recipient = recipients.get(new JceKeyTransRecipientId(certificate)); + + if (null == recipient) { + throw new MessagingException("no recipient"); + } + + JceKeyTransRecipient transportRecipient = new JceKeyTransEnvelopedRecipient(privateKey); + transportRecipient.setProvider(BouncyCastleProvider.PROVIDER_NAME); + return recipient.getContent(transportRecipient); + } + + private static void copyHeaderLines(MimeMessage fromMessage, MimeMessage toMessage) throws MessagingException { + Enumeration headerLines = fromMessage.getAllHeaderLines(); + while (headerLines.hasMoreElements()) { + String nextElement = headerLines.nextElement(); + toMessage.addHeaderLine(nextElement); + } + } + + private static void copyContent(MimeBodyPart fromBodyPart, MimeMessage toMessage) throws MessagingException, IOException { + toMessage.setContent(fromBodyPart.getContent(), fromBodyPart.getContentType()); + } + + /** + * Signs a MIME body part and yields a new S/MIME signed MIME body part. + * + * @param mimeBodyPart The original {@link MimeBodyPart} to be signed. + * @param smimeKey The {@link SmimeKey} used to obtain the {@link PrivateKey} to + * sign the original body part with. + * @return The new S/MIME signed {@link MimeBodyPart}. + */ + public static MimeBodyPart sign(MimeBodyPart mimeBodyPart, SmimeKey smimeKey) { + return sign(mimeBodyPart, smimeKey, defaultSignatureAlgorithmName); + } + + /** + * Signs a MIME body part and yields a new S/MIME signed MIME body part. + * + * @param mimeBodyPart The original {@link MimeBodyPart} to be signed. + * @param smimeKey The {@link SmimeKey} used to obtain the {@link PrivateKey} to + * sign the original body part with. + * @param algorithmName The name of the signature algorithm to use. Must be an algorithm + * supported by the Bouncy Castle security provider. + * @return The new S/MIME signed {@link MimeBodyPart}. + */ + public static MimeBodyPart sign(MimeBodyPart mimeBodyPart, SmimeKey smimeKey, String algorithmName) { + try { + SMIMESignedGenerator generator = getGenerator(smimeKey, algorithmName); + MimeMultipart signedMimeMultipart = generator.generate(MimeUtil.canonicalize(mimeBodyPart)); + MimeBodyPart signedMimeBodyPart = new MimeBodyPart(); + signedMimeBodyPart.setContent(signedMimeMultipart); + return signedMimeBodyPart; + + } catch (Exception e) { + throw handledException(e); + } + + } + + private static SMIMESignedGenerator getGenerator(SmimeKey smimeKey, String algorithmName) + throws CertificateEncodingException, OperatorCreationException { + SMIMESignedGenerator generator = new SMIMESignedGenerator(); + generator.addCertificates(getCertificateStore(smimeKey)); + generator.addSignerInfoGenerator(getInfoGenerator(smimeKey, algorithmName)); + return generator; + } + + private static SignerInfoGenerator getInfoGenerator(SmimeKey smimeKey, String algorithmName) + throws OperatorCreationException, CertificateEncodingException { + JcaSimpleSignerInfoGeneratorBuilder builder = new JcaSimpleSignerInfoGeneratorBuilder(); + builder.setSignedAttributeGenerator(new AttributeTable(getSignedAttributes(smimeKey))); + builder.setProvider(BouncyCastleProvider.PROVIDER_NAME); + + PrivateKey privateKey = smimeKey.getPrivateKey(); + X509Certificate certificate = smimeKey.getCertificate(); + return builder.build(algorithmName, privateKey, certificate); + } + + private static ASN1EncodableVector getSignedAttributes(SmimeKey smimeKey) { + ASN1EncodableVector signedAttributes = new ASN1EncodableVector(); + IssuerAndSerialNumber issuerAndSerialNumber = getIssuerAndSerialNumber(smimeKey); + signedAttributes.add(new SMIMEEncryptionKeyPreferenceAttribute(issuerAndSerialNumber)); + signedAttributes.add(new SMIMECapabilitiesAttribute(getCapabilityVector())); + return signedAttributes; + } + + private static SMIMECapabilityVector getCapabilityVector() { + SMIMECapabilityVector capabilityVector = new SMIMECapabilityVector(); + capabilityVector.addCapability(SMIMECapability.dES_EDE3_CBC); + capabilityVector.addCapability(SMIMECapability.rC2_CBC, 128); + capabilityVector.addCapability(SMIMECapability.dES_CBC); + return capabilityVector; + } + + private static IssuerAndSerialNumber getIssuerAndSerialNumber(SmimeKey smimeKey) { + X509Certificate certificate = smimeKey.getCertificate(); + BigInteger serialNumber = certificate.getSerialNumber(); + X500Name issuerName = new X500Name(certificate.getIssuerX500Principal().getName()); + return new IssuerAndSerialNumber(issuerName, serialNumber); + } + + private static JcaCertStore getCertificateStore(SmimeKey smimeKey) throws CertificateEncodingException { + Certificate[] certificateChain = smimeKey.getCertificateChain(); + X509Certificate certificate = smimeKey.getCertificate(); + + final List certificateList; + if (certificateChain != null && certificateChain.length > 0) { + certificateList = Arrays.asList(certificateChain); + } else { + certificateList = new ArrayList<>(); + certificateList.add(certificate); + } + return new JcaCertStore(certificateList); + } + + /** + * Signs a MIME message and yields a new S/MIME signed MIME message. + * + * @param session The {@link Session} that is used in conjunction with the + * original {@link MimeMessage}. + * @param mimeMessage The original {@link MimeMessage} or {@link SMTPMessage} to be signed. + * @param smimeKey The {@link SmimeKey} used to obtain the {@link PrivateKey} to + * sign the original message with. + * @return The new S/MIME signed {@link MimeMessage} or {@link SMTPMessage}. + */ + public static T sign(Session session, T mimeMessage, SmimeKey smimeKey) { + return sign(session, mimeMessage, smimeKey, defaultSignatureAlgorithmName); + } + + /** + * Signs a MIME message and yields a new S/MIME signed MIME message. + * + * @param session The {@link Session} that is used in conjunction with the + * original {@link MimeMessage}. + * @param mimeMessage The original {@link MimeMessage} or {@link SMTPMessage} to be signed. + * @param smimeKey The {@link SmimeKey} used to obtain the {@link PrivateKey} to + * sign the original message with. + * @param algorithmName The name of the signature algorithm to use. Must be an algorithm + * supported by the Bouncy Castle security provider. + * @return The new S/MIME signed {@link MimeMessage} or {@link SMTPMessage}. + */ + public static T sign(Session session, T mimeMessage, SmimeKey smimeKey, String algorithmName) { + //noinspection unchecked + return (mimeMessage instanceof SMTPMessage) + ? sign(mimeMessage, (T) new SMTPMessage(session), smimeKey, algorithmName) + : sign(mimeMessage, (T) new MimeMessage(session), smimeKey, algorithmName); + } + + private static T sign(T mimeMessage, T signedMessage, SmimeKey smimeKey, String algorithmName) { + try { + copyHeaderLines(mimeMessage, signedMessage); + copyContent(sign(extractMimeBodyPart(mimeMessage), smimeKey, algorithmName), signedMessage); + return signedMessage; + } catch (Exception e) { + throw handledException(e); + } + } + + private static MimeBodyPart extractMimeBodyPart(MimeMessage mimeMessage) throws IOException, MessagingException { + Object content = mimeMessage.getContent(); + UpdatableMimeBodyPart updateableMimeBodyPart = new UpdatableMimeBodyPart(); + if (content instanceof Multipart) { + updateableMimeBodyPart.setContent((Multipart) content); + } else { + updateableMimeBodyPart.setContent(content, mimeMessage.getDataHandler().getContentType()); + } + updateableMimeBodyPart.updateHeaders(); + return updateableMimeBodyPart; + } + + /** + * Checks the signature on an S/MIME signed MIME multipart. + * + * @param mimeMultipart The {@link MimeMultipart} to be checked. + * @return {@code true} if the multipart is correctly signed, {@code false} + * otherwise. + */ + public static boolean checkSignature(MimeMultipart mimeMultipart) { + try { + return checkSignature(new SMIMESigned(mimeMultipart)); + } catch (Exception e) { + throw handledException(e); + } + } + + /** + * Checks the signature on an S/MIME signed MIME part (i.e. MIME message). + * + * @param mimePart The {@link MimePart} to be checked. + * @return {@code true} if the part is correctly signed, {@code false} + * otherwise. + */ + public static boolean checkSignature(MimePart mimePart) { + try { + if (mimePart.isMimeType("multipart/signed")) { + return checkSignature(new SMIMESigned((MimeMultipart) mimePart.getContent())); + } else if (mimePart.isMimeType("application/pkcs7-mime") || mimePart.isMimeType("application/x-pkcs7-mime")) { + return checkSignature(new SMIMESigned(mimePart)); + } else { + throw new SmimeException("Message not signed"); + } + } catch (Exception e) { + throw handledException(e); + } + } + + /** + * Checks a SMIMESigned to make sure that the signature matches. + */ + private static boolean checkSignature(SMIMESigned smimeSigned) { + try { + boolean returnValue = true; + + @SuppressWarnings("rawtypes") + Store certificates = smimeSigned.getCertificates(); + Iterator signerInformations = smimeSigned.getSignerInfos().getSigners().iterator(); + + while (returnValue && signerInformations.hasNext()) { + SignerInformation signerInformation = signerInformations.next(); + X509Certificate certificate = getCertificate(certificates, signerInformation.getSID()); + SignerInformationVerifier verifier = getVerifier(certificate); + if (!signerInformation.verify(verifier)) { + returnValue = false; + } + } + return returnValue; + + } catch (Exception e) { + throw handledException(e); + } + } + + /** + * @param mimeMultipart The {@link MimeMultipart} to be checked. + * @return The subject / address to which the certificate was issued to. Email clients may use this to show + * {@code "Signed by: "} + */ + public static String getSignedByAddress(MimeMultipart mimeMultipart) { + try { + return getSignedByAddress(new SMIMESigned(mimeMultipart)); + } catch (Exception e) { + throw handledException(e); + } + } + + /** + * @param mimePart The {@link MimePart} to be checked. + * @return The subject / address to which the certificate was issued to. Email clients may use this to show + * {@code "Signed by: "} + */ + public static String getSignedByAddress(MimePart mimePart) { + try { + if (mimePart.isMimeType("multipart/signed")) { + return getSignedByAddress(new SMIMESigned((MimeMultipart) mimePart.getContent())); + } else if (mimePart.isMimeType("application/pkcs7-mime") || mimePart.isMimeType("application/x-pkcs7-mime")) { + return getSignedByAddress(new SMIMESigned(mimePart)); + } else { + throw new SmimeException("Message not signed"); + } + } catch (Exception e) { + throw handledException(e); + } + } + + /** + * Returns the subject / address to which the certificate was issued to. Email clients may use this to show + * {@code "Signed by: "} + */ + private static String getSignedByAddress(SMIMESigned smimeSigned) { + try { + @SuppressWarnings("rawtypes") + Store certificates = smimeSigned.getCertificates(); + + SignerInformation signerInformation = smimeSigned.getSignerInfos().getSigners().iterator().next(); + X509Certificate certificate = getCertificate(certificates, signerInformation.getSID()); + SignerInformationVerifier verifier = getVerifier(certificate); + X500Name x500name = verifier.getAssociatedCertificate().getSubject(); + RDN cn = x500name.getRDNs(BCStyle.CN)[0]; + return IETFUtils.valueToString(cn.getFirst().getValue()); + + } catch (Exception e) { + throw handledException(e); + } + } + + private static X509Certificate getCertificate(@SuppressWarnings("rawtypes") Store certificates, + SignerId signerId) throws CertificateException { + @SuppressWarnings({"unchecked"}) + X509CertificateHolder certificateHolder = (X509CertificateHolder) certificates.getMatches(signerId).iterator().next(); + JcaX509CertificateConverter certificateConverter = new JcaX509CertificateConverter(); + certificateConverter.setProvider(BouncyCastleProvider.PROVIDER_NAME); + return certificateConverter.getCertificate(certificateHolder); + } + + private static SignerInformationVerifier getVerifier(X509Certificate certificate) throws OperatorCreationException { + JcaSimpleSignerInfoVerifierBuilder builder = new JcaSimpleSignerInfoVerifierBuilder(); + builder.setProvider(BouncyCastleProvider.PROVIDER_NAME); + return builder.build(certificate); + } + + /** + * Returns the signed MIME body part of an S/MIME signed MIME multipart. + * + * @param mimeMultipart The {@link MimeMultipart} to be stripped off. + * @return The signed {@link MimeBodyPart} contained in the + * {@link MimeMultipart}. + */ + public static MimeBodyPart getSignedContent(MimeMultipart mimeMultipart) { + try { + return new SMIMESigned(mimeMultipart).getContent(); + } catch (Exception e) { + throw handledException(e); + } + } + + /** + * Returns the signed MIME body part of an S/MIME signed MIME part (i.e. MIME + * message). + * + * @param mimePart The {@link MimePart} to be stripped off. + * @return The signed {@link MimeBodyPart} contained in the {@link MimePart} + * . + */ + public static MimeBodyPart getSignedContent(MimePart mimePart) { + try { + if (mimePart.isMimeType("multipart/signed")) { + return new SMIMESigned((MimeMultipart) mimePart.getContent()).getContent(); + } else if (mimePart.isMimeType("application/pkcs7-mime") || mimePart.isMimeType("application/x-pkcs7-mime")) { + return new SMIMESigned(mimePart).getContent(); + } else { + throw new SmimeException("Message not signed"); + } + } catch (Exception e) { + throw handledException(e); + } + } + + /** + * Returns the S/MIME state of a MIME multipart. + * + * @param mimeMultipart The {@link MimeMultipart} to be checked. + * @return the {@link SmimeState} of the {@link MimeMultipart}. + */ + public static SmimeState getStatus(MimeMultipart mimeMultipart) { + try { + return getStatus(new ContentType(mimeMultipart.getContentType())); + } catch (Exception e) { + throw handledException(e); + } + } + + /** + * Returns the S/MIME state of a MIME part (i.e. MIME message). + * + * @param mimePart The {@link MimePart} to be checked. + * @return the {@link SmimeState} of the {@link MimePart}. + */ + public static SmimeState getStatus(MimePart mimePart) { + try { + return getStatus(new ContentType(mimePart.getContentType())); + } catch (Exception e) { + throw handledException(e); + } + } + + private static SmimeState getStatus(ContentType contentType) { + if (isSmimeSignatureContentType(contentType)) { + return SmimeState.SIGNED; + } else if (isSignatureSmimeType(contentType)) { + return SmimeState.SIGNED_ENVELOPED; + } else if (isSmimeEncryptionContenttype(contentType)) { + return SmimeState.ENCRYPTED; + } else { + return SmimeState.NEITHER; + } + } + + private static boolean isSmimeEncryptionContenttype(ContentType contentType) { + String baseContentType = contentType.getBaseType(); + return baseContentType.equalsIgnoreCase("application/pkcs7-mime") + || baseContentType.equalsIgnoreCase("application/x-pkcs7-mime"); + } + + private static boolean isSmimeSignatureContentType(ContentType contentType) { + String baseContentType = contentType.getBaseType(); + String protocol = contentType.getParameter("protocol"); + return baseContentType.equalsIgnoreCase("multipart/signed") + && protocol != null && isSmimeSignatureProtocoll(protocol); + } + + private static boolean isSignatureSmimeType(ContentType contentType) { + String baseContentType = contentType.getBaseType(); + return baseContentType.equalsIgnoreCase("application/x-pkcs7-mime") + && "signed-data".equals(contentType.getParameter("smime-type")); + } + + private static boolean isSmimeSignatureProtocoll(String protocol) { + return protocol.equalsIgnoreCase("application/pkcs7-signature") + || protocol.equalsIgnoreCase("application/x-pkcs7-signature"); + } + + private static SmimeException handledException(Exception e) { + if (e instanceof SmimeException) { + return (SmimeException) e; + } + return new SmimeException(e.getMessage(), e); + } } diff --git a/src/test/java/org/simplejavamail/utils/mail/smime/SmimeUtilTest.java b/src/test/java/org/simplejavamail/utils/mail/smime/SmimeUtilTest.java new file mode 100644 index 0000000..1d34764 --- /dev/null +++ b/src/test/java/org/simplejavamail/utils/mail/smime/SmimeUtilTest.java @@ -0,0 +1,125 @@ +package org.simplejavamail.utils.mail.smime; + +import jakarta.mail.MessagingException; +import jakarta.mail.Session; +import jakarta.mail.internet.InternetAddress; +import jakarta.mail.internet.MimeMessage; +import jakarta.mail.internet.MimeMultipart; +import org.bouncycastle.cms.CMSAlgorithm; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; + +import java.io.IOException; +import java.io.InputStream; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.Security; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.util.Properties; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +class SmimeUtilTest { + + private static final String SignatureAlgorithmRsa = "SHA256withRSA"; + private static final String SignatureAlgorithmRsaPss = "SHA256WITHRSAANDMGF1"; + private SmimeKeyStore alicesKeyStore; + private SmimeKeyStore bobsKeyStore; + private Session mailSession; + + @BeforeAll + void setup() throws MessagingException, KeyStoreException, NoSuchProviderException, CertificateException, IOException, NoSuchAlgorithmException { + Security.addProvider(new BouncyCastleProvider()); + InputStream alicesKeystoreStream = this.getClass().getClassLoader().getResourceAsStream("alice.p12"); + this.alicesKeyStore = new SmimeKeyStore(alicesKeystoreStream, "alice".toCharArray()); + InputStream bobsKeystoreStream = this.getClass().getClassLoader().getResourceAsStream("bob.p12"); + this.bobsKeyStore = new SmimeKeyStore(bobsKeystoreStream, "bob".toCharArray()); + + Properties sessionProps = System.getProperties(); // new Properties(); // Fake properties for a fake session + this.mailSession = Session.getDefaultInstance(sessionProps); + } + + private MimeMessage createTestMessage(String from, String to) throws MessagingException { + MimeMessage testMessage = new MimeMessage(this.mailSession); + testMessage.setFrom(new InternetAddress(from)); + testMessage.setRecipient(MimeMessage.RecipientType.TO, new InternetAddress(to)); + testMessage.setSubject("This is a test email"); + testMessage.setContent("This is some test content for the test email's body", "text/plain; charset=utf-8"); + return testMessage; + } + + @Test + void SuccessfullySignAndValidate() throws MessagingException, IOException { + MimeMessage testMessage = createTestMessage("alice@testcorp.com", "alice@testcorp.com"); + SmimeKey alicesKey = this.alicesKeyStore.getPrivateKey("alice", "alice".toCharArray()); + MimeMessage signedMessage = SmimeUtil.sign(this.mailSession, testMessage, alicesKey); + MimeMultipart multipartContent = (MimeMultipart) signedMessage.getContent(); + assertEquals(SmimeState.SIGNED, SmimeUtil.getStatus(multipartContent)); + boolean isSignatureValid = SmimeUtil.checkSignature(multipartContent); + assertTrue(isSignatureValid); + } + + @Test + void SuccessfullyEnvelopeAndDecryptDefault() throws MessagingException { + MimeMessage testMessage = createTestMessage("alice@testcorp.com", "alice@testcorp.com"); + SmimeKey alicesKey = this.alicesKeyStore.getPrivateKey("alice", "alice".toCharArray()); + X509Certificate alicesCert = alicesKey.getCertificate(); + MimeMessage encryptedMessage = SmimeUtil.encrypt(this.mailSession, + SmimeUtil.sign(this.mailSession, testMessage, alicesKey), + alicesCert); + assertEquals(SmimeState.ENCRYPTED, SmimeUtil.getStatus((encryptedMessage))); + MimeMessage decryptedMessage = SmimeUtil.decrypt(this.mailSession, encryptedMessage, alicesKey); + boolean isSignatureValid = SmimeUtil.checkSignature(decryptedMessage); + assertTrue(isSignatureValid); + } + + @Test + void SuccessfullyEnvelopeAndDecrypt() throws MessagingException { + MimeMessage testMessage = createTestMessage("alice@testcorp.com", "alice@testcorp.com"); + SmimeKey alicesKey = this.alicesKeyStore.getPrivateKey("alice", "alice".toCharArray()); + X509Certificate alicesCert = alicesKey.getCertificate(); + MimeMessage encryptedMessage = SmimeUtil.encrypt(this.mailSession, + SmimeUtil.sign(this.mailSession, testMessage, alicesKey, SignatureAlgorithmRsaPss), + alicesCert, KeyEncapsulationAlgorithm.RSA_OAEP_SHA256, CMSAlgorithm.AES256_CBC); + assertEquals(SmimeState.ENCRYPTED, SmimeUtil.getStatus((encryptedMessage))); + MimeMessage decryptedMessage = SmimeUtil.decrypt(this.mailSession, encryptedMessage, alicesKey); + boolean isSignatureValid = SmimeUtil.checkSignature(decryptedMessage); + assertTrue(isSignatureValid); + } + + @Test + void AliceToBoEnvelopeAndDecrypt() throws MessagingException { + MimeMessage testMessage = createTestMessage("alice@testcorp.com", "bob@testcorp.com"); + SmimeKey alicesKey = this.alicesKeyStore.getPrivateKey("alice", "alice".toCharArray()); + SmimeKey bobsKey = this.bobsKeyStore.getPrivateKey("bob", "bob".toCharArray()); + X509Certificate bobsCert = bobsKey.getCertificate(); + MimeMessage encryptedMessage = SmimeUtil.encrypt(this.mailSession, + SmimeUtil.sign(this.mailSession, testMessage, alicesKey, SignatureAlgorithmRsaPss), + bobsCert, KeyEncapsulationAlgorithm.RSA_OAEP_SHA512, CMSAlgorithm.AES256_GCM); + assertEquals(SmimeState.ENCRYPTED, SmimeUtil.getStatus((encryptedMessage))); + MimeMessage decryptedMessage = SmimeUtil.decrypt(this.mailSession, encryptedMessage, bobsKey); + boolean isSignatureValid = SmimeUtil.checkSignature(decryptedMessage); + assertTrue(isSignatureValid); + } + + @Test + void BobToAliceEnvelopeAndDecrypt() throws MessagingException { + MimeMessage testMessage = createTestMessage("bob@testcorp.com", "alice@testcorp.com"); + SmimeKey bobsKey = this.bobsKeyStore.getPrivateKey("bob", "bob".toCharArray()); + SmimeKey alicesKey = this.alicesKeyStore.getPrivateKey("alice", "alice".toCharArray()); + X509Certificate alicesCert = alicesKey.getCertificate(); + MimeMessage encryptedMessage = SmimeUtil.encrypt(this.mailSession, + SmimeUtil.sign(this.mailSession, testMessage, bobsKey, SignatureAlgorithmRsaPss), + alicesCert, KeyEncapsulationAlgorithm.RSA_OAEP_SHA384, CMSAlgorithm.AES192_CCM); + assertEquals(SmimeState.ENCRYPTED, SmimeUtil.getStatus((encryptedMessage))); + MimeMessage decryptedMessage = SmimeUtil.decrypt(this.mailSession, encryptedMessage, alicesKey); + boolean isSignatureValid = SmimeUtil.checkSignature(decryptedMessage); + assertTrue(isSignatureValid); + } +} \ No newline at end of file diff --git a/src/test/resources/alice-certgen-rsa.sh b/src/test/resources/alice-certgen-rsa.sh new file mode 100755 index 0000000..ab330e7 --- /dev/null +++ b/src/test/resources/alice-certgen-rsa.sh @@ -0,0 +1,27 @@ +#!/bin/bash + +# This script can be used to generate a self-signed test-certificate for the fictional principal "Alice". +# The certificate is issued on the basis of a standard RSA key-pair. + +### Set the openssl version to use. +openssl_bin="/usr/local/opt/openssl@1.1/bin/openssl" +account_name="alice" +priv_key_name="${account_name}.priv" +certificate_config_filename="${account_name}.cnf" +validity_days=1825 # Five years, so the tests won't fail too soon. + +echo "Generating private RSA key" +$openssl_bin genpkey -algorithm rsa -pkeyopt rsa_keygen_bits:4096 -pkeyopt rsa_keygen_pubexp:65537 -out ${priv_key_name}.rsakey +### Save the private key without password protection +$openssl_bin rsa -in ${priv_key_name}.rsakey -out ${priv_key_name}.nopass.rsakey + +echo "Issue certificate signing request (CSR) for the RSA key with parameters in ${certificate_config_filename}" +$openssl_bin req -new -key ${priv_key_name}.nopass.rsakey -sha256 -out ${account_name}.csr -config ${certificate_config_filename} +echo "Content of the certificate signing request:" +$openssl_bin req -text -noout -in ${account_name}.csr + +echo "Generating self-signed certificate..." +$openssl_bin x509 -req -days ${validity_days} -in ${account_name}.csr -signkey ${priv_key_name}.nopass.rsakey -sha256 -out ${account_name}.crt -extensions smime -extfile ${certificate_config_filename} + +echo "Generating .p12 file with certificate and private key..." +$openssl_bin pkcs12 -export -in ${account_name}.crt -inkey ${priv_key_name}.nopass.rsakey -out ${account_name}.p12 diff --git a/src/test/resources/alice.cnf b/src/test/resources/alice.cnf new file mode 100644 index 0000000..ea6fa52 --- /dev/null +++ b/src/test/resources/alice.cnf @@ -0,0 +1,35 @@ +[ req ] +default_bits = 4096 +distinguished_name = req_distinguished_name +string_mask = utf8only + +[ req_distinguished_name ] +countryName = Country Name (2 letter code) +countryName_default = AA +countryName_min = 2 +countryName_max = 2 +stateOrProvinceName = State or Province Name (full name) +stateOrProvinceName_default = Testprovince +localityName = Locality Name (eg, city) +localityName_default = Testtown +0.organizationName = Organization Name (eg, company) +0.organizationName_default = Testcorp +commonName = Common Name +commonName_default = Alice +commonName_max = 64 +emailAddress = Email Address +emailAddress_default = alice@testcorp.com +emailAddress_max = 64 + +[ usr_cert ] +subjectKeyIdentifier=hash +authorityKeyIdentifier=keyid,issuer +subjectAltName=email:move + +[ smime ] +basicConstraints = CA:FALSE +keyUsage = nonRepudiation, digitalSignature, keyEncipherment, dataEncipherment +extendedKeyUsage = emailProtection +subjectKeyIdentifier = hash +authorityKeyIdentifier = keyid:always, issuer +subjectAltName = email:copy \ No newline at end of file diff --git a/src/test/resources/alice.p12 b/src/test/resources/alice.p12 new file mode 100644 index 0000000000000000000000000000000000000000..1e3187e68464fc55540d9ee502ded727403f8ac5 GIT binary patch literal 4472 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 literal 0 HcmV?d00001 diff --git a/src/test/resources/bob-certgen-rsa.sh b/src/test/resources/bob-certgen-rsa.sh new file mode 100755 index 0000000..ad1441d --- /dev/null +++ b/src/test/resources/bob-certgen-rsa.sh @@ -0,0 +1,27 @@ +#!/bin/bash + +# This script can be used to generate a self-signed test-certificate for the fictional principal "Bob". +# The certificate is issued on the basis of a RSASSA-PSS key-pair. + +### Set the openssl version to use. Must be OpenSSL 1.1 for RSASSA-PSS support +openssl_bin="/usr/local/opt/openssl@1.1/bin/openssl" +account_name="bob" +priv_key_name="${account_name}.priv" +certificate_config_filename="${account_name}.cnf" +validity_days=1825 # Five years, so the tests won't fail too soon. + +echo "Generating private RSASSA-PSS key" +$openssl_bin genpkey -algorithm rsa-pss -pkeyopt rsa_keygen_bits:4096 -pkeyopt rsa_keygen_pubexp:65537 -pkeyopt rsa_pss_keygen_mgf1_md:sha256 -pkeyopt rsa_pss_keygen_saltlen:32 -out ${priv_key_name}.rsapsskey +### Save the private key without password protection +$openssl_bin rsa -in ${priv_key_name}.rsapsskey -out ${priv_key_name}.nopass.rsapsskey + +echo "Issue certificate signing request (CSR) for the RSASSA-PSS key with parameters in ${certificate_config_filename}" +$openssl_bin req -new -key ${priv_key_name}.nopass.rsapsskey -sha256 -out ${account_name}.csr -config ${certificate_config_filename} +echo "Content of the certificate signing request:" +$openssl_bin req -text -noout -in ${account_name}.csr + +echo "Generating self-signed certificate..." +$openssl_bin x509 -req -days ${validity_days} -in ${account_name}.csr -signkey ${priv_key_name}.nopass.rsapsskey -sha256 -out ${account_name}.crt -extensions smime -extfile ${certificate_config_filename} + +echo "Generating .p12 file with certificate and private key..." +$openssl_bin pkcs12 -export -in ${account_name}.crt -inkey ${priv_key_name}.nopass.rsapsskey -out ${account_name}.p12 diff --git a/src/test/resources/bob.cnf b/src/test/resources/bob.cnf new file mode 100644 index 0000000..d171334 --- /dev/null +++ b/src/test/resources/bob.cnf @@ -0,0 +1,35 @@ +[ req ] +default_bits = 4096 +distinguished_name = req_distinguished_name +string_mask = utf8only + +[ req_distinguished_name ] +countryName = Country Name (2 letter code) +countryName_default = BB +countryName_min = 2 +countryName_max = 2 +stateOrProvinceName = State or Province Name (full name) +stateOrProvinceName_default = Testprovince +localityName = Locality Name (eg, city) +localityName_default = Testtown +0.organizationName = Organization Name (eg, company) +0.organizationName_default = Testcorp +commonName = Common Name +commonName_default = Bob +commonName_max = 64 +emailAddress = Email Address +emailAddress_default = bob@testcorp.com +emailAddress_max = 64 + +[ usr_cert ] +subjectKeyIdentifier=hash +authorityKeyIdentifier=keyid,issuer +subjectAltName=email:move + +[ smime ] +basicConstraints = CA:FALSE +keyUsage = nonRepudiation, digitalSignature, keyEncipherment, dataEncipherment +extendedKeyUsage = emailProtection +subjectKeyIdentifier = hash +authorityKeyIdentifier = keyid:always, issuer +subjectAltName = email:copy \ No newline at end of file diff --git a/src/test/resources/bob.p12 b/src/test/resources/bob.p12 new file mode 100644 index 0000000000000000000000000000000000000000..63478b11136f4e5c8e7fc5ee71f72bf23c549c7a GIT binary patch literal 4452 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 Date: Wed, 18 May 2022 13:59:59 +0200 Subject: [PATCH 2/3] feat: Simplify generation of test certificates --- src/test/resources/alice-certgen-rsa.sh | 9 ++------- src/test/resources/alice.p12 | Bin 4472 -> 4456 bytes src/test/resources/bob-certgen-rsa.sh | 11 +++-------- src/test/resources/bob.cnf | 21 ++++++++++----------- src/test/resources/bob.p12 | Bin 4452 -> 4532 bytes 5 files changed, 15 insertions(+), 26 deletions(-) diff --git a/src/test/resources/alice-certgen-rsa.sh b/src/test/resources/alice-certgen-rsa.sh index ab330e7..b7a2063 100755 --- a/src/test/resources/alice-certgen-rsa.sh +++ b/src/test/resources/alice-certgen-rsa.sh @@ -15,13 +15,8 @@ $openssl_bin genpkey -algorithm rsa -pkeyopt rsa_keygen_bits:4096 -pkeyopt rsa_k ### Save the private key without password protection $openssl_bin rsa -in ${priv_key_name}.rsakey -out ${priv_key_name}.nopass.rsakey -echo "Issue certificate signing request (CSR) for the RSA key with parameters in ${certificate_config_filename}" -$openssl_bin req -new -key ${priv_key_name}.nopass.rsakey -sha256 -out ${account_name}.csr -config ${certificate_config_filename} -echo "Content of the certificate signing request:" -$openssl_bin req -text -noout -in ${account_name}.csr - echo "Generating self-signed certificate..." -$openssl_bin x509 -req -days ${validity_days} -in ${account_name}.csr -signkey ${priv_key_name}.nopass.rsakey -sha256 -out ${account_name}.crt -extensions smime -extfile ${certificate_config_filename} +$openssl_bin req -outform PEM -out ${account_name}.pem -key ${priv_key_name}.nopass.rsakey -keyform PEM -x509 -nodes -batch -days $validity_days -config $certificate_config_filename -pkeyopt rsa_keygen_bits:2048 -sha256 echo "Generating .p12 file with certificate and private key..." -$openssl_bin pkcs12 -export -in ${account_name}.crt -inkey ${priv_key_name}.nopass.rsakey -out ${account_name}.p12 +$openssl_bin pkcs12 -export -in ${account_name}.pem -inkey ${priv_key_name}.nopass.rsakey -out ${account_name}.p12 diff --git a/src/test/resources/alice.p12 b/src/test/resources/alice.p12 index 1e3187e68464fc55540d9ee502ded727403f8ac5..e0e73b7f2eae982cb3118cbad2079103d5f4e218 100644 GIT binary patch delta 4315 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 Z@6NBaA~Eb(x6}dx2msd^W77Zt diff --git a/src/test/resources/bob-certgen-rsa.sh b/src/test/resources/bob-certgen-rsa.sh index ad1441d..91cbb49 100755 --- a/src/test/resources/bob-certgen-rsa.sh +++ b/src/test/resources/bob-certgen-rsa.sh @@ -11,17 +11,12 @@ certificate_config_filename="${account_name}.cnf" validity_days=1825 # Five years, so the tests won't fail too soon. echo "Generating private RSASSA-PSS key" -$openssl_bin genpkey -algorithm rsa-pss -pkeyopt rsa_keygen_bits:4096 -pkeyopt rsa_keygen_pubexp:65537 -pkeyopt rsa_pss_keygen_mgf1_md:sha256 -pkeyopt rsa_pss_keygen_saltlen:32 -out ${priv_key_name}.rsapsskey +$openssl_bin genpkey -algorithm rsa-pss -pkeyopt rsa_keygen_bits:4096 -pkeyopt rsa_keygen_pubexp:65537 -pkeyopt rsa_pss_keygen_md:sha256 -pkeyopt rsa_pss_keygen_mgf1_md:sha256 -pkeyopt rsa_pss_keygen_saltlen:32 -out ${priv_key_name}.rsapsskey ### Save the private key without password protection $openssl_bin rsa -in ${priv_key_name}.rsapsskey -out ${priv_key_name}.nopass.rsapsskey -echo "Issue certificate signing request (CSR) for the RSASSA-PSS key with parameters in ${certificate_config_filename}" -$openssl_bin req -new -key ${priv_key_name}.nopass.rsapsskey -sha256 -out ${account_name}.csr -config ${certificate_config_filename} -echo "Content of the certificate signing request:" -$openssl_bin req -text -noout -in ${account_name}.csr - echo "Generating self-signed certificate..." -$openssl_bin x509 -req -days ${validity_days} -in ${account_name}.csr -signkey ${priv_key_name}.nopass.rsapsskey -sha256 -out ${account_name}.crt -extensions smime -extfile ${certificate_config_filename} +$openssl_bin req -outform PEM -out ${account_name}.pem -key ${priv_key_name}.nopass.rsapsskey -keyform PEM -x509 -nodes -batch -days $validity_days -config $certificate_config_filename -pkeyopt rsa_keygen_bits:4096 -sigopt rsa_padding_mode:pss -sigopt rsa_pss_saltlen:32 -sigopt rsa_mgf1_md:sha256 -sha256 echo "Generating .p12 file with certificate and private key..." -$openssl_bin pkcs12 -export -in ${account_name}.crt -inkey ${priv_key_name}.nopass.rsapsskey -out ${account_name}.p12 +$openssl_bin pkcs12 -export -in ${account_name}.pem -inkey ${priv_key_name}.nopass.rsapsskey -out ${account_name}.p12 diff --git a/src/test/resources/bob.cnf b/src/test/resources/bob.cnf index d171334..9a7fd64 100644 --- a/src/test/resources/bob.cnf +++ b/src/test/resources/bob.cnf @@ -1,6 +1,7 @@ [ req ] default_bits = 4096 distinguished_name = req_distinguished_name +x509_extensions = x509_ext string_mask = utf8only [ req_distinguished_name ] @@ -21,15 +22,13 @@ emailAddress = Email Address emailAddress_default = bob@testcorp.com emailAddress_max = 64 -[ usr_cert ] -subjectKeyIdentifier=hash -authorityKeyIdentifier=keyid,issuer -subjectAltName=email:move +# Section x509_ext is used when generating a self-signed certificate. I.e., openssl req -x509 ... +[ x509_ext ] -[ smime ] -basicConstraints = CA:FALSE -keyUsage = nonRepudiation, digitalSignature, keyEncipherment, dataEncipherment -extendedKeyUsage = emailProtection -subjectKeyIdentifier = hash -authorityKeyIdentifier = keyid:always, issuer -subjectAltName = email:copy \ No newline at end of file +subjectKeyIdentifier = hash +authorityKeyIdentifier = keyid,issuer + +basicConstraints = CA:FALSE +keyUsage = critical, digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment +extendedKeyUsage = clientAuth, emailProtection +#subjectAltName = email:copy \ No newline at end of file diff --git a/src/test/resources/bob.p12 b/src/test/resources/bob.p12 index 63478b11136f4e5c8e7fc5ee71f72bf23c549c7a..030791ad3535542d707a23976cf78175996163a0 100644 GIT binary patch delta 4396 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 delta 4315 zcmV<15G3!kBjh3?FoF?a0s#Xsf)N@92`Yw2hW8Bt2LYgh5eo!@5eG1W5d)DTR20$i zn6}7A+XtQqd<%eU+n%2}!4;8;B!4BoF;yxkIa`8DPr8^aAoCI~apWlr zREVVbNTKiWs0vwa8vL>9StX|aMbXEeEeY5y9bG%pmb1qnxY81=bbpd0WyUY@BDEj6 zvOg_FFS-$vaY7N*i{%N%P2}B>Vt$Gdf@@rVGVs$U_L%3-!cR(RyS# z=u-8+T5Uwz7Qp@`=zM#dv(K1z$zGA!fVX;d9bO^Wl-Z}fNdeUHgl&&gEOGYM5>=ir z!%yIkI?t+abHrf}LuzaOWj5}Fn{SL$RX>%L&fmi3U0L1$QGdOKBbLA^IY`^8OFg*sxI^yobuI1rm8t$PHpzeH$$D&=-HsB@_qzceK(bf~ z@v|drw~pHO5`R?sxn;TK(R&5Y3fdwr3^L`Rt6I%HK1`I*S}sM9(J43H z(X+t;XsMNbHlH0zigLpb9xa(<85OiXU5SDG$#h#Ob$?Zi&ZAIoq4%}q{6bGl?r{1o z6ID!LJ#EMeIt??zECA&Du)DmdTHSNQvxwIa=fydVM!xC81Pxc%-sD#n)`F)3KCxGp zQV~DzlPlv~&Q(K&(gAs}&CRE%nIvpQSsXO@5VdIIQSH7#S4wwW{O|6O0#3|33c~fO z!|iNuVt+TeW|G`1ne32Mk6oZD`%Y58=PH#GDut?bV&Ms&FEKdhom^$J}fp{7bWJR5+)Ot0zua zwI7@qyZfz*px{O(ToQm+kf&wm}04VVtGylxNX?&h-@v(SzX=@vPL zl~?fmfOBhFT^o1jdc5J67IDQ@uTqv$3xGZA@?3Qa5~UWWtcL(6gjM;W;_ybZ|?S5XOD+;_%#XnQ}* zK}3RiJ%YL`BlUWc6??HeDRR9*A=h$D5+GI`Toa#o%b=gQq3%OFw4)3VrImc0<$Y`l zn|-~zVp7b&mNE6{Km4xsWWc>tv;Zx=QL1b66(@K%|aoY$iW7TZ5R_*ne^dagNeq(|_5MkmgyqDHr!kNhHx*rd#4^Yo*|zLt6eeL|%bX zqTx!Fwe!|XqAhn^t7+KqQzwh^@ofvYo_T25YMo>qkAGetX7(OR zWMy-;A1AC>0NY{7N+)n4{D$1g@xwd|)ni@Qy+QIF60Oi41;ZxLPG{k;4O_3t%euo< zIefH)&k6nl$h81mFRwfHjnMF@TTvbbyBW==Jiwuz>+S+=Vbg>+o_-lEua<0nzLzW9%|JDyS{^I^;xi6p7fHKvFf~ zdxD?y?(jd=>jyM1#w1w(YY=O39lqrdGXpI6k3-ePF+|>% zM$xA?`ZH3H0g0!m6MyT~n`ot(k}b*;7+-D|M0UDQCU`Y9N82u3=W$Kfo^z5744q+2 z;lplIyc3$fRIDo;17N> z#EEdr_c`;ViCPIfhuJ(pK|nNkXx~`b5;ObE8RXH*Do2lt&LNRt~1DknEMHaRjmG&eRdf(FwD2`Yw2hW8Bt2L_;m2E;Ie2EYOV05F0E zxsz-P9e-QjDQWjPk=_%i`;ki5Tp-T~quK%j2mpYB27o)ZGD|O$=v^}QZ=#&}s8o(5 zb$*g}O+#Lftk)cr7LHX=jdp}IS2){08dU3V#db$sNr^NI|1qz0CTuT;ebkStB+Fco zFR0fdkScx3T?s>NYy|960Y%>F?QH5eZ`C@q{=R&LN2_=SBdpGrIW#1c!YEBBB7fsQhKAB*y z0!ZcgPwOe!xLTxCIKtb7CcE{yO=aR*1U;zh!`kEUU+)G8Hz8M4JJT|Zuecfri?--L z%OJgQ3u`5~g<;|;LcM}TDU3>=(51un)_;KZ)7Q|mKF+k4o=%6$nGOeDCp*3bKQ%uhkrGNDj zUr?=oB1@}~amuR<13u!f`~7}4<+$~88J8{0IIg;R&)CD_f+YufoC)^p2v#PEgw#Lm z5_(L3Hm{)A*L>D#Us1bZR>aU}5aWP6iFwb`z#KTxbRkAs3Wum6sK{1Q-$91FK_Ph;fxF&l+ejuws%C3ky&aN{BLijZ z>Zj>6SH}DDNwDd9#fd~{nu%iX=!@E0JHzCEPPEdSAjE+xrdD^a^*R;&lYg~=dT@QW zM3PiCpxp=$j$=v_>Lp4?WGlPb-G;pIp58a5hQd1Ed%&RF3SX|~)sAZ<9e==!+j)y} z0^n54IM{jWviC@ubSQGkL;TC>9bS6(H0FojT=Qx{WJ|mY&}LEDbv0x1TD0ce;)PA; zXqW5Ext5Q^c>@cyk_FEt{^r$v)dF}RQTs-=Jy;@3(>4}bWMhNmMA0!f2y zL&PNX+9!}-X%c9ESP%1nC9NWY)IMzf<~7_qmNoWbgE2v3E+^HHCDto}^p5#BWvfM) zPx7!DZQSIfcLz4dT@aL-3p_Ntiybr(D{$ClngR^Rw9)A^ms?gi0W4moFQ)#rrQttqRr<$1vw-LWZ?qWzI_6Rme2}8bx zlz@ioeRRqHH7ilDSiU;dv%|^?DcCN41+Mm$Carh8`?7xVeb*smGP(7QUT+bp9W&UW zq?AUxPg1I+L~Q6(CE{VxpKhY3JSYW4U;q;U1PI_25mI{mn16#89+ETE%P4AP4$HgP z+_*G-sj~_hNOyk_vI}N+YnSj?`yKku=mH(d)yRt-%kFJv372BaW;6S%!uKY0+^ozZ zxIGFvOwUHoQbp9@RtJiWf!mvh6#Hq5Q3fgt{VfpTLNQkZpC^-L<=8h0{erdzP&|Q)^3etd`*K-U z%u~>5oWYCNU|`l{(6b$O;^&&}V93*PW>eCqWsfD%(}s#mEQ25BNU0{*XO1j9YiC+bzd`=h2ZX=1QbQ74NN{0Qhmtaz_^jGpjgbm JW$pq32ms)yQzHNX From ab6d9e3dc2be4d1c910987a57bb76a9ec4c284d5 Mon Sep 17 00:00:00 2001 From: Ulrich Schuster Date: Wed, 18 May 2022 14:01:34 +0200 Subject: [PATCH 3/3] refactor: Simplify test certificate configuration --- src/test/resources/alice.cnf | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/src/test/resources/alice.cnf b/src/test/resources/alice.cnf index ea6fa52..e7ba56a 100644 --- a/src/test/resources/alice.cnf +++ b/src/test/resources/alice.cnf @@ -1,6 +1,7 @@ [ req ] default_bits = 4096 distinguished_name = req_distinguished_name +x509_extensions = x509_ext string_mask = utf8only [ req_distinguished_name ] @@ -21,15 +22,13 @@ emailAddress = Email Address emailAddress_default = alice@testcorp.com emailAddress_max = 64 -[ usr_cert ] -subjectKeyIdentifier=hash -authorityKeyIdentifier=keyid,issuer -subjectAltName=email:move +# Section x509_ext is used when generating a self-signed certificate. I.e., openssl req -x509 ... +[ x509_ext ] -[ smime ] -basicConstraints = CA:FALSE -keyUsage = nonRepudiation, digitalSignature, keyEncipherment, dataEncipherment -extendedKeyUsage = emailProtection -subjectKeyIdentifier = hash -authorityKeyIdentifier = keyid:always, issuer -subjectAltName = email:copy \ No newline at end of file +subjectKeyIdentifier = hash +authorityKeyIdentifier = keyid,issuer + +basicConstraints = CA:FALSE +keyUsage = critical, digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment +extendedKeyUsage = clientAuth, emailProtection +#subjectAltName = email:copy \ No newline at end of file