Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion deps/device-protocol
5 changes: 5 additions & 0 deletions include/keepkey/firmware/fsm.h
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,11 @@ void fsm_msgApplyPolicies(ApplyPolicies* msg);
// BIP-85 (chain-agnostic, available in all builds)
void fsm_msgGetBip85Mnemonic(const GetBip85Mnemonic* msg);

// Insight metadata key management (slots 4..6 user-provisioned)
void fsm_msgAddMetadataKey(const AddMetadataKey* msg);
void fsm_msgRemoveMetadataKey(const RemoveMetadataKey* msg);
void fsm_msgListMetadataKeys(const ListMetadataKeys* msg);

#ifndef BITCOIN_ONLY
// ethereum
void fsm_msgEthereumGetAddress(EthereumGetAddress* msg);
Expand Down
33 changes: 32 additions & 1 deletion include/keepkey/firmware/storage.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,14 @@
#include "keepkey/firmware/authenticator.h"

#define STORAGE_VERSION \
17 /* Must add case fallthrough in storage_fromFlash after increment*/
18 /* Must add case fallthrough in storage_fromFlash after increment*/

/* Number of user-provisioned metadata signing key slots (slots 4..6).
* Firmware-baked slots 0..3 live in signed_metadata.c and don't count here. */
#define METADATA_USER_KEY_COUNT 3
#define METADATA_USER_KEY_FIRST 4
#define METADATA_USER_KEY_LAST \
(METADATA_USER_KEY_FIRST + METADATA_USER_KEY_COUNT - 1)
#define STORAGE_RETRIES 3

#define RANDOM_SALT_LEN 32
Expand Down Expand Up @@ -165,6 +172,30 @@ void storage_getPolicies(PolicyType* policy_data);
/// \brief Status of policy in storage
bool storage_isPolicyEnabled(const char* policy_name);

typedef struct _MetadataKeyType MetadataKeyType;

/// \brief Add or replace a user-provisioned metadata signing key.
/// \param slot Must be in [METADATA_USER_KEY_FIRST, METADATA_USER_KEY_LAST].
/// \param pubkey 33-byte compressed secp256k1 public key.
/// \param label Null-terminated, ≤16 chars. Displayed during verification.
/// \returns true on success, false on invalid slot/pubkey/label.
bool storage_setMetadataKey(uint32_t slot, const uint8_t* pubkey,
const char* label);

/// \brief Remove a user-provisioned metadata signing key.
/// \returns true if a key was removed, false if slot was empty/invalid.
bool storage_removeMetadataKey(uint32_t slot);

/// \brief Look up a user-provisioned metadata signing key.
/// \param pubkey_out Receives 33 bytes on success.
/// \param label_out Receives null-terminated string (buffer must be ≥17 bytes).
/// \returns true if slot is populated.
bool storage_getMetadataKey(uint32_t slot, uint8_t pubkey_out[33],
char label_out[17]);

/// \brief Number of populated user-provisioned metadata key slots (0..3).
uint32_t storage_getMetadataKeyCount(void);

uint32_t storage_getAutoLockDelayMs(void);
void storage_setAutoLockDelayMs(uint32_t auto_lock_delay_ms);

Expand Down
4 changes: 4 additions & 0 deletions include/keepkey/transport/messages.options
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,10 @@ SignedIdentity.signature max_size:65

ApplyPolicies.policy max_count:6

AddMetadataKey.pubkey max_size:33
AddMetadataKey.label max_size:17
MetadataKeysList.keys max_count:7

# not used in firmware
FirmwareUpload.payload_hash max_size:32
FirmwareUpload.payload max_size:0
Expand Down
3 changes: 3 additions & 0 deletions include/keepkey/transport/types.options
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,6 @@ IdentityType.port max_size:6
IdentityType.path max_size:256

PolicyType.policy_name max_size:15

MetadataKeyType.pubkey max_size:33
MetadataKeyType.label max_size:17
1 change: 1 addition & 0 deletions lib/firmware/fsm.c
Original file line number Diff line number Diff line change
Expand Up @@ -294,3 +294,4 @@ void fsm_msgClearSession(ClearSession* msg) {
#include "fsm_msg_tron.h"
#include "fsm_msg_solana.h"
#include "fsm_msg_bip85.h"
#include "fsm_msg_metadata.h"
165 changes: 165 additions & 0 deletions lib/firmware/fsm_msg_metadata.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
/*
* This file is part of the KeepKey project.
*
* Copyright (C) 2026 KeepKey
*
* This library is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This library 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library. If not, see <http://www.gnu.org/licenses/>.
*/

/* User-provisioned metadata signing key management. Handlers for
* AddMetadataKey, RemoveMetadataKey, ListMetadataKeys. Slots 4..6 are
* user-writable; slots 0..3 are firmware-baked and not enumerated here. */

static void metadata_pubkey_fingerprint(const uint8_t pubkey[33],
char fp_out[9]) {
uint8_t digest[32];
sha256_Raw(pubkey, 33, digest);
/* First 4 bytes → 8 hex chars. Stable, short enough for OLED. */
for (int i = 0; i < 4; i++) {
snprintf(fp_out + i * 2, 3, "%02x", digest[i]);
}
fp_out[8] = '\0';
}

void fsm_msgAddMetadataKey(const AddMetadataKey *msg) {
CHECK_INITIALIZED

if (msg->slot < METADATA_USER_KEY_FIRST ||
msg->slot > METADATA_USER_KEY_LAST) {
fsm_sendFailure(FailureType_Failure_SyntaxError,
"slot must be in user range (4..6)");
layoutHome();
return;
}
if (msg->pubkey.size != 33 ||
(msg->pubkey.bytes[0] != 0x02 && msg->pubkey.bytes[0] != 0x03)) {
fsm_sendFailure(FailureType_Failure_SyntaxError,
"pubkey must be 33-byte compressed secp256k1");
layoutHome();
return;
}
if (msg->label[0] == '\0') {
fsm_sendFailure(FailureType_Failure_SyntaxError, "label must not be empty");
layoutHome();
return;
}

char fp[9];
metadata_pubkey_fingerprint(msg->pubkey.bytes, fp);

/* Confirm 1: slot + label. Lets user abort cheaply before PIN. */
if (!confirm(ButtonRequestType_ButtonRequest_Other, "Add Insight Key",
"Slot %lu\n%s", (unsigned long)msg->slot, msg->label)) {
fsm_sendFailure(FailureType_Failure_ActionCancelled,
"Add metadata key cancelled");
layoutHome();
return;
}

/* Confirm 2: fingerprint. User verifies against company-published value
* out-of-band. Truncating to 8 hex chars is intentional — full SHA256
* doesn't fit and the threat model here is mistake/typo, not collision. */
if (!confirm(ButtonRequestType_ButtonRequest_Other, "Verify Key Fingerprint",
"fp:\n%s", fp)) {
fsm_sendFailure(FailureType_Failure_ActionCancelled,
"Add metadata key cancelled");
layoutHome();
return;
}

CHECK_PIN_UNCACHED

if (!storage_setMetadataKey(msg->slot, msg->pubkey.bytes, msg->label)) {
fsm_sendFailure(FailureType_Failure_ActionCancelled,
"Could not store metadata key");
layoutHome();
return;
}
storage_commit();

fsm_sendSuccess("Metadata key added");
layoutHome();
}

void fsm_msgRemoveMetadataKey(const RemoveMetadataKey *msg) {
CHECK_INITIALIZED

if (msg->slot < METADATA_USER_KEY_FIRST ||
msg->slot > METADATA_USER_KEY_LAST) {
fsm_sendFailure(FailureType_Failure_SyntaxError,
"slot must be in user range (4..6)");
layoutHome();
return;
}

uint8_t pubkey[33];
char label[17];
if (!storage_getMetadataKey(msg->slot, pubkey, label)) {
fsm_sendFailure(FailureType_Failure_Other, "slot is empty");
layoutHome();
return;
}

/* Show fingerprint of the key being removed — defends against stealth
* removal where an attacker briefly accesses an unlocked device. */
char fp[9];
metadata_pubkey_fingerprint(pubkey, fp);

if (!confirm(ButtonRequestType_ButtonRequest_Other, "Remove Insight Key",
"Slot %lu: %s\nfp: %s", (unsigned long)msg->slot, label, fp)) {
fsm_sendFailure(FailureType_Failure_ActionCancelled,
"Remove metadata key cancelled");
layoutHome();
return;
}

CHECK_PIN_UNCACHED

if (!storage_removeMetadataKey(msg->slot)) {
fsm_sendFailure(FailureType_Failure_ActionCancelled,
"Could not remove metadata key");
layoutHome();
return;
}
storage_commit();

fsm_sendSuccess("Metadata key removed");
layoutHome();
}

void fsm_msgListMetadataKeys(const ListMetadataKeys *msg) {
(void)msg;
RESP_INIT(MetadataKeysList);

size_t count = 0;
for (uint32_t slot = METADATA_USER_KEY_FIRST; slot <= METADATA_USER_KEY_LAST;
++slot) {
uint8_t pubkey[33];
char label[17];
if (!storage_getMetadataKey(slot, pubkey, label)) continue;

MetadataKeyType *k = &resp->keys[count++];
memzero(k, sizeof(*k));
k->has_slot = true;
k->slot = slot;
k->has_pubkey = true;
k->pubkey.size = 33;
memcpy(k->pubkey.bytes, pubkey, 33);
k->has_label = true;
strlcpy(k->label, label, sizeof(k->label));
}
resp->keys_count = count;

msg_write(MessageType_MessageType_MetadataKeysList, resp);
}
6 changes: 6 additions & 0 deletions lib/firmware/messagemap.def
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@

MSG_IN(MessageType_MessageType_GetBip85Mnemonic, GetBip85Mnemonic, fsm_msgGetBip85Mnemonic)

MSG_IN(MessageType_MessageType_AddMetadataKey, AddMetadataKey, fsm_msgAddMetadataKey)
MSG_IN(MessageType_MessageType_RemoveMetadataKey, RemoveMetadataKey, fsm_msgRemoveMetadataKey)
MSG_IN(MessageType_MessageType_ListMetadataKeys, ListMetadataKeys, fsm_msgListMetadataKeys)

#ifndef BITCOIN_ONLY
MSG_IN(MessageType_MessageType_EthereumGetAddress, EthereumGetAddress, fsm_msgEthereumGetAddress)
MSG_IN(MessageType_MessageType_EthereumSignTx, EthereumSignTx, fsm_msgEthereumSignTx)
Expand Down Expand Up @@ -119,6 +123,8 @@

MSG_OUT(MessageType_MessageType_Bip85Mnemonic, Bip85Mnemonic, NO_PROCESS_FUNC)

MSG_OUT(MessageType_MessageType_MetadataKeysList, MetadataKeysList, NO_PROCESS_FUNC)

#ifndef BITCOIN_ONLY
MSG_OUT(MessageType_MessageType_EthereumAddress, EthereumAddress, NO_PROCESS_FUNC)
MSG_OUT(MessageType_MessageType_EthereumTxRequest, EthereumTxRequest, NO_PROCESS_FUNC)
Expand Down
Loading
Loading