1// Copyright (C) 2022 The Android Open Source Project 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15import {BigInteger, RSAKey} from 'jsbn-rsa'; 16 17import {assertExists, assertTrue} from '../../../base/logging'; 18import { 19 base64Decode, 20 base64Encode, 21 hexEncode, 22} from '../../../base/string_utils'; 23import {RecordingError} from '../recording_error_handling'; 24 25const WORD_SIZE = 4; 26const MODULUS_SIZE_BITS = 2048; 27const MODULUS_SIZE = MODULUS_SIZE_BITS / 8; 28const MODULUS_SIZE_WORDS = MODULUS_SIZE / WORD_SIZE; 29const PUBKEY_ENCODED_SIZE = 3 * WORD_SIZE + 2 * MODULUS_SIZE; 30const ADB_WEB_CRYPTO_ALGORITHM = { 31 name: 'RSASSA-PKCS1-v1_5', 32 hash: {name: 'SHA-1'}, 33 publicExponent: new Uint8Array([0x01, 0x00, 0x01]), // 65537 34 modulusLength: MODULUS_SIZE_BITS, 35}; 36 37const ADB_WEB_CRYPTO_EXPORTABLE = true; 38const ADB_WEB_CRYPTO_OPERATIONS: KeyUsage[] = ['sign']; 39 40const SIGNING_ASN1_PREFIX = [ 41 0x00, 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x0e, 0x03, 0x02, 0x1a, 0x05, 42 0x00, 0x04, 0x14, 43]; 44 45const R32 = BigInteger.ONE.shiftLeft(32); // 1 << 32 46 47interface ValidJsonWebKey { 48 n: string; 49 e: string; 50 d: string; 51 p: string; 52 q: string; 53 dp: string; 54 dq: string; 55 qi: string; 56} 57 58function isValidJsonWebKey(key: JsonWebKey): key is ValidJsonWebKey { 59 return ( 60 key.n !== undefined && 61 key.e !== undefined && 62 key.d !== undefined && 63 key.p !== undefined && 64 key.q !== undefined && 65 key.dp !== undefined && 66 key.dq !== undefined && 67 key.qi !== undefined 68 ); 69} 70 71// Convert a BigInteger to an array of a specified size in bytes. 72function bigIntToFixedByteArray(bn: BigInteger, size: number): Uint8Array { 73 const paddedBnBytes = bn.toByteArray(); 74 let firstNonZeroIndex = 0; 75 while ( 76 firstNonZeroIndex < paddedBnBytes.length && 77 paddedBnBytes[firstNonZeroIndex] === 0 78 ) { 79 firstNonZeroIndex++; 80 } 81 const bnBytes = Uint8Array.from(paddedBnBytes.slice(firstNonZeroIndex)); 82 const res = new Uint8Array(size); 83 assertTrue(bnBytes.length <= res.length); 84 res.set(bnBytes, res.length - bnBytes.length); 85 return res; 86} 87 88export class AdbKey { 89 // We use this JsonWebKey to: 90 // - create a private key and sign with it 91 // - create a public key and send it to the device 92 // - serialize the JsonWebKey and send it to the device (or retrieve it 93 // from the device and deserialize) 94 jwkPrivate: ValidJsonWebKey; 95 96 private constructor(jwkPrivate: ValidJsonWebKey) { 97 this.jwkPrivate = jwkPrivate; 98 } 99 100 static async GenerateNewKeyPair(): Promise<AdbKey> { 101 // Construct a new CryptoKeyPair and keep its private key in JWB format. 102 const keyPair = await crypto.subtle.generateKey( 103 ADB_WEB_CRYPTO_ALGORITHM, 104 ADB_WEB_CRYPTO_EXPORTABLE, 105 ADB_WEB_CRYPTO_OPERATIONS, 106 ); 107 const jwkPrivate = await crypto.subtle.exportKey('jwk', keyPair.privateKey); 108 if (!isValidJsonWebKey(jwkPrivate)) { 109 throw new RecordingError('Could not generate a valid private key.'); 110 } 111 return new AdbKey(jwkPrivate); 112 } 113 114 static DeserializeKey(serializedKey: string): AdbKey { 115 return new AdbKey(JSON.parse(serializedKey)); 116 } 117 118 // Perform an RSA signing operation for the ADB auth challenge. 119 // 120 // For the RSA signature, the token is expected to have already 121 // had the SHA-1 message digest applied. 122 // 123 // However, the adb token we receive from the device is made up of 20 randomly 124 // generated bytes that are treated like a SHA-1. Therefore, we need to update 125 // the message format. 126 sign(token: Uint8Array): Uint8Array { 127 const rsaKey = new RSAKey(); 128 rsaKey.setPrivateEx( 129 hexEncode(base64Decode(this.jwkPrivate.n)), 130 hexEncode(base64Decode(this.jwkPrivate.e)), 131 hexEncode(base64Decode(this.jwkPrivate.d)), 132 hexEncode(base64Decode(this.jwkPrivate.p)), 133 hexEncode(base64Decode(this.jwkPrivate.q)), 134 hexEncode(base64Decode(this.jwkPrivate.dp)), 135 hexEncode(base64Decode(this.jwkPrivate.dq)), 136 hexEncode(base64Decode(this.jwkPrivate.qi)), 137 ); 138 assertTrue(rsaKey.n.bitLength() === MODULUS_SIZE_BITS); 139 140 // Message Layout (size equals that of the key modulus): 141 // 00 01 FF FF FF FF ... FF [ASN.1 PREFIX] [TOKEN] 142 const message = new Uint8Array(MODULUS_SIZE); 143 144 // Initially fill the buffer with the padding 145 message.fill(0xff); 146 147 // add prefix 148 message[0] = 0x00; 149 message[1] = 0x01; 150 151 // add the ASN.1 prefix 152 message.set( 153 SIGNING_ASN1_PREFIX, 154 message.length - SIGNING_ASN1_PREFIX.length - token.length, 155 ); 156 157 // then the actual token at the end 158 message.set(token, message.length - token.length); 159 160 const messageInteger = new BigInteger(Array.from(message)); 161 const signature = rsaKey.doPrivate(messageInteger); 162 return new Uint8Array(bigIntToFixedByteArray(signature, MODULUS_SIZE)); 163 } 164 165 // Construct public key to match the adb format: 166 // go/codesearch/rvc-arc/system/core/libcrypto_utils/android_pubkey.c;l=38-53 167 getPublicKey(): string { 168 const rsaKey = new RSAKey(); 169 rsaKey.setPublic( 170 hexEncode(base64Decode(assertExists(this.jwkPrivate.n))), 171 hexEncode(base64Decode(assertExists(this.jwkPrivate.e))), 172 ); 173 174 const n0inv = R32.subtract(rsaKey.n.modInverse(R32)).intValue(); 175 const r = BigInteger.ONE.shiftLeft(1).pow(MODULUS_SIZE_BITS); 176 const rr = r.multiply(r).mod(rsaKey.n); 177 178 const buffer = new ArrayBuffer(PUBKEY_ENCODED_SIZE); 179 const dv = new DataView(buffer); 180 dv.setUint32(0, MODULUS_SIZE_WORDS, true); 181 dv.setUint32(WORD_SIZE, n0inv, true); 182 183 const dvU8 = new Uint8Array(dv.buffer, dv.byteOffset, dv.byteLength); 184 dvU8.set( 185 bigIntToFixedByteArray(rsaKey.n, MODULUS_SIZE).reverse(), 186 2 * WORD_SIZE, 187 ); 188 dvU8.set( 189 bigIntToFixedByteArray(rr, MODULUS_SIZE).reverse(), 190 2 * WORD_SIZE + MODULUS_SIZE, 191 ); 192 193 dv.setUint32(2 * WORD_SIZE + 2 * MODULUS_SIZE, rsaKey.e, true); 194 return base64Encode(dvU8) + ' ui.perfetto.dev'; 195 } 196 197 serializeKey(): string { 198 return JSON.stringify(this.jwkPrivate); 199 } 200} 201