matter_crypto.h
Matter message crypto: nonce, AES-CCM, session keys.
Overview
Matter message crypto: nonce, AES-CCM, session keys. Matter secures every message with AES-128-CCM: a 13-byte nonce built from fields the peer can see, a 16-byte tag, and the plaintext message header as additional authenticated data so the routing fields cannot be edited in flight. nonce security_flags:u8 message_counter:u32 node_id:u64 (little-endian) aad the message header exactly as it appears on the wire keys HKDF-SHA256(secret, salt, "SessionKeys") -> i2r | r2i | challenge
depends on matter_msg.h matter_status.h · used by matter_exchange.h matter_pase_sm.h matter_case.c matter_crypto.c
API
Cstruct matter_session_keys
Session keys, in the order the one HKDF output supplies them.
##define MATTER_KEY_LEN 16u
AES-128: one key size, one tag size, one nonce size. Matter fixes all three.
##define MATTER_NONCE_LEN 13u
##define MATTER_TAG_LEN 16u
##define MATTER_SESSION_KEYS_LEN 48u
i2r | r2i | attestation challenge, one HKDF output.
##define MATTER_AAD_MAX 0xFEFFu
Largest AAD this CCM accepts. CCM encodes an AAD length below 0xFF00 in two bytes and anything larger in a longer form. A Matter AAD is a message header, so it never exceeds MATTER_MSG_HEADER_MAX (24) and the long form would be dead code; the limit is enforced rather than assumed so an oversized AAD fails loudly instead of being encoded wrongly.
Fint crypto_aes_ecb_encrypt(const uint8_t *key, size_t key_bits, const uint8_t in[16], uint8_t out[16])
The AES block seam, shared with the CCC key ladder. Defined by exactly one backend per build: ccc_crypto_psa.c or ccc_crypto_mbedtls.c on target (the CONFIG_WOZ_CRYPTO_* choice in modules/woz_uwb/Kconfig), and tests/host/aes_ref.c -- a real FIPS-197 AES -- on the host. Declared here rather than pulled in from modules/woz_uwb/src/ccc/ccc_kdf.h so this module does not depend on the UWB module's private headers; tests/host/test_matter_crypto.c includes both, so a compiler sees the two declarations together and rejects any drift. Note for the first on-target Matter build: that Kconfig choice currently depends on WOZ_ALIRO, so a Matter-only image would find no definition and fail to link. Widening it belongs with that build, not here.
Fint matter_derive_session_keys(const uint8_t *secret, size_t secret_len, const uint8_t *salt, size_t salt_len, bool resume, struct matter_session_keys *out)
Derive the session key schedule from an established shared secret. @param secret PASE's Ke or CASE's ECDH-derived secret. @param salt may be NULL with salt_len 0, which is what PASE uses. @param resume true selects the "SessionResumptionKeys" info string. @return MATTER_OK or MATTER_E_INVAL.
Fint matter_build_nonce(uint8_t security_flags, uint32_t message_counter, uint64_t node_id, uint8_t out[MATTER_NONCE_LEN])
Build the 13-byte AEAD nonce. @param node_id the SENDER's node ID, which comes from the session rather than from the header being sent: the source node ID field is absent from most messages, so reading it off the wire would produce a nonce that only sometimes matched the peer's.
Fint matter_aead_encrypt(const uint8_t key[MATTER_KEY_LEN], const uint8_t nonce[MATTER_NONCE_LEN], const uint8_t *aad, size_t aad_len, const uint8_t *pt, size_t pt_len, uint8_t *ct_out, uint8_t tag_out[MATTER_TAG_LEN])
AES-128-CCM. ct_out may alias pt. @return MATTER_OK, or MATTER_E_INVAL for a bad argument or an oversized input, or MATTER_E_STATE if the AES backend failed.
Fint matter_aead_decrypt(const uint8_t key[MATTER_KEY_LEN], const uint8_t nonce[MATTER_NONCE_LEN], const uint8_t *aad, size_t aad_len, const uint8_t *ct, size_t ct_len, const uint8_t tag[MATTER_TAG_LEN], uint8_t *pt_out)
@return MATTER_OK, or MATTER_E_TYPE if the tag does not verify -- in which case pt_out has been zeroed, because a caller that ignores the return value must not be handed attacker-chosen plaintext.
Fint matter_crypto_seal(const struct matter_msg_header *h, const uint8_t key[MATTER_KEY_LEN], uint64_t sender_node_id, const uint8_t *payload, size_t payload_len, uint8_t *out, size_t cap, size_t *out_len)
Encode h and seal payload under it, producing header | ciphertext | tag. The header is written into out first and those exact bytes are then used as the AAD, so the authenticated header and the transmitted header cannot disagree. That is the reason this lives beside the cipher instead of in matter_msg.c. @param sender_node_id the local node ID, for the nonce. See matter_build_nonce(). @return MATTER_OK, MATTER_E_NOSPACE, or MATTER_E_INVAL.
Fint matter_crypto_open(const uint8_t *buf, size_t len, const uint8_t key[MATTER_KEY_LEN], uint64_t sender_node_id, struct matter_msg_header *h, uint8_t *pt_out, size_t pt_cap, size_t *pt_len)
Decode the header of buf and open the rest in place of it. @param sender_node_id the PEER's node ID, held by the session. @param pt_out receives the plaintext; needs len - header - tag bytes. @return MATTER_OK, MATTER_E_TRUNC if there is no room for a header and a tag, MATTER_E_TYPE if the tag does not verify, else as matter_msg_header_decode().