core/spdm/spdm_secure_session_manager.c (888 lines of code) (raw):
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
#include <string.h>
#include "spdm_secure_session_manager.h"
#include "common/unused.h"
#include "crypto/kdf.h"
/**
* Initialize a secure session's state.
*
* @param session_manager Session Manager.
* @param session_index Session index.
* @param session_id SPDM Session Id.
* @param is_requester true if the local host is the SPDM requester.
* @param connection_info Peer Connection info.
*/
static void spdm_secure_session_manager_init_session_state (
const struct spdm_secure_session_manager *session_manager, uint32_t session_index,
uint32_t session_id, bool is_requester, const struct spdm_connection_info *connection_info)
{
enum spdm_secure_session_type session_type;
struct spdm_get_capabilities_flags_format req_capability;
struct spdm_get_capabilities_flags_format resp_capability;
struct spdm_secure_session *session;
const struct spdm_transcript_manager *transcript_manager;
session = &session_manager->state->sessions[session_index];
transcript_manager = session_manager->transcript_manager;
/* Determine the session type. */
req_capability = connection_info->peer_capabilities.flags;
resp_capability = session_manager->local_capabilities->flags;
if ((req_capability.encrypt_cap == 1) && (req_capability.mac_cap == 1) &&
(resp_capability.encrypt_cap == 1) && (resp_capability.mac_cap == 1)) {
session_type = SPDM_SESSION_TYPE_ENC_MAC;
}
else if ((req_capability.mac_cap == 1) && (resp_capability.mac_cap == 1)) {
session_type = SPDM_SESSION_TYPE_MAC_ONLY;
}
else {
session_type = SPDM_SESSION_TYPE_NONE;
}
memset (session, 0, sizeof (struct spdm_secure_session));
/* Reset the session transcipt for TH hash. */
transcript_manager->reset_transcript (transcript_manager, TRANSCRIPT_CONTEXT_TYPE_TH, true,
session_index);
session->session_id = session_id;
session->session_index = session_index;
session->is_requester = is_requester;
session->session_type = session_type;
session->version = connection_info->version;
session->secure_message_version = connection_info->secure_message_version;
session->base_hash_algo = connection_info->peer_algorithms.base_hash_algo;
session->dhe_named_group = connection_info->peer_algorithms.dhe_named_group;
session->aead_cipher_suite = connection_info->peer_algorithms.aead_cipher_suite;
session->key_schedule = connection_info->peer_algorithms.key_schedule;
session->hash_size = hash_get_hash_length (spdm_get_hash_type (session->base_hash_algo));
session->dhe_key_size = spdm_get_dhe_pub_key_size (session->dhe_named_group);
session->aead_key_size = spdm_get_aead_key_size (session->aead_cipher_suite);
session->aead_iv_size = spdm_get_aead_iv_size (session->aead_cipher_suite);
session->aead_tag_size = spdm_get_aead_tag_size (session->aead_cipher_suite);
session->peer_capabilities = connection_info->peer_capabilities;
}
struct spdm_secure_session* spdm_secure_session_manager_create_session (
const struct spdm_secure_session_manager *session_manager, uint32_t session_id,
bool is_requester, const struct spdm_connection_info *connection_info)
{
struct spdm_secure_session *sessions;
uint8_t index;
if ((session_manager == NULL) || (session_id == SPDM_INVALID_SESSION_ID) ||
(connection_info == NULL) ||
(session_manager->state->current_session_count >= SPDM_MAX_SESSION_COUNT)) {
return NULL;
}
sessions = session_manager->state->sessions;
/* Check if the session exists. */
for (index = 0; index < SPDM_MAX_SESSION_COUNT; index++) {
if (sessions[index].session_id == session_id) {
return NULL;
}
}
/* Initialize a session. */
for (index = 0; index < SPDM_MAX_SESSION_COUNT; index++) {
if (sessions[index].session_id == SPDM_INVALID_SESSION_ID) {
spdm_secure_session_manager_init_session_state (session_manager, index, session_id,
is_requester, connection_info);
session_manager->state->current_session_count++;
return &sessions[index];
}
}
return NULL;
}
void spdm_secure_session_manager_release_session (
const struct spdm_secure_session_manager *session_manager, uint32_t session_id)
{
struct spdm_secure_session *sessions;
const struct spdm_transcript_manager *transcript_manager;
size_t session_index;
if ((session_manager == NULL) || (session_id == SPDM_INVALID_SESSION_ID)) {
return;
}
sessions = session_manager->state->sessions;
transcript_manager = session_manager->transcript_manager;
for (session_index = 0; session_index < SPDM_MAX_SESSION_COUNT; session_index++) {
if (sessions[session_index].session_id == session_id) {
memset (&sessions[session_index], 0, sizeof (struct spdm_secure_session));
transcript_manager->reset_session_transcript (transcript_manager, session_index);
session_manager->state->current_session_count--;
return;
}
}
}
/**
* Clear the handshake secrets.
*
* @param session SPDM secure session.
*/
static void spdm_secure_session_clear_handshake_secret (struct spdm_secure_session *session)
{
memset (session->master_secret.handshake_secret, 0,
sizeof (session->master_secret.handshake_secret));
memset (&session->handshake_secret, 0, sizeof (struct spdm_secure_session_handshake_secrets));
session->requester_backup_valid = false;
session->responder_backup_valid = false;
}
/**
* Clear the master secrets.
*
* @param session SPDM secure session.
*/
static void spdm_secure_session_clear_master_secret (struct spdm_secure_session *session)
{
memset (session->master_secret.master_secret, 0, sizeof (session->master_secret.master_secret));
}
void spdm_secure_session_manager_set_session_state (
const struct spdm_secure_session_manager *session_manager, uint32_t session_id,
enum spdm_secure_session_state session_state)
{
struct spdm_secure_session *session;
if ((session_manager == NULL) || (session_id == SPDM_INVALID_SESSION_ID) ||
(session_state == SPDM_SESSION_STATE_MAX)) {
return;
}
session = session_manager->get_session (session_manager, session_id);
if (session == NULL) {
return;
}
if (session->session_state != session_state) {
session->session_state = session_state;
/* Session handshake keys should be zeroized after the handshake phase. */
if (session_state == SPDM_SESSION_STATE_ESTABLISHED) {
spdm_secure_session_clear_handshake_secret (session);
spdm_secure_session_clear_master_secret (session);
}
}
}
void spdm_secure_session_manager_reset (const struct spdm_secure_session_manager *session_manager)
{
struct spdm_secure_session *sessions;
size_t session_index;
if (session_manager == NULL) {
return;
}
sessions = session_manager->state->sessions;
/* Release all sessions. */
for (session_index = 0; session_index < SPDM_MAX_SESSION_COUNT; session_index++) {
if (sessions[session_index].session_id != SPDM_INVALID_SESSION_ID) {
spdm_secure_session_manager_release_session (session_manager,
sessions[session_index].session_id);
}
}
/* Reset the state. */
memset (session_manager->state, 0, sizeof (struct spdm_secure_session_manager_state));
}
struct spdm_secure_session* spdm_secure_session_manager_get_session (
const struct spdm_secure_session_manager *session_manager, uint32_t session_id)
{
struct spdm_secure_session *sessions;
size_t index;
if ((session_manager == NULL) || (session_id == SPDM_INVALID_SESSION_ID)) {
return NULL;
}
sessions = (struct spdm_secure_session*) session_manager->state->sessions;
for (index = 0; index < SPDM_MAX_SESSION_COUNT; index++) {
if (sessions[index].session_id == session_id) {
return &sessions[index];
}
}
return NULL;
}
int spdm_secure_session_manager_generate_shared_secret (
const struct spdm_secure_session_manager *session_manager, struct spdm_secure_session *session,
const struct ecc_point_public_key *peer_pub_key_point, uint8_t *local_pub_key_point)
{
int status;
uint8_t peer_pub_key_der[ECC_DER_MAX_PUBLIC_LENGTH];
int der_len;
const struct ecc_engine *ecc_engine;
struct ecc_public_key peer_pub_key;
struct ecc_private_key local_priv_key;
struct ecc_public_key local_pub_key;
uint8_t *local_pub_key_der = NULL;
size_t local_pub_key_der_len;
bool release_local_key_pair = false;
bool release_peer_pub_key = false;
int shared_secret_len;
size_t key_point_length;
if ((session_manager == NULL) || (session == NULL) || (peer_pub_key_point == NULL) ||
(local_pub_key_point == NULL)) {
status = SPDM_SECURE_SESSION_MANAGER_INVALID_ARGUMENT;
goto exit;
}
ecc_engine = session_manager->ecc_engine;
key_point_length = peer_pub_key_point->key_length;
/* Step 1: Convert the peer public key to DER format. */
der_len = ecc_der_encode_public_key (peer_pub_key_point->x, peer_pub_key_point->y,
key_point_length, peer_pub_key_der, sizeof (peer_pub_key_der));
if (ROT_IS_ERROR (der_len)) {
status = CMD_HANDLER_SPDM_RESPONDER_INTERNAL_ERROR;
goto exit;
}
/* Step 2: Convert the peer public key (converted to DER format above) to internal format. */
status = ecc_engine->init_public_key (ecc_engine, peer_pub_key_der, der_len, &peer_pub_key);
if (status != 0) {
goto exit;
}
release_peer_pub_key = true;
/* Generate an ephemeral key pair. */
status = ecc_engine->generate_key_pair (ecc_engine, key_point_length, &local_priv_key,
&local_pub_key);
if (status != 0) {
goto exit;
}
release_local_key_pair = true;
/* Convert the local public key to DER format. */
status = ecc_engine->get_public_key_der (ecc_engine, &local_pub_key, &local_pub_key_der,
&local_pub_key_der_len);
if (status != 0) {
goto exit;
}
/* Convert the local public key from DER to point format and copy it to the output buffer. */
status = ecc_der_decode_public_key (local_pub_key_der, local_pub_key_der_len,
local_pub_key_point, &local_pub_key_point[key_point_length], key_point_length);
if (ROT_IS_ERROR (status)) {
status = CMD_HANDLER_SPDM_RESPONDER_INTERNAL_ERROR;
goto exit;
}
status = 0;
/* Get the shared secret length. */
shared_secret_len = ecc_engine->get_shared_secret_max_length (ecc_engine, &local_priv_key);
if (ROT_IS_ERROR (shared_secret_len)) {
status = CMD_HANDLER_SPDM_RESPONDER_INTERNAL_ERROR;
goto exit;
}
if (shared_secret_len > SPDM_MAX_DHE_SHARED_SECRET_SIZE) {
status = CMD_HANDLER_SPDM_RESPONDER_UNSUPPORTED_DHE_KEY_SIZE;
goto exit;
}
/* Generate the shared secret. */
shared_secret_len = ecc_engine->compute_shared_secret (ecc_engine, &local_priv_key,
&peer_pub_key, session->master_secret.dhe_secret, shared_secret_len);
if (ROT_IS_ERROR (shared_secret_len)) {
status = CMD_HANDLER_SPDM_RESPONDER_INTERNAL_ERROR;
goto exit;
}
session->dhe_key_size = shared_secret_len;
exit:
if (release_local_key_pair == true) {
ecc_engine->release_key_pair (ecc_engine, &local_priv_key, &local_pub_key);
}
if (release_peer_pub_key == true) {
ecc_engine->release_key_pair (ecc_engine, NULL, &peer_pub_key);
}
platform_free (local_pub_key_der);
return status;
}
/**
* Concatenate binary data to be used as info in HKDF.
*
* @param version The SPDM message version.
* @param label An ascii string label for the concat operation.
* @param label_size The size in bytes of the ASCII string label, not including NULL terminator.
* @param context A pre-defined hash value as the context for the concat operation.
* @param length 16 bits length for the concat operation.
* @param hash_size The size in bytes of the context hash.
* @param out_bin The buffer to store the output binary.
* @param out_bin_size The size in bytes for the out_bin.
*/
static void spdm_secure_session_manager_bin_concat (struct spdm_version_number version,
const char *label, size_t label_size, const uint8_t *context, uint16_t length, size_t hash_size,
uint8_t *out_bin, size_t *out_bin_size)
{
size_t final_size;
final_size = sizeof (uint16_t) + sizeof (SPDM_BIN_CONCAT_LABEL) - 1 + label_size;
if (context != NULL) {
final_size += hash_size;
}
*out_bin_size = final_size;
memcpy (out_bin, &length, sizeof (uint16_t));
memcpy ((out_bin + sizeof (uint16_t)), SPDM_BIN_CONCAT_LABEL,
sizeof (SPDM_BIN_CONCAT_LABEL) - 1);
/* Patch the version. */
out_bin[6] = (char) ('0' + version.major_version);
out_bin[8] = (char) ('0' + version.minor_version);
memcpy ((out_bin + sizeof (uint16_t) + sizeof (SPDM_BIN_CONCAT_LABEL) - 1), label, label_size);
if (context != NULL) {
memcpy ((out_bin + sizeof (uint16_t) + sizeof (SPDM_BIN_CONCAT_LABEL) - 1 + label_size),
context, hash_size);
}
}
/**
* Generate the SPDM finished key for a session.
*
* @param hash_engine Hash engine instance to use.
* @param session SPDM session.
* @param hmac_hash_type HMAC hash type.
* @param handshake_secret Handshake secret.
* @param finished_key Buffer to store the finished key.
*
* @return 0 if the SPDM finished key for a session is generated, error code otherwise.
*/
static int spdm_secure_session_manager_generate_finished_key (const struct hash_engine *hash_engine,
struct spdm_secure_session *session, enum hmac_hash hmac_hash_type,
const uint8_t *handshake_secret, uint8_t *finished_key)
{
int status;
size_t hash_size;
uint8_t bin_str7[128];
size_t bin_str7_size;
hash_size = session->hash_size;
bin_str7_size = sizeof (bin_str7);
spdm_secure_session_manager_bin_concat (session->version, SPDM_BIN_STR_7_LABEL,
sizeof (SPDM_BIN_STR_7_LABEL) - 1, NULL, (uint16_t) hash_size, hash_size, bin_str7,
&bin_str7_size);
status = kdf_hkdf_expand (hash_engine, hmac_hash_type, handshake_secret, hash_size, bin_str7,
bin_str7_size, finished_key, hash_size);
if (status != 0) {
goto exit;
}
exit:
return status;
}
/**
* Generate the SPDM AEAD key and IV for a session.
*
* @param hash_engine Hash engine instance to use.
* @param spdm_secured_message_context SPDM secured message context.
* @param hmac_hash_type HMAC hash type.
* @param major_secret Major secret.
* @param key Buffer to store the AEAD key.
* @param iv Buffer to store the AEAD IV.
*
* @return 0 if the SPDM AEAD key and IV are generated, error code otherwise.
*/
static int spdm_session_manager_generate_aead_key_and_iv (const struct hash_engine *hash_engine,
struct spdm_secure_session *session, enum hmac_hash hmac_hash_type, const uint8_t *major_secret,
uint8_t *key, uint8_t *iv)
{
bool status;
size_t hash_size;
size_t key_length;
size_t iv_length;
uint8_t bin_str5[128];
size_t bin_str5_size;
uint8_t bin_str6[128];
size_t bin_str6_size;
hash_size = session->hash_size;
key_length = session->aead_key_size;
iv_length = session->aead_iv_size;
/* Generate the AEAD key. */
bin_str5_size = sizeof (bin_str5);
spdm_secure_session_manager_bin_concat (session->version, SPDM_BIN_STR_5_LABEL,
sizeof (SPDM_BIN_STR_5_LABEL) - 1, NULL, (uint16_t) key_length, hash_size, bin_str5,
&bin_str5_size);
status = kdf_hkdf_expand (hash_engine, hmac_hash_type, major_secret, hash_size, bin_str5,
bin_str5_size, key, key_length);
if (status != 0) {
goto exit;
}
/* Generate the AEAD IV. */
bin_str6_size = sizeof (bin_str6);
spdm_secure_session_manager_bin_concat (session->version, SPDM_BIN_STR_6_LABEL,
sizeof (SPDM_BIN_STR_6_LABEL) - 1, NULL, (uint16_t) iv_length, hash_size, bin_str6,
&bin_str6_size);
status = kdf_hkdf_expand (hash_engine, hmac_hash_type, major_secret, hash_size, bin_str6,
bin_str6_size, iv, iv_length);
if (status != 0) {
goto exit;
}
exit:
return status;
}
int spdm_secure_session_manager_generate_session_handshake_keys (
const struct spdm_secure_session_manager *session_manager, struct spdm_secure_session *session)
{
int status;
const struct spdm_transcript_manager *transcript_manager;
const struct hash_engine *hash_engine;
uint8_t th1_hash[HASH_MAX_HASH_LEN];
size_t hash_size;
uint8_t bin_str1[128];
size_t bin_str1_size;
uint8_t bin_str2[128];
size_t bin_str2_size;
uint8_t salt0[HASH_MAX_HASH_LEN];
enum hmac_hash hmac_hash_type;
if ((session_manager == NULL) || (session == NULL)) {
status = SPDM_SECURE_SESSION_MANAGER_INVALID_ARGUMENT;
goto exit;
}
transcript_manager = session_manager->transcript_manager;
hash_engine = session_manager->hash_engine;
hash_size = session->hash_size;
/* Step 1: Get the TH hash; do not complete the hash context as it is needed later. */
status = transcript_manager->get_hash (transcript_manager, TRANSCRIPT_CONTEXT_TYPE_TH, false,
true, session->session_index, th1_hash, hash_size);
if (status != 0) {
goto exit;
}
/* Step 2: Use the TH hash to generate the session handshake key. */
hmac_hash_type = (enum hmac_hash) spdm_get_hash_type (session->base_hash_algo);
memset (salt0, 0, sizeof (salt0));
/* Generate HMAC of salt0 with DHE shared secret. */
status = hash_generate_hmac (hash_engine, salt0, session->dhe_key_size,
session->master_secret.dhe_secret, hash_size, hmac_hash_type,
session->master_secret.handshake_secret, hash_size);
if (status != 0) {
goto exit;
}
/* Derive the request handshake secret. */
bin_str1_size = sizeof (bin_str1);
spdm_secure_session_manager_bin_concat (session->version, SPDM_BIN_STR_1_LABEL,
sizeof (SPDM_BIN_STR_1_LABEL) - 1, th1_hash, (uint16_t) hash_size, hash_size, bin_str1,
&bin_str1_size);
status = kdf_hkdf_expand (hash_engine, hmac_hash_type, session->master_secret.handshake_secret,
hash_size, bin_str1, bin_str1_size, session->handshake_secret.request_handshake_secret,
hash_size);
if (status != 0) {
goto exit;
}
/* Derive the response handshake secret. */
bin_str2_size = sizeof (bin_str2);
spdm_secure_session_manager_bin_concat (session->version, SPDM_BIN_STR_2_LABEL,
sizeof (SPDM_BIN_STR_2_LABEL) - 1, th1_hash, (uint16_t) hash_size, hash_size, bin_str2,
&bin_str2_size);
status = kdf_hkdf_expand (hash_engine, hmac_hash_type, session->master_secret.handshake_secret,
hash_size, bin_str2, bin_str2_size, session->handshake_secret.response_handshake_secret,
hash_size);
if (status != 0) {
goto exit;
}
/* Generate the requester finished key. */
status = spdm_secure_session_manager_generate_finished_key (hash_engine, session,
hmac_hash_type, session->handshake_secret.request_handshake_secret,
session->handshake_secret.request_finished_key);
if (status != 0) {
goto exit;
}
/* Generate the responder finished key. */
status = spdm_secure_session_manager_generate_finished_key (hash_engine, session,
hmac_hash_type, session->handshake_secret.response_handshake_secret,
session->handshake_secret.response_finished_key);
if (status != 0) {
goto exit;
}
/* Generate the requester AEAD key and IV. */
status = spdm_session_manager_generate_aead_key_and_iv (hash_engine, session, hmac_hash_type,
session->handshake_secret.request_handshake_secret,
session->handshake_secret.request_handshake_encryption_key,
session->handshake_secret.request_handshake_salt);
if (status != 0) {
goto exit;
}
session->handshake_secret.request_handshake_sequence_number = 0;
/* Generate the responder AEAD key and IV. */
status = spdm_session_manager_generate_aead_key_and_iv (hash_engine, session, hmac_hash_type,
session->handshake_secret.response_handshake_secret,
session->handshake_secret.response_handshake_encryption_key,
session->handshake_secret.response_handshake_salt);
if (status != 0) {
goto exit;
}
session->handshake_secret.response_handshake_sequence_number = 0;
/* Clear the DHE shared secret. */
memset (session->master_secret.dhe_secret, 0, SPDM_MAX_DHE_SHARED_SECRET_SIZE);
exit:
return status;
}
bool spdm_secure_session_manager_is_last_session_id_valid (
const struct spdm_secure_session_manager *session_manager)
{
return session_manager->state->last_spdm_request_secure_session_id_valid;
}
uint32_t spdm_secure_session_manager_get_last_session_id (
const struct spdm_secure_session_manager *session_manager)
{
return session_manager->state->last_spdm_request_secure_session_id;
}
void spdm_secure_session_manager_reset_last_session_id_validity (
const struct spdm_secure_session_manager *session_manager)
{
session_manager->state->last_spdm_request_secure_session_id_valid = false;
}
int spdm_secure_session_manager_generate_session_data_keys (
const struct spdm_secure_session_manager *session_manager, struct spdm_secure_session *session)
{
int status;
size_t hash_size;
uint8_t salt1[HASH_MAX_HASH_LEN];
uint8_t bin_str0[128];
size_t bin_str0_size;
uint8_t bin_str3[128];
size_t bin_str3_size;
uint8_t bin_str4[128];
size_t bin_str4_size;
uint8_t bin_str8[128];
size_t bin_str8_size;
uint8_t zero_filled_buffer[HASH_MAX_HASH_LEN];
enum hmac_hash hmac_hash_type;
const struct hash_engine *hash_engine;
const struct spdm_transcript_manager *transcript_manager;
uint8_t th2_hash[HASH_MAX_HASH_LEN];
if ((session_manager == NULL) || (session == NULL)) {
return SPDM_SECURE_SESSION_MANAGER_INVALID_ARGUMENT;
}
hash_size = session->hash_size;
hmac_hash_type = (enum hmac_hash) spdm_get_hash_type (session->base_hash_algo);
hash_engine = session_manager->hash_engine;
transcript_manager = session_manager->transcript_manager;
/* Derive salt1 key. */
bin_str0_size = sizeof (bin_str0);
spdm_secure_session_manager_bin_concat (session->version, SPDM_BIN_STR_0_LABEL,
sizeof (SPDM_BIN_STR_0_LABEL) - 1, NULL, (uint16_t) hash_size, hash_size, bin_str0,
&bin_str0_size);
status = kdf_hkdf_expand (hash_engine, hmac_hash_type, session->master_secret.handshake_secret,
hash_size, bin_str0, bin_str0_size, salt1, hash_size);
if (status != 0) {
goto exit;
}
/* Generate the master secret. */
memset (zero_filled_buffer, 0, sizeof (zero_filled_buffer));
status = hash_generate_hmac (hash_engine, salt1, hash_size, zero_filled_buffer, hash_size,
hmac_hash_type, session->master_secret.master_secret, hash_size);
if (status != 0) {
goto exit;
}
/* Get the TH hash; do not complete the hash context as it is needed later. */
status = transcript_manager->get_hash (transcript_manager, TRANSCRIPT_CONTEXT_TYPE_TH, false,
true, session->session_index, th2_hash, hash_size);
if (status != 0) {
goto exit;
}
/* Generate the request data secret. */
bin_str3_size = sizeof (bin_str3);
spdm_secure_session_manager_bin_concat (session->version, SPDM_BIN_STR_3_LABEL,
sizeof (SPDM_BIN_STR_3_LABEL) - 1, th2_hash, (uint16_t) hash_size, hash_size, bin_str3,
&bin_str3_size);
status = kdf_hkdf_expand (hash_engine, hmac_hash_type, session->master_secret.master_secret,
hash_size, bin_str3, bin_str3_size, session->data_secret.request_data_secret, hash_size);
if (status != 0) {
goto exit;
}
/* Generate the response_data_secret. */
bin_str4_size = sizeof (bin_str4);
spdm_secure_session_manager_bin_concat (session->version, SPDM_BIN_STR_4_LABEL,
sizeof (SPDM_BIN_STR_4_LABEL) - 1, th2_hash, (uint16_t) hash_size, hash_size, bin_str4,
&bin_str4_size);
status = kdf_hkdf_expand (hash_engine, hmac_hash_type, session->master_secret.master_secret,
hash_size, bin_str4, bin_str4_size, session->data_secret.response_data_secret, hash_size);
if (status != 0) {
goto exit;
}
/* Generate the export master secret. */
bin_str8_size = sizeof (bin_str8);
spdm_secure_session_manager_bin_concat (session->version, SPDM_BIN_STR_8_LABEL,
sizeof (SPDM_BIN_STR_8_LABEL) - 1, th2_hash, (uint16_t) hash_size, hash_size, bin_str8,
&bin_str8_size);
status = kdf_hkdf_expand (hash_engine, hmac_hash_type, session->master_secret.master_secret,
hash_size, bin_str8, bin_str8_size, session->export_master_secret, hash_size);
if (status != 0) {
goto exit;
}
/* Generate the requester data encryption key and IV. */
status = spdm_session_manager_generate_aead_key_and_iv (hash_engine, session, hmac_hash_type,
session->data_secret.request_data_secret, session->data_secret.request_data_encryption_key,
session->data_secret.request_data_salt);
if (status != 0) {
goto exit;
}
session->data_secret.request_data_sequence_number = 0;
/* Generate the responder data encryption key and IV. */
status = spdm_session_manager_generate_aead_key_and_iv (hash_engine, session, hmac_hash_type,
session->data_secret.response_data_secret,
session->data_secret.response_data_encryption_key, session->data_secret.response_data_salt);
if (status != 0) {
goto exit;
}
session->data_secret.response_data_sequence_number = 0;
exit:
/* Zeroize salt1 for security */
memset (salt1, 0, hash_size);
return status;
}
/**
* Generate the AEAD IV for a session.
*
* @param sequence_number The sequence number.
* @param iv The buffer to store the AEAD IV.
* @param salt The salt to use in the AEAD IV generation.
* @param aead_iv_size The size of the AEAD IV.
*/
static void spdm_secure_session_manager_generate_iv (uint64_t sequence_number, uint8_t *iv,
const uint8_t *salt, size_t aead_iv_size)
{
uint8_t iv_temp[SPDM_MAX_AEAD_IV_SIZE];
size_t index;
/* Construct the AEAD IV from the salt and the sequence number. */
memcpy (iv, salt, aead_iv_size);
/*
* Per 'Secured Messages using SPDM' specification, Section 4.2.3,
* sequence number is little-endian, so it is zero-extended to the higher indices.
* The sequence number begins at the lowest index (0). */
memcpy (iv_temp, &sequence_number, sizeof (sequence_number));
for (index = 0; index < sizeof (sequence_number); index++) {
iv[index] = iv[index] ^ iv_temp[index];
}
}
/**
* Decrypt a message using the session's AEAD key and IV.
*
* @param session_manager The SPDM session manager.
* @param session The SPDM session.
* @param request The request message to decrypt.
* @param sequence_number The sequence number.
* @param sequence_num_in_header_size The size of the sequence number in the message header.
* @param key The AEAD key.
* @param salt The AEAD salt.
* @param iv The AEAD IV.
*
* @return 0 if the message is decrypted, error code otherwise.
*/
int spdm_secure_session_manager_decrypt_message (
const struct spdm_secure_session_manager *session_manager, struct spdm_secure_session *session,
struct cmd_interface_msg *request, uint64_t sequence_number,
uint8_t sequence_num_in_header_size, const uint8_t *key, const uint8_t *salt, const uint8_t *iv)
{
int status;
size_t aead_tag_size;
size_t aead_key_size;
size_t aead_iv_size;
size_t record_header_size;
struct spdm_secured_message_data_header_1 *record_header_1;
struct spdm_secured_message_data_header_2 *record_header_2;
struct spdm_secured_message_cipher_header *enc_msg_header;
const uint8_t *add_data;
uint8_t *ciphertext;
size_t plaintext_size;
size_t ciphertext_size;
const uint8_t *tag;
const struct aes_gcm_engine *aes_engine;
UNUSED (sequence_number);
UNUSED (salt);
aead_tag_size = session->aead_tag_size;
aead_key_size = session->aead_key_size;
aead_iv_size = session->aead_iv_size;
aes_engine = session_manager->aes_engine;
record_header_size = sizeof (struct spdm_secured_message_data_header_1) +
sequence_num_in_header_size + sizeof (struct spdm_secured_message_data_header_2);
if (request->payload_length < (record_header_size + aead_tag_size)) {
status = SPDM_SECURE_SESSION_MANAGER_INVALID_MESSAGE_SIZE;
goto exit;
}
record_header_1 = (void*) request->payload;
record_header_2 = (void*) ((uint8_t*) record_header_1 +
sizeof (struct spdm_secured_message_data_header_1) + sequence_num_in_header_size);
if (record_header_2->length > (request->payload_length - record_header_size)) {
status = SPDM_SECURE_SESSION_MANAGER_INVALID_MESSAGE_SIZE;
goto exit;
}
if (record_header_2->length < aead_tag_size) {
status = SPDM_SECURE_SESSION_MANAGER_INVALID_MESSAGE_SIZE;
goto exit;
}
ciphertext_size = (record_header_2->length - aead_tag_size);
if (ciphertext_size > (request->payload_length - (record_header_size + aead_tag_size))) {
status = SPDM_SECURE_SESSION_MANAGER_INVALID_MESSAGE_SIZE;
goto exit;
}
ciphertext = (void*) (record_header_2 + 1);
tag = (const uint8_t*) record_header_1 + record_header_size + ciphertext_size;
add_data = (const uint8_t*) record_header_1; /* Header is also included in MAC calculation. */
status = aes_engine->set_key (aes_engine, key, aead_key_size);
if (status != 0) {
goto exit;
}
status = aes_engine->decrypt_with_add_data (aes_engine, ciphertext, ciphertext_size, tag, iv,
aead_iv_size, add_data, record_header_size, ciphertext, ciphertext_size);
if (status != 0) {
/* Backup keys are valid, fail and alert rollback and retry if possible. */
if (session->requester_backup_valid == true) {
status = SPDM_SECURE_SESSION_MANAGER_SESSION_TRY_DISCARD_KEY_UPDATE;
goto exit;
}
/* [TODO] Set last spdm error. */
goto exit;
}
enc_msg_header = (void*) (record_header_2 + 1);
plaintext_size = enc_msg_header->application_data_length;
if (plaintext_size > ciphertext_size) {
status = SPDM_SECURE_SESSION_MANAGER_INVALID_MESSAGE_SIZE;
goto exit;
}
cmd_interface_msg_remove_protocol_header (request, record_header_size +
sizeof (struct spdm_secured_message_cipher_header));
request->payload_length = plaintext_size;
exit:
return status;
}
int spdm_secure_session_manager_decode_secure_message (
const struct spdm_secure_session_manager *session_manager, struct cmd_interface_msg *request)
{
int status;
struct spdm_secure_session *session;
enum spdm_secure_session_state session_state;
enum spdm_secure_session_type session_type;
size_t aead_iv_size;
const uint8_t *key;
uint8_t *salt;
uint64_t sequence_number;
uint8_t sequence_num_in_header_size;
uint8_t iv[SPDM_MAX_AEAD_IV_SIZE];
struct spdm_secured_message_data_header_1 *secured_message_data_header_1;
struct spdm_secure_session_manager_state *session_manager_state;
if ((session_manager == NULL) || (request == NULL)) {
status = SPDM_SECURE_SESSION_MANAGER_INVALID_ARGUMENT;
goto exit;
}
session_manager_state = session_manager->state;
/* Reset the last secure session id processed. */
session_manager_state->last_spdm_request_secure_session_id = SPDM_INVALID_SESSION_ID;
session_manager_state->last_spdm_request_secure_session_id_valid = false;
/* Get the session Id from the secure message. */
if (request->payload_length < sizeof (struct spdm_secured_message_data_header_1)) {
status = CMD_HANDLER_SPDM_RESPONDER_INVALID_REQUEST;
goto exit;
}
secured_message_data_header_1 = (struct spdm_secured_message_data_header_1*) request->payload;
/* Retrieve the session object. */
session = session_manager->get_session (session_manager,
secured_message_data_header_1->session_id);
if (session == NULL) {
status = CMD_HANDLER_SPDM_RESPONDER_INVALID_REQUEST;
goto exit;
}
session_state = session->session_state;
session_type = session->session_type;
aead_iv_size = session->aead_iv_size;
switch (session_state) {
case SPDM_SESSION_STATE_HANDSHAKING:
key = (const uint8_t*) session->handshake_secret.request_handshake_encryption_key;
salt = (uint8_t*) session->handshake_secret.request_handshake_salt;
sequence_number = session->handshake_secret.request_handshake_sequence_number;
break;
case SPDM_SESSION_STATE_ESTABLISHED:
key = (const uint8_t*) session->data_secret.request_data_encryption_key;
salt = (uint8_t*) session->data_secret.request_data_salt;
sequence_number = session->data_secret.request_data_sequence_number;
break;
default:
status = SPDM_SECURE_SESSION_MANAGER_INTERNAL_ERROR;
goto exit;
}
if (sequence_number >= session_manager->max_spdm_session_sequence_number) {
status = SPDM_SECURE_SESSION_MANAGER_SEQUENCE_NUMBER_OVERFLOW;
goto exit;
}
spdm_secure_session_manager_generate_iv (sequence_number, iv, salt, aead_iv_size);
/* Per CMA-SPDM specification section 6.31.4, sequence number in header should be not present. */
sequence_num_in_header_size = 0;
if (session_state == SPDM_SESSION_STATE_HANDSHAKING) {
session->handshake_secret.request_handshake_sequence_number++;
}
else {
session->data_secret.request_data_sequence_number++;
}
switch (session_type) {
case SPDM_SESSION_TYPE_ENC_MAC:
status = spdm_secure_session_manager_decrypt_message (session_manager, session, request,
sequence_number, sequence_num_in_header_size, key, salt, iv);
break;
case SPDM_SESSION_TYPE_MAC_ONLY:
default:
status = SPDM_SECURE_SESSION_MANAGER_UNSUPPORTED_CAPABILITY;
goto exit;
}
session_manager_state->last_spdm_request_secure_session_id = session->session_id;
session_manager_state->last_spdm_request_secure_session_id_valid = true;
exit:
return status;
}
/**
* Encrypt a message using the session's AEAD key and IV.
*
* @param session_manager The SPDM session manager.
* @param session The SPDM session.
* @param request The message to encrypt.
* @param sequence_num_in_header_size The size of the sequence number in the message header.
* @param key The AEAD key.
* @param iv The AEAD IV.
*
* @return 0 if the message is encrypted, error code otherwise.
*/
static int spdm_secure_session_manager_encrypt_message (
const struct spdm_secure_session_manager *session_manager, struct spdm_secure_session *session,
struct cmd_interface_msg *request, uint8_t sequence_num_in_header_size, const uint8_t *key,
const uint8_t *iv)
{
int status;
size_t record_header_size;
uint32_t random_data_size;
size_t total_secured_message_size;
size_t plaintext_size;
size_t application_data_length;
size_t ciphertext_size;
size_t aead_tag_size;
size_t aead_key_size;
size_t aead_iv_size;
struct spdm_secured_message_data_header_1 *record_header_1;
struct spdm_secured_message_data_header_2 *record_header_2;
struct spdm_secured_message_cipher_header *enc_msg_header;
const struct aes_gcm_engine *aes_engine;
uint8_t *tag;
uint8_t *add_data;
aead_tag_size = session->aead_tag_size;
aead_key_size = session->aead_key_size;
aead_iv_size = session->aead_iv_size;
aes_engine = session_manager->aes_engine;
record_header_size = sizeof (struct spdm_secured_message_data_header_1) +
sequence_num_in_header_size +
sizeof (struct spdm_secured_message_data_header_2);
/* Per CMA-SPDM specification section 6.31.4, random data must be not present. */
random_data_size = 0;
application_data_length = request->payload_length;
plaintext_size = sizeof (struct spdm_secured_message_cipher_header) + request->payload_length +
random_data_size;
ciphertext_size = plaintext_size;
total_secured_message_size = record_header_size + ciphertext_size + aead_tag_size;
if (request->max_response < total_secured_message_size) {
status = SPDM_SECURE_SESSION_MANAGER_BUFFER_TOO_SMALL;
goto exit;
}
/* Move the payload to accomodate the record headers and sequence number. */
cmd_interface_msg_add_protocol_header (request,
record_header_size + sizeof (struct spdm_secured_message_cipher_header));
record_header_1 = (void*) request->payload;
record_header_2 = (void*) ((uint8_t*) record_header_1 +
sizeof (struct spdm_secured_message_data_header_1) +
sequence_num_in_header_size);
record_header_1->session_id = session->session_id;
record_header_2->length = (uint16_t) (ciphertext_size + aead_tag_size);
enc_msg_header = (void*) (record_header_2 + 1);
enc_msg_header->application_data_length = (uint16_t) application_data_length;
tag = (uint8_t*) record_header_1 + record_header_size + plaintext_size;
add_data = (uint8_t*) record_header_1;
/* Set the encryption key. */
status = aes_engine->set_key (aes_engine, key, aead_key_size);
if (status != 0) {
goto exit;
}
status = aes_engine->encrypt_with_add_data (aes_engine, (const uint8_t*) enc_msg_header,
plaintext_size, iv, aead_iv_size, add_data, record_header_size, (uint8_t*) enc_msg_header,
ciphertext_size, tag, aead_tag_size);
if (status != 0) {
goto exit;
}
/* Set the payload size. */
cmd_interface_msg_set_message_payload_length (request, total_secured_message_size);
exit:
return status;
}
int spdm_secure_session_manager_encode_secure_message (
const struct spdm_secure_session_manager *session_manager, struct cmd_interface_msg *request)
{
int status;
enum spdm_secure_session_state session_state;
enum spdm_secure_session_type session_type;
const uint8_t *key;
uint8_t *salt;
size_t aead_iv_size;
uint8_t iv[SPDM_MAX_AEAD_IV_SIZE];
uint64_t sequence_number;
uint8_t sequence_num_in_header_size;
struct spdm_secure_session *session;
uint8_t req_rsp_code;
if ((session_manager == NULL) || (request == NULL)) {
status = SPDM_SECURE_SESSION_MANAGER_INVALID_ARGUMENT;
goto exit;
}
if (session_manager->state->last_spdm_request_secure_session_id_valid == false) {
status = SPDM_SECURE_SESSION_MANAGER_INTERNAL_ERROR;
goto exit;
}
session = session_manager->get_session (session_manager,
session_manager->state->last_spdm_request_secure_session_id);
if (session == NULL) {
status = SPDM_SECURE_SESSION_MANAGER_INTERNAL_ERROR;
goto exit;
}
session_state = session->session_state;
aead_iv_size = session->aead_iv_size;
session_type = session->session_type;
switch (session_state) {
case SPDM_SESSION_STATE_HANDSHAKING:
key = (const uint8_t*) session->handshake_secret.response_handshake_encryption_key;
salt = (uint8_t*) session->handshake_secret.response_handshake_salt;
sequence_number = session->handshake_secret.response_handshake_sequence_number;
break;
case SPDM_SESSION_STATE_ESTABLISHED:
key = (const uint8_t*) session->data_secret.response_data_encryption_key;
salt = (uint8_t*) session->data_secret.response_data_salt;
sequence_number = session->data_secret.response_data_sequence_number;
break;
default:
status = SPDM_SECURE_SESSION_MANAGER_INTERNAL_ERROR;
goto exit;
}
if (sequence_number >= session_manager->max_spdm_session_sequence_number) {
status = SPDM_SECURE_SESSION_MANAGER_SEQUENCE_NUMBER_OVERFLOW;
goto exit;
}
spdm_secure_session_manager_generate_iv (sequence_number, iv, salt, aead_iv_size);
/* Per CMA-SPDM specification section 6.31.4, sequence number in header should be not present. */
sequence_num_in_header_size = 0;
if (session_state == SPDM_SESSION_STATE_HANDSHAKING) {
session->handshake_secret.response_handshake_sequence_number++;
}
else {
session->data_secret.response_data_sequence_number++;
}
/* Get the req_rsp_code before encrypting the SPDM message. */
req_rsp_code = ((struct spdm_protocol_header*) request->payload)->req_rsp_code;
switch (session_type) {
case SPDM_SESSION_TYPE_ENC_MAC:
status = spdm_secure_session_manager_encrypt_message (session_manager, session, request,
sequence_num_in_header_size, key, iv);
break;
case SPDM_SESSION_TYPE_MAC_ONLY:
default:
status = SPDM_SECURE_SESSION_MANAGER_UNSUPPORTED_CAPABILITY;
goto exit;
}
if (status == 0) {
switch (req_rsp_code) {
case SPDM_RESPONSE_FINISH:
/* Change session state regardless of handshake type (in clear vs not)*/
spdm_secure_session_manager_set_session_state (session_manager, session->session_id,
SPDM_SESSION_STATE_ESTABLISHED);
break;
case SPDM_RESPONSE_END_SESSION:
session_manager->release_session (session_manager, session->session_id);
break;
}
}
exit:
return status;
}
/**
* Initialize the Session Manager.
*
* @param session_manager SPDM Session Manager to initialize.
* @param state SPDM Session Manager state.
* @param local_capabilities Local capabilities.
* @param local_algorithms Local algorithms.
* @param aes AES engine.
* @param hash Hashing engine.
* @param rng RNG engine.
* @param ecc ECC engine.
* @param transcript_manager Transcript Manager.
*
* @return 0 if the session manager is initialized successfully, error code otherwise.
*/
int spdm_secure_session_manager_init (struct spdm_secure_session_manager *session_manager,
struct spdm_secure_session_manager_state *state,
const struct spdm_device_capability *local_capabilities,
const struct spdm_device_algorithms *local_algorithms, const struct aes_gcm_engine *aes_engine,
const struct hash_engine *hash_engine, const struct rng_engine *rng_engine,
const struct ecc_engine *ecc_engine, const struct spdm_transcript_manager *transcript_manager)
{
int status;
if (session_manager == NULL) {
status = SPDM_SECURE_SESSION_MANAGER_INVALID_ARGUMENT;
goto exit;
}
memset (session_manager, 0, sizeof (struct spdm_secure_session_manager));
session_manager->state = state;
session_manager->local_capabilities = local_capabilities;
session_manager->local_algorithms = local_algorithms;
session_manager->aes_engine = aes_engine;
session_manager->hash_engine = hash_engine;
session_manager->rng_engine = rng_engine;
session_manager->ecc_engine = ecc_engine;
session_manager->transcript_manager = transcript_manager;
session_manager->max_spdm_session_sequence_number = SPDM_MAX_SECURE_SESSION_SEQUENCE_NUMBER;
session_manager->create_session = spdm_secure_session_manager_create_session;
session_manager->release_session = spdm_secure_session_manager_release_session;
session_manager->set_session_state = spdm_secure_session_manager_set_session_state;
session_manager->reset = spdm_secure_session_manager_reset;
session_manager->get_session = spdm_secure_session_manager_get_session;
session_manager->generate_shared_secret = spdm_secure_session_manager_generate_shared_secret;
session_manager->generate_session_handshake_keys =
spdm_secure_session_manager_generate_session_handshake_keys;
session_manager->generate_session_data_keys =
spdm_secure_session_manager_generate_session_data_keys;
session_manager->is_last_session_id_valid =
spdm_secure_session_manager_is_last_session_id_valid;
session_manager->get_last_session_id = spdm_secure_session_manager_get_last_session_id;
session_manager->reset_last_session_id_validity =
spdm_secure_session_manager_reset_last_session_id_validity;
session_manager->decode_secure_message = spdm_secure_session_manager_decode_secure_message;
session_manager->encode_secure_message = spdm_secure_session_manager_encode_secure_message;
status = spdm_secure_session_manager_init_state (session_manager);
exit:
return status;
}
/**
* Release the Session Manager.
*
* @param session_manager SPDM Session Manager to release.
*/
void spdm_secure_session_manager_release (const struct spdm_secure_session_manager *session_manager)
{
UNUSED (session_manager);
}
/**
* Initialize the Session Manager state.
*
* @param session_manager Session manager whose state is to be initialized.
*
* @return 0 if a session manager state was initialize successfully or an error code.
*/
int spdm_secure_session_manager_init_state (
const struct spdm_secure_session_manager *session_manager)
{
int status = 0;
struct spdm_secure_session_manager_state *state;
if ((session_manager == NULL) || (session_manager->state == NULL) ||
(session_manager->local_capabilities == NULL) ||
(session_manager->local_algorithms == NULL) ||
(session_manager->aes_engine == NULL) || (session_manager->hash_engine == NULL) ||
(session_manager->rng_engine == NULL) || (session_manager->ecc_engine == NULL) ||
(session_manager->transcript_manager == NULL) ||
(session_manager->max_spdm_session_sequence_number == 0)) {
status = SPDM_SECURE_SESSION_MANAGER_INVALID_ARGUMENT;
goto exit;
}
state = session_manager->state;
memset (state, 0, sizeof (struct spdm_secure_session_manager_state));
/* Initialize the state. */
state->last_spdm_request_secure_session_id = SPDM_INVALID_SESSION_ID;
state->last_spdm_request_secure_session_id_valid = false;
exit:
return status;
}