@mostajs/pdf-sign 0.3.2 → 0.4.1
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 +103 -0
- package/README.md +43 -0
- package/dist/cli.js +8 -0
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +9 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +12 -0
- package/dist/config.js.map +1 -1
- package/dist/node-signer.d.ts.map +1 -1
- package/dist/node-signer.js +3 -0
- package/dist/node-signer.js.map +1 -1
- package/dist/pyhanko-signer.d.ts.map +1 -1
- package/dist/pyhanko-signer.js +11 -0
- package/dist/pyhanko-signer.js.map +1 -1
- package/dist/sign.d.ts.map +1 -1
- package/dist/sign.js +55 -0
- package/dist/sign.js.map +1 -1
- package/dist/types.d.ts +19 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/verify.d.ts.map +1 -1
- package/dist/verify.js +20 -9
- package/dist/verify.js.map +1 -1
- package/docs/INSTALLATION-ET-EXPLOITATION.md +271 -0
- package/llms.txt +20 -3
- package/package.json +2 -1
- package/python/requirements.lock.txt +32 -0
- package/python/requirements.txt +14 -3
- package/python/setup-venv.sh +36 -5
- package/python/sign_with_perms.py +64 -6
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,108 @@
|
|
|
1
1
|
# Changelog — @mostajs/pdf-sign
|
|
2
2
|
|
|
3
|
+
## 0.4.1 — 29/09/2026
|
|
4
|
+
|
|
5
|
+
**pyhanko 0.37 taisait le diagnostic « identifiants refusés » — et rien ne fixait les versions.**
|
|
6
|
+
|
|
7
|
+
### Corrigé
|
|
8
|
+
|
|
9
|
+
pyhanko 0.37 a remplacé son client HTTP (`requests` → `aiohttp`). Son API n'a pas changé, mais le
|
|
10
|
+
code d'erreur HTTP a changé de place : il est porté par l'exception (`.status`) et non plus par la
|
|
11
|
+
réponse passée en argument (`.status_code`). Sous 0.37, la 0.4.0 présentait donc un 401 comme
|
|
12
|
+
« Error while contacting timestamp service » — ce qui envoie chercher un problème réseau. Les deux
|
|
13
|
+
emplacements sont désormais lus. La suite complète passe sous pyhanko **0.35.1 et 0.37.0**.
|
|
14
|
+
|
|
15
|
+
### Corrigé — la VÉRIFICATION, qu'aucun essai ne gardait
|
|
16
|
+
|
|
17
|
+
Relevé en vérifiant que l'adaptation à 0.37 n'avait rien cassé d'autre : `verifyPdf` n'était
|
|
18
|
+
essayé qu'avec le moteur Node. Le chemin pyhanko cachait deux défauts, **identiques sous 0.35 et
|
|
19
|
+
0.37** — donc antérieurs à la montée de version :
|
|
20
|
+
|
|
21
|
+
1. **Repli silencieux.** Tout échec de pyhanko (document verrouillé, plantage, API changée)
|
|
22
|
+
retombait sur la détection Node, qui rend `intact` et `valid` **indéfinis** — et un affichage
|
|
23
|
+
qui lit « pas false » y voit « valide ». Désormais : si pyhanko est présent, son échec est une
|
|
24
|
+
**erreur** qui nomme la cause ; `engine:"pyhanko"` sans pyhanko est une erreur. Le repli Node ne
|
|
25
|
+
vaut plus que si pyhanko **manque**.
|
|
26
|
+
2. **Mauvais mot de passe pris pour le bon.** `decrypt()` ne lève pas : il rend un statut `FAILED`.
|
|
27
|
+
La boucle attendait une exception, et tenait le document pour ouvert au premier essai. Le
|
|
28
|
+
statut est désormais lu ; un document verrouillé vérifié sans son mot de passe donne
|
|
29
|
+
« document verrouillé : son mot de passe d'ouverture est requis ».
|
|
30
|
+
|
|
31
|
+
`test-scripts/test-verification-pyhanko.ts` — 7 essais (T-VER-1 à 7) : intact / altéré /
|
|
32
|
+
horodaté / verrouillé sans et avec mot de passe / fichier illisible / cachet avec image. Suite
|
|
33
|
+
complète : **30 essais, 0 sauté, sous pyhanko 0.35.1 ET 0.37.0**.
|
|
34
|
+
|
|
35
|
+
⚠️ `valid` seul ne suffit pas : sur un document altéré, pyhanko rend `valid: true` (la signature
|
|
36
|
+
est mathématiquement bonne) et `intact: false` (elle ne couvre plus ce document). Lire les deux.
|
|
37
|
+
|
|
38
|
+
### Relecture de l'adaptation 0.37
|
|
39
|
+
|
|
40
|
+
La lecture de `.status` est restreinte aux exceptions d'`aiohttp` : ailleurs, un attribut du même
|
|
41
|
+
nom pourrait ne pas être un code HTTP. Le diagnostic ne peut, par construction, que **changer le
|
|
42
|
+
message** d'un échec — jamais transformer un échec en succès. Contre-épreuve : sans cette lecture,
|
|
43
|
+
T-TSA-4 et T-TSA-8 échouent sous 0.37.
|
|
44
|
+
|
|
45
|
+
### Ajouté — les versions Python sont FIXÉES
|
|
46
|
+
|
|
47
|
+
- `python/requirements.lock.txt` : l'ensemble exact vérifié (24 paquets). **Installé par défaut**.
|
|
48
|
+
- `python/requirements.txt` devient une **plage bornée** : `pyhanko>=0.35.1,<0.38`,
|
|
49
|
+
`pikepdf>=10.9,<11` (auparavant `>=0.25` / `>=8`, sans borne haute : deux installations à un
|
|
50
|
+
mois d'écart n'avaient pas les mêmes versions — c'est ainsi que 0.37 est passé inaperçu).
|
|
51
|
+
- `setup-venv.sh` : accepte un répertoire (pour poser le venv **hors de node_modules/**), vérifie
|
|
52
|
+
Python ≥ 3.10 et `python3-venv` **avant** d'installer, `--plage` pour une montée de version.
|
|
53
|
+
- `docs/INSTALLATION-ET-EXPLOITATION.md` (livré dans le paquet) : exigences, rôle de chaque
|
|
54
|
+
bibliothèque, installation en production, vérification, montée de version, sens de chaque
|
|
55
|
+
message d'erreur, secrets.
|
|
56
|
+
|
|
57
|
+
## 0.4.0 — 28/09/2026
|
|
58
|
+
|
|
59
|
+
**Joindre un horodateur qui authentifie — c'est-à-dire un horodateur QUALIFIÉ.**
|
|
60
|
+
|
|
61
|
+
Un horodateur qualifié (eIDAS art. 42) est un service payant : il authentifie son client. Evidency,
|
|
62
|
+
par exemple, exige une clé d'API dans l'en-tête `X-API-KEY`. Jusqu'en 0.3.2, ce module ne savait
|
|
63
|
+
transmettre qu'une adresse : seuls les horodateurs publics et gratuits — donc **non qualifiés** —
|
|
64
|
+
étaient joignables. Relevé le 28/09/2026, à la lecture de la réponse écrite d'Evidency.
|
|
65
|
+
|
|
66
|
+
### Ajouté
|
|
67
|
+
|
|
68
|
+
- `timestampHeaders` (en-têtes, typiquement une clé d'API), `timestampAuth` (HTTP Basic),
|
|
69
|
+
`timestampTimeout` (secondes, défaut 10 — pyhanko en fixait 5 en dur).
|
|
70
|
+
- `.env` : `PDF_SIGN_TSA`, `PDF_SIGN_TSA_API_KEY`, `PDF_SIGN_TSA_API_KEY_HEADER`,
|
|
71
|
+
`PDF_SIGN_TSA_USER`, `PDF_SIGN_TSA_PASSWORD`, `PDF_SIGN_TSA_TIMEOUT`.
|
|
72
|
+
- CLI : `--tsa <url>`, `--tsa-timeout <s>`. Les identifiants, eux, ne passent **que** par le `.env`.
|
|
73
|
+
- Un refus de l'horodateur est **nommé** : 401/403 ⇒ « identifiants refusés », 429 ⇒ « débit
|
|
74
|
+
dépassé ». pyhanko ne regarde que le type de contenu de la réponse et présentait un 401 comme
|
|
75
|
+
une « réponse malformée », ce qui envoyait chercher un défaut de protocole.
|
|
76
|
+
|
|
77
|
+
### Corrigé — un repli silencieux
|
|
78
|
+
|
|
79
|
+
**Le moteur Node ignorait `timestampUrl`.** En `auto` sur une machine sans pyhanko, un document
|
|
80
|
+
demandé horodaté sortait **signé sans date, sans erreur** — exactement le repli que ce module
|
|
81
|
+
interdit partout ailleurs. Désormais : erreur explicite, qui dit quoi installer.
|
|
82
|
+
|
|
83
|
+
### Garde-fous
|
|
84
|
+
|
|
85
|
+
| refusé | pourquoi |
|
|
86
|
+
|---|---|
|
|
87
|
+
| identifiants vers `http://` non local | une clé envoyée en clair est une clé publiée |
|
|
88
|
+
| identifiants sans `timestampUrl` | une configuration à moitié posée ne doit pas passer pour complète |
|
|
89
|
+
| clé du `.env` vers un autre horodateur que `PDF_SIGN_TSA` | la joindre à une requête vers un tiers, c'est la lui donner — même règle que le certificat et sa phrase |
|
|
90
|
+
|
|
91
|
+
### Essais
|
|
92
|
+
|
|
93
|
+
`test-scripts/test-horodatage-authentifie.ts` — 8 essais (T-TSA-1 à 8), par un relais local qui
|
|
94
|
+
EXIGE les identifiants puis transmet à l'horodateur public de DigiCert : le jeton obtenu est un vrai
|
|
95
|
+
jeton, dont la présence est vérifiée dans la signature. T-TSA-8 vérifie ce que le **serveur a reçu**,
|
|
96
|
+
pas ce que le client affirme avoir envoyé. Deux mutations (appariement retiré, refus Node retiré)
|
|
97
|
+
font chacune échouer l'essai qui les garde ; le détecteur de jeton répond « NON » sur un document
|
|
98
|
+
signé sans horodatage.
|
|
99
|
+
|
|
100
|
+
### Compatibilité
|
|
101
|
+
|
|
102
|
+
Aucun changement pour un appelant qui ne passe pas d'identifiants. **Un seul changement de
|
|
103
|
+
comportement** : `timestampUrl` avec le moteur Node lève désormais au lieu de signer sans date —
|
|
104
|
+
un appelant qui en dépendait recevait un document faux.
|
|
105
|
+
|
|
3
106
|
## 0.3.2 — 26/09/2026
|
|
4
107
|
|
|
5
108
|
**`modify_form` ne peut pas être refusé — et le dire vaut mieux que l'ignorer.**
|
package/README.md
CHANGED
|
@@ -24,6 +24,10 @@ Le module reste **générique** : il ne connaît aucun métier. La sémantique (
|
|
|
24
24
|
|
|
25
25
|
## Installation
|
|
26
26
|
|
|
27
|
+
> **Exploitation et maintenance** — exigences (Python ≥ 3.10), rôle de chaque bibliothèque Python,
|
|
28
|
+
> installation en production **hors de `node_modules/`**, sens de chaque message d'erreur, montée
|
|
29
|
+
> de version : **[docs/INSTALLATION-ET-EXPLOITATION.md](docs/INSTALLATION-ET-EXPLOITATION.md)**.
|
|
30
|
+
|
|
27
31
|
```bash
|
|
28
32
|
pnpm add @mostajs/pdf-sign
|
|
29
33
|
# Moteur pyhanko (optionnel : PAdES + permissions) :
|
|
@@ -108,6 +112,41 @@ await signPdf({ input: "note.pdf", p12: "cert.p12", passphrase: "•••", ope
|
|
|
108
112
|
> elle, ses signatures sont illisibles et la réponse serait « non signé » — sur un document
|
|
109
113
|
> parfaitement signé.
|
|
110
114
|
|
|
115
|
+
## Horodatage — et joindre un horodateur QUALIFIÉ *(0.4.0)*
|
|
116
|
+
|
|
117
|
+
`timestampUrl` fait passer la signature de **PAdES-B** à **PAdES-T** : la date n'est plus déclarée
|
|
118
|
+
par le signataire, elle est attestée par un tiers. Si l'horodateur ne répond pas, la signature
|
|
119
|
+
**échoue** — jamais de repli sur une signature sans date.
|
|
120
|
+
|
|
121
|
+
Un horodateur **qualifié** (eIDAS art. 42, celui dont la date renverse la charge de la preuve) est
|
|
122
|
+
un service payant : il **authentifie** son client. Depuis 0.4.0 :
|
|
123
|
+
|
|
124
|
+
```ts
|
|
125
|
+
await signPdf({
|
|
126
|
+
input: "in.pdf", output: "out.pdf", p12: "cachet.p12", passphraseFile: "cachet.pass",
|
|
127
|
+
timestampUrl: "https://api.evidency.io/v3/projects/<ID>/timestamp",
|
|
128
|
+
timestampHeaders: { "X-API-KEY": process.env.CLE }, // ou timestampAuth: { user, password }
|
|
129
|
+
timestampTimeout: 10,
|
|
130
|
+
});
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Ou, sans toucher au code, par le `.env` : `PDF_SIGN_TSA`, `PDF_SIGN_TSA_API_KEY` (voir plus bas).
|
|
134
|
+
|
|
135
|
+
**Quatre garde-fous**, chacun couvert par un essai (`test-scripts/test-horodatage-authentifie.ts`) :
|
|
136
|
+
|
|
137
|
+
| | ce qui est refusé | pourquoi |
|
|
138
|
+
|---|---|---|
|
|
139
|
+
| 1 | le moteur **Node** avec un `timestampUrl` | il ne sait pas horodater ; jusqu'en 0.3.2 il signait **sans date, sans rien dire** |
|
|
140
|
+
| 2 | des identifiants vers une adresse `http://` non locale | une clé envoyée en clair est une clé publiée |
|
|
141
|
+
| 3 | des identifiants sans adresse | une configuration à moitié posée ne doit pas passer pour complète |
|
|
142
|
+
| 4 | la clé du `.env` vers **un autre** horodateur que `PDF_SIGN_TSA` | joindre sa clé à une requête vers un tiers, c'est la lui donner |
|
|
143
|
+
|
|
144
|
+
Un refus d'identifiants est **nommé** : « identifiants refusés par l'horodateur (HTTP 401) ».
|
|
145
|
+
pyhanko, seul, l'aurait présenté comme une « réponse malformée ».
|
|
146
|
+
|
|
147
|
+
Les identifiants partent au moteur par le fichier de configuration temporaire (dossier en 0700,
|
|
148
|
+
effacé après usage), **jamais** par la ligne de commande — et le CLI ne les accepte qu'en `.env`.
|
|
149
|
+
|
|
111
150
|
## Configuration (`.env`)
|
|
112
151
|
|
|
113
152
|
| Clé | Rôle |
|
|
@@ -120,6 +159,10 @@ await signPdf({ input: "note.pdf", p12: "cert.p12", passphrase: "•••", ope
|
|
|
120
159
|
| `PDF_SIGN_STAMP_IMAGE` | image de fond du cachet visible (logo/tampon derrière le texte ; moteur pyhanko) |
|
|
121
160
|
| `PDF_SIGN_RESTRICT` | `true` → permissions restrictives (pyhanko) |
|
|
122
161
|
| `PDF_SIGN_TRUST_PEM` | certificat de confiance pour la vérification |
|
|
162
|
+
| `PDF_SIGN_TSA` | adresse de l'horodateur de la configuration — sert à **apparier** les identifiants ci-dessous *(0.4.0)* |
|
|
163
|
+
| `PDF_SIGN_TSA_API_KEY` / `PDF_SIGN_TSA_API_KEY_HEADER` | clé d'API de l'horodateur, et le nom de son en-tête (défaut `X-API-KEY`) *(0.4.0)* |
|
|
164
|
+
| `PDF_SIGN_TSA_USER` / `PDF_SIGN_TSA_PASSWORD` | identifiants HTTP Basic de l'horodateur *(0.4.0)* |
|
|
165
|
+
| `PDF_SIGN_TSA_TIMEOUT` | délai d'attente de l'horodateur, en secondes (défaut 10) *(0.4.0)* |
|
|
123
166
|
|
|
124
167
|
## Moteurs
|
|
125
168
|
|
package/dist/cli.js
CHANGED
|
@@ -24,6 +24,12 @@ const HELP = `mosta-pdf-sign — signature numérique de PDF (moteur hybride Nod
|
|
|
24
24
|
--restrict permissions restrictives (PAdES pyhanko requis)
|
|
25
25
|
--open-password <str> mot de passe exigé à l'OUVERTURE (chiffrement, pyhanko requis)
|
|
26
26
|
--reason --name --location --contact
|
|
27
|
+
--tsa <url> horodatage RFC 3161 (PAdES-T) ; échoue plutôt que signer sans date
|
|
28
|
+
--tsa-timeout <s> délai d'attente de l'horodateur (défaut 10 s)
|
|
29
|
+
Identifiants de l'horodateur : .env UNIQUEMENT (PDF_SIGN_TSA_API_KEY,
|
|
30
|
+
PDF_SIGN_TSA_USER/PASSWORD) — jamais en argument, la ligne de commande
|
|
31
|
+
se lit dans la table des processus. Ils ne partent que si --tsa
|
|
32
|
+
vaut PDF_SIGN_TSA.
|
|
27
33
|
mosta-pdf-sign verify <pdf> [--trust cert.pem] [--password <str>] [--engine ...]
|
|
28
34
|
mosta-pdf-sign engines état des moteurs disponibles
|
|
29
35
|
`;
|
|
@@ -55,6 +61,8 @@ async function main() {
|
|
|
55
61
|
engine: opt(argv, "engine"),
|
|
56
62
|
permissions: flag(argv, "restrict") ? {} : undefined,
|
|
57
63
|
openPassword: opt(argv, "open-password"),
|
|
64
|
+
timestampUrl: opt(argv, "tsa"),
|
|
65
|
+
timestampTimeout: opt(argv, "tsa-timeout") ? Number(opt(argv, "tsa-timeout")) : undefined,
|
|
58
66
|
meta: {
|
|
59
67
|
reason: opt(argv, "reason"), name: opt(argv, "name"),
|
|
60
68
|
location: opt(argv, "location"), contact: opt(argv, "contact"),
|
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA;;;;;GAKG;AACH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAG7C,SAAS,GAAG,CAAC,IAAc,EAAE,IAAY;IACvC,MAAM,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC;IACpC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;AACjE,CAAC;AACD,SAAS,IAAI,CAAC,IAAc,EAAE,IAAY;IACxC,OAAO,IAAI,CAAC,QAAQ,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC;AACpC,CAAC;AAED,MAAM,IAAI,GAAG
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|
package/dist/config.d.ts
CHANGED
|
@@ -9,6 +9,15 @@ export interface PdfSignConfig {
|
|
|
9
9
|
field: SignField;
|
|
10
10
|
restrict: boolean;
|
|
11
11
|
trustPem?: string;
|
|
12
|
+
/** Horodateur de la configuration et SES identifiants (0.4.0). */
|
|
13
|
+
tsa: {
|
|
14
|
+
url?: string;
|
|
15
|
+
apiKey?: string;
|
|
16
|
+
apiKeyHeader: string;
|
|
17
|
+
user?: string;
|
|
18
|
+
password?: string;
|
|
19
|
+
timeout?: number;
|
|
20
|
+
};
|
|
12
21
|
}
|
|
13
22
|
export declare function loadPdfSignConfig(): PdfSignConfig;
|
|
14
23
|
//# sourceMappingURL=config.d.ts.map
|
package/dist/config.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAElE,MAAM,WAAW,aAAa;IAC5B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,MAAM,EAAE,UAAU,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,QAAQ,CAAC;IACf,KAAK,EAAE,SAAS,CAAC;IACjB,QAAQ,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,kEAAkE;IAClE,GAAG,EAAE;QACH,GAAG,CAAC,EAAE,MAAM,CAAC;QACb,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,YAAY,EAAE,MAAM,CAAC;QACrB,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,OAAO,CAAC,EAAE,MAAM,CAAC;KAClB,CAAC;CACH;AAOD,wBAAgB,iBAAiB,IAAI,aAAa,CAiCjD"}
|
package/dist/config.js
CHANGED
|
@@ -7,6 +7,9 @@
|
|
|
7
7
|
* PDF_SIGN_REASON / PDF_SIGN_LOCATION / PDF_SIGN_NAME / PDF_SIGN_CONTACT
|
|
8
8
|
* PDF_SIGN_FIELD_PAGE / PDF_SIGN_FIELD_BOX ("x1,y1,x2,y2") · PDF_SIGN_STAMP_IMAGE (logo du cachet) · PDF_SIGN_RESTRICT (bool)
|
|
9
9
|
* PDF_SIGN_TRUST_PEM (validation)
|
|
10
|
+
* PDF_SIGN_TSA (adresse de l'horodateur — sert à APPARIER les identifiants, voir `sign.ts`)
|
|
11
|
+
* PDF_SIGN_TSA_API_KEY · PDF_SIGN_TSA_API_KEY_HEADER (défaut X-API-KEY)
|
|
12
|
+
* PDF_SIGN_TSA_USER · PDF_SIGN_TSA_PASSWORD (HTTP Basic) · PDF_SIGN_TSA_TIMEOUT (secondes)
|
|
10
13
|
*
|
|
11
14
|
* @author Dr Hamid MADANI <drmdh@msn.com>
|
|
12
15
|
* @license AGPL-3.0-or-later
|
|
@@ -20,6 +23,7 @@ export function loadPdfSignConfig() {
|
|
|
20
23
|
const engine = getEnv("PDF_SIGN_ENGINE", "auto");
|
|
21
24
|
const box = getEnv("PDF_SIGN_FIELD_BOX", "");
|
|
22
25
|
const page = getEnv("PDF_SIGN_FIELD_PAGE", "");
|
|
26
|
+
const timeout = getEnv("PDF_SIGN_TSA_TIMEOUT", "");
|
|
23
27
|
return {
|
|
24
28
|
p12: getEnv("PDF_SIGN_P12", "") || undefined,
|
|
25
29
|
passphrase: getEnv("PDF_SIGN_PASSPHRASE", "") || undefined,
|
|
@@ -39,6 +43,14 @@ export function loadPdfSignConfig() {
|
|
|
39
43
|
},
|
|
40
44
|
restrict: getEnvBool("PDF_SIGN_RESTRICT", false),
|
|
41
45
|
trustPem: getEnv("PDF_SIGN_TRUST_PEM", "") || undefined,
|
|
46
|
+
tsa: {
|
|
47
|
+
url: getEnv("PDF_SIGN_TSA", "") || undefined,
|
|
48
|
+
apiKey: getEnv("PDF_SIGN_TSA_API_KEY", "") || undefined,
|
|
49
|
+
apiKeyHeader: getEnv("PDF_SIGN_TSA_API_KEY_HEADER", "") || "X-API-KEY",
|
|
50
|
+
user: getEnv("PDF_SIGN_TSA_USER", "") || undefined,
|
|
51
|
+
password: getEnv("PDF_SIGN_TSA_PASSWORD", "") || undefined,
|
|
52
|
+
timeout: timeout && Number(timeout) > 0 ? Number(timeout) : undefined,
|
|
53
|
+
},
|
|
42
54
|
};
|
|
43
55
|
}
|
|
44
56
|
//# sourceMappingURL=config.js.map
|
package/dist/config.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"node-signer.d.ts","sourceRoot":"","sources":["../src/node-signer.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE1D,iEAAiE;AACjE,wBAAsB,QAAQ,CAAC,IAAI,EAAE,WAAW,GAAG,OAAO,CAAC,UAAU,CAAC,
|
|
1
|
+
{"version":3,"file":"node-signer.d.ts","sourceRoot":"","sources":["../src/node-signer.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE1D,iEAAiE;AACjE,wBAAsB,QAAQ,CAAC,IAAI,EAAE,WAAW,GAAG,OAAO,CAAC,UAAU,CAAC,CA6BrE"}
|
package/dist/node-signer.js
CHANGED
|
@@ -15,6 +15,9 @@ export async function signNode(opts) {
|
|
|
15
15
|
if (opts.permissions) {
|
|
16
16
|
throw new Error("Le cœur Node ne gère pas les permissions/PAdES — utilisez engine:'pyhanko' (python/setup-venv.sh).");
|
|
17
17
|
}
|
|
18
|
+
if (opts.timestampUrl) {
|
|
19
|
+
throw new Error("Le cœur Node ne sait pas horodater — un `timestampUrl` exige engine:'pyhanko' (python/setup-venv.sh).");
|
|
20
|
+
}
|
|
18
21
|
if (opts.openPassword) {
|
|
19
22
|
throw new Error("Le cœur Node ne chiffre pas — un mot de passe d'ouverture exige engine:'pyhanko' (python/setup-venv.sh).");
|
|
20
23
|
}
|
package/dist/node-signer.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"node-signer.js","sourceRoot":"","sources":["../src/node-signer.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACtD,OAAO,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAC3C,OAAO,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAChD,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AAGjE,iEAAiE;AACjE,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAC,IAAiB;IAC9C,IAAI,IAAI,CAAC,WAAW,EAAE,CAAC;QACrB,MAAM,IAAI,KAAK,CAAC,oGAAoG,CAAC,CAAC;IACxH,CAAC;IACD,IAAI,IAAI,CAAC,YAAY,EAAE,CAAC;QACtB,MAAM,IAAI,KAAK,CAAC,0GAA0G,CAAC,CAAC;IAC9H,CAAC;IACD,MAAM,SAAS,GAAG,OAAO,IAAI,CAAC,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACtG,MAAM,SAAS,GAAG,OAAO,IAAI,CAAC,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAC,YAAY,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAEhG,MAAM,eAAe,GAAG,mBAAmB,CAAC;QAC1C,SAAS;QACT,MAAM,EAAE,IAAI,CAAC,IAAI,EAAE,MAAM,IAAI,WAAW;QACxC,WAAW,EAAE,IAAI,CAAC,IAAI,EAAE,OAAO,IAAI,EAAE;QACrC,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,IAAI,IAAI,EAAE;QAC3B,QAAQ,EAAE,IAAI,CAAC,IAAI,EAAE,QAAQ,IAAI,EAAE;KACpC,CAAC,CAAC;IAEH,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC,SAAS,EAAE,EAAE,UAAU,EAAE,IAAI,CAAC,UAAU,IAAI,EAAE,EAAE,CAAC,CAAC;IAC/E,MAAM,MAAM,GAAG,MAAM,IAAI,OAAO,EAAE,CAAC,IAAI,CAAC,eAAe,EAAE,MAAM,CAAC,CAAC;IAEjE,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;QAChB,aAAa,CAAC,IAAI,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QACnC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;IACjD,CAAC;IACD,OAAO,EAAE,KAAK,EAAE,IAAI,UAAU,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;AAC3D,CAAC"}
|
|
1
|
+
{"version":3,"file":"node-signer.js","sourceRoot":"","sources":["../src/node-signer.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACtD,OAAO,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAC3C,OAAO,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAChD,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AAGjE,iEAAiE;AACjE,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAC,IAAiB;IAC9C,IAAI,IAAI,CAAC,WAAW,EAAE,CAAC;QACrB,MAAM,IAAI,KAAK,CAAC,oGAAoG,CAAC,CAAC;IACxH,CAAC;IACD,IAAI,IAAI,CAAC,YAAY,EAAE,CAAC;QACtB,MAAM,IAAI,KAAK,CAAC,uGAAuG,CAAC,CAAC;IAC3H,CAAC;IACD,IAAI,IAAI,CAAC,YAAY,EAAE,CAAC;QACtB,MAAM,IAAI,KAAK,CAAC,0GAA0G,CAAC,CAAC;IAC9H,CAAC;IACD,MAAM,SAAS,GAAG,OAAO,IAAI,CAAC,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACtG,MAAM,SAAS,GAAG,OAAO,IAAI,CAAC,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAC,YAAY,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAEhG,MAAM,eAAe,GAAG,mBAAmB,CAAC;QAC1C,SAAS;QACT,MAAM,EAAE,IAAI,CAAC,IAAI,EAAE,MAAM,IAAI,WAAW;QACxC,WAAW,EAAE,IAAI,CAAC,IAAI,EAAE,OAAO,IAAI,EAAE;QACrC,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,IAAI,IAAI,EAAE;QAC3B,QAAQ,EAAE,IAAI,CAAC,IAAI,EAAE,QAAQ,IAAI,EAAE;KACpC,CAAC,CAAC;IAEH,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC,SAAS,EAAE,EAAE,UAAU,EAAE,IAAI,CAAC,UAAU,IAAI,EAAE,EAAE,CAAC,CAAC;IAC/E,MAAM,MAAM,GAAG,MAAM,IAAI,OAAO,EAAE,CAAC,IAAI,CAAC,eAAe,EAAE,MAAM,CAAC,CAAC;IAEjE,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;QAChB,aAAa,CAAC,IAAI,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QACnC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;IACjD,CAAC;IACD,OAAO,EAAE,KAAK,EAAE,IAAI,UAAU,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;AAC3D,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pyhanko-signer.d.ts","sourceRoot":"","sources":["../src/pyhanko-signer.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,WAAW,EAAmB,UAAU,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"pyhanko-signer.d.ts","sourceRoot":"","sources":["../src/pyhanko-signer.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,WAAW,EAAmB,UAAU,EAAE,MAAM,YAAY,CAAC;AAsD3E,gFAAgF;AAChF,wBAAgB,WAAW,CAAC,IAAI,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,GAAG,UAAU,CAuDzE"}
|
package/dist/pyhanko-signer.js
CHANGED
|
@@ -16,6 +16,12 @@ import { SIGN_PY } from "./engines.js";
|
|
|
16
16
|
* Faute de reconnaître, on rend `undefined` et l'appelant garde son message générique.
|
|
17
17
|
*/
|
|
18
18
|
function diagnostiquer(trace) {
|
|
19
|
+
// L'horodatage a sa propre phrase, déjà rédigée côté Python : on la rend telle quelle, sans les
|
|
20
|
+
// six chemins temporaires d'un « Command failed » qui enverraient chercher ailleurs. EN PREMIER :
|
|
21
|
+
// sa trace peut contenir des mots que les motifs suivants prendraient pour les leurs.
|
|
22
|
+
const h = trace.match(/ERREUR -- (horodatage[^\n]*)/);
|
|
23
|
+
if (h)
|
|
24
|
+
return h[1].charAt(0).toUpperCase() + h[1].slice(1);
|
|
19
25
|
if (/Invalid password or PKCS12 data|Could not load key material/.test(trace)) {
|
|
20
26
|
return "Phrase secrète invalide pour ce certificat PKCS#12.";
|
|
21
27
|
}
|
|
@@ -78,6 +84,11 @@ export function signPyhanko(opts, python) {
|
|
|
78
84
|
// Horodatage RFC 3161 — voir `timestampUrl` dans les types : absent, on signe en PAdES-B ;
|
|
79
85
|
// présent, on exige PAdES-T ou l'échec.
|
|
80
86
|
timestamp_url: opts.timestampUrl || undefined,
|
|
87
|
+
// Identifiants de l'horodateur (0.4.0) : par CE fichier, jamais par `argv`. Le dossier
|
|
88
|
+
// temporaire est créé en 0700 par `mkdtempSync`, et effacé dans le `finally`.
|
|
89
|
+
timestamp_headers: opts.timestampHeaders,
|
|
90
|
+
timestamp_auth: opts.timestampAuth,
|
|
91
|
+
timestamp_timeout: opts.timestampTimeout,
|
|
81
92
|
};
|
|
82
93
|
const cfgPath = toFile(dir, "config.json", JSON.stringify(cfg));
|
|
83
94
|
// ⚠️ La sortie d'erreur est CAPTURÉE, pas seulement laissée filer vers le terminal.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pyhanko-signer.js","sourceRoot":"","sources":["../src/pyhanko-signer.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"pyhanko-signer.js","sourceRoot":"","sources":["../src/pyhanko-signer.ts"],"names":[],"mappings":"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"}
|
package/dist/sign.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sign.d.ts","sourceRoot":"","sources":["../src/sign.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"sign.d.ts","sourceRoot":"","sources":["../src/sign.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAO1D,wBAAsB,OAAO,CAAC,IAAI,EAAE,WAAW,GAAG,OAAO,CAAC,UAAU,CAAC,CAqDpE"}
|
package/dist/sign.js
CHANGED
|
@@ -24,9 +24,11 @@ export async function signPdf(opts) {
|
|
|
24
24
|
permissions: opts.permissions ?? (cfg.restrict ? {} : undefined),
|
|
25
25
|
openPassword: opts.openPassword,
|
|
26
26
|
engine: opts.engine ?? cfg.engine,
|
|
27
|
+
...identifiantsHorodateur(opts, cfg),
|
|
27
28
|
};
|
|
28
29
|
if (!merged.p12)
|
|
29
30
|
throw new Error("Certificat PKCS#12 manquant (opts.p12 ou PDF_SIGN_P12).");
|
|
31
|
+
controlerHorodatage(merged);
|
|
30
32
|
const avail = detectEngines(cfg.python);
|
|
31
33
|
const want = merged.engine ?? "auto";
|
|
32
34
|
let engine;
|
|
@@ -41,6 +43,59 @@ export async function signPdf(opts) {
|
|
|
41
43
|
else {
|
|
42
44
|
engine = avail.pyhanko ? "pyhanko" : "node"; // auto : préfère pyhanko
|
|
43
45
|
}
|
|
46
|
+
// ⚠️ Le cœur Node NE SAIT PAS horodater. Jusqu'en 0.3.2, `auto` sur une machine sans pyhanko
|
|
47
|
+
// tombait sur lui et signait SANS horodatage, sans rien dire : le repli silencieux que ce module
|
|
48
|
+
// interdit partout ailleurs. On refuse, et on dit quoi installer.
|
|
49
|
+
if (engine === "node" && merged.timestampUrl) {
|
|
50
|
+
throw new Error("Horodatage demandé, mais le moteur retenu (node) ne sait pas horodater : aucune signature " +
|
|
51
|
+
"n'a été produite. Installer pyhanko (python/setup-venv.sh) ou retirer `timestampUrl`.");
|
|
52
|
+
}
|
|
44
53
|
return engine === "pyhanko" ? signPyhanko(merged, avail.python) : signNode(merged);
|
|
45
54
|
}
|
|
55
|
+
/**
|
|
56
|
+
* Les identifiants d'un horodateur VOYAGENT AVEC SON ADRESSE — même règle que le certificat et sa
|
|
57
|
+
* phrase, plus haut.
|
|
58
|
+
*
|
|
59
|
+
* Ceux du `.env` ne partent que vers l'horodateur du `.env` (`PDF_SIGN_TSA`). Un appelant qui
|
|
60
|
+
* vise une autre adresse fournit les siens, ou n'en envoie aucun : joindre notre clé d'API à une
|
|
61
|
+
* requête vers un tiers, c'est la lui donner.
|
|
62
|
+
*/
|
|
63
|
+
function identifiantsHorodateur(opts, cfg) {
|
|
64
|
+
const t = cfg.tsa;
|
|
65
|
+
const memeHorodateur = !!opts.timestampUrl && opts.timestampUrl === t.url;
|
|
66
|
+
return {
|
|
67
|
+
timestampHeaders: opts.timestampHeaders
|
|
68
|
+
?? (memeHorodateur && t.apiKey ? { [t.apiKeyHeader]: t.apiKey } : undefined),
|
|
69
|
+
timestampAuth: opts.timestampAuth
|
|
70
|
+
?? (memeHorodateur && t.user ? { user: t.user, password: t.password ?? "" } : undefined),
|
|
71
|
+
timestampTimeout: opts.timestampTimeout ?? t.timeout,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
const LOCALES = new Set(["localhost", "127.0.0.1", "::1", "[::1]"]);
|
|
75
|
+
/** Refuse, AVANT toute requête, un horodatage dont la demande est incohérente ou dangereuse. */
|
|
76
|
+
function controlerHorodatage(o) {
|
|
77
|
+
const identifie = !!o.timestampAuth || Object.keys(o.timestampHeaders ?? {}).length > 0;
|
|
78
|
+
if (!o.timestampUrl) {
|
|
79
|
+
if (identifie) {
|
|
80
|
+
throw new Error("Identifiants d'horodatage fournis sans adresse d'horodateur (`timestampUrl`) : " +
|
|
81
|
+
"rien n'a été signé. Une configuration à moitié posée ne doit pas passer pour complète.");
|
|
82
|
+
}
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
let u;
|
|
86
|
+
try {
|
|
87
|
+
u = new URL(o.timestampUrl);
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
throw new Error(`Adresse d'horodateur invalide : ${o.timestampUrl}`);
|
|
91
|
+
}
|
|
92
|
+
// Une clé envoyée en clair est une clé publiée. On tolère la boucle locale (essais, relais).
|
|
93
|
+
if (identifie && u.protocol !== "https:" && !LOCALES.has(u.hostname)) {
|
|
94
|
+
throw new Error(`Horodatage refusé : des identifiants ne partent pas en clair vers ${u.host} — ` +
|
|
95
|
+
"l'adresse doit être en https://.");
|
|
96
|
+
}
|
|
97
|
+
if (o.timestampTimeout !== undefined && !(o.timestampTimeout > 0)) {
|
|
98
|
+
throw new Error(`Délai d'horodatage invalide : ${o.timestampTimeout} (secondes, > 0 attendu).`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
46
101
|
//# sourceMappingURL=sign.js.map
|
package/dist/sign.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sign.js","sourceRoot":"","sources":["../src/sign.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"sign.js","sourceRoot":"","sources":["../src/sign.ts"],"names":[],"mappings":"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"}
|
package/dist/types.d.ts
CHANGED
|
@@ -64,6 +64,25 @@ export interface SignOptions {
|
|
|
64
64
|
* opposable, ou rien. Un repli silencieux livrerait un document que le client croit horodaté.
|
|
65
65
|
*/
|
|
66
66
|
timestampUrl?: string;
|
|
67
|
+
/**
|
|
68
|
+
* En-têtes HTTP joints à la requête d'horodatage — typiquement une clé d'API
|
|
69
|
+
* (`{ "X-API-KEY": "…" }` chez Evidency). *(0.4.0)*
|
|
70
|
+
*
|
|
71
|
+
* Un horodateur **qualifié** est un service payant : il authentifie son client. Sans ce moyen,
|
|
72
|
+
* seuls les horodateurs publics et gratuits — donc non qualifiés — étaient joignables.
|
|
73
|
+
*
|
|
74
|
+
* ⚠️ Transmis au moteur par le fichier de configuration temporaire, **jamais** par la ligne de
|
|
75
|
+
* commande (lisible par toute la machine dans la table des processus). Refusé sur une adresse
|
|
76
|
+
* `http://` non locale : une clé envoyée en clair est une clé publiée.
|
|
77
|
+
*/
|
|
78
|
+
timestampHeaders?: Record<string, string>;
|
|
79
|
+
/** Authentification HTTP Basic auprès de l'horodateur. Mêmes garde-fous que `timestampHeaders`. *(0.4.0)* */
|
|
80
|
+
timestampAuth?: {
|
|
81
|
+
user: string;
|
|
82
|
+
password: string;
|
|
83
|
+
};
|
|
84
|
+
/** Délai d'attente de l'horodateur, en secondes (défaut : 10). Dépassé, la signature ÉCHOUE. *(0.4.0)* */
|
|
85
|
+
timestampTimeout?: number;
|
|
67
86
|
/**
|
|
68
87
|
* Mot de passe exigé à l'OUVERTURE du document (chiffrement AES-256, R=6).
|
|
69
88
|
*
|
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;;;;;;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;CACjB;AAED,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,OAAO,CAAC;IAChB,UAAU,EAAE,aAAa,EAAE,CAAC;IAC5B,MAAM,EAAE,SAAS,GAAG,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB"}
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/dist/verify.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"verify.d.ts","sourceRoot":"","sources":["../src/verify.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAI9D,wBAAgB,SAAS,CAAC,IAAI,EAAE,aAAa,GAAG,YAAY,
|
|
1
|
+
{"version":3,"file":"verify.d.ts","sourceRoot":"","sources":["../src/verify.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAI9D,wBAAgB,SAAS,CAAC,IAAI,EAAE,aAAa,GAAG,YAAY,CAkC3D"}
|
package/dist/verify.js
CHANGED
|
@@ -16,19 +16,30 @@ export function verifyPdf(opts) {
|
|
|
16
16
|
const avail = detectEngines(cfg.python);
|
|
17
17
|
const trust = opts.trustPem ?? cfg.trustPem;
|
|
18
18
|
const want = opts.engine ?? cfg.engine;
|
|
19
|
+
// ⚠️ AUCUN REPLI quand pyhanko EST là. Jusqu'en 0.4.0, un échec de pyhanko — document verrouillé
|
|
20
|
+
// sans son mot de passe, plantage, version de pyhanko qui change son API — retombait EN SILENCE
|
|
21
|
+
// sur la détection Node, qui ne sait dire que « une signature est présente » (intact et valid
|
|
22
|
+
// indéfinis). Un affichage qui lisait « pas false » y voyait « valide ». Une vérification qui
|
|
23
|
+
// n'a pas eu lieu doit se DIRE, pas se déguiser. Le repli Node ne vaut que si pyhanko MANQUE.
|
|
24
|
+
if (want === "pyhanko" && !avail.pyhanko) {
|
|
25
|
+
throw new Error("Vérification complète demandée, mais le moteur pyhanko est indisponible — lancer python/setup-venv.sh (ou PDF_SIGN_PYTHON).");
|
|
26
|
+
}
|
|
19
27
|
if (avail.pyhanko && want !== "node") {
|
|
28
|
+
const args = [SIGN_PY, "verify", opts.input, trust ?? ""];
|
|
29
|
+
if (opts.openPassword)
|
|
30
|
+
args.push(opts.openPassword);
|
|
31
|
+
let out;
|
|
20
32
|
try {
|
|
21
|
-
|
|
22
|
-
if (opts.openPassword)
|
|
23
|
-
args.push(opts.openPassword);
|
|
24
|
-
const out = execFileSync(avail.python, args, { encoding: "utf-8" });
|
|
25
|
-
const line = out.trim().split("\n").pop() ?? "{}";
|
|
26
|
-
const parsed = JSON.parse(line);
|
|
27
|
-
return { signed: !!parsed.signed, signatures: parsed.signatures ?? [], engine: "pyhanko" };
|
|
33
|
+
out = execFileSync(avail.python, args, { encoding: "utf-8", stdio: ["ignore", "pipe", "pipe"] });
|
|
28
34
|
}
|
|
29
|
-
catch {
|
|
30
|
-
|
|
35
|
+
catch (e) {
|
|
36
|
+
const trace = String(e?.stderr ?? "");
|
|
37
|
+
const net = trace.match(/ERREUR -- ([^\n]*)/);
|
|
38
|
+
throw new Error(net ? net[1] : `Vérification impossible (pyhanko) : ${trace.trim().split("\n").pop() || String(e?.message ?? e)}`);
|
|
31
39
|
}
|
|
40
|
+
const line = out.trim().split("\n").pop() ?? "{}";
|
|
41
|
+
const parsed = JSON.parse(line);
|
|
42
|
+
return { signed: !!parsed.signed, signatures: parsed.signatures ?? [], engine: "pyhanko" };
|
|
32
43
|
}
|
|
33
44
|
// Détection Node basique : présence d'un /ByteRange + dictionnaire de signature.
|
|
34
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,IAAI,KAAK,CAAC,OAAO,
|
|
1
|
+
{"version":3,"file":"verify.js","sourceRoot":"","sources":["../src/verify.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
# @mostajs/pdf-sign — installation, exploitation, maintenance
|
|
2
|
+
|
|
3
|
+
**Auteur** : Dr Hamid MADANI <drmdh@msn.com>
|
|
4
|
+
**Version du module** : 0.4.1 · **Rédigé le** : 2026-09-28
|
|
5
|
+
**Pour qui** : la personne qui reprend ce module — ou un produit qui l'emploie (mdseal, qatrax) —
|
|
6
|
+
sans avoir assisté à sa construction. Rien ici ne suppose de savoir ce qui s'est dit avant.
|
|
7
|
+
|
|
8
|
+
> **Si vous n'avez que deux minutes** : ce module signe des PDF. La signature simple se fait en
|
|
9
|
+
> Node ; **tout le reste** — protection, mot de passe, horodatage, vérification complète — passe
|
|
10
|
+
> par un programme **Python** (pyhanko), installé dans un environnement isolé (*venv*). Neuf
|
|
11
|
+
> pannes sur dix viennent de ce venv : absent, effacé, ou d'une version non essayée. Le §6 dit
|
|
12
|
+
> comment le vérifier en une commande, le §8 ce que veut dire chaque message d'erreur.
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## 1. Comment le module fonctionne
|
|
17
|
+
|
|
18
|
+
```
|
|
19
|
+
application (mdseal, qatrax…)
|
|
20
|
+
│ signPdf({ … })
|
|
21
|
+
▼
|
|
22
|
+
@mostajs/pdf-sign (Node, TypeScript)
|
|
23
|
+
│
|
|
24
|
+
├── moteur NODE ─── @signpdf ─────────► signature PKCS#7 simple, invisible
|
|
25
|
+
│ (rien d'autre : ni protection, ni date)
|
|
26
|
+
│
|
|
27
|
+
└── moteur PYHANKO
|
|
28
|
+
│ 1. écrit config.json dans un dossier temporaire (droits 0700)
|
|
29
|
+
│ 2. lance : <python du venv> python/sign_with_perms.py sign … config.json
|
|
30
|
+
▼
|
|
31
|
+
pikepdf ── chiffrement AES-256, permissions (ni impression, ni copie)
|
|
32
|
+
pyhanko ── signature PAdES visible
|
|
33
|
+
└─ horodatage RFC 3161 ──► HTTPS ──► horodateur (DigiCert, Evidency…)
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
**Ce qui exige le moteur pyhanko** — sans lui, ces demandes sont **refusées**, jamais ignorées :
|
|
37
|
+
|
|
38
|
+
| demande | option | pourquoi Node ne suffit pas |
|
|
39
|
+
|---|---|---|
|
|
40
|
+
| permissions (ni impression, ni copie) | `permissions` | chiffrement du PDF : pikepdf |
|
|
41
|
+
| mot de passe à l'ouverture | `openPassword` | idem |
|
|
42
|
+
| horodatage (date attestée par un tiers) | `timestampUrl` | client RFC 3161 : pyhanko |
|
|
43
|
+
| cachet visible avec image | `field.stampImage` | apparence PAdES : pyhanko |
|
|
44
|
+
| vérification complète (chaîne, intégrité) | `verifyPdf` | Node ne sait que détecter une signature |
|
|
45
|
+
|
|
46
|
+
`engine: "auto"` (défaut) prend pyhanko s'il est disponible, Node sinon. **C'est la raison pour
|
|
47
|
+
laquelle un venv absent ne se voit pas toujours** : une signature simple continue de marcher.
|
|
48
|
+
|
|
49
|
+
## 2. Exigences techniques
|
|
50
|
+
|
|
51
|
+
| élément | exigé | essayé | à savoir |
|
|
52
|
+
|---|---|---|---|
|
|
53
|
+
| système | Linux x86_64 ou aarch64 | Ubuntu 23.10 | pikepdf est livré compilé (*wheel*) pour ces deux architectures ; ailleurs, il faut compiler qpdf |
|
|
54
|
+
| **Node.js** | ≥ 18 | 20 et plus | `fetch` et `AbortSignal.timeout` sont employés par les essais |
|
|
55
|
+
| **Python** | **≥ 3.10** | 3.11.6 | exigé par pyhanko **et** pikepdf. Plus ancien : échec au milieu de `pip install`, sans nommer la cause (le script d'installation le vérifie avant) |
|
|
56
|
+
| `python3-venv` | oui (Debian/Ubuntu) | 3.11.4 | livré à part sur Debian/Ubuntu. Absent : `python3 -m venv` échoue sur « ensurepip » → `sudo apt install python3-venv` |
|
|
57
|
+
| `pip` | fourni par le venv | — | mis à jour par le script |
|
|
58
|
+
| accès réseau sortant | HTTPS 443 vers l'horodateur | — | **seulement** si l'horodatage est employé. DigiCert écoute en HTTP 80 |
|
|
59
|
+
| `openssl` | non (essais seulement) | 3.0.10 | sert aux essais à fabriquer un certificat jetable |
|
|
60
|
+
| `qpdf` | **non** | — | embarqué dans pikepdf |
|
|
61
|
+
| certificat de signature | fichier PKCS#12 (`.p12`) + sa phrase secrète | — | voir §9 |
|
|
62
|
+
|
|
63
|
+
## 3. Les bibliothèques Python, une par une
|
|
64
|
+
|
|
65
|
+
Versions : celles du **verrou** `python/requirements.lock.txt`, vérifiées par la suite d'essais.
|
|
66
|
+
|
|
67
|
+
| bibliothèque | version | licence | rôle ici | si elle manque ou change |
|
|
68
|
+
|---|---|---|---|---|
|
|
69
|
+
| **pyHanko** | 0.37.0 | MIT | signature PAdES, horodatage RFC 3161, vérification | le moteur pyhanko est déclaré indisponible |
|
|
70
|
+
| pyhanko-certvalidator | 0.32.1 | MIT | valide la chaîne de certificats (vérification) | tiré par pyHanko |
|
|
71
|
+
| **pikepdf** | 10.15.0 | MPL-2.0 | chiffre le PDF, pose les permissions ; contient qpdf | idem |
|
|
72
|
+
| cryptography | 50.0.1 | Apache-2.0 / BSD | ouvre le `.p12`, calcule la signature | « Phrase secrète invalide » si la phrase est fausse |
|
|
73
|
+
| asn1crypto | 1.5.1 | MIT | structures CMS et TSP (le jeton d'horodatage) | tiré par pyHanko |
|
|
74
|
+
| **aiohttp** | 3.14.3 | Apache-2.0 | **client HTTP de l'horodatage** depuis pyHanko 0.37 | voir le piège du §8, « pyhanko 0.37 » |
|
|
75
|
+
| certifi | 2026.7.22 | MPL-2.0 | autorités racines pour joindre un horodateur **en HTTPS** | un HTTPS vers l'horodateur échoue en « Error while contacting » |
|
|
76
|
+
| Pillow | 12.3.0 | MIT-CMU | image de fond du cachet visible | tiré par pikepdf |
|
|
77
|
+
| lxml | 6.1.3 | BSD | lecture XML (structures PDF annexes) | tiré par pyHanko |
|
|
78
|
+
| tzlocal | 5.4.4 | MIT | fuseau local de l'heure affichée dans le cachet | produit l'avertissement bénin « /etc/timezone is deprecated » |
|
|
79
|
+
| oscrypto, uritools, yarl, multidict, frozenlist, attrs, … | cf. verrou | MIT / Apache | dépendances de second rang | ne s'installent jamais seules |
|
|
80
|
+
|
|
81
|
+
**Côté Node**, pour mémoire : `@signpdf/*` (moteur Node), `pdf-lib`, `@mostajs/config` (lecture du
|
|
82
|
+
`.env`).
|
|
83
|
+
|
|
84
|
+
### Pourquoi deux fichiers de dépendances
|
|
85
|
+
|
|
86
|
+
| fichier | contenu | qui l'emploie |
|
|
87
|
+
|---|---|---|
|
|
88
|
+
| `python/requirements.lock.txt` | **l'ensemble exact** essayé (24 paquets, `==`) | `setup-venv.sh`, **par défaut** — donc la production |
|
|
89
|
+
| `python/requirements.txt` | la **plage admise** : `pyhanko>=0.35.1,<0.38`, `pikepdf>=10.9,<11` | `setup-venv.sh --plage`, pour une montée de version (§7) |
|
|
90
|
+
|
|
91
|
+
Sans verrou, deux installations à un mois d'écart n'ont pas les mêmes versions. **C'est arrivé** :
|
|
92
|
+
le poste de développement portait pyhanko 0.35.1 quand la dernière version publiée était 0.37.0,
|
|
93
|
+
qui avait changé de client HTTP (§8).
|
|
94
|
+
|
|
95
|
+
## 4. Installer
|
|
96
|
+
|
|
97
|
+
### 4.1 Poste de développement
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
cd mosta-pdf-sign
|
|
101
|
+
npm install
|
|
102
|
+
bash python/setup-venv.sh # crée python/.venv depuis le VERROU
|
|
103
|
+
npx mosta-pdf-sign engines # doit afficher "pyhanko": true
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Le module trouve seul `python/.venv` : aucune variable à poser.
|
|
107
|
+
|
|
108
|
+
### 4.2 Production — ⚠️ le venv HORS de `node_modules/`
|
|
109
|
+
|
|
110
|
+
Dans une application, le module vit sous `node_modules/@mostajs/pdf-sign/`. **Un venv créé là est
|
|
111
|
+
effacé par le `npm install` du déploiement suivant** — et rien ne le signale : les signatures
|
|
112
|
+
simples continuent, seules les demandes de protection ou d'horodatage se mettent à échouer, à la
|
|
113
|
+
première demande d'un client.
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
# depuis l'application, après npm install :
|
|
117
|
+
bash node_modules/@mostajs/pdf-sign/python/setup-venv.sh /home/hmd/prod/.venv-pdfsign
|
|
118
|
+
# puis, dans le .env de l'application :
|
|
119
|
+
PDF_SIGN_PYTHON=/home/hmd/prod/.venv-pdfsign/bin/python
|
|
120
|
+
# et redémarrer en relisant l'environnement :
|
|
121
|
+
pm2 restart <application> --update-env
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
**mdseal** a son propre script, `scripts/setup-pyhanko.sh`, qui fait la même chose et pose le venv
|
|
125
|
+
dans `/home/hmd/prod/.venv-pdfsign` (voir le README de mdseal). Un seul venv peut servir plusieurs
|
|
126
|
+
applications du même serveur : elles emploient la même version du module.
|
|
127
|
+
|
|
128
|
+
## 5. Configurer
|
|
129
|
+
|
|
130
|
+
Tout se règle par le `.env` (lu par `@mostajs/config`, cascade `MOSTA_ENV`). Rien en dur.
|
|
131
|
+
|
|
132
|
+
| clé | rôle | défaut |
|
|
133
|
+
|---|---|---|
|
|
134
|
+
| `PDF_SIGN_PYTHON` | chemin **absolu** du python du venv | voir ci-dessous |
|
|
135
|
+
| `PDF_SIGN_ENGINE` | `auto` · `node` · `pyhanko` | `auto` |
|
|
136
|
+
| `PDF_SIGN_P12` | certificat de signature | — (obligatoire) |
|
|
137
|
+
| `PDF_SIGN_PASSPHRASE_FILE` | fichier de la phrase secrète — **préférer à** `PDF_SIGN_PASSPHRASE` | — |
|
|
138
|
+
| `PDF_SIGN_REASON` / `_LOCATION` / `_NAME` / `_CONTACT` | métadonnées du cachet | — |
|
|
139
|
+
| `PDF_SIGN_FIELD_PAGE` / `_FIELD_BOX` / `_STAMP_IMAGE` | position et image du cachet | page 0, bas-droite |
|
|
140
|
+
| `PDF_SIGN_RESTRICT` | `true` → permissions restrictives | `false` |
|
|
141
|
+
| `PDF_SIGN_TRUST_PEM` | certificat de confiance pour la vérification | — |
|
|
142
|
+
| `PDF_SIGN_TSA` | adresse de l'horodateur — **apparie** les identifiants suivants | — |
|
|
143
|
+
| `PDF_SIGN_TSA_API_KEY` / `_API_KEY_HEADER` | clé d'API de l'horodateur, nom de l'en-tête | — / `X-API-KEY` |
|
|
144
|
+
| `PDF_SIGN_TSA_USER` / `_PASSWORD` | HTTP Basic auprès de l'horodateur | — |
|
|
145
|
+
| `PDF_SIGN_TSA_TIMEOUT` | délai d'attente, secondes | 10 |
|
|
146
|
+
|
|
147
|
+
⚠️ **Le choix du python, et son piège.** L'ordre est : `PDF_SIGN_PYTHON` **s'il existe** → sinon
|
|
148
|
+
`python/.venv` du module → sinon `python3` du système. Un `PDF_SIGN_PYTHON` **mal orthographié**
|
|
149
|
+
ne provoque donc **aucune erreur** : le module retombe sur le suivant. C'est pourquoi le §6
|
|
150
|
+
demande de lire le champ `python` de `engines`, pas seulement `pyhanko: true`.
|
|
151
|
+
|
|
152
|
+
⚠️ **Les identifiants de l'horodateur ne partent que vers `PDF_SIGN_TSA`.** Si l'application
|
|
153
|
+
passe une autre adresse, la clé du `.env` n'est pas jointe — c'est voulu (la joindre, ce serait
|
|
154
|
+
la donner à un tiers). Symptôme : « identifiants refusés (HTTP 401) » alors que la clé est bonne.
|
|
155
|
+
|
|
156
|
+
## 6. Vérifier que tout marche
|
|
157
|
+
|
|
158
|
+
```bash
|
|
159
|
+
# 1. le moteur, et SURTOUT le chemin du python réellement employé
|
|
160
|
+
PDF_SIGN_PYTHON=/home/hmd/prod/.venv-pdfsign/bin/python npx mosta-pdf-sign engines
|
|
161
|
+
# → { "node": true, "pyhanko": true, "python": "/home/hmd/prod/.venv-pdfsign/bin/python" }
|
|
162
|
+
|
|
163
|
+
# 2. les versions installées
|
|
164
|
+
/home/hmd/prod/.venv-pdfsign/bin/pip freeze | grep -iE 'pyhanko|pikepdf|aiohttp'
|
|
165
|
+
|
|
166
|
+
# 3. la suite d'essais complète CONTRE CE venv (depuis le dépôt du module)
|
|
167
|
+
PDF_SIGN_PYTHON=/home/hmd/prod/.venv-pdfsign/bin/python bash test-scripts/run-tests.sh
|
|
168
|
+
# → ✓ ALL TEST SUITES PASSED, et AUCUNE ligne « ⊘ SAUTÉ »
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
Une ligne `⊘ SAUTÉ` n'est **pas** un succès : elle dit qu'un essai n'a pas pu s'exécuter (pyhanko,
|
|
172
|
+
openssl ou réseau absent) et en donne la raison. Les essais d'horodatage joignent DigiCert : sans
|
|
173
|
+
réseau, trois d'entre eux sautent.
|
|
174
|
+
|
|
175
|
+
## 7. Monter de version (pyhanko, pikepdf)
|
|
176
|
+
|
|
177
|
+
**Jamais directement en production.** Une version non essayée n'y entre pas.
|
|
178
|
+
|
|
179
|
+
```bash
|
|
180
|
+
S=$(mktemp -d)
|
|
181
|
+
bash python/setup-venv.sh "$S/venv" --plage # dernières versions DANS la plage
|
|
182
|
+
PDF_SIGN_PYTHON="$S/venv/bin/python" bash test-scripts/run-tests.sh
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
- Tout passe, aucun saut → régénérer le verrou (`"$S/venv/bin/pip" freeze`, en gardant l'en-tête
|
|
186
|
+
de `requirements.lock.txt`), noter la date et les versions dans l'en-tête et le CHANGELOG,
|
|
187
|
+
publier une version du module, puis réinstaller le venv de production depuis le verrou.
|
|
188
|
+
- Une version **hors** de la plage (pyhanko 0.38…) → élargir la borne dans `requirements.txt`
|
|
189
|
+
**seulement après** que la suite passe dessus, et relire son journal des changements : 0.37 a
|
|
190
|
+
changé de client HTTP sans le dire dans son API.
|
|
191
|
+
|
|
192
|
+
## 8. Pannes connues — ce que veut dire chaque message
|
|
193
|
+
|
|
194
|
+
| message | cause | que faire |
|
|
195
|
+
|---|---|---|
|
|
196
|
+
| `Moteur pyhanko indisponible — lancer python/setup-venv.sh` | `engine:"pyhanko"` demandé, venv absent ou cassé | §6 étape 1 ; réinstaller (§4) |
|
|
197
|
+
| `Horodatage demandé, mais le moteur retenu (node) ne sait pas horodater` | pas de pyhanko, et un `timestampUrl` | installer le venv. (Avant 0.4.0, le document sortait **sans date, sans erreur**.) |
|
|
198
|
+
| `Le cœur Node ne gère pas les permissions/PAdES` | protection demandée sans pyhanko | idem |
|
|
199
|
+
| protections refusées **juste après un déploiement** | venv créé dans `node_modules/`, effacé par `npm install` | §4.2 : venv hors du dépôt |
|
|
200
|
+
| `Phrase secrète invalide pour ce certificat PKCS#12` | mauvaise phrase, ou phrase d'un autre certificat | vérifier le couple `PDF_SIGN_P12` / `PDF_SIGN_PASSPHRASE_FILE` |
|
|
201
|
+
| `Certificat PKCS#12 illisible` | le fichier n'est pas un `.p12` | le régénérer ou le recopier en binaire |
|
|
202
|
+
| `Horodatage impossible (…) : identifiants refusés par l'horodateur (HTTP 401)` | clé absente, fausse, expirée — **ou** adresse différente de `PDF_SIGN_TSA` (§5) | vérifier la clé et l'égalité exacte des adresses |
|
|
203
|
+
| `… débit de l'horodateur dépassé (HTTP 429)` | trop d'appels par seconde (Evidency : 5/s) | réessayer ; étaler les signatures |
|
|
204
|
+
| `… Error while contacting timestamp service` | réseau, DNS, pare-feu, délai dépassé | `curl -I <adresse>` depuis le serveur ; `PDF_SIGN_TSA_TIMEOUT` |
|
|
205
|
+
| `Horodatage refusé : des identifiants ne partent pas en clair` | adresse `http://` avec une clé | passer l'adresse en `https://` |
|
|
206
|
+
| `document verrouillé : son mot de passe d'ouverture est requis pour vérifier` | `verifyPdf` sur un document à mot de passe, sans `openPassword` (ou avec un faux) | fournir le mot de passe d'ouverture |
|
|
207
|
+
| `Vérification impossible (pyhanko) : …` | pyhanko n'a pas pu lire le document | le fichier n'est pas un PDF signé lisible ; **avant 0.4.1, ce cas répondait « signé » par la détection Node** |
|
|
208
|
+
| `modify_form ne peut pas etre refuse` | `permissions.modifyForm:false` | impossible en PDF signé : retirer l'option |
|
|
209
|
+
| `/etc/timezone is deprecated …` | avertissement de tzlocal | **bénin**, aucune action |
|
|
210
|
+
| `AttributeError: … __version__` | un script lit `pyhanko.__version__`, qui n'existe pas | lire `importlib.metadata.version("pyhanko")` |
|
|
211
|
+
| `✗ Python >= 3.10 requis` / `ensurepip` | Python trop ancien / `python3-venv` absent | installer Python ≥ 3.10 / `sudo apt install python3-venv` |
|
|
212
|
+
|
|
213
|
+
### Le piège « pyhanko 0.37 », en détail
|
|
214
|
+
|
|
215
|
+
pyhanko 0.37 a remplacé son client HTTP (`requests` → `aiohttp`). Son API n'a pas changé — les
|
|
216
|
+
signatures et l'horodatage marchent — mais **le code d'erreur HTTP a changé de place** dans
|
|
217
|
+
l'exception. Le module 0.4.0 lisait l'ancien emplacement : sous 0.37, un refus de clé redevenait
|
|
218
|
+
« Error while contacting timestamp service », qui envoie chercher un problème réseau. Corrigé en
|
|
219
|
+
0.4.1 (les deux emplacements sont lus) et gardé par l'essai T-TSA-4, qui passe sous 0.35.1 **et**
|
|
220
|
+
0.37.0. C'est l'exemple même de ce que le §7 prévient.
|
|
221
|
+
|
|
222
|
+
### Lire un résultat de vérification
|
|
223
|
+
|
|
224
|
+
| champ | sens | à retenir |
|
|
225
|
+
|---|---|---|
|
|
226
|
+
| `intact` | le document n'a pas changé depuis la signature | **false = document altéré** |
|
|
227
|
+
| `valid` | la signature est mathématiquement correcte | reste **true** sur un document altéré : ne jamais le lire seul |
|
|
228
|
+
| `trusted` | le certificat est reconnu (via `trustPem`) | false pour un certificat auto-signé non fourni |
|
|
229
|
+
| `engine` | `pyhanko` = vérifié ; `node` = **seulement détecté**, rien n'a été validé | un résultat `node` ne dit jamais « valide » |
|
|
230
|
+
|
|
231
|
+
## 9. Secrets
|
|
232
|
+
|
|
233
|
+
| secret | où | comment il circule |
|
|
234
|
+
|---|---|---|
|
|
235
|
+
| certificat `.p12` | hors dépôt (`/home/hmd/prod/secrets/`, `Entreprise/.secrets/`), droits 600 | chemin dans `PDF_SIGN_P12` |
|
|
236
|
+
| sa phrase secrète | fichier à part, droits 600 | `PDF_SIGN_PASSPHRASE_FILE` → lue par Python depuis le fichier |
|
|
237
|
+
| clé d'API / mot de passe de l'horodateur | `.env` de l'application, jamais commité | Node → `config.json` temporaire (dossier 0700, effacé après usage) → Python |
|
|
238
|
+
|
|
239
|
+
**Aucun secret ne passe par la ligne de commande** : elle se lit par n'importe quel utilisateur
|
|
240
|
+
de la machine (`ps aux`). Le CLI `mosta-pdf-sign` n'accepte d'ailleurs les identifiants de
|
|
241
|
+
l'horodateur que par le `.env`. Python n'imprime jamais ni la clé ni les en-têtes — seulement
|
|
242
|
+
« (authentifie) ».
|
|
243
|
+
|
|
244
|
+
## 10. État de la production — À COMPLÉTER
|
|
245
|
+
|
|
246
|
+
La lecture du serveur n'a pas pu être faite en rédigeant ce document. À lancer sur `amia`, et à
|
|
247
|
+
reporter ici :
|
|
248
|
+
|
|
249
|
+
```bash
|
|
250
|
+
ssh amia '/home/hmd/prod/.venv-pdfsign/bin/python --version;
|
|
251
|
+
/home/hmd/prod/.venv-pdfsign/bin/pip freeze | grep -iE "pyhanko|pikepdf|aiohttp|requests";
|
|
252
|
+
python3 --version; grep -E "^(PRETTY_NAME)" /etc/os-release'
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
| élément | valeur en production | relevé le |
|
|
256
|
+
|---|---|---|
|
|
257
|
+
| Python du venv | *à relever* | |
|
|
258
|
+
| pyhanko / pikepdf | *à relever* | |
|
|
259
|
+
| système | *à relever* | |
|
|
260
|
+
|
|
261
|
+
Si les versions diffèrent du verrou : réinstaller depuis le verrou (§4.2), puis §6.
|
|
262
|
+
|
|
263
|
+
## 11. Chronologie des leçons
|
|
264
|
+
|
|
265
|
+
| date | ce qui s'est passé | ce qui a changé |
|
|
266
|
+
|---|---|---|
|
|
267
|
+
| avant 0.4 | risque identifié dans mdseal : un venv sous `node_modules/` serait effacé par le déploiement | venv hors du dépôt, §4.2 (`mdseal/scripts/setup-pyhanko.sh`) |
|
|
268
|
+
| 26/09/2026 | des essais affichaient `✓` sans rien exécuter | `saute()` : un saut se voit |
|
|
269
|
+
| 28/09/2026 | Node signait sans date quand on lui demandait une date | refus explicite (0.4.0) |
|
|
270
|
+
| 28/09/2026 | pyhanko 0.37 : un 401 redevenait une « erreur de contact » | double lecture du code (0.4.1), verrou des versions |
|
|
271
|
+
| 29/09/2026 | la vérification pyhanko n'était gardée par aucun essai ; son échec retombait sur « signé » | erreur explicite, 7 essais T-VER (0.4.1) |
|
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.4.1 · 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
|
|
@@ -29,6 +29,9 @@ La signature PAdES + permissions AES-256 garantit intégrité, authenticité et
|
|
|
29
29
|
· opts.openPassword : mot de passe exigé à l'OUVERTURE (0.2.0). Seul verrou DUR : sans lui les
|
|
30
30
|
permissions ne sont que déclaratives. Exige pyhanko. Seul (sans permissions), ne restreint
|
|
31
31
|
rien d'autre que l'ouverture.
|
|
32
|
+
· opts.timestampUrl : horodatage RFC 3161 → PAdES-T (0.3.0). Horodateur muet ⇒ la signature ÉCHOUE.
|
|
33
|
+
· opts.timestampHeaders / opts.timestampAuth / opts.timestampTimeout (0.4.0) : joindre un
|
|
34
|
+
horodateur QUALIFIÉ, qui authentifie (clé d'API en en-tête, ou HTTP Basic). Exige pyhanko.
|
|
32
35
|
- verifyPdf(opts): VerifyResult — validation pyhanko complète si dispo, sinon détection Node (/ByteRange)
|
|
33
36
|
- detectEngines(python?): EngineAvailability — { node:true, pyhanko:bool, python }
|
|
34
37
|
- resolvePython / pyhankoAvailable
|
|
@@ -41,15 +44,20 @@ PDF_SIGN_ENGINE (auto|node|pyhanko) · PDF_SIGN_PYTHON (bin du venv)
|
|
|
41
44
|
PDF_SIGN_REASON / _LOCATION / _NAME / _CONTACT (métadonnées du cachet)
|
|
42
45
|
PDF_SIGN_FIELD_PAGE / PDF_SIGN_FIELD_BOX ("x1,y1,x2,y2") · PDF_SIGN_STAMP_IMAGE (logo du cachet visible, pyhanko) · PDF_SIGN_RESTRICT (bool → permissions)
|
|
43
46
|
PDF_SIGN_TRUST_PEM (certificat de confiance pour la vérification)
|
|
47
|
+
PDF_SIGN_TSA (adresse de l'horodateur — APPARIE les identifiants) · PDF_SIGN_TSA_API_KEY ·
|
|
48
|
+
PDF_SIGN_TSA_API_KEY_HEADER (défaut X-API-KEY) · PDF_SIGN_TSA_USER / _PASSWORD · PDF_SIGN_TSA_TIMEOUT (s, défaut 10)
|
|
44
49
|
|
|
45
50
|
## CLI
|
|
46
|
-
mosta-pdf-sign sign <in.pdf> [out.pdf] --p12 c.p12 --pass s [--engine …] [--restrict] [--open-password s] [--reason …]
|
|
51
|
+
mosta-pdf-sign sign <in.pdf> [out.pdf] --p12 c.p12 --pass s [--engine …] [--restrict] [--open-password s] [--tsa url] [--tsa-timeout s] [--reason …]
|
|
52
|
+
(identifiants d'horodateur : .env UNIQUEMENT, jamais en argument)
|
|
47
53
|
mosta-pdf-sign verify <pdf> [--trust cert.pem] [--password s] [--engine …]
|
|
48
54
|
mosta-pdf-sign engines
|
|
49
55
|
|
|
50
56
|
## MOTEUR pyhanko (optionnel)
|
|
51
57
|
bash python/setup-venv.sh → .venv local → exporter PDF_SIGN_PYTHON=…/.venv/bin/python
|
|
52
|
-
|
|
58
|
+
python/requirements.lock.txt = ensemble EXACT essayé (installé par défaut) ; requirements.txt = plage
|
|
59
|
+
admise (pyhanko>=0.35.1,<0.38 ; pikepdf>=10.9,<11). Python >= 3.10. En PRODUCTION : venv HORS de
|
|
60
|
+
node_modules/ (setup-venv.sh <répertoire>). Doc : docs/INSTALLATION-ET-EXPLOITATION.md.
|
|
53
61
|
|
|
54
62
|
## PIÈGES
|
|
55
63
|
- Le cœur Node lève si `permissions` OU `openPassword` est demandé : le chiffrement EXIGE pyhanko.
|
|
@@ -61,3 +69,12 @@ requiert pyhanko>=0.25, pikepdf>=8.
|
|
|
61
69
|
- box en points PDF, origine bas-gauche (A4 = 595×842) ; défaut cachet bas-droite [320,40,575,140].
|
|
62
70
|
- field.stampImage / PDF_SIGN_STAMP_IMAGE : cachet visible « image de fond + texte » (pyhanko stamp.TextStampStyle, opacité 0.35). Ignoré par le cœur Node.
|
|
63
71
|
- La passphrase peut passer par fichier (PASSPHRASE_FILE) pour ne jamais transiter en argv (pyhanko).
|
|
72
|
+
- timestampUrl + moteur node ⇒ ERREUR (0.4.0). Avant : signature SANS horodatage, silencieuse.
|
|
73
|
+
- Identifiants d'horodateur vers http:// non local ⇒ ERREUR (boucle locale tolérée, pour les essais).
|
|
74
|
+
- Identifiants sans timestampUrl ⇒ ERREUR.
|
|
75
|
+
- Les identifiants du .env ne partent QUE si timestampUrl === PDF_SIGN_TSA. Autre adresse ⇒ aucun
|
|
76
|
+
identifiant du .env n'est joint (l'appelant fournit les siens).
|
|
77
|
+
- HTTP 401/403 de l'horodateur ⇒ « identifiants refusés (HTTP 401) », 429 ⇒ « débit dépassé ».
|
|
78
|
+
- verifyPdf (0.4.1) : pyhanko présent ⇒ son échec est une ERREUR (plus de repli Node muet) ;
|
|
79
|
+
engine:'pyhanko' sans pyhanko ⇒ ERREUR. engine 'node' dans le résultat = DÉTECTÉ, pas validé.
|
|
80
|
+
- intact=false ⇒ document altéré ; valid reste true dans ce cas : lire intact ET valid.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mostajs/pdf-sign",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.1",
|
|
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",
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
"python/*.sh",
|
|
25
25
|
"examples/*.mjs",
|
|
26
26
|
"README.md",
|
|
27
|
+
"docs/INSTALLATION-ET-EXPLOITATION.md",
|
|
27
28
|
"llms.txt",
|
|
28
29
|
"CHANGELOG.md"
|
|
29
30
|
],
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# @mostajs/pdf-sign — VERROU : l'ensemble EXACT des paquets Python vérifié par la suite d'essais.
|
|
2
|
+
# Auteur : Dr Hamid MADANI <drmdh@msn.com> · AGPL-3.0-or-later
|
|
3
|
+
#
|
|
4
|
+
# Vérifié le 2026-09-28 : suite complète (23 essais, 0 sauté) sous Python 3.11.6, Ubuntu 23.10.
|
|
5
|
+
# C'est CE fichier que `setup-venv.sh` installe par défaut. `requirements.txt` ne donne que la
|
|
6
|
+
# plage admise ; ce fichier dit ce qui a été ESSAYÉ.
|
|
7
|
+
#
|
|
8
|
+
# Le régénérer = une montée de version : voir docs/INSTALLATION-ET-EXPLOITATION.md §7.
|
|
9
|
+
aiohappyeyeballs==2.7.1
|
|
10
|
+
aiohttp==3.14.3
|
|
11
|
+
aiosignal==1.4.0
|
|
12
|
+
asn1crypto==1.5.1
|
|
13
|
+
attrs==26.1.0
|
|
14
|
+
certifi==2026.7.22
|
|
15
|
+
cffi==2.1.1
|
|
16
|
+
cryptography==50.0.1
|
|
17
|
+
frozenlist==1.8.0
|
|
18
|
+
idna==3.20
|
|
19
|
+
lxml==6.1.3
|
|
20
|
+
multidict==6.9.1
|
|
21
|
+
oscrypto==1.3.0
|
|
22
|
+
packaging==26.3
|
|
23
|
+
pikepdf==10.15.0
|
|
24
|
+
pillow==12.3.0
|
|
25
|
+
propcache==0.5.4
|
|
26
|
+
pycparser==3.0
|
|
27
|
+
pyHanko==0.37.0
|
|
28
|
+
pyhanko-certvalidator==0.32.1
|
|
29
|
+
typing_extensions==4.16.0
|
|
30
|
+
tzlocal==5.4.4
|
|
31
|
+
uritools==6.1.3
|
|
32
|
+
yarl==1.25.1
|
package/python/requirements.txt
CHANGED
|
@@ -1,3 +1,14 @@
|
|
|
1
|
-
# @mostajs/pdf-sign — dépendances du moteur pyhanko (PAdES + permissions).
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
# @mostajs/pdf-sign — dépendances du moteur pyhanko (PAdES + permissions + horodatage).
|
|
2
|
+
# Auteur : Dr Hamid MADANI <drmdh@msn.com> · AGPL-3.0-or-later
|
|
3
|
+
#
|
|
4
|
+
# PLAGE ADMISE, pas versions exactes — l'ensemble exact vérifié est dans requirements.lock.txt,
|
|
5
|
+
# et c'est lui que setup-venv.sh installe.
|
|
6
|
+
#
|
|
7
|
+
# Pourquoi des bornes HAUTES : pyhanko 0.37 a changé de client HTTP (requests → aiohttp) sans
|
|
8
|
+
# changer son API publique ; seul l'endroit où se lit le code d'erreur HTTP a bougé, et le
|
|
9
|
+
# diagnostic « identifiants refusés » s'est tu. Une version non essayée n'entre pas en production.
|
|
10
|
+
# Élargir une borne = passer la suite d'essais sur la nouvelle version (docs/…EXPLOITATION.md §7).
|
|
11
|
+
#
|
|
12
|
+
# Pourquoi 0.35 au minimum : versions essayées 0.35.1 et 0.37.0. Python >= 3.10 exigé par les deux.
|
|
13
|
+
pyhanko>=0.35.1,<0.38
|
|
14
|
+
pikepdf>=10.9,<11
|
package/python/setup-venv.sh
CHANGED
|
@@ -1,9 +1,40 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
|
-
# @mostajs/pdf-sign — crée le venv Python du moteur pyhanko (
|
|
2
|
+
# @mostajs/pdf-sign — crée le venv Python du moteur pyhanko (PAdES + permissions + horodatage).
|
|
3
3
|
# @author Dr Hamid MADANI <drmdh@msn.com> · AGPL-3.0-or-later
|
|
4
|
+
#
|
|
5
|
+
# Usage : bash python/setup-venv.sh [répertoire-du-venv] [--plage]
|
|
6
|
+
# répertoire défaut : python/.venv à côté de ce script
|
|
7
|
+
# ⚠️ EN PRODUCTION, PASSEZ UN RÉPERTOIRE HORS DE node_modules/ : un `npm install`
|
|
8
|
+
# efface node_modules/, donc le venv, sans qu'aucune erreur ne le signale.
|
|
9
|
+
# --plage installe requirements.txt (dernières versions de la plage admise) au lieu du
|
|
10
|
+
# verrou requirements.lock.txt. Réservé à une MONTÉE DE VERSION, suivie des essais.
|
|
11
|
+
#
|
|
12
|
+
# Documentation complète : docs/INSTALLATION-ET-EXPLOITATION.md
|
|
4
13
|
set -euo pipefail
|
|
5
14
|
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
6
|
-
|
|
7
|
-
"$HERE
|
|
8
|
-
|
|
9
|
-
|
|
15
|
+
VENV="$HERE/.venv"
|
|
16
|
+
FICHIER="$HERE/requirements.lock.txt"
|
|
17
|
+
for a in "$@"; do
|
|
18
|
+
case "$a" in
|
|
19
|
+
--plage) FICHIER="$HERE/requirements.txt" ;;
|
|
20
|
+
*) VENV="$a" ;;
|
|
21
|
+
esac
|
|
22
|
+
done
|
|
23
|
+
|
|
24
|
+
# Python >= 3.10 : exigé par pyhanko ET pikepdf. Un Python plus ancien échouerait au milieu de
|
|
25
|
+
# `pip install` avec un message qui ne nomme pas la cause.
|
|
26
|
+
python3 -c 'import sys; sys.exit(0 if sys.version_info >= (3, 10) else 1)' || {
|
|
27
|
+
echo "✗ Python >= 3.10 requis — trouvé : $(python3 --version 2>&1)" >&2; exit 1; }
|
|
28
|
+
# Debian/Ubuntu livrent `venv` à part : sans lui, `python3 -m venv` échoue sur « ensurepip ».
|
|
29
|
+
python3 -c 'import ensurepip' 2>/dev/null || {
|
|
30
|
+
echo "✗ module venv/ensurepip absent — sudo apt install python3-venv" >&2; exit 1; }
|
|
31
|
+
|
|
32
|
+
echo "· venv : $VENV"
|
|
33
|
+
echo "· paquets : $(basename "$FICHIER")"
|
|
34
|
+
python3 -m venv "$VENV"
|
|
35
|
+
"$VENV/bin/pip" install --upgrade pip >/dev/null
|
|
36
|
+
"$VENV/bin/pip" install -r "$FICHIER"
|
|
37
|
+
# Versions lues par les MÉTADONNÉES : pyhanko n'expose pas `__version__` (AttributeError).
|
|
38
|
+
"$VENV/bin/python" -c 'import importlib.metadata as m, pyhanko, pikepdf
|
|
39
|
+
print("· pyhanko", m.version("pyhanko"), "· pikepdf", m.version("pikepdf"), "— import OK")'
|
|
40
|
+
echo "✓ venv prêt. Déclarer : PDF_SIGN_PYTHON=$(cd "$VENV" && pwd)/bin/python"
|
|
@@ -124,8 +124,18 @@ def do_sign(inp, outp, p12_path, p12_pass_file, config_path):
|
|
|
124
124
|
tsa_url = cfg.get("timestamp_url")
|
|
125
125
|
timestamper = None
|
|
126
126
|
if tsa_url:
|
|
127
|
-
|
|
128
|
-
|
|
127
|
+
# Identifiants (0.4.0) : un horodateur QUALIFIE est payant, donc authentifie son client.
|
|
128
|
+
# Ils arrivent par le fichier de configuration, jamais par argv, et ne sont JAMAIS imprimes.
|
|
129
|
+
auth_cfg = cfg.get("timestamp_auth")
|
|
130
|
+
auth = (auth_cfg["user"], auth_cfg.get("password", "")) if auth_cfg else None
|
|
131
|
+
print(f"-- horodatage : {tsa_url}%s --" % (" (authentifie)" if auth or cfg.get("timestamp_headers") else ""),
|
|
132
|
+
flush=True)
|
|
133
|
+
timestamper = timestamps.HTTPTimeStamper(
|
|
134
|
+
url=tsa_url,
|
|
135
|
+
headers=cfg.get("timestamp_headers") or None,
|
|
136
|
+
auth=auth,
|
|
137
|
+
timeout=cfg.get("timestamp_timeout") or 10,
|
|
138
|
+
)
|
|
129
139
|
|
|
130
140
|
with open(outp, "wb") as out_f:
|
|
131
141
|
try:
|
|
@@ -136,13 +146,47 @@ def do_sign(inp, outp, p12_path, p12_pass_file, config_path):
|
|
|
136
146
|
except Exception as e:
|
|
137
147
|
if timestamper is not None:
|
|
138
148
|
raise SystemExit(
|
|
139
|
-
f"ERREUR -- horodatage impossible ({tsa_url}) : {e}. "
|
|
149
|
+
f"ERREUR -- horodatage impossible ({tsa_url}) : {_cause_horodatage(e)}. "
|
|
140
150
|
"Le document n'a PAS été signé : une date non attestée vaut moins que pas de date."
|
|
141
151
|
)
|
|
142
152
|
raise
|
|
143
153
|
print(f"OK -- PDF signé : {outp}", flush=True)
|
|
144
154
|
|
|
145
155
|
|
|
156
|
+
def _cause_horodatage(e):
|
|
157
|
+
"""Nomme la VRAIE cause d'un echec d'horodatage.
|
|
158
|
+
|
|
159
|
+
pyhanko ne regarde que le type de contenu de la reponse : un 401 (cle absente ou fausse) y
|
|
160
|
+
devient « Timestamp server response is malformed » -- ce qui envoie chercher un defaut de
|
|
161
|
+
protocole quand il s'agit d'un identifiant. On remonte la chaine d'exceptions jusqu'a la
|
|
162
|
+
reponse HTTP, et on dit ce que son code signifie.
|
|
163
|
+
"""
|
|
164
|
+
vus, x = set(), e
|
|
165
|
+
while x is not None and id(x) not in vus:
|
|
166
|
+
vus.add(id(x))
|
|
167
|
+
# Deux clients HTTP selon la version de pyhanko -- et le code n'est pas au meme endroit :
|
|
168
|
+
# <= 0.36 : `requests`, la reponse est passee en argument -> args[i].status_code
|
|
169
|
+
# >= 0.37 : `aiohttp`, l'exception porte le code elle-meme -> ClientResponseError.status
|
|
170
|
+
# Constate le 28/09/2026 : sous 0.37, un 401 redevenait « Error while contacting
|
|
171
|
+
# timestamp service », sans que rien d'autre ne change.
|
|
172
|
+
codes = [getattr(a, "status_code", None) for a in getattr(x, "args", ())]
|
|
173
|
+
# `.status` n'est lu que sur une exception d'aiohttp : ailleurs, un attribut du meme nom
|
|
174
|
+
# pourrait ne pas etre un code HTTP, et le message accuserait l'horodateur a tort.
|
|
175
|
+
if type(x).__module__.split(".")[0] == "aiohttp":
|
|
176
|
+
codes.append(getattr(x, "status", None))
|
|
177
|
+
for code in codes:
|
|
178
|
+
if not isinstance(code, int):
|
|
179
|
+
continue
|
|
180
|
+
if code in (401, 403):
|
|
181
|
+
return f"identifiants refusés par l'horodateur (HTTP {code}) -- clé d'API ou mot de passe absent ou invalide"
|
|
182
|
+
if code == 429:
|
|
183
|
+
return "débit de l'horodateur dépassé (HTTP 429) -- réessayer plus tard"
|
|
184
|
+
if code != 200:
|
|
185
|
+
return f"l'horodateur a répondu HTTP {code}"
|
|
186
|
+
x = x.__cause__ or x.__context__
|
|
187
|
+
return str(e)
|
|
188
|
+
|
|
189
|
+
|
|
146
190
|
def _build_stamp_style(stamp_image, meta_cfg):
|
|
147
191
|
"""Style de cachet visible « image de fond + texte » (ou None → texte par défaut de pyhanko)."""
|
|
148
192
|
if not stamp_image:
|
|
@@ -193,12 +237,26 @@ def do_verify(pdf_path, cert_pem, open_password=None):
|
|
|
193
237
|
if getattr(r, "encrypted", False):
|
|
194
238
|
# Le mot de passe d'ouverture d'abord s'il est donne, la chaine vide ensuite : un
|
|
195
239
|
# document non verrouille s'authentifie avec elle, un document verrouille l'exige.
|
|
240
|
+
# ⚠️ `decrypt` ne LÈVE PAS sur un mauvais mot de passe : il rend un AuthResult au statut
|
|
241
|
+
# FAILED. Jusqu'en 0.4.0, la boucle attendait une exception et tenait donc le document
|
|
242
|
+
# pour ouvert des le premier essai, quel qu'il soit — l'echec surgissait plus loin, sans
|
|
243
|
+
# nom. On lit le STATUT. (Constate le 29/09/2026 par T-VER-4, sous pyhanko 0.35 et 0.37.)
|
|
244
|
+
from pyhanko.pdf_utils.crypt import AuthStatus
|
|
245
|
+
ouvert = False
|
|
196
246
|
for essai in ([open_password] if open_password else []) + [""]:
|
|
197
247
|
try:
|
|
198
|
-
r.decrypt(essai)
|
|
199
|
-
break
|
|
248
|
+
ouvert = r.decrypt(essai).status != AuthStatus.FAILED
|
|
200
249
|
except Exception:
|
|
201
|
-
|
|
250
|
+
ouvert = False
|
|
251
|
+
if ouvert:
|
|
252
|
+
break
|
|
253
|
+
# Verrouille et aucun mot de passe ne l'ouvre : on le DIT. Continuer faisait planter
|
|
254
|
+
# l'enumeration plus bas, et l'appelant retombait (<= 0.4.0) sur une detection muette.
|
|
255
|
+
if not ouvert:
|
|
256
|
+
raise SystemExit(
|
|
257
|
+
"ERREUR -- document verrouillé : son mot de passe d'ouverture est requis pour "
|
|
258
|
+
"vérifier ses signatures%s." % (" (celui fourni ne l'ouvre pas)" if open_password else "")
|
|
259
|
+
)
|
|
202
260
|
sigs = r.embedded_signatures
|
|
203
261
|
if not sigs:
|
|
204
262
|
print(json.dumps({"signed": False, "signatures": []}))
|