1use hap_tlv8::{Tlv8Map, Tlv8Writer};
41use num_bigint::BigUint;
42use sha2::Sha512;
43
44use crate::aead::{decrypt, encrypt, hap_nonce};
45use crate::error::{CryptoError, Result};
46use crate::kdf::hkdf_sha512;
47use crate::keys::{verify_ed25519, ControllerKeypair};
48use crate::srp::{hap_group, SrpClient, SrpServer};
49use crate::tlv_types as tlv;
50
51const PAIR_SETUP_USERNAME: &[u8] = b"Pair-Setup";
53
54const ENCRYPT_SALT: &[u8] = b"Pair-Setup-Encrypt-Salt";
56const ENCRYPT_INFO: &[u8] = b"Pair-Setup-Encrypt-Info";
57const CONTROLLER_SIGN_SALT: &[u8] = b"Pair-Setup-Controller-Sign-Salt";
59const CONTROLLER_SIGN_INFO: &[u8] = b"Pair-Setup-Controller-Sign-Info";
60const ACCESSORY_SIGN_SALT: &[u8] = b"Pair-Setup-Accessory-Sign-Salt";
62const ACCESSORY_SIGN_INFO: &[u8] = b"Pair-Setup-Accessory-Sign-Info";
63
64const NONCE_M5: &[u8] = b"PS-Msg05";
66const NONCE_M6: &[u8] = b"PS-Msg06";
67
68#[derive(Debug, Clone, PartialEq, Eq)]
73pub struct AccessoryPairing {
74 pub pairing_id: String,
77 pub ltpk: [u8; 32],
79}
80
81#[derive(Debug, Clone, PartialEq, Eq)]
83pub enum PairSetupStep {
84 Send(Vec<u8>),
86 Done(AccessoryPairing),
88}
89
90enum State {
92 Initial,
94 AwaitingM2,
96 AwaitingM4 { session_key: Vec<u8>, m1: Vec<u8> },
99 AwaitingM6 { session_key: Vec<u8> },
102 Done,
104}
105
106pub struct PairSetupClient {
112 password: String,
114 controller: ControllerKeypair,
115 srp: SrpClient<Sha512>,
116 state: State,
117}
118
119impl PairSetupClient {
120 pub fn new(setup_code: &str, controller: ControllerKeypair) -> Result<Self> {
137 let srp = SrpClient::<Sha512>::new(hap_group()?, PAIR_SETUP_USERNAME)?;
138 Ok(Self {
139 password: normalize_setup_code(setup_code),
140 controller,
141 srp,
142 state: State::Initial,
143 })
144 }
145
146 pub fn new_with_private(
158 setup_code: &str,
159 controller: ControllerKeypair,
160 a: &[u8],
161 ) -> Result<Self> {
162 let srp = SrpClient::<Sha512>::with_private(
163 hap_group()?,
164 PAIR_SETUP_USERNAME,
165 BigUint::from_bytes_be(a),
166 )?;
167 Ok(Self {
168 password: normalize_setup_code(setup_code),
169 controller,
170 srp,
171 state: State::Initial,
172 })
173 }
174
175 #[must_use]
181 pub fn start(&mut self) -> Vec<u8> {
182 self.state = State::AwaitingM2;
183 let mut out = Vec::new();
184 let mut w = Tlv8Writer::new(&mut out);
185 w.push_u8(tlv::STATE, tlv::STATE_M1);
186 w.push_u8(tlv::METHOD, tlv::METHOD_PAIR_SETUP);
187 out
188 }
189
190 pub fn handle(&mut self, response: &[u8]) -> Result<PairSetupStep> {
203 let map = Tlv8Map::parse(response)?;
204 check_error(&map)?;
205 match &self.state {
206 State::Initial => Err(CryptoError::Encoding("Pair Setup not started")),
207 State::AwaitingM2 => self.handle_m2(&map),
208 State::AwaitingM4 { .. } => self.handle_m4(&map),
209 State::AwaitingM6 { .. } => self.handle_m6(&map),
210 State::Done => Err(CryptoError::Encoding("Pair Setup already finished")),
211 }
212 }
213
214 fn handle_m2(&mut self, map: &Tlv8Map) -> Result<PairSetupStep> {
216 expect_state(map, tlv::STATE_M2)?;
217 let salt = map
218 .get(tlv::SALT)
219 .ok_or(CryptoError::Encoding("M2 missing salt"))?
220 .to_vec();
221 let b_bytes = map
222 .get(tlv::PUBLIC_KEY)
223 .ok_or(CryptoError::Encoding("M2 missing accessory public key B"))?;
224 let b_pub = BigUint::from_bytes_be(b_bytes);
225
226 let premaster = self
227 .srp
228 .premaster(&salt, self.password.as_bytes(), &b_pub)?;
229 let session_key = self.srp.session_key(&premaster);
230 let m1 = self.srp.proof_m1(&salt, &b_pub, &session_key);
231
232 let mut out = Vec::new();
233 let mut w = Tlv8Writer::new(&mut out);
234 w.push_u8(tlv::STATE, tlv::STATE_M3);
235 w.push(tlv::PUBLIC_KEY, &self.srp.a_pub_bytes());
236 w.push(tlv::PROOF, &m1);
237
238 self.state = State::AwaitingM4 { session_key, m1 };
239 Ok(PairSetupStep::Send(out))
240 }
241
242 fn handle_m4(&mut self, map: &Tlv8Map) -> Result<PairSetupStep> {
245 expect_state(map, tlv::STATE_M4)?;
246 let State::AwaitingM4 { session_key, m1 } = &self.state else {
247 return Err(CryptoError::Encoding("Pair Setup state corrupted"));
248 };
249 let session_key = session_key.clone();
250 let m1 = m1.clone();
251
252 let proof = map
253 .get(tlv::PROOF)
254 .ok_or(CryptoError::Encoding("M4 missing accessory proof M2"))?;
255 self.srp.verify_m2(&m1, &session_key, proof)?;
256
257 let mut enc_key = [0u8; 32];
259 hkdf_sha512(&session_key, ENCRYPT_SALT, ENCRYPT_INFO, &mut enc_key)?;
260 let mut ios_device_x = [0u8; 32];
261 hkdf_sha512(
262 &session_key,
263 CONTROLLER_SIGN_SALT,
264 CONTROLLER_SIGN_INFO,
265 &mut ios_device_x,
266 )?;
267
268 let id = self.controller.id.as_bytes();
269 let ltpk = self.controller.ltpk();
270
271 let mut signed = Vec::with_capacity(ios_device_x.len() + id.len() + ltpk.len());
273 signed.extend_from_slice(&ios_device_x);
274 signed.extend_from_slice(id);
275 signed.extend_from_slice(<pk);
276 let signature = self.controller.sign(&signed);
277
278 let mut sub = Vec::new();
279 let mut sw = Tlv8Writer::new(&mut sub);
280 sw.push(tlv::IDENTIFIER, id);
281 sw.push(tlv::PUBLIC_KEY, <pk);
282 sw.push(tlv::SIGNATURE, &signature);
283
284 let nonce = hap_nonce(NONCE_M5);
285 let sealed = encrypt(&enc_key, &nonce, b"", &sub)?;
286
287 let mut out = Vec::new();
288 let mut w = Tlv8Writer::new(&mut out);
289 w.push_u8(tlv::STATE, tlv::STATE_M5);
290 w.push(tlv::ENCRYPTED_DATA, &sealed);
291
292 self.state = State::AwaitingM6 { session_key };
293 Ok(PairSetupStep::Send(out))
294 }
295
296 fn handle_m6(&mut self, map: &Tlv8Map) -> Result<PairSetupStep> {
299 expect_state(map, tlv::STATE_M6)?;
300 let State::AwaitingM6 { session_key } = &self.state else {
301 return Err(CryptoError::Encoding("Pair Setup state corrupted"));
302 };
303 let session_key = session_key.clone();
304 self.state = State::Done;
305
306 let encrypted = map
307 .get(tlv::ENCRYPTED_DATA)
308 .ok_or(CryptoError::Encoding("M6 missing encrypted data"))?;
309
310 let mut enc_key = [0u8; 32];
311 hkdf_sha512(&session_key, ENCRYPT_SALT, ENCRYPT_INFO, &mut enc_key)?;
312 let nonce = hap_nonce(NONCE_M6);
313 let plaintext = decrypt(&enc_key, &nonce, b"", encrypted)?;
314
315 let sub = Tlv8Map::parse(&plaintext)?;
316 let id_bytes = sub
317 .get(tlv::IDENTIFIER)
318 .ok_or(CryptoError::Encoding("M6 sub-TLV missing identifier"))?;
319 let ltpk_bytes = sub
320 .get(tlv::PUBLIC_KEY)
321 .ok_or(CryptoError::Encoding("M6 sub-TLV missing public key"))?;
322 let signature = sub
323 .get(tlv::SIGNATURE)
324 .ok_or(CryptoError::Encoding("M6 sub-TLV missing signature"))?;
325
326 let ltpk: [u8; 32] = ltpk_bytes
327 .try_into()
328 .map_err(|_| CryptoError::Encoding("accessory LTPK is not 32 bytes"))?;
329 let signature: [u8; 64] = signature
330 .try_into()
331 .map_err(|_| CryptoError::Encoding("accessory signature is not 64 bytes"))?;
332 let pairing_id = String::from_utf8(id_bytes.to_vec())
333 .map_err(|_| CryptoError::Encoding("accessory pairing id is not valid UTF-8"))?;
334
335 let mut accessory_x = [0u8; 32];
337 hkdf_sha512(
338 &session_key,
339 ACCESSORY_SIGN_SALT,
340 ACCESSORY_SIGN_INFO,
341 &mut accessory_x,
342 )?;
343
344 let mut signed = Vec::with_capacity(accessory_x.len() + id_bytes.len() + ltpk.len());
346 signed.extend_from_slice(&accessory_x);
347 signed.extend_from_slice(id_bytes);
348 signed.extend_from_slice(<pk);
349 verify_ed25519(<pk, &signed, &signature)?;
350
351 Ok(PairSetupStep::Done(AccessoryPairing { pairing_id, ltpk }))
352 }
353}
354
355pub struct HapPairSetupSrpServer {
372 inner: SrpServer<Sha512>,
373}
374
375impl HapPairSetupSrpServer {
376 pub fn new(setup_code: &str) -> Result<(Self, Vec<u8>)> {
389 let password = normalize_setup_code(setup_code);
390 let inner =
391 SrpServer::<Sha512>::new(hap_group()?, PAIR_SETUP_USERNAME, password.as_bytes());
392 let salt = inner.salt().to_vec();
393 Ok((Self { inner }, salt))
394 }
395
396 #[must_use]
399 pub fn b_pub_bytes(&self) -> Vec<u8> {
400 self.inner.b_pub_bytes()
401 }
402
403 pub fn session_key(&self, a_pub_bytes: &[u8]) -> Result<Vec<u8>> {
411 let a_pub = BigUint::from_bytes_be(a_pub_bytes);
412 self.inner.session_key(&a_pub)
413 }
414
415 pub fn verify_m1_prove_m2(&self, a_pub_bytes: &[u8], m1: &[u8]) -> Result<Vec<u8>> {
429 let a_pub = BigUint::from_bytes_be(a_pub_bytes);
430 let session_key = self.inner.session_key(&a_pub)?;
431 self.inner.verify_m1_prove_m2(&a_pub, &session_key, m1)
432 }
433}
434
435fn normalize_setup_code(code: &str) -> String {
441 let digits: String = code.chars().filter(char::is_ascii_digit).collect();
442 if digits.len() == 8 {
443 format!("{}-{}-{}", &digits[0..3], &digits[3..5], &digits[5..8])
444 } else {
445 code.to_string()
446 }
447}
448
449fn check_error(map: &Tlv8Map) -> Result<()> {
451 match map.get(tlv::ERROR) {
452 None | Some([]) => Ok(()),
453 Some([2]) => Err(CryptoError::SrpProofMismatch),
456 Some(_) => Err(CryptoError::Encoding("accessory returned a pairing error")),
457 }
458}
459
460fn expect_state(map: &Tlv8Map, expected: u8) -> Result<()> {
462 match map.get_u8(tlv::STATE)? {
463 None => Ok(()),
465 Some(s) if s == expected => Ok(()),
466 Some(_) => Err(CryptoError::Encoding("unexpected Pair Setup state")),
467 }
468}
469
470#[cfg(test)]
471#[allow(clippy::unwrap_used, clippy::expect_used, clippy::indexing_slicing)]
475mod tests {
476 use super::*;
477 use crate::srp::{compute_b, compute_k, compute_u, compute_v, compute_x, SrpGroup};
478 use ed25519_dalek::Signer;
479 use sha2::{Digest, Sha512};
480
481 fn fixture(rel: &str) -> Option<Vec<u8>> {
483 let p = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
484 .join("../../test-vectors")
485 .join(rel);
486 std::fs::read(p).ok()
487 }
488
489 fn test_controller() -> ControllerKeypair {
490 let seed = [
492 0x4c, 0xcd, 0x08, 0x9b, 0x28, 0xff, 0x96, 0xda, 0x9d, 0xb6, 0xc3, 0x46, 0xec, 0x11,
493 0x4e, 0x0f, 0x5b, 0x8a, 0x31, 0x9f, 0x35, 0xab, 0xa6, 0x24, 0xda, 0x8c, 0xf6, 0xed,
494 0x4f, 0xb8, 0xa6, 0xfb,
495 ];
496 ControllerKeypair::from_seed("test-controller".to_string(), seed)
497 }
498
499 #[test]
502 fn m1_reproduces_captured_trace_byte_for_byte() {
503 let Some(expected) = fixture("pair-setup/m1.bin") else {
504 eprintln!("skipping: no test-vectors/pair-setup/m1.bin");
505 return;
506 };
507 let mut client = PairSetupClient::new("123-45-678", test_controller()).unwrap();
508 let m1 = client.start();
509 assert_eq!(
510 m1, expected,
511 "M1 must match the captured trace byte-for-byte"
512 );
513 }
514
515 fn captured_session_key() -> Option<Vec<u8>> {
523 let s = fixture("srp/S.bin")?;
524 Some(Sha512::digest(&s).to_vec())
526 }
527
528 #[test]
529 fn m6_decrypts_and_verifies_accessory_signature_from_real_trace() {
530 let (Some(m6), Some(session_key)) = (fixture("pair-setup/m6.bin"), captured_session_key())
531 else {
532 eprintln!("skipping: no captured S.bin / m6.bin");
533 return;
534 };
535
536 let mut client = PairSetupClient::new("000-00-000", test_controller()).unwrap();
537 client.state = State::AwaitingM6 { session_key };
539
540 let step = client.handle(&m6).expect("M6 must decrypt and verify");
541 let PairSetupStep::Done(pairing) = step else {
542 panic!("expected Done, got {step:?}");
543 };
544 assert_eq!(pairing.pairing_id, "AE:EC:86:C0:BF:D7");
545 assert_eq!(pairing.ltpk.len(), 32);
546 assert!(!pairing.pairing_id.is_empty());
547 }
548
549 #[test]
555 fn m5_decrypts_and_controller_signature_verifies_from_real_trace() {
556 let (Some(m5), Some(session_key)) = (fixture("pair-setup/m5.bin"), captured_session_key())
557 else {
558 eprintln!("skipping: no captured S.bin / m5.bin");
559 return;
560 };
561
562 let map = Tlv8Map::parse(&m5).unwrap();
563 let encrypted = map.get(tlv::ENCRYPTED_DATA).unwrap();
564
565 let mut enc_key = [0u8; 32];
566 hkdf_sha512(&session_key, ENCRYPT_SALT, ENCRYPT_INFO, &mut enc_key).unwrap();
567 let nonce = hap_nonce(NONCE_M5);
568 let plaintext = decrypt(&enc_key, &nonce, b"", encrypted).unwrap();
569
570 let sub = Tlv8Map::parse(&plaintext).unwrap();
571 let id = sub.get(tlv::IDENTIFIER).unwrap();
572 let pubkey = sub.get(tlv::PUBLIC_KEY).unwrap();
573 let sig = sub.get(tlv::SIGNATURE).unwrap();
574 assert_eq!(pubkey.len(), 32, "controller LTPK is 32 bytes");
575 assert_eq!(sig.len(), 64, "controller signature is 64 bytes");
576
577 let mut ios_device_x = [0u8; 32];
578 hkdf_sha512(
579 &session_key,
580 CONTROLLER_SIGN_SALT,
581 CONTROLLER_SIGN_INFO,
582 &mut ios_device_x,
583 )
584 .unwrap();
585
586 let mut signed = Vec::new();
587 signed.extend_from_slice(&ios_device_x);
588 signed.extend_from_slice(id);
589 signed.extend_from_slice(pubkey);
590
591 let ltpk: [u8; 32] = pubkey.try_into().unwrap();
592 let signature: [u8; 64] = sig.try_into().unwrap();
593 verify_ed25519(<pk, &signed, &signature)
594 .expect("captured M5 controller signature must verify");
595 }
596
597 struct TestAccessory {
602 group: SrpGroup,
603 pairing_id: String,
604 signing: ed25519_dalek::SigningKey,
605 salt: Vec<u8>,
606 b_priv: BigUint,
607 b_pub: BigUint,
608 session_key: Option<Vec<u8>>,
609 a_pub: Option<BigUint>,
610 }
611
612 impl TestAccessory {
613 fn new(password: &str) -> Self {
614 let group = hap_group().unwrap();
615 let salt = vec![0x11u8; 16];
616 let x = compute_x::<Sha512>(&salt, PAIR_SETUP_USERNAME, password.as_bytes());
617 let v = compute_v(&group, &x);
618 let k = compute_k::<Sha512>(&group);
619 let b_priv = BigUint::from_bytes_be(&[0x5Au8; 32]);
620 let b_pub = compute_b(&group, &k, &v, &b_priv);
621 let signing = ed25519_dalek::SigningKey::from_bytes(&[0x99u8; 32]);
622 Self {
623 group,
624 pairing_id: "11:22:33:44:55:66".to_string(),
625 signing,
626 salt,
627 b_priv,
628 b_pub,
629 session_key: None,
630 a_pub: None,
631 }
632 }
633
634 fn m2(&self) -> Vec<u8> {
637 let mut out = Vec::new();
638 let mut w = Tlv8Writer::new(&mut out);
639 w.push_u8(tlv::STATE, tlv::STATE_M2);
640 w.push(tlv::SALT, &self.salt);
641 w.push(tlv::PUBLIC_KEY, &pad_be(&self.b_pub, 384));
642 out
643 }
644
645 fn m4(&mut self, m3: &[u8]) -> Vec<u8> {
648 let map = Tlv8Map::parse(m3).unwrap();
649 let a_bytes = map.get(tlv::PUBLIC_KEY).unwrap();
650 let a_pub = BigUint::from_bytes_be(a_bytes);
651 let m1 = map.get(tlv::PROOF).unwrap().to_vec();
652
653 let modulus = self.group.modulus();
654 let scrambler = compute_u::<Sha512>(&self.group, &a_pub, &self.b_pub);
657 let x_priv =
658 compute_x::<Sha512>(&self.salt, PAIR_SETUP_USERNAME, TEST_PASSWORD.as_bytes());
659 let verifier = compute_v(&self.group, &x_priv);
660 let vu = verifier.modpow(&scrambler, modulus);
661 let base = (&a_pub * &vu) % modulus;
662 let premaster = base.modpow(&self.b_priv, modulus);
663 let session_key = Sha512::digest(pad_be(&premaster, 384)).to_vec();
664
665 let expected_m1 = self.controller_m1(&a_pub, &session_key);
668 assert_eq!(m1, expected_m1, "test accessory: controller M1 must verify");
669
670 self.a_pub = Some(a_pub);
671 self.session_key = Some(session_key.clone());
672
673 let m2_proof = {
674 let mut h = Sha512::new();
675 h.update(pad_be(self.a_pub.as_ref().unwrap(), 384));
676 h.update(&m1);
677 h.update(&session_key);
678 h.finalize().to_vec()
679 };
680
681 let mut out = Vec::new();
682 let mut w = Tlv8Writer::new(&mut out);
683 w.push_u8(tlv::STATE, tlv::STATE_M4);
684 w.push(tlv::PROOF, &m2_proof);
685 out
686 }
687
688 fn controller_m1(&self, a_pub: &BigUint, session_key: &[u8]) -> Vec<u8> {
689 let h_n = Sha512::digest(self.group.modulus().to_bytes_be());
690 let h_g = Sha512::digest(self.group.generator().to_bytes_be());
691 let h_xor: Vec<u8> = h_n.iter().zip(h_g.iter()).map(|(a, b)| a ^ b).collect();
692 let h_i = Sha512::digest(PAIR_SETUP_USERNAME);
693 let mut h = Sha512::new();
694 h.update(h_xor);
695 h.update(h_i);
696 h.update(&self.salt);
697 h.update(pad_be(a_pub, 384));
698 h.update(pad_be(&self.b_pub, 384));
699 h.update(session_key);
700 h.finalize().to_vec()
701 }
702
703 fn m6(&self, _m5: &[u8]) -> Vec<u8> {
706 let session_key = self.session_key.as_ref().unwrap();
707 let mut accessory_x = [0u8; 32];
708 hkdf_sha512(
709 session_key,
710 ACCESSORY_SIGN_SALT,
711 ACCESSORY_SIGN_INFO,
712 &mut accessory_x,
713 )
714 .unwrap();
715 let ltpk = self.signing.verifying_key().to_bytes();
716 let id = self.pairing_id.as_bytes();
717
718 let mut signed = Vec::new();
719 signed.extend_from_slice(&accessory_x);
720 signed.extend_from_slice(id);
721 signed.extend_from_slice(<pk);
722 let sig = self.signing.sign(&signed).to_bytes();
723
724 let mut sub = Vec::new();
725 let mut sw = Tlv8Writer::new(&mut sub);
726 sw.push(tlv::IDENTIFIER, id);
727 sw.push(tlv::PUBLIC_KEY, <pk);
728 sw.push(tlv::SIGNATURE, &sig);
729
730 let mut enc_key = [0u8; 32];
731 hkdf_sha512(session_key, ENCRYPT_SALT, ENCRYPT_INFO, &mut enc_key).unwrap();
732 let sealed = encrypt(&enc_key, &hap_nonce(NONCE_M6), b"", &sub).unwrap();
733
734 let mut out = Vec::new();
735 let mut w = Tlv8Writer::new(&mut out);
736 w.push_u8(tlv::STATE, tlv::STATE_M6);
737 w.push(tlv::ENCRYPTED_DATA, &sealed);
738 out
739 }
740 }
741
742 const TEST_PASSWORD: &str = "123-45-678";
743
744 fn pad_be(v: &BigUint, width: usize) -> Vec<u8> {
746 let raw = v.to_bytes_be();
747 if raw.len() >= width {
748 return raw;
749 }
750 let mut out = vec![0u8; width - raw.len()];
751 out.extend_from_slice(&raw);
752 out
753 }
754
755 #[test]
756 fn full_machine_replay_reaches_done() {
757 let mut accessory = TestAccessory::new(TEST_PASSWORD);
758 let a = [0x37u8; 32];
759 let mut client =
760 PairSetupClient::new_with_private(TEST_PASSWORD, test_controller(), &a).unwrap();
761
762 let m1 = client.start();
763 assert_eq!(
764 Tlv8Map::parse(&m1).unwrap().get_u8(tlv::STATE).unwrap(),
765 Some(tlv::STATE_M1)
766 );
767
768 let m2 = accessory.m2();
770 let PairSetupStep::Send(m3) = client.handle(&m2).unwrap() else {
771 panic!("expected M3");
772 };
773
774 let m4 = accessory.m4(&m3);
776 let PairSetupStep::Send(m5) = client.handle(&m4).unwrap() else {
777 panic!("expected M5");
778 };
779
780 let m6 = accessory.m6(&m5);
782 let PairSetupStep::Done(pairing) = client.handle(&m6).unwrap() else {
783 panic!("expected Done");
784 };
785 assert_eq!(pairing.pairing_id, "11:22:33:44:55:66");
786 assert_eq!(pairing.ltpk, accessory.signing.verifying_key().to_bytes());
787 }
788
789 #[test]
790 fn wrong_setup_code_fails_m4_proof() {
791 let accessory = TestAccessory::new(TEST_PASSWORD);
792 let a = [0x37u8; 32];
793 let mut client =
796 PairSetupClient::new_with_private("999-99-999", test_controller(), &a).unwrap();
797 let _ = client.start();
798 let m2 = accessory.m2();
799 let PairSetupStep::Send(m3) = client.handle(&m2).unwrap() else {
800 panic!("expected M3");
801 };
802 let _ = m3;
806 let mut bad_m4 = Vec::new();
807 let mut w = Tlv8Writer::new(&mut bad_m4);
808 w.push_u8(tlv::STATE, tlv::STATE_M4);
809 w.push(tlv::PROOF, &[0u8; 64]);
810 assert!(matches!(
811 client.handle(&bad_m4),
812 Err(CryptoError::SrpProofMismatch)
813 ));
814 }
815
816 #[test]
817 fn accessory_error_tlv_is_surfaced() {
818 let a = [0x37u8; 32];
819 let mut client =
820 PairSetupClient::new_with_private(TEST_PASSWORD, test_controller(), &a).unwrap();
821 let _ = client.start();
822 let mut err = Vec::new();
824 let mut w = Tlv8Writer::new(&mut err);
825 w.push_u8(tlv::STATE, tlv::STATE_M2);
826 w.push_u8(tlv::ERROR, 2); assert!(matches!(
828 client.handle(&err),
829 Err(CryptoError::SrpProofMismatch)
830 ));
831 }
832
833 #[test]
834 fn handle_before_start_errors() {
835 let a = [0x37u8; 32];
836 let mut client =
837 PairSetupClient::new_with_private(TEST_PASSWORD, test_controller(), &a).unwrap();
838 assert!(client.handle(b"").is_err());
839 }
840
841 #[test]
842 fn normalize_setup_code_regroups_bare_digits() {
843 assert_eq!(normalize_setup_code("12345678"), "123-45-678");
844 assert_eq!(normalize_setup_code("123-45-678"), "123-45-678");
845 assert_eq!(normalize_setup_code("oddball"), "oddball");
846 }
847
848 #[test]
856 fn hap_pair_setup_srp_server_interoperates_with_client() {
857 let (server, salt) = HapPairSetupSrpServer::new("123-45-678").unwrap();
858
859 let a = [0x37u8; 32];
860 let mut client =
861 PairSetupClient::new_with_private("123-45-678", test_controller(), &a).unwrap();
862 let _m1 = client.start();
863
864 let mut m2 = Vec::new();
866 let mut w = Tlv8Writer::new(&mut m2);
867 w.push_u8(tlv::STATE, tlv::STATE_M2);
868 w.push(tlv::SALT, &salt);
869 w.push(tlv::PUBLIC_KEY, &server.b_pub_bytes());
870
871 let PairSetupStep::Send(m3) = client.handle(&m2).unwrap() else {
873 panic!("expected M3 from the client");
874 };
875 let m3_map = Tlv8Map::parse(&m3).unwrap();
876 let a_pub = m3_map.get(tlv::PUBLIC_KEY).unwrap();
877 let m1 = m3_map.get(tlv::PROOF).unwrap();
878
879 let m2_proof = server.verify_m1_prove_m2(a_pub, m1).unwrap();
882
883 let mut m4 = Vec::new();
886 let mut w = Tlv8Writer::new(&mut m4);
887 w.push_u8(tlv::STATE, tlv::STATE_M4);
888 w.push(tlv::PROOF, &m2_proof);
889 assert!(
890 matches!(client.handle(&m4).unwrap(), PairSetupStep::Send(_)),
891 "client must accept M4 and emit M5"
892 );
893 }
894
895 #[test]
896 fn hap_pair_setup_srp_server_rejects_wrong_setup_code() {
897 let (setup_server, salt) = HapPairSetupSrpServer::new("123-45-678").unwrap();
900 let a = [0x37u8; 32];
901 let mut client =
902 PairSetupClient::new_with_private("123-45-678", test_controller(), &a).unwrap();
903 let _m1 = client.start();
904
905 let mut m2 = Vec::new();
906 let mut w = Tlv8Writer::new(&mut m2);
907 w.push_u8(tlv::STATE, tlv::STATE_M2);
908 w.push(tlv::SALT, &salt);
909 w.push(tlv::PUBLIC_KEY, &setup_server.b_pub_bytes());
910 let PairSetupStep::Send(m3) = client.handle(&m2).unwrap() else {
911 panic!("expected M3");
912 };
913 let m3_map = Tlv8Map::parse(&m3).unwrap();
914 let a_pub = m3_map.get(tlv::PUBLIC_KEY).unwrap();
915 let m1 = m3_map.get(tlv::PROOF).unwrap();
916
917 let (wrong_server, _) = HapPairSetupSrpServer::new("999-99-999").unwrap();
919 assert!(matches!(
920 wrong_server.verify_m1_prove_m2(a_pub, m1),
921 Err(CryptoError::SrpProofMismatch)
922 ));
923 }
924}