Skip to content

Commit

Permalink
Improved formatting
Browse files Browse the repository at this point in the history
  • Loading branch information
keeramis committed Jun 17, 2024
1 parent e5b9ad1 commit 352ce07
Show file tree
Hide file tree
Showing 6 changed files with 134 additions and 82 deletions.
22 changes: 16 additions & 6 deletions src/cli/device-protection.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
const unindent = require('../lib/unindent');

module.exports = ({ commandProcessor, root }) => {
const deviceProtection = commandProcessor.createCategory(root, 'protection', 'Commands for managing device protection');
const deviceProtection = commandProcessor.createCategory(root, 'device-protection', 'Commands for managing device protection');

commandProcessor.createCommand(deviceProtection, 'status', 'Gets the current device protection status', {
handler: () => {
Expand All @@ -14,18 +14,28 @@ module.exports = ({ commandProcessor, root }) => {
});

commandProcessor.createCommand(deviceProtection, 'disable', 'Disables device protection (temporary or permanent)', {
params: '[permanent]',
options: {
'open': {
boolean: true,
description: 'Unlocks a protected device and makes it an Open device'
}
},
handler: (args) => {
const DeviceProtectionCommands = require('../cmd/device-protection');
return new DeviceProtectionCommands().disableProtection(args.params);
return new DeviceProtectionCommands(args).disableProtection(args);
},
examples: {
'$0 $command': 'Disables device protection temporarily',
'$0 $command permanent': 'Disables device protection permanently'
'$0 $command': 'Device is temporarily unprotected',
'$0 $command --open': '[TBD] Device becomes an Open device'
}
});

commandProcessor.createCommand(deviceProtection, 'enable', 'Enables device protection', {
options: {
file: {
description: 'Provide file to use for device protection'
}
},
handler: (args) => {
const DeviceProtectionCommands = require('../cmd/device-protection');
return new DeviceProtectionCommands().enableProtection(args);
Expand All @@ -39,7 +49,7 @@ module.exports = ({ commandProcessor, root }) => {
params: '<file>',
handler: (args) => {
const DeviceProtectionCommands = require('../cmd/device-protection');
return new DeviceProtectionCommands().protectBinary(args.params.file);
return new DeviceProtectionCommands().protectBinary({ file: args.params.file, verbose: true });
},
examples: {
'$0 $command myBootloader.bin': 'Adds device-protection to your bootloader binary'
Expand Down
9 changes: 9 additions & 0 deletions src/cmd/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,15 @@ module.exports = class ParticleApi {
}));
}

getProduct({ product, auth, headers, context }) {
return this._wrap(this.api.getProduct({
product,
auth,
headers,
context
}));
}

_wrap(promise){
return Promise.resolve(promise)
.then(result => result.body || result)
Expand Down
165 changes: 96 additions & 69 deletions src/cmd/device-protection.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ const fs = require('fs-extra');
const { downloadDeviceOsVersionBinaries } = require('../lib/device-os-version-util');
const FlashCommand = require('./flash');
const { platformForId } = require('../lib/platform');
const chalk = require('chalk');

module.exports = class DeviceProtectionCommands extends CLICommandBase {
constructor({ ui } = {}) {
Expand All @@ -32,38 +31,29 @@ module.exports = class DeviceProtectionCommands extends CLICommandBase {
// then the device is not pretected. For now though, let's assume the device is in normal mode and not in dfu mode.

return this._withDevice(async () => {
let s;
try {
s = await this.device.getProtectionState();
} catch (error) {
if (error.message === 'Not implemented') {
throw new Error(`Device protection status is not supported on this device${os.EOL}${os.EOL}`);
}
throw new Error('Failed to get device protection status');
}
const s = await this._getDeviceProtection();

let res;
if (!s.protected && !s.overridden) {
res = 'not protected';
res = `Open (not protected)${os.EOL}Run 'particle device-protection enable' to protect the device${os.EOL}`;
} else if (s.protected && !s.overridden) {
res = 'protected';
res = `Protected${os.EOL}Run 'particle device-protection disable' unlock the device${os.EOL}`;
} else if (s.overridden) {
res = 'temporarily not protected. A reset is required to re-enable protection.';
res = `Protected (service mode)${os.EOL}Run 'particle device-protection enable' to enable protection${os.EOL}`;
}

this.ui.stdout.write(`Device (${this.deviceId}) is ${chalk.bold(res)}${os.EOL}${os.EOL}`);
this.ui.stdout.write(`Device protection: ${res}${os.EOL}`);

return s;
});
}

async disableProtection({ permanent }) {
// TODO : Remove logs with sensitive information
async disableProtection({ open } = {}) {
return this._withDevice(async () => {
const s = await this._getDeviceProtection();

return this._withDevice(async () => {
let s = await this.device.getProtectionState();
if (!s.protected && !s.overridden) {
this.ui.stdout.write(`Device is not protected${os.EOL}${os.EOL}`);
this.ui.stdout.write(`Device is not protected`);
return;
}

Expand All @@ -76,22 +66,22 @@ module.exports = class DeviceProtectionCommands extends CLICommandBase {
// console.log(`CLI -> Device:\n\tserver_nonce=${serverNonce.toString('base64')}`);
r = await this.device.unprotectDevice({ action: 'prepare', serverNonce });
if (!r.protected) {
console.log('Device is not protected');
return;
this.ui.stdout.write('Device is not protected');
return;
}
const { deviceNonce, deviceSignature, devicePublicKeyFingerprint } = r;
// console.log(`Device -> CLI:\n\tdevice_signature=${deviceSignature.toString('base64')}`);

// Verify the device signature and get a server signature
// console.log(`CLI -> Server:\n\tdevice_signature=${deviceSignature.toString('base64')}`);
r = await this.api.unprotectDevice({
deviceId: this.deviceId,
action: 'confirm',
serverNonce: serverNonce.toString('base64'),
deviceNonce: deviceNonce.toString('base64'),
deviceSignature: deviceSignature.toString('base64'),
devicePublicKeyFingerprint: devicePublicKeyFingerprint.toString('base64'),
auth: settings.access_token
deviceId: this.deviceId,
action: 'confirm',
serverNonce: serverNonce.toString('base64'),
deviceNonce: deviceNonce.toString('base64'),
deviceSignature: deviceSignature.toString('base64'),
devicePublicKeyFingerprint: devicePublicKeyFingerprint.toString('base64'),
auth: settings.access_token
});

const serverSignature = Buffer.from(r.server_signature, 'base64');
Expand All @@ -101,32 +91,30 @@ module.exports = class DeviceProtectionCommands extends CLICommandBase {
// Unprotect the device
await this.device.unprotectDevice({ action: 'confirm', serverSignature, serverPublicKeyFingerprint });

s = await this.device.getProtectionState();
if (!permanent) {
this.ui.stdout.write(`Device protection ${chalk.bold('temporarily disabled')}${os.EOL}${os.EOL}`);
s = await this._getDeviceProtection();
if (!open) {
this.ui.stdout.write(`Device protection temporarily disabled.${os.EOL}Device is put into service mode for 20 reboots or 24 hours.${os.EOL}`);
return;
}

if (permanent) {

const localBootloaderPath = await this._downloadBootloader();

await this._flashBootloader(localBootloaderPath);
const localBootloaderPath = await this._downloadBootloader();

this.ui.stdout.write(os.EOL);
await this._flashBootloader(localBootloaderPath, 'disable');

this.ui.stdout.write(`Device is permanently unlocked${os.EOL}${os.EOL}`);
this.ui.stdout.write(`Device protection disabled. Device is now open${os.EOL}`);
this.ui.stdout.write(`Putting device in developement mode to prevent cloud from enabling protection...${os.EOL}`);

const success = await this._markAsDevelopmentDevice(true);
const success = await this._markAsDevelopmentDevice(true);

if (!success) {
this.ui.stdout.write(`Failed to mark device as development device. Protection will be automatically enabled after a power cycle${os.EOL}`);
}
if (!success) {
this.ui.stdout.write('Failed to mark device as development device. Protection will be automatically enabled after a power cycle');
} else {
this.ui.stdout.write('Device is now in development mode');
}
});
}

async _downloadBootloader() {

let version;
const modules = await this.device.getFirmwareModuleInfo();
modules.forEach((m) => {
Expand All @@ -147,62 +135,99 @@ module.exports = class DeviceProtectionCommands extends CLICommandBase {



async enableProtection() {
async enableProtection({ file } = {}) {
// TODO: Option to provide bootloader binary in the path

// TODO: error if device protection is not supported on this firmware version
// TODO: Log better error if device protection is not supported on this firmware version
let protectedBinary = file;

return this._withDevice(async () => {
let s = await this.device.getProtectionState();
const s = await this._getDeviceProtection();

const attrs = await this.api.getDeviceAttributes(this.deviceId);
let deviceProtectionActiveInProduct = false;
if (attrs.platform_id !== attrs.product_id) {
// it's in a product
const res = await this.api.getProduct({ product: attrs.product_id, auth: settings.access_token });
deviceProtectionActiveInProduct = res.product.device_protection;
}

if (s.protected && !s.overridden) {
this.ui.stdout.write(`Device is protected${os.EOL}${os.EOL}`);
this.ui.stdout.write(`Device is protected`);
return;
}

if (s.overridden) {
// terminate unlock
await this.device.unprotectDevice({ action: 'reset' });
this.ui.stdout.write(`Device is ${chalk.bold('protected')}${os.EOL}`);

this.ui.stdout.write(`Device is protected`);
this.ui.stdout.write(`Removing device from developement mode...`);

const success = await this._markAsDevelopmentDevice(false);
if (success) {
this.ui.stdout.write(`Device was removed from development mode to keep in protection mode.${os.EOL}`);
} else {
this.ui.stdout.write(`Failed to mark device as development device${os.EOL}`);
if (!success) {
this.ui.stdout.write(`Failed to remove device from development mode. Ensure it is not in development mode for protection to work properly`);
}

return;
}

if (!s.protected && !s.overridden) {
if (!s.protected && !s.overridden && deviceProtectionActiveInProduct) {
// Protect device (permanently)

const localBootloaderPath = await this._downloadBootloader();

const resPath = await this.protectBinary(localBootloaderPath);
if (!protectedBinary) {
const localBootloaderPath = await this._downloadBootloader();
protectedBinary = await this.protectBinary({ file: localBootloaderPath, verbose: false });
}

await this._flashBootloader(resPath);
await this._flashBootloader(protectedBinary, 'enable');

this.ui.stdout.write(`Remove device as development device...${os.EOL}${os.EOL}`);
this.ui.write(`Device is protected`);
this.ui.stdout.write(`Removing device from developement mode...`);

const success = await this._markAsDevelopmentDevice(false);

if (success) {
this.ui.stdout.write(`Device was removed from development mode to enable protection.${os.EOL}`);
} else {
this.ui.stdout.write(`Failed to remove device from development mode.${os.EOL}`);

if (!success) {
this.ui.stdout.write(`Failed to remove device from development mode. Ensure it is not in development mode for protection to work properly`);
}
}
});
}

async _flashBootloader(path) {
async _getDeviceProtection() {
try {
const s = await this.device.getProtectionState();
return s;
} catch (error) {
if (error.message === 'Not supported') {
throw new Error(`Device protection feature is not supported on this device${os.EOL}`);
}
}
}

async _flashBootloader(path, action) {
let msg;
switch (action) {
case 'enable':
msg = 'Enabling protection on the device. Please wait...';
break;
case 'disable':
msg = 'Disabling protection on the device. Please wait...';
break;
default:
throw new Error('Invalid action');
}

const flashCmdInstance = new FlashCommand();
await flashCmdInstance.flashLocal({ files: [path], applicationOnly: true });

const flashPromise = flashCmdInstance.flashLocal({ files: [path], applicationOnly: true, verbose: false });

await this.ui.showBusySpinnerUntilResolved(msg, flashPromise);
}

async _markAsDevelopmentDevice(state) {
let attrs;
try {
// TODO: Refactor
attrs = await this.api.getDeviceAttributes(this.deviceId);
if (attrs.platform_id !== attrs.product_id) {
// it's in a product
Expand All @@ -225,7 +250,7 @@ module.exports = class DeviceProtectionCommands extends CLICommandBase {
if (this.device.isInDfuMode) {
this.ui.stdout.write(`Device is in DFU mode. Performing a reset to get the device in normal mode. Please wait...${os.EOL}`);
await this.resetDevice(this.device);
this.ui.stdout.write(`Done! Device is now in normal mode.${os.EOL}`);
this.ui.stdout.write(`Done! Device is now in normal mode`);
await this.getUsbDevice(this.device);
}

Expand All @@ -252,7 +277,7 @@ module.exports = class DeviceProtectionCommands extends CLICommandBase {
await new Promise(resolve => setTimeout(resolve, 3000));
}

async protectBinary(file) {
async protectBinary({ file, verbose=true }) {
if (!file) {
throw new Error('Please provide a file to add device protection');
}
Expand All @@ -266,7 +291,9 @@ module.exports = class DeviceProtectionCommands extends CLICommandBase {
const protectedBinary = await createProtectedModule(binary);
await fs.writeFile(resBinaryPath, protectedBinary);

this.ui.stdout.write(`Protected binary saved at ${resBinaryPath}${os.EOL}${os.EOL}`);
if (verbose) {
this.ui.stdout.write(`Protected binary saved at ${resBinaryPath}`);
}

return resBinaryPath;
}
Expand Down
8 changes: 5 additions & 3 deletions src/cmd/flash.js
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ module.exports = class FlashCommand extends CLICommandBase {
return new SerialCommands().flashDevice(binary, { port, yes });
}

async flashLocal({ files, applicationOnly, target }) {
async flashLocal({ files, applicationOnly, target, verbose=true }) {
const { files: parsedFiles, deviceIdOrName, knownApp } = await this._analyzeFiles(files);
const { api, auth } = this._particleApi();
const device = await usbUtils.getOneUsbDevice({ idOrName: deviceIdOrName, api, auth, ui: this.ui });
Expand All @@ -124,7 +124,9 @@ module.exports = class FlashCommand extends CLICommandBase {
const platformName = platformForId(platformId).name;
const currentDeviceOsVersion = device.firmwareVersion;

this.ui.write(`Flashing ${platformName} ${deviceIdOrName || device.id}`);
if (verbose) {
this.ui.write(`Flashing ${platformName} ${deviceIdOrName || device.id}`);
}

validateDFUSupport({ device, ui: this.ui });

Expand Down Expand Up @@ -160,7 +162,7 @@ module.exports = class FlashCommand extends CLICommandBase {
platformId
});

await flashFiles({ device, flashSteps, ui: this.ui });
await flashFiles({ device, flashSteps, ui: this.ui, verbose });
}

async _analyzeFiles(files) {
Expand Down
Loading

0 comments on commit 352ce07

Please sign in to comment.