Transform workspace into OpenKE Stock USB Installer generator (v1.0.0)
This commit is contained in:
@@ -0,0 +1,168 @@
|
||||
# CrealityOS OTA Packaging & Update Subsystem Specification
|
||||
|
||||
This document provides a technical specification of Creality's Over-The-Air (OTA) firmware update container format, encryption model, payload chunking, and the on-device update execution engine used across the **Ender-3 V3 KE** (board target `F005`) and the **Nebula Smart Kit / Nebula Pad** (board target `NEBULA`).
|
||||
|
||||
---
|
||||
|
||||
## 1. Overview & Security Model
|
||||
|
||||
Creality's local (USB) and network OTA update mechanism on Ingenic X2000 devices is **integrity-checked but unauthenticated**:
|
||||
|
||||
* **Archive Envelope**: A password-protected 7z archive with encrypted headers (`-mhe=on`).
|
||||
* **Encryption Key**: Derived deterministically from the hardware board name using standard Unix MD5 crypt.
|
||||
* **Payload Verification**: Each sub-payload (`xImage`, `rootfs.squashfs`, `zero.bin`) is checked using MD5 checksums.
|
||||
* **No Cryptographic Signatures**: The device does **not** enforce RSA digital signatures or public key verification during OTA updates. Unsigned custom kernels and root filesystems flash and boot cleanly through stock firmware.
|
||||
|
||||
---
|
||||
|
||||
## 2. Key Derivation Formula
|
||||
|
||||
The 7z archive password is generated per board type using standard Unix MD5 password encryption (`$1$` format) with a hardcoded salt:
|
||||
|
||||
$$\text{Password} = \text{MD5-Crypt}(\text{Key} = \text{BOARD\_NAME} + \text{"C3\_7e\_bz"}, \text{Salt} = \text{"cxswfile"})$$
|
||||
|
||||
In shell (using `openssl`):
|
||||
```bash
|
||||
openssl passwd -1 -salt cxswfile "${BOARD_NAME}C3_7e_bz"
|
||||
```
|
||||
|
||||
### Known Board Encryption Keys
|
||||
|
||||
| Target Device | Board Identifier | Input String | Resulting 7z Password |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| **Ender-3 V3 KE** | `F005` | `F005C3_7e_bz` | `$1$cxswfile$ZFd0RWFYkJQugbtKVGL9y0` |
|
||||
| **Nebula Pad** | `NEBULA` | `NEBULAC3_7e_bz` | `$1$cxswfile$XG7ANbYX3bq2H2xmmwrka.` |
|
||||
| **Creality K1** | `CR4CU220812S12` | `CR4CU220812S12C3_7e_bz` | Board-derived key |
|
||||
|
||||
> [!TIP]
|
||||
> `tools/pack_openke.py` includes a pure-Python implementation of Unix MD5-crypt, making password derivation completely cross-platform without external tools.
|
||||
|
||||
---
|
||||
|
||||
## 3. Archive Structure & Layout
|
||||
|
||||
Creality's update extractor (`/etc/ota_bin/local_ota_update.sh`) has strict structural expectations.
|
||||
|
||||
### Directory Hierarchy
|
||||
|
||||
For an archive file named `<ARCHIVE_STEM>.img` with package version `<VERSION>` (e.g. `NEBULA_ota_img_V1.1.0.34.img` with version `1.1.0.34`):
|
||||
|
||||
```
|
||||
<ARCHIVE_STEM>/ # Top-level directory inside 7z
|
||||
├── ota_config.in # Root version pointer
|
||||
└── ota_v<VERSION>/ # Versioned payload folder
|
||||
├── ota_v<VERSION>.ok # Readiness marker (touch file)
|
||||
├── ota_update.in # Primary payload manifest
|
||||
├── ota_md5_xImage.<FULL_MD5> # Chunk MD5 sidecar for kernel
|
||||
├── ota_md5_rootfs.squashfs.<FULL_MD5> # Chunk MD5 sidecar for rootfs
|
||||
├── ota_md5_zero.bin.<FULL_MD5> # Chunk MD5 sidecar for RTOS
|
||||
├── xImage.0000.<FULL_MD5> # 1 MiB chunk 0
|
||||
├── xImage.0001.<CHUNK0_MD5> # 1 MiB chunk 1
|
||||
├── ...
|
||||
├── rootfs.squashfs.0000.<FULL_MD5>
|
||||
├── ...
|
||||
└── zero.bin.0000.<FULL_MD5>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Manifest Formats
|
||||
|
||||
### 1. `ota_config.in`
|
||||
Located at the root of the extracted folder:
|
||||
```ini
|
||||
current_version=1.1.0.34
|
||||
```
|
||||
|
||||
### 2. `ota_v<VERSION>.ok`
|
||||
An empty 0-byte or 1-byte newline file placed in `ota_v<VERSION>/`. If missing, the updater aborts.
|
||||
|
||||
### 3. `ota_update.in`
|
||||
Lists each payload with its partition type, internal filename, byte size, and full-file MD5:
|
||||
```ini
|
||||
ota_version=1.1.0.34
|
||||
|
||||
img_type=kernel
|
||||
img_name=xImage
|
||||
img_size=5541952
|
||||
img_md5=f3bf93ad2fd1e770dacb3062ba9cf05c
|
||||
|
||||
img_type=rootfs
|
||||
img_name=rootfs.squashfs
|
||||
img_size=346923008
|
||||
img_md5=7d0d3742dedd7a236632e4f7439f52dd
|
||||
|
||||
img_type=rtos
|
||||
img_name=zero.bin
|
||||
img_size=424264
|
||||
img_md5=8e852ae9b8f5b78f3818e4236c3e8f1d
|
||||
```
|
||||
|
||||
### 4. Sidecar Checksum Files (`ota_md5_<NAME>.<FULL_MD5>`)
|
||||
Text files containing the MD5 checksum of each 1 MiB chunk in order, separated by newlines:
|
||||
```
|
||||
e10adc3949ba59abbe56e057f20f883e
|
||||
c33367701511b4f6020ec61ded352059
|
||||
...
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Payload Slicing & Chunking Algorithm
|
||||
|
||||
Creality splits large image files into fixed **1 MiB** (1,048,576 bytes) chunks to manage RAM constraints on the 208MB-RAM device during download and flashing:
|
||||
|
||||
1. **Chunk Naming Convention**:
|
||||
$$\text{chunk\_filename} = \text{base\_name} + \text{"."} + \text{index:04d} + \text{"."} + \text{prev\_md5}$$
|
||||
2. **First Chunk (`index = 0000`)**:
|
||||
`prev_md5` is set to the **FULL MD5** of the unchunked source file.
|
||||
3. **Subsequent Chunks (`index > 0000`)**:
|
||||
`prev_md5` is the MD5 checksum of the **immediately preceding chunk** (`index - 1`).
|
||||
4. **Final Chunk**:
|
||||
Carries the remaining bytes ($\le 1,048,576$ bytes).
|
||||
|
||||
---
|
||||
|
||||
## 6. On-Device Update Execution Flow
|
||||
|
||||
Understanding the device-side execution path in `/etc/ota_bin/local_ota_update.sh` explains the strict naming rules:
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
autonumber
|
||||
actor User as User via Touchscreen
|
||||
participant MS as master-server (UI)
|
||||
participant LU as local_ota_update.sh
|
||||
participant OF as ota_file (7z Decryptor)
|
||||
participant BL as Block Devices (mmcblk0)
|
||||
|
||||
User->>MS: Selects USB Update
|
||||
MS->>MS: Scans for 'ota_img_V' on USB
|
||||
MS->>MS: Compares versions (regex [0-9]{1,3})
|
||||
MS->>LU: Calls local_ota_update.sh /media/sda1/<NAME>.img
|
||||
LU->>LU: Computes OTA_UNZIP_FILE_NAME=${OTA_FILE_NAME%.img*}
|
||||
LU->>OF: Calls ota_file e <NAME>.img <TEMP_DIR>
|
||||
OF->>OF: Derives key & extracts 7z archive
|
||||
LU->>LU: Checks <TEMP_DIR>/<OTA_UNZIP_FILE_NAME>/ota_config.in
|
||||
LU->>LU: Validates partition sizes against device nodes
|
||||
LU->>BL: Streams xImage chunks -> mmcblk0p6 (kernel2)
|
||||
LU->>BL: Streams rootfs chunks -> mmcblk0p8 (rootfs2)
|
||||
LU->>BL: Streams zero.bin chunks -> mmcblk0p4 (rtos2)
|
||||
LU->>BL: Writes "ota:kernel2" to /dev/mmcblk0p1
|
||||
LU->>MS: Returns success
|
||||
MS->>User: Reboots printer into OpenKE
|
||||
```
|
||||
|
||||
### The Critical Folder-Name Matching Rule
|
||||
|
||||
Lines 312–320 of `local_ota_update.sh`:
|
||||
```bash
|
||||
OTA_FILE_NAME=$(basename ${OTA_FILE})
|
||||
OTA_UNZIP_FILE_NAME=${OTA_FILE_NAME%.img*}
|
||||
ota_site=${OTA_FILE_PATH}/${OTA_UNZIP_FILE_NAME}
|
||||
/usr/bin/ota_file e ${OTA_FILE_PATH}/${OTA_FILE_NAME} ${OTA_FILE_PATH}
|
||||
ota_site_config=$ota_site/ota_config.in
|
||||
```
|
||||
|
||||
> [!IMPORTANT]
|
||||
> If the archive file is named `NEBULA_ota_img_V1.1.0.34.img`, `OTA_UNZIP_FILE_NAME` evaluates to `NEBULA_ota_img_V1.1.0.34`. The root directory inside the 7z archive **must match that exact string**. If the archive has a different folder name (e.g. `OpenKE_...`), `cp -f $ota_site_config` fails and the update aborts immediately.
|
||||
Reference in New Issue
Block a user