@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.
- package/bin/scalebun.js +2 -0
- package/lib/api/client.d.ts +52 -0
- package/lib/api/client.d.ts.map +1 -0
- package/lib/api/client.js +335 -0
- package/lib/api/client.js.map +1 -0
- package/lib/commands/ci-init.d.ts +3 -0
- package/lib/commands/ci-init.d.ts.map +1 -0
- package/lib/commands/ci-init.js +348 -0
- package/lib/commands/ci-init.js.map +1 -0
- package/lib/commands/doctor.d.ts +31 -0
- package/lib/commands/doctor.d.ts.map +1 -0
- package/lib/commands/doctor.js +559 -0
- package/lib/commands/doctor.js.map +1 -0
- package/lib/commands/launch.d.ts +3 -0
- package/lib/commands/launch.d.ts.map +1 -0
- package/lib/commands/launch.js +23 -0
- package/lib/commands/launch.js.map +1 -0
- package/lib/commands/link.d.ts +16 -0
- package/lib/commands/link.d.ts.map +1 -0
- package/lib/commands/link.js +164 -0
- package/lib/commands/link.js.map +1 -0
- package/lib/commands/login.d.ts +12 -0
- package/lib/commands/login.d.ts.map +1 -0
- package/lib/commands/login.js +124 -0
- package/lib/commands/login.js.map +1 -0
- package/lib/commands/migrate-codepush.d.ts +13 -0
- package/lib/commands/migrate-codepush.d.ts.map +1 -0
- package/lib/commands/migrate-codepush.js +98 -0
- package/lib/commands/migrate-codepush.js.map +1 -0
- package/lib/commands/ota-bundle.d.ts +22 -0
- package/lib/commands/ota-bundle.d.ts.map +1 -0
- package/lib/commands/ota-bundle.js +253 -0
- package/lib/commands/ota-bundle.js.map +1 -0
- package/lib/commands/ota-channels.d.ts +3 -0
- package/lib/commands/ota-channels.d.ts.map +1 -0
- package/lib/commands/ota-channels.js +231 -0
- package/lib/commands/ota-channels.js.map +1 -0
- package/lib/commands/ota-delete-bundle.d.ts +16 -0
- package/lib/commands/ota-delete-bundle.d.ts.map +1 -0
- package/lib/commands/ota-delete-bundle.js +96 -0
- package/lib/commands/ota-delete-bundle.js.map +1 -0
- package/lib/commands/ota-inspect.d.ts +3 -0
- package/lib/commands/ota-inspect.d.ts.map +1 -0
- package/lib/commands/ota-inspect.js +258 -0
- package/lib/commands/ota-inspect.js.map +1 -0
- package/lib/commands/ota-keys.d.ts +91 -0
- package/lib/commands/ota-keys.d.ts.map +1 -0
- package/lib/commands/ota-keys.js +765 -0
- package/lib/commands/ota-keys.js.map +1 -0
- package/lib/commands/ota-promote.d.ts +3 -0
- package/lib/commands/ota-promote.d.ts.map +1 -0
- package/lib/commands/ota-promote.js +194 -0
- package/lib/commands/ota-promote.js.map +1 -0
- package/lib/commands/ota-publish.d.ts +26 -0
- package/lib/commands/ota-publish.d.ts.map +1 -0
- package/lib/commands/ota-publish.js +892 -0
- package/lib/commands/ota-publish.js.map +1 -0
- package/lib/commands/ota-rollout.d.ts +3 -0
- package/lib/commands/ota-rollout.d.ts.map +1 -0
- package/lib/commands/ota-rollout.js +265 -0
- package/lib/commands/ota-rollout.js.map +1 -0
- package/lib/commands/ota-sign.d.ts +19 -0
- package/lib/commands/ota-sign.d.ts.map +1 -0
- package/lib/commands/ota-sign.js +245 -0
- package/lib/commands/ota-sign.js.map +1 -0
- package/lib/commands/push-apns.d.ts +3 -0
- package/lib/commands/push-apns.d.ts.map +1 -0
- package/lib/commands/push-apns.js +166 -0
- package/lib/commands/push-apns.js.map +1 -0
- package/lib/commands/quickstart.d.ts +4 -0
- package/lib/commands/quickstart.d.ts.map +1 -0
- package/lib/commands/quickstart.js +672 -0
- package/lib/commands/quickstart.js.map +1 -0
- package/lib/commands/shell.d.ts +28 -0
- package/lib/commands/shell.d.ts.map +1 -0
- package/lib/commands/shell.js +512 -0
- package/lib/commands/shell.js.map +1 -0
- package/lib/commands/tls-pins.d.ts +27 -0
- package/lib/commands/tls-pins.d.ts.map +1 -0
- package/lib/commands/tls-pins.js +209 -0
- package/lib/commands/tls-pins.js.map +1 -0
- package/lib/index.d.ts +2 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +233 -0
- package/lib/index.js.map +1 -0
- package/lib/ui/banner.d.ts +23 -0
- package/lib/ui/banner.d.ts.map +1 -0
- package/lib/ui/banner.js +162 -0
- package/lib/ui/banner.js.map +1 -0
- package/lib/ui/graphics.d.ts +18 -0
- package/lib/ui/graphics.d.ts.map +1 -0
- package/lib/ui/graphics.js +134 -0
- package/lib/ui/graphics.js.map +1 -0
- package/lib/ui/mark.d.ts +4 -0
- package/lib/ui/mark.d.ts.map +1 -0
- package/lib/ui/mark.js +21 -0
- package/lib/ui/mark.js.map +1 -0
- package/lib/ui/menu.d.ts +109 -0
- package/lib/ui/menu.d.ts.map +1 -0
- package/lib/ui/menu.js +314 -0
- package/lib/ui/menu.js.map +1 -0
- package/lib/ui/sixel.d.ts +6 -0
- package/lib/ui/sixel.d.ts.map +1 -0
- package/lib/ui/sixel.js +9 -0
- package/lib/ui/sixel.js.map +1 -0
- package/lib/utils/apps.d.ts +29 -0
- package/lib/utils/apps.d.ts.map +1 -0
- package/lib/utils/apps.js +19 -0
- package/lib/utils/apps.js.map +1 -0
- package/lib/utils/browser.d.ts +9 -0
- package/lib/utils/browser.d.ts.map +1 -0
- package/lib/utils/browser.js +39 -0
- package/lib/utils/browser.js.map +1 -0
- package/lib/utils/bundle.d.ts +153 -0
- package/lib/utils/bundle.d.ts.map +1 -0
- package/lib/utils/bundle.js +459 -0
- package/lib/utils/bundle.js.map +1 -0
- package/lib/utils/bundleStore.d.ts +128 -0
- package/lib/utils/bundleStore.d.ts.map +1 -0
- package/lib/utils/bundleStore.js +250 -0
- package/lib/utils/bundleStore.js.map +1 -0
- package/lib/utils/context.d.ts +88 -0
- package/lib/utils/context.d.ts.map +1 -0
- package/lib/utils/context.js +247 -0
- package/lib/utils/context.js.map +1 -0
- package/lib/utils/credentials.d.ts +59 -0
- package/lib/utils/credentials.d.ts.map +1 -0
- package/lib/utils/credentials.js +147 -0
- package/lib/utils/credentials.js.map +1 -0
- package/lib/utils/guide.d.ts +63 -0
- package/lib/utils/guide.d.ts.map +1 -0
- package/lib/utils/guide.js +191 -0
- package/lib/utils/guide.js.map +1 -0
- package/lib/utils/hash.d.ts +3 -0
- package/lib/utils/hash.d.ts.map +1 -0
- package/lib/utils/hash.js +44 -0
- package/lib/utils/hash.js.map +1 -0
- package/lib/utils/native-id.d.ts +17 -0
- package/lib/utils/native-id.d.ts.map +1 -0
- package/lib/utils/native-id.js +105 -0
- package/lib/utils/native-id.js.map +1 -0
- package/lib/utils/output.d.ts +96 -0
- package/lib/utils/output.d.ts.map +1 -0
- package/lib/utils/output.js +135 -0
- package/lib/utils/output.js.map +1 -0
- package/lib/utils/prompt.d.ts +106 -0
- package/lib/utils/prompt.d.ts.map +1 -0
- package/lib/utils/prompt.js +457 -0
- package/lib/utils/prompt.js.map +1 -0
- package/lib/utils/provenance.d.ts +30 -0
- package/lib/utils/provenance.d.ts.map +1 -0
- package/lib/utils/provenance.js +157 -0
- package/lib/utils/provenance.js.map +1 -0
- package/lib/utils/resolve.d.ts +81 -0
- package/lib/utils/resolve.d.ts.map +1 -0
- package/lib/utils/resolve.js +139 -0
- package/lib/utils/resolve.js.map +1 -0
- package/lib/utils/sdkBin.d.ts +18 -0
- package/lib/utils/sdkBin.d.ts.map +1 -0
- package/lib/utils/sdkBin.js +76 -0
- package/lib/utils/sdkBin.js.map +1 -0
- package/lib/utils/signer.d.ts +94 -0
- package/lib/utils/signer.d.ts.map +1 -0
- package/lib/utils/signer.js +230 -0
- package/lib/utils/signer.js.map +1 -0
- package/lib/utils/signing.d.ts +102 -0
- package/lib/utils/signing.d.ts.map +1 -0
- package/lib/utils/signing.js +229 -0
- package/lib/utils/signing.js.map +1 -0
- package/lib/utils/transportSecurity.d.ts +36 -0
- package/lib/utils/transportSecurity.d.ts.map +1 -0
- package/lib/utils/transportSecurity.js +74 -0
- package/lib/utils/transportSecurity.js.map +1 -0
- package/lib/utils/trustAnchor.d.ts +109 -0
- package/lib/utils/trustAnchor.d.ts.map +1 -0
- package/lib/utils/trustAnchor.js +169 -0
- package/lib/utils/trustAnchor.js.map +1 -0
- package/lib/utils/ui.d.ts +12 -0
- package/lib/utils/ui.d.ts.map +1 -0
- package/lib/utils/ui.js +60 -0
- package/lib/utils/ui.js.map +1 -0
- package/lib/utils/validate.d.ts +62 -0
- package/lib/utils/validate.d.ts.map +1 -0
- package/lib/utils/validate.js +189 -0
- package/lib/utils/validate.js.map +1 -0
- package/lib/vendor/ota-protocol/base64.d.ts +11 -0
- package/lib/vendor/ota-protocol/base64.d.ts.map +1 -0
- package/lib/vendor/ota-protocol/base64.js +39 -0
- package/lib/vendor/ota-protocol/base64.js.map +1 -0
- package/lib/vendor/ota-protocol/codec.d.ts +26 -0
- package/lib/vendor/ota-protocol/codec.d.ts.map +1 -0
- package/lib/vendor/ota-protocol/codec.js +349 -0
- package/lib/vendor/ota-protocol/codec.js.map +1 -0
- package/lib/vendor/ota-protocol/hex.d.ts +8 -0
- package/lib/vendor/ota-protocol/hex.d.ts.map +1 -0
- package/lib/vendor/ota-protocol/hex.js +31 -0
- package/lib/vendor/ota-protocol/hex.js.map +1 -0
- package/lib/vendor/ota-protocol/index.d.ts +17 -0
- package/lib/vendor/ota-protocol/index.d.ts.map +1 -0
- package/lib/vendor/ota-protocol/index.js +41 -0
- package/lib/vendor/ota-protocol/index.js.map +1 -0
- package/lib/vendor/ota-protocol/limits.d.ts +39 -0
- package/lib/vendor/ota-protocol/limits.d.ts.map +1 -0
- package/lib/vendor/ota-protocol/limits.js +42 -0
- package/lib/vendor/ota-protocol/limits.js.map +1 -0
- package/lib/vendor/ota-protocol/types.d.ts +73 -0
- package/lib/vendor/ota-protocol/types.d.ts.map +1 -0
- package/lib/vendor/ota-protocol/types.js +24 -0
- package/lib/vendor/ota-protocol/types.js.map +1 -0
- 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"}
|