mirror of
https://github.com/google/pebble
synced 2026-10-17 08:44:11 +02:00
Import of the watch repository from Pebble
This commit is contained in:
@@ -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");
|
||||
Reference in New Issue
Block a user