1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2024-12-26 16:18:22 +00:00
trezor-firmware/core/embed/sec/optiga/optiga.c
2024-11-18 09:41:02 +01:00

981 lines
32 KiB
C

/*
* 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 <trezor_rtl.h>
#include <sec/optiga.h>
#include <sec/optiga_commands.h>
#include <sec/optiga_transport.h>
#include "hash_to_curve.h"
#include "hmac.h"
#include "memzero.h"
#include "rand.h"
#include "storage.h"
#ifdef KERNEL_MODE
// Counter-protected PIN secret and reset key for OID_STRETCHED_PIN_CTR (OID
// 0xF1D0).
#define OID_PIN_SECRET (OPTIGA_OID_DATA + 0)
// 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).
#define OID_PIN_HMAC (OPTIGA_OID_DATA + 8)
// Counter which limits the guesses at OID_STRETCHED_PIN (OID 0xE120).
#define OID_STRETCHED_PIN_CTR (OPTIGA_OID_COUNTER + 0)
// Counter which limits the use of OID_PIN_HMAC (OID 0xE122).
#define OID_PIN_HMAC_CTR (OPTIGA_OID_COUNTER + 2)
// Counter which limits the total number of PIN stretching operations over the
// lifetime of the device (OID 0xE121).
#define OID_PIN_TOTAL_CTR (OPTIGA_OID_COUNTER + 1)
// Key for HMAC-SHA256 PIN stretching step used in storage version 3 and 4 (OID
// 0xF1D1).
#define OID_PIN_HMAC_V4 (OPTIGA_OID_DATA + 1)
// Key for AES-CMAC PIN stretching step (OID 0xE200).
#define OID_PIN_CMAC OPTIGA_OID_SYM_KEY
// Key for ECDH PIN stretching step (OID 0xE0F3).
#define OID_PIN_ECDH (OPTIGA_OID_ECC_KEY + 3)
// The number of times that PIN stretching is repeated.
#define PIN_STRETCH_ITERATIONS 2
// The throttling delay when the security event counter is at its maximum.
#define OPTIGA_T_MAX_MS 5000
// Value of the PIN counter when it is reset.
static const uint8_t COUNTER_RESET[] = {0, 0, 0, 0, 0, 0, 0, PIN_MAX_TRIES};
// Value of the PIN counter with one extra attempt needed in optiga_pin_set().
static const uint8_t COUNTER_RESET_EXTRA[] = {0, 0, 0, 0,
0, 0, 0, PIN_MAX_TRIES + 1};
// Initial value of the counter which limits the total number of PIN stretching
// operations. The limit is 600000 stretching operations, which equates to
// 300000 / PIN_STRETCH_ITERATIONS unlock operations over the lifetime of the
// device.
static const uint8_t PIN_TOTAL_CTR_INIT[] = {0, 0, 0, 0, 0, 0x09, 0x27, 0xC0};
static const optiga_metadata_item TYPE_AUTOREF =
OPTIGA_META_VALUE(OPTIGA_DATA_TYPE_AUTOREF);
static const optiga_metadata_item TYPE_PRESSEC =
OPTIGA_META_VALUE(OPTIGA_DATA_TYPE_PRESSEC);
static const optiga_metadata_item ACCESS_STRETCHED_PIN =
OPTIGA_ACCESS_CONDITION(OPTIGA_ACCESS_COND_AUTO, OID_STRETCHED_PIN);
static const optiga_metadata_item ACCESS_PIN_SECRET =
OPTIGA_ACCESS_CONDITION(OPTIGA_ACCESS_COND_AUTO, OID_PIN_SECRET);
static const optiga_metadata_item ACCESS_STRETCHED_PIN_CTR =
OPTIGA_ACCESS_CONDITION(OPTIGA_ACCESS_COND_LUC, OID_STRETCHED_PIN_CTR);
static const optiga_metadata_item ACCESS_PIN_TOTAL_CTR =
OPTIGA_ACCESS_CONDITION(OPTIGA_ACCESS_COND_LUC, OID_PIN_TOTAL_CTR);
static const optiga_metadata_item ACCESS_PIN_HMAC_CTR =
OPTIGA_ACCESS_CONDITION(OPTIGA_ACCESS_COND_LUC, OID_PIN_HMAC_CTR);
// Size of the DER BIT STRING header required for inputs to optiga_calc_ssec().
#define BIT_STRING_HEADER_SIZE 3
// Size of the CMAC/HMAC prefix returned by Optiga.
#define ENCRYPT_SYM_PREFIX_SIZE 3
optiga_sign_result optiga_sign(uint8_t index, const uint8_t *digest,
size_t digest_size, uint8_t *signature,
size_t max_sig_size, size_t *sig_size) {
if (index >= OPTIGA_ECC_KEY_COUNT) {
return OPTIGA_SIGN_ERROR;
}
optiga_result res =
optiga_calc_sign(OPTIGA_OID_ECC_KEY + index, digest, digest_size,
&signature[2], max_sig_size - 2, sig_size);
if (res != OPTIGA_SUCCESS) {
uint8_t error_code = 0;
if (res == OPTIGA_ERR_CMD &&
optiga_get_error_code(&error_code) == OPTIGA_SUCCESS &&
error_code == OPTIGA_ERR_CODE_ACCESS_COND) {
return OPTIGA_SIGN_INACCESSIBLE;
} else {
return OPTIGA_SIGN_ERROR;
}
}
// Add sequence tag and length.
if (*sig_size >= 0x80) {
// Length not supported.
return OPTIGA_SIGN_ERROR;
}
signature[0] = 0x30;
signature[1] = *sig_size;
*sig_size += 2;
return OPTIGA_SIGN_SUCCESS;
}
bool optiga_cert_size(uint8_t index, size_t *cert_size) {
*cert_size = 0;
if (index >= OPTIGA_CERT_COUNT) {
return false;
}
uint8_t metadata_bytes[OPTIGA_MAX_METADATA_SIZE] = {0};
size_t metadata_size = 0;
optiga_metadata metadata = {0};
optiga_result ret =
optiga_get_data_object(OPTIGA_OID_CERT + index, true, metadata_bytes,
sizeof(metadata_bytes), &metadata_size);
if (OPTIGA_SUCCESS != ret) {
return false;
}
ret = optiga_parse_metadata(metadata_bytes, metadata_size, &metadata);
if (OPTIGA_SUCCESS != ret || metadata.used_size.ptr == NULL) {
return false;
}
for (int i = 0; i < metadata.used_size.len; ++i) {
*cert_size = (*cert_size << 8) + metadata.used_size.ptr[i];
}
return true;
}
bool optiga_read_cert(uint8_t index, uint8_t *cert, size_t max_cert_size,
size_t *cert_size) {
if (index >= OPTIGA_CERT_COUNT) {
return false;
}
optiga_result ret = optiga_get_data_object(OPTIGA_OID_CERT + index, false,
cert, max_cert_size, cert_size);
return OPTIGA_SUCCESS == ret;
}
bool optiga_read_sec(uint8_t *sec) {
size_t size = 0;
optiga_result ret = optiga_get_data_object(OPTIGA_OID_SEC, false, sec,
sizeof(uint8_t), &size);
return ret == OPTIGA_SUCCESS && size == sizeof(uint8_t);
}
void optiga_set_sec_max(void) {
uint8_t invalid_point[] = {
0x03, 0x42, 0x00, 0x04, 0xe2, 0x67, 0x5b, 0xe0, 0xbb, 0xf4, 0xfb, 0x9d,
0xec, 0xaa, 0x1e, 0x96, 0xac, 0xc8, 0xa7, 0xca, 0xd0, 0x05, 0x84, 0xfe,
0xfd, 0x7f, 0x24, 0xc6, 0xe7, 0x72, 0x5b, 0x56, 0xb3, 0x45, 0x06, 0x67,
0xbc, 0x73, 0xe3, 0xb8, 0xf5, 0x5d, 0x1c, 0xad, 0xa0, 0x3e, 0x59, 0x1b,
0x3b, 0x9c, 0x6e, 0xc4, 0xb6, 0xd1, 0x05, 0xf7, 0xd8, 0xc0, 0x67, 0x0d,
0xfb, 0xcc, 0xea, 0xb1, 0x65, 0xdb, 0xa6, 0x5f};
uint8_t buffer[32] = {0};
size_t size = 0;
optiga_calc_ssec(OPTIGA_CURVE_P256, OID_PIN_ECDH, invalid_point,
sizeof(invalid_point), buffer, sizeof(buffer), &size);
}
uint32_t optiga_estimate_time_ms(storage_pin_op_t op) {
uint8_t sec = 0;
if (!optiga_read_sec(&sec)) {
return UINT32_MAX;
}
// Heuristic: The SEC will increase by about 4 during the operation up to a
// maximum of 255.
sec = (sec < 255 - 4) ? sec + 4 : 255;
// If the SEC is above 127, then Optiga introduces a throttling delay before
// the execution of each protected command. The delay grows propotionally to
// the SEC value up to a maximum delay of OPTIGA_T_MAX_MS.
uint32_t throttling_delay =
sec > 127 ? (sec - 127) * OPTIGA_T_MAX_MS / 128 : 0;
// To estimate the overall time of the PIN operation we multiply the
// throttling delay by the number of protected Optiga commands and add the
// time required to execute all Optiga commands without throttling delays.
switch (op) {
case STORAGE_PIN_OP_SET:
return throttling_delay * 6 + 1300;
case STORAGE_PIN_OP_VERIFY:
return throttling_delay * 7 + 1000;
case STORAGE_PIN_OP_CHANGE:
return throttling_delay * 13 + 2300;
default:
return 0;
}
}
bool optiga_random_buffer(uint8_t *dest, size_t size) {
while (size > OPTIGA_RANDOM_MAX_SIZE) {
if (optiga_get_random(dest, OPTIGA_RANDOM_MAX_SIZE) != OPTIGA_SUCCESS) {
return false;
}
dest += OPTIGA_RANDOM_MAX_SIZE;
size -= OPTIGA_RANDOM_MAX_SIZE;
}
if (size < OPTIGA_RANDOM_MIN_SIZE) {
static uint8_t buffer[OPTIGA_RANDOM_MIN_SIZE] = {0};
optiga_result ret = optiga_get_random(buffer, OPTIGA_RANDOM_MIN_SIZE);
memcpy(dest, buffer, size);
return ret == OPTIGA_SUCCESS;
}
return optiga_get_random(dest, size) == OPTIGA_SUCCESS;
}
static bool read_metadata(uint16_t oid, optiga_metadata *metadata) {
static uint8_t serialized[OPTIGA_MAX_METADATA_SIZE] = {0};
size_t size = 0;
if (optiga_get_data_object(oid, true, serialized, sizeof(serialized),
&size) != OPTIGA_SUCCESS) {
return false;
}
return optiga_parse_metadata(serialized, size, metadata) == OPTIGA_SUCCESS;
}
static bool write_metadata(uint16_t oid, const optiga_metadata *metadata) {
uint8_t serialized[OPTIGA_MAX_METADATA_SIZE] = {0};
size_t size = 0;
if (optiga_serialize_metadata(metadata, serialized, sizeof(serialized),
&size) != OPTIGA_SUCCESS) {
return false;
}
return optiga_set_data_object(oid, true, serialized, size) == OPTIGA_SUCCESS;
}
bool optiga_set_metadata(uint16_t oid, const optiga_metadata *metadata) {
// Read the stored metadata.
optiga_metadata metadata_stored = {0};
if (!read_metadata(oid, &metadata_stored)) {
return false;
}
// If the stored metadata are different, then set them as requested.
if (!optiga_compare_metadata(metadata, &metadata_stored)) {
if (!write_metadata(oid, metadata)) {
return false;
}
// Check that the metadata was written correctly.
if (!read_metadata(oid, &metadata_stored)) {
return false;
}
if (!optiga_compare_metadata(metadata, &metadata_stored)) {
return false;
}
}
#if PRODUCTION
// If the metadata aren't locked, then lock them in production builds.
optiga_metadata metadata_locked = {0};
metadata_locked.lcso = OPTIGA_META_LCS_OPERATIONAL;
if (!optiga_compare_metadata(&metadata_locked, &metadata_stored)) {
if (!write_metadata(oid, &metadata_locked)) {
return false;
}
// Check that metadata were locked correctly.
if (!read_metadata(oid, &metadata_stored)) {
return false;
}
if (!optiga_compare_metadata(&metadata_locked, &metadata_stored)) {
return false;
}
}
#endif
return true;
}
static bool optiga_pin_init_metadata(void) {
optiga_metadata metadata = {0};
// Set metadata for counter-protected PIN secret.
memzero(&metadata, sizeof(metadata));
metadata.change = OPTIGA_META_ACCESS_ALWAYS;
metadata.read = ACCESS_STRETCHED_PIN;
metadata.execute = OPTIGA_META_ACCESS_ALWAYS;
metadata.data_type = TYPE_AUTOREF;
if (!optiga_set_metadata(OID_PIN_SECRET, &metadata)) {
return false;
}
// Set metadata for stretched PIN.
memzero(&metadata, sizeof(metadata));
metadata.change = ACCESS_PIN_SECRET;
metadata.read = OPTIGA_META_ACCESS_NEVER;
metadata.execute = ACCESS_STRETCHED_PIN_CTR;
metadata.data_type = TYPE_AUTOREF;
if (!optiga_set_metadata(OID_STRETCHED_PIN, &metadata)) {
return false;
}
// Set metadata for HMAC-SHA256 PIN stretching secret.
memzero(&metadata, sizeof(metadata));
metadata.change = ACCESS_STRETCHED_PIN;
metadata.read = OPTIGA_META_ACCESS_NEVER;
metadata.execute = ACCESS_PIN_HMAC_CTR;
metadata.data_type = TYPE_PRESSEC;
if (!optiga_set_metadata(OID_PIN_HMAC, &metadata)) {
return false;
}
// Set metadata for the counter of guesses at OID_STRETCHED_PIN.
memzero(&metadata, sizeof(metadata));
metadata.change = ACCESS_PIN_SECRET;
metadata.read = OPTIGA_META_ACCESS_ALWAYS;
metadata.execute = OPTIGA_META_ACCESS_ALWAYS;
if (!optiga_set_metadata(OID_STRETCHED_PIN_CTR, &metadata)) {
return false;
}
// Set metadata for the counter of OID_PIN_HMAC uses.
memzero(&metadata, sizeof(metadata));
metadata.change = ACCESS_STRETCHED_PIN;
metadata.read = OPTIGA_META_ACCESS_ALWAYS;
metadata.execute = OPTIGA_META_ACCESS_ALWAYS;
if (!optiga_set_metadata(OID_PIN_HMAC_CTR, &metadata)) {
return false;
}
// Initialize the counter of the total number of PIN stretching operations, if
// write access is possible.
memzero(&metadata, sizeof(metadata));
metadata.change = OPTIGA_META_ACCESS_ALWAYS;
if (write_metadata(OID_PIN_TOTAL_CTR, &metadata)) {
optiga_result res =
optiga_set_data_object(OID_PIN_TOTAL_CTR, false, PIN_TOTAL_CTR_INIT,
sizeof(PIN_TOTAL_CTR_INIT));
if (res != OPTIGA_SUCCESS) {
return false;
}
}
// Set metadata for the counter of the total number of PIN stretching
// operations.
memzero(&metadata, sizeof(metadata));
metadata.change = OPTIGA_META_ACCESS_NEVER;
metadata.read = OPTIGA_META_ACCESS_ALWAYS;
metadata.execute = OPTIGA_META_ACCESS_ALWAYS;
if (!optiga_set_metadata(OID_PIN_TOTAL_CTR, &metadata)) {
return false;
}
// Set metadata for AES-CMAC PIN stretching secret.
memzero(&metadata, sizeof(metadata));
metadata.change = OPTIGA_META_ACCESS_ALWAYS;
metadata.read = OPTIGA_META_ACCESS_NEVER;
metadata.execute = ACCESS_PIN_TOTAL_CTR;
metadata.key_usage = OPTIGA_META_KEY_USE_ENC;
if (!optiga_set_metadata(OID_PIN_CMAC, &metadata)) {
return false;
}
// Set metadata for ECDH PIN stretching secret.
memzero(&metadata, sizeof(metadata));
metadata.change = OPTIGA_META_ACCESS_ALWAYS;
metadata.read = OPTIGA_META_ACCESS_NEVER;
metadata.execute = ACCESS_PIN_TOTAL_CTR;
metadata.key_usage = OPTIGA_META_KEY_USE_KEYAGREE;
if (!optiga_set_metadata(OID_PIN_ECDH, &metadata)) {
return false;
}
return true;
}
static bool optiga_pin_init_stretch(void) {
// Generate a new key in OID_PIN_CMAC.
if (optiga_gen_sym_key(OPTIGA_AES_256, OPTIGA_KEY_USAGE_ENC, OID_PIN_CMAC) !=
OPTIGA_SUCCESS) {
return false;
}
// Generate a new key in OID_PIN_ECDH.
uint8_t public_key[6 + 65] = {0};
size_t size = 0;
return optiga_gen_key_pair(OPTIGA_CURVE_P256, OPTIGA_KEY_USAGE_KEYAGREE,
OID_PIN_ECDH, public_key, sizeof(public_key),
&size) == OPTIGA_SUCCESS;
}
static bool optiga_pin_stretch_common(
optiga_ui_progress_t ui_progress, HMAC_SHA256_CTX *ctx,
const uint8_t input[OPTIGA_PIN_SECRET_SIZE], bool version4) {
// Implements the functionality that is common to
// optiga_pin_stretch_cmac_ecdh() and the legacy function
// optiga_pin_stretch_secret_v4().
uint8_t buffer[ENCRYPT_SYM_PREFIX_SIZE + OPTIGA_PIN_SECRET_SIZE] = {0};
size_t size = 0;
bool ret = true;
// Combine intermediate result with OID_PIN_CMAC.
if (optiga_encrypt_sym(OPTIGA_SYM_MODE_CMAC, OID_PIN_CMAC, input,
OPTIGA_PIN_SECRET_SIZE, buffer, sizeof(buffer),
&size) != OPTIGA_SUCCESS) {
ret = false;
goto end;
}
hmac_sha256_Update(ctx, buffer, size);
if (version4) {
// Combine intermediate result with OID_PIN_HMAC
if (optiga_encrypt_sym(OPTIGA_SYM_MODE_HMAC_SHA256, OID_PIN_HMAC_V4, input,
OPTIGA_PIN_SECRET_SIZE, buffer, sizeof(buffer),
&size) != OPTIGA_SUCCESS) {
ret = false;
goto end;
}
hmac_sha256_Update(ctx, buffer, size);
}
// Combine intermediate result with OID_PIN_ECDH
uint8_t encoded_point[BIT_STRING_HEADER_SIZE + 65] = {0x03, 0x42, 0x00};
if (!hash_to_curve_optiga(input, &encoded_point[BIT_STRING_HEADER_SIZE])) {
ret = false;
goto end;
}
if (optiga_calc_ssec(OPTIGA_CURVE_P256, OID_PIN_ECDH, encoded_point,
sizeof(encoded_point), buffer, sizeof(buffer),
&size) != OPTIGA_SUCCESS) {
ret = false;
goto end;
}
ui_progress();
hmac_sha256_Update(ctx, buffer, size);
end:
memzero(encoded_point, sizeof(encoded_point));
memzero(buffer, sizeof(buffer));
return ret;
}
static bool optiga_pin_stretch_secret_v4(
optiga_ui_progress_t ui_progress, uint8_t secret[OPTIGA_PIN_SECRET_SIZE]) {
// Legacy PIN verification method used in storage versions 3 and 4.
// This step hardens the PIN verification process in case an attacker is able
// to extract the secret value of a data object in Optiga that has a
// particular configuration, but does not allow secret extraction for other
// kinds of data objects. An attacker would need to be able to extract each of
// the secrets in the different data objects 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.
// Pseudocode for the stretching process:
// cmac_out = CMAC(OID_PIN_CMAC, secret)
// hmac_out = HMAC(OID_PIN_HMAC_V4, secret)
// ecdh_out = ECDH(OID_PIN_ECDH, secret)
// secret = HMAC-SHA256(secret, cmac_out || hmac_out || ecdh_out)
HMAC_SHA256_CTX ctx = {0};
hmac_sha256_Init(&ctx, secret, OPTIGA_PIN_SECRET_SIZE);
bool ret = optiga_pin_stretch_common(ui_progress, &ctx, secret, true);
if (ret) {
hmac_sha256_Final(&ctx, secret);
}
memzero(&ctx, sizeof(ctx));
return ret;
}
static bool optiga_pin_stretch_cmac_ecdh(
optiga_ui_progress_t ui_progress,
uint8_t stretched_pin[OPTIGA_PIN_SECRET_SIZE]) {
// This step hardens the PIN verification process in case an attacker is able
// to extract the secret value of a data object in Optiga that has a
// particular configuration, but does not allow secret extraction for other
// kinds of data objects. An attacker would need to be able to extract each of
// the secrets in the different data objects 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
// OID_PIN_TOTAL_CTR.
// Pseudocode for the stretching process:
// for _ in range(PIN_STRETCH_ITERATIONS):
// digest = HMAC-SHA256(stretched_pin, "")
// cmac_out = CMAC(OID_PIN_CMAC, digest)
// ecdh_out = ECDH(OID_PIN_ECDH, digest)
// stretched_pin = HMAC-SHA256(stretched_pin, cmac_out || ecdh_out)
bool ret = true;
uint8_t digest[OPTIGA_PIN_SECRET_SIZE] = {0};
HMAC_SHA256_CTX ctx = {0};
for (int i = 0; i < PIN_STRETCH_ITERATIONS; ++i) {
// Process the stretched PIN 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, they will not gain
// knowledge of the stretched PIN.
hmac_sha256(stretched_pin, OPTIGA_PIN_SECRET_SIZE, NULL, 0, digest);
hmac_sha256_Init(&ctx, stretched_pin, OPTIGA_PIN_SECRET_SIZE);
if (!optiga_pin_stretch_common(ui_progress, &ctx, digest, false)) {
ret = false;
goto end;
}
hmac_sha256_Final(&ctx, stretched_pin);
}
end:
memzero(digest, sizeof(digest));
memzero(&ctx, sizeof(ctx));
return ret;
}
bool optiga_pin_set(optiga_ui_progress_t ui_progress,
uint8_t stretched_pin[OPTIGA_PIN_SECRET_SIZE]) {
optiga_set_ui_progress(ui_progress);
bool ret = true;
if (!optiga_pin_init_metadata() || !optiga_pin_init_stretch()) {
ret = false;
goto end;
}
ui_progress();
// Stretch the PIN more with stretching secrets from the Optiga. This step
// ensures that if an attacker extracts the value of OID_STRETCHED_PIN or
// OID_PIN_SECRET, then it cannot be used to conduct an offline brute-force
// search for the PIN.
if (!optiga_pin_stretch_cmac_ecdh(ui_progress, stretched_pin)) {
ret = false;
goto end;
}
// Generate and store the counter-protected PIN secret.
uint8_t pin_secret[OPTIGA_PIN_SECRET_SIZE] = {0};
if (optiga_get_random(pin_secret, sizeof(pin_secret)) != OPTIGA_SUCCESS) {
ret = false;
goto end;
}
random_xor(pin_secret, sizeof(pin_secret));
if (optiga_set_data_object(OID_PIN_SECRET, false, pin_secret,
sizeof(pin_secret)) != OPTIGA_SUCCESS) {
ret = false;
goto end;
}
// Generate the key for the HMAC-SHA256 PIN stretching step.
uint8_t pin_hmac[OPTIGA_PIN_SECRET_SIZE] = {0};
if (optiga_get_random(pin_hmac, sizeof(pin_hmac)) != OPTIGA_SUCCESS) {
ret = false;
goto end;
}
random_xor(pin_hmac, sizeof(pin_hmac));
// Authorise using OID_PIN_SECRET so that we can write to OID_STRETCHED_PIN
// and OID_STRETCHED_PIN_CTR.
if (optiga_set_auto_state(OPTIGA_OID_SESSION_CTX, OID_PIN_SECRET, pin_secret,
sizeof(pin_secret)) != OPTIGA_SUCCESS) {
ret = false;
goto end;
}
// Process the stretched PIN using a one-way function before using it in the
// operation that will be executed in Optiga during verification. This ensures
// that in the unlikely case of an attacker recording communication between
// the MCU and Optiga, they will not gain knowledge of the stretched PIN.
uint8_t digest[OPTIGA_PIN_SECRET_SIZE] = {0};
hmac_sha256(stretched_pin, OPTIGA_PIN_SECRET_SIZE, NULL, 0, digest);
// Compute the operation that will be executed in Optiga during verification.
uint8_t hmac_buffer[ENCRYPT_SYM_PREFIX_SIZE + OPTIGA_PIN_SECRET_SIZE] = {
0x61, 0x00, 0x20};
hmac_sha256(pin_hmac, sizeof(pin_hmac), digest, sizeof(digest),
&hmac_buffer[ENCRYPT_SYM_PREFIX_SIZE]);
// Stretch the PIN with the result.
hmac_sha256(stretched_pin, OPTIGA_PIN_SECRET_SIZE, hmac_buffer,
sizeof(hmac_buffer), stretched_pin);
// Process the stretched PIN 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, they will not gain knowledge of
// the stretched PIN.
hmac_sha256(stretched_pin, OPTIGA_PIN_SECRET_SIZE, NULL, 0, digest);
// Store the digest of the stretched PIN in OID_STRETCHED_PIN.
if (optiga_set_data_object(OID_STRETCHED_PIN, false, digest,
sizeof(digest)) != OPTIGA_SUCCESS) {
ret = false;
goto end;
}
// Initialize the counter which limits the guesses at OID_STRETCHED_PIN with
// one extra attempt that we will use up in the next step.
if (optiga_set_data_object(OID_STRETCHED_PIN_CTR, false, COUNTER_RESET_EXTRA,
sizeof(COUNTER_RESET_EXTRA)) != OPTIGA_SUCCESS) {
ret = false;
goto end;
}
ui_progress();
// Authorise using OID_STRETCHED_PIN so that we can write to OID_PIN_HMAC and
// OID_PIN_HMAC_CTR.
if (optiga_set_auto_state(OPTIGA_OID_SESSION_CTX, OID_STRETCHED_PIN, digest,
sizeof(digest)) != OPTIGA_SUCCESS) {
ret = false;
goto end;
}
// Initialize the key for HMAC-SHA256 PIN stretching.
if (optiga_set_data_object(OID_PIN_HMAC, false, pin_hmac, sizeof(pin_hmac)) !=
OPTIGA_SUCCESS) {
ret = false;
goto end;
}
// Initialize the PIN counter which limits the use of OID_PIN_HMAC.
if (optiga_set_data_object(OID_PIN_HMAC_CTR, false, COUNTER_RESET,
sizeof(COUNTER_RESET)) != OPTIGA_SUCCESS) {
ret = false;
goto end;
}
ui_progress();
// Stretch the PIN more with the counter-protected PIN secret. This method
// ensures that if the user chooses a high-entropy PIN, then even if the
// Optiga and its communication link is completely compromised, it will not
// reduce the security of their device any more than if the Optiga was not
// integrated into the device in the first place.
hmac_sha256(stretched_pin, OPTIGA_PIN_SECRET_SIZE, pin_secret,
sizeof(pin_secret), stretched_pin);
end:
memzero(hmac_buffer, sizeof(hmac_buffer));
memzero(pin_hmac, sizeof(pin_hmac));
memzero(pin_secret, sizeof(pin_secret));
memzero(digest, sizeof(digest));
optiga_clear_auto_state(OID_PIN_SECRET);
optiga_clear_auto_state(OID_STRETCHED_PIN);
optiga_set_ui_progress(NULL);
return ret;
}
optiga_pin_result optiga_pin_verify_v4(
optiga_ui_progress_t ui_progress,
const uint8_t pin_secret[OPTIGA_PIN_SECRET_SIZE],
uint8_t out_secret[OPTIGA_PIN_SECRET_SIZE]) {
// Legacy PIN verification method used in storage version 3 and 4.
optiga_set_ui_progress(ui_progress);
optiga_pin_result ret = OPTIGA_PIN_SUCCESS;
// Process the PIN-derived secret using a one-way function before sending it
// to the Optiga.
uint8_t stretched_pin[OPTIGA_PIN_SECRET_SIZE] = {0};
hmac_sha256(pin_secret, OPTIGA_PIN_SECRET_SIZE, NULL, 0, stretched_pin);
// Combine the result with stretching secrets from the Optiga.
if (!optiga_pin_stretch_secret_v4(ui_progress, stretched_pin)) {
ret = OPTIGA_PIN_ERROR;
goto end;
}
// Authorise using OID_STRETCHED_PIN so that we can read from OID_PIN_SECRET.
optiga_result res =
optiga_set_auto_state(OPTIGA_OID_SESSION_CTX, OID_STRETCHED_PIN,
stretched_pin, sizeof(stretched_pin));
if (res != OPTIGA_SUCCESS) {
uint8_t error_code = 0;
if (res != OPTIGA_ERR_CMD ||
optiga_get_error_code(&error_code) != OPTIGA_SUCCESS) {
ret = OPTIGA_PIN_ERROR;
goto end;
}
switch (error_code) {
case OPTIGA_ERR_CODE_CTR_LIMIT:
ret = OPTIGA_PIN_COUNTER_EXCEEDED;
break;
case OPTIGA_ERR_CODE_AUTH_FAIL:
ret = OPTIGA_PIN_INVALID;
break;
default:
ret = OPTIGA_PIN_ERROR;
}
goto end;
}
// Read the master secret from OID_PIN_SECRET.
size_t size = 0;
if (optiga_get_data_object(OID_PIN_SECRET, false, out_secret,
OPTIGA_PIN_SECRET_SIZE, &size) != OPTIGA_SUCCESS ||
size != OPTIGA_PIN_SECRET_SIZE) {
ret = OPTIGA_PIN_ERROR;
goto end;
}
ui_progress();
// Authorise using OID_PIN_SECRET so that we can write to OID_PIN_COUNTER.
if (optiga_set_auto_state(OPTIGA_OID_SESSION_CTX, OID_PIN_SECRET, out_secret,
OPTIGA_PIN_SECRET_SIZE) != OPTIGA_SUCCESS) {
ret = OPTIGA_PIN_ERROR;
goto end;
}
ui_progress();
// Combine the value of OID_PIN_SECRET with the PIN-derived secret and
// stretching secrets from the Optiga.
hmac_sha256(pin_secret, OPTIGA_PIN_SECRET_SIZE, out_secret,
OPTIGA_PIN_SECRET_SIZE, out_secret);
if (!optiga_pin_stretch_secret_v4(ui_progress, out_secret)) {
ret = OPTIGA_PIN_ERROR;
goto end;
}
// Combine the stretched master secret with the PIN-derived secret to derive
// the output secret.
hmac_sha256(pin_secret, OPTIGA_PIN_SECRET_SIZE, out_secret,
OPTIGA_PIN_SECRET_SIZE, out_secret);
end:
memzero(stretched_pin, sizeof(stretched_pin));
optiga_clear_auto_state(OID_STRETCHED_PIN);
optiga_set_ui_progress(NULL);
return ret;
}
static optiga_pin_result optiga_pin_stretch_hmac(
uint8_t stretched_pin[OPTIGA_PIN_SECRET_SIZE]) {
optiga_pin_result ret = OPTIGA_PIN_SUCCESS;
// Process the stretched PIN using a one-way function before sending it to the
// Optiga.
uint8_t digest[OPTIGA_PIN_SECRET_SIZE] = {0};
hmac_sha256(stretched_pin, OPTIGA_PIN_SECRET_SIZE, NULL, 0, digest);
// HMAC the digest with the key in OID_PIN_HMAC.
uint8_t hmac_buffer[ENCRYPT_SYM_PREFIX_SIZE + OPTIGA_PIN_SECRET_SIZE] = {0};
size_t size = 0;
optiga_result res = optiga_encrypt_sym(
OPTIGA_SYM_MODE_HMAC_SHA256, OID_PIN_HMAC, digest, sizeof(digest),
hmac_buffer, sizeof(hmac_buffer), &size);
if (res != OPTIGA_SUCCESS) {
uint8_t error_code = 0;
if (res == OPTIGA_ERR_CMD &&
optiga_get_error_code(&error_code) == OPTIGA_SUCCESS &&
error_code == OPTIGA_ERR_CODE_ACCESS_COND) {
ret = OPTIGA_PIN_COUNTER_EXCEEDED;
} else {
ret = OPTIGA_PIN_ERROR;
}
goto end;
}
// Stretch the PIN with the result.
hmac_sha256(stretched_pin, OPTIGA_PIN_SECRET_SIZE, hmac_buffer, size,
stretched_pin);
end:
memzero(digest, sizeof(digest));
memzero(hmac_buffer, sizeof(hmac_buffer));
return ret;
}
optiga_pin_result optiga_pin_verify(
optiga_ui_progress_t ui_progress,
uint8_t stretched_pin[OPTIGA_PIN_SECRET_SIZE]) {
optiga_set_ui_progress(ui_progress);
optiga_pin_result ret = OPTIGA_PIN_SUCCESS;
// Stretch the PIN more with stretching secrets from the Optiga.
if (!optiga_pin_stretch_cmac_ecdh(ui_progress, stretched_pin)) {
ret = OPTIGA_PIN_ERROR;
goto end;
}
ret = optiga_pin_stretch_hmac(stretched_pin);
if (ret != OPTIGA_PIN_SUCCESS) {
goto end;
}
// Process the stretched PIN using a one-way function before sending it to the
// Optiga.
uint8_t digest[OPTIGA_PIN_SECRET_SIZE] = {0};
hmac_sha256(stretched_pin, OPTIGA_PIN_SECRET_SIZE, NULL, 0, digest);
// Authorise using OID_STRETCHED_PIN so that we can read from OID_PIN_SECRET
// and reset OID_PIN_HMAC_CTR.
optiga_result res = optiga_set_auto_state(
OPTIGA_OID_SESSION_CTX, OID_STRETCHED_PIN, digest, sizeof(digest));
if (res != OPTIGA_SUCCESS) {
uint8_t error_code = 0;
if (res != OPTIGA_ERR_CMD ||
optiga_get_error_code(&error_code) != OPTIGA_SUCCESS) {
ret = OPTIGA_PIN_ERROR;
goto end;
}
switch (error_code) {
case OPTIGA_ERR_CODE_CTR_LIMIT:
ret = OPTIGA_PIN_COUNTER_EXCEEDED;
break;
case OPTIGA_ERR_CODE_AUTH_FAIL:
ret = OPTIGA_PIN_INVALID;
break;
default:
ret = OPTIGA_PIN_ERROR;
}
goto end;
}
ui_progress();
// Reset the counter which limits the use of OID_PIN_HMAC.
if (optiga_set_data_object(OID_PIN_HMAC_CTR, false, COUNTER_RESET,
sizeof(COUNTER_RESET)) != OPTIGA_SUCCESS) {
ret = OPTIGA_PIN_ERROR;
goto end;
}
// Read the counter-protected PIN secret from OID_PIN_SECRET.
uint8_t pin_secret[OPTIGA_PIN_SECRET_SIZE] = {0};
size_t size = 0;
if (optiga_get_data_object(OID_PIN_SECRET, false, pin_secret,
OPTIGA_PIN_SECRET_SIZE, &size) != OPTIGA_SUCCESS) {
ret = OPTIGA_PIN_ERROR;
goto end;
}
// Stretch the PIN more with the counter-protected PIN secret.
hmac_sha256(stretched_pin, OPTIGA_PIN_SECRET_SIZE, pin_secret, size,
stretched_pin);
// Authorise using OID_PIN_SECRET so that we can reset OID_STRETCHED_PIN_CTR.
if (optiga_set_auto_state(OPTIGA_OID_SESSION_CTX, OID_PIN_SECRET, pin_secret,
sizeof(pin_secret)) != OPTIGA_SUCCESS) {
ret = OPTIGA_PIN_ERROR;
goto end;
}
// Reset the counter which limits the guesses at OID_STRETCHED_PIN.
if (optiga_set_data_object(OID_STRETCHED_PIN_CTR, false, COUNTER_RESET,
sizeof(COUNTER_RESET)) != OPTIGA_SUCCESS) {
ret = OPTIGA_PIN_ERROR;
goto end;
}
ui_progress();
end:
memzero(pin_secret, sizeof(pin_secret));
memzero(digest, sizeof(digest));
optiga_clear_auto_state(OID_STRETCHED_PIN);
optiga_clear_auto_state(OID_PIN_SECRET);
optiga_set_ui_progress(NULL);
return ret;
}
static uint32_t uint32_from_be(uint8_t buf[4]) {
uint32_t i = buf[0];
i = (i << 8) + buf[1];
i = (i << 8) + buf[2];
i = (i << 8) + buf[3];
return i;
}
static bool optiga_get_counter_rem(uint16_t oid, uint32_t *ctr) {
uint8_t counter[8] = {0};
size_t counter_size = 0;
if (optiga_get_data_object(oid, false, counter, sizeof(counter),
&counter_size) != OPTIGA_SUCCESS ||
counter_size != sizeof(counter)) {
return false;
}
*ctr = uint32_from_be(&counter[4]) - uint32_from_be(&counter[0]);
return true;
}
bool optiga_pin_get_rem_v4(uint32_t *ctr) {
return optiga_get_counter_rem(OID_STRETCHED_PIN_CTR, ctr);
}
bool optiga_pin_get_rem(uint32_t *ctr) {
uint32_t ctr1 = 0;
uint32_t ctr2 = 0;
if (!optiga_get_counter_rem(OID_PIN_HMAC_CTR, &ctr1) ||
!optiga_get_counter_rem(OID_STRETCHED_PIN_CTR, &ctr2)) {
return false;
}
// Ensure that the counters are in sync.
if (ctr1 > ctr2) {
if (optiga_count_data_object(OID_PIN_HMAC_CTR, ctr1 - ctr2) !=
OPTIGA_SUCCESS) {
return false;
}
*ctr = ctr2;
} else if (ctr2 > ctr1) {
if (optiga_count_data_object(OID_STRETCHED_PIN_CTR, ctr2 - ctr1) !=
OPTIGA_SUCCESS) {
return false;
}
*ctr = ctr1;
} else {
*ctr = ctr2;
}
return true;
}
bool optiga_pin_decrease_rem_v4(uint32_t count) {
if (count > 0xff) {
return false;
}
return optiga_count_data_object(OID_STRETCHED_PIN_CTR, count) ==
OPTIGA_SUCCESS;
}
bool optiga_pin_decrease_rem(uint32_t count) {
if (count > 0xff) {
return false;
}
return optiga_count_data_object(OID_PIN_HMAC_CTR, count) == OPTIGA_SUCCESS &&
optiga_count_data_object(OID_STRETCHED_PIN_CTR, count) ==
OPTIGA_SUCCESS;
}
#endif // KERNEL_MODE