@mostajs/pdf-sign 0.4.1 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +20 -0
- package/dist/types.d.ts +43 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/verify.js +1 -1
- package/dist/verify.js.map +1 -1
- package/llms.txt +5 -1
- package/package.json +1 -1
- package/python/sign_with_perms.py +137 -9
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# Changelog — @mostajs/pdf-sign
|
|
2
2
|
|
|
3
|
+
## 0.5.0 — 07/10/2026
|
|
4
|
+
|
|
5
|
+
**`verifyPdf` dit QUAND et QUI, plus seulement « valide ».**
|
|
6
|
+
|
|
7
|
+
### Ajouté (additif : les champs existants ne changent pas)
|
|
8
|
+
- Par signature (moteur pyhanko) : `type` (signature ou horodatage de document), `level` (PAdES-B-B,
|
|
9
|
+
B-T, B-LT, horodatage de document), `claimedSigningTime` (la date DÉCLARÉE par le signataire),
|
|
10
|
+
`timestamp` (date ATTESTÉE `genTime`, politique `policy` (OID), numéro, autorité `tsa`, chaîne
|
|
11
|
+
embarquée, intact/valid/trusted du jeton), `signerCertificate` (titulaire, émetteur, numéro,
|
|
12
|
+
validité, `selfSigned`), `name` (nom affiché par les lecteurs), `reason`, `location`, `subFilter`,
|
|
13
|
+
`coverage`, `digestAlgorithm`. Au niveau du document : `dss`.
|
|
14
|
+
- Raison : la date déclarée (antidatable) et la date attestée par un horodateur étaient
|
|
15
|
+
indiscernables ; aucun écran de vérification ne pouvait les opposer.
|
|
16
|
+
|
|
17
|
+
### Corrigé
|
|
18
|
+
- Une signature que pyhanko REFUSE de valider (CMS abîmé, certificat de signature qui ne
|
|
19
|
+
correspond plus) est désormais rendue **invalide** (`intact: false, valid: false, error`) au lieu
|
|
20
|
+
de faire échouer toute la vérification. Jusqu'en 0.4.1, un document altéré de cette façon ne
|
|
21
|
+
rendait plus rien. T-DET-1..4.
|
|
22
|
+
|
|
3
23
|
## 0.4.1 — 29/09/2026
|
|
4
24
|
|
|
5
25
|
**pyhanko 0.37 taisait le diagnostic « identifiants refusés » — et rien ne fixait les versions.**
|
package/dist/types.d.ts
CHANGED
|
@@ -107,17 +107,60 @@ export interface VerifyOptions {
|
|
|
107
107
|
trustPem?: string;
|
|
108
108
|
engine?: SignEngine;
|
|
109
109
|
}
|
|
110
|
+
/** Ce qu'un vérificateur doit voir d'un certificat (0.5.0) — jamais sa clé. */
|
|
111
|
+
export interface CertificateInfo {
|
|
112
|
+
subject: string;
|
|
113
|
+
issuer?: string;
|
|
114
|
+
serial?: string;
|
|
115
|
+
notBefore?: string | null;
|
|
116
|
+
notAfter?: string | null;
|
|
117
|
+
/** Émetteur = titulaire : personne d'autre ne s'en porte garant. */
|
|
118
|
+
selfSigned?: boolean;
|
|
119
|
+
}
|
|
120
|
+
/** Le jeton d'horodatage RFC 3161 attaché à une signature, ou l'horodatage de document (0.5.0). */
|
|
121
|
+
export interface TimestampInfo {
|
|
122
|
+
/** La date ATTESTÉE par l'horodateur (ISO 8601) — à opposer à `claimedSigningTime`. */
|
|
123
|
+
genTime: string | null;
|
|
124
|
+
/** Politique d'horodatage (OID), celle que publie le prestataire. */
|
|
125
|
+
policy: string;
|
|
126
|
+
serial?: string;
|
|
127
|
+
hashAlgorithm?: string;
|
|
128
|
+
tsa: CertificateInfo | null;
|
|
129
|
+
/** Sujets des certificats embarqués dans le jeton. */
|
|
130
|
+
chain: string[];
|
|
131
|
+
intact: boolean | null;
|
|
132
|
+
valid: boolean | null;
|
|
133
|
+
trusted: boolean | null;
|
|
134
|
+
}
|
|
110
135
|
export interface SignatureInfo {
|
|
111
136
|
field?: string;
|
|
112
137
|
intact?: boolean;
|
|
113
138
|
valid?: boolean;
|
|
114
139
|
trusted?: boolean;
|
|
115
140
|
signer?: string;
|
|
141
|
+
type?: "signature" | "horodatage-document";
|
|
142
|
+
/** Nom AFFICHÉ par les lecteurs (« signé par … ») : déclaratif, à ne pas confondre avec le certificat. */
|
|
143
|
+
name?: string | null;
|
|
144
|
+
reason?: string | null;
|
|
145
|
+
location?: string | null;
|
|
146
|
+
subFilter?: string | null;
|
|
147
|
+
/** L'heure DÉCLARÉE par le signataire : sans horodatage, elle est antidatable. */
|
|
148
|
+
claimedSigningTime?: string | null;
|
|
149
|
+
coverage?: string | null;
|
|
150
|
+
digestAlgorithm?: string | null;
|
|
151
|
+
signerCertificate?: CertificateInfo | null;
|
|
152
|
+
timestamp?: TimestampInfo | null;
|
|
153
|
+
/** Cause, quand le moteur refuse de valider la signature (rendue invalide, jamais en panne). */
|
|
154
|
+
error?: string;
|
|
155
|
+
/** PAdES-B-B (aucune date attestée), B-T (jeton), B-LT (+ DSS), ou horodatage de document (B-LTA). */
|
|
156
|
+
level?: string;
|
|
116
157
|
}
|
|
117
158
|
export interface VerifyResult {
|
|
118
159
|
signed: boolean;
|
|
119
160
|
signatures: SignatureInfo[];
|
|
120
161
|
engine: "pyhanko" | "node";
|
|
162
|
+
/** Données de validation à long terme (DSS) présentes dans le document (0.5.0). */
|
|
163
|
+
dss?: boolean;
|
|
121
164
|
}
|
|
122
165
|
export interface EngineAvailability {
|
|
123
166
|
node: boolean;
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,6DAA6D;AAC7D,MAAM,WAAW,QAAQ;IACvB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,yFAAyF;AACzF,MAAM,WAAW,SAAS;IACxB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,0CAA0C;IAC1C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,iDAAiD;IACjD,GAAG,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;IACvC,gGAAgG;IAChG,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,kFAAkF;AAClF,MAAM,WAAW,eAAe;IAC9B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB;;;;;;;OAOG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;AAErD,MAAM,WAAW,WAAW;IAC1B,qCAAqC;IACrC,KAAK,EAAE,MAAM,GAAG,UAAU,CAAC;IAC3B,qEAAqE;IACrE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,6CAA6C;IAC7C,GAAG,EAAE,MAAM,GAAG,UAAU,CAAC;IACzB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,IAAI,CAAC,EAAE,QAAQ,CAAC;IAChB,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,qGAAqG;IACrG,WAAW,CAAC,EAAE,eAAe,GAAG,KAAK,CAAC;IAEtC;;;;;;;;OAQG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;;;;;;;;OAUG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC1C,6GAA6G;IAC7G,aAAa,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;IACnD,0GAA0G;IAC1G,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B;;;;;;OAMG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,2FAA2F;IAC3F,MAAM,CAAC,EAAE,UAAU,CAAC;CACrB;AAED,MAAM,WAAW,UAAU;IACzB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,UAAU,CAAC;IACnB,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;CAC5B;AAED,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,uGAAuG;IACvG,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,gEAAgE;IAChE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,UAAU,CAAC;CACrB;AAED,MAAM,WAAW,aAAa;IAC5B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/dist/verify.js
CHANGED
|
@@ -39,7 +39,7 @@ export function verifyPdf(opts) {
|
|
|
39
39
|
}
|
|
40
40
|
const line = out.trim().split("\n").pop() ?? "{}";
|
|
41
41
|
const parsed = JSON.parse(line);
|
|
42
|
-
return { signed: !!parsed.signed, signatures: parsed.signatures ?? [], engine: "pyhanko" };
|
|
42
|
+
return { signed: !!parsed.signed, signatures: parsed.signatures ?? [], engine: "pyhanko", dss: !!parsed.dss };
|
|
43
43
|
}
|
|
44
44
|
// Détection Node basique : présence d'un /ByteRange + dictionnaire de signature.
|
|
45
45
|
const s = readFileSync(opts.input).toString("latin1");
|
package/dist/verify.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"verify.js","sourceRoot":"","sources":["../src/verify.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAEvC,OAAO,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACtD,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAEhD,MAAM,UAAU,SAAS,CAAC,IAAmB;IAC3C,MAAM,GAAG,GAAG,iBAAiB,EAAE,CAAC;IAChC,MAAM,KAAK,GAAG,aAAa,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IACxC,MAAM,KAAK,GAAG,IAAI,CAAC,QAAQ,IAAI,GAAG,CAAC,QAAQ,CAAC;IAC5C,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,IAAI,GAAG,CAAC,MAAM,CAAC;IAEvC,iGAAiG;IACjG,gGAAgG;IAChG,8FAA8F;IAC9F,8FAA8F;IAC9F,8FAA8F;IAC9F,IAAI,IAAI,KAAK,SAAS,IAAI,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC;QACzC,MAAM,IAAI,KAAK,CAAC,6HAA6H,CAAC,CAAC;IACjJ,CAAC;IACD,IAAI,KAAK,CAAC,OAAO,IAAI,IAAI,KAAK,MAAM,EAAE,CAAC;QACrC,MAAM,IAAI,GAAG,CAAC,OAAO,EAAE,QAAQ,EAAE,IAAI,CAAC,KAAK,EAAE,KAAK,IAAI,EAAE,CAAC,CAAC;QAC1D,IAAI,IAAI,CAAC,YAAY;YAAE,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;QACpD,IAAI,GAAW,CAAC;QAChB,IAAI,CAAC;YACH,GAAG,GAAG,YAAY,CAAC,KAAK,CAAC,MAAgB,EAAE,IAAI,EAAE,EAAE,QAAQ,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC,CAAC;QAC7G,CAAC;QAAC,OAAO,CAAM,EAAE,CAAC;YAChB,MAAM,KAAK,GAAG,MAAM,CAAC,CAAC,EAAE,MAAM,IAAI,EAAE,CAAC,CAAC;YACtC,MAAM,GAAG,GAAG,KAAK,CAAC,KAAK,CAAC,oBAAoB,CAAC,CAAC;YAC9C,MAAM,IAAI,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,uCAAuC,KAAK,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,IAAI,MAAM,CAAC,CAAC,EAAE,OAAO,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC;QACrI,CAAC;QACD,MAAM,IAAI,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,IAAI,IAAI,CAAC;QAClD,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAChC,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC,MAAM,EAAE,UAAU,EAAE,MAAM,CAAC,UAAU,IAAI,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"verify.js","sourceRoot":"","sources":["../src/verify.ts"],"names":[],"mappings":"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"}
|
package/llms.txt
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# @mostajs/pdf-sign — fiche LLM
|
|
2
2
|
> Signature numérique de PDF, moteur HYBRIDE : cœur Node (PKCS#7 détaché via @signpdf, zéro Python) + bascule pyhanko (PAdES visible + permissions AES-256 + MOT DE PASSE d'ouverture) quand un venv est disponible. Config 100 % `.env`, CLI + API. Portage générique du pipeline pyhanko+pikepdf éprouvé. IMPLÉMENTÉ 0.1.0.
|
|
3
3
|
|
|
4
|
-
- Version: 0.
|
|
4
|
+
- Version: 0.5.0 · Licence: AGPL-3.0-or-later · Auteur: Dr Hamid MADANI <drmdh@msn.com>
|
|
5
5
|
- Chemin: mostajs/mosta-pdf-sign-stack/mosta-pdf-sign · UI: ../mosta-pdf-sign-ui
|
|
6
6
|
|
|
7
7
|
## RÔLE
|
|
@@ -33,6 +33,10 @@ La signature PAdES + permissions AES-256 garantit intégrité, authenticité et
|
|
|
33
33
|
· opts.timestampHeaders / opts.timestampAuth / opts.timestampTimeout (0.4.0) : joindre un
|
|
34
34
|
horodateur QUALIFIÉ, qui authentifie (clé d'API en en-tête, ou HTTP Basic). Exige pyhanko.
|
|
35
35
|
- verifyPdf(opts): VerifyResult — validation pyhanko complète si dispo, sinon détection Node (/ByteRange)
|
|
36
|
+
0.5.0 : + par signature type, level (PAdES-B-B/B-T/B-LT), claimedSigningTime (DÉCLARÉE), timestamp
|
|
37
|
+
{genTime (ATTESTÉE), policy OID, tsa, chain, intact/valid/trusted}, signerCertificate {subject, issuer,
|
|
38
|
+
notBefore, notAfter, selfSigned}, name/reason/location, coverage ; document : dss. Signature que
|
|
39
|
+
pyhanko refuse = invalide avec `error`, jamais une exception.
|
|
36
40
|
- detectEngines(python?): EngineAvailability — { node:true, pyhanko:bool, python }
|
|
37
41
|
- resolvePython / pyhankoAvailable
|
|
38
42
|
- loadPdfSignConfig(): PdfSignConfig — lecture `.env` via @mostajs/config
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mostajs/pdf-sign",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Signature numérique de PDF pour @mostajs — moteur HYBRIDE : cœur Node (PKCS#7 via @signpdf) + bascule pyhanko (PAdES + permissions AES-256) si disponible. Config .env, CLI, API. Réutilise le pipeline éprouvé pyhanko+pikepdf.",
|
|
5
5
|
"author": "Dr Hamid MADANI <drmdh@msn.com>",
|
|
6
6
|
"license": "AGPL-3.0-or-later",
|
|
@@ -212,6 +212,112 @@ def _cert_name(cert):
|
|
|
212
212
|
return str(cert) if cert else ""
|
|
213
213
|
|
|
214
214
|
|
|
215
|
+
def _iso(d):
|
|
216
|
+
try:
|
|
217
|
+
return d.isoformat() if d is not None else None
|
|
218
|
+
except Exception:
|
|
219
|
+
return None
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def _cert_info(c):
|
|
223
|
+
"""Ce qu'un vérificateur doit voir d'un certificat — jamais sa clé."""
|
|
224
|
+
if c is None:
|
|
225
|
+
return None
|
|
226
|
+
try:
|
|
227
|
+
v = c["tbs_certificate"]["validity"]
|
|
228
|
+
return {
|
|
229
|
+
"subject": c.subject.human_friendly,
|
|
230
|
+
"issuer": c.issuer.human_friendly,
|
|
231
|
+
"serial": format(c.serial_number, "x"),
|
|
232
|
+
"notBefore": _iso(v["not_before"].native),
|
|
233
|
+
"notAfter": _iso(v["not_after"].native),
|
|
234
|
+
"selfSigned": c.subject == c.issuer,
|
|
235
|
+
}
|
|
236
|
+
except Exception:
|
|
237
|
+
return {"subject": _cert_name(c)}
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
def _jeton(token):
|
|
241
|
+
"""Le jeton RFC 3161 : date, politique, autorité, chaîne embarquée."""
|
|
242
|
+
try:
|
|
243
|
+
sd = token["content"]
|
|
244
|
+
tst = sd["encap_content_info"]["content"].parsed
|
|
245
|
+
certs = [c.chosen for c in (sd["certificates"] or [])]
|
|
246
|
+
tsa = certs[0] if certs else None
|
|
247
|
+
sid = sd["signer_infos"][0]["sid"]
|
|
248
|
+
for c in certs: # le certificat qui a SIGNÉ le jeton, pas le premier venu
|
|
249
|
+
if sid.name == "issuer_and_serial_number" and c.serial_number == sid.chosen["serial_number"].native:
|
|
250
|
+
tsa = c
|
|
251
|
+
return {
|
|
252
|
+
"genTime": _iso(tst["gen_time"].native),
|
|
253
|
+
"policy": tst["policy"].dotted,
|
|
254
|
+
"serial": format(tst["serial_number"].native, "x"),
|
|
255
|
+
"hashAlgorithm": tst["message_imprint"]["hash_algorithm"]["algorithm"].native,
|
|
256
|
+
"tsa": _cert_info(tsa),
|
|
257
|
+
"chain": [c.subject.human_friendly for c in certs],
|
|
258
|
+
}
|
|
259
|
+
except Exception:
|
|
260
|
+
return None
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
def _details(s, status, est_horodatage, dss):
|
|
264
|
+
d = {"type": "horodatage-document" if est_horodatage else "signature"}
|
|
265
|
+
so = s.sig_object
|
|
266
|
+
for cle, nom in (("/Name", "name"), ("/Reason", "reason"), ("/Location", "location"), ("/SubFilter", "subFilter")):
|
|
267
|
+
try:
|
|
268
|
+
v = so.get(cle)
|
|
269
|
+
d[nom] = str(v).lstrip("/") if v is not None else None
|
|
270
|
+
except Exception:
|
|
271
|
+
d[nom] = None
|
|
272
|
+
try:
|
|
273
|
+
d["claimedSigningTime"] = _iso(s.self_reported_timestamp) # DÉCLARÉE par le signataire
|
|
274
|
+
except Exception:
|
|
275
|
+
d["claimedSigningTime"] = None
|
|
276
|
+
try:
|
|
277
|
+
d["coverage"] = status.coverage.name
|
|
278
|
+
except Exception:
|
|
279
|
+
d["coverage"] = None
|
|
280
|
+
try:
|
|
281
|
+
d["digestAlgorithm"] = status.md_algorithm
|
|
282
|
+
except Exception:
|
|
283
|
+
d["digestAlgorithm"] = None
|
|
284
|
+
try:
|
|
285
|
+
cert = getattr(status, "signing_cert", None) if status is not None else s.signer_cert
|
|
286
|
+
except Exception:
|
|
287
|
+
cert = None
|
|
288
|
+
d["signerCertificate"] = _cert_info(cert)
|
|
289
|
+
jeton = None
|
|
290
|
+
if est_horodatage:
|
|
291
|
+
try:
|
|
292
|
+
jeton = _jeton(s.signed_data_obj if hasattr(s, "signed_data_obj") else None) or None
|
|
293
|
+
except Exception:
|
|
294
|
+
jeton = None
|
|
295
|
+
if jeton is None:
|
|
296
|
+
try:
|
|
297
|
+
from asn1crypto import cms
|
|
298
|
+
jeton = _jeton(cms.ContentInfo({"content_type": "signed_data", "content": s.signed_data}))
|
|
299
|
+
except Exception:
|
|
300
|
+
jeton = None
|
|
301
|
+
else:
|
|
302
|
+
try:
|
|
303
|
+
attrs = s.signer_info["unsigned_attrs"] or []
|
|
304
|
+
toks = [a["values"][0] for a in attrs if a["type"].native == "signature_time_stamp_token"]
|
|
305
|
+
jeton = _jeton(toks[0]) if toks else None
|
|
306
|
+
except Exception:
|
|
307
|
+
jeton = None
|
|
308
|
+
if jeton is not None:
|
|
309
|
+
tv = getattr(status, "timestamp_validity", None) if not est_horodatage else status
|
|
310
|
+
jeton["intact"] = bool(getattr(tv, "intact", False)) if tv is not None else None
|
|
311
|
+
jeton["valid"] = bool(getattr(tv, "valid", False)) if tv is not None else None
|
|
312
|
+
jeton["trusted"] = bool(getattr(tv, "trusted", False)) if tv is not None else None
|
|
313
|
+
d["timestamp"] = jeton
|
|
314
|
+
if est_horodatage:
|
|
315
|
+
d["level"] = "horodatage de document (PAdES-B-LTA)"
|
|
316
|
+
else:
|
|
317
|
+
d["level"] = "PAdES-B-LT" if (jeton and dss) else ("PAdES-B-T" if jeton else "PAdES-B-B")
|
|
318
|
+
return d
|
|
319
|
+
|
|
320
|
+
|
|
215
321
|
def do_verify(pdf_path, cert_pem, open_password=None):
|
|
216
322
|
import logging
|
|
217
323
|
# Silence les logs internes (tracebacks de chaîne de confiance) : on renvoie le verdict en JSON.
|
|
@@ -262,16 +368,38 @@ def do_verify(pdf_path, cert_pem, open_password=None):
|
|
|
262
368
|
print(json.dumps({"signed": False, "signatures": []}))
|
|
263
369
|
return
|
|
264
370
|
out = []
|
|
371
|
+
dss = "/DSS" in r.root
|
|
265
372
|
for s in sigs:
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
373
|
+
est_horodatage = str(s.sig_object.get("/Type", "")) == "/DocTimeStamp"
|
|
374
|
+
# 0.5.0 : une signature que pyhanko REFUSE de valider (structure CMS abîmée, certificat
|
|
375
|
+
# qui ne correspond plus…) est un verdict — invalide —, pas une panne de la vérification.
|
|
376
|
+
# Jusqu'en 0.4.1, l'exception remontait et l'appelant ne lisait plus RIEN du document.
|
|
377
|
+
try:
|
|
378
|
+
if est_horodatage:
|
|
379
|
+
from pyhanko.sign.validation import validate_pdf_timestamp
|
|
380
|
+
status = validate_pdf_timestamp(s, vc)
|
|
381
|
+
else:
|
|
382
|
+
status = validate_pdf_signature(s, vc)
|
|
383
|
+
base = {
|
|
384
|
+
"field": s.field_name,
|
|
385
|
+
"intact": bool(status.intact),
|
|
386
|
+
"valid": bool(status.valid),
|
|
387
|
+
"trusted": bool(getattr(status, "trusted", False)),
|
|
388
|
+
"signer": _cert_name(getattr(status, "signing_cert", None)),
|
|
389
|
+
}
|
|
390
|
+
except Exception as e:
|
|
391
|
+
status = None
|
|
392
|
+
try:
|
|
393
|
+
signataire = s.signer_cert
|
|
394
|
+
except Exception:
|
|
395
|
+
signataire = None
|
|
396
|
+
base = {"field": s.field_name, "intact": False, "valid": False, "trusted": False,
|
|
397
|
+
"signer": _cert_name(signataire), "error": str(e).split("\n")[0][:300]}
|
|
398
|
+
# 0.5.0 — le DÉTAIL, additif : chaque champ est lu à part, et un champ illisible vaut
|
|
399
|
+
# null plutôt que de faire échouer la vérification entière.
|
|
400
|
+
base.update(_details(s, status, est_horodatage, dss))
|
|
401
|
+
out.append(base)
|
|
402
|
+
print(json.dumps({"signed": True, "signatures": out, "dss": dss}, default=str))
|
|
275
403
|
|
|
276
404
|
|
|
277
405
|
if __name__ == "__main__":
|