mirror of
https://github.com/chenxiaolong/avbroot.git
synced 2026-07-03 14:05:11 +02:00
Add support for supplying the private key passphrases non-interactively
Fixes: #99 Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
This commit is contained in:
@@ -276,6 +276,40 @@ ValueError: vbmeta flags disable AVB: 0x3
|
||||
|
||||
To forcibly enable AVB (by clearing the flags), pass in `--clear-vbmeta-flags`.
|
||||
|
||||
### Non-interactive use
|
||||
|
||||
avbroot prompts for the private key passphrases interactively by default. To run avbroot non-interactively, either:
|
||||
|
||||
* Supply the passphrases via files:
|
||||
|
||||
```bash
|
||||
avbroot patch \
|
||||
--passphrase-avb-file /path/to/avb.passphrase \
|
||||
--passphrase-ota-file /path/to/ota.passphrase \
|
||||
<...>
|
||||
```
|
||||
|
||||
On Unix-like systems, the "files" can be pipes. With shells that support process substituion (bash, zsh, etc.), the passphrase can be queried from a command (eg. querying a password manager).
|
||||
|
||||
```bash
|
||||
avbroot patch \
|
||||
--passphrase-avb-file <(command to query AVB passphrase) \
|
||||
--passphrase-ota-file <(command to query OTA passphrase) \
|
||||
<...>
|
||||
```
|
||||
|
||||
* Supply the passphrases via environment variables. This is less secure since any process running as the same user can see the environment variable values.
|
||||
|
||||
```bash
|
||||
export PASSPHRASE_AVB="the AVB passphrase"
|
||||
export PASSPHRASE_OTA="the OTA passphrase"
|
||||
|
||||
avbroot patch \
|
||||
--passphrase-avb-env-var PASSPHRASE_AVB \
|
||||
--passphrase-ota-env-var PASSPHRASE_OTA \
|
||||
<...>
|
||||
```
|
||||
|
||||
### Extracting the entire OTA
|
||||
|
||||
To extract all images contained within the OTA's `payload.bin`, run:
|
||||
|
||||
+122
-43
@@ -326,8 +326,16 @@ def patch_subcommand(args):
|
||||
root_patch = boot.PrepatchedImage(args.prepatched)
|
||||
|
||||
# Get passphrases for keys
|
||||
passphrase_avb = openssl.prompt_passphrase(args.privkey_avb)
|
||||
passphrase_ota = openssl.prompt_passphrase(args.privkey_ota)
|
||||
passphrase_avb = openssl.prompt_passphrase(
|
||||
args.privkey_avb,
|
||||
args.passphrase_avb_env_var,
|
||||
args.passphrase_avb_file,
|
||||
)
|
||||
passphrase_ota = openssl.prompt_passphrase(
|
||||
args.privkey_ota,
|
||||
args.passphrase_ota_env_var,
|
||||
args.passphrase_ota_file,
|
||||
)
|
||||
|
||||
# Ensure that the certificate matches the private key
|
||||
if not openssl.cert_matches_key(args.cert_ota, args.privkey_ota,
|
||||
@@ -425,61 +433,132 @@ def uint64_arg(arg):
|
||||
|
||||
def parse_args(argv=None):
|
||||
parser = argparse.ArgumentParser()
|
||||
subparsers = parser.add_subparsers(dest='subcommand', required=True,
|
||||
help='Subcommands')
|
||||
subparsers = parser.add_subparsers(
|
||||
dest='subcommand',
|
||||
required=True,
|
||||
help='Subcommands',
|
||||
)
|
||||
|
||||
patch = subparsers.add_parser('patch', help='Patch a full OTA zip')
|
||||
patch = subparsers.add_parser(
|
||||
'patch',
|
||||
help='Patch a full OTA zip',
|
||||
)
|
||||
|
||||
patch.add_argument('--input', required=True,
|
||||
help='Path to original raw payload or OTA zip')
|
||||
patch.add_argument('--output',
|
||||
help='Path to new raw payload or OTA zip')
|
||||
patch.add_argument('--privkey-avb', required=True,
|
||||
help='Private key for signing root vbmeta image')
|
||||
patch.add_argument('--privkey-ota', required=True,
|
||||
help='Private key for signing OTA payload')
|
||||
patch.add_argument('--cert-ota', required=True,
|
||||
help='Certificate for OTA payload signing key')
|
||||
patch.add_argument(
|
||||
'--input',
|
||||
required=True,
|
||||
help='Path to original raw payload or OTA zip',
|
||||
)
|
||||
patch.add_argument(
|
||||
'--output',
|
||||
help='Path to new raw payload or OTA zip',
|
||||
)
|
||||
patch.add_argument(
|
||||
'--privkey-avb',
|
||||
required=True,
|
||||
help='Private key for signing root vbmeta image',
|
||||
)
|
||||
patch.add_argument(
|
||||
'--privkey-ota',
|
||||
required=True,
|
||||
help='Private key for signing OTA payload',
|
||||
)
|
||||
patch.add_argument(
|
||||
'--cert-ota',
|
||||
required=True,
|
||||
help='Certificate for OTA payload signing key',
|
||||
)
|
||||
|
||||
for arg in ('AVB', 'OTA'):
|
||||
group = patch.add_mutually_exclusive_group()
|
||||
group.add_argument(
|
||||
f'--passphrase-{arg.lower()}-env-var',
|
||||
help=f'Environment variable containing {arg} private key passphrase',
|
||||
)
|
||||
group.add_argument(
|
||||
f'--passphrase-{arg.lower()}-file',
|
||||
help=f'File containing {arg} private key passphrase',
|
||||
)
|
||||
|
||||
boot_group = patch.add_mutually_exclusive_group(required=True)
|
||||
boot_group.add_argument('--magisk',
|
||||
help='Path to Magisk APK')
|
||||
boot_group.add_argument('--prepatched',
|
||||
help='Path to prepatched boot image')
|
||||
boot_group.add_argument('--rootless', action='store_true',
|
||||
help='Skip applying root patch')
|
||||
boot_group.add_argument(
|
||||
'--magisk',
|
||||
help='Path to Magisk APK',
|
||||
)
|
||||
boot_group.add_argument(
|
||||
'--prepatched',
|
||||
help='Path to prepatched boot image',
|
||||
)
|
||||
boot_group.add_argument(
|
||||
'--rootless',
|
||||
action='store_true',
|
||||
help='Skip applying root patch',
|
||||
)
|
||||
|
||||
patch.add_argument('--magisk-preinit-device',
|
||||
help='Magisk preinit device')
|
||||
patch.add_argument('--magisk-random-seed', type=uint64_arg,
|
||||
help='Magisk random seed')
|
||||
patch.add_argument('--ignore-magisk-warnings', action='store_true',
|
||||
help='Ignore Magisk compatibility/version warnings')
|
||||
patch.add_argument(
|
||||
'--magisk-preinit-device',
|
||||
help='Magisk preinit device',
|
||||
)
|
||||
patch.add_argument(
|
||||
'--magisk-random-seed',
|
||||
type=uint64_arg,
|
||||
help='Magisk random seed',
|
||||
)
|
||||
patch.add_argument(
|
||||
'--ignore-magisk-warnings',
|
||||
action='store_true',
|
||||
help='Ignore Magisk compatibility/version warnings',
|
||||
)
|
||||
|
||||
patch.add_argument('--clear-vbmeta-flags', action='store_true',
|
||||
help='Forcibly clear vbmeta flags if they disable AVB')
|
||||
patch.add_argument(
|
||||
'--clear-vbmeta-flags',
|
||||
action='store_true',
|
||||
help='Forcibly clear vbmeta flags if they disable AVB',
|
||||
)
|
||||
|
||||
extract = subparsers.add_parser(
|
||||
'extract', help='Extract patched images from a patched OTA zip')
|
||||
'extract',
|
||||
help='Extract patched images from a patched OTA zip',
|
||||
)
|
||||
|
||||
extract.add_argument('--input', required=True,
|
||||
help='Path to patched OTA zip')
|
||||
extract.add_argument('--directory', default='.',
|
||||
help='Output directory for extracted images')
|
||||
extract.add_argument(
|
||||
'--input',
|
||||
required=True,
|
||||
help='Path to patched OTA zip',
|
||||
)
|
||||
extract.add_argument(
|
||||
'--directory',
|
||||
default='.',
|
||||
help='Output directory for extracted images',
|
||||
)
|
||||
extract_group = extract.add_mutually_exclusive_group()
|
||||
extract_group.add_argument('--all', action='store_true',
|
||||
help='Extract all images from the payload')
|
||||
extract_group.add_argument('--boot-only', action='store_true',
|
||||
help='Extract only the boot image')
|
||||
extract_group.add_argument(
|
||||
'--all',
|
||||
action='store_true',
|
||||
help='Extract all images from the payload',
|
||||
)
|
||||
extract_group.add_argument(
|
||||
'--boot-only',
|
||||
action='store_true',
|
||||
help='Extract only the boot image',
|
||||
)
|
||||
|
||||
for subcmd in (patch, extract):
|
||||
subcmd.add_argument('--boot-partition', default='@gki_ramdisk',
|
||||
help='Boot partition name')
|
||||
subcmd.add_argument(
|
||||
'--boot-partition',
|
||||
default='@gki_ramdisk',
|
||||
help='Boot partition name',
|
||||
)
|
||||
|
||||
magisk_info = subparsers.add_parser(
|
||||
'magisk-info', help='Print Magisk config from a patched boot image')
|
||||
magisk_info.add_argument('--image', required=True,
|
||||
help='Patch to Magisk-patched boot image')
|
||||
'magisk-info',
|
||||
help='Print Magisk config from a patched boot image',
|
||||
)
|
||||
magisk_info.add_argument(
|
||||
'--image',
|
||||
required=True,
|
||||
help='Patch to Magisk-patched boot image',
|
||||
)
|
||||
|
||||
args = parser.parse_args(args=argv)
|
||||
|
||||
|
||||
+16
-3
@@ -192,15 +192,28 @@ def _is_encrypted(pkey):
|
||||
return False
|
||||
|
||||
|
||||
def prompt_passphrase(pkey):
|
||||
def prompt_passphrase(pkey, passphrase_env_var=None, passphrase_file=None):
|
||||
'''
|
||||
Prompt and return passphrase if the private key is encrypted.
|
||||
If the private key is encrypted:
|
||||
|
||||
* try to read from the specified passphrase file (first line with trailing
|
||||
line endings stripped)
|
||||
* try to read from the passphrase environment variable
|
||||
* prompt for the passphrase interactively
|
||||
|
||||
There is no fallback behavior.
|
||||
'''
|
||||
|
||||
if not _is_encrypted(pkey):
|
||||
return None
|
||||
|
||||
passphrase = getpass.getpass(f'Passphrase for {pkey}: ')
|
||||
if passphrase_file is not None:
|
||||
with open(passphrase_file, 'r') as f:
|
||||
passphrase = f.readline().rstrip('\r\n')
|
||||
elif passphrase_env_var is not None:
|
||||
passphrase = os.environ[passphrase_env_var]
|
||||
else:
|
||||
passphrase = getpass.getpass(f'Passphrase for {pkey}: ')
|
||||
|
||||
# Verify that it is correct
|
||||
with inject_passphrase(passphrase):
|
||||
|
||||
Reference in New Issue
Block a user