appium-ios-remotexpc 0.0.5 → 0.1.0
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.md +12 -0
- package/build/src/index.d.ts +1 -1
- package/build/src/index.d.ts.map +1 -1
- package/build/src/lib/apple-tv/srp/crypto-utils.d.ts +53 -0
- package/build/src/lib/apple-tv/srp/crypto-utils.d.ts.map +1 -0
- package/build/src/lib/apple-tv/srp/crypto-utils.js +128 -0
- package/build/src/lib/apple-tv/srp/index.d.ts +3 -0
- package/build/src/lib/apple-tv/srp/index.d.ts.map +1 -0
- package/build/src/lib/apple-tv/srp/index.js +2 -0
- package/build/src/lib/apple-tv/srp/srp-client.d.ts +130 -0
- package/build/src/lib/apple-tv/srp/srp-client.d.ts.map +1 -0
- package/build/src/lib/apple-tv/srp/srp-client.js +288 -0
- package/build/src/lib/remote-xpc/remote-xpc-connection.d.ts +1 -2
- package/build/src/lib/remote-xpc/remote-xpc-connection.d.ts.map +1 -1
- package/build/src/lib/remote-xpc/remote-xpc-connection.js +1 -2
- package/build/src/lib/tunnel/index.d.ts +3 -2
- package/build/src/lib/tunnel/index.d.ts.map +1 -1
- package/build/src/lib/tunnel/index.js +6 -3
- package/build/src/lib/types.d.ts +11 -0
- package/build/src/lib/types.d.ts.map +1 -1
- package/build/src/services/ios/diagnostic-service/index.d.ts.map +1 -1
- package/build/src/services/ios/diagnostic-service/index.js +0 -1
- package/build/src/services.d.ts +3 -3
- package/build/src/services.d.ts.map +1 -1
- package/build/src/services.js +8 -5
- package/package.json +1 -1
- package/src/index.ts +1 -0
- package/src/lib/apple-tv/srp/crypto-utils.ts +166 -0
- package/src/lib/apple-tv/srp/index.ts +8 -0
- package/src/lib/apple-tv/srp/srp-client.ts +387 -0
- package/src/lib/remote-xpc/remote-xpc-connection.ts +1 -2
- package/src/lib/tunnel/index.ts +7 -5
- package/src/lib/types.ts +12 -0
- package/src/services/ios/diagnostic-service/index.ts +0 -1
- package/src/services.ts +10 -7
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
## [0.1.0](https://github.com/appium/appium-ios-remotexpc/compare/v0.0.6...v0.1.0) (2025-07-05)
|
|
2
|
+
|
|
3
|
+
### Features
|
|
4
|
+
|
|
5
|
+
* add SRP implementation for Apple TV authentication ([#51](https://github.com/appium/appium-ios-remotexpc/issues/51)) ([4cc8c4e](https://github.com/appium/appium-ios-remotexpc/commit/4cc8c4ef92a689306a903bcc8dd5cfad5b024d7b))
|
|
6
|
+
|
|
7
|
+
## [0.0.6](https://github.com/appium/appium-ios-remotexpc/compare/v0.0.5...v0.0.6) (2025-07-05)
|
|
8
|
+
|
|
9
|
+
### Bug Fixes
|
|
10
|
+
|
|
11
|
+
* To close the connection on appium-xcuitest-driver we need to expose remoteXPC ([#52](https://github.com/appium/appium-ios-remotexpc/issues/52)) ([835c59d](https://github.com/appium/appium-ios-remotexpc/commit/835c59dcf203bb097d71a9c64e42d0888a45ec2a))
|
|
12
|
+
|
|
1
13
|
## [0.0.5](https://github.com/appium/appium-ios-remotexpc/compare/v0.0.4...v0.0.5) (2025-07-04)
|
|
2
14
|
|
|
3
15
|
### Miscellaneous Chores
|
package/build/src/index.d.ts
CHANGED
|
@@ -4,6 +4,6 @@ import { TunnelRegistryServer, startTunnelRegistryServer } from './lib/tunnel/tu
|
|
|
4
4
|
import { Usbmux, createUsbmux } from './lib/usbmux/index.js';
|
|
5
5
|
import * as Services from './services.js';
|
|
6
6
|
import { startCoreDeviceProxy } from './services/ios/tunnel-service/index.js';
|
|
7
|
-
export type { DiagnosticsService, SyslogService, SocketInfo, TunnelResult, TunnelRegistry, TunnelRegistryEntry, } from './lib/types.js';
|
|
7
|
+
export type { DiagnosticsService, SyslogService, SocketInfo, TunnelResult, TunnelRegistry, TunnelRegistryEntry, DiagnosticsServiceWithConnection, } from './lib/types.js';
|
|
8
8
|
export { createUsbmux, Services, Usbmux, TunnelManager, PacketStreamServer, PacketStreamClient, createLockdownServiceByUDID, startCoreDeviceProxy, TunnelRegistryServer, startTunnelRegistryServer, };
|
|
9
9
|
//# sourceMappingURL=index.d.ts.map
|
package/build/src/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,2BAA2B,EAAE,MAAM,yBAAyB,CAAC;AACtE,OAAO,EACL,kBAAkB,EAClB,kBAAkB,EAClB,aAAa,EACd,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACL,oBAAoB,EACpB,yBAAyB,EAC1B,MAAM,wCAAwC,CAAC;AAChD,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAC7D,OAAO,KAAK,QAAQ,MAAM,eAAe,CAAC;AAC1C,OAAO,EAAE,oBAAoB,EAAE,MAAM,wCAAwC,CAAC;AAE9E,YAAY,EACV,kBAAkB,EAClB,aAAa,EACb,UAAU,EACV,YAAY,EACZ,cAAc,EACd,mBAAmB,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,2BAA2B,EAAE,MAAM,yBAAyB,CAAC;AACtE,OAAO,EACL,kBAAkB,EAClB,kBAAkB,EAClB,aAAa,EACd,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACL,oBAAoB,EACpB,yBAAyB,EAC1B,MAAM,wCAAwC,CAAC;AAChD,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAC7D,OAAO,KAAK,QAAQ,MAAM,eAAe,CAAC;AAC1C,OAAO,EAAE,oBAAoB,EAAE,MAAM,wCAAwC,CAAC;AAE9E,YAAY,EACV,kBAAkB,EAClB,aAAa,EACb,UAAU,EACV,YAAY,EACZ,cAAc,EACd,mBAAmB,EACnB,gCAAgC,GACjC,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,YAAY,EACZ,QAAQ,EACR,MAAM,EACN,aAAa,EACb,kBAAkB,EAClB,kBAAkB,EAClB,2BAA2B,EAC3B,oBAAoB,EACpB,oBAAoB,EACpB,yBAAyB,GAC1B,CAAC"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Computes a cryptographic hash of the provided input buffers.
|
|
3
|
+
*
|
|
4
|
+
* @param inputs - Variable number of Buffer objects to hash
|
|
5
|
+
* @returns The computed hash as a Buffer
|
|
6
|
+
* @throws {Error} If no inputs provided
|
|
7
|
+
*/
|
|
8
|
+
export declare function hash(...inputs: Buffer[]): Buffer;
|
|
9
|
+
/**
|
|
10
|
+
* Calculates the SRP multiplier parameter k = H(N, g).
|
|
11
|
+
*
|
|
12
|
+
* @param N - The large safe prime modulus
|
|
13
|
+
* @param g - The generator
|
|
14
|
+
* @param keyLength - The key length in bytes
|
|
15
|
+
* @returns The calculated k value as a bigint
|
|
16
|
+
* @throws {Error} If parameters are invalid
|
|
17
|
+
*/
|
|
18
|
+
export declare function calculateK(N: bigint, g: bigint, keyLength: number): bigint;
|
|
19
|
+
/**
|
|
20
|
+
* Calculates the private key x = H(salt, H(username:password)).
|
|
21
|
+
*
|
|
22
|
+
* @param salt - The salt buffer
|
|
23
|
+
* @param username - The username string
|
|
24
|
+
* @param password - The password string
|
|
25
|
+
* @returns The calculated x value as a bigint
|
|
26
|
+
* @throws {Error} If parameters are invalid
|
|
27
|
+
*/
|
|
28
|
+
export declare function calculateX(salt: Buffer, username: string, password: string): bigint;
|
|
29
|
+
/**
|
|
30
|
+
* Calculates the random scrambling parameter u = H(A, B).
|
|
31
|
+
*
|
|
32
|
+
* @param A - The client's public key
|
|
33
|
+
* @param B - The server's public key
|
|
34
|
+
* @param keyLength - The key length in bytes
|
|
35
|
+
* @returns The calculated u value as a bigint
|
|
36
|
+
* @throws {Error} If parameters are invalid
|
|
37
|
+
*/
|
|
38
|
+
export declare function calculateU(A: bigint, B: bigint, keyLength: number): bigint;
|
|
39
|
+
/**
|
|
40
|
+
* Calculates the client evidence M1 = H(H(N) xor H(g), H(username), salt, A, B, K).
|
|
41
|
+
*
|
|
42
|
+
* @param N - The large safe prime modulus
|
|
43
|
+
* @param g - The generator
|
|
44
|
+
* @param username - The username string
|
|
45
|
+
* @param salt - The salt buffer
|
|
46
|
+
* @param A - The client's public key
|
|
47
|
+
* @param B - The server's public key
|
|
48
|
+
* @param K - The session key
|
|
49
|
+
* @returns The calculated M1 evidence as a Buffer
|
|
50
|
+
* @throws {Error} If parameters are invalid
|
|
51
|
+
*/
|
|
52
|
+
export declare function calculateM1(N: bigint, g: bigint, username: string, salt: Buffer, A: bigint, B: bigint, K: Buffer): Buffer;
|
|
53
|
+
//# sourceMappingURL=crypto-utils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"crypto-utils.d.ts","sourceRoot":"","sources":["../../../../../src/lib/apple-tv/srp/crypto-utils.ts"],"names":[],"mappings":"AASA;;;;;;GAMG;AACH,wBAAgB,IAAI,CAAC,GAAG,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,CAehD;AAED;;;;;;;;GAQG;AACH,wBAAgB,UAAU,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAa1E;AAED;;;;;;;;GAQG;AACH,wBAAgB,UAAU,CACxB,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,GACf,MAAM,CAcR;AAED;;;;;;;;GAQG;AACH,wBAAgB,UAAU,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAkB1E;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,WAAW,CACzB,CAAC,EAAE,MAAM,EACT,CAAC,EAAE,MAAM,EACT,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,EACZ,CAAC,EAAE,MAAM,EACT,CAAC,EAAE,MAAM,EACT,CAAC,EAAE,MAAM,GACR,MAAM,CA6BR"}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { SRP_HASH_ALGORITHM } from '../constants.js';
|
|
3
|
+
import { bigIntToBuffer, bigIntToMinimalBuffer, bufferToBigInt, } from '../utils/buffer-utils.js';
|
|
4
|
+
/**
|
|
5
|
+
* Computes a cryptographic hash of the provided input buffers.
|
|
6
|
+
*
|
|
7
|
+
* @param inputs - Variable number of Buffer objects to hash
|
|
8
|
+
* @returns The computed hash as a Buffer
|
|
9
|
+
* @throws {Error} If no inputs provided
|
|
10
|
+
*/
|
|
11
|
+
export function hash(...inputs) {
|
|
12
|
+
if (inputs.length === 0) {
|
|
13
|
+
throw new Error('At least one input buffer is required for hashing');
|
|
14
|
+
}
|
|
15
|
+
const hasher = createHash(SRP_HASH_ALGORITHM);
|
|
16
|
+
for (const input of inputs) {
|
|
17
|
+
if (!Buffer.isBuffer(input)) {
|
|
18
|
+
throw new Error('All inputs must be Buffer objects');
|
|
19
|
+
}
|
|
20
|
+
hasher.update(input);
|
|
21
|
+
}
|
|
22
|
+
return hasher.digest();
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Calculates the SRP multiplier parameter k = H(N, g).
|
|
26
|
+
*
|
|
27
|
+
* @param N - The large safe prime modulus
|
|
28
|
+
* @param g - The generator
|
|
29
|
+
* @param keyLength - The key length in bytes
|
|
30
|
+
* @returns The calculated k value as a bigint
|
|
31
|
+
* @throws {Error} If parameters are invalid
|
|
32
|
+
*/
|
|
33
|
+
export function calculateK(N, g, keyLength) {
|
|
34
|
+
if (N <= BigInt(0) || g <= BigInt(0)) {
|
|
35
|
+
throw new Error('N and g must be positive');
|
|
36
|
+
}
|
|
37
|
+
if (keyLength <= 0) {
|
|
38
|
+
throw new Error('Key length must be positive');
|
|
39
|
+
}
|
|
40
|
+
const NBuffer = bigIntToBuffer(N, keyLength);
|
|
41
|
+
const gBuffer = bigIntToBuffer(g, keyLength);
|
|
42
|
+
const kHash = hash(NBuffer, gBuffer);
|
|
43
|
+
return bufferToBigInt(kHash);
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Calculates the private key x = H(salt, H(username:password)).
|
|
47
|
+
*
|
|
48
|
+
* @param salt - The salt buffer
|
|
49
|
+
* @param username - The username string
|
|
50
|
+
* @param password - The password string
|
|
51
|
+
* @returns The calculated x value as a bigint
|
|
52
|
+
* @throws {Error} If parameters are invalid
|
|
53
|
+
*/
|
|
54
|
+
export function calculateX(salt, username, password) {
|
|
55
|
+
if (!Buffer.isBuffer(salt) || salt.length === 0) {
|
|
56
|
+
throw new Error('Salt must be a non-empty Buffer');
|
|
57
|
+
}
|
|
58
|
+
if (!username || !password) {
|
|
59
|
+
throw new Error('Username and password must be non-empty strings');
|
|
60
|
+
}
|
|
61
|
+
const usernamePasswordHash = hash(Buffer.from(`${username}:${password}`, 'utf8'));
|
|
62
|
+
const xHash = hash(salt, usernamePasswordHash);
|
|
63
|
+
return bufferToBigInt(xHash);
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Calculates the random scrambling parameter u = H(A, B).
|
|
67
|
+
*
|
|
68
|
+
* @param A - The client's public key
|
|
69
|
+
* @param B - The server's public key
|
|
70
|
+
* @param keyLength - The key length in bytes
|
|
71
|
+
* @returns The calculated u value as a bigint
|
|
72
|
+
* @throws {Error} If parameters are invalid
|
|
73
|
+
*/
|
|
74
|
+
export function calculateU(A, B, keyLength) {
|
|
75
|
+
if (A <= BigInt(0) || B <= BigInt(0)) {
|
|
76
|
+
throw new Error('Public keys A and B must be positive');
|
|
77
|
+
}
|
|
78
|
+
if (keyLength <= 0) {
|
|
79
|
+
throw new Error('Key length must be positive');
|
|
80
|
+
}
|
|
81
|
+
const ABuffer = bigIntToBuffer(A, keyLength);
|
|
82
|
+
const BBuffer = bigIntToBuffer(B, keyLength);
|
|
83
|
+
const uHash = hash(ABuffer, BBuffer);
|
|
84
|
+
const u = bufferToBigInt(uHash);
|
|
85
|
+
if (u === BigInt(0)) {
|
|
86
|
+
throw new Error('Calculated u value cannot be zero (hash collision)');
|
|
87
|
+
}
|
|
88
|
+
return u;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Calculates the client evidence M1 = H(H(N) xor H(g), H(username), salt, A, B, K).
|
|
92
|
+
*
|
|
93
|
+
* @param N - The large safe prime modulus
|
|
94
|
+
* @param g - The generator
|
|
95
|
+
* @param username - The username string
|
|
96
|
+
* @param salt - The salt buffer
|
|
97
|
+
* @param A - The client's public key
|
|
98
|
+
* @param B - The server's public key
|
|
99
|
+
* @param K - The session key
|
|
100
|
+
* @returns The calculated M1 evidence as a Buffer
|
|
101
|
+
* @throws {Error} If parameters are invalid
|
|
102
|
+
*/
|
|
103
|
+
export function calculateM1(N, g, username, salt, A, B, K) {
|
|
104
|
+
if (N <= BigInt(0) || g <= BigInt(0) || A <= BigInt(0) || B <= BigInt(0)) {
|
|
105
|
+
throw new Error('All bigint parameters must be positive');
|
|
106
|
+
}
|
|
107
|
+
if (!username) {
|
|
108
|
+
throw new Error('Username must be non-empty');
|
|
109
|
+
}
|
|
110
|
+
if (!Buffer.isBuffer(salt) || salt.length === 0) {
|
|
111
|
+
throw new Error('Salt must be a non-empty Buffer');
|
|
112
|
+
}
|
|
113
|
+
if (!Buffer.isBuffer(K) || K.length === 0) {
|
|
114
|
+
throw new Error('Session key K must be a non-empty Buffer');
|
|
115
|
+
}
|
|
116
|
+
const NBytes = bigIntToMinimalBuffer(N);
|
|
117
|
+
const gBytes = bigIntToMinimalBuffer(g);
|
|
118
|
+
const NHash = hash(NBytes);
|
|
119
|
+
const gHash = hash(gBytes);
|
|
120
|
+
const NgXorBytes = Buffer.alloc(NHash.length);
|
|
121
|
+
for (let i = 0; i < NHash.length; i++) {
|
|
122
|
+
NgXorBytes[i] = NHash[i] ^ gHash[i];
|
|
123
|
+
}
|
|
124
|
+
const usernameHash = hash(Buffer.from(username, 'utf8'));
|
|
125
|
+
const ABytes = bigIntToMinimalBuffer(A);
|
|
126
|
+
const BBytes = bigIntToMinimalBuffer(B);
|
|
127
|
+
return hash(NgXorBytes, usernameHash, salt, ABytes, BBytes, K);
|
|
128
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/lib/apple-tv/srp/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EACL,IAAI,EACJ,UAAU,EACV,UAAU,EACV,UAAU,EACV,WAAW,GACZ,MAAM,mBAAmB,CAAC"}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SRP (Secure Remote Password) client implementation following RFC 5054.
|
|
3
|
+
*
|
|
4
|
+
* This class handles the client-side operations of the SRP protocol,
|
|
5
|
+
* including key generation, authentication proof computation, and
|
|
6
|
+
* session key derivation.
|
|
7
|
+
*/
|
|
8
|
+
export declare class SRPClient {
|
|
9
|
+
private static readonly ZERO;
|
|
10
|
+
private static readonly ONE;
|
|
11
|
+
private static readonly MAX_KEY_GENERATION_ATTEMPTS;
|
|
12
|
+
private readonly N;
|
|
13
|
+
private readonly g;
|
|
14
|
+
private readonly k;
|
|
15
|
+
private readonly N_MINUS_ONE;
|
|
16
|
+
private username;
|
|
17
|
+
private password;
|
|
18
|
+
private _salt;
|
|
19
|
+
private _a;
|
|
20
|
+
private _A;
|
|
21
|
+
private _B;
|
|
22
|
+
private _S;
|
|
23
|
+
private _K;
|
|
24
|
+
private keysGenerated;
|
|
25
|
+
private disposed;
|
|
26
|
+
constructor();
|
|
27
|
+
/**
|
|
28
|
+
* Sets the user identity credentials.
|
|
29
|
+
* Note: Username is set to SRP_USERNAME constant, but can be overridden.
|
|
30
|
+
*
|
|
31
|
+
* @param username - The username for authentication
|
|
32
|
+
* @param password - The password for authentication
|
|
33
|
+
* @throws {SRPError} If username or password is empty
|
|
34
|
+
*/
|
|
35
|
+
setIdentity(username: string, password: string): void;
|
|
36
|
+
/**
|
|
37
|
+
* Gets the salt value received from the server.
|
|
38
|
+
*
|
|
39
|
+
* @returns The salt buffer or null if not set
|
|
40
|
+
*/
|
|
41
|
+
get salt(): Buffer | null;
|
|
42
|
+
/**
|
|
43
|
+
* Sets the salt value received from the server.
|
|
44
|
+
*
|
|
45
|
+
* @param value - The salt buffer from the server
|
|
46
|
+
* @throws {SRPError} If salt is empty or client is disposed
|
|
47
|
+
*/
|
|
48
|
+
set salt(value: Buffer);
|
|
49
|
+
/**
|
|
50
|
+
* Gets the server's public key B.
|
|
51
|
+
*
|
|
52
|
+
* @returns The server's public key as a Buffer or null if not set
|
|
53
|
+
*/
|
|
54
|
+
get serverPublicKey(): Buffer | null;
|
|
55
|
+
/**
|
|
56
|
+
* Sets the server's public key B.
|
|
57
|
+
*
|
|
58
|
+
* @param value - The server's public key as a Buffer
|
|
59
|
+
* @throws {SRPError} If the server public key is invalid or client is disposed
|
|
60
|
+
*/
|
|
61
|
+
set serverPublicKey(value: Buffer);
|
|
62
|
+
/**
|
|
63
|
+
* Gets the client's public key A.
|
|
64
|
+
*
|
|
65
|
+
* @returns The client's public key as a Buffer
|
|
66
|
+
* @throws {SRPError} If keys are not generated yet or client is disposed
|
|
67
|
+
*/
|
|
68
|
+
get publicKey(): Buffer;
|
|
69
|
+
/**
|
|
70
|
+
* Computes the authentication proof M1.
|
|
71
|
+
*
|
|
72
|
+
* @returns The authentication proof as a Buffer
|
|
73
|
+
* @throws {SRPError} If required parameters are not set or client is disposed
|
|
74
|
+
*/
|
|
75
|
+
computeProof(): Buffer;
|
|
76
|
+
/**
|
|
77
|
+
* Gets the computed session key K.
|
|
78
|
+
*
|
|
79
|
+
* @returns The session key as a Buffer
|
|
80
|
+
* @throws {SRPError} If session key is not computed or client is disposed
|
|
81
|
+
*/
|
|
82
|
+
get sessionKey(): Buffer;
|
|
83
|
+
/**
|
|
84
|
+
* Checks if the client is ready to perform operations.
|
|
85
|
+
*
|
|
86
|
+
* @returns True if salt and server public key are set
|
|
87
|
+
*/
|
|
88
|
+
isReady(): boolean;
|
|
89
|
+
/**
|
|
90
|
+
* Checks if session key has been computed.
|
|
91
|
+
*
|
|
92
|
+
* @returns True if a session key is available
|
|
93
|
+
*/
|
|
94
|
+
hasSessionKey(): boolean;
|
|
95
|
+
/**
|
|
96
|
+
* Clears sensitive data and disposes the client.
|
|
97
|
+
* After calling this method, the client instance should not be used.
|
|
98
|
+
*/
|
|
99
|
+
dispose(): void;
|
|
100
|
+
/**
|
|
101
|
+
* Generates client keys if both salt and server public key are available.
|
|
102
|
+
* This method ensures keys are generated only once.
|
|
103
|
+
*/
|
|
104
|
+
private generateClientKeysIfReady;
|
|
105
|
+
/**
|
|
106
|
+
* Generates the client's private and public keys using cryptographically secure methods.
|
|
107
|
+
*
|
|
108
|
+
* @throws {SRPError} If generated public key is invalid or key generation fails
|
|
109
|
+
*/
|
|
110
|
+
private generateClientKeys;
|
|
111
|
+
/**
|
|
112
|
+
* Computes the shared secret S and derives the session key K.
|
|
113
|
+
*
|
|
114
|
+
* @throws {SRPError} If required parameters are not set
|
|
115
|
+
*/
|
|
116
|
+
private computeSharedSecret;
|
|
117
|
+
/**
|
|
118
|
+
* Validates that identity has been set.
|
|
119
|
+
*
|
|
120
|
+
* @throws {SRPError} If password is not set (username is set by default)
|
|
121
|
+
*/
|
|
122
|
+
private validateIdentitySet;
|
|
123
|
+
/**
|
|
124
|
+
* Throws an error if the client has been disposed.
|
|
125
|
+
*
|
|
126
|
+
* @throws {SRPError} If client is disposed
|
|
127
|
+
*/
|
|
128
|
+
private throwIfDisposed;
|
|
129
|
+
}
|
|
130
|
+
//# sourceMappingURL=srp-client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"srp-client.d.ts","sourceRoot":"","sources":["../../../../../src/lib/apple-tv/srp/srp-client.ts"],"names":[],"mappings":"AA0BA;;;;;;GAMG;AACH,qBAAa,SAAS;IAEpB,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAa;IACzC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAa;IACxC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,2BAA2B,CAAO;IAE1D,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAkB;IACpC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAiB;IACnC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAS;IAC3B,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IAErC,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,KAAK,CAAuB;IACpC,OAAO,CAAC,EAAE,CAA0B;IACpC,OAAO,CAAC,EAAE,CAA0B;IACpC,OAAO,CAAC,EAAE,CAAuB;IACjC,OAAO,CAAC,EAAE,CAAuB;IACjC,OAAO,CAAC,EAAE,CAAuB;IAGjC,OAAO,CAAC,aAAa,CAAS;IAC9B,OAAO,CAAC,QAAQ,CAAS;;IAWzB;;;;;;;OAOG;IACI,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI;IAgB5D;;;;OAIG;IACH,IAAI,IAAI,IAAI,MAAM,GAAG,IAAI,CAExB;IAED;;;;;OAKG;IACH,IAAI,IAAI,CAAC,KAAK,EAAE,MAAM,EAWrB;IAED;;;;OAIG;IACH,IAAI,eAAe,IAAI,MAAM,GAAG,IAAI,CAEnC;IAED;;;;;OAKG;IACH,IAAI,eAAe,CAAC,KAAK,EAAE,MAAM,EAwBhC;IAED;;;;;OAKG;IACH,IAAI,SAAS,IAAI,MAAM,CAUtB;IAED;;;;;OAKG;IACI,YAAY,IAAI,MAAM;IAyB7B;;;;;OAKG;IACH,IAAI,UAAU,IAAI,MAAM,CAavB;IAED;;;;OAIG;IACI,OAAO,IAAI,OAAO;IAIzB;;;;OAIG;IACI,aAAa,IAAI,OAAO;IAI/B;;;OAGG;IACI,OAAO,IAAI,IAAI;IAqBtB;;;OAGG;IACH,OAAO,CAAC,yBAAyB;IAOjC;;;;OAIG;IACH,OAAO,CAAC,kBAAkB;IAqC1B;;;;OAIG;IACH,OAAO,CAAC,mBAAmB;IAiC3B;;;;OAIG;IACH,OAAO,CAAC,mBAAmB;IAQ3B;;;;OAIG;IACH,OAAO,CAAC,eAAe;CAKxB"}
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
import { logger } from '@appium/support';
|
|
2
|
+
import { randomBytes } from 'node:crypto';
|
|
3
|
+
import { SRP_GENERATOR, SRP_KEY_LENGTH_BYTES, SRP_PRIME_3072, SRP_PRIVATE_KEY_BITS, SRP_USERNAME, } from '../constants.js';
|
|
4
|
+
import { SRPError } from '../errors.js';
|
|
5
|
+
import { bigIntToBuffer, bufferToBigInt, modPow, } from '../utils/buffer-utils.js';
|
|
6
|
+
import { calculateK, calculateM1, calculateU, calculateX, hash, } from './crypto-utils.js';
|
|
7
|
+
const log = logger.getLogger('SRPClient');
|
|
8
|
+
/**
|
|
9
|
+
* SRP (Secure Remote Password) client implementation following RFC 5054.
|
|
10
|
+
*
|
|
11
|
+
* This class handles the client-side operations of the SRP protocol,
|
|
12
|
+
* including key generation, authentication proof computation, and
|
|
13
|
+
* session key derivation.
|
|
14
|
+
*/
|
|
15
|
+
export class SRPClient {
|
|
16
|
+
// Constants
|
|
17
|
+
static ZERO = BigInt(0);
|
|
18
|
+
static ONE = BigInt(1);
|
|
19
|
+
static MAX_KEY_GENERATION_ATTEMPTS = 100;
|
|
20
|
+
N = SRP_PRIME_3072;
|
|
21
|
+
g = SRP_GENERATOR;
|
|
22
|
+
k;
|
|
23
|
+
N_MINUS_ONE;
|
|
24
|
+
username;
|
|
25
|
+
password;
|
|
26
|
+
_salt = null;
|
|
27
|
+
_a = SRPClient.ZERO;
|
|
28
|
+
_A = SRPClient.ZERO;
|
|
29
|
+
_B = null;
|
|
30
|
+
_S = null;
|
|
31
|
+
_K = null;
|
|
32
|
+
// State tracking
|
|
33
|
+
keysGenerated = false;
|
|
34
|
+
disposed = false;
|
|
35
|
+
constructor() {
|
|
36
|
+
this.k = calculateK(this.N, this.g, SRP_KEY_LENGTH_BYTES);
|
|
37
|
+
this.N_MINUS_ONE = this.N - SRPClient.ONE;
|
|
38
|
+
this.username = SRP_USERNAME;
|
|
39
|
+
this.password = '';
|
|
40
|
+
log.debug('Initialized SRP client with k value');
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Sets the user identity credentials.
|
|
44
|
+
* Note: Username is set to SRP_USERNAME constant, but can be overridden.
|
|
45
|
+
*
|
|
46
|
+
* @param username - The username for authentication
|
|
47
|
+
* @param password - The password for authentication
|
|
48
|
+
* @throws {SRPError} If username or password is empty
|
|
49
|
+
*/
|
|
50
|
+
setIdentity(username, password) {
|
|
51
|
+
this.throwIfDisposed();
|
|
52
|
+
if (!username?.trim()) {
|
|
53
|
+
throw new SRPError('Username cannot be empty');
|
|
54
|
+
}
|
|
55
|
+
if (!password) {
|
|
56
|
+
throw new SRPError('Password cannot be empty');
|
|
57
|
+
}
|
|
58
|
+
this.username = username.trim();
|
|
59
|
+
this.password = password;
|
|
60
|
+
log.debug('Identity set successfully');
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Gets the salt value received from the server.
|
|
64
|
+
*
|
|
65
|
+
* @returns The salt buffer or null if not set
|
|
66
|
+
*/
|
|
67
|
+
get salt() {
|
|
68
|
+
return this._salt;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Sets the salt value received from the server.
|
|
72
|
+
*
|
|
73
|
+
* @param value - The salt buffer from the server
|
|
74
|
+
* @throws {SRPError} If salt is empty or client is disposed
|
|
75
|
+
*/
|
|
76
|
+
set salt(value) {
|
|
77
|
+
this.throwIfDisposed();
|
|
78
|
+
if (!value || value.length === 0) {
|
|
79
|
+
throw new SRPError('Salt cannot be empty');
|
|
80
|
+
}
|
|
81
|
+
this._salt = value;
|
|
82
|
+
this.generateClientKeysIfReady();
|
|
83
|
+
log.debug('Salt set successfully');
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Gets the server's public key B.
|
|
87
|
+
*
|
|
88
|
+
* @returns The server's public key as a Buffer or null if not set
|
|
89
|
+
*/
|
|
90
|
+
get serverPublicKey() {
|
|
91
|
+
return this._B ? bigIntToBuffer(this._B, SRP_KEY_LENGTH_BYTES) : null;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Sets the server's public key B.
|
|
95
|
+
*
|
|
96
|
+
* @param value - The server's public key as a Buffer
|
|
97
|
+
* @throws {SRPError} If the server public key is invalid or client is disposed
|
|
98
|
+
*/
|
|
99
|
+
set serverPublicKey(value) {
|
|
100
|
+
this.throwIfDisposed();
|
|
101
|
+
if (!value || value.length !== SRP_KEY_LENGTH_BYTES) {
|
|
102
|
+
throw new SRPError(`Server public key must be ${SRP_KEY_LENGTH_BYTES} bytes, got ${value?.length || 0}`);
|
|
103
|
+
}
|
|
104
|
+
this._B = bufferToBigInt(value);
|
|
105
|
+
if (this._B <= SRPClient.ONE || this._B >= this.N_MINUS_ONE) {
|
|
106
|
+
throw new SRPError('Invalid server public key B: must be in range (1, N-1)');
|
|
107
|
+
}
|
|
108
|
+
// Additional security check
|
|
109
|
+
if (this._B % this.N === SRPClient.ZERO) {
|
|
110
|
+
throw new SRPError('Invalid server public key B: divisible by N');
|
|
111
|
+
}
|
|
112
|
+
this.generateClientKeysIfReady();
|
|
113
|
+
log.debug('Server public key set successfully');
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Gets the client's public key A.
|
|
117
|
+
*
|
|
118
|
+
* @returns The client's public key as a Buffer
|
|
119
|
+
* @throws {SRPError} If keys are not generated yet or client is disposed
|
|
120
|
+
*/
|
|
121
|
+
get publicKey() {
|
|
122
|
+
this.throwIfDisposed();
|
|
123
|
+
if (this._A === SRPClient.ZERO) {
|
|
124
|
+
throw new SRPError('Client keys not generated yet. Set salt and serverPublicKey properties first.');
|
|
125
|
+
}
|
|
126
|
+
return bigIntToBuffer(this._A, SRP_KEY_LENGTH_BYTES);
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Computes the authentication proof M1.
|
|
130
|
+
*
|
|
131
|
+
* @returns The authentication proof as a Buffer
|
|
132
|
+
* @throws {SRPError} If required parameters are not set or client is disposed
|
|
133
|
+
*/
|
|
134
|
+
computeProof() {
|
|
135
|
+
this.throwIfDisposed();
|
|
136
|
+
this.validateIdentitySet();
|
|
137
|
+
if (!this._K) {
|
|
138
|
+
this.computeSharedSecret();
|
|
139
|
+
}
|
|
140
|
+
if (!this._salt || !this._K || !this._B) {
|
|
141
|
+
throw new SRPError('Cannot compute proof: salt, session key, and server public key must be set');
|
|
142
|
+
}
|
|
143
|
+
return calculateM1(this.N, this.g, this.username, this._salt, this._A, this._B, this._K);
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Gets the computed session key K.
|
|
147
|
+
*
|
|
148
|
+
* @returns The session key as a Buffer
|
|
149
|
+
* @throws {SRPError} If session key is not computed or client is disposed
|
|
150
|
+
*/
|
|
151
|
+
get sessionKey() {
|
|
152
|
+
this.throwIfDisposed();
|
|
153
|
+
this.validateIdentitySet();
|
|
154
|
+
if (!this._K) {
|
|
155
|
+
this.computeSharedSecret();
|
|
156
|
+
}
|
|
157
|
+
if (!this._K) {
|
|
158
|
+
throw new SRPError('Session key not computed');
|
|
159
|
+
}
|
|
160
|
+
return this._K;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Checks if the client is ready to perform operations.
|
|
164
|
+
*
|
|
165
|
+
* @returns True if salt and server public key are set
|
|
166
|
+
*/
|
|
167
|
+
isReady() {
|
|
168
|
+
return !this.disposed && !!(this._salt && this._B && this.keysGenerated);
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Checks if session key has been computed.
|
|
172
|
+
*
|
|
173
|
+
* @returns True if a session key is available
|
|
174
|
+
*/
|
|
175
|
+
hasSessionKey() {
|
|
176
|
+
return !this.disposed && !!this._K;
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Clears sensitive data and disposes the client.
|
|
180
|
+
* After calling this method, the client instance should not be used.
|
|
181
|
+
*/
|
|
182
|
+
dispose() {
|
|
183
|
+
if (this.disposed) {
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
// Clear sensitive data
|
|
187
|
+
this.password = '';
|
|
188
|
+
this._a = SRPClient.ZERO;
|
|
189
|
+
if (this._K) {
|
|
190
|
+
this._K.fill(0);
|
|
191
|
+
}
|
|
192
|
+
this._salt = null;
|
|
193
|
+
this._S = null;
|
|
194
|
+
this._B = null;
|
|
195
|
+
this.disposed = true;
|
|
196
|
+
log.debug('SRP client disposed and sensitive data cleared');
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Generates client keys if both salt and server public key are available.
|
|
200
|
+
* This method ensures keys are generated only once.
|
|
201
|
+
*/
|
|
202
|
+
generateClientKeysIfReady() {
|
|
203
|
+
if (this._salt && this._B && !this.keysGenerated) {
|
|
204
|
+
this.generateClientKeys();
|
|
205
|
+
this.keysGenerated = true;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Generates the client's private and public keys using cryptographically secure methods.
|
|
210
|
+
*
|
|
211
|
+
* @throws {SRPError} If generated public key is invalid or key generation fails
|
|
212
|
+
*/
|
|
213
|
+
generateClientKeys() {
|
|
214
|
+
this.validateIdentitySet();
|
|
215
|
+
let attempts = 0;
|
|
216
|
+
while (attempts < SRPClient.MAX_KEY_GENERATION_ATTEMPTS) {
|
|
217
|
+
const randomBits = randomBytes(SRP_PRIVATE_KEY_BITS / 8);
|
|
218
|
+
this._a = bufferToBigInt(randomBits);
|
|
219
|
+
// Ensure key is in valid range without introducing bias
|
|
220
|
+
if (this._a >= this.N) {
|
|
221
|
+
attempts++;
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
if (this._a === SRPClient.ZERO) {
|
|
225
|
+
attempts++;
|
|
226
|
+
continue;
|
|
227
|
+
}
|
|
228
|
+
this._A = modPow(this.g, this._a, this.N);
|
|
229
|
+
if (this._A <= SRPClient.ONE || this._A >= this.N_MINUS_ONE) {
|
|
230
|
+
attempts++;
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
233
|
+
// Successfully generated valid keys
|
|
234
|
+
log.debug('Generated client keys successfully');
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
throw new SRPError(`Failed to generate secure client keys after ${SRPClient.MAX_KEY_GENERATION_ATTEMPTS} attempts`);
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Computes the shared secret S and derives the session key K.
|
|
241
|
+
*
|
|
242
|
+
* @throws {SRPError} If required parameters are not set
|
|
243
|
+
*/
|
|
244
|
+
computeSharedSecret() {
|
|
245
|
+
this.validateIdentitySet();
|
|
246
|
+
if (!this._salt || !this._B) {
|
|
247
|
+
throw new SRPError('Salt and server public key must be set first');
|
|
248
|
+
}
|
|
249
|
+
if (this._A === SRPClient.ZERO) {
|
|
250
|
+
throw new SRPError('Client keys not generated');
|
|
251
|
+
}
|
|
252
|
+
const u = calculateU(this._A, this._B, SRP_KEY_LENGTH_BYTES);
|
|
253
|
+
log.debug('Calculated u value');
|
|
254
|
+
const x = calculateX(this._salt, this.username, this.password);
|
|
255
|
+
log.debug('Calculated x value');
|
|
256
|
+
const gx = modPow(this.g, x, this.N);
|
|
257
|
+
const kgx = (this.k * gx) % this.N;
|
|
258
|
+
// Fix negative modulo operation
|
|
259
|
+
let base = this._B - kgx;
|
|
260
|
+
base = ((base % this.N) + this.N) % this.N;
|
|
261
|
+
const exponent = this._a + u * x;
|
|
262
|
+
this._S = modPow(base, exponent, this.N);
|
|
263
|
+
log.debug('Calculated shared secret S');
|
|
264
|
+
const SBuffer = bigIntToBuffer(this._S, SRP_KEY_LENGTH_BYTES);
|
|
265
|
+
this._K = hash(SBuffer);
|
|
266
|
+
log.debug('Calculated session key K');
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* Validates that identity has been set.
|
|
270
|
+
*
|
|
271
|
+
* @throws {SRPError} If password is not set (username is set by default)
|
|
272
|
+
*/
|
|
273
|
+
validateIdentitySet() {
|
|
274
|
+
if (!this.password) {
|
|
275
|
+
throw new SRPError('Password must be set before performing operations. Call setIdentity() first.');
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* Throws an error if the client has been disposed.
|
|
280
|
+
*
|
|
281
|
+
* @throws {SRPError} If client is disposed
|
|
282
|
+
*/
|
|
283
|
+
throwIfDisposed() {
|
|
284
|
+
if (this.disposed) {
|
|
285
|
+
throw new SRPError('SRP client has been disposed');
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
@@ -50,6 +50,5 @@ declare class RemoteXpcConnection {
|
|
|
50
50
|
*/
|
|
51
51
|
private forceCleanup;
|
|
52
52
|
}
|
|
53
|
-
export
|
|
54
|
-
export { type Service, type ServicesResponse };
|
|
53
|
+
export { RemoteXpcConnection, type Service, type ServicesResponse };
|
|
55
54
|
//# sourceMappingURL=remote-xpc-connection.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"remote-xpc-connection.d.ts","sourceRoot":"","sources":["../../../../src/lib/remote-xpc/remote-xpc-connection.ts"],"names":[],"mappings":"AAgBA,UAAU,OAAO;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,UAAU,gBAAgB;IACxB,QAAQ,EAAE,OAAO,EAAE,CAAC;CACrB;AAKD,cAAM,mBAAmB;IACvB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAmB;IAC5C,OAAO,CAAC,OAAO,CAAyB;IACxC,OAAO,CAAC,UAAU,CAAwB;IAC1C,OAAO,CAAC,YAAY,CAAU;IAC9B,OAAO,CAAC,SAAS,CAAwB;gBAE7B,OAAO,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC;IAQrC;;;OAGG;IACG,OAAO,IAAI,OAAO,CAAC,gBAAgB,CAAC;IA0J1C;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IA0E5B;;;OAGG;IACH,WAAW,IAAI,OAAO,EAAE;IAOxB;;;OAGG;IACH,eAAe,IAAI,OAAO,EAAE;IAI5B;;;;OAIG;IACH,WAAW,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO;IAYzC;;OAEG;IACH,OAAO,CAAC,aAAa;IAgCrB;;OAEG;IACH,OAAO,CAAC,gBAAgB;IAOxB;;OAEG;IACH,OAAO,CAAC,YAAY;CAerB;AAkFD,
|
|
1
|
+
{"version":3,"file":"remote-xpc-connection.d.ts","sourceRoot":"","sources":["../../../../src/lib/remote-xpc/remote-xpc-connection.ts"],"names":[],"mappings":"AAgBA,UAAU,OAAO;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,UAAU,gBAAgB;IACxB,QAAQ,EAAE,OAAO,EAAE,CAAC;CACrB;AAKD,cAAM,mBAAmB;IACvB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAmB;IAC5C,OAAO,CAAC,OAAO,CAAyB;IACxC,OAAO,CAAC,UAAU,CAAwB;IAC1C,OAAO,CAAC,YAAY,CAAU;IAC9B,OAAO,CAAC,SAAS,CAAwB;gBAE7B,OAAO,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC;IAQrC;;;OAGG;IACG,OAAO,IAAI,OAAO,CAAC,gBAAgB,CAAC;IA0J1C;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IA0E5B;;;OAGG;IACH,WAAW,IAAI,OAAO,EAAE;IAOxB;;;OAGG;IACH,eAAe,IAAI,OAAO,EAAE;IAI5B;;;;OAIG;IACH,WAAW,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO;IAYzC;;OAEG;IACH,OAAO,CAAC,aAAa;IAgCrB;;OAEG;IACH,OAAO,CAAC,gBAAgB;IAOxB;;OAEG;IACH,OAAO,CAAC,YAAY;CAerB;AAkFD,OAAO,EAAE,mBAAmB,EAAE,KAAK,OAAO,EAAE,KAAK,gBAAgB,EAAE,CAAC"}
|