From 42e5ae3d39f7715180db474f57a9d944f16a2600 Mon Sep 17 00:00:00 2001 From: Joe Leong Date: Sat, 5 Jan 2019 21:33:30 -0500 Subject: [PATCH 1/4] Add encode/decode of custom public key format Implementation and some comments from the following: - [auth.cpp] (https://github.com/aosp-mirror/platform_system_core/blob/c55fab4a59cfa461857c6a61d8a0f1ae4591900c/adb/client/auth.cpp) - [android_pubkey.c] (https://github.com/aosp-mirror/platform_system_core/blob/c55fab4a59cfa461857c6a61d8a0f1ae4591900c/libcrypto_utils/android_pubkey.c) - [android_pubkey.h] (https://github.com/aosp-mirror/platform_system_core/blob/c55fab4a59cfa461857c6a61d8a0f1ae4591900c/libcrypto_utils/include/crypto_utils/android_pubkey.h) Note: It looks like [2dc4ca] (https://github.com/aosp-mirror/platform_system_core/commit/2dc4cabe0639c71014d729dd92eff19289429c89) removes the use of the public key file and directly extracts the public key from the private key. --- adb/android_pubkey.py | 141 ++++++++++++++++++++++++++++++++++++++++++ setup.py | 15 ++--- 2 files changed, 146 insertions(+), 10 deletions(-) create mode 100644 adb/android_pubkey.py diff --git a/adb/android_pubkey.py b/adb/android_pubkey.py new file mode 100644 index 0000000..2f5f34a --- /dev/null +++ b/adb/android_pubkey.py @@ -0,0 +1,141 @@ +"""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. + 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 six +import base64 +import socket +import struct + +import Crypto.Util +import Crypto.PublicKey.RSA + + +# Size of an RSA modulus such as an encrypted block or a signature. +ANDROID_PUBKEY_MODULUS_SIZE = (2048 // 8) + +# Size of an encoded RSA key. +ANDROID_PUBKEY_ENCODED_SIZE = \ + (3 * 4 + 2 * ANDROID_PUBKEY_MODULUS_SIZE) + # (3 * sizeof(uint32_t) + 2 * 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 six.PY2: + 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) + key_struct = struct.unpack(('n, r32, ctx) + n0inv = key.n % r32 + # BN_mod_inverse(n0inv, n0inv, r32, ctx) + n0inv = Crypto.Util.number.inverse(n0inv, r32) + # BN_sub(n0inv, r32, n0inv) + n0inv = r32 - n0inv + key_buffer += struct.pack('n, ctx) + rr = (rr ** 2) % key.n + key_buffer += _to_bytes(rr, ANDROID_PUBKEY_MODULUS_SIZE, 'little') + + key_buffer += struct.pack('=1.0.16', + 'pycryptodome', + 'six', rsa_signer_library ], From efa2a49c91d0c4a3a9bf711ac3763d844e0c911b Mon Sep 17 00:00:00 2001 From: Joe Leong Date: Sat, 5 Jan 2019 21:31:07 -0500 Subject: [PATCH 2/4] Add pyadb keygen command to generate keypairs Address (#95 and #131) --- adb/adb_debug.py | 11 +++++++++++ adb/android_pubkey.py | 16 ++++++++++++++++ 2 files changed, 27 insertions(+) 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 index 2f5f34a..c1dddf0 100644 --- a/adb/android_pubkey.py +++ b/adb/android_pubkey.py @@ -139,3 +139,19 @@ def write_public_keyfile(private_key_path, public_key_path): 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 + """ + key = Crypto.PublicKey.RSA.generate(2048) + with open(filepath, 'wb') as private_key_file: + private_key_file.write(key.export_key(format='PEM', pkcs=8)) + + write_public_keyfile(filepath, filepath + '.pub') From 0a791b9611136d5222c0e069332f60917bc21be4 Mon Sep 17 00:00:00 2001 From: Joe Leong Date: Wed, 24 Jul 2019 13:31:11 -0700 Subject: [PATCH 3/4] Refactor android_pubkey.py struct usage With help from @Halastra suggestions from code review of #144 - Define ANDROID_RSAPUBLICKEY_STRUCT - Remove use of six package - Fix some flake8 warnings Co-Authored-By: Halastra --- adb/android_pubkey.py | 58 +++++++++++++++++++++---------------------- setup.py | 1 - 2 files changed, 29 insertions(+), 30 deletions(-) diff --git a/adb/android_pubkey.py b/adb/android_pubkey.py index c1dddf0..15b335e 100644 --- a/adb/android_pubkey.py +++ b/adb/android_pubkey.py @@ -8,16 +8,16 @@ 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. + // 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. + // 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. + // 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 @@ -28,7 +28,6 @@ from __future__ import print_function import os -import six import base64 import socket import struct @@ -40,10 +39,16 @@ # Size of an RSA modulus such as an encrypted block or a signature. ANDROID_PUBKEY_MODULUS_SIZE = (2048 // 8) -# Size of an encoded RSA key. -ANDROID_PUBKEY_ENCODED_SIZE = \ - (3 * 4 + 2 * ANDROID_PUBKEY_MODULUS_SIZE) - # (3 * sizeof(uint32_t) + 2 * ANDROID_PUBKEY_MODULUS_SIZE) +# 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) @@ -52,7 +57,7 @@ def _to_bytes(n, length, endianess='big'): """partial python2 compatibility with int.to_bytes https://stackoverflow.com/a/20793663""" - if six.PY2: + 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] @@ -62,16 +67,11 @@ def _to_bytes(n, length, endianess='big'): def decode_pubkey(public_key): """decodes a public RSA key stored in Android's custom binary format""" binary_key_data = base64.b64decode(public_key) - key_struct = struct.unpack(('n, ctx) rr = (rr ** 2) % key.n - key_buffer += _to_bytes(rr, ANDROID_PUBKEY_MODULUS_SIZE, 'little') - key_buffer += struct.pack('=1.0.16', 'pycryptodome', - 'six', rsa_signer_library ], From 01614ea7f1bafdca67044045daeb7477daf2b5f1 Mon Sep 17 00:00:00 2001 From: Joe Leong Date: Wed, 24 Jul 2019 16:25:38 -0700 Subject: [PATCH 4/4] Replace pubkey pycryptodome with cryptography --- adb/android_pubkey.py | 27 ++++++++++++++++++--------- setup.py | 12 +----------- 2 files changed, 19 insertions(+), 20 deletions(-) diff --git a/adb/android_pubkey.py b/adb/android_pubkey.py index 15b335e..1670580 100644 --- a/adb/android_pubkey.py +++ b/adb/android_pubkey.py @@ -32,8 +32,9 @@ import socket import struct -import Crypto.Util -import Crypto.PublicKey.RSA +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. @@ -88,7 +89,11 @@ def decode_pubkey_file(public_key_path): def encode_pubkey(private_key_path): """encodes a public RSA key into Android's custom binary format""" - key = Crypto.PublicKey.RSA.import_key(private_key_path) + 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) @@ -96,7 +101,7 @@ def encode_pubkey(private_key_path): # BN_mod(n0inv, key->n, r32, ctx) n0inv = key.n % r32 # BN_mod_inverse(n0inv, n0inv, r32, ctx) - n0inv = Crypto.Util.number.inverse(n0inv, r32) + n0inv = rsa._modinv(n0inv, r32) # BN_sub(n0inv, r32, n0inv) n0inv = r32 - n0inv @@ -131,10 +136,8 @@ def get_user_info(): 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""" - with open(private_key_path, 'rb') as private_key_file: - private_key = private_key_file.read() - public_key = encode_pubkey(private_key) + 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)) @@ -150,8 +153,14 @@ def keygen(filepath): Args: filepath: File path to write the private/public keypair """ - key = Crypto.PublicKey.RSA.generate(2048) + 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(key.export_key(format='PEM', pkcs=8)) + 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 9cc6aa6..797867a 100644 --- a/setup.py +++ b/setup.py @@ -14,15 +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: - pass - setup( name = 'adb', @@ -55,8 +46,7 @@ install_requires = [ 'libusb1>=1.0.16', - 'pycryptodome', - rsa_signer_library + 'cryptography' ], extra_requires = {