@basmilius/apple-common 0.9.16 → 0.9.18

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/dist/index.d.mts CHANGED
@@ -86,6 +86,7 @@ declare class Logger {
86
86
  declare class Reporter {
87
87
  #private;
88
88
  all(): void;
89
+ none(): void;
89
90
  disable(group: DebugGroup): void;
90
91
  enable(group: DebugGroup): void;
91
92
  isEnabled(group: DebugGroup): boolean;
@@ -107,19 +108,82 @@ declare abstract class BasePairing {
107
108
  constructor(context: Context);
108
109
  tlv(buffer: Buffer): Map<number, Buffer>;
109
110
  }
111
+ /**
112
+ * Implements the HAP (HomeKit Accessory Protocol) Pair-Setup flow using SRP-6a.
113
+ *
114
+ * The pairing process uses Secure Remote Password (SRP) for PIN-based authentication,
115
+ * followed by Ed25519 signature exchange for long-term identity establishment.
116
+ *
117
+ * Flow: M1 (salt exchange) → M2 (SRP proof) → M3 (server proof) → M4 (shared secret)
118
+ * → M5 (encrypted credential exchange) → M6 (accessory verification)
119
+ *
120
+ * For transient pairing (HomePod), only M1-M4 are needed — no long-term credentials are stored.
121
+ */
110
122
  declare class AccessoryPair extends BasePairing {
111
123
  #private;
112
124
  constructor(context: Context, requestHandler: RequestHandler);
113
125
  start(): Promise<void>;
114
126
  pin(askPin: () => Promise<string>): Promise<AccessoryCredentials>;
115
127
  transient(): Promise<AccessoryKeys>;
128
+ /**
129
+ * SRP Step 1: Initiates pair-setup by requesting the accessory's SRP public key and salt.
130
+ *
131
+ * Sends: TLV with Method=PairSetup, State=M1, optional Flags (e.g. TransientPairing).
132
+ * Receives: Accessory's SRP public key (B) and salt for key derivation.
133
+ */
116
134
  m1(additionalTlv?: [number, number | Buffer][]): Promise<PairM1>;
135
+ /**
136
+ * SRP Step 2: Generates client proof using the PIN.
137
+ *
138
+ * Creates an SRP client with the accessory's salt and public key, then computes
139
+ * the client's public key (A) and proof (M1) which proves knowledge of the PIN.
140
+ */
117
141
  m2(m1: PairM1, pin?: string): Promise<PairM2>;
142
+ /**
143
+ * SRP Step 3: Sends the client's public key and proof; receives the server's proof.
144
+ *
145
+ * Sends: TLV with State=M3, client SRP public key (A), and client proof (M1).
146
+ * Receives: Server's M2-proof, confirming the accessory also knows the PIN.
147
+ */
118
148
  m3(m2: PairM2): Promise<PairM3>;
149
+ /**
150
+ * SRP Step 4: Verifies the server's proof and derives the shared secret.
151
+ *
152
+ * Validates the server's M2-proof, then computes the SRP session key (K).
153
+ * This shared secret is the root key for all subsequent HKDF derivations.
154
+ */
119
155
  m4(m3: PairM3): Promise<PairM4>;
156
+ /**
157
+ * HAP Step 5: Encrypted credential exchange — controller sends its identity.
158
+ *
159
+ * Derives a signing key (HKDF with "Pair-Setup-Controller-Sign-Salt/Info") and
160
+ * a session key (HKDF with "Pair-Setup-Encrypt-Salt/Info") from the SRP shared secret.
161
+ * Signs [signingKey || pairingId || Ed25519PublicKey] and sends it encrypted
162
+ * via ChaCha20-Poly1305 (nonce "PS-Msg05").
163
+ *
164
+ * Receives: Encrypted accessory credentials (decrypted in M6).
165
+ */
120
166
  m5(m4: PairM4): Promise<PairM5>;
167
+ /**
168
+ * HAP Step 6: Verifies the accessory's identity and extracts long-term credentials.
169
+ *
170
+ * Decrypts the accessory's response from M5 via ChaCha20-Poly1305 (nonce "PS-Msg06").
171
+ * Derives "Accessory X" (HKDF with "Pair-Setup-Accessory-Sign-Salt/Info"), then
172
+ * verifies the Ed25519 signature over [accessoryX || accessoryId || accessoryPublicKey].
173
+ *
174
+ * Returns the accessory's long-term public key and identifier for future Pair-Verify sessions.
175
+ */
121
176
  m6(m4: PairM4, m5: PairM5): Promise<AccessoryCredentials>;
122
177
  }
178
+ /**
179
+ * Implements the HAP (HomeKit Accessory Protocol) Pair-Verify flow using Curve25519 ECDH.
180
+ *
181
+ * Used to re-authenticate a previously paired accessory without needing the PIN again.
182
+ * Establishes a new session with forward secrecy via ephemeral Curve25519 key exchange.
183
+ *
184
+ * Flow: M1 (ephemeral key exchange) → M2 (signature verification)
185
+ * → M3 (controller proof) → M4 (session key derivation)
186
+ */
123
187
  declare class AccessoryVerify extends BasePairing {
124
188
  #private;
125
189
  constructor(context: Context, requestHandler: RequestHandler);
package/dist/index.mjs CHANGED
@@ -693,6 +693,8 @@ var Discovery = class Discovery {
693
693
  const cached = Discovery.#cache.get(this.#service);
694
694
  if (cached && cached.expiresAt > Date.now()) return cached.results;
695
695
  }
696
+ const now = Date.now();
697
+ for (const [key, entry] of Discovery.#cache) if (entry.expiresAt <= now) Discovery.#cache.delete(key);
696
698
  const mapped = (await multicast([this.#service], 4)).map(toDiscoveryResult);
697
699
  Discovery.#cache.set(this.#service, {
698
700
  results: mapped,
@@ -1049,6 +1051,9 @@ var Reporter = class {
1049
1051
  "warn"
1050
1052
  ];
1051
1053
  }
1054
+ none() {
1055
+ this.#enabled = [];
1056
+ }
1052
1057
  disable(group) {
1053
1058
  if (this.#enabled.includes(group)) this.#enabled.splice(this.#enabled.indexOf(group), 1);
1054
1059
  }
@@ -3135,6 +3140,17 @@ var BasePairing = class {
3135
3140
  return data;
3136
3141
  }
3137
3142
  };
3143
+ /**
3144
+ * Implements the HAP (HomeKit Accessory Protocol) Pair-Setup flow using SRP-6a.
3145
+ *
3146
+ * The pairing process uses Secure Remote Password (SRP) for PIN-based authentication,
3147
+ * followed by Ed25519 signature exchange for long-term identity establishment.
3148
+ *
3149
+ * Flow: M1 (salt exchange) → M2 (SRP proof) → M3 (server proof) → M4 (shared secret)
3150
+ * → M5 (encrypted credential exchange) → M6 (accessory verification)
3151
+ *
3152
+ * For transient pairing (HomePod), only M1-M4 are needed — no long-term credentials are stored.
3153
+ */
3138
3154
  var AccessoryPair = class extends BasePairing {
3139
3155
  #name;
3140
3156
  #pairingId;
@@ -3189,6 +3205,12 @@ var AccessoryPair = class extends BasePairing {
3189
3205
  controllerToAccessoryKey
3190
3206
  };
3191
3207
  }
3208
+ /**
3209
+ * SRP Step 1: Initiates pair-setup by requesting the accessory's SRP public key and salt.
3210
+ *
3211
+ * Sends: TLV with Method=PairSetup, State=M1, optional Flags (e.g. TransientPairing).
3212
+ * Receives: Accessory's SRP public key (B) and salt for key derivation.
3213
+ */
3192
3214
  async m1(additionalTlv = []) {
3193
3215
  const response = await this.#requestHandler("m1", TLV8.encode([
3194
3216
  [TLV8.Value.Method, TLV8.Method.PairSetup],
@@ -3201,6 +3223,12 @@ var AccessoryPair = class extends BasePairing {
3201
3223
  salt: data.get(TLV8.Value.Salt)
3202
3224
  };
3203
3225
  }
3226
+ /**
3227
+ * SRP Step 2: Generates client proof using the PIN.
3228
+ *
3229
+ * Creates an SRP client with the accessory's salt and public key, then computes
3230
+ * the client's public key (A) and proof (M1) which proves knowledge of the PIN.
3231
+ */
3204
3232
  async m2(m1, pin = AIRPLAY_TRANSIENT_PIN) {
3205
3233
  const srpKey = await import_srp.SRP.genKey(32);
3206
3234
  this.#srp = new import_srp.SrpClient(import_srp.SRP.params.hap, m1.salt, Buffer.from("Pair-Setup"), Buffer.from(pin), srpKey, true);
@@ -3210,6 +3238,12 @@ var AccessoryPair = class extends BasePairing {
3210
3238
  proof: this.#srp.computeM1()
3211
3239
  };
3212
3240
  }
3241
+ /**
3242
+ * SRP Step 3: Sends the client's public key and proof; receives the server's proof.
3243
+ *
3244
+ * Sends: TLV with State=M3, client SRP public key (A), and client proof (M1).
3245
+ * Receives: Server's M2-proof, confirming the accessory also knows the PIN.
3246
+ */
3213
3247
  async m3(m2) {
3214
3248
  const response = await this.#requestHandler("m3", TLV8.encode([
3215
3249
  [TLV8.Value.State, TLV8.State.M3],
@@ -3218,10 +3252,26 @@ var AccessoryPair = class extends BasePairing {
3218
3252
  ]));
3219
3253
  return { serverProof: this.tlv(response).get(TLV8.Value.Proof) };
3220
3254
  }
3255
+ /**
3256
+ * SRP Step 4: Verifies the server's proof and derives the shared secret.
3257
+ *
3258
+ * Validates the server's M2-proof, then computes the SRP session key (K).
3259
+ * This shared secret is the root key for all subsequent HKDF derivations.
3260
+ */
3221
3261
  async m4(m3) {
3222
3262
  this.#srp.checkM2(m3.serverProof);
3223
3263
  return { sharedSecret: this.#srp.computeK() };
3224
3264
  }
3265
+ /**
3266
+ * HAP Step 5: Encrypted credential exchange — controller sends its identity.
3267
+ *
3268
+ * Derives a signing key (HKDF with "Pair-Setup-Controller-Sign-Salt/Info") and
3269
+ * a session key (HKDF with "Pair-Setup-Encrypt-Salt/Info") from the SRP shared secret.
3270
+ * Signs [signingKey || pairingId || Ed25519PublicKey] and sends it encrypted
3271
+ * via ChaCha20-Poly1305 (nonce "PS-Msg05").
3272
+ *
3273
+ * Receives: Encrypted accessory credentials (decrypted in M6).
3274
+ */
3225
3275
  async m5(m4) {
3226
3276
  const iosDeviceX = hkdf({
3227
3277
  hash: "sha512",
@@ -3260,6 +3310,15 @@ var AccessoryPair = class extends BasePairing {
3260
3310
  sessionKey
3261
3311
  };
3262
3312
  }
3313
+ /**
3314
+ * HAP Step 6: Verifies the accessory's identity and extracts long-term credentials.
3315
+ *
3316
+ * Decrypts the accessory's response from M5 via ChaCha20-Poly1305 (nonce "PS-Msg06").
3317
+ * Derives "Accessory X" (HKDF with "Pair-Setup-Accessory-Sign-Salt/Info"), then
3318
+ * verifies the Ed25519 signature over [accessoryX || accessoryId || accessoryPublicKey].
3319
+ *
3320
+ * Returns the accessory's long-term public key and identifier for future Pair-Verify sessions.
3321
+ */
3263
3322
  async m6(m4, m5) {
3264
3323
  const data = Chacha20.decrypt(m5.sessionKey, Buffer.from("PS-Msg06"), null, m5.data, m5.authTag);
3265
3324
  const tlv = TLV8.decode(data);
@@ -3288,6 +3347,15 @@ var AccessoryPair = class extends BasePairing {
3288
3347
  };
3289
3348
  }
3290
3349
  };
3350
+ /**
3351
+ * Implements the HAP (HomeKit Accessory Protocol) Pair-Verify flow using Curve25519 ECDH.
3352
+ *
3353
+ * Used to re-authenticate a previously paired accessory without needing the PIN again.
3354
+ * Establishes a new session with forward secrecy via ephemeral Curve25519 key exchange.
3355
+ *
3356
+ * Flow: M1 (ephemeral key exchange) → M2 (signature verification)
3357
+ * → M3 (controller proof) → M4 (session key derivation)
3358
+ */
3291
3359
  var AccessoryVerify = class extends BasePairing {
3292
3360
  #ephemeralKeyPair;
3293
3361
  #requestHandler;
@@ -3302,6 +3370,12 @@ var AccessoryVerify = class extends BasePairing {
3302
3370
  await this.#m3(credentials.pairingId, credentials.secretKey, m2);
3303
3371
  return await this.#m4(m2, credentials.pairingId);
3304
3372
  }
3373
+ /**
3374
+ * Pair-Verify Step 1: Ephemeral Curve25519 key exchange initiation.
3375
+ *
3376
+ * Sends: TLV with State=M1 and controller's ephemeral Curve25519 public key.
3377
+ * Receives: Accessory's ephemeral public key and encrypted identity proof.
3378
+ */
3305
3379
  async #m1() {
3306
3380
  const response = await this.#requestHandler("m1", TLV8.encode([[TLV8.Value.State, TLV8.State.M1], [TLV8.Value.PublicKey, Buffer.from(this.#ephemeralKeyPair.publicKey)]]));
3307
3381
  const data = this.tlv(response);
@@ -3311,6 +3385,16 @@ var AccessoryVerify = class extends BasePairing {
3311
3385
  serverPublicKey
3312
3386
  };
3313
3387
  }
3388
+ /**
3389
+ * Pair-Verify Step 2: ECDH shared secret derivation and accessory signature verification.
3390
+ *
3391
+ * Computes the Curve25519 shared secret, derives a session key via HKDF
3392
+ * ("Pair-Verify-Encrypt-Salt/Info"), then decrypts the accessory's response
3393
+ * via ChaCha20-Poly1305 (nonce "PV-Msg02").
3394
+ *
3395
+ * Verifies the accessory's Ed25519 signature over [serverPubKey || accessoryId || clientPubKey]
3396
+ * and checks the accessory identifier matches the stored credentials.
3397
+ */
3314
3398
  async #m2(localAccessoryIdentifier, longTermPublicKey, m1) {
3315
3399
  const sharedSecret = Buffer.from(Curve25519.generateSharedSecKey(this.#ephemeralKeyPair.secretKey, m1.serverPublicKey));
3316
3400
  const sessionKey = hkdf({
@@ -3339,6 +3423,13 @@ var AccessoryVerify = class extends BasePairing {
3339
3423
  sharedSecret
3340
3424
  };
3341
3425
  }
3426
+ /**
3427
+ * Pair-Verify Step 3: Controller authentication proof.
3428
+ *
3429
+ * Signs [clientEphemeralPubKey || pairingId || serverEphemeralPubKey] with the
3430
+ * controller's long-term Ed25519 secret key. Encrypts the signed TLV via
3431
+ * ChaCha20-Poly1305 (nonce "PV-Msg03") and sends it to the accessory.
3432
+ */
3342
3433
  async #m3(pairingId, secretKey, m2) {
3343
3434
  const iosDeviceInfo = Buffer.concat([
3344
3435
  this.#ephemeralKeyPair.publicKey,
@@ -3352,6 +3443,12 @@ var AccessoryVerify = class extends BasePairing {
3352
3443
  await this.#requestHandler("m3", TLV8.encode([[TLV8.Value.State, TLV8.State.M3], [TLV8.Value.EncryptedData, encrypted]]));
3353
3444
  return {};
3354
3445
  }
3446
+ /**
3447
+ * Pair-Verify Step 4: Returns the established session keys.
3448
+ *
3449
+ * The shared secret from the ECDH exchange is used by the caller to derive
3450
+ * encryption keys (via HKDF with "Control-Salt" and "Control-Read/Write-Encryption-Key").
3451
+ */
3355
3452
  async #m4(m2, pairingId) {
3356
3453
  return {
3357
3454
  accessoryToControllerKey: Buffer.alloc(0),
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@basmilius/apple-common",
3
3
  "description": "Common features shared across various apple protocol packages.",
4
- "version": "0.9.16",
4
+ "version": "0.9.18",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "author": {
@@ -45,8 +45,8 @@
45
45
  }
46
46
  },
47
47
  "dependencies": {
48
- "@basmilius/apple-encoding": "0.9.16",
49
- "@basmilius/apple-encryption": "0.9.16"
48
+ "@basmilius/apple-encoding": "0.9.18",
49
+ "@basmilius/apple-encryption": "0.9.18"
50
50
  },
51
51
  "devDependencies": {
52
52
  "@types/bun": "^1.3.11",