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, 42 0x30, 43 0x21, 44 0x30, 45 0x09, 46 0x06, 47 0x05, 48 0x2B, 49 0x0E, 50 0x03, 51 0x02, 52 0x1A, 53 0x05, 54 0x00, 55 0x04, 56 0x14, 57]; 58 59const R32 = BigInteger.ONE.shiftLeft(32); // 1 << 32 60 61interface ValidJsonWebKey { 62 n: string; 63 e: string; 64 d: string; 65 p: string; 66 q: string; 67 dp: string; 68 dq: string; 69 qi: string; 70} 71 72function isValidJsonWebKey(key: JsonWebKey): key is ValidJsonWebKey { 73 return key.n !== undefined && key.e !== undefined && key.d !== undefined && 74 key.p !== undefined && key.q !== undefined && key.dp !== undefined && 75 key.dq !== undefined && key.qi !== undefined; 76} 77 78// Convert a BigInteger to an array of a specified size in bytes. 79function bigIntToFixedByteArray(bn: BigInteger, size: number): Uint8Array { 80 const paddedBnBytes = bn.toByteArray(); 81 let firstNonZeroIndex = 0; 82 while (firstNonZeroIndex < paddedBnBytes.length && 83 paddedBnBytes[firstNonZeroIndex] === 0) { 84 firstNonZeroIndex++; 85 } 86 const bnBytes = Uint8Array.from(paddedBnBytes.slice(firstNonZeroIndex)); 87 const res = new Uint8Array(size); 88 assertTrue(bnBytes.length <= res.length); 89 res.set(bnBytes, res.length - bnBytes.length); 90 return res; 91} 92 93export class AdbKey { 94 // We use this JsonWebKey to: 95 // - create a private key and sign with it 96 // - create a public key and send it to the device 97 // - serialize the JsonWebKey and send it to the device (or retrieve it 98 // from the device and deserialize) 99 jwkPrivate: ValidJsonWebKey; 100 101 private constructor(jwkPrivate: ValidJsonWebKey) { 102 this.jwkPrivate = jwkPrivate; 103 } 104 105 static async GenerateNewKeyPair(): Promise<AdbKey> { 106 // Construct a new CryptoKeyPair and keep its private key in JWB format. 107 const keyPair = await crypto.subtle.generateKey( 108 ADB_WEB_CRYPTO_ALGORITHM, 109 ADB_WEB_CRYPTO_EXPORTABLE, 110 ADB_WEB_CRYPTO_OPERATIONS); 111 const jwkPrivate = await crypto.subtle.exportKey('jwk', keyPair.privateKey); 112 if (!isValidJsonWebKey(jwkPrivate)) { 113 throw new RecordingError('Could not generate a valid private key.'); 114 } 115 return new AdbKey(jwkPrivate); 116 } 117 118 static DeserializeKey(serializedKey: string): AdbKey { 119 return new AdbKey(JSON.parse(serializedKey)); 120 } 121 122 // Perform an RSA signing operation for the ADB auth challenge. 123 // 124 // For the RSA signature, the token is expected to have already 125 // had the SHA-1 message digest applied. 126 // 127 // However, the adb token we receive from the device is made up of 20 randomly 128 // generated bytes that are treated like a SHA-1. Therefore, we need to update 129 // the message format. 130 sign(token: Uint8Array): Uint8Array { 131 const rsaKey = new RSAKey(); 132 rsaKey.setPrivateEx( 133 hexEncode(base64Decode(this.jwkPrivate.n)), 134 hexEncode(base64Decode(this.jwkPrivate.e)), 135 hexEncode(base64Decode(this.jwkPrivate.d)), 136 hexEncode(base64Decode(this.jwkPrivate.p)), 137 hexEncode(base64Decode(this.jwkPrivate.q)), 138 hexEncode(base64Decode(this.jwkPrivate.dp)), 139 hexEncode(base64Decode(this.jwkPrivate.dq)), 140 hexEncode(base64Decode(this.jwkPrivate.qi))); 141 assertTrue(rsaKey.n.bitLength() === MODULUS_SIZE_BITS); 142 143 // Message Layout (size equals that of the key modulus): 144 // 00 01 FF FF FF FF ... FF [ASN.1 PREFIX] [TOKEN] 145 const message = new Uint8Array(MODULUS_SIZE); 146 147 // Initially fill the buffer with the padding 148 message.fill(0xFF); 149 150 // add prefix 151 message[0] = 0x00; 152 message[1] = 0x01; 153 154 // add the ASN.1 prefix 155 message.set( 156 SIGNING_ASN1_PREFIX, 157 message.length - SIGNING_ASN1_PREFIX.length - token.length); 158 159 // then the actual token at the end 160 message.set(token, message.length - token.length); 161 162 const messageInteger = new BigInteger(Array.from(message)); 163 const signature = rsaKey.doPrivate(messageInteger); 164 return new Uint8Array(bigIntToFixedByteArray(signature, MODULUS_SIZE)); 165 } 166 167 // Construct public key to match the adb format: 168 // go/codesearch/rvc-arc/system/core/libcrypto_utils/android_pubkey.c;l=38-53 169 getPublicKey(): string { 170 const rsaKey = new RSAKey(); 171 rsaKey.setPublic( 172 hexEncode(base64Decode((assertExists(this.jwkPrivate.n)))), 173 hexEncode(base64Decode((assertExists(this.jwkPrivate.e))))); 174 175 const n0inv = R32.subtract(rsaKey.n.modInverse(R32)).intValue(); 176 const r = BigInteger.ONE.shiftLeft(1).pow(MODULUS_SIZE_BITS); 177 const rr = r.multiply(r).mod(rsaKey.n); 178 179 const buffer = new ArrayBuffer(PUBKEY_ENCODED_SIZE); 180 const dv = new DataView(buffer); 181 dv.setUint32(0, MODULUS_SIZE_WORDS, true); 182 dv.setUint32(WORD_SIZE, n0inv, true); 183 184 const dvU8 = new Uint8Array(dv.buffer, dv.byteOffset, dv.byteLength); 185 dvU8.set( 186 bigIntToFixedByteArray(rsaKey.n, MODULUS_SIZE).reverse(), 187 2 * WORD_SIZE); 188 dvU8.set( 189 bigIntToFixedByteArray(rr, MODULUS_SIZE).reverse(), 190 2 * WORD_SIZE + MODULUS_SIZE); 191 192 dv.setUint32(2 * WORD_SIZE + 2 * MODULUS_SIZE, rsaKey.e, true); 193 return base64Encode(dvU8) + ' ui.perfetto.dev'; 194 } 195 196 serializeKey(): string { 197 return JSON.stringify(this.jwkPrivate); 198 } 199} 200