summaryrefslogtreecommitdiff
path: root/test/fpsensor_crypto.cc
diff options
context:
space:
mode:
Diffstat (limited to 'test/fpsensor_crypto.cc')
-rw-r--r--test/fpsensor_crypto.cc788
1 files changed, 788 insertions, 0 deletions
diff --git a/test/fpsensor_crypto.cc b/test/fpsensor_crypto.cc
new file mode 100644
index 0000000000..275a1739b6
--- /dev/null
+++ b/test/fpsensor_crypto.cc
@@ -0,0 +1,788 @@
+/* Copyright 2020 The ChromiumOS Authors
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "compile_time_macros.h"
+#include "fpsensor_crypto.h"
+#include "fpsensor_state.h"
+
+extern "C" {
+#include "builtin/assert.h"
+#include "common.h"
+#include "ec_commands.h"
+#include "mock/fpsensor_crypto_mock.h"
+#include "mock/fpsensor_state_mock.h"
+#include "mock/rollback_mock.h"
+#include "mock/timer_mock.h"
+#include "test_util.h"
+#include "util.h"
+}
+
+extern int get_ikm(uint8_t *ikm);
+
+#include <stdbool.h>
+
+static const uint8_t fake_positive_match_salt[] = {
+ 0x04, 0x1f, 0x5a, 0xac, 0x5f, 0x79, 0x10, 0xaf,
+ 0x04, 0x1d, 0x46, 0x3a, 0x5f, 0x08, 0xee, 0xcb,
+};
+
+static const uint8_t fake_user_id[] = {
+ 0x28, 0xb5, 0x5a, 0x55, 0x57, 0x1b, 0x26, 0x88, 0xce, 0xc5, 0xd1,
+ 0xfe, 0x1d, 0x58, 0x5b, 0x94, 0x51, 0xa2, 0x60, 0x49, 0x9f, 0xea,
+ 0xb1, 0xea, 0xf7, 0x04, 0x2f, 0x0b, 0x20, 0xa5, 0x93, 0x64,
+};
+
+/*
+ * |expected_positive_match_secret_for_empty_user_id| is obtained by running
+ * BoringSSL locally.
+ * From https://boringssl.googlesource.com/boringssl
+ * commit 365b7a0fcbf273b1fa704d151059e419abd6cfb8
+ *
+ * Steps to reproduce:
+ *
+ * Open boringssl/crypto/hkdf/hkdf_test.cc
+ * Add the following case to static const HKDFTestVector kTests[]
+ *
+ * // test positive match secret
+ * {
+ * EVP_sha256,
+ * {
+ * // IKM:
+ * // fake_rollback_secret
+ * [ ***Copy 32 octets of fake_rollback_secret here*** ]
+ * // fake_tpm_seed
+ * [ ***Copy 32 octets of fake_tpm_seed here*** ]
+ * }, 64,
+ * {
+ * // fake_positive_match_salt
+ * [ ***Copy 16 octets of fake_positive_match_salt here*** ]
+ * }, 16,
+ * {
+ * // Info:
+ * // "positive_match_secret for user "
+ * 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x76, 0x65,
+ * 0x5f, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x5f, 0x73,
+ * 0x65, 0x63, 0x72, 0x65, 0x74, 0x20, 0x66, 0x6f,
+ * 0x72, 0x20, 0x75, 0x73, 0x65, 0x72, 0x20,
+ * // user_id
+ * [ ***Type 32 octets of 0x00 here*** ]
+ * }, 63,
+ * { // Expected PRK:
+ * 0xc2, 0xff, 0x50, 0x2d, 0xb1, 0x7e, 0x87, 0xb1,
+ * 0x25, 0x36, 0x3a, 0x88, 0xe1, 0xdb, 0x4f, 0x98,
+ * 0x22, 0xb5, 0x66, 0x8c, 0xab, 0xb7, 0xc7, 0x5e,
+ * 0xd7, 0x56, 0xbe, 0xde, 0x82, 0x3f, 0xd0, 0x62,
+ * }, 32,
+ * 32, { // 32 = L = FP_POSITIVE_MATCH_SECRET_BYTES
+ * // Expected positive match secret:
+ * [ ***Copy 32 octets of expected positive_match_secret here*** ]
+ * }
+ * },
+ *
+ * Then from boringssl/ execute:
+ * mkdir build
+ * cd build
+ * cmake ..
+ * make
+ * cd ..
+ * go run util/all_tests.go
+ */
+static const uint8_t expected_positive_match_secret_for_empty_user_id[] = {
+ 0x8d, 0xc4, 0x5b, 0xdf, 0x55, 0x1e, 0xa8, 0x72, 0xd6, 0xdd, 0xa1,
+ 0x4c, 0xb8, 0xa1, 0x76, 0x2b, 0xde, 0x38, 0xd5, 0x03, 0xce, 0xe4,
+ 0x74, 0x51, 0x63, 0x6c, 0x6a, 0x26, 0xa9, 0xb7, 0xfa, 0x68,
+};
+
+/*
+ * Same as |expected_positive_match_secret_for_empty_user_id| but use
+ * |fake_user_id| instead of all-zero user_id.
+ */
+static const uint8_t expected_positive_match_secret_for_fake_user_id[] = {
+ 0x0d, 0xf5, 0xac, 0x7c, 0xad, 0x37, 0x0a, 0x66, 0x2f, 0x71, 0xf6,
+ 0xc6, 0xca, 0x8a, 0x41, 0x69, 0x8a, 0xd3, 0xcf, 0x0b, 0xc4, 0x5a,
+ 0x5f, 0x4d, 0x54, 0xeb, 0x7b, 0xad, 0x5d, 0x1b, 0xbe, 0x30,
+};
+
+test_static int test_get_ikm_failure_seed_not_set(void)
+{
+ uint8_t ikm;
+
+ TEST_ASSERT(fp_tpm_seed_is_set() == 0);
+ TEST_ASSERT(get_ikm(&ikm) == EC_ERROR_ACCESS_DENIED);
+ return EC_SUCCESS;
+}
+
+test_static int test_get_ikm_failure_cannot_get_rollback_secret(void)
+{
+ uint8_t ikm[CONFIG_ROLLBACK_SECRET_SIZE + FP_CONTEXT_TPM_BYTES];
+
+ /* Given that the tmp seed has been set. */
+ TEST_ASSERT(fp_tpm_seed_is_set());
+
+ /* GIVEN that reading the rollback secret will fail. */
+ mock_ctrl_rollback.get_secret_fail = true;
+
+ /* THEN get_ikm should fail. */
+ TEST_ASSERT(get_ikm(ikm) == EC_ERROR_HW_INTERNAL);
+
+ /*
+ * Enable get_rollback_secret to succeed before returning from this
+ * test function.
+ */
+ mock_ctrl_rollback.get_secret_fail = false;
+
+ return EC_SUCCESS;
+}
+
+test_static int test_get_ikm_success(void)
+{
+ /*
+ * Expected ikm is the concatenation of the rollback secret and the
+ * seed from the TPM.
+ */
+ uint8_t ikm[CONFIG_ROLLBACK_SECRET_SIZE + FP_CONTEXT_TPM_BYTES];
+ static const uint8_t expected_ikm[] = {
+ 0xcf, 0xe3, 0x23, 0x76, 0x35, 0x04, 0xc2, 0x0f, 0x0d, 0xb6,
+ 0x02, 0xa9, 0x68, 0xba, 0x2a, 0x61, 0x86, 0x2a, 0x85, 0xd1,
+ 0xca, 0x09, 0x54, 0x8a, 0x6b, 0xe2, 0xe3, 0x38, 0xde, 0x5d,
+ 0x59, 0x14, 0xd9, 0x71, 0xaf, 0xc4, 0xcd, 0x36, 0xe3, 0x60,
+ 0xf8, 0x5a, 0xa0, 0xa6, 0x2c, 0xb3, 0xf5, 0xe2, 0xeb, 0xb9,
+ 0xd8, 0x2f, 0xb5, 0x78, 0x5c, 0x79, 0x82, 0xce, 0x06, 0x3f,
+ 0xcc, 0x23, 0xb9, 0xe7
+ };
+
+ /* GIVEN that the TPM seed has been set. */
+ TEST_ASSERT(fp_tpm_seed_is_set());
+
+ /* GIVEN that reading the rollback secret will succeed. */
+ mock_ctrl_rollback.get_secret_fail = false;
+
+ /* THEN get_ikm will succeed. */
+ TEST_ASSERT(get_ikm(ikm) == EC_SUCCESS);
+ TEST_ASSERT_ARRAY_EQ(ikm, expected_ikm,
+ CONFIG_ROLLBACK_SECRET_SIZE +
+ FP_CONTEXT_TPM_BYTES);
+
+ return EC_SUCCESS;
+}
+
+static int test_hkdf_expand_raw(const uint8_t *prk, size_t prk_size,
+ const uint8_t *info, size_t info_size,
+ const uint8_t *expected_okm, size_t okm_size)
+{
+ uint8_t actual_okm[okm_size];
+
+ TEST_ASSERT(hkdf_expand(actual_okm, okm_size, prk, prk_size, info,
+ info_size) == EC_SUCCESS);
+ TEST_ASSERT_ARRAY_EQ(expected_okm, actual_okm, okm_size);
+ return EC_SUCCESS;
+}
+
+test_static int test_hkdf_expand(void)
+{
+ /* Test vectors in https://tools.ietf.org/html/rfc5869#appendix-A */
+ static const uint8_t prk1[] = {
+ 0x07, 0x77, 0x09, 0x36, 0x2c, 0x2e, 0x32, 0xdf,
+ 0x0d, 0xdc, 0x3f, 0x0d, 0xc4, 0x7b, 0xba, 0x63,
+ 0x90, 0xb6, 0xc7, 0x3b, 0xb5, 0x0f, 0x9c, 0x31,
+ 0x22, 0xec, 0x84, 0x4a, 0xd7, 0xc2, 0xb3, 0xe5,
+ };
+ static const uint8_t info1[] = {
+ 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9,
+ };
+ static const uint8_t expected_okm1[] = {
+ 0x3c, 0xb2, 0x5f, 0x25, 0xfa, 0xac, 0xd5, 0x7a, 0x90,
+ 0x43, 0x4f, 0x64, 0xd0, 0x36, 0x2f, 0x2a, 0x2d, 0x2d,
+ 0x0a, 0x90, 0xcf, 0x1a, 0x5a, 0x4c, 0x5d, 0xb0, 0x2d,
+ 0x56, 0xec, 0xc4, 0xc5, 0xbf, 0x34, 0x00, 0x72, 0x08,
+ 0xd5, 0xb8, 0x87, 0x18, 0x58, 0x65,
+ };
+ static const uint8_t prk2[] = {
+ 0x06, 0xa6, 0xb8, 0x8c, 0x58, 0x53, 0x36, 0x1a,
+ 0x06, 0x10, 0x4c, 0x9c, 0xeb, 0x35, 0xb4, 0x5c,
+ 0xef, 0x76, 0x00, 0x14, 0x90, 0x46, 0x71, 0x01,
+ 0x4a, 0x19, 0x3f, 0x40, 0xc1, 0x5f, 0xc2, 0x44,
+ };
+ static const uint8_t info2[] = {
+ 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9,
+ 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf, 0xc0, 0xc1, 0xc2, 0xc3,
+ 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd,
+ 0xce, 0xcf, 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7,
+ 0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf, 0xe0, 0xe1,
+ 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xeb,
+ 0xec, 0xed, 0xee, 0xef, 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5,
+ 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff,
+ };
+ static const uint8_t expected_okm2[] = {
+ 0xb1, 0x1e, 0x39, 0x8d, 0xc8, 0x03, 0x27, 0xa1, 0xc8, 0xe7,
+ 0xf7, 0x8c, 0x59, 0x6a, 0x49, 0x34, 0x4f, 0x01, 0x2e, 0xda,
+ 0x2d, 0x4e, 0xfa, 0xd8, 0xa0, 0x50, 0xcc, 0x4c, 0x19, 0xaf,
+ 0xa9, 0x7c, 0x59, 0x04, 0x5a, 0x99, 0xca, 0xc7, 0x82, 0x72,
+ 0x71, 0xcb, 0x41, 0xc6, 0x5e, 0x59, 0x0e, 0x09, 0xda, 0x32,
+ 0x75, 0x60, 0x0c, 0x2f, 0x09, 0xb8, 0x36, 0x77, 0x93, 0xa9,
+ 0xac, 0xa3, 0xdb, 0x71, 0xcc, 0x30, 0xc5, 0x81, 0x79, 0xec,
+ 0x3e, 0x87, 0xc1, 0x4c, 0x01, 0xd5, 0xc1, 0xf3, 0x43, 0x4f,
+ 0x1d, 0x87,
+ };
+ static const uint8_t prk3[] = {
+ 0x19, 0xef, 0x24, 0xa3, 0x2c, 0x71, 0x7b, 0x16,
+ 0x7f, 0x33, 0xa9, 0x1d, 0x6f, 0x64, 0x8b, 0xdf,
+ 0x96, 0x59, 0x67, 0x76, 0xaf, 0xdb, 0x63, 0x77,
+ 0xac, 0x43, 0x4c, 0x1c, 0x29, 0x3c, 0xcb, 0x04,
+ };
+ static const uint8_t expected_okm3[] = {
+ 0x8d, 0xa4, 0xe7, 0x75, 0xa5, 0x63, 0xc1, 0x8f, 0x71,
+ 0x5f, 0x80, 0x2a, 0x06, 0x3c, 0x5a, 0x31, 0xb8, 0xa1,
+ 0x1f, 0x5c, 0x5e, 0xe1, 0x87, 0x9e, 0xc3, 0x45, 0x4e,
+ 0x5f, 0x3c, 0x73, 0x8d, 0x2d, 0x9d, 0x20, 0x13, 0x95,
+ 0xfa, 0xa4, 0xb6, 0x1a, 0x96, 0xc8,
+ };
+ static uint8_t unused_output[SHA256_DIGEST_SIZE] = { 0 };
+
+ TEST_ASSERT(test_hkdf_expand_raw(prk1, sizeof(prk1), info1,
+ sizeof(info1), expected_okm1,
+ sizeof(expected_okm1)) == EC_SUCCESS);
+ TEST_ASSERT(test_hkdf_expand_raw(prk2, sizeof(prk2), info2,
+ sizeof(info2), expected_okm2,
+ sizeof(expected_okm2)) == EC_SUCCESS);
+ TEST_ASSERT(test_hkdf_expand_raw(prk3, sizeof(prk3), NULL, 0,
+ expected_okm3,
+ sizeof(expected_okm3)) == EC_SUCCESS);
+
+ TEST_ASSERT(hkdf_expand(NULL, sizeof(unused_output), prk1, sizeof(prk1),
+ info1, sizeof(info1)) == EC_ERROR_INVAL);
+ TEST_ASSERT(hkdf_expand(unused_output, sizeof(unused_output), NULL,
+ sizeof(prk1), info1,
+ sizeof(info1)) == EC_ERROR_INVAL);
+ TEST_ASSERT(hkdf_expand(unused_output, sizeof(unused_output), prk1,
+ sizeof(prk1), NULL,
+ sizeof(info1)) == EC_ERROR_INVAL);
+ /* Info size too long. */
+ TEST_ASSERT(hkdf_expand(unused_output, sizeof(unused_output), prk1,
+ sizeof(prk1), info1, 1024) == EC_ERROR_INVAL);
+ /* OKM size too big. */
+ TEST_ASSERT(hkdf_expand(unused_output, 256 * SHA256_DIGEST_SIZE, prk1,
+ sizeof(prk1), info1,
+ sizeof(info1)) == EC_ERROR_INVAL);
+ return EC_SUCCESS;
+}
+
+test_static int test_derive_encryption_key_failure_seed_not_set(void)
+{
+ static uint8_t unused_key[SBP_ENC_KEY_LEN];
+ static const uint8_t unused_salt[FP_CONTEXT_ENCRYPTION_SALT_BYTES] = {
+ 0
+ };
+
+ /* GIVEN that the TPM seed is not set. */
+ if (fp_tpm_seed_is_set()) {
+ ccprintf("%s:%s(): this test should be executed before setting"
+ " TPM seed.\n",
+ __FILE__, __func__);
+ return -1;
+ }
+
+ /* THEN derivation will fail. */
+ TEST_ASSERT(derive_encryption_key(unused_key, unused_salt) ==
+ EC_ERROR_ACCESS_DENIED);
+
+ return EC_SUCCESS;
+}
+
+static int test_derive_encryption_key_raw(const uint32_t *user_id_,
+ const uint8_t *salt,
+ const uint8_t *expected_key)
+{
+ uint8_t key[SBP_ENC_KEY_LEN];
+ int rv;
+
+ /*
+ * |user_id| is a global variable used as "info" in HKDF expand
+ * in derive_encryption_key().
+ */
+ memcpy(user_id, user_id_, sizeof(user_id));
+ rv = derive_encryption_key(key, salt);
+
+ TEST_ASSERT(rv == EC_SUCCESS);
+ TEST_ASSERT_ARRAY_EQ(key, expected_key, sizeof(key));
+
+ memset(user_id, 0, sizeof(user_id));
+
+ return EC_SUCCESS;
+}
+
+test_static int test_derive_encryption_key(void)
+{
+ /*
+ * These vectors are obtained by choosing the salt and the user_id
+ * (used as "info" in HKDF), and running boringSSL's HKDF
+ * (https://boringssl.googlesource.com/boringssl/+/c0b4c72b6d4c6f4828a373ec454bd646390017d4/crypto/hkdf/)
+ * locally to get the output key. The IKM used in the run is the
+ * concatenation of |fake_rollback_secret| and |fake_tpm_seed|.
+ */
+ static const uint32_t user_id1[] = {
+ 0x608b1b0b, 0xe10d3d24, 0x0bbbe4e6, 0x807b36d9,
+ 0x2a1f8abc, 0xea38104a, 0x562d9431, 0x64d721c5,
+ };
+
+ static const uint8_t salt1[] = {
+ 0xd0, 0x88, 0x34, 0x15, 0xc0, 0xfa, 0x8e, 0x22,
+ 0x9f, 0xb4, 0xd5, 0xa9, 0xee, 0xd3, 0x15, 0x19,
+ };
+
+ static const uint8_t key1[] = {
+ 0xdb, 0x49, 0x6e, 0x1b, 0x67, 0x8a, 0x35, 0xc6,
+ 0xa0, 0x9d, 0xb6, 0xa0, 0x13, 0xf4, 0x21, 0xb3,
+ };
+
+ static const uint32_t user_id2[] = {
+ 0x2546a2ca, 0xf1891f7a, 0x44aad8b8, 0x0d6aac74,
+ 0x6a4ab846, 0x9c279796, 0x5a72eae1, 0x8276d2a3,
+ };
+
+ static const uint8_t salt2[] = {
+ 0x72, 0x6b, 0xc1, 0xe4, 0x64, 0xd4, 0xff, 0xa2,
+ 0x5a, 0xac, 0x5b, 0x0b, 0x06, 0x67, 0xe1, 0x53,
+ };
+
+ static const uint8_t key2[] = {
+ 0x8d, 0x53, 0xaf, 0x4c, 0x96, 0xa2, 0xee, 0x46,
+ 0x9c, 0xe2, 0xe2, 0x6f, 0xe6, 0x66, 0x3d, 0x3a,
+ };
+
+ /*
+ * GIVEN that the TPM seed is set, and reading the rollback secret will
+ * succeed.
+ */
+ TEST_ASSERT(fp_tpm_seed_is_set() &&
+ !mock_ctrl_rollback.get_secret_fail);
+
+ /* THEN the derivation will succeed. */
+ TEST_ASSERT(test_derive_encryption_key_raw(user_id1, salt1, key1) ==
+ EC_SUCCESS);
+
+ TEST_ASSERT(test_derive_encryption_key_raw(user_id2, salt2, key2) ==
+ EC_SUCCESS);
+
+ return EC_SUCCESS;
+}
+
+test_static int test_derive_encryption_key_failure_rollback_fail(void)
+{
+ static uint8_t unused_key[SBP_ENC_KEY_LEN];
+ static const uint8_t unused_salt[FP_CONTEXT_ENCRYPTION_SALT_BYTES] = {
+ 0
+ };
+
+ /* GIVEN that reading the rollback secret will fail. */
+ mock_ctrl_rollback.get_secret_fail = true;
+ /* THEN the derivation will fail. */
+ TEST_ASSERT(derive_encryption_key(unused_key, unused_salt) ==
+ EC_ERROR_HW_INTERNAL);
+
+ /* GIVEN that reading the rollback secret will succeed. */
+ mock_ctrl_rollback.get_secret_fail = false;
+ /* GIVEN that the TPM seed has been set. */
+ TEST_ASSERT(fp_tpm_seed_is_set());
+ /* THEN the derivation will succeed. */
+ TEST_ASSERT(derive_encryption_key(unused_key, unused_salt) ==
+ EC_SUCCESS);
+
+ return EC_SUCCESS;
+}
+
+test_static int test_derive_positive_match_secret_fail_seed_not_set(void)
+{
+ static uint8_t output[FP_POSITIVE_MATCH_SECRET_BYTES];
+
+ /* GIVEN that seed is not set. */
+ TEST_ASSERT(!fp_tpm_seed_is_set());
+ /* THEN EVEN IF the encryption salt is not trivial. */
+ TEST_ASSERT(!bytes_are_trivial(fake_positive_match_salt,
+ sizeof(fake_positive_match_salt)));
+
+ /* Deriving positive match secret will fail. */
+ TEST_ASSERT(derive_positive_match_secret(output,
+ fake_positive_match_salt) ==
+ EC_ERROR_ACCESS_DENIED);
+
+ return EC_SUCCESS;
+}
+
+test_static int test_derive_new_pos_match_secret(void)
+{
+ static uint8_t output[FP_POSITIVE_MATCH_SECRET_BYTES];
+
+ /* First, for empty user_id. */
+ memset(user_id, 0, sizeof(user_id));
+
+ /* GIVEN that the encryption salt is not trivial. */
+ TEST_ASSERT(!bytes_are_trivial(fake_positive_match_salt,
+ sizeof(fake_positive_match_salt)));
+ /*
+ * GIVEN that the TPM seed is set, and reading the rollback secret will
+ * succeed.
+ */
+ TEST_ASSERT(fp_tpm_seed_is_set() &&
+ !mock_ctrl_rollback.get_secret_fail);
+
+ /* GIVEN that the salt is not trivial. */
+ TEST_ASSERT(!bytes_are_trivial(fake_positive_match_salt,
+ sizeof(fake_positive_match_salt)));
+
+ /* THEN the derivation will succeed. */
+ TEST_ASSERT(derive_positive_match_secret(
+ output, fake_positive_match_salt) == EC_SUCCESS);
+ TEST_ASSERT_ARRAY_EQ(
+ output, expected_positive_match_secret_for_empty_user_id,
+ sizeof(expected_positive_match_secret_for_empty_user_id));
+
+ /* Now change the user_id to be non-trivial. */
+ memcpy(user_id, fake_user_id, sizeof(fake_user_id));
+ TEST_ASSERT(derive_positive_match_secret(
+ output, fake_positive_match_salt) == EC_SUCCESS);
+ TEST_ASSERT_ARRAY_EQ(
+ output, expected_positive_match_secret_for_fake_user_id,
+ sizeof(expected_positive_match_secret_for_fake_user_id));
+ memset(user_id, 0, sizeof(user_id));
+
+ return EC_SUCCESS;
+}
+
+test_static int test_derive_positive_match_secret_fail_rollback_fail(void)
+{
+ static uint8_t output[FP_POSITIVE_MATCH_SECRET_BYTES];
+
+ /* GIVEN that reading secret from anti-rollback block will fail. */
+ mock_ctrl_rollback.get_secret_fail = true;
+ /* THEN EVEN IF the encryption salt is not trivial. */
+ TEST_ASSERT(!bytes_are_trivial(fake_positive_match_salt,
+ sizeof(fake_positive_match_salt)));
+
+ /* Deriving positive match secret will fail. */
+ TEST_ASSERT(derive_positive_match_secret(output,
+ fake_positive_match_salt) ==
+ EC_ERROR_HW_INTERNAL);
+ mock_ctrl_rollback.get_secret_fail = false;
+
+ return EC_SUCCESS;
+}
+
+test_static int test_derive_positive_match_secret_fail_salt_trivial(void)
+{
+ static uint8_t output[FP_POSITIVE_MATCH_SECRET_BYTES];
+
+ /* GIVEN that the salt is trivial. */
+ static const uint8_t salt[FP_CONTEXT_ENCRYPTION_SALT_BYTES] = { 0 };
+
+ /* THEN deriving positive match secret will fail. */
+ TEST_ASSERT(derive_positive_match_secret(output, salt) ==
+ EC_ERROR_INVAL);
+ return EC_SUCCESS;
+}
+
+test_static int test_derive_positive_match_secret_fail_trivial_key_0x00(void)
+{
+ static uint8_t output[FP_POSITIVE_MATCH_SECRET_BYTES];
+
+ /* GIVEN that the user ID is set to a known value. */
+ memcpy(user_id, fake_user_id, sizeof(fake_user_id));
+
+ /*
+ * GIVEN that the TPM seed is set, and reading the rollback secret will
+ * succeed.
+ */
+ TEST_ASSERT(fp_tpm_seed_is_set() &&
+ !mock_ctrl_rollback.get_secret_fail);
+
+ /* GIVEN that the salt is not trivial. */
+ TEST_ASSERT(!bytes_are_trivial(fake_positive_match_salt,
+ sizeof(fake_positive_match_salt)));
+
+ /* GIVEN that the sha256 output is trivial (0x00) */
+ mock_ctrl_fpsensor_crypto.output_type =
+ MOCK_CTRL_FPSENSOR_CRYPTO_SHA256_TYPE_ZEROS;
+
+ /* THEN the derivation will fail with EC_ERROR_HW_INTERNAL. */
+ TEST_ASSERT(derive_positive_match_secret(output,
+ fake_positive_match_salt) ==
+ EC_ERROR_HW_INTERNAL);
+
+ /* Now verify success is possible after reverting */
+
+ /* GIVEN that the sha256 output is non-trivial */
+ mock_ctrl_fpsensor_crypto.output_type =
+ MOCK_CTRL_FPSENSOR_CRYPTO_SHA256_TYPE_REAL;
+
+ /* THEN the derivation will succeed */
+ TEST_ASSERT(derive_positive_match_secret(
+ output, fake_positive_match_salt) == EC_SUCCESS);
+
+ /* Clean up any mock changes */
+ mock_ctrl_fpsensor_crypto = MOCK_CTRL_DEFAULT_FPSENSOR_CRYPTO;
+
+ return EC_SUCCESS;
+}
+
+test_static int test_derive_positive_match_secret_fail_trivial_key_0xff(void)
+{
+ static uint8_t output[FP_POSITIVE_MATCH_SECRET_BYTES];
+
+ /* GIVEN that the user ID is set to a known value. */
+ memcpy(user_id, fake_user_id, sizeof(fake_user_id));
+
+ /*
+ * GIVEN that the TPM seed is set, and reading the rollback secret will
+ * succeed.
+ */
+ TEST_ASSERT(fp_tpm_seed_is_set() &&
+ !mock_ctrl_rollback.get_secret_fail);
+
+ /* GIVEN that the salt is not trivial. */
+ TEST_ASSERT(!bytes_are_trivial(fake_positive_match_salt,
+ sizeof(fake_positive_match_salt)));
+
+ /* GIVEN that the sha256 output is trivial (0xFF) */
+ mock_ctrl_fpsensor_crypto.output_type =
+ MOCK_CTRL_FPSENSOR_CRYPTO_SHA256_TYPE_FF;
+
+ /* THEN the derivation will fail with EC_ERROR_HW_INTERNAL. */
+ TEST_ASSERT(derive_positive_match_secret(output,
+ fake_positive_match_salt) ==
+ EC_ERROR_HW_INTERNAL);
+
+ /* Now verify success is possible after reverting */
+
+ /* GIVEN that the sha256 output is non-trivial */
+ mock_ctrl_fpsensor_crypto.output_type =
+ MOCK_CTRL_FPSENSOR_CRYPTO_SHA256_TYPE_REAL;
+
+ /* THEN the derivation will succeed */
+ TEST_ASSERT(derive_positive_match_secret(
+ output, fake_positive_match_salt) == EC_SUCCESS);
+
+ /* Clean up any mock changes */
+ mock_ctrl_fpsensor_crypto = MOCK_CTRL_DEFAULT_FPSENSOR_CRYPTO;
+
+ return EC_SUCCESS;
+}
+
+static int test_enable_positive_match_secret_once(
+ struct positive_match_secret_state *dumb_state)
+{
+ const int8_t kIndexToEnable = 0;
+ timestamp_t now = get_time();
+
+ TEST_ASSERT(fp_enable_positive_match_secret(kIndexToEnable,
+ dumb_state) == EC_SUCCESS);
+ TEST_ASSERT(dumb_state->template_matched == kIndexToEnable);
+ TEST_ASSERT(dumb_state->readable);
+ TEST_ASSERT(dumb_state->deadline.val == now.val + (5 * SECOND));
+
+ return EC_SUCCESS;
+}
+
+test_static int test_enable_positive_match_secret(void)
+{
+ struct positive_match_secret_state
+ dumb_state = { .template_matched = FP_NO_SUCH_TEMPLATE,
+ .readable = false,
+ .deadline = {
+ .val = 0,
+ } };
+
+ TEST_ASSERT(test_enable_positive_match_secret_once(&dumb_state) ==
+ EC_SUCCESS);
+
+ /* Trying to enable again before reading secret should fail. */
+ TEST_ASSERT(fp_enable_positive_match_secret(0, &dumb_state) ==
+ EC_ERROR_UNKNOWN);
+ TEST_ASSERT(dumb_state.template_matched == FP_NO_SUCH_TEMPLATE);
+ TEST_ASSERT(!dumb_state.readable);
+ TEST_ASSERT(dumb_state.deadline.val == 0);
+
+ return EC_SUCCESS;
+}
+
+test_static int test_disable_positive_match_secret(void)
+{
+ struct positive_match_secret_state
+ dumb_state = { .template_matched = FP_NO_SUCH_TEMPLATE,
+ .readable = false,
+ .deadline = {
+ .val = 0,
+ } };
+
+ TEST_ASSERT(test_enable_positive_match_secret_once(&dumb_state) ==
+ EC_SUCCESS);
+
+ fp_disable_positive_match_secret(&dumb_state);
+ TEST_ASSERT(dumb_state.template_matched == FP_NO_SUCH_TEMPLATE);
+ TEST_ASSERT(!dumb_state.readable);
+ TEST_ASSERT(dumb_state.deadline.val == 0);
+
+ return EC_SUCCESS;
+}
+
+test_static int test_command_read_match_secret(void)
+{
+ int rv;
+ struct ec_params_fp_read_match_secret params;
+ struct ec_response_fp_read_match_secret resp;
+ timestamp_t now = get_time();
+
+ /* For empty user_id. */
+ memset(user_id, 0, sizeof(user_id));
+
+ /* Invalid finger index should be rejected. */
+ params.fgr = FP_NO_SUCH_TEMPLATE;
+ rv = test_send_host_command(EC_CMD_FP_READ_MATCH_SECRET, 0, &params,
+ sizeof(params), NULL, 0);
+ TEST_ASSERT(rv == EC_RES_INVALID_PARAM);
+ params.fgr = FP_MAX_FINGER_COUNT;
+ rv = test_send_host_command(EC_CMD_FP_READ_MATCH_SECRET, 0, &params,
+ sizeof(params), NULL, 0);
+ TEST_ASSERT(rv == EC_RES_INVALID_PARAM);
+
+ memset(&resp, 0, sizeof(resp));
+ /* GIVEN that finger index is valid. */
+ params.fgr = 0;
+
+ /* GIVEN that positive match secret is enabled. */
+ fp_enable_positive_match_secret(params.fgr,
+ &positive_match_secret_state);
+
+ /* GIVEN that salt is non-trivial. */
+ memcpy(fp_positive_match_salt[0], fake_positive_match_salt,
+ sizeof(fp_positive_match_salt[0]));
+ /* THEN reading positive match secret should succeed. */
+ rv = test_send_host_command(EC_CMD_FP_READ_MATCH_SECRET, 0, &params,
+ sizeof(params), &resp, sizeof(resp));
+ if (rv != EC_RES_SUCCESS) {
+ ccprintf("%s:%s(): rv = %d\n", __FILE__, __func__, rv);
+ return -1;
+ }
+ /* AND the readable bit should be cleared after the read. */
+ TEST_ASSERT(positive_match_secret_state.readable == false);
+
+ TEST_ASSERT_ARRAY_EQ(
+ resp.positive_match_secret,
+ expected_positive_match_secret_for_empty_user_id,
+ sizeof(expected_positive_match_secret_for_empty_user_id));
+
+ /*
+ * Now try reading secret again.
+ * EVEN IF the deadline has not passed.
+ */
+ positive_match_secret_state.deadline.val = now.val + 1 * SECOND;
+ rv = test_send_host_command(EC_CMD_FP_READ_MATCH_SECRET, 0, &params,
+ sizeof(params), NULL, 0);
+ /*
+ * This time the command should fail because the
+ * fp_pos_match_secret_readable bit is cleared when the secret was read
+ * the first time.
+ */
+ TEST_ASSERT(rv == EC_RES_ACCESS_DENIED);
+
+ return EC_SUCCESS;
+}
+
+test_static int test_command_read_match_secret_wrong_finger(void)
+{
+ int rv;
+ struct ec_params_fp_read_match_secret params;
+
+ /* GIVEN that the finger is not the matched or enrolled finger. */
+ params.fgr = 0;
+ /*
+ * GIVEN that positive match secret is enabled for a different
+ * finger.
+ */
+ fp_enable_positive_match_secret(params.fgr + 1,
+ &positive_match_secret_state);
+
+ /* Reading secret will fail. */
+ rv = test_send_host_command(EC_CMD_FP_READ_MATCH_SECRET, 0, &params,
+ sizeof(params), NULL, 0);
+ TEST_ASSERT(rv == EC_RES_ACCESS_DENIED);
+ return EC_SUCCESS;
+}
+
+test_static int test_command_read_match_secret_timeout(void)
+{
+ int rv;
+ struct ec_params_fp_read_match_secret params;
+
+ params.fgr = 0;
+ /* GIVEN that the read is too late. */
+ fp_enable_positive_match_secret(params.fgr,
+ &positive_match_secret_state);
+ set_time(positive_match_secret_state.deadline);
+
+ /* EVEN IF encryption salt is non-trivial. */
+ memcpy(fp_positive_match_salt[0], fake_positive_match_salt,
+ sizeof(fp_positive_match_salt[0]));
+ /* Reading secret will fail. */
+ rv = test_send_host_command(EC_CMD_FP_READ_MATCH_SECRET, 0, &params,
+ sizeof(params), NULL, 0);
+ TEST_ASSERT(rv == EC_RES_TIMEOUT);
+ return EC_SUCCESS;
+}
+
+test_static int test_command_read_match_secret_unreadable(void)
+{
+ int rv;
+ struct ec_params_fp_read_match_secret params;
+
+ params.fgr = 0;
+ /* GIVEN that the readable bit is not set. */
+ fp_enable_positive_match_secret(params.fgr,
+ &positive_match_secret_state);
+ positive_match_secret_state.readable = false;
+
+ /* EVEN IF the finger is just matched. */
+ TEST_ASSERT(positive_match_secret_state.template_matched == params.fgr);
+
+ /* EVEN IF encryption salt is non-trivial. */
+ memcpy(fp_positive_match_salt[0], fake_positive_match_salt,
+ sizeof(fp_positive_match_salt[0]));
+ /* Reading secret will fail. */
+ rv = test_send_host_command(EC_CMD_FP_READ_MATCH_SECRET, 0, &params,
+ sizeof(params), NULL, 0);
+ TEST_ASSERT(rv == EC_RES_ACCESS_DENIED);
+ return EC_SUCCESS;
+}
+
+void run_test(int argc, const char **argv)
+{
+ RUN_TEST(test_hkdf_expand);
+ RUN_TEST(test_derive_encryption_key_failure_seed_not_set);
+ RUN_TEST(test_derive_positive_match_secret_fail_seed_not_set);
+ RUN_TEST(test_get_ikm_failure_seed_not_set);
+ /*
+ * Set the TPM seed here because it can only be set once and cannot be
+ * cleared.
+ */
+ ASSERT(fpsensor_state_mock_set_tpm_seed(default_fake_tpm_seed) ==
+ EC_SUCCESS);
+
+ /* The following test requires TPM seed to be already set. */
+ RUN_TEST(test_get_ikm_failure_cannot_get_rollback_secret);
+ RUN_TEST(test_get_ikm_success);
+ RUN_TEST(test_derive_encryption_key);
+ RUN_TEST(test_derive_encryption_key_failure_rollback_fail);
+ RUN_TEST(test_derive_new_pos_match_secret);
+ RUN_TEST(test_derive_positive_match_secret_fail_rollback_fail);
+ RUN_TEST(test_derive_positive_match_secret_fail_salt_trivial);
+ RUN_TEST(test_derive_positive_match_secret_fail_trivial_key_0x00);
+ RUN_TEST(test_derive_positive_match_secret_fail_trivial_key_0xff);
+ RUN_TEST(test_enable_positive_match_secret);
+ RUN_TEST(test_disable_positive_match_secret);
+ RUN_TEST(test_command_read_match_secret);
+ RUN_TEST(test_command_read_match_secret_wrong_finger);
+ RUN_TEST(test_command_read_match_secret_timeout);
+ RUN_TEST(test_command_read_match_secret_unreadable);
+ test_print_result();
+}