From e80e979fdd28512e8d20090b303dbc9c0f806d46 Mon Sep 17 00:00:00 2001 From: Andrew Kozlik Date: Fri, 14 Mar 2025 18:40:21 +0100 Subject: [PATCH] docs(core): Document Optiga configuration. [no changelog] --- core/embed/sec/optiga/optiga.c | 5 +- docs/SUMMARY.md | 1 + docs/core/misc/index.md | 1 + docs/core/misc/optiga.md | 111 +++++++++++++++++++++++++++++++++ 4 files changed, 116 insertions(+), 2 deletions(-) create mode 100644 docs/core/misc/optiga.md diff --git a/core/embed/sec/optiga/optiga.c b/core/embed/sec/optiga/optiga.c index a5262bc08b..bcb9eadb71 100644 --- a/core/embed/sec/optiga/optiga.c +++ b/core/embed/sec/optiga/optiga.c @@ -37,7 +37,7 @@ // Digest of the stretched PIN (OID 0xF1D4). #define OID_STRETCHED_PIN (OPTIGA_OID_DATA + 4) -// Counter-protected key for HMAC-SHA256 PIN stretching step (OID 0xF1D5). +// Counter-protected key for HMAC-SHA256 PIN stretching step (OID 0xF1D8). #define OID_PIN_HMAC (OPTIGA_OID_DATA + 8) // Counter which limits the guesses at OID_STRETCHED_PIN (OID 0xE120). @@ -745,7 +745,8 @@ optiga_pin_result optiga_pin_verify_v4( ui_progress(); - // Authorise using OID_PIN_SECRET so that we can write to OID_PIN_COUNTER. + // Authorise using OID_PIN_SECRET so that we can write to + // OID_STRETCHED_PIN_CTR. if (optiga_set_auto_state(OPTIGA_OID_SESSION_CTX, OID_PIN_SECRET, out_secret, OPTIGA_PIN_SECRET_SIZE) != OPTIGA_SUCCESS) { ret = OPTIGA_PIN_ERROR; diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md index 3912d48003..b6724a0e20 100644 --- a/docs/SUMMARY.md +++ b/docs/SUMMARY.md @@ -24,6 +24,7 @@ - [SLIP-39](core/misc/slip0039.md) - [Translation data format](core/misc/translations.md) - [UI Layout lifecycle](core/misc/layout-lifecycle.md) + - [Optiga configuration](core/misc/optiga.md) - [Legacy](legacy/index.md) - [Firmware format](legacy/firmware-format.md) - [Python](python/index.md) diff --git a/docs/core/misc/index.md b/docs/core/misc/index.md index fe2ea83d9f..3fe6a764cb 100644 --- a/docs/core/misc/index.md +++ b/docs/core/misc/index.md @@ -8,3 +8,4 @@ Topics that do not fit elsewhere: - [Memory fragmentation management](fragmentation.md) - [Running Trezor firmware on STM32F429I-DISC1](disc1.md) - [Translation data format](translations.md) +- [Optiga configuration](optiga.md) diff --git a/docs/core/misc/optiga.md b/docs/core/misc/optiga.md new file mode 100644 index 0000000000..c70ce3b27e --- /dev/null +++ b/docs/core/misc/optiga.md @@ -0,0 +1,111 @@ +# Optiga data object configuration + +The following table outlines the Optiga data object configuration used in Trezor Safe 3 and Trezor Safe 5. + +| **OID** | **Name** | **Read** | **Write** | **Execute** | **Type/Usage** | **Size (bytes)** | **Notes** | **Storage version** | +| --- | --- | --- | --- | --- | --- | --- | --- | --- | +| E0E0 | `CERT_INF` | Always | Never | Always | DEVCERT | | Infineon certificate. | | +| E0E1 | `CERT_DEV` | Always | Never | Always | DEVCERT | | Device certificate chain. | | +| E0E2 | `CERT_FIDO` | Always | Never | Always | DEVCERT | | FIDO attestation certificate. | | +| E0E3 | | | | | DEVCERT | ≤ 1728 | Unused public key certificate. | | +| E0E8 | | | | | TA | ≤ 1200 | Unused root CA public key certificate. | | +| E0E9 | | | | | TA | ≤ 1200 | Unused root CA public key certificate. | | +| E0F0 | `KEY_DEV` | Never | Never | Conf(`KEY_PAIRING`) | Sign | | NIST P-256 device private key. | | +| E0F1 | | | | | | | Unused private ECC key. | | +| E0F2 | `KEY_FIDO` | Never | Never | Conf(`KEY_PAIRING`) | Sign | | NIST P-256 FIDO attestation private key. | | +| E0F3 | `PIN_ECDH` | Never | Always | Luc(`PIN_TOTAL_CTR`) | KeyAgree | | NIST P-256 key for ECDH PIN stretching step. | ≥ 3 | +| E120 | `STRETCHED_PIN_CTR` | Always | Auto(`PIN_SECRET`) | Always | UPCTR | 8 | Counter which limits the guesses at `STRETCHED_PIN`. Limit set to 16. | ≥ 3 | +| E121 | `PIN_TOTAL_CTR` | Always | Never | Always | UPCTR | 8 | Counter which limits the total number of PIN stretching operations over the lifetime of the device. Limit set to 600000, meaning 150000 unlock attempts. | ≥ 3 | +| E122 | `PIN_HMAC_CTR` | Always | Auto(`STRETCHED_PIN`) | Always | UPCTR | 8 | Counter which limits the use of `PIN_HMAC`. Limit set to 16. | ≥ 5 | +| E123 | | | | | UPCTR | 8 | Unused monotonic counter. | | +| E140 | `KEY_PAIRING` | Never | Never | Always | PTFBIND | 32 | Shared platform binding secret. | | +| E200 | `PIN_CMAC` | Never | Always | Luc(`PIN_TOTAL_CTR`) | Enc | 32 | Key for AES-CMAC PIN stretching step. | ≥ 3 | +| F1D0 | `PIN_SECRET` | Auto(`STRETCHED_PIN`) | Always | Always | AUTOREF | 32 | Counter-protected PIN secret and reset key for `STRETCHED_PIN_CTR`. | ≥ 3 | +| F1D1 | `PIN_HMAC_V4` | Never | Never | Luc(`PIN_TOTAL_CTR`) | PRESSEC | 32 | Deprecated key for HMAC-SHA256 PIN stretching step. | 3, 4 | +| F1D2 | | | | | | ≤ 140 | Unused data object. | | +| F1D3 | | | | | | ≤ 140 | Unused data object. | | +| F1D4 | `STRETCHED_PIN` | Never | Auto(`PIN_SECRET`) | Luc(`STRETCHED_PIN_CTR`) | AUTOREF | 32 | Digest of the stretched PIN. | ≥ 3 | +| F1D5 | | | | | | ≤ 140 | Unused data object. | | +| F1D6 | | | | | | ≤ 140 | Unused data object. | | +| F1D7 | | | | | | ≤ 140 | Unused data object. | | +| F1D8 | `PIN_HMAC` | Never | Auto(`STRETCHED_PIN`) | Luc(`PIN_HMAC_CTR`) | PRESSEC | 32 | Counter-protected key for HMAC-SHA256 PIN stretching step. | ≥ 5 | +| F1D9 | | | | | | ≤ 140 | Unused data object. | | +| F1DA | | | | | | ≤ 140 | Unused data object. | | +| F1DB | | | | | | ≤ 140 | Unused data object. | | +| F1E0 | | | | | | ≤ 1500 | Unused data object. | | +| F1E1 | | | | | | ≤ 1500 | Unused data object. | | + +# Optiga PIN protection + +The diagram below summarizes the main relationships between the data objects that play a role in PIN protection. +An arrow from *X* to *Y* indicates that *X* authorizes the specified operation on *Y*. +* If *X* is a monotonic counter, then the specified operation on *Y* is allowed only if the counter value is below the threshold. The operation causes *X* to be incremented. +* If *X* is a secret, then the specified operation on *Y* is allowed only if the host MCU proves knowledge of the value stored in *X*. +* If *X* is Always, then the specified operation is always allowed. + +```mermaid +graph LR +Always -- Write --> PIN_SECRET +PIN_SECRET -- Write --> STRETCHED_PIN +PIN_SECRET -- Write --> STRETCHED_PIN_CTR +STRETCHED_PIN_CTR -- Execute --> STRETCHED_PIN +STRETCHED_PIN -- Read --> PIN_SECRET +STRETCHED_PIN -- Write --> PIN_HMAC_CTR +PIN_HMAC_CTR -- Execute --> PIN_HMAC +STRETCHED_PIN -- Write --> PIN_HMAC +``` + +## Stretching pattern + +The Optiga's role in PIN protection is to add a series of PIN stretching steps to the process of computing the storage key encryption key (KEK). All stretching operations are limited either by the global counter `PIN_TOTAL_CTR` or by one of the PIN attempt counters `PIN_HMAC_CTR` and `STRETCHED_PIN_CTR`. Each stretching step is wrapped in the following pattern: +```py +digest = hmac_sha256(stretched_pin, "") +output = optiga_operation(..., digest) +stretched_pin = hmac_sha256(stretched_pin, output) +``` +In this pattern, the stretched PIN is first processed using a one-way function before sending it to the Optiga. This ensures that in the unlikely case of an attacker recording communication between the MCU and Optiga or extracting the secret in `STRETCHED_PIN`, they will not gain knowledge of the stretched PIN, only its digest. Second, the digest is sent to the Optiga which uses it to generate an output. That output is then used to stretch the PIN. This method ensures that if the user chooses a high-entropy PIN or if at least one of the preceding stretching operations remains secure, then even if the communication link or the Optiga is completely compromised, possibly even malicious, it will not reduce the security of the Trezor any more than if the Optiga was not integrated into the Trezor in the first place. + +## Optiga stretching process + +The following pseudocode outlines the process of stretching the PIN by Optiga. +```py +optiga_pin_verify(stretched_pin): + # Stretch the PIN with the secrets in PIN_CMAC and PIN_ECDH. + PIN_STRETCH_ITERATIONS = 2 + for _ in range(PIN_STRETCH_ITERATIONS): + # Stretch the PIN with PIN_CMAC and PIN_ECDH secrets in the Optiga. + digest = hmac_sha256(stretched_pin, "") + cmac_out = encrypt_sym(SYM_MODE_CMAC, PIN_CMAC, digest) + point = hash_to_curve(digest) + ecdh_out = calc_ssec(CURVE_P256, PIN_ECDH, point) + stretched_pin = hmac_sha256(stretched_pin, cmac_out + ecdh_out) + + # Stretch the PIN with the secret in PIN_HMAC. + digest = hmac_sha256(stretched_pin, "") + hmac_buffer = encrypt_sym(SYM_MODE_HMAC_SHA256, PIN_HMAC, digest) + stretched_pin = hmac_sha256(stretched_pin, hmac_buffer) + + # Authorise using STRETCHED_PIN so that we can read from PIN_SECRET and reset PIN_HMAC_CTR. + digest = hmac_sha256(stretched_pin, "") + if not set_auto_state(STRETCHED_PIN, digest): + raise InvalidPinError + pin_secret = get_data_object(PIN_SECRET) + stretched_pin = hmac_sha256(stretched_pin, pin_secret) + set_data_object(PIN_HMAC_CTR, COUNTER_RESET) + + # Authorise using PIN_SECRET so that we can reset STRETCHED_PIN_CTR. + set_auto_state(PIN_SECRET, pin_secret) + set_data_object(STRETCHED_PIN_CTR, COUNTER_RESET) + + return stretched_pin +``` + +The first step of the stretching process hardens the PIN protection in case a vulnerability is discovered that allows the extraction of the secret value of a data object in Optiga that has a particular configuration, but does not allow extraction for other kinds of data objects. An attacker would need to be able to extract each of the secrets in `PIN_CMAC`, `PIN_ECDH`, `PIN_HMAC` and `PIN_SECRET` to conduct an offline brute-force search for the PIN. Thus it reduces the number of PIN values that the attacker can test in a unit of time by forcing them to involve the Optiga in each attempt, and restricts the overall number of attempts using `PIN_TOTAL_CTR`. + +The rest of the process makes use of the data objects that are depicted in the diagram. The configuration of these data objects is designed with the goal of making the system resettable under all circumstances, i.e. even if the previous PIN is forgotten, flash storage is wiped and counters are depleted. There are two PIN attempt counters `STRETCHED_PIN_CTR` and `PIN_HMAC_CTR`, which both have a limit of 16 attempts and have identical values under normal circumstances. + +`PIN_HMAC_CTR` limits the number of possible PIN attempts, but together with `PIN_HMAC` it also serves to mitigate a so called *PIN-only* attack. Without this counter it would be possible to partially reset the system by overwriting `PIN_SECRET` and resetting `STRETCHED_PIN_CTR` to gain an unlimited number of attempts at checking for the correct PIN, but without the possibility to derive the correct storage key encryption key, since the `PIN_SECRET` is overwritten. This attack is dangerous in case a user has two devices with the same PIN, because one of the devices could be used to determine the correct PIN and the other could then be unlocked. Note that in order for the mitigation to be effective, write access to `PIN_HMAC_CTR` must be authorized by `STRETCHED_PIN`. This implies that the attacker cannot reset `PIN_HMAC_CTR` without first overwriting `STRETCHED_PIN`, thus ruining the ability of checking for the correct PIN. + +`STRETCHED_PIN_CTR` also limits the number of possible PIN attempts. Its presence is actually not necessary, but serves as an additional countermeasure in case a vulnerability is discovered that allows an attacker to read data objects of type PRESSEC, specifically `PIN_HMAC`. In that case, the attacker would still be restricted by the `STRETCHED_PIN_CTR` in the number of PIN attempts they can make. After they depleted the counter, the worst they could do is overwrite `PIN_SECRET` to execute the aforementioned PIN-only attack. + +`PIN_SECRET` and `STRETCHED_PIN` play an essential role in making the system resettable, yet secure. Since `PIN_SECRET` is used in the last PIN stretching step, there needs to be a data object that grants read access to `PIN_SECRET`, which is why `STRETCHED_PIN` is needed in the system. Anyone attempting to reset the system must first overwrite `PIN_SECRET` thus losing the value that is necessary to complete the PIN stretching. Suppose there was no `PIN_SECRET` in the system and the PIN, stretched by `PIN_HMAC`, was used directly as the storage key encryption key. Then `STRETCHED_PIN` would have to be writable always, and an attacker could rewrite `STRETCHED_PIN`, reset the `PIN_HMAC_CTR`, and gain an unlimited number of attempts to guess the PIN.