@cenk1cenk2/md-printer 3.1.1 → 3.2.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/README.md CHANGED
@@ -275,7 +275,8 @@ md-printer invoice.yaml -t invoice --sign --sign-certificate ~/id-austria.pem
275
275
  - `--sign` enables signing. `--sign-provider` selects the provider, `a-trust` is the only one and the default. `--sign-certificate` is a DER or PEM file of your signer certificate.
276
276
  - The same keys can be set as `sign` in front-matter or `settings.json`: `enabled`, `provider`, `certificate`, `reason`, `location`, `contact`. Flags win. `reason`, `location` and `contact` are stored in the signature.
277
277
  - Signing only works for PDF output and is skipped with `--preview`.
278
- - The signing session has to run in your browser. md-printer opens a link to a temporary local page, you log in with ID Austria in the A-Trust page, and paste the resulting signature (starts with `MII`) back into the local page. It waits until the signature arrives, press Ctrl+C to abort.
278
+ - The signing session has to run in your browser. md-printer opens a temporary local page in an app window of the discovered browser, using your normal profile (or with `xdg-open` when none is found), you log in with ID Austria in the A-Trust page, and paste the resulting signature (starts with `MII`) back into the local page. A paste that is cut short is refused on the page, so you can copy it again. It waits until the signature arrives, press Ctrl+C to abort.
279
+ - The returned signature is always verified before it is embedded: its digest must match this document and it must be cryptographically valid. The result is printed with the signer name, serial and issuer. A failed verification fails the run, and with `--log-level debug` the returned signature is written next to the output as `<output>.cms.der`.
279
280
  - With a certificate, the signer name, issuer and serial are passed to the template as `signature`, so `SignatureBlock` can show who signed and when. The certificate must be valid at signing time, and the returned signature is rejected if it was not made with it. Without a certificate only the date is available.
280
281
  - Verify the result at https://www.signaturpruefung.gv.at.
281
282
 
@@ -1,4 +1,4 @@
1
- //#region \0@oxc-project+runtime@0.153.0/helpers/esm/decorate.js
1
+ //#region \0@oxc-project+runtime@0.154.0/helpers/esm/decorate.js
2
2
  function __decorate(decorators, target, key, desc) {
3
3
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
4
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
@@ -1,4 +1,4 @@
1
- //#region \0@oxc-project+runtime@0.153.0/helpers/esm/decorateMetadata.js
1
+ //#region \0@oxc-project+runtime@0.154.0/helpers/esm/decorateMetadata.js
2
2
  function __decorateMetadata(k, v) {
3
3
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
4
4
  }
@@ -1,4 +1,4 @@
1
- //#region \0@oxc-project+runtime@0.153.0/helpers/esm/decorateParam.js
1
+ //#region \0@oxc-project+runtime@0.154.0/helpers/esm/decorateParam.js
2
2
  function __decorateParam(paramIndex, decorator) {
3
3
  return function(target, key) {
4
4
  decorator(target, key, paramIndex);
@@ -1,4 +1,4 @@
1
- import __decorate from "../_virtual/_@oxc-project_runtime@0.153.0/helpers/esm/decorate.mjs";
1
+ import __decorate from "../_virtual/_@oxc-project_runtime@0.154.0/helpers/esm/decorate.mjs";
2
2
  import { MdPrinterHandler } from "../modules/md-printer/md-printer.handler.mjs";
3
3
  import { MdPrinterModule } from "../modules/md-printer/md-printer.module.mjs";
4
4
  import { MdPrinterSchema } from "../modules/md-printer/md-printer.schema.mjs";
@@ -1,5 +1,5 @@
1
- import __decorateMetadata from "../../_virtual/_@oxc-project_runtime@0.153.0/helpers/esm/decorateMetadata.mjs";
2
- import __decorate from "../../_virtual/_@oxc-project_runtime@0.153.0/helpers/esm/decorate.mjs";
1
+ import __decorateMetadata from "../../_virtual/_@oxc-project_runtime@0.154.0/helpers/esm/decorateMetadata.mjs";
2
+ import __decorate from "../../_virtual/_@oxc-project_runtime@0.154.0/helpers/esm/decorate.mjs";
3
3
  import { BrowserService } from "../../services/renderer/browser.service.mjs";
4
4
  import { PrinterService } from "../../services/printer.service.mjs";
5
5
  import { PreviewService } from "../../services/session/preview.service.mjs";
@@ -1,4 +1,4 @@
1
- import __decorate from "../../_virtual/_@oxc-project_runtime@0.153.0/helpers/esm/decorate.mjs";
1
+ import __decorate from "../../_virtual/_@oxc-project_runtime@0.154.0/helpers/esm/decorate.mjs";
2
2
  import { PrinterModule } from "../../services/printer.module.mjs";
3
3
  import "../../services/index.mjs";
4
4
  import { MdPrinterHandler } from "./md-printer.handler.mjs";
@@ -1,6 +1,6 @@
1
1
  import { I18N_ACCEPTED_TYPES } from "../constants/template.constants.mjs";
2
- import __decorateMetadata from "../_virtual/_@oxc-project_runtime@0.153.0/helpers/esm/decorateMetadata.mjs";
3
- import __decorate from "../_virtual/_@oxc-project_runtime@0.153.0/helpers/esm/decorate.mjs";
2
+ import __decorateMetadata from "../_virtual/_@oxc-project_runtime@0.154.0/helpers/esm/decorateMetadata.mjs";
3
+ import __decorate from "../_virtual/_@oxc-project_runtime@0.154.0/helpers/esm/decorate.mjs";
4
4
  import { Injectable } from "@nestjs/common";
5
5
  import { readdir } from "fs/promises";
6
6
  import { basename, extname, join } from "path";
@@ -2,6 +2,7 @@ import { I18nService } from "./i18n.service.mjs";
2
2
  import { MarkdownService } from "./markdown.service.mjs";
3
3
  import { BrowserService } from "./renderer/browser.service.mjs";
4
4
  import { RendererService } from "./renderer/renderer.service.mjs";
5
+ import { IncompleteSignatureError, SignatureVerificationError, parseSignedData } from "./signature/cms.mjs";
5
6
  import { SignatureAdapter } from "./signature/signature.adapter.mjs";
6
7
  import { ATrustAdapter } from "./signature/adapters/a-trust.adapter.mjs";
7
8
  import { SignatureService } from "./signature/signature.service.mjs";
@@ -11,4 +12,4 @@ import { PreviewService } from "./session/preview.service.mjs";
11
12
  import { SessionService } from "./session/session.service.mjs";
12
13
  import { PrinterModule } from "./printer.module.mjs";
13
14
  import "./signature/index.mjs";
14
- export { ATrustAdapter, BrowserService, I18nService, MarkdownService, PreviewService, PrinterModule, PrinterService, RendererService, SessionService, SignatureAdapter, SignatureService, TemplateService };
15
+ export { ATrustAdapter, BrowserService, I18nService, IncompleteSignatureError, MarkdownService, PreviewService, PrinterModule, PrinterService, RendererService, SessionService, SignatureAdapter, SignatureService, SignatureVerificationError, TemplateService, parseSignedData };
@@ -1,5 +1,5 @@
1
1
  import { DIRECTIVE_TAG, PAGE_BREAK_CLASS } from "../constants/template.constants.mjs";
2
- import __decorate from "../_virtual/_@oxc-project_runtime@0.153.0/helpers/esm/decorate.mjs";
2
+ import __decorate from "../_virtual/_@oxc-project_runtime@0.154.0/helpers/esm/decorate.mjs";
3
3
  import { Injectable } from "@nestjs/common";
4
4
  import { toHtml } from "hast-util-to-html";
5
5
  import { all } from "lowlight";
@@ -1,5 +1,5 @@
1
1
  import { PRINTER_MODULE_OPTIONS } from "../constants/template.constants.mjs";
2
- import __decorate from "../_virtual/_@oxc-project_runtime@0.153.0/helpers/esm/decorate.mjs";
2
+ import __decorate from "../_virtual/_@oxc-project_runtime@0.154.0/helpers/esm/decorate.mjs";
3
3
  import { I18nService } from "./i18n.service.mjs";
4
4
  import { MarkdownService } from "./markdown.service.mjs";
5
5
  import { BrowserService } from "./renderer/browser.service.mjs";
@@ -1,30 +1,33 @@
1
1
  import { DEFAULT_PDF_OPTIONS } from "../constants/template.constants.mjs";
2
2
  import { OutputFileType } from "../constants/file.constants.mjs";
3
- import __decorateMetadata from "../_virtual/_@oxc-project_runtime@0.153.0/helpers/esm/decorateMetadata.mjs";
4
- import __decorate from "../_virtual/_@oxc-project_runtime@0.153.0/helpers/esm/decorate.mjs";
3
+ import __decorateMetadata from "../_virtual/_@oxc-project_runtime@0.154.0/helpers/esm/decorateMetadata.mjs";
4
+ import __decorate from "../_virtual/_@oxc-project_runtime@0.154.0/helpers/esm/decorate.mjs";
5
5
  import { I18nService } from "./i18n.service.mjs";
6
6
  import { MarkdownService } from "./markdown.service.mjs";
7
7
  import { inlineHtml } from "./template/assets.mjs";
8
8
  import { RendererService } from "./renderer/renderer.service.mjs";
9
+ import { SignatureVerificationError } from "./signature/cms.mjs";
9
10
  import { SignatureService } from "./signature/signature.service.mjs";
10
11
  import { TemplateService } from "./template/template.service.mjs";
11
12
  import { Injectable } from "@nestjs/common";
12
13
  import { basename, dirname, extname, join, resolve } from "path";
13
- import { FileSystemService, LoggerService } from "@cenk1cenk2/oclif-common";
14
+ import { ConfigService, FileSystemService, LoggerService } from "@cenk1cenk2/oclif-common";
14
15
  import { cloneDeep, mergeWith, uniq } from "es-toolkit";
15
16
  import { parse } from "yaml";
16
17
  //#region src/services/printer.service.ts
17
18
  let PrinterService = class PrinterService {
18
19
  fs;
19
20
  logger;
21
+ config;
20
22
  i18n;
21
23
  markdown;
22
24
  template;
23
25
  renderer;
24
26
  signature;
25
- constructor(fs, logger, i18n, markdown, template, renderer, signature) {
27
+ constructor(fs, logger, config, i18n, markdown, template, renderer, signature) {
26
28
  this.fs = fs;
27
29
  this.logger = logger;
30
+ this.config = config;
28
31
  this.i18n = i18n;
29
32
  this.markdown = markdown;
30
33
  this.template = template;
@@ -134,6 +137,13 @@ let PrinterService = class PrinterService {
134
137
  ...ctx.options.sign,
135
138
  date: ctx.signingTime,
136
139
  certificate: ctx.certificate
140
+ }).catch(async (err) => {
141
+ if (err instanceof SignatureVerificationError && this.config.isDebug && ctx.options.dest) {
142
+ const path = `${ctx.options.dest}.cms.der`;
143
+ await this.fs.write(path, err.cms);
144
+ this.logger.debug("The returned signature is written to: %s", path);
145
+ }
146
+ throw err;
137
147
  });
138
148
  }
139
149
  signOptions(flags, ...sources) {
@@ -161,6 +171,7 @@ let PrinterService = class PrinterService {
161
171
  PrinterService = __decorate([Injectable(), __decorateMetadata("design:paramtypes", [
162
172
  typeof FileSystemService === "undefined" ? Object : FileSystemService,
163
173
  typeof LoggerService === "undefined" ? Object : LoggerService,
174
+ typeof ConfigService === "undefined" ? Object : ConfigService,
164
175
  typeof I18nService === "undefined" ? Object : I18nService,
165
176
  typeof MarkdownService === "undefined" ? Object : MarkdownService,
166
177
  typeof TemplateService === "undefined" ? Object : TemplateService,
@@ -1,8 +1,8 @@
1
1
  import { PRINTER_MODULE_OPTIONS } from "../../constants/template.constants.mjs";
2
- import __decorateMetadata from "../../_virtual/_@oxc-project_runtime@0.153.0/helpers/esm/decorateMetadata.mjs";
3
- import __decorate from "../../_virtual/_@oxc-project_runtime@0.153.0/helpers/esm/decorate.mjs";
2
+ import __decorateMetadata from "../../_virtual/_@oxc-project_runtime@0.154.0/helpers/esm/decorateMetadata.mjs";
3
+ import __decorate from "../../_virtual/_@oxc-project_runtime@0.154.0/helpers/esm/decorate.mjs";
4
4
  import { discoverBrowser } from "./browser.mjs";
5
- import __decorateParam from "../../_virtual/_@oxc-project_runtime@0.153.0/helpers/esm/decorateParam.mjs";
5
+ import __decorateParam from "../../_virtual/_@oxc-project_runtime@0.154.0/helpers/esm/decorateParam.mjs";
6
6
  import { Inject, Injectable } from "@nestjs/common";
7
7
  import { LoggerService } from "@cenk1cenk2/oclif-common";
8
8
  import puppeteer from "puppeteer-core";
@@ -1,6 +1,6 @@
1
1
  import { PAGE_BREAK_CLASS } from "../../constants/template.constants.mjs";
2
- import __decorateMetadata from "../../_virtual/_@oxc-project_runtime@0.153.0/helpers/esm/decorateMetadata.mjs";
3
- import __decorate from "../../_virtual/_@oxc-project_runtime@0.153.0/helpers/esm/decorate.mjs";
2
+ import __decorateMetadata from "../../_virtual/_@oxc-project_runtime@0.154.0/helpers/esm/decorateMetadata.mjs";
3
+ import __decorate from "../../_virtual/_@oxc-project_runtime@0.154.0/helpers/esm/decorate.mjs";
4
4
  import { BrowserService } from "./browser.service.mjs";
5
5
  import { mimeType, resolveConfined } from "../template/assets.mjs";
6
6
  import { Injectable } from "@nestjs/common";
@@ -1,14 +1,16 @@
1
1
  import "../../constants/file.constants.mjs";
2
- import __decorateMetadata from "../../_virtual/_@oxc-project_runtime@0.153.0/helpers/esm/decorateMetadata.mjs";
3
- import __decorate from "../../_virtual/_@oxc-project_runtime@0.153.0/helpers/esm/decorate.mjs";
2
+ import __decorateMetadata from "../../_virtual/_@oxc-project_runtime@0.154.0/helpers/esm/decorateMetadata.mjs";
3
+ import __decorate from "../../_virtual/_@oxc-project_runtime@0.154.0/helpers/esm/decorate.mjs";
4
4
  import { resolveConfined } from "../template/assets.mjs";
5
5
  import { isLoopbackRequest } from "../loopback.mjs";
6
- import { AppWindow } from "../renderer/app-window.mjs";
7
6
  import { TemplateService } from "../template/template.service.mjs";
8
7
  import { Injectable } from "@nestjs/common";
8
+ import { mkdtemp, rm } from "fs/promises";
9
9
  import { join } from "path";
10
10
  import { LoggerService } from "@cenk1cenk2/oclif-common";
11
11
  import { fileURLToPath } from "url";
12
+ import { tmpdir } from "os";
13
+ import { spawn } from "child_process";
12
14
  import { once } from "events";
13
15
  import { createServer } from "http";
14
16
  import serveStatic from "serve-static";
@@ -20,6 +22,7 @@ let PreviewService = class PreviewService {
20
22
  clients = /* @__PURE__ */ new Set();
21
23
  build;
22
24
  error;
25
+ profile;
23
26
  window;
24
27
  constructor(logger, template) {
25
28
  this.logger = logger;
@@ -57,26 +60,39 @@ let PreviewService = class PreviewService {
57
60
  this.send("failed", this.error);
58
61
  }
59
62
  async open(executable, url) {
60
- this.window = await AppWindow.open(executable, url);
61
- await this.window.closed.catch((err) => {
62
- this.logger.warn("Can not open the preview window, open it manually: %s (%s)", url, err.message);
63
- return new Promise(() => void 0);
63
+ this.profile = await mkdtemp(join(tmpdir(), "md-printer-preview-"));
64
+ this.window = spawn(executable, [
65
+ `--app=${url}`,
66
+ `--user-data-dir=${this.profile}`,
67
+ "--no-first-run",
68
+ "--no-default-browser-check"
69
+ ], { stdio: "ignore" });
70
+ await new Promise((done) => {
71
+ this.window.on("exit", () => done());
72
+ this.window.on("error", (err) => {
73
+ this.logger.warn("Can not open the preview window, open it manually: %s (%s)", url, err.message);
74
+ });
64
75
  });
65
76
  }
66
77
  async onModuleDestroy() {
67
78
  await this.close();
68
79
  }
69
80
  async close() {
70
- const { server, window } = this;
81
+ const { server, window, profile } = this;
71
82
  this.server = void 0;
72
83
  this.window = void 0;
84
+ this.profile = void 0;
73
85
  for (const client of this.clients) client.end();
74
86
  this.clients.clear();
75
- await window?.close();
87
+ if (window && window.exitCode === null && window.signalCode === null && window.kill()) await once(window, "exit");
76
88
  if (server?.listening) {
77
89
  server.close();
78
90
  await once(server, "close");
79
91
  }
92
+ if (profile) await rm(profile, {
93
+ recursive: true,
94
+ force: true
95
+ });
80
96
  }
81
97
  async handle(req, res, shell) {
82
98
  if (!isLoopbackRequest(req)) {
@@ -1,6 +1,6 @@
1
1
  import "../../constants/template.constants.mjs";
2
- import __decorateMetadata from "../../_virtual/_@oxc-project_runtime@0.153.0/helpers/esm/decorateMetadata.mjs";
3
- import __decorate from "../../_virtual/_@oxc-project_runtime@0.153.0/helpers/esm/decorate.mjs";
2
+ import __decorateMetadata from "../../_virtual/_@oxc-project_runtime@0.154.0/helpers/esm/decorateMetadata.mjs";
3
+ import __decorate from "../../_virtual/_@oxc-project_runtime@0.154.0/helpers/esm/decorate.mjs";
4
4
  import { PrinterService } from "../printer.service.mjs";
5
5
  import { Injectable } from "@nestjs/common";
6
6
  import { dirname, join, resolve } from "path";
@@ -1,10 +1,10 @@
1
1
  import "../../../constants/signature.constants.mjs";
2
- import __decorateMetadata from "../../../_virtual/_@oxc-project_runtime@0.153.0/helpers/esm/decorateMetadata.mjs";
3
- import __decorate from "../../../_virtual/_@oxc-project_runtime@0.153.0/helpers/esm/decorate.mjs";
2
+ import __decorateMetadata from "../../../_virtual/_@oxc-project_runtime@0.154.0/helpers/esm/decorateMetadata.mjs";
3
+ import __decorate from "../../../_virtual/_@oxc-project_runtime@0.154.0/helpers/esm/decorate.mjs";
4
4
  import { BrowserNotFoundError } from "../../renderer/browser.mjs";
5
5
  import { BrowserService } from "../../renderer/browser.service.mjs";
6
+ import { IncompleteSignatureError, parseSignedData } from "../cms.mjs";
6
7
  import { isLoopbackRequest } from "../../loopback.mjs";
7
- import { AppWindow } from "../../renderer/app-window.mjs";
8
8
  import { SignatureAdapter } from "../signature.adapter.mjs";
9
9
  import { Injectable } from "@nestjs/common";
10
10
  import { readFile } from "fs/promises";
@@ -93,39 +93,39 @@ let ATrustAdapter = class ATrustAdapter extends SignatureAdapter {
93
93
  res.writeHead(400);
94
94
  return res.end("This does not look like a signature, it should start with MII.");
95
95
  }
96
+ const signature = Buffer.from(cms, "base64");
97
+ try {
98
+ parseSignedData(signature);
99
+ } catch (err) {
100
+ res.writeHead(400);
101
+ if (err instanceof IncompleteSignatureError) return res.end(`The pasted signature is incomplete (got ${err.received} of ${err.declared} bytes), copy the whole signature text from the A-Trust page.`);
102
+ return res.end(`The pasted text is not a CMS signature: ${err.message}`);
103
+ }
96
104
  res.writeHead(200);
97
- res.end("Signature received.");
98
- resolve(Buffer.from(cms, "base64"));
105
+ res.end("Signature received, you can close this window.");
106
+ resolve(signature);
99
107
  });
100
108
  server.listen(0, "127.0.0.1");
101
109
  await once(server, "listening");
102
110
  const url = `http://127.0.0.1:${server.address().port}/`;
103
111
  this.logger.info("Sign the document in the browser, then paste the signature back: %s", url);
104
- const window = await this.open(url).catch((err) => {
105
- server.close();
106
- throw err;
107
- });
112
+ this.open(url);
108
113
  this.context.signal.addEventListener("abort", () => reject(/* @__PURE__ */ new Error("Signing is aborted.")), { once: true });
109
- window?.closed.then(() => reject(/* @__PURE__ */ new Error("The signing window is closed before the signature arrived.")), (err) => this.logger.warn("Can not open the signing window, open the link manually: %s (%s)", url, err.message));
110
- return result.finally(async () => {
111
- server.close();
112
- await window?.close();
113
- });
114
+ return result.finally(() => server.close());
114
115
  }
115
- async open(url) {
116
- let executable;
116
+ open(url) {
117
+ let command;
117
118
  try {
118
- executable = this.browser.executable();
119
+ command = [this.browser.executable(), [`--app=${url}`]];
119
120
  } catch (err) {
120
121
  if (!(err instanceof BrowserNotFoundError)) throw err;
121
- this.logger.warn("Can not find a browser to sign in a window of its own, close the tabs of any earlier signing before you continue. %s", err.message);
122
- spawn("xdg-open", [url], {
123
- stdio: "ignore",
124
- detached: true
125
- }).on("error", (err) => this.logger.warn("Can not open the browser, open the link manually: %s", err.message)).unref();
126
- return;
122
+ this.logger.warn("Can not find a browser to sign in a new window, close the tabs of any earlier signing before you continue. %s", err.message);
123
+ command = ["xdg-open", [url]];
127
124
  }
128
- return AppWindow.open(executable, url);
125
+ spawn(...command, {
126
+ stdio: "ignore",
127
+ detached: true
128
+ }).on("error", (err) => this.logger.warn("Can not open the browser, open the link manually: %s", err.message)).unref();
129
129
  }
130
130
  };
131
131
  ATrustAdapter = __decorate([Injectable(), __decorateMetadata("design:paramtypes", [
@@ -0,0 +1,35 @@
1
+ import { fromBER } from "asn1js";
2
+ import { ContentInfo, SignedData } from "pkijs";
3
+ //#region src/services/signature/cms.ts
4
+ var IncompleteSignatureError = class extends Error {
5
+ received;
6
+ declared;
7
+ constructor(received, declared) {
8
+ super(`The signature is incomplete, got ${received} of ${declared} bytes.`);
9
+ this.received = received;
10
+ this.declared = declared;
11
+ }
12
+ };
13
+ var SignatureVerificationError = class extends Error {
14
+ cms;
15
+ constructor(message, cms, options) {
16
+ super(message, options);
17
+ this.cms = cms;
18
+ }
19
+ };
20
+ /**
21
+ * Parses a DER encoded CMS SignedData, telling a signature that is cut short apart from one that is not a signature at all.
22
+ */
23
+ function parseSignedData(cms) {
24
+ const { offset, result } = fromBER(new Uint8Array(cms));
25
+ if (offset === -1) {
26
+ const declared = result.idBlock.blockLength + result.lenBlock.blockLength + result.lenBlock.length;
27
+ if (declared > cms.length) throw new IncompleteSignatureError(cms.length, declared);
28
+ throw new Error(result.error);
29
+ }
30
+ const info = new ContentInfo({ schema: result });
31
+ if (info.contentType !== ContentInfo.SIGNED_DATA) throw new Error(`Expected CMS SignedData but got content type ${info.contentType}.`);
32
+ return new SignedData({ schema: info.content });
33
+ }
34
+ //#endregion
35
+ export { IncompleteSignatureError, SignatureVerificationError, parseSignedData };
@@ -1,4 +1,5 @@
1
+ import { IncompleteSignatureError, SignatureVerificationError, parseSignedData } from "./cms.mjs";
1
2
  import { SignatureAdapter } from "./signature.adapter.mjs";
2
3
  import { ATrustAdapter } from "./adapters/a-trust.adapter.mjs";
3
4
  import { SignatureService } from "./signature.service.mjs";
4
- export { ATrustAdapter, SignatureAdapter, SignatureService };
5
+ export { ATrustAdapter, IncompleteSignatureError, SignatureAdapter, SignatureService, SignatureVerificationError, parseSignedData };
@@ -1,24 +1,29 @@
1
- import __decorateMetadata from "../../_virtual/_@oxc-project_runtime@0.153.0/helpers/esm/decorateMetadata.mjs";
2
- import __decorate from "../../_virtual/_@oxc-project_runtime@0.153.0/helpers/esm/decorate.mjs";
1
+ import __decorateMetadata from "../../_virtual/_@oxc-project_runtime@0.154.0/helpers/esm/decorateMetadata.mjs";
2
+ import __decorate from "../../_virtual/_@oxc-project_runtime@0.154.0/helpers/esm/decorate.mjs";
3
+ import { IncompleteSignatureError, SignatureVerificationError, parseSignedData } from "./cms.mjs";
3
4
  import { ATrustAdapter } from "./adapters/a-trust.adapter.mjs";
4
5
  import { Injectable } from "@nestjs/common";
5
6
  import { readFile } from "fs/promises";
6
7
  import { isAbsolute, join } from "path";
8
+ import { LoggerService } from "@cenk1cenk2/oclif-common";
9
+ import { getAlgorithmByOID } from "pkijs";
7
10
  import { plainAddPlaceholder } from "@signpdf/placeholder-plain";
8
11
  import { SUBFILTER_ETSI_CADES_DETACHED, findByteRange, removeTrailingNewLine } from "@signpdf/utils";
9
12
  import { X509Certificate, webcrypto } from "crypto";
10
13
  import { homedir } from "os";
11
- import { ContentInfo, SignedData, getAlgorithmByOID } from "pkijs";
12
14
  //#region src/services/signature/signature.service.ts
13
15
  var _SignatureService;
14
16
  let SignatureService = class SignatureService {
15
17
  static {
16
18
  _SignatureService = this;
17
19
  }
20
+ logger;
18
21
  static LENGTH = 16384;
19
22
  static MESSAGE_DIGEST = "1.2.840.113549.1.9.4";
20
23
  adapters;
21
- constructor(aTrust) {
24
+ constructor(logger, aTrust) {
25
+ this.logger = logger;
26
+ this.logger.setup(this.constructor.name);
22
27
  this.adapters = [aTrust];
23
28
  }
24
29
  get providers() {
@@ -41,7 +46,10 @@ let SignatureService = class SignatureService {
41
46
  async sign(pdf, options) {
42
47
  const request = this.prepare(pdf, options);
43
48
  const cms = await this.adapter(options.provider).sign(request);
44
- await this.verify(request, cms, options.certificate);
49
+ const signer = await this.verify(request, cms, options.certificate).catch((err) => {
50
+ throw new SignatureVerificationError(err.message, cms, { cause: err });
51
+ });
52
+ this.logger.info("Signature is verified, signed by %s: the document digest matches and the signature is valid.", this.identify(signer));
45
53
  return this.embed(request, cms);
46
54
  }
47
55
  adapter(provider) {
@@ -80,15 +88,16 @@ let SignatureService = class SignatureService {
80
88
  const content = new Uint8Array(Buffer.concat([pdf.subarray(0, from), pdf.subarray(to + 1)])).buffer;
81
89
  let signed;
82
90
  try {
83
- signed = new SignedData({ schema: ContentInfo.fromBER(new Uint8Array(cms)).content });
91
+ signed = parseSignedData(cms);
84
92
  } catch (err) {
85
- throw new Error(`The returned signature is not a CMS signature: ${err.message}`, { cause: err });
93
+ if (err instanceof IncompleteSignatureError) throw new Error(`The returned signature is incomplete, got ${err.received} of ${err.declared} bytes.`, { cause: err });
94
+ throw new Error(`The returned signature can not be parsed as a CMS signature: ${err.message}`, { cause: err });
86
95
  }
87
96
  const [info] = signed.signerInfos;
88
97
  const digest = info?.signedAttrs?.attributes.find((attribute) => attribute.type === _SignatureService.MESSAGE_DIGEST);
89
98
  if (!digest) throw new Error("The returned signature does not carry the digest of the document it signs.");
90
99
  const algorithm = getAlgorithmByOID(info.digestAlgorithm.algorithmId, true, "digestAlgorithm");
91
- if (!Buffer.from(digest.values[0].valueBlock.valueHexView).equals(Buffer.from(await webcrypto.subtle.digest(algorithm.name, content)))) throw new Error("The returned signature belongs to a different document, it is likely left over from an earlier signing.");
100
+ if (!Buffer.from(digest.values[0].valueBlock.valueHexView).equals(Buffer.from(await webcrypto.subtle.digest(algorithm.name, content)))) throw new Error("The returned signature belongs to a different document.");
92
101
  const { signatureVerified, signerCertificate } = await signed.verify({
93
102
  signer: 0,
94
103
  data: content,
@@ -99,6 +108,7 @@ let SignatureService = class SignatureService {
99
108
  const signer = new X509Certificate(Buffer.from(signerCertificate.toSchema().toBER()));
100
109
  if (certificate && !signer.raw.equals(certificate.raw)) throw new Error([`The returned signature is signed by ${this.identify(signer)}, but the configured certificate is ${this.identify(certificate)}.`, "The signature block would show the wrong signer."].join(" "));
101
110
  if (!signatureVerified) throw new Error(`The returned signature does not verify against the certificate of ${this.identify(signer)}.`);
111
+ return signer;
102
112
  }
103
113
  embed({ pdf, excluded: [from, to] }, cms) {
104
114
  const signature = cms.toString("hex");
@@ -111,12 +121,12 @@ let SignatureService = class SignatureService {
111
121
  ]);
112
122
  }
113
123
  identify(certificate) {
114
- return `${this.commonName(certificate.subject)} (serial ${certificate.serialNumber})`;
124
+ return `${this.commonName(certificate.subject)} (serial ${certificate.serialNumber}, issuer ${this.commonName(certificate.issuer)})`;
115
125
  }
116
126
  commonName(name) {
117
127
  return name.split("\n").find((part) => part.startsWith("CN="))?.slice(3);
118
128
  }
119
129
  };
120
- SignatureService = _SignatureService = __decorate([Injectable(), __decorateMetadata("design:paramtypes", [typeof ATrustAdapter === "undefined" ? Object : ATrustAdapter])], SignatureService);
130
+ SignatureService = _SignatureService = __decorate([Injectable(), __decorateMetadata("design:paramtypes", [typeof LoggerService === "undefined" ? Object : LoggerService, typeof ATrustAdapter === "undefined" ? Object : ATrustAdapter])], SignatureService);
121
131
  //#endregion
122
132
  export { SignatureService };
@@ -1,6 +1,6 @@
1
1
  import "../../constants/template.constants.mjs";
2
- import __decorateMetadata from "../../_virtual/_@oxc-project_runtime@0.153.0/helpers/esm/decorateMetadata.mjs";
3
- import __decorate from "../../_virtual/_@oxc-project_runtime@0.153.0/helpers/esm/decorate.mjs";
2
+ import __decorateMetadata from "../../_virtual/_@oxc-project_runtime@0.154.0/helpers/esm/decorateMetadata.mjs";
3
+ import __decorate from "../../_virtual/_@oxc-project_runtime@0.154.0/helpers/esm/decorate.mjs";
4
4
  import { inlineCss, inlineHtml } from "./assets.mjs";
5
5
  import { sveltePlugin } from "./svelte.plugin.mjs";
6
6
  import { Injectable } from "@nestjs/common";
@@ -198,5 +198,5 @@
198
198
  "enableJsonFlag": false
199
199
  }
200
200
  },
201
- "version": "3.1.1"
201
+ "version": "3.2.0"
202
202
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cenk1cenk2/md-printer",
3
- "version": "3.1.1",
3
+ "version": "3.2.0",
4
4
  "description": "Render markdown or yaml through Svelte templates into PDF or HTML with Puppeteer.",
5
5
  "type": "module",
6
6
  "repository": {
@@ -53,13 +53,14 @@
53
53
  "@fontsource/montserrat": "^5.3.0",
54
54
  "@listr2/manager": "^4.3.1",
55
55
  "@nestjs/common": "^12.1.2",
56
- "@oclif/core": "^5.1.2",
56
+ "@oclif/core": "^5.1.3",
57
57
  "@oclif/plugin-help": "^7.0.2",
58
58
  "@signpdf/placeholder-plain": "^3.3.0",
59
59
  "@signpdf/utils": "^3.3.0",
60
60
  "@tailwindcss/node": "^4.3.3",
61
61
  "@tailwindcss/oxide": "^4.3.3",
62
62
  "@tailwindcss/typography": "^0.5.20",
63
+ "asn1js": "^3.0.10",
63
64
  "change-case": "^5.4.4",
64
65
  "chokidar": "^5.0.0",
65
66
  "enhanced-resolve": "^5.26.0",
@@ -113,11 +114,10 @@
113
114
  "@types/serve-static": "^2.2.0",
114
115
  "@types/which": "^3.0.4",
115
116
  "@types/write-file-atomic": "^4.0.3",
116
- "asn1js": "^3.0.10",
117
117
  "eslint": "^10.12.0",
118
118
  "eslint-plugin-svelte": "^3.23.1",
119
119
  "execa": "^10.1.0",
120
- "oclif": "^6.0.2",
120
+ "oclif": "^6.0.4",
121
121
  "prettier": "^3.9.9",
122
122
  "prettier-plugin-svelte": "^4.1.1",
123
123
  "svelte-eslint-parser": "^1.9.0",
@@ -29,12 +29,12 @@
29
29
  <table class="w-full table-auto my-0!">
30
30
  <tbody>
31
31
  <tr>
32
- <td class="w-[30%] text-left align-middle font-bold">{t('fields.date')}</td>
33
- <td class="w-[70%] text-right align-middle font-semibold">{date}</td>
32
+ <td class="w-[30%] text-left align-middle text-sm font-bold">{t('fields.date')}</td>
33
+ <td class="w-[70%] text-right align-middle text-sm font-semibold whitespace-nowrap">{date}</td>
34
34
  </tr>
35
35
  <tr>
36
- <td class="w-[30%] text-left align-middle font-bold border-t! border-t-gray-100/30!">{t('fields.id')}</td>
37
- <td class="w-[70%] text-right align-middle font-semibold border-t! border-t-gray-100/30!">{id}</td>
36
+ <td class="w-[30%] text-left align-middle text-sm font-bold border-t! border-t-gray-100/30!">{t('fields.id')}</td>
37
+ <td class="w-[70%] text-right align-middle text-sm font-semibold whitespace-nowrap border-t! border-t-gray-100/30!">{id}</td>
38
38
  </tr>
39
39
  </tbody>
40
40
  </table>
@@ -1,44 +0,0 @@
1
- import { mkdtemp, rm } from "fs/promises";
2
- import { join } from "path";
3
- import { tmpdir } from "os";
4
- import { spawn } from "child_process";
5
- import { once } from "events";
6
- //#region src/services/renderer/app-window.ts
7
- /**
8
- * A browser window in app mode with a throwaway profile, so it shares no tabs, cookies or history with the browser of the user.
9
- */
10
- var AppWindow = class AppWindow {
11
- process;
12
- profile;
13
- /**
14
- * Resolves when the window exits, rejects when the browser can not be started.
15
- */
16
- closed;
17
- constructor(process, profile) {
18
- this.process = process;
19
- this.profile = profile;
20
- this.closed = new Promise((resolve, reject) => {
21
- process.once("exit", () => resolve());
22
- process.once("error", reject);
23
- });
24
- this.closed.catch(() => void 0);
25
- }
26
- static async open(executable, url) {
27
- const profile = await mkdtemp(join(tmpdir(), "md-printer-window-"));
28
- return new AppWindow(spawn(executable, [
29
- `--app=${url}`,
30
- `--user-data-dir=${profile}`,
31
- "--no-first-run",
32
- "--no-default-browser-check"
33
- ], { stdio: "ignore" }), profile);
34
- }
35
- async close() {
36
- if (this.process.exitCode === null && this.process.signalCode === null && this.process.kill()) await once(this.process, "exit");
37
- await rm(this.profile, {
38
- recursive: true,
39
- force: true
40
- });
41
- }
42
- };
43
- //#endregion
44
- export { AppWindow };