This is an automated email from the ASF dual-hosted git repository.
Arsnael pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git
The following commit(s) were added to refs/heads/master by this push:
new ffbb4a75b1 [FIX] IsSMIMESigned needs to account for protocol
ffbb4a75b1 is described below
commit ffbb4a75b14165e35088f5da026ac69c16e39093
Author: Benoit TELLIER <[email protected]>
AuthorDate: Sun Aug 23 22:27:04 2026 +0700
[FIX] IsSMIMESigned needs to account for protocol
Processing PGP mails triggers:
org.bouncycastle.cms.CMSException: IOException reading content.
...Caused by: java.io.IOException: unknown tag 13 encountered
Explanation:
-----BEGIN PGP SIGNATURE----- starts with '-' = 0x2D.
As an ASN.1 identifier octet that is universal/primitive, tag number 0x2D &
0x1F = 13 — an unassigned universal tag.
---
.../transport/mailets/SMIMECheckSignature.java | 7 +-
.../james/transport/matcher/IsSMIMESigned.java | 40 ++++++-
.../james/transport/matcher/IsSMIMESignedTest.java | 122 +++++++++++++++------
3 files changed, 129 insertions(+), 40 deletions(-)
diff --git
a/mailet/crypto/src/main/java/org/apache/james/transport/mailets/SMIMECheckSignature.java
b/mailet/crypto/src/main/java/org/apache/james/transport/mailets/SMIMECheckSignature.java
index 7dfa7c072d..791973ebfa 100644
---
a/mailet/crypto/src/main/java/org/apache/james/transport/mailets/SMIMECheckSignature.java
+++
b/mailet/crypto/src/main/java/org/apache/james/transport/mailets/SMIMECheckSignature.java
@@ -176,13 +176,14 @@ public class SMIMECheckSignature extends GenericMailet {
// These errors are logged but they don't cause the message to
change its state. The message
// is considered as not signed and the process will go on.
} catch (CMSException | SMIMEException e) {
- LOGGER.error("Error during the analysis of the signed message", e);
+ // Signatures are remote sender controlled thus we do not consider
a malformed one to be a server error.
+ LOGGER.warn("Error during the analysis of the signed message", e);
signers = null;
} catch (IOException e) {
- LOGGER.error("IO error during the analysis of the signed message",
e);
+ LOGGER.warn("IO error during the analysis of the signed message",
e);
signers = null;
} catch (Exception e) {
- LOGGER.error("Generic error occured during the analysis of the
message", e);
+ LOGGER.warn("Generic error occured during the analysis of the
message", e);
signers = null;
}
diff --git
a/mailet/crypto/src/main/java/org/apache/james/transport/matcher/IsSMIMESigned.java
b/mailet/crypto/src/main/java/org/apache/james/transport/matcher/IsSMIMESigned.java
index 343d87f909..db5c4babb1 100644
---
a/mailet/crypto/src/main/java/org/apache/james/transport/matcher/IsSMIMESigned.java
+++
b/mailet/crypto/src/main/java/org/apache/james/transport/matcher/IsSMIMESigned.java
@@ -21,19 +21,32 @@
package org.apache.james.transport.matcher;
import java.util.Collection;
+import java.util.Locale;
+import java.util.Optional;
import jakarta.mail.MessagingException;
+import jakarta.mail.internet.ContentType;
import jakarta.mail.internet.MimeMessage;
+import jakarta.mail.internet.ParseException;
import org.apache.james.core.MailAddress;
import org.apache.mailet.Mail;
import org.apache.mailet.base.GenericMatcher;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.collect.ImmutableSet;
/**
* checks if a mail is smime signed.
*/
public class IsSMIMESigned extends GenericMatcher {
+ private static final Logger LOGGER =
LoggerFactory.getLogger(IsSMIMESigned.class);
+
+ private static final ImmutableSet<String> PKCS7_SIGNATURE_PROTOCOLS =
ImmutableSet.of(
+ "application/pkcs7-signature",
+ "application/x-pkcs7-signature");
@Override
public Collection<MailAddress> match(Mail mail) throws MessagingException {
@@ -46,7 +59,7 @@ public class IsSMIMESigned extends GenericMatcher {
return null;
}
- if (message.isMimeType("multipart/signed")
+ if (isPkcs7SignedMultipart(message)
|| message.isMimeType("application/pkcs7-signature")
|| message.isMimeType("application/x-pkcs7-signature")
|| ((message.isMimeType("application/pkcs7-mime") ||
message.isMimeType("application/x-pkcs7-mime"))
@@ -56,4 +69,29 @@ public class IsSMIMESigned extends GenericMatcher {
return null;
}
}
+
+ /**
+ * A <code>multipart/signed</code> body is not necessarily S/MIME:
PGP/MIME (RFC 3156) relies on the very
+ * same content type and only the <code>protocol</code> parameter tells
the two apart (RFC 8551 section 3.4.3).
+ * Handing a PGP signature over to the S/MIME parser makes it choke on
ASN.1 it can not read, hence we require
+ * the protocol to explicitly designate a PKCS#7 signature.
+ */
+ private boolean isPkcs7SignedMultipart(MimeMessage message) throws
MessagingException {
+ if (!message.isMimeType("multipart/signed")) {
+ return false;
+ }
+ return protocolParameter(message)
+ .map(protocol -> protocol.toLowerCase(Locale.US).trim())
+ .filter(PKCS7_SIGNATURE_PROTOCOLS::contains)
+ .isPresent();
+ }
+
+ private Optional<String> protocolParameter(MimeMessage message) throws
MessagingException {
+ try {
+ return Optional.ofNullable(new
ContentType(message.getContentType()).getParameter("protocol"));
+ } catch (ParseException e) {
+ LOGGER.info("Could not parse Content-Type of a multipart/signed
message, treating it as not S/MIME signed", e);
+ return Optional.empty();
+ }
+ }
}
diff --git
a/mailet/crypto/src/test/java/org/apache/james/transport/matcher/IsSMIMESignedTest.java
b/mailet/crypto/src/test/java/org/apache/james/transport/matcher/IsSMIMESignedTest.java
index 58f068a14e..b40d64afbb 100644
---
a/mailet/crypto/src/test/java/org/apache/james/transport/matcher/IsSMIMESignedTest.java
+++
b/mailet/crypto/src/test/java/org/apache/james/transport/matcher/IsSMIMESignedTest.java
@@ -19,62 +19,112 @@
package org.apache.james.transport.matcher;
-import static org.apache.mailet.base.MailAddressFixture.RECIPIENT1;
-import static org.apache.mailet.base.MailAddressFixture.SENDER;
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.apache.mailet.base.MailAddressFixture.ANY_AT_JAMES;
import static org.assertj.core.api.Assertions.assertThat;
-import org.apache.james.core.builder.MimeMessageBuilder;
+import java.io.ByteArrayInputStream;
+import java.util.Properties;
+
+import jakarta.mail.Session;
+import jakarta.mail.internet.MimeMessage;
+
+import org.apache.mailet.Mail;
import org.apache.mailet.base.test.FakeMail;
+import org.apache.mailet.base.test.FakeMatcherConfig;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
-import org.junit.jupiter.params.ParameterizedTest;
-import org.junit.jupiter.params.provider.ValueSource;
-public class IsSMIMESignedTest {
- private IsSMIMESigned isSMIMESigned;
+class IsSMIMESignedTest {
+ private IsSMIMESigned testee;
@BeforeEach
- void beforeEach() {
- isSMIMESigned = new IsSMIMESigned();
+ void setUp() throws Exception {
+ testee = new IsSMIMESigned();
+ testee.init(FakeMatcherConfig.builder()
+ .matcherName("IsSMIMESigned")
+ .build());
}
- @ParameterizedTest
- @ValueSource(strings = {"multipart/signed",
- "application/pkcs7-signature",
- "application/x-pkcs7-signature",
- "application/pkcs7-mime; smime-type=signed-data; name=\"smime.p7m\"",
- "application/x-pkcs7-mime; smime-type=signed-data;
name=\"smime.p7m\""})
- void
matchShouldReturnNonEmptyListWhenMessageContentTypeIsSMIMERelated(String
contentType) throws Exception {
- FakeMail mail = FakeMail.builder()
- .name("mail")
-
.mimeMessage(MimeMessageBuilder.mimeMessageBuilder().addHeader("Content-Type",
contentType))
- .sender(SENDER)
- .recipient(RECIPIENT1)
- .build();
- assertThat(isSMIMESigned.match(mail)).isNotEmpty();
+ @Test
+ void shouldMatchMultipartSignedWithPkcs7Protocol() throws Exception {
+ assertThat(testee.match(mailWithContentType(
+ "multipart/signed; protocol=\"application/pkcs7-signature\";
micalg=sha-256; boundary=\"bound\"")))
+ .containsOnly(ANY_AT_JAMES);
}
@Test
- void matchShouldReturnNullWhenMessageContentTypeIsNotSMIMERelated() throws
Exception {
- FakeMail mail = FakeMail.builder()
- .name("mail")
-
.mimeMessage(MimeMessageBuilder.mimeMessageBuilder().addHeader("Content-Type",
"text/plain"))
- .sender(SENDER)
- .recipient(RECIPIENT1)
- .build();
- assertThat(isSMIMESigned.match(mail)).isNull();
+ void shouldMatchMultipartSignedWithLegacyPkcs7Protocol() throws Exception {
+ assertThat(testee.match(mailWithContentType(
+ "multipart/signed; protocol=\"application/x-pkcs7-signature\";
micalg=sha1; boundary=\"bound\"")))
+ .containsOnly(ANY_AT_JAMES);
+ }
+
+ @Test
+ void shouldMatchWhenProtocolCaseDiffers() throws Exception {
+ assertThat(testee.match(mailWithContentType(
+ "multipart/signed; protocol=\"Application/PKCS7-Signature\";
boundary=\"bound\"")))
+ .containsOnly(ANY_AT_JAMES);
+ }
+
+ @Test
+ void shouldNotMatchPgpMimeSignedMail() throws Exception {
+ assertThat(testee.match(mailWithContentType(
+ "multipart/signed; protocol=\"application/pgp-signature\";
micalg=pgp-sha256; boundary=\"bound\"")))
+ .isNull();
+ }
+
+ @Test
+ void shouldNotMatchMultipartSignedWithoutProtocol() throws Exception {
+ assertThat(testee.match(mailWithContentType("multipart/signed;
boundary=\"bound\"")))
+ .isNull();
+ }
+
+ @Test
+ void shouldNotMatchMultipartMixed() throws Exception {
+ assertThat(testee.match(mailWithContentType("multipart/mixed;
boundary=\"bound\"")))
+ .isNull();
+ }
+
+ @Test
+ void shouldMatchPkcs7SignatureMail() throws Exception {
+
assertThat(testee.match(mailWithContentType("application/pkcs7-signature")))
+ .containsOnly(ANY_AT_JAMES);
+ }
+
+ @Test
+ void shouldMatchPkcs7MimeSignedData() throws Exception {
+ assertThat(testee.match(mailWithContentType("application/pkcs7-mime;
smime-type=signed-data; name=smime.p7m")))
+ .containsOnly(ANY_AT_JAMES);
}
@Test
- void matchShouldReturnNullWhenMailIsNull() throws Exception {
- assertThat(isSMIMESigned.match(null)).isNull();
+ void shouldNotMatchPkcs7MimeEnvelopedData() throws Exception {
+ assertThat(testee.match(mailWithContentType("application/pkcs7-mime;
smime-type=enveloped-data; name=smime.p7m")))
+ .isNull();
}
@Test
- void matchShouldReturnNullWhenMessageIsNull() throws Exception {
- FakeMail mail = FakeMail.builder()
+ void shouldNotMatchTextPlain() throws Exception {
+ assertThat(testee.match(mailWithContentType("text/plain;
charset=UTF-8")))
+ .isNull();
+ }
+
+ private Mail mailWithContentType(String contentType) throws Exception {
+ String message = "Subject: any\r\n"
+ + "Content-Type: " + contentType + "\r\n"
+ + "\r\n"
+ + "--bound\r\n"
+ + "Content-Type: text/plain\r\n"
+ + "\r\n"
+ + "content\r\n"
+ + "--bound--\r\n";
+
+ return FakeMail.builder()
.name("mail")
+ .recipient(ANY_AT_JAMES)
+ .mimeMessage(new MimeMessage(Session.getInstance(new Properties()),
+ new ByteArrayInputStream(message.getBytes(UTF_8))))
.build();
- assertThat(isSMIMESigned.match(mail)).isNull();
}
}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]