/* * Copyright 2023 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ #define LOG_TAG "SEC_CB" #include "stack/btm/btm_sec_cb.h" #include #include #include "internal_include/bt_trace.h" #include "internal_include/stack_config.h" #include "os/log.h" #include "osi/include/allocator.h" #include "osi/include/fixed_queue.h" #include "osi/include/list.h" #include "stack/btm/btm_dev.h" #include "stack/btm/security_device_record.h" #include "stack/include/bt_psm_types.h" #include "types/raw_address.h" using namespace bluetooth; void tBTM_SEC_CB::Init(uint8_t initial_security_mode) { memset(&cfg, 0, sizeof(cfg)); memset(&devcb, 0, sizeof(devcb)); memset(&enc_rand, 0, sizeof(enc_rand)); memset(&api, 0, sizeof(api)); memset(&pin_code, 0, sizeof(pin_code)); memset(sec_serv_rec, 0, sizeof(sec_serv_rec)); connecting_bda = RawAddress::kEmpty; connecting_dc = kDevClassEmpty; sec_pending_q = fixed_queue_new(SIZE_MAX); sec_collision_timer = alarm_new("btm.sec_collision_timer"); pairing_timer = alarm_new("btm.pairing_timer"); execution_wait_timer = alarm_new("btm.execution_wait_timer"); security_mode = initial_security_mode; pairing_bda = RawAddress::kAny; sec_dev_rec = list_new([](void* ptr) { // Invoke destructor for all record objects and reset to default // initialized value so memory may be properly freed *((tBTM_SEC_DEV_REC*)ptr) = {}; osi_free(ptr); }); } void tBTM_SEC_CB::Free() { fixed_queue_free(sec_pending_q, nullptr); sec_pending_q = nullptr; list_free(sec_dev_rec); sec_dev_rec = nullptr; alarm_free(sec_collision_timer); sec_collision_timer = nullptr; alarm_free(pairing_timer); pairing_timer = nullptr; alarm_free(execution_wait_timer); execution_wait_timer = nullptr; } tBTM_SEC_CB btm_sec_cb; void BTM_Sec_Init() { btm_sec_cb.Init(stack_config_get_interface()->get_pts_secure_only_mode() ? BTM_SEC_MODE_SC : BTM_SEC_MODE_SP); } void BTM_Sec_Free() { btm_sec_cb.Free(); } /******************************************************************************* * * Function find_first_serv_rec * * Description Look for the first record in the service database * with specified PSM * * Returns Pointer to the record or NULL * ******************************************************************************/ tBTM_SEC_SERV_REC* tBTM_SEC_CB::find_first_serv_rec(bool is_originator, uint16_t psm) { tBTM_SEC_SERV_REC* p_serv_rec = &sec_serv_rec[0]; int i; if (is_originator && p_out_serv && p_out_serv->psm == psm) { /* If this is outgoing connection and the PSM matches p_out_serv, * use it as the current service */ return p_out_serv; } /* otherwise, just find the first record with the specified PSM */ for (i = 0; i < BTM_SEC_MAX_SERVICE_RECORDS; i++, p_serv_rec++) { if ((p_serv_rec->security_flags & BTM_SEC_IN_USE) && (p_serv_rec->psm == psm)) return (p_serv_rec); } return (NULL); } tBTM_SEC_REC* tBTM_SEC_CB::getSecRec(const RawAddress bd_addr) { tBTM_SEC_DEV_REC* p_dev_rec = btm_find_dev(bd_addr); if (p_dev_rec) { return &p_dev_rec->sec_rec; } return nullptr; } bool tBTM_SEC_CB::IsDeviceEncrypted(const RawAddress bd_addr, tBT_TRANSPORT transport) { tBTM_SEC_REC* sec_rec = getSecRec(bd_addr); if (sec_rec) { if (transport == BT_TRANSPORT_BR_EDR) { return sec_rec->is_device_encrypted(); } else if (transport == BT_TRANSPORT_LE) { return sec_rec->is_le_device_encrypted(); } log::error("unknown transport:{}", bt_transport_text(transport)); return false; } log::error("unknown device:{}", bd_addr); return false; } bool tBTM_SEC_CB::IsLinkKeyAuthenticated(const RawAddress bd_addr, tBT_TRANSPORT transport) { tBTM_SEC_REC* sec_rec = getSecRec(bd_addr); if (sec_rec) { if (transport == BT_TRANSPORT_BR_EDR) { return sec_rec->is_link_key_authenticated(); } else if (transport == BT_TRANSPORT_LE) { return sec_rec->is_le_link_key_authenticated(); } log::error("unknown transport:{}", bt_transport_text(transport)); return false; } log::error("unknown device:{}", bd_addr); return false; } bool tBTM_SEC_CB::IsDeviceAuthenticated(const RawAddress bd_addr, tBT_TRANSPORT transport) { tBTM_SEC_REC* sec_rec = getSecRec(bd_addr); if (sec_rec) { if (transport == BT_TRANSPORT_BR_EDR) { return sec_rec->is_device_authenticated(); } else if (transport == BT_TRANSPORT_LE) { return sec_rec->is_le_device_authenticated(); } log::error("unknown transport:{}", bt_transport_text(transport)); return false; } log::error("unknown device:{}", bd_addr); return false; } bool tBTM_SEC_CB::IsLinkKeyKnown(const RawAddress bd_addr, tBT_TRANSPORT transport) { tBTM_SEC_REC* sec_rec = getSecRec(bd_addr); if (sec_rec) { if (transport == BT_TRANSPORT_BR_EDR) { return sec_rec->is_link_key_known(); } else if (transport == BT_TRANSPORT_LE) { return sec_rec->is_le_link_key_known(); } log::error("unknown transport:{}", bt_transport_text(transport)); return false; } log::error("unknown device:{}", bd_addr); return false; } bool tBTM_SEC_CB::IsDeviceBonded(const RawAddress bd_addr) { tBTM_SEC_DEV_REC* p_dev_rec = btm_find_dev(bd_addr); bool is_bonded = false; if (p_dev_rec && ((p_dev_rec->sec_rec.ble_keys.key_type && p_dev_rec->sec_rec.is_le_link_key_known()) || p_dev_rec->sec_rec.is_link_key_known())) { is_bonded = true; } log::debug("Device record bonded check peer:{} is_bonded:{}", bd_addr, is_bonded); return is_bonded; } #define BTM_NO_AVAIL_SEC_SERVICES ((uint16_t)0xffff) bool tBTM_SEC_CB::AddService(bool is_originator, const char* p_name, uint8_t service_id, uint16_t sec_level, uint16_t psm, uint32_t mx_proto_id, uint32_t mx_chan_id) { tBTM_SEC_SERV_REC* p_srec; uint16_t index; uint16_t first_unused_record = BTM_NO_AVAIL_SEC_SERVICES; bool record_allocated = false; log::verbose("sec_level:0x{:x}", sec_level); /* See if the record can be reused (same service name, psm, mx_proto_id, service_id, and mx_chan_id), or obtain the next unused record */ p_srec = &sec_serv_rec[0]; for (index = 0; index < BTM_SEC_MAX_SERVICE_RECORDS; index++, p_srec++) { /* Check if there is already a record for this service */ if (p_srec->security_flags & BTM_SEC_IN_USE) { if (p_srec->psm == psm && p_srec->mx_proto_id == mx_proto_id && service_id == p_srec->service_id && p_name && (!strncmp(p_name, (char*)p_srec->orig_service_name, /* strlcpy replaces end char with termination char*/ BT_MAX_SERVICE_NAME_LEN - 1) || !strncmp(p_name, (char*)p_srec->term_service_name, /* strlcpy replaces end char with termination char*/ BT_MAX_SERVICE_NAME_LEN - 1))) { record_allocated = true; break; } } /* Mark the first available service record */ else if (!record_allocated) { *p_srec = {}; record_allocated = true; first_unused_record = index; } } if (!record_allocated) { log::warn("Out of Service Records ({})", BTM_SEC_MAX_SERVICE_RECORDS); return (record_allocated); } /* Process the request if service record is valid */ /* If a duplicate service wasn't found, use the first available */ if (index >= BTM_SEC_MAX_SERVICE_RECORDS) { index = first_unused_record; p_srec = &sec_serv_rec[index]; } p_srec->psm = psm; p_srec->service_id = service_id; p_srec->mx_proto_id = mx_proto_id; if (is_originator) { p_srec->orig_mx_chan_id = mx_chan_id; strlcpy((char*)p_srec->orig_service_name, p_name, BT_MAX_SERVICE_NAME_LEN + 1); /* clear out the old setting, just in case it exists */ { p_srec->security_flags &= ~(BTM_SEC_OUT_ENCRYPT | BTM_SEC_OUT_AUTHENTICATE | BTM_SEC_OUT_MITM); } /* Parameter validation. Originator should not set requirements for * incoming connections */ sec_level &= ~(BTM_SEC_IN_ENCRYPT | BTM_SEC_IN_AUTHENTICATE | BTM_SEC_IN_MITM | BTM_SEC_IN_MIN_16_DIGIT_PIN); if (security_mode == BTM_SEC_MODE_SP || security_mode == BTM_SEC_MODE_SC) { if (sec_level & BTM_SEC_OUT_AUTHENTICATE) sec_level |= BTM_SEC_OUT_MITM; } /* Make sure the authenticate bit is set, when encrypt bit is set */ if (sec_level & BTM_SEC_OUT_ENCRYPT) sec_level |= BTM_SEC_OUT_AUTHENTICATE; /* outgoing connections usually set the security level right before * the connection is initiated. * set it to be the outgoing service */ p_out_serv = p_srec; } else { p_srec->term_mx_chan_id = mx_chan_id; strlcpy((char*)p_srec->term_service_name, p_name, BT_MAX_SERVICE_NAME_LEN + 1); /* clear out the old setting, just in case it exists */ { p_srec->security_flags &= ~(BTM_SEC_IN_ENCRYPT | BTM_SEC_IN_AUTHENTICATE | BTM_SEC_IN_MITM | BTM_SEC_IN_MIN_16_DIGIT_PIN); } /* Parameter validation. Acceptor should not set requirements for outgoing * connections */ sec_level &= ~(BTM_SEC_OUT_ENCRYPT | BTM_SEC_OUT_AUTHENTICATE | BTM_SEC_OUT_MITM); if (security_mode == BTM_SEC_MODE_SP || security_mode == BTM_SEC_MODE_SC) { if (sec_level & BTM_SEC_IN_AUTHENTICATE) sec_level |= BTM_SEC_IN_MITM; } /* Make sure the authenticate bit is set, when encrypt bit is set */ if (sec_level & BTM_SEC_IN_ENCRYPT) sec_level |= BTM_SEC_IN_AUTHENTICATE; } p_srec->security_flags |= (uint16_t)(sec_level | BTM_SEC_IN_USE); log::debug( "[{}]: id:{}, is_orig:{} psm:0x{:04x} proto_id:{} chan_id:{} : " "sec:0x{:x} service_name:[{}] (up to {} chars saved)", index, service_id, is_originator, psm, mx_proto_id, mx_chan_id, p_srec->security_flags, p_name, BT_MAX_SERVICE_NAME_LEN); return (record_allocated); } uint8_t tBTM_SEC_CB::RemoveServiceById(uint8_t service_id) { tBTM_SEC_SERV_REC* p_srec = &sec_serv_rec[0]; uint8_t num_freed = 0; int i; for (i = 0; i < BTM_SEC_MAX_SERVICE_RECORDS; i++, p_srec++) { /* Delete services with specified name (if in use and not SDP) */ if ((p_srec->security_flags & BTM_SEC_IN_USE) && (p_srec->psm != BT_PSM_SDP) && (!service_id || (service_id == p_srec->service_id))) { log::verbose("BTM_SEC_CLR[{}]: id:{}", i, service_id); p_srec->security_flags = 0; num_freed++; } } return (num_freed); } uint8_t tBTM_SEC_CB::RemoveServiceByPsm(uint16_t psm) { tBTM_SEC_SERV_REC* p_srec = &sec_serv_rec[0]; uint8_t num_freed = 0; int i; for (i = 0; i < BTM_SEC_MAX_SERVICE_RECORDS; i++, p_srec++) { /* Delete services with specified name (if in use and not SDP) */ if ((p_srec->security_flags & BTM_SEC_IN_USE) && (p_srec->psm == psm)) { log::verbose("BTM_SEC_CLR[{}]: id {}", i, p_srec->service_id); p_srec->security_flags = 0; num_freed++; } } log::verbose("psm:0x{:x} num_freed:{}", psm, num_freed); return (num_freed); }