mirror of
https://github.com/trezor/trezor-firmware.git
synced 2025-01-31 09:50:58 +00:00
refactor(core): enclose monotonic counter to platform specific module
[no changelog]
This commit is contained in:
parent
d334b92073
commit
c1864a2a91
@ -149,6 +149,7 @@ SOURCE_TREZORHAL = [
|
||||
'embed/trezorhal/unix/fault_handlers.c',
|
||||
'embed/trezorhal/unix/flash.c',
|
||||
'embed/trezorhal/unix/flash_otp.c',
|
||||
'embed/trezorhal/unix/monoctr.c',
|
||||
'embed/trezorhal/unix/random_delays.c',
|
||||
'embed/trezorhal/unix/rng.c',
|
||||
'embed/trezorhal/unix/secret.c',
|
||||
|
@ -54,6 +54,7 @@
|
||||
|
||||
#include "lowlevel.h"
|
||||
#include "model.h"
|
||||
#include "monoctr.h"
|
||||
#include "version.h"
|
||||
|
||||
#include "memzero.h"
|
||||
@ -76,45 +77,17 @@ static const uint8_t * const BOARDLOADER_KEYS[] = {
|
||||
#endif
|
||||
};
|
||||
|
||||
#ifdef STM32U5
|
||||
uint8_t get_bootloader_min_version(void) {
|
||||
const uint8_t *counter_addr =
|
||||
flash_area_get_address(&SECRET_AREA, SECRET_MONOTONIC_COUNTER_OFFSET,
|
||||
SECRET_MONOTONIC_COUNTER_LEN);
|
||||
|
||||
ensure((counter_addr != NULL) * sectrue, "counter_addr is NULL");
|
||||
|
||||
int counter = 0;
|
||||
|
||||
for (int i = 0; i < SECRET_MONOTONIC_COUNTER_LEN / 16; i++) {
|
||||
secbool not_cleared = sectrue;
|
||||
for (int j = 0; j < 16; j++) {
|
||||
if (counter_addr[i * 16 + j] != 0xFF) {
|
||||
not_cleared = secfalse;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (not_cleared != sectrue) {
|
||||
counter++;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return counter;
|
||||
static uint8_t get_bootloader_min_version(void) {
|
||||
uint8_t version = 0;
|
||||
ensure(monoctr_read(MONOCTR_BOOTLOADER_VERSION, &version), "monoctr read");
|
||||
return version;
|
||||
}
|
||||
|
||||
void write_bootloader_min_version(uint8_t version) {
|
||||
static void write_bootloader_min_version(uint8_t version) {
|
||||
if (version > get_bootloader_min_version()) {
|
||||
for (int i = 0; i < version; i++) {
|
||||
uint32_t data[4] = {0};
|
||||
secret_write((uint8_t *)data, SECRET_MONOTONIC_COUNTER_OFFSET + i * 16,
|
||||
16);
|
||||
}
|
||||
ensure(monoctr_write(MONOCTR_BOOTLOADER_VERSION, version), "monoctr write");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
struct BoardCapabilities capabilities
|
||||
__attribute__((section(".capabilities_section"))) = {
|
||||
@ -197,11 +170,9 @@ static uint32_t check_sdcard(void) {
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef STM32U5
|
||||
if (hdr->monotonic < get_bootloader_min_version()) {
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
return hdr->codelen;
|
||||
}
|
||||
@ -310,7 +281,6 @@ int main(void) {
|
||||
#if defined USE_SD_CARD
|
||||
sdcard_init();
|
||||
|
||||
#ifdef STM32U5
|
||||
// If the bootloader is being updated from SD card, we need to preserve the
|
||||
// monotonic counter from the old bootloader. This is in case that the old
|
||||
// bootloader did not have the chance yet to write its monotonic counter to
|
||||
@ -327,7 +297,6 @@ int main(void) {
|
||||
check_image_contents(old_hdr, IMAGE_HEADER_SIZE, &BOOTLOADER_AREA))) {
|
||||
write_bootloader_min_version(old_hdr->monotonic);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (check_sdcard()) {
|
||||
return copy_sdcard() == sectrue ? 0 : 3;
|
||||
@ -348,14 +317,12 @@ int main(void) {
|
||||
ensure(check_image_contents(hdr, IMAGE_HEADER_SIZE, &BOOTLOADER_AREA),
|
||||
"invalid bootloader hash");
|
||||
|
||||
#ifdef STM32U5
|
||||
uint8_t bld_min_version = get_bootloader_min_version();
|
||||
ensure((hdr->monotonic >= bld_min_version) * sectrue,
|
||||
"BOOTLOADER DOWNGRADED");
|
||||
// Write the bootloader version to the secret area.
|
||||
// This includes the version of bootloader potentially updated from SD card.
|
||||
write_bootloader_min_version(hdr->monotonic);
|
||||
#endif
|
||||
|
||||
ensure_compatible_settings();
|
||||
|
||||
|
@ -69,6 +69,7 @@
|
||||
|
||||
#include "bootui.h"
|
||||
#include "messages.h"
|
||||
#include "monoctr.h"
|
||||
#include "rust_ui.h"
|
||||
#include "unit_variant.h"
|
||||
|
||||
@ -265,24 +266,10 @@ static secbool check_vendor_header_lock(const vendor_header *const vhdr) {
|
||||
#if PRODUCTION && !defined STM32U5
|
||||
|
||||
static void check_bootloader_version(void) {
|
||||
uint8_t bits[FLASH_OTP_BLOCK_SIZE];
|
||||
for (int i = 0; i < FLASH_OTP_BLOCK_SIZE * 8; i++) {
|
||||
if (i < VERSION_MONOTONIC) {
|
||||
bits[i / 8] &= ~(1 << (7 - (i % 8)));
|
||||
} else {
|
||||
bits[i / 8] |= (1 << (7 - (i % 8)));
|
||||
}
|
||||
}
|
||||
ensure(flash_otp_write(FLASH_OTP_BLOCK_BOOTLOADER_VERSION, 0, bits,
|
||||
FLASH_OTP_BLOCK_SIZE),
|
||||
NULL);
|
||||
|
||||
uint8_t bits2[FLASH_OTP_BLOCK_SIZE];
|
||||
ensure(flash_otp_read(FLASH_OTP_BLOCK_BOOTLOADER_VERSION, 0, bits2,
|
||||
FLASH_OTP_BLOCK_SIZE),
|
||||
NULL);
|
||||
|
||||
ensure(sectrue * (0 == memcmp(bits, bits2, FLASH_OTP_BLOCK_SIZE)),
|
||||
ensure(monoctr_write(MONOCTR_BOOTLOADER_VERSION, VERSION_MONOTONIC), NULL);
|
||||
uint8_t val = 0;
|
||||
ensure(monoctr_read(MONOCTR_BOOTLOADER_VERSION, &val), NULL);
|
||||
ensure(sectrue * (val == VERSION_MONOTONIC),
|
||||
"Bootloader downgrade protection");
|
||||
}
|
||||
|
||||
|
@ -9,6 +9,7 @@
|
||||
#define FLASH_OTP_BLOCK_VENDOR_HEADER_LOCK 2
|
||||
#define FLASH_OTP_BLOCK_RANDOMNESS 3
|
||||
#define FLASH_OTP_BLOCK_DEVICE_VARIANT 4
|
||||
#define FLASH_OTP_BLOCK_FIRMWARE_VERSION 5
|
||||
|
||||
#define STORAGE_AREAS_COUNT (2)
|
||||
|
||||
|
39
core/embed/trezorhal/monoctr.h
Normal file
39
core/embed/trezorhal/monoctr.h
Normal file
@ -0,0 +1,39 @@
|
||||
/*
|
||||
* This file is part of the Trezor project, https://trezor.io/
|
||||
*
|
||||
* Copyright (c) SatoshiLabs
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef TREZORHAL_MONOCTR
|
||||
#define TREZORHAL_MONOCTR
|
||||
|
||||
// Monoctr module provides monotonic counter functionality
|
||||
|
||||
#define MONOCTR_MAX_VALUE 63
|
||||
|
||||
#include <stdint.h>
|
||||
#include "secbool.h"
|
||||
|
||||
typedef enum {
|
||||
MONOCTR_BOOTLOADER_VERSION = 0,
|
||||
MONOCTR_FIRMWARE_VERSION = 1,
|
||||
} monoctr_type_t;
|
||||
|
||||
secbool monoctr_write(monoctr_type_t type, uint8_t value);
|
||||
|
||||
secbool monoctr_read(monoctr_type_t type, uint8_t* value);
|
||||
|
||||
#endif
|
@ -9,6 +9,7 @@
|
||||
|
||||
#define SECRET_MONOTONIC_COUNTER_OFFSET 48
|
||||
#define SECRET_MONOTONIC_COUNTER_LEN 1024
|
||||
#define SECRET_MONOTONIC_COUNTER2_OFFSET (SECRET_MONOTONIC_COUNTER_LEN + 48)
|
||||
|
||||
#define SECRET_BHK_OFFSET (1024 * 8)
|
||||
#define SECRET_BHK_LEN 32
|
||||
|
119
core/embed/trezorhal/stm32f4/monoctr.c
Normal file
119
core/embed/trezorhal/stm32f4/monoctr.c
Normal file
@ -0,0 +1,119 @@
|
||||
/*
|
||||
* This file is part of the Trezor project, https://trezor.io/
|
||||
*
|
||||
* Copyright (c) SatoshiLabs
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "monoctr.h"
|
||||
#include "flash_otp.h"
|
||||
#include "model.h"
|
||||
#include "string.h"
|
||||
|
||||
#if PRODUCTION
|
||||
static int get_otp_block(monoctr_type_t type) {
|
||||
switch (type) {
|
||||
case MONOCTR_BOOTLOADER_VERSION:
|
||||
return FLASH_OTP_BLOCK_BOOTLOADER_VERSION;
|
||||
case MONOCTR_FIRMWARE_VERSION:
|
||||
return FLASH_OTP_BLOCK_FIRMWARE_VERSION;
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
secbool monoctr_write(monoctr_type_t type, uint8_t value) {
|
||||
#if PRODUCTION
|
||||
if (value > MONOCTR_MAX_VALUE) {
|
||||
return secfalse;
|
||||
}
|
||||
|
||||
uint8_t bits[FLASH_OTP_BLOCK_SIZE];
|
||||
for (int i = 0; i < FLASH_OTP_BLOCK_SIZE * 8; i++) {
|
||||
if (i < value) {
|
||||
bits[i / 8] &= ~(1 << (7 - (i % 8)));
|
||||
} else {
|
||||
bits[i / 8] |= (1 << (7 - (i % 8)));
|
||||
}
|
||||
}
|
||||
|
||||
int block = get_otp_block(type);
|
||||
|
||||
if (block < 0) {
|
||||
return secfalse;
|
||||
}
|
||||
|
||||
ensure(flash_otp_write(block, 0, bits, FLASH_OTP_BLOCK_SIZE), NULL);
|
||||
|
||||
#endif
|
||||
return sectrue;
|
||||
}
|
||||
|
||||
secbool monoctr_read(monoctr_type_t type, uint8_t* value) {
|
||||
#if PRODUCTION
|
||||
uint8_t bits[FLASH_OTP_BLOCK_SIZE];
|
||||
|
||||
int block = get_otp_block(type);
|
||||
|
||||
if (block < 0) {
|
||||
return secfalse;
|
||||
}
|
||||
|
||||
ensure(flash_otp_read(block, 0, bits, FLASH_OTP_BLOCK_SIZE), NULL);
|
||||
|
||||
int result = 0;
|
||||
|
||||
int i;
|
||||
|
||||
// Iterate through each bit position in the bit field
|
||||
for (i = 0; i < FLASH_OTP_BLOCK_SIZE * 8; i++) {
|
||||
// Calculate the byte and bit index within the byte
|
||||
int byteIndex = i / 8;
|
||||
int bitIndex = 7 - (i % 8);
|
||||
|
||||
// Check if the current bit is 0
|
||||
if ((bits[byteIndex] & (1 << bitIndex)) == 0) {
|
||||
// If the bit is 0, increment the value
|
||||
result++;
|
||||
} else {
|
||||
// Stop when we find the first 1 bit
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (; i < FLASH_OTP_BLOCK_SIZE * 8; i++) {
|
||||
// Calculate the byte and bit index within the byte
|
||||
int byteIndex = i / 8;
|
||||
int bitIndex = 7 - (i % 8);
|
||||
if ((bits[byteIndex] & (1 << bitIndex)) == 0) {
|
||||
// If the bit is 0, return false - the monotonic counter is not valid
|
||||
return secfalse;
|
||||
}
|
||||
}
|
||||
|
||||
if (value != NULL) {
|
||||
*value = result;
|
||||
} else {
|
||||
return secfalse;
|
||||
}
|
||||
#else
|
||||
|
||||
*value = 0;
|
||||
|
||||
#endif
|
||||
|
||||
return sectrue;
|
||||
}
|
111
core/embed/trezorhal/stm32u5/monoctr.c
Normal file
111
core/embed/trezorhal/stm32u5/monoctr.c
Normal file
@ -0,0 +1,111 @@
|
||||
/*
|
||||
* This file is part of the Trezor project, https://trezor.io/
|
||||
*
|
||||
* Copyright (c) SatoshiLabs
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "monoctr.h"
|
||||
#include "flash_area.h"
|
||||
#include "model.h"
|
||||
#include "secret.h"
|
||||
|
||||
static int32_t get_offset(monoctr_type_t type) {
|
||||
switch (type) {
|
||||
case MONOCTR_BOOTLOADER_VERSION:
|
||||
return SECRET_MONOTONIC_COUNTER_OFFSET;
|
||||
case MONOCTR_FIRMWARE_VERSION:
|
||||
return SECRET_MONOTONIC_COUNTER2_OFFSET;
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
secbool monoctr_write(monoctr_type_t type, uint8_t value) {
|
||||
if (value > MONOCTR_MAX_VALUE) {
|
||||
return secfalse;
|
||||
}
|
||||
|
||||
int32_t offset = get_offset(type);
|
||||
|
||||
if (offset < 0) {
|
||||
return secfalse;
|
||||
}
|
||||
|
||||
for (int i = 0; i < value; i++) {
|
||||
uint32_t data[4] = {0};
|
||||
secret_write((uint8_t *)data, offset + i * 16, 16);
|
||||
}
|
||||
|
||||
return sectrue;
|
||||
}
|
||||
|
||||
secbool monoctr_read(monoctr_type_t type, uint8_t *value) {
|
||||
int32_t offset = get_offset(type);
|
||||
|
||||
if (offset < 0) {
|
||||
return secfalse;
|
||||
}
|
||||
|
||||
const uint8_t *counter_addr = flash_area_get_address(
|
||||
&SECRET_AREA, offset, SECRET_MONOTONIC_COUNTER_LEN);
|
||||
|
||||
if (counter_addr == NULL) {
|
||||
return secfalse;
|
||||
}
|
||||
|
||||
int counter = 0;
|
||||
|
||||
int i = 0;
|
||||
|
||||
for (i = 0; i < SECRET_MONOTONIC_COUNTER_LEN / 16; i++) {
|
||||
secbool not_cleared = sectrue;
|
||||
for (int j = 0; j < 16; j++) {
|
||||
if (counter_addr[i * 16 + j] != 0xFF) {
|
||||
not_cleared = secfalse;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (not_cleared != sectrue) {
|
||||
counter++;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (; i < SECRET_MONOTONIC_COUNTER_LEN / 16; i++) {
|
||||
secbool not_cleared = sectrue;
|
||||
for (int j = 0; j < 16; j++) {
|
||||
if (counter_addr[i * 16 + j] != 0xFF) {
|
||||
not_cleared = secfalse;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (not_cleared != sectrue) {
|
||||
// monotonic counter is not valid
|
||||
return secfalse;
|
||||
}
|
||||
}
|
||||
|
||||
if (value != NULL) {
|
||||
*value = counter;
|
||||
} else {
|
||||
return secfalse;
|
||||
}
|
||||
|
||||
return sectrue;
|
||||
}
|
109
core/embed/trezorhal/unix/monoctr.c
Normal file
109
core/embed/trezorhal/unix/monoctr.c
Normal file
@ -0,0 +1,109 @@
|
||||
/*
|
||||
* This file is part of the Trezor project, https://trezor.io/
|
||||
*
|
||||
* Copyright (c) SatoshiLabs
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "monoctr.h"
|
||||
#include "flash_otp.h"
|
||||
#include "model.h"
|
||||
#include "string.h"
|
||||
|
||||
static int get_otp_block(monoctr_type_t type) {
|
||||
switch (type) {
|
||||
case MONOCTR_BOOTLOADER_VERSION:
|
||||
return FLASH_OTP_BLOCK_BOOTLOADER_VERSION;
|
||||
case MONOCTR_FIRMWARE_VERSION:
|
||||
return FLASH_OTP_BLOCK_FIRMWARE_VERSION;
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
secbool monoctr_write(monoctr_type_t type, uint8_t value) {
|
||||
if (value > MONOCTR_MAX_VALUE) {
|
||||
return secfalse;
|
||||
}
|
||||
|
||||
uint8_t bits[FLASH_OTP_BLOCK_SIZE];
|
||||
for (int i = 0; i < FLASH_OTP_BLOCK_SIZE * 8; i++) {
|
||||
if (i < value) {
|
||||
bits[i / 8] &= ~(1 << (7 - (i % 8)));
|
||||
} else {
|
||||
bits[i / 8] |= (1 << (7 - (i % 8)));
|
||||
}
|
||||
}
|
||||
|
||||
int block = get_otp_block(type);
|
||||
|
||||
if (block < 0) {
|
||||
return secfalse;
|
||||
}
|
||||
|
||||
ensure(flash_otp_write(block, 0, bits, FLASH_OTP_BLOCK_SIZE), NULL);
|
||||
|
||||
return sectrue;
|
||||
}
|
||||
|
||||
secbool monoctr_read(monoctr_type_t type, uint8_t* value) {
|
||||
uint8_t bits[FLASH_OTP_BLOCK_SIZE];
|
||||
|
||||
int block = get_otp_block(type);
|
||||
|
||||
if (block < 0) {
|
||||
return secfalse;
|
||||
}
|
||||
|
||||
ensure(flash_otp_read(block, 0, bits, FLASH_OTP_BLOCK_SIZE), NULL);
|
||||
|
||||
int result = 0;
|
||||
|
||||
int i;
|
||||
|
||||
// Iterate through each bit position in the bit field
|
||||
for (i = 0; i < FLASH_OTP_BLOCK_SIZE * 8; i++) {
|
||||
// Calculate the byte and bit index within the byte
|
||||
int byteIndex = i / 8;
|
||||
int bitIndex = 7 - (i % 8);
|
||||
|
||||
// Check if the current bit is 0
|
||||
if ((bits[byteIndex] & (1 << bitIndex)) == 0) {
|
||||
// If the bit is 0, increment the value
|
||||
result++;
|
||||
} else {
|
||||
// Stop when we find the first 1 bit
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (; i < FLASH_OTP_BLOCK_SIZE * 8; i++) {
|
||||
// Calculate the byte and bit index within the byte
|
||||
int byteIndex = i / 8;
|
||||
int bitIndex = 7 - (i % 8);
|
||||
if ((bits[byteIndex] & (1 << bitIndex)) == 0) {
|
||||
// If the bit is 0, return false - the monotonic counter is not valid
|
||||
return secfalse;
|
||||
}
|
||||
}
|
||||
|
||||
if (value != NULL) {
|
||||
*value = result;
|
||||
} else {
|
||||
return secfalse;
|
||||
}
|
||||
|
||||
return sectrue;
|
||||
}
|
@ -47,6 +47,7 @@ def stm32f4_common_files(env, defines, sources, paths):
|
||||
"embed/trezorhal/stm32f4/flash.c",
|
||||
"embed/trezorhal/stm32f4/flash_otp.c",
|
||||
"embed/trezorhal/stm32f4/lowlevel.c",
|
||||
"embed/trezorhal/stm32f4/monoctr.c",
|
||||
"embed/trezorhal/stm32f4/mpu.c",
|
||||
"embed/trezorhal/stm32f4/platform.c",
|
||||
"embed/trezorhal/stm32f4/secret.c",
|
||||
|
@ -57,6 +57,7 @@ def stm32u5_common_files(env, defines, sources, paths):
|
||||
"embed/trezorhal/stm32u5/flash_otp.c",
|
||||
"embed/trezorhal/stm32u5/lowlevel.c",
|
||||
"embed/trezorhal/stm32u5/hash_processor.c",
|
||||
"embed/trezorhal/stm32u5/monoctr.c",
|
||||
"embed/trezorhal/stm32u5/mpu.c",
|
||||
"embed/trezorhal/stm32u5/platform.c",
|
||||
"embed/trezorhal/stm32u5/secret.c",
|
||||
|
Loading…
Reference in New Issue
Block a user