firmware/docs/secure-elements.md
2026-06-19 12:48:49 -04:00

300 lines
13 KiB
Markdown

# Dual Secure Elements
## Background
The **COLDCARD<sup>&reg;</sup>** Mk4 and Q have two secure elements:
- SE1 (Secure Element 1): Microchip ATECC608
- SE2 (Secure Element 2): Maxim DS28C36B
Because different vendors make them, they do not share bugs and weaknesses.
Although Mk3 uses the SE1 chip, Mk4 uses it a little differently.
SE2 is an entirely new chip, never previously deployed in a COLDCARD.
Both chips use secp256r1 for Elliptic-curve Cryptography (ECC) and
HMAC-SHA256.
## Design Goals
### The Assumption
Assume attackers have physical access to a COLDCARD, have opened
the case, and can probe the bus connections between the MCU and SE1
or SE2. They may even de-solder SE1 and SE2 from the board, and put
active circuits between them and the MCU &mdash; an active MiTM attack.
### The Solutions
Three parties hold secrets in the COLDCARD: the main MCU (microcontroller)
and the two secure elements. Our goal is that **all three** must
be fully compromised to access the seed words. Thus, if one part
has a vulnerability, the COLDCARD as a whole is still secure.
Additionally, knowledge of the correct PIN code is required, even
if all three devices are cracked wide open. (This is a last line
of defence, a brute-force attack on all PIN combinations will breach
it.)
COLDCARD also supports new Trick PIN codes with side effects such
as wiping or bricking the COLDCARD, or providing access to a decoy
or duress wallet. Ideally, attackers will not detect using a false
PIN, even while probing the signals on the board.
## MCU vs. SE1 and SE2
As in Mk1 through Mk3 of the COLDCARD, the MCU has a Pairing Secret.
The MCU uses the Pairing Secret to authenticate itself to SE1 and
vice versa. Flash memory holds the 32-byte Pairing Secret in a
protected area. Only the boot loader code can access this memory,
and the MicroPython code cannot read this area of the chip. Using
an internal firewall feature and PCROP (proprietary code readout
protection) achieves this result.
COLDCARD also shares a secret between SE2 and the MCU. Just like SE1,
this authenticates SE2 to the MCU and encrypts their mutual
communications. The Pairing Secret for SE2 is not stored in SE1 and
is unique from the other Pairing Secret used for SE1.
These Pairing Secrets secure the electrical connections between
SE1, SE2, and the MCU probing attempts. In practice, this means
most commands and responses are XOR'ed with an HMAC-SHA256 value
where the HMAC key is the Pairing Secret and the message is a hash
of the command arguments.
There are also cases where an ECC signature and ECDH establish a
shared secret between devices.
## Seed Decryption
SE1 still holds the seed words, but they are AES-encrypted, and SE1
does not contain a key to decrypt it. The MCU and SE2 store the
seed word decryption key. To access the AES key held in SE2, SE1
has to perform a public key signature and ECDH setup, which requires
the main PIN code. The MCU will only provide its key if both SE1
and SE2 are satisfied.
Earlier COLDCARD versions XOR-ed the stored value with a secret in
the MCU (one-time pad), but this new approach is more powerful
because it mixes in values from the SE2 and MCU using AES; this
increases flexibility and resistance to known plain text attacks.
## All the Keys
| Symbol | Chip's Name | Type | Holder | Purpose
|-----------------------|---------------------|--------------|----------|----------
| `SE1 pairing` | slot 1 | HMAC | SE1, MCU | Protects communications between SE1 and MCU
| `SE2 pairing` | secret A | HMAC | SE2, MCU | Pairing for SE2
| `SE2 comms` | keypair A | ECC | SE2 | MCU captures pubkey half, used in ECDH comms
| `SE joiner` | slot 7, pubkey C | ECC | SE1/SE2 | SE2 knows only public part, SE1 has privkey
| `pin stretch` | slot 2 | HMAC | SE1 | Key stretching for PIN entry and anti-phish words
| `firmware` | slot 14 | SHA256d | SE1 | Firmware checksum, controls green/red LEDs
| `nonce/chksum` | slot 10 | data | SE1 | AES nonce and GMAC tag, protected by PIN
| `SE2 easy key` | page 14 | AES via HMAC | SE2 | Another SE2 part of AES seed key
| `SE2 hard key` | page 15 | AES via ECC | SE2 | SE2's part of AES seed key; ECC used to unlock
| `tpin key` | `tpin_key` | HMAC(key) | MCU | Key for HMAC used to encrypt trick PINs
| `trick PIN slots` | pages 0-12 | HMAC | SE2 | Protect duress wallet seeds and pins (6 spots)
| `SE2 trash` | secret B | HMAC | SE2 | Used to destroy values (only SE2 knows the value)
| `hash cache secret` | `hash_cache_secret` | XOR/AES | MCU | In-memory encryption of actual PIN when unlocked
| `mcu hmac key` | `mcu_hmac_key` | HMAC | MCU | Used as HMAC key to compress other keys
| `replaceable mcu key` | `MCU_KEYS` | AES | MCU | Replaceable MCU key (up to 256 times)
All keys listed are 32 bytes long and picked randomly using the hardware RNG.
An entered PIN code goes through the same hashing process as with
previous COLDCARD versions. This involved process uses a value in
SE1 for key stretching purposes. Before the final hashed PIN value
unlocks a few slots in SE1, `MCU pin check` HMACs the hashed PIN
value and uses the result to check for Trick PINs in SE2. If no
Trick PINs decode in SE2's memory, the PIN is tested against SE1.
If the PIN is correct:
- The the PIN-attempt counter resets to 13 attempts remaining.
- The `SE joiner` slot on SE1 establishes ECDH communication between the MCU and SE1.
- `SE joiner` authorizes the page on SE2 holding `SE2 seed key`.
The `SE2 seed key` value and the `MCU seed key` are now available
to decrypt the seed words unlocked by the PIN.
These sources combine to create the final seed decryption key:
k = HMAC-SHA256 (key = (`mcu hmac key`), msg = (`SE2 easy key` + `SE2 hard key` + current `replaceable mcu key`))
### AES Details
COLDCARD uses AES-256 in
[CTR mode](https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Counter_(CTR)).
Message authentication involves adding 32 bytes of zeros to the
end of the message and checking they decode correctly.
A new keyslot, 10, stores the MAC data (encrypted zeros).
The starting nonce for CTR mode is fixed random value composed of
the first 15 bytes of the `mcu hmac key`, followed by a zero (this
increments if more than 16 bytes are encrypted or decrypted).
### MCU Keys
There are two keys in the MCU that affect the seed, the `mcu hmac
key`, and the `replaceable mcu key`.
The `mcu hmac key` is fixed at factory setup time. It acts as the key for
an HMAC operation that compresses the seed key.
The `replaceable mcu keys` are picked at runtime and saved to flash.
There is only one key active at a time, but there are a few slots
in flash for new ones. When the COLDCARD performs a wipe, it clears
the current `mcu replaceable key`. That process is very fast and
does not have any external signals to betray it's occurring. There
is no need to clear the keys in SE2 or SE1 since without the `mcu
replaceable key`, the actual AES key is still unknown.
New COLDCARDs will ship from the factory with no key picked yet.
Once the main PIN is set and a wallet imported or created, the
first `mcu replaceable key` is picked.
Each wipe operation consumes a replaceable key, and there are a limited
number of them (256) available during the life of the COLDCARD.
### Captured values
The MCU captures public and semi-public values and prohibits them
from changing. These include:
- SE1 and SE2 chip serial numbers (used in most HMAC responses, fully public)
- The public key for `SE joiner` and `SE2 comms` key pairs
Logically, these values are potentially readable at runtime. Since
they are not expected to change, storing them assures they will not
change when under attack (perhaps by substituting a different part).
Blocking the values from read-back out of the secure element is
done where possible.
## Trick PINs
Supporting certain COLDCARD features requires several distinct PIN
codes with various side effects, such as:
- Unlocking a duress wallet
- Triggering a long login delay
- Bricking the COLDCARD
- Blanking the COLDCARD
SE2's even-numbered pages store these PINs with the adjacent odd
slot holding the corresponding secret. The MCU tries each PIN a
user enters against all the slots and works silently to support the
Trick features.
The type of support depends on the type of Trick. Duress wallets
require storing 32 or 64 bytes of seed words (generated from the true
seed via BIP-85). Other cases dictate encoding a short numeric code
provided to higher layers for implementation. For example, a flag
in that code can trigger the boot ROM to wipe the `mcu seed key`.
External actors cannot interrupt or monitor this change because it
is internal to the MCU. The `mcu seed key` is as critical as the
other parts of the AES key to access the seed words. The `mcu seed
key` being zero makes the seed permanently inaccessible.
The MCU code may continue speaking to SE1 to complete the fraud,
but in general, SE1 will no longer store the duress wallet or Brick
Me PINs as in previous generations (Mk1-3). Mk4 and Q implement
those feature in SE2.
### Trick PIN Operation
When a PIN is entered, it is hashed through a series of operations
that take a round trip to SE1. This is the same as the Mk3 using
its secure element for key stretching. A 32-byte deterministic hash,
dependent on secrets from the main MCU and SE1 and unique to each
COLDCARD, is returned. The hash is compared against all 14 of the
slots in SE2.
A few least significant bits (LSB) are masked out of the hash. If
the PIN entered matches one of the slots, the masked-out bits are
checked. Based on the check, two values are taken: `tc_arg` and
`tc_flags`. These two values implement flags and arguments
required to implement the Trick PIN features.
### Trick PIN slot data
The flags are checked along with their corresponding arguments if
a match is found while iterating through all the slots. Some flags
are implemented directly in the boot loader before anything is sent
to Micropython. Other flags are passed to Micropython for implementation
or are implemented in both the boot ROM and Micropython.
**Example 1** A flag is set for Fast Wipe and an attacker has control
of the I2C-bus going to SE2 and the one-wire bus going to SE1. They
can attempt to block the wipe in hopes of trying different PINs
without damaging anything, but they will fail. If `tc_flags`, which
is a bitmask, indicates a Fast Wipe, the seed wiping happens inside
the main MCU. It clears data inside the main MCU extremely quickly
and with no external signals &mdash; either before or after &mdash;
to indicate it happened. This prevents blocking Fast Wipe.
**Example 2** The login countdown feature allows specifying a Trick
PIN. When the Trick PIN is entered, the COLDCARD will show a countdown
timer and the time that must elapse before logging in is allowed.
`tc_arg` encodes the number of minutes on the delay.
**Example 3** Although SE2 holds the Trick PINs, it does not know
the True PIN and Delta Mode will not reveal the full True PIN. The
"Delta Mode" Trick PIN has its unique hash result, and an entry from SE1 is used
to calculate the True PIN. This is accomplished by masking out the
last four digits of the hash and substituting four digits from
`tc_arg`. Limiting it to four digits enables a difference between
the Delta PIN and the True PIN.
This makes the True PIN unknowable without knowing the Delta Mode
Trick PIN; SE2 cannot be searched for the True PIN and generating
the PIN hashes for SE2 would only be possible after cracking SE1
and the main MCU.
## Spare Slots
Moving the duress wallet and Brick Me PINs to SE2 left free storage
inside SE1. This storage is called Spare Secrets. Spare Secrets has
3 &times; 72 bytes of space, protected by the same measures as the
seed words.
Mk4/Q still supports the Long Secret (416 bytes), but its API is
changed. The slow speed of fetching the Long Secret in 32-byte
blocks due to the reconstructing the primary AES Seed Key for each
call necessitated the change.
## Observations
It's essential that SE2 cannot validate a PIN code. Brute-forcing
SE2 would be easy because it lacks SE1's rate limiting (or usage
counter), so SE2 only stores Trick PINs. Testing against the Trick
PINs would require compromising the MCU. Due to SE2's performance,
the maximum attempt rate would be 6 ms. However, controlling the
MCU makes that testing pointless to such an attacker; they could
just NOP-out the Trick PIN checking.
A user expecting attackers smart enough to crack open the case and
monitor the buses should provide a Trick PIN that wipes the COLDCARD's
secrets and bricks it. Sophisticated attackers could detect Trick
PINs that continue operation (duress PINs), unlike the average thug.
## Fast Brick
Quickly bricking the system is done by rotating the SE1
pairing secret by mixing in a random nonce via the chip's key
rotation process. Only a knowledge of the old pairing secret is
needed for this change. This is similar the to `brick_me` PIN
and how that worked on previous products.