| // 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/sigverify.h" |
| |
| #include "sw/device/lib/base/bitfield.h" |
| #include "sw/device/lib/base/hardened.h" |
| #include "sw/device/lib/base/macros.h" |
| #include "sw/device/lib/base/memory.h" |
| #include "sw/device/silicon_creator/lib/drivers/otp.h" |
| #include "sw/device/silicon_creator/lib/sigverify/mod_exp_ibex.h" |
| #include "sw/device/silicon_creator/lib/sigverify/mod_exp_otbn.h" |
| |
| #include "hw/top_earlgrey/sw/autogen/top_earlgrey.h" |
| #include "otp_ctrl_regs.h" |
| |
| /* |
| * Shares for producing the `flash_exec` value in encoded message check. First |
| * 95 shares are generated using the `sparse-fsm-encode` script while the last |
| * share is `kSigverifyFlashExec ^ kShares[0] ^ ... ^ kShares[94]` so that |
| * xor'ing all shares produces kSigverifyFlashExec, i.e. the value that unlocks |
| * flash execution. |
| * |
| * Encoding generated with |
| * $ ./util/design/sparse-fsm-encode.py -d 6 -m 95 -n 32 \ |
| * -s 3118901143 --language=c |
| * |
| * Minimum Hamming distance: 7 |
| * Maximum Hamming distance: 26 |
| * Minimum Hamming weight: 9 |
| * Maximum Hamming weight: 23 |
| */ |
| static const uint32_t kSigverifyShares[kSigVerifyRsaNumWords] = { |
| 0xaf28073b, 0x5eb7dcfb, 0x177240b5, 0xa8469df3, 0x2e92e9c0, 0x83ed133b, |
| 0x0c9e99f0, 0x25611bd9, 0x411a9d85, 0x5c52b3df, 0x4347a537, 0x1e78e574, |
| 0x273e33af, 0x6f363bba, 0x11e4ee52, 0xd29ad9aa, 0x4fc2ac85, 0x52490c66, |
| 0x59c2528c, 0xef8d3ab2, 0xe74d7eb8, 0x2822259c, 0xe58efaa3, 0xe702fa04, |
| 0x82c670f6, 0x42be0a77, 0x3b021ea0, 0x09bd2a22, 0x26d656a4, 0x2f8e008f, |
| 0xefca5842, 0xfbc3a713, 0x4ce07aa1, 0xc1826ecc, 0xc697d53f, 0xf6a69161, |
| 0x4a7d7628, 0x87f2e957, 0x84db848d, 0xe05e01c5, 0x6188ff27, 0xbf1a2b31, |
| 0xb51d4166, 0x85fd6e7c, 0x59c5d2d5, 0x13c6e4e6, 0xff83c944, 0xc78cd4bb, |
| 0x8710d989, 0x7608c41e, 0x1061b036, 0x9c2fb244, 0x34a26844, 0x2bdc22a2, |
| 0xfd1d95f3, 0x94ac4e84, 0x1a99ce21, 0xd54eb8f7, 0x54c2cd9f, 0x70a967c8, |
| 0xde39d471, 0x652563cd, 0x3d4adea1, 0x1cf6631c, 0xb27d16ee, 0x3a18bafa, |
| 0xd8a86a86, 0xd839cd7b, 0xda2ab05a, 0x37fc1d99, 0xbc702308, 0x01d57596, |
| 0x480d3091, 0x51420446, 0xcc56d97c, 0x7aa57434, 0x7b6097ae, 0x45bca8ae, |
| 0xb0b1e322, 0x5487b90f, 0x1045e6ef, 0x87ad10f0, 0x4c72b7f0, 0xc527c9a3, |
| 0x29ed4350, 0xe345625b, 0x57063d83, 0xbb56900a, 0xbfb1be4c, 0x1c454e8f, |
| 0xdb27c1b7, 0xbe02c694, 0x2604d74a, 0x4d6516dd, 0x322918ab, 0xd25e8754, |
| }; |
| |
| /** |
| * Checks the padding and the digest of an EMSA-PKCS1-v1_5 encoded message. |
| * |
| * EMSA-PKCS1-v1_5 is described in Section 9.2 of PKCS #1: RSA Cryptography |
| * Specifications Version 2.2 (https://tools.ietf.org/html/rfc8017#section-9.2). |
| * In PKCS#1, sequences are indexed from the leftmost byte and the first byte is |
| * the most significant byte. An encoded message EM is an octet string of the |
| * form: |
| * EM = 0x00 || 0x01 || PS || 0x00 || T, where |
| * PS is a byte string of `0xff`s, T is the DER encoding of ASN.1 value of type |
| * DigestInfo that contains the digest algorithm and the digest, and || denotes |
| * concatenation. For SHA-256: |
| * T = (0x)30 31 30 0d 06 09 60 86 48 01 65 03 04 02 01 05 00 04 20 || H, |
| * where H is the digest. |
| * |
| * This function checks the padding and the digest of an encoded message as |
| * described in PKCS#1 but works on little-endian buffers. |
| * |
| * @param enc_msg An encoded message, little-endian. |
| * @param act_digest Actual digest of the message being verified, little-endian. |
| * @param[out] flash_exec Value to write to the flash_ctrl EXEC register. |
| * @return Result of the operation. |
| */ |
| static rom_error_t sigverify_encoded_message_check( |
| sigverify_rsa_buffer_t *enc_msg, const hmac_digest_t *act_digest, |
| uint32_t *flash_exec) { |
| // The algorithm below uses shares, i.e. trivial secret sharing, to check an |
| // encoded message and produce two values: `flash_exec` and `result`. |
| // `flash_exec` is the value to write to the flash_ctrl EXEC register to |
| // unlock flash execution and `result` is the return value. We produce |
| // `result` in addition to `flash_exec` to avoid having the unlock value in |
| // registers or memory just for checking the result of signature verification. |
| // The algorithm consists of two steps: |
| // |
| // 1. First, we xor each word of `enc_msg` with the corresponding expected |
| // value and share (`kSigverifyShares[i]`). At the end of this step, `enc_msg` |
| // becomes `kSigverifyShares` if it's correct and garbage otherwise. Note |
| // that this step of the algorithm is implemented using separate loops to |
| // reduce stack usage. |
| // |
| // 2. Next, we produce `flash_exec` and `result`. `flash_exec` is produced by |
| // xor'ing all words of `enc_msg` with each other. If `enc_msg` is correct, |
| // `flash_exec` will be `kSigverifyFlashExec` due to the way |
| // `kSigverifyShares` is defined. To make sure that we don't produce this |
| // value otherwise, we compare each word of `enc_msg` with the corresponding |
| // expected value and set `flash_exec` to `UINT32_MAX` at each iteration if |
| // there is a mismatch. Finally, we produce the return value `result` from |
| // `flash_exec` by xor'ing parts of it together. Note that the hardware |
| // constant `kSigverifyFlashExec` is chosen such that this operation results |
| // in `kErrorOk`. |
| |
| // Step 1: Process `enc_msg` so that it becomes `kSigverifyShares` if it's |
| // correct, garbage otherwise. |
| uint32_t *enc_msg_ptr = enc_msg->data; |
| size_t i = 0; |
| for (size_t j = 0; launder32(j) < kHmacDigestNumWords; ++j, ++i) { |
| enc_msg_ptr[i] ^= act_digest->digest[j] ^ kSigverifyShares[i]; |
| } |
| // Note: This also includes the zero byte right before PS. |
| static const uint32_t kEncodedSha256[] = { |
| 0x05000420, 0x03040201, 0x86480165, 0x0d060960, 0x00303130, |
| }; |
| for (size_t j = 0; launder32(j) < ARRAYSIZE(kEncodedSha256); ++j, ++i) { |
| enc_msg_ptr[i] ^= kEncodedSha256[j] ^ kSigverifyShares[i]; |
| } |
| // Note: `kPsLen` excludes the last word of `enc_msg`, which is 0x0001ffff. |
| static const size_t kPsLen = ARRAYSIZE(enc_msg->data) - |
| ARRAYSIZE(kEncodedSha256) - |
| ARRAYSIZE(act_digest->digest) - /*last word*/ 1; |
| // PS up to the last word. |
| for (size_t j = 0; launder32(j) < kPsLen; ++j, ++i) { |
| enc_msg_ptr[i] ^= 0xffffffff ^ kSigverifyShares[i]; |
| } |
| // Last word. |
| enc_msg_ptr[i] ^= 0x0001ffff ^ kSigverifyShares[i]; |
| HARDENED_CHECK_EQ(i, kSigVerifyRsaNumWords - 1); |
| |
| // Step 2: Reduce `enc_msg` to produce the value to write to flash_ctrl EXEC |
| // register (`flash_exec`) and the return value (`result`). |
| *flash_exec = 0; |
| uint32_t diff = 0; |
| for (i = 0; launder32(i) < kSigVerifyRsaNumWords; ++i) { |
| // Following three statements set `diff` to `UINT32_MAX` if `enc_msg[i]` is |
| // incorrect, no change otherwise. |
| diff |= enc_msg_ptr[i] ^ kSigverifyShares[i]; |
| diff |= ~diff + 1; // Set upper bits to 1 if not 0, no change o/w. |
| diff |= ~(diff >> 31) + 1; // Set to all 1s if MSB is set, no change o/w. |
| |
| *flash_exec ^= enc_msg_ptr[i]; |
| // Set `flash_exec` to `UINT32_MAX` if `enc_msg` is incorrect. |
| *flash_exec |= diff; |
| } |
| HARDENED_CHECK_EQ(i, kSigVerifyRsaNumWords); |
| |
| // Note: `kSigverifyFlashExec` is defined such that the following operation |
| // produces `kErrorOk`. |
| rom_error_t result = |
| (*flash_exec << 21 ^ *flash_exec << 10 ^ *flash_exec >> 1) >> 21; |
| if (launder32(result) == kErrorOk) { |
| HARDENED_CHECK_EQ(result, kErrorOk); |
| return result; |
| } |
| |
| return kErrorSigverifyBadEncodedMessage; |
| } |
| |
| /** |
| * Determines whether the software implementation should be used for signature |
| * verification. |
| * |
| * During manufacturing (TEST_UNLOCKED*), software implementation is used by |
| * default since OTP may not have been programmed yet. The implementation to use |
| * after manufacturing (PROD, PROD_END, DEV, RMA) is determined by the OTP |
| * value. |
| * |
| * @param lc_state Life cycle state of the device. |
| * @return Whether to use software implementation for signature verification. |
| */ |
| static hardened_bool_t sigverify_use_sw_rsa_verify(uintptr_t otp_addr, lifecycle_state_t lc_state) { |
| switch (launder32(lc_state)) { |
| case kLcStateTest: |
| HARDENED_CHECK_EQ(lc_state, kLcStateTest); |
| // Don't read from OTP during manufacturing. Use software |
| // implementation by default. |
| return kHardenedBoolTrue; |
| case kLcStateDev: |
| HARDENED_CHECK_EQ(lc_state, kLcStateDev); |
| return otp_read32(otp_addr, |
| OTP_CTRL_PARAM_CREATOR_SW_CFG_SIGVERIFY_RSA_MOD_EXP_IBEX_EN_OFFSET); |
| case kLcStateProd: |
| HARDENED_CHECK_EQ(lc_state, kLcStateProd); |
| return otp_read32(otp_addr, |
| OTP_CTRL_PARAM_CREATOR_SW_CFG_SIGVERIFY_RSA_MOD_EXP_IBEX_EN_OFFSET); |
| case kLcStateProdEnd: |
| HARDENED_CHECK_EQ(lc_state, kLcStateProdEnd); |
| return otp_read32(otp_addr, |
| OTP_CTRL_PARAM_CREATOR_SW_CFG_SIGVERIFY_RSA_MOD_EXP_IBEX_EN_OFFSET); |
| case kLcStateRma: |
| HARDENED_CHECK_EQ(lc_state, kLcStateRma); |
| return otp_read32(otp_addr, |
| OTP_CTRL_PARAM_CREATOR_SW_CFG_SIGVERIFY_RSA_MOD_EXP_IBEX_EN_OFFSET); |
| default: |
| HARDENED_UNREACHABLE(); |
| } |
| } |
| |
| rom_error_t sigverify_rsa_verify(uintptr_t otp_addr, |
| const sigverify_rsa_buffer_t *signature, |
| const sigverify_rsa_key_t *key, |
| const hmac_digest_t *act_digest, |
| lifecycle_state_t lc_state, |
| uint32_t *flash_exec) { |
| *flash_exec = UINT32_MAX; |
| hardened_bool_t use_sw = sigverify_use_sw_rsa_verify(otp_addr, lc_state); |
| sigverify_rsa_buffer_t enc_msg; |
| switch (use_sw) { |
| case kHardenedBoolTrue: |
| RETURN_IF_ERROR(sigverify_mod_exp_ibex(key, signature, &enc_msg)); |
| break; |
| case kHardenedBoolFalse: |
| RETURN_IF_ERROR(sigverify_mod_exp_otbn(key, signature, &enc_msg)); |
| break; |
| default: |
| HARDENED_UNREACHABLE(); |
| } |
| return sigverify_encoded_message_check(&enc_msg, act_digest, flash_exec); |
| } |
| |
| void sigverify_usage_constraints_get(uintptr_t otp_addr, |
| uint32_t selector_bits, manifest_usage_constraints_t *usage_constraints) { |
| usage_constraints->selector_bits = selector_bits; |
| lifecycle_device_id_get(&usage_constraints->device_id); |
| |
| usage_constraints->manuf_state_creator = |
| otp_read32(otp_addr, |
| OTP_CTRL_PARAM_CREATOR_SW_CFG_MANUF_STATE_OFFSET); |
| usage_constraints->manuf_state_owner = |
| otp_read32(otp_addr, |
| OTP_CTRL_PARAM_OWNER_SW_CFG_MANUF_STATE_OFFSET); |
| usage_constraints->life_cycle_state = lifecycle_state_get(); |
| |
| static_assert( |
| kManifestSelectorBitDeviceIdFirst == 0 && |
| kManifestSelectorBitDeviceIdLast == kLifecycleDeviceIdNumWords - 1, |
| "mapping from selector_bits to device_id changed, loop must be updated"); |
| for (size_t i = 0; i < kLifecycleDeviceIdNumWords; ++i) { |
| if (!bitfield_bit32_read(selector_bits, i)) { |
| usage_constraints->device_id.device_id[i] = |
| MANIFEST_USAGE_CONSTRAINT_UNSELECTED_WORD_VAL; |
| } |
| } |
| if (!bitfield_bit32_read(selector_bits, |
| kManifestSelectorBitManufStateCreator)) { |
| usage_constraints->manuf_state_creator = |
| MANIFEST_USAGE_CONSTRAINT_UNSELECTED_WORD_VAL; |
| } |
| if (!bitfield_bit32_read(selector_bits, |
| kManifestSelectorBitManufStateOwner)) { |
| usage_constraints->manuf_state_owner = |
| MANIFEST_USAGE_CONSTRAINT_UNSELECTED_WORD_VAL; |
| } |
| if (!bitfield_bit32_read(selector_bits, kManifestSelectorBitLifeCycleState)) { |
| usage_constraints->life_cycle_state = |
| MANIFEST_USAGE_CONSTRAINT_UNSELECTED_WORD_VAL; |
| } |
| } |
| |
| // `extern` declarations for `inline` functions in the header. |
| extern uint32_t sigverify_rsa_key_id_get(const sigverify_rsa_buffer_t *modulus); |