mirror of
https://github.com/chenxiaolong/avbroot.git
synced 2026-07-03 14:05:11 +02:00
Compare commits
28 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0cdc7172dd | |||
| 1296275418 | |||
| a62d0a5c91 | |||
| de433a2724 | |||
| c08af33343 | |||
| e224884591 | |||
| a525fc4550 | |||
| 8c6b0fdfc5 | |||
| 36d4ed19ad | |||
| f9b06b33e6 | |||
| 620c873be5 | |||
| e929ecbe44 | |||
| 31685713ef | |||
| 0dab7aa32c | |||
| ba6c1b1400 | |||
| 1ecbf1144d | |||
| 84fa6c6bc6 | |||
| 15b7db4631 | |||
| 1d9c1574da | |||
| 43db728b9d | |||
| fbe9f629ab | |||
| 80d5f19223 | |||
| cff5ac6b2e | |||
| 2e3b5db9fe | |||
| 58a279f3a4 | |||
| 1484cd47c3 | |||
| 2ee38b716b | |||
| d4eb231dd4 |
@@ -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: |
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 }}
|
||||
|
||||
@@ -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
File diff suppressed because it is too large
Load Diff
+1
-1
@@ -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"
|
||||
|
||||
@@ -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
@@ -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
@@ -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"
|
||||
|
||||
|
||||
@@ -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),
|
||||
}
|
||||
|
||||
|
||||
@@ -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)?;
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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);
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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());
|
||||
|
||||
|
||||
@@ -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
@@ -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 = ©.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
@@ -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))?;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
|
||||
|
||||
@@ -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
@@ -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,
|
||||
);
|
||||
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user