mirror of
https://github.com/chenxiaolong/avbroot.git
synced 2026-07-03 14:05:11 +02:00
Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3f09a506a0 | |||
| 4664f8ea37 | |||
| 796e2a4fa2 | |||
| e6b60d5d0f | |||
| e8cb4a8d53 | |||
| b2d280eb20 | |||
| e2dc5174b4 | |||
| 80c47e9a02 | |||
| 65ba3ad5cc | |||
| a7438876ce | |||
| c039901a85 |
@@ -36,8 +36,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 +85,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 +156,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: |
|
||||
|
||||
@@ -7,6 +7,13 @@
|
||||
to update the actual links at the bottom of the file.
|
||||
-->
|
||||
|
||||
### 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 +301,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 +328,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 +475,8 @@ 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
|
||||
|
||||
Generated
+51
-45
@@ -110,7 +110,7 @@ checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
|
||||
|
||||
[[package]]
|
||||
name = "avbroot"
|
||||
version = "3.13.0"
|
||||
version = "3.14.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"assert_matches",
|
||||
@@ -148,7 +148,7 @@ dependencies = [
|
||||
"rayon",
|
||||
"regex",
|
||||
"rsa",
|
||||
"rustix 0.38.44",
|
||||
"rustix 1.0.3",
|
||||
"serde",
|
||||
"sha1",
|
||||
"sha2",
|
||||
@@ -176,9 +176,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "aws-lc-sys"
|
||||
version = "0.27.0"
|
||||
version = "0.27.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6bbe221bbf523b625a4dd8585c7f38166e31167ec2ca98051dbcb4c3b6e825d2"
|
||||
checksum = "77926887776171ced7d662120a75998e444d3750c951abfe07f90da130514b1f"
|
||||
dependencies = [
|
||||
"bindgen",
|
||||
"cc",
|
||||
@@ -195,9 +195,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"
|
||||
@@ -350,9 +350,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.16"
|
||||
version = "1.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "be714c154be609ec7f5dad223a33bf1482fff90472de28f7362806e6d4832b8c"
|
||||
checksum = "1fcb57c740ae1daf453ae85f16e37396f672b039e00d9d866e07ddb24e328e3a"
|
||||
dependencies = [
|
||||
"jobserver",
|
||||
"libc",
|
||||
@@ -425,9 +425,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",
|
||||
]
|
||||
@@ -637,7 +637,7 @@ checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813"
|
||||
|
||||
[[package]]
|
||||
name = "e2e"
|
||||
version = "3.13.0"
|
||||
version = "3.14.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"avbroot",
|
||||
@@ -726,7 +726,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "94e7099f6313ecacbe1256e8ff9d617b75d1bcb16a6fddef94866d225a01a14a"
|
||||
dependencies = [
|
||||
"io-lifetimes",
|
||||
"rustix 1.0.2",
|
||||
"rustix 1.0.3",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
@@ -738,7 +738,7 @@ checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c"
|
||||
|
||||
[[package]]
|
||||
name = "fuzz"
|
||||
version = "3.13.0"
|
||||
version = "3.14.0"
|
||||
dependencies = [
|
||||
"avbroot",
|
||||
"honggfuzz",
|
||||
@@ -767,14 +767,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]]
|
||||
@@ -847,14 +847,15 @@ dependencies = [
|
||||
|
||||
[[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]]
|
||||
@@ -1024,9 +1025,9 @@ 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"
|
||||
@@ -1223,9 +1224,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.21.0"
|
||||
version = "1.21.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cde51589ab56b20a6f686b2c68f7a0bd6add753d697abf720d63f8db3ab7b1ad"
|
||||
checksum = "d75b0bedcc4fe52caa0e03d9f1151a323e4aa5e2d78ba3580400cd3c9e2bc4bc"
|
||||
|
||||
[[package]]
|
||||
name = "overload"
|
||||
@@ -1374,9 +1375,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "prettyplease"
|
||||
version = "0.2.30"
|
||||
version = "0.2.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f1ccf34da56fc294e7d4ccf69a85992b7dfb826b7cf57bac6a70bba3494cc08a"
|
||||
checksum = "5316f57387668042f561aae71480de936257848f9c43ce528e311d89a07cadeb"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"syn 2.0.100",
|
||||
@@ -1492,6 +1493,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"
|
||||
@@ -1625,14 +1632,14 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "1.0.2"
|
||||
version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f7178faa4b75a30e269c71e61c353ce2748cf3d76f0c44c393f4e60abf49b825"
|
||||
checksum = "e56a18552996ac8d29ecc3b190b4fdbb2d91ca4ec396de7bbffaf43f3d637e96"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys 0.9.2",
|
||||
"linux-raw-sys 0.9.3",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
@@ -1814,15 +1821,14 @@ 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",
|
||||
"rustix 1.0.3",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
@@ -2018,11 +2024,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,9 +2051,9 @@ 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",
|
||||
]
|
||||
@@ -2180,9 +2186,9 @@ dependencies = [
|
||||
|
||||
[[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 +2209,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "xtask"
|
||||
version = "3.13.0"
|
||||
version = "3.14.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
@@ -2213,18 +2219,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",
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ members = ["avbroot", "e2e", "fuzz", "xtask"]
|
||||
resolver = "2"
|
||||
|
||||
[workspace.package]
|
||||
version = "3.13.0"
|
||||
version = "3.14.0"
|
||||
license = "GPL-3.0-only"
|
||||
edition = "2021"
|
||||
repository = "https://github.com/chenxiaolong/avbroot"
|
||||
|
||||
+3
-1
@@ -24,6 +24,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"
|
||||
@@ -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"
|
||||
|
||||
+102
-53
@@ -20,7 +20,7 @@ use clap::{value_parser, ArgAction, Args, Parser, Subcommand};
|
||||
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};
|
||||
|
||||
@@ -683,8 +683,6 @@ pub fn compress_image(
|
||||
// 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 {
|
||||
@@ -705,6 +703,8 @@ pub fn compress_image(
|
||||
bail!("Unsupported VABC compression: {compression}");
|
||||
};
|
||||
|
||||
info!("Needs updated {vabc_algo} CoW size estimate: {name}");
|
||||
|
||||
Some(vabc_algo)
|
||||
} else {
|
||||
None
|
||||
@@ -1505,9 +1505,8 @@ pub fn extract_subcommand(cli: &ExtractCli, cancel_signal: &AtomicBool) -> Resul
|
||||
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 +1689,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 +1705,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 +1754,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 +1786,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 +1805,73 @@ 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 = 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() {
|
||||
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(())
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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],
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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],
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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],
|
||||
|
||||
@@ -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,
|
||||
@@ -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,
|
||||
@@ -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.
|
||||
|
||||
+112
-75
@@ -10,7 +10,7 @@ use std::{
|
||||
sync::atomic::AtomicBool,
|
||||
};
|
||||
|
||||
use aws_lc_rs::digest::Context;
|
||||
use aws_lc_rs::digest::{Algorithm, Context};
|
||||
use clap::ValueEnum;
|
||||
use cms::signed_data::SignedData;
|
||||
use const_oid::{db::rfc5912, ObjectIdentifier};
|
||||
@@ -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,110 @@ 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 {
|
||||
(
|
||||
&aws_lc_rs::digest::SHA256,
|
||||
SignatureAlgorithm::Sha256WithRsa,
|
||||
)
|
||||
} else {
|
||||
(
|
||||
&aws_lc_rs::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 +743,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,
|
||||
|
||||
@@ -138,7 +138,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],
|
||||
@@ -965,20 +965,22 @@ impl VabcAlgo {
|
||||
|
||||
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::Lz4 => lz4_flex::block::compress(chunk),
|
||||
Self::Gzip => {
|
||||
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)
|
||||
|
||||
@@ -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`].
|
||||
|
||||
+4
-4
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user