@carchalabs/seal-electron 0.6.2 → 0.7.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
@@ -1,6 +1,6 @@
1
1
  # @carchalabs/seal-electron
2
2
 
3
- The [Carcha Seal](https://seal.carchalabs.com) (formerly LicenseKit) SDK. It runs in the Electron main process or in plain Node. It activates a license key on this device, saves the signed lease, and verifies it offline with your public keys.
3
+ The [Carcha Seal](https://seal.carchalabs.com) SDK. It runs in the Electron main process or in plain Node. It activates a license key on this device, saves the signed lease, and verifies it offline with your public keys.
4
4
 
5
5
  ```js
6
6
  const { Seal } = require('@carchalabs/seal-electron');
package/dist/index.d.ts CHANGED
@@ -106,10 +106,16 @@ export declare class Seal {
106
106
  private emptyState;
107
107
  /**
108
108
  * The device's public key, sent with activation and check-ins so the server wraps feature keys for
109
- * this machine only. Only when the native module verifies for this app (it then also decrypts, with
110
- * the matching secret it derives itself) and hardware is not turned off. Never throws.
109
+ * this machine only. Sent when the native module can also decrypt what is wrapped for it, with the
110
+ * matching secret it derives itself: through the compiled-in keys when they verify for this app, or,
111
+ * with seal-hwid 0.5+, through this app's own keys (every hosted vendor, a self-hosted server). Not
112
+ * with hardware turned off. Never throws.
111
113
  */
112
114
  private deviceKey;
115
+ /** The native module's decryption with this app's own keys (seal-hwid 0.5+), or null: older module, none, or hardware off. */
116
+ private deviceDecrypter;
117
+ /** This app's public keys as SPKI PEM, for the native module. */
118
+ private keyPems;
113
119
  /**
114
120
  * The native verifier, used only when its compiled-in keys include every key this app was given:
115
121
  * an app that trusts other keys (a self-hosted server) is checked by the JavaScript verifier alone.
@@ -133,8 +139,9 @@ export declare class Seal {
133
139
  * Decrypts data you encrypted for a feature (encryptForFeature in @carchalabs/seal-core, with the
134
140
  * feature's content key from the console). Works offline: it uses the saved lease, which must be valid
135
141
  * (or in its offline grace period), grant the feature and carry its key, and the saved license key.
136
- * The native module does the work when it trusts this app's keys, so the content key never reaches
137
- * JavaScript; without it the JavaScript implementation is used. Throws a LicenseError:
142
+ * When the lease is wrapped for this machine's device key, the native module does the work (with its
143
+ * compiled-in keys or, seal-hwid 0.5+, this app's keys), so the device secret and the content key never
144
+ * reach JavaScript; a lease wrapped for the license key alone is opened in JavaScript. Throws a LicenseError:
138
145
  * `unlicensed`, a lease failure reason, `clock_suspect`, `feature_not_licensed` or `decrypt_failed`.
139
146
  */
140
147
  decrypt(feature: string, data: Uint8Array): Promise<Buffer>;
package/dist/index.js CHANGED
@@ -311,17 +311,20 @@ class Seal {
311
311
  }
312
312
  /**
313
313
  * The device's public key, sent with activation and check-ins so the server wraps feature keys for
314
- * this machine only. Only when the native module verifies for this app (it then also decrypts, with
315
- * the matching secret it derives itself) and hardware is not turned off. Never throws.
314
+ * this machine only. Sent when the native module can also decrypt what is wrapped for it, with the
315
+ * matching secret it derives itself: through the compiled-in keys when they verify for this app, or,
316
+ * with seal-hwid 0.5+, through this app's own keys (every hosted vendor, a self-hosted server). Not
317
+ * with hardware turned off. Never throws.
316
318
  */
317
319
  deviceKey() {
318
320
  if (this.deviceKeyCache === undefined) {
319
321
  let key = null;
320
322
  try {
323
+ const hwid = this.opts.hardware === false ? null : loadHwid();
321
324
  const native = this.opts.hardware === false ? null : this.native();
322
- if (native && typeof native.devicePublicKey === 'function' && typeof native.decryptFeature === 'function') {
323
- key = native.devicePublicKey(`licensekit:${this.opts.product}`);
324
- }
325
+ const decrypts = (native && typeof native.decryptFeature === 'function') || this.deviceDecrypter() !== null;
326
+ if (hwid && typeof hwid.devicePublicKey === 'function' && decrypts)
327
+ key = hwid.devicePublicKey(`licensekit:${this.opts.product}`);
325
328
  }
326
329
  catch {
327
330
  key = null;
@@ -330,6 +333,17 @@ class Seal {
330
333
  }
331
334
  return this.deviceKeyCache ? { devicePublicKey: this.deviceKeyCache } : {};
332
335
  }
336
+ /** The native module's decryption with this app's own keys (seal-hwid 0.5+), or null: older module, none, or hardware off. */
337
+ deviceDecrypter() {
338
+ if (this.opts.hardware === false)
339
+ return null;
340
+ const hwid = loadHwid();
341
+ return hwid && typeof hwid.decryptFeatureWithKeys === 'function' ? hwid : null;
342
+ }
343
+ /** This app's public keys as SPKI PEM, for the native module. */
344
+ keyPems() {
345
+ return [...this.keys.values()].map((key) => key.export({ type: 'spki', format: 'pem' }).toString());
346
+ }
333
347
  /**
334
348
  * The native verifier, used only when its compiled-in keys include every key this app was given:
335
349
  * an app that trusts other keys (a self-hosted server) is checked by the JavaScript verifier alone.
@@ -510,8 +524,9 @@ class Seal {
510
524
  * Decrypts data you encrypted for a feature (encryptForFeature in @carchalabs/seal-core, with the
511
525
  * feature's content key from the console). Works offline: it uses the saved lease, which must be valid
512
526
  * (or in its offline grace period), grant the feature and carry its key, and the saved license key.
513
- * The native module does the work when it trusts this app's keys, so the content key never reaches
514
- * JavaScript; without it the JavaScript implementation is used. Throws a LicenseError:
527
+ * When the lease is wrapped for this machine's device key, the native module does the work (with its
528
+ * compiled-in keys or, seal-hwid 0.5+, this app's keys), so the device secret and the content key never
529
+ * reach JavaScript; a lease wrapped for the license key alone is opened in JavaScript. Throws a LicenseError:
515
530
  * `unlicensed`, a lease failure reason, `clock_suspect`, `feature_not_licensed` or `decrypt_failed`.
516
531
  */
517
532
  async decrypt(feature, data) {
@@ -581,10 +596,27 @@ class Seal {
581
596
  const graceSeconds = state.status === 'grace' ? (claims?.grc ?? (this.opts.graceDays ?? DEFAULT_GRACE_DAYS) * 86400) : 0;
582
597
  const check = { product: this.opts.product, fingerprintHash, now: this.nowSec(), ...(graceSeconds ? { graceSeconds } : {}) };
583
598
  const native = this.native();
599
+ // A lease wrapped for this machine's device key (epk) opens only in the native module, which holds the device
600
+ // secret: through the compiled-in keys when they verify for this app, else through this app's keys (seal-hwid 0.5+).
601
+ const deviceBound = claims?.epk !== undefined;
602
+ const withKeys = deviceBound && !(native && typeof native.decryptFeature === 'function') ? this.deviceDecrypter() : null;
603
+ const pems = withKeys ? this.keyPems() : [];
584
604
  return (data) => {
585
- const result = native && typeof native.decryptFeature === 'function'
586
- ? native.decryptFeature(leased.token, check, leased.licenseKey, feature, Buffer.from(data))
587
- : (0, seal_core_1.decryptFeature)(leased.token, { keys: this.keys, ...check }, leased.licenseKey, feature, Buffer.from(data));
605
+ let result;
606
+ if (native && typeof native.decryptFeature === 'function') {
607
+ result = native.decryptFeature(leased.token, check, leased.licenseKey, feature, Buffer.from(data));
608
+ }
609
+ else if (withKeys) {
610
+ try {
611
+ result = withKeys.decryptFeatureWithKeys(leased.token, check, pems, leased.licenseKey, feature, Buffer.from(data));
612
+ }
613
+ catch {
614
+ result = { ok: false, reason: 'decrypt_failed' };
615
+ }
616
+ }
617
+ else {
618
+ result = (0, seal_core_1.decryptFeature)(leased.token, { keys: this.keys, ...check }, leased.licenseKey, feature, Buffer.from(data));
619
+ }
588
620
  if (result.ok && result.data)
589
621
  return Buffer.from(result.data);
590
622
  if (result.reason === 'not_licensed')
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@carchalabs/seal-electron",
3
- "version": "0.6.2",
3
+ "version": "0.7.0",
4
4
  "description": "Carcha Seal (formerly LicenseKit) SDK for the Electron main process and Node: activation, offline verification, grace period",
5
5
  "keywords": ["electron", "licensing", "license-key", "software-licensing", "offline-licensing", "node-locked", "ed25519", "jws", "carcha-seal"],
6
6
  "homepage": "https://seal.carchalabs.com/docs/",
@@ -18,7 +18,7 @@
18
18
  "@carchalabs/seal-core": "0.5.1"
19
19
  },
20
20
  "optionalDependencies": {
21
- "@carchalabs/seal-hwid": "0.4.1"
21
+ "@carchalabs/seal-hwid": "0.5.0"
22
22
  },
23
23
  "engines": { "node": ">=22.13" },
24
24
  "publishConfig": { "access": "public" },