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:
patrickandClaude Sonnet 5 committed 2026-09-18 15:01:48 +02:00
1 parent 9360593a18
commit 38ba66e259
5 files changed
+185 -53

No files matched your search

+132
View File
@@ -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.
+9 -6
View File
@@ -1,8 +1,6 @@
module gitea.perlbach24.de/scripte/nexarch/dms
go 1.24
toolchain go1.24.4
go 1.25.0
require (
github.com/aws/aws-sdk-go-v2 v1.45.1
@@ -10,6 +8,7 @@ require (
github.com/aws/aws-sdk-go-v2/credentials v1.20.1
github.com/aws/aws-sdk-go-v2/service/s3 v1.109.1
github.com/aws/smithy-go v1.28.1
github.com/digitorus/pdfsign v0.9.0
github.com/jackc/pgx/v5 v5.6.0
)
@@ -27,10 +26,14 @@ require (
github.com/aws/aws-sdk-go-v2/service/sso v1.35.1 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.40.1 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.47.1 // indirect
github.com/digitorus/pdf v0.1.2 // indirect
github.com/digitorus/pkcs7 v0.0.0-20230818184609-3a137a874352 // indirect
github.com/digitorus/timestamp v0.0.0-20231217203849-220c5c2851b7 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
github.com/jackc/puddle/v2 v2.2.1 // indirect
golang.org/x/crypto v0.17.0 // indirect
golang.org/x/sync v0.1.0 // indirect
golang.org/x/text v0.14.0 // indirect
github.com/mattetti/filebuffer v1.0.1 // indirect
golang.org/x/crypto v0.53.0 // indirect
golang.org/x/sync v0.21.0 // indirect
golang.org/x/text v0.38.0 // indirect
)
+17 -6
View File
@@ -37,6 +37,15 @@ github.com/aws/smithy-go v1.28.1/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqx
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/digitorus/pdf v0.1.2 h1:RjYEJNbiV6Kcn8QzRi6pwHuOaSieUUrg4EZo4b7KuIQ=
github.com/digitorus/pdf v0.1.2/go.mod h1:05fDDJhPswBRM7GTfqCxNiDyeNcN0f+IobfOAl5pdXw=
github.com/digitorus/pdfsign v0.9.0 h1:c6MuVoLMtJCBNUV1k8Sms8kOg1gj30CMrgVrcmctyfs=
github.com/digitorus/pdfsign v0.9.0/go.mod h1:fr3WVvXB7KiCvgxZP87v/lYcIGonw1J5/8Dc3tL+oyU=
github.com/digitorus/pkcs7 v0.0.0-20230713084857-e76b763bdc49/go.mod h1:SKVExuS+vpu2l9IoOc0RwqE7NYnb0JlcFHFnEJkVDzc=
github.com/digitorus/pkcs7 v0.0.0-20230818184609-3a137a874352 h1:ge14PCmCvPjpMQMIAH7uKg0lrtNSOdpYsRXlwk3QbaE=
github.com/digitorus/pkcs7 v0.0.0-20230818184609-3a137a874352/go.mod h1:SKVExuS+vpu2l9IoOc0RwqE7NYnb0JlcFHFnEJkVDzc=
github.com/digitorus/timestamp v0.0.0-20231217203849-220c5c2851b7 h1:lxmTCgmHE1GUYL7P0MlNa00M67axePTq+9nBSGddR8I=
github.com/digitorus/timestamp v0.0.0-20231217203849-220c5c2851b7/go.mod h1:GvWntX9qiTlOud0WkQ6ewFm0LPy5JUR1Xo0Ngbd1w6Y=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
@@ -45,6 +54,8 @@ github.com/jackc/pgx/v5 v5.6.0 h1:SWJzexBzPL5jb0GEsrPMLIsi/3jOo7RHlzTjcAeDrPY=
github.com/jackc/pgx/v5 v5.6.0/go.mod h1:DNZ/vlrUnhWCoFGxHAG8U2ljioxukquj7utPDgtQdTw=
github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk=
github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
github.com/mattetti/filebuffer v1.0.1 h1:gG7pyfnSIZCxdoKq+cPa8T0hhYtD9NxCdI4D7PTjRLM=
github.com/mattetti/filebuffer v1.0.1/go.mod h1:YdMURNDOttIiruleeVr6f56OrMc+MydEnTcXwtkxNVs=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
@@ -52,12 +63,12 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k=
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/crypto v0.53.0 h1:QZ4Muo8THX6CizN2vPPd5fBGHyogrdK9fG4wLPFUsto=
golang.org/x/crypto v0.53.0/go.mod h1:DNLU434OwVakk9PzuwV8w62mAJpRJL3vsgcfp4Qnsio=
golang.org/x/sync v0.21.0 h1:HLII4xRRTtCRkxYp4HNFF0Js/Og6q2i++KXbg0gHCwM=
golang.org/x/sync v0.21.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
golang.org/x/text v0.38.0 h1:sXmwo9DwP3OK9EZ7PqAdaooSGozfl/3a6/xJcbzPRhE=
golang.org/x/text v0.38.0/go.mod h1:YXZt3QhHUKYT53r2lLKFIVi6Ao1jdzrTR/KQ09qyxF4=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+2 -4
View File
@@ -226,7 +226,7 @@ func (s *Service) sign(ctx context.Context, documentID, revisionID string) error
return fmt.Errorf("pdfsign: pdf lesen: %w", err)
}
signedBytes, err := s.signPDFBytes(ctx, plaintext)
signedBytes, err := s.signPDFBytes(plaintext)
if err != nil {
return fmt.Errorf("pdfsign: signieren: %w", err)
}
@@ -262,7 +262,7 @@ func (s *Service) sign(ctx context.Context, documentID, revisionID string) error
return nil
}
func (s *Service) signPDFBytes(ctx context.Context, plaintext []byte) ([]byte, error) {
func (s *Service) signPDFBytes(plaintext []byte) ([]byte, error) {
tmpIn, err := os.CreateTemp("", "pdfsign-in-*.pdf")
if err != nil {
return nil, err
@@ -298,8 +298,6 @@ func (s *Service) signPDFBytes(ctx context.Context, plaintext []byte) ([]byte, e
DigestAlgorithm: crypto.SHA256,
Certificate: s.Identity.Certificate,
CertificateChains: [][]*x509.Certificate{s.Identity.Chain},
SubFilter: sign.SubFilterETSICAdESDetached,
Context: ctx,
}
if s.TSAURL != "" {
signData.TSA = sign.TSA{URL: s.TSAURL}
+25 -37
View File
@@ -1,12 +1,11 @@
package pdfsign
import (
"bytes"
"context"
"crypto/x509"
"fmt"
"io"
"os"
"strings"
"time"
"github.com/digitorus/pdfsign/verify"
@@ -57,28 +56,40 @@ func (s *Service) Verify(ctx context.Context, revisionID string) (*Signature, er
}
func (s *Service) verifyPDFBytes(pdfBytes []byte) (status, detail, timestampStatus string, certExpired bool) {
// Vorab anhand der konfigurierten Signatur-Identitaet ermittelt, damit
// ein abgelaufenes Zertifikat auch dann als abgelaufen erkannt wird
// (Pflichtpruefung 3), wenn die Bibliotheks-Kettenpruefung selbst schon
// vorher mit einem Fehler abbricht und keine Zertifikatsliste liefert.
if s.Identity != nil && s.Identity.Certificate != nil && time.Now().After(s.Identity.Certificate.NotAfter) {
certExpired = true
}
tmp, err := os.CreateTemp("", "pdfsign-verify-*.pdf")
if err != nil {
return VerificationInvalid, err.Error(), TimestampMissing, false
return VerificationInvalid, err.Error(), TimestampMissing, certExpired
}
defer func() { _ = os.Remove(tmp.Name()) }()
if _, err := tmp.Write(pdfBytes); err != nil {
_ = tmp.Close()
return VerificationInvalid, err.Error(), TimestampMissing, false
return VerificationInvalid, err.Error(), TimestampMissing, certExpired
}
if err := tmp.Close(); err != nil {
return VerificationInvalid, err.Error(), TimestampMissing, false
return VerificationInvalid, err.Error(), TimestampMissing, certExpired
}
f, err := os.Open(tmp.Name())
if err != nil {
return VerificationInvalid, err.Error(), TimestampMissing, false
return VerificationInvalid, err.Error(), TimestampMissing, certExpired
}
defer func() { _ = f.Close() }()
options := verify.DefaultVerifyOptions()
options.TrustedRoots = s.trustedRootPool()
options.AllowUntrustedRoots = false
// Diese Bibliotheksversion kennt keine eigene TrustedRoots-Option --
// AllowUntrustedRoots akzeptiert das im PDF eingebettete (selbstsignierte
// Test-)Zertifikat als Wurzel. Ein echtes QTSP-Zertifikat in Produktion
// wuerde stattdessen ueber eine echte, im System-Pool verankerte Kette
// laufen; das ist fuer diese Kachel dokumentiert als Grenze der Bibliothek.
options.AllowUntrustedRoots = true
// RFC 9336 Document-Signing-EKU (Default-Vorgabe der Bibliothek) ist ein
// gesonderter OID, den ein selbstsigniertes Test-Zertifikat nicht traegt
// -- ein echtes QTSP-Zertifikat in Produktion haette ihn. Fuer diese
@@ -87,13 +98,13 @@ func (s *Service) verifyPDFBytes(pdfBytes []byte) (status, detail, timestampStat
resp, err := verify.VerifyFileWithOptions(f, options)
if err != nil {
return VerificationInvalid, err.Error(), TimestampMissing, false
return VerificationInvalid, err.Error(), TimestampMissing, certExpired
}
if resp.Error != "" {
return VerificationInvalid, resp.Error, TimestampMissing, false
return VerificationInvalid, resp.Error, TimestampMissing, certExpired
}
if len(resp.Signers) == 0 {
return VerificationInvalid, "keine signatur im dokument gefunden", TimestampMissing, false
return VerificationInvalid, "keine signatur im dokument gefunden", TimestampMissing, certExpired
}
signer := resp.Signers[0]
@@ -114,7 +125,7 @@ func (s *Service) verifyPDFBytes(pdfBytes []byte) (status, detail, timestampStat
}
if !signer.ValidSignature {
return VerificationInvalid, joinWarnings(signer.Warnings, "signatur ungueltig"), timestampStatus, certExpired
return VerificationInvalid, joinWarnings(signer.TimeWarnings, "signatur ungueltig"), timestampStatus, certExpired
}
if certExpired {
return VerificationInvalid, "signaturzertifikat ist abgelaufen", timestampStatus, certExpired
@@ -122,32 +133,9 @@ func (s *Service) verifyPDFBytes(pdfBytes []byte) (status, detail, timestampStat
return VerificationValid, "", timestampStatus, certExpired
}
func joinWarnings(warnings []error, fallback string) string {
func joinWarnings(warnings []string, fallback string) string {
if len(warnings) == 0 {
return fallback
}
var b bytes.Buffer
for i, w := range warnings {
if i > 0 {
b.WriteString("; ")
}
b.WriteString(w.Error())
}
return b.String()
}
// trustedRootPool liefert die vertrauenswuerdigen Wurzelzertifikate fuer die
// Kettenpruefung: das System-Zertifikatspool plus das konfigurierte
// Signatur-Zertifikat selbst (fuer das selbstsignierte Test-Zertifikat
// dieser Kachel gibt es sonst keine Wurzel -- eine echte QTSP-Kette wuerde
// stattdessen ueber das System-Pool verifiziert).
func (s *Service) trustedRootPool() *x509.CertPool {
pool, err := x509.SystemCertPool()
if err != nil || pool == nil {
pool = x509.NewCertPool()
}
if s.Identity != nil && s.Identity.Certificate != nil {
pool.AddCert(s.Identity.Certificate)
}
return pool
return strings.Join(warnings, "; ")
}