@yume-chan/adb 0.0.22 → 0.0.24
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 +51 -0
- package/CHANGELOG.md +24 -1
- package/LICENSE +1 -1
- package/esm/adb.d.ts +6 -3
- package/esm/adb.d.ts.map +1 -1
- package/esm/adb.js +11 -4
- package/esm/adb.js.map +1 -1
- package/esm/banner.d.ts.map +1 -1
- package/esm/banner.js +1 -0
- package/esm/banner.js.map +1 -1
- package/esm/commands/power.d.ts.map +1 -1
- package/esm/commands/power.js +6 -5
- package/esm/commands/power.js.map +1 -1
- package/esm/commands/reverse.d.ts.map +1 -1
- package/esm/commands/reverse.js +23 -11
- package/esm/commands/reverse.js.map +1 -1
- package/esm/commands/subprocess/command.js +3 -3
- package/esm/commands/subprocess/command.js.map +1 -1
- package/esm/commands/subprocess/protocols/none.d.ts +2 -2
- package/esm/commands/subprocess/protocols/none.d.ts.map +1 -1
- package/esm/commands/subprocess/protocols/shell.d.ts +3 -3
- package/esm/commands/subprocess/protocols/shell.d.ts.map +1 -1
- package/esm/commands/subprocess/protocols/shell.js +14 -69
- package/esm/commands/subprocess/protocols/shell.js.map +1 -1
- package/esm/commands/subprocess/protocols/types.d.ts +2 -2
- package/esm/commands/subprocess/protocols/types.d.ts.map +1 -1
- package/esm/commands/subprocess/utils.js +1 -1
- package/esm/commands/subprocess/utils.js.map +1 -1
- package/esm/commands/sync/list.d.ts +2 -5
- package/esm/commands/sync/list.d.ts.map +1 -1
- package/esm/commands/sync/list.js +2 -4
- package/esm/commands/sync/list.js.map +1 -1
- package/esm/commands/sync/pull.d.ts +1 -4
- package/esm/commands/sync/pull.d.ts.map +1 -1
- package/esm/commands/sync/pull.js +2 -2
- package/esm/commands/sync/pull.js.map +1 -1
- package/esm/commands/sync/push.d.ts +4 -7
- package/esm/commands/sync/push.d.ts.map +1 -1
- package/esm/commands/sync/push.js +3 -6
- package/esm/commands/sync/push.js.map +1 -1
- package/esm/commands/sync/request.d.ts +13 -18
- package/esm/commands/sync/request.d.ts.map +1 -1
- package/esm/commands/sync/request.js +23 -28
- package/esm/commands/sync/request.js.map +1 -1
- package/esm/commands/sync/response.d.ts +18 -12
- package/esm/commands/sync/response.d.ts.map +1 -1
- package/esm/commands/sync/response.js +39 -17
- package/esm/commands/sync/response.js.map +1 -1
- package/esm/commands/sync/socket.d.ts +2 -2
- package/esm/commands/sync/socket.d.ts.map +1 -1
- package/esm/commands/sync/socket.js +10 -5
- package/esm/commands/sync/socket.js.map +1 -1
- package/esm/commands/sync/stat.d.ts +0 -3
- package/esm/commands/sync/stat.d.ts.map +1 -1
- package/esm/commands/sync/stat.js +0 -2
- package/esm/commands/sync/stat.js.map +1 -1
- package/esm/commands/sync/sync.d.ts +4 -3
- package/esm/commands/sync/sync.d.ts.map +1 -1
- package/esm/commands/sync/sync.js +9 -7
- package/esm/commands/sync/sync.js.map +1 -1
- package/esm/commands/tcpip.js +1 -1
- package/esm/commands/tcpip.js.map +1 -1
- package/esm/daemon/auth.js +1 -1
- package/esm/daemon/auth.js.map +1 -1
- package/esm/daemon/crypto.d.ts +3 -2
- package/esm/daemon/crypto.d.ts.map +1 -1
- package/esm/daemon/crypto.js +56 -49
- package/esm/daemon/crypto.js.map +1 -1
- package/esm/daemon/dispatcher.d.ts +38 -6
- package/esm/daemon/dispatcher.d.ts.map +1 -1
- package/esm/daemon/dispatcher.js +86 -28
- package/esm/daemon/dispatcher.js.map +1 -1
- package/esm/daemon/packet.d.ts +2 -2
- package/esm/daemon/packet.d.ts.map +1 -1
- package/esm/daemon/packet.js +13 -12
- package/esm/daemon/packet.js.map +1 -1
- package/esm/daemon/socket.d.ts +11 -6
- package/esm/daemon/socket.d.ts.map +1 -1
- package/esm/daemon/socket.js +42 -12
- package/esm/daemon/socket.js.map +1 -1
- package/esm/daemon/transport.d.ts +75 -11
- package/esm/daemon/transport.d.ts.map +1 -1
- package/esm/daemon/transport.js +60 -33
- package/esm/daemon/transport.js.map +1 -1
- package/esm/features.d.ts +2 -1
- package/esm/features.d.ts.map +1 -1
- package/esm/features.js +2 -1
- package/esm/features.js.map +1 -1
- package/esm/server/client.d.ts +128 -48
- package/esm/server/client.d.ts.map +1 -1
- package/esm/server/client.js +378 -145
- package/esm/server/client.js.map +1 -1
- package/esm/server/transport.d.ts +3 -0
- package/esm/server/transport.d.ts.map +1 -1
- package/esm/server/transport.js +30 -4
- package/esm/server/transport.js.map +1 -1
- package/esm/utils/base64.d.ts.map +1 -1
- package/esm/utils/base64.js +33 -22
- package/esm/utils/base64.js.map +1 -1
- package/esm/utils/hex.d.ts +1 -1
- package/esm/utils/hex.d.ts.map +1 -1
- package/esm/utils/hex.js +12 -14
- package/esm/utils/hex.js.map +1 -1
- package/esm/utils/index.d.ts +1 -1
- package/esm/utils/index.d.ts.map +1 -1
- package/esm/utils/index.js +1 -1
- package/esm/utils/index.js.map +1 -1
- package/esm/utils/sequence-equal.d.ts +2 -0
- package/esm/utils/sequence-equal.d.ts.map +1 -0
- package/esm/utils/sequence-equal.js +12 -0
- package/esm/utils/sequence-equal.js.map +1 -0
- package/package.json +13 -15
- package/src/adb.ts +22 -6
- package/src/banner.ts +1 -0
- package/src/commands/power.ts +7 -5
- package/src/commands/reverse.ts +31 -18
- package/src/commands/subprocess/command.ts +3 -3
- package/src/commands/subprocess/protocols/none.ts +2 -2
- package/src/commands/subprocess/protocols/shell.ts +21 -100
- package/src/commands/subprocess/protocols/types.ts +2 -2
- package/src/commands/subprocess/utils.ts +1 -1
- package/src/commands/sync/list.ts +2 -4
- package/src/commands/sync/pull.ts +2 -2
- package/src/commands/sync/push.ts +7 -10
- package/src/commands/sync/request.ts +32 -32
- package/src/commands/sync/response.ts +47 -20
- package/src/commands/sync/socket.ts +13 -8
- package/src/commands/sync/stat.ts +0 -2
- package/src/commands/sync/sync.ts +13 -9
- package/src/commands/tcpip.ts +1 -1
- package/src/daemon/auth.ts +1 -1
- package/src/daemon/crypto.ts +64 -55
- package/src/daemon/dispatcher.ts +175 -37
- package/src/daemon/packet.ts +21 -14
- package/src/daemon/socket.ts +62 -28
- package/src/daemon/transport.ts +145 -40
- package/src/features.ts +2 -1
- package/src/server/client.ts +472 -224
- package/src/server/transport.ts +32 -4
- package/src/utils/base64.ts +33 -22
- package/src/utils/hex.ts +16 -14
- package/src/utils/index.ts +1 -1
- package/src/utils/sequence-equal.ts +13 -0
- package/tsconfig.build.tsbuildinfo +1 -1
- package/esm/utils/conditional-variable.d.ts +0 -8
- package/esm/utils/conditional-variable.d.ts.map +0 -1
- package/esm/utils/conditional-variable.js +0 -34
- package/esm/utils/conditional-variable.js.map +0 -1
- package/src/utils/conditional-variable.ts +0 -45
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import type {
|
|
2
|
-
|
|
2
|
+
MaybeConsumable,
|
|
3
3
|
WritableStreamDefaultWriter,
|
|
4
4
|
} from "@yume-chan/stream-extra";
|
|
5
5
|
import {
|
|
6
6
|
BufferCombiner,
|
|
7
7
|
BufferedReadableStream,
|
|
8
|
-
|
|
8
|
+
Consumable,
|
|
9
9
|
} from "@yume-chan/stream-extra";
|
|
10
10
|
import type { AsyncExactReadable } from "@yume-chan/struct";
|
|
11
11
|
|
|
@@ -13,7 +13,7 @@ import type { AdbSocket } from "../../adb.js";
|
|
|
13
13
|
import { AutoResetEvent } from "../../utils/index.js";
|
|
14
14
|
|
|
15
15
|
export class AdbSyncSocketLocked implements AsyncExactReadable {
|
|
16
|
-
readonly #writer: WritableStreamDefaultWriter<
|
|
16
|
+
readonly #writer: WritableStreamDefaultWriter<MaybeConsumable<Uint8Array>>;
|
|
17
17
|
readonly #readable: BufferedReadableStream;
|
|
18
18
|
readonly #socketLock: AutoResetEvent;
|
|
19
19
|
readonly #writeLock = new AutoResetEvent();
|
|
@@ -24,7 +24,7 @@ export class AdbSyncSocketLocked implements AsyncExactReadable {
|
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
constructor(
|
|
27
|
-
writer: WritableStreamDefaultWriter<
|
|
27
|
+
writer: WritableStreamDefaultWriter<MaybeConsumable<Uint8Array>>,
|
|
28
28
|
readable: BufferedReadableStream,
|
|
29
29
|
bufferSize: number,
|
|
30
30
|
lock: AutoResetEvent,
|
|
@@ -35,8 +35,9 @@ export class AdbSyncSocketLocked implements AsyncExactReadable {
|
|
|
35
35
|
this.#combiner = new BufferCombiner(bufferSize);
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
async #
|
|
39
|
-
|
|
38
|
+
async #write(buffer: Uint8Array) {
|
|
39
|
+
// `#combiner` will reuse the buffer, so we need to use the Consumable pattern
|
|
40
|
+
await Consumable.WritableStream.write(this.#writer, buffer);
|
|
40
41
|
}
|
|
41
42
|
|
|
42
43
|
async flush() {
|
|
@@ -44,7 +45,7 @@ export class AdbSyncSocketLocked implements AsyncExactReadable {
|
|
|
44
45
|
await this.#writeLock.wait();
|
|
45
46
|
const buffer = this.#combiner.flush();
|
|
46
47
|
if (buffer) {
|
|
47
|
-
await this.#
|
|
48
|
+
await this.#write(buffer);
|
|
48
49
|
}
|
|
49
50
|
} finally {
|
|
50
51
|
this.#writeLock.notifyOne();
|
|
@@ -55,7 +56,7 @@ export class AdbSyncSocketLocked implements AsyncExactReadable {
|
|
|
55
56
|
try {
|
|
56
57
|
await this.#writeLock.wait();
|
|
57
58
|
for (const buffer of this.#combiner.push(data)) {
|
|
58
|
-
await this.#
|
|
59
|
+
await this.#write(buffer);
|
|
59
60
|
}
|
|
60
61
|
} finally {
|
|
61
62
|
this.#writeLock.notifyOne();
|
|
@@ -63,11 +64,15 @@ export class AdbSyncSocketLocked implements AsyncExactReadable {
|
|
|
63
64
|
}
|
|
64
65
|
|
|
65
66
|
async readExactly(length: number) {
|
|
67
|
+
// The request may still be in the internal buffer.
|
|
68
|
+
// Call `flush` to send it before starting reading
|
|
66
69
|
await this.flush();
|
|
67
70
|
return await this.#readable.readExactly(length);
|
|
68
71
|
}
|
|
69
72
|
|
|
70
73
|
release(): void {
|
|
74
|
+
// In theory, the writer shouldn't leave anything in the buffer,
|
|
75
|
+
// but to be safe, call `flush` to throw away any remaining data.
|
|
71
76
|
this.#combiner.flush();
|
|
72
77
|
this.#socketLock.notifyOne();
|
|
73
78
|
}
|
|
@@ -29,7 +29,6 @@ export const AdbSyncLstatResponse = new Struct({ littleEndian: true })
|
|
|
29
29
|
.int32("size")
|
|
30
30
|
.int32("mtime")
|
|
31
31
|
.extra({
|
|
32
|
-
id: AdbSyncResponseId.Lstat as const,
|
|
33
32
|
get type() {
|
|
34
33
|
return (this.mode >> 12) as LinuxFileType;
|
|
35
34
|
},
|
|
@@ -83,7 +82,6 @@ export const AdbSyncStatResponse = new Struct({ littleEndian: true })
|
|
|
83
82
|
.uint64("mtime")
|
|
84
83
|
.uint64("ctime")
|
|
85
84
|
.extra({
|
|
86
|
-
id: AdbSyncResponseId.Stat as const,
|
|
87
85
|
get type() {
|
|
88
86
|
return (this.mode >> 12) as LinuxFileType;
|
|
89
87
|
},
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { AutoDisposable } from "@yume-chan/event";
|
|
2
|
-
import type {
|
|
2
|
+
import type { MaybeConsumable, ReadableStream } from "@yume-chan/stream-extra";
|
|
3
3
|
|
|
4
4
|
import type { Adb, AdbSocket } from "../../adb.js";
|
|
5
5
|
import { AdbFeature } from "../../features.js";
|
|
@@ -9,6 +9,7 @@ import type { AdbSyncEntry } from "./list.js";
|
|
|
9
9
|
import { adbSyncOpenDir } from "./list.js";
|
|
10
10
|
import { adbSyncPull } from "./pull.js";
|
|
11
11
|
import { adbSyncPush } from "./push.js";
|
|
12
|
+
import type { AdbSyncSocketLocked } from "./socket.js";
|
|
12
13
|
import { AdbSyncSocket } from "./socket.js";
|
|
13
14
|
import type { AdbSyncStat, LinuxFileType } from "./stat.js";
|
|
14
15
|
import { adbSyncLstat, adbSyncStat } from "./stat.js";
|
|
@@ -31,7 +32,7 @@ export function dirname(path: string): string {
|
|
|
31
32
|
|
|
32
33
|
export interface AdbSyncWriteOptions {
|
|
33
34
|
filename: string;
|
|
34
|
-
file: ReadableStream<
|
|
35
|
+
file: ReadableStream<MaybeConsumable<Uint8Array>>;
|
|
35
36
|
type?: LinuxFileType;
|
|
36
37
|
permission?: number;
|
|
37
38
|
mtime?: number;
|
|
@@ -74,16 +75,15 @@ export class AdbSync extends AutoDisposable {
|
|
|
74
75
|
this._adb = adb;
|
|
75
76
|
this._socket = new AdbSyncSocket(socket, adb.maxPayloadSize);
|
|
76
77
|
|
|
77
|
-
this.#supportsStat = adb.
|
|
78
|
-
this.#supportsListV2 = adb.
|
|
79
|
-
this.#fixedPushMkdir = adb.
|
|
80
|
-
this.#supportsSendReceiveV2 = adb.
|
|
78
|
+
this.#supportsStat = adb.canUseFeature(AdbFeature.StatV2);
|
|
79
|
+
this.#supportsListV2 = adb.canUseFeature(AdbFeature.ListV2);
|
|
80
|
+
this.#fixedPushMkdir = adb.canUseFeature(AdbFeature.FixedPushMkdir);
|
|
81
|
+
this.#supportsSendReceiveV2 = adb.canUseFeature(
|
|
81
82
|
AdbFeature.SendReceiveV2,
|
|
82
83
|
);
|
|
83
84
|
// https://android.googlesource.com/platform/packages/modules/adb/+/91768a57b7138166e0a3d11f79cd55909dda7014/client/file_sync_client.cpp#1361
|
|
84
85
|
this.#needPushMkdirWorkaround =
|
|
85
|
-
this._adb.
|
|
86
|
-
!this.fixedPushMkdir;
|
|
86
|
+
this._adb.canUseFeature(AdbFeature.ShellV2) && !this.fixedPushMkdir;
|
|
87
87
|
}
|
|
88
88
|
|
|
89
89
|
/**
|
|
@@ -151,7 +151,7 @@ export class AdbSync extends AutoDisposable {
|
|
|
151
151
|
*/
|
|
152
152
|
async write(options: AdbSyncWriteOptions): Promise<void> {
|
|
153
153
|
if (this.needPushMkdirWorkaround) {
|
|
154
|
-
// It may fail if
|
|
154
|
+
// It may fail if `filename` already exists.
|
|
155
155
|
// Ignore the result.
|
|
156
156
|
// TODO: sync: test push mkdir workaround (need an Android 8 device)
|
|
157
157
|
await this._adb.subprocess.spawnAndWait([
|
|
@@ -168,6 +168,10 @@ export class AdbSync extends AutoDisposable {
|
|
|
168
168
|
});
|
|
169
169
|
}
|
|
170
170
|
|
|
171
|
+
lockSocket(): Promise<AdbSyncSocketLocked> {
|
|
172
|
+
return this._socket.lock();
|
|
173
|
+
}
|
|
174
|
+
|
|
171
175
|
override async dispose() {
|
|
172
176
|
super.dispose();
|
|
173
177
|
await this._socket.close();
|
package/src/commands/tcpip.ts
CHANGED
|
@@ -47,7 +47,7 @@ export class AdbTcpIpCommand extends AdbCommandBase {
|
|
|
47
47
|
|
|
48
48
|
async setPort(port: number): Promise<string> {
|
|
49
49
|
if (port <= 0) {
|
|
50
|
-
throw new
|
|
50
|
+
throw new TypeError(`Invalid port ${port}`);
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
const output = await this.adb.createSocketAndWait(`tcpip:${port}`);
|
package/src/daemon/auth.ts
CHANGED
package/src/daemon/crypto.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
getUint64BigEndian,
|
|
3
|
+
setInt64BigEndian,
|
|
4
|
+
setInt64LittleEndian,
|
|
5
|
+
} from "@yume-chan/no-data-view";
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* Gets the `BigInt` value at the specified byte offset and length from the start of the view. There is
|
|
@@ -11,7 +12,7 @@ import {
|
|
|
11
12
|
* @param byteOffset The place in the buffer at which the value should be retrieved.
|
|
12
13
|
*/
|
|
13
14
|
export function getBigUint(
|
|
14
|
-
|
|
15
|
+
array: Uint8Array,
|
|
15
16
|
byteOffset: number,
|
|
16
17
|
length: number,
|
|
17
18
|
): bigint {
|
|
@@ -22,8 +23,8 @@ export function getBigUint(
|
|
|
22
23
|
|
|
23
24
|
for (let i = byteOffset; i < byteOffset + length; i += 8) {
|
|
24
25
|
result <<= 64n;
|
|
25
|
-
const value =
|
|
26
|
-
result
|
|
26
|
+
const value = getUint64BigEndian(array, i);
|
|
27
|
+
result |= value;
|
|
27
28
|
}
|
|
28
29
|
|
|
29
30
|
return result;
|
|
@@ -32,40 +33,32 @@ export function getBigUint(
|
|
|
32
33
|
/**
|
|
33
34
|
* Stores an arbitrary-precision positive `BigInt` value at the specified byte offset from the start of the view.
|
|
34
35
|
* @param byteOffset The place in the buffer at which the value should be set.
|
|
36
|
+
* @param length The number of bytes to set.
|
|
35
37
|
* @param value The value to set.
|
|
36
38
|
* @param littleEndian If `false` or `undefined`, a big-endian value should be written,
|
|
37
39
|
* otherwise a little-endian value should be written.
|
|
38
40
|
*/
|
|
39
41
|
export function setBigUint(
|
|
40
|
-
|
|
42
|
+
array: Uint8Array,
|
|
41
43
|
byteOffset: number,
|
|
44
|
+
length: number,
|
|
42
45
|
value: bigint,
|
|
43
46
|
littleEndian?: boolean,
|
|
44
47
|
) {
|
|
45
|
-
const start = byteOffset;
|
|
46
|
-
|
|
47
48
|
if (littleEndian) {
|
|
48
49
|
while (value > 0n) {
|
|
49
|
-
|
|
50
|
+
setInt64LittleEndian(array, byteOffset, value);
|
|
50
51
|
byteOffset += 8;
|
|
51
52
|
value >>= 64n;
|
|
52
53
|
}
|
|
53
54
|
} else {
|
|
54
|
-
|
|
55
|
-
// Convert it to an array of `uint64` first.
|
|
56
|
-
const uint64Array: bigint[] = [];
|
|
55
|
+
let position = byteOffset + length - 8;
|
|
57
56
|
while (value > 0n) {
|
|
58
|
-
|
|
57
|
+
setInt64BigEndian(array, position, value);
|
|
58
|
+
position -= 8;
|
|
59
59
|
value >>= 64n;
|
|
60
60
|
}
|
|
61
|
-
|
|
62
|
-
for (let i = uint64Array.length - 1; i >= 0; i -= 1) {
|
|
63
|
-
setBigUint64(dataView, byteOffset, uint64Array[i]!, false);
|
|
64
|
-
byteOffset += 8;
|
|
65
|
-
}
|
|
66
61
|
}
|
|
67
|
-
|
|
68
|
-
return byteOffset - start;
|
|
69
62
|
}
|
|
70
63
|
|
|
71
64
|
// These values are correct only if
|
|
@@ -95,17 +88,26 @@ const RsaPrivateKeyDOffset = 303;
|
|
|
95
88
|
const RsaPrivateKeyDLength = 2048 / 8;
|
|
96
89
|
|
|
97
90
|
export function rsaParsePrivateKey(key: Uint8Array): [n: bigint, d: bigint] {
|
|
98
|
-
const
|
|
99
|
-
const
|
|
100
|
-
const d = getBigUint(view, RsaPrivateKeyDOffset, RsaPrivateKeyDLength);
|
|
91
|
+
const n = getBigUint(key, RsaPrivateKeyNOffset, RsaPrivateKeyNLength);
|
|
92
|
+
const d = getBigUint(key, RsaPrivateKeyDOffset, RsaPrivateKeyDLength);
|
|
101
93
|
return [n, d];
|
|
102
94
|
}
|
|
103
95
|
|
|
96
|
+
function nonNegativeMod(m: number, d: number) {
|
|
97
|
+
const r = m % d;
|
|
98
|
+
if (r > 0) {
|
|
99
|
+
return r;
|
|
100
|
+
}
|
|
101
|
+
return r + (d > 0 ? d : -d);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// https://en.wikipedia.org/wiki/Modular_multiplicative_inverse
|
|
105
|
+
// Solve for the smallest positive `x` in the equation `a * x ≡ 1 (mod m)`,
|
|
106
|
+
// or in other words, `a * x % m = 1`
|
|
104
107
|
// Taken from https://stackoverflow.com/a/51562038
|
|
105
|
-
// I can't understand, but it does work
|
|
106
108
|
// Only used with numbers smaller than 2^32 so doesn't need BigInt
|
|
107
109
|
export function modInverse(a: number, m: number) {
|
|
108
|
-
a = (
|
|
110
|
+
a = nonNegativeMod(a, m);
|
|
109
111
|
if (!a || m < 2) {
|
|
110
112
|
return NaN; // invalid input
|
|
111
113
|
}
|
|
@@ -116,6 +118,7 @@ export function modInverse(a: number, m: number) {
|
|
|
116
118
|
[a, b] = [b, a % b];
|
|
117
119
|
s.push({ a, b });
|
|
118
120
|
}
|
|
121
|
+
/* istanbul ignore next */
|
|
119
122
|
if (a !== 1) {
|
|
120
123
|
return NaN; // inverse does not exists
|
|
121
124
|
}
|
|
@@ -125,11 +128,14 @@ export function modInverse(a: number, m: number) {
|
|
|
125
128
|
for (let i = s.length - 2; i >= 0; i -= 1) {
|
|
126
129
|
[x, y] = [y, x - y * Math.floor(s[i]!.a / s[i]!.b)];
|
|
127
130
|
}
|
|
128
|
-
return (
|
|
131
|
+
return nonNegativeMod(y, m);
|
|
129
132
|
}
|
|
130
133
|
|
|
134
|
+
const ModulusLengthInBytes = 2048 / 8;
|
|
135
|
+
const ModulusLengthInWords = ModulusLengthInBytes / 4;
|
|
136
|
+
|
|
131
137
|
export function adbGetPublicKeySize() {
|
|
132
|
-
return 4 + 4 +
|
|
138
|
+
return 4 + 4 + ModulusLengthInBytes + ModulusLengthInBytes + 4;
|
|
133
139
|
}
|
|
134
140
|
|
|
135
141
|
export function adbGeneratePublicKey(privateKey: Uint8Array): Uint8Array;
|
|
@@ -141,22 +147,27 @@ export function adbGeneratePublicKey(
|
|
|
141
147
|
privateKey: Uint8Array,
|
|
142
148
|
output?: Uint8Array,
|
|
143
149
|
): Uint8Array | number {
|
|
144
|
-
//
|
|
145
|
-
//
|
|
146
|
-
|
|
147
|
-
//
|
|
150
|
+
// cspell: ignore: mincrypt
|
|
151
|
+
// Android 6 and earlier has its own encryption library called mincrypt
|
|
152
|
+
// This is the RSA public key format used by mincrypt:
|
|
153
|
+
// https://android.googlesource.com/platform/system/core/+/bb0c180e62703c2068a1b2c9f8ba6d634bf1553c/include/mincrypt/rsa.h#46
|
|
154
|
+
// `n0inv` and `rr` are pre-calculated to speed up RSA operations
|
|
155
|
+
|
|
156
|
+
// Android 7 switched its encryption library to BoringSSL, but still keeps the key format:
|
|
157
|
+
// https://android.googlesource.com/platform/system/core.git/+/91784040db2b9273687f88d8b95f729d4a61ecc2/libcrypto_utils/android_pubkey.cpp#38
|
|
158
|
+
// Except when reading a key, `n0inv` and `rr` are ignored (they are still populated when generating a key):
|
|
159
|
+
// https://android.googlesource.com/platform/system/core.git/+/91784040db2b9273687f88d8b95f729d4a61ecc2/libcrypto_utils/android_pubkey.cpp#55
|
|
160
|
+
|
|
161
|
+
// The public key is a struct (in little endian) of:
|
|
148
162
|
//
|
|
149
163
|
// [
|
|
150
164
|
// modulusLengthInWords, // 32-bit integer, a "word" is 32-bit so it must be 2048 / 8 / 4
|
|
151
|
-
// //
|
|
152
|
-
// n0inv, // 32-bit integer, the modular inverse of (
|
|
153
|
-
// modulus, // n
|
|
165
|
+
// // (the comment in Android source code is incorrect saying "This must be ANDROID_PUBKEY_MODULUS_SIZE")
|
|
166
|
+
// n0inv, // 32-bit integer, the modular inverse of (lower 32 bits of `n`)
|
|
167
|
+
// modulus, // `n`
|
|
154
168
|
// rr, // Montgomery parameter R^2
|
|
155
|
-
// exponent, // 32-bit integer, must be 65537
|
|
169
|
+
// exponent, // 32-bit integer, must be 3 or 65537
|
|
156
170
|
// ]
|
|
157
|
-
//
|
|
158
|
-
// (All in little endian)
|
|
159
|
-
// See https://android.googlesource.com/platform/system/core.git/+/91784040db2b9273687f88d8b95f729d4a61ecc2/libcrypto_utils/android_pubkey.cpp#38
|
|
160
171
|
|
|
161
172
|
let outputType: "Uint8Array" | "number";
|
|
162
173
|
const outputLength = adbGetPublicKeySize();
|
|
@@ -164,8 +175,8 @@ export function adbGeneratePublicKey(
|
|
|
164
175
|
output = new Uint8Array(outputLength);
|
|
165
176
|
outputType = "Uint8Array";
|
|
166
177
|
} else {
|
|
167
|
-
if (output.
|
|
168
|
-
throw new
|
|
178
|
+
if (output.length < outputLength) {
|
|
179
|
+
throw new TypeError("output buffer is too small");
|
|
169
180
|
}
|
|
170
181
|
|
|
171
182
|
outputType = "number";
|
|
@@ -174,31 +185,30 @@ export function adbGeneratePublicKey(
|
|
|
174
185
|
const outputView = new DataView(
|
|
175
186
|
output.buffer,
|
|
176
187
|
output.byteOffset,
|
|
177
|
-
output.
|
|
188
|
+
output.length,
|
|
178
189
|
);
|
|
179
190
|
let outputOffset = 0;
|
|
180
191
|
|
|
181
192
|
// modulusLengthInWords
|
|
182
|
-
outputView.setUint32(outputOffset,
|
|
193
|
+
outputView.setUint32(outputOffset, ModulusLengthInWords, true);
|
|
183
194
|
outputOffset += 4;
|
|
184
195
|
|
|
185
196
|
// extract `n` from private key
|
|
186
197
|
const [n] = rsaParsePrivateKey(privateKey);
|
|
187
198
|
|
|
188
199
|
// Calculate `n0inv`
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
const n0inv = modInverse(-Number(BigInt.asUintN(32, n)), 2 ** 32);
|
|
192
|
-
outputView.setUint32(outputOffset, n0inv, true);
|
|
200
|
+
const n0inv = -modInverse(Number(n % 2n ** 32n), 2 ** 32);
|
|
201
|
+
outputView.setInt32(outputOffset, n0inv, true);
|
|
193
202
|
outputOffset += 4;
|
|
194
203
|
|
|
195
|
-
// Write n
|
|
196
|
-
setBigUint(
|
|
197
|
-
outputOffset +=
|
|
204
|
+
// Write `n` (a.k.a. `modulus`)
|
|
205
|
+
setBigUint(output, outputOffset, ModulusLengthInBytes, n, true);
|
|
206
|
+
outputOffset += ModulusLengthInBytes;
|
|
198
207
|
|
|
199
|
-
// Calculate rr = (2
|
|
208
|
+
// Calculate rr = (2 ** (rsa_size)) ** 2 % n
|
|
200
209
|
const rr = 2n ** 4096n % n;
|
|
201
|
-
|
|
210
|
+
setBigUint(output, outputOffset, ModulusLengthInBytes, rr, true);
|
|
211
|
+
outputOffset += ModulusLengthInBytes;
|
|
202
212
|
|
|
203
213
|
// exponent
|
|
204
214
|
outputView.setUint32(outputOffset, 65537, true);
|
|
@@ -305,12 +315,11 @@ export function rsaSign(privateKey: Uint8Array, data: Uint8Array): Uint8Array {
|
|
|
305
315
|
|
|
306
316
|
// Encryption
|
|
307
317
|
// signature = padded ** d % n
|
|
308
|
-
const
|
|
309
|
-
const signature = powMod(getBigUint(view, 0, view.byteLength), d, n);
|
|
318
|
+
const signature = powMod(getBigUint(padded, 0, padded.length), d, n);
|
|
310
319
|
|
|
311
320
|
// `padded` is not used anymore,
|
|
312
321
|
// re-use the buffer to store the result
|
|
313
|
-
setBigUint(
|
|
322
|
+
setBigUint(padded, 0, padded.length, signature, false);
|
|
314
323
|
|
|
315
324
|
return padded;
|
|
316
325
|
}
|