@mostajs/pdf-sign-ui 0.4.0 → 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 +17 -0
- package/README.md +11 -0
- package/llms.txt +6 -0
- package/package.json +3 -3
- package/src/detached-html.js +277 -0
- package/src/index.js +2 -0
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,23 @@
|
|
|
5
5
|
Le format suit [Keep a Changelog](https://keepachangelog.com/fr/) ; versionnage [SemVer](https://semver.org/lang/fr/).
|
|
6
6
|
|
|
7
7
|
|
|
8
|
+
## 0.5.0 — 12/10/2026
|
|
9
|
+
|
|
10
|
+
- **La vue de la signature séparée (XAdES), en sept langues** — celle du Journal officiel de l'UE, dont les PDF ne
|
|
11
|
+
portent aucune signature : elle est dans un XML à côté.
|
|
12
|
+
- `detachedFormHtml({ lang, action })` : le formulaire de dépôt (`signature` : le XML ; `documents` : un ou
|
|
13
|
+
plusieurs fichiers à confronter) ;
|
|
14
|
+
- `detachedReportHtml(résultat, { lang })` : le verdict, le signataire (et ce que son certificat déclare :
|
|
15
|
+
qualifié, cachet ou personne, QSCD — en disant que la liste de confiance de l'UE n'est pas consultée), les dates
|
|
16
|
+
déclarée et attestée, chaque horodatage (signature, archive), les données de longue durée, chaque document
|
|
17
|
+
déposé (couvert ou non, et sous quel nom), la liste des documents couverts ;
|
|
18
|
+
- `detachedText`, `DETACHED_TEXTS`.
|
|
19
|
+
- Un jeton d'horodatage qui ne porte pas sur la signature n'est jamais affiché « valide ».
|
|
20
|
+
- La vue attend le résultat de `verifyDetached` (@mostajs/pdf-sign ≥ 0.8.0) ; mêmes classes que `VERIFY_CSS`.
|
|
21
|
+
- Dépendance « pair » `@mostajs/pdf-sign` élargie jusqu'à ^0.8.0 (elle s'arrêtait à ^0.5.0 alors que 0.4.0 sert
|
|
22
|
+
avec 0.7.0).
|
|
23
|
+
- Essais D-1 à D-4 ; `html.test.mjs` (0.4.0), qui n'avait pas été commité, l'est.
|
|
24
|
+
|
|
8
25
|
## 0.4.0 — 11/10/2026
|
|
9
26
|
|
|
10
27
|
- **La vue HTML de la vérification d'un PDF, en sept langues** (fr, en, ar, de, es, it, zh) :
|
package/README.md
CHANGED
|
@@ -101,3 +101,14 @@ const corps = `<style>${VERIFY_CSS}</style>${verifyFormHtml({ lang: "zh" })}${ve
|
|
|
101
101
|
```
|
|
102
102
|
|
|
103
103
|
Sept langues (fr, en, ar, de, es, it, zh) ; fragments sans gabarit, tout échappé.
|
|
104
|
+
|
|
105
|
+
## Vue de la signature séparée — XAdES (0.5.0)
|
|
106
|
+
|
|
107
|
+
Le Journal officiel de l'UE publie ses PDF sans signature : elle est dans un XML à côté.
|
|
108
|
+
|
|
109
|
+
```js
|
|
110
|
+
import { verifyDetached } from "@mostajs/pdf-sign"; // ≥ 0.8.0
|
|
111
|
+
import { detachedFormHtml, detachedReportHtml, VERIFY_CSS } from "@mostajs/pdf-sign-ui";
|
|
112
|
+
const r = verifyDetached({ xml, files: [{ name: "reglement.pdf", bytes: pdf }] });
|
|
113
|
+
const corps = `<style>${VERIFY_CSS}</style>${detachedFormHtml({ lang: "fr" })}${detachedReportHtml(r, { lang: "fr" })}`;
|
|
114
|
+
```
|
package/llms.txt
CHANGED
|
@@ -54,3 +54,9 @@ Composants en React.createElement (pas de JSX à transpiler ; bundler pour les .
|
|
|
54
54
|
- buildSignRequest (0.3.2) : transmet timestampUrl ET les identifiants de l horodateur — timestampHeaders
|
|
55
55
|
(ex. { "X-API-KEY": cle }), timestampAuth { user, password }, timestampTimeout (s). Jeu de clés FERMÉ :
|
|
56
56
|
toute option absente est PERDUE (avertissement), d où leur ajout explicite.
|
|
57
|
+
|
|
58
|
+
## 0.5.0 — SIGNATURE SÉPARÉE (XAdES)
|
|
59
|
+
- detachedFormHtml({ lang, action }) : multipart `signature` (XML) + `documents` (multiple).
|
|
60
|
+
- detachedReportHtml(verifyDetached(...), { lang }) : verdict (altéré si une partie SIGNÉE a changé, invalide si la
|
|
61
|
+
valeur de signature ne correspond pas), signataire + QCStatements DÉCLARÉS, dates, jetons (imprintOk false ⇒ NON
|
|
62
|
+
valide), documents couverts (coveredAs : le nom dans la signature), manifeste. DETACHED_TEXTS : 7 langues.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mostajs/pdf-sign-ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Couche de présentation de @mostajs/pdf-sign — logique headless (validation du formulaire de signature, préréglages de documents officiels/SIH, view-models de vérification, contrôleur). Framework-agnostique, zéro dépendance.",
|
|
5
5
|
"license": "AGPL-3.0-or-later",
|
|
6
6
|
"author": "Dr Hamid MADANI <drmdh@msn.com>",
|
|
@@ -17,12 +17,12 @@
|
|
|
17
17
|
"CHANGELOG.md"
|
|
18
18
|
],
|
|
19
19
|
"scripts": {
|
|
20
|
-
"test": "node test-scripts/headless.test.mjs && node test-scripts/html.test.mjs",
|
|
20
|
+
"test": "node test-scripts/headless.test.mjs && node test-scripts/html.test.mjs && node test-scripts/detached-html.test.mjs",
|
|
21
21
|
"test:react": "node --experimental-loader ./test-scripts/jsx-loader.mjs test-scripts/react.test.mjs",
|
|
22
22
|
"example": "node examples/verify-viewmodel.mjs"
|
|
23
23
|
},
|
|
24
24
|
"peerDependencies": {
|
|
25
|
-
"@mostajs/pdf-sign": "^0.3.0 || ^0.4.0 || ^0.5.0",
|
|
25
|
+
"@mostajs/pdf-sign": "^0.3.0 || ^0.4.0 || ^0.5.0 || ^0.6.0 || ^0.7.0 || ^0.8.0",
|
|
26
26
|
"react": ">=18"
|
|
27
27
|
},
|
|
28
28
|
"peerDependenciesMeta": {
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
// @mostajs/pdf-sign-ui 0.5.0 — LA VUE DE LA SIGNATURE SÉPARÉE (XAdES), en sept langues (12/10/2026).
|
|
2
|
+
// Le Journal officiel de l'UE publie ses PDF sans signature : elle est dans un XML à côté, dont le manifeste nomme
|
|
3
|
+
// chaque document par son empreinte. Cette vue rend le résultat de `verifyDetached` (@mostajs/pdf-sign ≥ 0.8.0).
|
|
4
|
+
// Fragments HTML sans gabarit, tout échappé ; mêmes classes que la vue d'un PDF (VERIFY_CSS).
|
|
5
|
+
// Author: Dr Hamid MADANI <drmdh@msn.com> · AGPL-3.0-or-later
|
|
6
|
+
|
|
7
|
+
/** Les textes, par langue (le français fait référence). */
|
|
8
|
+
export const DETACHED_TEXTS = Object.freeze({
|
|
9
|
+
fr: {
|
|
10
|
+
titre: "Vérifier une signature séparée (XML)",
|
|
11
|
+
aide: "Certains documents officiels — le Journal officiel de l’UE, par exemple — sont publiés sans signature dans le PDF : elle est dans un fichier XML à côté. Déposez ce XML et, si vous le souhaitez, le ou les documents : chacun est confronté aux empreintes que la signature couvre. Rien n’est conservé.",
|
|
12
|
+
signatureXml: "Signature (fichier XML)", documents: "Documents à confronter (facultatif, plusieurs possibles)", bouton: "Vérifier",
|
|
13
|
+
verdict_valide: "Signature valide : rien de ce qu’elle couvre n’a changé", verdict_altere: "ALTÉRÉE : une partie signée a été modifiée",
|
|
14
|
+
verdict_invalide: "Invalide : la signature ne correspond pas à son contenu", niveau: "Niveau", format: "Format",
|
|
15
|
+
signataire: "Signataire", emetteur: "Émetteur", validite: "Validité", numero: "Numéro de série",
|
|
16
|
+
qualifieOui: "se déclare qualifié (eIDAS)", qscd: "clé sur dispositif qualifié (QSCD)", type_eseal: "cachet d’organisation",
|
|
17
|
+
type_esign: "signature de personne", type_web: "authentification de site",
|
|
18
|
+
qualifieAide: "déclaration du certificat ; la liste de confiance de l’UE n’est pas consultée ici",
|
|
19
|
+
autoSigne: "auto-signé : personne d’autre que le signataire ne se porte garant de son identité",
|
|
20
|
+
certificatAnnonce: "Certificat annoncé par la signature", concorde: "concorde", concordePas: "NE concorde PAS",
|
|
21
|
+
dateDeclaree: "Date déclarée", dateDeclareeAide: "par le signataire lui-même — sans horodatage, elle peut être antidatée",
|
|
22
|
+
dateAttestee: "Date attestée", dateAttesteeAide: "par un horodateur tiers (RFC 3161)",
|
|
23
|
+
horodatageSignature: "Horodatage de la signature", horodatageArchive: "Horodatage d’archive (longue durée)", horodatageAutre: "Autre horodatage",
|
|
24
|
+
horodateur: "Horodateur", politique: "Politique d’horodatage (OID)", jeton: "Jeton",
|
|
25
|
+
jetonOk: "intact, valide, et porte bien sur la signature", jetonSansPortee: "intact et valide (portée non recalculée)", jetonKo: "NON valide",
|
|
26
|
+
aucunHorodatage: "Aucun horodatage : la date de signature n’est attestée par personne.",
|
|
27
|
+
longueDuree: "Données de validation à long terme", longueDureeValeur: "{c} certificat(s), {o} réponse(s) OCSP, {l} liste(s) de révocation",
|
|
28
|
+
documentsFournis: "Documents déposés", taille: "Taille", octets: "{n} octets", empreinte: "Empreinte SHA-256",
|
|
29
|
+
couvert: "COUVERT par la signature, sous le nom « {nom} »",
|
|
30
|
+
nonCouvert: "NON couvert : ce fichier n’est pas l’un des documents signés, ou il a été modifié",
|
|
31
|
+
aucunDocument: "Aucun document déposé : seule la signature a été vérifiée. Déposez le document pour savoir s’il est couvert.",
|
|
32
|
+
manifeste: "Documents couverts par la signature : {n}",
|
|
33
|
+
pasXml: "Ce fichier n’est pas une signature XML lisible.", aucunFichier: "Aucun fichier de signature fourni.",
|
|
34
|
+
moteurAbsent: "Vérification indisponible sur ce serveur (moteur Python absent).", erreur: "Motif du refus",
|
|
35
|
+
},
|
|
36
|
+
en: {
|
|
37
|
+
titre: "Verify a detached signature (XML)",
|
|
38
|
+
aide: "Some official documents — the EU Official Journal, for instance — are published without a signature inside the PDF: it sits in an XML file alongside. Upload that XML and, if you wish, the document(s): each one is matched against the fingerprints the signature covers. Nothing is kept.",
|
|
39
|
+
signatureXml: "Signature (XML file)", documents: "Documents to match (optional, several allowed)", bouton: "Verify",
|
|
40
|
+
verdict_valide: "Valid signature: nothing it covers has changed", verdict_altere: "ALTERED: a signed part has been modified",
|
|
41
|
+
verdict_invalide: "Invalid: the signature does not match its content", niveau: "Level", format: "Format",
|
|
42
|
+
signataire: "Signer", emetteur: "Issuer", validite: "Validity", numero: "Serial number",
|
|
43
|
+
qualifieOui: "declares itself qualified (eIDAS)", qscd: "key on a qualified device (QSCD)", type_eseal: "organisation seal",
|
|
44
|
+
type_esign: "personal signature", type_web: "website authentication",
|
|
45
|
+
qualifieAide: "as declared by the certificate; the EU trusted list is not consulted here",
|
|
46
|
+
autoSigne: "self-signed: nobody but the signer vouches for their identity",
|
|
47
|
+
certificatAnnonce: "Certificate announced by the signature", concorde: "matches", concordePas: "does NOT match",
|
|
48
|
+
dateDeclaree: "Declared date", dateDeclareeAide: "by the signer themself — without a timestamp, it can be backdated",
|
|
49
|
+
dateAttestee: "Attested date", dateAttesteeAide: "by a third-party timestamping authority (RFC 3161)",
|
|
50
|
+
horodatageSignature: "Signature timestamp", horodatageArchive: "Archive timestamp (long term)", horodatageAutre: "Other timestamp",
|
|
51
|
+
horodateur: "Timestamping authority", politique: "Timestamp policy (OID)", jeton: "Token",
|
|
52
|
+
jetonOk: "intact, valid, and covering the signature", jetonSansPortee: "intact and valid (coverage not recomputed)", jetonKo: "NOT valid",
|
|
53
|
+
aucunHorodatage: "No timestamp: nobody attests the signing date.",
|
|
54
|
+
longueDuree: "Long-term validation data", longueDureeValeur: "{c} certificate(s), {o} OCSP response(s), {l} revocation list(s)",
|
|
55
|
+
documentsFournis: "Uploaded documents", taille: "Size", octets: "{n} bytes", empreinte: "SHA-256 fingerprint",
|
|
56
|
+
couvert: "COVERED by the signature, under the name “{nom}”",
|
|
57
|
+
nonCouvert: "NOT covered: this file is not one of the signed documents, or it has been modified",
|
|
58
|
+
aucunDocument: "No document uploaded: only the signature was checked. Upload the document to find out whether it is covered.",
|
|
59
|
+
manifeste: "Documents covered by the signature: {n}",
|
|
60
|
+
pasXml: "This file is not a readable XML signature.", aucunFichier: "No signature file provided.",
|
|
61
|
+
moteurAbsent: "Verification is unavailable on this server (Python engine missing).", erreur: "Reason for rejection",
|
|
62
|
+
},
|
|
63
|
+
ar: {
|
|
64
|
+
titre: "التحقّق من توقيع منفصل (XML)",
|
|
65
|
+
aide: "تُنشر بعض الوثائق الرسمية — كالجريدة الرسمية للاتحاد الأوروبي — دون توقيع داخل ملف PDF: يوجد التوقيع في ملف XML مرافق. أودِع ملف XML هذا، وإن شئت الوثيقة أو الوثائق: تُقارَن كلّ واحدة بالبصمات التي يغطّيها التوقيع. لا يُحتفظ بشيء.",
|
|
66
|
+
signatureXml: "التوقيع (ملف XML)", documents: "الوثائق المراد مقارنتها (اختياري، يمكن أكثر من واحدة)", bouton: "تحقّق",
|
|
67
|
+
verdict_valide: "توقيع صالح: لم يتغيّر شيء ممّا يغطّيه", verdict_altere: "مُعدَّل: عُدّل جزء موقَّع",
|
|
68
|
+
verdict_invalide: "غير صالح: التوقيع لا يطابق محتواه", niveau: "المستوى", format: "الصيغة",
|
|
69
|
+
signataire: "الموقِّع", emetteur: "المُصدِر", validite: "الصلاحية", numero: "الرقم التسلسلي",
|
|
70
|
+
qualifieOui: "تُصرّح بأنها مؤهَّلة (eIDAS)", qscd: "مفتاح على جهاز مؤهَّل (QSCD)", type_eseal: "ختم مؤسّسة",
|
|
71
|
+
type_esign: "توقيع شخص", type_web: "مصادقة موقع",
|
|
72
|
+
qualifieAide: "وفق تصريح الشهادة؛ لا تُراجَع هنا قائمة الثقة الأوروبية",
|
|
73
|
+
autoSigne: "موقَّعة ذاتيًّا: لا أحد غير الموقِّع يضمن هويّته",
|
|
74
|
+
certificatAnnonce: "الشهادة المعلَنة في التوقيع", concorde: "مطابقة", concordePas: "غير مطابقة",
|
|
75
|
+
dateDeclaree: "التاريخ المُصرَّح به", dateDeclareeAide: "من الموقِّع نفسه — دون ختم زمني يمكن تقديمه",
|
|
76
|
+
dateAttestee: "التاريخ المشهود", dateAttesteeAide: "من جهة ختم زمني مستقلّة (RFC 3161)",
|
|
77
|
+
horodatageSignature: "الختم الزمني للتوقيع", horodatageArchive: "ختم زمني أرشيفي (طويل الأمد)", horodatageAutre: "ختم زمني آخر",
|
|
78
|
+
horodateur: "جهة الختم الزمني", politique: "سياسة الختم الزمني (OID)", jeton: "الرمز",
|
|
79
|
+
jetonOk: "سليم وصالح، ويغطّي التوقيع فعلًا", jetonSansPortee: "سليم وصالح (لم يُعَد حساب ما يغطّيه)", jetonKo: "غير صالح",
|
|
80
|
+
aucunHorodatage: "لا ختم زمني: لا أحد يشهد على تاريخ التوقيع.",
|
|
81
|
+
longueDuree: "بيانات التحقّق طويل الأمد", longueDureeValeur: "{c} شهادة، {o} ردّ OCSP، {l} قائمة إلغاء",
|
|
82
|
+
documentsFournis: "الوثائق المودَعة", taille: "الحجم", octets: "{n} بايت", empreinte: "بصمة SHA-256",
|
|
83
|
+
couvert: "يغطّيه التوقيع، باسم «{nom}»",
|
|
84
|
+
nonCouvert: "غير مغطّى: هذا الملف ليس من الوثائق الموقَّعة، أو أنه عُدّل",
|
|
85
|
+
aucunDocument: "لم تُودَع أيّ وثيقة: تمّ التحقّق من التوقيع وحده. أودِع الوثيقة لتعرف إن كان التوقيع يغطّيها.",
|
|
86
|
+
manifeste: "الوثائق التي يغطّيها التوقيع: {n}",
|
|
87
|
+
pasXml: "هذا الملف ليس توقيع XML مقروءًا.", aucunFichier: "لم يُقدَّم أيّ ملف توقيع.",
|
|
88
|
+
moteurAbsent: "التحقّق غير متاح على هذا الخادم (محرّك Python غائب).", erreur: "سبب الرفض",
|
|
89
|
+
},
|
|
90
|
+
de: {
|
|
91
|
+
titre: "Eine abgesetzte Signatur prüfen (XML)",
|
|
92
|
+
aide: "Manche amtlichen Dokumente — etwa das Amtsblatt der EU — werden ohne Signatur im PDF veröffentlicht: Sie liegt in einer XML-Datei daneben. Laden Sie diese XML-Datei hoch und, wenn Sie möchten, das oder die Dokumente: Jedes wird mit den Fingerabdrücken abgeglichen, die die Signatur abdeckt. Nichts wird gespeichert.",
|
|
93
|
+
signatureXml: "Signatur (XML-Datei)", documents: "Abzugleichende Dokumente (optional, mehrere möglich)", bouton: "Prüfen",
|
|
94
|
+
verdict_valide: "Gültige Signatur: Nichts von dem, was sie abdeckt, wurde geändert", verdict_altere: "VERÄNDERT: Ein signierter Teil wurde geändert",
|
|
95
|
+
verdict_invalide: "Ungültig: Die Signatur passt nicht zu ihrem Inhalt", niveau: "Stufe", format: "Format",
|
|
96
|
+
signataire: "Unterzeichner", emetteur: "Aussteller", validite: "Gültigkeit", numero: "Seriennummer",
|
|
97
|
+
qualifieOui: "erklärt sich als qualifiziert (eIDAS)", qscd: "Schlüssel auf qualifizierter Einheit (QSCD)", type_eseal: "Siegel einer Organisation",
|
|
98
|
+
type_esign: "Signatur einer Person", type_web: "Website-Authentifizierung",
|
|
99
|
+
qualifieAide: "laut Zertifikat; die EU-Vertrauensliste wird hier nicht abgefragt",
|
|
100
|
+
autoSigne: "selbstsigniert: Niemand außer dem Unterzeichner bürgt für seine Identität",
|
|
101
|
+
certificatAnnonce: "Von der Signatur angekündigtes Zertifikat", concorde: "stimmt überein", concordePas: "stimmt NICHT überein",
|
|
102
|
+
dateDeclaree: "Angegebenes Datum", dateDeclareeAide: "vom Unterzeichner selbst — ohne Zeitstempel kann es rückdatiert sein",
|
|
103
|
+
dateAttestee: "Bestätigtes Datum", dateAttesteeAide: "durch einen unabhängigen Zeitstempeldienst (RFC 3161)",
|
|
104
|
+
horodatageSignature: "Zeitstempel der Signatur", horodatageArchive: "Archivzeitstempel (Langzeit)", horodatageAutre: "Weiterer Zeitstempel",
|
|
105
|
+
horodateur: "Zeitstempeldienst", politique: "Zeitstempelrichtlinie (OID)", jeton: "Token",
|
|
106
|
+
jetonOk: "unversehrt, gültig und auf die Signatur bezogen", jetonSansPortee: "unversehrt und gültig (Bezug nicht nachgerechnet)", jetonKo: "NICHT gültig",
|
|
107
|
+
aucunHorodatage: "Kein Zeitstempel: Niemand bestätigt das Signaturdatum.",
|
|
108
|
+
longueDuree: "Daten zur Langzeitvalidierung", longueDureeValeur: "{c} Zertifikat(e), {o} OCSP-Antwort(en), {l} Sperrliste(n)",
|
|
109
|
+
documentsFournis: "Hochgeladene Dokumente", taille: "Größe", octets: "{n} Bytes", empreinte: "SHA-256-Fingerabdruck",
|
|
110
|
+
couvert: "Von der Signatur ABGEDECKT, unter dem Namen „{nom}“",
|
|
111
|
+
nonCouvert: "NICHT abgedeckt: Diese Datei gehört nicht zu den signierten Dokumenten oder wurde geändert",
|
|
112
|
+
aucunDocument: "Kein Dokument hochgeladen: Nur die Signatur wurde geprüft. Laden Sie das Dokument hoch, um zu erfahren, ob es abgedeckt ist.",
|
|
113
|
+
manifeste: "Von der Signatur abgedeckte Dokumente: {n}",
|
|
114
|
+
pasXml: "Diese Datei ist keine lesbare XML-Signatur.", aucunFichier: "Keine Signaturdatei übermittelt.",
|
|
115
|
+
moteurAbsent: "Die Prüfung ist auf diesem Server nicht verfügbar (Python-Modul fehlt).", erreur: "Ablehnungsgrund",
|
|
116
|
+
},
|
|
117
|
+
es: {
|
|
118
|
+
titre: "Verificar una firma separada (XML)",
|
|
119
|
+
aide: "Algunos documentos oficiales — el Diario Oficial de la UE, por ejemplo — se publican sin firma dentro del PDF: está en un archivo XML aparte. Cargue ese XML y, si lo desea, el documento o los documentos: cada uno se coteja con las huellas que cubre la firma. No se conserva nada.",
|
|
120
|
+
signatureXml: "Firma (archivo XML)", documents: "Documentos que cotejar (opcional, se admiten varios)", bouton: "Verificar",
|
|
121
|
+
verdict_valide: "Firma válida: nada de lo que cubre ha cambiado", verdict_altere: "ALTERADA: se ha modificado una parte firmada",
|
|
122
|
+
verdict_invalide: "No válida: la firma no corresponde a su contenido", niveau: "Nivel", format: "Formato",
|
|
123
|
+
signataire: "Firmante", emetteur: "Emisor", validite: "Validez", numero: "Número de serie",
|
|
124
|
+
qualifieOui: "se declara cualificado (eIDAS)", qscd: "clave en dispositivo cualificado (QSCD)", type_eseal: "sello de organización",
|
|
125
|
+
type_esign: "firma de persona", type_web: "autenticación de sitio web",
|
|
126
|
+
qualifieAide: "según declara el certificado; aquí no se consulta la lista de confianza de la UE",
|
|
127
|
+
autoSigne: "autofirmado: nadie salvo el firmante responde de su identidad",
|
|
128
|
+
certificatAnnonce: "Certificado anunciado por la firma", concorde: "coincide", concordePas: "NO coincide",
|
|
129
|
+
dateDeclaree: "Fecha declarada", dateDeclareeAide: "por el propio firmante — sin sello de tiempo, puede estar antedatada",
|
|
130
|
+
dateAttestee: "Fecha acreditada", dateAttesteeAide: "por una autoridad de sellado de tiempo independiente (RFC 3161)",
|
|
131
|
+
horodatageSignature: "Sello de tiempo de la firma", horodatageArchive: "Sello de tiempo de archivo (largo plazo)", horodatageAutre: "Otro sello de tiempo",
|
|
132
|
+
horodateur: "Autoridad de sellado de tiempo", politique: "Política de sellado de tiempo (OID)", jeton: "Sello",
|
|
133
|
+
jetonOk: "íntegro, válido y referido a la firma", jetonSansPortee: "íntegro y válido (alcance no recalculado)", jetonKo: "NO válido",
|
|
134
|
+
aucunHorodatage: "Sin sello de tiempo: nadie acredita la fecha de la firma.",
|
|
135
|
+
longueDuree: "Datos de validación a largo plazo", longueDureeValeur: "{c} certificado(s), {o} respuesta(s) OCSP, {l} lista(s) de revocación",
|
|
136
|
+
documentsFournis: "Documentos cargados", taille: "Tamaño", octets: "{n} bytes", empreinte: "Huella SHA-256",
|
|
137
|
+
couvert: "CUBIERTO por la firma, con el nombre «{nom}»",
|
|
138
|
+
nonCouvert: "NO cubierto: este archivo no es uno de los documentos firmados, o ha sido modificado",
|
|
139
|
+
aucunDocument: "No se ha cargado ningún documento: solo se ha verificado la firma. Cargue el documento para saber si está cubierto.",
|
|
140
|
+
manifeste: "Documentos cubiertos por la firma: {n}",
|
|
141
|
+
pasXml: "Este archivo no es una firma XML legible.", aucunFichier: "No se ha enviado ningún archivo de firma.",
|
|
142
|
+
moteurAbsent: "La verificación no está disponible en este servidor (falta el motor Python).", erreur: "Motivo del rechazo",
|
|
143
|
+
},
|
|
144
|
+
it: {
|
|
145
|
+
titre: "Verificare una firma separata (XML)",
|
|
146
|
+
aide: "Alcuni documenti ufficiali — la Gazzetta ufficiale dell’UE, per esempio — sono pubblicati senza firma all’interno del PDF: si trova in un file XML a parte. Carichi quel file XML e, se vuole, il documento o i documenti: ciascuno viene confrontato con le impronte coperte dalla firma. Nulla viene conservato.",
|
|
147
|
+
signatureXml: "Firma (file XML)", documents: "Documenti da confrontare (facoltativo, anche più di uno)", bouton: "Verifica",
|
|
148
|
+
verdict_valide: "Firma valida: nulla di ciò che copre è cambiato", verdict_altere: "ALTERATA: una parte firmata è stata modificata",
|
|
149
|
+
verdict_invalide: "Non valida: la firma non corrisponde al suo contenuto", niveau: "Livello", format: "Formato",
|
|
150
|
+
signataire: "Firmatario", emetteur: "Emittente", validite: "Validità", numero: "Numero di serie",
|
|
151
|
+
qualifieOui: "si dichiara qualificato (eIDAS)", qscd: "chiave su dispositivo qualificato (QSCD)", type_eseal: "sigillo di organizzazione",
|
|
152
|
+
type_esign: "firma di persona", type_web: "autenticazione di sito web",
|
|
153
|
+
qualifieAide: "secondo quanto dichiara il certificato; qui non si consulta l’elenco di fiducia dell’UE",
|
|
154
|
+
autoSigne: "autofirmato: nessuno oltre al firmatario garantisce la sua identità",
|
|
155
|
+
certificatAnnonce: "Certificato annunciato dalla firma", concorde: "corrisponde", concordePas: "NON corrisponde",
|
|
156
|
+
dateDeclaree: "Data dichiarata", dateDeclareeAide: "dal firmatario stesso — senza marca temporale può essere retrodatata",
|
|
157
|
+
dateAttestee: "Data attestata", dateAttesteeAide: "da un’autorità di marcatura temporale indipendente (RFC 3161)",
|
|
158
|
+
horodatageSignature: "Marca temporale della firma", horodatageArchive: "Marca temporale di archivio (lungo termine)", horodatageAutre: "Altra marca temporale",
|
|
159
|
+
horodateur: "Autorità di marcatura temporale", politique: "Politica di marcatura temporale (OID)", jeton: "Marca",
|
|
160
|
+
jetonOk: "integra, valida e riferita alla firma", jetonSansPortee: "integra e valida (copertura non ricalcolata)", jetonKo: "NON valida",
|
|
161
|
+
aucunHorodatage: "Nessuna marca temporale: nessuno attesta la data della firma.",
|
|
162
|
+
longueDuree: "Dati di convalida a lungo termine", longueDureeValeur: "{c} certificato/i, {o} risposta/e OCSP, {l} elenco/i di revoca",
|
|
163
|
+
documentsFournis: "Documenti caricati", taille: "Dimensione", octets: "{n} byte", empreinte: "Impronta SHA-256",
|
|
164
|
+
couvert: "COPERTO dalla firma, con il nome «{nom}»",
|
|
165
|
+
nonCouvert: "NON coperto: questo file non è uno dei documenti firmati, oppure è stato modificato",
|
|
166
|
+
aucunDocument: "Nessun documento caricato: è stata verificata solo la firma. Carichi il documento per sapere se è coperto.",
|
|
167
|
+
manifeste: "Documenti coperti dalla firma: {n}",
|
|
168
|
+
pasXml: "Questo file non è una firma XML leggibile.", aucunFichier: "Nessun file di firma fornito.",
|
|
169
|
+
moteurAbsent: "La verifica non è disponibile su questo server (motore Python assente).", erreur: "Motivo del rifiuto",
|
|
170
|
+
},
|
|
171
|
+
zh: {
|
|
172
|
+
titre: "核验分离式签名(XML)",
|
|
173
|
+
aide: "部分官方文件(例如欧盟《官方公报》)发布时 PDF 内不含签名:签名位于随附的 XML 文件中。请上传该 XML,如有需要,再上传一份或多份文件:每份文件都将与签名所覆盖的指纹逐一比对。不会保存任何内容。",
|
|
174
|
+
signatureXml: "签名(XML 文件)", documents: "待比对的文件(可选,可多份)", bouton: "核验",
|
|
175
|
+
verdict_valide: "签名有效:其覆盖的内容均未改动", verdict_altere: "已被篡改:某个已签名部分被修改",
|
|
176
|
+
verdict_invalide: "无效:签名与其内容不符", niveau: "级别", format: "格式",
|
|
177
|
+
signataire: "签名人", emetteur: "签发机构", validite: "有效期", numero: "序列号",
|
|
178
|
+
qualifieOui: "声明为合格证书(eIDAS)", qscd: "密钥位于合格设备(QSCD)", type_eseal: "机构印章",
|
|
179
|
+
type_esign: "个人签名", type_web: "网站认证",
|
|
180
|
+
qualifieAide: "依据证书自身的声明;此处未查询欧盟可信列表",
|
|
181
|
+
autoSigne: "自签名:除签名人自己外,无人为其身份担保",
|
|
182
|
+
certificatAnnonce: "签名中声明的证书", concorde: "一致", concordePas: "不一致",
|
|
183
|
+
dateDeclaree: "声明日期", dateDeclareeAide: "由签名人自行声明——没有时间戳时可能被倒签",
|
|
184
|
+
dateAttestee: "证明日期", dateAttesteeAide: "由独立的时间戳机构证明(RFC 3161)",
|
|
185
|
+
horodatageSignature: "签名时间戳", horodatageArchive: "归档时间戳(长期)", horodatageAutre: "其他时间戳",
|
|
186
|
+
horodateur: "时间戳机构", politique: "时间戳策略(OID)", jeton: "时间戳",
|
|
187
|
+
jetonOk: "完整、有效,且确实覆盖该签名", jetonSansPortee: "完整且有效(覆盖范围未重新计算)", jetonKo: "无效",
|
|
188
|
+
aucunHorodatage: "无时间戳:签名日期无人证明。",
|
|
189
|
+
longueDuree: "长期验证数据", longueDureeValeur: "{c} 个证书,{o} 个 OCSP 响应,{l} 个吊销列表",
|
|
190
|
+
documentsFournis: "已上传的文件", taille: "大小", octets: "{n} 字节", empreinte: "SHA-256 指纹",
|
|
191
|
+
couvert: "已被签名覆盖,签名中的名称为“{nom}”",
|
|
192
|
+
nonCouvert: "未被覆盖:该文件不是已签名文件之一,或已被修改",
|
|
193
|
+
aucunDocument: "未上传文件:仅核验了签名本身。请上传文件,以确认其是否被签名覆盖。",
|
|
194
|
+
manifeste: "签名覆盖的文件:{n} 份",
|
|
195
|
+
pasXml: "该文件不是可读的 XML 签名。", aucunFichier: "未提供签名文件。",
|
|
196
|
+
moteurAbsent: "本服务器无法进行核验(缺少 Python 引擎)。", erreur: "拒绝原因",
|
|
197
|
+
},
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
const esc = (s = "") => String(s).replace(/[&<>"']/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[c]);
|
|
201
|
+
const dateUtc = (iso) => (iso ? `${new Date(iso).toISOString().replace("T", " ").replace(/\.\d+Z$/, "")} UTC` : "—");
|
|
202
|
+
const ligne = (libelle, valeur, aide = "") => (valeur == null || valeur === "" ? "" : `<tr><th scope="row">${esc(libelle)}</th><td>${valeur}${aide ? `<br><span class="pdfv-aide">${esc(aide)}</span>` : ""}</td></tr>`);
|
|
203
|
+
|
|
204
|
+
/** Un texte dans la langue (repli : le français), `{x}` rempli. */
|
|
205
|
+
export function detachedText(lang, cle, vars = {}) {
|
|
206
|
+
const d = DETACHED_TEXTS[lang] ?? DETACHED_TEXTS.fr;
|
|
207
|
+
return String(d[cle] ?? DETACHED_TEXTS.fr[cle] ?? cle).replace(/\{(\w+)\}/g, (m, k) => (k in vars ? String(vars[k]) : m));
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/** Même règle que `detachedVerdict` de @mostajs/pdf-sign : une partie signée changée, puis la valeur de signature. */
|
|
211
|
+
const verdictDe = (r) => {
|
|
212
|
+
const internes = (r.references ?? []).filter((x) => x.type !== "document");
|
|
213
|
+
if (internes.some((x) => x.ok === false) || r.signedPropertiesOk === false || r.signingCertificateOk === false) return "altere";
|
|
214
|
+
return r.signatureValid === true ? "valide" : "invalide";
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
/** Le formulaire de dépôt (multipart : `signature`, `documents` — plusieurs). */
|
|
218
|
+
export function detachedFormHtml({ lang = "fr", action = "/verifier" } = {}) {
|
|
219
|
+
const t = (k) => esc(detachedText(lang, k));
|
|
220
|
+
return `<form class="pdfv-form" method="post" action="${esc(action)}" enctype="multipart/form-data">
|
|
221
|
+
<fieldset><legend>${t("signatureXml")}</legend><input type="file" name="signature" accept=".xml,application/xml,text/xml" required></fieldset>
|
|
222
|
+
<fieldset><legend>${t("documents")}</legend><input type="file" name="documents" multiple></fieldset>
|
|
223
|
+
<button type="submit">${t("bouton")}</button>
|
|
224
|
+
</form>`;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function blocJeton(lang, j) {
|
|
228
|
+
const t = (k) => detachedText(lang, k);
|
|
229
|
+
const titre = t(j.kind === "archive" ? "horodatageArchive" : j.kind === "signature" ? "horodatageSignature" : "horodatageAutre");
|
|
230
|
+
const etat = j.intact && j.valid && j.imprintOk !== false ? (j.imprintOk === true ? "jetonOk" : "jetonSansPortee") : "jetonKo";
|
|
231
|
+
return `<h4>${esc(titre)}</h4><table class="pdfv-table"><tbody>
|
|
232
|
+
${ligne(t("dateAttestee"), `<b>${esc(dateUtc(j.genTime))}</b>`, t("dateAttesteeAide"))}
|
|
233
|
+
${ligne(t("horodateur"), esc(j.tsa?.subject ?? ""))}
|
|
234
|
+
${ligne(t("politique"), j.policy ? `<code>${esc(j.policy)}</code>` : "")}
|
|
235
|
+
${ligne(t("jeton"), esc(t(etat)))}
|
|
236
|
+
</tbody></table>`;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/** Le rapport de `verifyDetached` : verdict, signataire, dates, horodatages, documents déposés, documents couverts. */
|
|
240
|
+
export function detachedReportHtml(r, { lang = "fr" } = {}) {
|
|
241
|
+
if (!r) return "";
|
|
242
|
+
const t = (k, v) => detachedText(lang, k, v);
|
|
243
|
+
const v = verdictDe(r);
|
|
244
|
+
const c = r.signerCertificate;
|
|
245
|
+
const qualite = c?.qualified
|
|
246
|
+
? [t("qualifieOui"), ...(c.qcType ?? []).filter((x) => DETACHED_TEXTS.fr[`type_${x}`]).map((x) => t(`type_${x}`)), ...(c.qscd ? [t("qscd")] : [])].join(" · ")
|
|
247
|
+
: "";
|
|
248
|
+
const lt = r.longTerm && (r.longTerm.certificates || r.longTerm.ocsp || r.longTerm.crl)
|
|
249
|
+
? t("longueDureeValeur", { c: r.longTerm.certificates, o: r.longTerm.ocsp, l: r.longTerm.crl }) : "";
|
|
250
|
+
const docs = (r.documents ?? []).length
|
|
251
|
+
? `<h3>${esc(t("documentsFournis"))}</h3>${r.documents.map((d) => `<section class="pdfv-signature pdfv-${d.covered ? "valide" : "altere"}">
|
|
252
|
+
<p class="pdfv-verdict">${esc(d.name)} — ${esc(d.covered ? t("couvert", { nom: d.coveredAs ?? d.name }) : t("nonCouvert"))}</p>
|
|
253
|
+
<table class="pdfv-table"><tbody>${ligne(t("taille"), esc(t("octets", { n: d.bytes })))}${ligne(t("empreinte"), `<code class="pdfv-empreinte">${esc(d.sha256)}</code>`)}</tbody></table></section>`).join("")}`
|
|
254
|
+
: `<p class="pdfv-msg">${esc(t("aucunDocument"))}</p>`;
|
|
255
|
+
const manifeste = (r.manifest ?? []).length
|
|
256
|
+
? `<details><summary>${esc(t("manifeste", { n: r.manifest.length }))}</summary><ul>${r.manifest.map((m) => `<li><code>${esc(m.uri)}</code>${m.matchedFile ? ` ← ${esc(m.matchedFile)}` : ""}</li>`).join("")}</ul></details>`
|
|
257
|
+
: "";
|
|
258
|
+
return `<div class="pdfv">
|
|
259
|
+
<section class="pdfv-signature pdfv-${v}">
|
|
260
|
+
<p class="pdfv-verdict">${esc(t(`verdict_${v}`))}</p>
|
|
261
|
+
<table class="pdfv-table"><tbody>
|
|
262
|
+
${ligne(t("niveau"), esc(r.level ?? ""))}
|
|
263
|
+
${ligne(t("signataire"), c ? `<b>${esc(c.subject)}</b>` : "", qualite ? t("qualifieAide") : "")}
|
|
264
|
+
${qualite ? `<tr><td colspan="2">${esc(qualite)}</td></tr>` : ""}
|
|
265
|
+
${ligne(t("emetteur"), esc(c?.issuer ?? ""), c?.selfSigned ? t("autoSigne") : "")}
|
|
266
|
+
${ligne(t("validite"), c?.notBefore ? `${esc(dateUtc(c.notBefore))} → ${esc(dateUtc(c.notAfter))}` : "")}
|
|
267
|
+
${ligne(t("numero"), c?.serial ? `<code>${esc(c.serial)}</code>` : "")}
|
|
268
|
+
${r.signingCertificateOk == null ? "" : ligne(t("certificatAnnonce"), esc(t(r.signingCertificateOk ? "concorde" : "concordePas")))}
|
|
269
|
+
${ligne(t("dateDeclaree"), r.signingTime ? esc(dateUtc(r.signingTime)) : "", t("dateDeclareeAide"))}
|
|
270
|
+
${ligne(t("longueDuree"), esc(lt))}
|
|
271
|
+
</tbody></table>
|
|
272
|
+
${(r.timestamps ?? []).length ? r.timestamps.map((j) => blocJeton(lang, j)).join("") : `<p class="pdfv-msg">${esc(t("aucunHorodatage"))}</p>`}
|
|
273
|
+
</section>
|
|
274
|
+
${docs}
|
|
275
|
+
${manifeste}
|
|
276
|
+
</div>`;
|
|
277
|
+
}
|
package/src/index.js
CHANGED
|
@@ -10,3 +10,5 @@ export {
|
|
|
10
10
|
export { DOCUMENT_PRESETS, listPresets, getPreset } from "./presets.js";
|
|
11
11
|
// 0.4.0 — la vue HTML de la vérification d'un PDF, en sept langues (descendue de mdseal).
|
|
12
12
|
export { VERIFY_LANGS, VERIFY_TEXTS, VERIFY_CSS, verifyText, verifyFormHtml, verifyReportHtml } from "./html.js";
|
|
13
|
+
// 0.5.0 — la vue de la signature séparée (XAdES), comme au Journal officiel de l'UE (pdf-sign ≥ 0.8.0).
|
|
14
|
+
export { DETACHED_TEXTS, detachedText, detachedFormHtml, detachedReportHtml } from "./detached-html.js";
|