| // Copyright lowRISC contributors. |
| // Licensed under the Apache License, Version 2.0, see LICENSE for details. |
| // SPDX-License-Identifier: Apache-2.0 |
| |
| #include "sw/device/silicon_creator/lib/sigverify.h" |
| |
| #include <cstring> |
| #include <unordered_set> |
| |
| #include "gtest/gtest.h" |
| #include "sw/device/lib/base/hardened.h" |
| #include "sw/device/silicon_creator/lib/drivers/mock_lifecycle.h" |
| #include "sw/device/silicon_creator/lib/drivers/mock_otp.h" |
| #include "sw/device/silicon_creator/lib/mock_sigverify_mod_exp_ibex.h" |
| #include "sw/device/silicon_creator/lib/mock_sigverify_mod_exp_otbn.h" |
| #include "sw/device/silicon_creator/testing/mask_rom_test.h" |
| |
| #include "hw/top_earlgrey/sw/autogen/top_earlgrey.h" |
| #include "otp_ctrl_regs.h" |
| |
| namespace sigverify_unittest { |
| namespace { |
| using ::testing::DoAll; |
| using ::testing::NotNull; |
| using ::testing::Return; |
| using ::testing::SetArgPointee; |
| |
| /** |
| * SHA2-256 digest of "test". |
| * |
| * Can be obtained using: |
| * ``` |
| * echo -n "test" | openssl dgst -sha256 -binary | \ |
| * xxd -p -c 4 | tac | sed 's|.*|0x&,|' |
| * ``` |
| */ |
| constexpr hmac_digest_t kTestDigest{ |
| .digest = |
| { |
| 0xb0f00a08, |
| 0xd15d6c15, |
| 0x2b0b822c, |
| 0xa3bf4f1b, |
| 0xc55ad015, |
| 0x9a2feaa0, |
| 0x884c7d65, |
| 0x9f86d081, |
| }, |
| }; |
| |
| /** |
| * 3072-bit EMSA-PKCS1-v1_5 encoding of `kTestDigest`. |
| * |
| * Can be obtained using: |
| * ``` |
| * # Create private key. |
| * openssl genrsa -out key.pem 3072 |
| * # Sign. |
| * echo -n "test" | openssl dgst -sha256 -sign key.pem -out signature |
| * # Print encoded message. |
| * openssl rsautl -verify -in signature -inkey key.pem -raw | \ |
| * xxd -p -c 4 | tac | sed 's|.*|0x&,|' |
| * ``` |
| */ |
| constexpr sigverify_rsa_buffer_t kEncMsg{ |
| .data = { |
| 0xb0f00a08, 0xd15d6c15, 0x2b0b822c, 0xa3bf4f1b, 0xc55ad015, 0x9a2feaa0, |
| 0x884c7d65, 0x9f86d081, 0x05000420, 0x03040201, 0x86480165, 0x0d060960, |
| 0x00303130, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, |
| 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, |
| 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, |
| 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, |
| 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, |
| 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, |
| 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, |
| 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, |
| 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, |
| 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, |
| 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, |
| 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, |
| 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, |
| 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x0001ffff, |
| }}; |
| |
| // The value of `kSignature` is not significant since we use mocks for |
| // `sigverify_mod_exp_ibex()` and `sigverify_mod_exp_otbn()`. |
| constexpr sigverify_rsa_buffer_t kSignature{}; |
| |
| /** |
| * Life cycle states used in parameterized tests. |
| */ |
| |
| constexpr std::array<lifecycle_state_t, 8> kLcStatesTest{ |
| kLcStateTestUnlocked0, kLcStateTestUnlocked1, kLcStateTestUnlocked2, |
| kLcStateTestUnlocked3, kLcStateTestUnlocked4, kLcStateTestUnlocked5, |
| kLcStateTestUnlocked6, kLcStateTestUnlocked7, |
| }; |
| |
| constexpr std::array<lifecycle_state_t, 4> kLcStatesNonTestOperational{ |
| kLcStateDev, |
| kLcStateProd, |
| kLcStateProdEnd, |
| kLcStateRma, |
| }; |
| |
| constexpr std::array<lifecycle_state_t, 12> kLcStatesNonOperational{ |
| kLcStateRaw, kLcStateTestLocked0, |
| kLcStateTestLocked1, kLcStateTestLocked2, |
| kLcStateTestLocked3, kLcStateTestLocked4, |
| kLcStateTestLocked5, kLcStateTestLocked6, |
| kLcStateScrap, kLcStatePostTransition, |
| kLcStateEscalate, kLcStateInvalid, |
| }; |
| |
| const std::unordered_set<lifecycle_state_t> &LcStatesAll() { |
| static const std::unordered_set<lifecycle_state_t> *const kLcStatesAll = |
| []() { |
| auto states = new std::unordered_set<lifecycle_state_t>(); |
| states->insert(kLcStatesTest.begin(), kLcStatesTest.end()); |
| states->insert(kLcStatesNonTestOperational.begin(), |
| kLcStatesNonTestOperational.end()); |
| states->insert(kLcStatesNonOperational.begin(), |
| kLcStatesNonOperational.end()); |
| return states; |
| }(); |
| return *kLcStatesAll; |
| } |
| |
| TEST(LcStateCount, IsCorrect) { |
| EXPECT_EQ(kLcStateNumStates, LcStatesAll().size()); |
| } |
| |
| class SigverifyInLcState |
| : public mask_rom_test::MaskRomTest, |
| public testing::WithParamInterface<lifecycle_state_t> { |
| protected: |
| mask_rom_test::MockSigverifyModExpIbex sigverify_mod_exp_ibex_; |
| mask_rom_test::MockSigverifyModExpOtbn sigverify_mod_exp_otbn_; |
| mask_rom_test::MockOtp otp_; |
| // The content of this key is not significant since we use mocks. |
| sigverify_rsa_key_t key_{}; |
| }; |
| |
| class SigverifyInNonTestStates : public SigverifyInLcState {}; |
| |
| TEST_P(SigverifyInNonTestStates, BadOtpValue) { |
| EXPECT_CALL(otp_, |
| read32(OTP_CTRL_PARAM_CREATOR_SW_CFG_USE_SW_RSA_VERIFY_OFFSET)) |
| .WillOnce(Return(0xA5A5A5A5)); |
| |
| EXPECT_EQ(sigverify_rsa_verify(&kSignature, &key_, &kTestDigest, GetParam()), |
| kErrorSigverifyBadOtpValue); |
| } |
| |
| TEST_P(SigverifyInNonTestStates, GoodSignatureIbex) { |
| EXPECT_CALL(otp_, |
| read32(OTP_CTRL_PARAM_CREATOR_SW_CFG_USE_SW_RSA_VERIFY_OFFSET)) |
| .WillOnce(Return(kHardenedBoolTrue)); |
| EXPECT_CALL(sigverify_mod_exp_ibex_, mod_exp(&key_, &kSignature, NotNull())) |
| .WillOnce(DoAll(SetArgPointee<2>(kEncMsg), Return(kErrorOk))); |
| |
| EXPECT_EQ(sigverify_rsa_verify(&kSignature, &key_, &kTestDigest, GetParam()), |
| kErrorOk); |
| } |
| |
| TEST_P(SigverifyInNonTestStates, GoodSignatureOtbn) { |
| EXPECT_CALL(otp_, |
| read32(OTP_CTRL_PARAM_CREATOR_SW_CFG_USE_SW_RSA_VERIFY_OFFSET)) |
| .WillOnce(Return(kHardenedBoolFalse)); |
| EXPECT_CALL(sigverify_mod_exp_otbn_, mod_exp(&key_, &kSignature, NotNull())) |
| .WillOnce(DoAll(SetArgPointee<2>(kEncMsg), Return(kErrorOk))); |
| |
| EXPECT_EQ(sigverify_rsa_verify(&kSignature, &key_, &kTestDigest, GetParam()), |
| kErrorOk); |
| } |
| |
| TEST_P(SigverifyInNonTestStates, BadSignatureOtbn) { |
| // Corrupt the words of the encoded message by flipping their bits and check |
| // that signature verification fails. |
| for (size_t i = 0; i < kSigVerifyRsaNumWords; ++i) { |
| auto bad_enc_msg = kEncMsg; |
| bad_enc_msg.data[i] = ~bad_enc_msg.data[i]; |
| |
| EXPECT_CALL(otp_, |
| read32(OTP_CTRL_PARAM_CREATOR_SW_CFG_USE_SW_RSA_VERIFY_OFFSET)) |
| .WillOnce(Return(kHardenedBoolFalse)); |
| EXPECT_CALL(sigverify_mod_exp_otbn_, mod_exp(&key_, &kSignature, NotNull())) |
| .WillOnce(DoAll(SetArgPointee<2>(bad_enc_msg), Return(kErrorOk))); |
| |
| EXPECT_EQ( |
| sigverify_rsa_verify(&kSignature, &key_, &kTestDigest, GetParam()), |
| kErrorSigverifyBadEncodedMessage); |
| } |
| } |
| |
| INSTANTIATE_TEST_SUITE_P(NonTestOperationalStates, SigverifyInNonTestStates, |
| testing::ValuesIn(kLcStatesNonTestOperational)); |
| |
| class SigverifyInTestStates : public SigverifyInLcState {}; |
| |
| TEST_P(SigverifyInTestStates, GoodSignatureIbex) { |
| EXPECT_CALL(sigverify_mod_exp_ibex_, mod_exp(&key_, &kSignature, NotNull())) |
| .WillOnce(DoAll(SetArgPointee<2>(kEncMsg), Return(kErrorOk))); |
| |
| EXPECT_EQ(sigverify_rsa_verify(&kSignature, &key_, &kTestDigest, GetParam()), |
| kErrorOk); |
| } |
| |
| TEST_P(SigverifyInTestStates, BadSignatureIbex) { |
| // Corrupt the words of the encoded message by flipping their bits and check |
| // that signature verification fails. |
| for (size_t i = 0; i < kSigVerifyRsaNumWords; ++i) { |
| auto bad_enc_msg = kEncMsg; |
| bad_enc_msg.data[i] = ~bad_enc_msg.data[i]; |
| |
| EXPECT_CALL(sigverify_mod_exp_ibex_, mod_exp(&key_, &kSignature, NotNull())) |
| .WillOnce(DoAll(SetArgPointee<2>(bad_enc_msg), Return(kErrorOk))); |
| |
| EXPECT_EQ( |
| sigverify_rsa_verify(&kSignature, &key_, &kTestDigest, GetParam()), |
| kErrorSigverifyBadEncodedMessage); |
| } |
| } |
| |
| INSTANTIATE_TEST_SUITE_P(TestStates, SigverifyInTestStates, |
| testing::ValuesIn(kLcStatesTest)); |
| |
| class SigverifyInInvalidStates : public SigverifyInLcState {}; |
| |
| TEST_P(SigverifyInInvalidStates, BadLcState) { |
| EXPECT_EQ(sigverify_rsa_verify(&kSignature, &key_, &kTestDigest, GetParam()), |
| kErrorSigverifyBadLcState); |
| } |
| |
| INSTANTIATE_TEST_SUITE_P(NonOperationalStates, SigverifyInInvalidStates, |
| testing::ValuesIn(kLcStatesNonOperational)); |
| |
| struct UsageConstraintsTestCase { |
| uint32_t selector_bits; |
| lifecycle_device_id_t exp_device_id; |
| uint32_t exp_manuf_state_creator; |
| uint32_t exp_manuf_state_owner; |
| uint32_t exp_life_cycle_state; |
| }; |
| |
| class SigverifyUsageConstraints |
| : public mask_rom_test::MaskRomTest, |
| public testing::WithParamInterface<UsageConstraintsTestCase> { |
| protected: |
| mask_rom_test::MockLifecycle lifecycle_; |
| mask_rom_test::MockOtp otp_; |
| }; |
| |
| /** |
| * Constants used in usage constraints tests. |
| */ |
| |
| constexpr uint32_t kUnusedWord = MANIFEST_USAGE_CONSTRAINT_UNSELECTED_WORD_VAL; |
| constexpr uint32_t kManufStateCreator = 0x30303030; |
| constexpr uint32_t kManufStateOwner = 0x31313131; |
| constexpr uint32_t kLifeCycleState = 0x32323232; |
| constexpr lifecycle_device_id_t kDeviceId{ |
| .device_id = |
| { |
| 0x01020304, |
| 0x05060708, |
| 0x09100A0B, |
| 0x0C0D0E0F, |
| 0x10111213, |
| 0x14151617, |
| 0x18192021, |
| 0x22232425, |
| }, |
| }; |
| |
| TEST_F(SigverifyUsageConstraints, ConstantCheck) { |
| // Changing this constant breaks compatibility with the mask ROM, existing |
| // images and tools. |
| EXPECT_EQ(MANIFEST_USAGE_CONSTRAINT_UNSELECTED_WORD_VAL, 0xA5A5A5A5); |
| EXPECT_NE(kManufStateCreator, kUnusedWord); |
| EXPECT_NE(kManufStateOwner, kUnusedWord); |
| EXPECT_NE(kLifeCycleState, kUnusedWord); |
| for (size_t i = 0; i < kLifecycleDeviceIdNumWords; ++i) { |
| EXPECT_NE(kDeviceId.device_id[i], kUnusedWord); |
| } |
| } |
| |
| constexpr std::array<UsageConstraintsTestCase, 4> kUsageConstraintsTestCases{{ |
| { |
| .selector_bits = 0x7FF, |
| .exp_device_id = kDeviceId, |
| .exp_manuf_state_creator = kUnusedWord, |
| .exp_manuf_state_owner = kUnusedWord, |
| .exp_life_cycle_state = kLifeCycleState, |
| }, |
| { |
| .selector_bits = 0x3AA, |
| .exp_device_id = {.device_id = |
| { |
| kUnusedWord, |
| kDeviceId.device_id[1], |
| kUnusedWord, |
| kDeviceId.device_id[3], |
| kUnusedWord, |
| kDeviceId.device_id[5], |
| kUnusedWord, |
| kDeviceId.device_id[7], |
| }}, |
| .exp_manuf_state_creator = kUnusedWord, |
| .exp_manuf_state_owner = kUnusedWord, |
| .exp_life_cycle_state = kUnusedWord, |
| }, |
| { |
| .selector_bits = 0x155, |
| .exp_device_id = {.device_id = |
| { |
| kDeviceId.device_id[0], |
| kUnusedWord, |
| kDeviceId.device_id[2], |
| kUnusedWord, |
| kDeviceId.device_id[4], |
| kUnusedWord, |
| kDeviceId.device_id[6], |
| kUnusedWord, |
| }}, |
| .exp_manuf_state_creator = kUnusedWord, |
| .exp_manuf_state_owner = kUnusedWord, |
| .exp_life_cycle_state = kUnusedWord, |
| }, |
| { |
| .selector_bits = 0, |
| .exp_device_id = {.device_id = |
| { |
| kUnusedWord, |
| kUnusedWord, |
| kUnusedWord, |
| kUnusedWord, |
| kUnusedWord, |
| kUnusedWord, |
| kUnusedWord, |
| kUnusedWord, |
| }}, |
| .exp_manuf_state_creator = kUnusedWord, |
| .exp_manuf_state_owner = kUnusedWord, |
| .exp_life_cycle_state = kUnusedWord, |
| }, |
| }}; |
| |
| TEST_P(SigverifyUsageConstraints, Read) { |
| EXPECT_CALL(lifecycle_, DeviceId(NotNull())) |
| .WillOnce(SetArgPointee<0>(kDeviceId)); |
| // TODO(#7948): Define OTP entries for manufacturing states. |
| EXPECT_CALL(lifecycle_, State()) |
| .WillOnce(Return(static_cast<lifecycle_state_t>(kLifeCycleState))); |
| |
| manifest_usage_constraints_t usage_constraints; |
| sigverify_usage_constraints_get(GetParam().selector_bits, &usage_constraints); |
| |
| EXPECT_EQ(usage_constraints.selector_bits, GetParam().selector_bits); |
| EXPECT_THAT(usage_constraints.device_id.device_id, |
| testing::ElementsAreArray(GetParam().exp_device_id.device_id)); |
| EXPECT_EQ(usage_constraints.manuf_state_creator, |
| GetParam().exp_manuf_state_creator); |
| EXPECT_EQ(usage_constraints.manuf_state_owner, |
| GetParam().exp_manuf_state_owner); |
| EXPECT_EQ(usage_constraints.life_cycle_state, |
| GetParam().exp_life_cycle_state); |
| } |
| |
| INSTANTIATE_TEST_SUITE_P(UsageConstraintsTestCases, SigverifyUsageConstraints, |
| testing::ValuesIn(kUsageConstraintsTestCases)); |
| |
| } // namespace |
| } // namespace sigverify_unittest |