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

Encrypted Flash option with Example #152

Open
wants to merge 11 commits into
base: feature/encrypted-flash
Choose a base branch
from
Open
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
6 changes: 6 additions & 0 deletions examples/typescript/src/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,12 @@ <h3> Program </h3>
</table>
<input class="btn btn-info btn-sm" type="button" id="addFile" value="Add File" />
<input class="btn btn-info btn-sm" type="button" id="programButton" value="Program" />
<br><br>
<label for="compressCheckbox">Flash compression:</label>
<input type="checkbox" id="compressCheckbox" name="compressCheckbox" checked>
<br>
<label for="encryptCheckbox">Encrypted flash:</label>
<input type="checkbox" id="encryptCheckbox" name="encryptCheckbox">
</div>
<output id="list"></output>
<hr/>
Expand Down
25 changes: 23 additions & 2 deletions examples/typescript/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -306,9 +306,29 @@ function validateProgramInputs() {
return "success";
}

document.addEventListener('DOMContentLoaded', (event) => {
const compressCheckbox = document.getElementById('compressCheckbox') as HTMLInputElement;
const encryptCheckbox = document.getElementById('encryptCheckbox') as HTMLInputElement;

encryptCheckbox.addEventListener('change', () => {
if (encryptCheckbox.checked) {
compressCheckbox.checked = false;
compressCheckbox.disabled = true;
} else {
compressCheckbox.checked = true;
compressCheckbox.disabled = false;
}
});
});

programButton.onclick = async () => {
const alertMsg = document.getElementById("alertmsg");
const err = validateProgramInputs();
const compressCheckbox = document.getElementById('compressCheckbox') as HTMLInputElement;
const encryptCheckbox = document.getElementById('encryptCheckbox') as HTMLInputElement;

console.log("compressCheckbox", compressCheckbox.checked);
console.log("encryptCheckbox", encryptCheckbox.checked);

if (err != "success") {
alertMsg.innerHTML = "<strong>" + err + "</strong>";
Expand Down Expand Up @@ -345,12 +365,13 @@ programButton.onclick = async () => {
fileArray: fileArray,
flashSize: "keep",
eraseAll: false,
compress: true,
compress: compressCheckbox.checked,
encrypt: encryptCheckbox.checked,
reportProgress: (fileIndex, written, total) => {
progressBars[fileIndex].value = (written / total) * 100;
},
calculateMD5Hash: (image) => CryptoJS.MD5(CryptoJS.enc.Latin1.parse(image)),
} as FlashOptions;
} as unknown as FlashOptions;
await esploader.writeFlash(flashOptions);
} catch (e) {
console.error(e);
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 2 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
{
"name": "esptool-js",
"version": "0.4.1",
"type": "module",
"version": "0.4.5",
"module": "lib/index.js",
"main": "lib/index.js",
"types": "lib/index.d.ts",
Expand All @@ -12,7 +11,7 @@
],
"scripts": {
"build": "npm run clean && tsc && rollup --config",
"clean": "rimraf lib bundle.js .parcel-cache",
"clean": "rimraf lib bundle.js .parcel-cache esptool-js-*.tgz",
"format": "prettier --write \"src/**/*.ts\"",
"genDocs": "rimraf docs && typedoc",
"lint": "eslint . --ext .ts",
Expand Down
2 changes: 1 addition & 1 deletion rollup.config.js → rollup.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ const config = {
inlineDynamicImports: true
},
plugins: [
resolve({ preferBuiltins: false}),
resolve({ preferBuiltins: false, mainFields: ["browser"]}),
commonjs(),
babel({ exclude: 'node_modules/**', babelHelpers: "runtime", skipPreflightCheck: true }),
json({ namedExports: false, preferConst: true }),
Expand Down
73 changes: 68 additions & 5 deletions src/esploader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,12 @@ export interface FlashOptions {
*/
compress: boolean;

/**
* Flag indicating whether to apply flash encryption when writing data.
* @type {boolean}
*/
encrypt: boolean;

/**
* A function to report the progress of the flashing operation (optional).
* @type {(fileIndex: number, written: number, total: number) => void}
Expand Down Expand Up @@ -155,6 +161,12 @@ async function magic2Chip(magic: number): Promise<ROM | null> {
const { ESP8266ROM } = await import("./targets/esp8266.js");
return new ESP8266ROM();
}
case 0x0:
case 0x0addbad0:
case 0x7039ad9: {
const { ESP32P4ROM } = await import("./targets/esp32p4.js");
return new ESP32P4ROM();
}
default:
return null;
}
Expand Down Expand Up @@ -207,6 +219,9 @@ export class ESPLoader {
ESP_READ_FLASH = 0xd2;
ESP_RUN_USER_CODE = 0xd3;

// Flash encryption encrypted data command
ESP_FLASH_ENCRYPT_DATA = 0xd4;

ESP_IMAGE_MAGIC = 0xe9;
ESP_CHECKSUM_MAGIC = 0xef;

Expand Down Expand Up @@ -449,6 +464,19 @@ export class ESPLoader {
}
}

/**
* Pads a given string to a specified alignment by appending a specified character.
* @param {string} data - String to be padded.
* @param {number} alignment - Alignment boundary.
* @param {string} padCharacter - The character to use for padding. Defaults to "\xff" if not specified.
* @returns {string} Return the padded string.
*/
padTo(data: string, alignment: number, padCharacter = "\xff"): string {
const pad_mod = data.length % alignment;
if (pad_mod !== 0) data += padCharacter.repeat(alignment).substring(alignment - pad_mod);
return data;
}

/**
* Use the device serial port read function with given timeout to create a valid packet.
* @param {number} op Operation number
Expand Down Expand Up @@ -896,6 +924,29 @@ export class ESPLoader {
await this.checkCommand("write to target Flash after seq " + seq, this.ESP_FLASH_DATA, pkt, checksum, timeout);
}

/**
* Write encrypted block to flash, retry if fail
* @param {Uint8Array} data Unsigned 8-bit array data.
* @param {number} seq Sequence number
* @param {number} timeout Timeout in milliseconds (ms)
*/
async flashEncryptBlock(data: Uint8Array, seq: number, timeout: number) {
let pkt = this._appendArray(this._intToByteArray(data.length), this._intToByteArray(seq));
pkt = this._appendArray(pkt, this._intToByteArray(0));
pkt = this._appendArray(pkt, this._intToByteArray(0));
pkt = this._appendArray(pkt, data);

const checksum = this.checksum(data);

await this.checkCommand(
"write encrypted to target Flash after seq " + seq,
this.ESP_FLASH_ENCRYPT_DATA,
pkt,
checksum,
timeout,
);
}

/**
* Write block to flash, send compressed, retry if fail
* @param {Uint8Array} data Unsigned int 8-bit array data to write
Expand Down Expand Up @@ -1367,9 +1418,15 @@ export class ESPLoader {
let image: string, address: number;
for (let i = 0; i < options.fileArray.length; i++) {
this.debug("Data Length " + options.fileArray[i].data.length);

if (options.compress && options.encrypt) {
this.info("WARNING: compress and encrypt options are mutually exclusive");
this.info("Will flash uncompressed");
options.compress = false;
}

image = options.fileArray[i].data;
const reminder = options.fileArray[i].data.length % 4;
if (reminder > 0) image += "\xff\xff\xff\xff".substring(4 - reminder);
image = this.padTo(image, options.encrypt ? this.chip.FLASH_ENCRYPTED_WRITE_ALIGN : 4);
address = options.fileArray[i].address;
this.debug("Image Length " + image.length);
if (image.length === 0) {
Expand Down Expand Up @@ -1416,7 +1473,7 @@ export class ESPLoader {
Math.floor((100 * (seq + 1)) / blocks) +
"%)",
);
const block = this.bstrToUi8(image.slice(0, this.FLASH_WRITE_SIZE));
let block = this.bstrToUi8(image.slice(0, this.FLASH_WRITE_SIZE));

if (options.compress) {
const lenUncompressedPrevious = totalLenUncompressed;
Expand All @@ -1436,7 +1493,13 @@ export class ESPLoader {
timeout = blockTimeout;
}
} else {
throw new ESPError("Yet to handle Non Compressed writes");
const padding = new Uint8Array(this.FLASH_WRITE_SIZE - block.length).fill(0xff);
block = this._appendArray(block, padding);
if (options.encrypt) {
await this.flashEncryptBlock(block, seq, timeout);
} else {
await this.flashBlock(block, seq, timeout);
}
}
bytesSent += block.length;
image = image.slice(this.FLASH_WRITE_SIZE, image.length);
Expand All @@ -1461,7 +1524,7 @@ export class ESPLoader {
" seconds.",
);
}
if (calcmd5) {
if (calcmd5 && !options.encrypt) {
const res = await this.flashMd5sum(address, uncsize);
if (new String(res).valueOf() != new String(calcmd5).valueOf()) {
this.info("File md5: " + calcmd5);
Expand Down
2 changes: 2 additions & 0 deletions src/targets/esp32.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ export class ESP32ROM extends ROM {
public FLASH_WRITE_SIZE = 0x400;
public BOOTLOADER_FLASH_OFFSET = 0x1000;

public FLASH_ENCRYPTED_WRITE_ALIGN = 32;

public SPI_REG_BASE = 0x3ff42000;
public SPI_USR_OFFS = 0x1c;
public SPI_USR1_OFFS = 0x20;
Expand Down
3 changes: 3 additions & 0 deletions src/targets/esp32c3.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ export class ESP32C3ROM extends ROM {
"16MB": 0x40,
};

public SUPPORTS_ENCRYPTED_FLASH = true;
public FLASH_ENCRYPTED_WRITE_ALIGN = 16;

public SPI_REG_BASE = 0x60002000;
public SPI_USR_OFFS = 0x18;
public SPI_USR1_OFFS = 0x1c;
Expand Down
3 changes: 3 additions & 0 deletions src/targets/esp32c6.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ export class ESP32C6ROM extends ROM {
"16MB": 0x40,
};

public SUPPORTS_ENCRYPTED_FLASH = true;
public FLASH_ENCRYPTED_WRITE_ALIGN = 16;

public SPI_REG_BASE = 0x60002000;
public SPI_USR_OFFS = 0x18;
public SPI_USR1_OFFS = 0x1c;
Expand Down
3 changes: 3 additions & 0 deletions src/targets/esp32h2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ export class ESP32H2ROM extends ROM {
public FLASH_WRITE_SIZE = 0x400;
public BOOTLOADER_FLASH_OFFSET = 0x0;

// NOT IMPLEMENTED, SETTING EMPTY VALUE
public FLASH_ENCRYPTED_WRITE_ALIGN = 0;

public FLASH_SIZES = {
"1MB": 0x00,
"2MB": 0x10,
Expand Down
Loading