@yume-chan/adb 0.0.15 → 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.
Files changed (103) hide show
  1. package/CHANGELOG.json +21 -0
  2. package/CHANGELOG.md +11 -1
  3. package/LICENSE +21 -21
  4. package/README.md +247 -247
  5. package/esm/adb.d.ts +7 -0
  6. package/esm/adb.d.ts.map +1 -1
  7. package/esm/adb.js +7 -0
  8. package/esm/adb.js.map +1 -1
  9. package/esm/commands/subprocess/protocols/none.d.ts +1 -0
  10. package/esm/commands/subprocess/protocols/none.d.ts.map +1 -1
  11. package/esm/commands/subprocess/protocols/none.js +6 -5
  12. package/esm/commands/subprocess/protocols/none.js.map +1 -1
  13. package/esm/commands/sync/stat.d.ts.map +1 -1
  14. package/esm/commands/sync/stat.js.map +1 -1
  15. package/esm/socket/dispatcher.d.ts +4 -1
  16. package/esm/socket/dispatcher.d.ts.map +1 -1
  17. package/esm/socket/dispatcher.js +11 -8
  18. package/esm/socket/dispatcher.js.map +1 -1
  19. package/esm/socket/socket.d.ts +6 -1
  20. package/esm/socket/socket.d.ts.map +1 -1
  21. package/esm/socket/socket.js +17 -7
  22. package/esm/socket/socket.js.map +1 -1
  23. package/esm/stream/buffered.d.ts +6 -2
  24. package/esm/stream/buffered.d.ts.map +1 -1
  25. package/esm/stream/buffered.js +67 -47
  26. package/esm/stream/buffered.js.map +1 -1
  27. package/esm/stream/detect.js +1 -0
  28. package/esm/stream/detect.js.map +1 -1
  29. package/esm/stream/detect.polyfill.d.ts.map +1 -1
  30. package/esm/stream/detect.polyfill.js +0 -5
  31. package/esm/stream/detect.polyfill.js.map +1 -1
  32. package/esm/stream/transform.d.ts +20 -0
  33. package/esm/stream/transform.d.ts.map +1 -1
  34. package/esm/stream/transform.js +7 -1
  35. package/esm/stream/transform.js.map +1 -1
  36. package/package.json +10 -10
  37. package/src/adb.ts +280 -273
  38. package/src/auth.ts +173 -173
  39. package/src/backend.ts +11 -11
  40. package/src/commands/base.ts +11 -11
  41. package/src/commands/framebuffer.ts +73 -73
  42. package/src/commands/index.ts +8 -8
  43. package/src/commands/install.ts +30 -30
  44. package/src/commands/power.ts +54 -54
  45. package/src/commands/reverse.ts +140 -140
  46. package/src/commands/subprocess/index.ts +139 -139
  47. package/src/commands/subprocess/protocols/none.ts +70 -68
  48. package/src/commands/subprocess/protocols/shell.ts +190 -190
  49. package/src/commands/subprocess/protocols/types.ts +59 -59
  50. package/src/commands/subprocess/utils.ts +20 -20
  51. package/src/commands/sync/index.ts +7 -7
  52. package/src/commands/sync/list.ts +86 -86
  53. package/src/commands/sync/pull.ts +46 -46
  54. package/src/commands/sync/push.ts +41 -41
  55. package/src/commands/sync/request.ts +50 -50
  56. package/src/commands/sync/response.ts +64 -64
  57. package/src/commands/sync/stat.ts +151 -150
  58. package/src/commands/sync/sync.ts +181 -181
  59. package/src/commands/tcpip.ts +21 -21
  60. package/src/crypto.ts +296 -296
  61. package/src/features.ts +9 -9
  62. package/src/index.ts +11 -11
  63. package/src/packet.ts +80 -80
  64. package/src/socket/dispatcher.ts +305 -304
  65. package/src/socket/index.ts +2 -2
  66. package/src/socket/socket.ts +155 -144
  67. package/src/stream/buffered.ts +168 -147
  68. package/src/stream/detect.native.ts +362 -362
  69. package/src/stream/detect.polyfill.ts +22 -28
  70. package/src/stream/detect.ts +7 -5
  71. package/src/stream/index.ts +3 -3
  72. package/src/stream/transform.ts +474 -447
  73. package/src/utils/auto-reset-event.ts +41 -41
  74. package/src/utils/base64.ts +306 -306
  75. package/src/utils/index.ts +3 -3
  76. package/tsconfig.build.json +3 -3
  77. package/tsconfig.build.tsbuildinfo +1 -1
  78. package/tsconfig.test.json +9 -10
  79. package/web-streams-polyfill-4.0.0-beta.2.tgz +0 -0
  80. package/esm/commands/subprocess/legacy.d.ts +0 -35
  81. package/esm/commands/subprocess/legacy.d.ts.map +0 -1
  82. package/esm/commands/subprocess/legacy.js +0 -53
  83. package/esm/commands/subprocess/legacy.js.map +0 -1
  84. package/esm/commands/subprocess/protocol.d.ts +0 -39
  85. package/esm/commands/subprocess/protocol.d.ts.map +0 -1
  86. package/esm/commands/subprocess/protocol.js +0 -157
  87. package/esm/commands/subprocess/protocol.js.map +0 -1
  88. package/esm/commands/subprocess/types.d.ts +0 -50
  89. package/esm/commands/subprocess/types.d.ts.map +0 -1
  90. package/esm/commands/subprocess/types.js +0 -2
  91. package/esm/commands/subprocess/types.js.map +0 -1
  92. package/esm/socket/controller.d.ts +0 -37
  93. package/esm/socket/controller.d.ts.map +0 -1
  94. package/esm/socket/controller.js +0 -66
  95. package/esm/socket/controller.js.map +0 -1
  96. package/esm/stream/detect.bak.d.ts +0 -240
  97. package/esm/stream/detect.bak.d.ts.map +0 -1
  98. package/esm/stream/detect.bak.js +0 -60
  99. package/esm/stream/detect.bak.js.map +0 -1
  100. package/esm/utils/encoding.d.ts +0 -3
  101. package/esm/utils/encoding.d.ts.map +0 -1
  102. package/esm/utils/encoding.js +0 -11
  103. package/esm/utils/encoding.js.map +0 -1
@@ -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
+ }