mirror of
https://github.com/ClusterM/open-bamboo-networking.git
synced 2026-10-19 01:29:46 -05:00
7 files changed
+72
-17
No files matched your search
@@ -69,7 +69,7 @@ X-BBL-Executable-info: {}
|
||||
X-BBL-Agent-OS-Type: linux
|
||||
```
|
||||
|
||||
`X-BBL-Client-Version` is the slicer version; `X-BBL-Agent-Version` is the networking plugin version. `X-BBL-OS-Type` and `X-BBL-Agent-OS-Type` both report `linux` when running through pjarczak's WSL2 bridge; native Linux builds send `linux` directly. `X-BBL-OS-Version` carries the OS build string (Windows build number when relayed through pjarczak). `X-BBL-Executable-info` is always the literal string `{}`. `X-BBL-Device-ID` is a machine-local UUID, not the printer serial.
|
||||
`X-BBL-Client-Version` is the slicer version; `X-BBL-Agent-Version` is the networking plugin version. `X-BBL-OS-Type` and `X-BBL-Agent-OS-Type` both report `linux` when running through pjarczak's WSL2 bridge; native Linux builds send `linux` directly. `X-BBL-OS-Version` carries the OS build string (Windows build number when relayed through pjarczak). `X-BBL-Executable-info` is the literal string `{}` on the Linux captures above; stock Windows Studio sends a code-signing JSON for the signed executable instead (subject `Shanghai Lunkuo Technology Co., Ltd`, GlobalSign EV CodeSigning — [#112](https://github.com/ClusterM/open-bamboo-networking/issues/112), mitmproxy 2026-10-05). `X-BBL-Device-ID` is a machine-local UUID, not the printer serial.
|
||||
|
||||
Plus anything Studio injects through `bambu_network_set_extra_http_header` ([§8.10](08.10-http.md)). Direct probes against the production server confirm that **none** of the `X-BBL-*` headers, nor even the custom `User-Agent`, are required for **most** endpoints (auth, profile, presets, bind, project/upload) — there they influence analytics only.
|
||||
|
||||
@@ -79,7 +79,21 @@ Plus anything Studio injects through `bambu_network_set_extra_http_header` ([§8
|
||||
>
|
||||
> Subtractive bisection against the production server (2026-07) narrowed the requirement to exactly these two `X-BBL-*` headers; the remaining fingerprint headers stay analytics-only even on `/my/task`. The signed **command-security** headers (`x-bbl-app-certification-id`, `x-bbl-device-security-sign`) are additionally needed for a *secured* printer — see [§10.5](10.05-http-pop.md) and [§8.8.3](08.08-print-abi.md#883-bambu_network_start_local_print_with_record).
|
||||
|
||||
> **Exception — `POST /v1/iot-service/api/user/ttcode` (camera mint).** Needs the same `X-BBL-Client-Name` / `X-BBL-OS-Type` pair **and** the PoP headers (`x-bbl-app-certification-id`, `x-bbl-device-security-sign`, [§10.5](10.05-http-pop.md)) regardless of the printer's Developer Mode. Without PoP the endpoint answers **HTTP 403** `{"code":8,"error":"The specified resource is forbidden. …"}` even with the full fingerprint set (live probe, P2S, 2026-09). A client without the slicer key therefore cannot mint TUTK credentials at all; this plugin then skips the mint and hands out the LAN URL ([§8.11](08.11-camera.md)).
|
||||
> **Exception — `POST /v1/iot-service/api/user/ttcode` (camera mint).** Needs `X-BBL-Client-Name: BambuStudio`, an `X-BBL-OS-Type` that matches the app leaf, and the PoP headers (`x-bbl-app-certification-id`, `x-bbl-device-security-sign`, [§10.5](10.05-http-pop.md)), regardless of the printer's Developer Mode. On a strict account a missing or rejected PoP is **HTTP 403** `{"code":8,"error":"The specified resource is forbidden. …"}` (P2S, 2026-09; X2D, 2026-10). On a lenient account the same call returns 200 and the cloud pushes a cleartext `prepare` that secured firmware rejects ([§10.3](10.03-mqtt-field-encryption.md)). A client without an accepted PoP therefore does not get a usable cloud camera on secured firmware.
|
||||
>
|
||||
> Which PoP the cloud accepts is bound to the **app leaf's platform**, not to the account and not to a release channel ([§10.2](10.02-secrets.md#embedded-in-the-stock-plugin-binary)). Live matrix (one account, 2026-10-05 / 2026-10-06; the same split on a second account in [#112](https://github.com/ClusterM/open-bamboo-networking/issues/112)):
|
||||
>
|
||||
> | Leaf | `X-BBL-OS-Type` | `X-BBL-Executable-info` | HTTP |
|
||||
> | --- | --- | --- | --- |
|
||||
> | Linux `a4e8faaa` (`CN=…-836763c70000`) | `linux` | `{}` or the Windows EV JSON | 200 |
|
||||
> | Linux `a4e8faaa` | `windows` / `macos` | `{}` or the Windows EV JSON | 403 `code:8` |
|
||||
> | Windows `4a63194e` (`CN=…-eed1e0410000`) | `windows` | real EV JSON (as stock Windows sends it) | 200 |
|
||||
> | Windows `4a63194e` | `windows` | `{}` or absent | 403 `code:8` |
|
||||
> | Windows `4a63194e` | `linux` | real EV JSON | 403 `code:8` |
|
||||
>
|
||||
> `X-BBL-Client-Version`, `X-BBL-Language`, `X-BBL-Agent-OS-Type` and `X-BBL-Client-ID` do not change the outcome; only `X-BBL-OS-Type` has to match the leaf, and the Windows leaf additionally requires a non-empty `X-BBL-Executable-info`. An earlier reading that the Windows leaf was rejected outright came from sending it as `linux` with `X-BBL-Executable-info: {}`. Stock release Windows Studio `02.08.02.61` presents the Windows leaf with `X-BBL-OS-Type: windows` and the EV JSON and gets 200. The cloud is still not equally strict about a **missing** PoP: some accounts get 403, others get 200 and a cleartext dispatch the printer rejects ([§10.3](10.03-mqtt-field-encryption.md)).
|
||||
>
|
||||
> **TODO**: what makes the cloud lenient (`200` without an accepted PoP) for some accounts, and whether a macOS leaf exists with the same OS-Type binding.
|
||||
|
||||
##### Device-scoped `X-BBL-Client-ID`
|
||||
|
||||
|
||||
@@ -13,7 +13,14 @@ These two values ship inside `libbambu_networking.so` / Bambu Connect natives. T
|
||||
|
||||
Neither value alone is a usable signing key: they only unlock the cloud response below.
|
||||
|
||||
**Two app identities, not a rotation (Studio Beta procdump 2026-09-10; cert endpoint 2026-10-04).** A Studio Beta build carries a **second** `client_auth_secret`, and the cert endpoint answers each secret with its own leaf: the release secret (`…-836763c70000` prefix) still yields `CN=GLOF3813734089-836763c70000` (serial `a4e8faaa1a38e3650a0ea590d192383f`), the Beta secret yields `CN=GLOF3813734089-eed1e0410000` (issuer `CN=GLOF3813734089.bambulab.com`, serial `4a63194e9c2427366567a8da2530a777`, RSA-2048, `n` starts `d9929cc2…`). Both leaves were issued on 2025-12-21, both are valid until 2027-06-25, neither is on the other's CRL, and on 2026-10-04 the endpoint returned **both** (one per secret), so the release leaf is not superseded. The two are not interchangeable on the cloud side: `POST /v1/iot-service/api/user/ttcode` ([§10.5](10.05-http-pop.md)) accepts PoP headers made with the release leaf and answers **403 `code:8` "The specified resource is forbidden"** to the same request made with the Beta leaf (same account, same body, `X-BBL-Client-Version` `02.05.03.99` or `02.08.04.57`, any `User-Agent`; 2026-10-04). **TODO:** which cloud calls the Beta leaf is good for, and whether a Beta plugin reaches the camera mint through another endpoint. Each **`client_auth_secret`** is a 43-byte heap string of the same shape as the table (leaf-CN prefix + 16 hardcoded hex chars). The hex tail is **not** recorded here — recover it from a dump or the stock binary, same as the rest of the signing material. The full app chain (leaf → `GLOF3813734089.bambulab.com` → `application_root.bambulab.com` → `BBL CA`) is resident and matches [§10.2](#shared-app-cert-crl-and-private-key-cloud-api). The **shared app private key** was recovered from the same dump **without the cloud fetch**: the plugin keeps the private key obfuscated at rest as the raw encrypted `key` blob (the AES-CTR form below), which stays resident even after the response JSON is freed; decrypting it in place with the fixed-key custom-S-box cipher yields the CRT key (`p·q == n`, RSA-SHA256 verified). Method: [§10.7](10.07-bearssl-crypto-island.md#recovering-the-shared-app-key-from-a-dump). `server_wrap_key` is **not** resident as a PEM (only an empty `PUBLIC KEY` template in the module) and at runtime is just one loaded key object among many RSA-2048 public keys — but the dump route to the private key does not need it (only the cloud route does).
|
||||
**App identities split by client platform, not by release channel (cert endpoint 2026-10-04; stock Windows Studio mitm 2026-10-05).** There is more than one embedded `client_auth_secret`, and the cert endpoint answers each with its own leaf. The leaves are **per client OS**, not per release channel (the earlier "release vs Beta" reading was wrong — see below):
|
||||
|
||||
- **Linux leaf** — secret `…-836763c70000` prefix → `CN=GLOF3813734089-836763c70000` (serial `a4e8faaa1a38e3650a0ea590d192383f`). Extracted from the Linux `libbambu_networking.so`.
|
||||
- **Windows leaf** — secret `…-eed1e0410000` prefix → `CN=GLOF3813734089-eed1e0410000` (issuer `CN=GLOF3813734089.bambulab.com`, serial `4a63194e9c2427366567a8da2530a777`, RSA-2048, `n` starts `d9929cc2…`). First recovered from a Windows process dump (2026-09-10) and previously mislabeled as a Studio Beta identity. Stock **release** Windows Studio `02.08.02.61` / agent `02.08.02.54` presents this same leaf on `/user/ttcode` and gets `200` ([#112](https://github.com/ClusterM/open-bamboo-networking/issues/112), mitmproxy 2026-10-05), so it is the ordinary Windows platform leaf.
|
||||
|
||||
Both were issued on 2025-12-21, both are valid until 2027-06-25, neither is on the other's CRL, and on 2026-10-04 the endpoint returned **both** (one per secret) — the two coexist, neither supersedes the other. The cloud binds each leaf to its platform on `POST /v1/iot-service/api/user/ttcode` ([§10.5](10.05-http-pop.md)): the Linux leaf is accepted only with `X-BBL-OS-Type: linux`; the Windows leaf only with `X-BBL-OS-Type: windows` **and** a real `X-BBL-Executable-info` (the signed-exe code-signing JSON — `{}` or absent → `403 code:8`). Cross combinations (Linux leaf + `windows`, Windows leaf + `linux`) all `403 code:8`. Confirmed on one account, 2026-10-05 ([§6.6](06.06-cloud-rest.md)). The earlier "the release leaf is accepted and the second leaf is rejected" reading was a measurement artefact: the second leaf was sent as `linux` with `X-BBL-Executable-info: {}`.
|
||||
|
||||
A **third** app leaf is resident in the current Linux plugin `02.08.04.60`: `CN=GLOF3813734089-c0cf542a0000` (serial `e0575f37…`, valid 2026-08-10…2028-02-12). It is newer than the `a4e8faaa` Linux leaf (issued 2026-08-10 rather than 2025-12-21). Whether the cert endpoint now returns it for the Linux secret, and whether Windows rotated the same way, is unconfirmed: its private key was not recovered from that build. **TODO:** recover the `02.08.04.60` private key (the extractor needs reworking for this build) and check which leaf the cert endpoint returns for each secret today. Each **`client_auth_secret`** is a 43-byte heap string of the same shape as the table (leaf-CN prefix + 16 hardcoded hex chars). The hex tail is **not** recorded here — recover it from a dump or the stock binary, same as the rest of the signing material. The full app chain (leaf → `GLOF3813734089.bambulab.com` → `application_root.bambulab.com` → `BBL CA`) is resident and matches [§10.2](#shared-app-cert-crl-and-private-key-cloud-api). The **shared app private key** was recovered from the same dump **without the cloud fetch**: the plugin keeps the private key obfuscated at rest as the raw encrypted `key` blob (the AES-CTR form below), which stays resident even after the response JSON is freed; decrypting it in place with the fixed-key custom-S-box cipher yields the CRT key (`p·q == n`, RSA-SHA256 verified). Method: [§10.7](10.07-bearssl-crypto-island.md#recovering-the-shared-app-key-from-a-dump). `server_wrap_key` is **not** resident as a PEM (only an empty `PUBLIC KEY` template in the module) and at runtime is just one loaded key object among many RSA-2048 public keys — but the dump route to the private key does not need it (only the cloud route does).
|
||||
|
||||
#### Shared app cert, CRL, and private key (cloud API)
|
||||
|
||||
@@ -95,9 +102,9 @@ On a secured printer the firmware will not accept signed MQTT commands until it
|
||||
|
||||
- Publish **`security.app_cert_install`** on `device/<dev_id>/request` with the app cert chain and CRL from the cert endpoint → printer installs them into its trust store and (on success) reports the same command on `device/<dev_id>/report` with **`printer_cert`** + `"result":"SUCCESS"` (device cert — see below).
|
||||
- Wire shape (Evidence: LAN mqtt_logger, 2026-07-27): request fields `command` / `sequence_id` / `app_cert` (3 PEMs) / `crl` (1 CRL); report fields `command` / `printer_cert` (device leaf + Device CA) / `result` / `sequence_id`. Full skeletons in [§8.4.7](08.04-lan.md).
|
||||
- **Install adds, it does not replace.** After installing the Beta leaf on a printer that already trusted the release leaf, `app_cert_list` returned both `cert_ids` (X2D `01.02.00.00`, 2026-10-04). No command that removes a single app cert is known; only the reboot volatility below clears the store.
|
||||
- **Install adds, it does not replace.** After installing the Windows leaf on a printer that already trusted the Linux leaf, `app_cert_list` returned both `cert_ids` (X2D `01.02.00.00`, 2026-10-04). No command that removes a single app cert is known; only the reboot volatility below clears the store.
|
||||
- **`security.app_cert_list`** returns the currently trusted `cert_ids` (observability only; does not install). The request must carry **`"type":"app"`**: `{"security":{"sequence_id":"1","command":"app_cert_list","type":"app"}}`. Without it the X2D answers `{"security":{"command":"app_cert_list","result":"FAIL","sequence_id":"1"}}` and no list (2026-10-04); with it the reply is `{"security":{"cert_ids":["<serial><issuer>", …],"command":"app_cert_list","result":"SUCCESS","sequence_id":"1","type":"app"}}`. A numeric `sequence_id` is also refused.
|
||||
- **Volatility (hardware):** installed app cert / CRL live in **printer RAM**; a reboot clears them — re-install every session. The client signing path never reads the CRL; only the printer uses it after install (see Validity above for `nextUpdate` behaviour).
|
||||
- **Volatility (hardware):** installed app cert / CRL live in **printer RAM**; a reboot clears them — re-install every session. Right after a reboot, with nothing installed, `app_cert_list` answers `{"security":{"command":"app_cert_list","reason":"device-endreaddevcertfailed","result":"FAIL",…,"type":"app"}}` (X2D, 2026-10-05). The client signing path never reads the CRL; only the printer uses it after install (see Validity above for `nextUpdate` behaviour).
|
||||
|
||||
Who fails when the trust store is empty or wrong: [§10.1](10.01-overview.md#who-verifies).
|
||||
|
||||
|
||||
@@ -38,7 +38,26 @@ When Developer Mode is off, firmware verifies privileged `liveview` requests aga
|
||||
|
||||
**Who sends it.** In the stock flow the client never publishes `liveview.prepare`: the cloud pushes it (signed, with `ttcode_enc`) to the printer in response to `POST /v1/iot-service/api/user/ttcode` ([§8.11](08.11-camera.md)), and the printer answers on `device/<serial>/report` with `result: "succeed"` (P2S, 2026-09) and then flips `ipcam.tutk_server` to `"enable"`. A client therefore needs neither to sign nor to field-encrypt `liveview` frames; it only waits for that reply ([§6.7.1](06.07-tutk.md#671-credentials-and-readiness)). The matrix below (from [PR #97](https://github.com/ClusterM/open-bamboo-networking/pull/97)) documents what firmware accepts when a client publishes `prepare` itself, which is not needed for liveview.
|
||||
|
||||
**Cleartext `ttcode` from the cloud.** The cloud does not always send `ttcode_enc`. The printer's reply echoes the request fields, so the two shapes are told apart on `device/<serial>/report`: a successful `prepare` on an X2D echoes `authkey`, `passwd`, `region`, `ttcode_enc` (684 Base64 chars = two RSA-2048 blocks) and `result: "succeed"`; the rejections reported in [#112](https://github.com/ClusterM/open-bamboo-networking/issues/112) (P1S `01.10.00.00`) and [#119](https://github.com/ClusterM/open-bamboo-networking/issues/119) (X1C `01.12.00.00`, stock plugin `02.08.02.54`) echo `authkey`, `passwd`, `region`, a **cleartext `ttcode`** and `err_code: 84033543`, with no `ttcode_enc` and no `header`. That is the first row of the matrix below applied to the cloud's own frame: secured firmware refuses a `prepare` whose token is not encrypted under its device certificate, whoever sent it. On the X2D the body of the `/user/ttcode` POST does not change the shape: with and without `dev_version`, with `dev_version: "01.10.00.00"`, and without `protocols`, the cloud still pushed `ttcode_enc` and the printer answered `succeed` (2026-10-04, 4 mints). **TODO:** what makes the cloud hold a printer's device certificate (so that it can encrypt `ttcode`) — [#119](https://github.com/ClusterM/open-bamboo-networking/issues/119) saw the cloud switch from cleartext to an accepted `prepare` after the stock plugin ran `update_cert` and `install_device_cert` ([§8.4.6](08.04-lan.md#846-bambu_network_update_cert), [§8.4.7](08.04-lan.md#847-bambu_network_install_device_cert)); whether the `app_cert_install` reply (which carries `printer_cert`) is also what registers that certificate with the cloud is unverified.
|
||||
**Cleartext `ttcode` from the cloud.** The cloud does not always send `ttcode_enc`. The printer's reply echoes the request fields, so the two shapes are told apart on `device/<serial>/report`: a successful `prepare` on an X2D echoes `authkey`, `passwd`, `region`, `ttcode_enc` (684 Base64 chars = two RSA-2048 blocks) and `result: "succeed"`; the rejections reported in [#112](https://github.com/ClusterM/open-bamboo-networking/issues/112) (P1S `01.10.00.00`) and [#119](https://github.com/ClusterM/open-bamboo-networking/issues/119) (X1C `01.12.00.00`, stock plugin `02.08.02.54`) echo `authkey`, `passwd`, `region`, a **cleartext `ttcode`** and `err_code: 84033543`, with no `ttcode_enc` and no `header`. That is the first row of the matrix below applied to the cloud's own frame: secured firmware refuses a `prepare` whose token is not encrypted under its device certificate, whoever sent it. The printer's app trust store ([§10.2](10.02-secrets.md#provisioning-printer-trust-store)) plays no part in it: right after a reboot, with no app cert installed (`app_cert_list` → `device-endreaddevcertfailed`), a mint still made the cloud push a `prepare` with `ttcode_enc` that the X2D answered with `succeed`, and a TUTK session then delivered 1080p video; the same held with only the Windows leaf installed (2026-10-05). On the X2D the body of the `/user/ttcode` POST does not change the shape: with and without `dev_version`, with `dev_version: "01.10.00.00"`, and without `protocols`, the cloud still pushed `ttcode_enc` and the printer answered `succeed` (2026-10-04, 4 mints).
|
||||
|
||||
**Cleartext dispatch correlates with whether the cloud accepts the mint's PoP.** "Accepts" means the leaf matches `X-BBL-OS-Type`, and for the Windows leaf `X-BBL-Executable-info` is a real code-signing JSON rather than `{}` ([§6.6](06.06-cloud-rest.md), [§10.2](10.02-secrets.md#embedded-in-the-stock-plugin-binary)). An earlier row that called the Windows leaf rejected was that leaf sent as `linux` with `X-BBL-Executable-info: {}`.
|
||||
|
||||
| Case | PoP on the request | HTTP | Cloud→printer frame (from the `report` echo) |
|
||||
| --- | --- | --- | --- |
|
||||
| X2D `01.02.00.00` | Linux leaf, `X-BBL-OS-Type: linux` | 200 | `ttcode_enc`, `succeed` |
|
||||
| X2D `01.02.00.00` | none, or Windows leaf sent as `linux` / with `{}` | 403 | — |
|
||||
| X2D `01.02.00.00` (2026-10-06) | Windows leaf, `windows`, real EV JSON | 200 | HTTP only; MQTT echo not captured |
|
||||
| P1S `01.10.00.00`, camera ([#112](https://github.com/ClusterM/open-bamboo-networking/issues/112)) | none, or Windows leaf with `X-BBL-Executable-info: {}` | 200 | cleartext `ttcode`, `84033543` |
|
||||
| P1S `01.10.00.00`, stock Windows Studio `02.08.02.61` ([#112](https://github.com/ClusterM/open-bamboo-networking/issues/112), 2026-10-05) | Windows leaf, `windows`, real EV JSON | 200 | `ttcode_enc`, `succeed`; camera streamed |
|
||||
| P1S `01.10.00.00`, cloud print ([#112](https://github.com/ClusterM/open-bamboo-networking/issues/112)) | Linux leaf, `linux` | 200 (`/my/task`) | print accepted (`success`); a signed `project_file` with a cleartext `url` is separately rejected `84033544` at file verify, so the accepted dispatch went out as `url_enc` |
|
||||
| P1S `01.10.00.00`, cloud print ([#112](https://github.com/ClusterM/open-bamboo-networking/issues/112)) | none | 200 (`/my/task`) | `project_file`, cleartext `url`, no `url_enc`, `84033543` |
|
||||
| P1S, `app_cert_install` over the cloud session, then a PoP-less mint ([#112](https://github.com/ClusterM/open-bamboo-networking/issues/112), 2026-10-05) | none | 200 | still cleartext `ttcode`, `84033543` |
|
||||
| X1C `01.12.00.00`, stock before `update_cert` ([#119](https://github.com/ClusterM/open-bamboo-networking/issues/119)) | none (no app leaf yet) | not reported | cleartext `ttcode`, rejected |
|
||||
| X1C `01.12.00.00`, stock after `update_cert` ([#119](https://github.com/ClusterM/open-bamboo-networking/issues/119)) | leaf from `update_cert` ([§8.4.6](08.04-lan.md#846-bambu_network_update_cert)) | 200 | `succeed` |
|
||||
|
||||
The cloud signs and field-encrypts its own command (`ttcode_enc`, `url_enc`) only when the originating REST call carries a PoP it accepts. Otherwise it either refuses the call (strict account, as with the X2D) or falls back to a legacy cleartext dispatch (lenient account), which secured firmware rejects. The plaintext `url` in an accepted `project_file` report is the echo decrypting `url_enc`: publishing a signed `project_file` with a cleartext `url` on the P1S was rejected `84033544` at file verify ([#112](https://github.com/ClusterM/open-bamboo-networking/issues/112)). A cloud memory of `app_cert_install` is ruled out for this gate: after a successful install published on the cloud session (broker delivered `printer_cert` and `result: SUCCESS`), the next PoP-less mint was still cleartext. The printer's current trust store does not matter either (X2D post-reboot). What still differs between accounts is only the strict-versus-lenient choice for a missing PoP.
|
||||
|
||||
**TODO**: what chooses strict (`403`) versus lenient (`200` + cleartext) for a missing PoP. The X2D Windows-leaf mint above still needs its MQTT echo checked.
|
||||
|
||||
| Payload shape | Result | Error code / HMS |
|
||||
|---|---|---|
|
||||
|
||||
@@ -92,6 +92,20 @@ In other words: take the on-wire envelope `{"header":{…},"print"…<rest>`, dr
|
||||
This is why a compact, sorted serializer *works* but is not *mandatory*: it just happens to make "what I signed" equal "what I send" trivially. The robust rule for a reimplementation is: **serialize the `print` value once (after field encryption), sign `{` + those exact bytes + (whatever trailing bytes you will also emit), then emit those exact same bytes after `,"print":` (or `, "print":`, matching your prefix) in the envelope.** *(RN-5 gives the algorithm shape; the "no canonicalization, byte-exact wire match" rule is from on-hardware testing.)*
|
||||
|
||||
> **Unrelated pitfall that masquerades as a signature error:** a non-monotonic or reused `sequence_id` (e.g. always `"0"`) can get the command rejected even though the signature is valid. Use a monotonically increasing `sequence_id` before suspecting the signing path. See [§6.2](06.02-mqtt.md#mqtt-sequence_id).
|
||||
>
|
||||
#### Trust store vs. signing leaf (X2D, 2026-10-05)
|
||||
|
||||
X2D `01.02.00.00`, Developer Mode off, freshly rebooted (empty trust store), app certs installed one by one over LAN `app_cert_install`; probe = `extrusion_cali_get` with `sequence_id` 21001–21012:
|
||||
|
||||
| Trust store | Unsigned | Signed, Linux leaf (`a4e8faaa…`) | Signed, Windows leaf (`4a63194e…`) |
|
||||
|---|---|---|---|
|
||||
| empty | `84033543` | `84033545` | `84033545` |
|
||||
| Windows leaf only | `84033543` | `84033545` | `success` |
|
||||
| Windows + Linux | — | `success` | `success` |
|
||||
|
||||
So `84033545` ("need reset device pub key") is what a valid signature gets when its `cert_id` is not in the trust store; any installed leaf verifies, independently of which leaf the cloud accepts for HTTP PoP ([§10.2](10.02-secrets.md)).
|
||||
|
||||
> **Small `sequence_id` on signed frames.** The same `84033545` comes back for a correctly signed frame from an installed leaf when its `sequence_id` is below **2000** (X2D, 2026-10-05). `1999` fails and `2000` succeeds; it is a fixed floor, not a high-water mark (after `900000002` was accepted, `40001`, `20001`, `5000` and `2000` were still accepted, in that order). An unsigned frame is rejected with `84033543` regardless of `sequence_id`. Studio's 20000–29999 window and this plugin's ids stay above the floor. The cloud's own `liveview.prepare` carries `sequence_id: "0"` and is accepted with an empty trust store ([§10.3](10.03-mqtt-field-encryption.md)), so the cloud's frames are not verified through this path.
|
||||
|
||||
Non-crypto `project_file` field catalogue: [§8.8.1](08.08-print-abi.md#mqtt-project_file-payload). HTTP PoP: [§10.5](10.05-http-pop.md). Provisioning / trust store: [§10.2](10.02-secrets.md#provisioning-printer-trust-store).
|
||||
|
||||
|
||||
@@ -46,6 +46,7 @@ The server recovers the timestamp and checks recency (replay protection). Pitfal
|
||||
#### Practical notes
|
||||
|
||||
- Absent a configured app key, **omit** both PoP headers rather than sending blanks.
|
||||
- The cloud accepts the PoP only from the leaf that matches `X-BBL-OS-Type` (Linux leaf with `linux`, Windows leaf with `windows`). The Windows leaf also requires a real `X-BBL-Executable-info`; `{}` or a missing header is `403 code:8`. Matrix: [§6.6](06.06-cloud-rest.md).
|
||||
- MQTT `header` signing ([§10.4](10.04-mqtt-signing.md)) is a separate path; cloud-dispatched prints often never publish MQTT `project_file` at all.
|
||||
- End-to-end cloud print authorization (trigger semantics, duplicate `/my/task`, fingerprint headers): [§8.8.3](08.08-print-abi.md#883-bambu_network_start_local_print_with_record).
|
||||
|
||||
|
||||
@@ -107,7 +107,7 @@ The shared app private key is **global** (identical for every install — [§10.
|
||||
So a single at-rest dump is sufficient — the "obfuscation at rest" is just the fixed-key AES-CTR blob, which is reversible with the S-box recovered above. The cloud route ([`fetch_slicer_credentials.py`](../tools/fetch_slicer_credentials.py) with `client_auth_secret` + `server_wrap_key`) reaches the identical blob over the network; the dump route skips the request and the wrap key entirely.
|
||||
|
||||
#### Sources
|
||||
- **Binary** — `bambu_networking.dll` (Studio Beta): packed section table (`.~C` / `.*_s` / `.b:@`), so constants must come from process memory.
|
||||
- **Binary** — `bambu_networking.dll` (Windows Studio, 2026-09-10 dump; the leaf it carries is the ordinary Windows platform leaf, [§10.2](10.02-secrets.md#embedded-in-the-stock-plugin-binary)): packed section table (`.~C` / `.*_s` / `.b:@`), so constants must come from process memory.
|
||||
- **Process dump** — full `procdump -ma` of running Bambu Studio (2026-09-10): anchored via the SHA-256 IV (`.rdata`) and `br_aes_small_ctr_vtable` pointers (`.text`); all 50+ `br_*` resolved; `br_aes_S` at RVA `0x650d60` byte-identical to the tool's `SBOX`; AES-CTR `ctx=256`/`block=16` from the vtable; key-bearing primitives found relocated+mutated in the `.~C` section behind `jmp` thunks, with generic hash/math helpers left clean; GCM/CTR/bignum caller sites identified by behaviour in `.~C`. Same dump: current app leaf + `client_auth_secret` recovered ([§10.2](10.02-secrets.md#embedded-in-the-stock-plugin-binary)); **shared app private key recovered offline** by locating the resident encrypted `key` blob (VA `0x1d36d9acbb0`) and unwrapping it with the S-box cipher — `p·q == n` and RSA-SHA256 verified.
|
||||
- **Reimplementation** — [`tools/fetch_slicer_credentials.py`](../tools/fetch_slicer_credentials.py): custom S-box AES-256-CTR blob unwrap + request envelope; the S-box and framing here match.
|
||||
- Pipeline this maps onto: [§10.2](10.02-secrets.md) (secrets, envelope, blob unwrap), [§10.4](10.04-mqtt-signing.md) (MQTT signing), [§10.5](10.05-http-pop.md) (HTTP PoP), [§10.3](10.03-mqtt-field-encryption.md) (device-cert field encryption — OpenSSL side).
|
||||
|
||||
@@ -58,8 +58,8 @@ uint16_t now_ms16()
|
||||
// [18] marks the first packet. The legacy login has the step as the type,
|
||||
// the UID at [518] and 2 at [542] and gets the plain slice stream.
|
||||
std::vector<uint8_t> build_av_login(uint8_t step, uint32_t nonce, bool framed,
|
||||
const std::string& passwd,
|
||||
const std::string& uid_upper)
|
||||
const std::string& passwd,
|
||||
const std::string& uid_upper)
|
||||
{
|
||||
std::vector<uint8_t> pkt(570, 0);
|
||||
put_le16(pkt.data() + 2, kAvVersion);
|
||||
@@ -349,8 +349,8 @@ public:
|
||||
next_ = it->first + 1;
|
||||
have_next_ = true;
|
||||
pending_.erase(it);
|
||||
continue;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
// The missing packets are gone once a later frame has started
|
||||
// and no FEC group can bring them back.
|
||||
const bool later_started = gap || pending_.size() > 1;
|
||||
@@ -360,8 +360,8 @@ public:
|
||||
if (gap) {
|
||||
dropped += (int)(it->first - next_);
|
||||
next_ = it->first;
|
||||
continue;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
++dropped;
|
||||
next_ = it->first + 1;
|
||||
have_next_ = true;
|
||||
@@ -682,8 +682,8 @@ int TutkSession::Impl::connect(const TutkSessionParams& p)
|
||||
if (open_path(p) != 0) {
|
||||
OBN_WARN("tutk: printer unreachable over LAN and relay (attempt %d/3)", attempt);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(500));
|
||||
continue;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (!conn.is_lan) path = conn.is_relay ? "relay" : "p2p";
|
||||
OBN_INFO("tutk: connected via %s (attempt %d/3)", path, attempt);
|
||||
|
||||
@@ -691,8 +691,8 @@ int TutkSession::Impl::connect(const TutkSessionParams& p)
|
||||
OBN_WARN("tutk: DTLS handshake failed (attempt %d/3)", attempt);
|
||||
iotc_close(&conn);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(500));
|
||||
continue;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
out_seq = 1;
|
||||
ioctrl_index = 0;
|
||||
|
||||
Reference in new issue
Block a user