BlueZ使能Privacy=device后,smp fail (本端DH key check fail) 原始需求在dual mode下客户需要Classic BLE分别使用不同的名字这就需要BLE使用RPA让Classic BLE成为两个不同的设备。# Default privacy setting.# Enables use of private address.# Possible values for LE mode: off, network/on, device# Possible values for Dual mode: off, network/on, device,# limited-network, limited-device## - off: Local privacy disabled.## - network/on: A device will only accept advertising packets from peer# devices that contain private addresses. It may not be compatible with some# legacy devices since it requires the use of RPA(s) all the time.## - device: A device in device privacy mode is only concerned about the# privacy of the device and will accept advertising packets from peer devices# that contain their Identity Address as well as ones that contain a private# address, even if the peer device has distributed its IRK in the past.# - limited-network: Apply Limited Discoverable Mode to advertising, which# follows the same policy as to BR/EDR that publishes the identity address when# discoverable, and Network Privacy Mode for scanning.## - limited-device: Apply Limited Discoverable Mode to advertising, which# follows the same policy as to BR/EDR that publishes the identity address when# discoverable, and Device Privacy Mode for scanning.## Defaults to off#Privacy offBlueZ main.conf中添加“Privacy device”使能BLE RPA。使用手机连接并配对DUT BLE发现配对失败。查看kernel中smp回复DH Key Check Failed的代码staticintsmp_cmd_dhkey_check(structl2cap_conn*conn,structsk_buff*skb){structsmp_cmd_dhkey_check*check(void*)skb-data;structl2cap_chan*chanconn-smp;structhci_conn*hconconn-hcon;structsmp_chan*smpchan-data;u8 a[7],b[7],*local_addr,*remote_addr;u8 io_cap[3],r[16],e[16];interr;bt_dev_dbg(hcon-hdev,conn %p,conn);if(skb-lensizeof(*check))returnSMP_INVALID_PARAMS;memcpy(a,hcon-init_addr,6);memcpy(b,hcon-resp_addr,6);a[6]hcon-init_addr_type;b[6]hcon-resp_addr_type;if(test_bit(SMP_FLAG_INITIATOR,smp-flags)){local_addra;remote_addrb;memcpy(io_cap,smp-prsp[1],3);}else{local_addrb;remote_addra;memcpy(io_cap,smp-preq[1],3);}memset(r,0,sizeof(r));if(smp-methodREQ_PASSKEY||smp-methodDSP_PASSKEY)put_unaligned_le32(hcon-passkey_notify,r);elseif(smp-methodREQ_OOB)memcpy(r,smp-lr,16);errsmp_f6(smp-tfm_cmac,smp-mackey,smp-rrnd,smp-prnd,r,io_cap,remote_addr,local_addr,e);if(err)returnSMP_UNSPECIFIED;if(crypto_memneq(check-e,e,16))returnSMP_DHKEY_CHECK_FAILED;if(!test_bit(SMP_FLAG_INITIATOR,smp-flags)){if(test_bit(SMP_FLAG_WAIT_USER,smp-flags)){set_bit(SMP_FLAG_DHKEY_PENDING,smp-flags);return0;}/* Responder sends DHKey check as response to initiator */sc_dhkey_check(smp);}sc_add_ltk(smp);if(test_bit(SMP_FLAG_INITIATOR,smp-flags)){hci_le_start_enc(hcon,0,0,smp-tk,smp-enc_key_size);hcon-enc_key_sizesmp-enc_key_size;}return0;}可以看到是因为本地smp_f6()算出来的结果与对端发过来的计算结果不一致。本地kernel smp算法库或者其他配置有问题做个对比实验不配置Privacy device看看是否可以正常连接配对。可以正常连接配对排除smp算法库和本地配置问题。staticintsmp_f6(structcrypto_shash*tfm_cmac,constu8 w[16],constu8 n1[16],constu8 n2[16],constu8 r[16],constu8 io_cap[3],constu8 a1[7],constu8 a2[7],u8 res[16]){u8 m[65];interr;SMP_DBG(w %16phN,w);SMP_DBG(n1 %16phN n2 %16phN,n1,n2);SMP_DBG(r %16phN io_cap %3phN a1 %7phN a2 %7phN,r,io_cap,a1,a2);memcpy(m,a2,7);memcpy(m7,a1,7);memcpy(m14,io_cap,3);memcpy(m17,r,16);memcpy(m33,n2,16);memcpy(m49,n1,16);erraes_cmac(tfm_cmac,w,m,sizeof(m),res);if(err)returnerr;SMP_DBG(res %16phN,res);returnerr;}这是smp_f6()算法除了参数7、8之外其他都是smp配对过程的中间产物我们已经排除了算法库本地配置的问题接下来可以重点关注a1、a2这两个变数上。我们追一下smp_f6()的参数7、8a1、a2他们是remote_addr addr_typelocal_addr addr_type组合起来的2个7字节数组。唯一的可能就是DUT和对端手机smp_f6()算法的a1、a2参数不一致。这是本端HCI Log中Host设置的RPA:这是用nRF-Connect搜索到的果然不一样难怪SMP DH Key Check Fail.HCI LE Set Extended Advertising Parameters Command的Own_Address_Type参数Spec的描述如下看起来是Controller另外生成了一个RPA生成RPA的条件是Controller的resolving list中包含可以匹配的entry否则controller广播会使用LE_Set_Advertising_Set_Random_Address命令带下来的address。但这里controller是如何匹配上了resolving list中的entry的呢从开机HCI log中看到两处不合理的地方1Host开机就下发了HCI_LE_Add_Device_To_Resolving_Listpeer address local public addrpeer IRK local IRK这条命令只能用于添加配对过的设备才对2LE_Set_Extended_Advertising_Pramam CMD中Peer Addr local public addr只有直连广播才会设置这个参数搜索kernel代码发现了下面这段看起来kernel对privacy的实现卡了一个spec的bug故意设置[HCI_LE_Add_Device_To_Resolving_Listpeer addr local public addr]且[LE_Set_Extended_Advertising_Pramam CMDpeer addr local public addr]这使得controller正好能够从resolving list匹配到一个entry使用这个entry的local IRK来生成一个RPA.staticinthci_powered_update_adv_sync(structhci_dev*hdev){structadv_info*adv,*tmp;interr;if(!hci_dev_test_flag(hdev,HCI_LE_ENABLED))return0;/* If RPA Resolution has not been enable yet it means the * resolving list is empty and we should attempt to program the * local IRK in order to support using own_addr_type * ADDR_LE_DEV_RANDOM_RESOLVED (0x03). */if(!hci_dev_test_flag(hdev,HCI_LL_RPA_RESOLUTION)){hci_le_add_resolve_list_sync(hdev,NULL);hci_le_set_addr_resolution_enable_sync(hdev,0x01);}。。。。。。/* Call for each tracked instance to be scheduled */list_for_each_entry_safe(adv,tmp,hdev-adv_instances,list)hci_schedule_adv_instance_sync(hdev,adv-instance,true);return0;}inthci_setup_ext_adv_instance_sync(structhci_dev*hdev,u8 instance){。。。。。。/* If Own_Address_Type equals 0x02 or 0x03, the Peer_Address parameter * contains the peer’s Identity Address and the Peer_Address_Type * parameter contains the peer’s Identity Type (i.e., 0x00 or 0x01). * These parameters are used to locate the corresponding local IRK in * the resolving list; this IRK is used to generate their own address * used in the advertisement. */if(own_addr_typeADDR_LE_DEV_RANDOM_RESOLVED)hci_copy_identity_address(hdev,cp.peer_addr,cp.peer_addr_type);errhci_set_ext_adv_params_sync(hdev,adv,cp,rp);if(err)returnerr;。。。。。。return0;}好了一切都说得通了要想解决这个问题只需要DUT 手机计算smp_f6()时a1、a2参数相匹配就行。那host如何能拿到controller实际广播的RPA呢答案就在LE Enhanced Connection Complete Event中我们的controller上报的Local RPA参数全为0所以smp_f6()计算结果匹配失败。只需要controller在这个evt中上报实际使用的RPA那么这个问题就解决了。我们来延伸一下如果使能了bluez privacy device应用层如何获取当前controller使用的RPA呢spec中有这样一条命令可以获取local RPA但是需要填两个参数peer identity address type peer identity address从描述上看是使用已配对设备的地址信息来获取local RPA但是在没有任何配对信息之前我们如何获取呢我们正好可以利用bluez实现privacy的技巧peer addr就填local addr这样就能以在resolving list中匹配到一个entry拿到local RPA。在bluez/lib/hci.c中增加接口typedefstruct{uint8_tpeer_addr_type;bdaddr_tpeer_addr;}__attribute__((packed))le_read_local_resolvable_addr_cp;typedefstruct{uint8_tstatus;bdaddr_tlocal_rpa;}__attribute__((packed))le_read_local_resolvable_addr_rp;#defineOCF_LE_READ_LOCAL_RESOLVABLE_ADDR0x002C#defineLE_READ_LOCAL_RESOLVABLE_ADDR_CP_SIZEsizeof(le_read_local_resolvable_addr_cp)#defineLE_READ_LOCAL_RESOLVABLE_ADDR_RP_SIZEsizeof(le_read_local_resolvable_addr_rp)inthci_le_read_local_resolvable_address(intdd,uint8_tpeer_addr_type,constbdaddr_t*peer_addr,bdaddr_t*local_rpa,intto){structhci_requestrq;le_read_local_resolvable_addr_cp cp;le_read_local_resolvable_addr_rp rp;if(!peer_addr||!local_rpa){errnoEINVAL;return-1;}memset(cp,0,sizeof(cp));memset(rp,0,sizeof(rp));cp.peer_addr_typepeer_addr_type;bacpy(cp.peer_addr,peer_addr);memset(rq,0,sizeof(rq));rq.ogfOGF_LE_CTL;rq.ocfOCF_LE_READ_LOCAL_RESOLVABLE_ADDR;rq.cparamcp;rq.clenLE_READ_LOCAL_RESOLVABLE_ADDR_CP_SIZE;rq.rparamrp;rq.rlenLE_READ_LOCAL_RESOLVABLE_ADDR_RP_SIZE;if(hci_send_req(dd,rq,to)0)return-1;if(rp.status){errnoEIO;return-1;}bacpy(local_rpa,rp.local_rpa);return0;}在应用层调用这个接口来验证一下staticintble_read_local_rpa(void){bdaddr_tlocal_bda,local_rpa;intdd;intdev_id;dev_idhci_get_route(NULL);if(dev_id0){AML_LOGI(no default bt adapter \n);returnLM_STATUS_FAIL;}ddhci_open_dev(dev_id);if(dd0){AML_LOGE(Failed to open HCI device\n);returnLM_STATUS_FAIL;}hci_devba(dev_id,local_bda);hci_le_read_local_resolvable_address(dd,0,local_bda,local_rpa,1000);hci_close_dev(dd);AML_LOGI(Local BDA %02X:%02X:%02X:%02X:%02X:%02X, Local RPA %02X:%02X:%02X:%02X:%02X:%02X\n,local_bda.b[0],local_bda.b[1],local_bda.b[2],local_bda.b[3],local_bda.b[4],local_bda.b[5],local_rpa.b[0],local_rpa.b[1],local_rpa.b[2],local_rpa.b[3],local_rpa.b[4],local_rpa.b[5]);}(ble_read_local_rpa): Local BDA 2E:2C:75:62:A5:10, Local RPA 88:8B:30:B6:61:47