diff --git a/README.md b/README.md index 31629d878ea..721fed7d72b 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ [DSP0274](https://www.dmtf.org/dsp/DSP0274) Security Protocol and Data Model (SPDM) Specification (version [1.0.2](https://www.dmtf.org/sites/default/files/standards/documents/DSP0274_1.0.2.pdf), version [1.1.4](https://www.dmtf.org/sites/default/files/standards/documents/DSP0274_1.1.4.pdf), version [1.2.3](https://www.dmtf.org/sites/default/files/standards/documents/DSP0274_1.2.3.pdf), version [1.3.2](https://www.dmtf.org/sites/default/files/standards/documents/DSP0274_1.3.2.pdf), and version [1.4.0](https://www.dmtf.org/sites/default/files/standards/documents/DSP0274_1.4.0.pdf)) - [DSP0277](https://www.dmtf.org/dsp/DSP0277) Secured Messages using SPDM Specification (version [1.0.1](https://www.dmtf.org/sites/default/files/standards/documents/DSP0277_1.0.1.pdf), version [1.1.1](https://www.dmtf.org/sites/default/files/standards/documents/DSP0277_1.1.1.pdf), version [1.2.0](https://www.dmtf.org/sites/default/files/standards/documents/DSP0277_1.2.0.pdf)) + [DSP0277](https://www.dmtf.org/dsp/DSP0277) Secured Messages using SPDM Specification (version [1.0.1](https://www.dmtf.org/sites/default/files/standards/documents/DSP0277_1.0.1.pdf), version [1.1.1](https://www.dmtf.org/sites/default/files/standards/documents/DSP0277_1.1.1.pdf), version [1.2.0](https://www.dmtf.org/sites/default/files/standards/documents/DSP0277_1.2.0.pdf), and version [1.3.0](https://www.dmtf.org/sites/default/files/standards/documents/DSP0277_1.3.0.pdf)) MCTP and secured MCTP follow : diff --git a/doc/aead_limit.md b/doc/aead_limit.md index 6b32653b7d3..0b1fc761326 100644 --- a/doc/aead_limit.md +++ b/doc/aead_limit.md @@ -24,3 +24,73 @@ accordingly. If `KEY_UPDATE` is not sent before the maximum sequence number is reached, the SPDM session will be terminated. + +## Negotiated AEAD limit (DSP0277 1.3) + +[DSP0277](https://www.dmtf.org/dsp/DSP0277) version 1.3 defines a Secured Message opaque data +element `AEADlimitOE` (`SMDataID` = 2) that carries a 1-byte `AeadLimitExponent`. The AEAD limit is +`2 ^ AeadLimitExponent` messages, and `AeadLimitExponent` shall not exceed 64. When the element is +absent, the default exponent is 64 (i.e. the full 64-bit sequence number space). + +This element is exchanged in the opaque data of the Session-Secrets-Exchange request and response +(`KEY_EXCHANGE` / `KEY_EXCHANGE_RSP` and `PSK_EXCHANGE` / `PSK_EXCHANGE_RSP`), and both the Requester +and the Responder populate it. + +Opaque data elements are not ordered, so `AEADlimitOE` is parsed regardless of where it appears +relative to the version-selection / supported-version elements. However, the element is only defined +for secured message version 1.3 and later, so it is tied to the negotiated secured message version: + +* The Requester advertises `AEADlimitOE` whenever its local secured message version list includes + 1.3 (it does not yet know which version the Responder will select). +* The Responder advertises `AEADlimitOE` only when it selects secured message version 1.3 or later. +* When the negotiated secured message version is older than 1.3, a received `AEADlimitOE` is ignored + (treated as absent, default exponent 64) even if the peer included it. + +### Single source of truth + +There is exactly one Integrator-settable knob for the AEAD limit: +`LIBSPDM_DATA_MAX_SPDM_SESSION_SEQUENCE_NUMBER` (set with `LIBSPDM_DATA_LOCATION_LOCAL`). There is no +separate "exponent" setting; the value advertised on the wire is *derived* from this cap, so the two +can never disagree. + +Because the session sequence number is checked with +`sequence_number >= max_spdm_session_sequence_number`, a cap of `2 ^ e` messages is stored as +`(2 ^ e) - 1`. The `AeadLimitExponent` this endpoint advertises is therefore the inverse: + +``` +AeadLimitExponent = floor(log2(max_spdm_session_sequence_number + 1)) +``` + +* The default cap `0xFFFFFFFFFFFFFFFF` maps to exponent 64 (the spec default), and avoids computing + `2 ^ 64` (which does not fit in a 64-bit value). +* A power-of-two-minus-one cap (e.g. `0xFFFFFFFF` -> 32, `0xFFFFFF` -> 24) maps to its exact + exponent. +* A cap that is not of the form `2 ^ e - 1` rounds the advertised exponent *down* to the nearest + representable AEAD limit, i.e. the advertised limit is always `<=` the configured cap (the safe + direction). + +### Establishment and enforcement + +Once the session is established, each endpoint sets the session's effective maximum sequence number +to the minimum of: + +* its own configured cap (`LIBSPDM_DATA_MAX_SPDM_SESSION_SEQUENCE_NUMBER`), and +* the peer's advertised AEAD limit of `2 ^ peer_AeadLimitExponent` messages (stored as + `(2 ^ peer_AeadLimitExponent) - 1`). + +So neither side's limit is ever raised by the other. When the session sequence number reaches this +effective maximum, the session is terminated unless a `KEY_UPDATE` (which resets the sequence number) +is performed first. + +### Reading the negotiated value + +The Integrator can read the per-session effective maximum sequence number by calling +`libspdm_get_data` with `LIBSPDM_DATA_MAX_SPDM_SESSION_SEQUENCE_NUMBER` and +`LIBSPDM_DATA_LOCATION_SESSION` (with the session ID in `additional_data`). The same data ID with +`LIBSPDM_DATA_LOCATION_LOCAL` returns the global Integrator-configured cap. The advertised exponent, +if needed, is `floor(log2(value + 1))`. + +The per-session maximum sequence number is read-only. It is derived once, at session establishment, +from `min(configured cap, peer AEAD limit)` and is owned by the negotiation result. `libspdm_set_data` +with `LIBSPDM_DATA_MAX_SPDM_SESSION_SEQUENCE_NUMBER` and `LIBSPDM_DATA_LOCATION_SESSION` is rejected; +only the context-level (`LIBSPDM_DATA_LOCATION_LOCAL`) cap is settable. diff --git a/include/industry_standard/spdm_secured_message.h b/include/industry_standard/spdm_secured_message.h index e0e054df90c..dec5a4d328c 100644 --- a/include/industry_standard/spdm_secured_message.h +++ b/include/industry_standard/spdm_secured_message.h @@ -1,6 +1,6 @@ /** * Copyright Notice: - * Copyright 2021-2024 DMTF. All rights reserved. + * Copyright 2021-2026 DMTF. All rights reserved. * License: BSD 3-Clause License. For full text see link: https://github.com/DMTF/libspdm/blob/main/LICENSE.md **/ @@ -51,12 +51,13 @@ * AssociatedData AeadTag */ -/* 3 means SPDM secured message version 1.0, 1.1, 1.2 */ -#define SECURED_SPDM_MAX_VERSION_COUNT 3 +/* 4 means SPDM secured message version 1.0, 1.1, 1.2, 1.3 */ +#define SECURED_SPDM_MAX_VERSION_COUNT 4 /* the DSP0277 specification version */ #define SECURED_SPDM_VERSION_10 0x10 #define SECURED_SPDM_VERSION_11 0x11 #define SECURED_SPDM_VERSION_12 0x12 +#define SECURED_SPDM_VERSION_13 0x13 typedef struct { uint32_t session_id; @@ -128,6 +129,20 @@ typedef struct { /*spdm_version_number_t versions_list[version_count];*/ } secured_message_opaque_element_supported_version_t; +/* DSP0277 1.3 AEAD limit data (Table 10). Added in secured message version 1.3. */ +#define SECURED_MESSAGE_OPAQUE_ELEMENT_SMDATA_ID_AEAD_LIMIT 0x2 + +/* AeadLimitExponent shall be <= 64; AeadLimit = 2 ^ AeadLimitExponent. + * When the AEAD limit data is absent, the exponent is interpreted as 64. */ +#define SECURED_MESSAGE_AEAD_LIMIT_EXPONENT_MAX 64 +#define SECURED_MESSAGE_AEAD_LIMIT_EXPONENT_DEFAULT 64 + +typedef struct { + uint8_t sm_data_version; /* SECURED_MESSAGE_OPAQUE_ELEMENT_SMDATA_DATA_VERSION*/ + uint8_t sm_data_id; /* SECURED_MESSAGE_OPAQUE_ELEMENT_SMDATA_ID_AEAD_LIMIT*/ + uint8_t aead_limit_exponent; /* <= 64; AeadLimit = 2 ^ aead_limit_exponent*/ +} secured_message_opaque_element_aead_limit_t; + #pragma pack() #endif /* SPDM_SECURED_MESSAGE_H */ diff --git a/include/internal/libspdm_common_lib.h b/include/internal/libspdm_common_lib.h index cb116703ba1..42f6d60dd5d 100644 --- a/include/internal/libspdm_common_lib.h +++ b/include/internal/libspdm_common_lib.h @@ -1255,6 +1255,94 @@ size_t libspdm_get_opaque_data_supported_version_data_size(libspdm_context_t *sp **/ size_t libspdm_get_opaque_data_version_selection_data_size(const libspdm_context_t *spdm_context); +/** + * Return the highest secured message version this endpoint offers in its local supported list, in + * version-number form (e.g. SECURED_SPDM_VERSION_13 << SPDM_VERSION_NUMBER_SHIFT_BIT), or 0 if the + * list is empty. + * + * @param spdm_context A pointer to the SPDM context. + * + * @return the highest locally-offered secured message version (version-number form). + **/ +spdm_version_number_t libspdm_local_max_secured_message_version( + const libspdm_context_t *spdm_context); + +/** + * Return the size in bytes of the DSP0277 1.3 AEAD limit opaque element when appended on its own, + * i.e. the size of a single secured-message opaque element carrying the AEADlimitOE data, with + * padding. This is the incremental size that the AEADlimitOE element adds to an opaque data blob + * that already has a general opaque data table header and at least one other element. + * + * The AEADlimitOE element is only defined for secured message version 1.3 and later; for an older + * secured_message_version this returns 0. + * + * @param spdm_context A pointer to the SPDM context. + * @param secured_message_version The secured message version this opaque data targets (the + * negotiated version for a response, or the highest locally-offered + * version for a request). + * + * @return the size in bytes of the AEAD limit opaque element (0 if the version is older than 1.3). + **/ +size_t libspdm_get_opaque_data_aead_limit_element_size(const libspdm_context_t *spdm_context, + spdm_version_number_t + secured_message_version); + +/** + * Append the DSP0277 1.3 AEAD limit opaque element (AEADlimitOE) to an opaque data blob that was + * built by libspdm_build_opaque_data_supported_version_data (requester) or + * libspdm_build_opaque_data_version_selection_data (responder). The general opaque data table + * header's total_elements is incremented and the new element is written after the existing + * element(s). + * + * The AEADlimitOE element is only defined for secured message version 1.3 and later; for an older + * secured_message_version this is a no-op (nothing is appended and *data_out_size is unchanged). + * + * @param spdm_context A pointer to the SPDM context. + * @param secured_message_version The secured message version this opaque data targets. + * @param data_out_size On input, the capacity of data_out. On output, the new total size. + * @param data_out The opaque data blob to extend in place. + **/ +void libspdm_append_opaque_data_aead_limit_element(libspdm_context_t *spdm_context, + spdm_version_number_t secured_message_version, + size_t *data_out_size, void *data_out); + +/** + * Parse the DSP0277 1.3 AEAD limit opaque element (AEADlimitOE) from received opaque data. + * + * The AEADlimitOE element is only defined for secured message version 1.3 and later. When the + * negotiated secured message version is older, the element is ignored (treated as absent) even if + * a peer included it, and the default exponent (64) is returned. + * + * @param spdm_context A pointer to the SPDM context. + * @param secured_message_version The negotiated secured message version (version-number form). + * @param data_in_size size in bytes of data_in. + * @param data_in The received opaque data. + * @param aead_limit_exponent On output, the peer's AeadLimitExponent. If the element is absent + * or ignored, this is set to the default (64). + * + * @retval LIBSPDM_STATUS_SUCCESS parsed successfully (or absent/ignored -> default). + * @retval LIBSPDM_STATUS_INVALID_MSG_FIELD the element is malformed or the exponent is > 64. + **/ +libspdm_return_t libspdm_process_opaque_data_aead_limit(libspdm_context_t *spdm_context, + spdm_version_number_t + secured_message_version, + size_t data_in_size, const void *data_in, + uint8_t *aead_limit_exponent); + +/** + * Apply the negotiated AEAD limit to a session's secured-message context, setting the maximum + * session sequence number to min(integrator-configured cap, 2 ^ effective_exponent), where + * effective_exponent = min(local exponent, peer exponent). Only takes effect when the session's + * secured message version is >= 1.3. + * + * @param spdm_context A pointer to the SPDM context. + * @param session_info The session to configure. + * @param peer_aead_limit_exponent The peer's AeadLimitExponent (64 if not advertised). + **/ +void libspdm_apply_aead_limit_to_session(libspdm_context_t *spdm_context, + void *session_info, + uint8_t peer_aead_limit_exponent); + /** * Return the SPDMversion field of the version number struct. * diff --git a/library/spdm_common_lib/libspdm_com_context_data.c b/library/spdm_common_lib/libspdm_com_context_data.c index d6dd98d85cc..4f2e032c5c4 100644 --- a/library/spdm_common_lib/libspdm_com_context_data.c +++ b/library/spdm_common_lib/libspdm_com_context_data.c @@ -766,6 +766,13 @@ libspdm_return_t libspdm_set_data(void *spdm_context, libspdm_data_type_t data_t if (data_size != sizeof(uint64_t)) { return LIBSPDM_STATUS_INVALID_PARAMETER; } + /* This is a context-level integrator cap only. A per-session value is not settable: a + * session's effective cap is derived once at establishment from min(this cap, negotiated + * DSP0277 1.3 AEAD limit) and must not be overridden afterwards. The per-session value is + * read-only via get_data(LIBSPDM_DATA_LOCATION_SESSION). */ + if (parameter->location == LIBSPDM_DATA_LOCATION_SESSION) { + return LIBSPDM_STATUS_INVALID_PARAMETER; + } context->max_spdm_session_sequence_number = *(const uint64_t *)data; if (context->max_spdm_session_sequence_number == 0) { context->max_spdm_session_sequence_number = LIBSPDM_MAX_SPDM_SESSION_SEQUENCE_NUMBER; @@ -1122,7 +1129,21 @@ libspdm_return_t libspdm_get_data(void *spdm_context, libspdm_data_type_t data_t break; case LIBSPDM_DATA_MAX_SPDM_SESSION_SEQUENCE_NUMBER: target_data_size = sizeof(uint64_t); - target_data = &context->max_spdm_session_sequence_number; + /* When a session is addressed (LIBSPDM_DATA_LOCATION_SESSION), return that session's + * effective maximum sequence number, i.e. the value negotiated via the DSP0277 1.3 AEAD + * limit (min of the integrator-configured cap and the negotiated AEAD limit). Otherwise + * return the context-level integrator-configured value. */ + if (parameter->location == LIBSPDM_DATA_LOCATION_SESSION) { + session_id = libspdm_read_uint32(parameter->additional_data); + session_info = libspdm_get_session_info_via_session_id(context, session_id); + if (session_info == NULL) { + return LIBSPDM_STATUS_INVALID_PARAMETER; + } + secured_context = session_info->secured_message_context; + target_data = &secured_context->max_spdm_session_sequence_number; + } else { + target_data = &context->max_spdm_session_sequence_number; + } break; case LIBSPDM_DATA_VCA_CACHE: target_data_size = context->transcript.message_a.buffer_size; @@ -2871,10 +2892,14 @@ libspdm_return_t libspdm_init_context_with_secured_context(void *spdm_context, SECURED_SPDM_VERSION_11 << SPDM_VERSION_NUMBER_SHIFT_BIT; context->local_context.secured_message_version.secured_message_version[2] = SECURED_SPDM_VERSION_12 << SPDM_VERSION_NUMBER_SHIFT_BIT; + context->local_context.secured_message_version.secured_message_version[3] = + SECURED_SPDM_VERSION_13 << SPDM_VERSION_NUMBER_SHIFT_BIT; context->local_context.capability.st1 = SPDM_ST1_VALUE_US; context->mut_auth_cert_chain_buffer_size = 0; + /* DSP0277 1.3 AEAD limit: max_spdm_session_sequence_number is the single source of truth for + * the AEAD limit. The default 0xFFFFFFFFFFFFFFFF encodes the spec-default exponent of 64. */ context->max_spdm_session_sequence_number = LIBSPDM_MAX_SPDM_SESSION_SEQUENCE_NUMBER; context->latest_session_id = INVALID_SESSION_ID; diff --git a/library/spdm_common_lib/libspdm_com_opaque_data.c b/library/spdm_common_lib/libspdm_com_opaque_data.c index 8e56485ba7e..7a555d2e3ba 100644 --- a/library/spdm_common_lib/libspdm_com_opaque_data.c +++ b/library/spdm_common_lib/libspdm_com_opaque_data.c @@ -5,6 +5,7 @@ **/ #include "internal/libspdm_common_lib.h" +#include "internal/libspdm_secured_message_lib.h" size_t libspdm_get_opaque_data_version_selection_data_size(const libspdm_context_t *spdm_context) { @@ -420,3 +421,219 @@ libspdm_return_t libspdm_process_opaque_data_version_selection_data( return LIBSPDM_STATUS_UNSUPPORTED_CAP; } + +/* Return the highest secured message version this endpoint offers in its local supported list, in + * version-number form (e.g. SECURED_SPDM_VERSION_13 << SPDM_VERSION_NUMBER_SHIFT_BIT), or 0 if the + * list is empty. The Requester uses this as the "version" to gate the AEADlimitOE element in its + * request, since no version has been negotiated yet at that point. */ +spdm_version_number_t libspdm_local_max_secured_message_version( + const libspdm_context_t *spdm_context) +{ + uint8_t index; + spdm_version_number_t max_version; + + max_version = 0; + for (index = 0; + index < spdm_context->local_context.secured_message_version.secured_message_version_count; + index++) { + spdm_version_number_t version = + spdm_context->local_context.secured_message_version.secured_message_version[index]; + if (libspdm_get_version_from_version_number(version) > + libspdm_get_version_from_version_number(max_version)) { + max_version = version; + } + } + return max_version; +} + +/* DSP0277 1.3: derive the AeadLimitExponent that this endpoint advertises from its single source of + * truth, max_spdm_session_sequence_number. The session cap is stored as (2 ^ exponent) - 1 (see + * libspdm_apply_aead_limit_to_session), so the advertised exponent is the inverse: the largest e + * such that (2 ^ e) - 1 <= max, i.e. floor(log2(max + 1)). The all-ones cap maps to 64 (the spec + * default) and avoids the max + 1 overflow. A non-power-of-two cap (e.g. 0xFFFFFF) rounds down to + * the nearest representable AEAD limit, which is <= the configured cap (the safe direction). */ +static uint8_t libspdm_aead_limit_exponent_from_max_sequence_number(uint64_t max_sequence_number) +{ + uint8_t exponent; + uint64_t value; + + if (max_sequence_number >= LIBSPDM_MAX_SPDM_SESSION_SEQUENCE_NUMBER) { + return SECURED_MESSAGE_AEAD_LIMIT_EXPONENT_MAX; + } + + value = max_sequence_number + 1; + exponent = 0; + while (value > 1) { + value >>= 1; + exponent++; + } + return exponent; +} + +size_t libspdm_get_opaque_data_aead_limit_element_size(const libspdm_context_t *spdm_context, + spdm_version_number_t secured_message_version) +{ + size_t size; + + /* The AEADlimitOE element is only defined for secured message version 1.3 and later. It must + * not be appended to opaque data for an older version. */ + if (libspdm_get_version_from_version_number(secured_message_version) < SECURED_SPDM_VERSION_13) { + return 0; + } + + size = sizeof(secured_message_opaque_element_table_header_t) + + sizeof(secured_message_opaque_element_aead_limit_t); + /* Add Padding*/ + return (size + 3) & ~3; +} + +void libspdm_append_opaque_data_aead_limit_element(libspdm_context_t *spdm_context, + spdm_version_number_t secured_message_version, + size_t *data_out_size, void *data_out) +{ + size_t existing_size; + size_t element_size; + secured_message_opaque_element_table_header_t *opaque_element_table_header; + secured_message_opaque_element_aead_limit_t *opaque_element_aead_limit; + uint8_t *element_ptr; + + /* The size helper enforces the secured message version 1.3 gate; for an older version it + * returns 0 and nothing is appended. */ + element_size = libspdm_get_opaque_data_aead_limit_element_size(spdm_context, + secured_message_version); + if (element_size == 0) { + return; + } + + /* The existing blob (built by the supported-version / version-selection builder) ends at + * *data_out_size. Append the AEAD limit element after it and bump total_elements. */ + existing_size = *data_out_size; + LIBSPDM_ASSERT(existing_size + element_size >= existing_size); + + if (libspdm_get_connection_version(spdm_context) >= SPDM_MESSAGE_VERSION_12) { + spdm_general_opaque_data_table_header_t *spdm_general_opaque_data_table_header; + spdm_general_opaque_data_table_header = data_out; + spdm_general_opaque_data_table_header->total_elements++; + } else { + secured_message_general_opaque_data_table_header_t *general_opaque_data_table_header; + general_opaque_data_table_header = data_out; + general_opaque_data_table_header->total_elements++; + } + + element_ptr = (uint8_t *)data_out + existing_size; + opaque_element_table_header = (void *)element_ptr; + opaque_element_table_header->id = SPDM_REGISTRY_ID_DMTF; + opaque_element_table_header->vendor_len = 0; + opaque_element_table_header->opaque_element_data_len = + sizeof(secured_message_opaque_element_aead_limit_t); + + opaque_element_aead_limit = (void *)(opaque_element_table_header + 1); + opaque_element_aead_limit->sm_data_version = + SECURED_MESSAGE_OPAQUE_ELEMENT_SMDATA_DATA_VERSION; + opaque_element_aead_limit->sm_data_id = + SECURED_MESSAGE_OPAQUE_ELEMENT_SMDATA_ID_AEAD_LIMIT; + opaque_element_aead_limit->aead_limit_exponent = + libspdm_aead_limit_exponent_from_max_sequence_number( + spdm_context->max_spdm_session_sequence_number); + + /* Zero the padding bytes between the element and the new end. */ + libspdm_zero_mem(element_ptr + sizeof(secured_message_opaque_element_table_header_t) + + sizeof(secured_message_opaque_element_aead_limit_t), + element_size - (sizeof(secured_message_opaque_element_table_header_t) + + sizeof(secured_message_opaque_element_aead_limit_t))); + + *data_out_size = existing_size + element_size; +} + +libspdm_return_t libspdm_process_opaque_data_aead_limit(libspdm_context_t *spdm_context, + spdm_version_number_t + secured_message_version, + size_t data_in_size, const void *data_in, + uint8_t *aead_limit_exponent) +{ + bool result; + const void *get_element_ptr; + size_t get_element_len; + const secured_message_opaque_element_table_header_t *opaque_element_table_header; + const secured_message_opaque_element_aead_limit_t *opaque_element_aead_limit; + + /* Default per DSP0277 1.3: when the AEAD limit element is absent, the exponent is 64. */ + *aead_limit_exponent = SECURED_MESSAGE_AEAD_LIMIT_EXPONENT_DEFAULT; + + /* The AEADlimitOE element is only defined for secured message version 1.3 and later. For an + * older negotiated version, ignore the element entirely (keep the default exponent) even if a + * peer erroneously included it. */ + if (libspdm_get_version_from_version_number(secured_message_version) < + SECURED_SPDM_VERSION_13) { + return LIBSPDM_STATUS_SUCCESS; + } + + get_element_ptr = NULL; + result = libspdm_get_sm_data_element_from_opaque_data( + spdm_context, data_in_size, data_in, + SECURED_MESSAGE_OPAQUE_ELEMENT_SMDATA_ID_AEAD_LIMIT, + &get_element_ptr, &get_element_len); + if ((!result) || (get_element_ptr == NULL)) { + /* Absent is allowed: keep the default exponent. */ + return LIBSPDM_STATUS_SUCCESS; + } + + opaque_element_table_header = (const secured_message_opaque_element_table_header_t *) + get_element_ptr; + if ((opaque_element_table_header->vendor_len != 0) || + (opaque_element_table_header->opaque_element_data_len != + sizeof(secured_message_opaque_element_aead_limit_t))) { + return LIBSPDM_STATUS_INVALID_MSG_FIELD; + } + + opaque_element_aead_limit = (const void *)(opaque_element_table_header + 1); + if ((const uint8_t *)opaque_element_aead_limit + + sizeof(secured_message_opaque_element_aead_limit_t) > + (const uint8_t *)opaque_element_table_header + get_element_len) { + return LIBSPDM_STATUS_INVALID_MSG_FIELD; + } + + /* AeadLimitExponent shall be <= 64. */ + if (opaque_element_aead_limit->aead_limit_exponent > + SECURED_MESSAGE_AEAD_LIMIT_EXPONENT_MAX) { + return LIBSPDM_STATUS_INVALID_MSG_FIELD; + } + + *aead_limit_exponent = opaque_element_aead_limit->aead_limit_exponent; + return LIBSPDM_STATUS_SUCCESS; +} + +void libspdm_apply_aead_limit_to_session(libspdm_context_t *spdm_context, + void *session_info_p, + uint8_t peer_aead_limit_exponent) +{ + libspdm_session_info_t *session_info; + uint64_t peer_aead_limit; + uint64_t effective_max; + + session_info = session_info_p; + + /* The caller invokes this only when the session negotiated secured message version >= 1.3. + * + * The single source of truth for this endpoint's limit is max_spdm_session_sequence_number, + * which is the threshold the per-direction sequence number is checked against (seq >= max + * triggers LIBSPDM_STATUS_SEQUENCE_NUMBER_OVERFLOW). It is also what this endpoint advertised, + * encoded as an exponent. We only need to fold in the peer's advertised AeadLimitExponent: + * peer AeadLimit = 2 ^ peer_aead_limit_exponent, stored as (2 ^ exp) - 1. At exponent 64 this is + * 0xFFFFFFFFFFFFFFFF (the default), which also avoids computing 2^64. */ + if (peer_aead_limit_exponent >= 64) { + peer_aead_limit = LIBSPDM_MAX_SPDM_SESSION_SEQUENCE_NUMBER; + } else { + peer_aead_limit = ((uint64_t)1 << peer_aead_limit_exponent) - 1; + } + + /* The effective cap is the smaller of this endpoint's configured cap and the peer's AEAD limit, + * so neither side's limit is ever raised by the other. */ + effective_max = spdm_context->max_spdm_session_sequence_number; + if (peer_aead_limit < effective_max) { + effective_max = peer_aead_limit; + } + + libspdm_secured_message_set_max_spdm_session_sequence_number( + session_info->secured_message_context, effective_max); +} diff --git a/library/spdm_requester_lib/libspdm_req_key_exchange.c b/library/spdm_requester_lib/libspdm_req_key_exchange.c index dbaaa5121aa..3f1a9ed8b22 100644 --- a/library/spdm_requester_lib/libspdm_req_key_exchange.c +++ b/library/spdm_requester_lib/libspdm_req_key_exchange.c @@ -335,6 +335,7 @@ static libspdm_return_t libspdm_try_send_receive_key_exchange( size_t transport_header_size; uint8_t mut_auth_requested; spdm_version_number_t secured_message_version; + uint8_t peer_aead_limit_exponent = SECURED_MESSAGE_AEAD_LIMIT_EXPONENT_DEFAULT; /* -=[Check Parameters Phase]=- */ LIBSPDM_ASSERT((slot_id < SPDM_MAX_SLOT_COUNT) || (slot_id == 0xff)); @@ -507,8 +508,18 @@ static libspdm_return_t libspdm_try_send_receive_key_exchange( requester_opaque_data, requester_opaque_data_size); opaque_key_exchange_req_size = requester_opaque_data_size; } else { - opaque_key_exchange_req_size = + size_t supported_version_size; + + spdm_version_number_t local_max_secured_version = + libspdm_local_max_secured_message_version(spdm_context); + + supported_version_size = libspdm_get_opaque_data_supported_version_data_size(spdm_context); + /* DSP0277 1.3: also advertise this endpoint's AEAD limit in the request opaque data when it + * offers secured message version 1.3 (no version is negotiated yet at request time). */ + opaque_key_exchange_req_size = supported_version_size + + libspdm_get_opaque_data_aead_limit_element_size( + spdm_context, local_max_secured_version); LIBSPDM_ASSERT (spdm_request_size >= sizeof(spdm_key_exchange_request_t) + req_key_exchange_size + sizeof(uint16_t) + opaque_key_exchange_req_size); @@ -516,8 +527,11 @@ static libspdm_return_t libspdm_try_send_receive_key_exchange( libspdm_write_uint16(ptr, (uint16_t)opaque_key_exchange_req_size); ptr += sizeof(uint16_t); + opaque_key_exchange_req_size = supported_version_size; libspdm_build_opaque_data_supported_version_data( spdm_context, &opaque_key_exchange_req_size, ptr); + libspdm_append_opaque_data_aead_limit_element( + spdm_context, local_max_secured_version, &opaque_key_exchange_req_size, ptr); } ptr += opaque_key_exchange_req_size; @@ -722,6 +736,14 @@ static libspdm_return_t libspdm_try_send_receive_key_exchange( status = LIBSPDM_STATUS_INVALID_MSG_FIELD; goto receive_done; } + /* DSP0277 1.3: read the Responder's AEAD limit (absent -> default 64). */ + status = libspdm_process_opaque_data_aead_limit( + spdm_context, secured_message_version, opaque_length, ptr, + &peer_aead_limit_exponent); + if (LIBSPDM_STATUS_IS_ERROR(status)) { + status = LIBSPDM_STATUS_INVALID_MSG_FIELD; + goto receive_done; + } } if ((responder_opaque_data != NULL) && (responder_opaque_data_size != NULL)) { @@ -750,6 +772,14 @@ static libspdm_return_t libspdm_try_send_receive_key_exchange( session_info->peer_used_cert_chain_slot_id = slot_id; session_info->local_used_cert_chain_slot_id = *req_slot_id_param; + /* DSP0277 1.3: program the session's AEAD limit (min of local and peer) when secured message + * version 1.3 was negotiated. */ + if (libspdm_get_version_from_version_number(secured_message_version) >= + SECURED_SPDM_VERSION_13) { + libspdm_apply_aead_limit_to_session(spdm_context, session_info, + peer_aead_limit_exponent); + } + /* -=[Process Response Phase]=- */ status = libspdm_append_message_k(spdm_context, session_info, true, spdm_request, spdm_request_size); diff --git a/library/spdm_requester_lib/libspdm_req_psk_exchange.c b/library/spdm_requester_lib/libspdm_req_psk_exchange.c index 97764dcc252..23bc2446548 100644 --- a/library/spdm_requester_lib/libspdm_req_psk_exchange.c +++ b/library/spdm_requester_lib/libspdm_req_psk_exchange.c @@ -1,6 +1,6 @@ /** * Copyright Notice: - * Copyright 2021-2025 DMTF. All rights reserved. + * Copyright 2021-2026 DMTF. All rights reserved. * License: BSD 3-Clause License. For full text see link: https://github.com/DMTF/libspdm/blob/main/LICENSE.md **/ @@ -158,6 +158,8 @@ static libspdm_return_t libspdm_try_send_receive_psk_exchange( size_t message_size; size_t transport_header_size; spdm_version_number_t secured_message_version; + spdm_version_number_t local_max_secured_version; + uint8_t peer_aead_limit_exponent = SECURED_MESSAGE_AEAD_LIMIT_EXPONENT_DEFAULT; LIBSPDM_ASSERT(measurement_hash_type == SPDM_PSK_EXCHANGE_REQUEST_NO_MEASUREMENT_SUMMARY_HASH || measurement_hash_type == SPDM_PSK_EXCHANGE_REQUEST_TCB_COMPONENT_MEASUREMENT_HASH || @@ -257,13 +259,20 @@ static libspdm_return_t libspdm_try_send_receive_psk_exchange( spdm_request->context_length = (uint16_t)requester_context_in_size; } + /* No secured message version is negotiated yet at request time, so the AEADlimitOE element is + * gated on the highest secured message version this endpoint offers. */ + local_max_secured_version = libspdm_local_max_secured_message_version(spdm_context); + if (requester_opaque_data != NULL) { LIBSPDM_ASSERT(requester_opaque_data_size <= SPDM_MAX_OPAQUE_DATA_SIZE); opaque_psk_exchange_req_size = (uint16_t)requester_opaque_data_size; } else { + /* DSP0277 1.3: also advertise this endpoint's AEAD limit in the request opaque data. */ opaque_psk_exchange_req_size = - libspdm_get_opaque_data_supported_version_data_size(spdm_context); + libspdm_get_opaque_data_supported_version_data_size(spdm_context) + + libspdm_get_opaque_data_aead_limit_element_size(spdm_context, + local_max_secured_version); } LIBSPDM_ASSERT(spdm_request_size >= sizeof(spdm_psk_exchange_request_t) + psk_hint_size + @@ -309,8 +318,13 @@ static libspdm_return_t libspdm_try_send_receive_psk_exchange( libspdm_copy_mem(ptr, opaque_psk_exchange_req_size, requester_opaque_data, opaque_psk_exchange_req_size); } else { + size_t supported_version_size = + libspdm_get_opaque_data_supported_version_data_size(spdm_context); libspdm_build_opaque_data_supported_version_data( - spdm_context, &opaque_psk_exchange_req_size, ptr); + spdm_context, &supported_version_size, ptr); + opaque_psk_exchange_req_size = supported_version_size; + libspdm_append_opaque_data_aead_limit_element( + spdm_context, local_max_secured_version, &opaque_psk_exchange_req_size, ptr); } ptr += opaque_psk_exchange_req_size; @@ -405,6 +419,14 @@ static libspdm_return_t libspdm_try_send_receive_psk_exchange( status = LIBSPDM_STATUS_INVALID_MSG_FIELD; goto receive_done; } + /* DSP0277 1.3: read the Responder's AEAD limit (absent -> default 64). */ + status = libspdm_process_opaque_data_aead_limit( + spdm_context, secured_message_version, spdm_response->opaque_length, ptr, + &peer_aead_limit_exponent); + if (LIBSPDM_STATUS_IS_ERROR(status)) { + status = LIBSPDM_STATUS_INVALID_MSG_FIELD; + goto receive_done; + } } spdm_response_size = sizeof(spdm_psk_exchange_response_t) + @@ -479,6 +501,14 @@ static libspdm_return_t libspdm_try_send_receive_psk_exchange( psk_hint, psk_hint_size); + /* DSP0277 1.3: program the session's AEAD limit (min of local and peer) when secured message + * version 1.3 was negotiated. */ + if (libspdm_get_version_from_version_number(secured_message_version) >= + SECURED_SPDM_VERSION_13) { + libspdm_apply_aead_limit_to_session(spdm_context, session_info, + peer_aead_limit_exponent); + } + /* Cache session data*/ status = libspdm_append_message_k(spdm_context, session_info, true, spdm_request, diff --git a/library/spdm_responder_lib/libspdm_rsp_key_exchange.c b/library/spdm_responder_lib/libspdm_rsp_key_exchange.c index 9fad5ed7b4e..b75afdb4314 100644 --- a/library/spdm_responder_lib/libspdm_rsp_key_exchange.c +++ b/library/spdm_responder_lib/libspdm_rsp_key_exchange.c @@ -211,6 +211,7 @@ libspdm_return_t libspdm_get_response_key_exchange(libspdm_context_t *spdm_conte bool use_default_opaque_data; uint8_t th1_hash_data[LIBSPDM_MAX_HASH_SIZE]; spdm_version_number_t secured_message_version; + uint8_t peer_aead_limit_exponent = SECURED_MESSAGE_AEAD_LIMIT_EXPONENT_DEFAULT; #if LIBSPDM_ENABLE_CAPABILITY_MUT_AUTH_CAP uint8_t req_slot_id; uint8_t mut_auth_requested; @@ -429,6 +430,20 @@ libspdm_return_t libspdm_get_response_key_exchange(libspdm_context_t *spdm_conte SPDM_ERROR_CODE_INVALID_REQUEST, 0, response_size, response); } + /* DSP0277 1.3: reserve room for this Responder's AEADlimitOE. The size helper returns 0 + * unless the negotiated secured message version is 1.3 or later. */ + opaque_key_exchange_rsp_size += + libspdm_get_opaque_data_aead_limit_element_size(spdm_context, + secured_message_version); + /* DSP0277 1.3: read the Requester's AEAD limit (absent -> default 64). */ + status = libspdm_process_opaque_data_aead_limit( + spdm_context, secured_message_version, opaque_data_length, req_opaque_data, + &peer_aead_limit_exponent); + if (LIBSPDM_STATUS_IS_ERROR(status)) { + return libspdm_generate_error_response(spdm_context, + SPDM_ERROR_CODE_INVALID_REQUEST, 0, + response_size, response); + } } else { /* use response buffer to temporarily store opaque data */ rsp_opaque_data = (uint8_t *)response; @@ -487,6 +502,14 @@ libspdm_return_t libspdm_get_response_key_exchange(libspdm_context_t *spdm_conte response_size, response); } + /* DSP0277 1.3: program the session's AEAD limit (min of local and peer) when secured message + * version 1.3 was negotiated. */ + if (libspdm_get_version_from_version_number(secured_message_version) >= + SECURED_SPDM_VERSION_13) { + libspdm_apply_aead_limit_to_session(spdm_context, session_info, + peer_aead_limit_exponent); + } + total_size = sizeof(spdm_key_exchange_response_t) + rsp_key_exchange_size + measurement_summary_hash_size + sizeof(uint16_t) + opaque_key_exchange_rsp_size + signature_size + hmac_size; @@ -702,7 +725,14 @@ libspdm_return_t libspdm_get_response_key_exchange(libspdm_context_t *spdm_conte if (opaque_key_exchange_rsp_size != 0) { if (use_default_opaque_data) { + size_t version_selection_size = + libspdm_get_opaque_data_version_selection_data_size(spdm_context); libspdm_build_opaque_data_version_selection_data( + spdm_context, secured_message_version, &version_selection_size, ptr); + opaque_key_exchange_rsp_size = version_selection_size; + /* DSP0277 1.3: advertise this Responder's own AEAD limit. The append is a no-op unless + * the negotiated secured message version is 1.3 or later. */ + libspdm_append_opaque_data_aead_limit_element( spdm_context, secured_message_version, &opaque_key_exchange_rsp_size, ptr); } else { result = libspdm_key_exchange_rsp_opaque_data( diff --git a/library/spdm_responder_lib/libspdm_rsp_psk_exchange_rsp.c b/library/spdm_responder_lib/libspdm_rsp_psk_exchange_rsp.c index 869d6ee666d..8da36ccbc7d 100644 --- a/library/spdm_responder_lib/libspdm_rsp_psk_exchange_rsp.c +++ b/library/spdm_responder_lib/libspdm_rsp_psk_exchange_rsp.c @@ -101,6 +101,7 @@ libspdm_return_t libspdm_get_response_psk_exchange(libspdm_context_t *spdm_conte const void *psk_hint; size_t psk_hint_size; spdm_version_number_t secured_message_version; + uint8_t peer_aead_limit_exponent = SECURED_MESSAGE_AEAD_LIMIT_EXPONENT_DEFAULT; spdm_request = request; @@ -311,6 +312,20 @@ libspdm_return_t libspdm_get_response_psk_exchange(libspdm_context_t *spdm_conte SPDM_ERROR_CODE_INVALID_REQUEST, 0, response_size, response); } + /* DSP0277 1.3: reserve room for this Responder's AEADlimitOE. The size helper returns 0 + * unless the negotiated secured message version is 1.3 or later. */ + opaque_psk_exchange_rsp_size += + libspdm_get_opaque_data_aead_limit_element_size(spdm_context, + secured_message_version); + /* DSP0277 1.3: read the Requester's AEAD limit (absent -> default 64). */ + status = libspdm_process_opaque_data_aead_limit( + spdm_context, secured_message_version, spdm_request->opaque_length, req_opaque_data, + &peer_aead_limit_exponent); + if (LIBSPDM_STATUS_IS_ERROR(status)) { + return libspdm_generate_error_response(spdm_context, + SPDM_ERROR_CODE_INVALID_REQUEST, 0, + response_size, response); + } } else { /* use response buffer to temporarily store opaque data */ rsp_opaque_data = (uint8_t *)response; @@ -377,6 +392,14 @@ libspdm_return_t libspdm_get_response_psk_exchange(libspdm_context_t *spdm_conte } libspdm_session_info_set_psk_hint(session_info, psk_hint, psk_hint_size); + /* DSP0277 1.3: program the session's AEAD limit (min of local and peer) when secured message + * version 1.3 was negotiated. */ + if (libspdm_get_version_from_version_number(secured_message_version) >= + SECURED_SPDM_VERSION_13) { + libspdm_apply_aead_limit_to_session(spdm_context, session_info, + peer_aead_limit_exponent); + } + libspdm_reset_message_buffer_via_request_code(spdm_context, NULL, spdm_request->header.request_response_code); @@ -426,7 +449,14 @@ libspdm_return_t libspdm_get_response_psk_exchange(libspdm_context_t *spdm_conte if (opaque_psk_exchange_rsp_size != 0) { if (use_default_opaque_data) { + size_t version_selection_size = + libspdm_get_opaque_data_version_selection_data_size(spdm_context); libspdm_build_opaque_data_version_selection_data( + spdm_context, secured_message_version, &version_selection_size, ptr); + opaque_psk_exchange_rsp_size = version_selection_size; + /* DSP0277 1.3: advertise this Responder's own AEAD limit. The append is a no-op unless + * the negotiated secured message version is 1.3 or later. */ + libspdm_append_opaque_data_aead_limit_element( spdm_context, secured_message_version, &opaque_psk_exchange_rsp_size, ptr); } else { result = libspdm_psk_exchange_rsp_opaque_data( diff --git a/unit_test/test_spdm_common/context_data.c b/unit_test/test_spdm_common/context_data.c index a18fccc7526..09ec053ac71 100644 --- a/unit_test/test_spdm_common/context_data.c +++ b/unit_test/test_spdm_common/context_data.c @@ -1715,6 +1715,485 @@ static void libspdm_test_process_opaque_data_case22(void **state) assert_int_equal (status, true); } +/* DSP0277 1.3 AEAD limit: build the supported-version opaque data then append AEADlimitOE, and + * verify the round-trip parse recovers the advertised exponent. */ +static void libspdm_test_aead_limit_build_parse_case23(void **state) +{ + libspdm_return_t status; + libspdm_test_context_t *spdm_test_context; + libspdm_context_t *spdm_context; + size_t opaque_data_size; + size_t element_size; + uint8_t *opaque_data_ptr; + uint8_t aead_limit_exponent; + + spdm_test_context = *state; + spdm_context = spdm_test_context->spdm_context; + spdm_test_context->case_id = 0x17; + + spdm_context->connection_info.version = SPDM_MESSAGE_VERSION_12 << + SPDM_VERSION_NUMBER_SHIFT_BIT; + + /* Local secured message version list includes 1.3, so AEADlimitOE is emitted. */ + spdm_context->local_context.secured_message_version.secured_message_version_count = 4; + spdm_context->local_context.secured_message_version.secured_message_version[0] = + SECURED_SPDM_VERSION_10 << SPDM_VERSION_NUMBER_SHIFT_BIT; + spdm_context->local_context.secured_message_version.secured_message_version[1] = + SECURED_SPDM_VERSION_11 << SPDM_VERSION_NUMBER_SHIFT_BIT; + spdm_context->local_context.secured_message_version.secured_message_version[2] = + SECURED_SPDM_VERSION_12 << SPDM_VERSION_NUMBER_SHIFT_BIT; + spdm_context->local_context.secured_message_version.secured_message_version[3] = + SECURED_SPDM_VERSION_13 << SPDM_VERSION_NUMBER_SHIFT_BIT; + + /* Advertise a non-default exponent by setting the single-source-of-truth cap to 2^32 - 1, which + * the builder encodes as exponent 32. */ + spdm_context->max_spdm_session_sequence_number = ((uint64_t)1 << 32) - 1; + + element_size = libspdm_get_opaque_data_aead_limit_element_size( + spdm_context, SECURED_SPDM_VERSION_13 << SPDM_VERSION_NUMBER_SHIFT_BIT); + assert_int_not_equal(element_size, 0); + + /* The element size is 0 for a sub-1.3 version. */ + assert_int_equal(libspdm_get_opaque_data_aead_limit_element_size( + spdm_context, SECURED_SPDM_VERSION_12 << SPDM_VERSION_NUMBER_SHIFT_BIT), + 0); + + opaque_data_size = libspdm_get_opaque_data_supported_version_data_size(spdm_context); + opaque_data_ptr = malloc(opaque_data_size + element_size); + assert_ptr_not_equal(opaque_data_ptr, NULL); + + libspdm_build_opaque_data_supported_version_data(spdm_context, &opaque_data_size, + opaque_data_ptr); + libspdm_append_opaque_data_aead_limit_element( + spdm_context, SECURED_SPDM_VERSION_13 << SPDM_VERSION_NUMBER_SHIFT_BIT, + &opaque_data_size, opaque_data_ptr); + + aead_limit_exponent = 0; + status = libspdm_process_opaque_data_aead_limit( + spdm_context, SECURED_SPDM_VERSION_13 << SPDM_VERSION_NUMBER_SHIFT_BIT, + opaque_data_size, opaque_data_ptr, &aead_limit_exponent); + assert_int_equal(status, LIBSPDM_STATUS_SUCCESS); + assert_int_equal(aead_limit_exponent, 32); + + free(opaque_data_ptr); +} + +/* DSP0277 1.3 AEAD limit: an exponent > 64 must be rejected, and an absent element must default to + * 64. */ +static void libspdm_test_aead_limit_invalid_and_default_case24(void **state) +{ + libspdm_return_t status; + libspdm_test_context_t *spdm_test_context; + libspdm_context_t *spdm_context; + size_t opaque_data_size; + size_t element_size; + uint8_t *opaque_data_ptr; + uint8_t aead_limit_exponent; + secured_message_opaque_element_aead_limit_t *aead_element; + + spdm_test_context = *state; + spdm_context = spdm_test_context->spdm_context; + spdm_test_context->case_id = 0x18; + + spdm_context->connection_info.version = SPDM_MESSAGE_VERSION_12 << + SPDM_VERSION_NUMBER_SHIFT_BIT; + + spdm_context->local_context.secured_message_version.secured_message_version_count = 4; + spdm_context->local_context.secured_message_version.secured_message_version[0] = + SECURED_SPDM_VERSION_10 << SPDM_VERSION_NUMBER_SHIFT_BIT; + spdm_context->local_context.secured_message_version.secured_message_version[1] = + SECURED_SPDM_VERSION_11 << SPDM_VERSION_NUMBER_SHIFT_BIT; + spdm_context->local_context.secured_message_version.secured_message_version[2] = + SECURED_SPDM_VERSION_12 << SPDM_VERSION_NUMBER_SHIFT_BIT; + spdm_context->local_context.secured_message_version.secured_message_version[3] = + SECURED_SPDM_VERSION_13 << SPDM_VERSION_NUMBER_SHIFT_BIT; + /* Default cap (all-ones) encodes the default exponent of 64. */ + spdm_context->max_spdm_session_sequence_number = LIBSPDM_MAX_SPDM_SESSION_SEQUENCE_NUMBER; + + element_size = libspdm_get_opaque_data_aead_limit_element_size( + spdm_context, SECURED_SPDM_VERSION_13 << SPDM_VERSION_NUMBER_SHIFT_BIT); + assert_int_not_equal(element_size, 0); + + /* Build a valid blob with the AEADlimitOE present. */ + opaque_data_size = libspdm_get_opaque_data_supported_version_data_size(spdm_context); + opaque_data_ptr = malloc(opaque_data_size + element_size); + assert_ptr_not_equal(opaque_data_ptr, NULL); + + libspdm_build_opaque_data_supported_version_data(spdm_context, &opaque_data_size, + opaque_data_ptr); + libspdm_append_opaque_data_aead_limit_element( + spdm_context, SECURED_SPDM_VERSION_13 << SPDM_VERSION_NUMBER_SHIFT_BIT, + &opaque_data_size, opaque_data_ptr); + + /* Corrupt the exponent so it exceeds the max (64). The AEADlimitOE element follows the + * element table header which is the last element in the blob. */ + aead_element = (secured_message_opaque_element_aead_limit_t *) + (opaque_data_ptr + opaque_data_size - + sizeof(secured_message_opaque_element_aead_limit_t)); + /* Account for the padding bytes (element_size rounds up to a multiple of 4). */ + aead_element = (secured_message_opaque_element_aead_limit_t *) + ((uint8_t *)aead_element - + (element_size - (sizeof(secured_message_opaque_element_table_header_t) + + sizeof(secured_message_opaque_element_aead_limit_t)))); + assert_int_equal(aead_element->sm_data_id, + SECURED_MESSAGE_OPAQUE_ELEMENT_SMDATA_ID_AEAD_LIMIT); + aead_element->aead_limit_exponent = SECURED_MESSAGE_AEAD_LIMIT_EXPONENT_MAX + 1; + + status = libspdm_process_opaque_data_aead_limit( + spdm_context, SECURED_SPDM_VERSION_13 << SPDM_VERSION_NUMBER_SHIFT_BIT, + opaque_data_size, opaque_data_ptr, &aead_limit_exponent); + assert_int_equal(status, LIBSPDM_STATUS_INVALID_MSG_FIELD); + + free(opaque_data_ptr); + + /* A blob without the AEADlimitOE element must default the exponent to 64. */ + opaque_data_size = libspdm_get_opaque_data_supported_version_data_size(spdm_context); + opaque_data_ptr = malloc(opaque_data_size); + assert_ptr_not_equal(opaque_data_ptr, NULL); + + libspdm_build_opaque_data_supported_version_data(spdm_context, &opaque_data_size, + opaque_data_ptr); + + aead_limit_exponent = 0; + status = libspdm_process_opaque_data_aead_limit( + spdm_context, SECURED_SPDM_VERSION_13 << SPDM_VERSION_NUMBER_SHIFT_BIT, + opaque_data_size, opaque_data_ptr, &aead_limit_exponent); + assert_int_equal(status, LIBSPDM_STATUS_SUCCESS); + assert_int_equal(aead_limit_exponent, SECURED_MESSAGE_AEAD_LIMIT_EXPONENT_DEFAULT); + + free(opaque_data_ptr); +} + +/* DSP0277 1.3 AEAD limit: applying the limit to a session sets the session's max sequence number to + * min(local cap, peer AEAD limit), and the integrator's smaller pre-set cap is never raised. The + * local limit's single source of truth is max_spdm_session_sequence_number. */ +static void libspdm_test_aead_limit_apply_to_session_case25(void **state) +{ + libspdm_test_context_t *spdm_test_context; + libspdm_context_t *spdm_context; + libspdm_data_parameter_t parameter; + uint16_t req_id; + uint16_t rsp_id; + uint32_t session_id; + void *session_info; + uint64_t max_seq; + size_t data_size; + + spdm_test_context = *state; + spdm_context = spdm_test_context->spdm_context; + spdm_test_context->case_id = 0x19; + + spdm_context->connection_info.capability.flags = + SPDM_GET_CAPABILITIES_REQUEST_FLAGS_ENCRYPT_CAP | + SPDM_GET_CAPABILITIES_REQUEST_FLAGS_MAC_CAP; + spdm_context->local_context.capability.flags = + SPDM_GET_CAPABILITIES_RESPONSE_FLAGS_ENCRYPT_CAP | + SPDM_GET_CAPABILITIES_RESPONSE_FLAGS_MAC_CAP; + + /* Local cap = 2^40 - 1 (encodes local exponent 40), peer exponent 30. The effective session cap + * is min(2^40 - 1, 2^30 - 1) = 2^30 - 1. */ + spdm_context->max_spdm_session_sequence_number = ((uint64_t)1 << 40) - 1; + + req_id = libspdm_allocate_req_session_id(spdm_context, false); + rsp_id = libspdm_allocate_rsp_session_id(spdm_context, false); + session_id = libspdm_generate_session_id(req_id, rsp_id); + session_info = libspdm_assign_session_id(spdm_context, session_id, + SECURED_SPDM_VERSION_13 << + SPDM_VERSION_NUMBER_SHIFT_BIT, false); + assert_ptr_not_equal(session_info, NULL); + + libspdm_zero_mem(¶meter, sizeof(parameter)); + parameter.location = LIBSPDM_DATA_LOCATION_SESSION; + libspdm_copy_mem(parameter.additional_data, sizeof(parameter.additional_data), + &session_id, sizeof(session_id)); + + libspdm_apply_aead_limit_to_session(spdm_context, session_info, 30); + + /* Read back the negotiated effective max sequence number per session via get_data. */ + max_seq = 0; + data_size = sizeof(max_seq); + assert_int_equal(libspdm_get_data(spdm_context, + LIBSPDM_DATA_MAX_SPDM_SESSION_SEQUENCE_NUMBER, + ¶meter, &max_seq, &data_size), + LIBSPDM_STATUS_SUCCESS); + assert_int_equal(max_seq, ((uint64_t)1 << 30) - 1); + + /* A smaller local cap must never be raised by the peer's AEAD limit. With local cap 0xFFFF and + * peer exponent 30 (2^30 - 1), the effective cap stays 0xFFFF. */ + spdm_context->max_spdm_session_sequence_number = 0xFFFF; + libspdm_apply_aead_limit_to_session(spdm_context, session_info, 30); + max_seq = 0; + data_size = sizeof(max_seq); + assert_int_equal(libspdm_get_data(spdm_context, + LIBSPDM_DATA_MAX_SPDM_SESSION_SEQUENCE_NUMBER, + ¶meter, &max_seq, &data_size), + LIBSPDM_STATUS_SUCCESS); + assert_int_equal(max_seq, 0xFFFF); + + /* A per-session set of the max sequence number is not allowed: the session cap is owned by the + * negotiated AEAD limit and must not be overridden. */ + max_seq = 0x1000; + assert_int_not_equal(libspdm_set_data(spdm_context, + LIBSPDM_DATA_MAX_SPDM_SESSION_SEQUENCE_NUMBER, + ¶meter, &max_seq, sizeof(max_seq)), + LIBSPDM_STATUS_SUCCESS); + + libspdm_free_session_id(spdm_context, session_id); +} + +/* DSP0277 1.3 AEAD limit: the advertised AeadLimitExponent is derived from the single source of + * truth, max_spdm_session_sequence_number (= floor(log2(max + 1))). */ +static void libspdm_test_aead_limit_set_data_case26(void **state) +{ + libspdm_return_t status; + libspdm_test_context_t *spdm_test_context; + libspdm_context_t *spdm_context; + size_t opaque_data_size; + size_t element_size; + uint8_t *opaque_data_ptr; + uint8_t aead_limit_exponent; + + spdm_test_context = *state; + spdm_context = spdm_test_context->spdm_context; + spdm_test_context->case_id = 0x1A; + + spdm_context->connection_info.version = SPDM_MESSAGE_VERSION_12 << + SPDM_VERSION_NUMBER_SHIFT_BIT; + spdm_context->local_context.secured_message_version.secured_message_version_count = 4; + spdm_context->local_context.secured_message_version.secured_message_version[0] = + SECURED_SPDM_VERSION_10 << SPDM_VERSION_NUMBER_SHIFT_BIT; + spdm_context->local_context.secured_message_version.secured_message_version[1] = + SECURED_SPDM_VERSION_11 << SPDM_VERSION_NUMBER_SHIFT_BIT; + spdm_context->local_context.secured_message_version.secured_message_version[2] = + SECURED_SPDM_VERSION_12 << SPDM_VERSION_NUMBER_SHIFT_BIT; + spdm_context->local_context.secured_message_version.secured_message_version[3] = + SECURED_SPDM_VERSION_13 << SPDM_VERSION_NUMBER_SHIFT_BIT; + + element_size = libspdm_get_opaque_data_aead_limit_element_size( + spdm_context, SECURED_SPDM_VERSION_13 << SPDM_VERSION_NUMBER_SHIFT_BIT); + assert_int_not_equal(element_size, 0); + + /* A non-power-of-two cap (0xFFFFFF = 2^24 - 1) advertises exponent 24. */ + spdm_context->max_spdm_session_sequence_number = 0xFFFFFF; + opaque_data_size = libspdm_get_opaque_data_supported_version_data_size(spdm_context); + opaque_data_ptr = malloc(opaque_data_size + element_size); + assert_ptr_not_equal(opaque_data_ptr, NULL); + libspdm_build_opaque_data_supported_version_data(spdm_context, &opaque_data_size, + opaque_data_ptr); + libspdm_append_opaque_data_aead_limit_element( + spdm_context, SECURED_SPDM_VERSION_13 << SPDM_VERSION_NUMBER_SHIFT_BIT, + &opaque_data_size, opaque_data_ptr); + aead_limit_exponent = 0; + status = libspdm_process_opaque_data_aead_limit( + spdm_context, SECURED_SPDM_VERSION_13 << SPDM_VERSION_NUMBER_SHIFT_BIT, + opaque_data_size, opaque_data_ptr, &aead_limit_exponent); + assert_int_equal(status, LIBSPDM_STATUS_SUCCESS); + assert_int_equal(aead_limit_exponent, 24); + free(opaque_data_ptr); + + /* The all-ones default cap advertises the default exponent of 64. */ + spdm_context->max_spdm_session_sequence_number = LIBSPDM_MAX_SPDM_SESSION_SEQUENCE_NUMBER; + opaque_data_size = libspdm_get_opaque_data_supported_version_data_size(spdm_context); + opaque_data_ptr = malloc(opaque_data_size + element_size); + assert_ptr_not_equal(opaque_data_ptr, NULL); + libspdm_build_opaque_data_supported_version_data(spdm_context, &opaque_data_size, + opaque_data_ptr); + libspdm_append_opaque_data_aead_limit_element( + spdm_context, SECURED_SPDM_VERSION_13 << SPDM_VERSION_NUMBER_SHIFT_BIT, + &opaque_data_size, opaque_data_ptr); + aead_limit_exponent = 0; + status = libspdm_process_opaque_data_aead_limit( + spdm_context, SECURED_SPDM_VERSION_13 << SPDM_VERSION_NUMBER_SHIFT_BIT, + opaque_data_size, opaque_data_ptr, &aead_limit_exponent); + assert_int_equal(status, LIBSPDM_STATUS_SUCCESS); + assert_int_equal(aead_limit_exponent, SECURED_MESSAGE_AEAD_LIMIT_EXPONENT_DEFAULT); + free(opaque_data_ptr); +} + +#pragma pack(1) +/* A general opaque data table (SPDM 1.2 format) with the AEADlimitOE element placed BEFORE the + * version-selection element, to exercise order-independent parsing. */ +typedef struct { + spdm_general_opaque_data_table_header_t opaque_header; + secured_message_opaque_element_table_header_t aead_limit_header; + secured_message_opaque_element_aead_limit_t aead_limit_opaque; + uint8_t aead_limit_align[1]; + secured_message_opaque_element_table_header_t ver_sel_header; + secured_message_opaque_element_version_selection_t ver_sel_opaque; +} test_aead_first_opaque_data_table_t; +#pragma pack() + +/* DSP0277 1.3 AEAD limit: opaque data elements may appear in any order. Verify that AEADlimitOE is + * still parsed correctly when it precedes the version-selection element, and that version-selection + * parsing is likewise unaffected by the ordering. */ +static void libspdm_test_aead_limit_element_order_case27(void **state) +{ + libspdm_return_t status; + libspdm_test_context_t *spdm_test_context; + libspdm_context_t *spdm_context; + test_aead_first_opaque_data_table_t opaque_data; + spdm_version_number_t secured_message_version; + uint8_t aead_limit_exponent; + + spdm_test_context = *state; + spdm_context = spdm_test_context->spdm_context; + spdm_test_context->case_id = 0x1B; + + spdm_context->connection_info.version = SPDM_MESSAGE_VERSION_12 << + SPDM_VERSION_NUMBER_SHIFT_BIT; + spdm_context->local_context.secured_message_version.secured_message_version_count = 4; + spdm_context->local_context.secured_message_version.secured_message_version[0] = + SECURED_SPDM_VERSION_10 << SPDM_VERSION_NUMBER_SHIFT_BIT; + spdm_context->local_context.secured_message_version.secured_message_version[1] = + SECURED_SPDM_VERSION_11 << SPDM_VERSION_NUMBER_SHIFT_BIT; + spdm_context->local_context.secured_message_version.secured_message_version[2] = + SECURED_SPDM_VERSION_12 << SPDM_VERSION_NUMBER_SHIFT_BIT; + spdm_context->local_context.secured_message_version.secured_message_version[3] = + SECURED_SPDM_VERSION_13 << SPDM_VERSION_NUMBER_SHIFT_BIT; + + libspdm_zero_mem(&opaque_data, sizeof(opaque_data)); + opaque_data.opaque_header.total_elements = 2; + + /* Element 1: AEADlimitOE (placed first). */ + opaque_data.aead_limit_header.id = SPDM_REGISTRY_ID_DMTF; + opaque_data.aead_limit_header.vendor_len = 0; + opaque_data.aead_limit_header.opaque_element_data_len = + sizeof(secured_message_opaque_element_aead_limit_t); + opaque_data.aead_limit_opaque.sm_data_version = + SECURED_MESSAGE_OPAQUE_ELEMENT_SMDATA_DATA_VERSION; + opaque_data.aead_limit_opaque.sm_data_id = + SECURED_MESSAGE_OPAQUE_ELEMENT_SMDATA_ID_AEAD_LIMIT; + opaque_data.aead_limit_opaque.aead_limit_exponent = 50; + + /* Element 2: version-selection (placed second). */ + opaque_data.ver_sel_header.id = SPDM_REGISTRY_ID_DMTF; + opaque_data.ver_sel_header.vendor_len = 0; + opaque_data.ver_sel_header.opaque_element_data_len = + sizeof(secured_message_opaque_element_version_selection_t); + opaque_data.ver_sel_opaque.sm_data_version = + SECURED_MESSAGE_OPAQUE_ELEMENT_SMDATA_DATA_VERSION; + opaque_data.ver_sel_opaque.sm_data_id = + SECURED_MESSAGE_OPAQUE_ELEMENT_SMDATA_ID_VERSION_SELECTION; + opaque_data.ver_sel_opaque.selected_version = + SECURED_SPDM_VERSION_13 << SPDM_VERSION_NUMBER_SHIFT_BIT; + + /* AEADlimitOE is found even though it precedes version-selection (negotiated version 1.3). */ + aead_limit_exponent = 0; + status = libspdm_process_opaque_data_aead_limit( + spdm_context, SECURED_SPDM_VERSION_13 << SPDM_VERSION_NUMBER_SHIFT_BIT, + sizeof(opaque_data), &opaque_data, &aead_limit_exponent); + assert_int_equal(status, LIBSPDM_STATUS_SUCCESS); + assert_int_equal(aead_limit_exponent, 50); + + /* The AEADlimitOE element is only defined for secured message version 1.3. With an older + * negotiated version (1.2) the element must be ignored and the default exponent (64) returned, + * even though the element is physically present. */ + aead_limit_exponent = 0; + status = libspdm_process_opaque_data_aead_limit( + spdm_context, SECURED_SPDM_VERSION_12 << SPDM_VERSION_NUMBER_SHIFT_BIT, + sizeof(opaque_data), &opaque_data, &aead_limit_exponent); + assert_int_equal(status, LIBSPDM_STATUS_SUCCESS); + assert_int_equal(aead_limit_exponent, SECURED_MESSAGE_AEAD_LIMIT_EXPONENT_DEFAULT); + + /* version-selection is also found regardless of the AEADlimitOE ordering. */ + secured_message_version = 0; + status = libspdm_process_opaque_data_version_selection_data(spdm_context, sizeof(opaque_data), + &opaque_data, + &secured_message_version); + assert_int_equal(status, LIBSPDM_STATUS_SUCCESS); + assert_int_equal(libspdm_get_version_from_version_number(secured_message_version), + SECURED_SPDM_VERSION_13); +} + +/* DSP0277 1.3 AEAD limit: peer-supports vs. peer-does-not-support, using a non-power-of-two local + * cap. With local cap 0xFF00FF: + * - peer does not support (absent element -> exponent 64): the session cap stays 0xFF00FF. + * - peer supports and advertises a tighter limit: the session cap is reduced to the peer's + * (rounded-down) AEAD limit, never raised. */ +static void libspdm_test_aead_limit_peer_support_case28(void **state) +{ + libspdm_test_context_t *spdm_test_context; + libspdm_context_t *spdm_context; + libspdm_data_parameter_t parameter; + uint16_t req_id; + uint16_t rsp_id; + uint32_t session_id; + void *session_info; + uint64_t max_seq; + size_t data_size; + + spdm_test_context = *state; + spdm_context = spdm_test_context->spdm_context; + spdm_test_context->case_id = 0x1C; + + spdm_context->connection_info.capability.flags = + SPDM_GET_CAPABILITIES_REQUEST_FLAGS_ENCRYPT_CAP | + SPDM_GET_CAPABILITIES_REQUEST_FLAGS_MAC_CAP; + spdm_context->local_context.capability.flags = + SPDM_GET_CAPABILITIES_RESPONSE_FLAGS_ENCRYPT_CAP | + SPDM_GET_CAPABILITIES_RESPONSE_FLAGS_MAC_CAP; + + /* Non-power-of-two local cap. */ + spdm_context->max_spdm_session_sequence_number = 0xFF00FF; + + libspdm_zero_mem(¶meter, sizeof(parameter)); + parameter.location = LIBSPDM_DATA_LOCATION_SESSION; + + /* Case 1: peer does NOT support AEAD limit. The message paths gate the apply on negotiated + * secured message version >= 1.3, so for an older session the apply is never called and the + * session cap is simply inherited from the context cap (0xFF00FF). */ + req_id = libspdm_allocate_req_session_id(spdm_context, false); + rsp_id = libspdm_allocate_rsp_session_id(spdm_context, false); + session_id = libspdm_generate_session_id(req_id, rsp_id); + session_info = libspdm_assign_session_id(spdm_context, session_id, + SECURED_SPDM_VERSION_12 << + SPDM_VERSION_NUMBER_SHIFT_BIT, false); + assert_ptr_not_equal(session_info, NULL); + libspdm_copy_mem(parameter.additional_data, sizeof(parameter.additional_data), + &session_id, sizeof(session_id)); + max_seq = 0; + data_size = sizeof(max_seq); + assert_int_equal(libspdm_get_data(spdm_context, + LIBSPDM_DATA_MAX_SPDM_SESSION_SEQUENCE_NUMBER, + ¶meter, &max_seq, &data_size), + LIBSPDM_STATUS_SUCCESS); + assert_int_equal(max_seq, 0xFF00FF); + libspdm_free_session_id(spdm_context, session_id); + + /* Case 2: peer supports but advertises the default (absent element -> exponent 64). Even on a + * 1.3 session, the negotiated cap is unchanged (min(0xFF00FF, 2^64 - 1) = 0xFF00FF). */ + req_id = libspdm_allocate_req_session_id(spdm_context, false); + rsp_id = libspdm_allocate_rsp_session_id(spdm_context, false); + session_id = libspdm_generate_session_id(req_id, rsp_id); + session_info = libspdm_assign_session_id(spdm_context, session_id, + SECURED_SPDM_VERSION_13 << + SPDM_VERSION_NUMBER_SHIFT_BIT, false); + assert_ptr_not_equal(session_info, NULL); + libspdm_copy_mem(parameter.additional_data, sizeof(parameter.additional_data), + &session_id, sizeof(session_id)); + libspdm_apply_aead_limit_to_session(spdm_context, session_info, + SECURED_MESSAGE_AEAD_LIMIT_EXPONENT_DEFAULT); + max_seq = 0; + data_size = sizeof(max_seq); + assert_int_equal(libspdm_get_data(spdm_context, + LIBSPDM_DATA_MAX_SPDM_SESSION_SEQUENCE_NUMBER, + ¶meter, &max_seq, &data_size), + LIBSPDM_STATUS_SUCCESS); + assert_int_equal(max_seq, 0xFF00FF); + + /* Case 3: peer supports and advertises a tighter limit (exponent 23 -> 2^23 - 1 = 0x7FFFFF). + * The session cap is reduced to min(0xFF00FF, 0x7FFFFF) = 0x7FFFFF. */ + libspdm_apply_aead_limit_to_session(spdm_context, session_info, 23); + max_seq = 0; + data_size = sizeof(max_seq); + assert_int_equal(libspdm_get_data(spdm_context, + LIBSPDM_DATA_MAX_SPDM_SESSION_SEQUENCE_NUMBER, + ¶meter, &max_seq, &data_size), + LIBSPDM_STATUS_SUCCESS); + assert_int_equal(max_seq, 0x7FFFFF); + libspdm_free_session_id(spdm_context, session_id); +} + static libspdm_test_context_t m_libspdm_common_context_data_test_context = { LIBSPDM_TEST_CONTEXT_VERSION, true, @@ -1766,6 +2245,19 @@ int libspdm_common_context_data_test_main(void) /* Successful response V1.2 for multi element */ cmocka_unit_test(libspdm_test_process_opaque_data_case22), + + /* DSP0277 1.3 AEAD limit: build + append AEADlimitOE then parse it back. */ + cmocka_unit_test(libspdm_test_aead_limit_build_parse_case23), + /* DSP0277 1.3 AEAD limit: reject exponent > 64, absent element defaults to 64. */ + cmocka_unit_test(libspdm_test_aead_limit_invalid_and_default_case24), + /* DSP0277 1.3 AEAD limit: apply min(local, peer) limit to a session. */ + cmocka_unit_test(libspdm_test_aead_limit_apply_to_session_case25), + /* DSP0277 1.3 AEAD limit: set_data validates the exponent. */ + cmocka_unit_test(libspdm_test_aead_limit_set_data_case26), + /* DSP0277 1.3 AEAD limit: parse is order-independent (AEADlimitOE before version-sel). */ + cmocka_unit_test(libspdm_test_aead_limit_element_order_case27), + /* DSP0277 1.3 AEAD limit: peer-supports vs peer-does-not-support with a non-pow2 cap. */ + cmocka_unit_test(libspdm_test_aead_limit_peer_support_case28), }; libspdm_setup_test_context(&m_libspdm_common_context_data_test_context); diff --git a/unit_test/test_spdm_responder/respond_if_ready.c b/unit_test/test_spdm_responder/respond_if_ready.c index 24768e2f9d9..9e5a4cb0891 100644 --- a/unit_test/test_spdm_responder/respond_if_ready.c +++ b/unit_test/test_spdm_responder/respond_if_ready.c @@ -669,7 +669,11 @@ static void rsp_respond_if_ready_case5(void **state) { assert_int_equal (response_size, sizeof(spdm_key_exchange_response_t) + dhe_key_size + 2 + libspdm_get_opaque_data_version_selection_data_size( - spdm_context) + libspdm_get_asym_signature_size ( + spdm_context) + + libspdm_get_opaque_data_aead_limit_element_size( + spdm_context, + SECURED_SPDM_VERSION_13 << SPDM_VERSION_NUMBER_SHIFT_BIT) + + libspdm_get_asym_signature_size ( m_libspdm_use_asym_algo) + libspdm_get_hash_size (m_libspdm_use_hash_algo)); assert_int_equal (libspdm_secured_message_get_session_state (spdm_context->session_info[0]. @@ -902,7 +906,11 @@ static void rsp_respond_if_ready_case7(void **state) { assert_int_equal (response_size, sizeof(spdm_psk_exchange_response_t) + LIBSPDM_PSK_CONTEXT_LENGTH + libspdm_get_opaque_data_version_selection_data_size( - spdm_context) + libspdm_get_hash_size (m_libspdm_use_hash_algo)); + spdm_context) + + libspdm_get_opaque_data_aead_limit_element_size( + spdm_context, + SECURED_SPDM_VERSION_13 << SPDM_VERSION_NUMBER_SHIFT_BIT) + + libspdm_get_hash_size (m_libspdm_use_hash_algo)); assert_int_equal (libspdm_secured_message_get_session_state (spdm_context->session_info[0]. secured_message_context), LIBSPDM_SESSION_STATE_HANDSHAKING);