• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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