diff --git a/example/Makefile.inc b/example/Makefile.inc index b1b5e6198..0b0e36f06 100644 --- a/example/Makefile.inc +++ b/example/Makefile.inc @@ -5,6 +5,7 @@ VPATH += ${BTSTACK_ROOT}/src/ble/gatt-service VPATH += ${BTSTACK_ROOT}/src/classic VPATH += ${BTSTACK_ROOT}/example VPATH += ${BTSTACK_ROOT}/3rd-party/hxcmod-player +VPATH += ${BTSTACK_ROOT}/3rd-party/md5 VPATH += ${BTSTACK_ROOT}/3rd-party/micro-ecc VPATH += ${BTSTACK_ROOT}/3rd-party/bluedroid/decoder/srce VPATH += ${BTSTACK_ROOT}/3rd-party/bluedroid/encoder//srce @@ -14,6 +15,7 @@ CFLAGS += -I${BTSTACK_ROOT}/src/ble CFLAGS += -I${BTSTACK_ROOT}/src/classic CFLAGS += -I${BTSTACK_ROOT}/src CFLAGS += -I${BTSTACK_ROOT}/3rd-party/hxcmod-player +CFLAGS += -I${BTSTACK_ROOT}/3rd-party/md5 CFLAGS += -I${BTSTACK_ROOT}/3rd-party/micro-ecc CFLAGS += -I${BTSTACK_ROOT}/3rd-party/bluedroid/decoder/include CFLAGS += -I${BTSTACK_ROOT}/3rd-party/bluedroid/encoder/include @@ -192,7 +194,7 @@ ant_test: ${CORE_OBJ} ${COMMON_OBJ} ${CLASSIC_OBJ} ant_test.c sdp_rfcomm_query: ${CORE_OBJ} ${COMMON_OBJ} ${CLASSIC_OBJ} ${PAN_OBJ} ${SDP_CLIENT} sdp_rfcomm_query.c ${CC} $^ ${CFLAGS} ${LDFLAGS} -o $@ -pbap_client_demo: ${CORE_OBJ} ${COMMON_OBJ} ${CLASSIC_OBJ} ${SDP_CLIENT} obex_iterator.c goep_client.c pbap_client.c pbap_client_demo.c +pbap_client_demo: ${CORE_OBJ} ${COMMON_OBJ} ${CLASSIC_OBJ} ${SDP_CLIENT} md5.o obex_iterator.c goep_client.c pbap_client.c pbap_client_demo.c ${CC} $^ ${CFLAGS} ${LDFLAGS} -o $@ sdp_general_query: ${CORE_OBJ} ${COMMON_OBJ} ${CLASSIC_OBJ} ${SDP_CLIENT} sdp_general_query.c diff --git a/example/pbap_client_demo.c b/example/pbap_client_demo.c index 279fc4c0c..4384546e4 100644 --- a/example/pbap_client_demo.c +++ b/example/pbap_client_demo.c @@ -70,7 +70,9 @@ static void packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packe static bd_addr_t remote_addr; // MBP2016 "F4-0F-24-3B-1B-E1" // Nexus 7 "30-85-A9-54-2E-78" -static const char * remote_addr_string = "BC:EC:5D:E6:15:03"; +// iPhone SE "BC:EC:5D:E6:15:03" +// PTS "001BDC080AA5" +static char * remote_addr_string = "001BDC080AA5"; static btstack_packet_callback_registration_t hci_event_callback_registration; static uint16_t pbap_cid; @@ -90,6 +92,7 @@ static void show_usage(void){ printf("d - get phonebook size\n"); printf("e - pull phonebook\n"); printf("f - disconnnect\n"); + printf("p - authenticate using password '0000'\n"); printf("\n"); } @@ -116,6 +119,9 @@ static void stdin_process(char c){ case 'f': pbap_disconnect(pbap_cid); break; + case 'p': + pbap_authentication_password(pbap_cid, "0000"); + break; default: show_usage(); break; @@ -148,6 +154,9 @@ static void packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packe case PBAP_SUBEVENT_OPERATION_COMPLETED: printf("[+] Operation complete\n"); break; + case PBAP_SUBEVENT_AUTHENTICATION_REQUEST: + printf("[?] Authentication requested\n"); + break; case PBAP_SUBEVENT_PHONEBOOK_SIZE: status = pbap_subevent_phonebook_size_get_status(packet); if (status){ diff --git a/port/esp32/components/btstack/component.mk b/port/esp32/components/btstack/component.mk index 4748546c1..531999a92 100644 --- a/port/esp32/components/btstack/component.mk +++ b/port/esp32/components/btstack/component.mk @@ -15,6 +15,7 @@ COMPONENT_ADD_INCLUDEDIRS := \ 3rd-party/bluedroid/encoder/include \ 3rd-party/hxcmod-player \ 3rd-party/hxcmod-player/mods \ + 3rd-party/md5 \ src/classic \ src/ble/gatt-service \ src/ble \ @@ -29,6 +30,7 @@ COMPONENT_SRCDIRS := \ 3rd-party/bluedroid/encoder/srce \ 3rd-party/hxcmod-player \ 3rd-party/hxcmod-player/mods \ + 3rd-party/md5 \ src/ble/gatt-service \ src/ble \ src/classic \ diff --git a/port/max32630-fthr/example/template/Makefile b/port/max32630-fthr/example/template/Makefile index bcaec7fd9..2d4568ea1 100644 --- a/port/max32630-fthr/example/template/Makefile +++ b/port/max32630-fthr/example/template/Makefile @@ -104,6 +104,7 @@ VPATH += ${BTSTACK_ROOT}/3rd-party/bluedroid/decoder/srce VPATH += ${BTSTACK_ROOT}/3rd-party/bluedroid/encoder/srce VPATH += ${BTSTACK_ROOT}/3rd-party/hxcmod-player VPATH += ${BTSTACK_ROOT}/3rd-party/hxcmod-player/mods +VPATH += ${BTSTACK_ROOT}/3rd-party/md5 VPATH += ${BTSTACK_ROOT}/3rd-party/micro-ecc VPATH += ${BTSTACK_ROOT}/platform/embedded VPATH += ${BTSTACK_ROOT}/src/ble/gatt-service/ @@ -119,6 +120,7 @@ PROJ_CFLAGS += \ -I${BTSTACK_ROOT}/example \ -I${BTSTACK_ROOT}/3rd-party/bluedroid/decoder/include \ -I${BTSTACK_ROOT}/3rd-party/bluedroid/encoder/include \ + -I${BTSTACK_ROOT}/3rd-party/md5 \ -I${BTSTACK_ROOT}/3rd-party/micro-ecc \ -I${BTSTACK_ROOT}/3rd-party/hxcmod-player \ @@ -226,7 +228,7 @@ SRCS += $(CVSD_PLC_OBJ) SRCS += $(HXCMOD_PLAYER_OBJ) SRCS += $(HFP_OBJ) SRCS += hsp_hs.o hsp_ag.o -SRCS += obex_iterator.o goep_client.o pbap_client.o +SRCS += obex_iterator.o goep_client.o pbap_client.o md5.o # Enable assertion checking for development PROJ_CFLAGS+=-DMXC_ASSERT_ENABLE diff --git a/src/btstack_defines.h b/src/btstack_defines.h index ff5846b6f..e61d89a07 100644 --- a/src/btstack_defines.h +++ b/src/btstack_defines.h @@ -2056,6 +2056,16 @@ typedef uint8_t sm_key_t[16]; */ #define PBAP_SUBEVENT_PHONEBOOK_SIZE 0x04 +/** + * @format 1211 + * @param subevent_code + * @param goep_cid + * @param user_id_required + * @param full_access + */ +#define PBAP_SUBEVENT_AUTHENTICATION_REQUEST 0x05 + + // HID Meta Event Group /** diff --git a/src/btstack_event.h b/src/btstack_event.h index 629f0610f..afa7ac77e 100644 --- a/src/btstack_event.h +++ b/src/btstack_event.h @@ -6534,6 +6534,34 @@ static inline uint16_t pbap_subevent_phonebook_size_get_phoneboook_size(const ui return little_endian_read_16(event, 6); } +/** + * @brief Get field goep_cid from event PBAP_SUBEVENT_AUTHENTICATION_REQUEST + * @param event packet + * @return goep_cid + * @note: btstack_type 2 + */ +static inline uint16_t pbap_subevent_authentication_request_get_goep_cid(const uint8_t * event){ + return little_endian_read_16(event, 3); +} +/** + * @brief Get field user_id_required from event PBAP_SUBEVENT_AUTHENTICATION_REQUEST + * @param event packet + * @return user_id_required + * @note: btstack_type 1 + */ +static inline uint8_t pbap_subevent_authentication_request_get_user_id_required(const uint8_t * event){ + return event[5]; +} +/** + * @brief Get field full_access from event PBAP_SUBEVENT_AUTHENTICATION_REQUEST + * @param event packet + * @return full_access + * @note: btstack_type 1 + */ +static inline uint8_t pbap_subevent_authentication_request_get_full_access(const uint8_t * event){ + return event[6]; +} + /** * @brief Get field hid_cid from event HID_SUBEVENT_CONNECTION_OPENED * @param event packet diff --git a/src/classic/pbap_client.c b/src/classic/pbap_client.c index 9fc9cefc5..2ec0df8d2 100644 --- a/src/classic/pbap_client.c +++ b/src/classic/pbap_client.c @@ -74,6 +74,7 @@ #include "bluetooth_sdp.h" #include "classic/sdp_client_rfcomm.h" #include "btstack_event.h" +#include "md5.h" #include "classic/obex.h" #include "classic/obex_iterator.h" @@ -90,6 +91,8 @@ typedef enum { PBAP_W4_GOEP_CONNECTION, PBAP_W2_SEND_CONNECT_REQUEST, PBAP_W4_CONNECT_RESPONSE, + PBAP_W4_USER_AUTHENTICATION, + PBAP_W2_SEND_AUTHENTICATED_CONNECT, PBAP_CONNECT_RESPONSE_RECEIVED, PBAP_CONNECTED, // @@ -116,6 +119,9 @@ typedef struct pbap_client { btstack_packet_handler_t client_handler; const char * current_folder; uint16_t set_path_offset; + uint8_t authentication_options; + uint16_t authentication_nonce[16]; + const char * authentication_password; } pbap_client_t; static pbap_client_t _pbap_client; @@ -183,11 +189,35 @@ static void pbap_client_emit_phonebook_size_event(pbap_client_t * context, uint8 context->client_handler(HCI_EVENT_PACKET, context->cid, &event[0], pos); } +static void pbap_client_emit_authentication_event(pbap_client_t * context, uint8_t options){ + // split options + uint8_t user_id_required = options & 1 ? 1 : 0; + uint8_t full_access = options & 2 ? 1 : 0; + + uint8_t event[7]; + int pos = 0; + event[pos++] = HCI_EVENT_PBAP_META; + pos++; // skip len + event[pos++] = PBAP_SUBEVENT_AUTHENTICATION_REQUEST; + little_endian_store_16(event,pos,context->cid); + pos+=2; + event[pos++] = user_id_required; + event[pos++] = full_access; + if (pos != sizeof(event)) log_error("pbap_client_emit_authentication_event size %u", pos); + context->client_handler(HCI_EVENT_PACKET, context->cid, &event[0], pos); +} + +static const uint8_t collon = (uint8_t) ':'; + static void pbap_handle_can_send_now(void){ uint8_t path_element[20]; uint16_t path_element_start; uint16_t path_element_len; uint8_t application_parameters[20]; + uint8_t challenge_response[36]; + int i; + + MD5_CTX md5_ctx; switch (pbap_client->state){ case PBAP_W2_SEND_CONNECT_REQUEST: @@ -195,7 +225,29 @@ static void pbap_handle_can_send_now(void){ goep_client_add_header_target(pbap_client->goep_cid, 16, pbap_uuid); pbap_client->state = PBAP_W4_CONNECT_RESPONSE; goep_client_execute(pbap_client->goep_cid); - return; + break; + case PBAP_W2_SEND_AUTHENTICATED_CONNECT: + goep_client_create_connect_request(pbap_client->goep_cid, OBEX_VERSION, 0, OBEX_MAX_PACKETLEN_DEFAULT); + goep_client_add_header_target(pbap_client->goep_cid, 16, pbap_uuid); + // setup authentication challenge response + i = 0; + challenge_response[i++] = 0; // Tag Digest + challenge_response[i++] = 16; // Len + // calculate md5 + MD5_Init(&md5_ctx); + MD5_Update(&md5_ctx, pbap_client->authentication_nonce, 16); + MD5_Update(&md5_ctx, &collon, 1); + MD5_Update(&md5_ctx, pbap_client->authentication_password, strlen(pbap_client->authentication_password)); + MD5_Final(&challenge_response[i], &md5_ctx); + i += 16; + challenge_response[i++] = 2; // Tag Nonce + challenge_response[i++] = 16; // Len + memcpy(&challenge_response[i], pbap_client->authentication_nonce, 16); + i += 16; + goep_client_add_header_challenge_response(pbap_client->goep_cid, i, challenge_response); + pbap_client->state = PBAP_W4_CONNECT_RESPONSE; + goep_client_execute(pbap_client->goep_cid); + break; case PBAP_W2_SEND_DISCONNECT_REQUEST: goep_client_create_disconnect_request(pbap_client->goep_cid); pbap_client->state = PBAP_W4_DISCONNECT_RESPONSE; @@ -257,6 +309,38 @@ static void pbap_handle_can_send_now(void){ } } +static void pbap_parse_authentication_challenge(pbap_client_t * context, const uint8_t * challenge_data, uint16_t challenge_len){ + // printf("Challenge: "); + // printf_hexdump(challenge_data, challenge_len); + int i; + uint8_t charset_code = 0; + for (i=0 ; iauthentication_nonce, &challenge_data[i], 16); + // printf("Nonce: "); + // printf_hexdump(context->authentication_nonce, 16); + break; + case 1: + context->authentication_options = challenge_data[i]; + // printf("Options %u\n", context->authentication_options); + break; + case 2: + // TODO: handle charset + charset_code = challenge_data[i]; + break; + } + i += len; + } +} + static void pbap_packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size){ UNUSED(channel); // ok: there is no channel @@ -303,24 +387,37 @@ static void pbap_packet_handler(uint8_t packet_type, uint16_t channel, uint8_t * break; case GOEP_DATA_PACKET: // TODO: handle chunked data -#if 0 - obex_dump_packet(goep_client_get_request_opcode(pbap_client->goep_cid), packet, size); +#if 1 + // obex_dump_packet(goep_client_get_request_opcode(pbap_client->goep_cid), packet, size); #endif switch (pbap_client->state){ case PBAP_W4_CONNECT_RESPONSE: - for (obex_iterator_init_with_response_packet(&it, goep_client_get_request_opcode(pbap_client->goep_cid), packet, size); obex_iterator_has_more(&it) ; obex_iterator_next(&it)){ - uint8_t hi = obex_iterator_get_hi(&it); - if (hi == OBEX_HEADER_CONNECTION_ID){ - goep_client_set_connection_id(pbap_client->goep_cid, obex_iterator_get_data_32(&it)); - } - } - if (packet[0] == OBEX_RESP_SUCCESS){ - pbap_client->state = PBAP_CONNECTED; - pbap_client_emit_connected_event(pbap_client, 0); - } else { - log_info("pbap: obex connect failed, result 0x%02x", packet[0]); - pbap_client->state = PBAP_INIT; - pbap_client_emit_connected_event(pbap_client, OBEX_CONNECT_FAILED); + switch (packet[0]){ + case OBEX_RESP_SUCCESS: + for (obex_iterator_init_with_response_packet(&it, goep_client_get_request_opcode(pbap_client->goep_cid), packet, size); obex_iterator_has_more(&it) ; obex_iterator_next(&it)){ + uint8_t hi = obex_iterator_get_hi(&it); + if (hi == OBEX_HEADER_CONNECTION_ID){ + goep_client_set_connection_id(pbap_client->goep_cid, obex_iterator_get_data_32(&it)); + } + } + pbap_client->state = PBAP_CONNECTED; + pbap_client_emit_connected_event(pbap_client, 0); + break; + case OBEX_RESP_UNAUTHORIZED: + for (obex_iterator_init_with_response_packet(&it, goep_client_get_request_opcode(pbap_client->goep_cid), packet, size); obex_iterator_has_more(&it) ; obex_iterator_next(&it)){ + uint8_t hi = obex_iterator_get_hi(&it); + if (hi == OBEX_HEADER_AUTHENTICATION_CHALLENGE){ + pbap_parse_authentication_challenge(pbap_client, obex_iterator_get_data(&it), obex_iterator_get_data_len(&it)); + } + } + pbap_client->state = PBAP_W4_USER_AUTHENTICATION; + pbap_client_emit_authentication_event(pbap_client, pbap_client->authentication_options); + break; + default: + log_info("pbap: obex connect failed, result 0x%02x", packet[0]); + pbap_client->state = PBAP_INIT; + pbap_client_emit_connected_event(pbap_client, OBEX_CONNECT_FAILED); + break; } break; case PBAP_W4_DISCONNECT_RESPONSE: @@ -451,3 +548,12 @@ uint8_t pbap_set_phonebook(uint16_t pbap_cid, const char * path){ goep_client_request_can_send_now(pbap_client->goep_cid); return 0; } + +uint8_t pbap_authentication_password(uint16_t pbap_cid, const char * password){ + UNUSED(pbap_cid); + if (pbap_client->state != PBAP_W4_USER_AUTHENTICATION) return BTSTACK_BUSY; + pbap_client->state = PBAP_W2_SEND_AUTHENTICATED_CONNECT; + pbap_client->authentication_password = password; + goep_client_request_can_send_now(pbap_client->goep_cid); + return 0; +} diff --git a/src/classic/pbap_client.h b/src/classic/pbap_client.h index 24587b516..0cf4b6973 100644 --- a/src/classic/pbap_client.h +++ b/src/classic/pbap_client.h @@ -60,6 +60,13 @@ void pbap_client_init(void); */ uint8_t pbap_connect(btstack_packet_handler_t handler, bd_addr_t addr, uint16_t * out_cid); +/** + * @brief Provide password for OBEX Authentication after receiving PBAP_SUBEVENT_AUTHENTICATION_REQUEST + * @param pbap_cid + * @param password (null terminated string) - not copied, needs to stay valid until connection completed +*/ +uint8_t pbap_authentication_password(uint16_t pbap_cid, const char * password); + /** * @brief Disconnects PBAP connection with given identifier. * @param pbap_cid