appium-ios-remotexpc 0.0.3 → 0.0.5

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 (213) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/build/src/base-plist-service.d.ts +51 -0
  3. package/build/src/base-plist-service.d.ts.map +1 -0
  4. package/build/src/base-plist-service.js +61 -0
  5. package/build/src/base-socket-service.d.ts +15 -0
  6. package/build/src/base-socket-service.d.ts.map +1 -0
  7. package/build/src/base-socket-service.js +46 -0
  8. package/build/src/index.d.ts +9 -0
  9. package/build/src/index.d.ts.map +1 -0
  10. package/build/src/index.js +7 -0
  11. package/build/src/lib/apple-tv/constants.d.ts +77 -0
  12. package/build/src/lib/apple-tv/constants.d.ts.map +1 -0
  13. package/build/src/lib/apple-tv/constants.js +106 -0
  14. package/build/src/lib/apple-tv/encryption/chacha20-poly1305.d.ts +22 -0
  15. package/build/src/lib/apple-tv/encryption/chacha20-poly1305.d.ts.map +1 -0
  16. package/build/src/lib/apple-tv/encryption/chacha20-poly1305.js +97 -0
  17. package/build/src/lib/apple-tv/encryption/ed25519.d.ts +16 -0
  18. package/build/src/lib/apple-tv/encryption/ed25519.d.ts.map +1 -0
  19. package/build/src/lib/apple-tv/encryption/ed25519.js +93 -0
  20. package/build/src/lib/apple-tv/encryption/hkdf.d.ts +18 -0
  21. package/build/src/lib/apple-tv/encryption/hkdf.d.ts.map +1 -0
  22. package/build/src/lib/apple-tv/encryption/hkdf.js +73 -0
  23. package/build/src/lib/apple-tv/encryption/index.d.ts +5 -0
  24. package/build/src/lib/apple-tv/encryption/index.d.ts.map +1 -0
  25. package/build/src/lib/apple-tv/encryption/index.js +4 -0
  26. package/build/src/lib/apple-tv/encryption/opack2.d.ts +57 -0
  27. package/build/src/lib/apple-tv/encryption/opack2.d.ts.map +1 -0
  28. package/build/src/lib/apple-tv/encryption/opack2.js +203 -0
  29. package/build/src/lib/apple-tv/errors.d.ts +17 -0
  30. package/build/src/lib/apple-tv/errors.d.ts.map +1 -0
  31. package/build/src/lib/apple-tv/errors.js +30 -0
  32. package/build/src/lib/apple-tv/tlv/decoder.d.ts +19 -0
  33. package/build/src/lib/apple-tv/tlv/decoder.d.ts.map +1 -0
  34. package/build/src/lib/apple-tv/tlv/decoder.js +49 -0
  35. package/build/src/lib/apple-tv/tlv/encoder.d.ts +10 -0
  36. package/build/src/lib/apple-tv/tlv/encoder.d.ts.map +1 -0
  37. package/build/src/lib/apple-tv/tlv/encoder.js +20 -0
  38. package/build/src/lib/apple-tv/tlv/index.d.ts +4 -0
  39. package/build/src/lib/apple-tv/tlv/index.d.ts.map +1 -0
  40. package/build/src/lib/apple-tv/tlv/index.js +3 -0
  41. package/build/src/lib/apple-tv/tlv/pairing-tlv.d.ts +14 -0
  42. package/build/src/lib/apple-tv/tlv/pairing-tlv.d.ts.map +1 -0
  43. package/build/src/lib/apple-tv/tlv/pairing-tlv.js +27 -0
  44. package/build/src/lib/apple-tv/types.d.ts +36 -0
  45. package/build/src/lib/apple-tv/types.d.ts.map +1 -0
  46. package/build/src/lib/apple-tv/types.js +1 -0
  47. package/build/src/lib/apple-tv/utils/buffer-utils.d.ts +40 -0
  48. package/build/src/lib/apple-tv/utils/buffer-utils.d.ts.map +1 -0
  49. package/build/src/lib/apple-tv/utils/buffer-utils.js +76 -0
  50. package/build/src/lib/apple-tv/utils/index.d.ts +3 -0
  51. package/build/src/lib/apple-tv/utils/index.d.ts.map +1 -0
  52. package/build/src/lib/apple-tv/utils/index.js +2 -0
  53. package/build/src/lib/apple-tv/utils/uuid-generator.d.ts +9 -0
  54. package/build/src/lib/apple-tv/utils/uuid-generator.d.ts.map +1 -0
  55. package/build/src/lib/apple-tv/utils/uuid-generator.js +36 -0
  56. package/build/src/lib/lockdown/index.d.ts +87 -0
  57. package/build/src/lib/lockdown/index.d.ts.map +1 -0
  58. package/build/src/lib/lockdown/index.js +324 -0
  59. package/build/src/lib/pair-record/index.d.ts +3 -0
  60. package/build/src/lib/pair-record/index.d.ts.map +1 -0
  61. package/build/src/lib/pair-record/index.js +2 -0
  62. package/build/src/lib/pair-record/pair-record.d.ts +48 -0
  63. package/build/src/lib/pair-record/pair-record.d.ts.map +1 -0
  64. package/build/src/lib/pair-record/pair-record.js +85 -0
  65. package/build/src/lib/plist/binary-plist-creator.d.ts +14 -0
  66. package/build/src/lib/plist/binary-plist-creator.d.ts.map +1 -0
  67. package/build/src/lib/plist/binary-plist-creator.js +475 -0
  68. package/build/src/lib/plist/binary-plist-parser.d.ts +14 -0
  69. package/build/src/lib/plist/binary-plist-parser.d.ts.map +1 -0
  70. package/build/src/lib/plist/binary-plist-parser.js +449 -0
  71. package/build/src/lib/plist/constants.d.ts +36 -0
  72. package/build/src/lib/plist/constants.d.ts.map +1 -0
  73. package/build/src/lib/plist/constants.js +43 -0
  74. package/build/src/lib/plist/index.d.ts +14 -0
  75. package/build/src/lib/plist/index.d.ts.map +1 -0
  76. package/build/src/lib/plist/index.js +16 -0
  77. package/build/src/lib/plist/length-based-splitter.d.ts +43 -0
  78. package/build/src/lib/plist/length-based-splitter.d.ts.map +1 -0
  79. package/build/src/lib/plist/length-based-splitter.js +228 -0
  80. package/build/src/lib/plist/plist-creator.d.ts +8 -0
  81. package/build/src/lib/plist/plist-creator.d.ts.map +1 -0
  82. package/build/src/lib/plist/plist-creator.js +33 -0
  83. package/build/src/lib/plist/plist-decoder.d.ts +25 -0
  84. package/build/src/lib/plist/plist-decoder.d.ts.map +1 -0
  85. package/build/src/lib/plist/plist-decoder.js +103 -0
  86. package/build/src/lib/plist/plist-encoder.d.ts +10 -0
  87. package/build/src/lib/plist/plist-encoder.d.ts.map +1 -0
  88. package/build/src/lib/plist/plist-encoder.js +27 -0
  89. package/build/src/lib/plist/plist-parser.d.ts +9 -0
  90. package/build/src/lib/plist/plist-parser.d.ts.map +1 -0
  91. package/build/src/lib/plist/plist-parser.js +109 -0
  92. package/build/src/lib/plist/plist-service.d.ts +86 -0
  93. package/build/src/lib/plist/plist-service.d.ts.map +1 -0
  94. package/build/src/lib/plist/plist-service.js +180 -0
  95. package/build/src/lib/plist/unified-plist-creator.d.ts +9 -0
  96. package/build/src/lib/plist/unified-plist-creator.d.ts.map +1 -0
  97. package/build/src/lib/plist/unified-plist-creator.js +14 -0
  98. package/build/src/lib/plist/unified-plist-parser.d.ts +8 -0
  99. package/build/src/lib/plist/unified-plist-parser.d.ts.map +1 -0
  100. package/build/src/lib/plist/unified-plist-parser.js +23 -0
  101. package/build/src/lib/plist/utils.d.ts +97 -0
  102. package/build/src/lib/plist/utils.d.ts.map +1 -0
  103. package/build/src/lib/plist/utils.js +287 -0
  104. package/build/src/lib/remote-xpc/constants.d.ts +20 -0
  105. package/build/src/lib/remote-xpc/constants.d.ts.map +1 -0
  106. package/build/src/lib/remote-xpc/constants.js +21 -0
  107. package/build/src/lib/remote-xpc/handshake-frames.d.ts +74 -0
  108. package/build/src/lib/remote-xpc/handshake-frames.d.ts.map +1 -0
  109. package/build/src/lib/remote-xpc/handshake-frames.js +285 -0
  110. package/build/src/lib/remote-xpc/handshake.d.ts +14 -0
  111. package/build/src/lib/remote-xpc/handshake.d.ts.map +1 -0
  112. package/build/src/lib/remote-xpc/handshake.js +95 -0
  113. package/build/src/lib/remote-xpc/remote-xpc-connection.d.ts +55 -0
  114. package/build/src/lib/remote-xpc/remote-xpc-connection.d.ts.map +1 -0
  115. package/build/src/lib/remote-xpc/remote-xpc-connection.js +365 -0
  116. package/build/src/lib/remote-xpc/xpc-protocol.d.ts +22 -0
  117. package/build/src/lib/remote-xpc/xpc-protocol.d.ts.map +1 -0
  118. package/build/src/lib/remote-xpc/xpc-protocol.js +368 -0
  119. package/build/src/lib/tunnel/index.d.ts +69 -0
  120. package/build/src/lib/tunnel/index.d.ts.map +1 -0
  121. package/build/src/lib/tunnel/index.js +205 -0
  122. package/build/src/lib/tunnel/packet-stream-client.d.ts +46 -0
  123. package/build/src/lib/tunnel/packet-stream-client.d.ts.map +1 -0
  124. package/build/src/lib/tunnel/packet-stream-client.js +152 -0
  125. package/build/src/lib/tunnel/packet-stream-server.d.ts +37 -0
  126. package/build/src/lib/tunnel/packet-stream-server.d.ts.map +1 -0
  127. package/build/src/lib/tunnel/packet-stream-server.js +109 -0
  128. package/build/src/lib/tunnel/tunnel-api-client.d.ts +85 -0
  129. package/build/src/lib/tunnel/tunnel-api-client.d.ts.map +1 -0
  130. package/build/src/lib/tunnel/tunnel-api-client.js +207 -0
  131. package/build/src/lib/tunnel/tunnel-registry-server.d.ts +68 -0
  132. package/build/src/lib/tunnel/tunnel-registry-server.d.ts.map +1 -0
  133. package/build/src/lib/tunnel/tunnel-registry-server.js +351 -0
  134. package/build/src/lib/types.d.ts +238 -0
  135. package/build/src/lib/types.d.ts.map +1 -0
  136. package/build/src/lib/types.js +4 -0
  137. package/build/src/lib/usbmux/index.d.ts +177 -0
  138. package/build/src/lib/usbmux/index.d.ts.map +1 -0
  139. package/build/src/lib/usbmux/index.js +490 -0
  140. package/build/src/lib/usbmux/usbmux-decoder.d.ts +19 -0
  141. package/build/src/lib/usbmux/usbmux-decoder.d.ts.map +1 -0
  142. package/build/src/lib/usbmux/usbmux-decoder.js +38 -0
  143. package/build/src/lib/usbmux/usbmux-encoder.d.ts +12 -0
  144. package/build/src/lib/usbmux/usbmux-encoder.d.ts.map +1 -0
  145. package/build/src/lib/usbmux/usbmux-encoder.js +32 -0
  146. package/build/src/service-connection.d.ts +34 -0
  147. package/build/src/service-connection.d.ts.map +1 -0
  148. package/build/src/service-connection.js +51 -0
  149. package/build/src/services/index.d.ts +6 -0
  150. package/build/src/services/index.d.ts.map +1 -0
  151. package/build/src/services/index.js +5 -0
  152. package/build/src/services/ios/base-service.d.ts +35 -0
  153. package/build/src/services/ios/base-service.d.ts.map +1 -0
  154. package/build/src/services/ios/base-service.js +55 -0
  155. package/build/src/services/ios/diagnostic-service/index.d.ts +46 -0
  156. package/build/src/services/ios/diagnostic-service/index.d.ts.map +1 -0
  157. package/build/src/services/ios/diagnostic-service/index.js +169 -0
  158. package/build/src/services/ios/diagnostic-service/keys.d.ts +5 -0
  159. package/build/src/services/ios/diagnostic-service/keys.d.ts.map +1 -0
  160. package/build/src/services/ios/diagnostic-service/keys.js +770 -0
  161. package/build/src/services/ios/syslog-service/index.d.ts +91 -0
  162. package/build/src/services/ios/syslog-service/index.d.ts.map +1 -0
  163. package/build/src/services/ios/syslog-service/index.js +323 -0
  164. package/build/src/services/ios/tunnel-service/index.d.ts +17 -0
  165. package/build/src/services/ios/tunnel-service/index.d.ts.map +1 -0
  166. package/build/src/services/ios/tunnel-service/index.js +57 -0
  167. package/build/src/services.d.ts +14 -0
  168. package/build/src/services.d.ts.map +1 -0
  169. package/build/src/services.js +48 -0
  170. package/package.json +12 -3
  171. package/src/lib/apple-tv/constants.ts +42 -0
  172. package/src/lib/apple-tv/encryption/chacha20-poly1305.ts +147 -0
  173. package/src/lib/apple-tv/encryption/ed25519.ts +126 -0
  174. package/src/lib/apple-tv/encryption/hkdf.ts +95 -0
  175. package/src/lib/apple-tv/encryption/index.ts +11 -0
  176. package/src/lib/apple-tv/encryption/opack2.ts +257 -0
  177. package/.github/dependabot.yml +0 -38
  178. package/.github/workflows/format-check.yml +0 -43
  179. package/.github/workflows/lint-and-build.yml +0 -40
  180. package/.github/workflows/pr-title.yml +0 -16
  181. package/.github/workflows/publish.js.yml +0 -43
  182. package/.github/workflows/test-validation.yml +0 -40
  183. package/.mocharc.json +0 -8
  184. package/.prettierignore +0 -3
  185. package/.prettierrc +0 -17
  186. package/.releaserc +0 -48
  187. package/assets/images/ios-arch.png +0 -0
  188. package/eslint.config.js +0 -45
  189. package/npm-shrinkwrap.json +0 -2711
  190. package/test/integration/diagnostics-test.ts +0 -44
  191. package/test/integration/read-pair-record-test.ts +0 -39
  192. package/test/integration/tunnel-test.ts +0 -104
  193. package/test/unit/apple-tv/tlv/decoder.spec.ts +0 -144
  194. package/test/unit/apple-tv/tlv/encoder.spec.ts +0 -91
  195. package/test/unit/apple-tv/tlv/pairing-tlv.spec.ts +0 -101
  196. package/test/unit/apple-tv/tlv/tlv-integration.spec.ts +0 -146
  197. package/test/unit/apple-tv/utils/buffer-utils.spec.ts +0 -74
  198. package/test/unit/apple-tv/utils/uuid-generator.spec.ts +0 -39
  199. package/test/unit/fixtures/index.ts +0 -88
  200. package/test/unit/fixtures/usbmuxconnectmessage.bin +0 -0
  201. package/test/unit/fixtures/usbmuxlistdevicemessage.bin +0 -0
  202. package/test/unit/plist/error-handling.spec.ts +0 -101
  203. package/test/unit/plist/fixtures/sample.binary.plist +0 -0
  204. package/test/unit/plist/fixtures/sample.xml.plist +0 -38
  205. package/test/unit/plist/plist-parser.spec.ts +0 -283
  206. package/test/unit/plist/plist.spec.ts +0 -205
  207. package/test/unit/plist/tag-position-handling.spec.ts +0 -90
  208. package/test/unit/plist/unified-plist-parser.spec.ts +0 -227
  209. package/test/unit/plist/utils.spec.ts +0 -249
  210. package/test/unit/plist/xml-cleaning.spec.ts +0 -60
  211. package/test/unit/tunnel/tunnel-registry-server.spec.ts +0 -194
  212. package/test/unit/usbmux/usbmux-specs.ts +0 -71
  213. package/tsconfig.json +0 -36
@@ -0,0 +1,147 @@
1
+ import { logger } from '@appium/support';
2
+ import { createCipheriv, createDecipheriv } from 'node:crypto';
3
+
4
+ import { CryptographyError } from '../errors.js';
5
+
6
+ const log = logger.getLogger('ChaCha20Poly1305');
7
+
8
+ export interface ChaCha20Poly1305Params {
9
+ plaintext?: Buffer;
10
+ ciphertext?: Buffer;
11
+ key: Buffer;
12
+ nonce: Buffer;
13
+ aad?: Buffer;
14
+ }
15
+
16
+ interface DecryptionAttempt {
17
+ tagLen: number;
18
+ aad?: Buffer;
19
+ }
20
+
21
+ /**
22
+ * Encrypts data using ChaCha20-Poly1305 AEAD cipher
23
+ * @param params - Encryption parameters including plaintext, key, nonce, and optional AAD
24
+ * @returns Buffer containing encrypted data concatenated with authentication tag
25
+ * @throws CryptographyError if encryption fails or required parameters are missing
26
+ */
27
+ export function encryptChaCha20Poly1305(
28
+ params: ChaCha20Poly1305Params,
29
+ ): Buffer {
30
+ const { plaintext, key, nonce, aad } = params;
31
+
32
+ if (!plaintext) {
33
+ throw new CryptographyError('Plaintext is required for encryption');
34
+ }
35
+
36
+ if (!key || key.length !== 32) {
37
+ throw new CryptographyError('Key must be 32 bytes');
38
+ }
39
+
40
+ if (!nonce || nonce.length !== 12) {
41
+ throw new CryptographyError('Nonce must be 12 bytes');
42
+ }
43
+
44
+ try {
45
+ const cipher = createCipheriv('chacha20-poly1305', key, nonce) as any;
46
+
47
+ if (aad) {
48
+ cipher.setAAD(aad, { plaintextLength: plaintext.length });
49
+ }
50
+
51
+ const encrypted = Buffer.concat([cipher.update(plaintext), cipher.final()]);
52
+ const authTag = cipher.getAuthTag();
53
+
54
+ return Buffer.concat([encrypted, authTag]);
55
+ } catch (error) {
56
+ log.error('ChaCha20-Poly1305 encryption failed:', error);
57
+ const message = error instanceof Error ? error.message : String(error);
58
+ throw new CryptographyError(
59
+ `ChaCha20-Poly1305 encryption failed: ${message}`,
60
+ );
61
+ }
62
+ }
63
+
64
+ /**
65
+ * Decrypts data using ChaCha20-Poly1305 AEAD cipher with multiple fallback strategies
66
+ * @param params - Decryption parameters including ciphertext, key, nonce, and optional AAD
67
+ * @returns Buffer containing decrypted plaintext
68
+ * @throws CryptographyError if all decryption attempts fail or required parameters are missing
69
+ */
70
+ export function decryptChaCha20Poly1305(
71
+ params: ChaCha20Poly1305Params,
72
+ ): Buffer {
73
+ const { ciphertext, key, nonce, aad } = params;
74
+
75
+ if (!ciphertext) {
76
+ throw new CryptographyError('Ciphertext is required for decryption');
77
+ }
78
+
79
+ if (!key || key.length !== 32) {
80
+ throw new CryptographyError('Key must be 32 bytes');
81
+ }
82
+
83
+ if (!nonce || nonce.length !== 12) {
84
+ throw new CryptographyError('Nonce must be 12 bytes');
85
+ }
86
+
87
+ if (ciphertext.length < 16) {
88
+ throw new CryptographyError(
89
+ 'Ciphertext too short to contain authentication tag',
90
+ );
91
+ }
92
+
93
+ // ChaCha20-Poly1305 in Node.js only supports 16-byte authentication tags
94
+ const tagLength = 16;
95
+ const decryptionAttempts: DecryptionAttempt[] = [
96
+ { tagLen: tagLength, aad },
97
+ { tagLen: tagLength, aad: Buffer.alloc(0) },
98
+ { tagLen: tagLength, aad: undefined },
99
+ ];
100
+
101
+ let lastError: Error | undefined;
102
+
103
+ for (const attempt of decryptionAttempts) {
104
+ try {
105
+ const encrypted = ciphertext.subarray(
106
+ 0,
107
+ ciphertext.length - attempt.tagLen,
108
+ );
109
+ const authTag = ciphertext.subarray(ciphertext.length - attempt.tagLen);
110
+
111
+ const decipher = createDecipheriv('chacha20-poly1305', key, nonce) as any;
112
+ decipher.setAuthTag(authTag);
113
+
114
+ if (attempt.aad !== undefined) {
115
+ decipher.setAAD(attempt.aad, { plaintextLength: encrypted.length });
116
+ }
117
+
118
+ const decrypted = Buffer.concat([
119
+ decipher.update(encrypted),
120
+ decipher.final(),
121
+ ]);
122
+
123
+ log.debug(
124
+ 'Decryption successful with AAD:',
125
+ attempt.aad ? 'provided' : 'none',
126
+ );
127
+ return decrypted;
128
+ } catch (error) {
129
+ lastError = error instanceof Error ? error : new Error(String(error));
130
+ }
131
+ }
132
+
133
+ const errorMessage = lastError
134
+ ? `ChaCha20-Poly1305 decryption failed: ${lastError.message}`
135
+ : 'ChaCha20-Poly1305 decryption failed: invalid ciphertext or authentication tag';
136
+
137
+ // Log the error with stack trace for debugging real failures
138
+ // Skip logging in test environment to avoid cluttering test output with expected failures
139
+ if (lastError && process.env.NODE_ENV !== 'test') {
140
+ log.error('All ChaCha20-Poly1305 decryption attempts failed:', {
141
+ message: lastError.message,
142
+ stack: lastError.stack,
143
+ });
144
+ }
145
+
146
+ throw new CryptographyError(errorMessage);
147
+ }
@@ -0,0 +1,126 @@
1
+ import { logger } from '@appium/support';
2
+ import {
3
+ type KeyPairKeyObjectResult,
4
+ generateKeyPairSync,
5
+ sign,
6
+ } from 'node:crypto';
7
+
8
+ import { CryptographyError } from '../errors.js';
9
+ import type { PairingKeys } from '../types.js';
10
+
11
+ const log = logger.getLogger('Ed25519');
12
+
13
+ const ED25519_PUBLIC_KEY_LENGTH = 32;
14
+ const ED25519_PRIVATE_KEY_LENGTH = 32;
15
+ const ED25519_PKCS8_PREFIX = Buffer.from(
16
+ '302e020100300506032b657004220420',
17
+ 'hex',
18
+ );
19
+
20
+ /**
21
+ * Generates a new Ed25519 key pair for cryptographic operations
22
+ * @returns PairingKeys object containing 32-byte public and private key buffers
23
+ * @throws CryptographyError if key generation fails
24
+ */
25
+ export function generateEd25519KeyPair(): PairingKeys {
26
+ try {
27
+ const keyPair: KeyPairKeyObjectResult = generateKeyPairSync('ed25519');
28
+
29
+ const publicKeyDer = keyPair.publicKey.export({
30
+ type: 'spki',
31
+ format: 'der',
32
+ }) as Buffer;
33
+
34
+ const privateKeyDer = keyPair.privateKey.export({
35
+ type: 'pkcs8',
36
+ format: 'der',
37
+ }) as Buffer;
38
+
39
+ const publicKeyBuffer = extractEd25519PublicKey(publicKeyDer);
40
+ const privateKeyBuffer = extractEd25519PrivateKey(privateKeyDer);
41
+
42
+ return {
43
+ publicKey: publicKeyBuffer,
44
+ privateKey: privateKeyBuffer,
45
+ };
46
+ } catch (error) {
47
+ log.error('Failed to generate Ed25519 key pair:', error);
48
+ const message = error instanceof Error ? error.message : String(error);
49
+ throw new CryptographyError(
50
+ `Failed to generate Ed25519 key pair: ${message}`,
51
+ );
52
+ }
53
+ }
54
+
55
+ /**
56
+ * Creates an Ed25519 digital signature for the provided data
57
+ * @param data - The data to sign
58
+ * @param privateKey - 32-byte Ed25519 private key
59
+ * @returns Buffer containing the 64-byte signature
60
+ * @throws CryptographyError if signing fails or private key is invalid
61
+ */
62
+ export function createEd25519Signature(
63
+ data: Buffer,
64
+ privateKey: Buffer,
65
+ ): Buffer {
66
+ if (!data || data.length === 0) {
67
+ throw new CryptographyError('Data to sign cannot be empty');
68
+ }
69
+
70
+ if (!privateKey || privateKey.length !== ED25519_PRIVATE_KEY_LENGTH) {
71
+ throw new CryptographyError(
72
+ `Private key must be ${ED25519_PRIVATE_KEY_LENGTH} bytes`,
73
+ );
74
+ }
75
+
76
+ try {
77
+ const privateKeyDer = Buffer.concat([ED25519_PKCS8_PREFIX, privateKey]);
78
+
79
+ return sign(null, data, {
80
+ key: privateKeyDer,
81
+ format: 'der',
82
+ type: 'pkcs8',
83
+ });
84
+ } catch (error) {
85
+ log.error('Failed to create Ed25519 signature:', error);
86
+ const message = error instanceof Error ? error.message : String(error);
87
+ throw new CryptographyError(
88
+ `Failed to create Ed25519 signature: ${message}`,
89
+ );
90
+ }
91
+ }
92
+
93
+ /**
94
+ * Extracts the raw 32-byte public key from DER-encoded SPKI format
95
+ * @param publicKeyDer - DER-encoded public key
96
+ * @returns 32-byte public key buffer
97
+ * @throws CryptographyError if extraction fails
98
+ */
99
+ function extractEd25519PublicKey(publicKeyDer: Buffer): Buffer {
100
+ if (publicKeyDer.length < ED25519_PUBLIC_KEY_LENGTH) {
101
+ throw new CryptographyError('Invalid public key DER format');
102
+ }
103
+
104
+ return publicKeyDer.subarray(publicKeyDer.length - ED25519_PUBLIC_KEY_LENGTH);
105
+ }
106
+
107
+ /**
108
+ * Extracts the raw 32-byte private key from DER-encoded PKCS#8 format
109
+ * @param privateKeyDer - DER-encoded private key
110
+ * @returns 32-byte private key buffer
111
+ * @throws CryptographyError if extraction fails
112
+ */
113
+ function extractEd25519PrivateKey(privateKeyDer: Buffer): Buffer {
114
+ const octetStringPattern = Buffer.from([0x04, 0x20]);
115
+ const index = privateKeyDer.indexOf(octetStringPattern);
116
+
117
+ if (index !== -1 && index + 34 <= privateKeyDer.length) {
118
+ return privateKeyDer.subarray(index + 2, index + 34);
119
+ }
120
+
121
+ if (privateKeyDer.length >= 48) {
122
+ return privateKeyDer.subarray(16, 48);
123
+ }
124
+
125
+ throw new CryptographyError('Unable to extract private key from DER format');
126
+ }
@@ -0,0 +1,95 @@
1
+ import { logger } from '@appium/support';
2
+ import { createHmac } from 'node:crypto';
3
+
4
+ import { HKDF_HASH_ALGORITHM, HKDF_HASH_LENGTH } from '../constants.js';
5
+ import { CryptographyError } from '../errors.js';
6
+
7
+ const log = logger.getLogger('HKDF');
8
+
9
+ export interface HKDFParams {
10
+ ikm: Buffer;
11
+ salt: Buffer | null;
12
+ info: Buffer;
13
+ length: number;
14
+ }
15
+
16
+ const MAX_OUTPUT_LENGTH = 255 * HKDF_HASH_LENGTH;
17
+
18
+ /**
19
+ * HMAC-based Key Derivation Function (HKDF) as defined in RFC 5869
20
+ * Derives cryptographic keys from input key material using a two-step process:
21
+ * 1. Extract: Generate a pseudorandom key from the input key material
22
+ * 2. Expand: Expand the pseudorandom key to the desired output length
23
+ *
24
+ * @param params - HKDF parameters including input key material, salt, info, and desired output length
25
+ * @returns Buffer containing the derived key material of specified length
26
+ * @throws CryptographyError if derivation fails or parameters are invalid
27
+ */
28
+ export function hkdf(params: HKDFParams): Buffer {
29
+ const { ikm, salt, info, length } = params;
30
+
31
+ if (!ikm || ikm.length === 0) {
32
+ throw new CryptographyError('Input key material (IKM) cannot be empty');
33
+ }
34
+
35
+ if (!info) {
36
+ throw new CryptographyError('Info parameter is required');
37
+ }
38
+
39
+ if (length <= 0) {
40
+ throw new CryptographyError('Output length must be positive');
41
+ }
42
+
43
+ if (length > MAX_OUTPUT_LENGTH) {
44
+ throw new CryptographyError(
45
+ `Output length cannot exceed ${MAX_OUTPUT_LENGTH} bytes`,
46
+ );
47
+ }
48
+
49
+ try {
50
+ const extractedKey = hkdfExtract(ikm, salt);
51
+ return hkdfExpand(extractedKey, info, length);
52
+ } catch (error) {
53
+ log.error('HKDF derivation failed:', error);
54
+ const message = error instanceof Error ? error.message : String(error);
55
+ throw new CryptographyError(`HKDF derivation failed: ${message}`);
56
+ }
57
+ }
58
+
59
+ /**
60
+ * HKDF Extract step: generates a pseudorandom key from input key material
61
+ * @param ikm - Input key material
62
+ * @param salt - Optional salt value (uses zero salt if null)
63
+ * @returns Pseudorandom key of hash length
64
+ */
65
+ function hkdfExtract(ikm: Buffer, salt: Buffer | null): Buffer {
66
+ const actualSalt = salt || Buffer.alloc(HKDF_HASH_LENGTH);
67
+ return createHmac(HKDF_HASH_ALGORITHM, actualSalt).update(ikm).digest();
68
+ }
69
+
70
+ /**
71
+ * HKDF Expand a step: expands a pseudorandom key to desired output length
72
+ * @param prk - Pseudorandom key from extract step
73
+ * @param info - Context and application specific information
74
+ * @param length - Desired output key material length
75
+ * @returns Output key material of specified length
76
+ */
77
+ function hkdfExpand(prk: Buffer, info: Buffer, length: number): Buffer {
78
+ const numberOfBlocks = Math.ceil(length / HKDF_HASH_LENGTH);
79
+ const blocks: Buffer[] = [];
80
+ let previousBlock: Buffer = Buffer.alloc(0);
81
+
82
+ for (let blockIndex = 1; blockIndex <= numberOfBlocks; blockIndex++) {
83
+ const hmac = createHmac(HKDF_HASH_ALGORITHM, prk);
84
+ hmac.update(previousBlock);
85
+ hmac.update(info);
86
+ hmac.update(Buffer.from([blockIndex]));
87
+
88
+ const currentBlock = hmac.digest();
89
+ blocks.push(currentBlock);
90
+ previousBlock = currentBlock;
91
+ }
92
+
93
+ const outputKeyMaterial = Buffer.concat(blocks);
94
+ return outputKeyMaterial.subarray(0, length);
95
+ }
@@ -0,0 +1,11 @@
1
+ export { Opack2 } from './opack2.js';
2
+
3
+ export {
4
+ encryptChaCha20Poly1305,
5
+ decryptChaCha20Poly1305,
6
+ type ChaCha20Poly1305Params,
7
+ } from './chacha20-poly1305.js';
8
+
9
+ export { generateEd25519KeyPair, createEd25519Signature } from './ed25519.js';
10
+
11
+ export { hkdf, type HKDFParams } from './hkdf.js';
@@ -0,0 +1,257 @@
1
+ import * as constants from '../constants.js';
2
+ import { AppleTVError } from '../errors.js';
3
+
4
+ interface SerializableArray extends Array<SerializableValue> {}
5
+ interface SerializableObject extends Record<string, SerializableValue> {}
6
+
7
+ type SerializableValue =
8
+ | null
9
+ | undefined
10
+ | boolean
11
+ | number
12
+ | string
13
+ | Buffer
14
+ | SerializableArray
15
+ | SerializableObject;
16
+
17
+ /**
18
+ * OPACK2 binary serialization format encoder
19
+ * Implements Apple's OPACK2 protocol for efficient binary serialization of structured data
20
+ */
21
+ export class Opack2 {
22
+ /**
23
+ * Serializes a JavaScript object to OPACK2 binary format
24
+ * @param obj - The object to serialize (supports primitives, arrays, objects, and Buffers)
25
+ * @returns Buffer containing the serialized data
26
+ * @throws AppleTVError if the object contains unsupported types
27
+ */
28
+ static dumps(obj: SerializableValue): Buffer {
29
+ return this.encode(obj);
30
+ }
31
+
32
+ /**
33
+ * Main encoding dispatcher that routes values to appropriate type-specific encoders
34
+ * @param obj - Value to encode
35
+ * @returns Buffer containing encoded value
36
+ * @throws AppleTVError for unsupported types
37
+ */
38
+ private static encode(obj: SerializableValue): Buffer {
39
+ if (obj === null || obj === undefined) {
40
+ return Buffer.from([constants.OPACK2_NULL]);
41
+ }
42
+
43
+ if (typeof obj === 'boolean') {
44
+ return Buffer.from([
45
+ obj ? constants.OPACK2_TRUE : constants.OPACK2_FALSE,
46
+ ]);
47
+ }
48
+
49
+ if (typeof obj === 'number') {
50
+ return this.encodeNumber(obj);
51
+ }
52
+
53
+ if (typeof obj === 'string') {
54
+ return this.encodeString(obj);
55
+ }
56
+
57
+ if (Buffer.isBuffer(obj)) {
58
+ return this.encodeBytes(obj);
59
+ }
60
+
61
+ if (Array.isArray(obj)) {
62
+ return this.encodeArray(obj);
63
+ }
64
+
65
+ if (
66
+ typeof obj === 'object' &&
67
+ !Array.isArray(obj) &&
68
+ !Buffer.isBuffer(obj)
69
+ ) {
70
+ return this.encodeDict(obj as Record<string, SerializableValue>);
71
+ }
72
+
73
+ throw new AppleTVError(
74
+ `Unsupported type for OPACK2 serialization: ${typeof obj}`,
75
+ );
76
+ }
77
+
78
+ /**
79
+ * Encodes numeric values with the appropriate size optimization
80
+ * @param num - Number to encode
81
+ * @returns Buffer containing encoded number
82
+ */
83
+ private static encodeNumber(num: number): Buffer {
84
+ if (!Number.isInteger(num) || num < 0) {
85
+ const buffer = Buffer.allocUnsafe(5);
86
+ buffer[0] = constants.OPACK2_FLOAT_MARKER;
87
+ buffer.writeFloatLE(num, 1);
88
+ return buffer;
89
+ }
90
+
91
+ if (num <= constants.OPACK2_SMALL_INT_MAX) {
92
+ return Buffer.from([num + constants.OPACK2_SMALL_INT_OFFSET]);
93
+ }
94
+
95
+ if (num <= constants.OPACK2_UINT8_MAX) {
96
+ return Buffer.from([constants.OPACK2_INT8_MARKER, num]);
97
+ }
98
+
99
+ if (num <= constants.OPACK2_UINT32_MAX) {
100
+ const buffer = Buffer.allocUnsafe(5);
101
+ buffer[0] = constants.OPACK2_INT32_MARKER;
102
+ buffer.writeUInt32LE(num, 1);
103
+ return buffer;
104
+ }
105
+
106
+ if (num <= Number.MAX_SAFE_INTEGER) {
107
+ const buffer = Buffer.allocUnsafe(9);
108
+ buffer[0] = constants.OPACK2_INT64_MARKER;
109
+ buffer.writeBigUInt64LE(BigInt(num), 1);
110
+ return buffer;
111
+ }
112
+
113
+ throw new AppleTVError(`Number too large for OPACK2 encoding: ${num}`);
114
+ }
115
+
116
+ /**
117
+ * Encodes UTF-8 strings with length-optimized headers
118
+ * @param str - String to encode
119
+ * @returns Buffer containing encoded string
120
+ */
121
+ private static encodeString(str: string): Buffer {
122
+ const encoded = Buffer.from(str, 'utf8');
123
+ const length = encoded.length;
124
+
125
+ if (length <= constants.OPACK2_SMALL_STRING_MAX) {
126
+ return Buffer.concat([
127
+ Buffer.from([constants.OPACK2_SMALL_STRING_BASE + length]),
128
+ encoded,
129
+ ]);
130
+ }
131
+
132
+ if (length <= constants.OPACK2_UINT8_MAX) {
133
+ return Buffer.concat([
134
+ Buffer.from([constants.OPACK2_STRING_8BIT_LEN_MARKER, length]),
135
+ encoded,
136
+ ]);
137
+ }
138
+
139
+ if (length <= constants.OPACK2_UINT16_MAX) {
140
+ const header = Buffer.allocUnsafe(3);
141
+ header[0] = constants.OPACK2_STRING_16BIT_LEN_MARKER;
142
+ header.writeUInt16BE(length, 1);
143
+ return Buffer.concat([header, encoded]);
144
+ }
145
+
146
+ if (length <= constants.OPACK2_UINT32_MAX) {
147
+ const header = Buffer.allocUnsafe(5);
148
+ header[0] = constants.OPACK2_STRING_32BIT_LEN_MARKER;
149
+ header.writeUInt32BE(length, 1);
150
+ return Buffer.concat([header, encoded]);
151
+ }
152
+
153
+ throw new AppleTVError(
154
+ `String too long for OPACK2 encoding: ${length} bytes`,
155
+ );
156
+ }
157
+
158
+ /**
159
+ * Encodes binary data with length-optimized headers
160
+ * @param bytes - Buffer to encode
161
+ * @returns Buffer containing encoded binary data
162
+ */
163
+ private static encodeBytes(bytes: Buffer): Buffer {
164
+ const length = bytes.length;
165
+
166
+ if (length <= constants.OPACK2_SMALL_BYTES_MAX) {
167
+ return Buffer.concat([
168
+ Buffer.from([constants.OPACK2_SMALL_BYTES_BASE + length]),
169
+ bytes,
170
+ ]);
171
+ }
172
+
173
+ if (length <= constants.OPACK2_UINT8_MAX) {
174
+ return Buffer.concat([
175
+ Buffer.from([constants.OPACK2_BYTES_8BIT_LEN_MARKER, length]),
176
+ bytes,
177
+ ]);
178
+ }
179
+
180
+ if (length <= constants.OPACK2_UINT16_MAX) {
181
+ const header = Buffer.allocUnsafe(3);
182
+ header[0] = constants.OPACK2_BYTES_16BIT_LEN_MARKER;
183
+ header.writeUInt16BE(length, 1);
184
+ return Buffer.concat([header, bytes]);
185
+ }
186
+
187
+ if (length <= constants.OPACK2_UINT32_MAX) {
188
+ const header = Buffer.allocUnsafe(5);
189
+ header[0] = constants.OPACK2_BYTES_32BIT_LEN_MARKER;
190
+ header.writeUInt32BE(length, 1);
191
+ return Buffer.concat([header, bytes]);
192
+ }
193
+
194
+ throw new AppleTVError(
195
+ `Byte array too long for OPACK2 encoding: ${length} bytes`,
196
+ );
197
+ }
198
+
199
+ /**
200
+ * Encodes arrays with count-optimized headers
201
+ * @param arr - Array to encode
202
+ * @returns Buffer containing encoded array
203
+ */
204
+ private static encodeArray(arr: SerializableValue[]): Buffer {
205
+ const length = arr.length;
206
+
207
+ if (length <= constants.OPACK2_SMALL_ARRAY_MAX) {
208
+ const parts: Buffer[] = [
209
+ Buffer.from([constants.OPACK2_SMALL_ARRAY_BASE + length]),
210
+ ];
211
+ for (const item of arr) {
212
+ parts.push(this.encode(item));
213
+ }
214
+ return Buffer.concat(parts);
215
+ }
216
+
217
+ const parts: Buffer[] = [
218
+ Buffer.from([constants.OPACK2_VARIABLE_ARRAY_MARKER]),
219
+ ];
220
+ for (const item of arr) {
221
+ parts.push(this.encode(item));
222
+ }
223
+ parts.push(Buffer.from([constants.OPACK2_NULL]));
224
+ return Buffer.concat(parts);
225
+ }
226
+
227
+ /**
228
+ * Encodes objects/dictionaries with count-optimized headers
229
+ * @param dict - Object to encode
230
+ * @returns Buffer containing encoded dictionary
231
+ */
232
+ private static encodeDict(dict: Record<string, SerializableValue>): Buffer {
233
+ const entries = Object.entries(dict);
234
+ const length = entries.length;
235
+
236
+ if (length < constants.OPACK2_SMALL_DICT_MAX) {
237
+ const parts: Buffer[] = [
238
+ Buffer.from([constants.OPACK2_SMALL_DICT_BASE + length]),
239
+ ];
240
+ for (const [key, value] of entries) {
241
+ parts.push(this.encode(key));
242
+ parts.push(this.encode(value));
243
+ }
244
+ return Buffer.concat(parts);
245
+ }
246
+
247
+ const parts: Buffer[] = [
248
+ Buffer.from([constants.OPACK2_VARIABLE_DICT_MARKER]),
249
+ ];
250
+ for (const [key, value] of entries) {
251
+ parts.push(this.encode(key));
252
+ parts.push(this.encode(value));
253
+ }
254
+ parts.push(Buffer.from([constants.OPACK2_NULL, constants.OPACK2_NULL]));
255
+ return Buffer.concat(parts);
256
+ }
257
+ }
@@ -1,38 +0,0 @@
1
- # Dependabot configuration file
2
- # See: https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
3
-
4
- version: 2
5
- updates:
6
- # Enable npm dependency updates
7
- - package-ecosystem: "npm"
8
- directory: "/"
9
- schedule:
10
- interval: "weekly"
11
- # Specify which branches to update
12
- target-branch: "main"
13
- # Set version update strategy
14
- versioning-strategy: "auto"
15
- # Set review requirements
16
- open-pull-requests-limit: 10
17
- # Group all dev dependencies together
18
- groups:
19
- dev-dependencies:
20
- patterns:
21
- - "*"
22
- exclude-patterns:
23
- - "@types/*" # Keep type definitions separate
24
- # Labels for pull requests
25
- labels:
26
- - "dependencies"
27
- - "npm"
28
-
29
- # Enable GitHub Actions updates
30
- - package-ecosystem: "github-actions"
31
- directory: "/"
32
- schedule:
33
- interval: "monthly"
34
- target-branch: "main"
35
- # Labels for pull requests
36
- labels:
37
- - "dependencies"
38
- - "github-actions"