ardupilot/libraries/AP_CheckFirmware/AP_CheckFirmware_secure_com...

384 lines
12 KiB
C++
Raw Normal View History

/*
support checking board ID and firmware CRC in the bootloader
*/
#include "AP_CheckFirmware.h"
#include <AP_HAL/HAL.h>
#if AP_CHECK_FIRMWARE_ENABLED && AP_SIGNED_FIRMWARE && !defined(HAL_BOOTLOADER_BUILD)
#include "monocypher.h"
#include <AP_Math/crc.h>
extern const AP_HAL::HAL &hal;
uint8_t AP_CheckFirmware::session_key[8];
/*
make a session key
*/
static void make_session_key(uint8_t key[8])
{
struct {
uint32_t time_us;
uint8_t unique_id[12];
uint16_t rand1;
uint16_t rand2;
} data {};
static_assert(sizeof(data) % 4 == 0, "data must be multiple of 4 bytes");
// get data which will not apply on a different board, and includes some randomness
uint8_t uid_len = 12;
hal.util->get_system_id_unformatted(data.unique_id, uid_len);
data.time_us = AP_HAL::micros();
data.rand1 = get_random16();
data.rand2 = get_random16();
const uint64_t c64 = crc_crc64((const uint32_t *)&data, sizeof(data)/sizeof(uint32_t));
memcpy(key, (uint8_t *)&c64, 8);
}
/*
find public keys in bootloader, or return NULL if signature not found
this assumes the public keys are in the first sector
*/
const struct ap_secure_data *AP_CheckFirmware::find_public_keys(void)
{
#if CONFIG_HAL_BOARD == HAL_BOARD_CHIBIOS
const uint32_t page_size = hal.flash->getpagesize(0);
const uint32_t flash_addr = hal.flash->getpageaddr(0);
const uint8_t *flash = (const uint8_t *)flash_addr;
const uint8_t key[] = AP_PUBLIC_KEY_SIGNATURE;
return (const struct ap_secure_data *)memmem(flash, page_size, key, sizeof(key));
#else
return nullptr;
#endif
}
#if CONFIG_HAL_BOARD == HAL_BOARD_CHIBIOS
/*
return true if 1k of data is all 0xff (empty flash)
*/
static bool empty_1k(const uint8_t *data)
{
for (uint32_t i=0; i<1024; i++) {
if (data[i] != 0xFFU) {
return false;
}
}
return true;
}
#endif
/*
read bootloader into memory. This is complicated by the potential presence
of persistent data from temperature calibration at the end of the sector
Also note this assumes the public keys are in the first sector if
the bootloader covers more than one sector. This is a reasonable
assumption given the linker file
*/
AP_CheckFirmware::bl_data *AP_CheckFirmware::read_bootloader(void)
{
#if CONFIG_HAL_BOARD == HAL_BOARD_CHIBIOS
struct bl_data *bld = new bl_data;
if (bld == nullptr) {
return nullptr;
}
const uint32_t page_size = hal.flash->getpagesize(0);
const uint32_t flash_addr = hal.flash->getpageaddr(0);
const uint8_t *flash = (uint8_t *)flash_addr;
const uint16_t block_size = 1024;
uint16_t num_blocks = page_size / block_size;
/*
find first empty block
*/
for (uint16_t i=0; i<num_blocks; i++) {
if (empty_1k(&flash[block_size*i])) {
break;
}
bld->length1 += block_size;
}
bld->data1 = new uint8_t[bld->length1];
if (bld->data1 == nullptr) {
delete bld;
return nullptr;
}
memcpy(bld->data1, flash, bld->length1);
flash += bld->length1;
num_blocks -= bld->length1 / block_size;
/*
find first non-empty block, which should be the persistent data if-any
*/
bld->offset2 = bld->length1;
while (num_blocks > 0) {
if (!empty_1k(&flash[bld->offset2])) {
break;
}
num_blocks--;
bld->offset2 += block_size;
}
if (num_blocks > 0) {
// we have persistent data to save
bld->length2 = num_blocks * block_size;
bld->data2 = new uint8_t[bld->length2];
if (bld->data2 == nullptr) {
delete bld;
return nullptr;
}
memcpy(bld->data2, &flash[bld->offset2], bld->length2);
}
return bld;
#else
return nullptr;
#endif
}
/*
write bootloader from memory
*/
bool AP_CheckFirmware::write_bootloader(const struct bl_data *bld)
{
#if CONFIG_HAL_BOARD == HAL_BOARD_CHIBIOS
const uint32_t flash_addr = hal.flash->getpageaddr(0);
EXPECT_DELAY_MS(3000);
if (!hal.flash->erasepage(0)) {
GCS_SEND_TEXT(MAV_SEVERITY_ERROR, "Bootloader erase failed");
return false;
}
EXPECT_DELAY_MS(3000);
if (!hal.flash->write(flash_addr, bld->data1, bld->length1)) {
GCS_SEND_TEXT(MAV_SEVERITY_ERROR, "Bootloader write1 failed");
return false;
}
EXPECT_DELAY_MS(3000);
if (bld->length2 != 0 &&
!hal.flash->write(flash_addr+bld->offset2, bld->data2, bld->length2)) {
GCS_SEND_TEXT(MAV_SEVERITY_ERROR, "Bootloader write1 failed");
return false;
}
return true;
#else
return false;
#endif
}
/*
check signature in a command against bootloader public keys
*/
bool AP_CheckFirmware::check_signature(const mavlink_secure_command_t &pkt)
{
const struct ap_secure_data *sec_data = find_public_keys();
if (sec_data == nullptr) {
return false;
}
if (pkt.sig_length != 64) {
// monocypher signatures are 64 bytes
return false;
}
const uint8_t zero_key[AP_PUBLIC_KEY_LEN] {};
bool all_zero_keys = true;
/*
look over all public keys, if one matches then we are OK
*/
for (const auto &public_key : sec_data->public_key) {
if (memcmp(public_key.key, zero_key, AP_PUBLIC_KEY_LEN) == 0) {
continue;
}
all_zero_keys = false;
crypto_check_ctx ctx {};
crypto_check_ctx_abstract *actx = (crypto_check_ctx_abstract*)&ctx;
crypto_check_init(actx, &pkt.data[pkt.data_length], public_key.key);
crypto_check_update(actx, (const uint8_t*)&pkt.sequence, sizeof(pkt.sequence));
crypto_check_update(actx, (const uint8_t*)&pkt.operation, sizeof(pkt.operation));
crypto_check_update(actx, pkt.data, pkt.data_length);
if (pkt.operation != SECURE_COMMAND_GET_SESSION_KEY) {
crypto_check_update(actx, session_key, sizeof(session_key));
}
if (crypto_check_final(actx) == 0) {
// good signature
return true;
}
}
return all_zero_keys;
}
/*
set public keys in bootloader
*/
bool AP_CheckFirmware::set_public_keys(uint8_t key_idx, uint8_t num_keys, const uint8_t *key_data)
{
auto *bld = read_bootloader();
if (bld == nullptr) {
GCS_SEND_TEXT(MAV_SEVERITY_INFO, "Failed to load bootloader into memory");
return false;
}
const uint8_t key[] = AP_PUBLIC_KEY_SIGNATURE;
struct ap_secure_data *sec_data = (struct ap_secure_data *)memmem(bld->data1, bld->length1, key, sizeof(key));
if (sec_data == nullptr) {
delete bld;
GCS_SEND_TEXT(MAV_SEVERITY_INFO, "Failed to find key signature");
return false;
}
memcpy(sec_data->public_key[key_idx].key, key_data, num_keys*AP_PUBLIC_KEY_LEN);
/*
pack so non-zero keys are at the start
*/
const uint8_t zero_key[AP_PUBLIC_KEY_LEN] {};
uint8_t max_keys = AP_PUBLIC_KEY_MAX_KEYS;
for (uint8_t i=0; max_keys>1 && i<max_keys-1; i++) {
if (memcmp(zero_key, sec_data->public_key[i].key, AP_PUBLIC_KEY_LEN) == 0) {
memmove(sec_data->public_key[i].key, sec_data->public_key[i+1].key, AP_PUBLIC_KEY_LEN*(max_keys-(i+1)));
max_keys--;
i--;
}
}
memset(sec_data->public_key[max_keys-1].key, 0, AP_PUBLIC_KEY_LEN*(AP_PUBLIC_KEY_MAX_KEYS-max_keys));
bool ret = write_bootloader(bld);
delete bld;
return ret;
}
/*
handle a SECURE_COMMAND
*/
void AP_CheckFirmware::handle_secure_command(mavlink_channel_t chan, const mavlink_secure_command_t &pkt)
{
mavlink_secure_command_reply_t reply {};
reply.result = MAV_RESULT_UNSUPPORTED;
reply.sequence = pkt.sequence;
reply.operation = pkt.operation;
if (uint16_t(pkt.data_length) + uint16_t(pkt.sig_length) > sizeof(pkt.data)) {
reply.result = MAV_RESULT_DENIED;
goto send_reply;
}
if (!check_signature(pkt)) {
reply.result = MAV_RESULT_DENIED;
goto send_reply;
}
switch (pkt.operation) {
case SECURE_COMMAND_GET_SESSION_KEY: {
make_session_key(session_key);
reply.data_length = sizeof(session_key);
memcpy(reply.data, session_key, reply.data_length);
reply.result = MAV_RESULT_ACCEPTED;
break;
}
case SECURE_COMMAND_GET_PUBLIC_KEYS: {
const struct ap_secure_data *sec_data = find_public_keys();
if (pkt.data_length != 2) {
reply.result = MAV_RESULT_UNSUPPORTED;
goto send_reply;
}
const uint8_t key_idx = pkt.data[0];
uint8_t num_keys = pkt.data[1];
const uint8_t max_fetch = (sizeof(reply.data)-1) / AP_PUBLIC_KEY_LEN;
if (key_idx >= AP_PUBLIC_KEY_MAX_KEYS ||
num_keys > max_fetch ||
key_idx+num_keys > AP_PUBLIC_KEY_MAX_KEYS ||
sec_data == nullptr) {
reply.result = MAV_RESULT_FAILED;
goto send_reply;
}
// remove zero keys
const uint8_t zero_key[AP_PUBLIC_KEY_LEN] {};
while (num_keys > 0 &&
memcmp(zero_key, &sec_data->public_key[key_idx+num_keys-1], AP_PUBLIC_KEY_LEN) == 0) {
num_keys--;
}
reply.data_length = 1+num_keys*AP_PUBLIC_KEY_LEN;
reply.data[0] = key_idx;
memcpy(&reply.data[1], &sec_data->public_key[key_idx], reply.data_length-1);
reply.result = MAV_RESULT_ACCEPTED;
break;
}
case SECURE_COMMAND_SET_PUBLIC_KEYS: {
if (pkt.data_length < AP_PUBLIC_KEY_LEN+1) {
reply.result = MAV_RESULT_FAILED;
goto send_reply;
}
const uint8_t key_idx = pkt.data[0];
const uint8_t num_keys = (pkt.data_length-1) / AP_PUBLIC_KEY_LEN;
if (num_keys == 0) {
reply.result = MAV_RESULT_FAILED;
goto send_reply;
}
if (key_idx >= AP_PUBLIC_KEY_MAX_KEYS ||
key_idx+num_keys > AP_PUBLIC_KEY_MAX_KEYS) {
reply.result = MAV_RESULT_FAILED;
goto send_reply;
}
if (set_public_keys(key_idx, num_keys, &pkt.data[1])) {
GCS_SEND_TEXT(MAV_SEVERITY_ERROR, "Bootloader update OK");
reply.result = MAV_RESULT_ACCEPTED;
} else {
reply.result = MAV_RESULT_FAILED;
}
break;
}
case SECURE_COMMAND_REMOVE_PUBLIC_KEYS: {
if (pkt.data_length != 2) {
reply.result = MAV_RESULT_FAILED;
goto send_reply;
}
const uint8_t key_idx = pkt.data[0];
const uint8_t num_keys = pkt.data[1];
if (num_keys == 0) {
reply.result = MAV_RESULT_FAILED;
goto send_reply;
}
if (key_idx >= AP_PUBLIC_KEY_MAX_KEYS ||
key_idx+num_keys > AP_PUBLIC_KEY_MAX_KEYS) {
reply.result = MAV_RESULT_FAILED;
goto send_reply;
}
uint8_t *data = new uint8_t[num_keys*AP_PUBLIC_KEY_LEN];
if (data == nullptr) {
reply.result = MAV_RESULT_FAILED;
goto send_reply;
}
if (set_public_keys(key_idx, num_keys, data)) {
GCS_SEND_TEXT(MAV_SEVERITY_ERROR, "Bootloader update OK");
reply.result = MAV_RESULT_ACCEPTED;
} else {
reply.result = MAV_RESULT_FAILED;
}
delete[] data;
break;
}
}
send_reply:
// send reply
mavlink_msg_secure_command_reply_send_struct(chan, &reply);
}
/*
implement secure command operations for updating public keys
*/
void AP_CheckFirmware::handle_msg(mavlink_channel_t chan, const mavlink_message_t &msg)
{
switch (msg.msgid) {
case MAVLINK_MSG_ID_SECURE_COMMAND: {
mavlink_secure_command_t pkt;
mavlink_msg_secure_command_decode(&msg, &pkt);
handle_secure_command(chan, pkt);
break;
}
}
}
#endif // AP_CHECK_FIRMWARE_ENABLED