def test_mitm_capable_devices_create_mitm_secured_link(central, peripheral):
    """If a MITM-resistant level is requested and devices have the capabilities, then it's achieved"""
    central_ss, peripheral_ss = init_security_sessions(
        central,
        peripheral,
        initiator_mitm=True,
        initiator_io_caps="IO_CAPS_KEYBOARD_ONLY",
        responder_mitm=True,
        responder_io_caps="IO_CAPS_DISPLAY_ONLY")
    central_ss.connect(peripheral_ss)

    # Always start peripheral_ss first
    peripheral_ss.wait_for_event()
    central_ss.start_pairing()

    # Accept request & perform MITM protection
    peripheral_ss.expect_pairing_request()
    peripheral_ss.accept_pairing_request()

    passkey = peripheral_ss.expect_passkey_display()
    peripheral_ss.wait_for_event()
    central_ss.enter_passkey(passkey)

    peripheral_ss.expect_pairing_success()

    # Now it should succeed
    central_ss.request_encryption(
        SecuritySession.ENCRYPTION_ENCRYPTED_WITH_MITM)
    central_ss.expect_encryption_changed(
        SecuritySession.ENCRYPTION_ENCRYPTED_WITH_MITM)
コード例 #2
0
def test_generate_whitelist_from_bonded_devices(central, peripheral):
    """Generate whitelist based on bonded devices"""
    central_ss, peripheral_ss = init_security_sessions(central, peripheral)

    # generation should create an empty whitelist
    result = central.securityManager.generateWhitelistFromBondTable.withRetcode(
        0)().result
    assert len(result) == 0

    # remember responder address which will later end up in the whitelist
    responder_address = peripheral.gap.getAddress.withRetcode(0)().result

    # connect and pair to create a bond
    central_ss.connect(peripheral_ss)
    central_ss.start_pairing()
    central_ss.expect_pairing_success()
    sleep(1)

    central.gap.disconnect(central_ss.connection_handle, "USER_TERMINATION")

    # generation should crate whitelist with one entry
    result = central.securityManager.generateWhitelistFromBondTable.withRetcode(
        0)().result
    assert len(result) == 2

    # check entry against the responder address
    assert responder_address in result
def test_pairing_fail_if_passkey_wrong(central, peripheral):
    """MITM with IO_CAPS_KEYBOARD_ONLY on both ends and two different passkeys are input should fail pairing"""
    central_ss, peripheral_ss = init_security_sessions(
        central,
        peripheral,
        initiator_mitm=True,
        initiator_io_caps="IO_CAPS_KEYBOARD_ONLY",
        responder_mitm=True,
        responder_io_caps="IO_CAPS_KEYBOARD_ONLY")
    central_ss.connect(peripheral_ss)

    # Always start peripheral_ss first
    peripheral_ss.wait_for_event()
    central_ss.start_pairing()

    # We should get a request
    peripheral_ss.expect_pairing_request()

    # Accept request
    peripheral_ss.accept_pairing_request()

    # Wait for passkey requests
    peripheral_ss.expect_passkey_request()
    central_ss.expect_passkey_request()

    # Input passkeys
    peripheral_ss.enter_passkey(123456, asynchronous=True)
    central_ss.enter_passkey(654321)

    # Both should fail here
    peripheral_ss.expect_pairing_failure()
    central_ss.expect_pairing_failure()
def perform_bonding_with_privacy_and_disconnect(peripheral, central):
    central_ss, peripheral_ss = init_security_sessions(central, peripheral)

    peripheral.gap.enablePrivacy(True)
    central.gap.enablePrivacy(True)

    central.gap.setCentralPrivacyConfiguration(False, "DO_NOT_RESOLVE")
    central.gap.setPeripheralPrivacyConfiguration(False, "DO_NOT_RESOLVE")
    peripheral.gap.setPeripheralPrivacyConfiguration(False, "DO_NOT_RESOLVE")
    peripheral.gap.setCentralPrivacyConfiguration(False, "DO_NOT_RESOLVE")

    central_ss.connect(peripheral_ss)

    # Always start peripheral_ss first
    peripheral_ss.wait_for_event()
    central_ss.start_pairing()

    # We should get a request
    peripheral_ss.expect_pairing_request()

    # Accept request, pairing should complete successfully
    peripheral_ss.accept_pairing_request()
    peripheral_ss.expect_pairing_success()

    # central_ss should see pairing complete successfully too
    central_ss.expect_pairing_success()

    central.gap.disconnect(peripheral_ss.connection_handle, "USER_TERMINATION")

    # FIXME: needs event from the peripheral and central that they have been disconnected
    sleep(0.5)
def test_pairing_combinations(central, peripheral, responder_io_caps,
                              initiator_io_caps, secure_connections, keygen):
    """Pairing procedure should follow table 2.8 from BLE Core spec Vol 3, Part H, 2.3.5.1"""
    central_ss, peripheral_ss = init_security_sessions(
        central,
        peripheral,
        initiator_mitm=True,
        initiator_io_caps=initiator_io_caps,
        responder_mitm=True,
        responder_io_caps=responder_io_caps)

    # Get Secure connections support
    sc_initiator = central.securityManager.getSecureConnectionsSupport().result
    sc_responder = peripheral.securityManager.getSecureConnectionsSupport(
    ).result

    if secure_connections:
        # Skip if unsupported
        if not sc_initiator or not sc_responder:
            return
    else:
        # Skip if unsupported
        if sc_initiator and sc_responder:
            return

        # Allow legacy pairing
        central.securityManager.allowLegacyPairing(True).success()
        peripheral.securityManager.allowLegacyPairing(True).success()

    central_ss.connect(peripheral_ss)

    # Perform pairing
    keygen.test(central_ss, peripheral_ss)
コード例 #6
0
def test_write_signed_data_unencrypted(central, peripheral):
    """Write data that requires signing over an unencrypted link"""
    central_ss, peripheral_ss = init_security_sessions(central, peripheral)

    server = peripheral.gattServer
    client = central.gattClient

    # build server and create a characteristic that requires signing
    signed_handle = create_signed_characteristic(server)

    # connect without pairing
    central_handle, peripheral_handle = central_ss.connect(peripheral_ss)

    # test that writes fail without encryption or signing
    change_value(server, client, central_handle, signed_handle, value="00000002", success=False, sign=False)

    # encrypt which will trigger pairing and bonding
    central_ss.request_encryption(SecuritySession.ENCRYPTION_ENCRYPTED)
    central_ss.expect_encryption_changed(SecuritySession.ENCRYPTION_ENCRYPTED)

    # Wait as we got no visibility on whether the keys have been exchanged or not
    sleep(1)

    # re-connect and test characteristics over unencrypted connection
    central.gap.disconnect(central_ss.connection_handle, "USER_TERMINATION")
    central_ss.connect(peripheral_ss)

    # test writing the characteristic with signing
    change_value(server, client, central_handle, signed_handle, value="00000002", success=True, sign=True)

    # writes without signing should fail
    change_value(server, client, central_handle, signed_handle, value="00000003", success=False, sign=False)
def test_unsecured_link(central, peripheral):
    """Asking for an unsecured link should succeed immediately"""
    central_ss, peripheral_ss = init_security_sessions(central, peripheral)
    central_ss.connect(peripheral_ss)

    # Always start peripheral_ss first
    central_ss.request_encryption(SecuritySession.ENCRYPTION_NOT_ENCRYPTED)

    # It should succeed immediately
    central_ss.expect_encryption_changed(
        SecuritySession.ENCRYPTION_NOT_ENCRYPTED)
def test_secure_connections_pairing_fails_if_comparison_fails(
        central, peripheral):
    """In Secure Connections pairing, when doing numeric comparison and rejecting comparison, pairing should fail"""
    central_ss, peripheral_ss = init_security_sessions(
        central,
        peripheral,
        initiator_mitm=True,
        initiator_io_caps="IO_CAPS_DISPLAY_YESNO",
        responder_mitm=True,
        responder_io_caps="IO_CAPS_DISPLAY_YESNO")
    # Get Secure connections support
    sc_initiator = central.securityManager.getSecureConnectionsSupport().result
    sc_responder = peripheral.securityManager.getSecureConnectionsSupport(
    ).result

    # Skip if unsupported
    if not sc_initiator or not sc_responder:
        return

    central_ss.connect(peripheral_ss)

    # Always start peripheral_ss first
    peripheral_ss.wait_for_event()
    central_ss.start_pairing()

    # We should get a request
    peripheral_ss.expect_pairing_request()

    # Accept request
    peripheral_ss.accept_pairing_request()

    # Get passkeys (should be identical)
    passkey1 = peripheral_ss.expect_passkey_display()
    passkey2 = central_ss.expect_passkey_display()

    assert passkey1 == passkey2

    # Expect confirmation requests
    # FIXME: too slow to set that expectation :(
    # peripheral_ss.expect_confirmation_request()
    # central_ss.expect_confirmation_request()

    # Reject on one end
    peripheral_ss.enter_confirmation(True, asynchronous=True)
    central_ss.enter_confirmation(False)

    # Both should fail here
    peripheral_ss.expect_pairing_failure()
    central_ss.expect_pairing_failure()
def test_encryption_triggers_pairing(central, peripheral):
    """Asking for a link should trigger pairing and then succeed"""
    central_ss, peripheral_ss = init_security_sessions(central, peripheral)
    central_ss.connect(peripheral_ss)

    # Always start peripheral_ss first
    peripheral_ss.wait_for_event()
    central_ss.request_encryption(SecuritySession.ENCRYPTION_ENCRYPTED)
    sleep(1)

    # Accept request, pairing should complete successfully
    peripheral_ss.accept_pairing_request()
    peripheral_ss.expect_pairing_success()

    # It should succeed
    central_ss.expect_encryption_changed(SecuritySession.ENCRYPTION_ENCRYPTED)
コード例 #10
0
def test_responder_can_reject_pairing_request(central, peripheral):
    """A slave should be able to reject a pairing request"""
    central_ss, peripheral_ss = init_security_sessions(central, peripheral)
    central_ss.connect(peripheral_ss)

    # Always start peripheral_ss first
    peripheral_ss.wait_for_event()
    central_ss.start_pairing()

    # We should get a request
    peripheral_ss.expect_pairing_request()

    # Reject pairing request
    peripheral_ss.reject_pairing_request()

    # central_ss should see pairing fail
    central_ss.expect_pairing_failure()
def test_non_mitm_capable_device_create_non_mitm_secured_link(
        central, peripheral):
    """If a MITM-resistant level is requested and devices don't have the capabilities, then an 'encrypted' level
    should be achieved instead """
    central_ss, peripheral_ss = init_security_sessions(central, peripheral)
    central_ss.connect(peripheral_ss)

    # Always start peripheral_ss first
    peripheral_ss.wait_for_event()
    central_ss.request_encryption(
        SecuritySession.ENCRYPTION_ENCRYPTED_WITH_MITM)

    # Accept request, pairing should complete successfully
    peripheral_ss.accept_pairing_request()

    # It should succeed
    central_ss.expect_encryption_changed(SecuritySession.ENCRYPTION_ENCRYPTED)
コード例 #12
0
def test_pairing_fail_if_rejected(central, peripheral):
    """Pairing should fail with default parameters on both ends if request is rejected"""
    central_ss, peripheral_ss = init_security_sessions(central, peripheral)
    central_ss.connect(peripheral_ss)

    # Always start peripheral_ss first
    peripheral_ss.wait_for_event()
    central_ss.start_pairing()

    # We should get a request
    peripheral_ss.expect_pairing_request()

    # Reject request (method is still successful)
    peripheral_ss.reject_pairing_request()

    # central_ss should see pairing fail
    central_ss.expect_pairing_failure()
コード例 #13
0
def test_pairing_with_defaults(central, peripheral):
    """Pairing should succeed with default parameters on both ends"""
    central_ss, peripheral_ss = init_security_sessions(central, peripheral)
    central_ss.connect(peripheral_ss)

    # Always start peripheral_ss first
    peripheral_ss.wait_for_event()
    central_ss.start_pairing()

    # We should get a request
    peripheral_ss.expect_pairing_request()

    # Accept request, pairing should complete successfully
    peripheral_ss.accept_pairing_request()
    peripheral_ss.expect_pairing_success()

    # central_ss should see pairing complete successfully too
    central_ss.expect_pairing_success()
コード例 #14
0
def test_write_signed_data_encrypted(central, peripheral):
    """Write data that requires signing over an encrypted link"""
    central_ss, peripheral_ss = init_security_sessions(central, peripheral)

    server = peripheral.gattServer
    client = central.gattClient

    # build server and create a characteristic that requires signing
    signed_handle = create_signed_characteristic(server)

    # connect without pairing
    central_handle, peripheral_handle = central_ss.connect(peripheral_ss)

    # encrypt which will trigger pairing and bonding
    central_ss.request_encryption(SecuritySession.ENCRYPTION_ENCRYPTED)
    central_ss.expect_encryption_changed(SecuritySession.ENCRYPTION_ENCRYPTED)

    # test characteristics over encrypted connection
    change_value(server, client, central_handle, signed_handle, value="00000002", success=True, sign=True)
def test_secure_link_succeeds_immediately_if_paired(central, peripheral):
    """If pairing was successful then asking for a secure link should succeed immediately"""
    central_ss, peripheral_ss = init_security_sessions(central, peripheral)
    central_ss.connect(peripheral_ss)

    # Always start peripheral_ss first
    peripheral_ss.wait_for_event()
    central_ss.start_pairing()

    # We should get a request
    peripheral_ss.expect_pairing_request()

    # Accept request, pairing should complete successfully
    peripheral_ss.accept_pairing_request()
    peripheral_ss.expect_pairing_success()

    # central_ss should see pairing complete successfully too
    central_ss.expect_pairing_success()

    # It should succeed
    central_ss.request_encryption(SecuritySession.ENCRYPTION_ENCRYPTED)
    central_ss.expect_encryption_changed(SecuritySession.ENCRYPTION_ENCRYPTED)
def test_downgrade_of_security_fails(central, peripheral):
    """Asking for an encryption level downgrade should fail"""
    central_ss, peripheral_ss = init_security_sessions(central, peripheral)
    central_ss.connect(peripheral_ss)

    # Always start peripheral_ss first
    peripheral_ss.wait_for_event()
    central_ss.start_pairing()

    # We should get a request
    peripheral_ss.expect_pairing_request()

    # Accept request, pairing should complete successfully
    peripheral_ss.accept_pairing_request()
    peripheral_ss.expect_pairing_success()

    # central_ss should see pairing complete successfully too
    central_ss.expect_pairing_success()

    # It should fail
    central_ss.request_encryption_expect_failure(
        SecuritySession.ENCRYPTION_NOT_ENCRYPTED, -1)
def test_perform_pairing_procedure_policy(central, peripheral):
    """validate that when a peripheral with privacy enabled with the policy
    PERFORM_PAIRING_PROCEDURE is connected by an unknown privacy enabled
    central then the pairing procedure is performed"""
    central_ss, peripheral_ss = init_security_sessions(central, peripheral)

    peripheral.gap.enablePrivacy(True)
    central.gap.enablePrivacy(True)

    peripheral.gap.setPeripheralPrivacyConfiguration(
        False, "PERFORM_PAIRING_PROCEDURE")
    central.gap.setCentralPrivacyConfiguration(False, "DO_NOT_RESOLVE")

    central_ss.connect(peripheral_ss)

    # We should get a request automatically because of the policy
    peripheral_ss.wait_for_event()
    peripheral_ss.expect_pairing_request()

    # Accept request, pairing should complete successfully
    peripheral_ss.accept_pairing_request()
    peripheral_ss.expect_pairing_success()
def test_recovering_keys(central, peripheral):
    """Change databases and recover old keys"""
    central_ss, peripheral_ss = init_security_sessions(central, peripheral)

    central.ble.createFilesystem()
    peripheral.ble.createFilesystem()

    # we create a new database and store keys in this one
    peripheral.securityManager.setDatabaseFilepath("/fs/sm_db1")
    central.securityManager.setDatabaseFilepath("/fs/sm_db1")

    peripheral.securityManager.preserveBondingStateOnReset(True)
    central.securityManager.preserveBondingStateOnReset(True)

    central_ss.connect(peripheral_ss)

    peripheral_ss.wait_for_event()

    # pair to bond and save the ltk in the /fs/sm_db1 for later
    central_ss.start_pairing()
    # accept request
    peripheral_ss.expect_pairing_request()
    peripheral_ss.accept_pairing_request()
    peripheral_ss.expect_pairing_success()
    # initiator should see pairing succeed
    central_ss.expect_pairing_success()

    # we will reconnect
    central.gap.disconnect(central_ss.connection_handle, "USER_TERMINATION")
    sleep(1)
    # but change database so that it doesn't have the keys
    peripheral.securityManager.setDatabaseFilepath("/fs/sm_db2")
    central.securityManager.setDatabaseFilepath("/fs/sm_db2")
    # now reconnect
    central_ss.connect(peripheral_ss)

    peripheral_ss.wait_for_event()

    # encrypt which will trigger pairing and bonding and prove we have no keys
    central_ss.request_encryption(SecuritySession.ENCRYPTION_ENCRYPTED)
    # reject request
    peripheral_ss.expect_pairing_request()
    peripheral_ss.accept_pairing_request()
    # initiator should see pairing fail
    peripheral_ss.expect_pairing_success()

    # we will reconnect
    central.gap.disconnect(central_ss.connection_handle, "USER_TERMINATION")
    sleep(1)
    # but first change database to recover old keys
    peripheral.securityManager.setDatabaseFilepath("/fs/sm_db1")
    central.securityManager.setDatabaseFilepath("/fs/sm_db1")
    # now reconnect
    central_ss.connect(peripheral_ss)

    # wait for a pairing request that should never come
    peripheral.securityManager.setPairingRequestAuthorisation(True).success()
    pairing_result = peripheral.securityManager.waitForEvent.setAsync(
    ).withRetcode(-1)(peripheral_ss.connection_handle, 1000)

    # encrypt with existing keys we found in the db (this should NOT trigger pairing)
    central_ss.request_encryption(SecuritySession.ENCRYPTION_ENCRYPTED)
    central_ss.expect_encryption_changed(SecuritySession.ENCRYPTION_ENCRYPTED)

    # make sure no pairing happened
    assert not pairing_result.success()
    assert "Pairing timeout" == pairing_result.result