• 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, 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