Antes de começar, leia aqui
Eu criei alguns containers para facilitar e agilizar a compilação e os testes do Pico FIDO e do Pico HSM. O container base já vem com o ambiente preparado para usar o ESP-IDF 5.5.4 (versão adotada pelo PicoKeys e com alguns novos suportes). No FIDO, ele configura o VID/PID YubiKey 5 com as correções documentadas para FIDO e com suporte EdDSA/Ed25519 para usar em chaves SSH. No HSM, uso o v6.6, com VID/PID da NitroHSM.
Existem quatro containers, cada um com uma finalidade:
gen_keys: Usado para gerar as chaves usadas na configuração de secure boot e flash encryption.picokeys_image: Imagem base com o ESP-IDF 5.5.4 e PicoKeys FIDO (8.2) e HSM (6.6). Essa imagem é usada pelo container abaixo.picokeys: Usa essa imagem acima para de fato compilar o FIDO e o HSM. No FIDO, deixei o suporte EdDSA/Ed25519 ativado e configurei o VID/PID YubiKey 5 (além das correções para FIDO). Para o HSM 6.6, configurei o VID/PID NitroHSM.picokeys_tools: Possui ferramentas para testar o FIDO, o HSM e dispositivos YubiKey.
O projeto está disponível aqui: picokeys_podman.
Somente use esses containers se você já conhece o processo manual de compilação do PicoKeys e sabe configurar o Secure Boot e o Flash Encryption no ESP32-S3 manualmente. Esses recursos possuem etapas delicadas e ordem para ativar cada um, qualquer erro na configuração ou na gravação podem inutilizar a placa. Os containers agilizam o processo, mas não substituem esse conhecimento.
Compilando o firmware sem Secure Boot
Primeiro eu vou mostrar como compilar sem habilitar Secure Boot, já que esse recurso é irreversível. Assim você pode testar, usar e se precisar, ficar regravando de forma mais simples. Use uma placa como laboratório, teste, aprenda bem antes de executar passos irreversíveis. Depois que tudo estiver funcionando e você estiver confortavel e confiante, vamos ativar os recursos de segurança.
Eu vou continuar usando o XIAO ESP32-S3 para os procedimentos descritos aqui. Vale deixar claro que eu não vou usar nenhuma tag quando clonar o repositório, já que o procedimento de instalação mudou e novas melhorias foram implementadas na branch principal de cada projeto. Abaixo eu deixo qual branch usei para cada projeto:
### FIDO
Hash: ade770281dd4fd8c627add34ed078f56ec40a07b
tag: nightly-main e nightly-development
### HSM
Hash: 5d6a32c81b2367fb83e5320400f61217d2bd0d2f
tag: nightly-master e nightly-development
Eu enviei duas atualizações para corrigir alguns problemas de segurança para o projeto FIDO, então pode ser que rapidamente sejam incorporados ao projeto princial, com isso, você poderá usar os hashes acima, mas terá que aplicar as correções manualmente, pelo menos até eles serem incorporados ao projeto.
Só quero que você entenda que usar a branch principal muda a forma de compilar, já que novos recursos foram adicionados e até facilita a compilação, por isso não especifiquei nenhuma tag na hora de clonar os repositórios.
Instalando dependências e o ESP-IDF
Eu vou instalar as ferramentas de compilação usadas pelo ESP-IDF usando uma máquina virtual com Ubuntu 26.04.1:
sudo apt update
sudo apt install -y git wget flex bison gperf python3 python3-pip \
python3-venv cmake ninja-build ccache libffi-dev libssl-dev \
dfu-util libusb-1.0-0 openssl
Vamos criar um diretório para deixar o código fonte da ESP-IDF e instalar:
mkdir -p ~/src/picokeys
cd ~/src/picokeys
Como o ESP-IDF usa submódulos Git para componentes e dependências, temos que clonar usando o parâmetro --recursive para baixar esses componentes e dependências. Sem isso, nós teriamos o repositório principal, mas não teriamos o código completo necessário para compilar o firmware. Também vou baixar especificamente a tag v5.5.1, que é a versão 5.5.1 do código. Mesmo que essa seja a última versão disponível enquanto estou escrevendo este tutorial, quero deixar bem explícito qual versão estou usando, para não haver divergências com a versão que você pode estar executando.
Outro motivo para usar especificamente a versão v5.5.1 é porque o script de compilação do projeto Pico FIDO usa exatamente essa versão, ele faz um git checkout tags/v5.5.1 antes de instalar as ferramentas e compilar.
Para saber qual versão usar, acesse o arquivo:
- Para HSM: https://github.com/polhenarejos/pico-hsm/blob/master/workflows/autobuild.sh
- Para FIDO: https://github.com/polhenarejos/pico-fido/blob/main/workflows/autobuild.sh
Procure por tags/v, você verá o seguinte:
elif [[ $1 == "esp32" ]]; then
sudo apt install -y git wget flex bison gperf python3 python3-pip python3-venv cmake ninja-build ccache libffi-dev libssl-dev dfu-util libusb-1.0-0
git clone --recursive https://github.com/espressif/esp-idf.git
cd esp-idf
git checkout tags/v5.5.1
./install.sh esp32s3
. ./export.sh
O mais importante para saber a versão é o git checkout tags/v5.5.1. Então vamos clonar o esp-idf exatamente na versão 5.5.1:
git clone --branch v5.5.1 --recursive https://github.com/espressif/esp-idf.git esp-idf && cd esp-idf
Agora vamos instalar as ferramentas necessárias para desenvolver e compilar o firmware para o ESP32-S3:
unset IDF_PYTHON_ENV_PATH ; ./install.sh esp32s3
A variável
IDF_PYTHON_ENV_PATHserve para indicar onde está instalado oesp-idf, estou deixando ali caso você tenha alguma outra versão carregada na memória.
Sempre que abrir um terminal novo, temos que recarregar o ambiente do ESP-IDF:
cd ~/src/picokeys/esp-idf
# Carregue o ambiente ESP-IDF:
. ./export.sh
# Verifique a versão:
$ idf.py --version
ESP-IDF v5.5.1
Baixar e compilar Pico FIDO
Com o ambiente do ESP-IDF carregado, vamos clonar e compilar o Pico FIDO:
cd ~/src/picokeys
git clone --recursive https://github.com/polhenarejos/pico-fido.git && cd pico-fido
Toda vez que conectamos a placa no computador, ela emite um popup via Chrome informando que o Pico Key foi detectado. Como eu não gosto desse comportamento, vou desativar ele.
No Pico FIDO v8.0, o endereço está no descritor USB do SDK:
cd ~/src/picokeys/pico-fido
vim pico-keys-sdk/src/usb/usb_descriptors.c
Pesquise por TUD_BOS_WEBUSB_DESCRIPTOR, depois troque de 1 para 0, exemplo:
// Antes
TUD_BOS_WEBUSB_DESCRIPTOR(VENDOR_REQUEST_WEBUSB, 1),
// Depois
TUD_BOS_WEBUSB_DESCRIPTOR(VENDOR_REQUEST_WEBUSB, 0),
Esse parâmetro é o índice da página de destino, com 0, o firmware deixa de anunciar uma página para o Chrome abrir e o aviso automático deve desaparecer.
Falha na aplicação da política alwaysUv
A configuração alwaysUv determina que o autenticador (chave física) deve sempre exigir a verificação do usuário (por PIN ou biometria), antes de começar a realizar as operações de autenticação. No entanto, identifiquei que o arquivo src/fido/cbor_get_assertion.c possui uma possível inconsistência, o firmware verifica a autorização quando ela é fornecida, mas não exige explicitamente essa autorização em todos os caminhos quando alwaysUv está habilitado.
Isso poderia permitir que determinadas operações de autenticação fossem realizadas sem a verificação do usuário, servindo como um bypass para a política de segurança configurada. Para corrigir, edite o arquivo abaixo:
vim src/fido/cbor_get_assertion.c
Troque isso:
if (next == false) {
if (pinUvAuthParam.present == true) {
if (pinUvAuthParam.len == 0 || pinUvAuthParam.data == NULL) {
if (check_user_presence() == false) {
CBOR_ERROR(CTAP2_ERR_OPERATION_DENIED);
}
Por isso:
if (next == false) {
bool alwaysUv = (get_opts() & FIDO2_OPT_AUV) ||
(file_has_data(ef_pin) && !keydev_unlocked);
if (alwaysUv && pinUvAuthParam.present == false) {
CBOR_ERROR(CTAP2_ERR_PUAT_REQUIRED);
}
if (pinUvAuthParam.present == true) {
if (pinUvAuthParam.len == 0 || pinUvAuthParam.data == NULL) {
if (check_user_presence() == false) {
CBOR_ERROR(CTAP2_ERR_OPERATION_DENIED);
}
Eu validei o comportamento com o firmware original e consegui confirmar o problema:
$ python3 fido_alwaysUv.py
O factory reset apaga PIN, credenciais e configurações FIDO.
Deixe conectado somente o dispositivo que será testado.
Uma Pico com VID/PID de YubiKey pode parecer igual a uma YubiKey real; o programa não consegue distingui-las fisicamente.
Com a Pico descartável conectada, pressione Enter. Depois, desconecte-a quando o programa avisar:
Aguardando a desconexão. Desconecte a Pico agora e mantenha-a fora do USB...
Desconexão detectada. Reconecte a Pico agora...
Enviando factory reset imediatamente após a reconexão...
Factory reset concluído.
Criando credencial FIDO de teste...
Definindo PIN de teste e habilitando alwaysUv...
alwaysUv está ativo.
Testando getAssertion sem PIN/UV...
FALHA: BUG PRESENTE — getAssertion retornou sucesso sem PIN/UV com alwaysUv ativo.
Executando controle positivo com PIN/UV...
OK: controle positivo autenticado funcionou e marcou UV.
Depois da correção acima, também fiz um novo teste que mostrou que a correção é efetiva.
$ python3 fido_alwaysUv.py
O factory reset apaga PIN, credenciais e configurações FIDO.
Deixe conectado somente o dispositivo que será testado.
Uma Pico com VID/PID de YubiKey pode parecer igual a uma YubiKey real; o programa não consegue distingui-las fisicamente.
Com a Pico descartável conectada, pressione Enter. Depois, desconecte-a quando o programa avisar:
Aguardando a desconexão. Desconecte a Pico agora e mantenha-a fora do USB...
Desconexão detectada. Reconecte a Pico agora...
Enviando factory reset imediatamente após a reconexão...
Factory reset concluído.
Criando credencial FIDO de teste...
Definindo PIN de teste e habilitando alwaysUv...
alwaysUv está ativo.
Testando getAssertion sem PIN/UV...
OK: getAssertion sem token UV foi recusado com PIN_REQUIRED.
Executando controle positivo com PIN/UV...
OK: controle positivo autenticado funcionou e marcou UV.
RESULTADO: PASSOU — getAssertion exigiu PIN/UV e o controle autenticado funcionou.
Validação de memória no processamento U2F
Identifiquei que o Pico FIDO possui um possível problema na validação do tamanho dos dados recebidos durante operações U2F. Apesar de ser um protocolo antigo, muitos sistemas ainda podem usar U2F, então é bom corrigir esse problema. O firmware usa o tamanho informado pelo cliente (como um navegador ou qualquer outro sistema que solicite a operação) sem verificar completamente se ele corresponde ao tamanho real dos dados recebidos.
Isso pode provocar leituras indevidas de memória, travamentos ou comportamentos inesperados quando o dispositivo recebe mensagens malformadas. Alguém com acesso ao computador conectado ao Pico FIDO poderia enviar comandos USB preparados para tentar explorar essa falha. Para resolver esse problema, edite o arqivo abaixo:
vim src/fido/cmd_authenticate.c
Troque essas linhas:
if (apdu.nc < CTAP_CHAL_SIZE + CTAP_APPID_SIZE + 1 + 1) {
return SW_WRONG_DATA();
}
if (req->keyHandleLen < KEY_HANDLE_LEN) {
return SW_INCORRECT_PARAMS();
}
Por essas:
const size_t key_handle_offset = offsetof(CTAP_AUTHENTICATE_REQ, keyHandle);
if (apdu.nc < key_handle_offset) {
return SW_WRONG_DATA();
}
if (req->keyHandleLen < KEY_HANDLE_LEN ||
req->keyHandleLen > CTAP_MAX_KH_SIZE) {
return SW_INCORRECT_PARAMS();
}
if ((size_t)apdu.nc < key_handle_offset + req->keyHandleLen) {
return SW_WRONG_DATA();
}
Ainda no mesmo arquivo, troque as duas linhas abaixo:
uint8_t *tmp_kh = (uint8_t *) calloc(1, req->keyHandleLen);
memcpy(tmp_kh, req->keyHandle, req->keyHandleLen);
Por essas:
uint8_t *tmp_kh = (uint8_t *) calloc(1, req->keyHandleLen);
if (tmp_kh == NULL) {
mbedtls_ecp_keypair_free(&key);
return SW_MEMORY_FAILURE();
}
memcpy(tmp_kh, req->keyHandle, req->keyHandleLen);
Eu validei o comportamento com o firmware original e consegui confirmar o problema:
$ python3 fido_u2f_memory_test.py
Dispositivo detectado: Pol Henarejos Pico Key, VID=1050, PID=0407
Confirme fisicamente que este é a Pico FIDO descartável.
Digite TESTAR para continuar: TESTAR
Key handle truncada (esperado SW_WRONG_DATA 6700): recebido 69 85
FALHOU: esperado 67 00
Key handle acima do limite (esperado SW_INCORRECT_PARAMS 6A80): recebido 69 85
FALHOU: esperado 6A 80
RESULTADO: FALHOU — confira os status recebidos e o firmware instalado.
Depois da correção acima, também fiz um novo teste que mostrou que a correção é efetiva.
$ python3 fido_u2f_memory_test.py
Dispositivo detectado: Pol Henarejos Pico Key, VID=1050, PID=0407
Confirme fisicamente que este é a Pico FIDO descartável.
Digite TESTAR para continuar: TESTAR
Key handle truncada (esperado SW_WRONG_DATA 6700): recebido 67 00
OK
Key handle acima do limite (esperado SW_INCORRECT_PARAMS 6A80): recebido 6A 80
OK
RESULTADO: PASSOU — os dois APDUs inválidos foram rejeitados.
Compilar FIDO
Antes de começar a compilação, queria falar sobre dois problemas de segurança que encontrei no firmware do FIDO. Apesar de já ter feito as correções e validado os resultados nos meus testes, também enviei uma sugestão ao projeto oficial para que os desenvolvedores possam avaliar tanto a correção quanto o impacto dela e no futuro, possam incluir essas melhorias no código oficial.
Os scripts usados para testes foram gerados via IA e avaliados por mim, você pode ter acesso aos scripts no meu Gitlab.
Agora vamos compilar.
# Se você compilou o HSM antes do FIDO, execute:
idf.py fullclean
# Define o ESP32-S3 como o microcontrolador de destino para a compilação do firmware:
idf.py set-target esp32s3
# Compilar o código-fonte usando o VID/PID da Yubikey5:
idf.py -DVIDPID=Yubikey5 -DENABLE_EDDSA=ON build
# Se quiser compilar o código-fonte com os VID/PID do PicoKeys, é só executar o comando abaixo:
idf.py build
Estou usando
-DENABLE_EDDSA=ONpara ativar o suporte a chaves ed25519-sk para ser usado no SSH via FIDO.
A compilação do Pico FIDO gera vários arquivos .bin, incluindo o bootloader, a tabela de partições e o firmware principal. O arquivo build/flash_args informa quais arquivos gravar e em quais endereços da flash. O comando abaixo junta esses arquivos em uma única imagem, que aqui estou chamando de pico_fido_esp32-s3.bin. Essa etapa é opcional se você for gravar o dispositivo com idf.py flash.
No entanto, esse comando é necessário quando você quer criar um arquivo .bin único para distribuir ou gravar por outro método, como via esptool.py.
mkdir -p release && cd build
# Combine os arquivos '.bin' gerados na compilação:
esptool.py --chip ESP32-S3 merge_bin -o ../release/pico_fido_esp32-s3.bin @flash_args && cd ..
Baixar e compilar Pico HSM
Eu fiz alguns testes parciais com o Pico HSM, tive muita dor de cabeça para fazer ele ser reconhecido inicialmente. Pelo que percebi, ele ainda não está totalmente maduro. Para que o dispositivo fosse reconhecido pelo pkcs11-tool e pelo opensc-tool, precisei usar exatamente a versão 6.6, que possui o commit 251b35dd9c4fd929923fc3b192f6793826efeaef registrado com a tag v6.6.
Nos meus testes, o firmware compilado a partir da branch principal não foi reconhecido por essas ferramentas. Também precisei usar o libccid na versão 1.8.2 ou superior. As versões anteriores que testei não reconheceram o HSM.
Outro detalhe é que você não deve usar YubiHSM no VID/PID, compile sem passar nenhuma modelo específico. O yubihsm-shell não reconhece o Pico HSM quando compilado com YubiHSM porque essa opção altera apenas o VID/PID USB. Ela não faz o firmware implementar o protocolo da YubiHSM 2, que é o protocolo usado pelo yubihsm-shell. O Pico HSM se comunica por CCID e deve ser acessado por ferramentas compatíveis, como opensc-tool e pkcs11-tool.
O tutorial descrito aqui funciona apenas na branch principal do Pico HSM. Eu vou manter aqui como fazer porque desde a versão 6.6 mudaram muitas coisas e a forma de compilar é diferente, então creio que daqui em diante esse padrão deve ser seguido com pequenos ajustes. Mas tenha em mente que usar a branch principal fará com que o HSM nao seja reconhecido (pelo menos até o momento em que escrevia esse tutorial).
Para ver como compilar exatamente a versão 6.6 leia aqui. Esse é o processo base de como fazer, outras customizações ainda podem ser aplicadas, como Secure Boot, Flash Encryption e etc, você só precisa corrigir os problemas para conseguir compilar seguindo o que está no link.
O Pico HSM também usa o ESP-IDF para o alvo ESP32-S3 e também vamos usar a versão 5.5.1, a explicação é a mesma do FIDO.
cd ~/src/picokeys
git clone --recursive https://github.com/polhenarejos/pico-hsm.git && cd pico-hsm
Toda vez que conectamos a placa no computador, ela emite um popup via Chrome informando que o Pico Key foi detectado. Como eu não gosto desse comportamento, vou desativar ele.
No Pico HSM, o endereço está no descritor USB do SDK:
cd ~/src/picokeys/pico-hsm
vim pico-keys-sdk/src/usb/usb_descriptors.c
Pesquise por TUD_BOS_WEBUSB_DESCRIPTOR, depois troque de 1 para 0, exemplo:
// Antes
TUD_BOS_WEBUSB_DESCRIPTOR(VENDOR_REQUEST_WEBUSB, 1),
// Depois
TUD_BOS_WEBUSB_DESCRIPTOR(VENDOR_REQUEST_WEBUSB, 0),
Esse parâmetro é o índice da página de destino, com 0, o firmware deixa de anunciar uma página para o Chrome abrir e o aviso automático deve desaparecer.
No código testado, foram necessários fazer dois ajustes antes da compilação, esses ajustes fazem o ESP-IDF registrar o componente HSM e localizar a dependência libcvc. Edite o arquivo CMakeLists.txt que fica na raiz do Pico HSM:
vim CMakeLists.txt
Dentro do bloco if(ESP_PLATFORM) (o primeiro bloco que normalmente fica na linha 23), defina PICOKEYS_COMPONENTS como hsm, antes do primeiro include(...) do ESP-IDF. O trecho original é assim:
if(ESP_PLATFORM)
set(EXTRA_COMPONENT_DIRS pico-keys-sdk/config/esp32/components src/hsm)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
else()
Ele deve ficar assim:
if(ESP_PLATFORM)
set(PICOKEYS_COMPONENTS hsm)
set(EXTRA_COMPONENT_DIRS pico-keys-sdk/config/esp32/components src/hsm)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
else()
Agora vamos fazer o ESP-IDF localizar a dependência do libcvc. Comece editando o importador do SDK Pico Keys:
vim pico-keys-sdk/picokeys_sdk_import.cmake
Dentro do bloco if(ESP_PLATFORM), existe uma definição set(EXTRA_COMPONENT_DIRS ...), localize ela (normalmente fica próxima a linha 73 no arquivo que está na main ou na linha ). O trecho completo original é assim:
if(ESP_PLATFORM)
if(DEFINED CONFIG_TINYUSB_DESC_CUSTOM_VID)
set(USB_VID CONFIG_TINYUSB_DESC_CUSTOM_VID)
endif()
if(DEFINED CONFIG_TINYUSB_DESC_CUSTOM_PID)
set(USB_PID CONFIG_TINYUSB_DESC_CUSTOM_PID)
endif()
if(NOT DEFINED ENABLE_POWER_ON_RESET)
set(ENABLE_POWER_ON_RESET 0)
endif()
if(NOT DEFINED ENABLE_PQC)
set(ENABLE_PQC 0)
endif()
if(NOT DEFINED SDKCONFIG_DEFAULTS AND NOT DEFINED ENV{SDKCONFIG_DEFAULTS})
set(SDKCONFIG_DEFAULTS "pico-keys-sdk/config/esp32/sdkconfig.defaults")
endif()
set(EXTRA_COMPONENT_DIRS
src/${PICOKEYS_COMPONENTS}
pico-keys-sdk/config/esp32/components/mbedtls
pico-keys-sdk/config/esp32/components/pico-keys-sdk
)
set(COMPONENTS ${PICOKEYS_COMPONENTS} pico-keys-sdk)
if (USB_ITF_HID)
list(APPEND EXTRA_COMPONENT_DIRS
pico-keys-sdk/config/esp32/components/tinycbor
)
list(APPEND COMPONENTS tinycbor)
endif()
if(ENABLE_PQC)
list(APPEND EXTRA_COMPONENT_DIRS
pico-keys-sdk/config/esp32/components/mlkem512
pico-keys-sdk/config/esp32/components/mlkem768
pico-keys-sdk/config/esp32/components/mlkem1024
)
endif()
endif()
Depois de set(EXTRA_COMPONENT_DIRS ...), acrescente:
if(ENABLE_LIBCVC)
list(APPEND EXTRA_COMPONENT_DIRS
pico-keys-sdk/config/esp32/components/libcvc
)
endif()
O trecho completo novo fica assim:
if(ESP_PLATFORM)
if(DEFINED CONFIG_TINYUSB_DESC_CUSTOM_VID)
set(USB_VID CONFIG_TINYUSB_DESC_CUSTOM_VID)
endif()
if(DEFINED CONFIG_TINYUSB_DESC_CUSTOM_PID)
set(USB_PID CONFIG_TINYUSB_DESC_CUSTOM_PID)
endif()
if(NOT DEFINED ENABLE_POWER_ON_RESET)
set(ENABLE_POWER_ON_RESET 0)
endif()
if(NOT DEFINED ENABLE_PQC)
set(ENABLE_PQC 0)
endif()
if(NOT DEFINED SDKCONFIG_DEFAULTS AND NOT DEFINED ENV{SDKCONFIG_DEFAULTS})
set(SDKCONFIG_DEFAULTS "pico-keys-sdk/config/esp32/sdkconfig.defaults")
endif()
set(EXTRA_COMPONENT_DIRS
src/${PICOKEYS_COMPONENTS}
pico-keys-sdk/config/esp32/components/mbedtls
pico-keys-sdk/config/esp32/components/pico-keys-sdk
)
if(ENABLE_LIBCVC)
list(APPEND EXTRA_COMPONENT_DIRS
pico-keys-sdk/config/esp32/components/libcvc
)
endif()
set(COMPONENTS ${PICOKEYS_COMPONENTS} pico-keys-sdk)
if (USB_ITF_HID)
list(APPEND EXTRA_COMPONENT_DIRS
pico-keys-sdk/config/esp32/components/tinycbor
)
list(APPEND COMPONENTS tinycbor)
endif()
if(ENABLE_PQC)
list(APPEND EXTRA_COMPONENT_DIRS
pico-keys-sdk/config/esp32/components/mlkem512
pico-keys-sdk/config/esp32/components/mlkem768
pico-keys-sdk/config/esp32/components/mlkem1024
)
endif()
endif()
Esse ajuste é necessário porque o componente HSM declara libcvc em REQUIRES, mas o importador do SDK não inclui (até o momento da escrita desse tutorial) o diretório desse componente na lista que o ESP-IDF procura.
Eu abri uma issue perguntando se a mudança acima seria elegível para incorporar ao projeto pricipal, caso afirmativo, em breve, não será necessário fazer isso manualmente.
Agora vamos desativar a opção CONFIG_ESP_COREDUMP_ENABLE_TO_UART. Com essa opção ativa, alguém poderia induzir o ESP32-S3 a algum erro, fazendo com que um dump da memória seja enviado pela UART, que é uma interface de comunicação serial usada para transmitir e receber dados. O problema é que esse dump pode conter informações sensíveis, como chaves criptográficas, PINs e outros dados presentes na RAM.
Esse recursos também é aplicado ao FIDO, mas na branch principal, ele não está presente, por isso não foi documentado como desativar ele lá.
Esse recurso pode ser explorado em um ataque de Information Disclosure (divulgação de informações), permitindo que alguém com acesso à UART obtenha dados sensíveis do dispositivo.
vim sdkconfig.defaults
Altere comentando a linha no código, exemplo antes de desativar:
# Antes de desativar:
CONFIG_ESP_COREDUMP_ENABLE_TO_UART=y
Como deve ficar depois de desativar:
# CONFIG_ESP_COREDUMP_ENABLE_TO_UART is not set
Agora vamos compilar o Pico HSM:
cd ~/src/picokeys/esp-idf
# Carregue o ambiente ESP-IDF:
. ./export.sh
# Entre no projeto do Pico HSM:
cd ~/src/picokeys/pico-hsm
# Se você compilou o FIDO antes do HSM, execute:
idf.py fullclean
# Define o ESP32-S3 como o microcontrolador de destino para a compilação do firmware:
idf.py set-target esp32s3
# Compilar o código-fonte usando o VID/PID da YubiHSM:
idf.py -DVIDPID=YubiHSM build
# Se quiser compilar o código-fonte com os VID/PID do PicoKeys, é só executar o comando abaixo:
idf.py build
Assim como o FIDO, a compilação do HSM gera vários .bin, o flash_args informa quais arquivos gravar e em quais endereços. O merge_bin junta esses arquivos em um só, mas isso é opcional, podemos usar o comando idf.py flash para gravar na placa. No entanto, esse comando é necessário quando você quer criar um arquivo .bin único para distribuir ou gravar por outro método, como via esptool.py.
mkdir -p release && cd build
# Combine os arquivos '.bin' gerados na compilação:
esptool.py --chip ESP32-S3 merge_bin -o ../release/pico_hsm_esp32-s3.bin @flash_args && cd ..
Gravar o firmware sem Secure Boot
Vamos gravar o firmware sem o Secure Boot, mas antes, coloque o ESP32-S3 em modo de download:
- Segure BOOT, pressione e solte RESET e então solte BOOT.
Depois execute para ver em qual porta ele subiu:
$ ls -l /dev/ttyACM* /dev/ttyUSB*
/dev/ttyACM0
No meu caso foi /dev/ttyACM0, vou usar esse nome no comando abaixo para gravar o firmware. Se o use foi outro, use o nome que apareceu ai, mas certifique-se de que é o ESP32-S3 correto que você quer gravar.
# Para FIDO:
cd ~/src/picokeys/pico-fido && esptool.py --chip ESP32-S3 \
-p /dev/ttyACM0 \
write_flash 0x0 \
release/pico_fido_esp32-s3.bin
# Para HSM:
cd ~/src/picokeys/pico-hsm && esptool.py --chip ESP32-S3 \
-p /dev/ttyACM0 \
write_flash 0x0 \
release/pico_hsm_esp32-s3.bin
Compilando o firmware com Secure Boot
O ESP32-S3 possui recursos de segurança que permitem proteger o dispositivo contra modificações não autorizadas no firmware. O Secure Boot verifica a assinatura digital do firmware antes de executar ele, permitindo que apenas imagens assinadas com uma chave autorizada sejam inicializadas. Isso impede que alguém consiga substituir o firmware armazenado na memória flash, já que o microcontrolador vai se recusar a executar o firmware novo caso a assinatura não seja válida.
Só de ativar o Secure Boot v2 no ESP32-S3, algumas proteções de segurança já são aplicadas automaticamente, mesmo sem usar o recurso de Secure Lock. No meu caso, os dois slots adicionais de digest do Secure Boot foram revogados, a interface JTAG foi desativada permanentemente e o dispositivo passou a operar em Secure Download Mode.
Revogar os slots adicionais de digest impede que alguém consiga cadastrar novas chaves de assinatura e, com isso, executar um firmware que originalmente não deveria ser aceito pela placa. Isso evita uma possível forma de contornar a proteção do Secure Boot.
Com a interface JTAG desativada permanentemente, impedimos que alguém use essa interface para depurar o processador, inspecionar a memória ou interferir na execução do firmware. Isso dificulta ataques que exigem acesso físico ao dispositivo.
Já o Secure Download Mode restringe os comandos disponíveis no modo de download, impedindo operações como a leitura direta dos eFuses e reduzindo as possibilidades de manipulação do dispositivo por essa interface. Isso dificulta o uso do modo de download para contornar as proteções configuradas.
Todas essas proteções foram ativadas automaticamente ao habilitar o secure boot. No entanto, esse comportamento pode mudar no futuro, por isso deixei os commits usados aqui no começo dessa página.
A ativação do Secure Boot envolve alterações irreversíveis no microcontrolador. Uma configuração incorreta pode impedir permanentemente a inicialização da placa.
Por isso, primeiro vamos preparar as chaves, configurar a assinatura do firmware e verificar seu funcionamento. Somente depois trataremos da gravação dos eFuses.
Preciso ativar Flash Encryption?
No código que analisei, tanto o Pico FIDO quanto o Pico HSM usam containers com criptografia autenticada para proteger objetos sensíveis, como chaves privadas e credenciais. Isso protege esses dados na camada do próprio firmware, mas não significa que todo o conteúdo da flash esteja criptografado.
O recurso de Flash Encryption da própria ESP32-S3 adiciona uma camada a mais, ela criptografa as partições configuradas para isso, dificultando a leitura do firmware e de outros dados ao acessar a flash diretamente. Essa proteção entra como um complemento a proteção dos containers, mas não impede o acesso aos dados enquanto o firmware está rodando no dispositivo.
Na teoria, os dados sensíveis que o Pico FIDO e o Pico HSM armazenam nos containers são criptografados antes de serem gravados na flash. Assim, uma leitura direta da flash não deveria revelar esses dados sem a chave. Como os dados estão cifradas, e as chaves para decifrar ficam armazenadas na flash de forma protegida, somente com um dump da flash não deveria ser possível ver as chaves em texto claro.
O Flash Encryption seria uma proteção a mais, porque também criptografa as partições configuradas. Assim, fica mais difícil analisar o conteúdo da flash se alguém tiver acesso direto a ela.
O processo da Flash Encryption precisa ocorrer antes de ativar o Secure Boot porque a chave usada para criptografar a flash precisa ser gravada e ter a leitura bloqueada nos eFuses. Ao concluir a configuração do Secure Boot, o ESP-IDF protege o controle que permitiria aplicar esse bloqueio depois. Como as eFuses são permanentes, preciso gravar e proteger a chave da Flash Encryption primeiro e só depois ativar o Secure Boot.
Se você já tem uma chave com secure boot configurado, essa placa não pode receber o recurso de Flash Encryption, sendo necessário fazer isso em outra placa.
Então se você quer ter essa proteção a mais, não siga o procedimento descrito aqui, faça o próximo tutorial, que é de como habilitar o Flash Encryption e depois ativar o Secure Boot.
Gerando as chaves de assinatura
Para usar o Secure Boot, precisamos gerar um par de chaves criptográficas:
- Chave privada: é usada para assinar o firmware durante a compilação ou posteriormente. Deve permanecer armazenada em um local seguro, fora do ESP32-S3.
- Chave pública: é usada para verificar se a assinatura do firmware é legítima. O ESP32-S3 armazena um resumo criptográfico (digest) dessa chave nos eFuses.
O funcionamento é relativamente simples, quando ligamos o ESP32-S3, o Secure Boot verifica se o firmware possui uma assinatura válida, produzida por uma chave privada correspondente a uma das chaves públicas autorizadas no dispositivo. Se a verificação falhar, o firmware não será executado.
O Secure Boot V2, usado pelo ESP32-S3, trabalha com assinaturas RSA-3072. Primeiro, vamos criar um diretório para armazenar as chaves:
# Criar o diretório
mkdir -p ~/.config/picokeys/signing
# Permitir acesso somente ao proprietário
chmod 700 ~/.config/picokeys/signing
# Restringir as permissões dos novos arquivos
umask 077
Agora, vamos usar o espsecure.py, ferramenta disponibilizada pelo ESP-IDF, para gerar uma chave privada RSA-3072:
espsecure.py generate_signing_key --version 2 --scheme rsa3072 ~/.config/picokeys/signing/secure-boot.pem
O comando acima gera o arquivo secure-boot.pem, contendo a chave privada que será usada para assinar o firmware. Os parâmetros usados são:
generate_signing_key: gera uma nova chave criptográfica.--version 2: especifica o Secure Boot V2.--scheme rsa3072: define o algoritmo RSA com chave de 3072 bits.
Se você pretende usar o Pico HSM em outra placa, podemos gerar uma segunda chave:
espsecure.py generate_signing_key --version 2 --scheme rsa3072 ~/.config/picokeys/signing/hsm-secure-boot.pem
Por fim, vamos garantir que somente o proprietário consiga ler ou modificar os arquivos:
chmod 600 ~/.config/picokeys/signing/*-secure-boot.pem
A resposta simples é sim. Não existe a necessidade de gerar uma chave para cada firmware, já que a chave fica vinculada ao microcontrolador (ESP32-S3), e não ao firmware instalado.
Isso acontece porque o digest da chave pública é gravado nos eFuses do ESP32-S3. Portanto, desde que o firmware seja assinado com a chave privada correspondente, ele poderá ser executado pelo Secure Boot. Isso permite, por exemplo, alternar entre o Pico FIDO e o Pico HSM usando a mesma chave de assinatura.
No entanto, existe um detalhe importante, a gravação dos eFuses é permanente. Embora o ESP32-S3 suporte até três chaves de assinatura, não será possível simplesmente substituir uma chave cujo digest já foi gravado e protegido. Por isso, é importante definir quais chaves serão usadas antes de ativar essas proteções.
Eu também recomendo que você configure pelo menos duas chaves no eFuses, uma para cada placa e uma de bypass caso você perca a primeira, mas isso deve ser feito antes de ativar o secure boot. Quando o Secure Boot V2 é ativado, durante a primeira inicialização, os slots de assinatura que não foram configurados são revogados. Esse comportamento pode ser alterado, mas não recomendo.
Se você pretende usar o secure lock, precisa saber que ele muda o comportamento descrito acima. Mesmo que você grave três chaves antecipadamente nos eFuses, o secure lock vai usar somente uma e revogar as outras duas.
Se você perder a chave privada e não tiver outra chave autorizada, não vai mais conseguir assinar novas versões do firmware. O dispositivo continuará funcionando com o firmware já instalado, mas poderá ficar impossibilitado de receber atualizações. Dependendo da configuração, isso pode tornar a placa inutilizável para novos projetos.
Se a chave privada for comprometida, outra pessoa poderá assinar firmwares que serão considerados legítimos pelo Secure Boot. Caso não exista outra chave autorizada para substituir ela, o recomendado é usar a placa apenas para testes locais e pessoais.
O recomendado é usar uma chave diferente para cada placa, evitando reusar a mesma chave privada em vários dispositivos. Dessa forma, a perda ou o comprometimento de uma chave vai afetar apenas a placa correspondente. Também é importante manter cópias de segurança das chaves privadas em um local seguro, preferencialmente offline. Afinal, perder uma chave pode significar perder a possibilidade de atualizar permanentemente o dispositivo.
Associando o digest da chave ao firmware PicoKeys
Agora que temos nossa chave privada, precisamos fazer uma pequena alteração no código-fonte do PicoKeys para usar nossa própria chave de assinatura. Esse passo é necessário porque o SDK do PicoKeys possui um digest de chave pública hard-coded, ou seja, um digest definido direto no código. Isso facilita o processo de ativação do Secure Boot e Secure Lock por meio do PicoKey App, já que o firmware possui um digest conhecido para realizar o provisionamento, dispensando a necessidade de cada usuário gerar e configurar sua própria chave.
No entanto, como estamos compilando o firmware e usando uma chave privada própria, precisamos substituir esse digest pelo correspondente à nossa chave pública. Dessa forma, garantimos que o Secure Boot utilize a mesma chave de assinatura configurada durante a compilação do firmware.
O digest é um resumo criptográfico que possui 32 bytes, ele é calculado a partir da chave pública. Ele é como se fosse uma impressão digital da chave pública, vai permitir que o Secure Boot identifique quais chaves estão autorizadas a assinar os firmwares executados pelo microcontrolador. O ESP32-S3 armazena esse digest nos eFuses.
Gerando a chave pública
Vamos usar novamente o espsecure.py, mas dessa vez para calcular o digest da chave pública correspondente à chave privada que geramos anteriormente:
espsecure.py digest_sbv2_public_key \
--keyfile ~/.config/picokeys/signing/secure-boot.pem \
--output public-digest.bin
Os parâmetros usados são:
digest_sbv2_public_key: calcula o digest da chave pública para o Secure Boot V2.--keyfile: indica o arquivo que contém nossa chave privada. A ferramenta extrai as informações da chave pública para calcular o digest.--output: define o nome do arquivo onde o digest será armazenado.
O comando gera o arquivo public-digest.bin, contendo os 32 bytes que precisamos adicionar ao código-fonte do PicoKeys.
Convertendo o digest para o formato usado pelo PicoKeys
O arquivo gerado é um binário, mas o SDK do PicoKeys armazena o digest em um array de bytes no código-fonte C. Para facilitar nossa vida, podemos usar um pequeno script Python para converter esses bytes para o formato hexadecimal:
python3 - public-digest.bin <<'PY'
from pathlib import Path
import sys
digest = Path(sys.argv[1]).read_bytes()
if len(digest) != 32:
raise SystemExit(f"Esperados 32 bytes; recebidos {len(digest)}")
for start in range(0, 32, 8):
print(" " + ", ".join(f"0x{b:02x}" for b in digest[start:start+8]) + ",")
PY
O script vai ler o arquivo, verificar se ele possui exatamente 32 bytes e imprime o resultado em hexadecimal, já no formato necessário para adicionar no código-fonte. A saída é semelhante ao código abaixo:
0x0c, 0x1e, 0xce, 0xf3, 0xb4, 0x8f, 0x4a, 0x81,
0x45, 0x6c, 0x85, 0x39, 0x15, 0xcc, 0x05, 0x36,
0xbe, 0x23, 0x24, 0xee, 0xac, 0x8e, 0x3b, 0xb5,
0x77, 0x6f, 0x2d, 0xb9, 0x62, 0x38, 0x75, 0x6a
Incluindo o Digest no código-fonte do PicoKeys FIDO e HSM
Tanto para FIDO quanto para HSM, edite o arquivo abaixo:
vim pico-keys-sdk/src/otp/otp_esp32.c
Dentro do arquivo correto, procure pela variável abaixo:
static const uint8_t esp_secure_boot_digest[32]
Ela contém os 32 bytes do digest usado originalmente pelo PicoKeys. Agora, basta substituir os valores existentes pelos bytes que acabamos de gerar, mantendo a estrutura do array:
static const uint8_t esp_secure_boot_digest[32] = {
0x0c, 0x1e, 0xce, 0xf3, 0xb4, 0x8f, 0x4a, 0x81,
0x45, 0x6c, 0x85, 0x39, 0x15, 0xcc, 0x05, 0x36,
0xbe, 0x23, 0x24, 0xee, 0xac, 0x8e, 0x3b, 0xb5,
0x77, 0x6f, 0x2d, 0xb9, 0x62, 0x38, 0x75, 0x6a
};
Se você possui diretórios separados para o Pico FIDO e o Pico HSM, será necessário repetir esse procedimento em cada um deles, use o mesmo digest se for a mesma placa ou coloque outro caso seja uma placa diferente.
Essa alteração apenas informa ao SDK do PicoKeys qual digest deverá ser usado durante o provisionamento do Secure Boot. Ela não assina o firmware nem grava automaticamente a chave nos eFuses.
Ainda precisamos configurar o ESP-IDF para assinar o bootloader e a aplicação usando a chave privada correspondente. Não grave os eFuses antes de confirmar que todos esses componentes estão usando a mesma chave de assinatura.
Depois que os eFuses forem gravados e protegidos, não será possível simplesmente substituir o digest por outro. Uma configuração incorreta poderá impedir permanentemente a inicialização de novos firmwares.
Configurando e assinando o Secure Boot para FIDO e HSM
Recomendo realizar esse procedimento em um diretório separado, para evitar confundir o firmware configurado com sua chave de assinatura com outra versão que não tenha essa configuração. Como estamos trabalhando com o Secure Boot e os eFuses do ESP32-S3, um erro pode resultar na gravação permanente de um digest incorreto, impedindo a inicialização de novos firmwares.
Não se esqueça de aplicar qualquer alteração que nao seja persistente, como desativar CONFIG_ESP_COREDUMP_ENABLE_TO_UART, configurar para não vir popup quando plugar o PicoKeys e etc.
No diretório raiz de cada projeto, execute o comando abaixo:
# Vamos limpar o ambiente e começar de novo:
idf.py fullclean
idf.py set-target esp32s3
# Agora vamos editar as configurações importantes:
idf.py menuconfig
Entre em Security features, depois marque Enable hardware Secure Boot in bootloader. Vai abrir um novo menu, nesse menu novo, clique em Select secure boot version (Enable Secure Boot version 2), depois, certifique de ver (X) Enable Secure Boot version 2 (no meu caso já estava tudo certo, mas quis deixar claro para que você não fique com dúvidas).
Agora, ainda dentro de Security features, selecione a opção Secure boot private signing key (NEW). No meu caso, ela veio configurada com o arquivo secure_boot_signing_key.pem. Vamos substituir esse valor pelo caminho da chave privada mais o nome da chave, que geramos anteriormente. No meu caso, eu configurei exatamente assim: /home/bruno/.config/picokeys/signing/secure-boot.pem.
Agora volte para tela principal apertando a tecla backspace, clique em Partition Table, e agora em Offset of partition table. Altere o valor de 0x8000 para 0x10000.
Para salvar usa a tecla s e para sair a letra q. para certificar que tudo ficou em ordem, podemos checar:
$ grep -iE 'CONFIG_SECURE_BOOT=y|CONFIG_PARTITION_TABLE_OFFSET|CONFIG_SECURE_BOOT_V2_ENABLED=y|CONFIG_SECURE_BOOT_BUILD_SIGNED_BINARIES=y|CONFIG_SECURE_BOOT_SIGNING_KEY=' /home/bruno/src/picokeys/pico-fido/sdkconfig
CONFIG_SECURE_BOOT=y
CONFIG_SECURE_BOOT_V2_ENABLED=y
CONFIG_SECURE_BOOT_BUILD_SIGNED_BINARIES=y
CONFIG_SECURE_BOOT_SIGNING_KEY="/home/bruno/.config/picokeys/signing/secure-boot.pem"
CONFIG_PARTITION_TABLE_OFFSET=0x10000
Habilitar Secure Boot na configuração e gravar/iniciar essa imagem no ESP32-S3 pode fazer o bootloader gravar eFuses na primeira inicialização. Essa gravação é permanente. Se você não quer ativar Secure Boot agora, não use nem grave uma imagem construída com CONFIG_SECURE_BOOT=y na sua placa. Compile e teste apenas a configuração de laboratório sem Secure Boot.
Gerando o firmware assinado FIDO e HSM
Agora que já preparamos o terreno, vamos fazer o build de um novo firmware, mas assinado.
Para FIDO:
cd ~/src/picokeys/pico-fido
# Compilar o código-fonte usando o VID/PID da Yubikey5:
idf.py -DVIDPID=Yubikey5 -DENABLE_EDDSA=ON build
Para HSM:
cd ~/src/picokeys/pico-hsm
# Compilar o código-fonte usando o VID/PID da Yubikey5:
idf.py -DVIDPID=YubiHSM build
Agora, dentro do diretório que clonamos o firmware (seja FIDO ou HSM), vamos validar o bootloader e o firmware:
#### FIDO
# Validar assinatura do bootloader:
$ idf.py secure-verify-signature --keyfile /home/bruno/.config/picokeys/signing/secure-boot.pem bootloader/bootloader.bin
Executing action: secure-verify-signature
Running espsecure in directory /home/bruno/src/picokeys/pico-fido/build
Executing "/home/bruno/.espressif/python_env/idf5.5_py3.14_env/bin/python -m espsecure verify_signature --version 2 --keyfile /home/bruno/.config/picokeys/signing/secure-boot.pem bootloader/bootloader.bin"...
espsecure.py v4.12.0
Signature block 0 is valid (RSA).
Signature block 0 verification successful using the supplied key (RSA).
Signature block 1 invalid. Skipping.
Signature block 2 invalid. Skipping.
# Validar assinatura do firmware:
$ idf.py secure-verify-signature --keyfile /home/bruno/.config/picokeys/signing/secure-boot.pem pico_fido.bin
Executing action: secure-verify-signature
Running espsecure in directory /home/bruno/src/picokeys/pico-fido/build
Executing "/home/bruno/.espressif/python_env/idf5.5_py3.14_env/bin/python -m espsecure verify_signature --version 2 --keyfile /home/bruno/.config/picokeys/signing/secure-boot.pem pico_fido.bin"...
espsecure.py v4.12.0
Signature block 0 is valid (RSA).
Signature block 0 verification successful using the supplied key (RSA).
Signature block 1 invalid. Skipping.
Signature block 2 invalid. Skipping.
#### HSM
# Validar assinatura do bootloader:
$ idf.py secure-verify-signature --keyfile /home/bruno/.config/picokeys/signing/secure-boot.pem bootloader/bootloader.bin
Executing action: secure-verify-signature
Running espsecure in directory /home/bruno/src/picokeys/pico-hsm/build
Executing "/home/bruno/.espressif/python_env/idf5.5_py3.12_env/bin/python -m espsecure verify_signature --version 2 --keyfile /home/bruno/.config/picokeys/signing/secure-boot.pem bootloader/bootloader.bin"...
espsecure.py v4.12.0
Signature block 0 is valid (RSA).
Signature block 0 verification successful using the supplied key (RSA).
Signature block 1 invalid. Skipping.
Signature block 2 invalid. Skipping.
# Validar assinatura do firmware:
$ idf.py secure-verify-signature --keyfile /home/bruno/.config/picokeys/signing/secure-boot.pem pico_hsm.bin
Executing action: secure-verify-signature
Running espsecure in directory /home/bruno/src/picokeys/pico-hsm/build
Executing "/home/bruno/.espressif/python_env/idf5.5_py3.12_env/bin/python -m espsecure verify_signature --version 2 --keyfile /home/bruno/.config/picokeys/signing/secure-boot.pem pico_hsm.bin"...
espsecure.py v4.12.0
Signature block 0 is valid (RSA).
Signature block 0 verification successful using the supplied key (RSA).
Signature block 1 invalid. Skipping.
Signature block 2 invalid. Skipping.
Gravar a imagem com Secure Boot para FIDO e HSM
O processo de gravar o firmware assinado é irreversível.
Vamos gerar o arquivo unificado para depois gravar:
mkdir -p release && cd build
# Combine os arquivos '.bin' gerados na compilação FIDO:
esptool.py --chip ESP32-S3 merge_bin -o ../release/pico_fido_esp32-s3.bin \
--flash_mode dio --flash_freq 80m --flash_size keep \
0x0 bootloader/bootloader.bin \
0x10000 partition_table/partition-table.bin \
0x20000 pico_fido.bin
# Combine os arquivos '.bin' gerados na compilação HSM:
esptool.py --chip ESP32-S3 merge_bin -o ../release/pico_hsm_esp32-s3.bin \
--flash_mode dio --flash_freq 80m --flash_size keep \
0x0 bootloader/bootloader.bin \
0x10000 partition_table/partition-table.bin \
0x20000 pico_hsm.bin
A compilação gera arquivos separados para o bootloader, a tabela de partições e o firmware. O comando merge_bin acima vai juntar esses arquivos em uma única imagem. Mas temos que passar o endereço da flash em cada um será gravado, já que os arquivos .bin não carregam essa posição por conta própria. O offset da tabela foi aumentado de 0x8000 para 0x10000 porque o build mostrou que o bootloader tinha 0x9060 bytes, que é maior que o espaço de 0x8000 reservado originalmente.
Depois de ter a imagem pronta, podemos gravar ela na placa:
# Para FIDO:
esptool.py --chip ESP32-S3 \
-p /dev/ttyACM0 \
write_flash 0x0 \
release/pico_fido_esp32-s3.bin
# Para HSM:
esptool.py --chip ESP32-S3 \
-p /dev/ttyACM0 \
write_flash 0x0 \
release/pico_hsm_esp32-s3.bin
Desconecte e reconecte a placa e entre em modo de download, depois execute o comando abaixo:
$ esptool.py --chip ESP32-S3 --port /dev/ttyACM0 --no-stub get_security_info
esptool.py v4.12.0
Serial port /dev/ttyACM0
Connecting...
Chip is ESP32-S3 in Secure Download Mode
Enabling default SPI flash mode...
Security Information:
=====================
Flags: 0x000000f5 (0b11110101)
Key Purposes: (9, 0, 0, 0, 0, 0, 12)
BLOCK_KEY0 - SECURE_BOOT_DIGEST0
BLOCK_KEY1 - USER/EMPTY
BLOCK_KEY2 - USER/EMPTY
BLOCK_KEY3 - USER/EMPTY
BLOCK_KEY4 - USER/EMPTY
BLOCK_KEY5 - USER/EMPTY
Chip ID: 9
API Version: 0
Secure Boot: Enabled
Secure Boot Key Revocation Status:
Secure Boot Key1 is Revoked
Secure Boot Key2 is Revoked
Flash Encryption: Disabled
SPI Boot Crypt Count (SPI_BOOT_CRYPT_CNT): 0x0
JTAG: Permanently Disabled
Hard resetting via RTS pin...
A linha BLOCK_KEY0 - SECURE_BOOT_DIGEST0 mostra que o digest da chave pública usada pelo Secure Boot foi gravado no bloco BLOCK_KEY0 dos eFuses. Também podemos observar as mensagens Secure Boot Key1 is Revoked e Secure Boot Key2 is Revoked, indicando que os slots 1 e 2 foram revogados e não poderão mais ser usados para validar assinaturas. Dessa forma, apenas a chave associada ao SECURE_BOOT_DIGEST0 permanece autorizada.
Fontes
https://www.picokeys.com/pico-hsm/
https://www.picokeys.com/pico-fido/
https://docs.picokeys.com/picohsm/first-steps/
https://www.picokeys.com/pico-openpgp/
https://www.picokeys.com/picokeyapp/
https://github.com/polhenarejos/pico-vault-enroller
https://www.picokeys.com/getting-started/
https://docs.picokeys.com/production/otp-fuses
https://docs.espressif.com/projects/esp-idf/en/stable/esp32s3/security/secure-boot-v2.html
https://docs.espressif.com/projects/esp-idf/en/v5.2/esp32s3/security/flash-encryption.html
https://docs.espressif.com/projects/esp-idf/en/stable/esp32s3/api-reference/peripherals/hmac.html
https://docs.picokeys.com/security/threat-model/
https://docs.picokeys.com/picohsm/sign-verify/
https://docs.picokeys.com/picohsm/key-generation/
https://docs.espressif.com/projects/esp-idf/en/v5.5/esp32s3/security/secure-boot-v2.html
https://docs.picokeys.com/production/otp-fuses/
https://docs.picokeys.com/picokeyapp/security/
https://docs.espressif.com/projects/esp-idf/en/v5.2/esp32/api-reference/kconfig.html