This is an automated email from the ASF dual-hosted git repository.
tballison pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tika.git
The following commit(s) were added to refs/heads/main by this push:
new dc9885ce44 TIKA-5002: parse PEM-armored CMS/PKCS7 like DER (#3437)
dc9885ce44 is described below
commit dc9885ce44e0b97e9fde6ac6c9da46671a50ba6b
Author: Tim Allison <[email protected]>
AuthorDate: Tue Oct 27 12:08:19 2026 -0400
TIKA-5002: parse PEM-armored CMS/PKCS7 like DER (#3437)
---
CHANGES.txt | 8 ++
.../org/apache/tika/mime/tika-mimetypes.xml | 3 +-
.../apache/tika/parser/crypto/CmsClassifier.java | 118 +++++++++++++++++++++
.../org/apache/tika/parser/crypto/Pkcs7Parser.java | 41 +++----
.../tika/detect/crypto/Pkcs7DetectorTest.java | 19 +++-
.../org/apache/tika/parser/crypto/PemFixtures.java | 43 ++++++++
.../tika/parser/crypto/Pkcs7DetectionTest.java | 3 +
.../apache/tika/parser/crypto/Pkcs7ParserTest.java | 85 ++++++++++++++-
8 files changed, 296 insertions(+), 24 deletions(-)
diff --git a/CHANGES.txt b/CHANGES.txt
index c5b945e6de..e36c8e43b2 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,3 +1,11 @@
+Release 4.2.1 - ???
+
+ OTHER CHANGES
+
+ * PEM-armored CMS/PKCS7 (-----BEGIN PKCS7----- or -----BEGIN CMS-----) is
parsed like
+ DER: Pkcs7Parser and the opt-in pkcs-7-detector report the smime-type and
extract
+ signed content; -----BEGIN CMS----- is now detected (TIKA-5002).
+
Release 4.2.0 - 10/25/2026
BREAKING CHANGES AND DEPRECATIONS
diff --git
a/tika-core/src/main/resources/org/apache/tika/mime/tika-mimetypes.xml
b/tika-core/src/main/resources/org/apache/tika/mime/tika-mimetypes.xml
index dda5383b43..37be9a47b7 100644
--- a/tika-core/src/main/resources/org/apache/tika/mime/tika-mimetypes.xml
+++ b/tika-core/src/main/resources/org/apache/tika/mime/tika-mimetypes.xml
@@ -921,8 +921,9 @@
<mime-type type="application/pkcs7-signature">
<glob pattern="*.p7s"/>
<magic priority="50">
- <!-- PEM encoded -->
+ <!-- PEM encoded; Pkcs7Parser decodes the armor and refines as for DER
-->
<match value="-----BEGIN PKCS7" type="string" offset="0"/>
+ <match value="-----BEGIN CMS-----" type="string" offset="0"/>
<!-- DER encoded: SEQUENCE then a CMS ContentInfo whose contentType is
in the pkcs7 arc
1.2.840.113549.1.7.x (signedData .2, envelopedData .3, digestedData
.5, encryptedData .6,
... last OID byte masked) followed by the [0] content tag (a0).
offset 2:6 spans the DER
diff --git
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-crypto-module/src/main/java/org/apache/tika/parser/crypto/CmsClassifier.java
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-crypto-module/src/main/java/org/apache/tika/parser/crypto/CmsClassifier.java
index 069fee76ae..dd5d16d5c8 100644
---
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-crypto-module/src/main/java/org/apache/tika/parser/crypto/CmsClassifier.java
+++
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-crypto-module/src/main/java/org/apache/tika/parser/crypto/CmsClassifier.java
@@ -16,10 +16,15 @@
*/
package org.apache.tika.parser.crypto;
+import java.io.BufferedInputStream;
+import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
+import java.util.Arrays;
+import java.util.Base64;
import java.util.Map;
import org.bouncycastle.asn1.ASN1Encodable;
@@ -48,6 +53,10 @@ public final class CmsClassifier {
// cap the first primitive read when peeking the content-type OID (an OID
is a few bytes)
private static final int OID_PEEK_LIMIT = 1 << 16;
+ private static final byte[][] PEM_PREFIXES = {
+ "-----BEGIN PKCS7".getBytes(StandardCharsets.US_ASCII),
+ "-----BEGIN CMS".getBytes(StandardCharsets.US_ASCII)};
+
static final MediaType PKCS7_MIME = MediaType.application("pkcs7-mime");
static final MediaType PKCS7_SIGNATURE =
MediaType.application("pkcs7-signature");
static final MediaType SIGNED = smime("signed-data");
@@ -70,6 +79,12 @@ public final class CmsClassifier {
* {@code null}), so a real I/O error is not silently masked as a
non-crypto stream.
*/
public static MediaType classify(TikaInputStream tis) throws IOException {
+ try (TikaInputStream der = decodePem(tis)) {
+ return classifyDer(der != null ? der : tis);
+ }
+ }
+
+ private static MediaType classifyDer(TikaInputStream tis) throws
IOException {
ASN1ObjectIdentifier oid = contentType(tis);
if (oid == null) {
return null;
@@ -113,6 +128,43 @@ public final class CmsClassifier {
return null;
}
+ /**
+ * A PEM-armored ({@code -----BEGIN PKCS7} / {@code -----BEGIN CMS})
stream decoded to DER, or
+ * {@code null} if the stream is not PEM CMS or its body does not decode.
The caller closes the
+ * result; {@code tis} itself is spooled, not consumed.
+ *
+ * @throws IOException on a genuine spool failure of {@code tis}.
+ */
+ static TikaInputStream decodePem(TikaInputStream tis) throws IOException {
+ if (tis == null || !startsWithPemPrefix(tis)) {
+ return null;
+ }
+ Path path = tis.getPath();
+ if (path == null) {
+ return null;
+ }
+ TikaInputStream der = TikaInputStream.get(
+ Base64.getMimeDecoder().wrap(new PemBody(new
BufferedInputStream(Files.newInputStream(path)))));
+ try {
+ der.getPath(); // decode eagerly so malformed base64 reads as
"not CMS", not an I/O error
+ return der;
+ } catch (IOException | IllegalArgumentException e) {
+ der.close();
+ return null;
+ }
+ }
+
+ private static boolean startsWithPemPrefix(TikaInputStream tis) throws
IOException {
+ byte[] head = new byte[PEM_PREFIXES[0].length];
+ int n = tis.peek(head);
+ for (byte[] prefix : PEM_PREFIXES) {
+ if (n >= prefix.length && Arrays.equals(head, 0, prefix.length,
prefix, 0, prefix.length)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
/** Cheap DER-{@code SEQUENCE} gate: peek one byte (mark/reset, no spool).
*/
private static boolean looksLikeDerSequence(TikaInputStream tis) throws
IOException {
byte[] head = new byte[1];
@@ -172,4 +224,70 @@ public final class CmsClassifier {
boolean hasCerts =
!parser.getCertificates().getMatches(null).isEmpty();
return (hasCerts && !hasSigners) ? CERTS_ONLY : PKCS7_SIGNATURE;
}
+
+ /** The base64 body of a PEM block: skips the BEGIN line, ends at the
{@code -----END} line. */
+ private static final class PemBody extends FilterInputStream {
+
+ private boolean inBody;
+ private boolean done;
+
+ PemBody(InputStream in) {
+ super(in);
+ }
+
+ @Override
+ public int read() throws IOException {
+ if (done) {
+ return -1;
+ }
+ int c;
+ while (!inBody) {
+ c = in.read();
+ if (c == -1) {
+ done = true;
+ return -1;
+ }
+ inBody = c == '\n';
+ }
+ c = in.read();
+ // '-' is outside the base64 alphabet, so it can only start the
END line
+ if (c == -1 || c == '-') {
+ done = true;
+ return -1;
+ }
+ return c;
+ }
+
+ @Override
+ public int read(byte[] b, int off, int len) throws IOException {
+ int i = 0;
+ for (; i < len; i++) {
+ int c = read();
+ if (c == -1) {
+ break;
+ }
+ b[off + i] = (byte) c;
+ }
+ return i == 0 && len > 0 ? -1 : i;
+ }
+
+ @Override
+ public long skip(long n) throws IOException {
+ long i = 0;
+ while (i < n && read() != -1) {
+ i++;
+ }
+ return i;
+ }
+
+ @Override
+ public int available() {
+ return 0;
+ }
+
+ @Override
+ public boolean markSupported() {
+ return false;
+ }
+ }
}
diff --git
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-crypto-module/src/main/java/org/apache/tika/parser/crypto/Pkcs7Parser.java
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-crypto-module/src/main/java/org/apache/tika/parser/crypto/Pkcs7Parser.java
index a901bc347a..a54119b7bc 100644
---
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-crypto-module/src/main/java/org/apache/tika/parser/crypto/Pkcs7Parser.java
+++
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-crypto-module/src/main/java/org/apache/tika/parser/crypto/Pkcs7Parser.java
@@ -74,29 +74,34 @@ public class Pkcs7Parser implements Parser {
public void parse(TikaInputStream tis, ContentHandler handler, Metadata
metadata,
ParseContext context) throws IOException, SAXException,
TikaException {
tis.setCloseShield();
- try {
- // route on the cheap ContentInfo OID; signedData is then parsed
exactly once (in
- // parseSignedData), which both labels the subtype and extracts
the payload
- ASN1ObjectIdentifier oid = CmsClassifier.contentType(tis);
- if (PKCSObjectIdentifiers.signedData.equals(oid)) {
- parseSignedData(tis, handler, metadata, context);
- } else if (PKCSObjectIdentifiers.id_ct_compressedData.equals(oid))
{
- metadata.set(HttpHeaders.CONTENT_TYPE,
CmsClassifier.COMPRESSED.toString());
- extractCompressedContent(tis, handler, metadata, context);
- } else {
- // enveloped / digested / encrypted (labelled; payload absent,
encrypted, or
- // unsupported) or unknown / non-CMS -> empty doc, no throw
- MediaType type = CmsClassifier.nonSignedType(oid);
- if (type != null) {
- metadata.set(HttpHeaders.CONTENT_TYPE, type.toString());
- }
- emptyDocument(handler, metadata);
- }
+ try (TikaInputStream der = CmsClassifier.decodePem(tis)) {
+ parseDer(der != null ? der : tis, handler, metadata, context);
} finally {
tis.removeCloseShield();
}
}
+ private void parseDer(TikaInputStream tis, ContentHandler handler,
Metadata metadata,
+ ParseContext context) throws IOException,
SAXException, TikaException {
+ // route on the cheap ContentInfo OID; signedData is then parsed
exactly once (in
+ // parseSignedData), which both labels the subtype and extracts the
payload
+ ASN1ObjectIdentifier oid = CmsClassifier.contentType(tis);
+ if (PKCSObjectIdentifiers.signedData.equals(oid)) {
+ parseSignedData(tis, handler, metadata, context);
+ } else if (PKCSObjectIdentifiers.id_ct_compressedData.equals(oid)) {
+ metadata.set(HttpHeaders.CONTENT_TYPE,
CmsClassifier.COMPRESSED.toString());
+ extractCompressedContent(tis, handler, metadata, context);
+ } else {
+ // enveloped / digested / encrypted (labelled; payload absent,
encrypted, or
+ // unsupported) or unknown / non-CMS -> empty doc, no throw
+ MediaType type = CmsClassifier.nonSignedType(oid);
+ if (type != null) {
+ metadata.set(HttpHeaders.CONTENT_TYPE, type.toString());
+ }
+ emptyDocument(handler, metadata);
+ }
+ }
+
/** Parse a signedData message once: set the refined subtype and extract
the embedded payload if present. */
private void parseSignedData(TikaInputStream tis, ContentHandler handler,
Metadata metadata,
ParseContext context)
diff --git
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-crypto-module/src/test/java/org/apache/tika/detect/crypto/Pkcs7DetectorTest.java
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-crypto-module/src/test/java/org/apache/tika/detect/crypto/Pkcs7DetectorTest.java
index 220e73410b..9750206196 100644
---
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-crypto-module/src/test/java/org/apache/tika/detect/crypto/Pkcs7DetectorTest.java
+++
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-crypto-module/src/test/java/org/apache/tika/detect/crypto/Pkcs7DetectorTest.java
@@ -16,6 +16,8 @@
*/
package org.apache.tika.detect.crypto;
+import static org.apache.tika.parser.crypto.PemFixtures.pem;
+import static org.apache.tika.parser.crypto.PemFixtures.resource;
import static org.junit.jupiter.api.Assertions.assertEquals;
import org.junit.jupiter.api.Test;
@@ -31,12 +33,25 @@ public class Pkcs7DetectorTest {
private final Pkcs7Detector detector = new Pkcs7Detector();
private MediaType detect(String resource) throws Exception {
- try (TikaInputStream tis = TikaInputStream.get(
- Pkcs7DetectorTest.class.getResourceAsStream("/test-documents/"
+ resource))) {
+ return detect(resource(resource));
+ }
+
+ private MediaType detect(byte[] bytes) throws Exception {
+ try (TikaInputStream tis = TikaInputStream.get(bytes)) {
return detector.detect(tis, new Metadata(), new ParseContext());
}
}
+ @Test
+ public void testPemSubtypeAtDetectTime() throws Exception {
+ assertEquals(MediaType.parse("application/pkcs7-mime;
smime-type=certs-only"),
+ detect(pem(resource("testPKCS7_certs_only_def.p7c"), "PKCS7",
"\n")));
+ assertEquals(MediaType.parse("application/pkcs7-mime;
smime-type=signed-data"),
+ detect(pem(resource("testPKCS7_signed_data_def.p7m"), "CMS",
"\r\n")));
+ assertEquals(MediaType.parse("application/pkcs7-mime;
smime-type=signed-data"),
+ detect("testPKCS7_signed_data_def.pem"));
+ }
+
@Test
public void testSubtypeAtDetectTime() throws Exception {
assertEquals(MediaType.parse("application/pkcs7-mime;
smime-type=signed-data"),
diff --git
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-crypto-module/src/test/java/org/apache/tika/parser/crypto/PemFixtures.java
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-crypto-module/src/test/java/org/apache/tika/parser/crypto/PemFixtures.java
new file mode 100644
index 0000000000..4fc97bce84
--- /dev/null
+++
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-crypto-module/src/test/java/org/apache/tika/parser/crypto/PemFixtures.java
@@ -0,0 +1,43 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.tika.parser.crypto;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.Base64;
+
+/** Builds PEM-armored test inputs from the DER fixtures. */
+public final class PemFixtures {
+
+ private PemFixtures() {
+ }
+
+ public static byte[] resource(String name) throws IOException {
+ try (InputStream is =
PemFixtures.class.getResourceAsStream("/test-documents/" + name)) {
+ return is.readAllBytes();
+ }
+ }
+
+ /** {@code der} armored as {@code -----BEGIN <label>-----} with 64-column
lines ending in {@code eol}. */
+ public static byte[] pem(byte[] der, String label, String eol) {
+ String body = Base64.getMimeEncoder(64,
eol.getBytes(StandardCharsets.US_ASCII))
+ .encodeToString(der);
+ return ("-----BEGIN " + label + "-----" + eol + body + eol + "-----END
" + label + "-----" + eol)
+ .getBytes(StandardCharsets.US_ASCII);
+ }
+}
diff --git
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-crypto-module/src/test/java/org/apache/tika/parser/crypto/Pkcs7DetectionTest.java
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-crypto-module/src/test/java/org/apache/tika/parser/crypto/Pkcs7DetectionTest.java
index 8e17722244..1c70765932 100644
---
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-crypto-module/src/test/java/org/apache/tika/parser/crypto/Pkcs7DetectionTest.java
+++
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-crypto-module/src/test/java/org/apache/tika/parser/crypto/Pkcs7DetectionTest.java
@@ -52,5 +52,8 @@ public class Pkcs7DetectionTest {
assertEquals("application/pkcs7-signature",
detect("testPKCS7_enveloped_def.p7m"));
// TIKA-2856: a short-form-length digestedData is now caught coarsely
(routes to Pkcs7Parser)
assertEquals("application/pkcs7-signature",
detect("testPKCS7_digested.p7"));
+ // TIKA-5002: `openssl cms -outform PEM` armor routes to Pkcs7Parser
+ assertEquals("application/pkcs7-signature",
tika.detect(PemFixtures.pem(
+ PemFixtures.resource("testPKCS7_certs_only_def.p7c"), "CMS",
"\n")));
}
}
diff --git
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-crypto-module/src/test/java/org/apache/tika/parser/crypto/Pkcs7ParserTest.java
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-crypto-module/src/test/java/org/apache/tika/parser/crypto/Pkcs7ParserTest.java
index 6fac51ff64..3a75ef100c 100644
---
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-crypto-module/src/test/java/org/apache/tika/parser/crypto/Pkcs7ParserTest.java
+++
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-crypto-module/src/test/java/org/apache/tika/parser/crypto/Pkcs7ParserTest.java
@@ -16,28 +16,107 @@
*/
package org.apache.tika.parser.crypto;
+import static org.apache.tika.parser.crypto.PemFixtures.pem;
+import static org.apache.tika.parser.crypto.PemFixtures.resource;
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.Set;
+import java.util.stream.Stream;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.xml.sax.ContentHandler;
import org.xml.sax.helpers.DefaultHandler;
import org.apache.tika.io.TikaInputStream;
import org.apache.tika.metadata.HttpHeaders;
import org.apache.tika.metadata.Metadata;
+import org.apache.tika.mime.MediaType;
import org.apache.tika.parser.ParseContext;
+import org.apache.tika.parser.Parser;
public class Pkcs7ParserTest {
/** Parse a fixture and return the content type the parser refined it to.
*/
private String parsedType(String resource) throws Exception {
+ return parsedType(resource(resource), new ParseContext());
+ }
+
+ private String parsedType(byte[] bytes, ParseContext context) throws
Exception {
Metadata metadata = new Metadata();
- try (TikaInputStream tis = TikaInputStream.get(
- Pkcs7ParserTest.class.getResourceAsStream("/test-documents/" +
resource))) {
- new Pkcs7Parser().parse(tis, new DefaultHandler(), metadata, new
ParseContext());
+ try (TikaInputStream tis = TikaInputStream.get(bytes)) {
+ new Pkcs7Parser().parse(tis, new DefaultHandler(), metadata,
context);
}
return metadata.get(HttpHeaders.CONTENT_TYPE);
}
+ static Stream<Arguments> pemCases() {
+ return Stream.of("testPKCS7_signed_data_def.p7m",
"testPKCS7_signed_data_ind.p7m",
+ "testPKCS7_enveloped_def.p7m",
"testPKCS7_certs_only_def.p7c",
+ "testPKCS7_certs_only_ind.p7c",
"testPKCS7_compressed_def_long.p7z",
+ "testPKCS7_digested.p7", "testPKCS7_signature_def.p7s")
+ .flatMap(r -> Stream.of(Arguments.of(r, "PKCS7", "\n"),
+ Arguments.of(r, "CMS", "\r\n")));
+ }
+
+ /** TIKA-5002: PEM armor refines exactly like the DER it wraps. */
+ @ParameterizedTest
+ @MethodSource("pemCases")
+ public void testPemRefinesLikeDer(String resource, String label, String
eol) throws Exception {
+ byte[] der = resource(resource);
+ String expected = parsedType(der, new ParseContext());
+ assertNotNull(expected);
+ assertEquals(expected, parsedType(pem(der, label, eol), new
ParseContext()));
+ }
+
+ @Test
+ public void testPemFixture() throws Exception {
+ assertEquals("application/pkcs7-mime; smime-type=signed-data",
+ parsedType("testPKCS7_signed_data_def.pem"));
+ }
+
+ @Test
+ public void testPemPayloadExtracted() throws Exception {
+ byte[] der = resource("test.xml.p7m");
+ assertArrayEquals(extractedPayload(der), extractedPayload(pem(der,
"PKCS7", "\n")));
+ }
+
+ @Test
+ public void testMalformedPemDoesNotThrow() throws Exception {
+ byte[] junk = "-----BEGIN PKCS7-----\nA===\n-----END PKCS7-----\n"
+ .getBytes(StandardCharsets.US_ASCII);
+ assertNull(parsedType(junk, new ParseContext()));
+ byte[] notCms = pem("not cms".getBytes(StandardCharsets.US_ASCII),
"CMS", "\n");
+ assertNull(parsedType(notCms, new ParseContext()));
+ }
+
+ private byte[] extractedPayload(byte[] input) throws Exception {
+ byte[][] payload = new byte[1][];
+ ParseContext context = new ParseContext();
+ context.set(Parser.class, new Parser() {
+ @Override
+ public Set<MediaType> getSupportedTypes(ParseContext ctx) {
+ return Set.of();
+ }
+
+ @Override
+ public void parse(TikaInputStream tis, ContentHandler handler,
Metadata metadata,
+ ParseContext ctx) throws IOException {
+ payload[0] = tis.readAllBytes();
+ }
+ });
+ parsedType(input, context);
+ assertNotNull(payload[0]);
+ return payload[0];
+ }
+
@Test
public void testSubtypeRefinement() throws Exception {
assertEquals("application/pkcs7-mime; smime-type=signed-data",