@yume-chan/adb 0.0.13 → 0.0.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.json +57 -0
- package/CHANGELOG.md +29 -1
- package/LICENSE +21 -21
- package/README.md +247 -247
- package/esm/adb.d.ts +12 -4
- package/esm/adb.d.ts.map +1 -1
- package/esm/adb.js +29 -19
- package/esm/adb.js.map +1 -1
- package/esm/commands/reverse.d.ts +9 -12
- package/esm/commands/reverse.d.ts.map +1 -1
- package/esm/commands/reverse.js +44 -36
- package/esm/commands/reverse.js.map +1 -1
- package/esm/commands/subprocess/protocols/none.d.ts +2 -1
- package/esm/commands/subprocess/protocols/none.d.ts.map +1 -1
- package/esm/commands/subprocess/protocols/none.js +10 -9
- package/esm/commands/subprocess/protocols/none.js.map +1 -1
- package/esm/commands/sync/pull.d.ts.map +1 -1
- package/esm/commands/sync/pull.js +6 -3
- package/esm/commands/sync/pull.js.map +1 -1
- package/esm/commands/sync/stat.d.ts.map +1 -1
- package/esm/commands/sync/stat.js +2 -2
- package/esm/commands/sync/stat.js.map +1 -1
- package/esm/commands/sync/sync.js +1 -1
- package/esm/commands/sync/sync.js.map +1 -1
- package/esm/socket/dispatcher.d.ts +25 -14
- package/esm/socket/dispatcher.d.ts.map +1 -1
- package/esm/socket/dispatcher.js +94 -63
- package/esm/socket/dispatcher.js.map +1 -1
- package/esm/socket/socket.d.ts +18 -4
- package/esm/socket/socket.d.ts.map +1 -1
- package/esm/socket/socket.js +36 -21
- package/esm/socket/socket.js.map +1 -1
- package/esm/stream/buffered.d.ts +7 -3
- package/esm/stream/buffered.d.ts.map +1 -1
- package/esm/stream/buffered.js +69 -49
- package/esm/stream/buffered.js.map +1 -1
- package/esm/stream/detect.js +1 -0
- package/esm/stream/detect.js.map +1 -1
- package/esm/stream/detect.polyfill.d.ts +1 -1
- package/esm/stream/detect.polyfill.d.ts.map +1 -1
- package/esm/stream/detect.polyfill.js +0 -5
- package/esm/stream/detect.polyfill.js.map +1 -1
- package/esm/stream/transform.d.ts +39 -13
- package/esm/stream/transform.d.ts.map +1 -1
- package/esm/stream/transform.js +67 -90
- package/esm/stream/transform.js.map +1 -1
- package/esm/utils/base64.js +5 -5
- package/esm/utils/base64.js.map +1 -1
- package/package.json +10 -10
- package/src/adb.ts +280 -270
- package/src/auth.ts +173 -173
- package/src/backend.ts +11 -11
- package/src/commands/base.ts +11 -11
- package/src/commands/framebuffer.ts +73 -73
- package/src/commands/index.ts +8 -8
- package/src/commands/install.ts +30 -30
- package/src/commands/power.ts +54 -54
- package/src/commands/reverse.ts +140 -136
- package/src/commands/subprocess/index.ts +139 -139
- package/src/commands/subprocess/protocols/none.ts +70 -68
- package/src/commands/subprocess/protocols/shell.ts +190 -190
- package/src/commands/subprocess/protocols/types.ts +59 -59
- package/src/commands/subprocess/utils.ts +20 -20
- package/src/commands/sync/index.ts +7 -7
- package/src/commands/sync/list.ts +86 -86
- package/src/commands/sync/pull.ts +46 -43
- package/src/commands/sync/push.ts +41 -41
- package/src/commands/sync/request.ts +50 -50
- package/src/commands/sync/response.ts +64 -64
- package/src/commands/sync/stat.ts +151 -150
- package/src/commands/sync/sync.ts +181 -181
- package/src/commands/tcpip.ts +21 -21
- package/src/crypto.ts +296 -296
- package/src/features.ts +9 -9
- package/src/index.ts +11 -11
- package/src/packet.ts +80 -80
- package/src/socket/dispatcher.ts +305 -270
- package/src/socket/index.ts +2 -2
- package/src/socket/socket.ts +155 -136
- package/src/stream/buffered.ts +168 -147
- package/src/stream/detect.native.ts +362 -362
- package/src/stream/detect.polyfill.ts +22 -28
- package/src/stream/detect.ts +7 -5
- package/src/stream/index.ts +3 -3
- package/src/stream/transform.ts +474 -483
- package/src/utils/auto-reset-event.ts +41 -41
- package/src/utils/base64.ts +306 -306
- package/src/utils/index.ts +3 -3
- package/tsconfig.build.json +3 -3
- package/tsconfig.build.tsbuildinfo +1 -1
- package/tsconfig.test.json +9 -10
- package/web-streams-polyfill-4.0.0-beta.2.tgz +0 -0
- package/esm/commands/subprocess/legacy.d.ts +0 -35
- package/esm/commands/subprocess/legacy.d.ts.map +0 -1
- package/esm/commands/subprocess/legacy.js +0 -53
- package/esm/commands/subprocess/legacy.js.map +0 -1
- package/esm/commands/subprocess/protocol.d.ts +0 -39
- package/esm/commands/subprocess/protocol.d.ts.map +0 -1
- package/esm/commands/subprocess/protocol.js +0 -157
- package/esm/commands/subprocess/protocol.js.map +0 -1
- package/esm/commands/subprocess/types.d.ts +0 -50
- package/esm/commands/subprocess/types.d.ts.map +0 -1
- package/esm/commands/subprocess/types.js +0 -2
- package/esm/commands/subprocess/types.js.map +0 -1
- package/esm/socket/controller.d.ts +0 -37
- package/esm/socket/controller.d.ts.map +0 -1
- package/esm/socket/controller.js +0 -66
- package/esm/socket/controller.js.map +0 -1
- package/esm/stream/detect.bak.d.ts +0 -240
- package/esm/stream/detect.bak.d.ts.map +0 -1
- package/esm/stream/detect.bak.js +0 -60
- package/esm/stream/detect.bak.js.map +0 -1
- package/esm/utils/encoding.d.ts +0 -3
- package/esm/utils/encoding.d.ts.map +0 -1
- package/esm/utils/encoding.js +0 -11
- package/esm/utils/encoding.js.map +0 -1
package/src/commands/tcpip.ts
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
import { AdbCommandBase } from './base.js';
|
|
2
|
-
|
|
3
|
-
export class AdbTcpIpCommand extends AdbCommandBase {
|
|
4
|
-
public async setPort(port: number): Promise<void> {
|
|
5
|
-
if (port <= 0) {
|
|
6
|
-
throw new Error(`Invalid port ${port}`);
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
const output = await this.adb.createSocketAndWait(`tcpip:${port}`);
|
|
10
|
-
if (output !== `restarting in TCP mode port: ${port}\n`) {
|
|
11
|
-
throw new Error('Invalid response');
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
public async disable(): Promise<void> {
|
|
16
|
-
const output = await this.adb.createSocketAndWait('usb:');
|
|
17
|
-
if (output !== 'restarting in USB mode\n') {
|
|
18
|
-
throw new Error('Invalid response');
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
}
|
|
1
|
+
import { AdbCommandBase } from './base.js';
|
|
2
|
+
|
|
3
|
+
export class AdbTcpIpCommand extends AdbCommandBase {
|
|
4
|
+
public async setPort(port: number): Promise<void> {
|
|
5
|
+
if (port <= 0) {
|
|
6
|
+
throw new Error(`Invalid port ${port}`);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const output = await this.adb.createSocketAndWait(`tcpip:${port}`);
|
|
10
|
+
if (output !== `restarting in TCP mode port: ${port}\n`) {
|
|
11
|
+
throw new Error('Invalid response');
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
public async disable(): Promise<void> {
|
|
16
|
+
const output = await this.adb.createSocketAndWait('usb:');
|
|
17
|
+
if (output !== 'restarting in USB mode\n') {
|
|
18
|
+
throw new Error('Invalid response');
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
package/src/crypto.ts
CHANGED
|
@@ -1,296 +1,296 @@
|
|
|
1
|
-
import { getBigUint64, setBigUint64 } from '@yume-chan/dataview-bigint-polyfill/esm/fallback.js';
|
|
2
|
-
|
|
3
|
-
const BigInt0 = BigInt(0);
|
|
4
|
-
const BigInt1 = BigInt(1);
|
|
5
|
-
const BigInt2 = BigInt(2);
|
|
6
|
-
const BigInt64 = BigInt(64);
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Gets the `BigInt` value at the specified byte offset and length from the start of the view. There is
|
|
10
|
-
* no alignment constraint; multi-byte values may be fetched from any offset.
|
|
11
|
-
*
|
|
12
|
-
* Only supports Big-Endian, because that's what ADB uses.
|
|
13
|
-
* @param byteOffset The place in the buffer at which the value should be retrieved.
|
|
14
|
-
*/
|
|
15
|
-
export function getBigUint(dataView: DataView, byteOffset: number, length: number): bigint {
|
|
16
|
-
let result = BigInt0;
|
|
17
|
-
|
|
18
|
-
// Currently `length` must be a multiplication of 8
|
|
19
|
-
// Support for arbitrary length can be easily added
|
|
20
|
-
|
|
21
|
-
for (let i = byteOffset; i < byteOffset + length; i += 8) {
|
|
22
|
-
result <<= BigInt64;
|
|
23
|
-
const value = getBigUint64(dataView, i, false);
|
|
24
|
-
result += value;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
return result;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Stores an arbitrary-precision positive `BigInt` value at the specified byte offset from the start of the view.
|
|
32
|
-
* @param byteOffset The place in the buffer at which the value should be set.
|
|
33
|
-
* @param value The value to set.
|
|
34
|
-
* @param littleEndian If `false` or `undefined`, a big-endian value should be written,
|
|
35
|
-
* otherwise a little-endian value should be written.
|
|
36
|
-
*/
|
|
37
|
-
export function setBigUint(dataView: DataView, byteOffset: number, value: bigint, littleEndian?: boolean) {
|
|
38
|
-
const start = byteOffset;
|
|
39
|
-
|
|
40
|
-
if (littleEndian) {
|
|
41
|
-
while (value > BigInt0) {
|
|
42
|
-
setBigUint64(dataView, byteOffset, value, true);
|
|
43
|
-
byteOffset += 8;
|
|
44
|
-
value >>= BigInt64;
|
|
45
|
-
}
|
|
46
|
-
} else {
|
|
47
|
-
// Because we don't know how long (in bits) the `value` is,
|
|
48
|
-
// Convert it to an array of `uint64` first.
|
|
49
|
-
const uint64Array: bigint[] = [];
|
|
50
|
-
while (value > BigInt0) {
|
|
51
|
-
uint64Array.push(BigInt.asUintN(64, value));
|
|
52
|
-
value >>= BigInt64;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
for (let i = uint64Array.length - 1; i >= 0; i -= 1) {
|
|
56
|
-
setBigUint64(dataView, byteOffset, uint64Array[i]!, false);
|
|
57
|
-
byteOffset += 8;
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
return byteOffset - start;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// These values are correct only if
|
|
65
|
-
// modulus length is 2048 and
|
|
66
|
-
// public exponent (e) is 65537
|
|
67
|
-
// Anyway, that's how this library generates keys
|
|
68
|
-
|
|
69
|
-
// To support other parameters,
|
|
70
|
-
// a proper ASN.1 parser can be used
|
|
71
|
-
|
|
72
|
-
// References:
|
|
73
|
-
//
|
|
74
|
-
// https://tools.ietf.org/html/rfc8017#appendix-A.1.2
|
|
75
|
-
// PKCS #1: RSA Cryptography Specifications Version 2.2
|
|
76
|
-
// A.1.2. RSA Private Key Syntax
|
|
77
|
-
//
|
|
78
|
-
// https://lapo.it/asn1js/
|
|
79
|
-
// https://github.com/lapo-luchini/asn1js
|
|
80
|
-
// ASN.1 JavaScript decoder
|
|
81
|
-
//
|
|
82
|
-
// https://www.itu.int/rec/T-REC-X.690-201508-I/en
|
|
83
|
-
// X.690: Specification of Distinguished Encoding Rules (DER)
|
|
84
|
-
|
|
85
|
-
const RsaPrivateKeyNOffset = 38;
|
|
86
|
-
const RsaPrivateKeyNLength = 2048 / 8;
|
|
87
|
-
const RsaPrivateKeyDOffset = 303;
|
|
88
|
-
const RsaPrivateKeyDLength = 2048 / 8;
|
|
89
|
-
|
|
90
|
-
export function parsePrivateKey(key: Uint8Array): [n: bigint, d: bigint] {
|
|
91
|
-
const view = new DataView(key.buffer, key.byteOffset, key.byteLength);
|
|
92
|
-
const n = getBigUint(view, RsaPrivateKeyNOffset, RsaPrivateKeyNLength);
|
|
93
|
-
const d = getBigUint(view, RsaPrivateKeyDOffset, RsaPrivateKeyDLength);
|
|
94
|
-
return [n, d];
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
// Taken from https://stackoverflow.com/a/51562038
|
|
98
|
-
// I can't understand, but it does work
|
|
99
|
-
// Only used with numbers less than 2^32 so doesn't need BigInt
|
|
100
|
-
export function modInverse(a: number, m: number) {
|
|
101
|
-
a = (a % m + m) % m;
|
|
102
|
-
if (!a || m < 2) {
|
|
103
|
-
return NaN; // invalid input
|
|
104
|
-
}
|
|
105
|
-
// find the gcd
|
|
106
|
-
const s = [];
|
|
107
|
-
let b = m;
|
|
108
|
-
while (b) {
|
|
109
|
-
[a, b] = [b, a % b];
|
|
110
|
-
s.push({ a, b });
|
|
111
|
-
}
|
|
112
|
-
if (a !== 1) {
|
|
113
|
-
return NaN; // inverse does not exists
|
|
114
|
-
}
|
|
115
|
-
// find the inverse
|
|
116
|
-
let x = 1;
|
|
117
|
-
let y = 0;
|
|
118
|
-
for (let i = s.length - 2; i >= 0; --i) {
|
|
119
|
-
[x, y] = [y, x - y * Math.floor(s[i]!.a / s[i]!.b)];
|
|
120
|
-
}
|
|
121
|
-
return (y % m + m) % m;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
export function calculatePublicKeyLength() {
|
|
125
|
-
return 4 + 4 + 2048 / 8 + 2048 / 8 + 4;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
export function calculatePublicKey(
|
|
129
|
-
privateKey: Uint8Array
|
|
130
|
-
): Uint8Array;
|
|
131
|
-
export function calculatePublicKey(
|
|
132
|
-
privateKey: Uint8Array,
|
|
133
|
-
output: Uint8Array,
|
|
134
|
-
): number;
|
|
135
|
-
export function calculatePublicKey(
|
|
136
|
-
privateKey: Uint8Array,
|
|
137
|
-
output?: Uint8Array,
|
|
138
|
-
): Uint8Array | number {
|
|
139
|
-
// Android has its own public key generation algorithm
|
|
140
|
-
// See https://android.googlesource.com/platform/system/core.git/+/91784040db2b9273687f88d8b95f729d4a61ecc2/libcrypto_utils/android_pubkey.cpp#111
|
|
141
|
-
|
|
142
|
-
// The public key is an array of
|
|
143
|
-
//
|
|
144
|
-
// [
|
|
145
|
-
// modulusLengthInWords, // 32-bit integer, a "word" is 32-bit so it must be 2048 / 8 / 4
|
|
146
|
-
// // Actually the comment in Android source code was wrong
|
|
147
|
-
// n0inv, // 32-bit integer, the modular inverse of (low 32 bits of n)
|
|
148
|
-
// modulus, // n
|
|
149
|
-
// rr, // Montgomery parameter R^2
|
|
150
|
-
// exponent, // 32-bit integer, must be 65537
|
|
151
|
-
// ]
|
|
152
|
-
//
|
|
153
|
-
// (All in little endian)
|
|
154
|
-
// See https://android.googlesource.com/platform/system/core.git/+/91784040db2b9273687f88d8b95f729d4a61ecc2/libcrypto_utils/android_pubkey.cpp#38
|
|
155
|
-
|
|
156
|
-
// extract `n` from private key
|
|
157
|
-
const [n] = parsePrivateKey(privateKey);
|
|
158
|
-
|
|
159
|
-
let outputType: 'Uint8Array' | 'number';
|
|
160
|
-
const outputLength = calculatePublicKeyLength();
|
|
161
|
-
if (!output) {
|
|
162
|
-
output = new Uint8Array(outputLength);
|
|
163
|
-
outputType = 'Uint8Array';
|
|
164
|
-
} else {
|
|
165
|
-
if (output.byteLength < outputLength) {
|
|
166
|
-
throw new Error('output buffer is too small');
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
outputType = 'number';
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
const outputView = new DataView(output.buffer, output.byteOffset, output.byteLength);
|
|
173
|
-
let outputOffset = 0;
|
|
174
|
-
|
|
175
|
-
// modulusLengthInWords
|
|
176
|
-
outputView.setUint32(outputOffset, 2048 / 8 / 4, true);
|
|
177
|
-
outputOffset += 4;
|
|
178
|
-
|
|
179
|
-
// Calculate `n0inv`
|
|
180
|
-
// Don't know why need to multiple -1
|
|
181
|
-
// Didn't exist in Android codebase
|
|
182
|
-
const n0inv = modInverse(Number(BigInt.asUintN(32, n) * BigInt(-1)), 2 ** 32);
|
|
183
|
-
outputView.setUint32(outputOffset, n0inv, true);
|
|
184
|
-
outputOffset += 4;
|
|
185
|
-
|
|
186
|
-
// Write n
|
|
187
|
-
setBigUint(outputView, outputOffset, n, true);
|
|
188
|
-
outputOffset += 256;
|
|
189
|
-
|
|
190
|
-
// Calculate rr = (2^(rsa_size)) ^ 2 mod n
|
|
191
|
-
let rr = BigInt(2) ** BigInt(4096) % n;
|
|
192
|
-
outputOffset += setBigUint(outputView, outputOffset, rr, true);
|
|
193
|
-
|
|
194
|
-
// exponent
|
|
195
|
-
outputView.setUint32(outputOffset, 65537, true);
|
|
196
|
-
outputOffset += 4;
|
|
197
|
-
|
|
198
|
-
if (outputType === 'Uint8Array') {
|
|
199
|
-
return output;
|
|
200
|
-
} else {
|
|
201
|
-
return outputLength;
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
/**
|
|
206
|
-
* Modular exponentiation.
|
|
207
|
-
*
|
|
208
|
-
* Calculate `(base ** exponent) % modulus` without actually calculating `(base ** exponent)`.
|
|
209
|
-
*
|
|
210
|
-
* See https://en.wikipedia.org/wiki/Modular_exponentiation#Implementation_in_Lua
|
|
211
|
-
*/
|
|
212
|
-
export function powMod(base: bigint, exponent: bigint, modulus: bigint): bigint {
|
|
213
|
-
if (modulus === BigInt1) {
|
|
214
|
-
return BigInt0;
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
let r = BigInt1;
|
|
218
|
-
base = base % modulus;
|
|
219
|
-
|
|
220
|
-
while (exponent > BigInt0) {
|
|
221
|
-
if (BigInt.asUintN(1, exponent) === BigInt1) {
|
|
222
|
-
r = r * base % modulus;
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
exponent >>= BigInt1;
|
|
226
|
-
base = base ** BigInt2 % modulus;
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
return r;
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
export const SHA1_DIGEST_LENGTH = 20;
|
|
233
|
-
|
|
234
|
-
export const ASN1_SEQUENCE = 0x30;
|
|
235
|
-
export const ASN1_OCTET_STRING = 0x04;
|
|
236
|
-
export const ASN1_NULL = 0x05;
|
|
237
|
-
export const ASN1_OID = 0x06;
|
|
238
|
-
|
|
239
|
-
// PKCS#1 SHA-1 hash digest info
|
|
240
|
-
export const SHA1_DIGEST_INFO = new Uint8Array([
|
|
241
|
-
ASN1_SEQUENCE, 0x0d + SHA1_DIGEST_LENGTH,
|
|
242
|
-
ASN1_SEQUENCE, 0x09,
|
|
243
|
-
// SHA-1 (1 3 14 3 2 26)
|
|
244
|
-
ASN1_OID, 0x05, 1 * 40 + 3, 14, 3, 2, 26,
|
|
245
|
-
ASN1_NULL, 0x00,
|
|
246
|
-
ASN1_OCTET_STRING, SHA1_DIGEST_LENGTH
|
|
247
|
-
]);
|
|
248
|
-
|
|
249
|
-
// SubtleCrypto.sign() will hash the given data and sign the hash
|
|
250
|
-
// But we don't need the hashing step
|
|
251
|
-
// (In another word, ADB just requires the client to
|
|
252
|
-
// encrypt the given data with its private key)
|
|
253
|
-
// However SubtileCrypto.encrypt() doesn't accept 'RSASSA-PKCS1-v1_5' algorithm
|
|
254
|
-
// So we need to implement the encryption by ourself
|
|
255
|
-
export function sign(privateKey: Uint8Array, data: Uint8Array): Uint8Array {
|
|
256
|
-
const [n, d] = parsePrivateKey(privateKey);
|
|
257
|
-
|
|
258
|
-
// PKCS#1 padding
|
|
259
|
-
const padded = new Uint8Array(256);
|
|
260
|
-
let index = 0;
|
|
261
|
-
|
|
262
|
-
padded[index] = 0;
|
|
263
|
-
index += 1;
|
|
264
|
-
|
|
265
|
-
padded[index] = 1;
|
|
266
|
-
index += 1;
|
|
267
|
-
|
|
268
|
-
const fillLength = padded.length - SHA1_DIGEST_INFO.length - data.length - 1;
|
|
269
|
-
while (index < fillLength) {
|
|
270
|
-
padded[index] = 0xff;
|
|
271
|
-
index += 1;
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
padded[index] = 0;
|
|
275
|
-
index += 1;
|
|
276
|
-
|
|
277
|
-
padded.set(SHA1_DIGEST_INFO, index);
|
|
278
|
-
index += SHA1_DIGEST_INFO.length;
|
|
279
|
-
|
|
280
|
-
padded.set(data, index);
|
|
281
|
-
|
|
282
|
-
// Encryption
|
|
283
|
-
// signature = padded ** d % n
|
|
284
|
-
const view = new DataView(padded.buffer);
|
|
285
|
-
const signature = powMod(
|
|
286
|
-
getBigUint(view, 0, view.byteLength),
|
|
287
|
-
d,
|
|
288
|
-
n
|
|
289
|
-
);
|
|
290
|
-
|
|
291
|
-
// `padded` is not used anymore,
|
|
292
|
-
// re-use the buffer to store the result
|
|
293
|
-
setBigUint(view, 0, signature, false);
|
|
294
|
-
|
|
295
|
-
return padded;
|
|
296
|
-
}
|
|
1
|
+
import { getBigUint64, setBigUint64 } from '@yume-chan/dataview-bigint-polyfill/esm/fallback.js';
|
|
2
|
+
|
|
3
|
+
const BigInt0 = BigInt(0);
|
|
4
|
+
const BigInt1 = BigInt(1);
|
|
5
|
+
const BigInt2 = BigInt(2);
|
|
6
|
+
const BigInt64 = BigInt(64);
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Gets the `BigInt` value at the specified byte offset and length from the start of the view. There is
|
|
10
|
+
* no alignment constraint; multi-byte values may be fetched from any offset.
|
|
11
|
+
*
|
|
12
|
+
* Only supports Big-Endian, because that's what ADB uses.
|
|
13
|
+
* @param byteOffset The place in the buffer at which the value should be retrieved.
|
|
14
|
+
*/
|
|
15
|
+
export function getBigUint(dataView: DataView, byteOffset: number, length: number): bigint {
|
|
16
|
+
let result = BigInt0;
|
|
17
|
+
|
|
18
|
+
// Currently `length` must be a multiplication of 8
|
|
19
|
+
// Support for arbitrary length can be easily added
|
|
20
|
+
|
|
21
|
+
for (let i = byteOffset; i < byteOffset + length; i += 8) {
|
|
22
|
+
result <<= BigInt64;
|
|
23
|
+
const value = getBigUint64(dataView, i, false);
|
|
24
|
+
result += value;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return result;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Stores an arbitrary-precision positive `BigInt` value at the specified byte offset from the start of the view.
|
|
32
|
+
* @param byteOffset The place in the buffer at which the value should be set.
|
|
33
|
+
* @param value The value to set.
|
|
34
|
+
* @param littleEndian If `false` or `undefined`, a big-endian value should be written,
|
|
35
|
+
* otherwise a little-endian value should be written.
|
|
36
|
+
*/
|
|
37
|
+
export function setBigUint(dataView: DataView, byteOffset: number, value: bigint, littleEndian?: boolean) {
|
|
38
|
+
const start = byteOffset;
|
|
39
|
+
|
|
40
|
+
if (littleEndian) {
|
|
41
|
+
while (value > BigInt0) {
|
|
42
|
+
setBigUint64(dataView, byteOffset, value, true);
|
|
43
|
+
byteOffset += 8;
|
|
44
|
+
value >>= BigInt64;
|
|
45
|
+
}
|
|
46
|
+
} else {
|
|
47
|
+
// Because we don't know how long (in bits) the `value` is,
|
|
48
|
+
// Convert it to an array of `uint64` first.
|
|
49
|
+
const uint64Array: bigint[] = [];
|
|
50
|
+
while (value > BigInt0) {
|
|
51
|
+
uint64Array.push(BigInt.asUintN(64, value));
|
|
52
|
+
value >>= BigInt64;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
for (let i = uint64Array.length - 1; i >= 0; i -= 1) {
|
|
56
|
+
setBigUint64(dataView, byteOffset, uint64Array[i]!, false);
|
|
57
|
+
byteOffset += 8;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return byteOffset - start;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// These values are correct only if
|
|
65
|
+
// modulus length is 2048 and
|
|
66
|
+
// public exponent (e) is 65537
|
|
67
|
+
// Anyway, that's how this library generates keys
|
|
68
|
+
|
|
69
|
+
// To support other parameters,
|
|
70
|
+
// a proper ASN.1 parser can be used
|
|
71
|
+
|
|
72
|
+
// References:
|
|
73
|
+
//
|
|
74
|
+
// https://tools.ietf.org/html/rfc8017#appendix-A.1.2
|
|
75
|
+
// PKCS #1: RSA Cryptography Specifications Version 2.2
|
|
76
|
+
// A.1.2. RSA Private Key Syntax
|
|
77
|
+
//
|
|
78
|
+
// https://lapo.it/asn1js/
|
|
79
|
+
// https://github.com/lapo-luchini/asn1js
|
|
80
|
+
// ASN.1 JavaScript decoder
|
|
81
|
+
//
|
|
82
|
+
// https://www.itu.int/rec/T-REC-X.690-201508-I/en
|
|
83
|
+
// X.690: Specification of Distinguished Encoding Rules (DER)
|
|
84
|
+
|
|
85
|
+
const RsaPrivateKeyNOffset = 38;
|
|
86
|
+
const RsaPrivateKeyNLength = 2048 / 8;
|
|
87
|
+
const RsaPrivateKeyDOffset = 303;
|
|
88
|
+
const RsaPrivateKeyDLength = 2048 / 8;
|
|
89
|
+
|
|
90
|
+
export function parsePrivateKey(key: Uint8Array): [n: bigint, d: bigint] {
|
|
91
|
+
const view = new DataView(key.buffer, key.byteOffset, key.byteLength);
|
|
92
|
+
const n = getBigUint(view, RsaPrivateKeyNOffset, RsaPrivateKeyNLength);
|
|
93
|
+
const d = getBigUint(view, RsaPrivateKeyDOffset, RsaPrivateKeyDLength);
|
|
94
|
+
return [n, d];
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Taken from https://stackoverflow.com/a/51562038
|
|
98
|
+
// I can't understand, but it does work
|
|
99
|
+
// Only used with numbers less than 2^32 so doesn't need BigInt
|
|
100
|
+
export function modInverse(a: number, m: number) {
|
|
101
|
+
a = (a % m + m) % m;
|
|
102
|
+
if (!a || m < 2) {
|
|
103
|
+
return NaN; // invalid input
|
|
104
|
+
}
|
|
105
|
+
// find the gcd
|
|
106
|
+
const s = [];
|
|
107
|
+
let b = m;
|
|
108
|
+
while (b) {
|
|
109
|
+
[a, b] = [b, a % b];
|
|
110
|
+
s.push({ a, b });
|
|
111
|
+
}
|
|
112
|
+
if (a !== 1) {
|
|
113
|
+
return NaN; // inverse does not exists
|
|
114
|
+
}
|
|
115
|
+
// find the inverse
|
|
116
|
+
let x = 1;
|
|
117
|
+
let y = 0;
|
|
118
|
+
for (let i = s.length - 2; i >= 0; --i) {
|
|
119
|
+
[x, y] = [y, x - y * Math.floor(s[i]!.a / s[i]!.b)];
|
|
120
|
+
}
|
|
121
|
+
return (y % m + m) % m;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export function calculatePublicKeyLength() {
|
|
125
|
+
return 4 + 4 + 2048 / 8 + 2048 / 8 + 4;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export function calculatePublicKey(
|
|
129
|
+
privateKey: Uint8Array
|
|
130
|
+
): Uint8Array;
|
|
131
|
+
export function calculatePublicKey(
|
|
132
|
+
privateKey: Uint8Array,
|
|
133
|
+
output: Uint8Array,
|
|
134
|
+
): number;
|
|
135
|
+
export function calculatePublicKey(
|
|
136
|
+
privateKey: Uint8Array,
|
|
137
|
+
output?: Uint8Array,
|
|
138
|
+
): Uint8Array | number {
|
|
139
|
+
// Android has its own public key generation algorithm
|
|
140
|
+
// See https://android.googlesource.com/platform/system/core.git/+/91784040db2b9273687f88d8b95f729d4a61ecc2/libcrypto_utils/android_pubkey.cpp#111
|
|
141
|
+
|
|
142
|
+
// The public key is an array of
|
|
143
|
+
//
|
|
144
|
+
// [
|
|
145
|
+
// modulusLengthInWords, // 32-bit integer, a "word" is 32-bit so it must be 2048 / 8 / 4
|
|
146
|
+
// // Actually the comment in Android source code was wrong
|
|
147
|
+
// n0inv, // 32-bit integer, the modular inverse of (low 32 bits of n)
|
|
148
|
+
// modulus, // n
|
|
149
|
+
// rr, // Montgomery parameter R^2
|
|
150
|
+
// exponent, // 32-bit integer, must be 65537
|
|
151
|
+
// ]
|
|
152
|
+
//
|
|
153
|
+
// (All in little endian)
|
|
154
|
+
// See https://android.googlesource.com/platform/system/core.git/+/91784040db2b9273687f88d8b95f729d4a61ecc2/libcrypto_utils/android_pubkey.cpp#38
|
|
155
|
+
|
|
156
|
+
// extract `n` from private key
|
|
157
|
+
const [n] = parsePrivateKey(privateKey);
|
|
158
|
+
|
|
159
|
+
let outputType: 'Uint8Array' | 'number';
|
|
160
|
+
const outputLength = calculatePublicKeyLength();
|
|
161
|
+
if (!output) {
|
|
162
|
+
output = new Uint8Array(outputLength);
|
|
163
|
+
outputType = 'Uint8Array';
|
|
164
|
+
} else {
|
|
165
|
+
if (output.byteLength < outputLength) {
|
|
166
|
+
throw new Error('output buffer is too small');
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
outputType = 'number';
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const outputView = new DataView(output.buffer, output.byteOffset, output.byteLength);
|
|
173
|
+
let outputOffset = 0;
|
|
174
|
+
|
|
175
|
+
// modulusLengthInWords
|
|
176
|
+
outputView.setUint32(outputOffset, 2048 / 8 / 4, true);
|
|
177
|
+
outputOffset += 4;
|
|
178
|
+
|
|
179
|
+
// Calculate `n0inv`
|
|
180
|
+
// Don't know why need to multiple -1
|
|
181
|
+
// Didn't exist in Android codebase
|
|
182
|
+
const n0inv = modInverse(Number(BigInt.asUintN(32, n) * BigInt(-1)), 2 ** 32);
|
|
183
|
+
outputView.setUint32(outputOffset, n0inv, true);
|
|
184
|
+
outputOffset += 4;
|
|
185
|
+
|
|
186
|
+
// Write n
|
|
187
|
+
setBigUint(outputView, outputOffset, n, true);
|
|
188
|
+
outputOffset += 256;
|
|
189
|
+
|
|
190
|
+
// Calculate rr = (2^(rsa_size)) ^ 2 mod n
|
|
191
|
+
let rr = BigInt(2) ** BigInt(4096) % n;
|
|
192
|
+
outputOffset += setBigUint(outputView, outputOffset, rr, true);
|
|
193
|
+
|
|
194
|
+
// exponent
|
|
195
|
+
outputView.setUint32(outputOffset, 65537, true);
|
|
196
|
+
outputOffset += 4;
|
|
197
|
+
|
|
198
|
+
if (outputType === 'Uint8Array') {
|
|
199
|
+
return output;
|
|
200
|
+
} else {
|
|
201
|
+
return outputLength;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Modular exponentiation.
|
|
207
|
+
*
|
|
208
|
+
* Calculate `(base ** exponent) % modulus` without actually calculating `(base ** exponent)`.
|
|
209
|
+
*
|
|
210
|
+
* See https://en.wikipedia.org/wiki/Modular_exponentiation#Implementation_in_Lua
|
|
211
|
+
*/
|
|
212
|
+
export function powMod(base: bigint, exponent: bigint, modulus: bigint): bigint {
|
|
213
|
+
if (modulus === BigInt1) {
|
|
214
|
+
return BigInt0;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
let r = BigInt1;
|
|
218
|
+
base = base % modulus;
|
|
219
|
+
|
|
220
|
+
while (exponent > BigInt0) {
|
|
221
|
+
if (BigInt.asUintN(1, exponent) === BigInt1) {
|
|
222
|
+
r = r * base % modulus;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
exponent >>= BigInt1;
|
|
226
|
+
base = base ** BigInt2 % modulus;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
return r;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
export const SHA1_DIGEST_LENGTH = 20;
|
|
233
|
+
|
|
234
|
+
export const ASN1_SEQUENCE = 0x30;
|
|
235
|
+
export const ASN1_OCTET_STRING = 0x04;
|
|
236
|
+
export const ASN1_NULL = 0x05;
|
|
237
|
+
export const ASN1_OID = 0x06;
|
|
238
|
+
|
|
239
|
+
// PKCS#1 SHA-1 hash digest info
|
|
240
|
+
export const SHA1_DIGEST_INFO = new Uint8Array([
|
|
241
|
+
ASN1_SEQUENCE, 0x0d + SHA1_DIGEST_LENGTH,
|
|
242
|
+
ASN1_SEQUENCE, 0x09,
|
|
243
|
+
// SHA-1 (1 3 14 3 2 26)
|
|
244
|
+
ASN1_OID, 0x05, 1 * 40 + 3, 14, 3, 2, 26,
|
|
245
|
+
ASN1_NULL, 0x00,
|
|
246
|
+
ASN1_OCTET_STRING, SHA1_DIGEST_LENGTH
|
|
247
|
+
]);
|
|
248
|
+
|
|
249
|
+
// SubtleCrypto.sign() will hash the given data and sign the hash
|
|
250
|
+
// But we don't need the hashing step
|
|
251
|
+
// (In another word, ADB just requires the client to
|
|
252
|
+
// encrypt the given data with its private key)
|
|
253
|
+
// However SubtileCrypto.encrypt() doesn't accept 'RSASSA-PKCS1-v1_5' algorithm
|
|
254
|
+
// So we need to implement the encryption by ourself
|
|
255
|
+
export function sign(privateKey: Uint8Array, data: Uint8Array): Uint8Array {
|
|
256
|
+
const [n, d] = parsePrivateKey(privateKey);
|
|
257
|
+
|
|
258
|
+
// PKCS#1 padding
|
|
259
|
+
const padded = new Uint8Array(256);
|
|
260
|
+
let index = 0;
|
|
261
|
+
|
|
262
|
+
padded[index] = 0;
|
|
263
|
+
index += 1;
|
|
264
|
+
|
|
265
|
+
padded[index] = 1;
|
|
266
|
+
index += 1;
|
|
267
|
+
|
|
268
|
+
const fillLength = padded.length - SHA1_DIGEST_INFO.length - data.length - 1;
|
|
269
|
+
while (index < fillLength) {
|
|
270
|
+
padded[index] = 0xff;
|
|
271
|
+
index += 1;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
padded[index] = 0;
|
|
275
|
+
index += 1;
|
|
276
|
+
|
|
277
|
+
padded.set(SHA1_DIGEST_INFO, index);
|
|
278
|
+
index += SHA1_DIGEST_INFO.length;
|
|
279
|
+
|
|
280
|
+
padded.set(data, index);
|
|
281
|
+
|
|
282
|
+
// Encryption
|
|
283
|
+
// signature = padded ** d % n
|
|
284
|
+
const view = new DataView(padded.buffer);
|
|
285
|
+
const signature = powMod(
|
|
286
|
+
getBigUint(view, 0, view.byteLength),
|
|
287
|
+
d,
|
|
288
|
+
n
|
|
289
|
+
);
|
|
290
|
+
|
|
291
|
+
// `padded` is not used anymore,
|
|
292
|
+
// re-use the buffer to store the result
|
|
293
|
+
setBigUint(view, 0, signature, false);
|
|
294
|
+
|
|
295
|
+
return padded;
|
|
296
|
+
}
|
package/src/features.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
// The order follows
|
|
2
|
-
// https://android.googlesource.com/platform/packages/modules/adb/+/79010dc6d5ca7490c493df800d4421730f5466ca/transport.cpp#1252
|
|
3
|
-
export enum AdbFeatures {
|
|
4
|
-
ShellV2 = "shell_v2",
|
|
5
|
-
Cmd = 'cmd',
|
|
6
|
-
StatV2 = 'stat_v2',
|
|
7
|
-
ListV2 = 'ls_v2',
|
|
8
|
-
FixedPushMkdir = 'fixed_push_mkdir',
|
|
9
|
-
}
|
|
1
|
+
// The order follows
|
|
2
|
+
// https://android.googlesource.com/platform/packages/modules/adb/+/79010dc6d5ca7490c493df800d4421730f5466ca/transport.cpp#1252
|
|
3
|
+
export enum AdbFeatures {
|
|
4
|
+
ShellV2 = "shell_v2",
|
|
5
|
+
Cmd = 'cmd',
|
|
6
|
+
StatV2 = 'stat_v2',
|
|
7
|
+
ListV2 = 'ls_v2',
|
|
8
|
+
FixedPushMkdir = 'fixed_push_mkdir',
|
|
9
|
+
}
|