Add PIN attempt limiting with PBKDF2 key stretching#135
Add PIN attempt limiting with PBKDF2 key stretching#135wksantiago wants to merge 3 commits intomainfrom
Conversation
|
Warning Rate limit exceeded
⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. WalkthroughIntroduces persistent PIN attempt limiting with per-device PBKDF2/HKDF key derivation, HMAC-backed PIN state stored in secure element or NVS, exponential backoff delays, device bricking after max failures, and new runtime/test accessors and native tests. Changes
Sequence Diagram(s)sequenceDiagram
actor User
participant App as Application
participant SC as StorageCrypto
participant SE as SecureElement/NVS
participant Crypto as PBKDF2/HMAC
User->>App: Enter PIN
App->>SC: storage_crypto_init()/verify attempt
SC->>SE: load pin_state (attempts,salt,hmac)
SE-->>SC: pin_state
SC->>Crypto: compute_state_hmac(state, device_id)
Crypto-->>SC: hmac_value
SC->>SC: validate HMAC == stored_hmac
alt HMAC invalid
SC->>App: error (tamper)
else HMAC valid
SC->>SC: if bricked -> return BRICKED
SC->>SC: compute delay based on attempts
alt delay > 0
SC->>App: indicate delay remaining
else
App->>SC: perform PIN check (derive key)
SC->>Crypto: PBKDF2/HKDF derive storage key
Crypto-->>SC: derived_key
alt PIN correct
SC->>SC: reset attempts, save state
SC->>SE: write pin_state (attempts=0,hmac)
SE-->>SC: ack
SC->>App: success
else PIN incorrect
SC->>SC: increment attempts
alt attempts >= max
SC->>SC: set bricked, wipe device
SC->>SE: save bricked state
SE-->>SC: ack
SC->>App: device locked
else
SC->>SE: save updated state
SE-->>SC: ack
SC->>App: retry info
end
end
end
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes Possibly related PRs
Suggested reviewers
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Fix all issues with AI agents
In `@main/storage_crypto.c`:
- Around line 343-368: The rate-limit can be bypassed after reboot because
storage_crypto_check_rate_limit reads pin_state.last_failure_time (set from a
monotonic timer) but compares it to get_time_ms which resets on reboot; change
the logic in storage_crypto_check_rate_limit and the pin state handling so you
persist a lockout_deadline (absolute expiry) instead of last_failure_time or
detect clock/monotonic resets: on failure set pin_state.lockout_deadline =
get_time_ms() + get_delay_ms(...), persist it, and in
storage_crypto_check_rate_limit compare current time to lockout_deadline (treat
missing/earlier current time than last stored time as still locked until
deadline) ensuring functions load_pin_state, get_delay_ms, and get_time_ms are
used consistently to compute and validate the absolute deadline.
- Around line 79-112: compute_state_hmac currently uses the device_id as the
HMAC key; replace that with a true secret key from a secure source (secure
element, eFuse, or flash-encryption-derived key). Modify compute_state_hmac to
accept or retrieve a secret key (e.g., a 32-byte key argument or call a secure
retrieval function like secure_element_get_key()), use that secret to build
key_padded/ipad/opad instead of device_id, and ensure the secret is never logged
and is wiped with secure_memzero after use; update callers to provide the secret
or ensure the function retrieves it from the secure element.
- Around line 305-319: The backoff function get_delay_ms currently returns
UINT32_MAX for attempts > 12, which prematurely triggers ERR_PIN_BRICKED before
PIN_MAX_ATTEMPTS (21); change the final condition so attempts
13–(PIN_MAX_ATTEMPTS-1) return the 15-minute backoff and only return UINT32_MAX
when attempts >= PIN_MAX_ATTEMPTS. Concretely, keep the existing branches for
<=3, <=6, <=9, then replace the <=12 branch with a branch that returns
15*60*1000 for attempts <= (PIN_MAX_ATTEMPTS - 1) (or explicitly for attempts <=
20) and return UINT32_MAX only when attempts >= PIN_MAX_ATTEMPTS; reference
get_delay_ms and the PIN_MAX_ATTEMPTS constant when making this change.
🧹 Nitpick comments (1)
test/native/CMakeLists.txt (1)
169-180: Consider using MbedTLS::mbedcrypto when available for consistency.Lines 95-117 use
find_package(MbedTLS)and theMbedTLS::mbedcryptotarget when available. This new target only usesfind_library, which could fail on systems where mbedtls is found via package config but not in standard library paths. For consistency:♻️ Suggested improvement for consistent mbedtls handling
-find_library(MBEDCRYPTO_LIB mbedcrypto) -if(MBEDCRYPTO_LIB) +if(MbedTLS_FOUND OR MBEDCRYPTO_LIB) add_executable(test_pin_attempt_limit test_pin_attempt_limit.c ${MAIN_DIR}/random_utils.c ${MAIN_DIR}/hw_entropy.c ${MAIN_DIR}/error_codes.c) target_include_directories(test_pin_attempt_limit PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/mocks ${MAIN_DIR} ) target_compile_definitions(test_pin_attempt_limit PRIVATE NATIVE_TEST=1 UNIT_TEST=1 MOCK_MBEDTLS=1) - target_link_libraries(test_pin_attempt_limit ${MBEDCRYPTO_LIB}) + if(MbedTLS_FOUND) + target_link_libraries(test_pin_attempt_limit MbedTLS::mbedcrypto) + else() + target_link_libraries(test_pin_attempt_limit ${MBEDCRYPTO_LIB}) + endif() else() message(STATUS "Skipping test_pin_attempt_limit (mbedtls not found)") endif()
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Fix all issues with AI agents
In `@main/storage_crypto.c`:
- Around line 50-55: The macro block defining NVS_NAMESPACE and NVS_KEY_*
constants violates clang-format style; run clang-format on main/storage_crypto.c
or reformat the block so the define names and string literals follow the
project's alignment rules (e.g. consistent spacing/tabs between macro names and
values) for NVS_NAMESPACE, NVS_KEY_FAILURES, NVS_KEY_LOCKOUT, NVS_KEY_BRICKED,
NVS_KEY_SALT and NVS_KEY_HMAC so the static analysis/CICD checks pass.
- Around line 153-192: compute_state_hmac currently swallows HMAC-key retrieval
failures by zeroing hmac_out and returning void; change its signature to return
an int (0 on success, non-zero on failure), propagate the error from
get_hmac_secret_key to the caller instead of writing a zero HMAC, and ensure
callers (e.g., the state load/save functions that call compute_state_hmac) check
the return value and abort/skip accepting or writing state when the HMAC cannot
be computed; keep the existing secure_memzero calls for secret_key,
ipad/opad/inner_hash on both success and failure paths and ensure hmac_out is
not used when compute_state_hmac returns an error.
- Around line 429-433: The lockout deadline overflows because get_time_ms(),
pin_state.lockout_deadline, and local now are 32-bit; change get_time_ms() to
return uint64_t, change pin_state.lockout_deadline to uint64_t, update any local
uint32_t now variables to uint64_t, and replace NVS calls
nvs_get_u32()/nvs_set_u32() with nvs_get_u64()/nvs_set_u64() where the deadline
is persisted (ensure reading/writing uses the 64-bit key). Update any related
comparisons and assignments in the functions that reference get_time_ms() and
pin_state.lockout_deadline (e.g., the lockout check and setting code) so all
time math uses 64-bit to prevent wrap-around.
♻️ Duplicate comments (1)
main/storage_crypto.c (1)
83-149: Fallback HMAC key is still predictable when SE is absent.
The non‑SE path hashes device_id/serial, which isn’t secret, so an attacker can recompute the HMAC and tamper with the NVS state. Please use a true secret source (e.g., SE-stored key, flash‑encryption/eFuse/NVS‑encryption derived key) or treat state as untrusted in this mode.
Summary
Test plan
test_pin_attempt_limitnative testsCloses #68
Summary by CodeRabbit
New Features
Security Improvements
Tests
✏️ Tip: You can customize this high-level summary in your review settings.