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 -> 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