core/cmd_interface/session_manager_ecc.c (188 lines of code) (raw):
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include "cerberus_protocol_optional_commands.h"
#include "session_manager.h"
#include "session_manager_ecc.h"
#include "crypto/ecc.h"
#include "crypto/ecdsa.h"
#include "crypto/hash.h"
#include "crypto/kdf.h"
#include "riot/riot_key_manager.h"
static int session_manager_ecc_establish_session (struct session_manager *session,
struct cmd_interface_msg *request)
{
struct session_manager_ecc *session_mgr = (struct session_manager_ecc*) session;
struct cerberus_protocol_key_exchange_type_0 *rq;
struct cerberus_protocol_key_exchange_response_type_0 *rsp;
struct session_manager_entry *curr_session;
struct ecc_private_key session_priv_key;
struct ecc_public_key session_pub_key;
struct ecc_public_key device_pub_key;
const struct riot_keys *keys;
uint8_t *session_pub_key_der;
uint8_t *shared_secret;
uint16_t hash_len;
size_t session_pub_key_der_len;
int shared_secret_len;
int sig_len;
int status;
if ((session_mgr == NULL) || (request == NULL)) {
return SESSION_MANAGER_INVALID_ARGUMENT;
}
if (request->length <= cerberus_protocol_key_exchange_type_0_length (0)) {
return SESSION_MANAGER_INVALID_REQUEST;
}
if (request->max_response <= sizeof (struct cerberus_protocol_key_exchange_response)) {
return SESSION_MANAGER_BUF_TOO_SMALL;
}
curr_session = (struct session_manager_entry*) session_manager_get_session (&session_mgr->base,
request->source_eid);
if (curr_session != NULL) {
if (curr_session->session_state != SESSION_STATE_SETUP) {
return SESSION_MANAGER_INVALID_ORDER;
}
}
else {
return SESSION_MANAGER_UNEXPECTED_EID;
}
rq = (struct cerberus_protocol_key_exchange_type_0*) request->data;
rsp = (struct cerberus_protocol_key_exchange_response_type_0*) request->data;
if (rq->hmac_type == CERBERUS_PROTOCOL_HMAC_SHA256) {
curr_session->hmac_hash_type = HMAC_SHA256;
hash_len = SHA256_HASH_LENGTH;
}
else {
return SESSION_MANAGER_OPERATION_UNSUPPORTED;
}
status = session_mgr->ecc->init_public_key (session_mgr->ecc,
cerberus_protocol_key_exchange_type_0_key_data (rq),
cerberus_protocol_key_exchange_type_0_key_len (request), &device_pub_key);
if (status != 0) {
return status;
}
status = session_mgr->ecc->generate_key_pair (session_mgr->ecc, ECC_KEY_LENGTH_256,
&session_priv_key, &session_pub_key);
if (status != 0) {
goto free_device_key;
}
status = session_mgr->ecc->get_public_key_der (session_mgr->ecc, &session_pub_key,
&session_pub_key_der, &session_pub_key_der_len);
if (status != 0) {
goto free_session_keys;
}
status = session_manager_generate_keys_digest (&session_mgr->base,
cerberus_protocol_key_exchange_type_0_key_data (rq),
cerberus_protocol_key_exchange_type_0_key_len (request), session_pub_key_der,
session_pub_key_der_len);
if (status != 0) {
goto free_session_pub_der;
}
rsp->reserved = 0;
if (CERBERUS_PROTOCOL_KEY_EXCHANGE_TYPE_0_RESPONSE_MAX_KEY_DATA (request) <=
session_pub_key_der_len) {
status = SESSION_MANAGER_BUF_TOO_SMALL;
goto free_session_pub_der;
}
memcpy (cerberus_protocol_key_exchange_type_0_response_key_data (rsp), session_pub_key_der,
session_pub_key_der_len);
rsp->key_len = (uint16_t) session_pub_key_der_len;
keys = riot_key_manager_get_riot_keys (session_mgr->base.riot);
if (keys == NULL) {
status = SESSION_MANAGER_INVALID_ARGUMENT;
goto free_session_pub_der;
}
sig_len = ecdsa_sign_hash_and_finish (session_mgr->ecc, session_mgr->base.hash, NULL,
keys->alias_key, keys->alias_key_length,
cerberus_protocol_key_exchange_type_0_response_sig_data (rsp),
CERBERUS_PROTOCOL_KEY_EXCHANGE_TYPE_0_RESPONSE_MAX_SIG_DATA (request));
riot_key_manager_release_riot_keys (session_mgr->base.riot, keys);
if (ROT_IS_ERROR (sig_len)) {
status = sig_len;
goto free_session_pub_der;
}
cerberus_protocol_key_exchange_type_0_response_sig_len (rsp) = (uint16_t) sig_len;
status = session_mgr->ecc->get_shared_secret_max_length (session_mgr->ecc, &session_priv_key);
if (ROT_IS_ERROR (status)) {
goto free_session_pub_der;
}
shared_secret = platform_malloc (status);
if (shared_secret == NULL) {
status = SESSION_MANAGER_NO_MEMORY;
goto free_session_pub_der;
}
shared_secret_len = session_mgr->ecc->compute_shared_secret (session_mgr->ecc,
&session_priv_key, &device_pub_key, shared_secret, status);
if (ROT_IS_ERROR (shared_secret_len)) {
status = shared_secret_len;
goto free_shared_secret;
}
status = kdf_nist800_108_counter_mode (session_mgr->base.hash, HMAC_SHA256, shared_secret,
shared_secret_len, curr_session->device_nonce, sizeof (curr_session->device_nonce),
curr_session->cerberus_nonce, sizeof (curr_session->cerberus_nonce),
curr_session->session_key, sizeof (curr_session->session_key));
if (status != 0) {
goto free_shared_secret;
}
status = kdf_nist800_108_counter_mode (session_mgr->base.hash, HMAC_SHA256, shared_secret,
shared_secret_len, curr_session->cerberus_nonce, sizeof (curr_session->cerberus_nonce),
curr_session->device_nonce, sizeof (curr_session->device_nonce), curr_session->hmac_key,
sizeof (curr_session->hmac_key));
if (status != 0) {
goto free_shared_secret;
}
status = hash_generate_hmac (session_mgr->base.hash, curr_session->hmac_key,
sizeof (curr_session->hmac_key), keys->alias_cert, keys->alias_cert_length,
curr_session->hmac_hash_type,
cerberus_protocol_key_exchange_type_0_response_hmac_data (rsp),
CERBERUS_PROTOCOL_KEY_EXCHANGE_TYPE_0_RESPONSE_MAX_HMAC_DATA (request));
if (status != 0) {
goto free_shared_secret;
}
cerberus_protocol_key_exchange_type_0_response_hmac_len (rsp) = hash_len;
curr_session->session_state = SESSION_STATE_ESTABLISHED;
request->length =
cerberus_protocol_key_exchange_type_0_response_length (session_pub_key_der_len, sig_len,
hash_len);
request->crypto_timeout = true;
free_shared_secret:
platform_free (shared_secret);
free_session_pub_der:
platform_free (session_pub_key_der);
free_session_keys:
session_mgr->ecc->release_key_pair (session_mgr->ecc, &session_priv_key, &session_pub_key);
free_device_key:
session_mgr->ecc->release_key_pair (session_mgr->ecc, NULL, &device_pub_key);
return status;
}
/**
* Initialize session manager instance
*
* @param session Session manager instance to initialize.
* @param aes AES engine to utilize for packet encryption/decryption.
* @param ecc ECC engine to utilize for AES key generation.
* @param hash Hash engine to utilize for AES key generation.
* @param riot RIoT key manager to utilize to get alias key for AES key generation.
* @param sessions_table Preallocated table to use to store session manager entries. Set to NULL to
* dynamically allocate from heap.
* @param num_sessions Number of sessions to support.
* @param pairing_eids List of supported devices for pairing mode.
* @param num_pairing_eids Total number of supported devices for pairing mode.
* @param store Keystore used to persist pairing keys.
*
* @return Initialization status, 0 if success or an error code.
*/
int session_manager_ecc_init (struct session_manager_ecc *session, const struct aes_gcm_engine *aes,
const struct ecc_engine *ecc, const struct hash_engine *hash,
const struct riot_key_manager *riot, struct session_manager_entry *sessions_table,
size_t num_sessions, const uint8_t *pairing_eids, size_t num_pairing_eids,
const struct keystore *store)
{
int status;
if ((session == NULL) || (ecc == NULL)) {
return SESSION_MANAGER_INVALID_ARGUMENT;
}
status = session_manager_init (&session->base, aes, hash, riot, sessions_table, num_sessions,
pairing_eids, num_pairing_eids, store);
if (status == 0) {
session->base.add_session = session_manager_add_session;
session->base.establish_session = session_manager_ecc_establish_session;
session->base.is_session_established = session_manager_is_session_established;
session->base.get_pairing_state = session_manager_get_pairing_state;
session->base.decrypt_message = session_manager_decrypt_message;
session->base.encrypt_message = session_manager_encrypt_message;
session->base.reset_session = session_manager_reset_session;
session->base.setup_paired_session = session_manager_setup_paired_session;
session->base.session_sync = session_manager_session_sync;
session->ecc = ecc;
}
return status;
}
/**
* Release session manager
*
* @param session Session manager instance to release
*/
void session_manager_ecc_release (struct session_manager_ecc *session)
{
session_manager_release (&session->base);
}