-
Notifications
You must be signed in to change notification settings - Fork 39
Enforce NONMODIFIABLE and TRUSTED NVM policy on keystore key commit #492
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -246,8 +246,30 @@ static int _KeystoreCheckPolicy(whServerContext* server, whKsOp op, | |||||||||||||||||||||||||||||||||||
| break; | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| case WH_KS_OP_COMMIT: | ||||||||||||||||||||||||||||||||||||
|
Frauschi marked this conversation as resolved.
Frauschi marked this conversation as resolved.
Frauschi marked this conversation as resolved.
|
||||||||||||||||||||||||||||||||||||
| /* Stored flags decide, not cached, so an unchecked cache path | ||||||||||||||||||||||||||||||||||||
| * cannot launder them; same pair as wh_Nvm_AddObjectChecked. | ||||||||||||||||||||||||||||||||||||
| * Fetched here too, so the verdict ignores cache residency. */ | ||||||||||||||||||||||||||||||||||||
| if (!foundInNvm && (server->nvm != NULL)) { | ||||||||||||||||||||||||||||||||||||
| ret = wh_Nvm_GetMetadata(server->nvm, keyId, &nvmMeta); | ||||||||||||||||||||||||||||||||||||
| if (ret == WH_ERROR_OK) { | ||||||||||||||||||||||||||||||||||||
| foundInNvm = 1; | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
| else if (ret != WH_ERROR_NOTFOUND) { | ||||||||||||||||||||||||||||||||||||
| /* Unreadable flags cannot be enforced: fail the commit. */ | ||||||||||||||||||||||||||||||||||||
| return ret; | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
| if (foundInNvm && | ||||||||||||||||||||||||||||||||||||
|
Frauschi marked this conversation as resolved.
|
||||||||||||||||||||||||||||||||||||
| (nvmMeta.flags & | ||||||||||||||||||||||||||||||||||||
| (WH_NVM_FLAGS_NONMODIFIABLE | WH_NVM_FLAGS_TRUSTED))) { | ||||||||||||||||||||||||||||||||||||
| return WH_ERROR_ACCESS; | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
| break; | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| case WH_KS_OP_REVOKE: | ||||||||||||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 [Medium] Revoke still launders cached flags into NVM, contradicting the new "Revocation only tightens policy" comment The diff reworks the shared Suggestion:
Suggested change
Recommendation: Either extend the stored-flag check to WH_KS_OP_REVOKE (at minimum for TRUSTED, since revoking a trusted KEK is never legitimate and would strip the flag), or reword the comment so it does not claim an invariant the revoke implementation does not enforce. A
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Took the second option: reworded the comment rather than extending the gate.
You are right that the old wording claimed more than the code holds, so it No behavior change. |
||||||||||||||||||||||||||||||||||||
| /* Always allowed */ | ||||||||||||||||||||||||||||||||||||
| /* No flag gate: _revokeKey only sets NONMODIFIABLE and clears | ||||||||||||||||||||||||||||||||||||
| * usage flags, so revocation never grants new access. The stored | ||||||||||||||||||||||||||||||||||||
| * object is still rewritten from the cache slot. */ | ||||||||||||||||||||||||||||||||||||
| break; | ||||||||||||||||||||||||||||||||||||
| default: | ||||||||||||||||||||||||||||||||||||
| /* unknown operation */ | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -28,6 +28,15 @@ | |||||||||||
| * _whTest_NonExportableKeystore - confirm WH_NVM_FLAGS_NONEXPORTABLE keys | ||||||||||||
| * cannot be exported while ordinary keys can | ||||||||||||
| * (std and DMA export paths) | ||||||||||||
| * _whTest_NonModifiableCommit - re-commit over a stored | ||||||||||||
| * WH_NVM_FLAGS_NONMODIFIABLE object is | ||||||||||||
| * denied, cached or not, and the stored key | ||||||||||||
| * and label survive the denial | ||||||||||||
| * _whTest_ModifiableRecommit - a key without the flag still re-commits | ||||||||||||
| * | ||||||||||||
| * _whTest_NonModifiableCommit is gated by | ||||||||||||
| * WOLFHSM_CFG_TEST_ALLOW_PERSISTENT_NVM_ARTIFACTS: the object it commits | ||||||||||||
| * cannot be erased, so it holds an NVM slot for the rest of the run. | ||||||||||||
| */ | ||||||||||||
|
|
||||||||||||
| #include "wolfhsm/wh_settings.h" | ||||||||||||
|
|
@@ -839,13 +848,149 @@ static int _whTest_NonExportableKeystore(whClientContext* ctx) | |||||||||||
| return 0; | ||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔵 [Low] Suite header comment and README not updated for the two new tests The file opens with a per-test inventory that the diff leaves stale — it still lists only Suggestion:
Suggested change
Recommendation: Extend the file header inventory and the README row for
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. The file header inventory now lists |
||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| #if defined(WOLFHSM_CFG_TEST_ALLOW_PERSISTENT_NVM_ARTIFACTS) | ||||||||||||
|
Frauschi marked this conversation as resolved.
Frauschi marked this conversation as resolved.
|
||||||||||||
| /* Committing a NONMODIFIABLE key leaves an object that | ||||||||||||
| * wh_Nvm_DestroyObjectsChecked refuses to erase, so it occupies one NVM | ||||||||||||
| * slot for the rest of the run. Gated like the keypolicy revocation test. */ | ||||||||||||
| static int _whTest_NonModifiableCommit(whClientContext* ctx) | ||||||||||||
| { | ||||||||||||
| int ret = 0; | ||||||||||||
| whKeyId keyId = WH_KEYID_ERASED; | ||||||||||||
| uint8_t key[WH_TEST_KEYSTORE_TEST_SZ] = { | ||||||||||||
| 0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF, 0x01, 0x23, 0x45, | ||||||||||||
| 0x67, 0x89, 0xAB, 0xCD, 0xEF, 0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, | ||||||||||||
| 0x32, 0x10, 0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}; | ||||||||||||
| uint8_t exportedKey[WH_TEST_KEYSTORE_TEST_SZ] = {0}; | ||||||||||||
| uint8_t label[WH_NVM_LABEL_LEN] = "NonModifiableCommitKey"; | ||||||||||||
| uint8_t exportedLabel[WH_NVM_LABEL_LEN] = {0}; | ||||||||||||
| uint16_t exportedKeySize; | ||||||||||||
|
|
||||||||||||
| WH_TEST_PRINT("Testing non-modifiable commit enforcement...\n"); | ||||||||||||
|
|
||||||||||||
| /* Test 1: first commit of a NONMODIFIABLE key stores it, and the commit | ||||||||||||
| * leaves the slot cached, so a repeat commit is an overwrite attempt. */ | ||||||||||||
| ret = wh_Client_KeyCache(ctx, WH_NVM_FLAGS_NONMODIFIABLE, label, | ||||||||||||
| sizeof(label), key, sizeof(key), &keyId); | ||||||||||||
| if (ret != 0) { | ||||||||||||
| WH_ERROR_PRINT("Failed to cache non-modifiable key: %d\n", ret); | ||||||||||||
| return ret; | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| ret = wh_Client_KeyCommit(ctx, keyId); | ||||||||||||
| if (ret != 0) { | ||||||||||||
| WH_ERROR_PRINT("Failed first commit of non-modifiable key: %d\n", ret); | ||||||||||||
| return ret; | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| /* Test 2: re-committing over the stored non-modifiable object is denied */ | ||||||||||||
| ret = wh_Client_KeyCommit(ctx, keyId); | ||||||||||||
| if (ret != WH_ERROR_ACCESS) { | ||||||||||||
| WH_ERROR_PRINT("Non-modifiable key was re-committed unexpectedly: %d\n", | ||||||||||||
| ret); | ||||||||||||
| return -1; | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| WH_TEST_DEBUG_PRINT("Non-modifiable key re-commit correctly denied\n"); | ||||||||||||
|
|
||||||||||||
| /* Test 3: the denial left the stored object intact. Evicting is allowed | ||||||||||||
| * because the key is committed, so the export below must freshen it back | ||||||||||||
| * out of NVM rather than read the surviving cache slot. */ | ||||||||||||
| ret = wh_Client_KeyEvict(ctx, keyId); | ||||||||||||
| if (ret != 0) { | ||||||||||||
| WH_ERROR_PRINT("Failed to evict committed non-modifiable key: %d\n", | ||||||||||||
| ret); | ||||||||||||
| return ret; | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| exportedKeySize = sizeof(exportedKey); | ||||||||||||
| ret = wh_Client_KeyExport(ctx, keyId, exportedLabel, sizeof(exportedLabel), | ||||||||||||
| exportedKey, &exportedKeySize); | ||||||||||||
| if (ret != 0) { | ||||||||||||
| WH_ERROR_PRINT("Failed to export stored non-modifiable key: %d\n", ret); | ||||||||||||
| return ret; | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| if (exportedKeySize != sizeof(key) || | ||||||||||||
| memcmp(key, exportedKey, exportedKeySize) != 0 || | ||||||||||||
| memcmp(label, exportedLabel, sizeof(label)) != 0) { | ||||||||||||
| WH_ERROR_PRINT("Denied commit altered the stored key\n"); | ||||||||||||
| return -1; | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| WH_TEST_DEBUG_PRINT("Stored non-modifiable key unchanged after denial\n"); | ||||||||||||
|
|
||||||||||||
| /* The key cannot be erased: wh_Nvm_DestroyObjectsChecked refuses a | ||||||||||||
|
Frauschi marked this conversation as resolved.
|
||||||||||||
| * NONMODIFIABLE object, so only the cache slot is reclaimed here. */ | ||||||||||||
| (void)wh_Client_KeyEvict(ctx, keyId); | ||||||||||||
|
|
||||||||||||
| /* Test 4: the denial does not depend on cache residency. With no slot | ||||||||||||
| * left, the stored flags still decide, so commit reports ACCESS rather | ||||||||||||
| * than the NOTFOUND raised by the missing slot. */ | ||||||||||||
| ret = wh_Client_KeyCommit(ctx, keyId); | ||||||||||||
| if (ret != WH_ERROR_ACCESS) { | ||||||||||||
| WH_ERROR_PRINT("Uncached non-modifiable commit not denied: %d\n", ret); | ||||||||||||
| return -1; | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| WH_TEST_DEBUG_PRINT("Uncached non-modifiable commit correctly denied\n"); | ||||||||||||
|
|
||||||||||||
| WH_TEST_PRINT("NON-MODIFIABLE COMMIT TEST SUCCESS\n"); | ||||||||||||
| return 0; | ||||||||||||
| } | ||||||||||||
| #endif /* WOLFHSM_CFG_TEST_ALLOW_PERSISTENT_NVM_ARTIFACTS */ | ||||||||||||
|
|
||||||||||||
| static int _whTest_ModifiableRecommit(whClientContext* ctx) | ||||||||||||
| { | ||||||||||||
| int ret = 0; | ||||||||||||
| whKeyId keyId = WH_KEYID_ERASED; | ||||||||||||
| uint8_t key[WH_TEST_KEYSTORE_TEST_SZ] = { | ||||||||||||
| 0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF, 0x01, 0x23, 0x45, | ||||||||||||
| 0x67, 0x89, 0xAB, 0xCD, 0xEF, 0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, | ||||||||||||
| 0x32, 0x10, 0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}; | ||||||||||||
| uint8_t label[WH_NVM_LABEL_LEN] = "ModifiableCommitKey"; | ||||||||||||
|
|
||||||||||||
| WH_TEST_PRINT("Testing modifiable commit is unaffected...\n"); | ||||||||||||
|
|
||||||||||||
| /* A key without the flag still commits repeatedly */ | ||||||||||||
| ret = wh_Client_KeyCache(ctx, WH_NVM_FLAGS_NONE, label, sizeof(label), key, | ||||||||||||
| sizeof(key), &keyId); | ||||||||||||
| if (ret != 0) { | ||||||||||||
| WH_ERROR_PRINT("Failed to cache modifiable key: %d\n", ret); | ||||||||||||
| return ret; | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| ret = wh_Client_KeyCommit(ctx, keyId); | ||||||||||||
| if (ret != 0) { | ||||||||||||
| WH_ERROR_PRINT("Failed first commit of modifiable key: %d\n", ret); | ||||||||||||
| return ret; | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| ret = wh_Client_KeyCommit(ctx, keyId); | ||||||||||||
| if (ret != 0) { | ||||||||||||
| WH_ERROR_PRINT("Failed repeat commit of modifiable key: %d\n", ret); | ||||||||||||
| return ret; | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| WH_TEST_DEBUG_PRINT("Modifiable key repeat commit allowed\n"); | ||||||||||||
|
|
||||||||||||
| /* Clean up */ | ||||||||||||
| (void)wh_Client_KeyErase(ctx, keyId); | ||||||||||||
|
|
||||||||||||
| WH_TEST_PRINT("MODIFIABLE COMMIT TEST SUCCESS\n"); | ||||||||||||
| return 0; | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| int whTest_Crypto_Keystore(whClientContext* ctx) | ||||||||||||
| { | ||||||||||||
| /* A preceding suite may leave the DMA-preferred dispatch mode set; reset | ||||||||||||
| * to the std path so this suite runs the same way in every config. */ | ||||||||||||
| (void)wh_Client_SetDmaMode(ctx, 0); | ||||||||||||
| WH_TEST_RETURN_ON_FAIL(_whTest_KeyCache(ctx)); | ||||||||||||
| WH_TEST_RETURN_ON_FAIL(_whTest_NonExportableKeystore(ctx)); | ||||||||||||
| #if defined(WOLFHSM_CFG_TEST_ALLOW_PERSISTENT_NVM_ARTIFACTS) | ||||||||||||
| WH_TEST_RETURN_ON_FAIL(_whTest_NonModifiableCommit(ctx)); | ||||||||||||
| #endif | ||||||||||||
| WH_TEST_RETURN_ON_FAIL(_whTest_ModifiableRecommit(ctx)); | ||||||||||||
| return 0; | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
|
|
||||||||||||
Uh oh!
There was an error while loading. Please reload this page.