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 52867927..67b45366 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 @@ -135,13 +135,16 @@ public final class EmailProcessor { boolean hasMatch = false; for (SignerInformation signer : signed.getSignerInfos().getSigners()) { hasMatch |= signer.verify(verifier); + if (hasMatch) { + break; + } } if (!hasMatch) { throw new AcmeInvalidMessageException("The S/MIME signature is invalid"); } MimeMessage content = signed.getContentAsMimeMessage(mailSession); - if (!content.getContentType().equalsIgnoreCase("message/rfc822; forwarded=no")) { + if (!"message/rfc822; forwarded=no".equalsIgnoreCase(content.getContentType())) { throw new AcmeInvalidMessageException("Message does not contain protected headers"); } @@ -214,7 +217,7 @@ public final class EmailProcessor { if (from == null) { throw new AcmeInvalidMessageException("Message has no 'From' header"); } - if (from.length != 1) { + if (from.length != 1 || from[0] == null) { throw new AcmeInvalidMessageException("Message must have exactly one sender, but has " + from.length); } if (validFromAddresses != null && !validFromAddresses.contains(from[0])) { @@ -222,8 +225,7 @@ public final class EmailProcessor { } if (strict && signedMessage != null) { Address[] outerFrom = message.getFrom(); - if ((outerFrom.length > 1) || (outerFrom.length == 1 && outerFrom[0] != null - && !outerFrom[0].equals(from[0]))) { + if (outerFrom == null || outerFrom.length != 1 || !from[0].equals(outerFrom[0])) { throw new AcmeInvalidMessageException("Protected 'From' header does not match envelope header"); } } @@ -233,13 +235,12 @@ public final class EmailProcessor { if (to == null) { throw new AcmeInvalidMessageException("Message has no 'To' header"); } - if (to.length != 1) { + if (to.length != 1 || to[0] == null) { 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]))) { + if (outerTo == null || outerTo.length != 1 || !to[0].equals(outerTo[0])) { throw new AcmeInvalidMessageException("Protected 'To' header does not match envelope header"); } } @@ -249,9 +250,8 @@ public final class EmailProcessor { if (subject == null) { throw new AcmeInvalidMessageException("Message has no 'Subject' header"); } - if (strict && signedMessage != null - && message.getSubject() != null - && !message.getSubject().equals(signedMessage.getSubject())) { + 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); 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 3fc25443..f7b08291 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 @@ -68,9 +68,9 @@ public abstract class SMIMETests { * * @param name * Name of the mock message to be read from the test resources. - * @return Mock {@link Message} that was created + * @return Mock {@link MimeMessage} that was created */ - protected Message mockMessage(String name) { + protected MimeMessage mockMessage(String name) { try (InputStream in = SMIMETests.class.getResourceAsStream("/email/" + name + ".eml")) { return new MimeMessage(mailSession, in); } catch (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 fd183aa6..2caf04e2 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 @@ -67,19 +67,21 @@ public class EmailProcessorTest extends SMIMETests { } @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); + public void testValidSignature() { + assertThatNoException().isThrownBy(() -> { + MimeMessage message = mockMessage("valid-mail"); + X509Certificate certificate = readCertificate("valid-signer"); + EmailProcessor.smimeMessage(message, mailSession, certificate, true); + }); } @Test public void testInvalidSignature() { assertThatExceptionOfType(AcmeInvalidMessageException.class) .isThrownBy(() -> { - MimeMessage message = (MimeMessage) mockMessage("invalid-signed-mail"); + MimeMessage message = mockMessage("invalid-signed-mail"); X509Certificate certificate = readCertificate("valid-signer"); - EmailProcessor processor = EmailProcessor.smimeMessage(message, mailSession, certificate, true); + EmailProcessor.smimeMessage(message, mailSession, certificate, true); }) .withMessage("The S/MIME signature is invalid"); } @@ -88,9 +90,9 @@ public class EmailProcessorTest extends SMIMETests { public void testValidSignatureButNoSAN() { assertThatExceptionOfType(AcmeInvalidMessageException.class) .isThrownBy(() -> { - MimeMessage message = (MimeMessage) mockMessage("invalid-nosan"); + MimeMessage message = mockMessage("invalid-nosan"); X509Certificate certificate = readCertificate("valid-signer-nosan"); - EmailProcessor processor = EmailProcessor.smimeMessage(message, mailSession, certificate, true); + EmailProcessor.smimeMessage(message, mailSession, certificate, true); }) .withMessage("Signing certificate does not provide a rfc822Name subjectAltName"); } @@ -99,9 +101,9 @@ public class EmailProcessorTest extends SMIMETests { public void testSANDoesNotMatchFrom() { assertThatExceptionOfType(AcmeInvalidMessageException.class) .isThrownBy(() -> { - MimeMessage message = (MimeMessage) mockMessage("invalid-cert-mismatch"); + MimeMessage message = mockMessage("invalid-cert-mismatch"); X509Certificate certificate = readCertificate("valid-signer"); - EmailProcessor processor = EmailProcessor.smimeMessage(message, mailSession, certificate, true); + EmailProcessor.smimeMessage(message, mailSession, certificate, true); }) .withMessage("Sender 'different-ca@example.com' was not found in signing certificate"); } @@ -110,9 +112,9 @@ public class EmailProcessorTest extends SMIMETests { public void testInvalidProtectedFromHeader() { assertThatExceptionOfType(AcmeInvalidMessageException.class) .isThrownBy(() -> { - MimeMessage message = (MimeMessage) mockMessage("invalid-protected-mail-from"); + MimeMessage message = mockMessage("invalid-protected-mail-from"); X509Certificate certificate = readCertificate("valid-signer"); - EmailProcessor processor = EmailProcessor.smimeMessage(message, mailSession, certificate, true); + EmailProcessor.smimeMessage(message, mailSession, certificate, true); }) .withMessage("Protected 'From' header does not match envelope header"); } @@ -121,9 +123,9 @@ public class EmailProcessorTest extends SMIMETests { public void testInvalidProtectedToHeader() { assertThatExceptionOfType(AcmeInvalidMessageException.class) .isThrownBy(() -> { - MimeMessage message = (MimeMessage) mockMessage("invalid-protected-mail-to"); + MimeMessage message = mockMessage("invalid-protected-mail-to"); X509Certificate certificate = readCertificate("valid-signer"); - EmailProcessor processor = EmailProcessor.smimeMessage(message, mailSession, certificate, true); + EmailProcessor.smimeMessage(message, mailSession, certificate, true); }) .withMessage("Protected 'To' header does not match envelope header"); } @@ -132,9 +134,9 @@ public class EmailProcessorTest extends SMIMETests { public void testInvalidProtectedSubjectHeader() { assertThatExceptionOfType(AcmeInvalidMessageException.class) .isThrownBy(() -> { - MimeMessage message = (MimeMessage) mockMessage("invalid-protected-mail-subject"); + MimeMessage message = mockMessage("invalid-protected-mail-subject"); X509Certificate certificate = readCertificate("valid-signer"); - EmailProcessor processor = EmailProcessor.smimeMessage(message, mailSession, certificate, true); + EmailProcessor.smimeMessage(message, mailSession, certificate, true); }) .withMessage("Protected 'Subject' header does not match envelope header"); } @@ -143,66 +145,80 @@ public class EmailProcessorTest extends SMIMETests { public void testNonStrictInvalidProtectedSubjectHeader() { assertThatNoException() .isThrownBy(() -> { - MimeMessage message = (MimeMessage) mockMessage("invalid-protected-mail-subject"); + MimeMessage message = mockMessage("invalid-protected-mail-subject"); X509Certificate certificate = readCertificate("valid-signer"); - EmailProcessor processor = EmailProcessor.smimeMessage(message, mailSession, certificate, false); + EmailProcessor.smimeMessage(message, mailSession, certificate, false); }); } @Test public void textExpectedFromFails() { - assertThrows(AcmeProtocolException.class, () -> { - EmailProcessor processor = EmailProcessor.plainMessage(message); - processor.expectedFrom(expectedTo); - }); + assertThatExceptionOfType(AcmeProtocolException.class) + .isThrownBy(() -> { + EmailProcessor processor = EmailProcessor.plainMessage(message); + processor.expectedFrom(expectedTo); + }) + .withMessage("Message is not sent by the expected sender"); } @Test public void textExpectedToFails() { - assertThrows(AcmeProtocolException.class, () -> { - EmailProcessor processor = EmailProcessor.plainMessage(message); - processor.expectedTo(expectedFrom); - }); + assertThatExceptionOfType(AcmeProtocolException.class) + .isThrownBy(() -> { + EmailProcessor processor = EmailProcessor.plainMessage(message); + processor.expectedTo(expectedFrom); + }) + .withMessage("Message is not addressed to expected recipient"); } @Test public void textExpectedIdentifierFails1() { - assertThrows(AcmeProtocolException.class, () -> { - EmailProcessor processor = EmailProcessor.plainMessage(message); - processor.expectedIdentifier(EmailIdentifier.email(expectedFrom)); - }); + assertThatExceptionOfType(AcmeProtocolException.class) + .isThrownBy(() -> { + EmailProcessor processor = EmailProcessor.plainMessage(message); + processor.expectedIdentifier(EmailIdentifier.email(expectedFrom)); + }) + .withMessage("Message is not addressed to expected recipient"); } @Test public void textExpectedIdentifierFails2() { - assertThrows(AcmeProtocolException.class, () -> { - EmailProcessor processor = EmailProcessor.plainMessage(message); - processor.expectedIdentifier(Identifier.ip("192.168.0.1")); - }); + assertThatExceptionOfType(AcmeProtocolException.class) + .isThrownBy(() -> { + EmailProcessor processor = EmailProcessor.plainMessage(message); + processor.expectedIdentifier(Identifier.ip("192.168.0.1")); + }) + .withMessage("Wrong identifier type: ip"); } @Test public void textNoChallengeFails1() { - assertThrows(IllegalStateException.class, () -> { - EmailProcessor processor = EmailProcessor.plainMessage(message); - processor.getToken(); - }); + assertThatExceptionOfType(IllegalStateException.class) + .isThrownBy(() -> { + EmailProcessor processor = EmailProcessor.plainMessage(message); + processor.getToken(); + }) + .withMessage("No challenge has been set yet"); } @Test public void textNoChallengeFails2() { - assertThrows(IllegalStateException.class, () -> { - EmailProcessor processor = EmailProcessor.plainMessage(message); - processor.getAuthorization(); - }); + assertThatExceptionOfType(IllegalStateException.class) + .isThrownBy(() -> { + EmailProcessor processor = EmailProcessor.plainMessage(message); + processor.getAuthorization(); + }) + .withMessage("No challenge has been set yet"); } @Test public void textNoChallengeFails3() { - assertThrows(IllegalStateException.class, () -> { - EmailProcessor processor = EmailProcessor.plainMessage(message); - processor.respond(); - }); + assertThatExceptionOfType(IllegalStateException.class) + .isThrownBy(() -> { + EmailProcessor processor = EmailProcessor.plainMessage(message); + processor.respond(); + }) + .withMessage("No challenge has been set yet"); } @Test @@ -218,11 +234,13 @@ public class EmailProcessorTest extends SMIMETests { @Test public void testChallengeMismatch() { - assertThrows(AcmeProtocolException.class, () -> { - EmailReply00Challenge challenge = mockChallenge("emailReplyChallengeMismatch"); - EmailProcessor processor = EmailProcessor.plainMessage(message); - processor.withChallenge(challenge); - }); + assertThatExceptionOfType(AcmeProtocolException.class) + .isThrownBy(() -> { + EmailReply00Challenge challenge = mockChallenge("emailReplyChallengeMismatch"); + EmailProcessor processor = EmailProcessor.plainMessage(message); + processor.withChallenge(challenge); + }) + .withMessage("Message is not sent by the expected sender"); } @Test