pptx/digital-signature-detection

Detect whether a .pptx package carries an ECMA-376 Part 2 digital signature.

Library verdicts: python-pptx: — pptxjs: —

Metadata

Feature idpptx/digital-signature-detection
Formatpptx
Categorypackaging
Spececma-376-5-part-2 § 12

XPath assertions

ID / partPredicateXPathpython-pptxpptxjs
origin-sigs-marker-present
exist
Same OPC marker as in docx/digital-signature-detection.
signature-xml-part-present
exist
signature-element-present
_xmlsignatures/sig1.xml
exist
/ds:Signature

Render assertions

No render assertions declared.

Generator source

scripts/gen_signed_fixtures.py

#!/usr/bin/env python3
"""Generate signed-package detection fixtures for pptx and xlsx.

The OPC-level digital-signature layout is format-agnostic:

- ``_xmlsignatures/origin.sigs`` — zero-byte marker part.
- ``_xmlsignatures/_rels/origin.sigs.rels`` — relationship to ``sig1.xml``.
- ``_xmlsignatures/sig1.xml`` — XMLDSIG ``<Signature>`` root.

This script clones the existing minimal .pptx and .xlsx fixtures and
appends the three signature parts so detection-layer assertions pass.
The embedded ``<SignatureValue>`` is a literal ``"ignored"`` — valid
for detection, NOT cryptographically verifiable. Full XMLDSIG
canonicalisation + signing is out of scope; see
``features/{pptx,xlsx}/digital-signature-detection.json``.

Mirrors ``python-docx/features/steps/test_files/pkg-signed.docx``.
"""

from __future__ import annotations

import shutil
import zipfile
from pathlib import Path

_REPO_ROOT = Path(__file__).resolve().parent.parent

_ORIGIN_SIGS = b""
_SIG_RELS = (
    b'<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'
    b'<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">'
    b'<Relationship Id="rIdSig1" '
    b'Type="http://schemas.openxmlformats.org/package/2006/relationships/digital-signature/signature" '
    b'Target="sig1.xml"/>'
    b"</Relationships>"
)
_SIG1_XML = (
    b'<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n'
    b'<Signature xmlns="http://www.w3.org/2000/09/xmldsig#">\n'
    b"  <SignedInfo/>\n"
    b"  <SignatureValue>ignored</SignatureValue>\n"
    b"  <KeyInfo>\n"
    b"    <X509Data>\n"
    b"      <X509SubjectName>CN=Alice Example</X509SubjectName>\n"
    b"    </X509Data>\n"
    b"  </KeyInfo>\n"
    b"  <Object>\n"
    b'    <SignatureProperties xmlns:xades="http://uri.etsi.org/01903/v1.3.2#">\n'
    b"      <SignatureProperty>\n"
    b"        <xades:SigningTime>2024-04-01T12:34:56Z</xades:SigningTime>\n"
    b"      </SignatureProperty>\n"
    b"    </SignatureProperties>\n"
    b"  </Object>\n"
    b"</Signature>"
)


def _clone_and_sign(src: Path, dst: Path) -> None:
    dst.parent.mkdir(parents=True, exist_ok=True)
    shutil.copy(src, dst)
    with zipfile.ZipFile(dst, "a", compression=zipfile.ZIP_DEFLATED) as zf:
        zf.writestr("_xmlsignatures/origin.sigs", _ORIGIN_SIGS)
        zf.writestr("_xmlsignatures/_rels/origin.sigs.rels", _SIG_RELS)
        zf.writestr("_xmlsignatures/sig1.xml", _SIG1_XML)


def main() -> int:
    pairs = [
        (
            _REPO_ROOT / "fixtures" / "pptx" / "package-thumbnail-dropped.pptx",
            _REPO_ROOT / "fixtures" / "pptx" / "digital-signature-detection.pptx",
        ),
        (
            _REPO_ROOT / "fixtures" / "xlsx" / "package-thumbnail-dropped.xlsx",
            _REPO_ROOT / "fixtures" / "xlsx" / "digital-signature-detection.xlsx",
        ),
    ]
    for src, dst in pairs:
        _clone_and_sign(src, dst)
        print(dst)
    return 0


if __name__ == "__main__":
    raise SystemExit(main())

Manifest (expanded)

{
  "$schema": "../manifest.schema.json",
  "id": "pptx/digital-signature-detection",
  "title": "Digital signature detection",
  "format": "pptx",
  "category": "packaging",
  "summary": "Detect whether a .pptx package carries an ECMA-376 Part 2 digital signature.",
  "roles": [
    "authoring"
  ],
  "spec": {
    "source": "ecma-376-5-part-2",
    "clause": "12",
    "notes": "See `docx/digital-signature-detection` for the detection pattern. The OPC-level layout (`_xmlsignatures/origin.sigs` marker + `sig1.xml` XMLDSIG part) is format-agnostic, so the same assertion set applies to .pptx. The committed fixture embeds a detection-only XMLDSIG stub (literal `<SignatureValue>ignored</SignatureValue>`) — valid for existence assertions, NOT cryptographically verifiable. Full XMLDSIG canonicalisation plus signing via xmlsec1 is a future wave."
  },
  "fixtures": {
    "machine": "pptx/digital-signature-detection"
  },
  "generator": {
    "python": "scripts/gen_signed_fixtures.py"
  },
  "assertions": [
    {
      "id": "origin-sigs-marker-present",
      "kind": "zip_contains_path",
      "path": "_xmlsignatures/origin.sigs",
      "must": "exist",
      "description": "Same OPC marker as in docx/digital-signature-detection."
    },
    {
      "id": "signature-xml-part-present",
      "kind": "zip_contains_path",
      "path": "_xmlsignatures/sig1.xml",
      "must": "exist"
    },
    {
      "id": "signature-element-present",
      "part": "_xmlsignatures/sig1.xml",
      "namespaces": {
        "ds": "http://www.w3.org/2000/09/xmldsig#"
      },
      "xpath": "/ds:Signature",
      "must": "exist"
    }
  ]
}

Fixture

Download digital-signature-detection.pptx (28.6 KB)

Reference preview

No rendered reference is available for this case.

Spec notes

See `docx/digital-signature-detection` for the detection pattern. The OPC-level layout (`_xmlsignatures/origin.sigs` marker + `sig1.xml` XMLDSIG part) is format-agnostic, so the same assertion set applies to .pptx. The committed fixture embeds a detection-only XMLDSIG stub (literal `<SignatureValue>ignored</SignatureValue>`) — valid for existence assertions, NOT cryptographically verifiable. Full XMLDSIG canonicalisation plus signing via xmlsec1 is a future wave.