Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Validate file used with enable-device-protection #750

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 34 additions & 3 deletions src/cmd/binary.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,25 +29,30 @@ const fs = require('fs-extra');
const path = require('path');
const VError = require('verror');
const chalk = require('chalk');
const { HalModuleParser: Parser, unpackApplicationAndAssetBundle, isAssetValid, createProtectedModule } = require('binary-version-reader');
const { HalModuleParser: Parser, unpackApplicationAndAssetBundle, isAssetValid, createProtectedModule, ModuleInfo } = require('binary-version-reader');
const utilities = require('../lib/utilities');
const ensureError = utilities.ensureError;

const INVALID_SUFFIX_SIZE = 65535;
const DEFAULT_PRODUCT_ID = 65535;
const DEFAULT_PRODUCT_VERSION = 65535;

const PROTECTED_MINIMUM_BOOTLOADER_VERSION = 3000;

class BinaryCommand {
async inspectBinary(file) {
await this._checkFile(file);
const extractedFiles = await this._extractFiles(file);
const extractedFiles = await this._extractApplicationFiles(file);
const parsedAppInfo = await this._parseApplicationBinary(extractedFiles.application);
const assets = extractedFiles.assets;
await this._verifyBundle(parsedAppInfo, assets);
}

async createProtectedBinary({ saveTo, file, verbose }) {
await this._checkFile(file);
const extractedFile = await this._extractFile(file);
const binaryModule = await this._parseBinary(extractedFile);
this._validateProtectedBinary(binaryModule);
let resBinaryName;

if (saveTo) {
Expand All @@ -69,6 +74,12 @@ class BinaryCommand {
return resBinaryPath;
}

_validateProtectedBinary(module) {
const { moduleFunction, moduleVersion, moduleIndex } = module.prefixInfo;
if (moduleFunction !== ModuleInfo.FunctionType.BOOTLOADER || moduleIndex !== 0 || moduleVersion < PROTECTED_MINIMUM_BOOTLOADER_VERSION) {
throw new Error('Device protection feature is not supported for this binary.');
}
}

async _checkFile(file) {
try {
Expand All @@ -79,7 +90,7 @@ class BinaryCommand {
return true;
}

async _extractFiles(file) {
async _extractApplicationFiles(file) {
if (utilities.getFilenameExt(file) === '.zip') {
return unpackApplicationAndAssetBundle(file);
} else if (utilities.getFilenameExt(file) === '.bin') {
Expand All @@ -90,6 +101,15 @@ class BinaryCommand {
}
}

async _extractFile(file) {
if (utilities.getFilenameExt(file) === '.bin') {
const data = await fs.readFile(file);
return { name: path.basename(file), data };
} else {
throw new VError(`File must be a .bin: ${file}`);
}
}

async _parseApplicationBinary(applicationBinary) {
const parser = new Parser();
let fileInfo;
Expand All @@ -110,6 +130,17 @@ class BinaryCommand {
return fileInfo;
}

async _parseBinary(binary) {
const parser = new Parser();
let fileInfo;
try {
fileInfo = await parser.parseBuffer({ filename: binary.name, fileBuffer: binary.data });
return fileInfo;
} catch (err) {
throw new VError(ensureError(err), `Could not parse ${binary.name}`);
}
}

async _verifyBundle(appInfo, assets) {
const appAssets = appInfo.assets;
if (appAssets && assets.length > 0) {
Expand Down
119 changes: 113 additions & 6 deletions src/cmd/binary.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,12 +56,12 @@ describe('Binary Inspect', () => {
});
});

describe('_extractFiles', () => {
describe('_extractApplicationFiles', () => {
it('errors if file is not .zip or .bin', async () => {
let error;

try {
await binaryCommand._extractFiles('not-a-zip-or-bin-file');
await binaryCommand._extractApplicationFiles('not-a-zip-or-bin-file');
} catch (_error) {
error = _error;
}
Expand All @@ -73,7 +73,7 @@ describe('Binary Inspect', () => {
it('extracts a .zip file', async () => {
const zipPath = path.join(PATH_FIXTURES_THIRDPARTY_OTA_DIR, 'bundle.zip');

const binaryInfo = await binaryCommand._extractFiles(zipPath);
const binaryInfo = await binaryCommand._extractApplicationFiles(zipPath);

expect(binaryInfo).to.have.property('application').with.property('name', 'app.bin');
expect(binaryInfo).to.have.property('assets').with.lengthOf(3);
Expand All @@ -87,7 +87,7 @@ describe('Binary Inspect', () => {
it('extracts a .bin file', async () => {
const binPath = path.join(PATH_FIXTURES_BINARIES_DIR, 'argon_stroby.bin');

const binaryInfo = await binaryCommand._extractFiles(binPath);
const binaryInfo = await binaryCommand._extractApplicationFiles(binPath);

expect(binaryInfo).to.have.property('application').with.property('name', 'argon_stroby.bin');
expect(binaryInfo).to.have.property('assets').with.lengthOf(0);
Expand All @@ -96,7 +96,7 @@ describe('Binary Inspect', () => {
it('handles if zip file does not have a binary or assets', async () => {
const zipPath = path.join(PATH_FIXTURES_THIRDPARTY_OTA_DIR, 'invalid-bundle.zip');

const binaryInfo = await binaryCommand._extractFiles(zipPath);
const binaryInfo = await binaryCommand._extractApplicationFiles(zipPath);

expect(binaryInfo).to.have.property('application').with.property('name', 'app.txt');
expect(binaryInfo).to.have.property('assets').with.lengthOf(0);
Expand Down Expand Up @@ -133,16 +133,123 @@ describe('Binary Inspect', () => {
});
});

describe('_parseBinary', () => {
it('parses a .bin file', async () => {
const name = 'argon_stroby.bin';
const data = await fs.readFile(path.join(PATH_FIXTURES_BINARIES_DIR, name));
const applicationBinary = { name, data };

const res = await binaryCommand._parseBinary(applicationBinary);

expect(path.basename(res.filename)).to.equal('argon_stroby.bin');
expect(res.crc.ok).to.equal(true);
expect(res).to.have.property('prefixInfo');
expect(res).to.have.property('suffixInfo');
});

it('errors if the binary is not valid', async () => {
const binary = { name: 'junk', data: Buffer.from('junk') };

let error;
try {
await binaryCommand._parseBinary(binary);
} catch (_error) {
error = _error;
}

expect(error).to.be.an.instanceof(Error);
expect(error.message).to.match(/Could not parse junk/);
});
});

describe('_verifyBundle', () => {
it('verifies bundle with asset info', async () => {
const zipPath = path.join(PATH_FIXTURES_THIRDPARTY_OTA_DIR, 'bundle.zip');
const res = await binaryCommand._extractFiles(zipPath);
const res = await binaryCommand._extractApplicationFiles(zipPath);
const parsedBinaryInfo = await binaryCommand._parseApplicationBinary(res.application);

const verify = await binaryCommand._verifyBundle(parsedBinaryInfo, res.assets);

expect(verify).to.equal(true);
});
});

describe('_validateProtectedBinary', () => {
it('validates a protected binary', async () => {
const module = {
prefixInfo: {
moduleIndex: 0,
moduleFunction: 2,
moduleVersion: 3000
}
};

let error;
try {
binaryCommand._validateProtectedBinary(module);
} catch (e) {
error = e;
}

expect(error).to.equal(undefined);
});

it('errors if binary is of the wrong module index', async () => {
const module = {
prefixInfo: {
moduleIndex: 1,
moduleFunction: 2,
moduleVersion: 3000
}
};

let error;
try {
binaryCommand._validateProtectedBinary(module);
} catch (e) {
error = e;
}

expect(error.message).to.equal('Device protection feature is not supported for this binary.');
});

it('errors if binary is not a bootloader', async () => {
const module = {
prefixInfo: {
moduleIndex: 1,
moduleFunction: 0,
moduleVersion: 3000
}
};

let error;
try {
binaryCommand._validateProtectedBinary(module);
} catch (e) {
error = e;
}

expect(error.message).to.equal('Device protection feature is not supported for this binary.');
});

it('errors if binary is of an older bootloader version', async () => {
const module = {
prefixInfo: {
moduleIndex: 1,
moduleFunction: 0,
moduleVersion: 2000
}
};

let error;
try {
binaryCommand._validateProtectedBinary(module);
} catch (e) {
error = e;
}

expect(error.message).to.equal('Device protection feature is not supported for this binary.');
});
});
});

Loading