Import of the watch repository from Pebble

This commit is contained in:
Matthieu Jeanson
2025-01-27 11:38:16 -08:00
committed by Katharine Berry
commit 3b92768480
10334 changed files with 2564465 additions and 0 deletions
@@ -0,0 +1,113 @@
/*
* Copyright 2024 Google LLC
*
* 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.
*/
#pragma once
#include <bluetooth/bluetooth_types.h>
#include <bluetooth/sm_types.h>
//!
//! This module is used to share data between PRF and Normal FW
//!
///////////////////////////////////////////////////////////////////////////////////////////////////
//! Custom Local Device Name
//! @param local_device_name_out Storage for the local device name.
//! @param max_size Size of the local_device_name_out buffer
//! @return true if there is a valid local device name stored, otherwise false (a zero-length string
//! will be assigned to local_device_name_out)
bool shared_prf_storage_get_local_device_name(char *local_device_name_out, size_t max_size);
//! Stores the customized local device name
//! @param local_device_name The device name to store
void shared_prf_storage_set_local_device_name(const char *local_device_name);
///////////////////////////////////////////////////////////////////////////////////////////////////
//! BLE Root Keys
//! Copies the BLE Encryption Root (ER) or Identity Root (IR) keys out of the shared storage.
//! @param key_out Storage into which ER or IR should be copied.
//! @param key_type The type of key to copy
//! @return true if ER and IR are copied, false if there are no keys have been found to copy.
bool shared_prf_storage_get_root_key(SMRootKeyType key_type, SM128BitKey *key_out);
//! Stores new BLE Encryption Root (ER) and Identity Root (IR) keys in the shared storage.
void shared_prf_storage_set_root_keys(SM128BitKey *keys_in);
///////////////////////////////////////////////////////////////////////////////////////////////////
//! BLE Pairing Data
//! Returns true if there is a valid pairing, otherwise false.
//! Out params are only valid if the function returns true
//! Pass in NULL for any values that you aren't interested in
bool shared_prf_storage_get_ble_pairing_data(SMPairingInfo *pairing_info_out,
char *name_out, bool *requires_address_pinning_out,
uint8_t *flags);
//! @param pairing_info Data structure containing all the pairing info available.
//! @param name Optional device name to store. Pass NULL if not available.
void shared_prf_storage_store_ble_pairing_data(const SMPairingInfo *pairing_info,
const char *name,
bool requires_address_pinning,
uint8_t flags);
void shared_prf_storage_erase_ble_pairing_data(void);
///////////////////////////////////////////////////////////////////////////////////////////////////
//! BLE Pinned Address
//! Returns true if there is a valid pinned address, otherwise false.
//! Out params are only valid if the function returns true
//! Pass in NULL for any values that you aren't interested in
bool shared_prf_storage_get_ble_pinned_address(BTDeviceAddress *address_out);
//! Stores the new BLE Pinned Address in the shared storage.
void shared_prf_storage_set_ble_pinned_address(const BTDeviceAddress *address);
///////////////////////////////////////////////////////////////////////////////////////////////////
//! BT Classic Pairing Data
//! Returns true if there is a valid pairing, otherwise false.
//! Out params are only valid if the function returns true
//! Pass in NULL for any values that you aren't interested in
bool shared_prf_storage_get_bt_classic_pairing_data(BTDeviceAddress *addr_out,
char *device_name_out,
SM128BitKey *link_key_out,
uint8_t *platform_bits);
void shared_prf_storage_store_bt_classic_pairing_data(BTDeviceAddress *addr,
const char *device_name,
SM128BitKey *link_key,
uint8_t platform_bits);
void shared_prf_storage_store_platform_bits(uint8_t platform_bits);
void shared_prf_storage_erase_bt_classic_pairing_data(void);
///////////////////////////////////////////////////////////////////////////////////////////////////
//! Getting Started Is Complete
bool shared_prf_storage_get_getting_started_complete(void);
void shared_prf_storage_set_getting_started_complete(bool set);
///////////////////////////////////////////////////////////////////////////////////////////////////
//! Factory Reset
void shared_prf_storage_wipe_all(void);
void shared_prf_storage_init(void);
@@ -0,0 +1,88 @@
/*
* Copyright 2024 Google LLC
*
* 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.
*/
#include "console/dbgserial.h"
#include "console/prompt.h"
#include "system/hexdump.h"
#include "system/logging.h"
#include "util/string.h"
#include "services/common/shared_prf_storage/shared_prf_storage.h"
#include "services/common/bluetooth/bluetooth_persistent_storage_debug.h"
#include <bluetooth/bluetooth_types.h>
#include <bluetooth/features.h>
#include <bluetooth/sm_types.h>
#include <btutil/bt_device.h>
#include <btutil/sm_util.h>
void shared_prf_storage_dump_contents(void) {
prompt_send_response("---Shared PRF Contents---\n------------------------\n");
char buf[DISPLAY_BUF_LEN];
SMPairingInfo pairing_info;
char name[BT_DEVICE_NAME_BUFFER_SIZE];
bool requires_address_pinning;
uint8_t flags;
if (shared_prf_storage_get_ble_pairing_data(&pairing_info, name, &requires_address_pinning,
&flags)) {
bluetooth_persistent_storage_debug_dump_ble_pairing_info(&buf[0], &pairing_info);
prompt_send_response_fmt(buf, sizeof(buf),
"Req addr pin: %u, flags: %x, BLE Dev Name: %s",
requires_address_pinning, flags, name);
} else {
prompt_send_response("No BLE Data");
}
SM128BitKey keys[SMRootKeyTypeNum];
if (shared_prf_storage_get_root_key(SMRootKeyTypeEncryption, &keys[SMRootKeyTypeEncryption]) &&
shared_prf_storage_get_root_key(SMRootKeyTypeIdentity, &keys[SMRootKeyTypeIdentity])) {
bluetooth_persistent_storage_debug_dump_root_keys(&keys[SMRootKeyTypeIdentity],
&keys[SMRootKeyTypeEncryption]);
} else {
prompt_send_response("Missing IRK and/or ERK root key(s)!");
}
BTDeviceAddress addr;
if (shared_prf_storage_get_ble_pinned_address(&addr)) {
prompt_send_response_fmt(buf, DISPLAY_BUF_LEN, "\nPinned address: "BT_DEVICE_ADDRESS_FMT,
BT_DEVICE_ADDRESS_XPLODE_PTR(&addr));
}
SM128BitKey link_key;
uint8_t platform_bits;
if (bt_driver_supports_bt_classic()) {
if (shared_prf_storage_get_bt_classic_pairing_data(&addr, &name[0],
&link_key, &platform_bits)) {
bluetooth_persistent_storage_debug_dump_classic_pairing_info(
&buf[0], &addr, &name[0], &link_key, platform_bits);
} else {
prompt_send_response("No BT classic data");
}
}
if (shared_prf_storage_get_local_device_name(name, BT_DEVICE_NAME_BUFFER_SIZE)) {
prompt_send_response_fmt(buf, BT_DEVICE_NAME_BUFFER_SIZE, "Local device name: %s", name);
} else {
prompt_send_response("No Device Name");
}
prompt_send_response_fmt(buf, BT_DEVICE_NAME_BUFFER_SIZE, "Started Complete: %s",
bool_to_str(shared_prf_storage_get_getting_started_complete()));
}
@@ -0,0 +1,19 @@
/*
* Copyright 2024 Google LLC
*
* 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.
*/
#pragma once
void shared_prf_storage_dump_contents(void);
@@ -0,0 +1,502 @@
/*
* Copyright 2024 Google LLC
*
* 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.
*/
#include "services/common/shared_prf_storage/shared_prf_storage.h"
#include "shared_prf_storage_private.h"
#include "drivers/flash.h"
#include "flash_region/flash_region.h"
#include "kernel/pbl_malloc.h"
#include "services/common/regular_timer.h"
#include "services/common/system_task.h"
#include "system/logging.h"
#include "util/size.h"
#include <bluetooth/sm_types.h>
#include <btutil/sm_util.h>
#include <os/mutex.h>
static RegularTimerInfo s_shared_prf_writeback_timer;
typedef struct {
SharedPRFData pending_data;
bool has_bt_classic_pairing_pending;
bool has_ble_pairing_pending;
} SharedPRFPendingBondings;
static SharedPRFPendingBondings s_pending_bondings = { };
static PebbleMutex *s_pending_data_mutex = NULL;
///////////////////////////////////////////////////////////////////////////////////////////////////
//! Private functions
static void prv_lock_pending_bonding(void) {
mutex_lock(s_pending_data_mutex);
}
static void prv_unlock_pending_bonding(void) {
mutex_unlock(s_pending_data_mutex);
}
static void prv_shared_prf_reschedule_writeback_timer(void) {
if (regular_timer_is_scheduled(&s_shared_prf_writeback_timer)) {
regular_timer_remove_callback(&s_shared_prf_writeback_timer);
}
regular_timer_add_multiminute_callback(&s_shared_prf_writeback_timer, 5);
}
// Helper to wipe memory to avoid leaking secrets stored in PRF shared storage through used stack
static void prv_cleanup_struct(SharedPRFData *data_out) {
*data_out = (SharedPRFData){};
}
static void prv_get_empty_struct(SharedPRFData *data_out) {
*data_out = (SharedPRFData) { .version = SHARED_PRF_STORAGE_VERSION };
}
static void prv_fetch_struct(SharedPRFData *data_out) {
flash_read_bytes((uint8_t*) data_out, FLASH_REGION_SHARED_PRF_STORAGE_BEGIN, sizeof(*data_out));
if (data_out->version != SHARED_PRF_STORAGE_VERSION) {
// No data present, just return an empty struct with the current version set.
prv_get_empty_struct(data_out);
}
}
static void prv_perform_write(SharedPRFData *data, bool should_erase) {
if (should_erase) {
flash_erase_subsector_blocking(FLASH_REGION_SHARED_PRF_STORAGE_BEGIN);
}
flash_write_bytes((const uint8_t*) data, FLASH_REGION_SHARED_PRF_STORAGE_BEGIN, sizeof(*data));
}
static void prv_apply_patch_to_struct(void *patch, size_t size, size_t offset, bool should_erase) {
SharedPRFData data;
prv_fetch_struct(&data);
uint8_t *unmodified_data_patch_start = ((uint8_t *)&data) + offset;
// Struct is packed, so it's OK to use memcmp:
if (memcmp(unmodified_data_patch_start, patch, size) != 0) {
// There is new data present, so perform a write!
memcpy(unmodified_data_patch_start, patch, size);
prv_perform_write(&data, should_erase);
}
prv_cleanup_struct(&data);
}
typedef struct {
size_t size;
size_t offset;
uint8_t patch[];
} WriteEraseCBData;
static void prv_perform_flash_erase_write_cb(void *data_in) {
WriteEraseCBData *cb_data = (WriteEraseCBData*) data_in;
prv_apply_patch_to_struct(cb_data->patch, cb_data->size, cb_data->offset, true);
kernel_free(cb_data);
}
// Does the flash write / erase on the background task
static void prv_update_and_cleanup_struct_async(const void *data, size_t size, size_t offset) {
WriteEraseCBData *callback_data = kernel_malloc_check(sizeof(*callback_data) + size);
*callback_data = (WriteEraseCBData) {
.size = size,
.offset = offset,
};
memcpy(callback_data->patch, data, size);
system_task_add_callback(prv_perform_flash_erase_write_cb, callback_data);
}
// Doesn't perform a flash erase. Should only be used to zero out parts / all of the struct
static void prv_update_and_cleanup_struct_no_erase(void *data, size_t size, size_t offset) {
prv_apply_patch_to_struct(data, size, offset, false);
}
// Does the flash write / erase on the current task
static void prv_update_and_cleanup_struct(void *data, size_t size, size_t offset) {
prv_apply_patch_to_struct(data, size, offset, true);
}
///////////////////////////////////////////////////////////////////////////////////////////////////
//! Custom Local Device Name
bool shared_prf_storage_get_local_device_name(char *local_device_name_out, size_t max_size) {
SharedPRFData data;
prv_fetch_struct(&data);
if (local_device_name_out) {
strncpy(local_device_name_out, data.local_device_name, max_size);
local_device_name_out[max_size - 1] = 0;
}
const bool result = (data.local_device_name[0] != 0); // Is not zero length?
prv_cleanup_struct(&data);
return result;
}
void shared_prf_storage_set_local_device_name(const char *local_device_name) {
prv_update_and_cleanup_struct_async(local_device_name,
MEMBER_SIZE(SharedPRFData, local_device_name),
offsetof(SharedPRFData, local_device_name));
}
///////////////////////////////////////////////////////////////////////////////////////////////////
//! BLE Root Keys
bool shared_prf_storage_get_root_key(SMRootKeyType key_type, SM128BitKey *key_out) {
SharedPRFData data;
prv_fetch_struct(&data);
bool result = false;
SM128BitKey nil_key = {};
if (0 == memcmp(&nil_key, &data.root_keys[key_type], sizeof(nil_key))) {
goto done;
}
if (key_out) {
memcpy(key_out, &data.root_keys[key_type], sizeof(*key_out));
}
result = true;
done:
prv_cleanup_struct(&data);
return result;
}
void shared_prf_storage_set_root_keys(SM128BitKey *keys_in) {
#ifdef RECOVERY_FW
// This can't be async because after it is set, sm.c accesses this key right away and will assert
// if it isn't available yet.
prv_update_and_cleanup_struct(keys_in, MEMBER_SIZE(SharedPRFData, root_keys),
offsetof(SharedPRFData, root_keys));
#else
// This can be async because the sm.c will read this key from bt_persistent_storage instead
prv_update_and_cleanup_struct_async(keys_in, MEMBER_SIZE(SharedPRFData, root_keys),
offsetof(SharedPRFData, root_keys));
#endif
}
///////////////////////////////////////////////////////////////////////////////////////////////////
//! BLE Pairing Data
bool shared_prf_storage_get_ble_pairing_data(SMPairingInfo *pairing_info_out,
char *name_out, bool *requires_address_pinning_out,
uint8_t *flags) {
SharedPRFData data;
prv_fetch_struct(&data);
const BLEPairingData *ble_data = &data.ble_data;
bool result = false;
if (!ble_data->is_local_encryption_info_valid &&
!ble_data->is_remote_encryption_info_valid &&
!ble_data->is_remote_identity_info_valid &&
!ble_data->is_remote_signing_info_valid) {
goto done;
}
if (pairing_info_out) {
*pairing_info_out = (const SMPairingInfo) {};
pairing_info_out->local_encryption_info.ediv = ble_data->local_ediv;
pairing_info_out->local_encryption_info.div = ble_data->local_div;
pairing_info_out->remote_encryption_info.ltk = ble_data->ltk;
pairing_info_out->remote_encryption_info.rand = ble_data->rand;
pairing_info_out->remote_encryption_info.ediv = ble_data->ediv;
pairing_info_out->irk = ble_data->irk;
pairing_info_out->identity = ble_data->identity;
pairing_info_out->csrk = ble_data->csrk;
pairing_info_out->is_local_encryption_info_valid = ble_data->is_local_encryption_info_valid;
pairing_info_out->is_remote_encryption_info_valid = ble_data->is_remote_encryption_info_valid;
pairing_info_out->is_remote_identity_info_valid = ble_data->is_remote_identity_info_valid;
pairing_info_out->is_remote_signing_info_valid = ble_data->is_remote_signing_info_valid;
}
if (name_out) {
strncpy(name_out, data.ble_data.name, BT_DEVICE_NAME_BUFFER_SIZE);
}
if (requires_address_pinning_out) {
// Not supported in v2
*requires_address_pinning_out = false;
}
if (flags) {
// Not supported in v2
*flags = 0;
}
result = true;
done:
prv_cleanup_struct(&data);
return result;
}
static void prv_load_ble_pairing_data(BLEPairingData *data,
const SMPairingInfo *pairing_info,
const char *name) {
*data = (BLEPairingData) {
.local_ediv = pairing_info->local_encryption_info.ediv,
.local_div = pairing_info->local_encryption_info.div,
.ltk = pairing_info->remote_encryption_info.ltk,
.rand = pairing_info->remote_encryption_info.rand,
.ediv = pairing_info->remote_encryption_info.ediv,
.irk = pairing_info->irk,
.identity = pairing_info->identity,
.csrk = pairing_info->csrk,
.is_local_encryption_info_valid = pairing_info->is_local_encryption_info_valid,
.is_remote_encryption_info_valid = pairing_info->is_remote_encryption_info_valid,
.is_remote_identity_info_valid = pairing_info->is_remote_identity_info_valid,
.is_remote_signing_info_valid = pairing_info->is_remote_signing_info_valid,
};
if (name) {
strncpy(data->name, name, BT_DEVICE_NAME_BUFFER_SIZE);
}
}
static void prv_shared_prf_storage_store_ble_pairing_data(BLEPairingData *data) {
#ifdef RECOVERY_FW
// The callers of bt_persistent_storage expect this store to be synchronous.
// In PRF bt_persistent_storage is just a wrapper for this
prv_update_and_cleanup_struct(data, sizeof(*data),
offsetof(SharedPRFData, ble_data));
#else
prv_update_and_cleanup_struct_async(data, sizeof(*data),
offsetof(SharedPRFData, ble_data));
#endif
}
void shared_prf_storage_store_ble_pairing_data(
const SMPairingInfo *pairing_info, const char *name, bool requires_address_pinning,
uint8_t flags) {
if (!pairing_info || sm_is_pairing_info_empty(pairing_info)) {
PBL_LOG(LOG_LEVEL_WARNING, "PRF Storage: Attempting to store an NULL or empty pairing info");
return;
}
#ifdef RECOVERY_FW
BLEPairingData data;
prv_load_ble_pairing_data(&data, pairing_info, name);
prv_shared_prf_storage_store_ble_pairing_data(&data);
#else
prv_lock_pending_bonding();
{
shared_prf_storage_erase_ble_pairing_data();
prv_load_ble_pairing_data(&s_pending_bondings.pending_data.ble_data, pairing_info, name);
s_pending_bondings.has_ble_pairing_pending = true;
prv_shared_prf_reschedule_writeback_timer();
}
prv_unlock_pending_bonding();
#endif
}
void shared_prf_storage_erase_ble_pairing_data(void) {
BLEPairingData empty_data = {};
// Call the version that doesn't do a flash erase because we are only writing zeros.
prv_update_and_cleanup_struct_no_erase(&empty_data, sizeof(empty_data),
offsetof(SharedPRFData, ble_data));
}
///////////////////////////////////////////////////////////////////////////////////////////////////
//! BT Classic Pairing Data
bool shared_prf_storage_get_bt_classic_pairing_data(BTDeviceAddress *addr_out,
char *device_name_out,
SM128BitKey *link_key_out,
uint8_t *platform_bits) {
SharedPRFData data;
prv_fetch_struct(&data);
bool result = false;
const BTDeviceAddress invalid_address = (BTDeviceAddress) {};
if (memcmp(&data.bt_classic_data.address, &invalid_address, sizeof(invalid_address)) == 0) {
PBL_LOG(LOG_LEVEL_WARNING, "Invalid pairing stored");
goto done;
}
if (addr_out) {
*addr_out = data.bt_classic_data.address;
}
if (link_key_out) {
*link_key_out = data.bt_classic_data.link_key;
}
if (platform_bits) {
*platform_bits = data.bt_classic_data.platform_bits;
}
if (device_name_out) {
strcpy(device_name_out, data.bt_classic_data.name);
}
result = true;
done:
prv_cleanup_struct(&data);
return result;
}
static void prv_load_bt_classic_pairing_data(
BTClassicPairingData *data, BTDeviceAddress *addr, const char *device_name,
SM128BitKey *link_key, uint8_t platform_bits) {
*data = (BTClassicPairingData) {
.address = *addr,
.link_key = *link_key,
.platform_bits = platform_bits
};
strncpy(data->name, device_name, BT_DEVICE_NAME_BUFFER_SIZE);
}
static void prv_shared_prf_storage_store_bt_classic_pairing_data(
BTDeviceAddress *addr, const char *device_name, SM128BitKey *link_key,
uint8_t platform_bits) {
if (link_key) {
// New pairing
BTClassicPairingData data;
prv_load_bt_classic_pairing_data(&data, addr, device_name, link_key, platform_bits);
prv_update_and_cleanup_struct_async(&data,
sizeof(BTClassicPairingData),
offsetof(SharedPRFData, bt_classic_data));
} else {
// Just updating the name
prv_update_and_cleanup_struct_async(device_name, MEMBER_SIZE(BTClassicPairingData, name),
offsetof(SharedPRFData, bt_classic_data) + offsetof(BTClassicPairingData, name));
}
}
void shared_prf_storage_store_bt_classic_pairing_data(
BTDeviceAddress *addr, const char *device_name, SM128BitKey *link_key, uint8_t platform_bits) {
if (!addr || !device_name) {
PBL_LOG(LOG_LEVEL_WARNING, "PRF Storage: Can't store this BT classic pairing");
return;
}
#ifdef RECOVERY_FW
prv_shared_prf_storage_store_bt_classic_pairing_data(addr, device_name, link_key, platform_bits);
#else
prv_lock_pending_bonding();
{
shared_prf_storage_erase_bt_classic_pairing_data();
prv_load_bt_classic_pairing_data(&s_pending_bondings.pending_data.bt_classic_data, addr,
device_name, link_key, platform_bits);
s_pending_bondings.has_bt_classic_pairing_pending = true;
prv_shared_prf_reschedule_writeback_timer();
}
prv_unlock_pending_bonding();
#endif
}
void shared_prf_storage_store_platform_bits(uint8_t platform_bits) {
prv_update_and_cleanup_struct_async(&platform_bits,
sizeof(platform_bits),
offsetof(SharedPRFData, bt_classic_data) +
offsetof(BTClassicPairingData, platform_bits));
}
void shared_prf_storage_erase_bt_classic_pairing_data(void) {
BTClassicPairingData empty_data = {};
// Call the version that doesn't do a flash erase because we are only writing zeros.
prv_update_and_cleanup_struct_no_erase(&empty_data, sizeof(empty_data),
offsetof(SharedPRFData, bt_classic_data));
}
///////////////////////////////////////////////////////////////////////////////////////////////////
//! Getting Started Is Complete
bool shared_prf_storage_get_getting_started_complete(void) {
SharedPRFData data;
prv_fetch_struct(&data);
const bool is_complete = data.getting_started_is_complete;
prv_cleanup_struct(&data);
return is_complete;
}
void shared_prf_storage_set_getting_started_complete(bool is_complete) {
prv_update_and_cleanup_struct_async(&is_complete,
sizeof(is_complete),
offsetof(SharedPRFData, getting_started_is_complete));
}
///////////////////////////////////////////////////////////////////////////////////////////////////
//! Factory Reset
void shared_prf_storage_wipe_all(void) {
SharedPRFData data;
prv_get_empty_struct(&data);
prv_update_and_cleanup_struct(&data, sizeof(data), 0);
}
static void prv_system_task_prf_update_cb(void *unused) {
PBL_LOG(LOG_LEVEL_DEBUG, "Syncing pairing information to SPRF");
prv_lock_pending_bonding();
{
bool le_bonding_update = s_pending_bondings.has_ble_pairing_pending;
bool classic_bonding_update = s_pending_bondings.has_bt_classic_pairing_pending;
SharedPRFData *data = &s_pending_bondings.pending_data;
if (le_bonding_update && classic_bonding_update) {
prv_update_and_cleanup_struct_async(
&data->ble_data, sizeof(BLEPairingData) + sizeof(BTClassicPairingData),
offsetof(SharedPRFData, ble_data));
} else if (classic_bonding_update) {
prv_update_and_cleanup_struct_async(&data->bt_classic_data, sizeof(BTClassicPairingData),
offsetof(SharedPRFData, bt_classic_data));
} else if (le_bonding_update) {
prv_shared_prf_storage_store_ble_pairing_data(&data->ble_data);
}
memset(&s_pending_bondings, 0x00, sizeof(s_pending_bondings));
}
prv_unlock_pending_bonding();
}
static void prv_async_shared_prf_update_timer_cb(void *data) {
system_task_add_callback(prv_system_task_prf_update_cb, NULL);
prv_lock_pending_bonding();
{
regular_timer_remove_callback(&s_shared_prf_writeback_timer);
}
prv_unlock_pending_bonding();
}
void shared_prf_storage_init(void) {
s_pending_data_mutex = mutex_create();
s_shared_prf_writeback_timer = (const RegularTimerInfo) {
.cb = prv_async_shared_prf_update_timer_cb,
};
}
///////////////////////////////////////////////////////////////////////////////////////////////////
//! Pinned Address Stubs
bool shared_prf_storage_get_ble_pinned_address(BTDeviceAddress *address_out) {
return false;
}
//! Stores the new BLE Pinned Address in the shared storage.
void shared_prf_storage_set_ble_pinned_address(const BTDeviceAddress *address) {
}
void command_force_shared_prf_flush(void) {
if (regular_timer_is_scheduled(&s_shared_prf_writeback_timer)) {
regular_timer_remove_callback(&s_shared_prf_writeback_timer);
s_shared_prf_writeback_timer.cb(NULL);
}
}
//! For unit tests
RegularTimerInfo *shared_prf_storage_get_writeback_timer(void) {
return &s_shared_prf_writeback_timer;
}
@@ -0,0 +1,84 @@
/*
* Copyright 2024 Google LLC
*
* 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.
*/
#pragma once
#include "util/attributes.h"
#include <bluetooth/bluetooth_types.h>
#include <bluetooth/sm_types.h>
//! Used to version the struct if we have to add additional fields in the future.
//! 1: Added BLE and BT Classic pairing data
//! 2: Added getting started is complete bit
//! 3: Added remote Rand, remote EDIV, local DIV, local EDIV, is_..._valid flags, local device name
#define SHARED_PRF_STORAGE_VERSION 3
typedef struct PACKED {
// Remote device name
char name[BT_DEVICE_NAME_BUFFER_SIZE];
// DIV / EDIV that was handed to the remote with our LTK (used when Pebble is Slave):
uint16_t local_ediv;
uint16_t local_div;
// Remote encryption info (used when Pebble is Master):
SMLongTermKey ltk;
uint64_t rand;
uint16_t ediv;
// Remote identity info (used when Pebble is Slave):
SMIdentityResolvingKey irk;
BTDeviceInternal identity;
// Remote signature key:
SM128BitKey csrk;
//! True if local_div and local_ediv are valid
bool is_local_encryption_info_valid:1;
//! True if ltk, rand and ediv are valid
bool is_remote_encryption_info_valid:1;
//! True if irk and identity are valid
bool is_remote_identity_info_valid:1;
//! True if csrk is valid
//! @note Since iOS 9, CSRK is no longer exchanged.
bool is_remote_signing_info_valid:1;
} BLEPairingData;
typedef struct PACKED {
BTDeviceAddress address;
SM128BitKey link_key;
char name[BT_DEVICE_NAME_BUFFER_SIZE];
uint8_t platform_bits;
} BTClassicPairingData;
typedef struct PACKED {
uint32_t version;
// Customized local device name, or zero-length string if the default device name should be used
char local_device_name[BT_DEVICE_NAME_BUFFER_SIZE];
SM128BitKey root_keys[SMRootKeyTypeNum]; // ER and IR key
// We rely on these two pieces of data being adjacent to each other
BLEPairingData ble_data;
BTClassicPairingData bt_classic_data;
bool getting_started_is_complete;
} SharedPRFData;
@@ -0,0 +1,672 @@
/*
* Copyright 2024 Google LLC
*
* 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.
*/
#include "services/common/shared_prf_storage/shared_prf_storage.h"
#include "shared_prf_storage_private.h"
#include "drivers/flash.h"
#include "flash_region/flash_region.h"
#include "kernel/pbl_malloc.h"
#include "system/logging.h"
#include "system/passert.h"
#include "util/crc32.h"
#include <btutil/sm_util.h>
#include <os/mutex.h>
#define SPRF_REGION_SIZE (FLASH_REGION_SHARED_PRF_STORAGE_END - \
FLASH_REGION_SHARED_PRF_STORAGE_BEGIN)
#define SPRF_NUM_PAGES (SPRF_REGION_SIZE / sizeof(SharedPRFData))
#define SPRF_PAGE_FLASH_ADDR(idx) (FLASH_REGION_SHARED_PRF_STORAGE_BEGIN + \
(idx * sizeof(SharedPRFData)))
// CRC Unwritten state and size
#define SPRF_UNWRITTEN_CRC ((uint32_t)0xFFFFFFFF)
#define SPRF_CRC_SIZE (sizeof(uint32_t))
// Accessors to a fields data and size. Basically skips over the CRC for the field.
#define SPRF_FIELD_DATA(f) (((uint8_t *)f) + SPRF_CRC_SIZE)
#define SPRF_FIELD_DATA_SIZE(sz) (sz - SPRF_CRC_SIZE)
// Accessors for the CRC of a field
// A field always has the CRC as the first element, and the CRC is 4 bytes long
#define FIELD_CRC_FROM_FIELD(f) (*(uint32_t *)f)
// 'DATA', or 'd', is SharedPRFData struct. Offset, or 'off', is the offset of the field from the
// data struct.
#define FIELD_CRC_FROM_DATA(d, off) *((uint32_t *)(((uint8_t *)d) + off))
// Tests if a bit is set in the flags variable
#define SPRF_FLAG_IS_SET(flags, bit) (flags & bit)
// Returns a bit in the correct place
#define SPRF_FLAG_GET_BIT(b, bit) ((b) ? bit : 0)
#define SPRF_CUR_VERSION 0x02
// Keeps track of the current page within the region that the valid (or empty) page is
static uint32_t s_valid_page_idx;
static PebbleMutex *s_sprf_mutex;
//
// Helper functions
//
static void prv_lock(void) {
mutex_lock(s_sprf_mutex);
}
static void prv_unlock(void) {
mutex_unlock(s_sprf_mutex);
}
static bool prv_buffer_empty(const uint8_t *buf, size_t num_bytes) {
for (uint32_t i = 0; i < num_bytes; i++) {
if (buf[i] != 0xFF) {
return false;
}
}
return true;
}
static uint32_t prv_current_page_flash_addr(void) {
return SPRF_PAGE_FLASH_ADDR(s_valid_page_idx);
}
static SprfMagic prv_get_magic_for_page(uint32_t page) {
SprfMagic magic;
flash_read_bytes((uint8_t *)&magic, SPRF_PAGE_FLASH_ADDR(page), sizeof(magic));
return magic;
}
//
// Struct validators
//
//! Pass a field (starting at address of CRC) and a field size. Return whether the field is valid.
//! A valid struct means that:
// 1. The field has not been written to (both CRC and field data are blank)
// 2. The CRC and the written data match
static bool prv_field_valid(const uint8_t *field, size_t field_size) {
const bool empty = prv_buffer_empty(field, field_size);
if (empty) {
return true;
}
const uint32_t field_crc = *(uint32_t*)field;
const bool valid_crc = (field_crc ==
crc32(CRC32_INIT, SPRF_FIELD_DATA(field), SPRF_FIELD_DATA_SIZE(field_size)));
return valid_crc;
}
//! Return whether the entire SharedPRFData given as an argument is valid.
//! It checks:
//! 1. That the struct header is either ValidEntry or UnpopulatedEntry.
//! 2. If the struct is an UnpopulatedEntry, it ensures the struct is entirely empty
//! 3. If the struct is *not* an UnpopulatedEntry, it ensures that each field is either empty
//! or written with a valid CRC.
static bool prv_valid_struct(SharedPRFData *data) {
if (data->magic == SprfMagic_UnpopulatedEntry &&
prv_buffer_empty((uint8_t *)data, sizeof(*data))) {
return true;
}
if (data->magic == SprfMagic_ValidEntry &&
(prv_field_valid((uint8_t *)&data->root_keys, sizeof(data->root_keys)) &&
prv_field_valid((uint8_t *)&data->ble_pairing_data, sizeof(data->ble_pairing_data)) &&
prv_field_valid((uint8_t *)&data->ble_pairing_name, sizeof(data->ble_pairing_name)) &&
prv_field_valid((uint8_t *)&data->pinned_address, sizeof(data->pinned_address)) &&
prv_field_valid((uint8_t *)&data->local_name, sizeof(data->local_name)) &&
prv_field_valid((uint8_t *)&data->getting_started, sizeof(data->getting_started)))) {
return true;
}
return false;
}
// Stored struct setters
static void prv_write_to_current_page(SharedPRFData *data, bool write_metadata) {
if (data) {
if (write_metadata) {
data->magic = SprfMagic_ValidEntry;
data->version = SPRF_CUR_VERSION;
}
flash_write_bytes((uint8_t *)data, prv_current_page_flash_addr(), sizeof(*data));
}
}
static void prv_erase_region_and_save(SharedPRFData *data) {
flash_region_erase_optimal_range_no_watchdog(FLASH_REGION_SHARED_PRF_STORAGE_BEGIN,
FLASH_REGION_SHARED_PRF_STORAGE_BEGIN,
FLASH_REGION_SHARED_PRF_STORAGE_END,
FLASH_REGION_SHARED_PRF_STORAGE_END);
s_valid_page_idx = 0;
prv_write_to_current_page(data, false);
}
static void prv_invalidate_current_page(void) {
PBL_LOG(LOG_LEVEL_DEBUG, "Invalidating current page: #%"PRIu32, s_valid_page_idx);
// First, check if the page is Unpopulated
SprfMagic magic = prv_get_magic_for_page(s_valid_page_idx);
if (magic == SprfMagic_UnpopulatedEntry) {
// This page is already Unpopulated. No need for invalidating
return;
}
// Invalidate current page
SprfMagic new_magic = SprfMagic_InvalidatedEntry;
flash_write_bytes((uint8_t *)&new_magic, prv_current_page_flash_addr(), sizeof(SprfMagic));
s_valid_page_idx++;
// Sanity check to make sure that the page we are moving to is actually empty.
if ((s_valid_page_idx >= SPRF_NUM_PAGES) ||
(prv_get_magic_for_page(s_valid_page_idx) != SprfMagic_UnpopulatedEntry)) {
PBL_LOG(LOG_LEVEL_WARNING, "Ran out of pages or found corrupted next page, erasing region");
// NOTE: This should not happen often. On boot, we delete and rewrite the region if >75% of
// regions are filled. In the worst case, this will happen if the user pair/repairs
// NUM_REGIONS * .25 times without rebooting in between. (e.g. 16 pages.
// We boot up on sector 12. User pairs 4 times, we now want to access page 16,
// that is past our region, we need to clean up.
//
// We've run out of blank pages. Delete the entire region and roll around to the front.
// This will take some time.
prv_erase_region_and_save(NULL);
s_valid_page_idx = 0;
}
}
//
// SharedPRFData allocators, deallocators, and getters
//
static void prv_fetch_struct(SharedPRFData *data_out) {
flash_read_bytes((uint8_t *)data_out, prv_current_page_flash_addr(), sizeof(*data_out));
if (!prv_valid_struct(data_out)) {
PBL_LOG(LOG_LEVEL_WARNING, "Shared PRF Storage sector # %"PRIu32" is corrupted. Invalidating"
" and starting a new one", s_valid_page_idx);
prv_invalidate_current_page();
memset(data_out, 0xFF, sizeof(*data_out));
}
}
static SharedPRFData *prv_alloc_and_fetch_struct(void) {
SharedPRFData *data = kernel_zalloc_check(sizeof(SharedPRFData));
prv_fetch_struct(data);
return data;
}
static void prv_dealloc_struct(SharedPRFData *data) {
kernel_free(data);
}
static void prv_persist_field(uint8_t *field, size_t offset, size_t field_size, bool calc_crc) {
SharedPRFData *data = prv_alloc_and_fetch_struct();
const size_t field_data_size = SPRF_FIELD_DATA_SIZE(field_size);
const uint32_t old_crc = FIELD_CRC_FROM_DATA(data, offset);
const uint32_t new_crc = (calc_crc) ? crc32(CRC32_INIT, SPRF_FIELD_DATA(field), field_data_size)
: SPRF_UNWRITTEN_CRC;
const bool same_data =
(0 == memcmp(SPRF_FIELD_DATA(field), SPRF_FIELD_DATA(((uint8_t *)data) + offset),
field_data_size));
if (data->magic == SprfMagic_UnpopulatedEntry) {
// Struct that is written is currently unpopulated. Set it up and write
// the struct's Magic and Version
prv_write_to_current_page(data, true);
} else if ((old_crc == new_crc) && same_data) {
// We are trying to write the same data, ignore the write
goto cleanup;
} else if (old_crc != SPRF_UNWRITTEN_CRC) {
// We are writing different data. Clear the field, write the struct again, and later write
// the new data in the empty field
memset(((uint8_t *)data) + offset, 0xFF, field_size);
prv_invalidate_current_page();
prv_write_to_current_page(data, true);
}
PBL_LOG(LOG_LEVEL_DEBUG, "Overwriting SPRF field at offset %d, size %d",
(int)offset, (int)field_size);
// write the crc first so it's easier to detect a non empty field (we can just read if the CRC is
// not 0xFFFFFFFF instead of comparing all bytes.
*(uint32_t *)field = new_crc;
flash_write_bytes(field, prv_current_page_flash_addr() + offset, field_size);
cleanup:
prv_dealloc_struct(data);
}
static void prv_erase_field(size_t offset, size_t field_size) {
SharedPRFData *data = prv_alloc_and_fetch_struct();
uint8_t *field_ptr = ((uint8_t *)data) + offset;
memset(field_ptr, 0xFF, field_size);
prv_persist_field(field_ptr, offset, field_size, false);
prv_dealloc_struct(data);
}
static bool prv_fetch_field(uint8_t *field_out, size_t offset, size_t field_size) {
flash_read_bytes(field_out, prv_current_page_flash_addr() + offset, field_size);
if (!prv_field_valid(field_out, field_size)) {
// If corrupted field, delete entire page
PBL_LOG(LOG_LEVEL_WARNING, "Shared PRF Storage sector # %"PRIu32" is corrupted. Invalidating"
" and starting a new one", s_valid_page_idx);
prv_invalidate_current_page();
return false;
}
uint32_t written_crc = FIELD_CRC_FROM_FIELD(field_out);
if (written_crc == SPRF_UNWRITTEN_CRC) {
// If empty field, return that we have invalid data too
return false;
}
return true;
}
#define SPRF_PERSIST_FIELD(data, name) \
prv_persist_field((uint8_t *)&data, offsetof(SharedPRFData, name), sizeof(data), true)
#define SPRF_ERASE_FIELD(name) \
prv_erase_field(offsetof(SharedPRFData, name), sizeof(((SharedPRFData *)0)->name))
#define SPRF_FETCH_FIELD(data, name) \
prv_fetch_field((uint8_t *)&data, offsetof(SharedPRFData, name), sizeof(data))
//!
//! SharedPRFStorage API
//!
//! Scans through the shared PRF flash region and finds the valid entry.
//! (Should only ever be one!) If we are > 75% through the shared PRF region,
//! erase the sector and re-write the info at offset 0. We want to make the chance
//! of blocking on an erase ~0, by doing this prep on init.
void shared_prf_storage_init(void) {
s_sprf_mutex = mutex_create();
prv_lock();
{
s_valid_page_idx = SPRF_PAGE_IDX_INVALID;
SharedPRFData data = {};
SprfMagic page_magic = 0;
for (uint32_t i = 0; i < SPRF_NUM_PAGES; i++) {
page_magic = prv_get_magic_for_page(i);
// Check the magic to see if we need to investigate further and read the entire contents.
if (page_magic == SprfMagic_ValidEntry || page_magic == SprfMagic_UnpopulatedEntry) {
flash_read_bytes((uint8_t *) &data, SPRF_PAGE_FLASH_ADDR(i), sizeof(data));
if (prv_valid_struct(&data)) {
s_valid_page_idx = i;
break;
}
}
}
// Keep a write offset, this won't work when we try to roll over to the other 25% of the sectors
if (s_valid_page_idx == SPRF_PAGE_IDX_INVALID) {
prv_erase_region_and_save(NULL);
} else if (s_valid_page_idx > (SPRF_MAX_NUM_PAGES_MULT(SPRF_NUM_PAGES))) {
prv_erase_region_and_save(&data);
}
}
prv_unlock();
}
void shared_prf_storage_wipe_all(void) {
prv_lock();
prv_invalidate_current_page();
prv_unlock();
}
//!
//! Custom Local Device Name APIs
//!
bool shared_prf_storage_get_local_device_name(char *local_device_name_out, size_t max_size) {
bool rv;
prv_lock();
{
SprfLocalName data;
rv = SPRF_FETCH_FIELD(data, local_name);
if (!rv) {
goto unlock;
}
if (local_device_name_out) {
strncpy(local_device_name_out, data.name, max_size);
local_device_name_out[max_size - 1] = 0;
}
// Is not zero length?
rv = (data.name[0] != 0);
}
unlock:
prv_unlock();
return rv;
}
void shared_prf_storage_set_local_device_name(const char *local_device_name) {
prv_lock();
{
SprfLocalName data = {};
if (local_device_name) {
strncpy(data.name, local_device_name, sizeof(data.name));
SPRF_PERSIST_FIELD(data, local_name);
} else {
SPRF_ERASE_FIELD(local_name);
}
}
prv_unlock();
}
//!
//! BLE Root Key APIs
//!
bool shared_prf_storage_get_root_key(SMRootKeyType key_type, SM128BitKey *key_out) {
bool rv;
prv_lock();
{
SprfRootKeys data;
rv = SPRF_FETCH_FIELD(data, root_keys);
if (!rv) {
goto unlock;
}
SM128BitKey nil_key = {};
if (0 == memcmp(&nil_key, &data.keys[key_type], sizeof(nil_key))) {
rv = false;
goto unlock;
}
if (key_out) {
memcpy(key_out, &data.keys[key_type], sizeof(*key_out));
}
rv = true;
}
unlock:
prv_unlock();
return rv;
}
void shared_prf_storage_set_root_keys(SM128BitKey *keys_in) {
prv_lock();
{
SprfRootKeys data = {};
if (keys_in) {
memcpy(&data.keys, keys_in, sizeof(data.keys));
}
SPRF_PERSIST_FIELD(data, root_keys);
}
prv_unlock();
}
//!
//! BLE Pairing Data APIs
//!
bool shared_prf_storage_get_ble_pairing_data(SMPairingInfo *pairing_info_out, char *name_out,
bool *requires_address_pinning_out,
uint8_t *flags) {
bool rv;
prv_lock();
{
SprfBlePairingData data;
rv = SPRF_FETCH_FIELD(data, ble_pairing_data);
if (!rv) {
goto unlock;
}
if (!data.fields) {
// The pairing stored is empty
rv = false;
goto unlock;
}
if (pairing_info_out) {
*pairing_info_out = (SMPairingInfo) {
.local_encryption_info.ltk = data.l_ltk,
.local_encryption_info.ediv = data.l_ediv,
.local_encryption_info.rand = data.l_rand,
.remote_encryption_info.ltk = data.r_ltk,
.remote_encryption_info.ediv = data.r_ediv,
.remote_encryption_info.rand = data.r_rand,
.irk = data.irk,
.identity = data.identity,
.csrk = data.csrk,
.is_mitm_protection_enabled = data.is_mitm_protection_enabled,
.is_local_encryption_info_valid =
SPRF_FLAG_IS_SET(data.fields, SprfValidFields_LocalEncryptionInfoValid),
.is_remote_encryption_info_valid =
SPRF_FLAG_IS_SET(data.fields, SprfValidFields_RemoteEncryptionInfoValid),
.is_remote_identity_info_valid =
SPRF_FLAG_IS_SET(data.fields, SprfValidFields_RemoteIdentityInfoValid),
.is_remote_signing_info_valid =
SPRF_FLAG_IS_SET(data.fields, SprfValidFields_RemoteSigningInfoValid),
};
}
if (requires_address_pinning_out) {
*requires_address_pinning_out = data.requires_address_pinning;
}
if (flags) {
*flags = data.flags;
}
if (name_out) {
SprfBlePairingName name_data = {};
const bool name_rv = SPRF_FETCH_FIELD(name_data, ble_pairing_name);
// Should we return a failure on a failed name get?
if (name_rv) {
strncpy(name_out, name_data.name, BT_DEVICE_NAME_BUFFER_SIZE);
} else {
name_out[0] = '\0';
}
}
rv = true;
}
unlock:
prv_unlock();
return rv;
}
void shared_prf_storage_store_ble_pairing_data(
const SMPairingInfo *pairing_info, const char *name, bool requires_address_pinning,
uint8_t flags) {
if (!pairing_info || sm_is_pairing_info_empty(pairing_info)) {
PBL_LOG(LOG_LEVEL_WARNING, "PRF Storage: Attempting to store an NULL or empty pairing info");
return;
}
prv_lock();
{
SprfBlePairingData data = (SprfBlePairingData) {
.l_ediv = pairing_info->local_encryption_info.ediv,
.l_ltk = pairing_info->local_encryption_info.ltk,
.l_rand = pairing_info->local_encryption_info.rand,
.r_ediv = pairing_info->remote_encryption_info.ediv,
.r_ltk = pairing_info->remote_encryption_info.ltk,
.r_rand = pairing_info->remote_encryption_info.rand,
.irk = pairing_info->irk,
.identity = pairing_info->identity,
.csrk = pairing_info->csrk,
.is_mitm_protection_enabled = pairing_info->is_mitm_protection_enabled,
.requires_address_pinning = requires_address_pinning,
.flags = flags,
.fields = SPRF_FLAG_GET_BIT(pairing_info->is_local_encryption_info_valid,
SprfValidFields_LocalEncryptionInfoValid) |
SPRF_FLAG_GET_BIT(pairing_info->is_remote_encryption_info_valid,
SprfValidFields_RemoteEncryptionInfoValid) |
SPRF_FLAG_GET_BIT(pairing_info->is_remote_identity_info_valid,
SprfValidFields_RemoteIdentityInfoValid) |
SPRF_FLAG_GET_BIT(pairing_info->is_remote_signing_info_valid,
SprfValidFields_RemoteSigningInfoValid),
};
SPRF_PERSIST_FIELD(data, ble_pairing_data);
if (name) {
// only persist name if one is actually included
SprfBlePairingName name_data = {};
strncpy(name_data.name, name, sizeof(name_data.name));
SPRF_PERSIST_FIELD(name_data, ble_pairing_name);
}
}
prv_unlock();
}
void shared_prf_storage_erase_ble_pairing_data(void) {
prv_lock();
{
SPRF_ERASE_FIELD(ble_pairing_data);
SPRF_ERASE_FIELD(ble_pairing_name);
}
prv_unlock();
}
//!
//! Getting started bit
//!
bool shared_prf_storage_get_ble_pinned_address(BTDeviceAddress *address_out) {
bool rv;
prv_lock();
{
SprfPinnedAddress data;
rv = SPRF_FETCH_FIELD(data, pinned_address);
if (!rv) {
goto unlock;
}
if (address_out) {
*address_out = data.pinned_address;
}
rv = true;
}
unlock:
prv_unlock();
return rv;
}
//! Stores the new BLE Pinned Address in the shared storage.
void shared_prf_storage_set_ble_pinned_address(const BTDeviceAddress *address) {
prv_lock();
{
if (address) {
SprfPinnedAddress data = {
.pinned_address = *address,
};
SPRF_PERSIST_FIELD(data, pinned_address);
} else {
SPRF_ERASE_FIELD(pinned_address);
}
}
prv_unlock();
}
//!
//! Getting started bit
//!
bool shared_prf_storage_get_getting_started_complete(void) {
bool rv;
prv_lock();
{
SprfGettingStarted data;
rv = SPRF_FETCH_FIELD(data, getting_started);
if (!rv) {
goto unlock;
}
rv = data.is_complete;
}
unlock:
prv_unlock();
return rv;
}
void shared_prf_storage_set_getting_started_complete(bool set) {
prv_lock();
{
SprfGettingStarted data = {
.is_complete = set
};
SPRF_PERSIST_FIELD(data, getting_started);
}
prv_unlock();
}
//!
//! Legacy Stubs for BT Classic - Should never be called so assert if they are!
//!
bool shared_prf_storage_get_bt_classic_pairing_data(
BTDeviceAddress *addr_out, char *device_name_out, SM128BitKey *link_key_out,
uint8_t *platform_bits) {
WTF;
}
void shared_prf_storage_store_bt_classic_pairing_data(
BTDeviceAddress *addr, const char *device_name, SM128BitKey *link_key,
uint8_t platform_bits) {
WTF;
}
void shared_prf_storage_store_platform_bits(uint8_t platform_bits) {
WTF;
}
void shared_prf_storage_erase_bt_classic_pairing_data(void) {
WTF;
}
void shared_prf_store_pairing_data(
SMPairingInfo *pairing_info, const char *device_name_ble, BTDeviceAddress *addr,
const char *device_name_classic, SM128BitKey *link_key, uint8_t platform_bits) {
WTF;
}
void command_force_shared_prf_flush(void) {
}
//!
//! Unit test functions
//!
uint16_t shared_prf_storage_get_valid_page_number(void) {
return s_valid_page_idx;
}
void shared_prf_storage_set_valid_page_number(uint32_t page_num) {
s_valid_page_idx = page_num;
}
@@ -0,0 +1,157 @@
/*
* Copyright 2024 Google LLC
*
* 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.
*/
#pragma once
#include "services/common/shared_prf_storage/shared_prf_storage.h"
#include <bluetooth/bluetooth_types.h>
#include <util/attributes.h>
#include <inttypes.h>
#define SPRF_PAGE_IDX_INVALID ((uint16_t)~0)
#define SPRF_MAX_NUM_PAGES_MULT(num) ((num) * 3 / 4)
typedef enum {
SprfValidFields_LocalEncryptionInfoValid = (1 << 0),
SprfValidFields_RemoteEncryptionInfoValid = (1 << 1),
SprfValidFields_RemoteIdentityInfoValid = (1 << 2),
SprfValidFields_RemoteSigningInfoValid = (1 << 3),
} SprfValidFields;
#ifndef __clang__
_Static_assert(sizeof(SprfValidFields) == 1, "SprfValidFields unexpected size");
#endif
typedef enum {
SprfMagic_ValidEntry = 0x46525053,
SprfMagic_UnpopulatedEntry = 0xFFFFFFFF,
SprfMagic_InvalidatedEntry = 0x0
} SprfMagic;
_Static_assert(sizeof(SprfMagic) == 4, "SprfMagic unexpected size");
//! This is the struct written out to the Shared PRF flash region
//!
//! It's composed of seven sub entries:
//! root_keys: Root keys (only identity since that is all Dialog needs)
//! ble_pairing_data: The pairing info for the device most recently paired to the watch
//! ble_pairing_name: The name of the device most recently paired to the watch
//! pinned_address: Pinned address of the device most recently paired to watch (may be empty)
//! getting_started: Captures whether or not we have gone through onboarding
//! local_name: Not used yet, but saved for future proofing
//! main_fw_scratch: A region for normal fw to stash info in the future if needed
//!
//! Each entry, or field, has its own crc which is written once the write of the field is complete.
//! @NOTE: The CRC _must_ be the first member of a field. There are static asserts to catch
//! this for current, please add a static assert for this if you create a new field
//!
//! A field is 'valid' iff a CRC of its contents matches the crc in flash
//! A field is 'unpopulated' if a memcmp of its contents is all 0xff.
//! A field is 'deleted' if its header has the value of SprfMagic_InvalidatedEntry
//! A field is 'corrupted' or 'partially written' if the content CRC does not match the field CRC
//!
//! On flash, there is a rolling list of entries. If a field above needs to be rewritten,
//! 'valid' entries must be copied from the current flash area to the next adjacent one.
//!
//! The shared PRF struct itself is defined as 256 bytes. Flash architectures have sectors
//! which are some 2^n multiple so this size pretty much guarantees that a divisible number
//! of structs can fit in the region allocated
typedef struct PACKED SprfRootKeys {
uint32_t crc;
SM128BitKey keys[SMRootKeyTypeNum];
} SprfRootKeys;
_Static_assert(offsetof(SprfRootKeys, crc) == 0, "crc must be the first field");
typedef struct PACKED SprfBlePairingData {
uint32_t crc; // CRC over the 'pairing_data' struct ('name' through 'fields')
// local encryption data
SMLongTermKey l_ltk; // 16 byte key
uint64_t l_rand;
uint16_t l_ediv;
// remote encryption data
uint16_t r_ediv;
SMLongTermKey r_ltk;
uint64_t r_rand;
SMIdentityResolvingKey irk; // 16 byte key
SMConnectionSignatureResolvingKey csrk; // 16 byte key
BTDeviceInternal identity;
SprfValidFields fields:8;
bool is_mitm_protection_enabled;
bool requires_address_pinning;
//! Added in SPRF_CUR_VERSION 2. In SPRF_CUR_VERSION 1, this field is always 0x00.
uint8_t flags;
} SprfBlePairingData;
_Static_assert(offsetof(SprfBlePairingData, crc) == 0, "crc must be the first field");
typedef struct PACKED SprfBlePairingName {
uint32_t crc;
char name[BT_DEVICE_NAME_BUFFER_SIZE];
} SprfBlePairingName;
_Static_assert(offsetof(SprfBlePairingName, crc) == 0, "crc must be the first field");
typedef struct PACKED SprfPinnedAddress {
uint32_t crc;
BTDeviceAddress pinned_address;
uint8_t rsvd[2];
} SprfPinnedAddress;
_Static_assert(offsetof(SprfPinnedAddress, crc) == 0, "crc must be the first field");
typedef struct PACKED SprfGettingStarted {
uint32_t crc;
bool is_complete;
uint8_t rsvd[3];
} SprfGettingStarted;
_Static_assert(offsetof(SprfGettingStarted, crc) == 0, "crc must be the first field");
typedef struct PACKED SprfLocalName {
// Not used today, but in the future we could replace 'Pebble XXXX' with
// a user friendly name, 'Chris' Pebble'
uint32_t crc;
char name[BT_DEVICE_NAME_BUFFER_SIZE];
} SprfLocalName;
_Static_assert(offsetof(SprfLocalName, crc) == 0, "crc must be the first field");
typedef struct PACKED SharedPRFData {
SprfMagic magic;
uint8_t version;
uint8_t rsvd[3];
SprfRootKeys root_keys;
SprfBlePairingData ble_pairing_data;
SprfBlePairingName ble_pairing_name;
SprfPinnedAddress pinned_address;
SprfGettingStarted getting_started;
SprfLocalName local_name;
// Occasions have arisen in the past where a region in sharedPRF that
// main FW can stash info related to a pairing. That is the intent of this region.
struct PACKED {
uint8_t rsvd[44];
} main_fw_scratch;
} SharedPRFData;
_Static_assert(BT_DEVICE_NAME_BUFFER_SIZE == 20, "Changing the length will break SharedPRF");
_Static_assert(sizeof(SharedPRFData) == 256, "SharedPRFData does not match expected size");