Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 73d060e7 authored by Abel Lucas's avatar Abel Lucas Committed by Gerrit Code Review
Browse files

Merge changes from topic "bug_290987423" into main

* changes:
  lmp: handle passkey entry failure on responding side
  lmp: refactor send/recv commitments
parents 549850b3 abc35c26
Loading
Loading
Loading
Loading
+137 −65
Original line number Diff line number Diff line
@@ -143,11 +143,14 @@ async fn receive_public_key(ctx: &impl Context, transaction_id: u8) -> PublicKey
const COMMITMENT_VALUE_SIZE: usize = 16;
const NONCE_SIZE: usize = 16;

async fn receive_commitment(ctx: &impl Context, skip_first: bool) {
    let commitment_value = [0; COMMITMENT_VALUE_SIZE];
fn build_commitment(_ctx: &impl Context) -> [u8; COMMITMENT_VALUE_SIZE] {
    [0; COMMITMENT_VALUE_SIZE]
}

    if !skip_first {
        let confirm = ctx.receive_lmp_packet::<lmp::SimplePairingConfirm>().await;
async fn receive_commitment(ctx: &impl Context, confirm: Option<lmp::SimplePairingConfirm>) {
    let commitment_value = build_commitment(ctx);

    if let Some(confirm) = confirm {
        if confirm.get_commitment_value() != &commitment_value {
            todo!();
        }
@@ -177,16 +180,8 @@ async fn receive_commitment(ctx: &impl Context, skip_first: bool) {
        .await;
}

async fn send_commitment(ctx: &impl Context, skip_first: bool) {
    let commitment_value = [0; COMMITMENT_VALUE_SIZE];

    if !skip_first {
        ctx.send_lmp_packet(
            lmp::SimplePairingConfirmBuilder { transaction_id: 0, commitment_value }.build(),
        );
    }

    let confirm = ctx.receive_lmp_packet::<lmp::SimplePairingConfirm>().await;
async fn send_commitment(ctx: &impl Context, confirm: lmp::SimplePairingConfirm) {
    let commitment_value = build_commitment(ctx);

    if confirm.get_commitment_value() != &commitment_value {
        todo!();
@@ -437,7 +432,8 @@ pub async fn initiate(ctx: &impl Context) -> Result<(), ()> {
        match auth_method {
            AuthenticationMethod::NumericComparisonJustWork
            | AuthenticationMethod::NumericComparisonUserConfirm => {
                send_commitment(ctx, true).await;
                let confirm = ctx.receive_lmp_packet::<lmp::SimplePairingConfirm>().await;
                send_commitment(ctx, confirm).await;

                if user_confirmation_request(ctx).await.is_err() {
                    ctx.send_lmp_packet(
@@ -448,13 +444,21 @@ pub async fn initiate(ctx: &impl Context) -> Result<(), ()> {
                Ok(())
            }
            AuthenticationMethod::PasskeyEntry => {
                if initiator.io_capability == hci::IoCapability::KeyboardOnly {
                let confirm = if initiator.io_capability == hci::IoCapability::KeyboardOnly {
                    if user_passkey_request(ctx).await.is_err() {
                        ctx.send_lmp_packet(
                            lmp::PasskeyFailedBuilder { transaction_id: 0 }.build(),
                        );
                        Err(())?;
                    }
                    ctx.send_lmp_packet(
                        lmp::SimplePairingConfirmBuilder {
                            transaction_id: 0,
                            commitment_value: build_commitment(ctx),
                        }
                        .build(),
                    );
                    ctx.receive_lmp_packet::<lmp::SimplePairingConfirm>().await
                } else {
                    ctx.send_hci_event(
                        hci::UserPasskeyNotificationBuilder {
@@ -463,9 +467,32 @@ pub async fn initiate(ctx: &impl Context) -> Result<(), ()> {
                        }
                        .build(),
                    );
                    ctx.send_lmp_packet(
                        lmp::SimplePairingConfirmBuilder {
                            transaction_id: 0,
                            commitment_value: build_commitment(ctx),
                        }
                for _ in 0..PASSKEY_ENTRY_REPEAT_NUMBER {
                    send_commitment(ctx, false).await;
                        .build(),
                    );
                    match ctx
                        .receive_lmp_packet::<Either<lmp::SimplePairingConfirm, lmp::NotAccepted>>()
                        .await
                    {
                        Either::Left(confirm) => confirm,
                        Either::Right(_) => Err(())?,
                    }
                };
                send_commitment(ctx, confirm).await;
                for _ in 1..PASSKEY_ENTRY_REPEAT_NUMBER {
                    ctx.send_lmp_packet(
                        lmp::SimplePairingConfirmBuilder {
                            transaction_id: 0,
                            commitment_value: build_commitment(ctx),
                        }
                        .build(),
                    );
                    let confirm = ctx.receive_lmp_packet::<lmp::SimplePairingConfirm>().await;
                    send_commitment(ctx, confirm).await;
                }
                Ok(())
            }
@@ -474,7 +501,15 @@ pub async fn initiate(ctx: &impl Context) -> Result<(), ()> {
                    remote_oob_data_request(ctx).await?;
                }

                send_commitment(ctx, false).await;
                ctx.send_lmp_packet(
                    lmp::SimplePairingConfirmBuilder {
                        transaction_id: 0,
                        commitment_value: build_commitment(ctx),
                    }
                    .build(),
                );
                let confirm = ctx.receive_lmp_packet::<lmp::SimplePairingConfirm>().await;
                send_commitment(ctx, confirm).await;
                Ok(())
            }
        }
@@ -653,48 +688,52 @@ pub async fn respond(ctx: &impl Context, request: lmp::IoCapabilityReq) -> Resul

    // Authentication Stage 1
    let auth_method = authentication_method(initiator, responder);
    let negative_user_confirmation = match auth_method {
    let result: Result<bool, ()> = async {
        match auth_method {
            AuthenticationMethod::NumericComparisonJustWork
            | AuthenticationMethod::NumericComparisonUserConfirm => {
            receive_commitment(ctx, true).await;
                receive_commitment(ctx, None).await;

                let user_confirmation = user_confirmation_request(ctx).await;
            user_confirmation.is_err()
                Ok(user_confirmation.is_err())
            }
            AuthenticationMethod::PasskeyEntry => {
            let skip_first_commitment = if responder.io_capability
                == hci::IoCapability::KeyboardOnly
            {
                // TODO: handle error
                let _user_passkey = user_passkey_request(ctx).await;
                false
                let confirm = if responder.io_capability == hci::IoCapability::KeyboardOnly {
                    let user_passkey = user_passkey_request(ctx).await;
                    let confirm = ctx.receive_lmp_packet::<lmp::SimplePairingConfirm>().await;
                    if user_passkey.is_err() {
                        ctx.send_lmp_packet(
                            lmp::NotAcceptedBuilder {
                                transaction_id: 0,
                                not_accepted_opcode: lmp::Opcode::SimplePairingConfirm,
                                error_code: hci::ErrorCode::AuthenticationFailure.into(),
                            }.build(),
                        );
                        return Err(());
                    }
                    confirm
                } else {
                    ctx.send_hci_event(
                    hci::UserPasskeyNotificationBuilder { bd_addr: ctx.peer_address(), passkey: 0 }
                        hci::UserPasskeyNotificationBuilder {
                            bd_addr: ctx.peer_address(),
                            passkey: 0,
                        }
                        .build(),
                    );
                    match ctx
                        .receive_lmp_packet::<Either<lmp::SimplePairingConfirm, lmp::PasskeyFailed>>()
                        .await
                    {
                    Either::Left(_) => true, // TODO: check for `confirm.get_commitment_value()`
                    Either::Right(_) => {
                        ctx.send_hci_event(
                            hci::SimplePairingCompleteBuilder {
                                status: hci::ErrorCode::AuthenticationFailure,
                                bd_addr: ctx.peer_address(),
                            }
                            .build(),
                        );
                        return Err(());
                    }
                        Either::Left(confirm) => confirm,
                        Either::Right(_) => Err(())?,
                    }
                };
            receive_commitment(ctx, skip_first_commitment).await;
                receive_commitment(ctx, Some(confirm)).await;
                for _ in 1..PASSKEY_ENTRY_REPEAT_NUMBER {
                receive_commitment(ctx, false).await;
                    let confirm = ctx.receive_lmp_packet::<lmp::SimplePairingConfirm>().await;
                    receive_commitment(ctx, Some(confirm)).await;
                }
            false
                Ok(false)
            }
            AuthenticationMethod::OutOfBand => {
                if responder.oob_data_present != hci::OobDataPresent::NotPresent {
@@ -702,8 +741,25 @@ pub async fn respond(ctx: &impl Context, request: lmp::IoCapabilityReq) -> Resul
                    let _remote_oob_data = remote_oob_data_request(ctx).await;
                }

            receive_commitment(ctx, false).await;
            false
                let confirm = ctx.receive_lmp_packet::<lmp::SimplePairingConfirm>().await;
                receive_commitment(ctx, Some(confirm)).await;
                Ok(false)
            }
        }
    }
    .await;

    let negative_user_confirmation = match result {
        Ok(negative_user_confirmation) => negative_user_confirmation,
        Err(_) => {
            ctx.send_hci_event(
                hci::SimplePairingCompleteBuilder {
                    status: hci::ErrorCode::AuthenticationFailure,
                    bd_addr: ctx.peer_address(),
                }
                .build(),
            );
            return Err(());
        }
    };

@@ -896,7 +952,7 @@ mod tests {
    }

    #[test]
    fn passkey_entry_initiator_failure_on_initiating_side() {
    fn passkey_entry_initiator_negative_reply_on_initiating_side() {
        let context = TestContext::new();
        let procedure = initiate;

@@ -904,13 +960,29 @@ mod tests {
    }

    #[test]
    fn passkey_entry_responder_failure_on_initiating_side() {
    fn passkey_entry_responder_negative_reply_on_responding_side() {
        let context = TestContext::new();
        let procedure = respond;

        include!("../../../test/SP/BV-14bis-C.in");
    }

    #[test]
    fn passkey_entry_responder_negative_reply_on_initiating_side() {
        let context = TestContext::new();
        let procedure = respond;

        include!("../../../test/SP/BV-15-C.in");
    }

    #[test]
    fn passkey_entry_initiator_negative_reply_on_responding_side() {
        let context = TestContext::new();
        let procedure = initiate;

        include!("../../../test/SP/BV-15bis-C.in");
    }

    #[test]
    #[should_panic] // TODO: make the test pass
    fn passkey_entry_initiator_failure_on_responding_side() {
+106 −0
Original line number Diff line number Diff line
// Passkey entry responder, negative reply on responding side:
// - Test case not present in LMP.TS, but other permutations are described in SP/BV-14-C, SP/BV-15-C
// - IUT is KeyboardOnly, responder
// - Lower Tester is Display, initiator
// - IUT fails passkey entry with User_Passkey_Request_NegativeReply, responds Not Accepted to the SimplePairingConfirm
sequence! { procedure, context,
    // ACL Connection Established
    Lower Tester -> IUT: IoCapabilityReq {
        transaction_id: 0,
        io_capabilities: 0x00,
        oob_authentication_data: 0x00,
        authentication_requirement: 0x01,
    }
    IUT -> Upper Tester: IoCapabilityResponse {
        bd_addr: context.peer_address(),
        io_capability: IoCapability::DisplayOnly,
        oob_data_present: OobDataPresent::NotPresent,
        authentication_requirements: AuthenticationRequirements::NoBondingMitmProtection,
    }
    IUT -> Upper Tester: IoCapabilityRequest {
        bd_addr: context.peer_address(),
    }
    Upper Tester -> IUT: IoCapabilityRequestReply {
        bd_addr: context.peer_address(),
        io_capability: IoCapability::KeyboardOnly,
        oob_present: OobDataPresent::NotPresent,
        authentication_requirements: AuthenticationRequirements::NoBondingMitmProtection,
    }
    IUT -> Upper Tester: IoCapabilityRequestReplyComplete {
        num_hci_command_packets: 1,
        status: ErrorCode::Success,
        bd_addr: context.peer_address(),
    }
    IUT -> Lower Tester: IoCapabilityRes {
        transaction_id: 0,
        io_capabilities: 0x02,
        oob_authentication_data: 0x00,
        authentication_requirement: 0x01,
    }
    // Public Key Exchange
    Lower Tester -> IUT: EncapsulatedHeader {
        transaction_id: 0,
        major_type: 1,
        minor_type: 1,
        payload_length: 48,
    }
    IUT -> Lower Tester: Accepted {
        transaction_id: 0,
        accepted_opcode: Opcode::EncapsulatedHeader,
    }
    repeat 3 times with (part in peer_p192_public_key()) {
        Lower Tester -> IUT: EncapsulatedPayload {
            transaction_id: 0,
            data: part,
        }
        IUT -> Lower Tester: Accepted {
            transaction_id: 0,
            accepted_opcode: Opcode::EncapsulatedPayload,
        }
    }
    IUT -> Lower Tester: EncapsulatedHeader {
        transaction_id: 0,
        major_type: 1,
        minor_type: 1,
        payload_length: 48,
    }
    Lower Tester -> IUT: Accepted {
        transaction_id: 0,
        accepted_opcode: Opcode::EncapsulatedHeader,
    }
    repeat 3 times with (part in local_p192_public_key(&context)) {
        IUT -> Lower Tester: EncapsulatedPayload {
            transaction_id: 0,
            data: part,
        }
        Lower Tester -> IUT: Accepted {
            transaction_id: 0,
            accepted_opcode: Opcode::EncapsulatedPayload,
        }
    }
    // Authentication Stage 1: Passkey Entry Protocol
    IUT -> Upper Tester: UserPasskeyRequest {
        bd_addr: context.peer_address(),
    }
    Upper Tester -> IUT: UserPasskeyRequestNegativeReply {
        bd_addr: context.peer_address(),
    }
    IUT -> Upper Tester: UserPasskeyRequestNegativeReplyComplete {
        num_hci_command_packets: 1,
        status: ErrorCode::Success,
        bd_addr: context.peer_address(),
    }
    Lower Tester -> IUT: SimplePairingConfirm {
        transaction_id: 0,
        commitment_value: [0; 16],
    }
    IUT -> Lower Tester: NotAccepted {
        transaction_id: 0,
        not_accepted_opcode: Opcode::SimplePairingConfirm,
        error_code: ErrorCode::AuthenticationFailure.into(),
    }
    IUT -> Upper Tester: SimplePairingComplete {
        status: ErrorCode::AuthenticationFailure,
        bd_addr: context.peer_address(),
    }
}
+118 −0
Original line number Diff line number Diff line
// Passkey entry initiator, negative reply on responding side:
// - Test case not present in LMP.TS, but other permutations are described in SP/BV-14-C, SP/BV-15-C
// - IUT is DisplayOnly, initiator
// - Lower Tester is KeyboardOnly, responder
// - Lower Tester fails passkey entry with User_Passkey_Request_NegativeReply, responds Not Accepted to the SimplePairingConfirm
sequence! { procedure, context,
    // ACL Connection Established
    Upper Tester -> IUT: AuthenticationRequested {
        connection_handle: context.peer_handle()
    }
    IUT -> Upper Tester: AuthenticationRequestedStatus {
       num_hci_command_packets: 1,
       status: ErrorCode::Success,
    }
    IUT -> Upper Tester: LinkKeyRequest {
        bd_addr: context.peer_address(),
    }
    Upper Tester -> IUT: LinkKeyRequestNegativeReply {
        bd_addr: context.peer_address(),
    }
    IUT -> Upper Tester: LinkKeyRequestNegativeReplyComplete {
       num_hci_command_packets: 1,
       status: ErrorCode::Success,
       bd_addr: context.peer_address(),
    }
    IUT -> Upper Tester: IoCapabilityRequest {
        bd_addr: context.peer_address(),
    }
    Upper Tester -> IUT: IoCapabilityRequestReply {
        bd_addr: context.peer_address(),
        io_capability: IoCapability::DisplayOnly,
        oob_present: OobDataPresent::NotPresent,
        authentication_requirements: AuthenticationRequirements::NoBondingMitmProtection,
    }
    IUT -> Upper Tester: IoCapabilityRequestReplyComplete {
        num_hci_command_packets: 1,
        status: ErrorCode::Success,
        bd_addr: context.peer_address(),
    }
    IUT -> Lower Tester: IoCapabilityReq {
        transaction_id: 0,
        io_capabilities: 0x00,
        oob_authentication_data: 0x00,
        authentication_requirement: 0x01,
    }
    Lower Tester -> IUT: IoCapabilityRes {
        transaction_id: 0,
        io_capabilities: 0x02,
        oob_authentication_data: 0x00,
        authentication_requirement: 0x01,
    }
    IUT -> Upper Tester: IoCapabilityResponse {
        bd_addr: context.peer_address(),
        io_capability: IoCapability::KeyboardOnly,
        oob_data_present: OobDataPresent::NotPresent,
        authentication_requirements: AuthenticationRequirements::NoBondingMitmProtection,
    }
    // Public Key Exchange
    IUT -> Lower Tester: EncapsulatedHeader {
        transaction_id: 0,
        major_type: 1,
        minor_type: 1,
        payload_length: 48,
    }
    Lower Tester -> IUT: Accepted {
        transaction_id: 0,
        accepted_opcode: Opcode::EncapsulatedHeader,
    }
    repeat 3 times with (part in local_p192_public_key(&context)) {
        IUT -> Lower Tester: EncapsulatedPayload {
            transaction_id: 0,
            data: part,
        }
        Lower Tester -> IUT: Accepted {
            transaction_id: 0,
            accepted_opcode: Opcode::EncapsulatedPayload,
        }
    }
    Lower Tester -> IUT: EncapsulatedHeader {
        transaction_id: 0,
        major_type: 1,
        minor_type: 1,
        payload_length: 48,
    }
    IUT -> Lower Tester: Accepted {
        transaction_id: 0,
        accepted_opcode: Opcode::EncapsulatedHeader,
    }
    repeat 3 times with (part in peer_p192_public_key()) {
        Lower Tester -> IUT: EncapsulatedPayload {
            transaction_id: 0,
            data: part,
        }
        IUT -> Lower Tester: Accepted {
            transaction_id: 0,
            accepted_opcode: Opcode::EncapsulatedPayload,
        }
    }
    // Authentication Stage 1: Passkey Entry Protocol
    IUT -> Upper Tester: UserPasskeyNotification { bd_addr: context.peer_address(), passkey: 0 }
    IUT -> Lower Tester: SimplePairingConfirm {
        transaction_id: 0,
        commitment_value: [0; 16],
    }
    Lower Tester -> IUT: NotAccepted {
        transaction_id: 0,
        not_accepted_opcode: Opcode::SimplePairingConfirm,
        error_code: ErrorCode::AuthenticationFailure.into(),
    }
    IUT -> Upper Tester: SimplePairingComplete {
        status: ErrorCode::AuthenticationFailure,
        bd_addr: context.peer_address(),
    }
    IUT -> Upper Tester: AuthenticationComplete {
        status: ErrorCode::AuthenticationFailure,
        connection_handle: context.peer_handle(),
    }
}