@camstack/system 1.2.355 → 1.2.357

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 (73) hide show
  1. package/dist/addon-runner.js +1 -1
  2. package/dist/addon-runner.mjs +1 -1
  3. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.js +1 -1
  4. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.mjs +1 -1
  5. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.js +1 -1
  6. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.mjs +1 -1
  7. package/dist/builtins/alerts/alerts.addon.js +1 -1
  8. package/dist/builtins/alerts/alerts.addon.mjs +1 -1
  9. package/dist/builtins/autotrack/index.js +1 -1
  10. package/dist/builtins/autotrack/index.mjs +1 -1
  11. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.js +1 -1
  12. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.mjs +1 -1
  13. package/dist/builtins/calendars/calendars.addon.js +1 -1
  14. package/dist/builtins/calendars/calendars.addon.mjs +1 -1
  15. package/dist/builtins/camera-grid/grid-activity-mirror.d.ts +12 -2
  16. package/dist/builtins/camera-grid/index.js +15 -3
  17. package/dist/builtins/camera-grid/index.mjs +15 -3
  18. package/dist/builtins/composer/composer.addon.js +2 -2
  19. package/dist/builtins/composer/composer.addon.mjs +2 -2
  20. package/dist/builtins/console-logging/index.js +1 -1
  21. package/dist/builtins/console-logging/index.mjs +1 -1
  22. package/dist/builtins/core-blocks/core-blocks.addon.js +2 -2
  23. package/dist/builtins/core-blocks/core-blocks.addon.mjs +2 -2
  24. package/dist/builtins/device-manager/device-manager.addon.js +2 -2
  25. package/dist/builtins/device-manager/device-manager.addon.mjs +2 -2
  26. package/dist/builtins/hub-forwarder/index.js +1 -1
  27. package/dist/builtins/hub-forwarder/index.mjs +1 -1
  28. package/dist/builtins/liveness-monitor/liveness-monitor.addon.js +1 -1
  29. package/dist/builtins/liveness-monitor/liveness-monitor.addon.mjs +1 -1
  30. package/dist/builtins/local-auth/local-auth.addon.js +1 -1
  31. package/dist/builtins/local-auth/local-auth.addon.mjs +1 -1
  32. package/dist/builtins/local-network/local-network.addon.d.ts +7 -0
  33. package/dist/builtins/local-network/local-network.addon.js +43 -24
  34. package/dist/builtins/local-network/local-network.addon.mjs +43 -24
  35. package/dist/builtins/loki-logging/index.js +1 -1
  36. package/dist/builtins/loki-logging/index.mjs +1 -1
  37. package/dist/builtins/native-metrics/native-metrics.addon.js +1 -1
  38. package/dist/builtins/native-metrics/native-metrics.addon.mjs +1 -1
  39. package/dist/builtins/platform-probe/index.js +1 -1
  40. package/dist/builtins/platform-probe/index.mjs +1 -1
  41. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.js +1 -1
  42. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.mjs +1 -1
  43. package/dist/builtins/snapshot/index.js +1 -1
  44. package/dist/builtins/snapshot/index.mjs +1 -1
  45. package/dist/builtins/sqlite-storage/filesystem-storage.addon.js +1 -1
  46. package/dist/builtins/sqlite-storage/filesystem-storage.addon.mjs +1 -1
  47. package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +0 -0
  48. package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +0 -0
  49. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +1 -1
  50. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +1 -1
  51. package/dist/builtins/system-config/system-config.addon.js +1 -1
  52. package/dist/builtins/system-config/system-config.addon.mjs +1 -1
  53. package/dist/builtins/winston-logging/index.js +1 -1
  54. package/dist/builtins/winston-logging/index.mjs +1 -1
  55. package/dist/{child-cap-dispatch-DMx2axev.mjs → child-cap-dispatch-BcRHQBKW.mjs} +1 -1
  56. package/dist/{child-cap-dispatch-CqV-7-QZ.js → child-cap-dispatch-wbIQJctP.js} +1 -1
  57. package/dist/{composition-sources-Dpl1G0Gr.mjs → composition-sources-BBfqepyT.mjs} +1 -1
  58. package/dist/{composition-sources-Bk-MjK1D.js → composition-sources-CeO0K2cl.js} +1 -1
  59. package/dist/{dist-OCi0K-iM.mjs → dist-B7WRgEZY.mjs} +10 -1
  60. package/dist/{dist-BWicEEVA.js → dist-CFVOy8VT.js} +10 -1
  61. package/dist/index.js +22 -11
  62. package/dist/index.mjs +11 -12
  63. package/dist/kernel/config-schema.d.ts +0 -2
  64. package/dist/{retired-settings-keys-DLJ7MvIB.js → retired-settings-keys-CRlTzxaS.js} +1 -1
  65. package/dist/{retired-settings-keys-TDzxE6GA.mjs → retired-settings-keys-DKRjfz0U.mjs} +1 -1
  66. package/dist/tls/boot-tls.d.ts +29 -0
  67. package/dist/tls/https-listener.d.ts +30 -0
  68. package/dist/tls/index.d.ts +4 -1
  69. package/dist/tls/tls-actions.d.ts +29 -0
  70. package/dist/tls/tls-status.d.ts +12 -1
  71. package/dist/{tls-ue_Xefmv.js → tls-CCMX7MjX.js} +319 -54
  72. package/dist/{tls-DTlVWwAI.mjs → tls-CJHogwTl.mjs} +246 -53
  73. package/package.json +1 -1
@@ -1,5 +1,8 @@
1
1
  export { CA_VALIDITY_DAYS, type CertEvaluationInput, LEAF_RENEWAL_WINDOW_DAYS, SERVER_AUTH_OID, } from './cert-evaluation.js';
2
2
  export { type CertIdentity, type CertOptions, type HostFacts, type CertRegenerationReason, collectCertIdentity, type EnsureTlsResult, ensureTlsCert, reissueTlsLeaf, evaluateExistingCert, loadTlsCert, MAX_LEAF_VALIDITY_DAYS, type TlsCertPair, } from './cert-manager.js';
3
+ export { type BootTls, type BootTlsMode, prepareBootTls } from './boot-tls.js';
4
+ export { clearHttpsListener, type HttpsReloadOutcome, leafFingerprintOf, registerHttpsListener, reloadHttpsListener, servedHttpsLeafFingerprint, } from './https-listener.js';
5
+ export { applyCertificateToListener, regenerateCertificate, revertToGeneratedCertificate, storeUploadedCertificate, type UploadCertificateInput, } from './tls-actions.js';
3
6
  export { CA_COMMON_NAME } from './cert-issue.js';
4
7
  export { validateUploadedTls, type UploadedTlsValidation } from './uploaded-cert.js';
5
- export { type TlsAccessStatus, readTlsAccessStatus, writeTlsMode, readTlsMode, readExtraSans, writeExtraSans, } from './tls-status.js';
8
+ export { type TlsAccessStatus, readTlsAccessStatus, splitPemCertificates, UPLOADED_CA_FILE, writeTlsMode, readTlsMode, readExtraSans, writeExtraSans, } from './tls-status.js';
@@ -0,0 +1,29 @@
1
+ import { CertOptions } from './cert-manager.js';
2
+ import { HttpsReloadOutcome } from './https-listener.js';
3
+ /**
4
+ * The certificate actions of Network → Local access (D726). Each one changes
5
+ * the files under `<dataDir>/tls` and then puts them on the listener, so the
6
+ * certificate the status reports is the certificate a client is served.
7
+ */
8
+ export interface UploadCertificateInput {
9
+ readonly certPem: string;
10
+ readonly keyPem: string;
11
+ readonly caPem?: string | undefined;
12
+ }
13
+ /**
14
+ * Store an operator's certificate. The leaf (or chain) and key replace the
15
+ * served pair; a CA, when given, goes to its own file. The local CA
16
+ * (`camstack-ca.crt` + key) is never touched — it did not sign the upload,
17
+ * and it is what "Revert to generated" and every device that trusts it
18
+ * still need.
19
+ */
20
+ export declare function storeUploadedCertificate(dataDir: string, input: UploadCertificateInput): Promise<void>;
21
+ /** Issue a new generated leaf under the local CA. Refused while an upload is served. */
22
+ export declare function regenerateCertificate(dataDir: string, options: CertOptions): Promise<void>;
23
+ /** Drop the upload and serve the generated local-CA leaf again (D227). */
24
+ export declare function revertToGeneratedCertificate(dataDir: string, options: CertOptions): Promise<void>;
25
+ /**
26
+ * Put the pair on disk on the HTTPS listener. Called after every action, so
27
+ * "the certificate changed" and "clients see it" are the same moment.
28
+ */
29
+ export declare function applyCertificateToListener(dataDir: string): HttpsReloadOutcome;
@@ -13,4 +13,15 @@ export declare function readTlsMode(dataDir: string): TlsMode;
13
13
  export declare function writeTlsMode(dataDir: string, mode: TlsMode): void;
14
14
  export declare function readExtraSans(dataDir: string): readonly string[];
15
15
  export declare function writeExtraSans(dataDir: string, sans: readonly string[]): void;
16
- export declare function readTlsAccessStatus(dataDir: string, restartRequired?: boolean): TlsAccessStatus;
16
+ /** Where an upload's own CA is kept — never over the local CA's `camstack-ca.crt`. */
17
+ export declare const UPLOADED_CA_FILE = "uploaded-ca.crt";
18
+ /** Every certificate in a PEM bundle, in order. */
19
+ export declare function splitPemCertificates(pem: string): readonly string[];
20
+ /**
21
+ * What the node's certificate is, and whether the listener serves it.
22
+ * `servedLeafFingerprint` is the leaf the HTTPS listener presents right now
23
+ * (`servedHttpsLeafFingerprint()`), or `null` when unknown; a restart is
24
+ * required exactly when the listener serves a different leaf than the one
25
+ * on disk — a measured fact, not a flag a mutation remembered to set.
26
+ */
27
+ export declare function readTlsAccessStatus(dataDir: string, servedLeafFingerprint: string | null): TlsAccessStatus;
@@ -11249,7 +11249,7 @@ var Pkcs10CertificateRequest = class extends PemData {
11249
11249
  _Pkcs10CertificateRequest_tbs = /* @__PURE__ */ new WeakMap(), _Pkcs10CertificateRequest_subjectName = /* @__PURE__ */ new WeakMap(), _Pkcs10CertificateRequest_subject = /* @__PURE__ */ new WeakMap(), _Pkcs10CertificateRequest_signatureAlgorithm = /* @__PURE__ */ new WeakMap(), _Pkcs10CertificateRequest_signature = /* @__PURE__ */ new WeakMap(), _Pkcs10CertificateRequest_publicKey = /* @__PURE__ */ new WeakMap(), _Pkcs10CertificateRequest_attributes = /* @__PURE__ */ new WeakMap(), _Pkcs10CertificateRequest_extensions = /* @__PURE__ */ new WeakMap();
11250
11250
  Pkcs10CertificateRequest.NAME = "PKCS#10 Certificate Request";
11251
11251
  var _X509Certificate_tbs, _X509Certificate_serialNumber, _X509Certificate_subjectName, _X509Certificate_subject, _X509Certificate_issuerName, _X509Certificate_issuer, _X509Certificate_notBefore, _X509Certificate_notAfter, _X509Certificate_signatureAlgorithm, _X509Certificate_signature, _X509Certificate_extensions, _X509Certificate_publicKey;
11252
- var X509Certificate$3 = class extends PemData {
11252
+ var X509Certificate$4 = class extends PemData {
11253
11253
  get publicKey() {
11254
11254
  if (!__classPrivateFieldGet(this, _X509Certificate_publicKey, "f")) __classPrivateFieldSet(this, _X509Certificate_publicKey, new PublicKey(this.asn.tbsCertificate.subjectPublicKeyInfo), "f");
11255
11255
  return __classPrivateFieldGet(this, _X509Certificate_publicKey, "f");
@@ -11449,7 +11449,7 @@ var X509Certificate$3 = class extends PemData {
11449
11449
  }
11450
11450
  };
11451
11451
  _X509Certificate_tbs = /* @__PURE__ */ new WeakMap(), _X509Certificate_serialNumber = /* @__PURE__ */ new WeakMap(), _X509Certificate_subjectName = /* @__PURE__ */ new WeakMap(), _X509Certificate_subject = /* @__PURE__ */ new WeakMap(), _X509Certificate_issuerName = /* @__PURE__ */ new WeakMap(), _X509Certificate_issuer = /* @__PURE__ */ new WeakMap(), _X509Certificate_notBefore = /* @__PURE__ */ new WeakMap(), _X509Certificate_notAfter = /* @__PURE__ */ new WeakMap(), _X509Certificate_signatureAlgorithm = /* @__PURE__ */ new WeakMap(), _X509Certificate_signature = /* @__PURE__ */ new WeakMap(), _X509Certificate_extensions = /* @__PURE__ */ new WeakMap(), _X509Certificate_publicKey = /* @__PURE__ */ new WeakMap();
11452
- X509Certificate$3.NAME = "Certificate";
11452
+ X509Certificate$4.NAME = "Certificate";
11453
11453
  function generateCertificateSerialNumber(input, crypto = cryptoProvider.get()) {
11454
11454
  const inputView = BufferSourceConverter.toUint8Array(Convert.FromHex(input || ""));
11455
11455
  let serialNumber = inputView && inputView.length && inputView.some((o) => o > 0) ? new Uint8Array(inputView) : void 0;
@@ -11530,7 +11530,7 @@ var X509CertificateGenerator = class {
11530
11530
  }
11531
11531
  if (!asnSignature) throw Error("Cannot convert ASN.1 signature value to WebCrypto format");
11532
11532
  asnX509.signatureValue = asnSignature;
11533
- return new X509Certificate$3(AsnConvert.serialize(asnX509));
11533
+ return new X509Certificate$4(AsnConvert.serialize(asnX509));
11534
11534
  }
11535
11535
  };
11536
11536
  var X509CrlReason;
@@ -11646,7 +11646,7 @@ async function loadCa(certPem, keyPem) {
11646
11646
  return {
11647
11647
  certPem,
11648
11648
  keyPem,
11649
- cert: new X509Certificate$3(certPem),
11649
+ cert: new X509Certificate$4(certPem),
11650
11650
  privateKey: await node_crypto.webcrypto.subtle.importKey("pkcs8", PemConverter.decodeFirst(keyPem), SIGNING_ALGORITHM, true, ["sign"])
11651
11651
  };
11652
11652
  }
@@ -11862,45 +11862,6 @@ function loadTlsCert(certPath, keyPath) {
11862
11862
  };
11863
11863
  }
11864
11864
  //#endregion
11865
- //#region src/tls/uploaded-cert.ts
11866
- /**
11867
- * Operator-supplied material must parse, match its key, and carry
11868
- * `extendedKeyUsage = serverAuth` — the same leaf defects D227 refused
11869
- * to keep generating.
11870
- */
11871
- function validateUploadedTls(certPem, keyPem) {
11872
- let leaf;
11873
- try {
11874
- leaf = new node_crypto.X509Certificate(certPem);
11875
- } catch {
11876
- return {
11877
- ok: false,
11878
- error: "Certificate PEM could not be parsed."
11879
- };
11880
- }
11881
- try {
11882
- if (!leaf.checkPrivateKey((0, node_crypto.createPrivateKey)(keyPem))) return {
11883
- ok: false,
11884
- error: "Private key does not match the certificate."
11885
- };
11886
- } catch {
11887
- return {
11888
- ok: false,
11889
- error: "Private key PEM could not be parsed."
11890
- };
11891
- }
11892
- const eku = leaf.keyUsage;
11893
- if (eku === void 0 || !eku.includes("1.3.6.1.5.5.7.3.1")) return {
11894
- ok: false,
11895
- error: "Certificate is missing extendedKeyUsage = serverAuth."
11896
- };
11897
- return {
11898
- ok: true,
11899
- fingerprintSha256: leaf.fingerprint256,
11900
- validTo: new Date(leaf.validTo).toISOString()
11901
- };
11902
- }
11903
- //#endregion
11904
11865
  //#region src/tls/tls-status.ts
11905
11866
  function tlsDir(dataDir) {
11906
11867
  return (0, node_path.join)(dataDir, "tls");
@@ -11933,10 +11894,51 @@ function writeExtraSans(dataDir, sans) {
11933
11894
  (0, node_fs.mkdirSync)(tlsDir(dataDir), { recursive: true });
11934
11895
  (0, node_fs.writeFileSync)((0, node_path.join)(tlsDir(dataDir), "extra-sans.json"), `${JSON.stringify(sans)}\n`, "utf-8");
11935
11896
  }
11936
- function readTlsAccessStatus(dataDir, restartRequired = false) {
11897
+ /** Where an upload's own CA is kept — never over the local CA's `camstack-ca.crt`. */
11898
+ var UPLOADED_CA_FILE = "uploaded-ca.crt";
11899
+ /** Every certificate in a PEM bundle, in order. */
11900
+ function splitPemCertificates(pem) {
11901
+ const blocks = pem.match(/-----BEGIN CERTIFICATE-----[\s\S]+?-----END CERTIFICATE-----/g);
11902
+ return blocks === null ? [] : blocks.map((block) => `${block}\n`);
11903
+ }
11904
+ /**
11905
+ * The certificate that signed `leaf`, from the upload itself: the CA the
11906
+ * operator supplied, else the rest of the uploaded chain. `null` when the
11907
+ * upload did not include it — and then nothing may stand in for it: the
11908
+ * local CA did not sign an uploaded leaf, so handing it out as "the CA" would
11909
+ * tell a client to trust a root that verifies nothing it is served.
11910
+ */
11911
+ function uploadedIssuer(dir, leaf, chainPem) {
11912
+ const caPath = (0, node_path.join)(dir, UPLOADED_CA_FILE);
11913
+ const candidates = [...(0, node_fs.existsSync)(caPath) ? splitPemCertificates((0, node_fs.readFileSync)(caPath, "utf-8")) : [], ...splitPemCertificates(chainPem).slice(1)];
11914
+ for (const pem of candidates) try {
11915
+ const cert = new node_crypto.X509Certificate(pem);
11916
+ if (leaf.checkIssued(cert) && leaf.verify(cert.publicKey)) return {
11917
+ pem,
11918
+ cert
11919
+ };
11920
+ } catch {}
11921
+ return null;
11922
+ }
11923
+ function generatedIssuer(dir) {
11924
+ const caPath = (0, node_path.join)(dir, "camstack-ca.crt");
11925
+ if (!(0, node_fs.existsSync)(caPath)) return null;
11926
+ const pem = (0, node_fs.readFileSync)(caPath, "utf-8");
11927
+ return {
11928
+ pem,
11929
+ cert: new node_crypto.X509Certificate(pem)
11930
+ };
11931
+ }
11932
+ /**
11933
+ * What the node's certificate is, and whether the listener serves it.
11934
+ * `servedLeafFingerprint` is the leaf the HTTPS listener presents right now
11935
+ * (`servedHttpsLeafFingerprint()`), or `null` when unknown; a restart is
11936
+ * required exactly when the listener serves a different leaf than the one
11937
+ * on disk — a measured fact, not a flag a mutation remembered to set.
11938
+ */
11939
+ function readTlsAccessStatus(dataDir, servedLeafFingerprint) {
11937
11940
  const dir = tlsDir(dataDir);
11938
11941
  const certPath = (0, node_path.join)(dir, "camstack.crt");
11939
- const caPath = (0, node_path.join)(dir, "camstack-ca.crt");
11940
11942
  const mode = readTlsMode(dataDir);
11941
11943
  if (!(0, node_fs.existsSync)(certPath)) return {
11942
11944
  mode: "disabled",
@@ -11944,24 +11946,24 @@ function readTlsAccessStatus(dataDir, restartRequired = false) {
11944
11946
  caFingerprintSha256: null,
11945
11947
  validTo: null,
11946
11948
  sans: [],
11947
- caCertPem: (0, node_fs.existsSync)(caPath) ? (0, node_fs.readFileSync)(caPath, "utf-8") : null,
11949
+ caCertPem: (mode === "uploaded" ? null : generatedIssuer(dir))?.pem ?? null,
11948
11950
  reissueError: null,
11949
- restartRequired
11951
+ restartRequired: false
11950
11952
  };
11951
11953
  try {
11952
- const leaf = new node_crypto.X509Certificate((0, node_fs.readFileSync)(certPath));
11953
- const caPem = (0, node_fs.existsSync)(caPath) ? (0, node_fs.readFileSync)(caPath, "utf-8") : null;
11954
- const ca = caPem !== null ? new node_crypto.X509Certificate(caPem) : null;
11954
+ const chainPem = (0, node_fs.readFileSync)(certPath, "utf-8");
11955
+ const leaf = new node_crypto.X509Certificate(chainPem);
11956
+ const ca = mode === "uploaded" ? uploadedIssuer(dir, leaf, chainPem) : generatedIssuer(dir);
11955
11957
  const san = leaf.subjectAltName ?? "";
11956
11958
  return {
11957
11959
  mode,
11958
11960
  leafFingerprintSha256: leaf.fingerprint256,
11959
- caFingerprintSha256: ca?.fingerprint256 ?? null,
11961
+ caFingerprintSha256: ca?.cert.fingerprint256 ?? null,
11960
11962
  validTo: new Date(leaf.validTo).toISOString(),
11961
11963
  sans: san === "" ? [] : san.split(", "),
11962
- caCertPem: caPem,
11964
+ caCertPem: ca?.pem ?? null,
11963
11965
  reissueError: null,
11964
- restartRequired
11966
+ restartRequired: servedLeafFingerprint !== null && servedLeafFingerprint !== leaf.fingerprint256
11965
11967
  };
11966
11968
  } catch {
11967
11969
  return {
@@ -11972,11 +11974,202 @@ function readTlsAccessStatus(dataDir, restartRequired = false) {
11972
11974
  sans: [],
11973
11975
  caCertPem: null,
11974
11976
  reissueError: "Certificate on disk could not be parsed.",
11975
- restartRequired
11977
+ restartRequired: false
11976
11978
  };
11977
11979
  }
11978
11980
  }
11979
11981
  //#endregion
11982
+ //#region src/tls/boot-tls.ts
11983
+ /**
11984
+ * Decide what the listener serves at boot. The admin UI (Network → Local
11985
+ * access) is the only way to set a certificate (D726): it either leaves the
11986
+ * hub on its generated local-CA leaf (D227) or uploads one, recorded by the
11987
+ * `tls/mode` marker. An uploaded certificate is served verbatim — running it
11988
+ * through `ensureTlsCert` would replace it on the first restart, because a
11989
+ * leaf the local root did not issue is one of that rule's reissue triggers.
11990
+ */
11991
+ async function prepareBootTls(dataDir, options) {
11992
+ const certPath = (0, node_path.join)(dataDir, "tls", "camstack.crt");
11993
+ const keyPath = (0, node_path.join)(dataDir, "tls", "camstack.key");
11994
+ const marked = readTlsMode(dataDir);
11995
+ if (marked === "uploaded" && (0, node_fs.existsSync)(certPath) && (0, node_fs.existsSync)(keyPath)) return {
11996
+ mode: "uploaded",
11997
+ pair: loadTlsCert(certPath, keyPath),
11998
+ ensured: void 0,
11999
+ uploadedMissing: false
12000
+ };
12001
+ const uploadedMissing = marked === "uploaded";
12002
+ if (uploadedMissing) writeTlsMode(dataDir, "generated");
12003
+ const ensured = await ensureTlsCert(dataDir, options);
12004
+ return {
12005
+ mode: "generated",
12006
+ pair: loadTlsCert(ensured.certPath, ensured.keyPath),
12007
+ ensured,
12008
+ uploadedMissing
12009
+ };
12010
+ }
12011
+ //#endregion
12012
+ //#region src/tls/https-listener.ts
12013
+ var SLOT_KEY = Symbol.for("camstack.tls.https-listener");
12014
+ var EMPTY_SLOT = {
12015
+ reload: null,
12016
+ servedLeafFingerprint: null
12017
+ };
12018
+ function isSlot(value) {
12019
+ if (typeof value !== "object" || value === null) return false;
12020
+ if (!("reload" in value) || !("servedLeafFingerprint" in value)) return false;
12021
+ const reload = value.reload;
12022
+ const served = value.servedLeafFingerprint;
12023
+ return (reload === null || typeof reload === "function") && (served === null || typeof served === "string");
12024
+ }
12025
+ function readSlot() {
12026
+ const current = Reflect.get(globalThis, SLOT_KEY);
12027
+ return isSlot(current) ? current : EMPTY_SLOT;
12028
+ }
12029
+ function writeSlot(next) {
12030
+ Reflect.set(globalThis, SLOT_KEY, next);
12031
+ }
12032
+ /** SHA-256 fingerprint of the first certificate (the leaf) in a PEM chain. */
12033
+ function leafFingerprintOf(chain) {
12034
+ return new node_crypto.X509Certificate(chain).fingerprint256;
12035
+ }
12036
+ /**
12037
+ * Called once by the server after its HTTPS listener is up, with the pair it
12038
+ * was started on. From here on a certificate action swaps the served
12039
+ * certificate in place (`setSecureContext`) — new handshakes get the new
12040
+ * leaf, open connections keep theirs, nothing restarts.
12041
+ */
12042
+ function registerHttpsListener(server, pair) {
12043
+ writeSlot({
12044
+ reload: (next) => server.setSecureContext({
12045
+ cert: next.cert,
12046
+ key: next.key
12047
+ }),
12048
+ servedLeafFingerprint: leafFingerprintOf(pair.cert)
12049
+ });
12050
+ }
12051
+ /** Forget the listener — on shutdown, and between specs. */
12052
+ function clearHttpsListener() {
12053
+ writeSlot(EMPTY_SLOT);
12054
+ }
12055
+ /**
12056
+ * The leaf the HTTPS listener serves now, or `null` when this process has no
12057
+ * registered listener (TLS off, or a process that does not own one) — in
12058
+ * which case nobody can say, and `null` is the honest answer.
12059
+ */
12060
+ function servedHttpsLeafFingerprint() {
12061
+ return readSlot().servedLeafFingerprint;
12062
+ }
12063
+ /** Serve `pair` from now on. Never throws: the outcome says what happened. */
12064
+ function reloadHttpsListener(pair) {
12065
+ const slot = readSlot();
12066
+ if (slot.reload === null) return { kind: "no-listener" };
12067
+ try {
12068
+ const servedLeafFingerprint = leafFingerprintOf(pair.cert);
12069
+ slot.reload(pair);
12070
+ writeSlot({
12071
+ reload: slot.reload,
12072
+ servedLeafFingerprint
12073
+ });
12074
+ return {
12075
+ kind: "reloaded",
12076
+ servedLeafFingerprint
12077
+ };
12078
+ } catch (err) {
12079
+ return {
12080
+ kind: "failed",
12081
+ error: err instanceof Error ? err.message : String(err)
12082
+ };
12083
+ }
12084
+ }
12085
+ //#endregion
12086
+ //#region src/tls/uploaded-cert.ts
12087
+ /**
12088
+ * Operator-supplied material must parse, match its key, and carry
12089
+ * `extendedKeyUsage = serverAuth` — the same leaf defects D227 refused
12090
+ * to keep generating.
12091
+ */
12092
+ function validateUploadedTls(certPem, keyPem) {
12093
+ let leaf;
12094
+ try {
12095
+ leaf = new node_crypto.X509Certificate(certPem);
12096
+ } catch {
12097
+ return {
12098
+ ok: false,
12099
+ error: "Certificate PEM could not be parsed."
12100
+ };
12101
+ }
12102
+ try {
12103
+ if (!leaf.checkPrivateKey((0, node_crypto.createPrivateKey)(keyPem))) return {
12104
+ ok: false,
12105
+ error: "Private key does not match the certificate."
12106
+ };
12107
+ } catch {
12108
+ return {
12109
+ ok: false,
12110
+ error: "Private key PEM could not be parsed."
12111
+ };
12112
+ }
12113
+ const eku = leaf.keyUsage;
12114
+ if (eku === void 0 || !eku.includes("1.3.6.1.5.5.7.3.1")) return {
12115
+ ok: false,
12116
+ error: "Certificate is missing extendedKeyUsage = serverAuth."
12117
+ };
12118
+ return {
12119
+ ok: true,
12120
+ fingerprintSha256: leaf.fingerprint256,
12121
+ validTo: new Date(leaf.validTo).toISOString()
12122
+ };
12123
+ }
12124
+ //#endregion
12125
+ //#region src/tls/tls-actions.ts
12126
+ function withNewline(pem) {
12127
+ return pem.endsWith("\n") ? pem : `${pem}\n`;
12128
+ }
12129
+ /**
12130
+ * Store an operator's certificate. The leaf (or chain) and key replace the
12131
+ * served pair; a CA, when given, goes to its own file. The local CA
12132
+ * (`camstack-ca.crt` + key) is never touched — it did not sign the upload,
12133
+ * and it is what "Revert to generated" and every device that trusts it
12134
+ * still need.
12135
+ */
12136
+ async function storeUploadedCertificate(dataDir, input) {
12137
+ const valid = validateUploadedTls(input.certPem, input.keyPem);
12138
+ if (!valid.ok) throw new Error(valid.error);
12139
+ const dir = (0, node_path.join)(dataDir, "tls");
12140
+ await (0, node_fs_promises.mkdir)(dir, { recursive: true });
12141
+ await (0, node_fs_promises.writeFile)((0, node_path.join)(dir, "camstack.crt"), withNewline(input.certPem));
12142
+ await (0, node_fs_promises.writeFile)((0, node_path.join)(dir, "camstack.key"), withNewline(input.keyPem), { mode: 384 });
12143
+ const caPath = (0, node_path.join)(dir, UPLOADED_CA_FILE);
12144
+ if (input.caPem !== void 0 && input.caPem.trim() !== "") await (0, node_fs_promises.writeFile)(caPath, withNewline(input.caPem));
12145
+ else (0, node_fs.rmSync)(caPath, { force: true });
12146
+ writeTlsMode(dataDir, "uploaded");
12147
+ }
12148
+ /** Issue a new generated leaf under the local CA. Refused while an upload is served. */
12149
+ async function regenerateCertificate(dataDir, options) {
12150
+ if (readTlsMode(dataDir) === "uploaded") throw new Error("Cannot regenerate an uploaded certificate — revert to generated first.");
12151
+ await reissueTlsLeaf(dataDir, options);
12152
+ }
12153
+ /** Drop the upload and serve the generated local-CA leaf again (D227). */
12154
+ async function revertToGeneratedCertificate(dataDir, options) {
12155
+ writeTlsMode(dataDir, "generated");
12156
+ (0, node_fs.rmSync)((0, node_path.join)(dataDir, "tls", UPLOADED_CA_FILE), { force: true });
12157
+ await ensureTlsCert(dataDir, options);
12158
+ }
12159
+ /**
12160
+ * Put the pair on disk on the HTTPS listener. Called after every action, so
12161
+ * "the certificate changed" and "clients see it" are the same moment.
12162
+ */
12163
+ function applyCertificateToListener(dataDir) {
12164
+ const certPath = (0, node_path.join)(dataDir, "tls", "camstack.crt");
12165
+ const keyPath = (0, node_path.join)(dataDir, "tls", "camstack.key");
12166
+ if (!(0, node_fs.existsSync)(certPath) || !(0, node_fs.existsSync)(keyPath)) return {
12167
+ kind: "failed",
12168
+ error: `No certificate pair under ${(0, node_path.join)(dataDir, "tls")}`
12169
+ };
12170
+ return reloadHttpsListener(loadTlsCert(certPath, keyPath));
12171
+ }
12172
+ //#endregion
11980
12173
  Object.defineProperty(exports, "CA_COMMON_NAME", {
11981
12174
  enumerable: true,
11982
12175
  get: function() {
@@ -12013,12 +12206,24 @@ Object.defineProperty(exports, "SERVER_AUTH_OID", {
12013
12206
  return SERVER_AUTH_OID;
12014
12207
  }
12015
12208
  });
12209
+ Object.defineProperty(exports, "UPLOADED_CA_FILE", {
12210
+ enumerable: true,
12211
+ get: function() {
12212
+ return UPLOADED_CA_FILE;
12213
+ }
12214
+ });
12016
12215
  Object.defineProperty(exports, "allFamiliesListenHost", {
12017
12216
  enumerable: true,
12018
12217
  get: function() {
12019
12218
  return allFamiliesListenHost;
12020
12219
  }
12021
12220
  });
12221
+ Object.defineProperty(exports, "applyCertificateToListener", {
12222
+ enumerable: true,
12223
+ get: function() {
12224
+ return applyCertificateToListener;
12225
+ }
12226
+ });
12022
12227
  Object.defineProperty(exports, "applyLanHttp", {
12023
12228
  enumerable: true,
12024
12229
  get: function() {
@@ -12031,6 +12236,12 @@ Object.defineProperty(exports, "bindPendingLanHttp", {
12031
12236
  return bindPendingLanHttp;
12032
12237
  }
12033
12238
  });
12239
+ Object.defineProperty(exports, "clearHttpsListener", {
12240
+ enumerable: true,
12241
+ get: function() {
12242
+ return clearHttpsListener;
12243
+ }
12244
+ });
12034
12245
  Object.defineProperty(exports, "closeLanHttp", {
12035
12246
  enumerable: true,
12036
12247
  get: function() {
@@ -12055,12 +12266,24 @@ Object.defineProperty(exports, "evaluateExistingCert", {
12055
12266
  return evaluateExistingCert;
12056
12267
  }
12057
12268
  });
12269
+ Object.defineProperty(exports, "leafFingerprintOf", {
12270
+ enumerable: true,
12271
+ get: function() {
12272
+ return leafFingerprintOf;
12273
+ }
12274
+ });
12058
12275
  Object.defineProperty(exports, "loadTlsCert", {
12059
12276
  enumerable: true,
12060
12277
  get: function() {
12061
12278
  return loadTlsCert;
12062
12279
  }
12063
12280
  });
12281
+ Object.defineProperty(exports, "prepareBootTls", {
12282
+ enumerable: true,
12283
+ get: function() {
12284
+ return prepareBootTls;
12285
+ }
12286
+ });
12064
12287
  Object.defineProperty(exports, "readExtraSans", {
12065
12288
  enumerable: true,
12066
12289
  get: function() {
@@ -12085,6 +12308,18 @@ Object.defineProperty(exports, "readTlsMode", {
12085
12308
  return readTlsMode;
12086
12309
  }
12087
12310
  });
12311
+ Object.defineProperty(exports, "regenerateCertificate", {
12312
+ enumerable: true,
12313
+ get: function() {
12314
+ return regenerateCertificate;
12315
+ }
12316
+ });
12317
+ Object.defineProperty(exports, "registerHttpsListener", {
12318
+ enumerable: true,
12319
+ get: function() {
12320
+ return registerHttpsListener;
12321
+ }
12322
+ });
12088
12323
  Object.defineProperty(exports, "registerLanHttpHandler", {
12089
12324
  enumerable: true,
12090
12325
  get: function() {
@@ -12097,6 +12332,36 @@ Object.defineProperty(exports, "reissueTlsLeaf", {
12097
12332
  return reissueTlsLeaf;
12098
12333
  }
12099
12334
  });
12335
+ Object.defineProperty(exports, "reloadHttpsListener", {
12336
+ enumerable: true,
12337
+ get: function() {
12338
+ return reloadHttpsListener;
12339
+ }
12340
+ });
12341
+ Object.defineProperty(exports, "revertToGeneratedCertificate", {
12342
+ enumerable: true,
12343
+ get: function() {
12344
+ return revertToGeneratedCertificate;
12345
+ }
12346
+ });
12347
+ Object.defineProperty(exports, "servedHttpsLeafFingerprint", {
12348
+ enumerable: true,
12349
+ get: function() {
12350
+ return servedHttpsLeafFingerprint;
12351
+ }
12352
+ });
12353
+ Object.defineProperty(exports, "splitPemCertificates", {
12354
+ enumerable: true,
12355
+ get: function() {
12356
+ return splitPemCertificates;
12357
+ }
12358
+ });
12359
+ Object.defineProperty(exports, "storeUploadedCertificate", {
12360
+ enumerable: true,
12361
+ get: function() {
12362
+ return storeUploadedCertificate;
12363
+ }
12364
+ });
12100
12365
  Object.defineProperty(exports, "validateUploadedTls", {
12101
12366
  enumerable: true,
12102
12367
  get: function() {