Skip to content

feat(tbor): implement AesGenerateKey + AesEncryptDecrypt (masked-key AES)#584

Open
vsonims wants to merge 1 commit into
tbor/unwrap-keyfrom
tbor/aes-crypto
Open

feat(tbor): implement AesGenerateKey + AesEncryptDecrypt (masked-key AES)#584
vsonims wants to merge 1 commit into
tbor/unwrap-keyfrom
tbor/aes-crypto

Conversation

@vsonims

@vsonims vsonims commented Jul 18, 2026

Copy link
Copy Markdown
Collaborator

Summary

Adds the TBOR AES crypto command pair, stacked on the RSA-AES
key-unwrap commands (#583), following the MBOR AES commands but with the
TBOR masked-key (stateless) model — no vault key_id / key_tag.

AesGenerateKey (opcode 0x15)

Generate a fresh random AES key (128 / 192 / 256 bits, non-bulk only,
mirroring MBOR) and return it masked (AEAD-GCM-256) under the
requested scope's masking key. The masked-blob length is fixed by the key
size, so the handler reserves the response slot up front and masks the
generated key straight into it (encoder *_reserve + decode_mut) — no
scratch buffer, no copy.

AesEncryptDecrypt (opcode 0x16)

AES-CBC encrypt or decrypt a host-supplied message with a caller-held
masked AES key. Fully zero-copy:

  • request masked_key is #[tbor(mutable)] → the handler decode_muts
    the request and unmasks the blob in place, using the recovered
    key directly (no blob / key scratch copy);
  • the response msg / iv slots are reserved, and the non-in-place
    aes_cbc_enc_dec reads the request message and writes the ciphertext /
    chaining IV straight into the response (no message copy).

Rejects non-AES key kinds (InvalidKeyType) and direction-permission
mismatches (InvalidPermissions); the recovered key is wiped on every
path (including the unmask-failure path).

AES key import

Already provided by UnwrapKey (0x14, key class Aes) in #583 — an
emu test exercises the UnwrapKeyAesEncryptDecrypt round-trip.

Wiring

Both opcodes are routed through the fw dispatcher, the is_known_opcode
/ is_in_session / needs_session_id_cross_check classifiers, and
op::SessionCtrl::from_tbor_opcode (InSession, CO/CU). Reuses the shared
validate_active_session / resolve_masking_key helpers and the encoder
reserve/fill support from #583.

Tests / validation

  • Emu: keygen round-trip across all sizes/scopes, before-finalize +
    SD-scope + unknown-size rejects; encrypt→decrypt round-trip all sizes,
    CBC chaining IV, tamper + bad-length rejects, and the UnwrapKey→AES
    cross-command round-trip.
  • Full TBOR emu suite green (127 tests), clippy clean, nightly
    fmt + copyright clean, Uno (thumbv7em) build passes.
  • Docs: docs/tbor-ddi/commands/{aes_generate_key,aes_encrypt_decrypt}.md
    • README rows (0x15, 0x16).

@vsonims
vsonims force-pushed the tbor/unwrap-key branch 2 times, most recently from b3e53f6 to 427588b Compare July 19, 2026 03:32
…AES)

Add the TBOR AES crypto command pair, stacked on the RSA-AES key-unwrap
commands, following the MBOR AES commands but with the TBOR masked-key
(stateless) model.

AesGenerateKey (opcode 0x15): generate a fresh random AES key (128 / 192
/ 256 bits) and return it masked (AEAD-GCM-256) under the requested
scope's masking key. Non-bulk sizes only (mirroring MBOR); no vault
`key_id` / `key_tag`. The masked-blob length is fixed by the key size, so
the handler reserves the response slot up front and masks the generated
key straight into it (encoder `*_reserve` + `decode_mut`) — no scratch
buffer and no copy.

AesEncryptDecrypt (opcode 0x16): AES-CBC encrypt or decrypt a
host-supplied message with a caller-held masked AES key. Fully
zero-copy: the request `masked_key` is `#[tbor(mutable)]`, so the handler
`decode_mut`s the request and `unmask`s the blob in place, then the
non-in-place `aes_cbc_enc_dec` reads the request message and writes the
ciphertext / chaining IV straight into the reserved response slots — no
blob / key scratch copy and no message copy. Rejects non-AES key kinds
(`InvalidKeyType`) and direction-permission mismatches
(`InvalidPermissions`); the recovered key is wiped on every path.

AES key import is already provided by `UnwrapKey` (0x14, key class Aes);
an emu test exercises the unwrap -> encrypt/decrypt round-trip.

Reuses the shared session/masking helpers (`validate_active_session`,
`resolve_masking_key`) and the encoder reserve/fill support from the
key-unwrap PR below it in the stack. Wires both opcodes through the fw
dispatcher, the `is_known_opcode` / `is_in_session` /
`needs_session_id_cross_check` classifiers, and
`op::SessionCtrl::from_tbor_opcode`. Adds fw + host wire schemas, command
docs, and emu tests: keygen round-trip across all sizes/scopes,
encrypt->decrypt round-trip, CBC chaining IV, tamper / bad-length
rejects, and the UnwrapKey->AES cross-command round-trip.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 0b09e50a-a9be-4bae-b347-d42dc775a258
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant