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 ffd488e9e5 TIKA-1997 -- pkcs detection
ffd488e9e5 is described below

commit ffd488e9e59e4fa90a9f79e7ba0443a2476d4ed9
Author: tallison <[email protected]>
AuthorDate: Tue Jul 14 12:26:52 2026 -0400

    TIKA-1997 -- pkcs detection
---
 CHANGES.txt                                        |   9 ++
 docs/modules/ROOT/examples/detector-pkcs7.json     |   6 +
 .../ROOT/pages/configuration/detectors.adoc        |  57 ++++++++
 docs/modules/ROOT/pages/configuration/index.adoc   |   1 +
 .../org/apache/tika/mime/tika-mimetypes.xml        |  25 +++-
 .../org/apache/tika/config/ConfigExamplesTest.java |  30 +++++
 .../java/org/apache/tika/mime/TestMimeTypes.java   |   7 +-
 .../tika/parser/crypto/Pkcs7EndToEndTest.java      |  65 ++++++++++
 .../resources/config-examples/detector-pkcs7.json  |   6 +
 .../src/test/resources/test-documents/test.xml.p7m | Bin 0 -> 2504 bytes
 .../testPKCS7_compressed_def_long.p7z              | Bin 0 -> 331 bytes
 .../test-documents/testPKCS7_signed_data_def.p7m   | Bin 0 -> 1574 bytes
 .../apache/tika/detect/crypto/Pkcs7Detector.java   |  50 +++++++
 .../apache/tika/parser/crypto/CmsClassifier.java   | 143 +++++++++++++++++++++
 .../org/apache/tika/parser/crypto/Pkcs7Parser.java |  77 ++++++++---
 .../tika/detect/crypto/Pkcs7DetectorTest.java      |  59 +++++++++
 .../tika/parser/crypto/CmsClassifierTest.java      |  62 +++++++++
 .../tika/parser/crypto/Pkcs7DetectionTest.java     |  54 ++++++++
 .../apache/tika/parser/crypto/Pkcs7ParserTest.java |  58 ++++++---
 .../src/test/resources/test-documents/test.xml.p7m | Bin 0 -> 2504 bytes
 .../test-documents/testPKCS7_certs_only_def.p7c    | Bin 0 -> 2972 bytes
 .../test-documents/testPKCS7_certs_only_ind.p7c    | Bin 0 -> 2976 bytes
 .../testPKCS7_compressed_def_long.p7z              | Bin 0 -> 331 bytes
 .../testPKCS7_compressed_def_short.p7z             | Bin 0 -> 125 bytes
 .../test-documents/testPKCS7_compressed_ind.p7z    | Bin 0 -> 141 bytes
 .../test-documents/testPKCS7_enveloped_def.p7m     | Bin 0 -> 350 bytes
 .../test-documents/testPKCS7_enveloped_ind.p7m     | Bin 0 -> 356 bytes
 .../test-documents/testPKCS7_signature_def.p7s     | Bin 0 -> 1566 bytes
 .../test-documents/testPKCS7_signature_ind.p7s     | Bin 0 -> 1568 bytes
 .../test-documents/testPKCS7_signed_data_def.p7m   | Bin 0 -> 1574 bytes
 .../test-documents/testPKCS7_signed_data_def.pem   |  35 +++++
 .../test-documents/testPKCS7_signed_data_ind.p7m   | Bin 0 -> 1582 bytes
 .../resources/test-documents/testRSAKEYandCERT.p12 | Bin 0 -> 1717 bytes
 33 files changed, 706 insertions(+), 38 deletions(-)

diff --git a/CHANGES.txt b/CHANGES.txt
index 92606e27fd..7fe618fb7e 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,3 +1,12 @@
+Release 4.0.0 - ???
+
+  NEW FEATURES
+
+   * Content-based detection of ASN.1/DER crypto containers at parse time. An
+     opt-in Pkcs7Detector surfaces the subtype at detect() time,
+     but must be enabled via configuration (TIKA-1997).
+
+
 Release 4.0.0-beta-1 - 6/29/2026
 
   BREAKING CHANGES
diff --git a/docs/modules/ROOT/examples/detector-pkcs7.json 
b/docs/modules/ROOT/examples/detector-pkcs7.json
new file mode 100644
index 0000000000..c4d7c5c9b8
--- /dev/null
+++ b/docs/modules/ROOT/examples/detector-pkcs7.json
@@ -0,0 +1,6 @@
+{
+  "detectors": [
+    { "pkcs-7-detector": {} },
+    { "default-detector": {} }
+  ]
+}
diff --git a/docs/modules/ROOT/pages/configuration/detectors.adoc 
b/docs/modules/ROOT/pages/configuration/detectors.adoc
new file mode 100644
index 0000000000..1ef2e1c058
--- /dev/null
+++ b/docs/modules/ROOT/pages/configuration/detectors.adoc
@@ -0,0 +1,57 @@
+//
+// 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.
+//
+
+= Detector Configuration
+
+A `detectors` list loads *only* the detectors it names — every other detector 
is dropped.
+To add a detector while keeping the default detection chain, include a 
`default-detector`
+entry (the same pattern as `default-parser`; see 
xref:configuration/index.adoc[Configuration]).
+
+== Enabling the PKCS7/CMS detector
+
+Content detection of ASN.1/DER cryptographic containers is *coarse by 
default*: a CMS/PKCS7
+message is detected as `application/pkcs7-signature`, and the exact CMS subtype
+(signed vs. enveloped vs. certs-only) is refined by `Pkcs7Parser` at *parse* 
time — it is set
+on the output content type, the same way `PDFParser` refines a PDF to 
`application/illustrator`.
+
+If you need that subtype from `Tika.detect()` *without* parsing, enable the 
opt-in
+`pkcs-7-detector`. It is not loaded by default (so the common detect path 
stays cheap and does
+no ASN.1 work on every stream); add it ahead of `default-detector`:
+
+[source,json]
+----
+include::example$detector-pkcs7.json[]
+----
+icon:github[] 
https://github.com/apache/tika/blob/main/tika-parsers/tika-parsers-standard/tika-parsers-standard-integration-tests/src/test/resources/config-examples/detector-pkcs7.json[View
 source on GitHub]
+
+[IMPORTANT]
+====
+List `pkcs-7-detector` *before* `default-detector`. It returns a parameterized
+`application/pkcs7-mime; smime-type=...`, which is a *sibling* of the magic 
detector's
+`application/pkcs7-signature` (neither is a specialization of the other). A 
`CompositeDetector`
+replaces the running type only with a strict specialization, so of two sibling 
results it keeps
+the first — the opt-in detector must run first to win.
+====
+
+With this configuration, `Tika.detect()` on a signed CMS message returns
+`application/pkcs7-mime; smime-type=signed-data` instead of the coarse
+`application/pkcs7-signature`. This is verified end-to-end in
+link:https://github.com/apache/tika/blob/main/tika-parsers/tika-parsers-standard/tika-parsers-standard-integration-tests/src/test/java/org/apache/tika/config/ConfigExamplesTest.java[`ConfigExamplesTest`].
+
+NOTE: The default (magic-only) path already detects the crypto *families* — 
PKCS7/CMS,
+RFC 5544 timestamped-data, and PKCS12 — so most users do not need this 
detector. Enable it only
+when a routing or downstream decision depends on the CMS subtype before 
parsing.
diff --git a/docs/modules/ROOT/pages/configuration/index.adoc 
b/docs/modules/ROOT/pages/configuration/index.adoc
index 19bcbe165f..afd6f35fd7 100644
--- a/docs/modules/ROOT/pages/configuration/index.adoc
+++ b/docs/modules/ROOT/pages/configuration/index.adoc
@@ -131,6 +131,7 @@ JSON uses the backslash as an escape character, so path 
options (e.g. `tesseract
 
 === Other Configuration
 
+* xref:configuration/detectors.adoc[Detectors] — Configuring content (MIME) 
detection, incl. the opt-in PKCS7/CMS detector
 * xref:configuration/digesters.adoc[Digesters] — Computing cryptographic 
hashes of documents
 * xref:configuration/encoding-detectors.adoc[Encoding Detectors] — Configuring 
charset/encoding detection
 
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 34b9d27943..a992928769 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
@@ -893,6 +893,15 @@
   <mime-type type="application/pkcs7-mime">
     <glob pattern="*.p7m"/>
     <glob pattern="*.p7c"/>
+    <!-- CMS content types in the 1.2.840.113549.1.9.16.1.* arc 
(compressedData .9, authData .2,
+         authEnvelopedData .23, ...) with the final OID byte masked. 
timestamped-data (.31) has the
+         same shape but its own higher-priority magic below. The .7 arc 
(signed/enveloped/...) is
+         matched by the pkcs7-signature block. Pkcs7Parser refines all of 
these to the smime-type. -->
+    <magic priority="50">
+      <match value="0x30" offset="0">
+        <match value="0x060B2A864886F70D0109100100" 
mask="0xFFFFFFFFFFFFFFFFFFFFFFFF00" type="string" offset="2:6"/>
+      </match>
+    </magic>
   </mime-type>
 
   <mime-type type="application/pkcs7-signature">
@@ -926,8 +935,13 @@
 
   <mime-type type="application/timestamped-data">
     <glob pattern="*.tsd"/>
-    <magic priority="50">
-      <match value="0x3080060B2A864886F7" type="string" offset="0"/>
+    <!-- CMS ContentInfo whose contentType is id-ct-timestampedData 
(1.2.840.113549.1.9.16.1.31),
+         matching the full OID across the DER SEQUENCE length forms (short + 
1..4-byte long).
+         Higher priority than the masked pkcs7-mime .9.16.1.* magic so this 
exact OID wins. -->
+    <magic priority="60">
+      <match value="0x30" offset="0">
+        <match value="0x060B2A864886F70D010910011F" type="string" 
offset="2:6"/>
+      </match>
     </magic>
   </mime-type>
 
@@ -4867,6 +4881,13 @@
   <mime-type type="application/x-pkcs12">
     <glob pattern="*.p12"/>
     <glob pattern="*.pfx"/>
+    <!-- PFX ::= SEQUENCE { version INTEGER (v3), authSafe ContentInfo 
SEQUENCE, ... }: the
+         INTEGER 3 then a SEQUENCE (02 01 03 30) tells it apart from CMS (OID 
next) and keys. -->
+    <magic priority="50">
+      <match value="0x30" offset="0">
+        <match value="0x02010330" offset="2:6"/>
+      </match>
+    </magic>
   </mime-type>
   <mime-type type="application/x-pkcs7-certificates">
     <glob pattern="*.p7b"/>
diff --git 
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-integration-tests/src/test/java/org/apache/tika/config/ConfigExamplesTest.java
 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-integration-tests/src/test/java/org/apache/tika/config/ConfigExamplesTest.java
index 1429984cbf..b16a94b408 100644
--- 
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-integration-tests/src/test/java/org/apache/tika/config/ConfigExamplesTest.java
+++ 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-integration-tests/src/test/java/org/apache/tika/config/ConfigExamplesTest.java
@@ -16,6 +16,7 @@
  */
 package org.apache.tika.config;
 
+import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
 
 import java.io.InputStream;
@@ -27,6 +28,10 @@ import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.io.TempDir;
 
 import org.apache.tika.config.loader.TikaLoader;
+import org.apache.tika.detect.Detector;
+import org.apache.tika.io.TikaInputStream;
+import org.apache.tika.metadata.Metadata;
+import org.apache.tika.parser.ParseContext;
 import org.apache.tika.parser.Parser;
 
 /**
@@ -94,4 +99,29 @@ public class ConfigExamplesTest {
     public void testFullMigrationExample() throws Exception {
         loadAndValidate("migration-full-example.json");
     }
+
+    /** The detectors.adoc example: enabling the opt-in pkcs-7-detector yields 
the CMS smime-type. */
+    @Test
+    public void testPkcs7DetectorConfig() throws Exception {
+        Detector detector = loadDetectors("detector-pkcs7.json");
+        assertEquals("application/pkcs7-mime; smime-type=signed-data",
+                detect(detector, "testPKCS7_signed_data_def.p7m"));
+    }
+
+    private Detector loadDetectors(String resourceName) throws Exception {
+        try (InputStream is = getClass().getResourceAsStream(EXAMPLES_DIR + 
resourceName)) {
+            assertNotNull(is, "Resource not found: " + resourceName);
+            Path configFile = tempDir.resolve("tika-config.json");
+            Files.writeString(configFile, new String(is.readAllBytes(), 
StandardCharsets.UTF_8),
+                    StandardCharsets.UTF_8);
+            return TikaLoader.load(configFile).loadDetectors();
+        }
+    }
+
+    private String detect(Detector detector, String resource) throws Exception 
{
+        try (TikaInputStream tis = TikaInputStream.get(
+                getClass().getResourceAsStream("/test-documents/" + 
resource))) {
+            return detector.detect(tis, new Metadata(), new 
ParseContext()).toString();
+        }
+    }
 }
diff --git 
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-integration-tests/src/test/java/org/apache/tika/mime/TestMimeTypes.java
 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-integration-tests/src/test/java/org/apache/tika/mime/TestMimeTypes.java
index ae731d9f30..b3e220235a 100644
--- 
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-integration-tests/src/test/java/org/apache/tika/mime/TestMimeTypes.java
+++ 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-integration-tests/src/test/java/org/apache/tika/mime/TestMimeTypes.java
@@ -1242,10 +1242,9 @@ public class TestMimeTypes {
         // Parameters only have PEM form, always need data
         assertTypeByData("application/x-x509-dsa-parameters", 
"testDSAPARAMS.pem");
         assertTypeByData("application/x-x509-ec-parameters", 
"testECPARAMS.pem");
-        // PKCS12 wrappers of Certs+Keys cannot currently be identified
-        // Once solved, see TIKA-3784, ought to work for name or data
-        //assertType("application/x-pkcs12", "testRSAKEYandCERT.p12");
-        //assertTypeByData("application/x-pkcs12", "testRSAKEYandCERT.p12"); 
// pass=tika
+        // PKCS12 is now identified by content via the PFX version-3 anchor 
(TIKA-1997/TIKA-3784)
+        assertType("application/x-pkcs12", "testRSAKEYandCERT.p12");
+        assertTypeByData("application/x-pkcs12", "testRSAKEYandCERT.p12");
         assertTypeByData("application/x-java-keystore", "KeyStore.jks");
     }
 
diff --git 
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-integration-tests/src/test/java/org/apache/tika/parser/crypto/Pkcs7EndToEndTest.java
 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-integration-tests/src/test/java/org/apache/tika/parser/crypto/Pkcs7EndToEndTest.java
new file mode 100644
index 0000000000..2dcdc8788a
--- /dev/null
+++ 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-integration-tests/src/test/java/org/apache/tika/parser/crypto/Pkcs7EndToEndTest.java
@@ -0,0 +1,65 @@
+/*
+ * 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 static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.List;
+
+import org.junit.jupiter.api.Test;
+
+import org.apache.tika.TikaTest;
+import org.apache.tika.metadata.Metadata;
+import org.apache.tika.metadata.TikaCoreProperties;
+
+public class Pkcs7EndToEndTest extends TikaTest {
+
+    /**
+     * TIKA-1997: a CMS-signed XML is detected as PKCS7, routed to 
Pkcs7Parser, refined to
+     * signed-data, and its inner XML payload is unwrapped and its content 
extracted.
+     */
+    @Test
+    public void testSignedXmlIsExtracted() throws Exception {
+        List<Metadata> metadataList = getRecursiveMetadata("test.xml.p7m");
+        // the container is refined from the coarse detected type to the CMS 
subtype
+        assertEquals("application/pkcs7-mime; smime-type=signed-data",
+                metadataList.get(0).get(Metadata.CONTENT_TYPE));
+        // the signed XML payload is unwrapped and extracted
+        StringBuilder content = new StringBuilder();
+        for (Metadata m : metadataList) {
+            String c = m.get(TikaCoreProperties.TIKA_CONTENT);
+            if (c != null) {
+                content.append(c);
+            }
+        }
+        assertContains("TEST_APP", content.toString());
+        assertContains("ESTRAZIONE", content.toString());
+    }
+
+    /** CMS compressedData (RFC 3274) is inflated and its inner payload 
extracted (here a PDF). */
+    @Test
+    public void testCompressedContentIsExtracted() throws Exception {
+        List<Metadata> metadataList = 
getRecursiveMetadata("testPKCS7_compressed_def_long.p7z");
+        assertEquals("application/pkcs7-mime; smime-type=compressed-data",
+                metadataList.get(0).get(Metadata.CONTENT_TYPE));
+        boolean pdf = metadataList.stream()
+                .map(m -> m.get(Metadata.CONTENT_TYPE))
+                .anyMatch(ct -> ct != null && 
ct.startsWith("application/pdf"));
+        assertTrue(pdf, "expected an embedded application/pdf inflated from 
the compressed payload");
+    }
+}
diff --git 
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-integration-tests/src/test/resources/config-examples/detector-pkcs7.json
 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-integration-tests/src/test/resources/config-examples/detector-pkcs7.json
new file mode 100644
index 0000000000..c4d7c5c9b8
--- /dev/null
+++ 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-integration-tests/src/test/resources/config-examples/detector-pkcs7.json
@@ -0,0 +1,6 @@
+{
+  "detectors": [
+    { "pkcs-7-detector": {} },
+    { "default-detector": {} }
+  ]
+}
diff --git 
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-integration-tests/src/test/resources/test-documents/test.xml.p7m
 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-integration-tests/src/test/resources/test-documents/test.xml.p7m
new file mode 100644
index 0000000000..ce21cc961f
Binary files /dev/null and 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-integration-tests/src/test/resources/test-documents/test.xml.p7m
 differ
diff --git 
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-integration-tests/src/test/resources/test-documents/testPKCS7_compressed_def_long.p7z
 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-integration-tests/src/test/resources/test-documents/testPKCS7_compressed_def_long.p7z
new file mode 100644
index 0000000000..74474876ed
Binary files /dev/null and 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-integration-tests/src/test/resources/test-documents/testPKCS7_compressed_def_long.p7z
 differ
diff --git 
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-integration-tests/src/test/resources/test-documents/testPKCS7_signed_data_def.p7m
 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-integration-tests/src/test/resources/test-documents/testPKCS7_signed_data_def.p7m
new file mode 100644
index 0000000000..1f0adb7499
Binary files /dev/null and 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-integration-tests/src/test/resources/test-documents/testPKCS7_signed_data_def.p7m
 differ
diff --git 
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-crypto-module/src/main/java/org/apache/tika/detect/crypto/Pkcs7Detector.java
 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-crypto-module/src/main/java/org/apache/tika/detect/crypto/Pkcs7Detector.java
new file mode 100644
index 0000000000..bda01baa6f
--- /dev/null
+++ 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-crypto-module/src/main/java/org/apache/tika/detect/crypto/Pkcs7Detector.java
@@ -0,0 +1,50 @@
+/*
+ * 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.detect.crypto;
+
+import java.io.IOException;
+
+import org.apache.tika.annotation.TikaComponent;
+import org.apache.tika.detect.Detector;
+import org.apache.tika.io.TikaInputStream;
+import org.apache.tika.metadata.Metadata;
+import org.apache.tika.mime.MediaType;
+import org.apache.tika.parser.ParseContext;
+import org.apache.tika.parser.crypto.CmsClassifier;
+
+/**
+ * Optional detector that surfaces the CMS/PKCS7 {@code smime-type} at 
detection time, using the
+ * same {@link CmsClassifier} as {@code Pkcs7Parser}. Marked {@code spi = 
false} so it is NOT
+ * auto-loaded (the default detect path stays cheap — magic gives the coarse 
family and the parser
+ * refines the subtype); enable it by name in a tika-config when you need the 
subtype from
+ * {@code detect()} without parsing.
+ */
+@TikaComponent(spi = false)
+public class Pkcs7Detector implements Detector {
+
+    private static final long serialVersionUID = 6879398011936510897L;
+
+    @Override
+    public MediaType detect(TikaInputStream tis, Metadata metadata, 
ParseContext parseContext)
+            throws IOException {
+        if (tis == null) {
+            return MediaType.OCTET_STREAM;
+        }
+        MediaType type = CmsClassifier.classify(tis);
+        return type != null ? type : MediaType.OCTET_STREAM;
+    }
+}
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
new file mode 100644
index 0000000000..2e3777ac1f
--- /dev/null
+++ 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-crypto-module/src/main/java/org/apache/tika/parser/crypto/CmsClassifier.java
@@ -0,0 +1,143 @@
+/*
+ * 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.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Map;
+
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1SequenceParser;
+import org.bouncycastle.asn1.ASN1StreamParser;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.cms.CMSSignedDataParser;
+import org.bouncycastle.cms.CMSTypedStream;
+import org.bouncycastle.operator.DigestCalculatorProvider;
+import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder;
+
+import org.apache.tika.io.TikaInputStream;
+import org.apache.tika.mime.MediaType;
+
+/**
+ * Classifies a CMS/PKCS7 stream by its {@code ContentInfo} content-type OID 
(and, for signedData,
+ * its sub-shape) into a refined Tika {@link MediaType}. Shared by {@link 
Pkcs7Parser} (parse-time
+ * labelling) and the optional detector. Reads via the spooled file and 
returns {@code null} if the
+ * stream is not recognizable CMS, so a caller can fall back to the coarse 
type.
+ */
+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;
+
+    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");
+    static final MediaType CERTS_ONLY = smime("certs-only");
+    static final MediaType ENVELOPED = smime("enveloped-data");
+    static final MediaType COMPRESSED = smime("compressed-data");
+    static final MediaType DIGESTED = smime("digested-data");
+    static final MediaType ENCRYPTED = smime("encrypted-data");
+
+    private CmsClassifier() {
+    }
+
+    private static MediaType smime(String smimeType) {
+        return new MediaType("application", "pkcs7-mime", Map.of("smime-type", 
smimeType));
+    }
+
+    /** @return the refined media type, or {@code null} if not recognizable 
CMS. */
+    public static MediaType classify(TikaInputStream tis) {
+        if (tis == null) {
+            return null;
+        }
+        Path path;
+        try {
+            path = tis.getPath();   // spool to a file so we can read it more 
than once
+        } catch (Exception e) {
+            return null;
+        }
+        if (path == null) {
+            return null;
+        }
+        ASN1ObjectIdentifier oid = contentType(path);
+        if (oid == null) {
+            return null;
+        }
+        if (PKCSObjectIdentifiers.signedData.equals(oid)) {
+            return refineSigned(path);
+        } else if (PKCSObjectIdentifiers.envelopedData.equals(oid)) {
+            return ENVELOPED;
+        } else if (PKCSObjectIdentifiers.id_ct_compressedData.equals(oid)) {
+            return COMPRESSED;
+        } else if (PKCSObjectIdentifiers.digestedData.equals(oid)) {
+            return DIGESTED;
+        } else if (PKCSObjectIdentifiers.encryptedData.equals(oid)) {
+            return ENCRYPTED;
+        }
+        return null;   // plain data or a non-CMS OID: let the coarse type 
stand
+    }
+
+    /** Lazily read only the outer {@code SEQUENCE}'s first element (the 
ContentInfo OID). */
+    private static ASN1ObjectIdentifier contentType(Path path) {
+        try (InputStream is = Files.newInputStream(path)) {
+            ASN1StreamParser sp = new ASN1StreamParser(is, OID_PEEK_LIMIT);
+            ASN1Encodable top = sp.readObject();
+            if (!(top instanceof ASN1SequenceParser)) {
+                return null;
+            }
+            ASN1Encodable first = ((ASN1SequenceParser) top).readObject();
+            if (first == null) {
+                return null;
+            }
+            ASN1Primitive prim = first.toASN1Primitive();
+            return prim instanceof ASN1ObjectIdentifier ? 
(ASN1ObjectIdentifier) prim : null;
+        } catch (Exception e) {
+            return null;
+        }
+    }
+
+    /**
+     * Distinguish a signedData's sub-shape via BouncyCastle's streaming 
parser: content present ->
+     * signed-data (no drain); otherwise certs + no signers -> certs-only, 
else a detached signature.
+     */
+    private static MediaType refineSigned(Path path) {
+        try (InputStream is = Files.newInputStream(path)) {
+            DigestCalculatorProvider dcp =
+                    new 
JcaDigestCalculatorProviderBuilder().setProvider("BC").build();
+            CMSSignedDataParser parser = new CMSSignedDataParser(dcp, is);
+            try {
+                CMSTypedStream content = parser.getSignedContent();
+                if (content != null) {
+                    return SIGNED;   // has an embedded payload; no need to 
drain it to classify
+                }
+                boolean hasSigners = 
!parser.getSignerInfos().getSigners().isEmpty();
+                boolean hasCerts = 
!parser.getCertificates().getMatches(null).isEmpty();
+                if (hasCerts && !hasSigners) {
+                    return CERTS_ONLY;
+                }
+                return PKCS7_SIGNATURE;
+            } finally {
+                parser.close();
+            }
+        } catch (Exception e) {
+            return PKCS7_MIME;   // it is signedData but we could not refine 
the sub-shape
+        }
+    }
+}
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 a7a4dd7ec9..75b7c570ec 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
@@ -17,11 +17,15 @@
 package org.apache.tika.parser.crypto;
 
 import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
 import java.util.Set;
 
+import org.bouncycastle.cms.CMSCompressedDataParser;
 import org.bouncycastle.cms.CMSException;
 import org.bouncycastle.cms.CMSSignedDataParser;
 import org.bouncycastle.cms.CMSTypedStream;
+import org.bouncycastle.cms.jcajce.ZlibExpanderProvider;
 import org.bouncycastle.operator.DigestCalculatorProvider;
 import org.bouncycastle.operator.OperatorCreationException;
 import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder;
@@ -30,31 +34,35 @@ import org.xml.sax.SAXException;
 
 import org.apache.tika.annotation.TikaComponent;
 import org.apache.tika.exception.TikaException;
+import org.apache.tika.io.BoundedInputStream;
 import org.apache.tika.io.TikaInputStream;
 import org.apache.tika.metadata.Metadata;
 import org.apache.tika.mime.MediaType;
 import org.apache.tika.parser.EmptyParser;
 import org.apache.tika.parser.ParseContext;
 import org.apache.tika.parser.Parser;
+import org.apache.tika.sax.XHTMLContentHandler;
 
 /**
- * Basic parser for PKCS7 data.
+ * Parser for PKCS7/CMS data. Refines the content type to the CMS subtype 
(illustrator-style:
+ * coarse magic routes here, this parser sets the {@code smime-type}) and 
extracts the embedded
+ * content of a signed-data message. Non-signed or detached messages are 
labelled but not
+ * extracted (their payload is absent, encrypted, or unsupported) and never 
throw.
  */
 @TikaComponent
 public class Pkcs7Parser implements Parser {
 
-    /**
-     * Serial version UID
-     */
     private static final long serialVersionUID = -7310531559075115044L;
 
     private static final MediaType PKCS7_MIME = 
MediaType.application("pkcs7-mime");
-
     private static final MediaType PKCS7_SIGNATURE = 
MediaType.application("pkcs7-signature");
-
     private static final Set<MediaType> SUPPORTED_TYPES =
             MediaType.set(PKCS7_MIME, PKCS7_SIGNATURE);
 
+    // cap the inflated output of a CMS compressedData so a zlib bomb (tiny 
compressed -> huge
+    // output) can't blow up the parse; content beyond this is dropped
+    private static final long MAX_DECOMPRESSED = 100L * 1024 * 1024;
+
     public Set<MediaType> getSupportedTypes(ParseContext context) {
         return SUPPORTED_TYPES;
     }
@@ -63,18 +71,40 @@ public class Pkcs7Parser implements Parser {
                       ParseContext context) throws IOException, SAXException, 
TikaException {
         tis.setCloseShield();
         try {
+            MediaType type = CmsClassifier.classify(tis);
+            if (type != null) {
+                metadata.set(Metadata.CONTENT_TYPE, type.toString());
+            }
+            if (CmsClassifier.SIGNED.equals(type)) {
+                extractSignedContent(tis, handler, metadata, context);
+            } else if (CmsClassifier.COMPRESSED.equals(type)) {
+                extractCompressedContent(tis, handler, metadata, context);
+            } else {
+                // detached signature / certs-only / enveloped / encrypted / 
unknown: no embedded
+                // plaintext to extract (absent, encrypted, or unsupported) -> 
empty doc, no throw
+                XHTMLContentHandler xhtml = new XHTMLContentHandler(handler, 
metadata);
+                xhtml.startDocument();
+                xhtml.endDocument();
+            }
+        } finally {
+            tis.removeCloseShield();
+        }
+    }
+
+    private void extractSignedContent(TikaInputStream tis, ContentHandler 
handler, Metadata metadata,
+                                      ParseContext context)
+            throws IOException, SAXException, TikaException {
+        try (InputStream is = Files.newInputStream(tis.getPath())) {
             DigestCalculatorProvider digestCalculatorProvider =
                     new 
JcaDigestCalculatorProviderBuilder().setProvider("BC").build();
-            CMSSignedDataParser parser = new 
CMSSignedDataParser(digestCalculatorProvider, tis);
+            CMSSignedDataParser parser = new 
CMSSignedDataParser(digestCalculatorProvider, is);
             try {
                 CMSTypedStream content = parser.getSignedContent();
-                if (content == null) {
-                    throw new TikaException(
-                            "cannot parse detached pkcs7 signature (no signed 
data to parse)");
-                }
-                try (TikaInputStream contentTis = 
TikaInputStream.get(content.getContentStream())) {
-                    Parser delegate = context.get(Parser.class, 
EmptyParser.INSTANCE);
-                    delegate.parse(contentTis, handler, 
Metadata.newInstance(context), context);
+                if (content != null) {
+                    try (TikaInputStream contentTis = 
TikaInputStream.get(content.getContentStream())) {
+                        Parser delegate = context.get(Parser.class, 
EmptyParser.INSTANCE);
+                        delegate.parse(contentTis, handler, 
Metadata.newInstance(context), context);
+                    }
                 }
             } finally {
                 parser.close();
@@ -83,8 +113,23 @@ public class Pkcs7Parser implements Parser {
             throw new TikaException("Unable to create 
DigestCalculatorProvider", e);
         } catch (CMSException e) {
             throw new TikaException("Unable to parse pkcs7 signed data", e);
-        } finally {
-            tis.removeCloseShield();
+        }
+    }
+
+    /** Inflate CMS compressedData (RFC 3274, zlib) and delegate-parse the 
inner payload. */
+    private void extractCompressedContent(TikaInputStream tis, ContentHandler 
handler,
+                                          Metadata metadata, ParseContext 
context)
+            throws IOException, SAXException, TikaException {
+        try (InputStream is = Files.newInputStream(tis.getPath())) {
+            CMSCompressedDataParser parser = new CMSCompressedDataParser(is);
+            CMSTypedStream content = parser.getContent(new 
ZlibExpanderProvider());
+            InputStream inflated = new BoundedInputStream(MAX_DECOMPRESSED, 
content.getContentStream());
+            try (TikaInputStream contentTis = TikaInputStream.get(inflated)) {
+                Parser delegate = context.get(Parser.class, 
EmptyParser.INSTANCE);
+                delegate.parse(contentTis, handler, 
Metadata.newInstance(context), context);
+            }
+        } catch (CMSException e) {
+            throw new TikaException("Unable to parse pkcs7 compressed data", 
e);
         }
     }
 
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
new file mode 100644
index 0000000000..220e73410b
--- /dev/null
+++ 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-crypto-module/src/test/java/org/apache/tika/detect/crypto/Pkcs7DetectorTest.java
@@ -0,0 +1,59 @@
+/*
+ * 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.detect.crypto;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.junit.jupiter.api.Test;
+
+import org.apache.tika.io.TikaInputStream;
+import org.apache.tika.metadata.Metadata;
+import org.apache.tika.mime.MediaType;
+import org.apache.tika.parser.ParseContext;
+
+/** The opt-in detector surfaces the CMS smime-type at detect time 
(instantiated directly, not SPI). */
+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 detector.detect(tis, new Metadata(), new ParseContext());
+        }
+    }
+
+    @Test
+    public void testSubtypeAtDetectTime() throws Exception {
+        assertEquals(MediaType.parse("application/pkcs7-mime; 
smime-type=signed-data"),
+                detect("testPKCS7_signed_data_def.p7m"));
+        assertEquals(MediaType.parse("application/pkcs7-mime; 
smime-type=enveloped-data"),
+                detect("testPKCS7_enveloped_def.p7m"));
+        assertEquals(MediaType.parse("application/pkcs7-mime; 
smime-type=certs-only"),
+                detect("testPKCS7_certs_only_def.p7c"));
+        assertEquals(MediaType.application("pkcs7-signature"),
+                detect("testPKCS7_signature_def.p7s"));
+    }
+
+    @Test
+    public void testNonCmsAndNull() throws Exception {
+        assertEquals(MediaType.OCTET_STREAM, detector.detect(null, new 
Metadata(), new ParseContext()));
+        try (TikaInputStream tis = TikaInputStream.get(new byte[]{0x30, 0x03, 
0x02, 0x01, 0x00})) {
+            assertEquals(MediaType.OCTET_STREAM, detector.detect(tis, new 
Metadata(), new ParseContext()));
+        }
+    }
+}
diff --git 
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-crypto-module/src/test/java/org/apache/tika/parser/crypto/CmsClassifierTest.java
 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-crypto-module/src/test/java/org/apache/tika/parser/crypto/CmsClassifierTest.java
new file mode 100644
index 0000000000..f50619ec67
--- /dev/null
+++ 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-crypto-module/src/test/java/org/apache/tika/parser/crypto/CmsClassifierTest.java
@@ -0,0 +1,62 @@
+/*
+ * 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 static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.DEROctetString;
+import org.bouncycastle.asn1.cms.ContentInfo;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.junit.jupiter.api.Test;
+
+import org.apache.tika.io.TikaInputStream;
+import org.apache.tika.mime.MediaType;
+
+/**
+ * Verifies the ContentInfo content-type OID -&gt; smime-type mapping 
directly, including the
+ * digested/encrypted branches for which real fixtures are impractical. A 
minimal
+ * {@code ContentInfo{ contentType, content }} is enough: the classifier keys 
off the OID (the
+ * signedData sub-shape, which needs a full message, is exercised by the 
fixtures in
+ * {@link Pkcs7ParserTest}).
+ */
+public class CmsClassifierTest {
+
+    private String classify(ASN1ObjectIdentifier contentType) throws Exception 
{
+        byte[] der = new ContentInfo(contentType, new DEROctetString(new 
byte[]{1, 2, 3}))
+                .getEncoded();
+        try (TikaInputStream tis = TikaInputStream.get(der)) {
+            MediaType type = CmsClassifier.classify(tis);
+            return type == null ? null : type.toString();
+        }
+    }
+
+    @Test
+    public void testContentTypeMapping() throws Exception {
+        assertEquals("application/pkcs7-mime; smime-type=enveloped-data",
+                classify(PKCSObjectIdentifiers.envelopedData));
+        assertEquals("application/pkcs7-mime; smime-type=digested-data",
+                classify(PKCSObjectIdentifiers.digestedData));
+        assertEquals("application/pkcs7-mime; smime-type=encrypted-data",
+                classify(PKCSObjectIdentifiers.encryptedData));
+        assertEquals("application/pkcs7-mime; smime-type=compressed-data",
+                classify(PKCSObjectIdentifiers.id_ct_compressedData));
+        // plain data (and any unrecognized OID) is not a routed CMS container
+        assertNull(classify(PKCSObjectIdentifiers.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/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
new file mode 100644
index 0000000000..36905eeb41
--- /dev/null
+++ 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-crypto-module/src/test/java/org/apache/tika/parser/crypto/Pkcs7DetectionTest.java
@@ -0,0 +1,54 @@
+/*
+ * 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 static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.io.InputStream;
+
+import org.junit.jupiter.api.Test;
+
+import org.apache.tika.Tika;
+
+/**
+ * Content-only (magic) detection of the ASN.1/DER crypto families. Magic is 
coarse — the CMS
+ * subtype (signed vs enveloped) is refined by {@link Pkcs7Parser}, not magic 
— so signed/enveloped
+ * both detect as pkcs7-signature here.
+ */
+public class Pkcs7DetectionTest {
+
+    private final Tika tika = new Tika();
+
+    private String detect(String resource) throws Exception {
+        try (InputStream is = Pkcs7DetectionTest.class.getResourceAsStream(
+                "/test-documents/" + resource)) {
+            return tika.detect(is);
+        }
+    }
+
+    @Test
+    public void testContentOnlyDetection() throws Exception {
+        // new/fixed magic
+        assertEquals("application/x-pkcs12", detect("testRSAKEYandCERT.p12"));
+        assertEquals("application/timestamped-data", detect("Test4.pdf.tsd"));
+        assertEquals("application/pkcs7-mime", 
detect("testPKCS7_compressed_def_long.p7z"));
+        assertEquals("application/pkcs7-mime", 
detect("testPKCS7_compressed_ind.p7z"));
+        // existing masked CMS magic detects signed/enveloped coarsely as 
pkcs7-signature by content
+        assertEquals("application/pkcs7-signature", 
detect("testPKCS7_signed_data_def.p7m"));
+        assertEquals("application/pkcs7-signature", 
detect("testPKCS7_enveloped_def.p7m"));
+    }
+}
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 dbd2c7f167..36dffdc80e 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,54 @@
  */
 package org.apache.tika.parser.crypto;
 
-import static org.junit.jupiter.api.Assertions.assertTrue;
-import static org.junit.jupiter.api.Assertions.fail;
+import static org.junit.jupiter.api.Assertions.assertEquals;
 
-import org.xml.sax.ContentHandler;
+import org.junit.jupiter.api.Test;
+import org.xml.sax.helpers.DefaultHandler;
 
-import org.apache.tika.TikaTest;
-import org.apache.tika.exception.TikaException;
 import org.apache.tika.io.TikaInputStream;
 import org.apache.tika.metadata.Metadata;
 import org.apache.tika.parser.ParseContext;
-import org.apache.tika.sax.BodyContentHandler;
 
-public class Pkcs7ParserTest extends TikaTest {
-    public void testDetachedSignature() throws Exception {
-        try (TikaInputStream tis = 
getResourceAsStream("/test-documents/testDetached.p7s")) {
-            ContentHandler handler = new BodyContentHandler();
-            Metadata metadata = new Metadata();
-            new Pkcs7Parser().parse(tis, handler, metadata, new 
ParseContext());
-        } catch (NullPointerException npe) {
-            fail("should not get NPE");
-        } catch (TikaException te) {
-            assertTrue(te.toString().contains("cannot parse detached pkcs7 
signature"));
+public class Pkcs7ParserTest {
+
+    /** Parse a fixture and return the content type the parser refined it to. 
*/
+    private String parsedType(String resource) 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());
         }
+        return metadata.get(Metadata.CONTENT_TYPE);
+    }
+
+    @Test
+    public void testSubtypeRefinement() throws Exception {
+        assertEquals("application/pkcs7-mime; smime-type=signed-data",
+                parsedType("testPKCS7_signed_data_def.p7m"));
+        assertEquals("application/pkcs7-mime; smime-type=signed-data",
+                parsedType("testPKCS7_signed_data_ind.p7m"));
+        assertEquals("application/pkcs7-mime; smime-type=signed-data",
+                parsedType("test.xml.p7m"));
+        assertEquals("application/pkcs7-mime; smime-type=enveloped-data",
+                parsedType("testPKCS7_enveloped_def.p7m"));
+        assertEquals("application/pkcs7-mime; smime-type=enveloped-data",
+                parsedType("testPKCS7_enveloped_ind.p7m"));
+        assertEquals("application/pkcs7-mime; smime-type=certs-only",
+                parsedType("testPKCS7_certs_only_def.p7c"));
+        assertEquals("application/pkcs7-mime; smime-type=certs-only",
+                parsedType("testPKCS7_certs_only_ind.p7c"));
+        assertEquals("application/pkcs7-mime; smime-type=compressed-data",
+                parsedType("testPKCS7_compressed_def_long.p7z"));
+        assertEquals("application/pkcs7-mime; smime-type=compressed-data",
+                parsedType("testPKCS7_compressed_ind.p7z"));
+    }
+
+    /** Detached signatures are labelled pkcs7-signature and no longer throw. 
*/
+    @Test
+    public void testDetachedSignatures() throws Exception {
+        assertEquals("application/pkcs7-signature", 
parsedType("testPKCS7_signature_def.p7s"));
+        assertEquals("application/pkcs7-signature", 
parsedType("testPKCS7_signature_ind.p7s"));
+        assertEquals("application/pkcs7-signature", 
parsedType("testDetached.p7s"));
     }
 }
diff --git 
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-crypto-module/src/test/resources/test-documents/test.xml.p7m
 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-crypto-module/src/test/resources/test-documents/test.xml.p7m
new file mode 100644
index 0000000000..ce21cc961f
Binary files /dev/null and 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-crypto-module/src/test/resources/test-documents/test.xml.p7m
 differ
diff --git 
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-crypto-module/src/test/resources/test-documents/testPKCS7_certs_only_def.p7c
 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-crypto-module/src/test/resources/test-documents/testPKCS7_certs_only_def.p7c
new file mode 100644
index 0000000000..5f25350c48
Binary files /dev/null and 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-crypto-module/src/test/resources/test-documents/testPKCS7_certs_only_def.p7c
 differ
diff --git 
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-crypto-module/src/test/resources/test-documents/testPKCS7_certs_only_ind.p7c
 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-crypto-module/src/test/resources/test-documents/testPKCS7_certs_only_ind.p7c
new file mode 100644
index 0000000000..c0bc161e84
Binary files /dev/null and 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-crypto-module/src/test/resources/test-documents/testPKCS7_certs_only_ind.p7c
 differ
diff --git 
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-crypto-module/src/test/resources/test-documents/testPKCS7_compressed_def_long.p7z
 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-crypto-module/src/test/resources/test-documents/testPKCS7_compressed_def_long.p7z
new file mode 100644
index 0000000000..74474876ed
Binary files /dev/null and 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-crypto-module/src/test/resources/test-documents/testPKCS7_compressed_def_long.p7z
 differ
diff --git 
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-crypto-module/src/test/resources/test-documents/testPKCS7_compressed_def_short.p7z
 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-crypto-module/src/test/resources/test-documents/testPKCS7_compressed_def_short.p7z
new file mode 100644
index 0000000000..da9fbfaf1b
Binary files /dev/null and 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-crypto-module/src/test/resources/test-documents/testPKCS7_compressed_def_short.p7z
 differ
diff --git 
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-crypto-module/src/test/resources/test-documents/testPKCS7_compressed_ind.p7z
 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-crypto-module/src/test/resources/test-documents/testPKCS7_compressed_ind.p7z
new file mode 100644
index 0000000000..6ec9716b65
Binary files /dev/null and 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-crypto-module/src/test/resources/test-documents/testPKCS7_compressed_ind.p7z
 differ
diff --git 
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-crypto-module/src/test/resources/test-documents/testPKCS7_enveloped_def.p7m
 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-crypto-module/src/test/resources/test-documents/testPKCS7_enveloped_def.p7m
new file mode 100644
index 0000000000..dad330064d
Binary files /dev/null and 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-crypto-module/src/test/resources/test-documents/testPKCS7_enveloped_def.p7m
 differ
diff --git 
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-crypto-module/src/test/resources/test-documents/testPKCS7_enveloped_ind.p7m
 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-crypto-module/src/test/resources/test-documents/testPKCS7_enveloped_ind.p7m
new file mode 100644
index 0000000000..eb182562c8
Binary files /dev/null and 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-crypto-module/src/test/resources/test-documents/testPKCS7_enveloped_ind.p7m
 differ
diff --git 
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-crypto-module/src/test/resources/test-documents/testPKCS7_signature_def.p7s
 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-crypto-module/src/test/resources/test-documents/testPKCS7_signature_def.p7s
new file mode 100644
index 0000000000..b90e41ea17
Binary files /dev/null and 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-crypto-module/src/test/resources/test-documents/testPKCS7_signature_def.p7s
 differ
diff --git 
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-crypto-module/src/test/resources/test-documents/testPKCS7_signature_ind.p7s
 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-crypto-module/src/test/resources/test-documents/testPKCS7_signature_ind.p7s
new file mode 100644
index 0000000000..dd9400a9b4
Binary files /dev/null and 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-crypto-module/src/test/resources/test-documents/testPKCS7_signature_ind.p7s
 differ
diff --git 
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-crypto-module/src/test/resources/test-documents/testPKCS7_signed_data_def.p7m
 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-crypto-module/src/test/resources/test-documents/testPKCS7_signed_data_def.p7m
new file mode 100644
index 0000000000..1f0adb7499
Binary files /dev/null and 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-crypto-module/src/test/resources/test-documents/testPKCS7_signed_data_def.p7m
 differ
diff --git 
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-crypto-module/src/test/resources/test-documents/testPKCS7_signed_data_def.pem
 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-crypto-module/src/test/resources/test-documents/testPKCS7_signed_data_def.pem
new file mode 100644
index 0000000000..34ef17fd53
--- /dev/null
+++ 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-crypto-module/src/test/resources/test-documents/testPKCS7_signed_data_def.pem
@@ -0,0 +1,35 @@
+-----BEGIN PKCS7-----
+MIIGIgYJKoZIhvcNAQcCoIIGEzCCBg8CAQExDTALBglghkgBZQMEAgEwEwYJKoZI
+hvcNAQcBoAYEBHRleHSgggPDMIIDvzCCAyigAwIBAgICAKEwDQYJKoZIhvcNAQEF
+BQAwcjELMAkGA1UEBhMCRVMxDzANBgNVBAgTBk1hZHJpZDEPMA0GA1UEBxMGTWFk
+cmlkMQ4wDAYDVQQKEwVNSVR5QzEbMBkGA1UECxMSTUlUeUMgRE5JZSBQcnVlYmFz
+MRQwEgYDVQQDEwtDQSB1c3VhcmlvczAeFw0xMDA0MDgxMjM4MjJaFw0yMDA0MDUx
+MjM4MjJaMHwxCzAJBgNVBAYTAkVTMQ8wDQYDVQQIEwZNYWRyaWQxDzANBgNVBAcT
+Bk1hZHJpZDEOMAwGA1UEChMFTUlUeUMxGzAZBgNVBAsTEk1JVHlDIEROSWUgUHJ1
+ZWJhczEeMBwGA1UEAxMVVXN1YXJpbyBkZSBwcnVlYmFzIDYxMIGfMA0GCSqGSIb3
+DQEBAQUAA4GNADCBiQKBgQC2ehoLcO6lXWmKzJfdz2m+vRZmGeDo5OF+Q8MNdVtL
+8AKbMykP6G9JOzBT3WLhzQKszMg43DQjViN6mTQsPLYCfe/n6LmTWZkRvsIzrffM
+aL7goy47VCX1CeUQ80cuUAHJpRq7UMObNvgV/8rn+zPfYmErqZVhAckleP4/RgeC
+9QIDAQABo4IBWDCCAVQwCQYDVR0TBAIwADALBgNVHQ8EBAMCBeAwHQYDVR0OBBYE
+FNhww+tqmd7gvdedMv0Gk1mEolYdMIGYBgNVHSMEgZAwgY2AFPWhaqh3T1uxBIyn
+fkjxDp/Fdo8boXKkcDBuMQ8wDQYDVQQIEwZNYWRyaWQxDzANBgNVBAcTBk1hZHJp
+ZDEOMAwGA1UEChMFTUlUeUMxGzAZBgNVBAsTEk1JVHlDIEROSWUgUHJ1ZWJhczEQ
+MA4GA1UEAxMHUm9vdCBDQTELMAkGA1UEBhMCRVOCAQMwCQYDVR0RBAIwADA2BgNV
+HRIELzAthitodHRwOi8vbWluaXN0ZXItOGpneHk5Lm1pdHljLmFnZS9QS0kvQ0Eu
+Y3J0MD0GA1UdHwQ2MDQwMqAwoC6GLGh0dHA6Ly9taW5pc3Rlci04amd4eTkubWl0
+eWMuYWdlL1BLSS9jcmwuY3JsMA0GCSqGSIb3DQEBBQUAA4GBADn3vfsgfhyreHhn
+4VCG4WuG+g5qTRKpu72ZScMbyY+e3d2m6fOSMkoEC+NwaXgl4Y/vKlXGKgdhYjLD
+WjobRDVQOQRQ4Q/Wv/aPPno1CjBkYdY5rnCM/oiy8QXNjdboXRiE40kCyqj7jiop
+B20uO2a0yacC5ooWQz1pqZEhSWRDMYICHTCCAhkCAQEweDByMQswCQYDVQQGEwJF
+UzEPMA0GA1UECBMGTWFkcmlkMQ8wDQYDVQQHEwZNYWRyaWQxDjAMBgNVBAoTBU1J
+VHlDMRswGQYDVQQLExJNSVR5QyBETkllIFBydWViYXMxFDASBgNVBAMTC0NBIHVz
+dWFyaW9zAgIAoTALBglghkgBZQMEAgGggfowGAYJKoZIhvcNAQkDMQsGCSqGSIb3
+DQEHATAcBgkqhkiG9w0BCQUxDxcNMTkwMTI0MTM0MjI1WjAvBgkqhkiG9w0BCQQx
+IgQgmC2ePrmW9VnmM/TRlN7zdh2Qn1o7ZH0ahR/q1nwyydEwgY4GCSqGSIb3DQEJ
+DzGBgDB+MAsGCWCGSAFlAwQBKjAIBgYqhQMCAgkwCAYGKoUDAgIVMAsGCWCGSAFl
+AwQBFjALBglghkgBZQMEAQIwCgYIKoZIhvcNAwcwDgYIKoZIhvcNAwICAgCAMA0G
+CCqGSIb3DQMCAgFAMAcGBSsOAwIHMA0GCCqGSIb3DQMCAgEoMA0GCSqGSIb3DQEB
+AQUABIGAFR6+Q41Ak8HXUNnbkEQDWN3JBacSYi5mAPtHyJNHGLewKTwxOrf/nGhk
+Qq5zqvMLhci0NBU3wvW+lAKX3ytavfkRkRFlTruuH151Stkr2TRW6h132ggA2AAJ
+3gGxGqN2bYAYB6O8QY4NhmJdSi+j4bK0ayo15HhpagRfp2181lU=
+-----END PKCS7-----
diff --git 
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-crypto-module/src/test/resources/test-documents/testPKCS7_signed_data_ind.p7m
 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-crypto-module/src/test/resources/test-documents/testPKCS7_signed_data_ind.p7m
new file mode 100644
index 0000000000..cf54083951
Binary files /dev/null and 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-crypto-module/src/test/resources/test-documents/testPKCS7_signed_data_ind.p7m
 differ
diff --git 
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-crypto-module/src/test/resources/test-documents/testRSAKEYandCERT.p12
 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-crypto-module/src/test/resources/test-documents/testRSAKEYandCERT.p12
new file mode 100644
index 0000000000..1c536e8fbe
Binary files /dev/null and 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-crypto-module/src/test/resources/test-documents/testRSAKEYandCERT.p12
 differ

Reply via email to