tisura-light 0.0.1-91

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.
Files changed (102) hide show
  1. package/README.md +176 -0
  2. package/dist/certify/embedCertificateEml.d.ts +21 -0
  3. package/dist/certify/embedCertificateEml.js +106 -0
  4. package/dist/certify/embedCertificatePdf.d.ts +23 -0
  5. package/dist/certify/embedCertificatePdf.js +98 -0
  6. package/dist/certify/embedQrCode.d.ts +27 -0
  7. package/dist/certify/embedQrCode.js +78 -0
  8. package/dist/certify/index.d.ts +73 -0
  9. package/dist/certify/index.js +478 -0
  10. package/dist/certify/parseEml.d.ts +27 -0
  11. package/dist/certify/parseEml.js +37 -0
  12. package/dist/certify/pdfExtractor.d.ts +3 -0
  13. package/dist/certify/pdfExtractor.js +76 -0
  14. package/dist/certify/providers/bourso.d.ts +2 -0
  15. package/dist/certify/providers/bourso.js +83 -0
  16. package/dist/certify/providers/dgfip.d.ts +2 -0
  17. package/dist/certify/providers/dgfip.js +45 -0
  18. package/dist/certify/providers/free.d.ts +2 -0
  19. package/dist/certify/providers/free.js +44 -0
  20. package/dist/certify/providers/index.d.ts +11 -0
  21. package/dist/certify/providers/index.js +42 -0
  22. package/dist/certify/providers/orange.d.ts +2 -0
  23. package/dist/certify/providers/orange.js +34 -0
  24. package/dist/certify/providers/types.d.ts +12 -0
  25. package/dist/certify/providers/types.js +1 -0
  26. package/dist/certify/redactPdf.d.ts +12 -0
  27. package/dist/certify/redactPdf.js +47 -0
  28. package/dist/certify/utils.d.ts +5 -0
  29. package/dist/certify/utils.js +83 -0
  30. package/dist/certify/zip.d.ts +42 -0
  31. package/dist/certify/zip.js +217 -0
  32. package/dist/client-light.d.ts +76 -0
  33. package/dist/client-light.js +113 -0
  34. package/dist/client.d.ts +73 -0
  35. package/dist/client.js +122 -0
  36. package/dist/config.d.ts +12 -0
  37. package/dist/config.js +39 -0
  38. package/dist/connect/connect.d.ts +55 -0
  39. package/dist/connect/connect.js +133 -0
  40. package/dist/connect/index.d.ts +67 -0
  41. package/dist/connect/index.js +221 -0
  42. package/dist/connect/internal/connectionContext.d.ts +48 -0
  43. package/dist/connect/internal/connectionContext.js +43 -0
  44. package/dist/connect/internal/fileTypes.d.ts +3 -0
  45. package/dist/connect/internal/fileTypes.js +28 -0
  46. package/dist/connect/internal/httpRequest.d.ts +57 -0
  47. package/dist/connect/internal/httpRequest.js +612 -0
  48. package/dist/connect/internal/httpStatus.d.ts +6 -0
  49. package/dist/connect/internal/httpStatus.js +29 -0
  50. package/dist/connect/internal/tlsSession.d.ts +6 -0
  51. package/dist/connect/internal/tlsSession.js +182 -0
  52. package/dist/connect/internal/tokenize.d.ts +6 -0
  53. package/dist/connect/internal/tokenize.js +31 -0
  54. package/dist/connect/internal/tunnel.d.ts +15 -0
  55. package/dist/connect/internal/tunnel.js +154 -0
  56. package/dist/connect/tls/index.d.ts +8 -0
  57. package/dist/connect/tls/index.js +14 -0
  58. package/dist/connect/tls/wasm/tls.d.ts +123 -0
  59. package/dist/connect/tls/wasm/tls.js +692 -0
  60. package/dist/connect/tls/wasm/tls_bg.wasm +0 -0
  61. package/dist/connect/tls/wasm/tls_bg.wasm.d.ts +29 -0
  62. package/dist/index.d.ts +22 -0
  63. package/dist/index.js +14 -0
  64. package/dist/index.light.d.ts +9 -0
  65. package/dist/index.light.js +5 -0
  66. package/dist/inputs.d.ts +80 -0
  67. package/dist/inputs.js +234 -0
  68. package/dist/parse/fetcher.d.ts +7 -0
  69. package/dist/parse/fetcher.js +59 -0
  70. package/dist/parse/index.d.ts +12 -0
  71. package/dist/parse/index.js +27 -0
  72. package/dist/parse/parser.d.ts +14 -0
  73. package/dist/parse/parser.js +142 -0
  74. package/dist/parse/types.d.ts +31 -0
  75. package/dist/parse/types.js +1 -0
  76. package/dist/prove/circuits/airdrop/contributions/target/Contributions.json +1 -0
  77. package/dist/prove/circuits/airdrop/ownership/target/Ownership.json +1 -0
  78. package/dist/prove/circuits/providers/bin/target/providers.json +1 -0
  79. package/dist/prove/circuits/substring/bin/target/chacha20_decrypt.json +1 -0
  80. package/dist/prove/index.d.ts +20 -0
  81. package/dist/prove/index.js +179 -0
  82. package/dist/tls13.d.ts +32 -0
  83. package/dist/tls13.js +152 -0
  84. package/dist/tls_bg.wasm +0 -0
  85. package/dist/types.d.ts +6 -0
  86. package/dist/types.js +1 -0
  87. package/dist/utils.d.ts +12 -0
  88. package/dist/utils.js +147 -0
  89. package/dist/verify/index.d.ts +22 -0
  90. package/dist/verify/index.js +31 -0
  91. package/dist/verify/twoddoc/certificates.d.ts +13 -0
  92. package/dist/verify/twoddoc/certificates.js +21 -0
  93. package/dist/verify/twoddoc/pdfBarcode.d.ts +1 -0
  94. package/dist/verify/twoddoc/pdfBarcode.js +43 -0
  95. package/dist/verify/twoddoc/twoDDoc.d.ts +23 -0
  96. package/dist/verify/twoddoc/twoDDoc.js +142 -0
  97. package/dist/verify/twoddoc/twoDDocTags.d.ts +3 -0
  98. package/dist/verify/twoddoc/twoDDocTags.js +72 -0
  99. package/dist/verify/twoddoc/twoDDocVerify.d.ts +8 -0
  100. package/dist/verify/twoddoc/twoDDocVerify.js +95 -0
  101. package/package.json +99 -0
  102. package/public/.gitkeep +0 -0
package/README.md ADDED
@@ -0,0 +1,176 @@
1
+ # Sentinel SDK
2
+
3
+ This project contains the code for [tisura](https://www.npmjs.com/package/tisura) npm package.
4
+ It provides modules for parsing network packets, proving statements, managing connections, and handling TLS operations.
5
+
6
+ ## Build
7
+
8
+ ### Build Wasm files
9
+
10
+ Make sure to use `rustc` version of `1.82.0`.
11
+
12
+ To install it, you can run:
13
+
14
+ ```bash
15
+ # first install rustup
16
+ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
17
+ # then install rust
18
+ rustup install 1.82.0
19
+ rustup default 1.82.0
20
+ ```
21
+
22
+ ### Build Noir files
23
+
24
+ Make sure to install `noir`.
25
+
26
+ To install it, you can run:
27
+
28
+ ```bash
29
+ # first install noirup
30
+ curl -L https://raw.githubusercontent.com/noir-lang/noirup/refs/heads/main/install | bash
31
+ # then install noir
32
+ noirup --version 1.0.0-beta.6
33
+ ```
34
+
35
+ ## Local SDK Development
36
+
37
+ The `build-and-install.sh` script builds the SDK as a `.tgz` (exactly as it would be published to npm) and installs it into a sibling consumer project.
38
+
39
+ ```bash
40
+ ./build-and-install.sh <mode> <dest>
41
+ ```
42
+
43
+ - **`<mode>`** (optional, default `full`): `full` builds with WASM + TS, `light` produces a slim ESM bundle (no TLS/circuit code, just stamping + provider requests).
44
+ - **`<dest>`** (optional, default `twallet`): one of `extension`, `platform`, `airdrop`, `twallet`, `explorer`. `platform` and `airdrop` are installed via `yarn`, the others via `npm`.
45
+
46
+ The script removes any existing `tisura*.tgz` and `node_modules/tisura` in the destination, clears the package manager's cache for tisura, and force-installs the new tarball — so re-running with the same version reliably picks up the new SDK code.
47
+
48
+ Examples:
49
+
50
+ ```bash
51
+ ./build-and-install.sh # full build → twallet (npm)
52
+ ./build-and-install.sh light extension # light build → sentinel_extension (npm)
53
+ ./build-and-install.sh full platform # full build → sentinel/platform (yarn)
54
+ ```
55
+
56
+ ## Installation
57
+
58
+ ```bash
59
+ npm install tisura
60
+ ```
61
+
62
+ ## Usage
63
+
64
+ ### Basic Setup
65
+
66
+ ```typescript
67
+ import { Tisura } from "tisura";
68
+
69
+ const client = new Tisura({
70
+ apiKey: "your-api-key", // Format: sk_dev_xxx key, sk_prod_xxx key, or simply sk_local for local development
71
+ });
72
+ ```
73
+
74
+ ### Sending a request to the server
75
+
76
+ ```typescript
77
+ const connectClient = client.connect;
78
+
79
+ const conn = await connectClient.sendRequest({
80
+ serverHost, // e.g. "api.github.com"
81
+ serverPort, // e.g. 443
82
+ endpoint, // e.g. "/users"
83
+ params, // e.g. [{ key: "since", value: "1" }]
84
+ method, // e.g. "POST"
85
+ body , // eg. [{ key: "name", value: "sherlock" }] - Optional
86
+ headers, // e.g. { key: "Authorization", value: `Bearer ${token}` } - Optional
87
+ }: SendRequestArgs);
88
+ ```
89
+
90
+ ### Generating a proof and verifying it
91
+
92
+ ```typescript
93
+ const proveClient = client.prove;
94
+
95
+ const proof = await proveClient.generateProof({
96
+ circuitName, // e.g. "ownership"
97
+ connectionId, // optional, defaults to the last connection
98
+ plaintext, // the data you want to prove in "substring" circuit - Optional
99
+ nodeId, // the github node ID to prove in "ownership" circuit - Optional
100
+ repoName, // the github repo name to prove in "contributions" circuit - Optional
101
+ repoOwner, // the github repo owner to prove in "contributions" circuit - Optional
102
+ count, // the number of contributions to prove in "contributions" circuit - Optional
103
+ }: GenerateProofArgs);
104
+
105
+ const isValid = await proveClient.verifyProof({
106
+ circuitName, // e.g. "ownership"
107
+ proof, // the proof above
108
+ }: VerifyProofArgs);
109
+ ```
110
+
111
+ ### Certifying a statement
112
+
113
+ ```typescript
114
+ const certifyClient = client.certify;
115
+
116
+ const cert = await certifyClient.certify({
117
+ inputBytes, // the data you want to certify (in bytes)
118
+ connectionId, // optional, defaults to the last connection
119
+ inputSessionKey, // optional, defaults to the last session key
120
+ }: CertifyArgs)
121
+ ```
122
+
123
+ ### Decrypting an encrypted statement
124
+
125
+ ```typescript
126
+ const certifyClient = client.certify;
127
+ const decrypted = await certifyClient.decrypt({
128
+ connectionId, // optional, defaults to the last connection
129
+ }: DecryptArgs);
130
+ ```
131
+
132
+ ### Available Modules
133
+
134
+ The SDK consists of several modules:
135
+
136
+ - **Parse**: Network packet parsing and analysis
137
+ - **Prove**: Statement proving and verification
138
+ - **Certify**: Statement certifying from client side
139
+ - **Connect**: Connection management
140
+
141
+ ### Module-specific Imports
142
+
143
+ You can import specific modules directly:
144
+
145
+ ```typescript
146
+ import { ParseClient } from "tisura/parse";
147
+ import { ProveClient } from "tisura/prove";
148
+ import { CertifyClient } from "tisura/certify";
149
+ import { ConnectClient } from "tisura/connect";
150
+ ```
151
+
152
+ ## Configuration
153
+
154
+ The SDK requires the following configuration parameters:
155
+
156
+ ```typescript
157
+ interface TisuraConfig {
158
+ proxyUrl: string;
159
+ storeUrl: string;
160
+ }
161
+ ```
162
+
163
+ ## Development
164
+
165
+ ### Install, build and run tests
166
+
167
+ ```bash
168
+ # Install dependencies
169
+ npm install
170
+
171
+ # Build the package
172
+ npm run build
173
+
174
+ # Run tests
175
+ npm test
176
+ ```
@@ -0,0 +1,21 @@
1
+ import { SessionKey } from "../inputs.js";
2
+ /**
3
+ * Embed certificate.json as a folded `X-Tisura-Certificate` header (RFC 5322).
4
+ *
5
+ * The cert JSON is base64-encoded and inserted right before the header/body
6
+ * separator. Body bytes are preserved byte-identical, and the original `.eml`
7
+ * is recoverable by stripping the inserted header.
8
+ */
9
+ export declare function embedCertificateInEml(originalEmlBytes: Uint8Array, certificate: SessionKey): {
10
+ stampedEmlBytes: Uint8Array;
11
+ };
12
+ /**
13
+ * Extract the embedded certificate from a stamped `.eml` and return the
14
+ * recovered original bytes (with the `X-Tisura-Certificate` header stripped).
15
+ * Returns null if no embedded certificate is found.
16
+ */
17
+ export declare function extractCertificateFromEml(emlBytes: Uint8Array): {
18
+ certObj: unknown;
19
+ originalEmlBytes: Uint8Array;
20
+ } | null;
21
+ export declare function isEmlWithCertificate(emlBytes: Uint8Array): boolean;
@@ -0,0 +1,106 @@
1
+ const HEADER_NAME = "X-Tisura-Certificate";
2
+ const FOLD_CHUNK = 76; // RFC 5322 recommended max line length for header folding
3
+ /**
4
+ * Embed certificate.json as a folded `X-Tisura-Certificate` header (RFC 5322).
5
+ *
6
+ * The cert JSON is base64-encoded and inserted right before the header/body
7
+ * separator. Body bytes are preserved byte-identical, and the original `.eml`
8
+ * is recoverable by stripping the inserted header.
9
+ */
10
+ export function embedCertificateInEml(originalEmlBytes, certificate) {
11
+ const { sepIdx, eol } = locateHeaderBodySeparator(originalEmlBytes);
12
+ const certBytes = new TextEncoder().encode(JSON.stringify(certificate));
13
+ let bin = "";
14
+ for (const b of certBytes)
15
+ bin += String.fromCharCode(b);
16
+ const certB64 = btoa(bin);
17
+ const chunks = [];
18
+ for (let i = 0; i < certB64.length; i += FOLD_CHUNK) {
19
+ chunks.push(certB64.slice(i, i + FOLD_CHUNK));
20
+ }
21
+ const headerLine = `${HEADER_NAME}: ${chunks[0]}` +
22
+ chunks
23
+ .slice(1)
24
+ .map((c) => `${eol} ${c}`)
25
+ .join("");
26
+ const insertion = new TextEncoder().encode(eol + headerLine);
27
+ const stampedEmlBytes = new Uint8Array(originalEmlBytes.length + insertion.length);
28
+ stampedEmlBytes.set(originalEmlBytes.subarray(0, sepIdx), 0);
29
+ stampedEmlBytes.set(insertion, sepIdx);
30
+ stampedEmlBytes.set(originalEmlBytes.subarray(sepIdx), sepIdx + insertion.length);
31
+ return { stampedEmlBytes };
32
+ }
33
+ /**
34
+ * Extract the embedded certificate from a stamped `.eml` and return the
35
+ * recovered original bytes (with the `X-Tisura-Certificate` header stripped).
36
+ * Returns null if no embedded certificate is found.
37
+ */
38
+ export function extractCertificateFromEml(emlBytes) {
39
+ let sep;
40
+ try {
41
+ sep = locateHeaderBodySeparator(emlBytes);
42
+ }
43
+ catch {
44
+ return null;
45
+ }
46
+ const headerBytes = emlBytes.subarray(0, sep.sepIdx);
47
+ const headerText = new TextDecoder("utf-8", { fatal: false }).decode(headerBytes);
48
+ const lines = headerText.split(sep.eol);
49
+ let startIdx = -1;
50
+ let endIdx = -1;
51
+ for (let i = 0; i < lines.length; i++) {
52
+ if (lines[i].toLowerCase().startsWith(HEADER_NAME.toLowerCase() + ":")) {
53
+ startIdx = i;
54
+ endIdx = i;
55
+ while (endIdx + 1 < lines.length && /^[ \t]/.test(lines[endIdx + 1])) {
56
+ endIdx++;
57
+ }
58
+ break;
59
+ }
60
+ }
61
+ if (startIdx === -1)
62
+ return null;
63
+ let value = lines[startIdx].slice(HEADER_NAME.length + 1).trim();
64
+ for (let i = startIdx + 1; i <= endIdx; i++)
65
+ value += lines[i].trim();
66
+ let certObj;
67
+ try {
68
+ certObj = JSON.parse(atob(value));
69
+ }
70
+ catch {
71
+ return null;
72
+ }
73
+ // Strip the header (and the EOL preceding it that we inserted) to recover
74
+ // original bytes. The inserted region is `eol + headerLine`, placed at the
75
+ // byte offset where the previous header line ended.
76
+ const headerLines = lines.slice(0, startIdx);
77
+ const trailingLines = lines.slice(endIdx + 1);
78
+ const recoveredHeader = [...headerLines, ...trailingLines].join(sep.eol);
79
+ const recoveredHeaderBytes = new TextEncoder().encode(recoveredHeader);
80
+ const originalEmlBytes = new Uint8Array(recoveredHeaderBytes.length + (emlBytes.length - sep.sepIdx));
81
+ originalEmlBytes.set(recoveredHeaderBytes, 0);
82
+ originalEmlBytes.set(emlBytes.subarray(sep.sepIdx), recoveredHeaderBytes.length);
83
+ return { certObj, originalEmlBytes };
84
+ }
85
+ export function isEmlWithCertificate(emlBytes) {
86
+ return extractCertificateFromEml(emlBytes) !== null;
87
+ }
88
+ function locateHeaderBodySeparator(emlBytes) {
89
+ const findSeq = (haystack, needle) => {
90
+ outer: for (let i = 0; i <= haystack.length - needle.length; i++) {
91
+ for (let j = 0; j < needle.length; j++) {
92
+ if (haystack[i + j] !== needle[j])
93
+ continue outer;
94
+ }
95
+ return i;
96
+ }
97
+ return -1;
98
+ };
99
+ let sepIdx = findSeq(emlBytes, [13, 10, 13, 10]);
100
+ if (sepIdx !== -1)
101
+ return { sepIdx, eol: "\r\n" };
102
+ sepIdx = findSeq(emlBytes, [10, 10]);
103
+ if (sepIdx !== -1)
104
+ return { sepIdx, eol: "\n" };
105
+ throw new Error("EML header/body separator not found");
106
+ }
@@ -0,0 +1,23 @@
1
+ import { SessionKey } from "../inputs.js";
2
+ /**
3
+ * Embed certificate.json as a PDF file attachment (ISO 32000-2:2020, 7.11.4).
4
+ *
5
+ * Uses incremental save to preserve original bytes as a prefix of the stamped
6
+ * PDF. The certificate includes `original_content_length` so the verifier can
7
+ * slice the stamped PDF to recover the exact original bytes and compare them
8
+ * against the TLS-decrypted body.
9
+ */
10
+ export declare function embedCertificateInPdf(originalPdfBytes: Uint8Array, certificate: SessionKey): Promise<{
11
+ stampedPdfBytes: Uint8Array;
12
+ }>;
13
+ /**
14
+ * Extract certificate.json from a PDF's EmbeddedFiles name tree.
15
+ * Returns null if no embedded certificate is found.
16
+ */
17
+ export declare function extractCertificateFromPdf(pdfBytes: Uint8Array): Promise<{
18
+ certObj: unknown;
19
+ } | null>;
20
+ /**
21
+ * Check if a PDF has an embedded certificate attachment.
22
+ */
23
+ export declare function isPdfWithCertificate(pdfBytes: Uint8Array): Promise<boolean>;
@@ -0,0 +1,98 @@
1
+ import { PDFDocument, PDFDict, PDFName, PDFArray, PDFRawStream, PDFRef, } from "@cantoo/pdf-lib";
2
+ import { unzlibSync } from "fflate";
3
+ /**
4
+ * Embed certificate.json as a PDF file attachment (ISO 32000-2:2020, 7.11.4).
5
+ *
6
+ * Uses incremental save to preserve original bytes as a prefix of the stamped
7
+ * PDF. The certificate includes `original_content_length` so the verifier can
8
+ * slice the stamped PDF to recover the exact original bytes and compare them
9
+ * against the TLS-decrypted body.
10
+ */
11
+ export async function embedCertificateInPdf(originalPdfBytes, certificate) {
12
+ const pdfDoc = await PDFDocument.load(originalPdfBytes, {
13
+ forIncrementalUpdate: true,
14
+ });
15
+ const certWithLength = {
16
+ ...certificate,
17
+ original_content_length: originalPdfBytes.length,
18
+ };
19
+ const certJson = JSON.stringify(certWithLength, null, 2);
20
+ await pdfDoc.attach(new TextEncoder().encode(certJson), "certificate.json", {
21
+ mimeType: "application/json",
22
+ description: "Tisura TLS verification certificate",
23
+ });
24
+ const stampedPdfBytes = await pdfDoc.save();
25
+ return { stampedPdfBytes };
26
+ }
27
+ /**
28
+ * Extract certificate.json from a PDF's EmbeddedFiles name tree.
29
+ * Returns null if no embedded certificate is found.
30
+ */
31
+ export async function extractCertificateFromPdf(pdfBytes) {
32
+ const pdfDoc = await PDFDocument.load(pdfBytes);
33
+ const namesArray = getEmbeddedFilesNamesArray(pdfDoc);
34
+ if (!namesArray)
35
+ return null;
36
+ // The only embedded file is certificate.json — find the first entry
37
+ const certBytes = readFirstEmbeddedFile(pdfDoc, namesArray);
38
+ if (!certBytes)
39
+ return null;
40
+ try {
41
+ return { certObj: JSON.parse(new TextDecoder().decode(certBytes)) };
42
+ }
43
+ catch {
44
+ return null;
45
+ }
46
+ }
47
+ /**
48
+ * Check if a PDF has an embedded certificate attachment.
49
+ */
50
+ export async function isPdfWithCertificate(pdfBytes) {
51
+ try {
52
+ const result = await extractCertificateFromPdf(pdfBytes);
53
+ return result !== null;
54
+ }
55
+ catch {
56
+ return false;
57
+ }
58
+ }
59
+ // ── Internal helpers ──────────────────────────────────────────────────
60
+ function getEmbeddedFilesNamesArray(pdfDoc) {
61
+ const catalog = pdfDoc.catalog;
62
+ if (!catalog.has(PDFName.of("Names")))
63
+ return null;
64
+ const names = catalog.lookup(PDFName.of("Names"), PDFDict);
65
+ if (!names.has(PDFName.of("EmbeddedFiles")))
66
+ return null;
67
+ const embeddedFiles = names.lookup(PDFName.of("EmbeddedFiles"), PDFDict);
68
+ if (!embeddedFiles.has(PDFName.of("Names")))
69
+ return null;
70
+ return embeddedFiles.lookup(PDFName.of("Names"), PDFArray);
71
+ }
72
+ /**
73
+ * Read the first embedded file from the EmbeddedFiles Names array.
74
+ * Array structure: [name1, filespec1, name2, filespec2, ...].
75
+ */
76
+ function readFirstEmbeddedFile(pdfDoc, namesArray) {
77
+ if (namesArray.size() < 2)
78
+ return null;
79
+ const fileSpecObj = namesArray.get(1);
80
+ const fileSpec = fileSpecObj instanceof PDFRef
81
+ ? pdfDoc.context.lookup(fileSpecObj, PDFDict)
82
+ : fileSpecObj;
83
+ if (!fileSpec.has(PDFName.of("EF")))
84
+ return null;
85
+ const ef = fileSpec.lookup(PDFName.of("EF"), PDFDict);
86
+ const streamRef = ef.get(PDFName.of("F"));
87
+ if (!streamRef)
88
+ return null;
89
+ const stream = pdfDoc.context.lookup(streamRef);
90
+ if (stream instanceof PDFRawStream) {
91
+ const filter = stream.dict.get(PDFName.of("Filter"));
92
+ if (filter && filter === PDFName.of("FlateDecode")) {
93
+ return unzlibSync(stream.contents);
94
+ }
95
+ return stream.contents;
96
+ }
97
+ return null;
98
+ }
@@ -0,0 +1,27 @@
1
+ import { SessionKey } from "../inputs.js";
2
+ export type QrPlacement = {
3
+ pageIndex: number;
4
+ x: number;
5
+ y: number;
6
+ size?: number;
7
+ };
8
+ /**
9
+ * Embed a QR code on the PDF.
10
+ *
11
+ * Defaults to bottom-right of the last page. Pass `placement` to override —
12
+ * callers that detect whitespace can pick a corner that won't collide with
13
+ * existing content.
14
+ *
15
+ * The QR encodes a verification URL with certificate query params:
16
+ * <verifyBaseUrl>?cr=<client_random>&secret=<secret>&len=<original_content_length>
17
+ *
18
+ * Uses QRCode.toCanvas with OffscreenCanvas in service workers (no DOM needed),
19
+ * falls back to QRCode.toDataURL in Node.js.
20
+ *
21
+ * Uses incremental save to preserve all preceding bytes (including any
22
+ * certificate attachment from embedCertificateInPdf) as a prefix of the
23
+ * output, so `original_content_length` slice still works for verification.
24
+ */
25
+ export declare function embedQrCodeInPdf(pdfBytes: Uint8Array, certificate: SessionKey & {
26
+ original_content_length: number;
27
+ }, verifyBaseUrl?: string, placement?: QrPlacement): Promise<Uint8Array>;
@@ -0,0 +1,78 @@
1
+ /* eslint-disable no-undef */
2
+ import { PDFDocument } from "@cantoo/pdf-lib";
3
+ import QRCode from "qrcode";
4
+ const DEFAULT_VERIFY_BASE_URL = "https://tisura.xyz/verifier";
5
+ const DEFAULT_QR_SIZE = 50;
6
+ const DEFAULT_QR_MARGIN = 20;
7
+ /**
8
+ * Embed a QR code on the PDF.
9
+ *
10
+ * Defaults to bottom-right of the last page. Pass `placement` to override —
11
+ * callers that detect whitespace can pick a corner that won't collide with
12
+ * existing content.
13
+ *
14
+ * The QR encodes a verification URL with certificate query params:
15
+ * <verifyBaseUrl>?cr=<client_random>&secret=<secret>&len=<original_content_length>
16
+ *
17
+ * Uses QRCode.toCanvas with OffscreenCanvas in service workers (no DOM needed),
18
+ * falls back to QRCode.toDataURL in Node.js.
19
+ *
20
+ * Uses incremental save to preserve all preceding bytes (including any
21
+ * certificate attachment from embedCertificateInPdf) as a prefix of the
22
+ * output, so `original_content_length` slice still works for verification.
23
+ */
24
+ export async function embedQrCodeInPdf(pdfBytes, certificate, verifyBaseUrl = DEFAULT_VERIFY_BASE_URL, placement) {
25
+ const params = new URLSearchParams({
26
+ cr: certificate.client_random,
27
+ secret: certificate.secret,
28
+ len: String(certificate.original_content_length),
29
+ });
30
+ const verifyUrl = `${verifyBaseUrl}?${params}`;
31
+ const qrPng = await generateQrPng(verifyUrl);
32
+ const pdfDoc = await PDFDocument.load(pdfBytes, {
33
+ forIncrementalUpdate: true,
34
+ });
35
+ const qrImage = await pdfDoc.embedPng(qrPng);
36
+ const pages = pdfDoc.getPages();
37
+ const { page, x, y, size } = resolvePlacement(pages, placement);
38
+ page.drawImage(qrImage, { x, y, width: size, height: size });
39
+ return pdfDoc.save();
40
+ }
41
+ function resolvePlacement(pages, placement) {
42
+ if (placement) {
43
+ if (placement.pageIndex < 0 || placement.pageIndex >= pages.length) {
44
+ throw new Error(`embedQrCodeInPdf: pageIndex ${placement.pageIndex} out of range (0..${pages.length - 1})`);
45
+ }
46
+ return {
47
+ page: pages[placement.pageIndex],
48
+ x: placement.x,
49
+ y: placement.y,
50
+ size: placement.size ?? DEFAULT_QR_SIZE,
51
+ };
52
+ }
53
+ const page = pages[pages.length - 1];
54
+ return {
55
+ page,
56
+ x: page.getWidth() - DEFAULT_QR_SIZE - DEFAULT_QR_MARGIN,
57
+ y: DEFAULT_QR_MARGIN,
58
+ size: DEFAULT_QR_SIZE,
59
+ };
60
+ }
61
+ async function generateQrPng(content) {
62
+ const width = 200;
63
+ const opts = { width, margin: 1 };
64
+ // In service workers, use OffscreenCanvas + convertToBlob (no DOM needed).
65
+ // In Node.js, use toDataURL which works with node-canvas/pngjs.
66
+ if (typeof OffscreenCanvas !== "undefined") {
67
+ const canvas = new OffscreenCanvas(width, width);
68
+ await QRCode.toCanvas(canvas, content, opts);
69
+ const blob = await canvas.convertToBlob({ type: "image/png" });
70
+ return new Uint8Array(await blob.arrayBuffer());
71
+ }
72
+ const dataUrl = await QRCode.toDataURL(content, {
73
+ ...opts,
74
+ type: "image/png",
75
+ });
76
+ const base64 = dataUrl.split(",")[1];
77
+ return Uint8Array.from(atob(base64), (c) => c.charCodeAt(0));
78
+ }
@@ -0,0 +1,73 @@
1
+ import { TisuraConfig } from "../config.js";
2
+ import { CertifyInputs, SessionKey, ScreenshotCertificate, ExtractedAttributesCertificate, AttributeVerificationResult, BatchCertificationResult } from "../inputs.js";
3
+ import type { ExtractionZone } from "./providers/types.js";
4
+ export declare class CertifyClient {
5
+ private config;
6
+ private parseClient;
7
+ constructor(config: TisuraConfig);
8
+ certify({ inputBytes, sessionKey, }: {
9
+ inputBytes: Uint8Array;
10
+ sessionKey: SessionKey;
11
+ }): Promise<boolean>;
12
+ certifyScreenshot({ imageBytes, certificate, }: {
13
+ imageBytes: Uint8Array;
14
+ certificate: ScreenshotCertificate;
15
+ }): Promise<boolean>;
16
+ certifyZipFile({ zipFile }: {
17
+ zipFile: File;
18
+ }): Promise<boolean>;
19
+ /**
20
+ * Certify a PDF that has certificate.json embedded as a file attachment
21
+ * (ISO 32000-2:2020, 7.11.4).
22
+ *
23
+ * Extracts the TLS session key from the embedded certificate, recovers the
24
+ * original PDF bytes using `original_content_length` (incremental save
25
+ * preserves them as a prefix), then verifies by comparing against the
26
+ * TLS-decrypted response body.
27
+ */
28
+ certifyPdfFile({ pdfFile }: {
29
+ pdfFile: File;
30
+ }): Promise<boolean>;
31
+ /**
32
+ * Certify an `.eml` that has its TLS session key embedded as a folded
33
+ * `X-Tisura-Certificate` header (RFC 5322).
34
+ *
35
+ * Extracts the cert + recovers original bytes by stripping the header,
36
+ * then verifies by comparing against the TLS-decrypted response body.
37
+ */
38
+ certifyEmlFile({ emlFile }: {
39
+ emlFile: File;
40
+ }): Promise<boolean>;
41
+ certifyExtractedAttributes({ pdfBytes, certificate, extractedAttributes, }: {
42
+ pdfBytes: Uint8Array;
43
+ certificate: ExtractedAttributesCertificate;
44
+ extractedAttributes: Record<string, string>;
45
+ }): Promise<AttributeVerificationResult>;
46
+ getExtractionZones(domain: string): Record<string, ExtractionZone> | null;
47
+ extractAttributes(rawZoneText: Record<string, string>, domain: string): Record<string, string>;
48
+ /**
49
+ * Shared logic for certifying a payload with its certificate.
50
+ * Detects certificate type and calls the appropriate certify method.
51
+ */
52
+ private certifyPayloadWithCert;
53
+ certifyBatchZipFile({ zipFile, }: {
54
+ zipFile: File;
55
+ }): Promise<BatchCertificationResult[]>;
56
+ private certifySingleItem;
57
+ decrypt(sessionKey: SessionKey): Promise<Uint8Array>;
58
+ decryptBodyAndHeaders(sessionKey: SessionKey): Promise<{
59
+ body: Uint8Array;
60
+ headers: Record<string, string>;
61
+ }>;
62
+ decryptToString(sessionKey: SessionKey): Promise<string>;
63
+ getInput(sessionKey: SessionKey): Promise<CertifyInputs[]>;
64
+ private verifyExtractionData;
65
+ private log;
66
+ private computeHash;
67
+ private verifySignature;
68
+ private importRSAPublicKey;
69
+ private hexToBytes;
70
+ private isScreenshotCertificate;
71
+ private isExtractedAttributesCertificate;
72
+ private isSessionKeyCertificate;
73
+ }