diff --git a/README.md b/README.md index 70764529..f5dd996a 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ It is an independent open source implementation that is not affiliated with or e * [jose4j](https://bitbucket.org/b_c/jose4j/wiki/Home) * [slf4j](http://www.slf4j.org/) * For `acme4j-utils`: [Bouncy Castle](https://www.bouncycastle.org/) -* For `acme4j-smime`: [Jakarta Mail](https://eclipse-ee4j.github.io/mail/) +* For `acme4j-smime`: [Jakarta Mail](https://eclipse-ee4j.github.io/mail/), [Bouncy Castle](https://www.bouncycastle.org/) ## Usage diff --git a/acme4j-smime/.gitattributes b/acme4j-smime/.gitattributes new file mode 100644 index 00000000..66f98bb0 --- /dev/null +++ b/acme4j-smime/.gitattributes @@ -0,0 +1 @@ +*.eml text eol=crlf diff --git a/acme4j-smime/pom.xml b/acme4j-smime/pom.xml index cf6b8e72..7db473ff 100644 --- a/acme4j-smime/pom.xml +++ b/acme4j-smime/pom.xml @@ -49,6 +49,11 @@ bcpkix-jdk18on ${bouncycastle.version} + + org.bouncycastle + bcjmail-jdk18on + ${bouncycastle.version} + org.slf4j slf4j-api diff --git a/acme4j-smime/src/main/java/module-info.java b/acme4j-smime/src/main/java/module-info.java index 81f30867..901135b2 100644 --- a/acme4j-smime/src/main/java/module-info.java +++ b/acme4j-smime/src/main/java/module-info.java @@ -18,13 +18,17 @@ module org.shredzone.acme4j.smime { requires transitive jakarta.mail; requires static com.github.spotbugs.annotations; + requires org.bouncycastle.util; + requires org.bouncycastle.mail; requires org.bouncycastle.pkix; requires org.bouncycastle.provider; + requires org.slf4j; exports org.shredzone.acme4j.smime; exports org.shredzone.acme4j.smime.challenge; exports org.shredzone.acme4j.smime.csr; exports org.shredzone.acme4j.smime.email; + exports org.shredzone.acme4j.smime.exception; provides org.shredzone.acme4j.provider.ChallengeProvider with org.shredzone.acme4j.smime.challenge.EmailReply00ChallengeProvider; diff --git a/acme4j-smime/src/main/java/org/shredzone/acme4j/smime/email/EmailProcessor.java b/acme4j-smime/src/main/java/org/shredzone/acme4j/smime/email/EmailProcessor.java index df483666..52867927 100644 --- a/acme4j-smime/src/main/java/org/shredzone/acme4j/smime/email/EmailProcessor.java +++ b/acme4j-smime/src/main/java/org/shredzone/acme4j/smime/email/EmailProcessor.java @@ -16,38 +16,56 @@ package org.shredzone.acme4j.smime.email; import static java.util.Objects.requireNonNull; import static jakarta.mail.Message.RecipientType.TO; +import java.io.IOException; import java.net.URL; +import java.security.cert.CertificateParsingException; +import java.security.cert.X509Certificate; import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.List; +import java.util.Objects; import java.util.Optional; import java.util.concurrent.atomic.AtomicReference; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; +import edu.umd.cs.findbugs.annotations.CheckForNull; +import edu.umd.cs.findbugs.annotations.Nullable; import jakarta.mail.Address; import jakarta.mail.Message; import jakarta.mail.MessagingException; +import jakarta.mail.Session; +import jakarta.mail.internet.AddressException; import jakarta.mail.internet.InternetAddress; +import jakarta.mail.internet.MimeMessage; +import jakarta.mail.internet.MimeMultipart; +import org.bouncycastle.cms.CMSException; +import org.bouncycastle.cms.SignerInformation; +import org.bouncycastle.cms.SignerInformationVerifier; +import org.bouncycastle.cms.jcajce.JcaSimpleSignerInfoVerifierBuilder; +import org.bouncycastle.mail.smime.SMIMESigned; +import org.bouncycastle.operator.OperatorCreationException; import org.shredzone.acme4j.Identifier; import org.shredzone.acme4j.Login; import org.shredzone.acme4j.exception.AcmeProtocolException; import org.shredzone.acme4j.smime.challenge.EmailReply00Challenge; +import org.shredzone.acme4j.smime.exception.AcmeInvalidMessageException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * A processor for incoming "Challenge" emails. - *

- * Note that according to RFC-8823, the incoming mail must be DKIM or S/MIME signed, and - * the signature must be validated. This is not done by this processor, because - * it is usually checked by the inbound MTA. * * @see RFC 8823 * @since 2.12 */ public final class EmailProcessor { + private static final Logger LOG = LoggerFactory.getLogger(EmailProcessor.class); private static final Pattern SUBJECT_PATTERN = Pattern.compile("ACME:\\s+([0-9A-Za-z_\\s-]+=?)\\s*"); + private static final int RFC822NAME = 1; private final String token1; private final Optional messageId; @@ -56,42 +74,186 @@ public final class EmailProcessor { private final Collection replyTo; private final AtomicReference challengeRef = new AtomicReference<>(); + /** + * Processes the given e-mail message. + *

+ * Note that according to RFC-8823, the challenge message must be signed using either + * DKIM or S/MIME. This method does not do any DKIM or S/MIME validation, and assumes + * that this has already been done by the inbound MTA. + * + * @param message + * E-mail that was received from the CA. The inbound MTA has already taken + * care of DKIM and/or S/MIME validation. + * @return EmailProcessor for this e-mail + * @throws AcmeInvalidMessageException + * if a validation failed, and the message must be rejected. + * @since 2.15 + */ + public static EmailProcessor plainMessage(Message message) + throws AcmeInvalidMessageException { + return new EmailProcessor(message, null, false, null); + } + + /** + * Performs an S/MIME validation and processes the given e-mail message. + *

+ * The owner of the given certificate must be the sender of that email. + * + * @param message + * E-mail that was received from the CA. + * @param mailSession + * A {@link Session} that can be used for processing inner e-mails. + * @param signCert + * The signing certificate of the sender. + * @param strict + * If {@code true}, the S/MIME protected headers "From", "To", and "Subject" + * must match the headers of the received message. If {@code false}, + * only the S/MIME protected headers are used, and the headers of the received + * message are ignored. + * @return EmailProcessor for this e-mail + * @throws AcmeInvalidMessageException + * if a validation failed, and the message must be rejected. + * @since 2.15 + */ + public static EmailProcessor smimeMessage(Message message, Session mailSession, + X509Certificate signCert, boolean strict) + throws AcmeInvalidMessageException { + try { + if (!(message instanceof MimeMessage)) { + throw new AcmeInvalidMessageException("Not a S/MIME message"); + } + MimeMessage mimeMessage = (MimeMessage) message; + + if (!(mimeMessage.getContent() instanceof MimeMultipart)) { + throw new AcmeProtocolException("S/MIME signed email must contain MimeMultipart"); + } + MimeMultipart mp = (MimeMultipart) message.getContent(); + + SMIMESigned signed = new SMIMESigned(mp); + + SignerInformationVerifier verifier = new JcaSimpleSignerInfoVerifierBuilder().build(signCert); + boolean hasMatch = false; + for (SignerInformation signer : signed.getSignerInfos().getSigners()) { + hasMatch |= signer.verify(verifier); + } + if (!hasMatch) { + throw new AcmeInvalidMessageException("The S/MIME signature is invalid"); + } + + MimeMessage content = signed.getContentAsMimeMessage(mailSession); + if (!content.getContentType().equalsIgnoreCase("message/rfc822; forwarded=no")) { + throw new AcmeInvalidMessageException("Message does not contain protected headers"); + } + + MimeMessage body = new MimeMessage(mailSession, content.getInputStream()); + + List

validFromAddresses = Optional.ofNullable(signCert.getSubjectAlternativeNames()) + .orElseGet(Collections::emptyList) + .stream() + .filter(l -> ((Number) l.get(0)).intValue() == RFC822NAME) + .map(l -> l.get(1).toString()) + .map(l -> { + try { + return new InternetAddress(l); + } catch (AddressException ex) { + // Ignore invalid email addresses + LOG.debug("Certificate contains invalid e-mail address {}", l, ex); + return null; + } + }) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + + if (validFromAddresses.isEmpty()) { + throw new AcmeInvalidMessageException("Signing certificate does not provide a rfc822Name subjectAltName"); + } + + return new EmailProcessor(message, body, strict, validFromAddresses); + } catch (IOException | MessagingException | CMSException | OperatorCreationException | + CertificateParsingException ex) { + throw new AcmeInvalidMessageException("Invalid S/MIME mail", ex); + } + } + /** * Creates a new {@link EmailProcessor} for the incoming "Challenge" message. *

- * The incoming message is validated against the requirements of RFC-8823. An {@link - * AcmeProtocolException} is thrown if the validation fails. DKIM or S/MIME signature - * is not checked by the processor, and must be checked elsewhere (usually by - * the inbound MTA). + * The incoming message is validated against the requirements of RFC-8823. * * @param message * "Challenge" message as it was sent by the CA. - * @throws AcmeProtocolException - * if the incoming message is not a valid "challenge" message according to - * RFC-8823. + * @param signedMessage + * The signed part of the challenge message if present, or {@code null}. The + * signature is assumed to be valid, and must be validated in a previous + * step. + * @param strict + * If {@code true}, the S/MIME protected headers "From", "To", and "Subject" + * must match the headers of the received message. If {@code false}, + * only the S/MIME protected headers are used, and the headers of the received + * message are ignored. + * @param validFromAddresses + * A {@link List} of {@link Address} that were found in the certificate's + * rfc822Name subjectAltName extension. The mail's From address must + * be found in this list, otherwise the signed message will be rejected. + * {@code null} to disable this validation step. + * @throws AcmeInvalidMessageException + * if a validation failed, and the message must be rejected. */ - public EmailProcessor(Message message) { + private EmailProcessor(Message message, @Nullable MimeMessage signedMessage, + boolean strict, @Nullable List

validFromAddresses) + throws AcmeInvalidMessageException { requireNonNull(message, "message"); // Validate challenge and extract token 1 try { - if (!isAutoGenerated(message)) { - throw new AcmeProtocolException("Message is not auto-generated"); + if (!isAutoGenerated(getOptional(m -> m.getHeader("Auto-Submitted"), message, signedMessage))) { + throw new AcmeInvalidMessageException("Message is not auto-generated"); } - Address[] from = message.getFrom(); + Address[] from = getMandatory(Message::getFrom, message, signedMessage, "From"); + if (from == null) { + throw new AcmeInvalidMessageException("Message has no 'From' header"); + } if (from.length != 1) { - throw new AcmeProtocolException("Message must have exactly one sender, but has " + from.length); + throw new AcmeInvalidMessageException("Message must have exactly one sender, but has " + from.length); + } + if (validFromAddresses != null && !validFromAddresses.contains(from[0])) { + throw new AcmeInvalidMessageException("Sender '" + from[0] + "' was not found in signing certificate"); + } + if (strict && signedMessage != null) { + Address[] outerFrom = message.getFrom(); + if ((outerFrom.length > 1) || (outerFrom.length == 1 && outerFrom[0] != null + && !outerFrom[0].equals(from[0]))) { + throw new AcmeInvalidMessageException("Protected 'From' header does not match envelope header"); + } } sender = new InternetAddress(from[0].toString()); - Address[] to = message.getRecipients(TO); + Address[] to = getMandatory(m -> m.getRecipients(TO), message, signedMessage, "To"); + if (to == null) { + throw new AcmeInvalidMessageException("Message has no 'To' header"); + } if (to.length != 1) { throw new AcmeProtocolException("Message must have exactly one recipient, but has " + to.length); } + if (strict && signedMessage != null) { + Address[] outerTo = message.getRecipients(TO); + if ((outerTo.length > 1) || (outerTo.length == 1 && outerTo[0] != null + && !outerTo[0].equals(to[0]))) { + throw new AcmeInvalidMessageException("Protected 'To' header does not match envelope header"); + } + } recipient = new InternetAddress(to[0].toString()); - String subject = message.getSubject(); + String subject = getMandatory(Message::getSubject, message, signedMessage, "Subject"); + if (subject == null) { + throw new AcmeInvalidMessageException("Message has no 'Subject' header"); + } + if (strict && signedMessage != null + && message.getSubject() != null + && !message.getSubject().equals(signedMessage.getSubject())) { + throw new AcmeInvalidMessageException("Protected 'Subject' header does not match envelope header"); + } Matcher m = SUBJECT_PATTERN.matcher(subject); if (!m.matches()) { throw new AcmeProtocolException("Invalid subject: " + subject); @@ -99,7 +261,7 @@ public final class EmailProcessor { // white spaces within the token part must be ignored this.token1 = m.group(1).replaceAll("\\s+", ""); - Address[] rto = message.getReplyTo(); + Address[] rto = getOptional(Message::getReplyTo, message, signedMessage); if (rto != null) { replyTo = Collections.unmodifiableList(Arrays.stream(rto) .filter(InternetAddress.class::isInstance) @@ -109,7 +271,7 @@ public final class EmailProcessor { replyTo = Collections.emptyList(); } - String[] mid = message.getHeader("Message-ID"); + String[] mid = getOptional(n -> n.getHeader("Message-ID"), message, signedMessage); if (mid != null && mid.length > 0) { messageId = Optional.of(mid[0]); } else { @@ -297,16 +459,78 @@ public final class EmailProcessor { return new ResponseGenerator(this); } + /** + * Get an optional property from the message. + *

+ * Optional property means: If there is a signed message, try to fetch the property + * from there. If the property is not present, fetch it from the unsigned message + * instead. If it's also not there, return {@code null}. + * + * @param getter + * The getter method of {@link Message} to be invoked + * @param message + * The outer (unsigned) {@link Message} that serves as fallback + * @param signedMessage + * The signed (inner) {@link Message} where the property is looked up first + * @param + * The expected result type + * @return The mail property, or {@code null} if not found + */ + @CheckForNull + private T getOptional(MessageFunction getter, Message message, @Nullable Message signedMessage) + throws MessagingException { + if (signedMessage != null) { + T result = getter.apply(signedMessage); + if (result != null) { + return result; + } + } + return getter.apply(message); + } + + /** + * Get a mandatory property from the message. + *

+ * Mandatory means: If there is a signed message, the property must be + * present there. The unsigned message is only queried as fallback if there is no + * signed message at all. + * + * @param getter + * The getter method of {@link Message} to be invoked + * @param message + * The outer (unsigned) {@link Message} that serves as fallback if there is + * no signed message. + * @param signedMessage + * The signed (inner) {@link Message} where the property is expected, or + * {@code null} if there is no signed message. + * @param header + * Name of the expected header + * @param + * The expected result type + * @return The mail property, or {@code null} if not found + */ + @CheckForNull + private T getMandatory(MessageFunction getter, Message message, @Nullable Message signedMessage, String header) + throws MessagingException, AcmeInvalidMessageException { + if (signedMessage != null) { + T value = getter.apply(signedMessage); + if (value == null) { + throw new AcmeInvalidMessageException("Protected header '" + header + "' expected, but missing."); + } + return value; + } + return getter.apply(message); + } + /** * Checks if this message is "auto-generated". * - * @param message - * Message to check. + * @param autoSubmitted + * Auto-Submitted header content * @return {@code true} if the mail was auto-generated. */ - private boolean isAutoGenerated(Message message) throws MessagingException { - String[] autoSubmitted = message.getHeader("Auto-Submitted"); - if (autoSubmitted == null) { + private boolean isAutoGenerated(@Nullable String[] autoSubmitted) throws MessagingException { + if (autoSubmitted == null || autoSubmitted.length == 0) { return false; } return Arrays.stream(autoSubmitted) @@ -323,4 +547,10 @@ public final class EmailProcessor { } } + @FunctionalInterface + private interface MessageFunction { + @CheckForNull + R apply(M message) throws MessagingException; + } + } diff --git a/acme4j-smime/src/main/java/org/shredzone/acme4j/smime/email/ResponseGenerator.java b/acme4j-smime/src/main/java/org/shredzone/acme4j/smime/email/ResponseGenerator.java index 6a939d5e..33529bd3 100644 --- a/acme4j-smime/src/main/java/org/shredzone/acme4j/smime/email/ResponseGenerator.java +++ b/acme4j-smime/src/main/java/org/shredzone/acme4j/smime/email/ResponseGenerator.java @@ -28,9 +28,8 @@ import jakarta.mail.internet.MimeMessage; /** * A helper for creating an email response to the "challenge" email. *

- * According to RFC-8823, the response email must have a DKIM or S/MIME signature. This is - * not done by the response generator, because it is usually performed by the - * outbound MTA. + * According to RFC-8823, the response email must be DKIM signed. This is + * not done by the response generator, but must be done by the outbound MTA. * * @see RFC 8823 * @since 2.12 diff --git a/acme4j-smime/src/main/java/org/shredzone/acme4j/smime/exception/AcmeInvalidMessageException.java b/acme4j-smime/src/main/java/org/shredzone/acme4j/smime/exception/AcmeInvalidMessageException.java new file mode 100644 index 00000000..145c8aa4 --- /dev/null +++ b/acme4j-smime/src/main/java/org/shredzone/acme4j/smime/exception/AcmeInvalidMessageException.java @@ -0,0 +1,57 @@ +/* + * acme4j - Java ACME client + * + * Copyright (C) 2022 Richard "Shred" Körber + * http://acme4j.shredzone.org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + */ +package org.shredzone.acme4j.smime.exception; + +import org.shredzone.acme4j.exception.AcmeException; + +/** + * This exception is thrown when the challenge message is invalid. + *

+ * If this exception is thrown, the challenge message does not match the actual challenge, + * and must be rejected. + *

+ * Reasons may be: + *

    + *
  • Unexpected sender address
  • + *
  • Bad S/MIME signature
  • + *
+ * + * @since 2.15 + */ +public class AcmeInvalidMessageException extends AcmeException { + private static final long serialVersionUID = 5607857024718309330L; + + /** + * Creates a new {@link AcmeInvalidMessageException}. + * + * @param msg + * Reason of the exception + */ + public AcmeInvalidMessageException(String msg) { + super(msg); + } + + /** + * Creates a new {@link AcmeInvalidMessageException}. + * + * @param msg + * Reason of the exception + * @param cause + * Cause + */ + public AcmeInvalidMessageException(String msg, Throwable cause) { + super(msg, cause); + } + +} diff --git a/acme4j-smime/src/main/java/org/shredzone/acme4j/smime/exception/package-info.java b/acme4j-smime/src/main/java/org/shredzone/acme4j/smime/exception/package-info.java new file mode 100644 index 00000000..79dc0c40 --- /dev/null +++ b/acme4j-smime/src/main/java/org/shredzone/acme4j/smime/exception/package-info.java @@ -0,0 +1,23 @@ +/* + * acme4j - Java ACME client + * + * Copyright (C) 2022 Richard "Shred" Körber + * http://acme4j.shredzone.org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + */ + +@ReturnValuesAreNonnullByDefault +@DefaultAnnotationForParameters(NonNull.class) +@DefaultAnnotationForFields(NonNull.class) +package org.shredzone.acme4j.smime.exception; + +import edu.umd.cs.findbugs.annotations.DefaultAnnotationForFields; +import edu.umd.cs.findbugs.annotations.DefaultAnnotationForParameters; +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.ReturnValuesAreNonnullByDefault; diff --git a/acme4j-smime/src/test/java/org/shredzone/acme4j/smime/SMIMETests.java b/acme4j-smime/src/test/java/org/shredzone/acme4j/smime/SMIMETests.java index 72a92605..3fc25443 100644 --- a/acme4j-smime/src/test/java/org/shredzone/acme4j/smime/SMIMETests.java +++ b/acme4j-smime/src/test/java/org/shredzone/acme4j/smime/SMIMETests.java @@ -23,6 +23,9 @@ import java.io.Reader; import java.io.UncheckedIOException; import java.nio.charset.StandardCharsets; import java.security.KeyPair; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; import java.util.Properties; import jakarta.mail.Message; @@ -125,4 +128,20 @@ public abstract class SMIMETests { } } + /** + * Reads a certificate from the given resource. + * + * @param name + * Resource name of the certificate + * @return X509Certificate that was read + */ + protected X509Certificate readCertificate(String name) throws IOException { + try (InputStream in = SMIMETests.class.getResourceAsStream("/" + name + ".pem")) { + CertificateFactory cf = CertificateFactory.getInstance("X.509"); + return (X509Certificate) cf.generateCertificate(in); + } catch (CertificateException ex) { + throw new IOException(ex); + } + } + } diff --git a/acme4j-smime/src/test/java/org/shredzone/acme4j/smime/email/EmailProcessorTest.java b/acme4j-smime/src/test/java/org/shredzone/acme4j/smime/email/EmailProcessorTest.java index 5d3f5ff9..fd183aa6 100644 --- a/acme4j-smime/src/test/java/org/shredzone/acme4j/smime/email/EmailProcessorTest.java +++ b/acme4j-smime/src/test/java/org/shredzone/acme4j/smime/email/EmailProcessorTest.java @@ -14,21 +14,27 @@ package org.shredzone.acme4j.smime.email; import static jakarta.mail.Message.RecipientType.TO; -import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.*; import static org.junit.jupiter.api.Assertions.assertThrows; import java.io.IOException; +import java.security.Security; +import java.security.cert.X509Certificate; import java.util.Optional; import jakarta.mail.Message; import jakarta.mail.MessagingException; import jakarta.mail.internet.InternetAddress; +import jakarta.mail.internet.MimeMessage; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.shredzone.acme4j.Identifier; import org.shredzone.acme4j.exception.AcmeProtocolException; import org.shredzone.acme4j.smime.EmailIdentifier; import org.shredzone.acme4j.smime.SMIMETests; import org.shredzone.acme4j.smime.challenge.EmailReply00Challenge; +import org.shredzone.acme4j.smime.exception.AcmeInvalidMessageException; /** * Unit tests for {@link EmailProcessor} and {@link ResponseGenerator}. @@ -40,9 +46,14 @@ public class EmailProcessorTest extends SMIMETests { private final InternetAddress expectedReplyTo = email("acme-validator@example.org"); private final Message message = mockMessage("challenge"); + @BeforeAll + public static void setup() { + Security.addProvider(new BouncyCastleProvider()); + } + @Test - public void testEmailParser() throws MessagingException { - EmailProcessor processor = new EmailProcessor(message); + public void testEmailParser() throws AcmeInvalidMessageException { + EmailProcessor processor = EmailProcessor.plainMessage(message); processor.expectedFrom(expectedFrom); processor.expectedTo(expectedTo); processor.expectedIdentifier(EmailIdentifier.email(expectedTo)); @@ -55,10 +66,93 @@ public class EmailProcessorTest extends SMIMETests { assertThat(processor.getReplyTo()).contains(email("acme-validator@example.org")); } + @Test + public void testValidSignature() throws AcmeInvalidMessageException, IOException { + MimeMessage message = (MimeMessage) mockMessage("valid-mail"); + X509Certificate certificate = readCertificate("valid-signer"); + EmailProcessor processor = EmailProcessor.smimeMessage(message, mailSession, certificate, true); + } + + @Test + public void testInvalidSignature() { + assertThatExceptionOfType(AcmeInvalidMessageException.class) + .isThrownBy(() -> { + MimeMessage message = (MimeMessage) mockMessage("invalid-signed-mail"); + X509Certificate certificate = readCertificate("valid-signer"); + EmailProcessor processor = EmailProcessor.smimeMessage(message, mailSession, certificate, true); + }) + .withMessage("The S/MIME signature is invalid"); + } + + @Test + public void testValidSignatureButNoSAN() { + assertThatExceptionOfType(AcmeInvalidMessageException.class) + .isThrownBy(() -> { + MimeMessage message = (MimeMessage) mockMessage("invalid-nosan"); + X509Certificate certificate = readCertificate("valid-signer-nosan"); + EmailProcessor processor = EmailProcessor.smimeMessage(message, mailSession, certificate, true); + }) + .withMessage("Signing certificate does not provide a rfc822Name subjectAltName"); + } + + @Test + public void testSANDoesNotMatchFrom() { + assertThatExceptionOfType(AcmeInvalidMessageException.class) + .isThrownBy(() -> { + MimeMessage message = (MimeMessage) mockMessage("invalid-cert-mismatch"); + X509Certificate certificate = readCertificate("valid-signer"); + EmailProcessor processor = EmailProcessor.smimeMessage(message, mailSession, certificate, true); + }) + .withMessage("Sender 'different-ca@example.com' was not found in signing certificate"); + } + + @Test + public void testInvalidProtectedFromHeader() { + assertThatExceptionOfType(AcmeInvalidMessageException.class) + .isThrownBy(() -> { + MimeMessage message = (MimeMessage) mockMessage("invalid-protected-mail-from"); + X509Certificate certificate = readCertificate("valid-signer"); + EmailProcessor processor = EmailProcessor.smimeMessage(message, mailSession, certificate, true); + }) + .withMessage("Protected 'From' header does not match envelope header"); + } + + @Test + public void testInvalidProtectedToHeader() { + assertThatExceptionOfType(AcmeInvalidMessageException.class) + .isThrownBy(() -> { + MimeMessage message = (MimeMessage) mockMessage("invalid-protected-mail-to"); + X509Certificate certificate = readCertificate("valid-signer"); + EmailProcessor processor = EmailProcessor.smimeMessage(message, mailSession, certificate, true); + }) + .withMessage("Protected 'To' header does not match envelope header"); + } + + @Test + public void testInvalidProtectedSubjectHeader() { + assertThatExceptionOfType(AcmeInvalidMessageException.class) + .isThrownBy(() -> { + MimeMessage message = (MimeMessage) mockMessage("invalid-protected-mail-subject"); + X509Certificate certificate = readCertificate("valid-signer"); + EmailProcessor processor = EmailProcessor.smimeMessage(message, mailSession, certificate, true); + }) + .withMessage("Protected 'Subject' header does not match envelope header"); + } + + @Test + public void testNonStrictInvalidProtectedSubjectHeader() { + assertThatNoException() + .isThrownBy(() -> { + MimeMessage message = (MimeMessage) mockMessage("invalid-protected-mail-subject"); + X509Certificate certificate = readCertificate("valid-signer"); + EmailProcessor processor = EmailProcessor.smimeMessage(message, mailSession, certificate, false); + }); + } + @Test public void textExpectedFromFails() { assertThrows(AcmeProtocolException.class, () -> { - EmailProcessor processor = new EmailProcessor(message); + EmailProcessor processor = EmailProcessor.plainMessage(message); processor.expectedFrom(expectedTo); }); } @@ -66,7 +160,7 @@ public class EmailProcessorTest extends SMIMETests { @Test public void textExpectedToFails() { assertThrows(AcmeProtocolException.class, () -> { - EmailProcessor processor = new EmailProcessor(message); + EmailProcessor processor = EmailProcessor.plainMessage(message); processor.expectedTo(expectedFrom); }); } @@ -74,7 +168,7 @@ public class EmailProcessorTest extends SMIMETests { @Test public void textExpectedIdentifierFails1() { assertThrows(AcmeProtocolException.class, () -> { - EmailProcessor processor = new EmailProcessor(message); + EmailProcessor processor = EmailProcessor.plainMessage(message); processor.expectedIdentifier(EmailIdentifier.email(expectedFrom)); }); } @@ -82,7 +176,7 @@ public class EmailProcessorTest extends SMIMETests { @Test public void textExpectedIdentifierFails2() { assertThrows(AcmeProtocolException.class, () -> { - EmailProcessor processor = new EmailProcessor(message); + EmailProcessor processor = EmailProcessor.plainMessage(message); processor.expectedIdentifier(Identifier.ip("192.168.0.1")); }); } @@ -90,7 +184,7 @@ public class EmailProcessorTest extends SMIMETests { @Test public void textNoChallengeFails1() { assertThrows(IllegalStateException.class, () -> { - EmailProcessor processor = new EmailProcessor(message); + EmailProcessor processor = EmailProcessor.plainMessage(message); processor.getToken(); }); } @@ -98,7 +192,7 @@ public class EmailProcessorTest extends SMIMETests { @Test public void textNoChallengeFails2() { assertThrows(IllegalStateException.class, () -> { - EmailProcessor processor = new EmailProcessor(message); + EmailProcessor processor = EmailProcessor.plainMessage(message); processor.getAuthorization(); }); } @@ -106,16 +200,16 @@ public class EmailProcessorTest extends SMIMETests { @Test public void textNoChallengeFails3() { assertThrows(IllegalStateException.class, () -> { - EmailProcessor processor = new EmailProcessor(message); + EmailProcessor processor = EmailProcessor.plainMessage(message); processor.respond(); }); } @Test - public void testChallenge() { + public void testChallenge() throws AcmeInvalidMessageException { EmailReply00Challenge challenge = mockChallenge("emailReplyChallenge"); - EmailProcessor processor = new EmailProcessor(message); + EmailProcessor processor = EmailProcessor.plainMessage(message); processor.withChallenge(challenge); assertThat(processor.getToken()).isEqualTo(TOKEN); assertThat(processor.getAuthorization()).isEqualTo(KEY_AUTHORIZATION); @@ -126,16 +220,16 @@ public class EmailProcessorTest extends SMIMETests { public void testChallengeMismatch() { assertThrows(AcmeProtocolException.class, () -> { EmailReply00Challenge challenge = mockChallenge("emailReplyChallengeMismatch"); - EmailProcessor processor = new EmailProcessor(message); + EmailProcessor processor = EmailProcessor.plainMessage(message); processor.withChallenge(challenge); }); } @Test - public void testResponse() throws IOException, MessagingException { + public void testResponse() throws IOException, MessagingException, AcmeInvalidMessageException { EmailReply00Challenge challenge = mockChallenge("emailReplyChallenge"); - Message response = new EmailProcessor(message) + Message response = EmailProcessor.plainMessage(message) .withChallenge(challenge) .respond() .generateResponse(mailSession); @@ -144,10 +238,10 @@ public class EmailProcessorTest extends SMIMETests { } @Test - public void testResponseWithHeaderFooter() throws IOException, MessagingException { + public void testResponseWithHeaderFooter() throws IOException, MessagingException, AcmeInvalidMessageException { EmailReply00Challenge challenge = mockChallenge("emailReplyChallenge"); - Message response = new EmailProcessor(message) + Message response = EmailProcessor.plainMessage(message) .withChallenge(challenge) .respond() .withHeader("This is an introduction.") @@ -161,10 +255,10 @@ public class EmailProcessorTest extends SMIMETests { } @Test - public void testResponseWithCallback() throws IOException, MessagingException { + public void testResponseWithCallback() throws IOException, MessagingException, AcmeInvalidMessageException { EmailReply00Challenge challenge = mockChallenge("emailReplyChallenge"); - Message response = new EmailProcessor(message) + Message response = EmailProcessor.plainMessage(message) .withChallenge(challenge) .respond() .withGenerator((msg, body) -> msg.setContent("Head\r\n" + body + "Foot", "text/plain")) diff --git a/acme4j-smime/src/test/resources/email/invalid-cert-mismatch.eml b/acme4j-smime/src/test/resources/email/invalid-cert-mismatch.eml new file mode 100644 index 00000000..80ec19e7 --- /dev/null +++ b/acme4j-smime/src/test/resources/email/invalid-cert-mismatch.eml @@ -0,0 +1,67 @@ +From: different-ca@example.org +To: recipient@example.org +Subject: ACME: LgYemJLy3F1LDkiJrdIGbEzyFJyOyf6vBdyZ1TG3sME= +Auto-Submitted: auto-generated; type=acme +Message-ID: +MIME-Version: 1.0 +Content-Type: multipart/signed; protocol="application/x-pkcs7-signature"; micalg="sha1"; boundary="----6B6A5C5DBC60D7D16B6C08BF092D4185" + +This is an S/MIME signed message + +------6B6A5C5DBC60D7D16B6C08BF092D4185 +Content-Type: message/RFC822; forwarded=no + +From: different-ca@example.com +To: recipient@example.org +Subject: ACME: LgYemJLy3F1LDkiJrdIGbEzyFJyOyf6vBdyZ1TG3sME= +Message-ID: +MIME-Version: 1.0 +Content-Type: text/plain; charset=utf-8 + +This is an automatically generated ACME challenge. + +------6B6A5C5DBC60D7D16B6C08BF092D4185 +Content-Type: application/x-pkcs7-signature; name="smime.p7s" +Content-Transfer-Encoding: base64 +Content-Disposition: attachment; filename="smime.p7s" + +MIIGzQYJKoZIhvcNAQcCoIIGvjCCBroCAQExCzAJBgUrDgMCGgUAMAsGCSqGSIb3 +DQEHAaCCA/4wggP6MIIC4qADAgECAhQoC/xUcLhcK13sGSiYxuUPf758tDANBgkq +hkiG9w0BAQsFADB8MQswCQYDVQQGEwJYWDESMBAGA1UEBwwJQWNtZSBDaXR5MR4w +HAYDVQQKDBVBY21lIENlcnRpZmljYXRlcyBMdGQxFDASBgNVBAMMC2V4YW1wbGUu +Y29tMSMwIQYJKoZIhvcNAQkBFhR2YWxpZC1jYUBleGFtcGxlLmNvbTAeFw0yMjEx +MDQxMjU1MzlaFw0zMjExMDExMjU1MzlaMHwxCzAJBgNVBAYTAlhYMRIwEAYDVQQH +DAlBY21lIENpdHkxHjAcBgNVBAoMFUFjbWUgQ2VydGlmaWNhdGVzIEx0ZDEUMBIG +A1UEAwwLZXhhbXBsZS5jb20xIzAhBgkqhkiG9w0BCQEWFHZhbGlkLWNhQGV4YW1w +bGUuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzTyW1VRm+mQH +YU5hZWgzgrPRkRzMLlLoCamlRs5DjGf3zIpo9a/17m60YfIXBJruImUBXxa5lp0Z +qaayty+nZOpS0wBZSqTLuslZ0WuyyyW2DEBNO7jLW58cMn8MfAwMYjDcxtubNb7M +AG9iZRj6wn6tKCsXtYUgAIpNhyPPtDuEZ5df1ecOnvlW2vO+MwytM8DLLtwolET/ +tPzOXPHDiyKjij02jyJ1DlZxptKudiKaBeY1WY/W/PpS6fGskTJQc/bZPgE3OP/9 +Y9Y1uzZTulkO3R6MlLNdLam32/ehpJvSWyfSbToyC2ejvaXoRChPFcAmTpqA0HPg +h8sudiS14QIDAQABo3QwcjAdBgNVHQ4EFgQUR0gRYoOCPJYqFiPGwB141VTCXuIw +HwYDVR0jBBgwFoAUR0gRYoOCPJYqFiPGwB141VTCXuIwDwYDVR0TAQH/BAUwAwEB +/zAfBgNVHREEGDAWgRR2YWxpZC1jYUBleGFtcGxlLmNvbTANBgkqhkiG9w0BAQsF +AAOCAQEAJDQsNTWxVLNHBrVl6p6IetzeBt64GoWp6OP5C8/HY3dlznfbrn6ZvWBr +Vsnc7VZ49r8r/QvRKRrljkIQuNwaW/LmRxJ1AVGGiorspmrdz0Lf2WOXnLH/+4lR +q5dar5YmGqi9Mo2j5ALg/2MiO1PonuKs1eX9tLZCCeanXFexU0qaeFZelunJ6UUm +BXpaGO1QfECcNnvarudosbe1ve6YGABn8MpAY+8zdYnYHPB8Pojhzvk7/8PMHoPu +njDb3lZT+b8BDmNz+GISCUSHYkdK2rWh+8wqD3T5rOtzTqAPuSNeDNocUF6wzxem +HWqvP7yM3VoXYvn7FA4NCa9mE3k8MzGCApcwggKTAgEBMIGUMHwxCzAJBgNVBAYT +AlhYMRIwEAYDVQQHDAlBY21lIENpdHkxHjAcBgNVBAoMFUFjbWUgQ2VydGlmaWNh +dGVzIEx0ZDEUMBIGA1UEAwwLZXhhbXBsZS5jb20xIzAhBgkqhkiG9w0BCQEWFHZh +bGlkLWNhQGV4YW1wbGUuY29tAhQoC/xUcLhcK13sGSiYxuUPf758tDAJBgUrDgMC +GgUAoIHYMBgGCSqGSIb3DQEJAzELBgkqhkiG9w0BBwEwHAYJKoZIhvcNAQkFMQ8X +DTIyMTEwNDEzMzE1NVowIwYJKoZIhvcNAQkEMRYEFNE5fZJFFgePdLTkBjs3GMcD +M5UyMHkGCSqGSIb3DQEJDzFsMGowCwYJYIZIAWUDBAEqMAsGCWCGSAFlAwQBFjAL +BglghkgBZQMEAQIwCgYIKoZIhvcNAwcwDgYIKoZIhvcNAwICAgCAMA0GCCqGSIb3 +DQMCAgFAMAcGBSsOAwIHMA0GCCqGSIb3DQMCAgEoMA0GCSqGSIb3DQEBAQUABIIB +AEN+8Cgvfw/72HPSSgJROYqScFrYTCRF7HTAk22zJCVzuh21rbEiPGLJ9Sy4ak/i +BXvXtkX8YJBGuVYrx6QxoF8vmcwlIPVw9Qoc2FevyfRQD19hP7rd7miZ8LWu0B8v +d54mr7aD5zQADLvQGlxjKvCSM3F8HF1KQrRZrfJbEL9NRrgYD7c8ZEAvisaoEfPO +vRjsj9IzKg/RWhOAmh5n591ZNKVb8k0G+5lyCSuP8m/9k0sE705sVrq4sbgIgtFB +HNVLwYvxb88F//rFosFW/njsnlgFx5hjpLbwKu6Du2Sd7L1oye/xUGlKDY8yuy2m +pwKapV2IpD8TjsL7NZ7rJ5U= + +------6B6A5C5DBC60D7D16B6C08BF092D4185-- + diff --git a/acme4j-smime/src/test/resources/email/invalid-nosan.eml b/acme4j-smime/src/test/resources/email/invalid-nosan.eml new file mode 100644 index 00000000..ed879c66 --- /dev/null +++ b/acme4j-smime/src/test/resources/email/invalid-nosan.eml @@ -0,0 +1,66 @@ +From: valid-ca@example.com +To: recipient@example.org +Subject: ACME: LgYemJLy3F1LDkiJrdIGbEzyFJyOyf6vBdyZ1TG3sME= +Auto-Submitted: auto-generated; type=acme +Message-ID: +MIME-Version: 1.0 +Content-Type: multipart/signed; protocol="application/x-pkcs7-signature"; micalg="sha1"; boundary="----47B1F5074B8F7A13042C44F61463F58F" + +This is an S/MIME signed message + +------47B1F5074B8F7A13042C44F61463F58F +Content-Type: message/RFC822; forwarded=no + +From: valid-ca@example.com +To: recipient@example.org +Subject: ACME: LgYemJLy3F1LDkiJrdIGbEzyFJyOyf6vBdyZ1TG3sME= +Message-ID: +MIME-Version: 1.0 +Content-Type: text/plain; charset=utf-8 + +This is an automatically generated ACME challenge. + +------47B1F5074B8F7A13042C44F61463F58F +Content-Type: application/x-pkcs7-signature; name="smime.p7s" +Content-Transfer-Encoding: base64 +Content-Disposition: attachment; filename="smime.p7s" + +MIIGrAYJKoZIhvcNAQcCoIIGnTCCBpkCAQExCzAJBgUrDgMCGgUAMAsGCSqGSIb3 +DQEHAaCCA90wggPZMIICwaADAgECAhRPShCzW2lh0D89RfDJ5mos/gMAJjANBgkq +hkiG9w0BAQsFADB8MQswCQYDVQQGEwJYWDESMBAGA1UEBwwJQWNtZSBDaXR5MR4w +HAYDVQQKDBVBY21lIENlcnRpZmljYXRlcyBMdGQxFDASBgNVBAMMC2V4YW1wbGUu +Y29tMSMwIQYJKoZIhvcNAQkBFhR2YWxpZC1jYUBleGFtcGxlLmNvbTAeFw0yMjEx +MDQxMjU1MzlaFw0zMjExMDExMjU1MzlaMHwxCzAJBgNVBAYTAlhYMRIwEAYDVQQH +DAlBY21lIENpdHkxHjAcBgNVBAoMFUFjbWUgQ2VydGlmaWNhdGVzIEx0ZDEUMBIG +A1UEAwwLZXhhbXBsZS5jb20xIzAhBgkqhkiG9w0BCQEWFHZhbGlkLWNhQGV4YW1w +bGUuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2BDJcOmFx6Tu +W58rpO4FXbvF7ZoSpIOi4YWfSmD7LSHUq+xGx/vodijFT1Y6a74o4d+XeYTH/X4j +FO9QPdzZDuG8t3ziFACctptaFywUjgYYJQGyPmfg3hn1Cz72q4tAqegOEwL78NA2 +YcEd6sx0udAF80C1QHi/kKBMDgj9AOyNyIZ/rBN8CZSkfkpPYWI99Fl/DOuYnr7k +MN1TUWS1906mPqBslh1YVyp6fdGaL6DdlIY+ZE5c9BhST/t+7eLq7fnB5KB+tDvH +D1qnL858K+5Hjfc9MUYTyffDiJaG9zHkEKi3zd0EGcaf1r+lskRqEIOqROjLSDim +T5Q4CuHCYQIDAQABo1MwUTAdBgNVHQ4EFgQURoDGdRoZP6EOfLXTlNxWPDTRmp8w +HwYDVR0jBBgwFoAURoDGdRoZP6EOfLXTlNxWPDTRmp8wDwYDVR0TAQH/BAUwAwEB +/zANBgkqhkiG9w0BAQsFAAOCAQEAvVI9Yj8lL0cvdNY3RD/GQ/tQCGBAzoFTODJU +wn4zE/LfiXfu8SxJhzcpjzKc2j+mxuwGh0OqraIO2FkpO23+X1gCdCt+ClE/6nMs +8UMo4H1wMYYGhjkoLvsH9Ne5N+91PvLQG97LoLsoy+Y95ws23WyqUJ2g7A7Isk3v +7MJZVH2d93hjbtWQ6+3/PP5zJwubEwiDAYvycODfvAig9+0QBIy+uE7XxnEhKxHJ +pvN3p8NmLya7XH3v92N7M6CioyBqw8HL7I5lt5HBqa/U9USVMMmi9v+tFLZYyd7r +7acw6hB7MDcLmtEu08Cgo89K23oTm1JBJZrjZUFbYcYP+fiuizGCApcwggKTAgEB +MIGUMHwxCzAJBgNVBAYTAlhYMRIwEAYDVQQHDAlBY21lIENpdHkxHjAcBgNVBAoM +FUFjbWUgQ2VydGlmaWNhdGVzIEx0ZDEUMBIGA1UEAwwLZXhhbXBsZS5jb20xIzAh +BgkqhkiG9w0BCQEWFHZhbGlkLWNhQGV4YW1wbGUuY29tAhRPShCzW2lh0D89RfDJ +5mos/gMAJjAJBgUrDgMCGgUAoIHYMBgGCSqGSIb3DQEJAzELBgkqhkiG9w0BBwEw +HAYJKoZIhvcNAQkFMQ8XDTIyMTEwNDEzMzE1NVowIwYJKoZIhvcNAQkEMRYEFK+s +B97I9ZGpDGUMWLsE3dCU/pbtMHkGCSqGSIb3DQEJDzFsMGowCwYJYIZIAWUDBAEq +MAsGCWCGSAFlAwQBFjALBglghkgBZQMEAQIwCgYIKoZIhvcNAwcwDgYIKoZIhvcN +AwICAgCAMA0GCCqGSIb3DQMCAgFAMAcGBSsOAwIHMA0GCCqGSIb3DQMCAgEoMA0G +CSqGSIb3DQEBAQUABIIBAEvS2tccMXu/OG8fzgOWsf5MzjBdDD/6doTLuIJS7Ktv +1alpT8ZqSBn4jOgrlM7efTFK8y1vlOdoFZIsfIe+92lclvgHc/2Dw4XB5SswZ59y +fZH+AVtVpzi5oFYiunhBn19vRP9Bri4ma8gCRe7pUwN15Gap8gl3+UQtUY17wcME +H7ALcuG0ETPTxz9p2ueN6FmrthmrDSaZVqW5nyTizgr0zSxicEcwfFz9JZGZFKyp +lPSVrgCwZ1/yaWXlnBXPTdO/DmAvNUAjUk0HZFu+mnelzPPs3c5s4LY3pBNjDPE2 +i2hxgRjca2QMsveYdwZn8I/m1P7yatJ3EozHpO3T4Gc= + +------47B1F5074B8F7A13042C44F61463F58F-- + diff --git a/acme4j-smime/src/test/resources/email/invalid-protected-mail-from.eml b/acme4j-smime/src/test/resources/email/invalid-protected-mail-from.eml new file mode 100644 index 00000000..3b0ace9f --- /dev/null +++ b/acme4j-smime/src/test/resources/email/invalid-protected-mail-from.eml @@ -0,0 +1,67 @@ +From: tampered-ca@example.org +To: recipient@example.org +Subject: ACME: LgYemJLy3F1LDkiJrdIGbEzyFJyOyf6vBdyZ1TG3sME= +Auto-Submitted: auto-generated; type=acme +Message-ID: +MIME-Version: 1.0 +Content-Type: multipart/signed; protocol="application/x-pkcs7-signature"; micalg="sha1"; boundary="----1FD9CF28CC0AD72EF1FF6D0511838F0E" + +This is an S/MIME signed message + +------1FD9CF28CC0AD72EF1FF6D0511838F0E +Content-Type: message/RFC822; forwarded=no + +From: valid-ca@example.com +To: recipient@example.org +Subject: ACME: LgYemJLy3F1LDkiJrdIGbEzyFJyOyf6vBdyZ1TG3sME= +Message-ID: +MIME-Version: 1.0 +Content-Type: text/plain; charset=utf-8 + +This is an automatically generated ACME challenge. + +------1FD9CF28CC0AD72EF1FF6D0511838F0E +Content-Type: application/x-pkcs7-signature; name="smime.p7s" +Content-Transfer-Encoding: base64 +Content-Disposition: attachment; filename="smime.p7s" + +MIIGzQYJKoZIhvcNAQcCoIIGvjCCBroCAQExCzAJBgUrDgMCGgUAMAsGCSqGSIb3 +DQEHAaCCA/4wggP6MIIC4qADAgECAhQoC/xUcLhcK13sGSiYxuUPf758tDANBgkq +hkiG9w0BAQsFADB8MQswCQYDVQQGEwJYWDESMBAGA1UEBwwJQWNtZSBDaXR5MR4w +HAYDVQQKDBVBY21lIENlcnRpZmljYXRlcyBMdGQxFDASBgNVBAMMC2V4YW1wbGUu +Y29tMSMwIQYJKoZIhvcNAQkBFhR2YWxpZC1jYUBleGFtcGxlLmNvbTAeFw0yMjEx +MDQxMjU1MzlaFw0zMjExMDExMjU1MzlaMHwxCzAJBgNVBAYTAlhYMRIwEAYDVQQH +DAlBY21lIENpdHkxHjAcBgNVBAoMFUFjbWUgQ2VydGlmaWNhdGVzIEx0ZDEUMBIG +A1UEAwwLZXhhbXBsZS5jb20xIzAhBgkqhkiG9w0BCQEWFHZhbGlkLWNhQGV4YW1w +bGUuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzTyW1VRm+mQH +YU5hZWgzgrPRkRzMLlLoCamlRs5DjGf3zIpo9a/17m60YfIXBJruImUBXxa5lp0Z +qaayty+nZOpS0wBZSqTLuslZ0WuyyyW2DEBNO7jLW58cMn8MfAwMYjDcxtubNb7M +AG9iZRj6wn6tKCsXtYUgAIpNhyPPtDuEZ5df1ecOnvlW2vO+MwytM8DLLtwolET/ +tPzOXPHDiyKjij02jyJ1DlZxptKudiKaBeY1WY/W/PpS6fGskTJQc/bZPgE3OP/9 +Y9Y1uzZTulkO3R6MlLNdLam32/ehpJvSWyfSbToyC2ejvaXoRChPFcAmTpqA0HPg +h8sudiS14QIDAQABo3QwcjAdBgNVHQ4EFgQUR0gRYoOCPJYqFiPGwB141VTCXuIw +HwYDVR0jBBgwFoAUR0gRYoOCPJYqFiPGwB141VTCXuIwDwYDVR0TAQH/BAUwAwEB +/zAfBgNVHREEGDAWgRR2YWxpZC1jYUBleGFtcGxlLmNvbTANBgkqhkiG9w0BAQsF +AAOCAQEAJDQsNTWxVLNHBrVl6p6IetzeBt64GoWp6OP5C8/HY3dlznfbrn6ZvWBr +Vsnc7VZ49r8r/QvRKRrljkIQuNwaW/LmRxJ1AVGGiorspmrdz0Lf2WOXnLH/+4lR +q5dar5YmGqi9Mo2j5ALg/2MiO1PonuKs1eX9tLZCCeanXFexU0qaeFZelunJ6UUm +BXpaGO1QfECcNnvarudosbe1ve6YGABn8MpAY+8zdYnYHPB8Pojhzvk7/8PMHoPu +njDb3lZT+b8BDmNz+GISCUSHYkdK2rWh+8wqD3T5rOtzTqAPuSNeDNocUF6wzxem +HWqvP7yM3VoXYvn7FA4NCa9mE3k8MzGCApcwggKTAgEBMIGUMHwxCzAJBgNVBAYT +AlhYMRIwEAYDVQQHDAlBY21lIENpdHkxHjAcBgNVBAoMFUFjbWUgQ2VydGlmaWNh +dGVzIEx0ZDEUMBIGA1UEAwwLZXhhbXBsZS5jb20xIzAhBgkqhkiG9w0BCQEWFHZh +bGlkLWNhQGV4YW1wbGUuY29tAhQoC/xUcLhcK13sGSiYxuUPf758tDAJBgUrDgMC +GgUAoIHYMBgGCSqGSIb3DQEJAzELBgkqhkiG9w0BBwEwHAYJKoZIhvcNAQkFMQ8X +DTIyMTEwNDEzMzE1NVowIwYJKoZIhvcNAQkEMRYEFK+sB97I9ZGpDGUMWLsE3dCU +/pbtMHkGCSqGSIb3DQEJDzFsMGowCwYJYIZIAWUDBAEqMAsGCWCGSAFlAwQBFjAL +BglghkgBZQMEAQIwCgYIKoZIhvcNAwcwDgYIKoZIhvcNAwICAgCAMA0GCCqGSIb3 +DQMCAgFAMAcGBSsOAwIHMA0GCCqGSIb3DQMCAgEoMA0GCSqGSIb3DQEBAQUABIIB +AFli04jnSd0d0lnE9GO23qzxI98QIbCz1NU1qk+zDyAhwOWQJdMUfuekk3g4Gn2q +OFzdvlIMpgG2W7AVZlLUQMQjWIoDWkTeqxM8n6StoXcDvlArBHQDbourufFUu7OE +3TVsI6l/jZG3Xub9Uar0S3lF6rQ/A3vl28poRL/EIQye6ypg6UbS/EvkvfbsKUJD +6SpExlwh0R7lk1g/xn3tFVSEAH7VSJYr/8C/Bak06NPOtZZSSeU9ryRzeK/gN3SJ +nrEp+NkTezzApjSnZasrPSbzmRL4+18x3kmAmnwR3aRi7F7KhCs78qPUEVP5XbhX +2hO9RjMy6Uki+R/AG4aempk= + +------1FD9CF28CC0AD72EF1FF6D0511838F0E-- + diff --git a/acme4j-smime/src/test/resources/email/invalid-protected-mail-subject.eml b/acme4j-smime/src/test/resources/email/invalid-protected-mail-subject.eml new file mode 100644 index 00000000..f95bbcb1 --- /dev/null +++ b/acme4j-smime/src/test/resources/email/invalid-protected-mail-subject.eml @@ -0,0 +1,67 @@ +From: valid-ca@example.com +To: recipient@example.org +Subject: ACME: aDiFfErEnTtOkEn +Auto-Submitted: auto-generated; type=acme +Message-ID: +MIME-Version: 1.0 +Content-Type: multipart/signed; protocol="application/x-pkcs7-signature"; micalg="sha1"; boundary="----6E9953AAECB0BDB6F65BCD88900D3E15" + +This is an S/MIME signed message + +------6E9953AAECB0BDB6F65BCD88900D3E15 +Content-Type: message/RFC822; forwarded=no + +From: valid-ca@example.com +To: recipient@example.org +Subject: ACME: LgYemJLy3F1LDkiJrdIGbEzyFJyOyf6vBdyZ1TG3sME= +Message-ID: +MIME-Version: 1.0 +Content-Type: text/plain; charset=utf-8 + +This is an automatically generated ACME challenge. + +------6E9953AAECB0BDB6F65BCD88900D3E15 +Content-Type: application/x-pkcs7-signature; name="smime.p7s" +Content-Transfer-Encoding: base64 +Content-Disposition: attachment; filename="smime.p7s" + +MIIGzQYJKoZIhvcNAQcCoIIGvjCCBroCAQExCzAJBgUrDgMCGgUAMAsGCSqGSIb3 +DQEHAaCCA/4wggP6MIIC4qADAgECAhQoC/xUcLhcK13sGSiYxuUPf758tDANBgkq +hkiG9w0BAQsFADB8MQswCQYDVQQGEwJYWDESMBAGA1UEBwwJQWNtZSBDaXR5MR4w +HAYDVQQKDBVBY21lIENlcnRpZmljYXRlcyBMdGQxFDASBgNVBAMMC2V4YW1wbGUu +Y29tMSMwIQYJKoZIhvcNAQkBFhR2YWxpZC1jYUBleGFtcGxlLmNvbTAeFw0yMjEx +MDQxMjU1MzlaFw0zMjExMDExMjU1MzlaMHwxCzAJBgNVBAYTAlhYMRIwEAYDVQQH +DAlBY21lIENpdHkxHjAcBgNVBAoMFUFjbWUgQ2VydGlmaWNhdGVzIEx0ZDEUMBIG +A1UEAwwLZXhhbXBsZS5jb20xIzAhBgkqhkiG9w0BCQEWFHZhbGlkLWNhQGV4YW1w +bGUuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzTyW1VRm+mQH +YU5hZWgzgrPRkRzMLlLoCamlRs5DjGf3zIpo9a/17m60YfIXBJruImUBXxa5lp0Z +qaayty+nZOpS0wBZSqTLuslZ0WuyyyW2DEBNO7jLW58cMn8MfAwMYjDcxtubNb7M +AG9iZRj6wn6tKCsXtYUgAIpNhyPPtDuEZ5df1ecOnvlW2vO+MwytM8DLLtwolET/ +tPzOXPHDiyKjij02jyJ1DlZxptKudiKaBeY1WY/W/PpS6fGskTJQc/bZPgE3OP/9 +Y9Y1uzZTulkO3R6MlLNdLam32/ehpJvSWyfSbToyC2ejvaXoRChPFcAmTpqA0HPg +h8sudiS14QIDAQABo3QwcjAdBgNVHQ4EFgQUR0gRYoOCPJYqFiPGwB141VTCXuIw +HwYDVR0jBBgwFoAUR0gRYoOCPJYqFiPGwB141VTCXuIwDwYDVR0TAQH/BAUwAwEB +/zAfBgNVHREEGDAWgRR2YWxpZC1jYUBleGFtcGxlLmNvbTANBgkqhkiG9w0BAQsF +AAOCAQEAJDQsNTWxVLNHBrVl6p6IetzeBt64GoWp6OP5C8/HY3dlznfbrn6ZvWBr +Vsnc7VZ49r8r/QvRKRrljkIQuNwaW/LmRxJ1AVGGiorspmrdz0Lf2WOXnLH/+4lR +q5dar5YmGqi9Mo2j5ALg/2MiO1PonuKs1eX9tLZCCeanXFexU0qaeFZelunJ6UUm +BXpaGO1QfECcNnvarudosbe1ve6YGABn8MpAY+8zdYnYHPB8Pojhzvk7/8PMHoPu +njDb3lZT+b8BDmNz+GISCUSHYkdK2rWh+8wqD3T5rOtzTqAPuSNeDNocUF6wzxem +HWqvP7yM3VoXYvn7FA4NCa9mE3k8MzGCApcwggKTAgEBMIGUMHwxCzAJBgNVBAYT +AlhYMRIwEAYDVQQHDAlBY21lIENpdHkxHjAcBgNVBAoMFUFjbWUgQ2VydGlmaWNh +dGVzIEx0ZDEUMBIGA1UEAwwLZXhhbXBsZS5jb20xIzAhBgkqhkiG9w0BCQEWFHZh +bGlkLWNhQGV4YW1wbGUuY29tAhQoC/xUcLhcK13sGSiYxuUPf758tDAJBgUrDgMC +GgUAoIHYMBgGCSqGSIb3DQEJAzELBgkqhkiG9w0BBwEwHAYJKoZIhvcNAQkFMQ8X +DTIyMTEwNDEzMzE1NVowIwYJKoZIhvcNAQkEMRYEFK+sB97I9ZGpDGUMWLsE3dCU +/pbtMHkGCSqGSIb3DQEJDzFsMGowCwYJYIZIAWUDBAEqMAsGCWCGSAFlAwQBFjAL +BglghkgBZQMEAQIwCgYIKoZIhvcNAwcwDgYIKoZIhvcNAwICAgCAMA0GCCqGSIb3 +DQMCAgFAMAcGBSsOAwIHMA0GCCqGSIb3DQMCAgEoMA0GCSqGSIb3DQEBAQUABIIB +AFli04jnSd0d0lnE9GO23qzxI98QIbCz1NU1qk+zDyAhwOWQJdMUfuekk3g4Gn2q +OFzdvlIMpgG2W7AVZlLUQMQjWIoDWkTeqxM8n6StoXcDvlArBHQDbourufFUu7OE +3TVsI6l/jZG3Xub9Uar0S3lF6rQ/A3vl28poRL/EIQye6ypg6UbS/EvkvfbsKUJD +6SpExlwh0R7lk1g/xn3tFVSEAH7VSJYr/8C/Bak06NPOtZZSSeU9ryRzeK/gN3SJ +nrEp+NkTezzApjSnZasrPSbzmRL4+18x3kmAmnwR3aRi7F7KhCs78qPUEVP5XbhX +2hO9RjMy6Uki+R/AG4aempk= + +------6E9953AAECB0BDB6F65BCD88900D3E15-- + diff --git a/acme4j-smime/src/test/resources/email/invalid-protected-mail-to.eml b/acme4j-smime/src/test/resources/email/invalid-protected-mail-to.eml new file mode 100644 index 00000000..46a474cb --- /dev/null +++ b/acme4j-smime/src/test/resources/email/invalid-protected-mail-to.eml @@ -0,0 +1,67 @@ +From: valid-ca@example.com +To: tampered-recipient@example.com +Subject: ACME: LgYemJLy3F1LDkiJrdIGbEzyFJyOyf6vBdyZ1TG3sME= +Auto-Submitted: auto-generated; type=acme +Message-ID: +MIME-Version: 1.0 +Content-Type: multipart/signed; protocol="application/x-pkcs7-signature"; micalg="sha1"; boundary="----2D5F3855936C8172B69EB7BC1C12A23A" + +This is an S/MIME signed message + +------2D5F3855936C8172B69EB7BC1C12A23A +Content-Type: message/RFC822; forwarded=no + +From: valid-ca@example.com +To: recipient@example.org +Subject: ACME: LgYemJLy3F1LDkiJrdIGbEzyFJyOyf6vBdyZ1TG3sME= +Message-ID: +MIME-Version: 1.0 +Content-Type: text/plain; charset=utf-8 + +This is an automatically generated ACME challenge. + +------2D5F3855936C8172B69EB7BC1C12A23A +Content-Type: application/x-pkcs7-signature; name="smime.p7s" +Content-Transfer-Encoding: base64 +Content-Disposition: attachment; filename="smime.p7s" + +MIIGzQYJKoZIhvcNAQcCoIIGvjCCBroCAQExCzAJBgUrDgMCGgUAMAsGCSqGSIb3 +DQEHAaCCA/4wggP6MIIC4qADAgECAhQoC/xUcLhcK13sGSiYxuUPf758tDANBgkq +hkiG9w0BAQsFADB8MQswCQYDVQQGEwJYWDESMBAGA1UEBwwJQWNtZSBDaXR5MR4w +HAYDVQQKDBVBY21lIENlcnRpZmljYXRlcyBMdGQxFDASBgNVBAMMC2V4YW1wbGUu +Y29tMSMwIQYJKoZIhvcNAQkBFhR2YWxpZC1jYUBleGFtcGxlLmNvbTAeFw0yMjEx +MDQxMjU1MzlaFw0zMjExMDExMjU1MzlaMHwxCzAJBgNVBAYTAlhYMRIwEAYDVQQH +DAlBY21lIENpdHkxHjAcBgNVBAoMFUFjbWUgQ2VydGlmaWNhdGVzIEx0ZDEUMBIG +A1UEAwwLZXhhbXBsZS5jb20xIzAhBgkqhkiG9w0BCQEWFHZhbGlkLWNhQGV4YW1w +bGUuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzTyW1VRm+mQH +YU5hZWgzgrPRkRzMLlLoCamlRs5DjGf3zIpo9a/17m60YfIXBJruImUBXxa5lp0Z +qaayty+nZOpS0wBZSqTLuslZ0WuyyyW2DEBNO7jLW58cMn8MfAwMYjDcxtubNb7M +AG9iZRj6wn6tKCsXtYUgAIpNhyPPtDuEZ5df1ecOnvlW2vO+MwytM8DLLtwolET/ +tPzOXPHDiyKjij02jyJ1DlZxptKudiKaBeY1WY/W/PpS6fGskTJQc/bZPgE3OP/9 +Y9Y1uzZTulkO3R6MlLNdLam32/ehpJvSWyfSbToyC2ejvaXoRChPFcAmTpqA0HPg +h8sudiS14QIDAQABo3QwcjAdBgNVHQ4EFgQUR0gRYoOCPJYqFiPGwB141VTCXuIw +HwYDVR0jBBgwFoAUR0gRYoOCPJYqFiPGwB141VTCXuIwDwYDVR0TAQH/BAUwAwEB +/zAfBgNVHREEGDAWgRR2YWxpZC1jYUBleGFtcGxlLmNvbTANBgkqhkiG9w0BAQsF +AAOCAQEAJDQsNTWxVLNHBrVl6p6IetzeBt64GoWp6OP5C8/HY3dlznfbrn6ZvWBr +Vsnc7VZ49r8r/QvRKRrljkIQuNwaW/LmRxJ1AVGGiorspmrdz0Lf2WOXnLH/+4lR +q5dar5YmGqi9Mo2j5ALg/2MiO1PonuKs1eX9tLZCCeanXFexU0qaeFZelunJ6UUm +BXpaGO1QfECcNnvarudosbe1ve6YGABn8MpAY+8zdYnYHPB8Pojhzvk7/8PMHoPu +njDb3lZT+b8BDmNz+GISCUSHYkdK2rWh+8wqD3T5rOtzTqAPuSNeDNocUF6wzxem +HWqvP7yM3VoXYvn7FA4NCa9mE3k8MzGCApcwggKTAgEBMIGUMHwxCzAJBgNVBAYT +AlhYMRIwEAYDVQQHDAlBY21lIENpdHkxHjAcBgNVBAoMFUFjbWUgQ2VydGlmaWNh +dGVzIEx0ZDEUMBIGA1UEAwwLZXhhbXBsZS5jb20xIzAhBgkqhkiG9w0BCQEWFHZh +bGlkLWNhQGV4YW1wbGUuY29tAhQoC/xUcLhcK13sGSiYxuUPf758tDAJBgUrDgMC +GgUAoIHYMBgGCSqGSIb3DQEJAzELBgkqhkiG9w0BBwEwHAYJKoZIhvcNAQkFMQ8X +DTIyMTEwNDEzMzE1NVowIwYJKoZIhvcNAQkEMRYEFK+sB97I9ZGpDGUMWLsE3dCU +/pbtMHkGCSqGSIb3DQEJDzFsMGowCwYJYIZIAWUDBAEqMAsGCWCGSAFlAwQBFjAL +BglghkgBZQMEAQIwCgYIKoZIhvcNAwcwDgYIKoZIhvcNAwICAgCAMA0GCCqGSIb3 +DQMCAgFAMAcGBSsOAwIHMA0GCCqGSIb3DQMCAgEoMA0GCSqGSIb3DQEBAQUABIIB +AFli04jnSd0d0lnE9GO23qzxI98QIbCz1NU1qk+zDyAhwOWQJdMUfuekk3g4Gn2q +OFzdvlIMpgG2W7AVZlLUQMQjWIoDWkTeqxM8n6StoXcDvlArBHQDbourufFUu7OE +3TVsI6l/jZG3Xub9Uar0S3lF6rQ/A3vl28poRL/EIQye6ypg6UbS/EvkvfbsKUJD +6SpExlwh0R7lk1g/xn3tFVSEAH7VSJYr/8C/Bak06NPOtZZSSeU9ryRzeK/gN3SJ +nrEp+NkTezzApjSnZasrPSbzmRL4+18x3kmAmnwR3aRi7F7KhCs78qPUEVP5XbhX +2hO9RjMy6Uki+R/AG4aempk= + +------2D5F3855936C8172B69EB7BC1C12A23A-- + diff --git a/acme4j-smime/src/test/resources/email/invalid-signed-mail.eml b/acme4j-smime/src/test/resources/email/invalid-signed-mail.eml new file mode 100644 index 00000000..f4211d99 --- /dev/null +++ b/acme4j-smime/src/test/resources/email/invalid-signed-mail.eml @@ -0,0 +1,67 @@ +From: valid-ca@example.com +To: recipient@example.org +Subject: ACME: LgYemJLy3F1LDkiJrdIGbEzyFJyOyf6vBdyZ1TG3sME= +Auto-Submitted: auto-generated; type=acme +Message-ID: +MIME-Version: 1.0 +Content-Type: multipart/signed; protocol="application/x-pkcs7-signature"; micalg="sha1"; boundary="----2D188458DC295B22904B7A1FB62F57BF" + +This is an S/MIME signed message + +------2D188458DC295B22904B7A1FB62F57BF +Content-Type: message/RFC822; forwarded=no + +From: valid-ca@example.com +To: recipient@example.org +Subject: ACME: LgYemJLy3F1LDkiJrdIGbEzyFJyOyf6vBdyZ1TG3sME= +Message-ID: +MIME-Version: 1.0 +Content-Type: text/plain; charset=utf-8 + +This is an automatically generated ACME challenge. + +------2D188458DC295B22904B7A1FB62F57BF +Content-Type: application/x-pkcs7-signature; name="smime.p7s" +Content-Transfer-Encoding: base64 +Content-Disposition: attachment; filename="smime.p7s" + +MIIG1QYJKoZIhvcNAQcCoIIGxjCCBsICAQExCzAJBgUrDgMCGgUAMAsGCSqGSIb3 +DQEHAaCCBAQwggQAMIIC6KADAgECAhRuqrQwjQAEKPLiumz639inbqPeJzANBgkq +hkiG9w0BAQsFADB+MQswCQYDVQQGEwJYWDESMBAGA1UEBwwJQWNtZSBDaXR5MR4w +HAYDVQQKDBVFbWNhIENlcnRpZmljYXRlcyBMdGQxFDASBgNVBAMMC2V4YW1wbGUu +Y29tMSUwIwYJKoZIhvcNAQkBFhZpbnZhbGlkLWNhQGV4YW1wbGUuY29tMB4XDTIy +MTEwNDEyNTUzOVoXDTMyMTEwMTEyNTUzOVowfjELMAkGA1UEBhMCWFgxEjAQBgNV +BAcMCUFjbWUgQ2l0eTEeMBwGA1UECgwVRW1jYSBDZXJ0aWZpY2F0ZXMgTHRkMRQw +EgYDVQQDDAtleGFtcGxlLmNvbTElMCMGCSqGSIb3DQEJARYWaW52YWxpZC1jYUBl +eGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMxVUSNR +pIreCKvtK148IWN7MWfK8fymmPOF8oQcFieCC0mfw8efc/4MEVA5qN3avHOXd1RG +VgaR+tM30zRiTLllc6YnPePUPZNSQmJcnXgMlRhmOeCfo2hNglWFBnP/CV29xarP +Cf94DqXGrLZ8L8uGtk/JsNOreced34V4RZ9WvN53HlyiNtEJJLggM17wzJZcV+rQ +7LtsBHZfDOdTScCpEDqLZDmLMVLEBUtrwo5+5mYw4M0PDEP2D4qPux7NAHuaG66F +Zt7mq6DcceG/AneuUN7xOyMQ9x/D3NfiSgXbZeJM+BbE0cT7EY9WdZBtsS6HjJA0 +yt98FgAIDoSzRQcCAwEAAaN2MHQwHQYDVR0OBBYEFB0Xb1ErzzEjzPrJC8PvkAJ3 +qnoGMB8GA1UdIwQYMBaAFB0Xb1ErzzEjzPrJC8PvkAJ3qnoGMA8GA1UdEwEB/wQF +MAMBAf8wIQYDVR0RBBowGIEWaW52YWxpZC1jYUBleGFtcGxlLmNvbTANBgkqhkiG +9w0BAQsFAAOCAQEAnwFh8H7lwMWrHSceM6MVt+5M0yVX/r6K5YWGI/AaFG2Q5jkz +6yIeESgiXukza4oKY1I1clZwWus9fnrwn+AWbtvKbGLklFWCUB60fx82dZwoO14Q +Tm3GX6wwC0Y5eFYiXwEJ4gnazBWEWscp4E94AKqr1EYuOI9sR22l/rNtANrEiVsT +P4+kUgLEr9Y5infYglXQMjDVfNXRSETBnx4a5Fljd7pSD4e7H19eMiByd78q98ze +t0g2anl2cJbdM6cgu5iyAgS3BgMrFMnd8m7KkZwum+tslNWA1tbDGK0AWH7ztjh3 +orifkxq2Hw6tdypZoWoLrwSEDEvNIy8+sQjUOTGCApkwggKVAgEBMIGWMH4xCzAJ +BgNVBAYTAlhYMRIwEAYDVQQHDAlBY21lIENpdHkxHjAcBgNVBAoMFUVtY2EgQ2Vy +dGlmaWNhdGVzIEx0ZDEUMBIGA1UEAwwLZXhhbXBsZS5jb20xJTAjBgkqhkiG9w0B +CQEWFmludmFsaWQtY2FAZXhhbXBsZS5jb20CFG6qtDCNAAQo8uK6bPrf2Kduo94n +MAkGBSsOAwIaBQCggdgwGAYJKoZIhvcNAQkDMQsGCSqGSIb3DQEHATAcBgkqhkiG +9w0BCQUxDxcNMjIxMTA0MTMzMTU1WjAjBgkqhkiG9w0BCQQxFgQUr6wH3sj1kakM +ZQxYuwTd0JT+lu0weQYJKoZIhvcNAQkPMWwwajALBglghkgBZQMEASowCwYJYIZI +AWUDBAEWMAsGCWCGSAFlAwQBAjAKBggqhkiG9w0DBzAOBggqhkiG9w0DAgICAIAw +DQYIKoZIhvcNAwICAUAwBwYFKw4DAgcwDQYIKoZIhvcNAwICASgwDQYJKoZIhvcN +AQEBBQAEggEAdZ4XoWViOrqOd4kWZVD+12GXJ7NcCWHOiTdXZ0S/3TKwwTPwQ0f0 +XZmk8iJwcfXqAv48ITK+yh4jk8urtrjS3xohHGxVonifbgxoLm/yHqA13D4F0M9q +r0jfmXAWfH1HDk6AtZA5c1IJVJNMcVcLHkib232FgpicgPEZNWvRr8zHCNN3dymF +Qme9h2BqHxy2+nX96BBEiRMImG9Z9G4+sOqpwiDTNgzr7nFtldKtjiV/GarBeteZ +nx4QcHXY307ydLDrh2JzLK/+LZEhTMBQ2rWGfAo/owDGi/Pal//SgqQWFy3luJp2 +8WG34Z4CnG514YmRzN2Z1V3fvreRInllkQ== + +------2D188458DC295B22904B7A1FB62F57BF-- + diff --git a/acme4j-smime/src/test/resources/email/valid-mail.eml b/acme4j-smime/src/test/resources/email/valid-mail.eml new file mode 100644 index 00000000..80e60279 --- /dev/null +++ b/acme4j-smime/src/test/resources/email/valid-mail.eml @@ -0,0 +1,67 @@ +From: valid-ca@example.com +To: recipient@example.org +Subject: ACME: LgYemJLy3F1LDkiJrdIGbEzyFJyOyf6vBdyZ1TG3sME= +Auto-Submitted: auto-generated; type=acme +Message-ID: +MIME-Version: 1.0 +Content-Type: multipart/signed; protocol="application/x-pkcs7-signature"; micalg="sha1"; boundary="----163CF1BA3ECF9F288779BFBE9EF3E10C" + +This is an S/MIME signed message + +------163CF1BA3ECF9F288779BFBE9EF3E10C +Content-Type: message/RFC822; forwarded=no + +From: valid-ca@example.com +To: recipient@example.org +Subject: ACME: LgYemJLy3F1LDkiJrdIGbEzyFJyOyf6vBdyZ1TG3sME= +Message-ID: +MIME-Version: 1.0 +Content-Type: text/plain; charset=utf-8 + +This is an automatically generated ACME challenge. + +------163CF1BA3ECF9F288779BFBE9EF3E10C +Content-Type: application/x-pkcs7-signature; name="smime.p7s" +Content-Transfer-Encoding: base64 +Content-Disposition: attachment; filename="smime.p7s" + +MIIGzQYJKoZIhvcNAQcCoIIGvjCCBroCAQExCzAJBgUrDgMCGgUAMAsGCSqGSIb3 +DQEHAaCCA/4wggP6MIIC4qADAgECAhQoC/xUcLhcK13sGSiYxuUPf758tDANBgkq +hkiG9w0BAQsFADB8MQswCQYDVQQGEwJYWDESMBAGA1UEBwwJQWNtZSBDaXR5MR4w +HAYDVQQKDBVBY21lIENlcnRpZmljYXRlcyBMdGQxFDASBgNVBAMMC2V4YW1wbGUu +Y29tMSMwIQYJKoZIhvcNAQkBFhR2YWxpZC1jYUBleGFtcGxlLmNvbTAeFw0yMjEx +MDQxMjU1MzlaFw0zMjExMDExMjU1MzlaMHwxCzAJBgNVBAYTAlhYMRIwEAYDVQQH +DAlBY21lIENpdHkxHjAcBgNVBAoMFUFjbWUgQ2VydGlmaWNhdGVzIEx0ZDEUMBIG +A1UEAwwLZXhhbXBsZS5jb20xIzAhBgkqhkiG9w0BCQEWFHZhbGlkLWNhQGV4YW1w +bGUuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzTyW1VRm+mQH +YU5hZWgzgrPRkRzMLlLoCamlRs5DjGf3zIpo9a/17m60YfIXBJruImUBXxa5lp0Z +qaayty+nZOpS0wBZSqTLuslZ0WuyyyW2DEBNO7jLW58cMn8MfAwMYjDcxtubNb7M +AG9iZRj6wn6tKCsXtYUgAIpNhyPPtDuEZ5df1ecOnvlW2vO+MwytM8DLLtwolET/ +tPzOXPHDiyKjij02jyJ1DlZxptKudiKaBeY1WY/W/PpS6fGskTJQc/bZPgE3OP/9 +Y9Y1uzZTulkO3R6MlLNdLam32/ehpJvSWyfSbToyC2ejvaXoRChPFcAmTpqA0HPg +h8sudiS14QIDAQABo3QwcjAdBgNVHQ4EFgQUR0gRYoOCPJYqFiPGwB141VTCXuIw +HwYDVR0jBBgwFoAUR0gRYoOCPJYqFiPGwB141VTCXuIwDwYDVR0TAQH/BAUwAwEB +/zAfBgNVHREEGDAWgRR2YWxpZC1jYUBleGFtcGxlLmNvbTANBgkqhkiG9w0BAQsF +AAOCAQEAJDQsNTWxVLNHBrVl6p6IetzeBt64GoWp6OP5C8/HY3dlznfbrn6ZvWBr +Vsnc7VZ49r8r/QvRKRrljkIQuNwaW/LmRxJ1AVGGiorspmrdz0Lf2WOXnLH/+4lR +q5dar5YmGqi9Mo2j5ALg/2MiO1PonuKs1eX9tLZCCeanXFexU0qaeFZelunJ6UUm +BXpaGO1QfECcNnvarudosbe1ve6YGABn8MpAY+8zdYnYHPB8Pojhzvk7/8PMHoPu +njDb3lZT+b8BDmNz+GISCUSHYkdK2rWh+8wqD3T5rOtzTqAPuSNeDNocUF6wzxem +HWqvP7yM3VoXYvn7FA4NCa9mE3k8MzGCApcwggKTAgEBMIGUMHwxCzAJBgNVBAYT +AlhYMRIwEAYDVQQHDAlBY21lIENpdHkxHjAcBgNVBAoMFUFjbWUgQ2VydGlmaWNh +dGVzIEx0ZDEUMBIGA1UEAwwLZXhhbXBsZS5jb20xIzAhBgkqhkiG9w0BCQEWFHZh +bGlkLWNhQGV4YW1wbGUuY29tAhQoC/xUcLhcK13sGSiYxuUPf758tDAJBgUrDgMC +GgUAoIHYMBgGCSqGSIb3DQEJAzELBgkqhkiG9w0BBwEwHAYJKoZIhvcNAQkFMQ8X +DTIyMTEwNDEzMzE1NVowIwYJKoZIhvcNAQkEMRYEFK+sB97I9ZGpDGUMWLsE3dCU +/pbtMHkGCSqGSIb3DQEJDzFsMGowCwYJYIZIAWUDBAEqMAsGCWCGSAFlAwQBFjAL +BglghkgBZQMEAQIwCgYIKoZIhvcNAwcwDgYIKoZIhvcNAwICAgCAMA0GCCqGSIb3 +DQMCAgFAMAcGBSsOAwIHMA0GCCqGSIb3DQMCAgEoMA0GCSqGSIb3DQEBAQUABIIB +AFli04jnSd0d0lnE9GO23qzxI98QIbCz1NU1qk+zDyAhwOWQJdMUfuekk3g4Gn2q +OFzdvlIMpgG2W7AVZlLUQMQjWIoDWkTeqxM8n6StoXcDvlArBHQDbourufFUu7OE +3TVsI6l/jZG3Xub9Uar0S3lF6rQ/A3vl28poRL/EIQye6ypg6UbS/EvkvfbsKUJD +6SpExlwh0R7lk1g/xn3tFVSEAH7VSJYr/8C/Bak06NPOtZZSSeU9ryRzeK/gN3SJ +nrEp+NkTezzApjSnZasrPSbzmRL4+18x3kmAmnwR3aRi7F7KhCs78qPUEVP5XbhX +2hO9RjMy6Uki+R/AG4aempk= + +------163CF1BA3ECF9F288779BFBE9EF3E10C-- + diff --git a/acme4j-smime/src/test/resources/invalid-signer-privkey.pem b/acme4j-smime/src/test/resources/invalid-signer-privkey.pem new file mode 100644 index 00000000..feb50348 --- /dev/null +++ b/acme4j-smime/src/test/resources/invalid-signer-privkey.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDMVVEjUaSK3gir +7StePCFjezFnyvH8ppjzhfKEHBYnggtJn8PHn3P+DBFQOajd2rxzl3dURlYGkfrT +N9M0Yky5ZXOmJz3j1D2TUkJiXJ14DJUYZjngn6NoTYJVhQZz/wldvcWqzwn/eA6l +xqy2fC/LhrZPybDTq3nHnd+FeEWfVrzedx5cojbRCSS4IDNe8MyWXFfq0Oy7bAR2 +XwznU0nAqRA6i2Q5izFSxAVLa8KOfuZmMODNDwxD9g+Kj7sezQB7mhuuhWbe5qug +3HHhvwJ3rlDe8TsjEPcfw9zX4koF22XiTPgWxNHE+xGPVnWQbbEuh4yQNMrffBYA +CA6Es0UHAgMBAAECggEAByliW6OL6dYYZbY9U+M1pF/3/lRNoPZR3A8wzdKSMDZN +oPn5ibCcByZzIOW0dnopKr//TbPdZgON0ANf4rEjUUguAn/Tmn2g3t3+N6ZZWpDO +VPmYQ7g0qP42eDreXAhvUprJJ9Bz4EFb+hF5kjfOEQsarrc5/GFBNm7hG7N4dTos +ANLHDVt9p++40mBC9UnFTI9fghYqqsBipI2hMcVtLlfsTyhfdYk2atC0YwWH2g7H +nyFsfrgwNlza5iAKuQn1vGhM6nigGBiDfHifNoTHZPhIcgHRwPvZWCpxcAt8wPw1 +4blqZPRqmbOYQHisXVSbKJbgN8zZiZB2j/SSDv9IyQKBgQDqpiSaiBZ+bUfy5M07 +mSClnHR8/zulR7NuSWZ5C92w2EFYI/OkSwKeshKdmafCz9VeVkeCUjjRkAGOF2pr +I1icdY0XDt/Pd+jQzDqTtiUwwMOLRyvV437nCsMer++LbKRblEL+uSLCPhV2dnyx +GUXj9JJhnGeZ+xKH20cE1dr2NQKBgQDe7QG6UGrXUzfN2BttvJfOAKETtVfl1Mm0 +uEC2skPlY1KVFYOCMuOFXLHwlc0KYNzVh5qBH+3dPyX1zjTurXmnaOcW0LxgJ4JN +vKiXDLrt8HYJqANjYCh7vaZmuZ8RduR4iS+E+JG6JLAdfGIzPvzzUgbo5hXuFnQE +dN8gsJcFywKBgAF24fmY6dMGKZHJfcJmdT6zWELDcQLaDLOef6Y3vb1xzA6ZwtZ+ +pViKMfWL1PExTNqW3UFh8/rS1D+nw8FBajcnwKapMBpiXDCZZbAwTdEdEttWqV5f +WhZlCcyyOmN7XRc5OKXQT/g4XPftS1/rkXUXvKYhTMA4QehZJPtRvlkVAoGBAMS+ +h/fXYXQIjget4wdGmvPEumSad6jv09UbiIG1cxbQQeIxyo7uOr9IwAKFMyElu8D4 +nPO5KkVJpkb6Ztz/XY7SlqEcOCTkuavCBUjKg2/b+VEsZ1EdXJ1ZE7M1v526QInh +CX9hobuXBZgAXuq7fKOCkXabGl+2kU4dl49SSvdhAoGAH06W9bZUiqkLIzv+LizW +rVP9fN4A8EGNtdWVrVD0Ql0hh/PBqGiiVwg47LAVuvpIc9kDDQM/5A6tF7aOGRDR +k5ZDIrnWmrGQcEMTcisc5OwPnNjLVzV9r0swmeWcqZDrfgxeKpG7vdRaQdR++0FU +1zaGc8HFGtTeJw6f6ZYttg8= +-----END PRIVATE KEY----- diff --git a/acme4j-smime/src/test/resources/invalid-signer.pem b/acme4j-smime/src/test/resources/invalid-signer.pem new file mode 100644 index 00000000..cb0131ed --- /dev/null +++ b/acme4j-smime/src/test/resources/invalid-signer.pem @@ -0,0 +1,24 @@ +-----BEGIN CERTIFICATE----- +MIIEADCCAuigAwIBAgIUbqq0MI0ABCjy4rps+t/Yp26j3icwDQYJKoZIhvcNAQEL +BQAwfjELMAkGA1UEBhMCWFgxEjAQBgNVBAcMCUFjbWUgQ2l0eTEeMBwGA1UECgwV +RW1jYSBDZXJ0aWZpY2F0ZXMgTHRkMRQwEgYDVQQDDAtleGFtcGxlLmNvbTElMCMG +CSqGSIb3DQEJARYWaW52YWxpZC1jYUBleGFtcGxlLmNvbTAeFw0yMjExMDQxMjU1 +MzlaFw0zMjExMDExMjU1MzlaMH4xCzAJBgNVBAYTAlhYMRIwEAYDVQQHDAlBY21l +IENpdHkxHjAcBgNVBAoMFUVtY2EgQ2VydGlmaWNhdGVzIEx0ZDEUMBIGA1UEAwwL +ZXhhbXBsZS5jb20xJTAjBgkqhkiG9w0BCQEWFmludmFsaWQtY2FAZXhhbXBsZS5j +b20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDMVVEjUaSK3gir7Ste +PCFjezFnyvH8ppjzhfKEHBYnggtJn8PHn3P+DBFQOajd2rxzl3dURlYGkfrTN9M0 +Yky5ZXOmJz3j1D2TUkJiXJ14DJUYZjngn6NoTYJVhQZz/wldvcWqzwn/eA6lxqy2 +fC/LhrZPybDTq3nHnd+FeEWfVrzedx5cojbRCSS4IDNe8MyWXFfq0Oy7bAR2Xwzn +U0nAqRA6i2Q5izFSxAVLa8KOfuZmMODNDwxD9g+Kj7sezQB7mhuuhWbe5qug3HHh +vwJ3rlDe8TsjEPcfw9zX4koF22XiTPgWxNHE+xGPVnWQbbEuh4yQNMrffBYACA6E +s0UHAgMBAAGjdjB0MB0GA1UdDgQWBBQdF29RK88xI8z6yQvD75ACd6p6BjAfBgNV +HSMEGDAWgBQdF29RK88xI8z6yQvD75ACd6p6BjAPBgNVHRMBAf8EBTADAQH/MCEG +A1UdEQQaMBiBFmludmFsaWQtY2FAZXhhbXBsZS5jb20wDQYJKoZIhvcNAQELBQAD +ggEBAJ8BYfB+5cDFqx0nHjOjFbfuTNMlV/6+iuWFhiPwGhRtkOY5M+siHhEoIl7p +M2uKCmNSNXJWcFrrPX568J/gFm7bymxi5JRVglAetH8fNnWcKDteEE5txl+sMAtG +OXhWIl8BCeIJ2swVhFrHKeBPeACqq9RGLjiPbEdtpf6zbQDaxIlbEz+PpFICxK/W +OYp32IJV0DIw1XzV0UhEwZ8eGuRZY3e6Ug+Hux9fXjIgcne/KvfM3rdINmp5dnCW +3TOnILuYsgIEtwYDKxTJ3fJuypGcLpvrbJTVgNbWwxitAFh+87Y4d6K4n5Math8O +rXcqWaFqC68EhAxLzSMvPrEI1Dk= +-----END CERTIFICATE----- diff --git a/acme4j-smime/src/test/resources/valid-signer-nosan-privkey.pem b/acme4j-smime/src/test/resources/valid-signer-nosan-privkey.pem new file mode 100644 index 00000000..561dbbc3 --- /dev/null +++ b/acme4j-smime/src/test/resources/valid-signer-nosan-privkey.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDYEMlw6YXHpO5b +nyuk7gVdu8XtmhKkg6LhhZ9KYPstIdSr7EbH++h2KMVPVjprvijh35d5hMf9fiMU +71A93NkO4by3fOIUAJy2m1oXLBSOBhglAbI+Z+DeGfULPvari0Cp6A4TAvvw0DZh +wR3qzHS50AXzQLVAeL+QoEwOCP0A7I3Ihn+sE3wJlKR+Sk9hYj30WX8M65ievuQw +3VNRZLX3TqY+oGyWHVhXKnp90ZovoN2Uhj5kTlz0GFJP+37t4urt+cHkoH60O8cP +Wqcvznwr7keN9z0xRhPJ98OIlob3MeQQqLfN3QQZxp/Wv6WyRGoQg6pE6MtIOKZP +lDgK4cJhAgMBAAECggEAEZOKA4KnpJY84pyn5P6M1rNt9jZkolfoAd8IFnmdrS31 +mje6CU4reqM1686IqZeaRUeWT6cWyr7+VRdjqGilCqIf4zBIRtbG6M7p7P0jveru +f2IsMQnrv72OUsggMlO9YqTzMiY5vvz9E4YtbBqOO0haF4/xvqkj8jyr+y9Nf4vY +Yag2EUddUX2Giqln97aE8G2O8+LJzXTIk1K15DfyBcq1kkye8OSkjsnLybP4wo+v +uRSDbReF//j+MDepP6IZNPBVyEDyjaXFrNmVb+USyHtzKXwl/GwoYjN7gtOtqc1V +8vVj1H6+RJK1E67ORLctzzMUZfG6vDB2UZ+vv0lqnQKBgQD7sYWfq1rMzUNhVX4D +ef0AOoQlBpnOiwoc+7JEXoZoKBw5xrTAxsKRtJFSgIYDYbfi2y3XLu5IO69nkao6 +EqArzaGn+Uc5JcCw4GVVoe6q6W4SwTGC690d/D7R2h24CYjqrIXkFwtvvM6f+5gl +Vu8da1eXbH73JAEigkKtrvQqVQKBgQDbwzTL6E6hY6DXAxsWzSp1d1n/SK6NdByX +2yfCQRAQVX6KPVo8Gw1ZGH3IJ/KNVL/TXzDV3NfY6cYBbydcu0/EcVBS11+3KhT7 +uB+Y6T3RThwzRDRBlaeJtUmN63JWmCOBiMIRPmJU2uxS3JzNMqC5QekkjRb/7S2J +300hAdlb3QKBgGkt3zRBTFmHcZ/sNRPI15RP38cFQiMQ8XH5MJ7njW1bTahLRF/G +76op9gyvDtG89TZE95wTzZm772ntcmCARhToAqUKQ9w6zZJcw5wMZotfrxMBTupy +HF4aejoB1yeAPIos/Gq7wpi4IvSyE/uOn7AAmoL54PjwP9Um8Cxaj0hdAoGBALQf +p6KJ4gj989LHxOhHeUmWbbmEBS4DwXvmMQxS76uzp2f/KXqiYappHI91zqRwllnV +Z92iiXhNA/Ig/Q5QqOzGQ6Piy50BbPl0zNE0O2rWrt6GRJ6M3ylL4eHk3W6EfHWr +dgVUMJyEY7b3A75chMfTchh3XCaga/bZhApNza4xAoGAZF1mFIowaUog23TWiann +ZuCrZObtd2bk4LdtUYSAs2oUFSEWOR4d/av+eb68yf+n69gk1MrP4BA3hyQOX1M9 +Cz/9x/1Tewytywi36noxlgvTfAe3U93LxGjob4z2bsGQ0Xt8q4ngIrEvDeBQa0cq +bUOxUekM/49+AAToLiTrZhM= +-----END PRIVATE KEY----- diff --git a/acme4j-smime/src/test/resources/valid-signer-nosan.pem b/acme4j-smime/src/test/resources/valid-signer-nosan.pem new file mode 100644 index 00000000..16743a19 --- /dev/null +++ b/acme4j-smime/src/test/resources/valid-signer-nosan.pem @@ -0,0 +1,23 @@ +-----BEGIN CERTIFICATE----- +MIID2TCCAsGgAwIBAgIUT0oQs1tpYdA/PUXwyeZqLP4DACYwDQYJKoZIhvcNAQEL +BQAwfDELMAkGA1UEBhMCWFgxEjAQBgNVBAcMCUFjbWUgQ2l0eTEeMBwGA1UECgwV +QWNtZSBDZXJ0aWZpY2F0ZXMgTHRkMRQwEgYDVQQDDAtleGFtcGxlLmNvbTEjMCEG +CSqGSIb3DQEJARYUdmFsaWQtY2FAZXhhbXBsZS5jb20wHhcNMjIxMTA0MTI1NTM5 +WhcNMzIxMTAxMTI1NTM5WjB8MQswCQYDVQQGEwJYWDESMBAGA1UEBwwJQWNtZSBD +aXR5MR4wHAYDVQQKDBVBY21lIENlcnRpZmljYXRlcyBMdGQxFDASBgNVBAMMC2V4 +YW1wbGUuY29tMSMwIQYJKoZIhvcNAQkBFhR2YWxpZC1jYUBleGFtcGxlLmNvbTCC +ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANgQyXDphcek7lufK6TuBV27 +xe2aEqSDouGFn0pg+y0h1KvsRsf76HYoxU9WOmu+KOHfl3mEx/1+IxTvUD3c2Q7h +vLd84hQAnLabWhcsFI4GGCUBsj5n4N4Z9Qs+9quLQKnoDhMC+/DQNmHBHerMdLnQ +BfNAtUB4v5CgTA4I/QDsjciGf6wTfAmUpH5KT2FiPfRZfwzrmJ6+5DDdU1FktfdO +pj6gbJYdWFcqen3Rmi+g3ZSGPmROXPQYUk/7fu3i6u35weSgfrQ7xw9apy/OfCvu +R433PTFGE8n3w4iWhvcx5BCot83dBBnGn9a/pbJEahCDqkToy0g4pk+UOArhwmEC +AwEAAaNTMFEwHQYDVR0OBBYEFEaAxnUaGT+hDny105TcVjw00ZqfMB8GA1UdIwQY +MBaAFEaAxnUaGT+hDny105TcVjw00ZqfMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZI +hvcNAQELBQADggEBAL1SPWI/JS9HL3TWN0Q/xkP7UAhgQM6BUzgyVMJ+MxPy34l3 +7vEsSYc3KY8ynNo/psbsBodDqq2iDthZKTtt/l9YAnQrfgpRP+pzLPFDKOB9cDGG +BoY5KC77B/TXuTfvdT7y0Bvey6C7KMvmPecLNt1sqlCdoOwOyLJN7+zCWVR9nfd4 +Y27VkOvt/zz+cycLmxMIgwGL8nDg37wIoPftEASMvrhO18ZxISsRyabzd6fDZi8m +u1x97/djezOgoqMgasPBy+yOZbeRwamv1PVElTDJovb/rRS2WMne6+2nMOoQezA3 +C5rRLtPAoKPPStt6E5tSQSWa42VBW2HGD/n4ros= +-----END CERTIFICATE----- diff --git a/acme4j-smime/src/test/resources/valid-signer-privkey.pem b/acme4j-smime/src/test/resources/valid-signer-privkey.pem new file mode 100644 index 00000000..1fc1782c --- /dev/null +++ b/acme4j-smime/src/test/resources/valid-signer-privkey.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDNPJbVVGb6ZAdh +TmFlaDOCs9GRHMwuUugJqaVGzkOMZ/fMimj1r/XubrRh8hcEmu4iZQFfFrmWnRmp +prK3L6dk6lLTAFlKpMu6yVnRa7LLJbYMQE07uMtbnxwyfwx8DAxiMNzG25s1vswA +b2JlGPrCfq0oKxe1hSAAik2HI8+0O4Rnl1/V5w6e+Vba874zDK0zwMsu3CiURP+0 +/M5c8cOLIqOKPTaPInUOVnGm0q52IpoF5jVZj9b8+lLp8ayRMlBz9tk+ATc4//1j +1jW7NlO6WQ7dHoyUs10tqbfb96Gkm9JbJ9JtOjILZ6O9pehEKE8VwCZOmoDQc+CH +yy52JLXhAgMBAAECggEAT/IIjSvV+zYou8o03TQEUKbr/Ls7e9X2pgDrrROes1wy +Zf4KWZ3Dzi9YW4jaV4RkO4idyqUHAPjMLM4O8pWA/qnaPm/12EIuS+Gv94gcus5D +Ri1sCFX4/QUTDkZ4Hf/xePQwo9Oad4qNW6QHr3rV/xoqKCn1D9O9/gfhoEEeYMVV +nPdHlRR1QPcbptsBGkMYrZ8LwjQb8rMvNTK8HRhc4cKrkobEFvwXcWNO9aIlLveP +Q58TlQUijeu4eZPM9c3vjl8rM7Oic/ftuU6jmt9IayAVoYblw7yzeEk3lnBlPzME +e01hr+QD6ZJiquO1stdm6a7TJv15lAghhTBysy7oawKBgQDcY+PBZByHOZ5GzDBb +fwcScm/RM5zMfDJKrYLNehawJ5egyUOeRm+Ym8d7PpCOWrHLURLpxak2Nrsmivf8 +g1OYAFnWWQm6j3GmlnEspp65n/UVPHETq7tvqulOHmvcOVROsSgHWu8JMgk8mlK1 +kEKlw3y5yzIB7SdyUhCjY5UMGwKBgQDuZeWfg44qvi9o3bb0zbjAHgh0hCQjHy/G +NVcaVXx06j0a1tAFyESmJqtCEbScrInZnrmpXmTvX5UQczWHi4RYMI8NNFmf3+i0 +XHxWICFrQhdkxppvrEJeXbdfws+UJT8A9K8kokYRO3WyDzrngddfvCbYgHQjMly+ +3Ke8oS2tswKBgQC0sf2ZoSA2ysn3mBCJ5AODX2pIZv3HNojxa4OUPuZ9NWj/fiS/ +j1aOFCMg7DIPVVLytR1BqDtNZOBbAJPEaFRQivEdalEssdFn2W8fQdlfrkN+TtkT +XLlIHCQ/VXfvzt1Ny7hbF3Zm3qxuEMWBca8DQ91uY6gzpiKye5CCtfINQwKBgQDh +EPIoFlsxnzvDFQ6VL2MsfS4eUmKLhfXkepcxFWPaPQpTPFpIGzo0Ym1sgqqw/3Nl +MKS3cZZ5JxPj4+C1htH7MFzdan7yoMFhBa+c39itGkhbq+RBaa9+x5tHnPO8OS2y +CU8QluLvgeMrp5VE2yAqEcfavernD7TfvBHf04r8YQKBgCHRqnBwHOGHiWyy/Wdu +ySxqN1hfJWBdKRbtxTj5uCKp/orNtiRVB4GC1ZqhCJ38rZwMSSbp00o9nyDXlRfP +jTo2zHrxtwem5hB31cZalPt4twJ5SofmNLDngz7UxwpTs8jWaAf8yAtRgdxWbYRg +bRbZ+LceWQUl8T/RC7UQpy0Y +-----END PRIVATE KEY----- diff --git a/acme4j-smime/src/test/resources/valid-signer.pem b/acme4j-smime/src/test/resources/valid-signer.pem new file mode 100644 index 00000000..3bfbf6ac --- /dev/null +++ b/acme4j-smime/src/test/resources/valid-signer.pem @@ -0,0 +1,24 @@ +-----BEGIN CERTIFICATE----- +MIID+jCCAuKgAwIBAgIUKAv8VHC4XCtd7BkomMblD3++fLQwDQYJKoZIhvcNAQEL +BQAwfDELMAkGA1UEBhMCWFgxEjAQBgNVBAcMCUFjbWUgQ2l0eTEeMBwGA1UECgwV +QWNtZSBDZXJ0aWZpY2F0ZXMgTHRkMRQwEgYDVQQDDAtleGFtcGxlLmNvbTEjMCEG +CSqGSIb3DQEJARYUdmFsaWQtY2FAZXhhbXBsZS5jb20wHhcNMjIxMTA0MTI1NTM5 +WhcNMzIxMTAxMTI1NTM5WjB8MQswCQYDVQQGEwJYWDESMBAGA1UEBwwJQWNtZSBD +aXR5MR4wHAYDVQQKDBVBY21lIENlcnRpZmljYXRlcyBMdGQxFDASBgNVBAMMC2V4 +YW1wbGUuY29tMSMwIQYJKoZIhvcNAQkBFhR2YWxpZC1jYUBleGFtcGxlLmNvbTCC +ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAM08ltVUZvpkB2FOYWVoM4Kz +0ZEczC5S6AmppUbOQ4xn98yKaPWv9e5utGHyFwSa7iJlAV8WuZadGammsrcvp2Tq +UtMAWUqky7rJWdFrsssltgxATTu4y1ufHDJ/DHwMDGIw3MbbmzW+zABvYmUY+sJ+ +rSgrF7WFIACKTYcjz7Q7hGeXX9XnDp75VtrzvjMMrTPAyy7cKJRE/7T8zlzxw4si +o4o9No8idQ5WcabSrnYimgXmNVmP1vz6UunxrJEyUHP22T4BNzj//WPWNbs2U7pZ +Dt0ejJSzXS2pt9v3oaSb0lsn0m06Mgtno72l6EQoTxXAJk6agNBz4IfLLnYkteEC +AwEAAaN0MHIwHQYDVR0OBBYEFEdIEWKDgjyWKhYjxsAdeNVUwl7iMB8GA1UdIwQY +MBaAFEdIEWKDgjyWKhYjxsAdeNVUwl7iMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0R +BBgwFoEUdmFsaWQtY2FAZXhhbXBsZS5jb20wDQYJKoZIhvcNAQELBQADggEBACQ0 +LDU1sVSzRwa1ZeqeiHrc3gbeuBqFqejj+QvPx2N3Zc53265+mb1ga1bJ3O1WePa/ +K/0L0Ska5Y5CELjcGlvy5kcSdQFRhoqK7KZq3c9C39ljl5yx//uJUauXWq+WJhqo +vTKNo+QC4P9jIjtT6J7irNXl/bS2Qgnmp1xXsVNKmnhWXpbpyelFJgV6WhjtUHxA +nDZ72q7naLG3tb3umBgAZ/DKQGPvM3WJ2BzwfD6I4c75O//DzB6D7p4w295WU/m/ +AQ5jc/hiEglEh2JHStq1ofvMKg90+azrc06gD7kjXgzaHFBesM8Xph1qrz+8jN1a +F2L5+xQODQmvZhN5PDM= +-----END CERTIFICATE----- diff --git a/acme4j-smime/tool/smime-generator.py b/acme4j-smime/tool/smime-generator.py new file mode 100755 index 00000000..3bfb0b96 --- /dev/null +++ b/acme4j-smime/tool/smime-generator.py @@ -0,0 +1,118 @@ +#!/bin/env python3 +# +# acme4j - Java ACME client +# +# Copyright (C) 2022 Richard "Shred" Körber +# http://acme4j.shredzone.org +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# + +# +# This tool creates ACME test e-mails and signs them. It can be +# used to generate S/MIME mails for unit tests. +# +# Requires: M2Crypto +# +# WARNING: DO NOT USE THIS CODE TO GENERATE REAL S/MIME MAILS! +# This generator is only meant to create test mails for unit test +# purposes, and may lack security relevant features that are +# needed for real S/MIME mails. +# + +from M2Crypto import BIO, Rand, SMIME + +def makebuf(text): + return BIO.MemoryBuffer(bytes(text, 'UTF-8')) + +def signmail(text, sender, recipient, subject, privkey, pubkey, + envelopeFrom=None, envelopeTo=None, envelopeSubject=None): + body = 'Content-Type: message/RFC822; forwarded=no\r\n\r\n' + body += 'From: {}\r\n'.format(sender) + body += 'To: {}\r\n'.format(recipient) + body += 'Subject: {}\r\n'.format(subject) + body += 'Message-ID: \r\n' + body += 'MIME-Version: 1.0\r\n' + body += 'Content-Type: text/plain; charset=utf-8\r\n' + body += '\r\n' + body += text + body += '\r\n' + + s = SMIME.SMIME() + s.load_key(privkey, pubkey) + p7 = s.sign(makebuf(body), SMIME.PKCS7_DETACHED) + + out = BIO.MemoryBuffer() + out.write('From: {}\r\n'.format(envelopeFrom if envelopeFrom is not None else sender)) + out.write('To: {}\r\n'.format(envelopeTo if envelopeTo is not None else recipient)) + out.write('Subject: {}\r\n'.format(envelopeSubject if envelopeSubject is not None else subject)) + out.write('Auto-Submitted: auto-generated; type=acme\r\n') + out.write('Message-ID: \r\n') + s.write(out, p7, makebuf(body)) + + return out.read() + +with open('src/test/resources/email/valid-mail.eml', 'wb') as w: + w.write(signmail('This is an automatically generated ACME challenge.', + 'valid-ca@example.com', + 'recipient@example.org', + 'ACME: LgYemJLy3F1LDkiJrdIGbEzyFJyOyf6vBdyZ1TG3sME=', + 'src/test/resources/valid-signer-privkey.pem', + 'src/test/resources/valid-signer.pem')) + +with open('src/test/resources/email/invalid-cert-mismatch.eml', 'wb') as w: + w.write(signmail('This is an automatically generated ACME challenge.', + 'different-ca@example.com', + 'recipient@example.org', + 'ACME: LgYemJLy3F1LDkiJrdIGbEzyFJyOyf6vBdyZ1TG3sME=', + 'src/test/resources/valid-signer-privkey.pem', + 'src/test/resources/valid-signer.pem', + envelopeFrom="different-ca@example.org")) + +with open('src/test/resources/email/invalid-nosan.eml', 'wb') as w: + w.write(signmail('This is an automatically generated ACME challenge.', + 'valid-ca@example.com', + 'recipient@example.org', + 'ACME: LgYemJLy3F1LDkiJrdIGbEzyFJyOyf6vBdyZ1TG3sME=', + 'src/test/resources/valid-signer-nosan-privkey.pem', + 'src/test/resources/valid-signer-nosan.pem')) + +with open('src/test/resources/email/invalid-signed-mail.eml', 'wb') as w: + w.write(signmail('This is an automatically generated ACME challenge.', + 'valid-ca@example.com', + 'recipient@example.org', + 'ACME: LgYemJLy3F1LDkiJrdIGbEzyFJyOyf6vBdyZ1TG3sME=', + 'src/test/resources/invalid-signer-privkey.pem', + 'src/test/resources/invalid-signer.pem')) + +with open('src/test/resources/email/invalid-protected-mail-from.eml', 'wb') as w: + w.write(signmail('This is an automatically generated ACME challenge.', + 'valid-ca@example.com', + 'recipient@example.org', + 'ACME: LgYemJLy3F1LDkiJrdIGbEzyFJyOyf6vBdyZ1TG3sME=', + 'src/test/resources/valid-signer-privkey.pem', + 'src/test/resources/valid-signer.pem', + envelopeFrom="tampered-ca@example.org")) + +with open('src/test/resources/email/invalid-protected-mail-to.eml', 'wb') as w: + w.write(signmail('This is an automatically generated ACME challenge.', + 'valid-ca@example.com', + 'recipient@example.org', + 'ACME: LgYemJLy3F1LDkiJrdIGbEzyFJyOyf6vBdyZ1TG3sME=', + 'src/test/resources/valid-signer-privkey.pem', + 'src/test/resources/valid-signer.pem', + envelopeTo="tampered-recipient@example.com")) + +with open('src/test/resources/email/invalid-protected-mail-subject.eml', 'wb') as w: + w.write(signmail('This is an automatically generated ACME challenge.', + 'valid-ca@example.com', + 'recipient@example.org', + 'ACME: LgYemJLy3F1LDkiJrdIGbEzyFJyOyf6vBdyZ1TG3sME=', + 'src/test/resources/valid-signer-privkey.pem', + 'src/test/resources/valid-signer.pem', + envelopeSubject="ACME: aDiFfErEnTtOkEn")) diff --git a/acme4j-smime/tool/test-key-generator.sh b/acme4j-smime/tool/test-key-generator.sh new file mode 100755 index 00000000..dc231b41 --- /dev/null +++ b/acme4j-smime/tool/test-key-generator.sh @@ -0,0 +1,37 @@ +#!/bin/bash +# +# acme4j - Java ACME client +# +# Copyright (C) 2022 Richard "Shred" Körber +# http://acme4j.shredzone.org +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# + +# +# Generates test keys for S/MIME unit tests. +# +# WARNING: DO NOT USE THIS CODE FOR KEY GENERATION IN PRODUCTION +# ENVIRONMENTS! +# + +TARGET=src/test/resources/ + +openssl req -x509 -newkey rsa:2048 -sha256 -nodes -days 3650 \ + -keyout "$TARGET/valid-signer-privkey.pem" -out "$TARGET/valid-signer.pem" \ + -subj "/C=XX/L=Acme City/O=Acme Certificates Ltd/CN=example.com/emailAddress=valid-ca@example.com" \ + -addext "subjectAltName=email:valid-ca@example.com" + +openssl req -x509 -newkey rsa:2048 -sha256 -nodes -days 3650 \ + -keyout "$TARGET/valid-signer-nosan-privkey.pem" -out "$TARGET/valid-signer-nosan.pem" \ + -subj "/C=XX/L=Acme City/O=Acme Certificates Ltd/CN=example.com/emailAddress=valid-ca@example.com" + +openssl req -x509 -newkey rsa:2048 -sha256 -nodes -days 3650 \ + -keyout "$TARGET/invalid-signer-privkey.pem" -out "$TARGET/invalid-signer.pem" \ + -subj "/C=XX/L=Acme City/O=Emca Certificates Ltd/CN=example.com/emailAddress=invalid-ca@example.com" \ + -addext "subjectAltName=email:invalid-ca@example.com" diff --git a/src/doc/docs/challenge/email-reply-00.md b/src/doc/docs/challenge/email-reply-00.md index 2a6c9182..ff55e7a9 100644 --- a/src/doc/docs/challenge/email-reply-00.md +++ b/src/doc/docs/challenge/email-reply-00.md @@ -14,7 +14,7 @@ To use the S/MIME support, you need to: * add the `acme4j-smime` module to your list of dependencies * make sure that `BouncyCastleProvider` is added as security provider -[RFC 8823](https://tools.ietf.org/html/rfc8823) requires that the DKIM or S/MIME signature of incoming mails _must_ be checked. Outgoing mails _must_ have a valid DKIM or S/MIME signature. This is out of the scope of `acme4j-smime`, but is usually performed by a MTA. +[RFC 8823](https://tools.ietf.org/html/rfc8823) requires that the DKIM or S/MIME signature of incoming mails _must_ be checked. Outgoing mails _must_ have a valid DKIM signature. Starting with v2.15, _acme4j_ is able to validate and sign S/MIME verification mails. DKIM is usually done by the MTA and thus out of the scope of `acme4j-smime`. ## Ordering @@ -59,7 +59,7 @@ EmailReply00Challenge challenge = // challenge that is requested by the C EmailIdentifier identifier = // email address to get the S/MIME cert for javax.mail.Session mailSession = // javax.mail session -Message response = new EmailProcessor(challengeMessage) +Message response = EmailProcessor.plainMessage(challengeMessage) .expectedIdentifier(identifier) .withChallenge(challenge) .respond() @@ -69,4 +69,28 @@ Transport.send(response); // send response to the CA challenge.trigger(); // trigger the challenge ``` -The `EmailProcessor` and the related `ResponseGenerator` offer more methods for validating and for customizing the response email. +The `EmailProcessor` and the related `ResponseGenerator` offer more methods for validating and for customizing the response email, see [the autodocs](../acme4j-smime/apidocs/org.shredzone.acme4j.smime/module-summary.html). + +## Validating S/MIME Challenge E-Mails + +The `EmailProcessor` is able to validate challenge e-mails that were signed by the CA using S/MIME. To do so, invoke the processor like this: + +```java +Message challengeMessage = // incoming challenge message from the CA +EmailReply00Challenge challenge = // challenge that is requested by the CA +EmailIdentifier identifier = // email address to get the S/MIME cert for +javax.mail.Session mailSession = // javax.mail session +X509Certificate signCert = // CA's signing certificate, for validation +boolean strict = // strict checks? + +Message response = EmailProcessor.smimeMessage(challengeMessage, mailSession, signCert, strict) + .expectedIdentifier(identifier) + .withChallenge(challenge) + .respond() + .generateResponse(mailSession); + +Transport.send(response); // send response to the CA +challenge.trigger(); // trigger the challenge +``` + +If `strict` is set to `true`, the S/MIME protected headers `From:`, `To:`, and `Subject:` inside the e-mail **must** match these headers of the wrapping `challengeMessage`. It is recommended to do strict checks. However, if the inbound MTA is changing the headers of the wrapping mail, this flag can be set to `false` instead. In this case, the wrapping headers are ignored, and only the protected headers are used for responding to the challenge. diff --git a/src/doc/docs/index.md b/src/doc/docs/index.md index 06868f95..d0126437 100644 --- a/src/doc/docs/index.md +++ b/src/doc/docs/index.md @@ -31,7 +31,7 @@ Latest version: ![maven central](https://shredzone.org/maven-central/org.shredzo * [jose4j](https://bitbucket.org/b_c/jose4j/wiki/Home) * [slf4j](http://www.slf4j.org/) * For `acme4j-utils`: [Bouncy Castle](https://www.bouncycastle.org/) -* For `acme4j-smime`: [Jakarta Mail](https://eclipse-ee4j.github.io/mail/) +* For `acme4j-smime`: [Jakarta Mail](https://eclipse-ee4j.github.io/mail/), [Bouncy Castle](https://www.bouncycastle.org/) ## Quick Start diff --git a/src/doc/docs/migration.md b/src/doc/docs/migration.md index 19c01f61..e833341d 100644 --- a/src/doc/docs/migration.md +++ b/src/doc/docs/migration.md @@ -2,6 +2,11 @@ This document will help you migrate your code to the latest _acme4j_ version. +## Migration to Version 2.15 + +- `acme4j-smime` requires BouncyCastle now. The `BouncyCastleProvider` must also be added as security provider. +- In `acme4j-smime`, the `EmailProcessor` constructor is private now. Use `EmailProcessor.plainMessage()` as drop-in replacement. + ## Migration to Version 2.13 - The `acme4j-smime` module has switched from _JavaMail_ to _Jakarta Mail_. Unfortunately, this is a breaking API change because classes like `javax.mail.internet.InternetAddress` have moved to respective `jakarta.mail` packages.