VER-07: digitale signatur - abhaengigkeit und pruefprotokoll
go.mod/go.sum um github.com/digitorus/pdfsign v0.9.0 ergaenzt (auf 192.168.1.131 aufgeloest, keine Go-Toolchain lokal verfuegbar). API-Anpassung an die tatsaechlich installierte Version (kein TrustedRoots/SubFilter/Context in v0.9.0, andere Signer.Warnings-Form) sowie ein Bugfix: cert_expired wurde bei einem fruehen Verify-Fehlerpfad faelschlich auf false zurueckgesetzt statt den vorab anhand der Signatur-Identitaet ermittelten Wert zu behalten. Alle vier automatisierten Tests real auf 192.168.1.131 gruen, inklusive echtem RFC-3161-Aufruf gegen freetsa.org. Externe Verifikation mit pdfsig (poppler-utils) bestaetigt: gueltige Signatur, Manipulationserkennung (Digest Mismatch) und Erkennung eines abgelaufenen Zertifikats -- Details in dms/docs/VER-07-PRUEFPROTOKOLL.md. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
9360593a18
commit
38ba66e259
5 files changed
+185
-53
No files matched your search
@@ -0,0 +1,132 @@
|
||||
# VER-07 – Prüfprotokoll: Digitale Signatur
|
||||
|
||||
Stand: 2026-09-18. Testhost 192.168.1.131.
|
||||
|
||||
## Umsetzung
|
||||
|
||||
`internal/pdfsign` folgt dem Zustands-Muster von `internal/pdfconvert` (DOC-06) und
|
||||
`internal/thumbnail` (DOC-04): eine neue Tabelle `revision_signatures` (Migration `0017`), ein
|
||||
Zustand je Datei-Revision. Signiert wird immer die PDF-Form der Revision: ist das Original bereits
|
||||
PDF, wird es direkt verwendet; sonst ausschließlich das bereits von DOC-06 erzeugte, fertige
|
||||
(`ready`) Derivat aus `revision_pdf_derivatives` — kein zweiter Konvertierungspfad.
|
||||
|
||||
**Bibliothekswahl:** `github.com/digitorus/pdfsign` (v0.9.0). `pdfcpu`, das DOC-14 bereits
|
||||
vorausgesetzt hätte, ist in diesem `go.mod` gar nicht vorhanden — DOC-14 nutzt es hier offenbar
|
||||
nicht (oder wurde anders umgesetzt); es gibt also keinen bereits vorhandenen Signatur-Code, an den
|
||||
angeknüpft werden könnte. `digitorus/pdfsign` ist eine reale, aktiv gepflegte Go-Bibliothek, die
|
||||
PAdES nativ unterstützt (CMS/PKCS#7-Signatur eingebettet in die PDF-`/Sig`-Struktur, ISO 32000),
|
||||
inklusive eingebautem RFC-3161-TSA-Client (`SignData.TSA.URL`) und eigener Verifikationslogik
|
||||
(`verify`-Paket) — genau der geforderte offene, extern prüfbare Signaturmechanismus, ohne
|
||||
herstellereigenes Format. Kleinster zusätzlicher Abhängigkeitsfußabdruck unter den geprüften
|
||||
Optionen.
|
||||
|
||||
**Bekannte Einschränkung der installierten Version (v0.9.0):** Anders als eine neuere, noch nicht
|
||||
veröffentlichte Version der Bibliothek (siehe deren `master`-Branch) kennt v0.9.0 keine eigene
|
||||
`TrustedRoots`-Option in `verify.VerifyOptions` und kein `SubFilter`-Feld in `sign.SignData` für das
|
||||
explizite ETSI.CAdES.detached-Profil. Verifikation läuft deshalb mit `AllowUntrustedRoots = true`
|
||||
(akzeptiert das im PDF eingebettete Zertifikat als Wurzel) und dem Bibliotheks-Standardprofil
|
||||
(`adbe.pkcs7.detached`, weiterhin ein offenes, in ISO 32000 spezifiziertes PAdES-kompatibles CMS/
|
||||
PKCS#7-Profil, keine Herstellersonderform). Für eine echte QTSP-Zertifikatskette in Produktion
|
||||
würde stattdessen über das System-Zertifikatspool verifiziert, nicht über `AllowUntrustedRoots`.
|
||||
|
||||
**Zertifikat/Zeitstempel-Konfiguration:** `SigningIdentity` wird über zwei Umgebungsvariablen mit
|
||||
PEM-Dateipfaden geladen (kein Schlüsselmaterial im Code oder in der DB), der TSA-Endpunkt über
|
||||
`Service.TSAURL` (leer = kein Zeitstempel, für Umgebungen ohne ausgehenden Netzwerkzugriff). Für
|
||||
diese Kachel wurde ein selbstsigniertes Test-Zertifikat verwendet (`generateTestIdentity` in den
|
||||
Tests) — **ausdrücklich kein echtes, von einem qualifizierten Vertrauensdiensteanbieter (QTSP)
|
||||
ausgestelltes Zertifikat**. Eine Produktivbereitstellung benötigt ein echtes QTSP-Zertifikat; das
|
||||
Beschaffen eines solchen ist nicht Bestandteil dieser Kachel.
|
||||
|
||||
**Nicht Bestandteil (Schnittstelle vorbereitet, nicht implementiert):** QA-08 (Revisionssicherheits-
|
||||
Prüfung) — `revision_signatures.verification_status`/`cert_expired`/`timestamp_status` liegen bereits
|
||||
als maschinenlesbare Spalten vor, auf denen ein späteres QA-08-Prüfgate direkt aufsetzen kann, ohne
|
||||
weitere Datenmodell-Änderung.
|
||||
|
||||
## Automatisierte Tests (je Akzeptanzkriterium mindestens einer)
|
||||
|
||||
Alle Tests real gegen die Tenant-Testdatenbank auf 192.168.1.131 ausgeführt
|
||||
(`go test ./internal/pdfsign/... -v -p 1`), mit einem selbst erzeugten, minimalen echten PDF
|
||||
(klassische Xref-Tabelle) und selbstsignierten Test-Zertifikaten (kein echtes Kundendokument, keine
|
||||
personenbezogenen Daten).
|
||||
|
||||
| Test | Akzeptanzkriterium / Prüfung | Ergebnis |
|
||||
|---|---|---|
|
||||
| `TestSignAndVerify_ValidSignature` | AC1 (eingebettet, prüfbar), AC2 (Prüfergebnis gespeichert) | **PASS** |
|
||||
| `TestVerify_ModificationInvalidatesSignature` | Pflichtprüfung 2 | **PASS** |
|
||||
| `TestVerify_ExpiredCertificateRecognized` | Pflichtprüfung 3 | **PASS** |
|
||||
| `TestSign_UsesConfiguredTSA` | AC3 (echter externer TSA-Aufruf) | **PASS** (mit `NEXARCH_DMS_SIGNING_TSA_URL_TEST=https://freetsa.org/tsr`) |
|
||||
|
||||
## Prüfungen vor der Abnahme
|
||||
|
||||
### Prüfung 1: Signatur mit externem Prüfwerkzeug validiert
|
||||
|
||||
**Ziel:** eine mit dieser Kachel erzeugte Signatur lässt sich mit einem Werkzeug außerhalb des
|
||||
eigenen Codes als gültig bestätigen.
|
||||
|
||||
**Ergebnis: bestanden, real geprüft.** `pdfsig` (poppler-utils, bereits auf 192.168.1.131 vorhanden,
|
||||
`which pdfsig` → `/usr/bin/pdfsig`, dieselbe Suite wie `pdftoppm`/`pdftotext` aus DOC-04) gegen ein
|
||||
mit `digitorus/pdfsign` echt erzeugtes, signiertes und mit einem echten freetsa.org-Zeitstempel
|
||||
versehenes Test-PDF:
|
||||
|
||||
```
|
||||
Signature Validation: Signature is Valid.
|
||||
Certificate Validation: Certificate issuer is unknown.
|
||||
```
|
||||
|
||||
"Certificate issuer is unknown" ist der erwartete Befund für ein selbstsigniertes Test-Zertifikat
|
||||
(keine Kette zu einer vertrauenswürdigen Wurzel-CA) — die kryptografische Signatur selbst ist
|
||||
gültig, extern durch ein fremdes Werkzeug bestätigt, nicht nur durch den eigenen Verify()-Code.
|
||||
|
||||
### Prüfung 2: Nachträgliche Änderung invalidiert die Signatur nachweislich
|
||||
|
||||
**Ergebnis: bestanden, real geprüft, doppelt belegt.** (a) `TestVerify_ModificationInvalidatesSignature`
|
||||
manipuliert die verschlüsselt gespeicherte, signierte PDF-Datei nach dem Signieren (ein Byte im
|
||||
Textinhalt) und ruft erneut `Service.Verify` auf → `verification_status = "invalid"`. (b) Zusätzlich
|
||||
extern mit `pdfsig` an einer separat erzeugten, manipulierten Kopie derselben Signatur bestätigt:
|
||||
|
||||
```
|
||||
Signature Validation: Digest Mismatch.
|
||||
```
|
||||
|
||||
### Prüfung 3: Abgelaufenes Zertifikat wird als solches erkannt
|
||||
|
||||
**Ergebnis: bestanden, real geprüft, doppelt belegt.** (a) `TestVerify_ExpiredCertificateRecognized`
|
||||
signiert mit einem Test-Zertifikat, dessen `NotAfter` 24 Stunden in der Vergangenheit liegt →
|
||||
`cert_expired = true`, `verification_status = "invalid"`. (b) Extern mit `pdfsig` bestätigt:
|
||||
|
||||
```
|
||||
Certificate Validation: Certificate has Expired
|
||||
```
|
||||
|
||||
## Akzeptanzkriterium 3 im Detail: Zeitstempel von vertrauenswürdiger Quelle
|
||||
|
||||
Ausgehender Netzwerkzugriff von 192.168.1.131 wurde real geprüft: `freetsa.org` ist erreichbar (TCP/
|
||||
TLS-Verbindung erfolgreich; `GET /tsr` liefert regelkonform `403`, da der Endpunkt nur `POST` mit
|
||||
binärer RFC-3161-TSQ-Anfrage akzeptiert). Ein echter RFC-3161-Aufruf wurde real durchgeführt: sowohl
|
||||
über `go test -run TestSign_UsesConfiguredTSA` (Bibliotheks-eigener `verify.Signer.TimestampStatus ==
|
||||
"valid"`) als auch manuell beim Erzeugen des für Prüfung 1 verwendeten Test-PDFs (`SignData.TSA.URL =
|
||||
"https://freetsa.org/tsr"`, Signieren erfolgreich, kein Netzwerkfehler). Der Zeitstempel-Token ist
|
||||
damit eine echte Antwort einer externen, öffentlichen RFC-3161-Zeitstempelstelle, kein lokal
|
||||
erzeugtes Datum. **Einschränkung:** `freetsa.org` ist ein kostenloser, nicht-qualifizierter
|
||||
öffentlicher TSA-Dienst — für eine echte PAdES-T/qualifizierte Zeitstempel-Anforderung in Produktion
|
||||
wäre ein TSA eines akkreditierten Vertrauensdiensteanbieters zu konfigurieren; die Codeseite
|
||||
(`Service.TSAURL`) ist dafür bereits generisch genug (beliebige RFC-3161-URL über Umgebungsvariable).
|
||||
|
||||
## Regressionsnachweis
|
||||
|
||||
`go build ./...`, `go vet ./...` sauber auf 192.168.1.131 (Go-Toolchain automatisch auf 1.26.8
|
||||
aktualisiert, ausgelöst durch die neue Abhängigkeit `digitorus/pdfsign@v0.9.0`, die `go >= 1.25.0`
|
||||
voraussetzt — `go.mod` musste dafür von `go 1.24`/Toolchain `go1.24.4` auf `go 1.25.0` angehoben
|
||||
werden; keine anderen Pakete wurden dabei angefasst). `go test ./internal/pdfsign/... -v -p 1`:
|
||||
4 Tests, 4 PASS (davon 1 nur mit gesetztem `NEXARCH_DMS_SIGNING_TSA_URL_TEST` real ausgeführt, siehe
|
||||
oben — ohne gesetzte Variable läuft er als dokumentierter `SKIP`, kein `go test ./...`-Gesamtlauf
|
||||
durchgeführt, da das nicht Bestandteil dieser Kachel angrenzende Pakete berührt hätte).
|
||||
|
||||
## Offene Punkte für die Abnahme
|
||||
|
||||
1. Für Produktivbetrieb: echtes, von einem QTSP ausgestelltes Zertifikat beschaffen und über die
|
||||
Pfad-Umgebungsvariablen konfigurieren — außerhalb des Umfangs dieser Kachel.
|
||||
2. Für Produktivbetrieb: TSA eines akkreditierten Anbieters statt des kostenlosen freetsa.org-Diensts
|
||||
konfigurieren (`Service.TSAURL`).
|
||||
3. QA-08 (Revisionssicherheits-Prüfung) bewusst nicht implementiert — Datenmodell ist dafür
|
||||
vorbereitet (siehe oben), keine weitere Schnittstelle geschaffen.
|
||||
Reference in new issue
Block a user