Skip to content

Commit

Permalink
initial flash encryption implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
brianignacio5 committed May 30, 2024
1 parent 22e115b commit b4d8753
Show file tree
Hide file tree
Showing 11 changed files with 148 additions and 53 deletions.
1 change: 1 addition & 0 deletions examples/typescript/src/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ <h3> Program </h3>
<thead class="thead-light">
<tr>
<th>Flash Address</th>
<th>Encrypt file ?</th>
<th>File</th>
<th></th>
</tr>
Expand Down
67 changes: 39 additions & 28 deletions examples/typescript/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,35 +153,43 @@ addFileButton.onclick = () => {
element1.value = "0x1000";
cell1.appendChild(element1);

// Column 2 - File selector
// Column 2 - Encrypt file ?
const cell2 = row.insertCell(1);
const element2 = document.createElement("input");
element2.type = "file";
element2.id = "selectFile" + rowCount;
element2.name = "selected_File" + rowCount;
element2.addEventListener("change", handleFileSelect, false);
element2.type = "checkbox";
element2.id = "file_encrypted" + rowCount;
element2.checked = false;
cell2.appendChild(element2);

// Column 3 - Progress
// Column 3 - File selector
const cell3 = row.insertCell(2);
cell3.classList.add("progress-cell");
cell3.style.display = "none";
cell3.innerHTML = `<progress value="0" max="100"></progress>`;

// Column 4 - Remove File
const element3 = document.createElement("input");
element3.type = "file";
element3.id = "selectFile" + rowCount;
element3.name = "selected_File" + rowCount;
element3.addEventListener("change", handleFileSelect, false);
cell3.appendChild(element3);

// Column 4 - Progress
const cell4 = row.insertCell(3);
cell4.classList.add("action-cell");
cell4.classList.add("progress-cell");
cell4.style.display = "none";
cell4.innerHTML = `<progress value="0" max="100"></progress>`;

// Column 5 - Remove File
const cell5 = row.insertCell(4);
cell5.classList.add("action-cell");
if (rowCount > 1) {
const element4 = document.createElement("input");
element4.type = "button";
const element5 = document.createElement("input");
element5.type = "button";
const btnName = "button" + rowCount;
element4.name = btnName;
element4.setAttribute("class", "btn");
element4.setAttribute("value", "Remove"); // or element1.value = "button";
element4.onclick = function () {
element5.name = btnName;
element5.setAttribute("class", "btn");
element5.setAttribute("value", "Remove"); // or element1.value = "button";
element5.onclick = function () {
removeRow(row);
};
cell4.appendChild(element4);
cell5.appendChild(element5);
}
};

Expand Down Expand Up @@ -297,7 +305,7 @@ function validateProgramInputs() {
else if (offsetArr.includes(offset)) return "Offset field in row " + index + " is already in use!";
else offsetArr.push(offset);

const fileObj = row.cells[1].childNodes[0];
const fileObj = row.cells[2].childNodes[0];
fileData = fileObj.data;
if (fileData == null) return "No file selected for row " + index + "!";
}
Expand All @@ -317,7 +325,7 @@ programButton.onclick = async () => {
// Hide error message
alertDiv.style.display = "none";

const fileArray = [];
const fileArray: { data: string; address: number; encrypted: boolean }[] = [];
const progressBars = [];

for (let index = 1; index < table.rows.length; index++) {
Expand All @@ -326,16 +334,19 @@ programButton.onclick = async () => {
const offSetObj = row.cells[0].childNodes[0] as HTMLInputElement;
const offset = parseInt(offSetObj.value);

const fileObj = row.cells[1].childNodes[0] as ChildNode & { data: string };
const progressBar = row.cells[2].childNodes[0];
const encryptedObj = row.cells[1].childNodes[0] as HTMLInputElement;
const encryptFlag = encryptedObj.checked;

const fileObj = row.cells[2].childNodes[0] as ChildNode & { data: string };

const progressBar = row.cells[3].childNodes[0];
progressBar.textContent = "0";
progressBars.push(progressBar);

row.cells[2].style.display = "initial";
row.cells[3].style.display = "none";
row.cells[3].style.display = "initial";
row.cells[4].style.display = "none";

fileArray.push({ data: fileObj.data, address: offset });
fileArray.push({ data: fileObj.data, address: offset, encrypted: encryptFlag });
}

try {
Expand All @@ -356,8 +367,8 @@ programButton.onclick = async () => {
} finally {
// Hide progress bars and show erase buttons
for (let index = 1; index < table.rows.length; index++) {
table.rows[index].cells[2].style.display = "none";
table.rows[index].cells[3].style.display = "initial";
table.rows[index].cells[3].style.display = "none";
table.rows[index].cells[4].style.display = "initial";
}
}
};
Expand Down
117 changes: 92 additions & 25 deletions src/esploader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@ import atob from "atob-lite";
*/
export interface FlashOptions {
/**
* An array of file objects representing the data to be flashed.
* @type {Array<{ data: string; address: number }>}
* An array of file objects representing the data to be flashed, address offset and if to be encrypted.
* @type {Array<{ data: string; address: number, encrypted: boolean }>}
*/
fileArray: { data: string; address: number }[];
fileArray: { data: string; address: number; encrypted: boolean }[];

/**
* The size of the flash memory to be used.
Expand Down Expand Up @@ -207,12 +207,17 @@ export class ESPLoader {
ESP_READ_FLASH = 0xd2;
ESP_RUN_USER_CODE = 0xd3;

ESP_FLASH_ENCRYPT_DATA = 0xd4;

ESP_IMAGE_MAGIC = 0xe9;
ESP_CHECKSUM_MAGIC = 0xef;

// Response code(s) sent by ROM
ROM_INVALID_RECV_MSG = 0x05; // response if an invalid message is received

// Commands supported by ESP32-S2 and later chips ROM bootloader only
ESP_GET_SECURITY_INFO = 0x14;

ERASE_REGION_TIMEOUT_PER_MB = 30000;
ERASE_WRITE_TIMEOUT_PER_MB = 40000;
MD5_TIMEOUT_PER_MB = 8000;
Expand Down Expand Up @@ -254,6 +259,7 @@ export class ESPLoader {
private terminal?: IEspLoaderTerminal;
private romBaudrate = 115200;
private debugLogging = false;
private secureDownloadMode = false;

/**
* Create a new ESPLoader to perform serial communication
Expand All @@ -265,7 +271,7 @@ export class ESPLoader {
*/
constructor(options: LoaderOptions) {
this.IS_STUB = false;
this.FLASH_WRITE_SIZE = 0x4000;
this.FLASH_WRITE_SIZE = 0x400;

this.transport = options.transport;
this.baudrate = options.baudrate;
Expand Down Expand Up @@ -664,13 +670,21 @@ export class ESPLoader {
this.info("\n\r", false);

if (!detecting) {
const chipMagicValue = (await this.readReg(0x40001000)) >>> 0;
this.debug("Chip Magic " + chipMagicValue.toString(16));
const chip = await magic2Chip(chipMagicValue);
if (this.chip === null) {
throw new ESPError(`Unexpected CHIP magic value ${chipMagicValue}. Failed to autodetect chip type.`);
} else {
this.chip = chip as ROM;
try {
const chipMagicValue = (await this.readReg(0x40001000)) >>> 0;
this.debug("Chip Magic " + chipMagicValue.toString(16));
const chip = await magic2Chip(chipMagicValue);
if (this.chip === null) {
throw new ESPError(`Unexpected CHIP magic value ${chipMagicValue}. Failed to autodetect chip type.`);
} else {
this.chip = chip as ROM;
}
} catch (error) {
if (error instanceof ESPError && error.message && error.message.indexOf("unsupported command error") !== -1) {
this.secureDownloadMode = true;
} else {
throw error;
}
}
}
}
Expand All @@ -689,6 +703,20 @@ export class ESPLoader {
}
}

/**
* Get the chip ID using the check command
* @returns {number} Chip ID
*/
async getChipId() {
const res = (await this.checkCommand(
"get security info",
this.ESP_GET_SECURITY_INFO,
new Uint8Array(0),
0,
)) as Uint8Array;
return res[4 + 9 - 1];
}

/**
* Execute the command and check the command response.
* @param {string} opDescription Command operation description.
Expand Down Expand Up @@ -774,7 +802,10 @@ export class ESPLoader {
* @param {number} hspiArg - Argument for SPI attachment
*/
async flashSpiAttach(hspiArg: number) {
const pkt = this._intToByteArray(hspiArg);
let pkt = this._intToByteArray(hspiArg);
if (!this.IS_STUB) {
pkt = this._appendArray(pkt, this._intToByteArray(0));
}
await this.checkCommand("configure SPI flash pins", this.ESP_SPI_ATTACH, pkt);
}

Expand All @@ -797,9 +828,10 @@ export class ESPLoader {
* Start downloading to Flash (performs an erase)
* @param {number} size Size to erase
* @param {number} offset Offset to erase
* @param {boolean} encrypt Flag if encrypt
* @returns {number} Number of blocks (of size self.FLASH_WRITE_SIZE) to write.
*/
async flashBegin(size: number, offset: number) {
async flashBegin(size: number, offset: number, encrypt = false) {
const numBlocks = Math.floor((size + this.FLASH_WRITE_SIZE - 1) / this.FLASH_WRITE_SIZE);
const eraseSize = this.chip.getEraseSize(offset, size);

Expand All @@ -816,7 +848,7 @@ export class ESPLoader {
pkt = this._appendArray(pkt, this._intToByteArray(this.FLASH_WRITE_SIZE));
pkt = this._appendArray(pkt, this._intToByteArray(offset));
if (this.IS_STUB == false) {
pkt = this._appendArray(pkt, this._intToByteArray(0)); // XXX: Support encrypted
pkt = this._appendArray(pkt, this._intToByteArray(encrypt ? 1 : 0));
}

await this.checkCommand("enter Flash download mode", this.ESP_FLASH_BEGIN, pkt, undefined, timeout);
Expand Down Expand Up @@ -890,6 +922,31 @@ export class ESPLoader {
await this.checkCommand("write to target Flash after seq " + seq, this.ESP_FLASH_DATA, pkt, checksum, timeout);
}

/**
* Encrypt, write 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) {
if (this.chip.SUPPORTS_ENCRYPTED_FLASH && !this.IS_STUB) {
await this.flashBlock(data, seq, timeout);
} else {
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 @@ -1324,13 +1381,13 @@ export class ESPLoader {
aFlashSize = this.parseFlashSizeArg(flashSize);
}

const flashParams = (aFlashMode << 8) | (aFlashFreq + aFlashSize);
const flashParams = (aFlashMode << 8) | (aFlashSize + aFlashFreq);
this.info("Flash params set to " + flashParams.toString(16));
if (parseInt(image[2]) !== aFlashMode << 8) {
image = image.substring(0, 2) + (aFlashMode << 8).toString() + image.substring(2 + 1);
}
if (parseInt(image[3]) !== aFlashFreq + aFlashSize) {
image = image.substring(0, 3) + (aFlashFreq + aFlashSize).toString() + image.substring(3 + 1);
if (parseInt(image[3]) !== aFlashSize + aFlashFreq) {
image = image.substring(0, 3) + (aFlashSize + aFlashFreq).toString() + image.substring(3 + 1);
}
return image;
}
Expand All @@ -1356,6 +1413,11 @@ 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);

let compress = options.compress;
if (compress && options.fileArray[i].encrypted) {
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);
Expand All @@ -1373,12 +1435,12 @@ export class ESPLoader {
}
const uncsize = image.length;
let blocks: number;
if (options.compress) {
if (compress) {
const uncimage = this.bstrToUi8(image);
image = this.ui8ToBstr(deflate(uncimage, { level: 9 }));
blocks = await this.flashDeflBegin(uncsize, image.length, address);
} else {
blocks = await this.flashBegin(uncsize, address);
blocks = await this.flashBegin(uncsize, address, options.fileArray[i].encrypted);
}
let seq = 0;
let bytesSent = 0;
Expand Down Expand Up @@ -1407,7 +1469,7 @@ export class ESPLoader {
);
const block = this.bstrToUi8(image.slice(0, this.FLASH_WRITE_SIZE));

if (options.compress) {
if (compress) {
const lenUncompressedPrevious = totalLenUncompressed;
inflate.push(block, false);
const blockUncompressed = totalLenUncompressed - lenUncompressedPrevious;
Expand All @@ -1425,7 +1487,11 @@ export class ESPLoader {
timeout = blockTimeout;
}
} else {
throw new ESPError("Yet to handle Non Compressed writes");
if (options.fileArray[i].encrypted) {
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 @@ -1437,7 +1503,7 @@ export class ESPLoader {
}
d = new Date();
const t = d.getTime() - t1;
if (options.compress) {
if (compress) {
this.info(
"Wrote " +
uncsize +
Expand All @@ -1450,7 +1516,7 @@ export class ESPLoader {
" seconds.",
);
}
if (calcmd5) {
if (calcmd5 && !options.fileArray[i].encrypted && !this.secureDownloadMode) {
const res = await this.flashMd5sum(address, uncsize);
if (new String(res).valueOf() != new String(calcmd5).valueOf()) {
this.info("File md5: " + calcmd5);
Expand All @@ -1465,7 +1531,8 @@ export class ESPLoader {

if (this.IS_STUB) {
await this.flashBegin(0, 0);
if (options.compress) {
const lastFileIndex = options.fileArray && options.fileArray.length ? options.fileArray.length - 1 : 0;
if (options.compress && lastFileIndex && !options.fileArray[lastFileIndex].encrypted) {
await this.flashDeflFinish();
} else {
await this.flashFinish();
Expand All @@ -1486,7 +1553,7 @@ export class ESPLoader {
}

async getFlashSize() {
this.debug("flash_id");
this.debug("flash_size");
const flashid = await this.readFlashId();
const flidLowbyte = (flashid >> 16) & 0xff;
return this.DETECTED_FLASH_SIZES_NUM[flidLowbyte];
Expand Down
Loading

0 comments on commit b4d8753

Please sign in to comment.