/* support checking board ID and firmware CRC in the bootloader */ #include "AP_CheckFirmware.h" #include #if AP_CHECK_FIRMWARE_ENABLED && AP_SIGNED_FIRMWARE && !defined(HAL_BOOTLOADER_BUILD) #include "monocypher.h" #include #if HAL_GCS_ENABLED #include #endif extern const AP_HAL::HAL &hal; /* 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 } /* return true if all keys are zeros */ bool AP_CheckFirmware::all_zero_keys(const struct ap_secure_data *sec_data) { const uint8_t zero_key[AP_PUBLIC_KEY_LEN] {}; /* 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) { return false; } } return true; } #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_NOTHROW 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; ilength1 += block_size; } bld->data1 = NEW_NOTHROW 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_NOTHROW 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 } #if HAL_GCS_ENABLED 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); } /* 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 (all_zero_keys(sec_data)) { // allow through if no keys are setup return true; } if (pkt.sig_length != 64) { // monocypher signatures are 64 bytes return false; } /* look over all public keys, if one matches then we are OK */ for (const auto &public_key : sec_data->public_key) { 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 false; } /* 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 && ipublic_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_NOTHROW 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 // HAL_GCS_ENABLED /* check that a bootloader is OK to flash. We don't want to allow flashing of a bootloader unless we either have no public keys setup or the bootloader has public keys embedded. This prevents an easy mistake of including an insecure bootloader in ROMFS with a secure build */ bool AP_CheckFirmware::check_signed_bootloader(const uint8_t *fw, uint32_t fw_size) { const struct ap_secure_data *sec_data = find_public_keys(); if (sec_data == nullptr || all_zero_keys(sec_data)) { // current bootloader doesn't have public keys, so OK to load any bootloader return true; } const uint8_t key[] = AP_PUBLIC_KEY_SIGNATURE; sec_data = (const struct ap_secure_data *)memmem(fw, fw_size, key, sizeof(key)); if (sec_data == nullptr || all_zero_keys(sec_data)) { // new bootloader doesn't have any public keys, not allowed return false; } return true; } #endif // AP_CHECK_FIRMWARE_ENABLED