static HAPError HAPPairingPairSetupGetM4()

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;
}