diff --git a/adb/adb_debug.py b/adb/adb_debug.py index 6037269..cc90282 100644 --- a/adb/adb_debug.py +++ b/adb/adb_debug.py @@ -25,6 +25,7 @@ from adb import adb_commands from adb import common_cli +from adb import android_pubkey try: from adb import sign_cryptography @@ -149,6 +150,14 @@ def main(): subparser.add_argument( '--output_port_path', action='store_true', help='Outputs the port_path alongside the serial') + subparser = subparsers.add_parser( + name='keygen', help='generate adb public/private key ' + 'private key stored in {filepath} ' + 'public key stored in {filepath}.pub ' + '(existing files overwritten)') + subparser.add_argument( + 'filepath', + help='File path to write the private/public keypair (existing files overwritten)') common_cli.MakeSubparser( subparsers, parents, adb_commands.AdbCommands.Install) @@ -195,6 +204,8 @@ def main(): if args.command_name == 'help': parser.print_help() return 0 + if args.command_name == 'keygen': + return android_pubkey.keygen(args.filepath) if args.command_name == 'logcat': args.positional = args.options elif args.command_name == 'shell': diff --git a/adb/android_pubkey.py b/adb/android_pubkey.py new file mode 100644 index 0000000..1670580 --- /dev/null +++ b/adb/android_pubkey.py @@ -0,0 +1,166 @@ +"""This file implements encoding and decoding logic for Android's custom RSA +public key binary format. Public keys are stored as a sequence of +little-endian 32 bit words. Note that Android only supports little-endian +processors, so we don't do any byte order conversions when parsing the binary +struct. + +Structure from: +https://github.com/aosp-mirror/platform_system_core/blob/c55fab4a59cfa461857c6a61d8a0f1ae4591900c/libcrypto_utils/android_pubkey.c + +typedef struct RSAPublicKey { + // Modulus length. This must be ANDROID_PUBKEY_MODULUS_SIZE_WORDS + uint32_t modulus_size_words; + + // Precomputed montgomery parameter: -1 / n[0] mod 2^32 + uint32_t n0inv; + + // RSA modulus as a little-endian array + uint8_t modulus[ANDROID_PUBKEY_MODULUS_SIZE]; + + // Montgomery parameter R^2 as a little-endian array of little-endian words + uint8_t rr[ANDROID_PUBKEY_MODULUS_SIZE]; + + // RSA modulus: 3 or 65537 + uint32_t exponent; +} RSAPublicKey;""" + + +from __future__ import print_function + +import os +import base64 +import socket +import struct + +from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.primitives import serialization +from cryptography.hazmat.primitives.asymmetric import rsa + + +# Size of an RSA modulus such as an encrypted block or a signature. +ANDROID_PUBKEY_MODULUS_SIZE = (2048 // 8) + +# Python representation of "struct RSAPublicKey": +ANDROID_RSAPUBLICKEY_STRUCT = ( + '<' # Little-endian + 'L' # uint32_t modulus_size_words; + 'L' # uint32_t n0inv; + '{modulus_size}s' # uint8_t modulus[ANDROID_PUBKEY_MODULUS_SIZE]; + '{modulus_size}s' # uint8_t rr[ANDROID_PUBKEY_MODULUS_SIZE]; + 'L' # uint32_t exponent; + ).format(modulus_size=ANDROID_PUBKEY_MODULUS_SIZE) + + +# Size of the RSA modulus in words. +ANDROID_PUBKEY_MODULUS_SIZE_WORDS = (ANDROID_PUBKEY_MODULUS_SIZE // 4) + + +def _to_bytes(n, length, endianess='big'): + """partial python2 compatibility with int.to_bytes + https://stackoverflow.com/a/20793663""" + if not hasattr(n, 'to_bytes'): + h = '{:x}'.format(n) + s = ('0' * (len(h) % 2) + h).zfill(length * 2).decode('hex') + return s if endianess == 'big' else s[::-1] + return n.to_bytes(length, endianess) + + +def decode_pubkey(public_key): + """decodes a public RSA key stored in Android's custom binary format""" + binary_key_data = base64.b64decode(public_key) + modulus_size_words, n0inv, modulus_bytes, rr_bytes, exponent = \ + struct.unpack(ANDROID_RSAPUBLICKEY_STRUCT, binary_key_data) + assert modulus_size_words == ANDROID_PUBKEY_MODULUS_SIZE_WORDS + modulus = reversed(modulus_bytes) + rr = reversed(rr_bytes) + print('modulus_size_words:', hex(modulus_size_words)) + print('n0inv:', hex(n0inv)) + print('modulus: ', end='') + print(*map(hex, modulus), sep=':') + print('rr: ', end='') + print(*map(hex, rr), sep=':') + print('exponent:', hex(exponent)) + + +def decode_pubkey_file(public_key_path): + with open(public_key_path, 'rb') as fd: + decode_pubkey(fd.read()) + + +def encode_pubkey(private_key_path): + """encodes a public RSA key into Android's custom binary format""" + with open(private_key_path, 'rb') as key_file: + key = serialization.load_pem_private_key( + key_file.read(), + password=None, + backend=default_backend()).private_numbers().public_numbers + + # Compute and store n0inv = -1 / N[0] mod 2^32. + # BN_set_bit(r32, 32) + r32 = 1 << 32 + # BN_mod(n0inv, key->n, r32, ctx) + n0inv = key.n % r32 + # BN_mod_inverse(n0inv, n0inv, r32, ctx) + n0inv = rsa._modinv(n0inv, r32) + # BN_sub(n0inv, r32, n0inv) + n0inv = r32 - n0inv + + # Compute and store rr = (2^(rsa_size)) ^ 2 mod N. + # BN_set_bit(rr, ANDROID_PUBKEY_MODULUS_SIZE * 8) + rr = 1 << (ANDROID_PUBKEY_MODULUS_SIZE * 8) + # BN_mod_sqr(rr, rr, key->n, ctx) + rr = (rr ** 2) % key.n + + return struct.pack( + ANDROID_RSAPUBLICKEY_STRUCT, + ANDROID_PUBKEY_MODULUS_SIZE_WORDS, + n0inv, + _to_bytes(key.n, ANDROID_PUBKEY_MODULUS_SIZE, 'little'), + _to_bytes(rr, ANDROID_PUBKEY_MODULUS_SIZE, 'little'), + key.e + ) + + +def get_user_info(): + username = os.getlogin() + if not username: + username = 'unknown' + + hostname = socket.gethostname() + if not hostname: + hostname = 'unknown' + + return ' ' + username + '@' + hostname + + +def write_public_keyfile(private_key_path, public_key_path): + """write public keyfile to public_key_path in Android's custom + RSA public key format given a path to a private keyfile""" + + public_key = encode_pubkey(private_key_path) + assert len(public_key) == struct.calcsize(ANDROID_RSAPUBLICKEY_STRUCT) + with open(public_key_path, 'wb') as public_key_file: + public_key_file.write(base64.b64encode(public_key)) + public_key_file.write(get_user_info().encode()) + + +def keygen(filepath): + """generate adb public/private key + private key stored in {filepath} + public key stored in {filepath}.pub + (existing files overwritten) + + Args: + filepath: File path to write the private/public keypair + """ + private_key = rsa.generate_private_key( + public_exponent=65537, + key_size=2048, + backend=default_backend()) + with open(filepath, 'wb') as private_key_file: + private_key_file.write(private_key.private_bytes( + encoding=serialization.Encoding.PEM, + format=serialization.PrivateFormat.PKCS8, + encryption_algorithm=serialization.NoEncryption())) + + write_public_keyfile(filepath, filepath + '.pub') diff --git a/setup.py b/setup.py index fbcdb81..797867a 100644 --- a/setup.py +++ b/setup.py @@ -14,22 +14,6 @@ from setuptools import setup -# Figure out if the system already has a supported Crypto library -rsa_signer_library = 'cryptography' -try: - import rsa - - rsa_signer_library = 'rsa' -except ImportError: - try: - from Crypto.Hash import SHA256 - from Crypto.PublicKey import RSA - from Crypto.Signature import pkcs1_15 - - rsa_signer_library = 'pycryptodome' - except ImportError: - pass - setup( name = 'adb', @@ -62,7 +46,7 @@ install_requires = [ 'libusb1>=1.0.16', - rsa_signer_library + 'cryptography' ], extra_requires = {