Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
2cffd34
[p256] Harden bn.sel and rshi
siemen11 Sep 11, 2025
445b3f1
[sw,cryptolib] Hardened bn.sel for internal point mult
h-filali Oct 9, 2025
7555e30
[sw,cryptolib] Only do instr count checks for OTBN status code OK
h-filali Oct 6, 2025
addba72
[sw,cryptolib] Only return input errors on user errors
h-filali Oct 6, 2025
32f99a0
[sw,cryptolib] Fix trigger_fault_if_fg-1 functions
h-filali Oct 27, 2025
2604665
[sw,otbnsim] Replace ignored subfunction by nops
h-filali Oct 29, 2025
f527aac
[sw,cryptolib] p384 move flag clear in keygen
h-filali Oct 30, 2025
2cef28b
[sw,cryptolib] Change expected values for tests with result 0
h-filali Oct 30, 2025
113227e
[sw,cryptolib] Update ECC random scalar gen function header
h-filali Oct 30, 2025
5dc883f
[crypto] Fix shift in p256_random_scalar share generation
nasahlpa Jul 23, 2025
2b927f9
[crypto] Fix comment in p384_random_scalar share generation
nasahlpa Jul 23, 2025
f400d57
[crypto] Modify p384_{sign,scalar_mult}.s to comply with OTBN SCA gui…
pqcfox May 28, 2025
1603ca6
[crypto] Fix .data to .bss for P384 code
Jul 29, 2025
3aa7065
[otbn,cov] Add p256 key generation sim test
sasdf Jul 4, 2025
5208047
[quality,sw/otbn] Add SHA-3 and SHAKE OTBN SW implementations
dop-amin Jul 30, 2024
b98bb20
[crypto] Speed up multiplication and squaring mod 2^255 - 19.
Nov 9, 2022
42a6400
[crypto/mul] Support arbitrary-length `bignum_mul` inputs
andrea-caforio Oct 8, 2025
5c3ba99
[crypto/rsa] Remove obsolete OTBN tests
andrea-caforio Oct 8, 2025
87c3438
[rom] Measure ROM space utilization
cfrantz Mar 11, 2024
72e301b
[sw/crypto] Clear HMAC ctx struct
h-filali Jul 10, 2025
039368b
[crypto] Harden `otbn_write` function
nasahlpa Sep 2, 2025
9fa594b
[crypto] Check LOAD_CHECKSUM when writing into DMEM
nasahlpa Sep 2, 2025
94a77ff
[cryptolib,sw] Add instruction count checks to ECC functions
h-filali Jul 7, 2025
f5e3353
[crypto] Wipe DMEM also in error cases
nasahlpa Jul 17, 2025
67972bc
[sw] clang format fix
h-filali Nov 2, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .bazelrc
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ build --strip='never'
# impact of the hardened sequences on code size.
build:disable_hardening --features=-guards --copt=-DOT_DISABLE_HARDENING=1

# Use --config=disable_hardening to disable hardening to measure the
# impact of the hardened sequences on code size.
build:disable_hardening --features=-guards --copt=-DOT_DISABLE_HARDENING=1

# Enable toolchain resolution with cc
build --incompatible_enable_cc_toolchain_resolution

Expand Down
2 changes: 1 addition & 1 deletion hw/ip/otbn/util/analyze_information_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ def main() -> int:
'Initially secret information-flow nodes. If provided, the final '
'secrets will be printed.'))
args = parser.parse_args()
program = decode_elf(args.elf)
program = decode_elf(args.elf, [])

# Compute control-flow graph.
if args.subroutine is None:
Expand Down
2 changes: 1 addition & 1 deletion hw/ip/otbn/util/check_call_stack.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ def main() -> int:
parser.add_argument('elf', help=('The .elf file to check.'))
parser.add_argument('-v', '--verbose', action='store_true')
args = parser.parse_args()
program = decode_elf(args.elf)
program = decode_elf(args.elf, [])
result = check_call_stack(program)
if args.verbose or result.has_errors() or result.has_warnings():
print(result.report())
Expand Down
17 changes: 3 additions & 14 deletions hw/ip/otbn/util/check_const_time.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ def main() -> int:
constants = parse_required_constants(args.constants)

# Compute control graph and get all nodes that influence control flow.
program = decode_elf(args.elf)
program = decode_elf(args.elf, args.ignore or [])
if args.subroutine is None:
graph = program_control_graph(program)
to_analyze = 'entire program'
Expand All @@ -75,14 +75,6 @@ def main() -> int:
_, _, control_deps = get_subroutine_iflow(program, graph,
args.subroutine, constants)

control_deps_ignore = {}
if args.ignore is not None:
for subroutine in args.ignore:
graph = subroutine_control_graph(program, subroutine)
_, _, control_deps_ignore_temp = get_subroutine_iflow(program, graph,
subroutine, {})
control_deps_ignore.update(control_deps_ignore_temp)

if args.secrets is None:
if args.verbose:
print(
Expand All @@ -100,15 +92,12 @@ def main() -> int:
for node, pcs in control_deps.items() if node in args.secrets
}

secret_control_deps_filt = {k: v for k, v in secret_control_deps.items()
if v not in control_deps_ignore.values()}

out = CheckResult()

if len(secret_control_deps_filt) != 0:
if len(secret_control_deps) != 0:
msg = 'The following secrets may influence control flow:\n '
msg += '\n '.join(stringify_control_deps(program,
secret_control_deps_filt))
secret_control_deps))
out.err(msg)

if args.verbose or out.has_errors() or out.has_warnings():
Expand Down
2 changes: 1 addition & 1 deletion hw/ip/otbn/util/check_loop.py
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@ def main() -> int:
parser.add_argument('elf', help=('The .elf file to check.'))
parser.add_argument('-v', '--verbose', action='store_true')
args = parser.parse_args()
program = decode_elf(args.elf)
program = decode_elf(args.elf, [])
result = check_loop(program)
if args.verbose or result.has_errors() or result.has_warnings():
print(result.report())
Expand Down
2 changes: 1 addition & 1 deletion hw/ip/otbn/util/get_instruction_count_range.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ def main() -> int:
help=('The specific subroutine to check. If not provided, the start '
'point is _imem_start (whole program).'))
args = parser.parse_args()
program = decode_elf(args.elf)
program = decode_elf(args.elf, [])

# Compute instruction count range.
if args.subroutine is None:
Expand Down
66 changes: 55 additions & 11 deletions hw/ip/otbn/util/shared/decode.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,19 +20,63 @@

class OTBNProgram:
def __init__(self, symbols: Dict[str, int], insns: Dict[int, int],
data: Dict[int, int]):
data: Dict[int, int], nop_subfuncs: List[str]):
self.symbols = symbols # label -> PC
self.data = data # addr -> data (32b word)
self.data = data # addr -> data (32b word)

# For each function name in nop_subfuncs, we assume that it will have
# control flow limited in the following way. If the function starts at
# address A and the first JALR instruction after A is at address B,
# then execution will not leave the interval [A, B] until the function
# returns by executing the JALR at B.
#
# With this assumption, we can imagine replacing the instructions in
# that interval with a "nop sled". The ranges of the these sleds are
# stored in the nop_ranges list, as pairs (A, B).
nop_ranges: list[tuple[int, int]] = []

for symbol in nop_subfuncs:
# Get the start address of the function. If the function dosen't
# appear in the list of symbols, there's nothing to do for it.
start_addr = symbols.get(symbol)
if start_addr is None:
continue

for pc in range(start_addr, 1 << 32, 4):
opcode = insns.get(pc)
if opcode is None:
raise RuntimeError("Fell off the end of the binary "
"when searching for a JALR for a "
f"function with symbol {symbol}, "
f"starting at {start_addr:#x}")

# Check whether we just found 'B' (see note above nop_ranges)
if INSNS_FILE.mnem_for_word(opcode) == 'jalr':
nop_ranges.append((start_addr, pc))
break

self.insns = {}
for pc, opcode in insns.items():
mnem = INSNS_FILE.mnem_for_word(opcode)
if mnem is None:
raise ValueError(
'No legal decoding for mnemonic: {}'.format(mnem))
insn = INSNS_FILE.mnemonic_to_insn[mnem]
assert insn.encoding is not None
enc_vals = insn.encoding.extract_operands(opcode)
# Check if PC lies within one of the NOP ranges (equal to or after
# the start of a nop subfunc and strictly before the JALR
# instruction at the end).
in_nop_region = any(a <= pc < b for a, b in nop_ranges)

# If the PC *is* in a NOP region, interpret the opcode at PC as a
# NOP (addi x0, x0, x0). If not, decode the opcode and find the
# appropriate instruction and operands.
if in_nop_region:
insn = INSNS_FILE.mnemonic_to_insn["addi"]
enc_vals = {'imm': 0, 'grs1': 0, 'grd': 0}
else:
mnem = INSNS_FILE.mnem_for_word(opcode)
if mnem is None:
raise ValueError(f'No mnemonic for opcode {opcode:#08x}')

insn = INSNS_FILE.mnemonic_to_insn[mnem]
assert insn.encoding is not None
enc_vals = insn.encoding.extract_operands(opcode)

op_vals = insn.enc_vals_to_op_vals(pc, enc_vals)
self.insns[pc] = (insn, op_vals)

Expand Down Expand Up @@ -69,7 +113,7 @@ def _decode_mem(base_addr: int, data: bytes) -> Dict[int, int]:
for offset, int_val in enumerate(struct.iter_unpack('<I', data))}


def decode_elf(path: str) -> OTBNProgram:
def decode_elf(path: str, nop_subfuncs: List[str]) -> OTBNProgram:
'''Read ELF file at path and decode contents into an OTBNProgram instance

Returns the OTBNProgram instance representing the program in the ELF file.
Expand All @@ -79,4 +123,4 @@ def decode_elf(path: str) -> OTBNProgram:
insns = _decode_mem(0, imem_bytes)
data = _decode_mem(0, dmem_bytes)

return OTBNProgram(symbols, insns, data)
return OTBNProgram(symbols, insns, data, nop_subfuncs)
3 changes: 3 additions & 0 deletions quality/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ licence_test(

# Other Licences,
"util/wavegen/LICENSE.wavedrom",
"sw/otbn/crypto/LICENSE.tiny_sha3",
# Site Assets
"site/**/assets/scss/**",
"site/landing/static/js/tiny-slider.js",
Expand All @@ -62,6 +63,8 @@ licence_test(
"sw/otbn/crypto/rsa_verify.s",
"sw/otbn/crypto/handwritten/rsa_verify_3072.s",
"sw/otbn/crypto/sha512.s",
# Code based on tiny_sha3
"sw/otbn/crypto/sha3_shake.s",
# Mersenne Twister PRNG
"sw/device/sca/lib/prng.c",
# SPHINCS+ known-answer test data
Expand Down
6 changes: 2 additions & 4 deletions sw/device/lib/base/hardened.h
Original file line number Diff line number Diff line change
Expand Up @@ -241,8 +241,7 @@ inline uint32_t launder32(uint32_t val) {

// When we're building for static analysis, reduce false positives by
// short-circuiting the inline assembly block.
#if OT_BUILD_FOR_STATIC_ANALYZER || \
(defined(OT_DISABLE_HARDENING) && OT_DISABLE_HARDENING)
#if OT_BUILD_FOR_STATIC_ANALYZER || OT_DISABLE_HARDENING
return val;
#endif

Expand All @@ -264,8 +263,7 @@ inline uint32_t launder32(uint32_t val) {
*/
OT_WARN_UNUSED_RESULT
inline uintptr_t launderw(uintptr_t val) {
#if OT_BUILD_FOR_STATIC_ANALYZER || \
(defined(OT_DISABLE_HARDENING) && OT_DISABLE_HARDENING)
#if OT_BUILD_FOR_STATIC_ANALYZER || OT_DISABLE_HARDENING
return val;
#endif
asm volatile("" : "+r"(val));
Expand Down
9 changes: 9 additions & 0 deletions sw/device/lib/crypto/drivers/hmac.c
Original file line number Diff line number Diff line change
Expand Up @@ -460,6 +460,9 @@ static status_t oneshot(const uint32_t cfg, const hmac_key_t *key,
// Check that the block is idle.
HARDENED_TRY(ensure_idle());

// Make sure that the entropy complex is configured correctly.
HARDENED_TRY(entropy_complex_check());

// Configure the HMAC block.
abs_mmio_write32(kHmacBaseAddr + HMAC_CFG_REG_OFFSET, cfg);

Expand Down Expand Up @@ -751,6 +754,9 @@ hardened_bool_t hmac_key_integrity_checksum_check(const hmac_key_t *key) {
}

status_t hmac_update(hmac_ctx_t *ctx, const uint8_t *data, size_t len) {
// Make sure that the entropy complex is configured correctly.
HARDENED_TRY(entropy_complex_check());

// If we don't have enough new bytes to fill a block, just update the partial
// block and return.
size_t block_bytelen = ctx->msg_block_wordlen * sizeof(uint32_t);
Expand Down Expand Up @@ -806,6 +812,9 @@ status_t hmac_update(hmac_ctx_t *ctx, const uint8_t *data, size_t len) {
}

status_t hmac_final(hmac_ctx_t *ctx, uint32_t *digest) {
// Make sure that the entropy complex is configured correctly.
HARDENED_TRY(entropy_complex_check());

// Retore context will restore the context and also hit start or continue
// button as necessary.
HARDENED_TRY(context_restore(ctx));
Expand Down
30 changes: 24 additions & 6 deletions sw/device/lib/crypto/impl/ecc/p256.c
Original file line number Diff line number Diff line change
Expand Up @@ -82,12 +82,12 @@ enum {
/*
* The expected instruction counts for constant time functions.
*/
kModeKeygenInsCnt = 567175,
kModeKeygenSideloadInsCnt = 567060,
kModeEcdhInsCnt = 574858,
kModeEcdhSideloadInsCnt = 574918,
kModeEcdsaSignInsCnt = 600349,
kModeEcdsaSignSideloadInsCnt = 600409,
kModeKeygenInsCnt = 573915,
kModeKeygenSideloadInsCnt = 573800,
kModeEcdhInsCnt = 581598,
kModeEcdhSideloadInsCnt = 581658,
kModeEcdsaSignInsCnt = 607087,
kModeEcdsaSignSideloadInsCnt = 607147,
};

static status_t p256_masked_scalar_write(p256_masked_scalar_t *src,
Expand Down Expand Up @@ -177,6 +177,7 @@ status_t p256_keygen_finalize(p256_masked_scalar_t *private_key,
p256_point_t *public_key) {
// Spin here waiting for OTBN to complete.
HARDENED_TRY_WIPE_DMEM(otbn_busy_wait_for_done());
HARDENED_CHECK_EQ(otbn_instruction_count_get(), kModeKeygenInsCnt);

// Read the masked private key from OTBN dmem.
HARDENED_TRY_WIPE_DMEM(otbn_dmem_read(kP256MaskedScalarShareWords, kOtbnVarD0,
Expand All @@ -200,6 +201,7 @@ status_t p256_keygen_finalize(p256_masked_scalar_t *private_key,
status_t p256_sideload_keygen_finalize(p256_point_t *public_key) {
// Spin here waiting for OTBN to complete.
HARDENED_TRY_WIPE_DMEM(otbn_busy_wait_for_done());
HARDENED_CHECK_EQ(otbn_instruction_count_get(), kModeKeygenSideloadInsCnt);

// Read the public key from OTBN dmem.
HARDENED_TRY_WIPE_DMEM(
Expand Down Expand Up @@ -273,8 +275,15 @@ status_t p256_ecdsa_sideload_sign_start(
}

status_t p256_ecdsa_sign_finalize(p256_ecdsa_signature_t *result) {
uint32_t ins_cnt;
// Spin here waiting for OTBN to complete.
HARDENED_TRY_WIPE_DMEM(otbn_busy_wait_for_done());
ins_cnt = otbn_instruction_count_get();
if (launder32(ins_cnt) == kModeEcdsaSignSideloadInsCnt) {
HARDENED_CHECK_EQ(ins_cnt, kModeEcdsaSignSideloadInsCnt);
} else {
HARDENED_CHECK_EQ(ins_cnt, kModeEcdsaSignInsCnt);
}

// Read signature R out of OTBN dmem.
HARDENED_TRY_WIPE_DMEM(
Expand Down Expand Up @@ -381,6 +390,15 @@ status_t p256_ecdh_finalize(p256_ecdh_shared_key_t *shared_key) {
}
HARDENED_CHECK_EQ(ok, kHardenedBoolTrue);

// OTBN returned the status code OK, so check for the expected instr. count.
uint32_t ins_cnt;
ins_cnt = otbn_instruction_count_get();
if (launder32(ins_cnt) == kModeEcdhSideloadInsCnt) {
HARDENED_CHECK_EQ(ins_cnt, kModeEcdhSideloadInsCnt);
} else {
HARDENED_CHECK_EQ(ins_cnt, kModeEcdhInsCnt);
}

// Read the shares of the key from OTBN dmem (at vars x and y).
HARDENED_TRY_WIPE_DMEM(
otbn_dmem_read(kP256CoordWords, kOtbnVarX, shared_key->share0));
Expand Down
30 changes: 24 additions & 6 deletions sw/device/lib/crypto/impl/ecc/p384.c
Original file line number Diff line number Diff line change
Expand Up @@ -92,12 +92,12 @@ enum {
/*
* The expected instruction counts for constant time functions.
*/
kModeKeygenInsCnt = 1899010,
kModeKeygenSideloadInsCnt = 1898904,
kModeEcdhInsCnt = 1910613,
kModeEcdhSideloadInsCnt = 1910762,
kModeEcdsaSignInsCnt = 1546539,
kModeEcdsaSignSideloadInsCnt = 1546688,
kModeKeygenInsCnt = 1935430,
kModeKeygenSideloadInsCnt = 1935323,
kModeEcdhInsCnt = 1947029,
kModeEcdhSideloadInsCnt = 1947177,
kModeEcdsaSignInsCnt = 1574769,
kModeEcdsaSignSideloadInsCnt = 1574917,
};

static status_t p384_masked_scalar_write(p384_masked_scalar_t *src,
Expand Down Expand Up @@ -218,6 +218,7 @@ status_t p384_keygen_finalize(p384_masked_scalar_t *private_key,
p384_point_t *public_key) {
// Spin here waiting for OTBN to complete.
HARDENED_TRY_WIPE_DMEM(otbn_busy_wait_for_done());
HARDENED_CHECK_EQ(otbn_instruction_count_get(), kModeKeygenInsCnt);

// Read the masked private key from OTBN dmem.
HARDENED_TRY_WIPE_DMEM(otbn_dmem_read(kP384MaskedScalarShareWords, kOtbnVarD0,
Expand Down Expand Up @@ -253,6 +254,7 @@ status_t p384_sideload_keygen_start(void) {
status_t p384_sideload_keygen_finalize(p384_point_t *public_key) {
// Spin here waiting for OTBN to complete.
HARDENED_TRY_WIPE_DMEM(otbn_busy_wait_for_done());
HARDENED_CHECK_EQ(otbn_instruction_count_get(), kModeKeygenSideloadInsCnt);

// Read the public key from OTBN dmem.
HARDENED_TRY_WIPE_DMEM(
Expand Down Expand Up @@ -302,8 +304,15 @@ status_t p384_ecdsa_sideload_sign_start(
}

status_t p384_ecdsa_sign_finalize(p384_ecdsa_signature_t *result) {
uint32_t ins_cnt;
// Spin here waiting for OTBN to complete.
HARDENED_TRY_WIPE_DMEM(otbn_busy_wait_for_done());
ins_cnt = otbn_instruction_count_get();
if (launder32(ins_cnt) == kModeEcdsaSignSideloadInsCnt) {
HARDENED_CHECK_EQ(ins_cnt, kModeEcdsaSignSideloadInsCnt);
} else {
HARDENED_CHECK_EQ(ins_cnt, kModeEcdsaSignInsCnt);
}

// Read signature R out of OTBN dmem.
HARDENED_TRY_WIPE_DMEM(
Expand Down Expand Up @@ -405,6 +414,15 @@ status_t p384_ecdh_finalize(p384_ecdh_shared_key_t *shared_key) {
}
HARDENED_CHECK_EQ(ok, kHardenedBoolTrue);

// OTBN returned the status code OK, so check for the expected instr. count.
uint32_t ins_cnt;
ins_cnt = otbn_instruction_count_get();
if (launder32(ins_cnt) == kModeEcdhSideloadInsCnt) {
HARDENED_CHECK_EQ(ins_cnt, kModeEcdhSideloadInsCnt);
} else {
HARDENED_CHECK_EQ(ins_cnt, kModeEcdhInsCnt);
}

// Read the shares of the key from OTBN dmem (at vars x and y).
HARDENED_TRY_WIPE_DMEM(
otbn_dmem_read(kP384CoordWords, kOtbnVarX, shared_key->share0));
Expand Down
Loading
Loading