Post-release deep review of the merged HOMECORE platform PR (#1451) turned up several real issues, fixed here: - homecore-hap: StoredAccessory's permanent Ed25519 signing seed and StoredSetup's SRP salt/verifier were reachable via derived Debug. Not actively triggered by any current code path, but a future logging/panic-message change (an ordinary thing to add) would have printed the accessory's compromise-forever identity key in plaintext -- there's no rotation mechanism. Added manual, redacted Debug impls matching the existing SetupCode pattern; StoreState/ PairingStore's derived Debug inherits the redaction automatically. New test pins the exact rendered output. - homecore-api: /api/history/period and /api/logbook rejected the default (no filter_entity_id/entity) call shape once a room had more than 32 known entities -- exactly how the real HA frontend calls these endpoints. The MAX_HISTORY_ENTITIES cap now only applies to an explicit, unusually-large filter list; the existing MAX_API_HISTORY_ROWS total-row budget already bounds the actual work regardless of entity count. Two new tests: unfiltered succeeds with 40 known entities, explicit oversized filter is still rejected. - homecore-api: fire_event (both REST and WS) restricted event_type to [a-z0-9_]+, but real HA integrations commonly fire mixed-case, dotted, or hyphenated types (mobile_app.notification_action, ios.action_fired). Factored the check into a single is_valid_event_type() shared by both transports, relaxed to what actually matters for server safety: non-empty, length-bounded, no control characters. - homecore-migrate: write_config_entries/write_device_registry/ write_entity_registry's atomic no-clobber write had no escape hatch -- an operator who fixed a bad source row (or wanted a fresh re-import) had to manually delete prior output first. Added a --force CLI flag (default off, preserving the existing no-clobber default and its explicit malformed_entry_is_an_error_not_a_partial_write test) that atomically replaces an existing destination. First attempt used bare fs::rename, which hit real ERROR_ACCESS_DENIED sharing-violation flakiness on Windows; switched to pre-clearing the destination then publishing through the same hard_link step the default path already uses reliably. All touched crates re-verified: homecore-hap 46 tests (was 45), homecore-api 20+6+6+7=39 tests (was 18+6+6+7=37), homecore-migrate 25 tests (was 24), all 0 failed, clippy clean under -D warnings. Also corrected the public v2051 GitHub release notes: "Wasmtime 36.0.12 component loading" was inaccurate (the crate uses the core-module API with a hand-rolled host ABI, not the Component Model/WIT) -- doc-only, not a code change. Co-Authored-By: claude-flow <ruv@ruv.net>
homecore-hap
homecore-hap is HOMECORE's bounded, fail-closed HAP IP accessory server
(ADR-125). It implements the HAP R2 cryptographic pairing and transport
boundary without relying on the broken hap 0.1 pre-release crate.
Security and protocol coverage
- Pair-Setup M1-M6 uses the RFC 5054 3072-bit group with SHA-512, the HAP compatibility proof construction, HKDF-SHA512, ChaCha20-Poly1305, and Ed25519 long-term keys.
- Pair-Verify M1-M4 uses ephemeral X25519, strict Ed25519 transcript verification, HKDF-SHA512, and authenticated encrypted sub-TLVs.
- After successful M4, all HTTP and
EVENT/1.0traffic uses HAP records: two-byte little-endian authenticated lengths, at most 1024 plaintext bytes, independent directional keys, and monotonic 64-bit nonces. Authentication, replay, truncation, and oversize failures close the connection without an oracle response. /accessories,/characteristics, and/pairingsare inaccessible until Pair-Verify succeeds on that TCP connection. Pairings add/remove/list is restricted to a currently persisted administrator.- Accessory identity, Ed25519 seed, SRP salt/verifier, and controller records
are stored in a versioned file using same-directory atomic replacement.
Created Unix directories use mode
0700, files use0600, and permissive, oversized, symlinked, legacy, or malformed stores fail closed. - The raw setup code is returned only during first provisioning. Only its SRP
verifier is persisted;
SetupCoderedactsDebugoutput and zeroizes on drop. - Removing the last administrator atomically clears every pairing. Live sessions observe pairing revisions and are revoked, while the removal response is delivered before the requesting session closes.
The server also bounds connections, headers, bodies, request time, shutdown,
TLV sizes, controller counts, setup attempts, and concurrent Pair-Setup.
mDNS advertises the persisted accessory identifier and updates sf after
pairing or unpairing.
Provisioning and server integration
use std::{net::IpAddr, sync::Arc};
use homecore_hap::{
start_server, HapBridge, HapServerConfig, HapServiceRecord,
MdnsSdAdvertiser, PairingStore,
};
# async fn run() -> Result<(), Box<dyn std::error::Error>> {
let provisioned = PairingStore::load_or_create(
"/var/lib/homecore-hap/security.json",
)?;
if let Some(setup_code) = provisioned.setup_code.as_ref() {
// Send this once to a trusted local display or provisioning boundary.
println!("HAP setup code: {}", setup_code.expose());
}
let pairings = Arc::new(provisioned.store);
let record = HapServiceRecord::bridge(
"HOMECORE Bridge",
51826,
pairings.accessory_id()?,
);
let bridge = HapBridge::new(record);
let advertiser = Arc::new(MdnsSdAdvertiser::new(
"homecore",
"192.168.1.50".parse::<IpAddr>()?,
)?);
let server = start_server(
HapServerConfig::default(),
bridge.clone(),
pairings,
advertiser,
).await?;
// Feed HOMECORE StateChanged events through bridge.update_accessory(...).
server.shutdown().await?;
# Ok(())
# }
The mDNS device ID must equal the persisted accessory ID; startup rejects a mismatch. Real mDNS also requires a LAN-routable advertised address and multicast access.
Validation and remaining interoperability limits
The deterministic suite covers the HAP SRP session-key vector, complete
Pair-Setup and Pair-Verify ceremonies, transcript tampering, wrong proofs,
malformed/replayed/oversized records, atomic restart, last-admin removal, and
a real TCP lifecycle from Pair-Verify through encrypted /accessories.
This is protocol-level HAP R2 coverage, not a claim of Apple certification:
- It has not yet been exercised against a current Apple Home controller or the current commercial MFi specification.
- Transient and split Pair-Setup flags are rejected as
Unavailable. - Writable characteristic service calls, timed writes, resource endpoints, and stable persisted AID/IID allocation are not implemented. The present characteristic surface is read and event subscription only.
- Operational hardening still depends on protecting the host and the
0600security file; no hardware-backed key store is integrated.
Build and test:
cargo test -p homecore-hap --no-default-features
cargo test -p homecore-hap --features hap-server
cargo clippy -p homecore-hap --all-targets --features hap-server -- -D warnings