@capgo/cli 4.2.3 → 5.0.0-alpha.3
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/CHANGELOG.md +803 -588
- package/README.md +6 -6
- package/dist/index.js +91 -51
- package/package.json +1 -1
- package/src/api/crypto.ts +17 -12
- package/src/bundle/decrypt.ts +23 -12
- package/src/bundle/encrypt.ts +23 -14
- package/src/bundle/upload.ts +14 -9
- package/src/index.ts +4 -4
- package/src/key.ts +38 -11
- package/src/types/supabase.types.ts +57 -29
- package/src/utils.ts +22 -1
package/README.md
CHANGED
|
@@ -128,8 +128,8 @@ Optionally, you can give:
|
|
|
128
128
|
* `--path [/path/to/my/bundle]` to upload a specific folder.
|
|
129
129
|
* `--channel [test]` to upload to a specific channel.
|
|
130
130
|
* `--external="https://mydomain.com/myapp.zip"` to link to an external URL instead of upload to Capgo cloud, it should be a zip URL in HTTPS.
|
|
131
|
-
* `--key [/path/to/my/
|
|
132
|
-
* `--key-data [
|
|
131
|
+
* `--key [/path/to/my/public_key]` the path of your public key.
|
|
132
|
+
* `--key-data [publicKey]` the public key data, if you want to use inline.
|
|
133
133
|
* `--no-key` to ignore the signing key and send clear update.
|
|
134
134
|
* `--bundle [1.0.0]` to set the bundle version number of the file to upload.
|
|
135
135
|
* `--iv-session-key [key]` to send a custom session key to the cloud.
|
|
@@ -217,9 +217,9 @@ The command will print your `ivSessionKey`y and generate an encrypted zip, to us
|
|
|
217
217
|
|
|
218
218
|
Optionally, you can give:
|
|
219
219
|
|
|
220
|
-
`--key [/path/to/my/
|
|
220
|
+
`--key [/path/to/my/public_key]` the path of your public key.
|
|
221
221
|
|
|
222
|
-
`--key-data [
|
|
222
|
+
`--key-data [publicKey]` the public key data, if you want to use inline. This command is mainly used for test purpose, it will decrypt the zip and print the base64 decrypted session key in the console.
|
|
223
223
|
|
|
224
224
|
### **Zip**
|
|
225
225
|
|
|
@@ -304,9 +304,9 @@ Optionally, you can give: `--force` to overwrite the existing key. This command
|
|
|
304
304
|
|
|
305
305
|
Optionally, you can give:
|
|
306
306
|
|
|
307
|
-
`--key [/path/to/my/
|
|
307
|
+
`--key [/path/to/my/public_key]` the path of your public key.
|
|
308
308
|
|
|
309
|
-
`--key-data [
|
|
309
|
+
`--key-data [publicKey]` the public key data, if you want to use inline. This command is useful if you followed the recommendation and didn't commit the key in your app, and in the config.
|
|
310
310
|
## Dev contribution
|
|
311
311
|
|
|
312
312
|
1. Install development dependencies
|
package/dist/index.js
CHANGED
|
@@ -61137,7 +61137,7 @@ var {
|
|
|
61137
61137
|
// package.json
|
|
61138
61138
|
var package_default = {
|
|
61139
61139
|
name: "@capgo/cli",
|
|
61140
|
-
version: "
|
|
61140
|
+
version: "5.0.0-alpha.3",
|
|
61141
61141
|
description: "A CLI to upload to capgo servers",
|
|
61142
61142
|
main: "dist/index.js",
|
|
61143
61143
|
bin: {
|
|
@@ -62565,6 +62565,19 @@ async function findMainFile() {
|
|
|
62565
62565
|
}
|
|
62566
62566
|
return mainFile;
|
|
62567
62567
|
}
|
|
62568
|
+
async function checKOldEncryption() {
|
|
62569
|
+
const config = await getConfig();
|
|
62570
|
+
const { extConfig } = config.app;
|
|
62571
|
+
const hasPrivateKeyInConfig = !!extConfig?.plugins?.CapacitorUpdater?.privateKey;
|
|
62572
|
+
const hasPublicKeyInConfig = !!extConfig?.plugins?.CapacitorUpdater?.publicKey;
|
|
62573
|
+
if (hasPrivateKeyInConfig)
|
|
62574
|
+
f2.warning(`You still have privateKey in the capacitor config, this is deprecated, please remove it`);
|
|
62575
|
+
f2.warning(`Encryption with private will be ignored`);
|
|
62576
|
+
if (!hasPublicKeyInConfig) {
|
|
62577
|
+
f2.warning(`publicKey not found in capacitor config, please run npx @capgo/cli key save`);
|
|
62578
|
+
program.error("");
|
|
62579
|
+
}
|
|
62580
|
+
}
|
|
62568
62581
|
async function updateOrCreateVersion(supabase, update) {
|
|
62569
62582
|
return supabase.from("app_versions").upsert(update, { onConflict: "name,app_id" }).eq("app_id", update.app_id).eq("name", update.name);
|
|
62570
62583
|
}
|
|
@@ -63144,16 +63157,14 @@ var import_config2 = __toESM(require_config());
|
|
|
63144
63157
|
var import_node_crypto2 = require("node:crypto");
|
|
63145
63158
|
var import_node_buffer2 = require("node:buffer");
|
|
63146
63159
|
var algorithm = "aes-128-cbc";
|
|
63147
|
-
var oaepHash = "sha256";
|
|
63148
63160
|
var formatB64 = "base64";
|
|
63149
|
-
var padding = import_node_crypto2.constants.
|
|
63150
|
-
function decryptSource(source, ivSessionKey,
|
|
63161
|
+
var padding = import_node_crypto2.constants.RSA_PKCS1_PADDING;
|
|
63162
|
+
function decryptSource(source, ivSessionKey, key2) {
|
|
63151
63163
|
const [ivB64, sessionb64Encrypted] = ivSessionKey.split(":");
|
|
63152
|
-
const sessionKey = (0, import_node_crypto2.
|
|
63164
|
+
const sessionKey = (0, import_node_crypto2.publicDecrypt)(
|
|
63153
63165
|
{
|
|
63154
|
-
key:
|
|
63155
|
-
padding
|
|
63156
|
-
oaepHash
|
|
63166
|
+
key: key2,
|
|
63167
|
+
padding
|
|
63157
63168
|
},
|
|
63158
63169
|
import_node_buffer2.Buffer.from(sessionb64Encrypted, formatB64)
|
|
63159
63170
|
);
|
|
@@ -63163,17 +63174,16 @@ function decryptSource(source, ivSessionKey, privateKey) {
|
|
|
63163
63174
|
const decryptedData = import_node_buffer2.Buffer.concat([decipher.update(source), decipher.final()]);
|
|
63164
63175
|
return decryptedData;
|
|
63165
63176
|
}
|
|
63166
|
-
function encryptSource(source,
|
|
63177
|
+
function encryptSource(source, key2) {
|
|
63167
63178
|
const initVector = (0, import_node_crypto2.randomBytes)(16);
|
|
63168
63179
|
const sessionKey = (0, import_node_crypto2.randomBytes)(16);
|
|
63169
63180
|
const cipher = (0, import_node_crypto2.createCipheriv)(algorithm, sessionKey, initVector);
|
|
63170
63181
|
cipher.setAutoPadding(true);
|
|
63171
63182
|
const ivB64 = initVector.toString(formatB64);
|
|
63172
|
-
const sessionb64Encrypted = (0, import_node_crypto2.
|
|
63183
|
+
const sessionb64Encrypted = (0, import_node_crypto2.privateEncrypt)(
|
|
63173
63184
|
{
|
|
63174
|
-
key:
|
|
63175
|
-
padding
|
|
63176
|
-
oaepHash
|
|
63185
|
+
key: key2,
|
|
63186
|
+
padding
|
|
63177
63187
|
},
|
|
63178
63188
|
sessionKey
|
|
63179
63189
|
).toString(formatB64);
|
|
@@ -63207,18 +63217,28 @@ async function saveKey(options, log = true) {
|
|
|
63207
63217
|
oe(`Save keys \u{1F511}`);
|
|
63208
63218
|
const config = await getConfig();
|
|
63209
63219
|
const { extConfig } = config.app;
|
|
63210
|
-
const keyPath = options.key ||
|
|
63211
|
-
let
|
|
63212
|
-
if (!(0, import_node_fs7.existsSync)(keyPath) && !
|
|
63220
|
+
const keyPath = options.key || baseKeyPub;
|
|
63221
|
+
let publicKey = options.keyData || "";
|
|
63222
|
+
if (!(0, import_node_fs7.existsSync)(keyPath) && !publicKey) {
|
|
63213
63223
|
if (log) {
|
|
63214
|
-
f2.error(`Cannot find public key ${keyPath} or as keyData option or in ${config.app.extConfigFilePath}`);
|
|
63224
|
+
f2.error(`Cannot find a public key at ${keyPath} or as keyData option or in ${config.app.extConfigFilePath}`);
|
|
63215
63225
|
program.error("");
|
|
63216
63226
|
} else {
|
|
63217
63227
|
return false;
|
|
63218
63228
|
}
|
|
63219
63229
|
} else if ((0, import_node_fs7.existsSync)(keyPath)) {
|
|
63220
63230
|
const keyFile = (0, import_node_fs7.readFileSync)(keyPath);
|
|
63221
|
-
|
|
63231
|
+
publicKey = keyFile.toString();
|
|
63232
|
+
}
|
|
63233
|
+
if (publicKey) {
|
|
63234
|
+
if (!publicKey.startsWith("-----BEGIN RSA PUBLIC KEY-----")) {
|
|
63235
|
+
if (log) {
|
|
63236
|
+
f2.error(`the public key provided is not a valid RSA Public key`);
|
|
63237
|
+
program.error("");
|
|
63238
|
+
} else {
|
|
63239
|
+
return false;
|
|
63240
|
+
}
|
|
63241
|
+
}
|
|
63222
63242
|
}
|
|
63223
63243
|
if (extConfig) {
|
|
63224
63244
|
if (!extConfig.plugins) {
|
|
@@ -63229,11 +63249,13 @@ async function saveKey(options, log = true) {
|
|
|
63229
63249
|
}
|
|
63230
63250
|
if (!extConfig.plugins.CapacitorUpdater)
|
|
63231
63251
|
extConfig.plugins.CapacitorUpdater = {};
|
|
63232
|
-
extConfig.plugins.CapacitorUpdater.privateKey
|
|
63252
|
+
if (extConfig.plugins.CapacitorUpdater.privateKey)
|
|
63253
|
+
delete extConfig.plugins.CapacitorUpdater.privateKey;
|
|
63254
|
+
extConfig.plugins.CapacitorUpdater.publicKey = publicKey;
|
|
63233
63255
|
(0, import_config2.writeConfig)(extConfig, config.app.extConfigFilePath);
|
|
63234
63256
|
}
|
|
63235
63257
|
if (log) {
|
|
63236
|
-
f2.success(`
|
|
63258
|
+
f2.success(`public key saved into ${config.app.extConfigFilePath} file in local directory`);
|
|
63237
63259
|
f2.success(`your app will decode the zip archive with this key`);
|
|
63238
63260
|
}
|
|
63239
63261
|
return true;
|
|
@@ -63274,19 +63296,23 @@ async function createKey(options, log = true) {
|
|
|
63274
63296
|
CapacitorUpdater: {}
|
|
63275
63297
|
};
|
|
63276
63298
|
}
|
|
63277
|
-
extConfig.plugins.CapacitorUpdater
|
|
63299
|
+
if (!extConfig.plugins.CapacitorUpdater)
|
|
63300
|
+
extConfig.plugins.CapacitorUpdater = {};
|
|
63301
|
+
if (extConfig.plugins.CapacitorUpdater.privateKey)
|
|
63302
|
+
delete extConfig.plugins.CapacitorUpdater.privateKey;
|
|
63303
|
+
extConfig.plugins.CapacitorUpdater.publicKey = publicKey;
|
|
63278
63304
|
(0, import_config2.writeConfig)(extConfig, config.app.extConfigFilePath);
|
|
63279
63305
|
}
|
|
63280
63306
|
if (log) {
|
|
63281
63307
|
f2.success("Your RSA key has been generated");
|
|
63282
|
-
f2.success(`
|
|
63308
|
+
f2.success(`Private key saved in ${baseKey}`);
|
|
63283
63309
|
f2.success("This key will be use to encrypt your bundle before sending it to Capgo");
|
|
63284
63310
|
f2.success("Keep it safe");
|
|
63285
63311
|
f2.success("Than make it unreadable by Capgo and unmodifiable by anyone");
|
|
63286
|
-
f2.success(`
|
|
63312
|
+
f2.success(`Public key saved in ${config.app.extConfigFilePath}`);
|
|
63287
63313
|
f2.success("Your app will be the only one having it");
|
|
63288
63314
|
f2.success("Only your users can decrypt your update");
|
|
63289
|
-
f2.success("Only
|
|
63315
|
+
f2.success("Only your key can send them an update");
|
|
63290
63316
|
$e(`Done \u2705`);
|
|
63291
63317
|
}
|
|
63292
63318
|
return true;
|
|
@@ -63463,7 +63489,7 @@ async function uploadBundle(appid, options, shouldExit = true) {
|
|
|
63463
63489
|
oe(`Uploading`);
|
|
63464
63490
|
await checkLatest();
|
|
63465
63491
|
let { bundle: bundle2, path: path3, channel: channel2 } = options;
|
|
63466
|
-
const { external, key: key2
|
|
63492
|
+
const { external, key: key2, displayIvSession, autoMinUpdateVersion, ignoreMetadataCheck } = options;
|
|
63467
63493
|
let { minUpdateVersion } = options;
|
|
63468
63494
|
options.apikey = options.apikey || findSavedKey();
|
|
63469
63495
|
const snag = useLogSnag();
|
|
@@ -63586,11 +63612,14 @@ Trial expires in ${isTrial2} days`);
|
|
|
63586
63612
|
s.start(`Calculating checksum`);
|
|
63587
63613
|
checksum = await (0, import_checksum2.checksum)(zipped, "crc32");
|
|
63588
63614
|
s.stop(`Checksum: ${checksum}`);
|
|
63589
|
-
if (key2
|
|
63590
|
-
|
|
63615
|
+
if (key2 === false) {
|
|
63616
|
+
f2.info(`Encryption ignored`);
|
|
63617
|
+
} else if (key2 || (0, import_node_fs8.existsSync)(baseKey)) {
|
|
63618
|
+
await checKOldEncryption();
|
|
63619
|
+
const privateKey = typeof key2 === "string" ? key2 : baseKey;
|
|
63591
63620
|
let keyData = options.keyData || "";
|
|
63592
|
-
if (!keyData && !(0, import_node_fs8.existsSync)(
|
|
63593
|
-
f2.error(`Cannot find
|
|
63621
|
+
if (!keyData && !(0, import_node_fs8.existsSync)(privateKey)) {
|
|
63622
|
+
f2.error(`Cannot find private key ${privateKey}`);
|
|
63594
63623
|
if (import_ci_info.default.isCI)
|
|
63595
63624
|
program.error("");
|
|
63596
63625
|
const res2 = await se({ message: "Do you want to use our public key ?" });
|
|
@@ -63611,7 +63640,7 @@ Trial expires in ${isTrial2} days`);
|
|
|
63611
63640
|
notify: false
|
|
63612
63641
|
}).catch();
|
|
63613
63642
|
if (!keyData) {
|
|
63614
|
-
const keyFile = (0, import_node_fs8.readFileSync)(
|
|
63643
|
+
const keyFile = (0, import_node_fs8.readFileSync)(privateKey);
|
|
63615
63644
|
keyData = keyFile.toString();
|
|
63616
63645
|
}
|
|
63617
63646
|
f2.info(`Encrypting your bundle`);
|
|
@@ -64415,23 +64444,29 @@ async function decryptZip(zipPath, ivsessionKey, options) {
|
|
|
64415
64444
|
}
|
|
64416
64445
|
const config = await getConfig();
|
|
64417
64446
|
const { extConfig } = config.app;
|
|
64418
|
-
|
|
64419
|
-
|
|
64447
|
+
await checKOldEncryption();
|
|
64448
|
+
if (!options.key && !(0, import_node_fs12.existsSync)(baseKeyPub) && !extConfig.plugins?.CapacitorUpdater?.publicKey) {
|
|
64449
|
+
f2.error(`Public key not found at the path ${baseKeyPub} or in ${config.app.extConfigFilePath}`);
|
|
64420
64450
|
program.error("");
|
|
64421
64451
|
}
|
|
64422
|
-
const keyPath = options.key ||
|
|
64423
|
-
let
|
|
64424
|
-
if (!(0, import_node_fs12.existsSync)(keyPath) && !
|
|
64425
|
-
f2.error(`Cannot find public key ${keyPath} or as keyData option or in ${config.app.extConfigFilePath}`);
|
|
64452
|
+
const keyPath = options.key || baseKeyPub;
|
|
64453
|
+
let publicKey = extConfig?.plugins?.CapacitorUpdater?.publicKey;
|
|
64454
|
+
if (!(0, import_node_fs12.existsSync)(keyPath) && !publicKey) {
|
|
64455
|
+
f2.error(`Cannot find a public key at ${keyPath} or as keyData option or in ${config.app.extConfigFilePath}`);
|
|
64426
64456
|
program.error("");
|
|
64427
64457
|
} else if ((0, import_node_fs12.existsSync)(keyPath)) {
|
|
64428
64458
|
const keyFile = (0, import_node_fs12.readFileSync)(keyPath);
|
|
64429
|
-
|
|
64459
|
+
publicKey = keyFile.toString();
|
|
64460
|
+
}
|
|
64461
|
+
if (publicKey && !publicKey.startsWith("-----BEGIN RSA PUBLIC KEY-----")) {
|
|
64462
|
+
f2.error(`the public key provided is not a valid RSA Public key`);
|
|
64463
|
+
program.error("");
|
|
64430
64464
|
}
|
|
64431
64465
|
const zipFile = (0, import_node_fs12.readFileSync)(zipPath);
|
|
64432
|
-
const decodedZip = decryptSource(zipFile, ivsessionKey, options.keyData ??
|
|
64466
|
+
const decodedZip = decryptSource(zipFile, ivsessionKey, options.keyData ?? publicKey ?? "");
|
|
64433
64467
|
(0, import_node_fs12.writeFileSync)(`${zipPath}_decrypted.zip`, decodedZip);
|
|
64434
|
-
|
|
64468
|
+
f2.success(`Decrypted zip file at ${zipPath}_decrypted.zip`);
|
|
64469
|
+
$e(`Done \u2705`);
|
|
64435
64470
|
import_node_process19.default.exit();
|
|
64436
64471
|
}
|
|
64437
64472
|
|
|
@@ -64443,30 +64478,35 @@ async function encryptZip(zipPath, options) {
|
|
|
64443
64478
|
oe(`Encryption`);
|
|
64444
64479
|
await checkLatest();
|
|
64445
64480
|
const localConfig = await getLocalConfig();
|
|
64481
|
+
await checKOldEncryption();
|
|
64446
64482
|
if (!(0, import_node_fs13.existsSync)(zipPath)) {
|
|
64447
64483
|
f2.error(`Error: Zip not found at the path ${zipPath}`);
|
|
64448
64484
|
program.error("");
|
|
64449
64485
|
}
|
|
64450
|
-
const keyPath = options.key ||
|
|
64451
|
-
let
|
|
64452
|
-
if (!(0, import_node_fs13.existsSync)(keyPath) && !
|
|
64453
|
-
f2.warning(`Cannot find
|
|
64486
|
+
const keyPath = options.key || baseKey;
|
|
64487
|
+
let privateKey = options.keyData || "";
|
|
64488
|
+
if (!(0, import_node_fs13.existsSync)(keyPath) && !privateKey) {
|
|
64489
|
+
f2.warning(`Cannot find a private key at ${keyPath} or as a keyData option`);
|
|
64454
64490
|
if (import_ci_info2.default.isCI) {
|
|
64455
|
-
f2.error(`Error: Missing
|
|
64491
|
+
f2.error(`Error: Missing key`);
|
|
64456
64492
|
program.error("");
|
|
64457
64493
|
}
|
|
64458
|
-
const res = await se({ message:
|
|
64494
|
+
const res = await se({ message: `Do you want to use our private key?` });
|
|
64459
64495
|
if (!res) {
|
|
64460
|
-
f2.error(`Error: Missing
|
|
64496
|
+
f2.error(`Error: Missing private key`);
|
|
64461
64497
|
program.error("");
|
|
64462
64498
|
}
|
|
64463
|
-
|
|
64499
|
+
privateKey = localConfig.signKey || "";
|
|
64464
64500
|
} else if ((0, import_node_fs13.existsSync)(keyPath)) {
|
|
64465
64501
|
const keyFile = (0, import_node_fs13.readFileSync)(keyPath);
|
|
64466
|
-
|
|
64502
|
+
privateKey = keyFile.toString();
|
|
64503
|
+
}
|
|
64504
|
+
if (privateKey && !privateKey.startsWith("-----BEGIN RSA PRIVATE KEY-----")) {
|
|
64505
|
+
f2.error(`the private key provided is not a valid RSA Private key`);
|
|
64506
|
+
program.error("");
|
|
64467
64507
|
}
|
|
64468
64508
|
const zipFile = (0, import_node_fs13.readFileSync)(zipPath);
|
|
64469
|
-
const encodedZip = encryptSource(zipFile,
|
|
64509
|
+
const encodedZip = encryptSource(zipFile, privateKey);
|
|
64470
64510
|
f2.success(`ivSessionKey: ${encodedZip.ivSessionKey}`);
|
|
64471
64511
|
(0, import_node_fs13.writeFileSync)(`${zipPath}_encrypted.zip`, encodedZip.encryptedData);
|
|
64472
64512
|
f2.success(`Encrypted zip saved at ${zipPath}_encrypted.zip`);
|
|
@@ -65124,7 +65164,7 @@ bundle.command("delete [bundleId] [appId]").alias("d").description("Delete a bun
|
|
|
65124
65164
|
bundle.command("list [appId]").alias("l").description("List bundle in Capgo Cloud").action(listBundle).option("-a, --apikey <apikey>", "apikey to link to your account");
|
|
65125
65165
|
bundle.command("unlink [appId]").description("Unlink a bundle in Capgo Cloud").action(listBundle).option("-a, --apikey <apikey>", "apikey to link to your account").option("-b, --bundle <bundle>", "bundle version number of the bundle to unlink");
|
|
65126
65166
|
bundle.command("cleanup [appId]").alias("c").action(cleanupBundle).description("Cleanup bundle in Capgo Cloud").option("-b, --bundle <bundle>", "bundle version number of the app to delete").option("-a, --apikey <apikey>", "apikey to link to your account").option("-k, --keep <keep>", "number of version to keep").option("-f, --force", "force removal");
|
|
65127
|
-
bundle.command("decrypt [zipPath] [sessionKey]").description("Decrypt a signed zip bundle").action(decryptZip).option("--key <key>", "custom path for
|
|
65167
|
+
bundle.command("decrypt [zipPath] [sessionKey]").description("Decrypt a signed zip bundle").action(decryptZip).option("--key <key>", "custom path for public signing key").option("--key-data <keyData>", "base64 public signing key");
|
|
65128
65168
|
bundle.command("encrypt [zipPath]").description("Encrypt a zip bundle").action(encryptZip).option("--key <key>", "custom path for private signing key").option("--key-data <keyData>", "base64 private signing key");
|
|
65129
65169
|
bundle.command("zip [appId]").description("Zip a bundle").action(zipBundle).option("-p, --path <path>", "path of the folder to upload").option("-b, --bundle <bundle>", "bundle version number to name the zip file").option("-n, --name <name>", "name of the zip file").option("-j, --json", "output in JSON").option("--no-code-check", "Ignore checking if notifyAppReady() is called in soure code and index present in root folder");
|
|
65130
65170
|
var channel = program.command("channel").description("Manage channel");
|
|
@@ -65134,7 +65174,7 @@ channel.command("list [appId]").alias("l").description("List channel").action(li
|
|
|
65134
65174
|
channel.command("currentBundle [channel] [appId]").description("Get current bundle for specific channel in Capgo Cloud").action(currentBundle).option("-c, --channel <channel>", "channel to get the current bundle from").option("-a, --apikey <apikey>", "apikey to link to your account").option("--quiet", "only print the bundle version");
|
|
65135
65175
|
channel.command("set [channelId] [appId]").alias("s").description("Set channel").action(setChannel).option("-a, --apikey <apikey>", "apikey to link to your account").option("-b, --bundle <bundle>", "bundle version number of the file to set").option("-s, --state <state>", "set the state of the channel, default or normal").option("--latest", "get the latest version key in the package.json to set it to the channel").option("--downgrade", "Allow to downgrade to version under native one").option("--no-downgrade", "Disable downgrade to version under native one").option("--upgrade", "Allow to upgrade to version above native one").option("--no-upgrade", "Disable upgrade to version above native one").option("--ios", "Allow sending update to ios devices").option("--no-ios", "Disable sending update to ios devices").option("--android", "Allow sending update to android devices").option("--no-android", "Disable sending update to android devices").option("--self-assign", "Allow to device to self assign to this channel").option("--no-self-assign", "Disable devices to self assign to this channel").option("--disable-auto-update <disableAutoUpdate>", "Disable auto update strategy for this channel.The possible options are: major, minor, metadata, none");
|
|
65136
65176
|
var key = program.command("key").description("Manage key");
|
|
65137
|
-
key.command("save").description("Save base64 signing key in capacitor config,
|
|
65177
|
+
key.command("save").description("Save base64 signing key in capacitor config, useful for CI").action(saveKeyCommand).option("-f, --force", "force generate a new one").option("--key", "key path to save in capacitor config").option("--key-data <keyData>", "key data to save in capacitor config");
|
|
65138
65178
|
key.command("create").description("Create a new signing key").action(createKeyCommand).option("-f, --force", "force generate a new one");
|
|
65139
65179
|
program.command("upload [appId]").alias("u").description("(Deprecated) Upload a new bundle to Capgo Cloud").action(uploadDeprecatedCommand).option("-a, --apikey <apikey>", "apikey to link to your account").option("-p, --path <path>", "path of the folder to upload").option("-c, --channel <channel>", "channel to link to").option("-e, --external <url>", "link to external url intead of upload to Capgo Cloud").option("--key <key>", "custom path for public signing key").option("--key-data <keyData>", "base64 public signing key").option("--bundle-url", "prints bundle url into stdout").option("--no-key", "ignore signing key and send clear update").option("--display-iv-session", "Show in the console the iv and session key used to encrypt the update").option("-b, --bundle <bundle>", "bundle version number of the file to upload").option(
|
|
65140
65180
|
"--min-update-version <minUpdateVersion>",
|
package/package.json
CHANGED
package/src/api/crypto.ts
CHANGED
|
@@ -3,30 +3,31 @@ import {
|
|
|
3
3
|
createCipheriv,
|
|
4
4
|
createDecipheriv,
|
|
5
5
|
generateKeyPairSync,
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
privateEncrypt,
|
|
7
|
+
publicDecrypt,
|
|
8
8
|
randomBytes,
|
|
9
9
|
} from 'node:crypto'
|
|
10
10
|
import { Buffer } from 'node:buffer'
|
|
11
11
|
|
|
12
12
|
const algorithm = 'aes-128-cbc'
|
|
13
|
-
const oaepHash = 'sha256'
|
|
14
13
|
const formatB64 = 'base64'
|
|
15
|
-
const padding = constants.
|
|
14
|
+
const padding = constants.RSA_PKCS1_PADDING
|
|
16
15
|
|
|
17
|
-
export function decryptSource(source: Buffer, ivSessionKey: string,
|
|
16
|
+
export function decryptSource(source: Buffer, ivSessionKey: string, key: string): Buffer {
|
|
17
|
+
// console.log('decryptKeyType - ', decryptKeyType);
|
|
18
|
+
// console.log(key);
|
|
18
19
|
// console.log('\nivSessionKey', ivSessionKey)
|
|
19
20
|
const [ivB64, sessionb64Encrypted] = ivSessionKey.split(':')
|
|
20
21
|
// console.log('\nsessionb64Encrypted', sessionb64Encrypted)
|
|
21
22
|
// console.log('\nivB64', ivB64)
|
|
22
|
-
const sessionKey =
|
|
23
|
+
const sessionKey = publicDecrypt(
|
|
23
24
|
{
|
|
24
|
-
key
|
|
25
|
+
key,
|
|
25
26
|
padding,
|
|
26
|
-
oaepHash,
|
|
27
27
|
},
|
|
28
28
|
Buffer.from(sessionb64Encrypted, formatB64),
|
|
29
29
|
)
|
|
30
|
+
|
|
30
31
|
// ivB64 to uft-8
|
|
31
32
|
const initVector = Buffer.from(ivB64, formatB64)
|
|
32
33
|
// console.log('\nSessionB64', sessionB64)
|
|
@@ -41,7 +42,10 @@ export interface Encoded {
|
|
|
41
42
|
ivSessionKey: string
|
|
42
43
|
encryptedData: Buffer
|
|
43
44
|
}
|
|
44
|
-
export function encryptSource(source: Buffer,
|
|
45
|
+
export function encryptSource(source: Buffer, key: string): Encoded {
|
|
46
|
+
// console.log('decryptKeyType - ', decryptKeyType);
|
|
47
|
+
// console.log(key);
|
|
48
|
+
|
|
45
49
|
// encrypt zip with key
|
|
46
50
|
const initVector = randomBytes(16)
|
|
47
51
|
const sessionKey = randomBytes(16)
|
|
@@ -54,14 +58,15 @@ export function encryptSource(source: Buffer, publicKey: string): Encoded {
|
|
|
54
58
|
// console.log('\nsessionB64', sessionB64)
|
|
55
59
|
const ivB64 = initVector.toString(formatB64)
|
|
56
60
|
// console.log('\nivB64', ivB64)
|
|
57
|
-
|
|
61
|
+
|
|
62
|
+
const sessionb64Encrypted = privateEncrypt(
|
|
58
63
|
{
|
|
59
|
-
key
|
|
64
|
+
key,
|
|
60
65
|
padding,
|
|
61
|
-
oaepHash,
|
|
62
66
|
},
|
|
63
67
|
sessionKey,
|
|
64
68
|
).toString(formatB64)
|
|
69
|
+
|
|
65
70
|
// console.log('\nsessionb64Encrypted', sessionb64Encrypted)
|
|
66
71
|
const ivSessionKey = `${ivB64}:${sessionb64Encrypted}`
|
|
67
72
|
// console.log('\nivSessionKey', sessionKey)
|
package/src/bundle/decrypt.ts
CHANGED
|
@@ -3,7 +3,7 @@ import process from 'node:process'
|
|
|
3
3
|
import { program } from 'commander'
|
|
4
4
|
import * as p from '@clack/prompts'
|
|
5
5
|
import { decryptSource } from '../api/crypto'
|
|
6
|
-
import {
|
|
6
|
+
import { baseKeyPub, checKOldEncryption, getConfig } from '../utils'
|
|
7
7
|
import { checkLatest } from '../api/update'
|
|
8
8
|
|
|
9
9
|
interface Options {
|
|
@@ -23,32 +23,43 @@ export async function decryptZip(zipPath: string, ivsessionKey: string, options:
|
|
|
23
23
|
|
|
24
24
|
const config = await getConfig()
|
|
25
25
|
const { extConfig } = config.app
|
|
26
|
+
// console.log('config - ', config)
|
|
27
|
+
// console.log('extConfig - ', extConfig)
|
|
26
28
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
+
await checKOldEncryption()
|
|
30
|
+
// console.log(`There ${hasPrivateKeyInConfig ? 'IS' : 'IS NOT'} a privateKey in the config`);
|
|
31
|
+
|
|
32
|
+
if (!options.key && !existsSync(baseKeyPub) && !extConfig.plugins?.CapacitorUpdater?.publicKey) {
|
|
33
|
+
p.log.error(`Public key not found at the path ${baseKeyPub} or in ${config.app.extConfigFilePath}`)
|
|
29
34
|
program.error('')
|
|
30
35
|
}
|
|
31
|
-
const keyPath = options.key ||
|
|
32
|
-
// check if
|
|
36
|
+
const keyPath = options.key || baseKeyPub
|
|
37
|
+
// check if private exist
|
|
33
38
|
|
|
34
|
-
let
|
|
39
|
+
let publicKey = extConfig?.plugins?.CapacitorUpdater?.publicKey
|
|
35
40
|
|
|
36
|
-
if (!existsSync(keyPath) && !
|
|
37
|
-
p.log.error(`Cannot find public key ${keyPath} or as keyData option or in ${config.app.extConfigFilePath}`)
|
|
41
|
+
if (!existsSync(keyPath) && !publicKey) {
|
|
42
|
+
p.log.error(`Cannot find a public key at ${keyPath} or as keyData option or in ${config.app.extConfigFilePath}`)
|
|
38
43
|
program.error('')
|
|
39
44
|
}
|
|
40
45
|
else if (existsSync(keyPath)) {
|
|
41
46
|
// open with fs publicKey path
|
|
42
47
|
const keyFile = readFileSync(keyPath)
|
|
43
|
-
|
|
48
|
+
publicKey = keyFile.toString()
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// let's doublecheck and make sure the key we are using is the right type based on the decryption strategy
|
|
52
|
+
if (publicKey && !publicKey.startsWith('-----BEGIN RSA PUBLIC KEY-----')) {
|
|
53
|
+
p.log.error(`the public key provided is not a valid RSA Public key`)
|
|
54
|
+
program.error('')
|
|
44
55
|
}
|
|
45
|
-
// console.log('privateKey', privateKey)
|
|
46
56
|
|
|
47
57
|
const zipFile = readFileSync(zipPath)
|
|
48
58
|
|
|
49
|
-
const decodedZip = decryptSource(zipFile, ivsessionKey, options.keyData ??
|
|
59
|
+
const decodedZip = decryptSource(zipFile, ivsessionKey, options.keyData ?? publicKey ?? '')
|
|
50
60
|
// write decodedZip in a file
|
|
51
61
|
writeFileSync(`${zipPath}_decrypted.zip`, decodedZip)
|
|
52
|
-
p.
|
|
62
|
+
p.log.success(`Decrypted zip file at ${zipPath}_decrypted.zip`)
|
|
63
|
+
p.outro(`Done ✅`)
|
|
53
64
|
process.exit()
|
|
54
65
|
}
|
package/src/bundle/encrypt.ts
CHANGED
|
@@ -5,7 +5,7 @@ import ciDetect from 'ci-info'
|
|
|
5
5
|
import * as p from '@clack/prompts'
|
|
6
6
|
import { checkLatest } from '../api/update'
|
|
7
7
|
import { encryptSource } from '../api/crypto'
|
|
8
|
-
import {
|
|
8
|
+
import { baseKey, checKOldEncryption, getLocalConfig } from '../utils'
|
|
9
9
|
|
|
10
10
|
interface Options {
|
|
11
11
|
key?: string
|
|
@@ -17,40 +17,49 @@ export async function encryptZip(zipPath: string, options: Options) {
|
|
|
17
17
|
|
|
18
18
|
await checkLatest()
|
|
19
19
|
const localConfig = await getLocalConfig()
|
|
20
|
+
// console.log('localConfig - ', localConfig)
|
|
21
|
+
// console.log('config - ', config)
|
|
20
22
|
|
|
21
|
-
|
|
23
|
+
await checKOldEncryption()
|
|
22
24
|
|
|
23
25
|
if (!existsSync(zipPath)) {
|
|
24
26
|
p.log.error(`Error: Zip not found at the path ${zipPath}`)
|
|
25
27
|
program.error('')
|
|
26
28
|
}
|
|
27
29
|
|
|
28
|
-
const keyPath = options.key ||
|
|
29
|
-
// check if
|
|
30
|
+
const keyPath = options.key || baseKey
|
|
31
|
+
// check if privateKey exist
|
|
30
32
|
|
|
31
|
-
let
|
|
33
|
+
let privateKey = options.keyData || ''
|
|
32
34
|
|
|
33
|
-
if (!existsSync(keyPath) && !
|
|
34
|
-
p.log.warning(`Cannot find
|
|
35
|
+
if (!existsSync(keyPath) && !privateKey) {
|
|
36
|
+
p.log.warning(`Cannot find a private key at ${keyPath} or as a keyData option`)
|
|
35
37
|
if (ciDetect.isCI) {
|
|
36
|
-
p.log.error(`Error: Missing
|
|
38
|
+
p.log.error(`Error: Missing key`)
|
|
37
39
|
program.error('')
|
|
38
40
|
}
|
|
39
|
-
const res = await p.confirm({ message:
|
|
41
|
+
const res = await p.confirm({ message: `Do you want to use our private key?` })
|
|
40
42
|
if (!res) {
|
|
41
|
-
p.log.error(`Error: Missing
|
|
43
|
+
p.log.error(`Error: Missing private key`)
|
|
42
44
|
program.error('')
|
|
43
45
|
}
|
|
44
|
-
|
|
46
|
+
|
|
47
|
+
privateKey = localConfig.signKey || ''
|
|
45
48
|
}
|
|
46
49
|
else if (existsSync(keyPath)) {
|
|
47
|
-
// open with fs
|
|
50
|
+
// open with fs key path
|
|
48
51
|
const keyFile = readFileSync(keyPath)
|
|
49
|
-
|
|
52
|
+
privateKey = keyFile.toString()
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// let's doublecheck and make sure the key we are using is the right type based on the decryption strategy
|
|
56
|
+
if (privateKey && !privateKey.startsWith('-----BEGIN RSA PRIVATE KEY-----')) {
|
|
57
|
+
p.log.error(`the private key provided is not a valid RSA Private key`)
|
|
58
|
+
program.error('')
|
|
50
59
|
}
|
|
51
60
|
|
|
52
61
|
const zipFile = readFileSync(zipPath)
|
|
53
|
-
const encodedZip = encryptSource(zipFile,
|
|
62
|
+
const encodedZip = encryptSource(zipFile, privateKey)
|
|
54
63
|
p.log.success(`ivSessionKey: ${encodedZip.ivSessionKey}`)
|
|
55
64
|
// write decodedZip in a file
|
|
56
65
|
writeFileSync(`${zipPath}_encrypted.zip`, encodedZip.encryptedData)
|
package/src/bundle/upload.ts
CHANGED
|
@@ -16,7 +16,8 @@ import type {
|
|
|
16
16
|
} from '../utils'
|
|
17
17
|
import {
|
|
18
18
|
OrganizationPerm,
|
|
19
|
-
|
|
19
|
+
baseKey,
|
|
20
|
+
checKOldEncryption,
|
|
20
21
|
checkCompatibility,
|
|
21
22
|
checkPlanValid,
|
|
22
23
|
convertAppName,
|
|
@@ -60,7 +61,7 @@ export async function uploadBundle(appid: string, options: Options, shouldExit =
|
|
|
60
61
|
p.intro(`Uploading`)
|
|
61
62
|
await checkLatest()
|
|
62
63
|
let { bundle, path, channel } = options
|
|
63
|
-
const { external, key
|
|
64
|
+
const { external, key, displayIvSession, autoMinUpdateVersion, ignoreMetadataCheck } = options
|
|
64
65
|
let { minUpdateVersion } = options
|
|
65
66
|
options.apikey = options.apikey || findSavedKey()
|
|
66
67
|
const snag = useLogSnag()
|
|
@@ -227,12 +228,16 @@ export async function uploadBundle(appid: string, options: Options, shouldExit =
|
|
|
227
228
|
s.start(`Calculating checksum`)
|
|
228
229
|
checksum = await getChecksum(zipped, 'crc32')
|
|
229
230
|
s.stop(`Checksum: ${checksum}`)
|
|
230
|
-
if (key
|
|
231
|
-
|
|
231
|
+
if (key === false) {
|
|
232
|
+
p.log.info(`Encryption ignored`)
|
|
233
|
+
}
|
|
234
|
+
else if (key || existsSync(baseKey)) {
|
|
235
|
+
await checKOldEncryption()
|
|
236
|
+
const privateKey = typeof key === 'string' ? key : baseKey
|
|
232
237
|
let keyData = options.keyData || ''
|
|
233
|
-
// check if
|
|
234
|
-
if (!keyData && !existsSync(
|
|
235
|
-
p.log.error(`Cannot find
|
|
238
|
+
// check if privateKey exist
|
|
239
|
+
if (!keyData && !existsSync(privateKey)) {
|
|
240
|
+
p.log.error(`Cannot find private key ${privateKey}`)
|
|
236
241
|
if (ciDetect.isCI)
|
|
237
242
|
program.error('')
|
|
238
243
|
|
|
@@ -253,9 +258,9 @@ export async function uploadBundle(appid: string, options: Options, shouldExit =
|
|
|
253
258
|
},
|
|
254
259
|
notify: false,
|
|
255
260
|
}).catch()
|
|
256
|
-
// open with fs
|
|
261
|
+
// open with fs privateKey path
|
|
257
262
|
if (!keyData) {
|
|
258
|
-
const keyFile = readFileSync(
|
|
263
|
+
const keyFile = readFileSync(privateKey)
|
|
259
264
|
keyData = keyFile.toString()
|
|
260
265
|
}
|
|
261
266
|
// encrypt
|
package/src/index.ts
CHANGED
|
@@ -169,8 +169,8 @@ bundle
|
|
|
169
169
|
.command('decrypt [zipPath] [sessionKey]')
|
|
170
170
|
.description('Decrypt a signed zip bundle')
|
|
171
171
|
.action(decryptZip)
|
|
172
|
-
.option('--key <key>', 'custom path for
|
|
173
|
-
.option('--key-data <keyData>', 'base64
|
|
172
|
+
.option('--key <key>', 'custom path for public signing key')
|
|
173
|
+
.option('--key-data <keyData>', 'base64 public signing key')
|
|
174
174
|
|
|
175
175
|
bundle
|
|
176
176
|
.command('encrypt [zipPath]')
|
|
@@ -250,11 +250,11 @@ const key = program
|
|
|
250
250
|
|
|
251
251
|
key
|
|
252
252
|
.command('save')
|
|
253
|
-
.description('Save base64 signing key in capacitor config,
|
|
253
|
+
.description('Save base64 signing key in capacitor config, useful for CI')
|
|
254
254
|
.action(saveKeyCommand)
|
|
255
255
|
.option('-f, --force', 'force generate a new one')
|
|
256
256
|
.option('--key', 'key path to save in capacitor config')
|
|
257
|
-
.option('--key-data', 'key data to save in capacitor config')
|
|
257
|
+
.option('--key-data <keyData>', 'key data to save in capacitor config')
|
|
258
258
|
|
|
259
259
|
key
|
|
260
260
|
.command('create')
|