ksef-client-ts 0.7.2-alpha.0 → 0.8.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/dist/cli.js CHANGED
@@ -2453,20 +2453,13 @@ var init_cryptography_service = __esm({
2453
2453
  * Generate a random AES-256 key and IV, then wrap the key with the
2454
2454
  * SymmetricKeyEncryption certificate's RSA public key (RSA-OAEP SHA-256).
2455
2455
  */
2456
- getEncryptionData() {
2456
+ async getEncryptionData() {
2457
2457
  const key = crypto.randomBytes(32);
2458
2458
  const iv = crypto.randomBytes(16);
2459
2459
  const certPem = this.fetcher.getSymmetricKeyEncryptionPem();
2460
- const encryptedKey = crypto.publicEncrypt(
2461
- {
2462
- key: this.extractSpkiPem(certPem),
2463
- oaepHash: "sha256",
2464
- padding: crypto.constants.RSA_PKCS1_OAEP_PADDING
2465
- },
2466
- key
2467
- );
2460
+ const encryptedKey = await this.rsaOaepEncrypt(certPem, new Uint8Array(key));
2468
2461
  const encryptionInfo = {
2469
- encryptedSymmetricKey: encryptedKey.toString("base64"),
2462
+ encryptedSymmetricKey: Buffer.from(encryptedKey).toString("base64"),
2470
2463
  initializationVector: iv.toString("base64")
2471
2464
  };
2472
2465
  return {
@@ -2491,14 +2484,14 @@ var init_cryptography_service = __esm({
2491
2484
  * The EC variant outputs bytes in the Java-compatible format:
2492
2485
  * `[ephemeralSPKI | nonce(12) | ciphertext+tag]`.
2493
2486
  */
2494
- encryptKsefToken(token, challengeTimestamp) {
2487
+ async encryptKsefToken(token, challengeTimestamp) {
2495
2488
  const timestampMs = new Date(challengeTimestamp).getTime();
2496
2489
  const plaintext = Buffer.from(`${token}|${timestampMs}`, "utf-8");
2497
2490
  const certPem = this.fetcher.getKsefTokenEncryptionPem();
2498
2491
  const cert = new crypto.X509Certificate(certPem);
2499
2492
  const publicKey = cert.publicKey;
2500
2493
  if (publicKey.asymmetricKeyType === "rsa") {
2501
- return this.encryptRsaOaep(certPem, plaintext);
2494
+ return this.rsaOaepEncrypt(certPem, plaintext);
2502
2495
  }
2503
2496
  if (publicKey.asymmetricKeyType === "ec") {
2504
2497
  return this.encryptEcdhAesGcm(publicKey, plaintext);
@@ -2590,27 +2583,39 @@ var init_cryptography_service = __esm({
2590
2583
  // Private helpers
2591
2584
  // ---------------------------------------------------------------------------
2592
2585
  /**
2593
- * Extract a public-key SPKI PEM (`-----BEGIN PUBLIC KEY-----`) from a full
2594
- * CERTIFICATE PEM. Node's `publicEncrypt` accepts either form, but Deno's
2595
- * Node compatibility layer only accepts SPKI PEM, so we normalize upfront
2596
- * to keep the library portable across runtimes.
2586
+ * Extract the public-key SPKI DER bytes from a CERTIFICATE PEM. Web Crypto's
2587
+ * `importKey('spki', ...)` consumes raw DER; we parse the cert via native
2588
+ * `X509Certificate` (available on Node and Deno) and export the key directly.
2597
2589
  */
2598
- extractSpkiPem(certPem) {
2590
+ spkiDerFromCert(certPem) {
2599
2591
  const publicKey = new crypto.X509Certificate(certPem).publicKey;
2600
- return publicKey.export({ type: "spki", format: "pem" });
2592
+ return new Uint8Array(publicKey.export({ type: "spki", format: "der" }));
2601
2593
  }
2602
- /** RSA-OAEP SHA-256 encryption. */
2603
- encryptRsaOaep(certPem, plaintext) {
2604
- return new Uint8Array(
2605
- crypto.publicEncrypt(
2606
- {
2607
- key: this.extractSpkiPem(certPem),
2608
- oaepHash: "sha256",
2609
- padding: crypto.constants.RSA_PKCS1_OAEP_PADDING
2610
- },
2611
- plaintext
2612
- )
2594
+ /**
2595
+ * RSA-OAEP SHA-256 encryption via Web Crypto.
2596
+ *
2597
+ * Uses `crypto.webcrypto.subtle` instead of `node:crypto.publicEncrypt`
2598
+ * because Deno's Node-compat layer silently ignores the `oaepHash` option
2599
+ * and defaults MGF1+OAEP to SHA-1, producing ciphertext KSeF rejects as
2600
+ * "Invalid token encryption". Web Crypto requires the hash up-front in
2601
+ * `importKey` as part of the key's algorithm identity, so there is no room
2602
+ * for a runtime to swap it — output is identical OAEP-SHA256 on every
2603
+ * Web Crypto implementation (Node 18+, Deno, Bun, browsers, edge runtimes).
2604
+ */
2605
+ async rsaOaepEncrypt(certPem, plaintext) {
2606
+ const key = await crypto.webcrypto.subtle.importKey(
2607
+ "spki",
2608
+ this.spkiDerFromCert(certPem),
2609
+ { name: "RSA-OAEP", hash: "SHA-256" },
2610
+ false,
2611
+ ["encrypt"]
2612
+ );
2613
+ const ciphertext = await crypto.webcrypto.subtle.encrypt(
2614
+ { name: "RSA-OAEP" },
2615
+ key,
2616
+ plaintext
2613
2617
  );
2618
+ return new Uint8Array(ciphertext);
2614
2619
  }
2615
2620
  /**
2616
2621
  * ECDH (P-256) + AES-256-GCM encryption.
@@ -3061,7 +3066,7 @@ var init_offline_invoice_workflow = __esm({
3061
3066
  }
3062
3067
  if (pending.length === 0) return result;
3063
3068
  await client.crypto.init();
3064
- const encData = client.crypto.getEncryptionData();
3069
+ const encData = await client.crypto.getEncryptionData();
3065
3070
  const formCode = options.formCode ?? DEFAULT_FORM_CODE;
3066
3071
  const openResp = await client.onlineSession.openSession(
3067
3072
  { formCode, encryption: encData.encryptionInfo }
@@ -3150,7 +3155,7 @@ var init_offline_invoice_workflow = __esm({
3150
3155
  }
3151
3156
  const originalHash = crypto3.createHash("sha256").update(original.invoiceXml).digest("base64");
3152
3157
  await client.crypto.init();
3153
- const encData = client.crypto.getEncryptionData();
3158
+ const encData = await client.crypto.getEncryptionData();
3154
3159
  const formCode = options.formCode ?? DEFAULT_FORM_CODE;
3155
3160
  const openResp = await client.onlineSession.openSession(
3156
3161
  { formCode, encryption: encData.encryptionInfo }
@@ -3700,7 +3705,7 @@ var init_client = __esm({
3700
3705
  async loginWithToken(token, nip) {
3701
3706
  const challenge2 = await this.auth.getChallenge();
3702
3707
  await this.crypto.init();
3703
- const encryptedToken = this.crypto.encryptKsefToken(token, challenge2.timestamp);
3708
+ const encryptedToken = await this.crypto.encryptKsefToken(token, challenge2.timestamp);
3704
3709
  const submitResult = await this.auth.submitKsefTokenAuthRequest({
3705
3710
  challenge: challenge2.challenge,
3706
3711
  contextIdentifier: { type: "Nip", value: nip },
@@ -6689,7 +6694,7 @@ async function uploadBatch(client, zipData, options) {
6689
6694
  }
6690
6695
  }
6691
6696
  }
6692
- const encData = client.crypto.getEncryptionData();
6697
+ const encData = await client.crypto.getEncryptionData();
6693
6698
  const formCode = options?.formCode ?? DEFAULT_FORM_CODE;
6694
6699
  const encryptFn = (part) => client.crypto.encryptAES256(part, encData.cipherKey, encData.cipherIv);
6695
6700
  const { batchFile, encryptedParts } = BatchFileBuilder.build(zipData, encryptFn, {
@@ -6737,7 +6742,7 @@ async function uploadBatchStream(client, zipStreamFactory, zipSize, options) {
6737
6742
  throw new Error("parallelism must be a positive integer");
6738
6743
  }
6739
6744
  await client.crypto.init();
6740
- const encData = client.crypto.getEncryptionData();
6745
+ const encData = await client.crypto.getEncryptionData();
6741
6746
  const formCode = options?.formCode ?? DEFAULT_FORM_CODE;
6742
6747
  const encryptStreamFn = (stream) => client.crypto.encryptAES256Stream(stream, encData.cipherKey, encData.cipherIv);
6743
6748
  const hashStreamFn = (stream) => client.crypto.getFileMetadataFromStream(stream);
@@ -7206,7 +7211,7 @@ init_auth_xml_builder();
7206
7211
  async function authenticateWithToken(client, options) {
7207
7212
  const challenge2 = await client.auth.getChallenge();
7208
7213
  await client.crypto.init();
7209
- const encryptedToken = client.crypto.encryptKsefToken(options.token, challenge2.timestamp);
7214
+ const encryptedToken = await client.crypto.encryptKsefToken(options.token, challenge2.timestamp);
7210
7215
  const submitResult = await client.auth.submitKsefTokenAuthRequest({
7211
7216
  challenge: challenge2.challenge,
7212
7217
  contextIdentifier: { type: "Nip", value: options.nip },
@@ -7790,7 +7795,7 @@ var open = defineCommand3({
7790
7795
  throw new Error("NIP is required. Provide --nip or set it via `ksef config set --nip <nip>`.");
7791
7796
  }
7792
7797
  await client.crypto.init();
7793
- const encryptionData = client.crypto.getEncryptionData();
7798
+ const encryptionData = await client.crypto.getEncryptionData();
7794
7799
  const formCodeKey = args.formCode;
7795
7800
  let formCode = DEFAULT_FORM_CODE;
7796
7801
  if (formCodeKey) {
@@ -8261,7 +8266,7 @@ function verifyHash(data, expectedHash) {
8261
8266
  // src/workflows/invoice-export-workflow.ts
8262
8267
  async function doExport(client, filters, options) {
8263
8268
  await client.crypto.init();
8264
- const encData = client.crypto.getEncryptionData();
8269
+ const encData = await client.crypto.getEncryptionData();
8265
8270
  const opResp = await client.invoices.exportInvoices({
8266
8271
  encryption: encData.encryptionInfo,
8267
8272
  filters,
@@ -9257,7 +9262,7 @@ var send = defineCommand6({
9257
9262
  }
9258
9263
  if (!args.json) consola11.start(`Sending ${xmlFiles.length} invoices via batch session...`);
9259
9264
  await client.crypto.init();
9260
- const encryptionData = client.crypto.getEncryptionData();
9265
+ const encryptionData = await client.crypto.getEncryptionData();
9261
9266
  const parts = fileBuffers.map(({ content }, i) => {
9262
9267
  const metadata = client.crypto.getFileMetadata(new Uint8Array(content));
9263
9268
  return {
@@ -9434,7 +9439,7 @@ var exportCmd = defineCommand6({
9434
9439
  const { client } = await requireSession(globalOpts);
9435
9440
  if (!args.json) consola11.start("Starting invoice export...");
9436
9441
  await client.crypto.init();
9437
- const encryptionData = client.crypto.getEncryptionData();
9442
+ const encryptionData = await client.crypto.getEncryptionData();
9438
9443
  const filters = buildQueryFilters(args);
9439
9444
  const result = await client.invoices.exportInvoices(
9440
9445
  { encryption: encryptionData.encryptionInfo, filters, onlyMetadata: args.onlyMetadata }