@antondziuin/talos-sdk 0.1.0 → 0.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 +87 -33
- package/dist/index.cjs +326 -13
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +177 -1
- package/dist/index.d.ts +177 -1
- package/dist/index.js +318 -8
- package/dist/index.js.map +1 -1
- package/package.json +2 -3
package/dist/index.d.cts
CHANGED
|
@@ -1,5 +1,46 @@
|
|
|
1
1
|
import { KeyObject } from 'node:crypto';
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* The air-gapped exchange, and the identity that makes it possible.
|
|
5
|
+
*
|
|
6
|
+
* Online, a machine proves who it is with the `activation_secret` the server
|
|
7
|
+
* hands back from `activate`. A machine that cannot reach a server never gets
|
|
8
|
+
* one — so it makes its own: an Ed25519 pair at the first offline request,
|
|
9
|
+
* public half in `talos-request.json`, private half never leaving this disk. The
|
|
10
|
+
* file the portal issues is bound to that key as well as to the fingerprint, and
|
|
11
|
+
* anything this machine sends back afterwards is signed by it.
|
|
12
|
+
*
|
|
13
|
+
* The document format deliberately is **not** Talos-compact. That format's `typ`
|
|
14
|
+
* set is the closed list of things the *platform* signs, and putting a machine's
|
|
15
|
+
* key into the same namespace as a product's is exactly the confusion the tag
|
|
16
|
+
* set exists to prevent. What is copied is the part that matters: the payload
|
|
17
|
+
* travels base64url-encoded and is verified as received, never re-serialised, so
|
|
18
|
+
* three implementations cannot disagree about JSON key order and turn that into
|
|
19
|
+
* a signature one of them rejects.
|
|
20
|
+
*/
|
|
21
|
+
/** Domain tag of `talos-request.json`; also part of what is signed. */
|
|
22
|
+
declare const REQUEST_DOMAIN = "talos-offline-request";
|
|
23
|
+
/**
|
|
24
|
+
* Domain tag of `talos-deactivation.json`, the document that hands a seat back.
|
|
25
|
+
*
|
|
26
|
+
* The second thing a machine key signs, and the reason the domain is inside the
|
|
27
|
+
* signed bytes rather than beside them: without it a captured request would be a
|
|
28
|
+
* valid receipt for the same machine, and returning a seat is not what its owner
|
|
29
|
+
* asked for when they asked for a licence.
|
|
30
|
+
*/
|
|
31
|
+
declare const RECEIPT_DOMAIN = "talos-offline-deactivation";
|
|
32
|
+
/** `kind` of the file the portal issues back. */
|
|
33
|
+
declare const LICENSE_FILE_KIND = "talos-offline-license";
|
|
34
|
+
/** A document this machine signed: what gets written to disk and carried. */
|
|
35
|
+
interface SignedDoc {
|
|
36
|
+
v: number;
|
|
37
|
+
kind: string;
|
|
38
|
+
/** base64url of the payload JSON. */
|
|
39
|
+
req: string;
|
|
40
|
+
/** base64url Ed25519 signature over `"<kind>.1.<req>"`. */
|
|
41
|
+
sig: string;
|
|
42
|
+
}
|
|
43
|
+
|
|
3
44
|
/**
|
|
4
45
|
* What a response has to offer this SDK.
|
|
5
46
|
*
|
|
@@ -428,6 +469,12 @@ declare class TalosClient {
|
|
|
428
469
|
/** Default instance id for floating leases: one per client object. */
|
|
429
470
|
private readonly instanceId;
|
|
430
471
|
current: LicenseState | null;
|
|
472
|
+
/**
|
|
473
|
+
* Return a local JSON support report without credentials, identifiers or
|
|
474
|
+
* exception text. Pass a caught error for classification. Makes no requests
|
|
475
|
+
* and does not change licensing state; `decision` is the last known result.
|
|
476
|
+
*/
|
|
477
|
+
createDiagnosticReport(error?: unknown): string;
|
|
431
478
|
private readonly listeners;
|
|
432
479
|
/** The newest release this client has already announced. */
|
|
433
480
|
private announcedReleaseSeq;
|
|
@@ -462,6 +509,15 @@ declare class TalosClient {
|
|
|
462
509
|
* construction, so this is the startup question: prompt for a licence key, or
|
|
463
510
|
* go straight to {@link validate}?
|
|
464
511
|
*/
|
|
512
|
+
/**
|
|
513
|
+
* Whether this install holds a licence for this machine.
|
|
514
|
+
*
|
|
515
|
+
* True for an online activation (the activation secret is held) **and** for an
|
|
516
|
+
* air-gapped install that has loaded a licence file. Widened rather than
|
|
517
|
+
* split, because what a host does with this is decide whether to ask the user
|
|
518
|
+
* for a licence key — and an air-gapped machine that already holds a valid
|
|
519
|
+
* file must not be asked for one it has no way to redeem.
|
|
520
|
+
*/
|
|
465
521
|
get isActivated(): boolean;
|
|
466
522
|
/** The server's id for this machine, once activated. */
|
|
467
523
|
get currentMachineId(): string | null;
|
|
@@ -528,6 +584,90 @@ declare class TalosClient {
|
|
|
528
584
|
* elsewhere.
|
|
529
585
|
*/
|
|
530
586
|
activate(licenseKey: string): Promise<LicenseState>;
|
|
587
|
+
/**
|
|
588
|
+
* This machine's own keypair, generated once and kept in the state file.
|
|
589
|
+
*
|
|
590
|
+
* The air-gapped counterpart of the activation secret: a machine that cannot
|
|
591
|
+
* reach a server is never issued one, so it makes an identity of its own and
|
|
592
|
+
* the vendor binds the file to it.
|
|
593
|
+
*/
|
|
594
|
+
private machineKey;
|
|
595
|
+
/**
|
|
596
|
+
* The binding as it stands, or an empty one to build on.
|
|
597
|
+
*
|
|
598
|
+
* An air-gapped install reaches its first write with nothing stored — no
|
|
599
|
+
* licence key, no activation secret, no machine id — so there has to be
|
|
600
|
+
* something for the offline fields to be added to.
|
|
601
|
+
*/
|
|
602
|
+
private bindingBase;
|
|
603
|
+
/**
|
|
604
|
+
* Write the request an air-gapped machine hands to its vendor.
|
|
605
|
+
*
|
|
606
|
+
* Save the returned object as `talos-request.json`, carry it to whoever sold
|
|
607
|
+
* the licence, and they upload it in the portal. What comes back is a
|
|
608
|
+
* `license.talos` for {@link loadOfflineLicense} — and only for *this*
|
|
609
|
+
* machine: the request carries the fingerprint and the public half of a
|
|
610
|
+
* keypair generated here, and the file is bound to both.
|
|
611
|
+
*
|
|
612
|
+
* Calling it twice produces two requests under the same identity. The vendor
|
|
613
|
+
* can issue against either; the second file supersedes the first without
|
|
614
|
+
* spending a second seat.
|
|
615
|
+
*
|
|
616
|
+
* ```ts
|
|
617
|
+
* writeFileSync("talos-request.json", JSON.stringify(talos.createOfflineRequest(), null, 2));
|
|
618
|
+
* ```
|
|
619
|
+
*
|
|
620
|
+
* @param licenseKey Optional. The operator picks the licence in the portal, so
|
|
621
|
+
* it is not needed — but when it is present the server checks it against the
|
|
622
|
+
* licence being issued against, which catches issuing from the wrong tab.
|
|
623
|
+
*/
|
|
624
|
+
createOfflineRequest(licenseKey?: string): SignedDoc;
|
|
625
|
+
/**
|
|
626
|
+
* Adopt an air-gapped licence file issued for this machine.
|
|
627
|
+
*
|
|
628
|
+
* Everything is verified locally, because there is nothing to ask: the
|
|
629
|
+
* anchor-signed keyset travels inside the file, the verdict is signed by a key
|
|
630
|
+
* that keyset lists, and the file names both halves of this machine's identity
|
|
631
|
+
* — its fingerprint and the public key it generated. The verdict is then kept,
|
|
632
|
+
* so later starts are licensed with no further ceremony.
|
|
633
|
+
*
|
|
634
|
+
* **Throws** only for a file that is not a licence: unreadable, not one of
|
|
635
|
+
* ours, addressed to another product, or a broken signature. A file that *is*
|
|
636
|
+
* one but cannot license this machine right now comes back as a verdict —
|
|
637
|
+
* `machine_mismatch` for another machine's file, `offline_file_expired` for
|
|
638
|
+
* one past its horizon, `expired` for a licence that has ended, and
|
|
639
|
+
* `clock_tampered` for a clock wound back behind what this install has already
|
|
640
|
+
* seen. Those are things the user can be told and can act on, and turning
|
|
641
|
+
* them into exceptions would make the launch path throw for a reason nobody
|
|
642
|
+
* can catch usefully.
|
|
643
|
+
*/
|
|
644
|
+
loadOfflineLicense(pathOrBuffer: string | Buffer | Uint8Array): Promise<LicenseState>;
|
|
645
|
+
/**
|
|
646
|
+
* Hand this machine's air-gapped seat back.
|
|
647
|
+
*
|
|
648
|
+
* Save the returned object as `talos-deactivation.json` and send it to the
|
|
649
|
+
* vendor; they upload it in the portal and the seat returns to the pool.
|
|
650
|
+
* Signed by the same key the licence file is bound to, which is what makes it
|
|
651
|
+
* worth anything to a server that has never spoken to this machine.
|
|
652
|
+
*
|
|
653
|
+
* **The licence is dropped here, before the document is returned** — so an
|
|
654
|
+
* application that writes the file, or loses it, or never sends it has still
|
|
655
|
+
* stopped using the seat it is giving up. A receipt proves intent, not
|
|
656
|
+
* destruction: the vendor's copy of the file cannot be recalled, and if this
|
|
657
|
+
* machine is later restored from a snapshot it keeps working until that file
|
|
658
|
+
* expires. That is the accepted cost, and it is why the horizon is short.
|
|
659
|
+
*
|
|
660
|
+
* Throws when this install holds no offline licence — there is nothing to hand
|
|
661
|
+
* back, and a document saying otherwise would be a lie the vendor acts on.
|
|
662
|
+
*/
|
|
663
|
+
createOfflineDeactivation(): SignedDoc;
|
|
664
|
+
/**
|
|
665
|
+
* Verify the offline file against this machine and this clock, and adopt the
|
|
666
|
+
* verdict. Runs on load and again on every {@link validate}, because the two
|
|
667
|
+
* things that can change afterwards — the clock and the calendar — are
|
|
668
|
+
* exactly what it checks.
|
|
669
|
+
*/
|
|
670
|
+
private adoptOfflineToken;
|
|
531
671
|
/**
|
|
532
672
|
* The heartbeat cadence the server's policy asks for, in seconds, taken from
|
|
533
673
|
* the last heartbeat's **signed** claims — so it cannot be lengthened in
|
|
@@ -912,6 +1052,42 @@ interface StoredBinding {
|
|
|
912
1052
|
version: string;
|
|
913
1053
|
releaseSeq: number;
|
|
914
1054
|
};
|
|
1055
|
+
/**
|
|
1056
|
+
* This machine's own Ed25519 private key (PKCS#8 DER, base64), generated at
|
|
1057
|
+
* its first offline request.
|
|
1058
|
+
*
|
|
1059
|
+
* The air-gapped counterpart of `activationSecret`, and it exists for the same
|
|
1060
|
+
* reason that one does: an install has to be able to prove it is the install a
|
|
1061
|
+
* decision was made for. It never leaves this file — only the public half
|
|
1062
|
+
* travels, inside `talos-request.json` — and losing the file means repeating
|
|
1063
|
+
* the ceremony with the vendor, because the identity has exactly one copy by
|
|
1064
|
+
* construction.
|
|
1065
|
+
*/
|
|
1066
|
+
machineKey?: string;
|
|
1067
|
+
/** The raw public half, standard base64. Checked against the file's `mpk`. */
|
|
1068
|
+
machinePublicKey?: string;
|
|
1069
|
+
/** The last verified `typ=offline` licence file. */
|
|
1070
|
+
offlineToken?: string;
|
|
1071
|
+
/**
|
|
1072
|
+
* The highest local clock ever seen while running on an offline file, in unix
|
|
1073
|
+
* seconds.
|
|
1074
|
+
*
|
|
1075
|
+
* Deliberately separate from {@link timeHighWater}, which holds *server*
|
|
1076
|
+
* times taken from signed claims. An air-gapped install sees exactly one
|
|
1077
|
+
* server time in its life — the `iat` of its file — so the server mark stops
|
|
1078
|
+
* moving, and nothing would notice a clock wound back to the day after
|
|
1079
|
+
* issuance to keep a year-long file alive forever.
|
|
1080
|
+
*
|
|
1081
|
+
* Mixing the two would be worse than useless: a machine whose clock ran fast
|
|
1082
|
+
* would inflate the server mark, and correcting the clock afterwards would
|
|
1083
|
+
* then read as tampering on an install that had done nothing wrong.
|
|
1084
|
+
*
|
|
1085
|
+
* What this bounds is **rollback, not freeze**. A user who stops their clock
|
|
1086
|
+
* keeps a file past its expiry; a user who winds it back is told
|
|
1087
|
+
* `clock_tampered`. That is the honest limit of enforcing a deadline against
|
|
1088
|
+
* a clock its owner controls, and it is the price of offline.
|
|
1089
|
+
*/
|
|
1090
|
+
offlineSeen?: number;
|
|
915
1091
|
}
|
|
916
1092
|
/**
|
|
917
1093
|
* Where the binding lives, per product, following each OS's convention.
|
|
@@ -982,4 +1158,4 @@ declare function verifyManifest(releaseKeys: Keys, expectedAud: string, token: s
|
|
|
982
1158
|
*/
|
|
983
1159
|
declare function verifyKeyset(anchorKey: KeyObject, expectedAud: string, token: string): Record<string, unknown>;
|
|
984
1160
|
|
|
985
|
-
export { type AcquireLeaseOptions, type CheckForUpdateOptions, type CheckInOptions, type DeltaOptions, type DownloadProgress, type DownloadProgressCallback, type FetchLike, type FetchResponseLike, type FingerprintData, type Lease, type LicenseListener, LicenseState, StateStore, type StoredBinding, TalosApiError, TalosClient, TalosError, type TalosEventMap, type TalosEventName, type TalosOptions, TalosSignatureError, type UpdateArtifact, type UpdateDelta, type UpdateDeltaTarget, type UpdateInfo, collectFingerprint, defaultStatePath, hashLicenseKey, publicKeyFromBase64, verifyAppliedUpdate, verifyKeyset, verifyManifest, verifyToken };
|
|
1161
|
+
export { type AcquireLeaseOptions, type CheckForUpdateOptions, type CheckInOptions, type DeltaOptions, type DownloadProgress, type DownloadProgressCallback, type FetchLike, type FetchResponseLike, type FingerprintData, LICENSE_FILE_KIND, type Lease, type LicenseListener, LicenseState, RECEIPT_DOMAIN, REQUEST_DOMAIN, type SignedDoc, StateStore, type StoredBinding, TalosApiError, TalosClient, TalosError, type TalosEventMap, type TalosEventName, type TalosOptions, TalosSignatureError, type UpdateArtifact, type UpdateDelta, type UpdateDeltaTarget, type UpdateInfo, collectFingerprint, defaultStatePath, hashLicenseKey, publicKeyFromBase64, verifyAppliedUpdate, verifyKeyset, verifyManifest, verifyToken };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,46 @@
|
|
|
1
1
|
import { KeyObject } from 'node:crypto';
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* The air-gapped exchange, and the identity that makes it possible.
|
|
5
|
+
*
|
|
6
|
+
* Online, a machine proves who it is with the `activation_secret` the server
|
|
7
|
+
* hands back from `activate`. A machine that cannot reach a server never gets
|
|
8
|
+
* one — so it makes its own: an Ed25519 pair at the first offline request,
|
|
9
|
+
* public half in `talos-request.json`, private half never leaving this disk. The
|
|
10
|
+
* file the portal issues is bound to that key as well as to the fingerprint, and
|
|
11
|
+
* anything this machine sends back afterwards is signed by it.
|
|
12
|
+
*
|
|
13
|
+
* The document format deliberately is **not** Talos-compact. That format's `typ`
|
|
14
|
+
* set is the closed list of things the *platform* signs, and putting a machine's
|
|
15
|
+
* key into the same namespace as a product's is exactly the confusion the tag
|
|
16
|
+
* set exists to prevent. What is copied is the part that matters: the payload
|
|
17
|
+
* travels base64url-encoded and is verified as received, never re-serialised, so
|
|
18
|
+
* three implementations cannot disagree about JSON key order and turn that into
|
|
19
|
+
* a signature one of them rejects.
|
|
20
|
+
*/
|
|
21
|
+
/** Domain tag of `talos-request.json`; also part of what is signed. */
|
|
22
|
+
declare const REQUEST_DOMAIN = "talos-offline-request";
|
|
23
|
+
/**
|
|
24
|
+
* Domain tag of `talos-deactivation.json`, the document that hands a seat back.
|
|
25
|
+
*
|
|
26
|
+
* The second thing a machine key signs, and the reason the domain is inside the
|
|
27
|
+
* signed bytes rather than beside them: without it a captured request would be a
|
|
28
|
+
* valid receipt for the same machine, and returning a seat is not what its owner
|
|
29
|
+
* asked for when they asked for a licence.
|
|
30
|
+
*/
|
|
31
|
+
declare const RECEIPT_DOMAIN = "talos-offline-deactivation";
|
|
32
|
+
/** `kind` of the file the portal issues back. */
|
|
33
|
+
declare const LICENSE_FILE_KIND = "talos-offline-license";
|
|
34
|
+
/** A document this machine signed: what gets written to disk and carried. */
|
|
35
|
+
interface SignedDoc {
|
|
36
|
+
v: number;
|
|
37
|
+
kind: string;
|
|
38
|
+
/** base64url of the payload JSON. */
|
|
39
|
+
req: string;
|
|
40
|
+
/** base64url Ed25519 signature over `"<kind>.1.<req>"`. */
|
|
41
|
+
sig: string;
|
|
42
|
+
}
|
|
43
|
+
|
|
3
44
|
/**
|
|
4
45
|
* What a response has to offer this SDK.
|
|
5
46
|
*
|
|
@@ -428,6 +469,12 @@ declare class TalosClient {
|
|
|
428
469
|
/** Default instance id for floating leases: one per client object. */
|
|
429
470
|
private readonly instanceId;
|
|
430
471
|
current: LicenseState | null;
|
|
472
|
+
/**
|
|
473
|
+
* Return a local JSON support report without credentials, identifiers or
|
|
474
|
+
* exception text. Pass a caught error for classification. Makes no requests
|
|
475
|
+
* and does not change licensing state; `decision` is the last known result.
|
|
476
|
+
*/
|
|
477
|
+
createDiagnosticReport(error?: unknown): string;
|
|
431
478
|
private readonly listeners;
|
|
432
479
|
/** The newest release this client has already announced. */
|
|
433
480
|
private announcedReleaseSeq;
|
|
@@ -462,6 +509,15 @@ declare class TalosClient {
|
|
|
462
509
|
* construction, so this is the startup question: prompt for a licence key, or
|
|
463
510
|
* go straight to {@link validate}?
|
|
464
511
|
*/
|
|
512
|
+
/**
|
|
513
|
+
* Whether this install holds a licence for this machine.
|
|
514
|
+
*
|
|
515
|
+
* True for an online activation (the activation secret is held) **and** for an
|
|
516
|
+
* air-gapped install that has loaded a licence file. Widened rather than
|
|
517
|
+
* split, because what a host does with this is decide whether to ask the user
|
|
518
|
+
* for a licence key — and an air-gapped machine that already holds a valid
|
|
519
|
+
* file must not be asked for one it has no way to redeem.
|
|
520
|
+
*/
|
|
465
521
|
get isActivated(): boolean;
|
|
466
522
|
/** The server's id for this machine, once activated. */
|
|
467
523
|
get currentMachineId(): string | null;
|
|
@@ -528,6 +584,90 @@ declare class TalosClient {
|
|
|
528
584
|
* elsewhere.
|
|
529
585
|
*/
|
|
530
586
|
activate(licenseKey: string): Promise<LicenseState>;
|
|
587
|
+
/**
|
|
588
|
+
* This machine's own keypair, generated once and kept in the state file.
|
|
589
|
+
*
|
|
590
|
+
* The air-gapped counterpart of the activation secret: a machine that cannot
|
|
591
|
+
* reach a server is never issued one, so it makes an identity of its own and
|
|
592
|
+
* the vendor binds the file to it.
|
|
593
|
+
*/
|
|
594
|
+
private machineKey;
|
|
595
|
+
/**
|
|
596
|
+
* The binding as it stands, or an empty one to build on.
|
|
597
|
+
*
|
|
598
|
+
* An air-gapped install reaches its first write with nothing stored — no
|
|
599
|
+
* licence key, no activation secret, no machine id — so there has to be
|
|
600
|
+
* something for the offline fields to be added to.
|
|
601
|
+
*/
|
|
602
|
+
private bindingBase;
|
|
603
|
+
/**
|
|
604
|
+
* Write the request an air-gapped machine hands to its vendor.
|
|
605
|
+
*
|
|
606
|
+
* Save the returned object as `talos-request.json`, carry it to whoever sold
|
|
607
|
+
* the licence, and they upload it in the portal. What comes back is a
|
|
608
|
+
* `license.talos` for {@link loadOfflineLicense} — and only for *this*
|
|
609
|
+
* machine: the request carries the fingerprint and the public half of a
|
|
610
|
+
* keypair generated here, and the file is bound to both.
|
|
611
|
+
*
|
|
612
|
+
* Calling it twice produces two requests under the same identity. The vendor
|
|
613
|
+
* can issue against either; the second file supersedes the first without
|
|
614
|
+
* spending a second seat.
|
|
615
|
+
*
|
|
616
|
+
* ```ts
|
|
617
|
+
* writeFileSync("talos-request.json", JSON.stringify(talos.createOfflineRequest(), null, 2));
|
|
618
|
+
* ```
|
|
619
|
+
*
|
|
620
|
+
* @param licenseKey Optional. The operator picks the licence in the portal, so
|
|
621
|
+
* it is not needed — but when it is present the server checks it against the
|
|
622
|
+
* licence being issued against, which catches issuing from the wrong tab.
|
|
623
|
+
*/
|
|
624
|
+
createOfflineRequest(licenseKey?: string): SignedDoc;
|
|
625
|
+
/**
|
|
626
|
+
* Adopt an air-gapped licence file issued for this machine.
|
|
627
|
+
*
|
|
628
|
+
* Everything is verified locally, because there is nothing to ask: the
|
|
629
|
+
* anchor-signed keyset travels inside the file, the verdict is signed by a key
|
|
630
|
+
* that keyset lists, and the file names both halves of this machine's identity
|
|
631
|
+
* — its fingerprint and the public key it generated. The verdict is then kept,
|
|
632
|
+
* so later starts are licensed with no further ceremony.
|
|
633
|
+
*
|
|
634
|
+
* **Throws** only for a file that is not a licence: unreadable, not one of
|
|
635
|
+
* ours, addressed to another product, or a broken signature. A file that *is*
|
|
636
|
+
* one but cannot license this machine right now comes back as a verdict —
|
|
637
|
+
* `machine_mismatch` for another machine's file, `offline_file_expired` for
|
|
638
|
+
* one past its horizon, `expired` for a licence that has ended, and
|
|
639
|
+
* `clock_tampered` for a clock wound back behind what this install has already
|
|
640
|
+
* seen. Those are things the user can be told and can act on, and turning
|
|
641
|
+
* them into exceptions would make the launch path throw for a reason nobody
|
|
642
|
+
* can catch usefully.
|
|
643
|
+
*/
|
|
644
|
+
loadOfflineLicense(pathOrBuffer: string | Buffer | Uint8Array): Promise<LicenseState>;
|
|
645
|
+
/**
|
|
646
|
+
* Hand this machine's air-gapped seat back.
|
|
647
|
+
*
|
|
648
|
+
* Save the returned object as `talos-deactivation.json` and send it to the
|
|
649
|
+
* vendor; they upload it in the portal and the seat returns to the pool.
|
|
650
|
+
* Signed by the same key the licence file is bound to, which is what makes it
|
|
651
|
+
* worth anything to a server that has never spoken to this machine.
|
|
652
|
+
*
|
|
653
|
+
* **The licence is dropped here, before the document is returned** — so an
|
|
654
|
+
* application that writes the file, or loses it, or never sends it has still
|
|
655
|
+
* stopped using the seat it is giving up. A receipt proves intent, not
|
|
656
|
+
* destruction: the vendor's copy of the file cannot be recalled, and if this
|
|
657
|
+
* machine is later restored from a snapshot it keeps working until that file
|
|
658
|
+
* expires. That is the accepted cost, and it is why the horizon is short.
|
|
659
|
+
*
|
|
660
|
+
* Throws when this install holds no offline licence — there is nothing to hand
|
|
661
|
+
* back, and a document saying otherwise would be a lie the vendor acts on.
|
|
662
|
+
*/
|
|
663
|
+
createOfflineDeactivation(): SignedDoc;
|
|
664
|
+
/**
|
|
665
|
+
* Verify the offline file against this machine and this clock, and adopt the
|
|
666
|
+
* verdict. Runs on load and again on every {@link validate}, because the two
|
|
667
|
+
* things that can change afterwards — the clock and the calendar — are
|
|
668
|
+
* exactly what it checks.
|
|
669
|
+
*/
|
|
670
|
+
private adoptOfflineToken;
|
|
531
671
|
/**
|
|
532
672
|
* The heartbeat cadence the server's policy asks for, in seconds, taken from
|
|
533
673
|
* the last heartbeat's **signed** claims — so it cannot be lengthened in
|
|
@@ -912,6 +1052,42 @@ interface StoredBinding {
|
|
|
912
1052
|
version: string;
|
|
913
1053
|
releaseSeq: number;
|
|
914
1054
|
};
|
|
1055
|
+
/**
|
|
1056
|
+
* This machine's own Ed25519 private key (PKCS#8 DER, base64), generated at
|
|
1057
|
+
* its first offline request.
|
|
1058
|
+
*
|
|
1059
|
+
* The air-gapped counterpart of `activationSecret`, and it exists for the same
|
|
1060
|
+
* reason that one does: an install has to be able to prove it is the install a
|
|
1061
|
+
* decision was made for. It never leaves this file — only the public half
|
|
1062
|
+
* travels, inside `talos-request.json` — and losing the file means repeating
|
|
1063
|
+
* the ceremony with the vendor, because the identity has exactly one copy by
|
|
1064
|
+
* construction.
|
|
1065
|
+
*/
|
|
1066
|
+
machineKey?: string;
|
|
1067
|
+
/** The raw public half, standard base64. Checked against the file's `mpk`. */
|
|
1068
|
+
machinePublicKey?: string;
|
|
1069
|
+
/** The last verified `typ=offline` licence file. */
|
|
1070
|
+
offlineToken?: string;
|
|
1071
|
+
/**
|
|
1072
|
+
* The highest local clock ever seen while running on an offline file, in unix
|
|
1073
|
+
* seconds.
|
|
1074
|
+
*
|
|
1075
|
+
* Deliberately separate from {@link timeHighWater}, which holds *server*
|
|
1076
|
+
* times taken from signed claims. An air-gapped install sees exactly one
|
|
1077
|
+
* server time in its life — the `iat` of its file — so the server mark stops
|
|
1078
|
+
* moving, and nothing would notice a clock wound back to the day after
|
|
1079
|
+
* issuance to keep a year-long file alive forever.
|
|
1080
|
+
*
|
|
1081
|
+
* Mixing the two would be worse than useless: a machine whose clock ran fast
|
|
1082
|
+
* would inflate the server mark, and correcting the clock afterwards would
|
|
1083
|
+
* then read as tampering on an install that had done nothing wrong.
|
|
1084
|
+
*
|
|
1085
|
+
* What this bounds is **rollback, not freeze**. A user who stops their clock
|
|
1086
|
+
* keeps a file past its expiry; a user who winds it back is told
|
|
1087
|
+
* `clock_tampered`. That is the honest limit of enforcing a deadline against
|
|
1088
|
+
* a clock its owner controls, and it is the price of offline.
|
|
1089
|
+
*/
|
|
1090
|
+
offlineSeen?: number;
|
|
915
1091
|
}
|
|
916
1092
|
/**
|
|
917
1093
|
* Where the binding lives, per product, following each OS's convention.
|
|
@@ -982,4 +1158,4 @@ declare function verifyManifest(releaseKeys: Keys, expectedAud: string, token: s
|
|
|
982
1158
|
*/
|
|
983
1159
|
declare function verifyKeyset(anchorKey: KeyObject, expectedAud: string, token: string): Record<string, unknown>;
|
|
984
1160
|
|
|
985
|
-
export { type AcquireLeaseOptions, type CheckForUpdateOptions, type CheckInOptions, type DeltaOptions, type DownloadProgress, type DownloadProgressCallback, type FetchLike, type FetchResponseLike, type FingerprintData, type Lease, type LicenseListener, LicenseState, StateStore, type StoredBinding, TalosApiError, TalosClient, TalosError, type TalosEventMap, type TalosEventName, type TalosOptions, TalosSignatureError, type UpdateArtifact, type UpdateDelta, type UpdateDeltaTarget, type UpdateInfo, collectFingerprint, defaultStatePath, hashLicenseKey, publicKeyFromBase64, verifyAppliedUpdate, verifyKeyset, verifyManifest, verifyToken };
|
|
1161
|
+
export { type AcquireLeaseOptions, type CheckForUpdateOptions, type CheckInOptions, type DeltaOptions, type DownloadProgress, type DownloadProgressCallback, type FetchLike, type FetchResponseLike, type FingerprintData, LICENSE_FILE_KIND, type Lease, type LicenseListener, LicenseState, RECEIPT_DOMAIN, REQUEST_DOMAIN, type SignedDoc, StateStore, type StoredBinding, TalosApiError, TalosClient, TalosError, type TalosEventMap, type TalosEventName, type TalosOptions, TalosSignatureError, type UpdateArtifact, type UpdateDelta, type UpdateDeltaTarget, type UpdateInfo, collectFingerprint, defaultStatePath, hashLicenseKey, publicKeyFromBase64, verifyAppliedUpdate, verifyKeyset, verifyManifest, verifyToken };
|