Skip to content

[crypto] Re-mask AES-GCM keys #27647

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

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
48 changes: 48 additions & 0 deletions sw/device/lib/base/hardened_memory.c
Original file line number Diff line number Diff line change
Expand Up @@ -177,3 +177,51 @@ hardened_bool_t hardened_memeq(const uint32_t *lhs, const uint32_t *rhs,
HARDENED_CHECK_NE(ones, UINT32_MAX);
return kHardenedBoolFalse;
}

void hardened_xor(uint32_t *restrict x, const uint32_t *restrict y,
size_t word_len) {
// Generate a random ordering.
random_order_t order;
random_order_init(&order, word_len);
size_t count = 0;
size_t expected_count = random_order_len(&order);

// Create some random values for decoy operations.
uint32_t decoys[8];
hardened_memshred(decoys, ARRAYSIZE(decoys));

// Cast pointers to `uintptr_t` to erase their provenance.
uintptr_t x_addr = (uintptr_t)x;
uintptr_t y_addr = (uintptr_t)y;
uintptr_t decoy_addr = (uintptr_t)&decoys;

// XOR the mask with the first share. This loop is modelled off the one in
// `hardened_memcpy`; see the comments there for more details.
size_t byte_len = word_len * sizeof(uint32_t);
for (; launderw(count) < expected_count; count = launderw(count) + 1) {
size_t byte_idx = launderw(random_order_advance(&order)) * sizeof(uint32_t);

// Prevent the compiler from re-ordering the loop.
barrierw(byte_idx);

// Calculate pointers. The x and y pointers might not be valid, but in this
// case they will not be selected.
uintptr_t xp = x_addr + byte_idx;
uintptr_t yp = y_addr + byte_idx;
uintptr_t decoy1 = decoy_addr + (byte_idx % sizeof(decoys));
uintptr_t decoy2 =
decoy_addr +
((byte_idx + (sizeof(decoys) / 2) + sizeof(uint32_t)) % sizeof(decoys));

// Select in constant-time either the real pointers or decoys.
void *xv = (void *)launderw(
ct_cmovw(ct_sltuw(launderw(byte_idx), byte_len), xp, decoy1));
void *yv = (void *)launderw(
ct_cmovw(ct_sltuw(launderw(byte_idx), byte_len), yp, decoy2));

// Perform an XOR in either the decoy array or the real array.
write_32(read_32(xv) ^ read_32(yv), xv);
}
RANDOM_ORDER_HARDENED_CHECK_DONE(order);
HARDENED_CHECK_EQ(count, expected_count);
}
14 changes: 14 additions & 0 deletions sw/device/lib/base/hardened_memory.h
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,20 @@ void hardened_memshred(uint32_t *dest, size_t word_len);
hardened_bool_t hardened_memeq(const uint32_t *lhs, const uint32_t *rhs,
size_t word_len);

/**
* Combines two word buffers with XOR.
*
* Callers should ensure the entropy complex is up before calling this
* function. The implementation uses random-order hardening primitives for
* side-channel defense.
*
* @param[in,out] x Pointer to the first operand (modified in-place).
* @param y Pointer to the second operand.
* @param word_len Length in words of each operand.
*/
void hardened_xor(uint32_t *OT_RESTRICT x, const uint32_t *OT_RESTRICT y,
size_t word_len);

#ifdef __cplusplus
} // extern "C"
#endif // __cplusplus
Expand Down
34 changes: 34 additions & 0 deletions sw/device/lib/crypto/impl/aes_gcm.c
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,34 @@ static inline void gcm_context_restore(otcrypto_aes_gcm_context_t *api_ctx,
kAesGcmContextNumWords);
}

/**
* Remask the key if it is not sideloaded.
*
* Generate a fresh mask and apply it to the current key.
*
* @param[in,out] internal_ctx Internal context object.
* @return Result of the operation.
*/
status_t gcm_remask_key(aes_gcm_context_t *internal_ctx) {
if (launder32(internal_ctx->key.sideload) == kHardenedBoolFalse) {
HARDENED_CHECK_EQ(internal_ctx->key.sideload, kHardenedBoolFalse);

// Generate a fresh mask the size of one share.
uint32_t mask[internal_ctx->key.key_len];
hardened_memshred(mask, internal_ctx->key.key_len);

// XOR each share with the mask.
hardened_xor((uint32_t *)internal_ctx->key.key_shares[0], mask,
internal_ctx->key.key_len);
hardened_xor((uint32_t *)internal_ctx->key.key_shares[1], mask,
internal_ctx->key.key_len);
} else {
HARDENED_CHECK_EQ(internal_ctx->key.sideload, kHardenedBoolTrue);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shall we return error upon calling remask with a sideload key to notify user?

sideloaded keys are always remasked on every power cycle iiuc

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

gcm_remask_key() cannot be called by the user - it is only called by the CL internally in otcrypto_aes_gcm_update_encrypted_data, otcrypto_aes_gcm_decrypt_final, or otcrypto_aes_gcm_encrypt_final.

In this case, I think just returning OTCRYPTO_OK for this function in the sideloaded key case should be fine.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks sgtm

}

return OTCRYPTO_OK;
}

/**
* Construct the underlying AES key for AES-GCM.
*
Expand Down Expand Up @@ -427,6 +455,8 @@ otcrypto_status_t otcrypto_aes_gcm_update_encrypted_data(
aes_gcm_context_t internal_ctx;
gcm_context_restore(ctx, &internal_ctx);
HARDENED_TRY(load_key_if_sideloaded(internal_ctx.key));
// Remask the key if it is not sideloaded.
HARDENED_TRY(gcm_remask_key(&internal_ctx));

// The output buffer must be long enough to hold all full blocks that will
// exist after `input` is added.
Expand Down Expand Up @@ -474,6 +504,8 @@ otcrypto_status_t otcrypto_aes_gcm_encrypt_final(
aes_gcm_context_t internal_ctx;
gcm_context_restore(ctx, &internal_ctx);
HARDENED_TRY(load_key_if_sideloaded(internal_ctx.key));
// Remask the key if it is not sideloaded.
HARDENED_TRY(gcm_remask_key(&internal_ctx));

// If the partial block is nonempty, the output must be at least as long as
// the partial block.
Expand Down Expand Up @@ -517,6 +549,8 @@ otcrypto_status_t otcrypto_aes_gcm_decrypt_final(
aes_gcm_context_t internal_ctx;
gcm_context_restore(ctx, &internal_ctx);
HARDENED_TRY(load_key_if_sideloaded(internal_ctx.key));
// Remask the key if it is not sideloaded.
HARDENED_TRY(gcm_remask_key(&internal_ctx));

// If the partial block is nonempty, the output must be at least as long as
// the partial block.
Expand Down
59 changes: 0 additions & 59 deletions sw/device/lib/crypto/impl/keyblob.c
Original file line number Diff line number Diff line change
Expand Up @@ -210,65 +210,6 @@ status_t keyblob_from_key_and_mask(const uint32_t *key, const uint32_t *mask,
return keyblob_from_shares(share0, mask, config, keyblob);
}

/**
* Combines two word buffers with XOR.
*
* Callers should ensure the entropy complex is up before calling this
* function. The implementation uses random-order hardening primitives for
* side-channel defense.
*
* @param[in,out] x Pointer to the first operand (modified in-place).
* @param y Pointer to the second operand.
* @param word_len Length in words of each operand.
*/
void hardened_xor(uint32_t *restrict x, const uint32_t *restrict y,
size_t word_len) {
// Generate a random ordering.
random_order_t order;
random_order_init(&order, word_len);
size_t count = 0;
size_t expected_count = random_order_len(&order);

// Create some random values for decoy operations.
uint32_t decoys[8];
hardened_memshred(decoys, ARRAYSIZE(decoys));

// Cast pointers to `uintptr_t` to erase their provenance.
uintptr_t x_addr = (uintptr_t)x;
uintptr_t y_addr = (uintptr_t)y;
uintptr_t decoy_addr = (uintptr_t)&decoys;

// XOR the mask with the first share. This loop is modelled off the one in
// `hardened_memcpy`; see the comments there for more details.
size_t byte_len = word_len * sizeof(uint32_t);
for (; launderw(count) < expected_count; count = launderw(count) + 1) {
size_t byte_idx = launderw(random_order_advance(&order)) * sizeof(uint32_t);

// Prevent the compiler from re-ordering the loop.
barrierw(byte_idx);

// Calculate pointers. The x and y pointers might not be valid, but in this
// case they will not be selected.
uintptr_t xp = x_addr + byte_idx;
uintptr_t yp = y_addr + byte_idx;
uintptr_t decoy1 = decoy_addr + (byte_idx % sizeof(decoys));
uintptr_t decoy2 =
decoy_addr +
((byte_idx + (sizeof(decoys) / 2) + sizeof(uint32_t)) % sizeof(decoys));

// Select in constant-time either the real pointers or decoys.
void *xv = (void *)launderw(
ct_cmovw(ct_sltuw(launderw(byte_idx), byte_len), xp, decoy1));
void *yv = (void *)launderw(
ct_cmovw(ct_sltuw(launderw(byte_idx), byte_len), yp, decoy2));

// Perform an XOR in either the decoy array or the real array.
write_32(read_32(xv) ^ read_32(yv), xv);
}
RANDOM_ORDER_HARDENED_CHECK_DONE(order);
HARDENED_CHECK_EQ(count, expected_count);
}

status_t keyblob_remask(otcrypto_blinded_key_t *key) {
// Check that the key is masked with XOR.
HARDENED_TRY(keyblob_ensure_xor_masked(key->config));
Expand Down
Loading