@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 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;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
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");
@@ -1 +1 @@
1
- {"version":3,"file":"verify.js","sourceRoot":"","sources":["../src/verify.ts"],"names":[],"mappings":"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"}
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.1 · Licence: AGPL-3.0-or-later · Auteur: Dr Hamid MADANI <drmdh@msn.com>
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.4.1",
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
- status = validate_pdf_signature(s, vc)
267
- out.append({
268
- "field": s.field_name,
269
- "intact": bool(status.intact),
270
- "valid": bool(status.valid),
271
- "trusted": bool(getattr(status, "trusted", False)),
272
- "signer": _cert_name(getattr(status, "signing_cert", None)),
273
- })
274
- print(json.dumps({"signed": True, "signatures": out}))
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__":