Minor optimizations

pull/134/head
Richard Körber 2022-11-25 13:14:33 +01:00
parent 06e03880c9
commit ef5ca28fd9
No known key found for this signature in database
GPG Key ID: AAB9FD19C78AA3E0
3 changed files with 81 additions and 63 deletions

View File

@ -135,13 +135,16 @@ public final class EmailProcessor {
boolean hasMatch = false; boolean hasMatch = false;
for (SignerInformation signer : signed.getSignerInfos().getSigners()) { for (SignerInformation signer : signed.getSignerInfos().getSigners()) {
hasMatch |= signer.verify(verifier); hasMatch |= signer.verify(verifier);
if (hasMatch) {
break;
}
} }
if (!hasMatch) { if (!hasMatch) {
throw new AcmeInvalidMessageException("The S/MIME signature is invalid"); throw new AcmeInvalidMessageException("The S/MIME signature is invalid");
} }
MimeMessage content = signed.getContentAsMimeMessage(mailSession); 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"); throw new AcmeInvalidMessageException("Message does not contain protected headers");
} }
@ -214,7 +217,7 @@ public final class EmailProcessor {
if (from == null) { if (from == null) {
throw new AcmeInvalidMessageException("Message has no 'From' header"); 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); throw new AcmeInvalidMessageException("Message must have exactly one sender, but has " + from.length);
} }
if (validFromAddresses != null && !validFromAddresses.contains(from[0])) { if (validFromAddresses != null && !validFromAddresses.contains(from[0])) {
@ -222,8 +225,7 @@ public final class EmailProcessor {
} }
if (strict && signedMessage != null) { if (strict && signedMessage != null) {
Address[] outerFrom = message.getFrom(); Address[] outerFrom = message.getFrom();
if ((outerFrom.length > 1) || (outerFrom.length == 1 && outerFrom[0] != null if (outerFrom == null || outerFrom.length != 1 || !from[0].equals(outerFrom[0])) {
&& !outerFrom[0].equals(from[0]))) {
throw new AcmeInvalidMessageException("Protected 'From' header does not match envelope header"); throw new AcmeInvalidMessageException("Protected 'From' header does not match envelope header");
} }
} }
@ -233,13 +235,12 @@ public final class EmailProcessor {
if (to == null) { if (to == null) {
throw new AcmeInvalidMessageException("Message has no 'To' header"); 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); throw new AcmeProtocolException("Message must have exactly one recipient, but has " + to.length);
} }
if (strict && signedMessage != null) { if (strict && signedMessage != null) {
Address[] outerTo = message.getRecipients(TO); Address[] outerTo = message.getRecipients(TO);
if ((outerTo.length > 1) || (outerTo.length == 1 && outerTo[0] != null if (outerTo == null || outerTo.length != 1 || !to[0].equals(outerTo[0])) {
&& !outerTo[0].equals(to[0]))) {
throw new AcmeInvalidMessageException("Protected 'To' header does not match envelope header"); throw new AcmeInvalidMessageException("Protected 'To' header does not match envelope header");
} }
} }
@ -249,9 +250,8 @@ public final class EmailProcessor {
if (subject == null) { if (subject == null) {
throw new AcmeInvalidMessageException("Message has no 'Subject' header"); throw new AcmeInvalidMessageException("Message has no 'Subject' header");
} }
if (strict && signedMessage != null if (strict && signedMessage != null &&
&& message.getSubject() != null (message.getSubject() == null || !message.getSubject().equals(signedMessage.getSubject()))) {
&& !message.getSubject().equals(signedMessage.getSubject())) {
throw new AcmeInvalidMessageException("Protected 'Subject' header does not match envelope header"); throw new AcmeInvalidMessageException("Protected 'Subject' header does not match envelope header");
} }
Matcher m = SUBJECT_PATTERN.matcher(subject); Matcher m = SUBJECT_PATTERN.matcher(subject);

View File

@ -68,9 +68,9 @@ public abstract class SMIMETests {
* *
* @param name * @param name
* Name of the mock message to be read from the test resources. * 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")) { try (InputStream in = SMIMETests.class.getResourceAsStream("/email/" + name + ".eml")) {
return new MimeMessage(mailSession, in); return new MimeMessage(mailSession, in);
} catch (IOException ex) { } catch (IOException ex) {

View File

@ -67,19 +67,21 @@ public class EmailProcessorTest extends SMIMETests {
} }
@Test @Test
public void testValidSignature() throws AcmeInvalidMessageException, IOException { public void testValidSignature() {
MimeMessage message = (MimeMessage) mockMessage("valid-mail"); assertThatNoException().isThrownBy(() -> {
MimeMessage message = mockMessage("valid-mail");
X509Certificate certificate = readCertificate("valid-signer"); X509Certificate certificate = readCertificate("valid-signer");
EmailProcessor processor = EmailProcessor.smimeMessage(message, mailSession, certificate, true); EmailProcessor.smimeMessage(message, mailSession, certificate, true);
});
} }
@Test @Test
public void testInvalidSignature() { public void testInvalidSignature() {
assertThatExceptionOfType(AcmeInvalidMessageException.class) assertThatExceptionOfType(AcmeInvalidMessageException.class)
.isThrownBy(() -> { .isThrownBy(() -> {
MimeMessage message = (MimeMessage) mockMessage("invalid-signed-mail"); MimeMessage message = mockMessage("invalid-signed-mail");
X509Certificate certificate = readCertificate("valid-signer"); 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"); .withMessage("The S/MIME signature is invalid");
} }
@ -88,9 +90,9 @@ public class EmailProcessorTest extends SMIMETests {
public void testValidSignatureButNoSAN() { public void testValidSignatureButNoSAN() {
assertThatExceptionOfType(AcmeInvalidMessageException.class) assertThatExceptionOfType(AcmeInvalidMessageException.class)
.isThrownBy(() -> { .isThrownBy(() -> {
MimeMessage message = (MimeMessage) mockMessage("invalid-nosan"); MimeMessage message = mockMessage("invalid-nosan");
X509Certificate certificate = readCertificate("valid-signer-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"); .withMessage("Signing certificate does not provide a rfc822Name subjectAltName");
} }
@ -99,9 +101,9 @@ public class EmailProcessorTest extends SMIMETests {
public void testSANDoesNotMatchFrom() { public void testSANDoesNotMatchFrom() {
assertThatExceptionOfType(AcmeInvalidMessageException.class) assertThatExceptionOfType(AcmeInvalidMessageException.class)
.isThrownBy(() -> { .isThrownBy(() -> {
MimeMessage message = (MimeMessage) mockMessage("invalid-cert-mismatch"); MimeMessage message = mockMessage("invalid-cert-mismatch");
X509Certificate certificate = readCertificate("valid-signer"); 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"); .withMessage("Sender 'different-ca@example.com' was not found in signing certificate");
} }
@ -110,9 +112,9 @@ public class EmailProcessorTest extends SMIMETests {
public void testInvalidProtectedFromHeader() { public void testInvalidProtectedFromHeader() {
assertThatExceptionOfType(AcmeInvalidMessageException.class) assertThatExceptionOfType(AcmeInvalidMessageException.class)
.isThrownBy(() -> { .isThrownBy(() -> {
MimeMessage message = (MimeMessage) mockMessage("invalid-protected-mail-from"); MimeMessage message = mockMessage("invalid-protected-mail-from");
X509Certificate certificate = readCertificate("valid-signer"); 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"); .withMessage("Protected 'From' header does not match envelope header");
} }
@ -121,9 +123,9 @@ public class EmailProcessorTest extends SMIMETests {
public void testInvalidProtectedToHeader() { public void testInvalidProtectedToHeader() {
assertThatExceptionOfType(AcmeInvalidMessageException.class) assertThatExceptionOfType(AcmeInvalidMessageException.class)
.isThrownBy(() -> { .isThrownBy(() -> {
MimeMessage message = (MimeMessage) mockMessage("invalid-protected-mail-to"); MimeMessage message = mockMessage("invalid-protected-mail-to");
X509Certificate certificate = readCertificate("valid-signer"); 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"); .withMessage("Protected 'To' header does not match envelope header");
} }
@ -132,9 +134,9 @@ public class EmailProcessorTest extends SMIMETests {
public void testInvalidProtectedSubjectHeader() { public void testInvalidProtectedSubjectHeader() {
assertThatExceptionOfType(AcmeInvalidMessageException.class) assertThatExceptionOfType(AcmeInvalidMessageException.class)
.isThrownBy(() -> { .isThrownBy(() -> {
MimeMessage message = (MimeMessage) mockMessage("invalid-protected-mail-subject"); MimeMessage message = mockMessage("invalid-protected-mail-subject");
X509Certificate certificate = readCertificate("valid-signer"); 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"); .withMessage("Protected 'Subject' header does not match envelope header");
} }
@ -143,66 +145,80 @@ public class EmailProcessorTest extends SMIMETests {
public void testNonStrictInvalidProtectedSubjectHeader() { public void testNonStrictInvalidProtectedSubjectHeader() {
assertThatNoException() assertThatNoException()
.isThrownBy(() -> { .isThrownBy(() -> {
MimeMessage message = (MimeMessage) mockMessage("invalid-protected-mail-subject"); MimeMessage message = mockMessage("invalid-protected-mail-subject");
X509Certificate certificate = readCertificate("valid-signer"); X509Certificate certificate = readCertificate("valid-signer");
EmailProcessor processor = EmailProcessor.smimeMessage(message, mailSession, certificate, false); EmailProcessor.smimeMessage(message, mailSession, certificate, false);
}); });
} }
@Test @Test
public void textExpectedFromFails() { public void textExpectedFromFails() {
assertThrows(AcmeProtocolException.class, () -> { assertThatExceptionOfType(AcmeProtocolException.class)
.isThrownBy(() -> {
EmailProcessor processor = EmailProcessor.plainMessage(message); EmailProcessor processor = EmailProcessor.plainMessage(message);
processor.expectedFrom(expectedTo); processor.expectedFrom(expectedTo);
}); })
.withMessage("Message is not sent by the expected sender");
} }
@Test @Test
public void textExpectedToFails() { public void textExpectedToFails() {
assertThrows(AcmeProtocolException.class, () -> { assertThatExceptionOfType(AcmeProtocolException.class)
.isThrownBy(() -> {
EmailProcessor processor = EmailProcessor.plainMessage(message); EmailProcessor processor = EmailProcessor.plainMessage(message);
processor.expectedTo(expectedFrom); processor.expectedTo(expectedFrom);
}); })
.withMessage("Message is not addressed to expected recipient");
} }
@Test @Test
public void textExpectedIdentifierFails1() { public void textExpectedIdentifierFails1() {
assertThrows(AcmeProtocolException.class, () -> { assertThatExceptionOfType(AcmeProtocolException.class)
.isThrownBy(() -> {
EmailProcessor processor = EmailProcessor.plainMessage(message); EmailProcessor processor = EmailProcessor.plainMessage(message);
processor.expectedIdentifier(EmailIdentifier.email(expectedFrom)); processor.expectedIdentifier(EmailIdentifier.email(expectedFrom));
}); })
.withMessage("Message is not addressed to expected recipient");
} }
@Test @Test
public void textExpectedIdentifierFails2() { public void textExpectedIdentifierFails2() {
assertThrows(AcmeProtocolException.class, () -> { assertThatExceptionOfType(AcmeProtocolException.class)
.isThrownBy(() -> {
EmailProcessor processor = EmailProcessor.plainMessage(message); EmailProcessor processor = EmailProcessor.plainMessage(message);
processor.expectedIdentifier(Identifier.ip("192.168.0.1")); processor.expectedIdentifier(Identifier.ip("192.168.0.1"));
}); })
.withMessage("Wrong identifier type: ip");
} }
@Test @Test
public void textNoChallengeFails1() { public void textNoChallengeFails1() {
assertThrows(IllegalStateException.class, () -> { assertThatExceptionOfType(IllegalStateException.class)
.isThrownBy(() -> {
EmailProcessor processor = EmailProcessor.plainMessage(message); EmailProcessor processor = EmailProcessor.plainMessage(message);
processor.getToken(); processor.getToken();
}); })
.withMessage("No challenge has been set yet");
} }
@Test @Test
public void textNoChallengeFails2() { public void textNoChallengeFails2() {
assertThrows(IllegalStateException.class, () -> { assertThatExceptionOfType(IllegalStateException.class)
.isThrownBy(() -> {
EmailProcessor processor = EmailProcessor.plainMessage(message); EmailProcessor processor = EmailProcessor.plainMessage(message);
processor.getAuthorization(); processor.getAuthorization();
}); })
.withMessage("No challenge has been set yet");
} }
@Test @Test
public void textNoChallengeFails3() { public void textNoChallengeFails3() {
assertThrows(IllegalStateException.class, () -> { assertThatExceptionOfType(IllegalStateException.class)
.isThrownBy(() -> {
EmailProcessor processor = EmailProcessor.plainMessage(message); EmailProcessor processor = EmailProcessor.plainMessage(message);
processor.respond(); processor.respond();
}); })
.withMessage("No challenge has been set yet");
} }
@Test @Test
@ -218,11 +234,13 @@ public class EmailProcessorTest extends SMIMETests {
@Test @Test
public void testChallengeMismatch() { public void testChallengeMismatch() {
assertThrows(AcmeProtocolException.class, () -> { assertThatExceptionOfType(AcmeProtocolException.class)
.isThrownBy(() -> {
EmailReply00Challenge challenge = mockChallenge("emailReplyChallengeMismatch"); EmailReply00Challenge challenge = mockChallenge("emailReplyChallengeMismatch");
EmailProcessor processor = EmailProcessor.plainMessage(message); EmailProcessor processor = EmailProcessor.plainMessage(message);
processor.withChallenge(challenge); processor.withChallenge(challenge);
}); })
.withMessage("Message is not sent by the expected sender");
} }
@Test @Test