Compare commits

...

28 Commits

Author SHA1 Message Date
Andrew Gunnerson 0cdc7172dd Version 3.13.0
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2025-03-12 18:39:59 -04:00
Andrew Gunnerson 1296275418 CHANGELOG.md: Add entry for PR #427
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2025-03-12 18:38:43 -04:00
Andrew Gunnerson a62d0a5c91 Update dependencies
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2025-03-12 18:25:35 -04:00
Andrew Gunnerson de433a2724 README.md: Clarify what --skip-{system,recovery}-ota-cert affects
Discussion: #426

Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2025-03-12 18:07:17 -04:00
Andrew Gunnerson c08af33343 CHANGELOG.md: Add entry for PR #425
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2025-02-24 21:17:44 -05:00
Andrew Gunnerson e224884591 format/lp: Relax validation checks to parse on-device super partition
There are two documented behaviors in liblp that are violated with the
on-disk super partition layout after a virtual A/B CoW merge:

1. The partition name includes a `-` due to the `-cow` suffix. This is
   not meant to be a valid character.
2. The extent list is likely to have many gaps and not be sorted. The
   format documentation says that gaps are not allowed.

This commit updates avbroot's LP parser to be less strict so that it can
load real on-device super partitions. The extent allocator for the
`pack` subcommand remains unchanged though, so avbroot will always
produce LP images with sorted, gapless extents.

Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2025-02-24 21:05:28 -05:00
Andrew Gunnerson a525fc4550 CHANGELOG.md: Add entry for PR #424
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2025-02-21 21:34:30 -05:00
Andrew Gunnerson 8c6b0fdfc5 cli/ota: Discard unmodified system image temp file earlier
This is the same optimization as is currently done for boot images.
There's no reason to keep the temp file around for the entire patching
process if it's unmodified and we're not going to be copying its data
into the payload.

Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2025-02-21 21:23:10 -05:00
Andrew Gunnerson 36d4ed19ad CHANGELOG.md: Add entry for PR #423
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2025-02-21 21:18:12 -05:00
Andrew Gunnerson f9b06b33e6 cli/ota: Fix incorrect unprotected partition warning with --skip-system-ota-cert
The filtering out of partitions was done at the wrong scope, causing
avbroot to warn that extracted-but-unmodified partitions were not
protected by AVB. We never encountered this before because the system
image was always patched and unmodified boot images got filtered out at
an earlier phase during patching.

Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2025-02-21 21:00:59 -05:00
Andrew Gunnerson 620c873be5 CHANGELOG.md: Add entry for PR #422
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2025-02-21 21:00:20 -05:00
Andrew Gunnerson e929ecbe44 Replace ring with aws-lc
The author of ring recently announced that the library is no longer
being maintained and fixes for security issues may be significantly
delayed. Big thanks to Brian Smith for creating and maintaining the
library for so long!

This commit replaces ring with aws-lc, a cryptography library maintained
by Amazon AWS. It seems to be well-regarded and is used by high-profile
projects like rustls. It is also API-compatible with ring, so it is
effectively a drop-in replacement.

Unfortunately, we still cannot switch back to the RustCrypto SHA1 and
SHA2 implementations because they are still significantly slower than
ring and aws-lc on systems that do not support the SHA-NI extensions.

https://rustsec.org/advisories/RUSTSEC-2025-0007

Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2025-02-21 20:44:19 -05:00
Andrew Gunnerson 31685713ef CHANGELOG.md: Add entry for PR #421
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2025-02-21 19:33:58 -05:00
Andrew Gunnerson 0dab7aa32c Switch to stable bzip2-rs release and use zlib-rs flate2 backend
* There is now a stable release of bzip2-rs with the fix for both the C
  and Rust versions of bzip2 being compiled.

* The zlib-rs deflate implementation is faster than the default
  miniz_oxide. Changing this requires updating the checksums in the e2e
  tests due to slight differences in compression levels between the two
  implementations.

* Temporarily silence RUSTSEC-2025-0007 to avoid blocking CI. The ring
  library is no longer maintained.

Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2025-02-21 19:27:18 -05:00
Ivan ba6c1b1400 README.ru.md: update translation
* https://github.com/chenxiaolong/avbroot/commit/c198646c7c9b3eb8013dca43df6df8502e619e3c
* https://github.com/chenxiaolong/avbroot/commit/d4eb231dd49e7e6c7320135785cde7cd57634a50
* https://github.com/chenxiaolong/avbroot/commit/1484cd47c354197e1179a554f9f001020297aac8
* https://github.com/chenxiaolong/avbroot/commit/84fa6c6bc63b62d1b3b96dcb50b18b13dea0076f

Signed-off-by: Ivan <reddxae@proton.me>
2025-02-11 14:20:11 +03:00
Andrew Gunnerson 1ecbf1144d CHANGELOG.md: Add entry for PR #418
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2025-02-09 21:59:13 -05:00
Andrew Gunnerson 84fa6c6bc6 Add option to skip replacing OTA cert in system image
This is analogous to the existing --skip-recovery-ota-cert option,
except for the system image.

Discussion: #417

Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2025-02-09 21:42:18 -05:00
Andrew Gunnerson 15b7db4631 CHANGELOG.md: Add entry for PR #415
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2025-02-04 00:16:38 -05:00
Andrew Gunnerson 1d9c1574da lp: Stop checking for zeroed first block
AOSP says that for non-empty images, the first 4 KiB block is supposed
to be filled with zeros to prevent it from being interpreted as an old
BIOS boot sector. The previous implementation relied on that to
distinguish between empty and non-empty images. However, Samsung decided
to use this region for their own SignerVer02 structure, so the heuristic
doesn't work.

AOSP's liblp tries to parse the input file as an empty image before
falling back to parsing as a normal image. We'll do the same.

Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2025-02-04 00:13:27 -05:00
Andrew Gunnerson 43db728b9d Version 3.12.0
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2025-01-26 18:20:06 -05:00
Andrew Gunnerson fbe9f629ab CHANGELOG.md: Add entry for PR #411
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2025-01-26 18:11:40 -05:00
Andrew Gunnerson 80d5f19223 Update dependencies
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2025-01-26 18:10:42 -05:00
Andrew Gunnerson cff5ac6b2e CHANGELOG.md: Add entry for PR #410
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2025-01-26 18:04:35 -05:00
Andrew Gunnerson 2e3b5db9fe cli/key: Rename extract-avb to encode-avb
For consistency with decode-avb. The old syntax will remain supported
indefinitely for backwards compatibility.

Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2025-01-26 18:01:24 -05:00
Andrew Gunnerson 58a279f3a4 CHANGELOG.md: Add entry for PR #409
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2025-01-26 17:53:08 -05:00
Andrew Gunnerson 1484cd47c3 cli/ota: Add support for extracting OTA certificate and AVB public key
This also adds a new --none option so that these two components can be
extracted without extracting any partition images.

Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2025-01-26 17:50:09 -05:00
Andrew Gunnerson 2ee38b716b CHANGELOG.md: Add entry for PR #408
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2025-01-26 16:48:08 -05:00
Andrew Gunnerson d4eb231dd4 cli/ota: Add support for extracting specific images
Previously, we only supported extracting all images or the subset of
images that could potentially be patched by avbroot. This was
unnecessarily slow if the user only needed to extract a specific image.

This commit also deprecates and hides the `--boot-only` option, though
the functionality will remain indefinitely.

Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2025-01-26 16:43:35 -05:00
32 changed files with 953 additions and 513 deletions
+5 -3
View File
@@ -20,6 +20,8 @@ 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:
@@ -65,7 +67,7 @@ jobs:
run: |
cargo install \
--git https://github.com/chenxiaolong/cargo-android \
--tag v0.1.1
--tag v0.1.3
- name: Get version
id: get_version
@@ -84,7 +86,7 @@ jobs:
done
- name: Cache Rust dependencies
uses: Swatinem/rust-cache@82a92a6e8fbeee089604da2575dc567ae9ddeaab # v2.7.5
uses: Swatinem/rust-cache@f0deed1e0edfc6a9be95417288c0e1099b1eeec3 # v2.7.7
with:
key: ${{ matrix.artifact.name }}
@@ -155,7 +157,7 @@ jobs:
run: cp LICENSE README.md target/output/
- name: Archive executable
uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0
uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1
with:
name: avbroot-${{ steps.get_version.outputs.version }}-${{ matrix.artifact.name }}
path: |
+1 -1
View File
@@ -13,4 +13,4 @@ jobs:
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Run cargo-deny
uses: EmbarkStudios/cargo-deny-action@e2f4ede4a4e60ea15ff31bc0647485d80c66cfba # v2.0.4
uses: EmbarkStudios/cargo-deny-action@34899fc7ba81ca6268d5947a7a16b4649013fea1 # v2.0.11
+1 -1
View File
@@ -27,7 +27,7 @@ jobs:
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Create release
uses: softprops/action-gh-release@7b4da11513bf3f43f9999e90eabced41ab8bb048 # v2.2.0
uses: softprops/action-gh-release@c95fe1489396fe8a9eb87c0abf8aa5b2ef267fda # v2.2.1
with:
tag_name: v${{ steps.get_version.outputs.version }}
name: Version ${{ steps.get_version.outputs.version }}
+36
View File
@@ -7,6 +7,29 @@
to update the actual links at the bottom of the file.
-->
### 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])
* Add advanced option to skip replacing the OTA certificate in the system image ([Discussion #417], [PR #418])
* Switch to stable bzip2-rs release and use zlib-rs as the backend for flate2 ([PR #421])
* Switch to the aws-lc cryptography library for SHA1 and SHA2 hashing ([PR #422])
* The ring library is no longer maintained
* Fix incorrect `Partitions aren't protected by AVB: system` warning when using `--skip-system-ota-cert` ([PR #423])
* Discard unneeded temp file sooner when using `--skip-system-ota-cert` ([PR #424])
* Make `avbroot lp`'s parser less strict so that it can load on-device `super` partitions ([PR #425])
* The on-disk layout on virtual A/B devices violates some requirements stated in AOSP's documentation
* Update dependencies ([PR #427])
### Version 3.12.0
* Add new `-p <name>` option to `avbroot ota extract` for extracting specific partitions ([PR #408])
* Deprecate the `--boot-only` option in `avbroot ota extract` ([PR #408])
* The option will remain indefinitely for backwards compatibility, but is hidden from `--help`
* Add support for extracting the embedded OTA certificate and AVB public key in `avbroot ota extract` ([PR #409])
* Rename `avbroot key extract-avb` to `avbroot key encode-avb` for consistency with `avbroot key decode-avb` ([PR #410])
* The old syntax will remain supported indefinitely for backwards compatibility, but is hidden from `--help`
* Update dependencies ([PR #411])
### Version 3.11.0
* Fix crash when ignoring warning about `--magisk-preinit-device` not being specified ([PR #394])
@@ -270,6 +293,7 @@ Behind-the-scenes changes:
[Discussion #235]: https://github.com/chenxiaolong/avbroot/discussions/235
[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
[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
@@ -430,3 +454,15 @@ Behind-the-scenes changes:
[PR #402]: https://github.com/chenxiaolong/avbroot/pull/402
[PR #403]: https://github.com/chenxiaolong/avbroot/pull/403
[PR #404]: https://github.com/chenxiaolong/avbroot/pull/404
[PR #408]: https://github.com/chenxiaolong/avbroot/pull/408
[PR #409]: https://github.com/chenxiaolong/avbroot/pull/409
[PR #410]: https://github.com/chenxiaolong/avbroot/pull/410
[PR #411]: https://github.com/chenxiaolong/avbroot/pull/411
[PR #415]: https://github.com/chenxiaolong/avbroot/pull/415
[PR #418]: https://github.com/chenxiaolong/avbroot/pull/418
[PR #421]: https://github.com/chenxiaolong/avbroot/pull/421
[PR #422]: https://github.com/chenxiaolong/avbroot/pull/422
[PR #423]: https://github.com/chenxiaolong/avbroot/pull/423
[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
Generated
+438 -251
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -4,7 +4,7 @@ members = ["avbroot", "e2e", "fuzz", "xtask"]
resolver = "2"
[workspace.package]
version = "3.11.0"
version = "3.13.0"
license = "GPL-3.0-only"
edition = "2021"
repository = "https://github.com/chenxiaolong/avbroot"
+29 -10
View File
@@ -121,7 +121,7 @@ When patching OTAs for multiple devices, generating unique keys for each device
2. Convert the public key portion of the AVB signing key to the AVB public key metadata format. This is the format that the bootloader requires when setting the custom root of trust.
```bash
avbroot key extract-avb -k avb.key -o avb_pkmd.bin
avbroot key encode-avb -k avb.key -o avb_pkmd.bin
```
3. Generate a self-signed certificate for the OTA signing key. This is used by recovery to verify OTA updates when sideloading.
@@ -297,8 +297,7 @@ Magisk versions 25211 and newer require a writable partition for storing custom
```bash
avbroot ota extract \
--input /path/to/ota.zip \
--directory . \
--boot-only
--partition <name> # init_boot or boot, depending on device
```
2. Patch the boot image via the Magisk app. This **MUST** be done on the target device or a device of the same model! The partition name will be incorrect if patched from Magisk on a different device model.
@@ -385,11 +384,28 @@ Note that avbroot will validate that the prepatched image is compatible with the
avbroot can be used for just re-signing an OTA by specifying `--rootless` instead of `--magisk`/`--prepatched`. With this option, the patched OTA will not be rooted. The only modification applied is the replacement of the OTA verification certificate so that the OS can be upgraded with future (patched) OTAs.
### Skipping recovery OTA certificate patches
### Skipping OTA certificate patches
avbroot can skip modifying `otacerts.zip` in the recovery image with the `--skip-recovery-ota-cert` option. **Do not do this unless you have a good reason to do so.** (For example, if you've already manually inserted the OTA certificate into a boot image specified with `--prepatched` or `--replace`.) When this option is used with `--rootless` (and `--dsu` is not specified), then no modifications are performed on any boot image besides ensuring they are properly signed.
avbroot can skip modifying `otacerts.zip` with the `--skip-system-ota-cert` and `--skip-recovery-ota-cert` options. **Do not use these unless you have a good reason to do so.**
When manually adding the OTA certificate to a boot image, [verifying the patched OTA](#verifying-otas) afterwards is recommended to ensure that it was properly done.
When `--skip-system-ota-cert` is used, the OTA certificates in the `system` partition will not be modified. This prevents custom OTA updater apps from installing further patched OTAs while booted into Android.
When `--skip-recovery-ota-cert` is used, the OTA certificates in the `vendor_boot` or `recovery` partition will not be modified. **This prevents sideloading further patched OTAs from recovery mode.**
If `--skip-recovery-ota-cert` is used because the OTA certificate was already manually added to the boot image, then [verifying the patched OTA](#verifying-otas) afterwards is recommended to ensure that it was properly done. The verification process is only capable of checking the boot image's copy of the OTA certificates, not the system image's copy of them.
### Skipping all patches
To have avbroot make the absolute minimal changes:
* Specify `--skip-system-ota-cert`
* Specify `--skip-recovery-ota-cert`
* Specify `--rootless`
* Omit `--dsu`
This will re-sign the `vbmeta` partition and the OTA with the custom keys, but leave all other partitions untouched.
**This should only be used for advanced troubleshooting.** Without the OTA certificate patches, the resulting OTA will not be able to install further updates.
### Replacing partitions
@@ -451,17 +467,20 @@ avbroot prompts for the private key passphrases interactively by default. To run
* Use unencrypted private keys. This is strongly discouraged.
### Extracting the entire OTA
### Extracting an OTA
To extract all images contained within the OTA's `payload.bin`, run:
To extract the partition images contained within an OTA's `payload.bin`, run:
```bash
avbroot ota extract \
--input /path/to/ota.zip \
--directory extracted \
--all
--directory extracted
```
By default, this only extracts the images that could potentially be patched by avbroot. To extract all images, use the `--all` option. To extract specific images, use the `--partition <name>` option, which can be specified multiple times.
This command also supports extracting the embedded OTA certificate and AVB public key using the `--cert-ota` and `--public-key-avb` options. To extract only these components, pass in `--none` to skip extracting partition images.
### Zip write mode
By default, avbroot uses streaming writes for the output OTA during patching. This means it computes the sha256 digest for the digital signature as the file is being written. This mode causes the zip file to contain data descriptors, which is part of the zip standard and works on the vast majority of devices. However, some devices may have broken zip file parsers and fail to properly read OTA zip files containing data descriptors. If this is the case, pass in `--zip-mode seekable` when patching.
+66 -10
View File
@@ -43,7 +43,7 @@ avbroot модифицирует следующие образы:
## Использование
1. Убедитесь, что вы ознакомились и поняли указанные выше [предостережения.](#предостережения)
1. Убедитесь, что вы ознакомились и поняли указанные выше [предостережения.](#предостережения)
2. Скачайте последнюю версию со страницы [релизов.](https://github.com/chenxiaolong/avbroot/releases) Чтобы сверить цифровую подпись, см. раздел [проверки цифровых подписей.](#проверка-цифровых-подписей)
@@ -107,7 +107,7 @@ avbroot модифицирует следующие образы:
Первые два компонента подписываются ключом AVB, а последние два – ключом OTA. Можно использовать один и тот же ключ, однако в следующих шагах описано, как сгенерировать два отдельных.
Если вы патчите OTA сразу для нескольких устройств, настоятельно рекомендуется генерировать уникальные ключи для каждого девайса – так вы защитите себя от случайной прошивки неподходящего OTA для другого телефона.
Если вы патчите OTA сразу для нескольких устройств, настоятельно рекомендуется генерировать уникальные ключи для каждого девайса – так вы защитите себя от случайной прошивки неподходящего OTA для другого телефона.
1. Сгенерируйте ключи подписи для AVB и OTA.
@@ -119,7 +119,7 @@ avbroot модифицирует следующие образы:
2. Преобразуйте публичную часть ключа подписи AVB в формат метаданных публичного ключа AVB. Именно этот формат используется в загрузчике устройства для установки пользовательского ключа.
```bash
avbroot key extract-avb -k avb.key -o avb_pkmd.bin
avbroot key encode-avb -k avb.key -o avb_pkmd.bin
```
3. Сгенерируйте самоподписанный сертификат для ключа подписи OTA. Он используется режимом Recovery для проверки подписи OTA при сайдлоадинге обновления.
@@ -295,6 +295,7 @@ Magisk версии 25211 и новее требует наличие разде
--input /path/to/ota.zip \
--directory . \
--boot-only
--partition <название раздела> # init_boot или boot, в зависимости от устройства
```
2. Теперь нужно пропатчить загрузочный образ с помощью приложения Magisk. Это **ДОЛЖНО** быть сделано именно на целевом устройстве или устройстве той же модели! Имя раздела будет неверным и не подойдет, если пропатчить образ на устройстве иной модели.
@@ -381,11 +382,16 @@ avbroot может подменить используемый загрузоч
avbroot можно использовать для простого переподписания OTA, указав аргумент `--rootless` вместо `--magisk`/`--prepatched`. В таком случае пропатченный OTA не будет рутирован. Единственная модификация, которая будет применена – это замена сертификата проверки OTA, чтобы систему можно было обновлять с помощью будущих пропатченных OTA.
### Пропуск патчинга сертификата OTA в разделе Recovery
### Пропуск патчинга сертификата OTA
avbroot может пропустить изменение файла `otacerts.zip` в разделе Recovery с помощью опции `--skip-recovery-ota-cert`. **Не используйте эту функцию, если на то нет веской причины.** (Например, если вы уже самостоятельно встроили сертификат OTA в загрузочный образ (`boot.img`) и передаете его программе через опции `--prepatched` или `--replace`.) Если эта опция применяется совместно с `--rootless` (и без указания параметра `--dsu`), то в загрузочный образ не будут внесены никакие изменения, кроме обеспечения его корректной подписи.
avbroot может пропускать изменение `otacerts.zip` с помощью аргументов `--skip-system-ota-cert` и `--skip-recovery-ota-cert`. **Не используйте их без веской причины.** (Например, если вы уже самостоятельно встроили сертификат OTA в загрузочный (boot) образ и передаете его программе через опции `--prepatched` или `--replace`.)
В противном случае, на устройстве может не остаться возможности устанавливать дальнейшие обновления.
Если вы вручную добавили сертификат OTA в загрузочный образ, рекомендуем [предварительно проверить пропатченный OTA.](#проверка-ota)
При использовании `--skip-system-ota-cert`, никаких изменений в образ `system` не вносится.
При использовании `--skip-recovery-ota-cert` совместно с `--rootless` и без указания `--dsu`, не вносится никаких изменений в загрузочные образы, кроме обеспечения их корректной подписи.
Если вы вручную добавили сертификат OTA в загрузочный (boot) образ, рекомендуем [предварительно проверить пропатченный OTA.](#проверка-ota)
### Подмена разделов
@@ -441,17 +447,20 @@ Verified boot is disabled by vbmeta's header flags: 0x3
* Использовать незашифрованные приватные ключи. Крайне не рекомендуется.
### Извлечение всей OTA
### Извлечение образов из OTA
Чтобы извлечь все образы, содержащиеся в `payload.bin`, используйте команду:
Чтобы извлечь образы разделов, содержащихся в `payload.bin`, используйте команду:
```bash
avbroot ota extract \
--input /путь/к/ota.zip \
--directory extracted \
--all
--directory extracted
```
По умолчанию извлекаются только те образы, которые потенциально могут быть пропатчены с помощью avbroot. Чтобы извлечь все образы, используйте опцию `--all`. Для извлечения конкретных образов используйте опцию `--partition <название раздела>`, которую можно указать несколько раз.
Эта команда также поддерживает извлечение встроенного сертификата OTA и публичного ключа AVB с помощью опций `--cert-ota` и `--public-key-avb`. Чтобы извлечь только эти компоненты, укажите аргумент `--none`, чтобы пропустить извлечение образов разделов.
### Режим записи ZIP
По умолчанию, avbroot использует потоковую запись для вывода OTA во время патчинга. Это означает, что он вычисляет дайджест sha256 для цифровой подписи одновременно с записью файла. Такой режим приводит к тому, что в ZIP-файле появляются описатели данных, что является частью стандарта ZIP и работает на подавляющем большинстве устройств. Однако некоторые устройства могут иметь некорректно работающие парсеры ZIP-файлов и не смогут правильно прочитать ZIP-файлы OTA, содержащие описатели данных. Если это так, используйте опцию `--zip-mode seekable` при патчинге.
@@ -480,6 +489,53 @@ avbroot поддерживает делегирование всех опера
Обратите внимание, что avbroot проверит подпись, возвращенную внешней программой, на соответствие с публичным ключом. Это гарантирует, что процесс патчинга завершится ошибкой, если был использован неправильный приватный ключ.
### Размер страницы 16 КБ в настройках для разработчиков
На современных устройствах с Android 16 и выше, в настройках для разработчиков может появиться опция переключения на ядро с размером страницы 16 КБ. Однако, эта функция не будет работать в системе, пропатченной с помощью avbroot, поскольку переключение данной настройки осуществляется путём установки инкрементальной OTA:
* `/vendor/boot_otas/boot_ota_16k.zip` — используется для переключения на ядро с размером страницы 16 КБ (в разделе `boot` уже должно быть прошито ядро с размером страницы 4K)
* `/vendor/boot_otas/boot_ota_4k.zip` — используется для переключения на ядро с размером страницы 4 КБ (в разделе `boot` уже должно быть прошито ядро с размером страницы 16K)
Эти файлы (в `boot_otas`) невозможно прошить на системе, пропатченной avbroot, потому что `payload.bin` внутри них подписан ключом производителя. Кроме того, это неполноценные OTA-файлы: у них нет метаданных, характерных для OTA, а сам zip-файл не подписан. Это просто обычный архив, который содержит подписанный `payload.bin`.
Поддержка `boot_otas` не планируется. Это потребует реализации функционала для модификации ФС в инкрементальных OTA и их дальнейшей обработки, что сделать очень непросто.
Если вы всё же хотите завести эту функцию, можно попробовать вручную подписать файлы в `boot_otas` собственным ключом. Поскольку инкрементальные OTA не пересоздаются, раздел `boot` должен оставаться без изменений во время выполнения команды `avbroot ota patch`.
1. Распакуйте `vendor.img` с помощью avbroot и [afsr](https://github.com/chenxiaolong/afsr):
```bash
avbroot avb unpack -i vendor.img
afsr unpack -i raw.img
```
2. Извлеките `payload.bin` из `boot_otas/boot_ota_16k.zip`.
3. Переподпишите `payload.bin` вашим OTA-ключом:
```bash
avbroot payload repack \
-i payload.bin.orig \
-o payload.bin \
-k ota.key \
--output-properties payload_properties.txt
```
4. Создайте новый zip, включающий `payload.bin` и `payload_properties.txt`. Файлы должны быть добавлены без сжатия (например, с помощью `zip -0`).
5. Повторите эту процедуру для `boot_otas/boot_ota_4k.zip`.
6. Соберите `vendor.img` обратно и подпишите его вашим AVB-ключом:
```bash
afsr pack -o raw.img
avbroot avb pack -o vendor.img -k avb.key --recompute-size
```
7. Пропатчите обычный OTA-архив с прошивкой, подменив `vendor` на модифицированный образ:
```bash
avbroot ota patch \
--replace vendor <модифицированный vendor.img> \
<дальше указываются аргументы, как при обычном патчинге>
```
## Сборка из исходного кода
Убедитесь, что у вас установлен [набор инструментов Rust.](https://www.rust-lang.org/ru/) Затем выполните:
+9 -15
View File
@@ -10,9 +10,15 @@ 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"
bzip2 = { version = "0.5.1", default-features = false, features = ["libbz2-rs-sys"] }
cap-std = "3.0.0"
cap-tempfile = "3.0.0"
clap = { version = "4.4.1", features = ["derive"] }
@@ -22,27 +28,22 @@ const-oid = "0.9.5"
crc32fast = "1.4.2"
ctrlc = "3.4.0"
dlv-list = "0.6.0"
flate2 = "1.0.27"
flate2 = { version = "1.0.29", features = ["zlib-rs"] }
gf256 = { version = "0.3.0", features = ["rs"] }
hex = { version = "0.4.3", features = ["serde"] }
liblzma = "0.3.0"
lz4_flex = "0.11.1"
memchr = "2.6.0"
miniz_oxide = "0.8.0"
num-bigint-dig = "0.8.4"
num-traits = "0.2.16"
passterm = "2.0.3"
phf = { version = "0.11.2", features = ["macros"] }
pkcs8 = { version = "0.10.2", features = ["encryption", "pem"] }
prost = "0.13.1"
# We can't upgrade to 0.9.0 until rsa updates its rand_core dependency.
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.0"
rsa = { version = "0.9.2", features = ["sha1", "sha2"] }
serde = { version = "1.0.188", features = ["derive"] }
sha1 = "0.10.5"
@@ -57,13 +58,6 @@ x509-cert = { version = "0.2.4", features = ["builder"] }
zerocopy = { version = "0.8.10", features = ["std"] }
zerocopy-derive = "0.8.5"
# Waiting for next stable release.
[dependencies.bzip2]
git = "https://github.com/trifectatechfoundation/bzip2-rs"
rev = "15258feb0cdc1114d6fc6b936254c4f4e1d5730c"
default-features = false
features = ["libbz2-rs-sys"]
# https://github.com/zip-rs/zip/pull/383
[dependencies.zip]
git = "https://github.com/chenxiaolong/zip"
@@ -76,7 +70,7 @@ libc = "0.2.158"
rustix = { version = "0.38.9", default-features = false, features = ["process"] }
[build-dependencies]
constcat = "0.5.0"
constcat = "0.6.0"
prost-build = "0.13.1"
protox = "0.7.0"
+3
View File
@@ -30,10 +30,13 @@ pub enum Command {
Payload(payload::PayloadCli),
Sparse(sparse::SparseCli),
/// (Deprecated: Use `avbroot ota patch` instead.)
#[command(hide = true)]
Patch(ota::PatchCli),
/// (Deprecated: Use `avbroot ota extract` instead.)
#[command(hide = true)]
Extract(ota::ExtractCli),
/// (Deprecated: Use `avbroot boot magisk-info` instead.)
#[command(hide = true)]
MagiskInfo(boot::MagiskInfoCli),
}
+2 -2
View File
@@ -599,7 +599,7 @@ pub fn verify_descriptors(
fn compute_digest_recursive(
directory: &Dir,
name: &str,
context: &mut ring::digest::Context,
context: &mut aws_lc_rs::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 = ring::digest::Context::new(&ring::digest::SHA256);
let mut context = aws_lc_rs::digest::Context::new(&aws_lc_rs::digest::SHA256);
compute_digest_recursive(directory, name, &mut context, 2, &mut seen, cancel_signal)?;
+7 -4
View File
@@ -46,7 +46,7 @@ pub fn key_main(cli: &KeyCli) -> Result<()> {
crypto::write_pem_cert_file(&c.output, &cert)
.with_context(|| format!("Failed to write certificate: {:?}", c.output))?;
}
KeyCommand::ExtractAvb(c) => {
KeyCommand::ExtractAvb(c) | KeyCommand::EncodeAvb(c) => {
let public_key = if let Some(p) = &c.input.key {
let passphrase = get_passphrase_source(&c.passphrase, p);
let private_key = crypto::read_pem_key_file(p, &passphrase)
@@ -150,12 +150,12 @@ struct GenerateCertCli {
validity: u64,
}
/// Extract the AVB public key from a private key or certificate.
/// Convert a key or certificate to an AVB-encoded public key.
///
/// The public key is stored in both the private key and the certificate. Either
/// one can be used interchangeably.
#[derive(Debug, Parser)]
struct ExtractAvbCli {
struct EncodeAvbCli {
/// Path to output AVB public key.
#[arg(short, long, value_name = "FILE", value_parser)]
output: PathBuf,
@@ -183,7 +183,10 @@ struct DecodeAvbCli {
enum KeyCommand {
GenerateKey(GenerateKeyCli),
GenerateCert(GenerateCertCli),
ExtractAvb(ExtractAvbCli),
/// (Deprecated: Use `avbroot key encode-avb` instead.)
#[command(hide = true)]
ExtractAvb(EncodeAvbCli),
EncodeAvb(EncodeAvbCli),
DecodeAvb(DecodeAvbCli),
}
+150 -33
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::{
@@ -45,7 +45,7 @@ use crate::{
},
stream::{
self, CountingWriter, FromReader, HashingWriter, PSeekFile, ReadSeekReopen, Reopen,
SectionReader, ToWriter, WriteSeekReopen,
SectionReader, SharedCursor, ToWriter, WriteSeekReopen,
},
util,
};
@@ -79,8 +79,9 @@ impl RequiredImages {
let partitions = manifest
.partitions
.iter()
.map(|p| p.partition_name.clone())
.map(|p| &p.partition_name)
.filter(|n| Self::is_boot(n) || Self::is_system(n) || Self::is_vbmeta(n))
.cloned()
.collect();
Self(partitions)
@@ -372,8 +373,9 @@ fn get_vbmeta_patch_order(
.get_mut(vbmeta_name.as_str())
.unwrap()
.insert(partition_name.to_owned());
missing.remove(partition_name);
}
missing.remove(partition_name);
}
}
@@ -591,7 +593,12 @@ fn update_vbmeta_headers(
// Only sign and rewrite the image if we need to. Some vbmeta images may
// have no dependencies and are only being processed to ensure that the
// flags are set to a sane value.
if parent_header != &orig_parent_header {
//
// The root vbmeta image is always signed because it is possible to
// invoke avbroot is a way that no modifications are made (rootless +
// skipping recovery otacerts.zip patch). We still want the result to be
// bootable.
if parent_header != &orig_parent_header || name == "vbmeta" {
parent_header
.set_algo_for_key(key)
.with_context(|| format!("Failed to set signature algorithm: {name}"))?;
@@ -722,6 +729,7 @@ fn patch_ota_payload(
writer: impl Write,
external_images: &HashMap<String, PathBuf>,
boot_patchers: &[Box<dyn BootImagePatch + Sync>],
skip_system_ota_cert: bool,
clear_vbmeta_flags: bool,
key_avb: &RsaSigningKey,
key_ota: &RsaSigningKey,
@@ -776,18 +784,23 @@ fn patch_ota_payload(
cancel_signal,
)?;
// Main patching operation is done. Unmodified boot images no longer need to
// be kept around.
input_files
.retain(|n, f| !(f.state == InputFileState::Extracted && RequiredImages::is_boot(n)));
let (system_target, system_ranges) = patch_system_image(
&required_images,
&mut input_files,
cert_ota,
key_avb,
cancel_signal,
)?;
let system_result = if skip_system_ota_cert {
None
} else {
Some(patch_system_image(
&required_images,
&mut input_files,
cert_ota,
key_avb,
cancel_signal,
)?)
};
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)?;
@@ -821,8 +834,12 @@ fn patch_ota_payload(
&mut header,
// We can only perform the optimization of avoiding
// recompression if the image came from the original payload.
if name == system_target && !external_images.contains_key(&name) {
Some(&system_ranges)
if let Some((system_target, system_ranges)) = &system_result {
if name == *system_target && !external_images.contains_key(&name) {
Some(system_ranges)
} else {
None
}
} else {
None
},
@@ -914,6 +931,7 @@ fn patch_ota_zip(
mut zip_writer: &mut ZipWriter<impl Write>,
external_images: &HashMap<String, PathBuf>,
boot_patchers: &[Box<dyn BootImagePatch + Sync>],
skip_system_ota_cert: bool,
clear_vbmeta_flags: bool,
zip_mode: ZipMode,
key_avb: &RsaSigningKey,
@@ -1036,6 +1054,7 @@ fn patch_ota_zip(
&mut writer,
external_images,
boot_patchers,
skip_system_ota_cert,
clear_vbmeta_flags,
key_avb,
key_ota,
@@ -1149,7 +1168,7 @@ pub fn extract_payload(
)
.context("Failed to extract images from payload")?;
info!("Successfully extracted OTA");
info!("Successfully extracted images from payload");
Ok(())
}
@@ -1182,7 +1201,7 @@ fn verify_partition_hashes(
let mut writer = HashingWriter::new(
io::sink(),
ring::digest::Context::new(&ring::digest::SHA256),
aws_lc_rs::digest::Context::new(&aws_lc_rs::digest::SHA256),
);
stream::copy(file, &mut writer, cancel_signal)?;
@@ -1304,6 +1323,10 @@ pub fn patch_subcommand(cli: &PatchCli, cancel_signal: &AtomicBool) -> Result<()
assert!(cli.root.rootless);
};
if cli.skip_system_ota_cert {
warn!("Not inserting OTA cert into system image; sideloading further updates may fail");
}
if cli.skip_recovery_ota_cert {
warn!("Not inserting OTA cert into recovery image; sideloading further updates may fail");
} else {
@@ -1346,6 +1369,7 @@ pub fn patch_subcommand(cli: &PatchCli, cancel_signal: &AtomicBool) -> Result<()
&mut zip_writer,
&external_images,
&boot_patchers,
cli.skip_system_ota_cert,
cli.clear_vbmeta_flags,
cli.zip_mode,
&key_avb,
@@ -1409,7 +1433,7 @@ pub fn extract_subcommand(cli: &ExtractCli, cancel_signal: &AtomicBool) -> Resul
warn!("Ignoring --boot-partition: deprecated and no longer needed");
}
let raw_reader = File::open(&cli.input)
let mut raw_reader = File::open(&cli.input)
.map(PSeekFile::new)
.with_context(|| format!("Failed to open for reading: {:?}", cli.input))?;
let mut zip = ZipArchive::new(BufReader::new(raw_reader.reopen()?))
@@ -1437,7 +1461,7 @@ pub fn extract_subcommand(cli: &ExtractCli, cancel_signal: &AtomicBool) -> Resul
let mut unique_images = BTreeSet::new();
if cli.all {
if cli.extract.all {
unique_images.extend(
header
.manifest
@@ -1446,16 +1470,68 @@ pub fn extract_subcommand(cli: &ExtractCli, cancel_signal: &AtomicBool) -> Resul
.map(|p| &p.partition_name)
.cloned(),
);
} else {
} else if !cli.extract.partition.is_empty() {
// We check this later too, but also do it here so we don't create a
// bunch of empty files before failing.
let valid_images = header
.manifest
.partitions
.iter()
.map(|p| &p.partition_name)
.collect::<BTreeSet<_>>();
let missing_images = cli
.extract
.partition
.iter()
.filter(|p| !valid_images.contains(p))
.collect::<Vec<_>>();
if !missing_images.is_empty() {
bail!("Invalid partitions: {}", joined(missing_images));
}
unique_images.extend(cli.extract.partition.iter().cloned());
} else if !cli.extract.none {
let images = RequiredImages::new(&header.manifest);
if cli.boot_only {
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()));
}
}
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)
.with_context(|| format!("Failed to write OTA certificate: {path:?}"))?;
}
if let Some(path) = &cli.public_key_avb {
info!("Extracting AVB public key from vbmeta image");
let mut data = SharedCursor::new();
payload::extract_image(&payload_reader, &data, &header, "vbmeta", cancel_signal)
.context("Failed to extract vbmeta image")?;
data.rewind()?;
let (header, _, _) = avb::load_image(data).context("Failed to parse vbmeta image")?;
fs::write(path, header.public_key)
.with_context(|| format!("Failed to write AVB public key: {path:?}"))?;
}
if unique_images.is_empty() {
info!("No partition images to extract");
return Ok(());
}
let authority = ambient_authority();
Dir::create_ambient_dir_all(&cli.directory, authority)
.with_context(|| format!("Failed to create directory: {:?}", cli.directory))?;
@@ -1786,7 +1862,7 @@ pub struct RootGroup {
pub rootless: bool,
}
/// Patch a full OTA zip.
/// Patch a full OTA.
#[derive(Debug, Parser)]
pub struct PatchCli {
/// Patch to original OTA zip.
@@ -1928,11 +2004,19 @@ pub struct PatchCli {
)]
pub ignore_prepatched_compat: u8,
/// Skip adding OTA certificate to system image.
///
/// DO NOT USE THIS unless you've manually added the certificate to the
/// system image already. Otherwise, installing further updates via a custom
/// OTA updater app while booted into Android will not be possible.
#[arg(long, help_heading = HEADING_OTHER)]
pub skip_system_ota_cert: bool,
/// Skip adding OTA certificate to recovery image.
///
/// DO NOT USE THIS unless you've manually added the certificate to the
/// recovery image already. Otherwise, sideloading further updates will not
/// be possible.
/// recovery image already. Otherwise, sideloading further updates while
/// booted into recovery mode will not be possible.
///
/// When this option is used with --rootless, the boot images in the OTA
/// will not be modified.
@@ -1975,7 +2059,33 @@ pub struct PatchCli {
pub boot_partition: Option<String>,
}
/// Extract partition images from an OTA zip's payload.
#[derive(Debug, Args)]
#[group(multiple = false)]
pub struct ExtractGroup {
/// Extract all images from the payload.
///
/// By default, only images that could potentially be patched by avbroot are
/// extracted.
#[arg(short, long)]
pub all: bool,
/// Don't extract anything from the payload.
///
/// This is useful for using --cert-ota and --public-key-avb without
/// extracting any partition images.
#[arg(short, long)]
pub none: bool,
/// (Deprecated: Specify an exact partition name instead.)
#[arg(long, hide = true)]
pub boot_only: bool,
/// Extract specific images from the payload.
#[arg(short, long)]
pub partition: Vec<String>,
}
/// Extract a full OTA.
#[derive(Debug, Parser)]
pub struct ExtractCli {
/// Path to OTA zip.
@@ -1986,13 +2096,8 @@ pub struct ExtractCli {
#[arg(short, long, value_parser, default_value = ".")]
pub directory: PathBuf,
/// Extract all images from the payload.
#[arg(short, long, group = "extract")]
pub all: bool,
/// Extract only the boot image.
#[arg(long, group = "extract")]
pub boot_only: bool,
#[command(flatten)]
pub extract: ExtractGroup,
/// (Deprecated: no longer needed)
#[arg(long, value_name = "PARTITION", hide = true)]
@@ -2001,6 +2106,18 @@ pub struct ExtractCli {
/// Generate fastboot info files.
#[arg(long)]
pub fastboot: bool,
/// Extract OTA certificate to file.
///
/// This is not extracted by default.
#[arg(long, value_name = "FILE", value_parser)]
pub cert_ota: Option<PathBuf>,
/// Extract AVB public key to file.
///
/// This is not extracted by default.
#[arg(long, value_name = "FILE", value_parser)]
pub public_key_avb: Option<PathBuf>,
}
/// Verify signatures of an OTA.
+11 -15
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2023-2024 Andrew Gunnerson
// SPDX-FileCopyrightText: 2023-2025 Andrew Gunnerson
// SPDX-License-Identifier: GPL-3.0-only
use std::{
@@ -686,21 +686,17 @@ pub fn parse_cms(data: &[u8]) -> Result<SignedData> {
Ok(sd)
}
/// Get a list of all standard X509 certificates contained within a
/// Get an iterator to all standard X509 certificates contained within a
/// [`SignedData`] structure.
pub fn get_cms_certs(sd: &SignedData) -> Vec<Certificate> {
sd.certificates.as_ref().map_or_else(Vec::new, |certs| {
certs
.0
.iter()
.filter_map(|cc| {
if let CertificateChoices::Certificate(c) = cc {
Some(c.clone())
} else {
None
}
})
.collect()
pub fn iter_cms_certs(sd: &SignedData) -> impl Iterator<Item = &Certificate> {
sd.certificates.iter().flat_map(|certs| {
certs.0.iter().filter_map(|cc| {
if let CertificateChoices::Certificate(c) = cc {
Some(c)
} else {
None
}
})
})
}
+10 -10
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, &ring::digest::SHA256, b"")
/// let size = HashTree::new(4096, &aws_lc_rs::digest::SHA256, b"")
/// .compute_level_offsets(8 * 1024 * 1024 * 1024)
/// .unwrap()
/// .first()
@@ -163,11 +163,11 @@ pub enum Error {
type Result<T> = std::result::Result<T, Error>;
pub(crate) fn ring_algorithm(name: &str, for_verify: bool) -> Result<&'static Algorithm> {
pub(crate) fn digest_algorithm(name: &str, for_verify: bool) -> Result<&'static Algorithm> {
match name {
"sha1" if for_verify => Ok(&ring::digest::SHA1_FOR_LEGACY_USE_ONLY),
"sha256" => Ok(&ring::digest::SHA256),
"sha512" => Ok(&ring::digest::SHA512),
"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),
a => Err(Error::UnsupportedHashAlgorithm(a.to_owned())),
}
}
@@ -534,7 +534,7 @@ impl HashTreeDescriptor {
ranges: Option<&[Range<u64>]>,
cancel_signal: &AtomicBool,
) -> Result<()> {
let algorithm = ring_algorithm(&self.hash_algorithm, false)?;
let algorithm = digest_algorithm(&self.hash_algorithm, false)?;
let hash_tree = HashTree::new(self.data_block_size, algorithm, &self.salt);
let (root_digest, hash_tree_data) = match ranges {
Some(r) => {
@@ -642,7 +642,7 @@ impl HashTreeDescriptor {
) -> Result<()> {
self.check_offsets()?;
let algorithm = ring_algorithm(&self.hash_algorithm, true)?;
let algorithm = digest_algorithm(&self.hash_algorithm, true)?;
util::check_bounds(self.tree_size, ..=HASH_TREE_MAX_SIZE)
.map_err(|e| Error::IntOutOfBounds("HashTree::tree_size", e))?;
@@ -905,8 +905,8 @@ impl HashDescriptor {
reader: impl Read,
for_verify: bool,
cancel_signal: &AtomicBool,
) -> Result<ring::digest::Digest> {
let algorithm = ring_algorithm(&self.hash_algorithm, for_verify)?;
) -> Result<aws_lc_rs::digest::Digest> {
let algorithm = digest_algorithm(&self.hash_algorithm, for_verify)?;
let mut context = Context::new(algorithm);
context.update(&self.salt);
+2 -2
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};
@@ -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(&ring::digest::SHA256);
let mut context = Context::new(&aws_lc_rs::digest::SHA256);
let image_size;
if let Some(v4) = &self.v4_extra {
+4 -4
View File
@@ -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 = ring::digest::digest(&ring::digest::SHA256, &self.fec);
let digest = aws_lc_rs::digest::digest(&aws_lc_rs::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 = ring::digest::digest(&ring::digest::SHA256, &fec[..fec_size]);
let actual_digest = aws_lc_rs::digest::digest(&aws_lc_rs::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();
ring::digest::digest(&ring::digest::SHA256, &buf)
aws_lc_rs::digest::digest(&aws_lc_rs::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();
ring::digest::digest(&ring::digest::SHA256, &buf)
aws_lc_rs::digest::digest(&aws_lc_rs::digest::SHA256, &buf)
};
assert_eq!(repaired_digest.as_ref(), orig_digest.as_ref());
+14 -14
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 = ring::digest::digest(algorithm, hash_tree_data);
let actual = ring::digest::digest(algorithm, &actual_hash_tree_data);
let expected = aws_lc_rs::digest::digest(algorithm, hash_tree_data);
let actual = aws_lc_rs::digest::digest(algorithm, &actual_hash_tree_data);
return Err(Error::InvalidHashTree {
expected: hex::encode(expected),
@@ -492,8 +492,8 @@ impl HashTreeImage {
const MAGIC: &'static [u8; 16] = b"avbroot!hashtree";
const VERSION: u16 = 1;
fn ring_algorithm(name: &str) -> Result<&'static Algorithm> {
avb::ring_algorithm(name, false)
fn digest_algorithm(name: &str) -> Result<&'static Algorithm> {
avb::digest_algorithm(name, false)
.map_err(|_| Error::UnsupportedHashAlgorithm(name.to_owned().into_bytes()))
}
@@ -509,8 +509,8 @@ impl HashTreeImage {
.reopen_boxed()
.and_then(|mut f| f.seek(SeekFrom::End(0)))
.map_err(Error::InputReopen)?;
let ring_algorithm = Self::ring_algorithm(algorithm)?;
let hash_tree = HashTree::new(block_size, ring_algorithm, salt);
let digest_algorithm = Self::digest_algorithm(algorithm)?;
let hash_tree = HashTree::new(block_size, digest_algorithm, salt);
let (root_digest, hash_tree_data) = hash_tree.generate(input, image_size, cancel_signal)?;
Ok(Self {
@@ -530,8 +530,8 @@ impl HashTreeImage {
ranges: &[Range<u64>],
cancel_signal: &AtomicBool,
) -> Result<()> {
let ring_algorithm = Self::ring_algorithm(&self.algorithm)?;
let hash_tree = HashTree::new(self.block_size, ring_algorithm, &self.salt);
let digest_algorithm = Self::digest_algorithm(&self.algorithm)?;
let hash_tree = HashTree::new(self.block_size, digest_algorithm, &self.salt);
self.root_digest = hash_tree.update(
input,
@@ -550,8 +550,8 @@ impl HashTreeImage {
input: &(dyn ReadSeekReopen + Sync),
cancel_signal: &AtomicBool,
) -> Result<()> {
let ring_algorithm = Self::ring_algorithm(&self.algorithm)?;
let hash_tree = HashTree::new(self.block_size, ring_algorithm, &self.salt);
let digest_algorithm = Self::digest_algorithm(&self.algorithm)?;
let hash_tree = HashTree::new(self.block_size, digest_algorithm, &self.salt);
hash_tree.verify(
input,
@@ -662,7 +662,7 @@ mod tests {
#[test]
fn calculate_level_ranges() {
let hash_tree = HashTree::new(4096, &ring::digest::SHA256, &[]);
let hash_tree = HashTree::new(4096, &aws_lc_rs::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, &ring::digest::SHA256, b"Salt");
let hash_tree = HashTree::new(4096, &aws_lc_rs::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, &ring::digest::SHA256, b"Salt");
let hash_tree = HashTree::new(64, &aws_lc_rs::digest::SHA256, b"Salt");
let mut input = SharedCursor::new();
// Try input smaller than one block.
+53 -55
View File
@@ -1,8 +1,7 @@
// SPDX-FileCopyrightText: 2024 Andrew Gunnerson
// SPDX-FileCopyrightText: 2024-2025 Andrew Gunnerson
// SPDX-License-Identifier: GPL-3.0-only
use std::{
cmp::Ordering,
fmt,
io::{self, Read, Seek, Write},
mem,
@@ -152,10 +151,8 @@ pub enum Error {
ExtentTypeZeroNotEmpty { index: usize },
#[error("Extent #{index}: Invalid type: {extent_type}")]
ExtentInvalidType { index: usize, extent_type: u32 },
#[error("Extent #{index}: Overlaps previous extent")]
ExtentOverlapsPrevious { index: usize },
#[error("Extent #{index}: Earlier block device index than previous extent")]
ExtentDeviceNotConsecutive { index: usize },
#[error("Extent #{index}: Overlaps another extent: #{other}")]
ExtentOverlapsAnother { index: usize, other: usize },
#[error("Extent #{index}: Block device index too large")]
ExtentDeviceIndexTooLarge { index: usize },
// Partition group errors.
@@ -304,7 +301,7 @@ impl RawGeometry {
let mut copy = *self;
copy.checksum.fill(0);
let digest = ring::digest::digest(&ring::digest::SHA256, copy.as_bytes());
let digest = aws_lc_rs::digest::digest(&aws_lc_rs::digest::SHA256, copy.as_bytes());
if digest.as_ref() != self.checksum {
return Err(Error::GeometryInvalidDigest {
expected: hex::encode(self.checksum),
@@ -520,7 +517,7 @@ impl RawHeader {
let portion = &copy.as_bytes()[..expected_size];
let digest = ring::digest::digest(&ring::digest::SHA256, portion);
let digest = aws_lc_rs::digest::digest(&aws_lc_rs::digest::SHA256, portion);
if digest.as_ref() != self.header_checksum {
return Err(Error::HeaderInvalidDigest {
expected: hex::encode(self.header_checksum),
@@ -598,17 +595,15 @@ impl PartitionName {
fn validate(&self) -> Result<()> {
let (prefix, suffix) = self.split();
let mut has_alnum = false;
for b in prefix {
match b {
b'a'..=b'z' | b'A'..=b'Z' | b'0'..=b'9' => has_alnum = true,
b'_' => {}
_ => return Err(Error::PartitionNameInvalid(DebugString::new(self))),
}
}
// AOSP liblp's metadata_format.h says "Characters may only be
// alphanumeric or _", but AOSP creates partitions named like
// "system_b-cow".
let prefix_valid = prefix
.iter()
.all(|b| matches!(*b, b'a'..=b'z' | b'A'..=b'Z' | b'0'..=b'9' | b'_' | b'-'));
if has_alnum && is_zero(suffix) {
if prefix_valid && is_zero(suffix) {
Ok(())
} else {
Err(Error::PartitionNameInvalid(DebugString::new(self)))
@@ -1010,23 +1005,29 @@ impl RawMetadataSlot {
extent.validate(i, &self.block_devices)?;
}
// Ensure that all extents are in increasing order and not overlapping.
let mut iter = self
// Ensure that all extents are not overlapping. We have to sort here
// because the extents may not be in order when loading the super
// partition on an actual device. Also, AOSP liblp's `metadata_format.h`
// says "Gaps between extents are not allowed", but AOSP frequently
// creates this situation after a virtual A/B CoW merge.
let mut sorted_extents = self
.extents
.iter()
.filter(|e| e.target_type.get() == RawExtent::TARGET_TYPE_LINEAR)
.enumerate();
while let (Some((_, a)), Some((i, b))) = (iter.next(), iter.next()) {
match a.target_source.get().cmp(&b.target_source.get()) {
Ordering::Equal => {
if a.target_data.get() + a.num_sectors.get() > b.target_data.get() {
return Err(Error::ExtentOverlapsPrevious { index: i });
}
}
Ordering::Greater => {
return Err(Error::ExtentDeviceNotConsecutive { index: i });
}
Ordering::Less => {}
.enumerate()
.filter(|(_, e)| e.target_type.get() == RawExtent::TARGET_TYPE_LINEAR)
.collect::<Vec<_>>();
sorted_extents.sort_by_key(|(_, e)| (e.target_source, e.target_data));
for window in sorted_extents.windows(2) {
let ((a_i, a), (b_i, b)) = (window[0], window[1]);
if a.target_source == b.target_source
&& a.target_data.get() + a.num_sectors.get() > b.target_data.get()
{
return Err(Error::ExtentOverlapsAnother {
index: b_i,
other: a_i,
});
}
}
@@ -1060,14 +1061,19 @@ impl RawMetadata {
.read_exact(&mut buf)
.map_err(|e| Error::DataRead("geometry", e))?;
let image_type = if util::is_zero(&buf) {
ImageType::Normal
} else {
ImageType::Empty
};
// For non-empty images, AOSP says the first block is supposed to be
// filled with zeros, but Samsung puts their own SignerVer02 structure
// in there, so we can't rely on that.
let mut geometry = RawGeometry::ref_from_prefix(&buf).unwrap().0;
let geometry = match image_type {
ImageType::Normal => {
let image_type = match geometry.validate() {
Ok(()) => {
// This is an empty image for use with fastboot. These have no
// extra padding at the beginning of the file nor backup copies
// of the geometry and metadata structs.
ImageType::Empty
}
Err(Error::GeometryInvalidMagic(_)) => {
// This is an normal non-empty image, which has extra padding at
// the beginning to avoid having the geometry struct interpreted
// as a boot sector.
@@ -1077,7 +1083,7 @@ impl RawMetadata {
.read_exact(&mut buf)
.map_err(|e| Error::DataRead("geometry_primary", e))?;
let mut geometry = RawGeometry::ref_from_prefix(&buf).unwrap().0;
geometry = RawGeometry::ref_from_prefix(&buf).unwrap().0;
if geometry.validate().is_ok() {
// Skip the backup copy.
@@ -1094,17 +1100,9 @@ impl RawMetadata {
geometry.validate()?;
}
geometry
}
ImageType::Empty => {
// This is an empty image for use with fastboot. These have no
// extra padding at the beginning of the file nor backup copies
// of the geometry and metadata structs.
let geometry = RawGeometry::ref_from_prefix(&buf).unwrap().0;
geometry.validate()?;
geometry
ImageType::Normal
}
Err(e) => return Err(e),
};
Ok((image_type, geometry.to_owned()))
@@ -1181,7 +1179,7 @@ impl RawMetadata {
for slot in &self.slots {
#[cfg(not(fuzzing))]
{
let mut context = ring::digest::Context::new(&ring::digest::SHA256);
let mut context = aws_lc_rs::digest::Context::new(&aws_lc_rs::digest::SHA256);
context.update(slot.partitions.as_bytes());
context.update(slot.extents.as_bytes());
context.update(slot.groups.as_bytes());
@@ -1851,7 +1849,7 @@ impl TryFrom<&MetadataSlot> for RawMetadataSlot {
raw_slot.header.tables_size = offset.into();
let tables_digest = {
let mut context = ring::digest::Context::new(&ring::digest::SHA256);
let mut context = aws_lc_rs::digest::Context::new(&aws_lc_rs::digest::SHA256);
context.update(raw_slot.partitions.as_bytes());
context.update(raw_slot.extents.as_bytes());
context.update(raw_slot.groups.as_bytes());
@@ -1863,8 +1861,8 @@ impl TryFrom<&MetadataSlot> for RawMetadataSlot {
.tables_checksum
.copy_from_slice(tables_digest.as_ref());
let header_digest = ring::digest::digest(
&ring::digest::SHA256,
let header_digest = aws_lc_rs::digest::digest(
&aws_lc_rs::digest::SHA256,
&raw_slot.header.as_bytes()[..header_size],
);
raw_slot
@@ -1953,7 +1951,7 @@ impl TryFrom<&Metadata> for RawMetadata {
};
let geometry_digest =
ring::digest::digest(&ring::digest::SHA256, raw_metadata.geometry.as_bytes());
aws_lc_rs::digest::digest(&aws_lc_rs::digest::SHA256, raw_metadata.geometry.as_bytes());
raw_metadata
.geometry
.checksum
+55 -29
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::{
@@ -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::Context;
use thiserror::Error;
use x509_cert::{der::Encode, Certificate};
use zip::{result::ZipError, write::FileOptions, CompressionMethod, ZipArchive, ZipWriter};
@@ -565,11 +565,33 @@ pub fn verify_metadata(
Ok(())
}
/// Parse the CMS signature from the OTA zip comment. Returns the decoded CMS
/// [`SignedData`] structure and the length of the file (from the beginning)
/// that's covered by the signature. This does not perform any parsing of zip
/// data structures.
fn parse_ota_sig(mut reader: impl Read + Seek) -> Result<(SignedData, u64)> {
#[derive(Clone, Debug)]
pub struct OtaSignature {
/// Decoded CMS structure.
pub signed_data: SignedData,
/// Length of the file (from the beginning) that's covered by the signature.
pub hashed_size: u64,
}
impl OtaSignature {
pub fn embedded_cert(&self) -> Result<&Certificate> {
let mut iter = crypto::iter_cms_certs(&self.signed_data);
let Some(cert) = iter.next() else {
return Err(Error::NotOneCmsCertificate(0));
};
let None = iter.next() else {
return Err(Error::NotOneCmsCertificate(2 + iter.count()));
};
Ok(cert)
}
}
/// 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> {
let file_size = reader
.seek(SeekFrom::End(0))
.map_err(|e| Error::DataRead("file_size", e))?;
@@ -614,13 +636,16 @@ fn parse_ota_sig(mut reader: impl Read + Seek) -> Result<(SignedData, u64)> {
}
let sig_offset = eocd_size as usize - usize::from(abs_eoc_offset);
let sd =
let signed_data =
crypto::parse_cms(&eocd[sig_offset..eocd_size as usize - 6]).map_err(Error::CmsLoad)?;
// The signature covers everything aside from the archive comment and its
// length field.
let hashed_size = file_size - 2 - u64::from(comment_size);
Ok((sd, hashed_size))
Ok(OtaSignature {
signed_data,
hashed_size,
})
}
/// Verify an OTA zip against its embedded certificates. This function makes no
@@ -631,26 +656,19 @@ fn parse_ota_sig(mut reader: impl Read + Seek) -> Result<(SignedData, u64)> {
/// 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 (sd, hashed_size) = parse_ota_sig(&mut reader)?;
// Make sure the certificate in the CMS structure matches the otacert zip
// entry.
let certs = crypto::get_cms_certs(&sd);
if certs.len() != 1 {
return Err(Error::NotOneCmsCertificate(certs.len()));
}
let cert = &certs[0];
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.
if sd.signer_infos.0.len() != 1 {
return Err(Error::NotOneCmsSignerInfo(sd.signer_infos.0.len()));
let signers_len = ota_sig.signed_data.signer_infos.0.len();
if signers_len != 1 {
return Err(Error::NotOneCmsSignerInfo(signers_len));
}
let signer = sd.signer_infos.0.get(0).unwrap();
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
@@ -668,18 +686,26 @@ pub fn verify_ota(mut reader: impl Read + Seek, cancel_signal: &AtomicBool) -> R
// We support SHA1 for verification only.
let (algorithm, algo) = if signer.digest_alg.oid == rfc5912::ID_SHA_256 {
(&ring::digest::SHA256, SignatureAlgorithm::Sha256WithRsa)
(
&aws_lc_rs::digest::SHA256,
SignatureAlgorithm::Sha256WithRsa,
)
} else {
(
&ring::digest::SHA1_FOR_LEGACY_USE_ONLY,
&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(), hashed_size, cancel_signal)
.map_err(|e| Error::DataRead("raw_data", e))?;
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();
@@ -768,7 +794,7 @@ fn validate_eocd(eocd: &[u8]) -> Result<()> {
fn compute_signature_comment(
key: &RsaSigningKey,
cert: &Certificate,
digest: ring::digest::Digest,
digest: aws_lc_rs::digest::Digest,
) -> Result<Vec<u8>> {
let cms_signature =
crypto::cms_sign_external(key, cert, digest.as_ref()).map_err(Error::CmsSign)?;
@@ -820,7 +846,7 @@ pub struct StreamingSigningWriter<W> {
impl<W: Write> StreamingSigningWriter<W> {
pub fn new(inner: W) -> Self {
Self {
inner: HashingWriter::new(inner, Context::new(&ring::digest::SHA256)),
inner: HashingWriter::new(inner, Context::new(&aws_lc_rs::digest::SHA256)),
queue: Default::default(),
used: 0,
}
@@ -924,7 +950,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(),
ring::digest::Context::new(&ring::digest::SHA256),
aws_lc_rs::digest::Context::new(&aws_lc_rs::digest::SHA256),
);
self.rewind().map_err(|e| Error::DataRead("raw_data", e))?;
+26 -18
View File
@@ -9,9 +9,11 @@ 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 flate2::{write::GzEncoder, Compression};
use liblzma::{
stream::{Check, Stream},
write::XzDecoder,
@@ -22,7 +24,6 @@ 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;
@@ -121,6 +122,8 @@ pub enum Error {
InputOpen(String, #[source] io::Error),
#[error("Failed to open output file for partition: {0}")]
OutputOpen(String, #[source] io::Error),
#[error("Failed to GZ compress partition image chunk")]
GzCompress(#[source] io::Error),
#[error("Failed to initialize XZ encoder")]
XzInit(#[source] liblzma::stream::Error),
#[error("Failed to XZ compress partition image chunk")]
@@ -371,7 +374,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(
ring::digest::digest(&ring::digest::SHA256, b"").as_ref(),
aws_lc_rs::digest::digest(&aws_lc_rs::digest::SHA256, b"").as_ref(),
&key,
)?;
let dummy_sig_size = dummy_sig.encoded_len();
@@ -384,9 +387,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(&ring::digest::SHA256);
let mut h_partial = Context::new(&aws_lc_rs::digest::SHA256);
// Includes signatures (hashes are for properties file).
let mut h_full = Context::new(&ring::digest::SHA256);
let mut h_full = Context::new(&aws_lc_rs::digest::SHA256);
// Write header to output file.
let raw_header = RawHeader {
@@ -597,9 +600,9 @@ pub fn verify_payload(
.ok_or(Error::MissingField("signatures_size"))?;
// Excludes signatures (hashes are for signing).
let mut h_partial = Context::new(&ring::digest::SHA256);
let mut h_partial = Context::new(&aws_lc_rs::digest::SHA256);
// Includes signatures (hashes are for properties file).
let mut h_full = Context::new(&ring::digest::SHA256);
let mut h_full = Context::new(&aws_lc_rs::digest::SHA256);
// Read from the beginning to the metadata signature.
let metadata_size = header.blob_offset - u64::from(header.metadata_signature_size);
@@ -753,7 +756,7 @@ pub fn apply_operation(
writer.seek(SeekFrom::Start(out_offset)).map_err(error_fn)?;
let mut hasher = Context::new(&ring::digest::SHA256);
let mut hasher = Context::new(&aws_lc_rs::digest::SHA256);
match op.r#type() {
// Handle ZERO/DISCARD specially since they don't require access to
@@ -922,7 +925,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(&ring::digest::SHA256));
let hashing_writer = HashingWriter::new(writer, Context::new(&aws_lc_rs::digest::SHA256));
// AOSP's payload_consumer does not support checking CRC during
// decompression. Also, we intentionally pick the lowest compression level
@@ -957,17 +960,20 @@ impl VabcAlgo {
}
}
fn compressed_size(self, mut raw_data: &[u8], block_size: u32) -> u64 {
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);
// This should match CompressWorker::GetDefaultCompressionLevel() in
// AOSP's libsnapshot.
let compressed = match self {
Self::Lz4 => lz4_flex::block::compress(&raw_data[..n]),
// We use the miniz_oxide backend for flate2, but flate2 doesn't
// expose a nice function for compressing to a vec, so just use
// miniz_oxide directly.
Self::Gzip => miniz_oxide::deflate::compress_to_vec_zlib(&raw_data[..n], 9),
Self::Gzip => {
let mut encoder = GzEncoder::new(Vec::new(), Compression::best());
encoder.write_all(raw_data).map_err(Error::GzCompress)?;
encoder.finish().map_err(Error::GzCompress)?
}
};
total += compressed.len().min(n) as u64;
@@ -975,7 +981,7 @@ impl VabcAlgo {
raw_data = &raw_data[n..];
}
total
Ok(total)
}
}
@@ -1029,7 +1035,7 @@ pub fn compress_image(
let chunks_total = file_size.div_ceil(CHUNK_SIZE);
let mut bytes_compressed = 0;
let mut context_uncompressed = Context::new(&ring::digest::SHA256);
let mut context_uncompressed = Context::new(&aws_lc_rs::digest::SHA256);
let mut cow_estimate = 0;
let mut operations = vec![];
@@ -1069,8 +1075,10 @@ pub fn compress_image(
.map(
|(raw_offset, raw_data)| -> Result<(Vec<u8>, InstallOperation, u64)> {
let (data, digest_compressed) = compress_chunk(&raw_data, cancel_signal)?;
let cow_size =
vabc_algo.map_or(0, |a| a.compressed_size(&raw_data, block_size));
let cow_size = vabc_algo
.map(|a| a.compressed_size(&raw_data, block_size))
.transpose()?
.unwrap_or(0);
let extent = Extent {
start_block: Some(raw_offset / u64::from(block_size)),
@@ -1198,7 +1206,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(&ring::digest::SHA256);
let mut context_uncompressed = Context::new(&aws_lc_rs::digest::SHA256);
let mut modified_operations = vec![];
// Read the file one group at a time. This allows for some parallelization
+2 -2
View File
@@ -14,6 +14,7 @@ use std::{
sync::atomic::AtomicBool,
};
use aws_lc_rs::digest::Context;
use bstr::ByteSlice;
use liblzma::{
stream::{Check, Stream},
@@ -21,7 +22,6 @@ 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(&ring::digest::SHA256);
let mut context = Context::new(&aws_lc_rs::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 ring::digest::Context;
use aws_lc_rs::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(&ring::digest::SHA256));
let mut reader = HashingReader::new(raw_reader, Context::new(&aws_lc_rs::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(&ring::digest::SHA256));
let mut writer = HashingWriter::new(raw_writer, Context::new(&aws_lc_rs::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!(
ring::digest::digest(&ring::digest::SHA512, &data).as_ref(),
aws_lc_rs::digest::digest(&aws_lc_rs::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!(
ring::digest::digest(&ring::digest::SHA512, &data).as_ref(),
aws_lc_rs::digest::digest(&aws_lc_rs::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!(
ring::digest::digest(&ring::digest::SHA512, &data).as_ref(),
aws_lc_rs::digest::digest(&aws_lc_rs::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!(
ring::digest::digest(&ring::digest::SHA512, &data).as_ref(),
aws_lc_rs::digest::digest(&aws_lc_rs::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!(
ring::digest::digest(&ring::digest::SHA512, &data).as_ref(),
aws_lc_rs::digest::digest(&aws_lc_rs::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!(
ring::digest::digest(&ring::digest::SHA512, &data).as_ref(),
aws_lc_rs::digest::digest(&aws_lc_rs::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!(
ring::digest::digest(&ring::digest::SHA512, &data).as_ref(),
aws_lc_rs::digest::digest(&aws_lc_rs::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!(
ring::digest::digest(&ring::digest::SHA512, &data).as_ref(),
aws_lc_rs::digest::digest(&aws_lc_rs::digest::SHA512, &data).as_ref(),
sha512,
);
+3 -8
View File
@@ -39,13 +39,7 @@ allow = [
"MIT",
"OpenSSL",
"Unicode-3.0",
]
[[licenses.clarify]]
name = "ring"
expression = "MIT AND ISC AND OpenSSL"
license-files = [
{ path = "LICENSE", hash = 0xbd0eed23 },
"Zlib",
]
[bans]
@@ -63,6 +57,8 @@ 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]
@@ -70,5 +66,4 @@ unknown-registry = "deny"
unknown-git = "deny"
allow-git = [
"https://github.com/chenxiaolong/zip",
"https://github.com/trifectatechfoundation/bzip2-rs",
]
+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.0"
rsa = { version = "0.9.6", features = ["hazmat"] }
serde = { version = "1.0.188", features = ["derive"] }
tempfile = "3.8.0"
+10 -10
View File
@@ -50,11 +50,11 @@ data.ramdisks = [["otacerts", "first_stage", "dsu_key_dir"]]
[profile.pixel_v4_gki.hashes_streaming]
original = "c00f891f941f3dddb28966f7b07f3acea773bee104dace82b37c2d1341f09422"
patched = "ce9d8ee97828d233809742a5d3f23aa27b042675b1935ca9e3df0592c55788fd"
patched = "6c27ffb07f4497af8539f8283e506066af9417580230c3209c9875fc15d5069d"
[profile.pixel_v4_gki.hashes_seekable]
original = "96a6c366b5de1c3b10d4d6cb4ca503c83ac4cd9ca952a965cceb041990ba7022"
patched = "e7b4609ba7a23609211dcae143bc43f091f286fbbb3a9301c02ee25614d35deb"
patched = "e4fc12523ffc312796b92210bc1e3bbb70dd60797a47daae76c8b5852e48b382"
# Google Pixel 6a
# What's unique: boot (boot v4, no ramdisk) + vendor_boot (vendor v4, 2 ramdisks)
@@ -91,11 +91,11 @@ data.ramdisks = [["init", "otacerts", "first_stage", "dsu_key_dir"], ["dlkm"]]
[profile.pixel_v4_non_gki.hashes_streaming]
original = "4d692bc777b568b0626d3c08d2e6f83f1b472db5ad903486daaec6a78d0cc26e"
patched = "e27673e4f30933710c11d51f0e73849068cbe9bc9f54e6076bdd93f9a5c8ea0a"
patched = "6832ded3e98a14edc8c5ea7284fcea0b958fa710ebf222c27116faec8dfefe2e"
[profile.pixel_v4_non_gki.hashes_seekable]
original = "ea27ecd9718c17b63400b2548680bb3cee93ce63b4fc44ff9654ca0d9c5372a8"
patched = "3456b14e014cf565a808a9e834d9105a23539f07b2c460db19c9384aadbc3b93"
patched = "114f8936e917d7e4a71bc1521adb3c8e676de3a738f8c7c505b86464d20bd95c"
# Google Pixel 4a 5G
# What's unique: boot (boot v3) + vendor_boot (vendor v3)
@@ -133,11 +133,11 @@ data.ramdisks = [["otacerts", "first_stage", "dsu_key_dir"]]
[profile.pixel_v3.hashes_streaming]
original = "f432dc7931520feb238474aa707dd5299747562ffe6129f3f763b5f11ac473ab"
patched = "3850a2e73bd783a1ec4a70c59f37d2374e017c20df7ab4b591182b14d187c18e"
patched = "1f28d9210a17e233cd5da4af55b07db764b19eeab991394514170b405240464f"
[profile.pixel_v3.hashes_seekable]
original = "7d29ecc6780953c22052a576b8dc85066c8667a875e918a786a08ff4545b47d1"
patched = "9f6342940b7cfbeb27b0567f006bb35cbee910ef038ec535403c662d5252ca71"
patched = "27b80c7be9c1e527ea26abe3dabde245c580e6f26ec084204278fbfd81a39f83"
# Google Pixel 4a
# What's unique: boot (boot v2)
@@ -168,9 +168,9 @@ data.type = "vbmeta"
data.deps = ["system"]
[profile.pixel_v2.hashes_streaming]
original = "1b45235b58054009cc496f6c3ee11d3dc16ed5c388c861761e26a6fce83103a0"
patched = "193b2dc70dd465d686f35c7b7f74d2cc1b06a55e48cf5c2e4df0f667e03032fc"
original = "4b7e5675f834ac56bf3459628adfe1425c2346a1c224ee6ea0a3be9f996db254"
patched = "4a7ca99808b4e49a2dc77a620d4fb7d8219b974ab5b3d5b5709084942805dc9a"
[profile.pixel_v2.hashes_seekable]
original = "52284308fae10cbaf09ade14e92f3bbe6149751a42bff15432982fcef8d890ab"
patched = "7ad74ac87ddcaf34938017e6149a646041d70926e31ecda93e156e9397467b3b"
original = "66b44b148b35a8a998214e0ae36470b42ffd6d2974e18cd8fa12cd8af0c98540"
patched = "5ddec4bb56dd78a49fbc49e9ad3de7710b0b008b031ff4dfce8fdc775e753ad1"
+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 = ring::digest::Context::new(&ring::digest::SHA256);
let context = aws_lc_rs::digest::Context::new(&aws_lc_rs::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 = ring::digest::digest(&ring::digest::SHA256, b"avbroot");
let salt = aws_lc_rs::digest::digest(&aws_lc_rs::digest::SHA256, b"avbroot");
let descriptors = vec![
if hash_tree {
let mut descriptor = HashTreeDescriptor {