blob: 71f7d19b8448c538e3e22af481c9c1df97e7a63e [file] [log] [blame]
// Copyright (C) 2022 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import {BigInteger, RSAKey} from 'jsbn-rsa';
import {assertExists, assertTrue} from '../../../base/logging';
import {
base64Decode,
base64Encode,
hexEncode,
} from '../../../base/string_utils';
import {RecordingError} from '../recording_error_handling';
const WORD_SIZE = 4;
const MODULUS_SIZE_BITS = 2048;
const MODULUS_SIZE = MODULUS_SIZE_BITS / 8;
const MODULUS_SIZE_WORDS = MODULUS_SIZE / WORD_SIZE;
const PUBKEY_ENCODED_SIZE = 3 * WORD_SIZE + 2 * MODULUS_SIZE;
const ADB_WEB_CRYPTO_ALGORITHM = {
name: 'RSASSA-PKCS1-v1_5',
hash: {name: 'SHA-1'},
publicExponent: new Uint8Array([0x01, 0x00, 0x01]), // 65537
modulusLength: MODULUS_SIZE_BITS,
};
const ADB_WEB_CRYPTO_EXPORTABLE = true;
const ADB_WEB_CRYPTO_OPERATIONS: KeyUsage[] = ['sign'];
const SIGNING_ASN1_PREFIX = [
0x00,
0x30,
0x21,
0x30,
0x09,
0x06,
0x05,
0x2B,
0x0E,
0x03,
0x02,
0x1A,
0x05,
0x00,
0x04,
0x14,
];
const R32 = BigInteger.ONE.shiftLeft(32); // 1 << 32
interface ValidJsonWebKey {
n: string;
e: string;
d: string;
p: string;
q: string;
dp: string;
dq: string;
qi: string;
}
function isValidJsonWebKey(key: JsonWebKey): key is ValidJsonWebKey {
return key.n !== undefined && key.e !== undefined && key.d !== undefined &&
key.p !== undefined && key.q !== undefined && key.dp !== undefined &&
key.dq !== undefined && key.qi !== undefined;
}
// Convert a BigInteger to an array of a specified size in bytes.
function bigIntToFixedByteArray(bn: BigInteger, size: number): Uint8Array {
const paddedBnBytes = bn.toByteArray();
let firstNonZeroIndex = 0;
while (firstNonZeroIndex < paddedBnBytes.length &&
paddedBnBytes[firstNonZeroIndex] === 0) {
firstNonZeroIndex++;
}
const bnBytes = Uint8Array.from(paddedBnBytes.slice(firstNonZeroIndex));
const res = new Uint8Array(size);
assertTrue(bnBytes.length <= res.length);
res.set(bnBytes, res.length - bnBytes.length);
return res;
}
export class AdbKey {
// We use this JsonWebKey to:
// - create a private key and sign with it
// - create a public key and send it to the device
// - serialize the JsonWebKey and send it to the device (or retrieve it
// from the device and deserialize)
jwkPrivate: ValidJsonWebKey;
private constructor(jwkPrivate: ValidJsonWebKey) {
this.jwkPrivate = jwkPrivate;
}
static async GenerateNewKeyPair(): Promise<AdbKey> {
// Construct a new CryptoKeyPair and keep its private key in JWB format.
const keyPair = await crypto.subtle.generateKey(
ADB_WEB_CRYPTO_ALGORITHM,
ADB_WEB_CRYPTO_EXPORTABLE,
ADB_WEB_CRYPTO_OPERATIONS);
const jwkPrivate = await crypto.subtle.exportKey('jwk', keyPair.privateKey);
if (!isValidJsonWebKey(jwkPrivate)) {
throw new RecordingError('Could not generate a valid private key.');
}
return new AdbKey(jwkPrivate);
}
static DeserializeKey(serializedKey: string): AdbKey {
return new AdbKey(JSON.parse(serializedKey));
}
// Perform an RSA signing operation for the ADB auth challenge.
//
// For the RSA signature, the token is expected to have already
// had the SHA-1 message digest applied.
//
// However, the adb token we receive from the device is made up of 20 randomly
// generated bytes that are treated like a SHA-1. Therefore, we need to update
// the message format.
sign(token: Uint8Array): Uint8Array {
const rsaKey = new RSAKey();
rsaKey.setPrivateEx(
hexEncode(base64Decode(this.jwkPrivate.n)),
hexEncode(base64Decode(this.jwkPrivate.e)),
hexEncode(base64Decode(this.jwkPrivate.d)),
hexEncode(base64Decode(this.jwkPrivate.p)),
hexEncode(base64Decode(this.jwkPrivate.q)),
hexEncode(base64Decode(this.jwkPrivate.dp)),
hexEncode(base64Decode(this.jwkPrivate.dq)),
hexEncode(base64Decode(this.jwkPrivate.qi)));
assertTrue(rsaKey.n.bitLength() === MODULUS_SIZE_BITS);
// Message Layout (size equals that of the key modulus):
// 00 01 FF FF FF FF ... FF [ASN.1 PREFIX] [TOKEN]
const message = new Uint8Array(MODULUS_SIZE);
// Initially fill the buffer with the padding
message.fill(0xFF);
// add prefix
message[0] = 0x00;
message[1] = 0x01;
// add the ASN.1 prefix
message.set(
SIGNING_ASN1_PREFIX,
message.length - SIGNING_ASN1_PREFIX.length - token.length);
// then the actual token at the end
message.set(token, message.length - token.length);
const messageInteger = new BigInteger(Array.from(message));
const signature = rsaKey.doPrivate(messageInteger);
return new Uint8Array(bigIntToFixedByteArray(signature, MODULUS_SIZE));
}
// Construct public key to match the adb format:
// go/codesearch/rvc-arc/system/core/libcrypto_utils/android_pubkey.c;l=38-53
getPublicKey(): string {
const rsaKey = new RSAKey();
rsaKey.setPublic(
hexEncode(base64Decode((assertExists(this.jwkPrivate.n)))),
hexEncode(base64Decode((assertExists(this.jwkPrivate.e)))));
const n0inv = R32.subtract(rsaKey.n.modInverse(R32)).intValue();
const r = BigInteger.ONE.shiftLeft(1).pow(MODULUS_SIZE_BITS);
const rr = r.multiply(r).mod(rsaKey.n);
const buffer = new ArrayBuffer(PUBKEY_ENCODED_SIZE);
const dv = new DataView(buffer);
dv.setUint32(0, MODULUS_SIZE_WORDS, true);
dv.setUint32(WORD_SIZE, n0inv, true);
const dvU8 = new Uint8Array(dv.buffer, dv.byteOffset, dv.byteLength);
dvU8.set(
bigIntToFixedByteArray(rsaKey.n, MODULUS_SIZE).reverse(),
2 * WORD_SIZE);
dvU8.set(
bigIntToFixedByteArray(rr, MODULUS_SIZE).reverse(),
2 * WORD_SIZE + MODULUS_SIZE);
dv.setUint32(2 * WORD_SIZE + 2 * MODULUS_SIZE, rsaKey.e, true);
return base64Encode(dvU8) + ' ui.perfetto.dev';
}
serializeKey(): string {
return JSON.stringify(this.jwkPrivate);
}
}