in HAP/HAPPairingPairSetup.c [430:808]
static HAPError HAPPairingPairSetupGetM4(
HAPAccessoryServerRef* server_,
HAPSessionRef* session_,
HAPTLVWriterRef* responseWriter) {
HAPPrecondition(server_);
HAPAccessoryServer* server = (HAPAccessoryServer*) server_;
HAPPrecondition(server->pairSetup.sessionThatIsCurrentlyPairing == session_);
HAPPrecondition(session_);
HAPSession* session = (HAPSession*) session_;
HAPPrecondition(session->state.pairSetup.state == 4);
HAPPrecondition(!session->state.pairSetup.error);
HAPPrecondition(responseWriter);
HAPError err;
// See HomeKit Accessory Protocol Specification R14
// Section 5.6.4 M4: Accessory -> iOS Device -- `SRP Verify Response'
HAPLogDebug(&logObject, "Pair Setup M4: SRP Verify Response.");
// Compute SRP shared secret key.
{
void* bytes;
size_t maxBytes;
HAPTLVWriterGetScratchBytes(responseWriter, &bytes, &maxBytes);
void* u = HAPTLVScratchBufferAlloc(&bytes, &maxBytes, SRP_SCRAMBLING_PARAMETER_BYTES);
void* S = HAPTLVScratchBufferAlloc(&bytes, &maxBytes, SRP_PREMASTER_SECRET_BYTES);
void* M1 = HAPTLVScratchBufferAlloc(&bytes, &maxBytes, SRP_PROOF_BYTES);
if (!u || !S || !M1) {
HAPLog(&logObject, "Pair Setup M4: Not enough memory to allocate u / S / M1.");
return kHAPError_OutOfResources;
}
HAP_srp_scrambling_parameter(u, server->pairSetup.A, server->pairSetup.B);
HAPLogSensitiveBufferDebug(&logObject, u, SRP_SCRAMBLING_PARAMETER_BYTES, "Pair Setup M4: u.");
bool restorePrevious = false;
if (server->pairSetup.flagsPresent) {
restorePrevious = !(server->pairSetup.flags & kHAPPairingFlag_Transient) &&
server->pairSetup.flags & kHAPPairingFlag_Split;
}
HAPSetupInfo* _Nullable setupInfo = HAPAccessorySetupInfoGetSetupInfo(server_, restorePrevious);
HAPAssert(setupInfo);
int e = HAP_srp_premaster_secret(S, server->pairSetup.A, server->pairSetup.b, u, setupInfo->verifier);
if (e) {
HAPAssert(e == 1);
// Illegal key A.
HAPLog(&logObject, "Pair Setup M4: Illegal key A.");
session->state.pairSetup.error = kHAPPairingError_Authentication;
return kHAPError_None;
}
HAPLogSensitiveBufferDebug(&logObject, S, SRP_PREMASTER_SECRET_BYTES, "Pair Setup M4: S.");
HAP_srp_session_key(server->pairSetup.K, S);
HAPLogSensitiveBufferDebug(&logObject, server->pairSetup.K, sizeof server->pairSetup.K, "Pair Setup M4: K.");
static const uint8_t userName[] = "Pair-Setup";
HAP_srp_proof_m1(
M1,
userName,
sizeof userName - 1,
setupInfo->salt,
server->pairSetup.A,
server->pairSetup.B,
server->pairSetup.K);
HAPLogSensitiveBufferDebug(&logObject, M1, SRP_PROOF_BYTES, "Pair Setup M4: M1");
// Verify the controller's SRP proof.
if (!HAPRawBufferAreEqual(M1, server->pairSetup.M1, SRP_PROOF_BYTES)) {
bool found;
size_t numBytes;
uint8_t numAuthAttemptsBytes[sizeof(uint8_t)];
err = HAPPlatformKeyValueStoreGet(
server->platform.keyValueStore,
kHAPKeyValueStoreDomain_Configuration,
kHAPKeyValueStoreKey_Configuration_NumUnsuccessfulAuthAttempts,
numAuthAttemptsBytes,
sizeof numAuthAttemptsBytes,
&numBytes,
&found);
if (err) {
HAPAssert(err == kHAPError_Unknown);
return err;
}
if (!found) {
HAPRawBufferZero(numAuthAttemptsBytes, sizeof numAuthAttemptsBytes);
} else if (numBytes != sizeof numAuthAttemptsBytes) {
HAPLog(&logObject, "Invalid authentication attempts counter.");
return kHAPError_Unknown;
}
uint8_t numAuthAttempts = numAuthAttemptsBytes[0];
HAPAssert(numAuthAttempts < UINT8_MAX);
numAuthAttempts++;
numAuthAttemptsBytes[0] = numAuthAttempts;
err = HAPPlatformKeyValueStoreSet(
server->platform.keyValueStore,
kHAPKeyValueStoreDomain_Configuration,
kHAPKeyValueStoreKey_Configuration_NumUnsuccessfulAuthAttempts,
numAuthAttemptsBytes,
sizeof numAuthAttemptsBytes);
if (err) {
HAPAssert(err == kHAPError_Unknown);
return err;
}
HAPLog(&logObject,
"Pair Setup M4: Incorrect setup code. Unsuccessful authentication attempts = %u / 100.",
numAuthAttempts);
session->state.pairSetup.error = kHAPPairingError_Authentication;
return kHAPError_None;
}
// Reset authentication attempts counter.
err = HAPPlatformKeyValueStoreRemove(
server->platform.keyValueStore,
kHAPKeyValueStoreDomain_Configuration,
kHAPKeyValueStoreKey_Configuration_NumUnsuccessfulAuthAttempts);
if (err) {
HAPAssert(err == kHAPError_Unknown);
return err;
}
// Generate accessory-side SRP proof.
HAP_srp_proof_m2(server->pairSetup.M2, server->pairSetup.A, M1, server->pairSetup.K);
HAPLogBufferDebug(&logObject, server->pairSetup.M2, sizeof server->pairSetup.M2, "Pair Setup M4: M2.");
// Derive the symmetric session encryption key.
static const uint8_t salt[] = "Pair-Setup-Encrypt-Salt";
static const uint8_t info[] = "Pair-Setup-Encrypt-Info";
HAP_hkdf_sha512(
server->pairSetup.SessionKey,
sizeof server->pairSetup.SessionKey,
server->pairSetup.K,
sizeof server->pairSetup.K,
salt,
sizeof salt - 1,
info,
sizeof info - 1);
HAPLogSensitiveBufferDebug(
&logObject,
server->pairSetup.SessionKey,
sizeof server->pairSetup.SessionKey,
"Pair Setup M4: SessionKey");
}
// kTLVType_State.
err = HAPTLVWriterAppend(
responseWriter,
&(const HAPTLV) { .type = kHAPPairingTLVType_State,
.value = { .bytes = &session->state.pairSetup.state, .numBytes = 1 } });
if (err) {
HAPAssert(err == kHAPError_OutOfResources);
return err;
}
// kTLVType_Proof.
err = HAPTLVWriterAppend(
responseWriter,
&(const HAPTLV) { .type = kHAPPairingTLVType_Proof,
.value = { .bytes = server->pairSetup.M2, .numBytes = sizeof server->pairSetup.M2 } });
if (err) {
HAPAssert(err == kHAPError_OutOfResources);
return err;
}
// kTLVType_EncryptedData.
if (session->state.pairSetup.method == kHAPPairingMethod_PairSetupWithAuth) {
// Construct sub-TLV writer.
HAPTLVWriterRef subWriter;
{
void* bytes;
size_t maxBytes;
HAPTLVWriterGetScratchBytes(responseWriter, &bytes, &maxBytes);
if (maxBytes < CHACHA20_POLY1305_TAG_BYTES) {
HAPLog(&logObject, "Pair Setup M4: Not enough memory for kTLVType_EncryptedData auth tag.");
return kHAPError_OutOfResources;
}
maxBytes -= CHACHA20_POLY1305_TAG_BYTES;
HAPTLVWriterCreate(&subWriter, bytes, maxBytes);
}
if (session->state.pairSetup.method == kHAPPairingMethod_PairSetupWithAuth) {
HAPMFiAuth mfiAuth;
{
if (!server->platform.authentication.mfiHWAuth || !HAPAccessoryServerSupportsMFiHWAuth(server_)) {
HAPLog(&logObject, "Pair Setup M4: Apple Authentication Coprocessor is not available.");
return kHAPError_InvalidState;
}
HAPLogInfo(&logObject, "Using Apple Authentication Coprocessor.");
mfiAuth.copyCertificate = HAPMFiHWAuthCopyCertificate;
mfiAuth.createSignature = HAPMFiHWAuthCreateSignature;
}
// kTLVType_Signature.
{
void* bytes;
size_t maxBytes;
HAPTLVWriterGetScratchBytes(&subWriter, &bytes, &maxBytes);
const size_t numChallengeBytes = 32;
void* challengeBytes = HAPTLVScratchBufferAlloc(&bytes, &maxBytes, numChallengeBytes);
const size_t maxMFiProofBytes = maxBytes;
void* mfiProofBytes = HAPTLVScratchBufferAllocUnaligned(&bytes, &maxBytes, maxMFiProofBytes);
if (!challengeBytes || !mfiProofBytes) {
HAPLog(&logObject, "Pair Setup M4: Not enough memory to allocate MFiChallenge / MFi Proof.");
return kHAPError_OutOfResources;
}
// Generate MFi challenge.
static const uint8_t salt[] = "MFi-Pair-Setup-Salt";
static const uint8_t info[] = "MFi-Pair-Setup-Info";
HAP_hkdf_sha512(
challengeBytes,
numChallengeBytes,
server->pairSetup.K,
sizeof server->pairSetup.K,
salt,
sizeof salt - 1,
info,
sizeof info - 1);
HAPLogSensitiveBufferDebug(
&logObject, challengeBytes, numChallengeBytes, "Pair Setup M4: MFiChallenge.");
// Generate the MFi proof.
size_t numMFiProofBytes;
err = mfiAuth.createSignature(
server_, challengeBytes, numChallengeBytes, mfiProofBytes, maxMFiProofBytes, &numMFiProofBytes);
if (err) {
HAPAssert(err == kHAPError_Unknown);
return err;
}
HAPLogSensitiveBufferDebug(
&logObject, mfiProofBytes, numMFiProofBytes, "Pair Setup M4: kTLVType_Signature.");
// kTLVType_Signature.
err = HAPTLVWriterAppend(
&subWriter,
&(const HAPTLV) { .type = kHAPPairingTLVType_Signature,
.value = { .bytes = mfiProofBytes, .numBytes = numMFiProofBytes } });
if (err) {
HAPAssert(err == kHAPError_OutOfResources);
return err;
}
}
// kTLVType_Certificate.
{
void* bytes;
size_t maxBytes;
HAPTLVWriterGetScratchBytes(&subWriter, &bytes, &maxBytes);
const size_t maxCertificateBytes = maxBytes;
void* certificateBytes = HAPTLVScratchBufferAllocUnaligned(&bytes, &maxBytes, maxCertificateBytes);
if (!certificateBytes) {
HAPLog(&logObject, "Pair Setup M4: Not enough memory to allocate Accessory Certificate.");
return kHAPError_OutOfResources;
}
// Read the Accessory Certificate.
size_t numCertificateBytes;
err = mfiAuth.copyCertificate(server_, certificateBytes, maxCertificateBytes, &numCertificateBytes);
if (err) {
HAPAssert(err == kHAPError_Unknown);
return err;
}
HAPLogSensitiveBufferDebug(
&logObject, certificateBytes, numCertificateBytes, "Pair Setup M4: kTLVType_Certificate.");
// kTLVType_Certificate.
err = HAPTLVWriterAppend(
&subWriter,
&(const HAPTLV) { .type = kHAPPairingTLVType_Certificate,
.value = { .bytes = certificateBytes, .numBytes = numCertificateBytes } });
if (err) {
HAPAssert(err == kHAPError_OutOfResources);
return err;
}
}
}
// Encrypt the sub-TLV.
void* bytes;
size_t numBytes;
HAPTLVWriterGetBuffer(&subWriter, &bytes, &numBytes);
static const uint8_t nonce[] = "PS-Msg04";
HAP_chacha20_poly1305_encrypt(
&((uint8_t*) bytes)[numBytes],
bytes,
bytes,
numBytes,
nonce,
sizeof nonce - 1,
server->pairSetup.SessionKey);
numBytes += CHACHA20_POLY1305_TAG_BYTES;
HAPLogBufferDebug(&logObject, bytes, numBytes, "Pair Setup M4: kTLVType_EncryptedData.");
// kTLVType_EncryptedData.
err = HAPTLVWriterAppend(
responseWriter,
&(const HAPTLV) { .type = kHAPPairingTLVType_EncryptedData,
.value = { .bytes = bytes, .numBytes = numBytes } });
if (err) {
HAPAssert(err == kHAPError_OutOfResources);
return err;
}
}
if (session->state.pairSetup.method == kHAPPairingMethod_PairSetup && server->pairSetup.flagsPresent &&
server->pairSetup.flags & kHAPPairingFlag_Transient) {
// Initialize HAP session.
HAPRawBufferZero(&session->hap, sizeof session->hap);
// Derive encryption keys.
static const uint8_t salt[] = "SplitSetupSalt";
{
static const uint8_t info[] = "AccessoryEncrypt-Control";
HAP_hkdf_sha512(
session->hap.accessoryToController.controlChannel.key.bytes,
sizeof session->hap.accessoryToController.controlChannel.key.bytes,
server->pairSetup.K,
sizeof server->pairSetup.K,
salt,
sizeof salt - 1,
info,
sizeof info - 1);
HAPLogSensitiveBufferDebug(
&logObject,
session->hap.accessoryToController.controlChannel.key.bytes,
sizeof session->hap.accessoryToController.controlChannel.key.bytes,
"Transient Pair Setup Start Session: AccessoryEncryptKey");
}
{
static const uint8_t info[] = "ControllerEncrypt-Control";
HAP_hkdf_sha512(
session->hap.controllerToAccessory.controlChannel.key.bytes,
sizeof session->hap.controllerToAccessory.controlChannel.key.bytes,
server->pairSetup.K,
sizeof server->pairSetup.K,
salt,
sizeof salt - 1,
info,
sizeof info - 1);
HAPLogSensitiveBufferDebug(
&logObject,
session->hap.controllerToAccessory.controlChannel.key.bytes,
sizeof session->hap.controllerToAccessory.controlChannel.key.bytes,
"Transient Pair Setup Start Session: ControllerEncryptKey");
}
session->hap.accessoryToController.controlChannel.nonce = 0;
session->hap.controllerToAccessory.controlChannel.nonce = 0;
// Activate session.
session->hap.isTransient = true;
session->hap.active = true;
// Persist setup info for next Pair Setup procedure if requested.
if (server->pairSetup.flags & kHAPPairingFlag_Split) {
server->pairSetup.keepSetupInfo = true;
} else {
HAPLog(&logObject, "Transient Pair Setup procedure requested without kHAPPairingFlag_Split.");
}
// Reset Pair Setup procedure.
HAPPairingPairSetupResetForSession(server_, session_);
HAPLogInfo(&logObject, "Transient Pair Setup procedure completed.");
// Inform application.
if (server->callbacks.handleSessionAccept) {
server->callbacks.handleSessionAccept(server_, session_, server->context);
}
if (server->transports.ble) {
HAPNonnull(server->transports.ble)->peripheralManager.handleSessionAccept(server_, session_);
}
}
return kHAPError_None;
}