@scalebun-release/cli 2.5.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.
Files changed (210) hide show
  1. package/bin/scalebun.js +2 -0
  2. package/lib/api/client.d.ts +52 -0
  3. package/lib/api/client.d.ts.map +1 -0
  4. package/lib/api/client.js +335 -0
  5. package/lib/api/client.js.map +1 -0
  6. package/lib/commands/ci-init.d.ts +3 -0
  7. package/lib/commands/ci-init.d.ts.map +1 -0
  8. package/lib/commands/ci-init.js +348 -0
  9. package/lib/commands/ci-init.js.map +1 -0
  10. package/lib/commands/doctor.d.ts +31 -0
  11. package/lib/commands/doctor.d.ts.map +1 -0
  12. package/lib/commands/doctor.js +559 -0
  13. package/lib/commands/doctor.js.map +1 -0
  14. package/lib/commands/launch.d.ts +3 -0
  15. package/lib/commands/launch.d.ts.map +1 -0
  16. package/lib/commands/launch.js +23 -0
  17. package/lib/commands/launch.js.map +1 -0
  18. package/lib/commands/link.d.ts +16 -0
  19. package/lib/commands/link.d.ts.map +1 -0
  20. package/lib/commands/link.js +164 -0
  21. package/lib/commands/link.js.map +1 -0
  22. package/lib/commands/login.d.ts +12 -0
  23. package/lib/commands/login.d.ts.map +1 -0
  24. package/lib/commands/login.js +124 -0
  25. package/lib/commands/login.js.map +1 -0
  26. package/lib/commands/migrate-codepush.d.ts +13 -0
  27. package/lib/commands/migrate-codepush.d.ts.map +1 -0
  28. package/lib/commands/migrate-codepush.js +98 -0
  29. package/lib/commands/migrate-codepush.js.map +1 -0
  30. package/lib/commands/ota-bundle.d.ts +22 -0
  31. package/lib/commands/ota-bundle.d.ts.map +1 -0
  32. package/lib/commands/ota-bundle.js +253 -0
  33. package/lib/commands/ota-bundle.js.map +1 -0
  34. package/lib/commands/ota-channels.d.ts +3 -0
  35. package/lib/commands/ota-channels.d.ts.map +1 -0
  36. package/lib/commands/ota-channels.js +231 -0
  37. package/lib/commands/ota-channels.js.map +1 -0
  38. package/lib/commands/ota-delete-bundle.d.ts +16 -0
  39. package/lib/commands/ota-delete-bundle.d.ts.map +1 -0
  40. package/lib/commands/ota-delete-bundle.js +96 -0
  41. package/lib/commands/ota-delete-bundle.js.map +1 -0
  42. package/lib/commands/ota-inspect.d.ts +3 -0
  43. package/lib/commands/ota-inspect.d.ts.map +1 -0
  44. package/lib/commands/ota-inspect.js +258 -0
  45. package/lib/commands/ota-inspect.js.map +1 -0
  46. package/lib/commands/ota-keys.d.ts +91 -0
  47. package/lib/commands/ota-keys.d.ts.map +1 -0
  48. package/lib/commands/ota-keys.js +765 -0
  49. package/lib/commands/ota-keys.js.map +1 -0
  50. package/lib/commands/ota-promote.d.ts +3 -0
  51. package/lib/commands/ota-promote.d.ts.map +1 -0
  52. package/lib/commands/ota-promote.js +194 -0
  53. package/lib/commands/ota-promote.js.map +1 -0
  54. package/lib/commands/ota-publish.d.ts +26 -0
  55. package/lib/commands/ota-publish.d.ts.map +1 -0
  56. package/lib/commands/ota-publish.js +892 -0
  57. package/lib/commands/ota-publish.js.map +1 -0
  58. package/lib/commands/ota-rollout.d.ts +3 -0
  59. package/lib/commands/ota-rollout.d.ts.map +1 -0
  60. package/lib/commands/ota-rollout.js +265 -0
  61. package/lib/commands/ota-rollout.js.map +1 -0
  62. package/lib/commands/ota-sign.d.ts +19 -0
  63. package/lib/commands/ota-sign.d.ts.map +1 -0
  64. package/lib/commands/ota-sign.js +245 -0
  65. package/lib/commands/ota-sign.js.map +1 -0
  66. package/lib/commands/push-apns.d.ts +3 -0
  67. package/lib/commands/push-apns.d.ts.map +1 -0
  68. package/lib/commands/push-apns.js +166 -0
  69. package/lib/commands/push-apns.js.map +1 -0
  70. package/lib/commands/quickstart.d.ts +4 -0
  71. package/lib/commands/quickstart.d.ts.map +1 -0
  72. package/lib/commands/quickstart.js +672 -0
  73. package/lib/commands/quickstart.js.map +1 -0
  74. package/lib/commands/shell.d.ts +28 -0
  75. package/lib/commands/shell.d.ts.map +1 -0
  76. package/lib/commands/shell.js +512 -0
  77. package/lib/commands/shell.js.map +1 -0
  78. package/lib/commands/tls-pins.d.ts +27 -0
  79. package/lib/commands/tls-pins.d.ts.map +1 -0
  80. package/lib/commands/tls-pins.js +209 -0
  81. package/lib/commands/tls-pins.js.map +1 -0
  82. package/lib/index.d.ts +2 -0
  83. package/lib/index.d.ts.map +1 -0
  84. package/lib/index.js +233 -0
  85. package/lib/index.js.map +1 -0
  86. package/lib/ui/banner.d.ts +23 -0
  87. package/lib/ui/banner.d.ts.map +1 -0
  88. package/lib/ui/banner.js +162 -0
  89. package/lib/ui/banner.js.map +1 -0
  90. package/lib/ui/graphics.d.ts +18 -0
  91. package/lib/ui/graphics.d.ts.map +1 -0
  92. package/lib/ui/graphics.js +134 -0
  93. package/lib/ui/graphics.js.map +1 -0
  94. package/lib/ui/mark.d.ts +4 -0
  95. package/lib/ui/mark.d.ts.map +1 -0
  96. package/lib/ui/mark.js +21 -0
  97. package/lib/ui/mark.js.map +1 -0
  98. package/lib/ui/menu.d.ts +109 -0
  99. package/lib/ui/menu.d.ts.map +1 -0
  100. package/lib/ui/menu.js +314 -0
  101. package/lib/ui/menu.js.map +1 -0
  102. package/lib/ui/sixel.d.ts +6 -0
  103. package/lib/ui/sixel.d.ts.map +1 -0
  104. package/lib/ui/sixel.js +9 -0
  105. package/lib/ui/sixel.js.map +1 -0
  106. package/lib/utils/apps.d.ts +29 -0
  107. package/lib/utils/apps.d.ts.map +1 -0
  108. package/lib/utils/apps.js +19 -0
  109. package/lib/utils/apps.js.map +1 -0
  110. package/lib/utils/browser.d.ts +9 -0
  111. package/lib/utils/browser.d.ts.map +1 -0
  112. package/lib/utils/browser.js +39 -0
  113. package/lib/utils/browser.js.map +1 -0
  114. package/lib/utils/bundle.d.ts +153 -0
  115. package/lib/utils/bundle.d.ts.map +1 -0
  116. package/lib/utils/bundle.js +459 -0
  117. package/lib/utils/bundle.js.map +1 -0
  118. package/lib/utils/bundleStore.d.ts +128 -0
  119. package/lib/utils/bundleStore.d.ts.map +1 -0
  120. package/lib/utils/bundleStore.js +250 -0
  121. package/lib/utils/bundleStore.js.map +1 -0
  122. package/lib/utils/context.d.ts +88 -0
  123. package/lib/utils/context.d.ts.map +1 -0
  124. package/lib/utils/context.js +247 -0
  125. package/lib/utils/context.js.map +1 -0
  126. package/lib/utils/credentials.d.ts +59 -0
  127. package/lib/utils/credentials.d.ts.map +1 -0
  128. package/lib/utils/credentials.js +147 -0
  129. package/lib/utils/credentials.js.map +1 -0
  130. package/lib/utils/guide.d.ts +63 -0
  131. package/lib/utils/guide.d.ts.map +1 -0
  132. package/lib/utils/guide.js +191 -0
  133. package/lib/utils/guide.js.map +1 -0
  134. package/lib/utils/hash.d.ts +3 -0
  135. package/lib/utils/hash.d.ts.map +1 -0
  136. package/lib/utils/hash.js +44 -0
  137. package/lib/utils/hash.js.map +1 -0
  138. package/lib/utils/native-id.d.ts +17 -0
  139. package/lib/utils/native-id.d.ts.map +1 -0
  140. package/lib/utils/native-id.js +105 -0
  141. package/lib/utils/native-id.js.map +1 -0
  142. package/lib/utils/output.d.ts +96 -0
  143. package/lib/utils/output.d.ts.map +1 -0
  144. package/lib/utils/output.js +135 -0
  145. package/lib/utils/output.js.map +1 -0
  146. package/lib/utils/prompt.d.ts +106 -0
  147. package/lib/utils/prompt.d.ts.map +1 -0
  148. package/lib/utils/prompt.js +457 -0
  149. package/lib/utils/prompt.js.map +1 -0
  150. package/lib/utils/provenance.d.ts +30 -0
  151. package/lib/utils/provenance.d.ts.map +1 -0
  152. package/lib/utils/provenance.js +157 -0
  153. package/lib/utils/provenance.js.map +1 -0
  154. package/lib/utils/resolve.d.ts +81 -0
  155. package/lib/utils/resolve.d.ts.map +1 -0
  156. package/lib/utils/resolve.js +139 -0
  157. package/lib/utils/resolve.js.map +1 -0
  158. package/lib/utils/sdkBin.d.ts +18 -0
  159. package/lib/utils/sdkBin.d.ts.map +1 -0
  160. package/lib/utils/sdkBin.js +76 -0
  161. package/lib/utils/sdkBin.js.map +1 -0
  162. package/lib/utils/signer.d.ts +94 -0
  163. package/lib/utils/signer.d.ts.map +1 -0
  164. package/lib/utils/signer.js +230 -0
  165. package/lib/utils/signer.js.map +1 -0
  166. package/lib/utils/signing.d.ts +102 -0
  167. package/lib/utils/signing.d.ts.map +1 -0
  168. package/lib/utils/signing.js +229 -0
  169. package/lib/utils/signing.js.map +1 -0
  170. package/lib/utils/transportSecurity.d.ts +36 -0
  171. package/lib/utils/transportSecurity.d.ts.map +1 -0
  172. package/lib/utils/transportSecurity.js +74 -0
  173. package/lib/utils/transportSecurity.js.map +1 -0
  174. package/lib/utils/trustAnchor.d.ts +109 -0
  175. package/lib/utils/trustAnchor.d.ts.map +1 -0
  176. package/lib/utils/trustAnchor.js +169 -0
  177. package/lib/utils/trustAnchor.js.map +1 -0
  178. package/lib/utils/ui.d.ts +12 -0
  179. package/lib/utils/ui.d.ts.map +1 -0
  180. package/lib/utils/ui.js +60 -0
  181. package/lib/utils/ui.js.map +1 -0
  182. package/lib/utils/validate.d.ts +62 -0
  183. package/lib/utils/validate.d.ts.map +1 -0
  184. package/lib/utils/validate.js +189 -0
  185. package/lib/utils/validate.js.map +1 -0
  186. package/lib/vendor/ota-protocol/base64.d.ts +11 -0
  187. package/lib/vendor/ota-protocol/base64.d.ts.map +1 -0
  188. package/lib/vendor/ota-protocol/base64.js +39 -0
  189. package/lib/vendor/ota-protocol/base64.js.map +1 -0
  190. package/lib/vendor/ota-protocol/codec.d.ts +26 -0
  191. package/lib/vendor/ota-protocol/codec.d.ts.map +1 -0
  192. package/lib/vendor/ota-protocol/codec.js +349 -0
  193. package/lib/vendor/ota-protocol/codec.js.map +1 -0
  194. package/lib/vendor/ota-protocol/hex.d.ts +8 -0
  195. package/lib/vendor/ota-protocol/hex.d.ts.map +1 -0
  196. package/lib/vendor/ota-protocol/hex.js +31 -0
  197. package/lib/vendor/ota-protocol/hex.js.map +1 -0
  198. package/lib/vendor/ota-protocol/index.d.ts +17 -0
  199. package/lib/vendor/ota-protocol/index.d.ts.map +1 -0
  200. package/lib/vendor/ota-protocol/index.js +41 -0
  201. package/lib/vendor/ota-protocol/index.js.map +1 -0
  202. package/lib/vendor/ota-protocol/limits.d.ts +39 -0
  203. package/lib/vendor/ota-protocol/limits.d.ts.map +1 -0
  204. package/lib/vendor/ota-protocol/limits.js +42 -0
  205. package/lib/vendor/ota-protocol/limits.js.map +1 -0
  206. package/lib/vendor/ota-protocol/types.d.ts +73 -0
  207. package/lib/vendor/ota-protocol/types.d.ts.map +1 -0
  208. package/lib/vendor/ota-protocol/types.js +24 -0
  209. package/lib/vendor/ota-protocol/types.js.map +1 -0
  210. package/package.json +37 -0
@@ -0,0 +1,230 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.ExternalCommandSigner = exports.LocalPemSigner = void 0;
37
+ exports.resolveSigner = resolveSigner;
38
+ /**
39
+ * Where a release signature comes from.
40
+ *
41
+ * ── Why this exists ──────────────────────────────────────────────────────────
42
+ *
43
+ * Control T-1 of a customer's security assessment reads: "Signing key held in an
44
+ * HSM or approved key vault; signing only within controlled CI/CD." That is an
45
+ * obligation on THEM — we never hold the key — but until now our CLI could only
46
+ * sign with a PEM on disk, so a customer whose security policy requires an HSM
47
+ * had no way to use the product at all.
48
+ *
49
+ * ── Why an external COMMAND rather than an SDK per vault ─────────────────────
50
+ *
51
+ * A command covers AWS KMS, Google Cloud KMS, Azure Key Vault, PKCS#11, YubiHSM
52
+ * and whatever the customer has already standardised on, without this package
53
+ * taking a dependency on any of them. Every dependency a signing tool carries is
54
+ * a dependency someone has to audit forever, and the customers who need this are
55
+ * precisely the ones who will audit it.
56
+ *
57
+ * ── What does NOT change ─────────────────────────────────────────────────────
58
+ *
59
+ * The bytes that get signed, and the self-verify that happens afterwards. The
60
+ * canonical payload is serialized exactly as before, and the resulting signature
61
+ * is verified before it is returned, so a vault that produces an unusable
62
+ * signature fails on the build machine instead of on a million handsets.
63
+ *
64
+ * That check is the reason an external signer must ALSO supply the public key:
65
+ * with a local PEM the public half is derived from the private one, and we do
66
+ * not have the private one here. Skipping the verification instead would remove
67
+ * the guarantee exactly where it matters most — a remote signer is the case with
68
+ * the most ways to go quietly wrong.
69
+ */
70
+ const crypto = __importStar(require("crypto"));
71
+ const child_process_1 = require("child_process");
72
+ const fs = __importStar(require("fs"));
73
+ /** RSA-3072 produces a 384-byte signature. Anything else is not our algorithm. */
74
+ const RSA3072_SIGNATURE_BYTES = 384;
75
+ /** A remote signer that hangs must fail the build, not hold it open forever. */
76
+ const DEFAULT_TIMEOUT_MS = 60000;
77
+ /** Today's behaviour, unchanged: a PKCS#8 PEM on this machine. */
78
+ class LocalPemSigner {
79
+ constructor(privateKeyPem, passphrase) {
80
+ this.key = crypto.createPrivateKey(passphrase ? { key: privateKeyPem, passphrase } : privateKeyPem);
81
+ if (this.key.asymmetricKeyType !== 'rsa') {
82
+ throw new Error(`private key is ${this.key.asymmetricKeyType}, not RSA`);
83
+ }
84
+ }
85
+ async sign(bytes) {
86
+ return crypto.sign('sha256', Buffer.from(bytes), this.key);
87
+ }
88
+ async publicKey() {
89
+ return crypto.createPublicKey(this.key);
90
+ }
91
+ describe() {
92
+ return 'local PEM';
93
+ }
94
+ }
95
+ exports.LocalPemSigner = LocalPemSigner;
96
+ /**
97
+ * Sign by handing the payload to a command on stdin and reading the signature
98
+ * from stdout.
99
+ *
100
+ * The contract is deliberately the smallest one that can work:
101
+ *
102
+ * stdin the raw canonical payload bytes
103
+ * stdout the raw signature bytes, or the same base64-encoded
104
+ * exit 0 success; anything else is a failure and the build stops
105
+ *
106
+ * Base64 is accepted because most cloud KMS CLIs emit it by default, and
107
+ * requiring every customer to pipe through a decoder would be a footgun that
108
+ * buys nothing.
109
+ */
110
+ class ExternalCommandSigner {
111
+ // Assigned explicitly rather than as a parameter property: the test runner
112
+ // uses Node type-stripping, which does not support them.
113
+ constructor(cfg) {
114
+ this.cfg = cfg;
115
+ if (!cfg.command?.trim())
116
+ throw new Error('external signer: no command configured');
117
+ if (!cfg.publicKeyPem?.trim()) {
118
+ throw new Error('external signer: a public key is required. The signature is verified ' +
119
+ 'before it is used, and the public half cannot be derived from a key ' +
120
+ 'this machine does not hold.');
121
+ }
122
+ }
123
+ async sign(bytes) {
124
+ const out = await this.run(Buffer.from(bytes));
125
+ // Raw bytes of the right length, or base64 of the same.
126
+ if (out.length === RSA3072_SIGNATURE_BYTES)
127
+ return out;
128
+ const text = out.toString('utf8').trim();
129
+ if (/^[A-Za-z0-9+/=\s]+$/.test(text) && text.length > 0) {
130
+ const decoded = Buffer.from(text.replace(/\s+/g, ''), 'base64');
131
+ if (decoded.length === RSA3072_SIGNATURE_BYTES)
132
+ return decoded;
133
+ throw new Error(`external signer returned ${decoded.length} bytes after base64-decoding; ` +
134
+ `SB-OTA-RSA-SHA256-V1 expects ${RSA3072_SIGNATURE_BYTES} (RSA-3072)`);
135
+ }
136
+ throw new Error(`external signer returned ${out.length} bytes, which is neither a ` +
137
+ `${RSA3072_SIGNATURE_BYTES}-byte RSA-3072 signature nor base64 of one`);
138
+ }
139
+ async publicKey() {
140
+ if (this.pub)
141
+ return this.pub;
142
+ const raw = this.cfg.publicKeyPem.includes('-----BEGIN')
143
+ ? this.cfg.publicKeyPem
144
+ : fs.readFileSync(this.cfg.publicKeyPem, 'utf8');
145
+ const key = crypto.createPublicKey(raw);
146
+ if (key.asymmetricKeyType !== 'rsa') {
147
+ throw new Error(`external signer public key is ${key.asymmetricKeyType}, not RSA`);
148
+ }
149
+ this.pub = key;
150
+ return key;
151
+ }
152
+ describe() {
153
+ return `external command (${this.cfg.command.split(/\s+/)[0]})`;
154
+ }
155
+ run(input) {
156
+ const timeoutMs = this.cfg.timeoutMs ?? DEFAULT_TIMEOUT_MS;
157
+ return new Promise((resolve, reject) => {
158
+ // `shell: true` so a customer can configure a pipeline as one string. The
159
+ // command comes from this machine's own configuration, not from the
160
+ // payload or from anything the server sent — it is the operator's shell,
161
+ // used deliberately.
162
+ const child = (0, child_process_1.spawn)(this.cfg.command, { shell: true });
163
+ const stdout = [];
164
+ const stderr = [];
165
+ let settled = false;
166
+ const timer = setTimeout(() => {
167
+ if (settled)
168
+ return;
169
+ settled = true;
170
+ child.kill('SIGKILL');
171
+ reject(new Error(`external signer timed out after ${timeoutMs}ms. A signing command ` +
172
+ 'that hangs must fail the build rather than hold it open.'));
173
+ }, timeoutMs);
174
+ child.stdout.on('data', (d) => stdout.push(d));
175
+ child.stderr.on('data', (d) => stderr.push(d));
176
+ child.on('error', (err) => {
177
+ if (settled)
178
+ return;
179
+ settled = true;
180
+ clearTimeout(timer);
181
+ reject(new Error(`external signer could not be started: ${err.message}`));
182
+ });
183
+ child.on('close', (code) => {
184
+ if (settled)
185
+ return;
186
+ settled = true;
187
+ clearTimeout(timer);
188
+ if (code !== 0) {
189
+ const msg = Buffer.concat(stderr).toString('utf8').trim();
190
+ reject(new Error(`external signer exited ${code}${msg ? `: ${msg.slice(0, 500)}` : ''}`));
191
+ return;
192
+ }
193
+ const out = Buffer.concat(stdout);
194
+ if (out.length === 0) {
195
+ reject(new Error('external signer exited 0 but produced no signature'));
196
+ return;
197
+ }
198
+ resolve(out);
199
+ });
200
+ child.stdin.on('error', () => {
201
+ /* the close handler reports it; an EPIPE here must not crash the CLI */
202
+ });
203
+ child.stdin.end(input);
204
+ });
205
+ }
206
+ }
207
+ exports.ExternalCommandSigner = ExternalCommandSigner;
208
+ const ENV_SIGNER_COMMAND = 'SCALEBUN_SIGNER_COMMAND';
209
+ const ENV_SIGNER_PUBLIC_KEY = 'SCALEBUN_SIGNER_PUBLIC_KEY';
210
+ /**
211
+ * Build a signer from configuration.
212
+ *
213
+ * An external signer is used ONLY when explicitly configured. With nothing set,
214
+ * this returns the local PEM signer and every existing flow behaves exactly as
215
+ * it did — which is the whole point: this is additive, and a customer who has
216
+ * not asked for an HSM must not notice it exists.
217
+ */
218
+ function resolveSigner(opts) {
219
+ const command = opts.signerCommand ?? process.env[ENV_SIGNER_COMMAND];
220
+ if (command?.trim()) {
221
+ const publicKeyPem = opts.signerPublicKey ?? process.env[ENV_SIGNER_PUBLIC_KEY] ?? '';
222
+ return new ExternalCommandSigner({ command, publicKeyPem });
223
+ }
224
+ if (!opts.privateKeyPem) {
225
+ throw new Error('no signing key configured: set a private key, or configure an external ' +
226
+ `signer with ${ENV_SIGNER_COMMAND} and ${ENV_SIGNER_PUBLIC_KEY}`);
227
+ }
228
+ return new LocalPemSigner(opts.privateKeyPem, opts.passphrase);
229
+ }
230
+ //# sourceMappingURL=signer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"signer.js","sourceRoot":"","sources":["../../src/utils/signer.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,102 @@
1
+ /**
2
+ * RSA-3072 / SHA-256 release signing for SB-OTA-RSA-SHA256-V1.
3
+ *
4
+ * Security model (unchanged in spirit from the ed25519 era, stronger in fact):
5
+ * the private key is generated on the developer's machine and NEVER leaves it —
6
+ * only the SPKI public key is registered, and only the detached signature +
7
+ * signed payload are uploaded. A server that cannot sign cannot forge a release.
8
+ *
9
+ * The signed object is the exact binary ScaleBunSignedReleaseV1 payload (see the
10
+ * vendored codec + docs/ota/SB-OTA-RSA-SHA256-V1.md). We sign those bytes and
11
+ * immediately self-verify with the derived public key before returning, so a
12
+ * broken key or a codec regression fails here rather than on a user's device.
13
+ */
14
+ import * as crypto from 'crypto';
15
+ import { ScaleBunSignedReleaseV1, OtaReleaseEnvelope } from '../vendor/ota-protocol';
16
+ import type { Signer } from './signer';
17
+ export { LocalPemSigner, ExternalCommandSigner, resolveSigner } from './signer';
18
+ export type { Signer, ExternalSignerConfig } from './signer';
19
+ export interface KeyInfo {
20
+ algorithm: 'RSA_SHA256';
21
+ modulusBits: number;
22
+ /** SHA-256 over the SPKI DER, lowercase hex. */
23
+ fingerprint: string;
24
+ /** Colon-grouped uppercase hex, for display. */
25
+ fingerprintDisplay: string;
26
+ spkiPem: string;
27
+ }
28
+ /** SHA-256 fingerprint over the SPKI DER of a public key. */
29
+ export declare function spkiFingerprint(publicKey: crypto.KeyObject): {
30
+ hex: string;
31
+ display: string;
32
+ };
33
+ /**
34
+ * Validate that a public key is RSA of an acceptable size and describe it.
35
+ * Throws on non-RSA keys or a modulus below {@link MIN_RSA_MODULUS_BITS}.
36
+ */
37
+ export declare function inspectPublicKey(pem: string | Buffer): KeyInfo;
38
+ export interface GeneratedKeyPair {
39
+ privatePem: string;
40
+ publicPem: string;
41
+ info: KeyInfo;
42
+ }
43
+ /**
44
+ * Generate a fresh RSA keypair (default 3072-bit). The private key is exported as
45
+ * PKCS#8 PEM (encrypted when a passphrase is supplied); the public key as SPKI
46
+ * PEM. This function never writes to disk or prints the private key — callers own
47
+ * storage + permissions.
48
+ */
49
+ export declare function generateRsaKeyPair(opts?: {
50
+ modulusBits?: number;
51
+ passphrase?: string;
52
+ }): GeneratedKeyPair;
53
+ export interface SignResult {
54
+ /** standard Base64 of the binary payload. */
55
+ signedPayload: string;
56
+ /** standard Base64 of the RSA signature. */
57
+ signature: string;
58
+ /** raw payload bytes (handy for tests / diagnostics). */
59
+ payloadBytes: Uint8Array;
60
+ }
61
+ /**
62
+ * Serialize + RSA-SHA256 sign a release payload, then self-verify. Throws if the
63
+ * payload is invalid (codec validation), the key is not usable RSA, or the
64
+ * self-verify fails.
65
+ */
66
+ export declare function signReleasePayload(payload: ScaleBunSignedReleaseV1, privateKeyPem: string | Buffer, passphrase?: string): SignResult;
67
+ /** Build the full transport envelope (docs §4) around a signed payload. */
68
+ export declare function buildReleaseEnvelope(payload: ScaleBunSignedReleaseV1, privateKeyPem: string | Buffer, opts?: {
69
+ passphrase?: string;
70
+ downloadUrl?: string;
71
+ displayMetadata?: Record<string, unknown>;
72
+ }): OtaReleaseEnvelope;
73
+ /**
74
+ * Sign a release payload with any Signer — a local PEM, or an HSM behind a
75
+ * command.
76
+ *
77
+ * The async sibling of `signReleasePayload`. That function is deliberately left
78
+ * alone and still used by everything that has a private key on disk: the whole
79
+ * value of this change is that it is ADDITIVE, and a customer who has not asked
80
+ * for an external signer must not be able to tell it exists.
81
+ *
82
+ * What is identical either way, and has to be:
83
+ *
84
+ * - the bytes. `serializeSignedRelease` produces the canonical payload, and
85
+ * nothing here touches it. A signer sees exactly what the local path signs.
86
+ * - the SELF-VERIFY. The signature is checked before it is returned, so a
87
+ * vault that produced an unusable one fails on the build machine rather
88
+ * than on a million devices that will each reject it.
89
+ *
90
+ * The self-verify is also what catches the single most likely misconfiguration
91
+ * of a cloud KMS: signing with PSS padding instead of PKCS#1 v1.5. `crypto.verify`
92
+ * with an RSA key defaults to PKCS#1 v1.5, so a PSS signature simply fails here —
93
+ * at build time, with a message, instead of silently shipping a release every
94
+ * device refuses.
95
+ */
96
+ export declare function signReleasePayloadWith(payload: ScaleBunSignedReleaseV1, signer: Signer): Promise<SignResult>;
97
+ /** `buildReleaseEnvelope`, for any Signer. */
98
+ export declare function buildReleaseEnvelopeWith(payload: ScaleBunSignedReleaseV1, signer: Signer, opts?: {
99
+ downloadUrl?: string;
100
+ displayMetadata?: Record<string, unknown>;
101
+ }): Promise<OtaReleaseEnvelope>;
102
+ //# sourceMappingURL=signing.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"signing.d.ts","sourceRoot":"","sources":["../../src/utils/signing.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,KAAK,MAAM,MAAM,QAAQ,CAAC;AACjC,OAAO,EAML,uBAAuB,EACvB,kBAAkB,EACnB,MAAM,wBAAwB,CAAC;AAChC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAEvC,OAAO,EAAE,cAAc,EAAE,qBAAqB,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAChF,YAAY,EAAE,MAAM,EAAE,oBAAoB,EAAE,MAAM,UAAU,CAAC;AAE7D,MAAM,WAAW,OAAO;IACtB,SAAS,EAAE,YAAY,CAAC;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,gDAAgD;IAChD,WAAW,EAAE,MAAM,CAAC;IACpB,gDAAgD;IAChD,kBAAkB,EAAE,MAAM,CAAC;IAC3B,OAAO,EAAE,MAAM,CAAC;CACjB;AAUD,6DAA6D;AAC7D,wBAAgB,eAAe,CAAC,SAAS,EAAE,MAAM,CAAC,SAAS,GAAG;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAK7F;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAsB9D;AAED,MAAM,WAAW,gBAAgB;IAC/B,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,OAAO,CAAC;CACf;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,CAAC,EAAE;IACxC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,GAAG,gBAAgB,CAkBnB;AAED,MAAM,WAAW,UAAU;IACzB,6CAA6C;IAC7C,aAAa,EAAE,MAAM,CAAC;IACtB,4CAA4C;IAC5C,SAAS,EAAE,MAAM,CAAC;IAClB,yDAAyD;IACzD,YAAY,EAAE,UAAU,CAAC;CAC1B;AAkBD;;;;GAIG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,uBAAuB,EAChC,aAAa,EAAE,MAAM,GAAG,MAAM,EAC9B,UAAU,CAAC,EAAE,MAAM,GAClB,UAAU,CAgBZ;AAED,2EAA2E;AAC3E,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,uBAAuB,EAChC,aAAa,EAAE,MAAM,GAAG,MAAM,EAC9B,IAAI,CAAC,EAAE;IAAE,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAAC,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,GAC9F,kBAAkB,CAUpB;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAsB,sBAAsB,CAC1C,OAAO,EAAE,uBAAuB,EAChC,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,UAAU,CAAC,CAmBrB;AAED,8CAA8C;AAC9C,wBAAsB,wBAAwB,CAC5C,OAAO,EAAE,uBAAuB,EAChC,MAAM,EAAE,MAAM,EACd,IAAI,CAAC,EAAE;IAAE,WAAW,CAAC,EAAE,MAAM,CAAC;IAAC,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,GACzE,OAAO,CAAC,kBAAkB,CAAC,CAU7B"}
@@ -0,0 +1,229 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.resolveSigner = exports.ExternalCommandSigner = exports.LocalPemSigner = void 0;
37
+ exports.spkiFingerprint = spkiFingerprint;
38
+ exports.inspectPublicKey = inspectPublicKey;
39
+ exports.generateRsaKeyPair = generateRsaKeyPair;
40
+ exports.signReleasePayload = signReleasePayload;
41
+ exports.buildReleaseEnvelope = buildReleaseEnvelope;
42
+ exports.signReleasePayloadWith = signReleasePayloadWith;
43
+ exports.buildReleaseEnvelopeWith = buildReleaseEnvelopeWith;
44
+ /**
45
+ * RSA-3072 / SHA-256 release signing for SB-OTA-RSA-SHA256-V1.
46
+ *
47
+ * Security model (unchanged in spirit from the ed25519 era, stronger in fact):
48
+ * the private key is generated on the developer's machine and NEVER leaves it —
49
+ * only the SPKI public key is registered, and only the detached signature +
50
+ * signed payload are uploaded. A server that cannot sign cannot forge a release.
51
+ *
52
+ * The signed object is the exact binary ScaleBunSignedReleaseV1 payload (see the
53
+ * vendored codec + docs/ota/SB-OTA-RSA-SHA256-V1.md). We sign those bytes and
54
+ * immediately self-verify with the derived public key before returning, so a
55
+ * broken key or a codec regression fails here rather than on a user's device.
56
+ */
57
+ const crypto = __importStar(require("crypto"));
58
+ const ota_protocol_1 = require("../vendor/ota-protocol");
59
+ var signer_1 = require("./signer");
60
+ Object.defineProperty(exports, "LocalPemSigner", { enumerable: true, get: function () { return signer_1.LocalPemSigner; } });
61
+ Object.defineProperty(exports, "ExternalCommandSigner", { enumerable: true, get: function () { return signer_1.ExternalCommandSigner; } });
62
+ Object.defineProperty(exports, "resolveSigner", { enumerable: true, get: function () { return signer_1.resolveSigner; } });
63
+ function modulusBits(key) {
64
+ const bits = key.asymmetricKeyDetails?.modulusLength;
65
+ if (typeof bits !== 'number') {
66
+ throw new Error('key has no RSA modulus length — not an RSA key?');
67
+ }
68
+ return bits;
69
+ }
70
+ /** SHA-256 fingerprint over the SPKI DER of a public key. */
71
+ function spkiFingerprint(publicKey) {
72
+ const der = publicKey.export({ type: 'spki', format: 'der' });
73
+ const hex = crypto.createHash('sha256').update(der).digest('hex');
74
+ const display = (hex.match(/.{2}/g) ?? []).join(':').toUpperCase();
75
+ return { hex, display };
76
+ }
77
+ /**
78
+ * Validate that a public key is RSA of an acceptable size and describe it.
79
+ * Throws on non-RSA keys or a modulus below {@link MIN_RSA_MODULUS_BITS}.
80
+ */
81
+ function inspectPublicKey(pem) {
82
+ let key;
83
+ try {
84
+ key = crypto.createPublicKey(pem);
85
+ }
86
+ catch (err) {
87
+ throw new Error(`not a valid public key: ${err?.message ?? err}`);
88
+ }
89
+ if (key.asymmetricKeyType !== 'rsa') {
90
+ throw new Error(`unsupported key type "${key.asymmetricKeyType}" — SB-OTA-RSA-SHA256-V1 requires RSA`);
91
+ }
92
+ const bits = modulusBits(key);
93
+ if (bits < ota_protocol_1.MIN_RSA_MODULUS_BITS) {
94
+ throw new Error(`RSA modulus ${bits} bits is below the ${ota_protocol_1.MIN_RSA_MODULUS_BITS}-bit minimum`);
95
+ }
96
+ const fp = spkiFingerprint(key);
97
+ return {
98
+ algorithm: 'RSA_SHA256',
99
+ modulusBits: bits,
100
+ fingerprint: fp.hex,
101
+ fingerprintDisplay: fp.display,
102
+ spkiPem: key.export({ type: 'spki', format: 'pem' }),
103
+ };
104
+ }
105
+ /**
106
+ * Generate a fresh RSA keypair (default 3072-bit). The private key is exported as
107
+ * PKCS#8 PEM (encrypted when a passphrase is supplied); the public key as SPKI
108
+ * PEM. This function never writes to disk or prints the private key — callers own
109
+ * storage + permissions.
110
+ */
111
+ function generateRsaKeyPair(opts) {
112
+ const bits = opts?.modulusBits ?? ota_protocol_1.RECOMMENDED_RSA_MODULUS_BITS;
113
+ if (bits < ota_protocol_1.MIN_RSA_MODULUS_BITS) {
114
+ throw new Error(`refusing to generate a ${bits}-bit key; minimum is ${ota_protocol_1.MIN_RSA_MODULUS_BITS}`);
115
+ }
116
+ const { privateKey, publicKey } = crypto.generateKeyPairSync('rsa', { modulusLength: bits });
117
+ const privatePem = (opts?.passphrase
118
+ ? privateKey.export({
119
+ type: 'pkcs8',
120
+ format: 'pem',
121
+ cipher: 'aes-256-cbc',
122
+ passphrase: opts.passphrase,
123
+ })
124
+ : privateKey.export({ type: 'pkcs8', format: 'pem' }));
125
+ const publicPem = publicKey.export({ type: 'spki', format: 'pem' });
126
+ return { privatePem, publicPem, info: inspectPublicKey(publicPem) };
127
+ }
128
+ function loadRsaPrivateKey(pem, passphrase) {
129
+ let key;
130
+ try {
131
+ key = crypto.createPrivateKey(passphrase ? { key: pem, passphrase } : pem);
132
+ }
133
+ catch (err) {
134
+ throw new Error(`could not load private key: ${err?.message ?? err}`);
135
+ }
136
+ if (key.asymmetricKeyType !== 'rsa') {
137
+ throw new Error(`private key is ${key.asymmetricKeyType}, not RSA`);
138
+ }
139
+ if (modulusBits(key) < ota_protocol_1.MIN_RSA_MODULUS_BITS) {
140
+ throw new Error(`private key modulus below ${ota_protocol_1.MIN_RSA_MODULUS_BITS} bits`);
141
+ }
142
+ return key;
143
+ }
144
+ /**
145
+ * Serialize + RSA-SHA256 sign a release payload, then self-verify. Throws if the
146
+ * payload is invalid (codec validation), the key is not usable RSA, or the
147
+ * self-verify fails.
148
+ */
149
+ function signReleasePayload(payload, privateKeyPem, passphrase) {
150
+ const bytes = (0, ota_protocol_1.serializeSignedRelease)(payload);
151
+ const privateKey = loadRsaPrivateKey(privateKeyPem, passphrase);
152
+ const signature = crypto.sign('sha256', Buffer.from(bytes), privateKey);
153
+ // Self-check with the derived public key — the same check the backend runs
154
+ // after signing (PROMPT §7.9). A failure here means we would have shipped an
155
+ // unverifiable release.
156
+ const publicKey = crypto.createPublicKey(privateKey);
157
+ if (!crypto.verify('sha256', Buffer.from(bytes), publicKey, signature)) {
158
+ throw new Error('self-verify failed: produced signature does not verify with its own public key');
159
+ }
160
+ return {
161
+ signedPayload: (0, ota_protocol_1.encodeBase64)(bytes),
162
+ signature: signature.toString('base64'),
163
+ payloadBytes: bytes,
164
+ };
165
+ }
166
+ /** Build the full transport envelope (docs §4) around a signed payload. */
167
+ function buildReleaseEnvelope(payload, privateKeyPem, opts) {
168
+ const signed = signReleasePayload(payload, privateKeyPem, opts?.passphrase);
169
+ return {
170
+ protocol: ota_protocol_1.SIGNATURE_ALGORITHM,
171
+ keyId: payload.keyId,
172
+ signedPayload: signed.signedPayload,
173
+ signature: signed.signature,
174
+ downloadUrl: opts?.downloadUrl,
175
+ displayMetadata: opts?.displayMetadata,
176
+ };
177
+ }
178
+ /**
179
+ * Sign a release payload with any Signer — a local PEM, or an HSM behind a
180
+ * command.
181
+ *
182
+ * The async sibling of `signReleasePayload`. That function is deliberately left
183
+ * alone and still used by everything that has a private key on disk: the whole
184
+ * value of this change is that it is ADDITIVE, and a customer who has not asked
185
+ * for an external signer must not be able to tell it exists.
186
+ *
187
+ * What is identical either way, and has to be:
188
+ *
189
+ * - the bytes. `serializeSignedRelease` produces the canonical payload, and
190
+ * nothing here touches it. A signer sees exactly what the local path signs.
191
+ * - the SELF-VERIFY. The signature is checked before it is returned, so a
192
+ * vault that produced an unusable one fails on the build machine rather
193
+ * than on a million devices that will each reject it.
194
+ *
195
+ * The self-verify is also what catches the single most likely misconfiguration
196
+ * of a cloud KMS: signing with PSS padding instead of PKCS#1 v1.5. `crypto.verify`
197
+ * with an RSA key defaults to PKCS#1 v1.5, so a PSS signature simply fails here —
198
+ * at build time, with a message, instead of silently shipping a release every
199
+ * device refuses.
200
+ */
201
+ async function signReleasePayloadWith(payload, signer) {
202
+ const bytes = (0, ota_protocol_1.serializeSignedRelease)(payload);
203
+ const signature = await signer.sign(bytes);
204
+ const publicKey = await signer.publicKey();
205
+ if (!crypto.verify('sha256', Buffer.from(bytes), publicKey, signature)) {
206
+ throw new Error(`self-verify failed: the signature produced by ${signer.describe()} does not ` +
207
+ 'verify against the configured public key. Check that the signer uses ' +
208
+ 'RSA PKCS#1 v1.5 with SHA-256 (not PSS) and that the public key belongs ' +
209
+ 'to the key it signed with.');
210
+ }
211
+ return {
212
+ signedPayload: (0, ota_protocol_1.encodeBase64)(bytes),
213
+ signature: signature.toString('base64'),
214
+ payloadBytes: bytes,
215
+ };
216
+ }
217
+ /** `buildReleaseEnvelope`, for any Signer. */
218
+ async function buildReleaseEnvelopeWith(payload, signer, opts) {
219
+ const signed = await signReleasePayloadWith(payload, signer);
220
+ return {
221
+ protocol: ota_protocol_1.SIGNATURE_ALGORITHM,
222
+ keyId: payload.keyId,
223
+ signedPayload: signed.signedPayload,
224
+ signature: signed.signature,
225
+ downloadUrl: opts?.downloadUrl,
226
+ displayMetadata: opts?.displayMetadata,
227
+ };
228
+ }
229
+ //# sourceMappingURL=signing.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"signing.js","sourceRoot":"","sources":["../../src/utils/signing.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,36 @@
1
+ /**
2
+ * HTTPS enforcement for everything the CLI authenticates to.
3
+ *
4
+ * WHAT WAS WRONG. The API client picked its transport straight off the URL
5
+ * scheme — `parsed.protocol === 'https:' ? https : http` — with nothing
6
+ * requiring HTTPS in the first place, and every request attaches
7
+ * `Authorization: Bearer <token>`. `scalebun login` accepts an arbitrary
8
+ * `--api-url` and POSTs an email and password to it. So a CLI pointed at an
9
+ * `http://` URL — by a typo, a copied CI snippet, or a tampered config —
10
+ * would put a personal access token, or the user's actual password, on the
11
+ * wire in cleartext, with no warning and no failure.
12
+ *
13
+ * THE RULE. Credentials only travel over TLS. The single exception is
14
+ * loopback, because local development against `http://localhost:3000` is a
15
+ * real workflow and traffic that never leaves the machine cannot be
16
+ * intercepted on the network. Anything else is refused before a socket is
17
+ * opened, rather than warned about — a warning printed above a successful
18
+ * request is not a control.
19
+ *
20
+ * Loopback is matched on resolved literals, not on name. `localhost` and the
21
+ * reserved 127/8 and ::1 literals are loopback by definition; an arbitrary
22
+ * hostname that merely happens to resolve to 127.0.0.1 today is not, because
23
+ * whoever controls the DNS answer controls where the token goes tomorrow.
24
+ */
25
+ export declare class InsecureApiUrlError extends Error {
26
+ constructor(url: string, hostname: string);
27
+ }
28
+ /**
29
+ * Validate an API base URL before it is used or persisted.
30
+ *
31
+ * @returns the normalised URL (trailing slashes stripped)
32
+ * @throws InsecureApiUrlError for a non-loopback `http://` URL
33
+ * @throws Error for a URL that is malformed or uses a non-HTTP scheme
34
+ */
35
+ export declare function assertSecureApiUrl(rawUrl: string): string;
36
+ //# sourceMappingURL=transportSecurity.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transportSecurity.d.ts","sourceRoot":"","sources":["../../src/utils/transportSecurity.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAYH,qBAAa,mBAAoB,SAAQ,KAAK;gBAChC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM;CAW1C;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAkBzD"}