Compare commits

..

18 Commits

Author SHA1 Message Date
Andrew Gunnerson da124e4e05 Version 3.15.0
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2025-04-06 00:09:21 -04:00
Andrew Gunnerson 7b778515d1 CHANGELOG.md: Add entry for PR #439
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2025-04-06 00:02:48 -04:00
Andrew Gunnerson 98745afe33 Update dependencies
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2025-04-05 23:54:20 -04:00
Andrew Gunnerson a47c501211 CHANGELOG.md: Add entry for PR #438
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2025-04-05 23:53:50 -04:00
Andrew Gunnerson f2e47a65d4 Switch back to ring
This reverts commit e929ecbe44.

Ring is back to being maintained again, so let's switch back to it since
it has fewer build dependencies and is much faster to compile.

Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2025-04-05 23:52:53 -04:00
Andrew Gunnerson e11ddd2ba7 CHANGELOG.md: Add entry for PR #437
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2025-04-05 23:38:51 -04:00
Andrew Gunnerson 5d66774d13 Add support for changing VABC CoW compression algorithm
Devices that launch with Android <14 generally use gzip as the CoW
compression algorithm. This never changes because future full OTAs
always need to be installable from the version of Android the device
launched with.

However, for users that don't care about the upgrade path from old
versions of Android, a new --vabc-algo option can be used to switch from
gz to lz4 compression. This can cut down the OTA installation time by
more than 2/3rds when installing via a custom OTA updater app. On my
Pixel Tablet, the installation time for the update_engine DOWNLOADING
phase decreased from 32:05 to 9:41. Note that this has absolutely no
effect on the performance when sideloading from recovery mode because
that does not use CoW.

When this new option is used, all dynamic partitions need to be
extracted from the OTA during patching so that the CoW estimates can be
recomputed. This will slow down the patching process and use up more
temporary disk space.

Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2025-04-05 23:06:13 -04:00
Andrew Gunnerson 3f09a506a0 Version 3.14.0
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2025-03-23 19:27:05 -04:00
Andrew Gunnerson 4664f8ea37 CHANGELOG.md: Add entry for PR #435
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2025-03-23 19:26:29 -04:00
Andrew Gunnerson 796e2a4fa2 Update dependencies
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2025-03-23 19:17:15 -04:00
Andrew Gunnerson e6b60d5d0f CHANGELOG.md: Add entry for PR #434
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2025-03-23 19:02:39 -04:00
Andrew Gunnerson e8cb4a8d53 Fix incorrect compression input for gzip CoW size estimation
Instead of compressing the 64 MiB input in 2 MiB chunks, each loop
iteration was compressing the full 64 MiB. This massively slowed down
the patching process from seconds to potentially hours and would
temporarily waste a bunch of space during OTA installation.

Fixes: #433

Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2025-03-23 18:50:44 -04:00
Andrew Gunnerson b2d280eb20 CHANGELOG.md: Add entry for PR #430
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2025-03-13 20:23:48 -04:00
Andrew Gunnerson e2dc5174b4 format/ota: Decouple OTA signature parsing from verification
This way, we can fail hard for parsing errors, but not for verification
errors in `avbroot ota verify`.

Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2025-03-13 20:22:16 -04:00
Andrew Gunnerson 80c47e9a02 CHANGELOG.md: Add entry for PR #429
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2025-03-13 19:49:54 -04:00
Andrew Gunnerson 65ba3ad5cc Fix clippy 1.85 warnings
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2025-03-13 19:48:32 -04:00
Andrew Gunnerson a7438876ce CHANGELOG.md: Add entry for PR #428
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2025-03-13 19:38:16 -04:00
Andrew Gunnerson c039901a85 cli/ota: Improve ota verify otacerts.zip handling
* Try to avoid fail-fast behavior to show as many errors as possible.
  Parsing errors always fail immediately, but verification errors don't.
* Move the recovery otacerts.zip check to the end to let more important
  checks run first.
* Improve error message when otacerts.zip does not contain the signing
  certificate for the OTA to make it clear the issue is not that the zip
  contains no certificates at all.
* Always run the recovery otacerts.zip check, but just log the error as
  a warning when running with --skip-recovery-ota-cert.
* Fix unformatted error context string when parsing a boot image's
  otacerts.zip file fails.

Discussion: #426

Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2025-03-13 19:34:42 -04:00
29 changed files with 874 additions and 647 deletions
+3 -6
View File
@@ -20,8 +20,6 @@ jobs:
RUSTFLAGS: -C strip=symbols -C target-feature=+crt-static
TARGETS: ${{ join(matrix.artifact.targets, ' ') || matrix.artifact.name }}
ANDROID_API: ${{ matrix.artifact.android_api }}
# https://aws.github.io/aws-lc-rs/requirements/windows.html#use-of-prebuilt-nasm-objects
AWS_LC_SYS_PREBUILT_NASM: 1
strategy:
fail-fast: false
matrix:
@@ -36,8 +34,7 @@ jobs:
- aarch64-apple-darwin
- x86_64-apple-darwin
combine: lipo
# ubuntu-latest is not 24.04 yet and 22.04's qemu-user-static segfaults.
- os: ubuntu-24.04
- os: ubuntu-latest
name: aarch64-linux-android31
targets:
- aarch64-linux-android
@@ -86,7 +83,7 @@ jobs:
done
- name: Cache Rust dependencies
uses: Swatinem/rust-cache@f0deed1e0edfc6a9be95417288c0e1099b1eeec3 # v2.7.7
uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8
with:
key: ${{ matrix.artifact.name }}
@@ -157,7 +154,7 @@ jobs:
run: cp LICENSE README.md target/output/
- name: Archive executable
uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: avbroot-${{ steps.get_version.outputs.version }}-${{ matrix.artifact.name }}
path: |
+25
View File
@@ -7,6 +7,21 @@
to update the actual links at the bottom of the file.
-->
### Version 3.15.0
* Add support for changing the virtual A/B compression algorithm ([PR #437])
* For devices that launched with Android <14, `--vabc-algo lz4` can significantly increase OTA installation speed when using a custom OTA updater app (with caveats). There is no difference when sideloading from recovery mode.
* See [the documentation](#changing-virtual-ab-cow-compression-algorithm) for more details.
* Switch back to the ring library now that it is maintained again ([PR #438])
* Update dependencies ([PR #439])
### Version 3.14.0
* Report as many errors as possible before failing in `avbroot ota verify` and improve error messages ([Discussion #426], [PR #428], [PR #430])
* Fix new clippy warnings introduced in Rust 1.85 ([PR #429])
* Fix massive performance regression introduced in 3.13.0 for OTAs that use gzip for virtual A/B CoW compression ([Issue #433], [PR #434])
* Update dependencies ([PR #435])
### Version 3.13.0
* Fix parsing Samsung `super.img` files in `avbroot lp` due to Samsung putting their own data structures in a region that's supposed to be filled with zeros ([PR #415])
@@ -294,6 +309,7 @@ Behind-the-scenes changes:
[Discussion #286]: https://github.com/chenxiaolong/avbroot/discussions/286
[Discussion #294]: https://github.com/chenxiaolong/avbroot/discussions/294
[Discussion #417]: https://github.com/chenxiaolong/avbroot/discussions/417
[Discussion #426]: https://github.com/chenxiaolong/avbroot/discussions/426
[Issue #138]: https://github.com/chenxiaolong/avbroot/issues/138
[Issue #144]: https://github.com/chenxiaolong/avbroot/issues/144
[Issue #145]: https://github.com/chenxiaolong/avbroot/issues/145
@@ -320,6 +336,7 @@ Behind-the-scenes changes:
[Issue #356]: https://github.com/chenxiaolong/avbroot/issues/356
[Issue #366]: https://github.com/chenxiaolong/avbroot/issues/366
[Issue #393]: https://github.com/chenxiaolong/avbroot/issues/393
[Issue #433]: https://github.com/chenxiaolong/avbroot/issues/433
[PR #130]: https://github.com/chenxiaolong/avbroot/pull/130
[PR #132]: https://github.com/chenxiaolong/avbroot/pull/132
[PR #133]: https://github.com/chenxiaolong/avbroot/pull/133
@@ -466,3 +483,11 @@ Behind-the-scenes changes:
[PR #424]: https://github.com/chenxiaolong/avbroot/pull/424
[PR #425]: https://github.com/chenxiaolong/avbroot/pull/425
[PR #427]: https://github.com/chenxiaolong/avbroot/pull/427
[PR #428]: https://github.com/chenxiaolong/avbroot/pull/428
[PR #429]: https://github.com/chenxiaolong/avbroot/pull/429
[PR #430]: https://github.com/chenxiaolong/avbroot/pull/430
[PR #434]: https://github.com/chenxiaolong/avbroot/pull/434
[PR #435]: https://github.com/chenxiaolong/avbroot/pull/435
[PR #437]: https://github.com/chenxiaolong/avbroot/pull/437
[PR #438]: https://github.com/chenxiaolong/avbroot/pull/438
[PR #439]: https://github.com/chenxiaolong/avbroot/pull/439
Generated
+126 -251
View File
@@ -70,7 +70,7 @@ version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c"
dependencies = [
"windows-sys",
"windows-sys 0.59.0",
]
[[package]]
@@ -81,7 +81,7 @@ checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e"
dependencies = [
"anstyle",
"once_cell",
"windows-sys",
"windows-sys 0.59.0",
]
[[package]]
@@ -110,11 +110,10 @@ checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
[[package]]
name = "avbroot"
version = "3.13.0"
version = "3.15.0"
dependencies = [
"anyhow",
"assert_matches",
"aws-lc-rs",
"base64",
"bitflags",
"bstr",
@@ -147,8 +146,9 @@ dependencies = [
"rand",
"rayon",
"regex",
"ring",
"rsa",
"rustix 0.38.44",
"rustix 1.0.5",
"serde",
"sha1",
"sha2",
@@ -164,29 +164,6 @@ dependencies = [
"zip",
]
[[package]]
name = "aws-lc-rs"
version = "1.12.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dabb68eb3a7aa08b46fddfd59a3d55c978243557a90ab804769f7e20e67d2b01"
dependencies = [
"aws-lc-sys",
"zeroize",
]
[[package]]
name = "aws-lc-sys"
version = "0.27.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6bbe221bbf523b625a4dd8585c7f38166e31167ec2ca98051dbcb4c3b6e825d2"
dependencies = [
"bindgen",
"cc",
"cmake",
"dunce",
"fs_extra",
]
[[package]]
name = "base64"
version = "0.22.1"
@@ -195,9 +172,9 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
[[package]]
name = "base64ct"
version = "1.7.1"
version = "1.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb97d56060ee67d285efb8001fec9d2a4c710c32efd2e14b5cbb5ba71930fc2d"
checksum = "89e25b6adfb930f02d1981565a6e5d9c547ac15a96606256d3b59040e5cd4ca3"
[[package]]
name = "beef"
@@ -205,29 +182,6 @@ version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a8241f3ebb85c056b509d4327ad0358fbbba6ffb340bf388f26350aeda225b1"
[[package]]
name = "bindgen"
version = "0.69.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088"
dependencies = [
"bitflags",
"cexpr",
"clang-sys",
"itertools 0.12.1",
"lazy_static",
"lazycell",
"log",
"prettyplease",
"proc-macro2",
"quote",
"regex",
"rustc-hash",
"shlex",
"syn 2.0.100",
"which",
]
[[package]]
name = "bitflags"
version = "2.9.0"
@@ -300,9 +254,9 @@ dependencies = [
[[package]]
name = "cap-primitives"
version = "3.4.2"
version = "3.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fc15faeed2223d8b8e8cc1857f5861935a06d06713c4ac106b722ae9ce3c369"
checksum = "50ad0183a9659850877cefe8f5b87d564b2dd1fe78b18945813687f29c0a6878"
dependencies = [
"ambient-authority",
"fs-set-times",
@@ -311,15 +265,15 @@ dependencies = [
"ipnet",
"maybe-owned",
"rustix 0.38.44",
"windows-sys",
"windows-sys 0.59.0",
"winx",
]
[[package]]
name = "cap-std"
version = "3.4.2"
version = "3.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3dbd3e8e8d093d6ccb4b512264869e1281cdb032f7940bd50b2894f96f25609"
checksum = "1c41814365b796ed12688026cb90a1e03236a84ccf009628f9c43c8aa3af250a"
dependencies = [
"cap-primitives",
"io-extras",
@@ -329,9 +283,9 @@ dependencies = [
[[package]]
name = "cap-tempfile"
version = "3.4.2"
version = "3.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ffa1c0edc4958d742bab2e903e52f93ccee482072680e08d6ce0784873e65b1"
checksum = "87f95996888e8bb5198a6f27f9f9454244d1ddf4cf82202808ad6b7d570e858e"
dependencies = [
"cap-std",
"rand",
@@ -350,24 +304,15 @@ dependencies = [
[[package]]
name = "cc"
version = "1.2.16"
version = "1.2.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be714c154be609ec7f5dad223a33bf1482fff90472de28f7362806e6d4832b8c"
checksum = "525046617d8376e3db1deffb079e91cef90a89fc3ca5c185bbf8c9ecdd15cd5c"
dependencies = [
"jobserver",
"libc",
"shlex",
]
[[package]]
name = "cexpr"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766"
dependencies = [
"nom",
]
[[package]]
name = "cfg-if"
version = "1.0.0"
@@ -390,22 +335,11 @@ dependencies = [
"inout",
]
[[package]]
name = "clang-sys"
version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4"
dependencies = [
"glob",
"libc",
"libloading",
]
[[package]]
name = "clap"
version = "4.5.32"
version = "4.5.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6088f3ae8c3608d19260cd7445411865a485688711b78b5be70d78cd96136f83"
checksum = "d8aa86934b44c19c50f87cc2790e19f54f7a67aedb64101c2e1a2e5ecfb73944"
dependencies = [
"clap_builder",
"clap_derive",
@@ -413,9 +347,9 @@ dependencies = [
[[package]]
name = "clap_builder"
version = "4.5.32"
version = "4.5.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22a7ef7f676155edfb82daa97f99441f3ebf4a58d5e32f295a56259f1b6facc8"
checksum = "2414dbb2dd0695280da6ea9261e327479e9d37b0630f6b53ba2a11c60c679fd9"
dependencies = [
"anstream",
"anstyle",
@@ -425,9 +359,9 @@ dependencies = [
[[package]]
name = "clap_complete"
version = "4.5.46"
version = "4.5.47"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f5c5508ea23c5366f77e53f5a0070e5a84e51687ec3ef9e0464c86dc8d13ce98"
checksum = "c06f5378ea264ad4f82bbc826628b5aad714a75abf6ece087e923010eb937fb6"
dependencies = [
"clap",
]
@@ -450,15 +384,6 @@ version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6"
[[package]]
name = "cmake"
version = "0.1.54"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7caa3f9de89ddbe2c607f4101924c5abec803763ae9534e4f4d7d8f84aa81f0"
dependencies = [
"cc",
]
[[package]]
name = "cms"
version = "0.2.3"
@@ -544,12 +469,12 @@ dependencies = [
[[package]]
name = "ctrlc"
version = "3.4.5"
version = "3.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90eeab0aa92f3f9b4e87f258c72b139c207d251f9cbc1080a0086b86a8870dd3"
checksum = "697b5419f348fd5ae2478e8018cb016c00a5881c7f46c717de98ffd135a5651c"
dependencies = [
"nix",
"windows-sys",
"windows-sys 0.59.0",
]
[[package]]
@@ -629,22 +554,16 @@ version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ecb08c4819242b1ec89b3d0c6affa229005bef46ae4f7eed8b80768187c10087"
[[package]]
name = "dunce"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813"
[[package]]
name = "e2e"
version = "3.13.0"
version = "3.15.0"
dependencies = [
"anyhow",
"avbroot",
"aws-lc-rs",
"clap",
"ctrlc",
"hex",
"ring",
"rsa",
"serde",
"tempfile",
@@ -670,12 +589,12 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
[[package]]
name = "errno"
version = "0.3.10"
version = "0.3.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d"
checksum = "976dd42dc7e85965fe702eb8164f21f450704bdde31faefd6471dba214cb594e"
dependencies = [
"libc",
"windows-sys",
"windows-sys 0.59.0",
]
[[package]]
@@ -698,15 +617,15 @@ checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99"
[[package]]
name = "flagset"
version = "0.4.6"
version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b3ea1ec5f8307826a5b71094dd91fc04d4ae75d5709b20ad351c7fb4815c86ec"
checksum = "b7ac824320a75a52197e8f2d787f6a38b6718bb6897a35142d749af3c0e8f4fe"
[[package]]
name = "flate2"
version = "1.1.0"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "11faaf5a5236997af9848be0bef4db95824b1d534ebc64d0f0c6cf3e67bd38dc"
checksum = "7ced92e76e966ca2fd84c8f7aa01a4aea65b0eb6648d72f7c8f3e2764a67fece"
dependencies = [
"crc32fast",
"libz-rs-sys",
@@ -726,19 +645,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94e7099f6313ecacbe1256e8ff9d617b75d1bcb16a6fddef94866d225a01a14a"
dependencies = [
"io-lifetimes",
"rustix 1.0.2",
"windows-sys",
"rustix 1.0.5",
"windows-sys 0.59.0",
]
[[package]]
name = "fs_extra"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c"
[[package]]
name = "fuzz"
version = "3.13.0"
version = "3.15.0"
dependencies = [
"avbroot",
"honggfuzz",
@@ -767,14 +680,14 @@ dependencies = [
[[package]]
name = "getrandom"
version = "0.3.1"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8"
checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0"
dependencies = [
"cfg-if",
"libc",
"wasi 0.13.3+wasi-0.2.2",
"windows-targets",
"r-efi",
"wasi 0.14.2+wasi-0.2.4",
]
[[package]]
@@ -800,12 +713,6 @@ dependencies = [
"syn 1.0.109",
]
[[package]]
name = "glob"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2"
[[package]]
name = "hashbrown"
version = "0.15.2"
@@ -836,25 +743,17 @@ dependencies = [
"digest",
]
[[package]]
name = "home"
version = "0.5.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf"
dependencies = [
"windows-sys",
]
[[package]]
name = "honggfuzz"
version = "0.5.56"
version = "0.5.57"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c76b6234c13c9ea73946d1379d33186151148e0da231506b964b44f3d023505"
checksum = "fc563d4f41b17364d5c48ded509f2bcf1c3f6ae9c7f203055b4a5c325072d57e"
dependencies = [
"arbitrary",
"lazy_static",
"memmap2",
"rustc_version",
"semver",
]
[[package]]
@@ -865,9 +764,9 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
[[package]]
name = "indexmap"
version = "2.8.0"
version = "2.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3954d50fe15b02142bf25d3b8bdadb634ec3948f103d04ffe3031bc8fe9d7058"
checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e"
dependencies = [
"equivalent",
"hashbrown",
@@ -890,7 +789,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2285ddfe3054097ef4b2fe909ef8c3bcd1ea52a8f0d274416caebeef39f04a65"
dependencies = [
"io-lifetimes",
"windows-sys",
"windows-sys 0.59.0",
]
[[package]]
@@ -911,15 +810,6 @@ version = "1.70.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
[[package]]
name = "itertools"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569"
dependencies = [
"either",
]
[[package]]
name = "itertools"
version = "0.14.0"
@@ -937,10 +827,11 @@ checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
[[package]]
name = "jobserver"
version = "0.1.32"
version = "0.1.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0"
checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a"
dependencies = [
"getrandom 0.3.2",
"libc",
]
@@ -953,12 +844,6 @@ dependencies = [
"spin",
]
[[package]]
name = "lazycell"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
[[package]]
name = "libbz2-rs-sys"
version = "0.1.3"
@@ -971,16 +856,6 @@ version = "0.2.171"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6"
[[package]]
name = "libloading"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34"
dependencies = [
"cfg-if",
"windows-targets",
]
[[package]]
name = "liblzma"
version = "0.3.6"
@@ -1009,9 +884,9 @@ checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa"
[[package]]
name = "libz-rs-sys"
version = "0.4.2"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "902bc563b5d65ad9bba616b490842ef0651066a1a1dc3ce1087113ffcb873c8d"
checksum = "6489ca9bd760fe9642d7644e827b0c9add07df89857b0416ee15c1cc1a3b8c5a"
dependencies = [
"zlib-rs",
]
@@ -1024,15 +899,15 @@ checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab"
[[package]]
name = "linux-raw-sys"
version = "0.9.2"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6db9c683daf087dc577b7506e9695b3d556a9f3849903fa28186283afd6809e9"
checksum = "fe7db12097d22ec582439daf8618b8fdd1a7bef6270e9af3b1ebcd30893cf413"
[[package]]
name = "log"
version = "0.4.26"
version = "0.4.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e"
checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
[[package]]
name = "logos"
@@ -1120,17 +995,11 @@ dependencies = [
"syn 2.0.100",
]
[[package]]
name = "minimal-lexical"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]]
name = "miniz_oxide"
version = "0.8.5"
version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e3e04debbb59698c15bacbb6d93584a8c0ca9cc3213cb423d31f760d8843ce5"
checksum = "ff70ce3e48ae43fa075863cef62e8b43b71a4f2382229920e0df362592919430"
dependencies = [
"adler2",
]
@@ -1153,16 +1022,6 @@ dependencies = [
"libc",
]
[[package]]
name = "nom"
version = "7.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
dependencies = [
"memchr",
"minimal-lexical",
]
[[package]]
name = "nu-ansi-term"
version = "0.46.0"
@@ -1223,9 +1082,9 @@ dependencies = [
[[package]]
name = "once_cell"
version = "1.21.0"
version = "1.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cde51589ab56b20a6f686b2c68f7a0bd6add753d697abf720d63f8db3ab7b1ad"
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
[[package]]
name = "overload"
@@ -1374,9 +1233,9 @@ dependencies = [
[[package]]
name = "prettyplease"
version = "0.2.30"
version = "0.2.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1ccf34da56fc294e7d4ccf69a85992b7dfb826b7cf57bac6a70bba3494cc08a"
checksum = "664ec5419c51e34154eec046ebcba56312d5a2fc3b09a06da188e1ad21afadf6"
dependencies = [
"proc-macro2",
"syn 2.0.100",
@@ -1408,7 +1267,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be769465445e8c1474e9c5dac2018218498557af32d9ed057325ec9a41ae81bf"
dependencies = [
"heck",
"itertools 0.14.0",
"itertools",
"log",
"multimap",
"once_cell",
@@ -1428,7 +1287,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a56d757972c98b346a9b766e3f02746cde6dd1cd1d1d563472929fdd74bec4d"
dependencies = [
"anyhow",
"itertools 0.14.0",
"itertools",
"proc-macro2",
"quote",
"syn 2.0.100",
@@ -1492,6 +1351,12 @@ dependencies = [
"proc-macro2",
]
[[package]]
name = "r-efi"
version = "5.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5"
[[package]]
name = "rand"
version = "0.8.5"
@@ -1571,6 +1436,20 @@ version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
[[package]]
name = "ring"
version = "0.17.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7"
dependencies = [
"cc",
"cfg-if",
"getrandom 0.2.15",
"libc",
"untrusted",
"windows-sys 0.52.0",
]
[[package]]
name = "rsa"
version = "0.9.8"
@@ -1593,12 +1472,6 @@ dependencies = [
"zeroize",
]
[[package]]
name = "rustc-hash"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
[[package]]
name = "rustc_version"
version = "0.4.1"
@@ -1620,20 +1493,20 @@ dependencies = [
"libc",
"linux-raw-sys 0.4.15",
"once_cell",
"windows-sys",
"windows-sys 0.59.0",
]
[[package]]
name = "rustix"
version = "1.0.2"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7178faa4b75a30e269c71e61c353ce2748cf3d76f0c44c393f4e60abf49b825"
checksum = "d97817398dd4bb2e6da002002db259209759911da105da92bec29ccb12cf58bf"
dependencies = [
"bitflags",
"errno",
"libc",
"linux-raw-sys 0.9.2",
"windows-sys",
"linux-raw-sys 0.9.3",
"windows-sys 0.59.0",
]
[[package]]
@@ -1746,9 +1619,9 @@ checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d"
[[package]]
name = "smallvec"
version = "1.14.0"
version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd"
checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9"
[[package]]
name = "spin"
@@ -1814,16 +1687,15 @@ dependencies = [
[[package]]
name = "tempfile"
version = "3.18.0"
version = "3.19.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c317e0a526ee6120d8dabad239c8dadca62b24b6f168914bbbc8e2fb1f0e567"
checksum = "7437ac7763b9b123ccf33c338a5cc1bac6f69b45a136c19bdd8a65e3916435bf"
dependencies = [
"cfg-if",
"fastrand",
"getrandom 0.3.1",
"getrandom 0.3.2",
"once_cell",
"rustix 1.0.2",
"windows-sys",
"rustix 1.0.5",
"windows-sys 0.59.0",
]
[[package]]
@@ -2010,6 +1882,12 @@ version = "0.1.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af"
[[package]]
name = "untrusted"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
[[package]]
name = "utf8parse"
version = "0.2.2"
@@ -2018,11 +1896,11 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]]
name = "uuid"
version = "1.15.1"
version = "1.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0f540e3240398cce6128b64ba83fdbdd86129c16a3aa1a3a252efd66eb3d587"
checksum = "458f7a779bf54acc9f347480ac654f68407d3aab21269a6e3c9f922acd9e2da9"
dependencies = [
"getrandom 0.3.1",
"getrandom 0.3.2",
]
[[package]]
@@ -2045,25 +1923,13 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "wasi"
version = "0.13.3+wasi-0.2.2"
version = "0.14.2+wasi-0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2"
checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3"
dependencies = [
"wit-bindgen-rt",
]
[[package]]
name = "which"
version = "4.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7"
dependencies = [
"either",
"home",
"once_cell",
"rustix 0.38.44",
]
[[package]]
name = "winapi"
version = "0.3.9"
@@ -2086,6 +1952,15 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows-sys"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-sys"
version = "0.59.0"
@@ -2175,14 +2050,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f3fd376f71958b862e7afb20cfe5a22830e1963462f3a17f49d82a6c1d1f42d"
dependencies = [
"bitflags",
"windows-sys",
"windows-sys 0.59.0",
]
[[package]]
name = "wit-bindgen-rt"
version = "0.33.0"
version = "0.39.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c"
checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1"
dependencies = [
"bitflags",
]
@@ -2203,7 +2078,7 @@ dependencies = [
[[package]]
name = "xtask"
version = "3.13.0"
version = "3.15.0"
dependencies = [
"anyhow",
"clap",
@@ -2213,18 +2088,18 @@ dependencies = [
[[package]]
name = "zerocopy"
version = "0.8.23"
version = "0.8.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd97444d05a4328b90e75e503a34bad781f14e28a823ad3557f0750df1ebcbc6"
checksum = "2586fea28e186957ef732a5f8b3be2da217d65c5969d4b1e17f973ebbe876879"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.8.23"
version = "0.8.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6352c01d0edd5db859a63e2605f4ea3183ddbd15e2c4a9e7d32184df75e4f154"
checksum = "a996a8f63c5c4448cd959ac1bab0aaa3306ccfd060472f85943ee0750f0169be"
dependencies = [
"proc-macro2",
"quote",
@@ -2264,6 +2139,6 @@ dependencies = [
[[package]]
name = "zlib-rs"
version = "0.4.2"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b20717f0917c908dc63de2e44e97f1e6b126ca58d0e391cee86d504eb8fbd05"
checksum = "868b928d7949e09af2f6086dfc1e01936064cc7a819253bce650d4e2a2d63ba8"
+1 -1
View File
@@ -4,7 +4,7 @@ members = ["avbroot", "e2e", "fuzz", "xtask"]
resolver = "2"
[workspace.package]
version = "3.13.0"
version = "3.15.0"
license = "GPL-3.0-only"
edition = "2021"
repository = "https://github.com/chenxiaolong/avbroot"
+12
View File
@@ -431,6 +431,18 @@ Verified boot is disabled by vbmeta's header flags: 0x3
To forcibly enable AVB (by clearing the flags), pass in `--clear-vbmeta-flags`.
### Changing virtual A/B CoW compression algorithm
The virtual A/B CoW compression algorithm can be changed by passing in `--vabc-algo <algo>` with `gz` or `lz4`. OTAs normally use an algorithm that is compatible with the initial version of Android shipped on the device.
* Devices launching with Android 12 support `gz` and `brotli` (unsupported by avbroot)
* Devices launching with Android 14 support `lz4`
* Devices launching with Android 15 support `zstd` (unsupported by avbroot)
Picking a fast algorithm, like lz4, can speed up OTA installation significantly when installing via a custom OTA updater app. However, there is no performance difference when sideloading an OTA from recovery mode.
Note that the currently running version of Android must support the specified compression algorithm or else the OTA will fail to install. For example, trying to install an Android 14 OTA that uses lz4 CoW compression will fail if the running system is Android 13.
### Non-interactive use
avbroot prompts for the private key passphrases interactively by default. To run avbroot non-interactively, either:
+8 -6
View File
@@ -10,11 +10,6 @@ publish = false
[dependencies]
anyhow = "1.0.75"
# We use aws-lc-rs instead of sha2 for sha256 digest computation of large files
# because sha2 is significantly slower on older x86_64 CPUs without the SHA-NI
# instructions. sha2 is still used for signing purposes.
# https://github.com/RustCrypto/hashes/issues/327
aws-lc-rs = { version = "1.0.0", default-features = false, features = ["aws-lc-sys"] }
base64 = "0.22.1"
bitflags = { version = "2.4.1", features = ["serde"] }
bstr = "1.6.2"
@@ -24,6 +19,8 @@ cap-tempfile = "3.0.0"
clap = { version = "4.4.1", features = ["derive"] }
clap_complete = "4.4.0"
cms = { version = "0.2.2", features = ["std"] }
# We can't upgrade to 0.10.0 until x509-cert updates it too, since it's part of
# the public API.
const-oid = "0.9.5"
crc32fast = "1.4.2"
ctrlc = "3.4.0"
@@ -44,6 +41,11 @@ prost = "0.13.1"
rand = "0.8.5"
rayon = "1.7.0"
regex = { version = "1.9.4", default-features = false, features = ["perf", "std"] }
# We use ring instead of sha2 for sha256 digest computation of large files
# because sha2 is significantly slower on older x86_64 CPUs without the SHA-NI
# instructions. sha2 is still used for signing purposes.
# https://github.com/RustCrypto/hashes/issues/327
ring = "0.17.14"
rsa = { version = "0.9.2", features = ["sha1", "sha2"] }
serde = { version = "1.0.188", features = ["derive"] }
sha1 = "0.10.5"
@@ -67,7 +69,7 @@ features = ["deflate"]
[target.'cfg(unix)'.dependencies]
libc = "0.2.158"
rustix = { version = "0.38.9", default-features = false, features = ["process"] }
rustix = { version = "1.0.3", default-features = false, features = ["process"] }
[build-dependencies]
constcat = "0.6.0"
+2 -2
View File
@@ -599,7 +599,7 @@ pub fn verify_descriptors(
fn compute_digest_recursive(
directory: &Dir,
name: &str,
context: &mut aws_lc_rs::digest::Context,
context: &mut ring::digest::Context,
max_depth: u8,
seen: &mut HashSet<String>,
cancel_signal: &AtomicBool,
@@ -670,7 +670,7 @@ fn compute_digest_recursive(
/// chained partitions more than one level deep are ignored.
pub fn compute_digest(directory: &Dir, name: &str, cancel_signal: &AtomicBool) -> Result<[u8; 32]> {
let mut seen = HashSet::<String>::new();
let mut context = aws_lc_rs::digest::Context::new(&aws_lc_rs::digest::SHA256);
let mut context = ring::digest::Context::new(&ring::digest::SHA256);
compute_digest_recursive(directory, name, &mut context, 2, &mut seen, cancel_signal)?;
+370 -159
View File
@@ -14,13 +14,14 @@ use std::{
};
use anyhow::{anyhow, bail, Context, Result};
use bitflags::bitflags;
use cap_std::{ambient_authority, fs::Dir};
use cap_tempfile::TempDir;
use clap::{value_parser, ArgAction, Args, Parser, Subcommand};
use clap::{value_parser, ArgAction, Args, Parser, Subcommand, ValueEnum};
use rayon::{iter::IntoParallelRefIterator, prelude::ParallelIterator};
use tempfile::NamedTempFile;
use topological_sort::TopologicalSort;
use tracing::{debug_span, info, warn};
use tracing::{debug_span, error, info, warn};
use x509_cert::Certificate;
use zip::{write::FileOptions, CompressionMethod, ZipArchive, ZipWriter};
@@ -72,48 +73,65 @@ fn sorted<T: Ord>(iter: impl Iterator<Item = T>) -> Vec<T> {
items
}
pub struct RequiredImages(HashSet<String>);
bitflags! {
#[repr(transparent)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct PartitionFlags: u8 {
const BOOT = 1 << 0;
const SYSTEM = 1 << 1;
const VBMETA = 1 << 2;
const COW = 1 << 3;
impl RequiredImages {
pub fn new(manifest: &DeltaArchiveManifest) -> Self {
let partitions = manifest
.partitions
.iter()
.map(|p| &p.partition_name)
.filter(|n| Self::is_boot(n) || Self::is_system(n) || Self::is_vbmeta(n))
.cloned()
.collect();
Self(partitions)
const KNOWN = Self::BOOT.bits() | Self::SYSTEM.bits() | Self::VBMETA.bits();
}
pub fn is_boot(name: &str) -> bool {
name == "boot" || name == "init_boot" || name == "recovery" || name == "vendor_boot"
#[repr(transparent)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct RequiredFlags: u8 {
const SYSTEM = 1 << 0;
const ALL_COW = 1 << 1;
}
}
/// Get the images required for patching. If [`RequiredFlags::SYSTEM`] is
/// specified, then the system image is included. If [`RequiredFlags::ALL_COW`]
/// is specified, then all images with CoW size estimates are included.
pub fn get_required_images(
manifest: &DeltaArchiveManifest,
required_flags: RequiredFlags,
) -> HashMap<String, PartitionFlags> {
let mut result = HashMap::new();
for partition in &manifest.partitions {
let name = &partition.partition_name;
let mut flags = PartitionFlags::empty();
if name == "boot" || name == "init_boot" || name == "recovery" || name == "vendor_boot" {
flags |= PartitionFlags::BOOT;
} else if required_flags.contains(RequiredFlags::SYSTEM) && name == "system" {
flags |= PartitionFlags::SYSTEM;
} else if name.starts_with("vbmeta") {
flags |= PartitionFlags::VBMETA;
}
if partition.estimate_cow_size.is_some() {
flags |= PartitionFlags::COW;
}
// Skip completely unrecognized partitions.
if flags.is_empty() {
continue;
}
// Skip unrecognized CoW partitions unless we ask for them.
if flags == PartitionFlags::COW && !required_flags.contains(RequiredFlags::ALL_COW) {
continue;
}
result.insert(name.clone(), flags);
}
pub fn is_system(name: &str) -> bool {
name == "system"
}
pub fn is_vbmeta(name: &str) -> bool {
name.starts_with("vbmeta")
}
pub fn iter(&self) -> impl Iterator<Item = &str> {
self.0.iter().map(|n| n.as_str())
}
pub fn iter_boot(&self) -> impl Iterator<Item = &str> {
self.iter().filter(|n| Self::is_boot(n))
}
pub fn iter_system(&self) -> impl Iterator<Item = &str> {
self.iter().filter(|n| Self::is_system(n))
}
pub fn iter_vbmeta(&self) -> impl Iterator<Item = &str> {
self.iter().filter(|n| Self::is_vbmeta(n))
}
result
}
#[derive(Clone, Copy, PartialEq, Eq)]
@@ -134,7 +152,7 @@ struct InputFile {
/// operating system).
fn open_input_files(
payload: &(dyn ReadSeekReopen + Sync),
required_images: &RequiredImages,
required_images: &HashMap<String, PartitionFlags>,
external_images: &HashMap<String, PathBuf>,
header: &PayloadHeader,
cancel_signal: &AtomicBool,
@@ -144,8 +162,8 @@ fn open_input_files(
// We always include replacement images that the user specifies, even if
// they don't need to be patched.
let all_images = required_images
.iter()
.chain(external_images.keys().map(|k| k.as_str()))
.keys()
.chain(external_images.keys())
.collect::<HashSet<_>>();
for name in all_images {
@@ -158,7 +176,7 @@ fn open_input_files(
.map(PSeekFile::new)
.with_context(|| format!("Failed to open external image: {path:?}"))?;
input_files.insert(
name.to_owned(),
name.clone(),
InputFile {
file,
state: InputFileState::External,
@@ -174,7 +192,7 @@ fn open_input_files(
payload::extract_image(payload, &file, header, name, cancel_signal)
.with_context(|| format!("Failed to extract from original payload: {name}"))?;
input_files.insert(
name.to_owned(),
name.clone(),
InputFile {
file,
state: InputFileState::Extracted,
@@ -190,15 +208,19 @@ fn open_input_files(
/// necessarily patched. Each patcher will determine which image it should
/// target. If the original image is signed, then it will be re-signed with
/// `key_avb`.
fn patch_boot_images<'a, 'b: 'a>(
required_images: &'b RequiredImages,
fn patch_boot_images(
required_images: &HashMap<String, PartitionFlags>,
input_files: &mut HashMap<String, InputFile>,
boot_patchers: &[Box<dyn BootImagePatch + Sync>],
key_avb: &RsaSigningKey,
cancel_signal: &AtomicBool,
) -> Result<()> {
let input_files = Mutex::new(input_files);
let boot_partitions = required_images.iter_boot().collect::<Vec<_>>();
let boot_partitions = required_images
.iter()
.filter(|(_, flags)| flags.contains(PartitionFlags::BOOT))
.map(|(name, _)| name.as_str())
.collect::<Vec<_>>();
info!(
"Candidate boot images: {}",
@@ -234,16 +256,23 @@ fn patch_boot_images<'a, 'b: 'a>(
/// Patch the single system image listed in `required_images` to replace the
/// `otacerts.zip` contents.
fn patch_system_image<'a, 'b: 'a>(
required_images: &'b RequiredImages,
fn patch_system_image<'a>(
required_images: &'a HashMap<String, PartitionFlags>,
input_files: &mut HashMap<String, InputFile>,
cert_ota: &Certificate,
key_avb: &RsaSigningKey,
cancel_signal: &AtomicBool,
) -> Result<(&'b str, Vec<Range<u64>>)> {
let Some(target) = required_images.iter_system().next() else {
) -> Result<(&'a str, Vec<Range<u64>>)> {
let mut system_iter = required_images
.iter()
.filter(|(_, flags)| flags.contains(PartitionFlags::SYSTEM))
.map(|(name, _)| name);
let Some(target) = system_iter.next() else {
bail!("No system partition found");
};
if system_iter.next().is_some() {
bail!("Multiple system partitions found");
};
let _span = debug_span!("image", name = target).entered();
@@ -309,12 +338,13 @@ fn load_vbmeta_images(
/// Check that all critical partitions within the payload are protected by a
/// vbmeta image in `vbmeta_headers`.
fn ensure_partitions_protected(
required_images: &RequiredImages,
required_images: &HashMap<String, PartitionFlags>,
vbmeta_headers: &HashMap<String, Header>,
) -> Result<()> {
let critical_partitions = required_images
.iter_boot()
.chain(required_images.iter_vbmeta())
.iter()
.filter(|(_, flags)| flags.intersects(PartitionFlags::BOOT | PartitionFlags::VBMETA))
.map(|(name, _)| name.as_str())
.collect::<BTreeSet<_>>();
// vbmeta partitions first.
@@ -546,6 +576,58 @@ fn update_metadata_descriptors(parent_header: &mut Header, child_header: &Header
}
}
/// Get the VABC algorithm from the payload header. This will fail if an
/// unsupported VABC algorithm is specified, but not if VABC is disabled.
fn get_vabc_algo(header: &PayloadHeader) -> Result<Option<VabcAlgo>> {
// Only CoW v2 seems to exist in the wild currently, so that is all we
// support.
let Some(dpm) = &header.manifest.dynamic_partition_metadata else {
return Ok(None);
};
if !dpm.vabc_enabled() {
return Ok(None);
}
let cow_version = dpm.cow_version();
if dpm.cow_version() != 2 {
bail!("Unsupported CoW version: {cow_version}");
}
let compression = dpm.vabc_compression_param();
let Ok(vabc_algo) = VabcAlgo::from_str(compression, false) else {
bail!("Unsupported VABC compression: {compression}");
};
Ok(Some(vabc_algo))
}
/// Set the VABC algorithm in the payload header and return whether it was
/// changed. This will fail if VABC was originally disabled. Returns whether the
/// new algorithm is different from the old algorithm.
fn set_vabc_algo(header: &mut PayloadHeader, vabc_algo: VabcAlgo) -> Result<bool> {
let Some(dpm) = &mut header.manifest.dynamic_partition_metadata else {
bail!("Dynamic partition metadata is missing");
};
if !dpm.vabc_enabled() {
bail!("Cannot change VABC algorithm when VABC is disabled");
}
let compression = dpm.vabc_compression_param();
let Ok(old_vabc_algo) = VabcAlgo::from_str(compression, false) else {
bail!("Unsupported VABC compression: {compression}");
};
if vabc_algo == old_vabc_algo {
return Ok(false);
}
dpm.vabc_compression_param = Some(vabc_algo.to_string());
Ok(true)
}
/// Update vbmeta headers.
///
/// * If [`Header::flags`] is non-zero, then an error is returned because the
@@ -566,6 +648,11 @@ fn update_vbmeta_headers(
key: &RsaSigningKey,
block_size: u64,
) -> Result<()> {
info!(
"Patching vbmeta images: {}",
joined(order.iter().map(|(n, _)| n)),
);
for (name, deps) in order {
let parent_header = headers.get_mut(name).unwrap();
let orig_parent_header = parent_header.clone();
@@ -644,6 +731,7 @@ pub fn compress_image(
.map(PSeekFile::new)
.with_context(|| format!("Failed to create temp file for: {name}"))?;
let vabc_algo = get_vabc_algo(header)?;
let block_size = header.manifest.block_size();
let partition = header
.manifest
@@ -652,6 +740,20 @@ pub fn compress_image(
.find(|p| p.partition_name == name)
.unwrap();
// If VABC is enabled, we need to update the CoW size estimate or else the
// CoW block device may run out of space during flashing.
let vabc_algo = if partition.estimate_cow_size.is_some() {
let Some(vabc_algo) = vabc_algo else {
bail!("Partition has CoW estimate, but VABC is disabled: {name}");
};
info!("Needs updated {vabc_algo} CoW size estimate: {name}");
Some(vabc_algo)
} else {
None
};
if let Some(r) = ranges {
info!("Compressing partial image: {name}: {r:?}");
@@ -665,7 +767,23 @@ pub fn compress_image(
cancel_signal,
) {
Ok(indices) => {
// The changes we make usually aren't any less compressible, but
// we'll still recompute the CoW size estimate to handle the
// case where the user requested a different algorithm.
if let Some(vabc_algo) = vabc_algo {
let cow_estimate = payload::compute_cow_estimate(
&*file,
name,
block_size,
vabc_algo,
cancel_signal,
)?;
partition.estimate_cow_size = Some(cow_estimate);
}
*file = writer;
return Ok(indices);
}
// If we can't take advantage of the optimization, we can still
@@ -679,37 +797,6 @@ pub fn compress_image(
info!("Compressing full image: {name}");
// Otherwise, compress the entire image. If VABC is enabled, we need to
// update the CoW size estimate or else the CoW block device may run out of
// space during flashing.
let vabc_algo = if partition.estimate_cow_size.is_some() {
info!("Needs updated CoW size estimate: {name}");
// Only CoW v2 seems to exist in the wild currently, so that is all we
// support.
let Some(dpm) = &header.manifest.dynamic_partition_metadata else {
bail!("Dynamic partition metadata is missing");
};
if !dpm.vabc_enabled() {
bail!("Partition has CoW estimate, but VABC is disabled: {name}");
}
let cow_version = dpm.cow_version();
if dpm.cow_version() != 2 {
bail!("Unsupported CoW version: {cow_version}");
}
let compression = dpm.vabc_compression_param();
let Some(vabc_algo) = VabcAlgo::new(compression) else {
bail!("Unsupported VABC compression: {compression}");
};
Some(vabc_algo)
} else {
None
};
let (partition_info, operations, cow_estimate) =
payload::compress_image(&*file, &writer, name, block_size, vabc_algo, cancel_signal)?;
@@ -723,6 +810,45 @@ pub fn compress_image(
Ok(vec![0..partition.operations.len()])
}
/// Recompute the CoW estimate for an image and update the OTA manifest
/// partition entry appropriately. The input file is not modified.
fn recow_image(
name: &str,
file: &mut PSeekFile,
header: &mut PayloadHeader,
cancel_signal: &AtomicBool,
) -> Result<()> {
let _span = debug_span!("image", name).entered();
file.rewind()?;
let vabc_algo = get_vabc_algo(header)?;
let block_size = header.manifest.block_size();
let partition = header
.manifest
.partitions
.iter_mut()
.find(|p| p.partition_name == name)
.unwrap();
if partition.estimate_cow_size.is_none() {
bail!("Partition has no original CoW estimate: {name}");
};
let Some(vabc_algo) = vabc_algo else {
bail!("Partition has CoW estimate, but VABC is disabled: {name}");
};
info!("Recomputing {vabc_algo} CoW size estimate: {name}");
let cow_estimate =
payload::compute_cow_estimate(&*file, name, block_size, vabc_algo, cancel_signal)?;
partition.estimate_cow_size = Some(cow_estimate);
Ok(())
}
#[allow(clippy::too_many_arguments)]
fn patch_ota_payload(
payload: &(dyn ReadSeekReopen + Sync),
@@ -731,6 +857,7 @@ fn patch_ota_payload(
boot_patchers: &[Box<dyn BootImagePatch + Sync>],
skip_system_ota_cert: bool,
clear_vbmeta_flags: bool,
vabc_algo_override: Option<VabcAlgo>,
key_avb: &RsaSigningKey,
key_ota: &RsaSigningKey,
cert_ota: &Certificate,
@@ -742,6 +869,16 @@ fn patch_ota_payload(
bail!("Payload is a delta OTA, not a full OTA");
}
let mut required_flags = RequiredFlags::empty();
if !skip_system_ota_cert {
required_flags |= RequiredFlags::SYSTEM;
}
if let Some(vabc_algo) = vabc_algo_override {
if set_vabc_algo(&mut header, vabc_algo)? {
required_flags |= RequiredFlags::ALL_COW;
}
}
let all_partitions = header
.manifest
.partitions
@@ -757,11 +894,17 @@ fn patch_ota_payload(
}
}
// Determine what images need to be patched. For simplicity, we pre-read all
// vbmeta images since they're tiny. They're discarded later if the they
// don't need to be modified.
let required_images = RequiredImages::new(&header.manifest);
let vbmeta_images = required_images.iter_vbmeta().collect::<HashSet<_>>();
let required_images = get_required_images(&header.manifest, required_flags);
let vbmeta_images = required_images
.iter()
.filter(|(_, flags)| flags.contains(PartitionFlags::VBMETA))
.map(|(name, _)| name.as_str())
.collect::<HashSet<_>>();
let cow_images = required_images
.iter()
.filter(|(_, flags)| flags.contains(PartitionFlags::COW))
.map(|(name, _)| name.as_str())
.collect::<HashSet<_>>();
// The set of source images to be inserted into the new payload, replacing
// what was in the original payload. Initially, this refers to either user
@@ -784,9 +927,6 @@ fn patch_ota_payload(
cancel_signal,
)?;
input_files
.retain(|n, f| !(f.state == InputFileState::Extracted && RequiredImages::is_boot(n)));
let system_result = if skip_system_ota_cert {
None
} else {
@@ -799,20 +939,12 @@ fn patch_ota_payload(
)?)
};
input_files
.retain(|n, f| !(f.state == InputFileState::Extracted && RequiredImages::is_system(n)));
let mut vbmeta_headers = load_vbmeta_images(&mut input_files, &vbmeta_images)?;
ensure_partitions_protected(&required_images, &vbmeta_headers)?;
let mut vbmeta_order = get_vbmeta_patch_order(&input_files, &vbmeta_headers)?;
info!(
"Patching vbmeta images: {}",
joined(vbmeta_order.iter().map(|(n, _)| n)),
);
update_vbmeta_headers(
&mut input_files,
&mut vbmeta_headers,
@@ -822,7 +954,19 @@ fn patch_ota_payload(
header.manifest.block_size().into(),
)?;
// Unmodified vbmeta images no longer need to be kept around either.
// Recompute CoW estimates for partitions we don't modify.
input_files
.iter_mut()
.filter(|(name, f)| {
f.state == InputFileState::Extracted && cow_images.contains(name.as_str())
})
.try_for_each(|(name, input_file)| {
recow_image(name, &mut input_file.file, &mut header, cancel_signal)
})?;
// Drop all unmodified images. We only want to compress modified images.
// For recowed images, the payload header was already updated with the new
// estimate. The actual data can be copied from the original payload.
input_files.retain(|_, f| f.state != InputFileState::Extracted);
let mut compressed_files = input_files
@@ -933,6 +1077,7 @@ fn patch_ota_zip(
boot_patchers: &[Box<dyn BootImagePatch + Sync>],
skip_system_ota_cert: bool,
clear_vbmeta_flags: bool,
vabc_algo_override: Option<VabcAlgo>,
zip_mode: ZipMode,
key_avb: &RsaSigningKey,
key_ota: &RsaSigningKey,
@@ -1056,6 +1201,7 @@ fn patch_ota_zip(
boot_patchers,
skip_system_ota_cert,
clear_vbmeta_flags,
vabc_algo_override,
key_avb,
key_ota,
cert_ota,
@@ -1201,7 +1347,7 @@ fn verify_partition_hashes(
let mut writer = HashingWriter::new(
io::sink(),
aws_lc_rs::digest::Context::new(&aws_lc_rs::digest::SHA256),
ring::digest::Context::new(&ring::digest::SHA256),
);
stream::copy(file, &mut writer, cancel_signal)?;
@@ -1371,6 +1517,7 @@ pub fn patch_subcommand(cli: &PatchCli, cancel_signal: &AtomicBool) -> Result<()
&boot_patchers,
cli.skip_system_ota_cert,
cli.clear_vbmeta_flags,
cli.vabc_algo,
cli.zip_mode,
&key_avb,
&key_ota,
@@ -1492,22 +1639,20 @@ pub fn extract_subcommand(cli: &ExtractCli, cancel_signal: &AtomicBool) -> Resul
unique_images.extend(cli.extract.partition.iter().cloned());
} else if !cli.extract.none {
let images = RequiredImages::new(&header.manifest);
let images = get_required_images(&header.manifest, RequiredFlags::SYSTEM)
.into_iter()
.filter(|(_, flags)| !cli.extract.boot_only || flags.contains(PartitionFlags::BOOT))
.map(|(name, _)| name);
if cli.extract.boot_only {
unique_images.extend(images.iter_boot().map(|n| n.to_owned()));
} else {
unique_images.extend(images.iter().map(|n| n.to_owned()));
}
unique_images.extend(images);
}
if let Some(path) = &cli.cert_ota {
info!("Extracting embedded OTA certificate from zip signature");
let ota_sig = ota::parse_ota_sig(&mut raw_reader)?;
let embedded_cert = ota_sig.embedded_cert()?;
crypto::write_pem_cert_file(path, embedded_cert)
crypto::write_pem_cert_file(path, &ota_sig.cert)
.with_context(|| format!("Failed to write OTA certificate: {path:?}"))?;
}
@@ -1690,6 +1835,15 @@ pub fn extract_subcommand(cli: &ExtractCli, cancel_signal: &AtomicBool) -> Resul
}
pub fn verify_subcommand(cli: &VerifyCli, cancel_signal: &AtomicBool) -> Result<()> {
let mut errors = 0;
macro_rules! fail_later {
($($arg:tt)+) => {
error!($($arg)+);
errors += 1;
};
}
let raw_reader = File::open(&cli.input)
.map(PSeekFile::new)
.with_context(|| format!("Failed to open for reading: {:?}", cli.input))?;
@@ -1697,27 +1851,38 @@ pub fn verify_subcommand(cli: &VerifyCli, cancel_signal: &AtomicBool) -> Result<
info!("Verifying whole-file signature");
let embedded_cert = ota::verify_ota(&mut reader, cancel_signal)?;
let ota_sig = ota::parse_ota_sig(&mut reader).context("Failed to parse OTA signature")?;
let (metadata, ota_cert, header, properties) = ota::parse_zip_ota_info(&mut reader)?;
if embedded_cert != ota_cert {
bail!(
"CMS embedded certificate does not match {}",
if let Err(e) = ota_sig
.verify_ota(&mut reader, cancel_signal)
.context("Failed to verify OTA against embedded certificate")
{
fail_later!("{e:?}");
}
let (metadata, ota_cert, header, properties) =
ota::parse_zip_ota_info(&mut reader).context("Failed to parse OTA metadata")?;
if ota_cert != ota_sig.cert {
fail_later!(
"{} does not match CMS embedded certificate",
ota::PATH_OTACERT,
);
} else if let Some(p) = &cli.cert_ota {
let verify_cert = crypto::read_pem_cert_file(p)
.with_context(|| format!("Failed to load certificate: {p:?}"))?;
if embedded_cert != verify_cert {
bail!("OTA has a valid signature, but was not signed with: {p:?}");
if ota_sig.cert != verify_cert {
fail_later!("OTA has a valid signature, but was not signed with: {p:?}");
}
} else {
warn!("Whole-file signature is valid, but its trust is unknown");
}
ota::verify_metadata(&mut reader, &metadata, header.blob_offset)
.context("Failed to verify OTA metadata offsets")?;
if let Err(e) = ota::verify_metadata(&mut reader, &metadata, header.blob_offset)
.context("Failed to verify OTA metadata offsets")
{
fail_later!("{e:?}");
}
info!("Verifying payload");
@@ -1735,7 +1900,12 @@ pub fn verify_subcommand(cli: &VerifyCli, cancel_signal: &AtomicBool) -> Result<
let section_reader = SectionReader::new(&mut reader, pf_payload.offset, pf_payload.size)
.context("Failed to directly open payload section")?;
payload::verify_payload(section_reader, &ota_cert, &properties, cancel_signal)?;
if let Err(e) =
payload::verify_payload(section_reader, &ota_sig.cert, &properties, cancel_signal)
.context("Failed to verify payload signatures and digests")
{
fail_later!("{e:?}");
}
info!("Extracting partition images to temporary directory");
@@ -1762,40 +1932,8 @@ pub fn verify_subcommand(cli: &VerifyCli, cancel_signal: &AtomicBool) -> Result<
info!("Verifying partition hashes");
verify_partition_hashes(&temp_dir, &header, &unique_images, cancel_signal)?;
if cli.skip_recovery_ota_cert {
warn!("Not verifying recovery ramdisk's otacerts.zip");
} else {
info!("Checking recovery ramdisk's otacerts.zip");
let required_images = RequiredImages::new(&header.manifest);
let boot_images =
boot::load_boot_images(&required_images.iter_boot().collect::<Vec<_>>(), |name| {
Ok(Box::new(
temp_dir
.open(format!("{name}.img"))
.map(|f| PSeekFile::new(f.into_std()))?,
))
})
.context("Failed to load all boot images")?;
let targets = OtaCertPatcher::new(ota_cert.clone())
.find_targets(&boot_images, cancel_signal)
.context("Failed to find boot image containing otacerts.zip")?;
if targets.is_empty() {
bail!("No boot image contains otacerts.zip");
}
for target in targets {
let boot_image = &boot_images[target].boot_image;
let ramdisk_certs = OtaCertPatcher::get_certificates(boot_image, cancel_signal)
.context("Failed to read {target}'s otacerts.zip")?;
if !ramdisk_certs.contains(&ota_cert) {
bail!("{target}'s otacerts.zip does not contain OTA certificate");
}
}
if let Err(e) = verify_partition_hashes(&temp_dir, &header, &unique_images, cancel_signal) {
fail_later!("{e:?}");
}
info!("Verifying AVB signatures");
@@ -1813,16 +1951,77 @@ pub fn verify_subcommand(cli: &VerifyCli, cancel_signal: &AtomicBool) -> Result<
let mut seen = HashSet::<String>::new();
let mut descriptors = HashMap::<String, Descriptor>::new();
cli::avb::verify_headers(
if let Err(e) = cli::avb::verify_headers(
&temp_dir,
"vbmeta",
public_key.as_ref(),
&mut seen,
&mut descriptors,
)?;
cli::avb::verify_descriptors(&temp_dir, &descriptors, false, cancel_signal)?;
)
.context("Failed to verify AVB signatures")
{
fail_later!("{e:?}");
}
info!("Signatures are all valid!");
if let Err(e) = cli::avb::verify_descriptors(&temp_dir, &descriptors, false, cancel_signal)
.context("Failed to verify images against AVB descriptors")
{
fail_later!("{e:?}");
}
info!("Checking recovery ramdisk's otacerts.zip");
let required_images = get_required_images(&header.manifest, RequiredFlags::empty());
let boot_image_names = required_images
.iter()
.filter(|(_, flags)| flags.contains(PartitionFlags::BOOT))
.map(|(name, _)| name.as_str())
.collect::<Vec<_>>();
let boot_images = boot::load_boot_images(&boot_image_names, |name| {
Ok(Box::new(
temp_dir
.open(format!("{name}.img"))
.map(|f| PSeekFile::new(f.into_std()))?,
))
})
.context("Failed to load all boot images")?;
let targets = OtaCertPatcher::new(ota_cert.clone())
.find_targets(&boot_images, cancel_signal)
.context("Failed to find boot image containing otacerts.zip")?;
if targets.is_empty() {
let msg = "No boot image contains otacerts.zip";
if cli.skip_recovery_ota_cert {
warn!("{msg}");
} else {
fail_later!("{msg}");
}
}
for target in targets {
let boot_image = &boot_images[target].boot_image;
let ramdisk_certs = OtaCertPatcher::get_certificates(boot_image, cancel_signal)
.with_context(|| format!("Failed to read {target}'s otacerts.zip"))?;
if !ramdisk_certs.contains(&ota_cert) {
let msg = format!(
"{target}'s otacerts.zip does not contain the certificate that signed the OTA"
);
if cli.skip_recovery_ota_cert {
warn!("{msg}");
} else {
fail_later!("{msg}");
}
}
}
if errors == 0 {
info!("OK!");
} else {
bail!("Encountered {errors} error(s) during verification");
}
Ok(())
}
@@ -2031,6 +2230,18 @@ pub struct PatchCli {
#[arg(long, help_heading = HEADING_OTHER)]
pub clear_vbmeta_flags: bool,
/// Override the virtual A/B CoW compression algorithm.
///
/// This will slow down the patching process because every dynamic partition
/// needs to be extracted to recompute the CoW size estimate. However, if a
/// faster algorithm is chosen, then OTA installation using an OTA updater
/// app will be faster. This does not affect sideloading from recovery mode.
///
/// Note that selecting a newer algorithm will prevent upgrading from older
/// Android versions before support for the algorithm was introduced.
#[arg(long, value_name = "ALGO", help_heading = HEADING_OTHER)]
pub vabc_algo: Option<VabcAlgo>,
/// Zip creation mode for the output OTA zip.
///
/// The streaming mode produces zip files that contain data descriptors.
+2 -2
View File
@@ -122,13 +122,13 @@ fn find_allocated_regions(
loop {
stream::check_cancel(cancel_signal)?;
start = match rustix::fs::seek(reader, SeekFrom::Data(end as i64)) {
start = match rustix::fs::seek(reader, SeekFrom::Data(end)) {
Ok(offset) => offset,
Err(e) if e == Errno::NXIO => break,
Err(e) => return Err(e).with_context(|| format!("Failed to seek to data: {path:?}")),
};
end = rustix::fs::seek(reader, SeekFrom::Hole(start as i64))
end = rustix::fs::seek(reader, SeekFrom::Hole(start))
.with_context(|| format!("Failed to seek to hole: {path:?}"))?;
result.push(start..end);
+15 -16
View File
@@ -10,10 +10,10 @@ use std::{
sync::atomic::AtomicBool,
};
use aws_lc_rs::digest::{Algorithm, Context};
use bstr::ByteSlice;
use num_bigint_dig::{ModInverse, ToBigInt};
use num_traits::{Pow, ToPrimitive};
use ring::digest::{Algorithm, Context};
use rsa::{traits::PublicKeyParts, BigUint, RsaPublicKey};
use serde::{Deserialize, Serialize};
use thiserror::Error;
@@ -55,7 +55,7 @@ pub const HEADER_MAX_SIZE: u64 = 64 * 1024;
///
/// ```rust
/// use avbroot::format::hashtree::HashTree;
/// let size = HashTree::new(4096, &aws_lc_rs::digest::SHA256, b"")
/// let size = HashTree::new(4096, &ring::digest::SHA256, b"")
/// .compute_level_offsets(8 * 1024 * 1024 * 1024)
/// .unwrap()
/// .first()
@@ -165,9 +165,9 @@ type Result<T> = std::result::Result<T, Error>;
pub(crate) fn digest_algorithm(name: &str, for_verify: bool) -> Result<&'static Algorithm> {
match name {
"sha1" if for_verify => Ok(&aws_lc_rs::digest::SHA1_FOR_LEGACY_USE_ONLY),
"sha256" => Ok(&aws_lc_rs::digest::SHA256),
"sha512" => Ok(&aws_lc_rs::digest::SHA512),
"sha1" if for_verify => Ok(&ring::digest::SHA1_FOR_LEGACY_USE_ONLY),
"sha256" => Ok(&ring::digest::SHA256),
"sha512" => Ok(&ring::digest::SHA512),
a => Err(Error::UnsupportedHashAlgorithm(a.to_owned())),
}
}
@@ -292,7 +292,7 @@ trait DescriptorTag {
/// Raw on-disk layout for the AVB property descriptor after the prefix.
#[derive(Clone, Copy, FromBytes, IntoBytes, KnownLayout, Immutable, Unaligned)]
#[repr(packed)]
#[repr(C, packed)]
struct RawPropertyDescriptor {
key_size: big_endian::U64,
value_size: big_endian::U64,
@@ -395,7 +395,7 @@ impl<W: Write> ToWriter<W> for PropertyDescriptor {
/// Raw on-disk layout for the AVB hash tree descriptor after the prefix.
#[derive(Clone, Copy, FromBytes, IntoBytes, KnownLayout, Immutable, Unaligned)]
#[repr(packed)]
#[repr(C, packed)]
struct RawHashTreeDescriptor {
dm_verity_version: big_endian::U32,
image_size: big_endian::U64,
@@ -860,7 +860,7 @@ impl<W: Write> ToWriter<W> for HashTreeDescriptor {
/// Raw on-disk layout for the AVB hash descriptor after the prefix.
#[derive(Clone, Copy, FromBytes, IntoBytes, KnownLayout, Immutable, Unaligned)]
#[repr(packed)]
#[repr(C, packed)]
struct RawHashDescriptor {
image_size: big_endian::U64,
hash_algorithm: [u8; 32],
@@ -905,7 +905,7 @@ impl HashDescriptor {
reader: impl Read,
for_verify: bool,
cancel_signal: &AtomicBool,
) -> Result<aws_lc_rs::digest::Digest> {
) -> Result<ring::digest::Digest> {
let algorithm = digest_algorithm(&self.hash_algorithm, for_verify)?;
let mut context = Context::new(algorithm);
context.update(&self.salt);
@@ -1065,7 +1065,7 @@ impl<W: Write> ToWriter<W> for HashDescriptor {
/// Raw on-disk layout for the AVB kernel command line descriptor after the
/// prefix.
#[derive(Clone, Copy, FromBytes, IntoBytes, KnownLayout, Immutable, Unaligned)]
#[repr(packed)]
#[repr(C, packed)]
struct RawKernelCmdlineDescriptor {
flags: big_endian::U32,
cmdline_len: big_endian::U32,
@@ -1139,7 +1139,7 @@ impl<W: Write> ToWriter<W> for KernelCmdlineDescriptor {
/// Raw on-disk layout for the AVB chain partition descriptor after the prefix.
#[derive(Clone, Copy, FromBytes, IntoBytes, KnownLayout, Immutable, Unaligned)]
#[repr(packed)]
#[repr(C, packed)]
struct RawChainPartitionDescriptor {
rollback_index_location: big_endian::U32,
partition_name_len: big_endian::U32,
@@ -1258,7 +1258,7 @@ impl<W: Write> ToWriter<W> for ChainPartitionDescriptor {
/// Raw on-disk layout for the AVB descriptor prefix.
#[derive(Clone, Copy, FromBytes, IntoBytes, KnownLayout, Immutable, Unaligned)]
#[repr(packed)]
#[repr(C, packed)]
struct RawDescriptor {
tag: big_endian::U64,
num_bytes_following: big_endian::U64,
@@ -1458,7 +1458,7 @@ impl<'a> TryFrom<&'a mut Descriptor> for AppendedDescriptorMut<'a> {
/// Raw on-disk layout for the AVB header.
#[derive(Clone, Copy, FromBytes, IntoBytes, KnownLayout, Immutable, Unaligned)]
#[repr(packed)]
#[repr(C, packed)]
struct RawHeader {
/// Magic value. This should be equal to [`HEADER_MAGIC`].
magic: [u8; 4],
@@ -1927,7 +1927,7 @@ impl<W: Write> ToWriter<W> for Header {
/// Raw on-disk layout for the AVB footer.
#[derive(Clone, Copy, FromBytes, IntoBytes, KnownLayout, Immutable, Unaligned)]
#[repr(packed)]
#[repr(C, packed)]
struct RawFooter {
/// Magic value. This should be equal to [`FOOTER_MAGIC`].
magic: [u8; 4],
@@ -2015,10 +2015,9 @@ impl<W: Write> ToWriter<W> for Footer {
/// Raw on-disk layout for the AVB binary public key header.
#[derive(Clone, Copy, FromBytes, IntoBytes, KnownLayout, Immutable, Unaligned)]
#[repr(packed)]
#[repr(C, packed)]
struct RawPublicKey {
key_num_bits: big_endian::U32,
#[expect(unused)]
n0inv: big_endian::U32,
}
+10 -10
View File
@@ -8,9 +8,9 @@ use std::{
str::{self, Utf8Error},
};
use aws_lc_rs::digest::Context;
use bstr::ByteSlice;
use num_traits::ToPrimitive;
use ring::digest::Context;
use serde::{Deserialize, Serialize};
use thiserror::Error;
use zerocopy::{little_endian, FromBytes, IntoBytes};
@@ -117,7 +117,7 @@ pub trait BootImageExt {
/// Raw on-disk layout for the v0 image header.
#[derive(Clone, Copy, FromBytes, IntoBytes, KnownLayout, Immutable, Unaligned)]
#[repr(packed)]
#[repr(C, packed)]
struct RawV0 {
/// Magic value. This should be equal to [`BOOT_MAGIC`].
magic: [u8; 8],
@@ -139,7 +139,7 @@ struct RawV0 {
/// Raw on-disk layout for the extra v1 image header fields.
#[derive(Clone, Copy, FromBytes, IntoBytes, KnownLayout, Immutable, Unaligned)]
#[repr(packed)]
#[repr(C, packed)]
struct RawV1Extra {
recovery_dtbo_size: little_endian::U32,
recovery_dtbo_offset: little_endian::U64,
@@ -148,7 +148,7 @@ struct RawV1Extra {
/// Raw on-disk layout for the extra v2 image header fields.
#[derive(Clone, Copy, FromBytes, IntoBytes, KnownLayout, Immutable, Unaligned)]
#[repr(packed)]
#[repr(C, packed)]
struct RawV2Extra {
dtb_size: little_endian::U32,
dtb_addr: little_endian::U64,
@@ -595,7 +595,7 @@ impl<W: Write> ToWriter<W> for BootImageV0Through2 {
/// Raw on-disk layout for the v3 image header.
#[derive(Clone, Copy, FromBytes, IntoBytes, KnownLayout, Immutable, Unaligned)]
#[repr(packed)]
#[repr(C, packed)]
struct RawV3 {
/// Magic value. This should be equal to [`BOOT_MAGIC`].
magic: [u8; 8],
@@ -610,7 +610,7 @@ struct RawV3 {
/// Raw on-disk layout for the extra v4 image header fields.
#[derive(Clone, Copy, FromBytes, IntoBytes, KnownLayout, Immutable, Unaligned)]
#[repr(packed)]
#[repr(C, packed)]
struct RawV4Extra {
signature_size: little_endian::U32,
}
@@ -890,7 +890,7 @@ impl BootImageV3Through4 {
/// image was successfully signed. Returns false if there's no vbmeta
/// structure to sign in [`V4Extra::signature`].
pub fn sign(&mut self, key: &RsaSigningKey) -> Result<bool> {
let mut context = Context::new(&aws_lc_rs::digest::SHA256);
let mut context = Context::new(&ring::digest::SHA256);
let image_size;
if let Some(v4) = &self.v4_extra {
@@ -961,7 +961,7 @@ impl<W: Write> ToWriter<W> for BootImageV3Through4 {
/// Raw on-disk layout for the vendor v3 image header.
#[derive(Clone, Copy, FromBytes, IntoBytes, KnownLayout, Immutable, Unaligned)]
#[repr(packed)]
#[repr(C, packed)]
struct RawVendorV3 {
/// Magic value. This should be equal to [`VENDOR_BOOT_MAGIC`].
magic: [u8; 8],
@@ -980,7 +980,7 @@ struct RawVendorV3 {
/// Raw on-disk layout for the extra vendor v4 image header fields.
#[derive(Clone, Copy, FromBytes, IntoBytes, KnownLayout, Immutable, Unaligned)]
#[repr(packed)]
#[repr(C, packed)]
struct RawVendorV4Extra {
vendor_ramdisk_table_size: little_endian::U32,
vendor_ramdisk_table_entry_num: little_endian::U32,
@@ -990,7 +990,7 @@ struct RawVendorV4Extra {
/// Raw on-disk layout for the vendor v4 ramdisk table entry.
#[derive(Clone, Copy, FromBytes, IntoBytes, KnownLayout, Immutable, Unaligned)]
#[repr(packed)]
#[repr(C, packed)]
struct RawVendorV4RamdiskTableEntry {
ramdisk_size: little_endian::U32,
ramdisk_offset: little_endian::U32,
+2 -2
View File
@@ -76,7 +76,7 @@ pub struct InvalidHexCharError(RawHexU32, char);
/// ASCII-encoded hex integer value used in cpio header fields.
#[derive(Clone, Copy, FromBytes, IntoBytes, KnownLayout, Immutable, Unaligned)]
#[repr(packed)]
#[repr(C, packed)]
struct RawHexU32([u8; 8]);
impl fmt::Debug for RawHexU32 {
@@ -121,7 +121,7 @@ impl TryFrom<RawHexU32> for u32 {
/// Raw on-disk layout for the cpio header.
#[derive(Clone, Copy, FromBytes, IntoBytes, KnownLayout, Immutable, Unaligned)]
#[repr(packed)]
#[repr(C, packed)]
struct RawHeader {
/// Magic value. This should be equal to [`MAGIC_NEW`] or [`MAGIC_NEW_CRC`].
magic: [u8; 6],
+5 -5
View File
@@ -592,7 +592,7 @@ impl Fec {
/// Raw on-disk layout for the FEC image header.
#[derive(Clone, Copy, FromBytes, IntoBytes, KnownLayout, Immutable, Unaligned)]
#[repr(packed)]
#[repr(C, packed)]
struct RawHeader {
/// Magic value. This should be equal to [`FEC_MAGIC`].
magic: little_endian::U32,
@@ -707,7 +707,7 @@ impl FecImage {
let fec_size: u32 =
util::try_cast(self.fec.len()).map_err(|e| Error::IntOutOfBounds("fec_size", e))?;
let digest = aws_lc_rs::digest::digest(&aws_lc_rs::digest::SHA256, &self.fec);
let digest = ring::digest::digest(&ring::digest::SHA256, &self.fec);
let header = RawHeader {
magic: FEC_MAGIC.into(),
@@ -781,7 +781,7 @@ impl<R: Read> FromReader<R> for FecImage {
let data_size = header.data_size.get();
let actual_digest = aws_lc_rs::digest::digest(&aws_lc_rs::digest::SHA256, &fec[..fec_size]);
let actual_digest = ring::digest::digest(&ring::digest::SHA256, &fec[..fec_size]);
if header.digest != actual_digest.as_ref() {
return Err(Error::InvalidFecDigest {
expected: hex::encode(header.digest),
@@ -886,7 +886,7 @@ mod tests {
let mut buf = vec![0u8; size];
rand::thread_rng().fill_bytes(&mut buf);
file.write_all(&buf).unwrap();
aws_lc_rs::digest::digest(&aws_lc_rs::digest::SHA256, &buf)
ring::digest::digest(&ring::digest::SHA256, &buf)
};
let fec = Fec::new(size as u64, block_size, parity).unwrap();
@@ -921,7 +921,7 @@ mod tests {
let mut buf = Vec::new();
file.rewind().unwrap();
file.read_to_end(&mut buf).unwrap();
aws_lc_rs::digest::digest(&aws_lc_rs::digest::SHA256, &buf)
ring::digest::digest(&ring::digest::SHA256, &buf)
};
assert_eq!(repaired_digest.as_ref(), orig_digest.as_ref());
+7 -7
View File
@@ -9,12 +9,12 @@ use std::{
sync::atomic::AtomicBool,
};
use aws_lc_rs::digest::{Algorithm, Context};
use bstr::ByteSlice;
use rayon::{
iter::{IndexedParallelIterator, ParallelIterator},
slice::ParallelSliceMut,
};
use ring::digest::{Algorithm, Context};
use thiserror::Error;
use zerocopy::{little_endian, FromBytes, IntoBytes};
use zerocopy_derive::{FromBytes, Immutable, IntoBytes, KnownLayout, Unaligned};
@@ -416,8 +416,8 @@ impl HashTree {
if hash_tree_data != actual_hash_tree_data {
// These are multiple megabytes, so only report the hashes.
let algorithm = self.salted_context.algorithm();
let expected = aws_lc_rs::digest::digest(algorithm, hash_tree_data);
let actual = aws_lc_rs::digest::digest(algorithm, &actual_hash_tree_data);
let expected = ring::digest::digest(algorithm, hash_tree_data);
let actual = ring::digest::digest(algorithm, &actual_hash_tree_data);
return Err(Error::InvalidHashTree {
expected: hex::encode(expected),
@@ -431,7 +431,7 @@ impl HashTree {
/// Raw on-disk layout for our custom hash tree image header.
#[derive(Clone, Copy, FromBytes, IntoBytes, KnownLayout, Immutable, Unaligned)]
#[repr(packed)]
#[repr(C, packed)]
struct RawHeader {
/// Magic value. This should be equal to [`HashTreeImage::MAGIC`].
magic: [u8; 16],
@@ -662,7 +662,7 @@ mod tests {
#[test]
fn calculate_level_ranges() {
let hash_tree = HashTree::new(4096, &aws_lc_rs::digest::SHA256, &[]);
let hash_tree = HashTree::new(4096, &ring::digest::SHA256, &[]);
assert_eq!(
hash_tree.compute_level_offsets(0).unwrap(),
&[] as &[Range<usize>],
@@ -675,7 +675,7 @@ mod tests {
#[test]
fn blocks_for_ranges() {
let hash_tree = HashTree::new(4096, &aws_lc_rs::digest::SHA256, b"Salt");
let hash_tree = HashTree::new(4096, &ring::digest::SHA256, b"Salt");
assert_eq!(
hash_tree.blocks_for_ranges(16384, &[0..16384]).unwrap(),
&[0..4],
@@ -696,7 +696,7 @@ mod tests {
#[test]
fn generate_update_verify() {
let cancel_signal = AtomicBool::new(false);
let hash_tree = HashTree::new(64, &aws_lc_rs::digest::SHA256, b"Salt");
let hash_tree = HashTree::new(64, &ring::digest::SHA256, b"Salt");
let mut input = SharedCursor::new();
// Try input smaller than one block.
+16 -16
View File
@@ -250,7 +250,7 @@ impl PartitionAttributes {
/// Raw on-disk layout for the metadata geometry.
#[derive(Clone, Copy, FromBytes, IntoBytes, KnownLayout, Immutable, Unaligned)]
#[repr(packed)]
#[repr(C, packed)]
struct RawGeometry {
/// Magic value. This should be equal to [`GEOMETRY_MAGIC`].
magic: little_endian::U32,
@@ -301,7 +301,7 @@ impl RawGeometry {
let mut copy = *self;
copy.checksum.fill(0);
let digest = aws_lc_rs::digest::digest(&aws_lc_rs::digest::SHA256, copy.as_bytes());
let digest = ring::digest::digest(&ring::digest::SHA256, copy.as_bytes());
if digest.as_ref() != self.checksum {
return Err(Error::GeometryInvalidDigest {
expected: hex::encode(self.checksum),
@@ -332,7 +332,7 @@ impl RawGeometry {
/// Raw on-disk layout for a table descriptor within a [`RawHeader`].
#[derive(Clone, Copy, FromBytes, IntoBytes, KnownLayout, Immutable, Unaligned)]
#[repr(packed)]
#[repr(C, packed)]
struct RawTableDescriptor {
/// Offset relative to the end of the [`RawHeader`].
offset: little_endian::U32,
@@ -391,7 +391,7 @@ impl RawTableDescriptor {
/// Raw on-disk layout for the metadata header.
#[derive(Clone, Copy, FromBytes, IntoBytes, KnownLayout, Immutable, Unaligned)]
#[repr(packed)]
#[repr(C, packed)]
struct RawHeader {
/// Magic value. This should be equal to [`HEADER_MAGIC`].
magic: little_endian::U32,
@@ -517,7 +517,7 @@ impl RawHeader {
let portion = &copy.as_bytes()[..expected_size];
let digest = aws_lc_rs::digest::digest(&aws_lc_rs::digest::SHA256, portion);
let digest = ring::digest::digest(&ring::digest::SHA256, portion);
if digest.as_ref() != self.header_checksum {
return Err(Error::HeaderInvalidDigest {
expected: hex::encode(self.header_checksum),
@@ -569,7 +569,7 @@ impl RawHeader {
/// A potentially invalid raw partition name string.
#[derive(Clone, Copy, FromBytes, IntoBytes, KnownLayout, Immutable, Unaligned)]
#[repr(packed)]
#[repr(C, packed)]
struct PartitionName([u8; 36]);
impl fmt::Debug for PartitionName {
@@ -639,7 +639,7 @@ impl FromStr for PartitionName {
/// Raw on-disk layout for an entry in the logical partitions table.
#[derive(Clone, Copy, FromBytes, IntoBytes, KnownLayout, Immutable, Unaligned)]
#[repr(packed)]
#[repr(C, packed)]
struct RawPartition {
/// Partition name in ASCII. This must be unique across all partitions.
name: PartitionName,
@@ -701,7 +701,7 @@ impl RawPartition {
.first_extent_index
.get()
.checked_add(self.num_extents.get())
.map_or(true, |n| n as usize > extents.len())
.is_none_or(|n| n as usize > extents.len())
{
return Err(Error::PartitionExtentIndicesTooLarge {
name: DebugString::new(self.name),
@@ -730,7 +730,7 @@ impl RawPartition {
/// Raw on-disk layout for an entry in the extent table.
#[derive(Clone, Copy, FromBytes, IntoBytes, KnownLayout, Immutable, Unaligned)]
#[repr(packed)]
#[repr(C, packed)]
struct RawExtent {
/// Number of [`SECTOR_SIZE`]-byte sectors in this extent.
num_sectors: little_endian::U64,
@@ -822,7 +822,7 @@ impl RawExtent {
/// Raw on-disk layout for an entry in the partition groups table.
#[derive(Clone, Copy, FromBytes, IntoBytes, KnownLayout, Immutable, Unaligned)]
#[repr(packed)]
#[repr(C, packed)]
struct RawPartitionGroup {
/// Partition group name in ASCII. This must be unique across all groups.
name: PartitionName,
@@ -889,7 +889,7 @@ impl RawPartitionGroup {
/// Raw on-disk layout for an entry in the block devices table.
#[derive(Clone, Copy, FromBytes, IntoBytes, KnownLayout, Immutable, Unaligned)]
#[repr(packed)]
#[repr(C, packed)]
struct RawBlockDevice {
/// The first [`SECTOR_SIZE`]-byte sector where actual data for the logical
/// partitions can be allocated.
@@ -1179,7 +1179,7 @@ impl RawMetadata {
for slot in &self.slots {
#[cfg(not(fuzzing))]
{
let mut context = aws_lc_rs::digest::Context::new(&aws_lc_rs::digest::SHA256);
let mut context = ring::digest::Context::new(&ring::digest::SHA256);
context.update(slot.partitions.as_bytes());
context.update(slot.extents.as_bytes());
context.update(slot.groups.as_bytes());
@@ -1849,7 +1849,7 @@ impl TryFrom<&MetadataSlot> for RawMetadataSlot {
raw_slot.header.tables_size = offset.into();
let tables_digest = {
let mut context = aws_lc_rs::digest::Context::new(&aws_lc_rs::digest::SHA256);
let mut context = ring::digest::Context::new(&ring::digest::SHA256);
context.update(raw_slot.partitions.as_bytes());
context.update(raw_slot.extents.as_bytes());
context.update(raw_slot.groups.as_bytes());
@@ -1861,8 +1861,8 @@ impl TryFrom<&MetadataSlot> for RawMetadataSlot {
.tables_checksum
.copy_from_slice(tables_digest.as_ref());
let header_digest = aws_lc_rs::digest::digest(
&aws_lc_rs::digest::SHA256,
let header_digest = ring::digest::digest(
&ring::digest::SHA256,
&raw_slot.header.as_bytes()[..header_size],
);
raw_slot
@@ -1951,7 +1951,7 @@ impl TryFrom<&Metadata> for RawMetadata {
};
let geometry_digest =
aws_lc_rs::digest::digest(&aws_lc_rs::digest::SHA256, raw_metadata.geometry.as_bytes());
ring::digest::digest(&ring::digest::SHA256, raw_metadata.geometry.as_bytes());
raw_metadata
.geometry
.checksum
+112 -78
View File
@@ -10,12 +10,12 @@ use std::{
sync::atomic::AtomicBool,
};
use aws_lc_rs::digest::Context;
use clap::ValueEnum;
use cms::signed_data::SignedData;
use const_oid::{db::rfc5912, ObjectIdentifier};
use memchr::memmem;
use prost::Message;
use ring::digest::{Algorithm, Context};
use thiserror::Error;
use x509_cert::{der::Encode, Certificate};
use zip::{result::ZipError, write::FileOptions, CompressionMethod, ZipArchive, ZipWriter};
@@ -566,14 +566,14 @@ pub fn verify_metadata(
}
#[derive(Clone, Debug)]
pub struct OtaSignature {
struct RawOtaSignature {
/// Decoded CMS structure.
pub signed_data: SignedData,
signed_data: SignedData,
/// Length of the file (from the beginning) that's covered by the signature.
pub hashed_size: u64,
hashed_size: u64,
}
impl OtaSignature {
impl RawOtaSignature {
pub fn embedded_cert(&self) -> Result<&Certificate> {
let mut iter = crypto::iter_cms_certs(&self.signed_data);
@@ -589,9 +589,107 @@ impl OtaSignature {
}
}
#[derive(Clone, Debug)]
pub struct OtaSignature {
pub cert: Certificate,
pub digest_algo: &'static Algorithm,
pub sig_algo: SignatureAlgorithm,
pub sig: Vec<u8>,
pub data_size: u64,
}
impl TryFrom<RawOtaSignature> for OtaSignature {
type Error = Error;
fn try_from(raw_ota_sig: RawOtaSignature) -> Result<Self> {
let cert = raw_ota_sig.embedded_cert()?;
// Make sure this is a signature scheme we can handle. There's currently
// no Rust library to verify arbitrary CMS signatures for large files
// without fully reading them into memory.
let signers_len = raw_ota_sig.signed_data.signer_infos.0.len();
if signers_len != 1 {
return Err(Error::NotOneCmsSignerInfo(signers_len));
}
let signer = raw_ota_sig.signed_data.signer_infos.0.get(0).unwrap();
if signer.digest_alg.oid != rfc5912::ID_SHA_256
&& signer.digest_alg.oid != rfc5912::ID_SHA_1
{
return Err(Error::UnsupportedDigestAlgorithm(signer.digest_alg.oid));
} else if signer.signature_algorithm.oid != rfc5912::RSA_ENCRYPTION
&& signer.signature_algorithm.oid != rfc5912::SHA_256_WITH_RSA_ENCRYPTION
{
return Err(Error::UnsupportedSignatureAlgorithm(
signer.signature_algorithm.oid,
));
}
// We support SHA1 for verification only.
let (digest_algo, sig_algo) = if signer.digest_alg.oid == rfc5912::ID_SHA_256 {
(&ring::digest::SHA256, SignatureAlgorithm::Sha256WithRsa)
} else {
(
&ring::digest::SHA1_FOR_LEGACY_USE_ONLY,
SignatureAlgorithm::Sha1WithRsa,
)
};
Ok(Self {
cert: cert.clone(),
digest_algo,
sig_algo,
sig: signer.signature.as_bytes().to_vec(),
data_size: raw_ota_sig.hashed_size,
})
}
}
impl OtaSignature {
/// Verify an OTA zip against its embedded certificate. This function makes
/// no assertion about whether the certificate is actually trusted.
///
/// CMS signed attributes are intentionally not supported because AOSP
/// recovery does not support them either. It expects the CMS [`SignedData`]
/// structure to be used for nothing more than a raw signature transport
/// mechanism.
pub fn verify_ota(
&self,
mut reader: impl Read + Seek,
cancel_signal: &AtomicBool,
) -> Result<()> {
let public_key = crypto::get_public_key(&self.cert).map_err(Error::OtaCertExtractPubKey)?;
// Manually hash the parts of the file covered by the signature.
reader
.seek(SeekFrom::Start(0))
.map_err(|e| Error::DataRead("raw_data", e))?;
let mut hashing_reader = HashingReader::new(reader, Context::new(self.digest_algo));
stream::copy_n(
&mut hashing_reader,
io::sink(),
self.data_size,
cancel_signal,
)
.map_err(|e| Error::DataRead("raw_data", e))?;
let (_, context) = hashing_reader.finish();
let digest = context.finish();
// Verify the signature against the public key.
public_key
.verify_sig(self.sig_algo, digest.as_ref(), &self.sig)
.map_err(Error::CmsVerify)?;
Ok(())
}
}
/// Parse the CMS signature from the OTA zip comment. This does not perform any
/// parsing of zip data structures.
pub fn parse_ota_sig(mut reader: impl Read + Seek) -> Result<OtaSignature> {
fn parse_raw_ota_sig(mut reader: impl Read + Seek) -> Result<RawOtaSignature> {
let file_size = reader
.seek(SeekFrom::End(0))
.map_err(|e| Error::DataRead("file_size", e))?;
@@ -642,80 +740,16 @@ pub fn parse_ota_sig(mut reader: impl Read + Seek) -> Result<OtaSignature> {
// length field.
let hashed_size = file_size - 2 - u64::from(comment_size);
Ok(OtaSignature {
Ok(RawOtaSignature {
signed_data,
hashed_size,
})
}
/// Verify an OTA zip against its embedded certificates. This function makes no
/// assertion about whether the certificate is actually trusted. Returns the
/// embedded certificate.
///
/// CMS signed attributes are intentionally not supported because AOSP recovery
/// does not support them either. It expects the CMS [`SignedData`] structure to
/// be used for nothing more than a raw signature transport mechanism.
pub fn verify_ota(mut reader: impl Read + Seek, cancel_signal: &AtomicBool) -> Result<Certificate> {
let ota_sig = parse_ota_sig(&mut reader)?;
let cert = ota_sig.embedded_cert()?;
let public_key = crypto::get_public_key(cert).map_err(Error::OtaCertExtractPubKey)?;
// Make sure this is a signature scheme we can handle. There's currently no
// Rust library to verify arbitrary CMS signatures for large files without
// fully reading them into memory.
let signers_len = ota_sig.signed_data.signer_infos.0.len();
if signers_len != 1 {
return Err(Error::NotOneCmsSignerInfo(signers_len));
}
let signer = ota_sig.signed_data.signer_infos.0.get(0).unwrap();
if signer.digest_alg.oid != rfc5912::ID_SHA_256 && signer.digest_alg.oid != rfc5912::ID_SHA_1 {
return Err(Error::UnsupportedDigestAlgorithm(signer.digest_alg.oid));
} else if signer.signature_algorithm.oid != rfc5912::RSA_ENCRYPTION
&& signer.signature_algorithm.oid != rfc5912::SHA_256_WITH_RSA_ENCRYPTION
{
return Err(Error::UnsupportedSignatureAlgorithm(
signer.signature_algorithm.oid,
));
}
// Manually hash the parts of the file covered by the signature.
reader
.seek(SeekFrom::Start(0))
.map_err(|e| Error::DataRead("raw_data", e))?;
// We support SHA1 for verification only.
let (algorithm, algo) = if signer.digest_alg.oid == rfc5912::ID_SHA_256 {
(
&aws_lc_rs::digest::SHA256,
SignatureAlgorithm::Sha256WithRsa,
)
} else {
(
&aws_lc_rs::digest::SHA1_FOR_LEGACY_USE_ONLY,
SignatureAlgorithm::Sha1WithRsa,
)
};
let mut hashing_reader = HashingReader::new(reader, Context::new(algorithm));
stream::copy_n(
&mut hashing_reader,
io::sink(),
ota_sig.hashed_size,
cancel_signal,
)
.map_err(|e| Error::DataRead("raw_data", e))?;
let (_, context) = hashing_reader.finish();
let digest = context.finish();
// Verify the signature against the public key.
public_key
.verify_sig(algo, digest.as_ref(), signer.signature.as_bytes())
.map_err(Error::CmsVerify)?;
Ok(cert.clone())
/// Parse the signature information from the CMS signature embedded in the OTA
/// zip archive comment.
pub fn parse_ota_sig(reader: impl Read + Seek) -> Result<OtaSignature> {
parse_raw_ota_sig(reader)?.try_into()
}
/// Get and parse the protobuf-encoded OTA metadata, the PEM-encoded otacert,
@@ -794,7 +828,7 @@ fn validate_eocd(eocd: &[u8]) -> Result<()> {
fn compute_signature_comment(
key: &RsaSigningKey,
cert: &Certificate,
digest: aws_lc_rs::digest::Digest,
digest: ring::digest::Digest,
) -> Result<Vec<u8>> {
let cms_signature =
crypto::cms_sign_external(key, cert, digest.as_ref()).map_err(Error::CmsSign)?;
@@ -846,7 +880,7 @@ pub struct StreamingSigningWriter<W> {
impl<W: Write> StreamingSigningWriter<W> {
pub fn new(inner: W) -> Self {
Self {
inner: HashingWriter::new(inner, Context::new(&aws_lc_rs::digest::SHA256)),
inner: HashingWriter::new(inner, Context::new(&ring::digest::SHA256)),
queue: Default::default(),
used: 0,
}
@@ -950,7 +984,7 @@ impl<W: Read + Write + Seek> SeekableSigningWriter<W> {
// Compute the digest of everything up until the comment size field.
let mut hashing_writer = HashingWriter::new(
io::sink(),
aws_lc_rs::digest::Context::new(&aws_lc_rs::digest::SHA256),
ring::digest::Context::new(&ring::digest::SHA256),
);
self.rewind().map_err(|e| Error::DataRead("raw_data", e))?;
+127 -59
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2022-2024 Andrew Gunnerson
// SPDX-FileCopyrightText: 2022-2025 Andrew Gunnerson
// SPDX-License-Identifier: GPL-3.0-only
use std::{
@@ -9,10 +9,10 @@ use std::{
sync::atomic::AtomicBool,
};
use aws_lc_rs::digest::{Context, Digest};
use base64::engine::general_purpose::STANDARD;
use base64::Engine;
use bzip2::write::BzDecoder;
use clap::ValueEnum;
use flate2::{write::GzEncoder, Compression};
use liblzma::{
stream::{Check, Stream},
@@ -24,6 +24,7 @@ use rayon::{
iter::{IndexedParallelIterator, IntoParallelRefMutIterator},
prelude::{IntoParallelIterator, IntoParallelRefIterator, ParallelIterator},
};
use ring::digest::{Context, Digest};
use serde::{Deserialize, Serialize};
use thiserror::Error;
use x509_cert::Certificate;
@@ -48,6 +49,9 @@ const PAYLOAD_VERSION: u64 = 2;
const MANIFEST_MAX_SIZE: usize = 4 * 1024 * 1024;
/// Size of each extent. This matches what AOSP's delta_generator does.
const CHUNK_SIZE: u64 = 2 * 1024 * 1024;
#[derive(Debug, Error)]
pub enum Error {
#[error("Unknown magic: {0:?}")]
@@ -138,7 +142,7 @@ type Result<T> = std::result::Result<T, Error>;
/// Raw on-disk layout for the payload header.
#[derive(Clone, Copy, FromBytes, IntoBytes, KnownLayout, Immutable, Unaligned)]
#[repr(packed)]
#[repr(C, packed)]
struct RawHeader {
/// Magic value. This should be equal to [`PAYLOAD_MAGIC`].
magic: [u8; 4],
@@ -374,7 +378,7 @@ impl<W: Write> PayloadWriter<W> {
// Get the length of an dummy signature struct since the length fields
// are part of the data to be signed.
let dummy_sig = sign_digest(
aws_lc_rs::digest::digest(&aws_lc_rs::digest::SHA256, b"").as_ref(),
ring::digest::digest(&ring::digest::SHA256, b"").as_ref(),
&key,
)?;
let dummy_sig_size = dummy_sig.encoded_len();
@@ -387,9 +391,9 @@ impl<W: Write> PayloadWriter<W> {
let manifest_raw_new = header.manifest.encode_to_vec();
// Excludes signatures (hashes are for signing).
let mut h_partial = Context::new(&aws_lc_rs::digest::SHA256);
let mut h_partial = Context::new(&ring::digest::SHA256);
// Includes signatures (hashes are for properties file).
let mut h_full = Context::new(&aws_lc_rs::digest::SHA256);
let mut h_full = Context::new(&ring::digest::SHA256);
// Write header to output file.
let raw_header = RawHeader {
@@ -600,9 +604,9 @@ pub fn verify_payload(
.ok_or(Error::MissingField("signatures_size"))?;
// Excludes signatures (hashes are for signing).
let mut h_partial = Context::new(&aws_lc_rs::digest::SHA256);
let mut h_partial = Context::new(&ring::digest::SHA256);
// Includes signatures (hashes are for properties file).
let mut h_full = Context::new(&aws_lc_rs::digest::SHA256);
let mut h_full = Context::new(&ring::digest::SHA256);
// Read from the beginning to the metadata signature.
let metadata_size = header.blob_offset - u64::from(header.metadata_signature_size);
@@ -756,7 +760,7 @@ pub fn apply_operation(
writer.seek(SeekFrom::Start(out_offset)).map_err(error_fn)?;
let mut hasher = Context::new(&aws_lc_rs::digest::SHA256);
let mut hasher = Context::new(&ring::digest::SHA256);
match op.r#type() {
// Handle ZERO/DISCARD specially since they don't require access to
@@ -925,7 +929,7 @@ pub fn extract_images<'a>(
fn compress_chunk(raw_data: &[u8], cancel_signal: &AtomicBool) -> Result<(Vec<u8>, Digest)> {
let reader = Cursor::new(raw_data);
let writer = Cursor::new(Vec::new());
let hashing_writer = HashingWriter::new(writer, Context::new(&aws_lc_rs::digest::SHA256));
let hashing_writer = HashingWriter::new(writer, Context::new(&ring::digest::SHA256));
// AOSP's payload_consumer does not support checking CRC during
// decompression. Also, we intentionally pick the lowest compression level
@@ -945,40 +949,34 @@ fn compress_chunk(raw_data: &[u8], cancel_signal: &AtomicBool) -> Result<(Vec<u8
Ok((data, digest_compressed))
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Deserialize, Serialize)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Deserialize, Serialize, ValueEnum)]
pub enum VabcAlgo {
Lz4,
Gzip,
Gz,
}
impl VabcAlgo {
pub fn new(name: &str) -> Option<Self> {
match name {
"lz4" => Some(Self::Lz4),
"gz" => Some(Self::Gzip),
_ => None,
}
}
fn compressed_size(self, mut raw_data: &[u8], block_size: u32) -> Result<u64> {
let mut total = 0;
while !raw_data.is_empty() {
let n = raw_data.len().min(block_size as usize);
let (chunk, remaining) = raw_data.split_at(n);
// This should match CompressWorker::GetDefaultCompressionLevel() in
// AOSP's libsnapshot.
let compressed = match self {
Self::Lz4 => lz4_flex::block::compress(&raw_data[..n]),
Self::Gzip => {
Self::Lz4 => lz4_flex::block::compress(chunk),
Self::Gz => {
let mut encoder = GzEncoder::new(Vec::new(), Compression::best());
encoder.write_all(raw_data).map_err(Error::GzCompress)?;
encoder.write_all(chunk).map_err(Error::GzCompress)?;
encoder.finish().map_err(Error::GzCompress)?
}
};
total += compressed.len().min(n) as u64;
raw_data = &raw_data[n..];
raw_data = remaining;
}
Ok(total)
@@ -987,13 +985,100 @@ impl VabcAlgo {
impl fmt::Display for VabcAlgo {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Lz4 => f.write_str("lz4"),
Self::Gzip => f.write_str("gz"),
}
f.write_str(self.to_possible_value().ok_or(fmt::Error)?.get_name())
}
}
/// Add fudge factor to CoW estimate to account for overhead.
fn fudge_cow_estimate(mut estimate: u64) -> Option<u64> {
// lz4_flex and zlib-rs usually compress better than the lz4 and zlib
// implementations used by libsnapshot_cow. Make up for this by adding
// percentage-based overhead.
estimate = estimate.checked_add(estimate / 100)?;
// We also need to account for constant overhead, especially with smaller
// partitions. We can match what delta_generator normally adds in
// CowWriterV2::InitPos() exactly. Since we only ever create full OTAs, we
// can assume that all CoW operations are kCowReplaceOp.
// sizeof(CowHeader).
estimate = estimate.checked_add(38)?;
// header_.buffer_size (equal to BUFFER_REGION_DEFAULT_SIZE).
estimate = estimate.checked_add(2 * 1024 * 1024)?;
// CowOptions::cluster_ops * sizeof(CowOperationV2).
estimate = estimate.checked_add(200 * 20)?;
Some(estimate)
}
/// Compute the VABC CoW v2 size estimate. The caller must update
/// [`PartitionUpdate::estimate_cow_size`] with this value or else update_engine
/// may fail to flash the partition due to running out of space on the CoW block
/// device. CoW v2 + other algorithms and also CoW v3 are currently unsupported
/// because there currently are no known OTAs that use those configurations.
pub fn compute_cow_estimate(
input: &(dyn ReadSeekReopen + Sync),
partition_name: &str,
block_size: u32,
vabc_algo: VabcAlgo,
cancel_signal: &AtomicBool,
) -> Result<u64> {
let file_size = input
.reopen_boxed()
.and_then(|mut r| r.seek(SeekFrom::End(0)))
.map_err(|e| Error::InputOpen(partition_name.to_owned(), e))?;
let final_chunk_different = file_size % CHUNK_SIZE != 0;
if file_size % u64::from(block_size) != 0 || CHUNK_SIZE % u64::from(block_size) != 0 {
return Err(Error::InvalidPartitionSize {
name: partition_name.to_owned(),
size: file_size,
block_size,
});
}
let chunks_total = file_size.div_ceil(CHUNK_SIZE);
let cow_estimate = (0..chunks_total)
.into_par_iter()
.map(|chunk| -> Result<u64> {
let data = (|| {
let mut reader = input.reopen_boxed()?;
reader.seek(SeekFrom::Start(chunk * CHUNK_SIZE))?;
let chunk_size = if final_chunk_different && chunk == chunks_total - 1 {
file_size % CHUNK_SIZE
} else {
CHUNK_SIZE
};
stream::check_cancel(cancel_signal)?;
reader.read_vec_exact(chunk_size as usize)
})()
.map_err(Error::ChunkRead)?;
vabc_algo.compressed_size(&data, block_size)
})
.try_fold(
|| 0u64,
|total, chunk_estimate| -> Result<u64> {
total
.checked_add(chunk_estimate?)
.ok_or(Error::IntOverflow("cow_estimate"))
},
)
.try_reduce(
|| 0u64,
|total, partial| {
total
.checked_add(partial)
.ok_or(Error::IntOverflow("cow_estimate"))
},
)?;
fudge_cow_estimate(cow_estimate).ok_or(Error::IntOverflow("cow_estimate_fudged"))
}
/// Compress the image and return the corresponding information to insert into
/// the payload manifest's [`PartitionUpdate`] instance. The uncompressed data
/// is split into 2 MiB chunks, which are read and compressed in parallel, and
@@ -1002,12 +1087,9 @@ impl fmt::Display for VabcAlgo {
/// update [`InstallOperation::data_offset`] in each operation manually because
/// the initial values are relative to 0.
///
/// If `vabc_algo` is set, the VABC CoW v2 size estimate will be computed. The
/// caller must update [`PartitionUpdate::estimate_cow_size`] with this value or
/// else update_engine may fail to flash the partition due to running out of
/// space on the CoW block device. CoW v2 + other algorithms and also CoW v3 are
/// currently unsupported because there currently are no known OTAs that use
/// those configurations.
/// If `vabc_algo` is set, the VABC CoW size estimate will also be computed.
/// This is more efficient than separately calling [`compute_cow_estimate`]
/// since the input does not need to be read twice.
pub fn compress_image(
input: &(dyn ReadSeekReopen + Sync),
output: &(dyn WriteSeekReopen + Sync),
@@ -1016,7 +1098,6 @@ pub fn compress_image(
vabc_algo: Option<VabcAlgo>,
cancel_signal: &AtomicBool,
) -> Result<(PartitionInfo, Vec<InstallOperation>, Option<u64>)> {
const CHUNK_SIZE: u64 = 2 * 1024 * 1024;
const CHUNK_GROUP: u64 = 32;
let file_size = input
@@ -1034,9 +1115,9 @@ pub fn compress_image(
}
let chunks_total = file_size.div_ceil(CHUNK_SIZE);
let mut bytes_compressed = 0;
let mut context_uncompressed = Context::new(&aws_lc_rs::digest::SHA256);
let mut cow_estimate = 0;
let mut bytes_compressed = 0u64;
let mut context_uncompressed = Context::new(&ring::digest::SHA256);
let mut cow_estimate = 0u64;
let mut operations = vec![];
// Read the file one group at a time. This allows for some parallelization
@@ -1098,8 +1179,12 @@ pub fn compress_image(
for (data, operation, cow_size) in &mut compressed_data_group {
operation.data_offset = Some(bytes_compressed);
bytes_compressed += data.len() as u64;
cow_estimate += *cow_size;
bytes_compressed = bytes_compressed
.checked_add(data.len() as u64)
.ok_or(Error::IntOverflow("bytes_compressed"))?;
cow_estimate = cow_estimate
.checked_add(*cow_size)
.ok_or(Error::IntOverflow("cow_estimate"))?;
}
let group_operations = compressed_data_group
@@ -1124,24 +1209,7 @@ pub fn compress_image(
};
let cow_estimate = if vabc_algo.is_some() {
// lz4_flex and miniz_oxide usually compress better than the lz4 and
// zlib implementations used by libsnapshot_cow. Make up for this by
// adding percentage-based overhead.
cow_estimate += cow_estimate / 100;
// We also need to account for constant overhead, especially with
// smaller partitions. We can match what delta_generator normally adds
// in CowWriterV2::InitPos() exactly. Since we only ever create full
// OTAs, we can assume that all CoW operations are kCowReplaceOp.
// sizeof(CowHeader).
cow_estimate += 38;
// header_.buffer_size (equal to BUFFER_REGION_DEFAULT_SIZE).
cow_estimate += 2 * 1024 * 1024;
// CowOptions::cluster_ops * sizeof(CowOperationV2).
cow_estimate += 200 * 20;
Some(cow_estimate)
Some(fudge_cow_estimate(cow_estimate).ok_or(Error::IntOverflow("cow_estimate_fudged"))?)
} else {
None
};
@@ -1206,7 +1274,7 @@ pub fn compress_modified_image(
let groups_total = operations.len().div_ceil(OPERATION_GROUP);
let mut bytes_compressed = 0;
let mut context_uncompressed = Context::new(&aws_lc_rs::digest::SHA256);
let mut context_uncompressed = Context::new(&ring::digest::SHA256);
let mut modified_operations = vec![];
// Read the file one group at a time. This allows for some parallelization
+2 -2
View File
@@ -106,7 +106,7 @@ type Result<T> = std::result::Result<T, Error>;
/// Raw on-disk layout for the header.
#[derive(Clone, Copy, FromBytes, IntoBytes, KnownLayout, Immutable, Unaligned)]
#[repr(packed)]
#[repr(C, packed)]
struct RawHeader {
/// Magic value. This should be equal to [`HEADER_MAGIC`].
magic: little_endian::U32,
@@ -183,7 +183,7 @@ impl RawHeader {
/// Raw on-disk layout for the chunk header.
#[derive(Clone, Copy, FromBytes, IntoBytes, KnownLayout, Immutable, Unaligned)]
#[repr(packed)]
#[repr(C, packed)]
struct RawChunk {
/// Chunk type. Must be [`CHUNK_TYPE_RAW`], [`CHUNK_TYPE_FILL`],
/// [`CHUNK_TYPE_DONT_CARE`], or [`CHUNK_TYPE_CRC32`].
+2 -2
View File
@@ -14,7 +14,6 @@ use std::{
sync::atomic::AtomicBool,
};
use aws_lc_rs::digest::Context;
use bstr::ByteSlice;
use liblzma::{
stream::{Check, Stream},
@@ -22,6 +21,7 @@ use liblzma::{
};
use rayon::iter::{IntoParallelRefIterator, IntoParallelRefMutIterator, ParallelIterator};
use regex::bytes::Regex;
use ring::digest::Context;
use rsa::RsaPublicKey;
use thiserror::Error;
use tracing::{debug, debug_span, trace, warn, Span};
@@ -1180,7 +1180,7 @@ fn save_boot_image(
};
// Write new boot image. We reuse the existing salt for the digest.
let mut context = Context::new(&aws_lc_rs::digest::SHA256);
let mut context = Context::new(&ring::digest::SHA256);
context.update(&descriptor.salt);
let mut hashing_writer = HashingWriter::new(writer, context);
info.boot_image
+4 -4
View File
@@ -10,8 +10,8 @@ use std::{
},
};
use aws_lc_rs::digest::Context;
use num_traits::ToPrimitive;
use ring::digest::Context;
use crate::util;
@@ -637,7 +637,7 @@ mod tests {
sync::atomic::{AtomicBool, Ordering},
};
use aws_lc_rs::digest::Context;
use ring::digest::Context;
use super::{
CountingReader, CountingWriter, HashingReader, HashingWriter, PSeekFile, ReadDiscardExt,
@@ -713,7 +713,7 @@ mod tests {
#[test]
fn hashing_reader() {
let raw_reader = Cursor::new(b"foobar");
let mut reader = HashingReader::new(raw_reader, Context::new(&aws_lc_rs::digest::SHA256));
let mut reader = HashingReader::new(raw_reader, Context::new(&ring::digest::SHA256));
let mut buf = [0u8; 6];
reader.read_exact(&mut buf[..0]).unwrap();
@@ -730,7 +730,7 @@ mod tests {
#[test]
fn hashing_writer() {
let raw_writer = Cursor::new([0u8; 6]);
let mut writer = HashingWriter::new(raw_writer, Context::new(&aws_lc_rs::digest::SHA256));
let mut writer = HashingWriter::new(raw_writer, Context::new(&ring::digest::SHA256));
writer.write_all(b"").unwrap();
writer.write_all(b"foo").unwrap();
+4 -4
View File
@@ -134,7 +134,7 @@ fn round_trip_root_image() {
// Verify checksum of the output.
assert_eq!(
aws_lc_rs::digest::digest(&aws_lc_rs::digest::SHA512, &data).as_ref(),
ring::digest::digest(&ring::digest::SHA512, &data).as_ref(),
[
0x3b, 0x01, 0xf6, 0x04, 0x04, 0x6e, 0x6f, 0x60, 0x9c, 0xb0, 0x8b, 0x8a, 0x43, 0xf7,
0x91, 0x2e, 0xc4, 0x1b, 0xc0, 0x7f, 0xa1, 0xe4, 0xe6, 0x59, 0x14, 0x08, 0xbe, 0x83,
@@ -233,7 +233,7 @@ fn round_trip_appended_hash_image() {
// Verify checksum of the output.
assert_eq!(
aws_lc_rs::digest::digest(&aws_lc_rs::digest::SHA512, &data).as_ref(),
ring::digest::digest(&ring::digest::SHA512, &data).as_ref(),
[
0x91, 0x38, 0x61, 0xc0, 0x68, 0x2a, 0x8b, 0xd8, 0x01, 0xa6, 0xe4, 0x4c, 0x1d, 0x27,
0x93, 0x1b, 0xa4, 0x63, 0xd1, 0xbb, 0xf1, 0x64, 0x05, 0xf2, 0xa1, 0xa0, 0xb3, 0x35,
@@ -341,7 +341,7 @@ fn round_trip_appended_hash_tree_image_fixed_size() {
// Verify checksum of the output.
assert_eq!(
aws_lc_rs::digest::digest(&aws_lc_rs::digest::SHA512, &data).as_ref(),
ring::digest::digest(&ring::digest::SHA512, &data).as_ref(),
[
0x92, 0xdd, 0x4d, 0xc5, 0xb0, 0x5b, 0x4f, 0x65, 0x97, 0x5a, 0x72, 0x66, 0xde, 0x82,
0xc2, 0x2f, 0x33, 0x86, 0x8b, 0x65, 0x67, 0x80, 0x1d, 0xca, 0xd6, 0x2c, 0xfc, 0xca,
@@ -448,7 +448,7 @@ fn round_trip_appended_hash_tree_image_minimum_size() {
// Verify checksum of the output.
assert_eq!(
aws_lc_rs::digest::digest(&aws_lc_rs::digest::SHA512, &data).as_ref(),
ring::digest::digest(&ring::digest::SHA512, &data).as_ref(),
[
0xcf, 0x6b, 0x90, 0xcf, 0x77, 0x76, 0x62, 0x12, 0xc2, 0x22, 0xe6, 0xd5, 0x5b, 0xab,
0x82, 0xd8, 0x6c, 0x93, 0xa3, 0x35, 0x5b, 0x77, 0xe0, 0x38, 0x12, 0x48, 0x90, 0x0c,
+1 -1
View File
@@ -49,7 +49,7 @@ fn round_trip(image: &BootImage, sha512: &[u8; 64], expected_version: u32) {
let data = writer.into_inner();
assert_eq!(
aws_lc_rs::digest::digest(&aws_lc_rs::digest::SHA512, &data).as_ref(),
ring::digest::digest(&ring::digest::SHA512, &data).as_ref(),
sha512,
);
+1 -1
View File
@@ -41,7 +41,7 @@ fn generate_archive() -> Vec<u8> {
let data = writer.into_inner();
assert_eq!(
aws_lc_rs::digest::digest(&aws_lc_rs::digest::SHA512, &data).as_ref(),
ring::digest::digest(&ring::digest::SHA512, &data).as_ref(),
[
0xb0, 0x51, 0xac, 0x28, 0x6f, 0x78, 0xe2, 0xe7, 0x45, 0xa0, 0x52, 0x7c, 0xff, 0x42,
0x30, 0x55, 0xbd, 0x64, 0x7d, 0x4e, 0xb8, 0xe6, 0x95, 0xe5, 0x9b, 0xd1, 0x13, 0xd6,
+1 -1
View File
@@ -17,7 +17,7 @@ fn round_trip(metadata: &Metadata, sha512: &[u8; 64]) {
let data = writer.into_inner();
assert_eq!(
aws_lc_rs::digest::digest(&aws_lc_rs::digest::SHA512, &data).as_ref(),
ring::digest::digest(&ring::digest::SHA512, &data).as_ref(),
sha512,
);
+1 -1
View File
@@ -39,7 +39,7 @@ fn round_trip(block_size: u32, crc32: u32, test_chunks: &[TestChunk], sha512: &[
let data = writer.into_inner();
assert_eq!(
aws_lc_rs::digest::digest(&aws_lc_rs::digest::SHA512, &data).as_ref(),
ring::digest::digest(&ring::digest::SHA512, &data).as_ref(),
sha512,
);
+7 -3
View File
@@ -37,11 +37,17 @@ allow = [
"GPL-3.0",
"ISC",
"MIT",
"OpenSSL",
"Unicode-3.0",
"Zlib",
]
[[licenses.clarify]]
name = "ring"
expression = "MIT AND ISC AND OpenSSL"
license-files = [
{ path = "LICENSE", hash = 0xbd0eed23 },
]
[bans]
multiple-versions = "warn"
multiple-versions-include-dev = true
@@ -57,8 +63,6 @@ include-workspace = true
bypass = [
# Copies of unmodified crashwrangler objects for old macOS versions.
{ name = "honggfuzz", allow-globs = ["honggfuzz/third_party/mac/CrashReport_*.o"] },
# Only used in tests.
{ name = "libloading", allow-globs = ["tests/*.dll"] }
]
[sources]
+1 -1
View File
@@ -9,12 +9,12 @@ publish = false
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
aws-lc-rs = { version = "1.0.0", default-features = false, features = ["aws-lc-sys"] }
anyhow = "1.0.75"
avbroot = { path = "../avbroot" }
clap = { version = "4.4.1", features = ["derive"] }
ctrlc = "3.4.0"
hex = { version = "0.4.3", features = ["serde"] }
ring = "0.17.14"
rsa = { version = "0.9.6", features = ["hazmat"] }
serde = { version = "1.0.188", features = ["derive"] }
tempfile = "3.8.0"
+5 -5
View File
@@ -143,7 +143,7 @@ patched = "27b80c7be9c1e527ea26abe3dabde245c580e6f26ec084204278fbfd81a39f83"
# What's unique: boot (boot v2)
[profile.pixel_v2]
vabc_algo = "Gzip"
vabc_algo = "Gz"
[profile.pixel_v2.partitions.boot]
avb.signed = false
@@ -168,9 +168,9 @@ data.type = "vbmeta"
data.deps = ["system"]
[profile.pixel_v2.hashes_streaming]
original = "4b7e5675f834ac56bf3459628adfe1425c2346a1c224ee6ea0a3be9f996db254"
patched = "4a7ca99808b4e49a2dc77a620d4fb7d8219b974ab5b3d5b5709084942805dc9a"
original = "bd2f19cf3d2285e35e8b36d44f75ed910e8e0be44c3ebd29f17a812521ba754b"
patched = "cf65d5b90500af54cd1204a646379bb852825061bc7c3f973b7a042f353f75ad"
[profile.pixel_v2.hashes_seekable]
original = "66b44b148b35a8a998214e0ae36470b42ffd6d2974e18cd8fa12cd8af0c98540"
patched = "5ddec4bb56dd78a49fbc49e9ad3de7710b0b008b031ff4dfce8fdc775e753ad1"
original = "7f96ebf7366e0b60c91ac1e5f196a2189ffdb0bbc73f77804a736466fcab7315"
patched = "c2d9d60d73c038da39f82073ffadb459c96b901d66db7af11f59da58e0dd53e4"
+2 -2
View File
@@ -69,7 +69,7 @@ fn hash_file(path: &Path, cancel_signal: &AtomicBool) -> Result<[u8; 32]> {
let raw_reader =
File::open(path).with_context(|| format!("Failed to open for reading: {path:?}"))?;
let buf_reader = BufReader::new(raw_reader);
let context = aws_lc_rs::digest::Context::new(&aws_lc_rs::digest::SHA256);
let context = ring::digest::Context::new(&ring::digest::SHA256);
let mut hashing_reader = HashingReader::new(buf_reader, context);
stream::copy(&mut hashing_reader, io::sink(), cancel_signal)?;
@@ -104,7 +104,7 @@ fn append_avb(
cancel_signal: &AtomicBool,
) -> Result<()> {
let image_size = file.seek(SeekFrom::End(0))?;
let salt = aws_lc_rs::digest::digest(&aws_lc_rs::digest::SHA256, b"avbroot");
let salt = ring::digest::digest(&ring::digest::SHA256, b"avbroot");
let descriptors = vec![
if hash_tree {
let mut descriptor = HashTreeDescriptor {