blob: 73d7aca681e2cea63061e0c3f645229d84cdbf74 [file] [log] [blame]
/* Copyright 2019 The Chromium OS Authors. All rights reserved.
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include <stdbool.h>
#include "aes.h"
#include "aes-gcm.h"
#include "cryptoc/util.h"
#include "fpsensor_crypto.h"
#include "fpsensor_private.h"
#include "fpsensor_state.h"
#include "rollback.h"
#if !defined(CONFIG_AES) || !defined(CONFIG_AES_GCM) || \
!defined(CONFIG_ROLLBACK_SECRET_SIZE)
#error "fpsensor requires AES, AES_GCM and ROLLBACK_SECRET_SIZE"
#endif
static int get_ikm(uint8_t *ikm)
{
int ret;
if (!fp_tpm_seed_is_set()) {
CPRINTS("Seed hasn't been set.");
return EC_ERROR_ACCESS_DENIED;
}
/*
* The first CONFIG_ROLLBACK_SECRET_SIZE bytes of IKM are read from the
* anti-rollback blocks.
*/
ret = rollback_get_secret(ikm);
if (ret != EC_SUCCESS) {
CPRINTS("Failed to read rollback secret: %d", ret);
return EC_ERROR_HW_INTERNAL;
}
/*
* IKM is the concatenation of the rollback secret and the seed from
* the TPM.
*/
memcpy(ikm + CONFIG_ROLLBACK_SECRET_SIZE, tpm_seed, sizeof(tpm_seed));
return EC_SUCCESS;
}
static void hkdf_extract(uint8_t *prk, const uint8_t *salt, size_t salt_size,
const uint8_t *ikm, size_t ikm_size)
{
/*
* Derive a key with the "extract" step of HKDF
* https://tools.ietf.org/html/rfc5869#section-2.2
*/
hmac_SHA256(prk, salt, salt_size, ikm, ikm_size);
}
static int hkdf_expand_one_step(uint8_t *out_key, size_t out_key_size,
uint8_t *prk, size_t prk_size,
uint8_t *info, size_t info_size)
{
uint8_t key_buf[SHA256_DIGEST_SIZE];
uint8_t message_buf[SHA256_DIGEST_SIZE + 1];
if (out_key_size > SHA256_DIGEST_SIZE) {
CPRINTS("Deriving key material longer than SHA256_DIGEST_SIZE "
"requires more steps of HKDF expand.");
return EC_ERROR_INVAL;
}
if (info_size > SHA256_DIGEST_SIZE) {
CPRINTS("Info size too big for HKDF.");
return EC_ERROR_INVAL;
}
memcpy(message_buf, info, info_size);
/* 1 step, set the counter byte to 1. */
message_buf[info_size] = 0x01;
hmac_SHA256(key_buf, prk, prk_size, message_buf, info_size + 1);
memcpy(out_key, key_buf, out_key_size);
always_memset(key_buf, 0, sizeof(key_buf));
return EC_SUCCESS;
}
int hkdf_expand(uint8_t *out_key, size_t L, const uint8_t *prk,
size_t prk_size, const uint8_t *info, size_t info_size)
{
/*
* "Expand" step of HKDF.
* https://tools.ietf.org/html/rfc5869#section-2.3
*/
#define HASH_LEN SHA256_DIGEST_SIZE
uint8_t count = 1;
const uint8_t *T = out_key;
size_t T_len = 0;
uint8_t T_buffer[HASH_LEN];
/* Number of blocks. */
const uint32_t N = DIV_ROUND_UP(L, HASH_LEN);
uint8_t info_buffer[HASH_LEN + HKDF_MAX_INFO_SIZE + sizeof(count)];
bool arguments_valid = false;
if (out_key == NULL || L == 0)
CPRINTS("HKDF expand: output buffer not valid.");
else if (prk == NULL)
CPRINTS("HKDF expand: prk is NULL.");
else if (info == NULL && info_size > 0)
CPRINTS("HKDF expand: info is NULL but info size is not zero.");
else if (info_size > HKDF_MAX_INFO_SIZE)
CPRINTF("HKDF expand: info size larger than %d bytes.\n",
HKDF_MAX_INFO_SIZE);
else if (N > HKDF_SHA256_MAX_BLOCK_COUNT)
CPRINTS("HKDF expand: output key size too large.");
else
arguments_valid = true;
if (!arguments_valid)
return EC_ERROR_INVAL;
while (L > 0) {
const size_t block_size = L < HASH_LEN ? L : HASH_LEN;
memcpy(info_buffer, T, T_len);
memcpy(info_buffer + T_len, info, info_size);
info_buffer[T_len + info_size] = count;
hmac_SHA256(T_buffer, prk, prk_size, info_buffer,
T_len + info_size + sizeof(count));
memcpy(out_key, T_buffer, block_size);
T += T_len;
T_len = HASH_LEN;
count++;
out_key += block_size;
L -= block_size;
}
always_memset(T_buffer, 0, sizeof(T_buffer));
always_memset(info_buffer, 0, sizeof(info_buffer));
return EC_SUCCESS;
#undef HASH_LEN
}
int derive_positive_match_secret(uint8_t *output,
const uint8_t *input_positive_match_salt)
{
int ret;
uint8_t ikm[CONFIG_ROLLBACK_SECRET_SIZE + sizeof(tpm_seed)];
uint8_t prk[SHA256_DIGEST_SIZE];
static const char info_prefix[] = "positive_match_secret for user ";
uint8_t info[sizeof(info_prefix) - 1 + sizeof(user_id)];
if (bytes_are_trivial(input_positive_match_salt,
FP_POSITIVE_MATCH_SALT_BYTES)) {
CPRINTS("Failed to derive positive match secret: "
"salt bytes are trivial.");
return EC_ERROR_INVAL;
}
ret = get_ikm(ikm);
if (ret != EC_SUCCESS) {
CPRINTS("Failed to get IKM: %d", ret);
return ret;
}
/* "Extract" step of HKDF. */
hkdf_extract(prk, input_positive_match_salt,
FP_POSITIVE_MATCH_SALT_BYTES, ikm, sizeof(ikm));
always_memset(ikm, 0, sizeof(ikm));
memcpy(info, info_prefix, strlen(info_prefix));
memcpy(info + strlen(info_prefix), user_id, sizeof(user_id));
/* "Expand" step of HKDF. */
ret = hkdf_expand(output, FP_POSITIVE_MATCH_SECRET_BYTES, prk,
sizeof(prk), info, sizeof(info));
always_memset(prk, 0, sizeof(prk));
/* Check that secret is not full of 0x00 or 0xff. */
if (bytes_are_trivial(output, FP_POSITIVE_MATCH_SECRET_BYTES)) {
CPRINTS("Failed to derive positive match secret: "
"derived secret bytes are trivial.");
ret = EC_ERROR_HW_INTERNAL;
}
return ret;
}
int derive_encryption_key(uint8_t *out_key, const uint8_t *salt)
{
int ret;
uint8_t ikm[CONFIG_ROLLBACK_SECRET_SIZE + sizeof(tpm_seed)];
uint8_t prk[SHA256_DIGEST_SIZE];
BUILD_ASSERT(SBP_ENC_KEY_LEN <= SHA256_DIGEST_SIZE);
BUILD_ASSERT(SBP_ENC_KEY_LEN <= CONFIG_ROLLBACK_SECRET_SIZE);
BUILD_ASSERT(sizeof(user_id) == SHA256_DIGEST_SIZE);
ret = get_ikm(ikm);
if (ret != EC_SUCCESS) {
CPRINTS("Failed to get IKM: %d", ret);
return ret;
}
/* "Extract step of HKDF. */
hkdf_extract(prk, salt, FP_CONTEXT_ENCRYPTION_SALT_BYTES, ikm,
sizeof(ikm));
always_memset(ikm, 0, sizeof(ikm));
/*
* Only 1 "expand" step of HKDF since the size of the "info" context
* (user_id in our case) is exactly SHA256_DIGEST_SIZE.
* https://tools.ietf.org/html/rfc5869#section-2.3
*/
ret = hkdf_expand_one_step(out_key, SBP_ENC_KEY_LEN, prk, sizeof(prk),
(uint8_t *)user_id, sizeof(user_id));
always_memset(prk, 0, sizeof(prk));
return ret;
}
int aes_gcm_encrypt(const uint8_t *key, int key_size,
const uint8_t *plaintext,
uint8_t *ciphertext, int text_size,
const uint8_t *nonce, int nonce_size,
uint8_t *tag, int tag_size)
{
int res;
AES_KEY aes_key;
GCM128_CONTEXT ctx;
if (nonce_size != FP_CONTEXT_NONCE_BYTES) {
CPRINTS("Invalid nonce size %d bytes", nonce_size);
return EC_ERROR_INVAL;
}
res = AES_set_encrypt_key(key, 8 * key_size, &aes_key);
if (res) {
CPRINTS("Failed to set encryption key: %d", res);
return EC_ERROR_UNKNOWN;
}
CRYPTO_gcm128_init(&ctx, &aes_key, (block128_f)AES_encrypt, 0);
CRYPTO_gcm128_setiv(&ctx, &aes_key, nonce, nonce_size);
/* CRYPTO functions return 1 on success, 0 on error. */
res = CRYPTO_gcm128_encrypt(&ctx, &aes_key, plaintext, ciphertext,
text_size);
if (!res) {
CPRINTS("Failed to encrypt: %d", res);
return EC_ERROR_UNKNOWN;
}
CRYPTO_gcm128_tag(&ctx, tag, tag_size);
return EC_SUCCESS;
}
int aes_gcm_decrypt(const uint8_t *key, int key_size, uint8_t *plaintext,
const uint8_t *ciphertext, int text_size,
const uint8_t *nonce, int nonce_size,
const uint8_t *tag, int tag_size)
{
int res;
AES_KEY aes_key;
GCM128_CONTEXT ctx;
if (nonce_size != FP_CONTEXT_NONCE_BYTES) {
CPRINTS("Invalid nonce size %d bytes", nonce_size);
return EC_ERROR_INVAL;
}
res = AES_set_encrypt_key(key, 8 * key_size, &aes_key);
if (res) {
CPRINTS("Failed to set decryption key: %d", res);
return EC_ERROR_UNKNOWN;
}
CRYPTO_gcm128_init(&ctx, &aes_key, (block128_f)AES_encrypt, 0);
CRYPTO_gcm128_setiv(&ctx, &aes_key, nonce, nonce_size);
/* CRYPTO functions return 1 on success, 0 on error. */
res = CRYPTO_gcm128_decrypt(&ctx, &aes_key, ciphertext, plaintext,
text_size);
if (!res) {
CPRINTS("Failed to decrypt: %d", res);
return EC_ERROR_UNKNOWN;
}
res = CRYPTO_gcm128_finish(&ctx, tag, tag_size);
if (!res) {
CPRINTS("Found incorrect tag: %d", res);
return EC_ERROR_UNKNOWN;
}
return EC_SUCCESS;
}