mbed-os/source/Security/protocols/key_sec_prot/key_sec_prot.c

435 lines
14 KiB
C

/*
* Copyright (c) 2016-2019, Arm Limited and affiliates.
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "nsconfig.h"
#include <string.h>
#include "ns_types.h"
#include "ns_list.h"
#include "ns_trace.h"
#include "nsdynmemLIB.h"
#include "fhss_config.h"
#include "NWK_INTERFACE/Include/protocol.h"
#include "6LoWPAN/ws/ws_config.h"
#include "Security/protocols/sec_prot_cfg.h"
#include "Security/kmp/kmp_addr.h"
#include "Security/kmp/kmp_api.h"
#include "Security/PANA/pana_eap_header.h"
#include "Security/eapol/eapol_helper.h"
#include "Security/eapol/kde_helper.h"
#include "Security/protocols/sec_prot_certs.h"
#include "Security/protocols/sec_prot_keys.h"
#include "Security/protocols/sec_prot.h"
#include "Security/protocols/sec_prot_lib.h"
#include "Security/protocols/key_sec_prot/key_sec_prot.h"
#ifdef HAVE_WS
#define TRACE_GROUP "ksep"
#define KEY_SEC_FINISHED_TIMEOUT 1 // Finishes right away
typedef enum {
KEY_STATE_INIT = SEC_STATE_INIT,
KEY_STATE_CREATE_REQ = SEC_STATE_CREATE_REQ,
KEY_STATE_CREATE_RESP = SEC_STATE_CREATE_RESP,
KEY_STATE_TX_DONE = SEC_STATE_FIRST,
KEY_STATE_INITIAL_KEY_RECEIVED,
KEY_STATE_FINISH = SEC_STATE_FINISH,
KEY_STATE_FINISHED = SEC_STATE_FINISHED
} key_sec_prot_state_e;
typedef struct {
sec_prot_common_t common; /**< Common data */
} key_sec_prot_int_t;
static uint16_t key_sec_prot_size(void);
static int8_t supp_key_sec_prot_init(sec_prot_t *prot);
static int8_t auth_key_sec_prot_init(sec_prot_t *prot);
static void key_sec_prot_create_request(sec_prot_t *prot, sec_prot_keys_t *sec_keys);
static void key_sec_prot_create_response(sec_prot_t *prot, sec_prot_result_e result);
static void key_sec_prot_delete(sec_prot_t *prot);
static int8_t key_sec_prot_initial_key_send(sec_prot_t *prot, sec_prot_keys_t *sec_keys);
static int8_t key_sec_prot_receive(sec_prot_t *prot, void *pdu, uint16_t size);
static int8_t key_sec_prot_tx_status_ind(sec_prot_t *prot, sec_prot_tx_status_e tx_status);
static void key_sec_prot_timer_timeout(sec_prot_t *prot, uint16_t ticks);
static void supp_key_sec_prot_state_machine(sec_prot_t *prot);
static void auth_key_sec_prot_state_machine(sec_prot_t *prot);
#define key_sec_prot_get(prot) (key_sec_prot_int_t *) &prot->data
int8_t supp_key_sec_prot_register(kmp_service_t *service)
{
if (!service) {
return -1;
}
if (kmp_service_sec_protocol_register(service, IEEE_802_1X_MKA_KEY, key_sec_prot_size, supp_key_sec_prot_init) < 0) {
return -1;
}
if (kmp_service_sec_protocol_register(service, IEEE_802_11_GKH_KEY, key_sec_prot_size, supp_key_sec_prot_init) < 0) {
return -1;
}
return 0;
}
int8_t auth_key_sec_prot_register(kmp_service_t *service)
{
if (!service) {
return -1;
}
if (kmp_service_sec_protocol_register(service, IEEE_802_1X_MKA_KEY, key_sec_prot_size, auth_key_sec_prot_init) < 0) {
return -1;
}
if (kmp_service_sec_protocol_register(service, IEEE_802_11_GKH_KEY, key_sec_prot_size, auth_key_sec_prot_init) < 0) {
return -1;
}
return 0;
}
static uint16_t key_sec_prot_size(void)
{
return sizeof(key_sec_prot_int_t);
}
static int8_t supp_key_sec_prot_init(sec_prot_t *prot)
{
prot->create_req = key_sec_prot_create_request;
prot->tx_status_ind = key_sec_prot_tx_status_ind;
prot->delete = key_sec_prot_delete;
prot->state_machine = supp_key_sec_prot_state_machine;
prot->timer_timeout = key_sec_prot_timer_timeout;
key_sec_prot_int_t *data = key_sec_prot_get(prot);
sec_prot_init(&data->common);
sec_prot_state_set(prot, &data->common, KEY_STATE_INIT);
return 0;
}
static int8_t auth_key_sec_prot_init(sec_prot_t *prot)
{
prot->create_resp = key_sec_prot_create_response;
prot->receive = key_sec_prot_receive;
prot->delete = key_sec_prot_delete;
prot->state_machine = auth_key_sec_prot_state_machine;
prot->timer_timeout = key_sec_prot_timer_timeout;
key_sec_prot_int_t *data = key_sec_prot_get(prot);
sec_prot_init(&data->common);
sec_prot_state_set(prot, &data->common, KEY_STATE_INIT);
return 0;
}
static void key_sec_prot_delete(sec_prot_t *prot)
{
(void) prot;
}
static void key_sec_prot_create_request(sec_prot_t *prot, sec_prot_keys_t *sec_keys)
{
(void) sec_keys;
prot->state_machine_call(prot);
}
static void key_sec_prot_create_response(sec_prot_t *prot, sec_prot_result_e result)
{
key_sec_prot_int_t *data = key_sec_prot_get(prot);
sec_prot_state_set(prot, &data->common, KEY_STATE_CREATE_RESP);
sec_prot_result_set(&data->common, result);
prot->state_machine_call(prot);
}
static int8_t key_sec_prot_initial_key_send(sec_prot_t *prot, sec_prot_keys_t *sec_keys)
{
uint8_t result = 0;
uint16_t kde_len = KDE_GTKL_LEN;
uint8_t *pmk = sec_prot_keys_pmk_get(sec_keys);
uint8_t pmkid[PMKID_LEN];
if (pmk) {
if (sec_prot_lib_pmkid_generate(prot, pmkid, false, false, NULL) >= 0) {
kde_len += KDE_PMKID_LEN;
} else {
pmk = NULL;
}
}
uint8_t *ptk = sec_prot_keys_ptk_get(sec_keys);
uint8_t ptkid[PTKID_LEN];
if (ptk) {
if (sec_prot_lib_ptkid_generate(prot, ptkid, false) >= 0) {
kde_len += KDE_PTKID_LEN;
} else {
ptk = NULL;
}
}
uint8_t *kde_start = ns_dyn_mem_temporary_alloc(kde_len);
if (!kde_start) {
return -1;
}
uint8_t *kde_end = kde_start;
if (pmk) {
kde_end = kde_pmkid_write(kde_end, pmkid);
}
if (ptk) {
kde_end = kde_ptkid_write(kde_end, ptkid);
}
uint8_t gtkl = sec_prot_keys_fresh_gtkl_get(sec_keys->gtks);
kde_end = kde_gtkl_write(kde_end, gtkl);
kde_len = kde_end - kde_start;
eapol_pdu_t eapol_pdu;
uint16_t eapol_pdu_size = eapol_pdu_key_frame_init(&eapol_pdu, kde_len, kde_start);
uint8_t *eapol_decoded_data = ns_dyn_mem_temporary_alloc(eapol_pdu_size + prot->header_size); // In future fill with data that defines eapol message
if (!eapol_decoded_data) {
result = -1;
goto initial_key_exit;
}
eapol_pdu.msg.key.key_information.install = false;
eapol_pdu.msg.key.key_information.pairwise_key = false;
eapol_pdu.msg.key.key_information.request = true;
eapol_pdu.msg.key.replay_counter = 0;
eapol_pdu.msg.key.key_length = 0;
eapol_write_pdu_frame(eapol_decoded_data + prot->header_size, &eapol_pdu);
tr_info("Initial EAPOL-Key send, PMKID %s PTKID %s GTKL %x", pmk ? "set" : "not set", ptk ? "set" : "not set", gtkl);
if (prot->send(prot, eapol_decoded_data, eapol_pdu_size + prot->header_size) < 0) {
result = -1;
}
initial_key_exit:
ns_dyn_mem_free(kde_start);
return result;
}
static int8_t key_sec_prot_receive(sec_prot_t *prot, void *pdu, uint16_t size)
{
eapol_pdu_t eapol_pdu;
key_sec_prot_int_t *data = key_sec_prot_get(prot);
sec_prot_result_e result = SEC_RESULT_OK;
tr_info("Initial EAPOL-Key recv, eui-64: %s", trace_array(sec_prot_remote_eui_64_addr_get(prot), 8));
// Decoding is successful
if (eapol_parse_pdu_header(pdu, size, &eapol_pdu)) {
if (eapol_pdu.packet_type != EAPOL_KEY_TYPE) {
tr_info("not EAPOL-Key packet");
prot->finished(prot);
return -1;
}
uint16_t kde_len;
uint8_t *kde = sec_prot_lib_message_handle(prot->sec_keys->ptk, &kde_len, &eapol_pdu);
if (!kde) {
tr_error("no KDEs");
prot->finished(prot);
return -1;
}
// Default assumption is that PMK and PTK are not valid
prot->sec_keys->pmk_mismatch = true;
prot->sec_keys->ptk_mismatch = true;
// Checks if supplicant indicates that it has valid PMK
uint8_t remote_keyid[KEYID_LEN];
if (kde_pmkid_read(kde, kde_len, remote_keyid) >= 0) {
tr_debug("recv PMKID: %s", trace_array(remote_keyid, 16));
uint8_t pmkid[PMKID_LEN];
if (sec_prot_lib_pmkid_generate(prot, pmkid, true, false, NULL) >= 0) {
if (memcmp(remote_keyid, pmkid, PMKID_LEN) == 0) {
prot->sec_keys->pmk_mismatch = false;
}
}
}
// Checks if supplicant indicates that it has valid PTK
if (kde_ptkid_read(kde, kde_len, remote_keyid) >= 0) {
tr_debug("recv PTKID: %s", trace_array(remote_keyid, 16));
uint8_t ptkid[PTKID_LEN];
if (sec_prot_lib_ptkid_generate(prot, ptkid, true) >= 0) {
if (memcmp(remote_keyid, ptkid, PTKID_LEN) == 0) {
prot->sec_keys->ptk_mismatch = false;
}
}
}
// Get the GTKL that supplicant indicates
uint8_t gtkl;
if (kde_gtkl_read(kde, kde_len, &gtkl) >= 0) {
prot->sec_keys->gtkl = gtkl;
} else {
tr_error("no GTKL");
return -1;
}
tr_info("PMK %s PTK %s GTKL %x", prot->sec_keys->pmk_mismatch ? "not live" : "live", prot->sec_keys->ptk_mismatch ? "not live" : "live", gtkl);
ns_dyn_mem_free(kde);
} else {
tr_error("Invalid");
result = SEC_RESULT_ERROR;
}
sec_prot_result_set(&data->common, result);
prot->state_machine(prot);
if (result != SEC_RESULT_OK) {
return -1;
}
return 0;
}
static int8_t key_sec_prot_tx_status_ind(sec_prot_t *prot, sec_prot_tx_status_e tx_status)
{
key_sec_prot_int_t *data = key_sec_prot_get(prot);
// Indicates TX failure
if (tx_status == SEC_PROT_TX_ERR_TX_NO_ACK) {
sec_prot_result_set(&data->common, SEC_RESULT_ERR_TX_NO_ACK);
} else if (tx_status != SEC_PROT_TX_OK) {
// Indicates other failure
sec_prot_result_set(&data->common, SEC_RESULT_ERR_UNSPEC);
}
prot->state_machine_call(prot);
return 0;
}
static void key_sec_prot_timer_timeout(sec_prot_t *prot, uint16_t ticks)
{
key_sec_prot_int_t *data = key_sec_prot_get(prot);
sec_prot_timer_timeout_handle(prot, &data->common, NULL, ticks);
}
static void supp_key_sec_prot_state_machine(sec_prot_t *prot)
{
key_sec_prot_int_t *data = key_sec_prot_get(prot);
switch (sec_prot_state_get(&data->common)) {
case KEY_STATE_INIT:
tr_info("Initial-key init");
sec_prot_state_set(prot, &data->common, KEY_STATE_CREATE_REQ);
prot->timer_start(prot);
break;
case KEY_STATE_CREATE_REQ:
// KMP-CREATE.confirm
prot->create_conf(prot, sec_prot_result_get(&data->common));
// Send initial-key message
if (key_sec_prot_initial_key_send(prot, prot->sec_keys) < 0) {
// Error on sending, ready to be deleted
sec_prot_state_set(prot, &data->common, KEY_STATE_FINISH);
return;
}
// Waits for TX acknowledge
sec_prot_state_set(prot, &data->common, KEY_STATE_TX_DONE);
break;
case KEY_STATE_TX_DONE:
sec_prot_state_set(prot, &data->common, KEY_STATE_FINISH);
break;
case KEY_STATE_FINISH:
// KMP-FINISHED.indication,
prot->finished_ind(prot, sec_prot_result_get(&data->common), 0);
sec_prot_state_set(prot, &data->common, KEY_STATE_FINISHED);
data->common.ticks = KEY_SEC_FINISHED_TIMEOUT;
break;
case KEY_STATE_FINISHED:
tr_info("Initial-key finished");
prot->timer_stop(prot);
prot->finished(prot);
break;
default:
break;
}
}
static void auth_key_sec_prot_state_machine(sec_prot_t *prot)
{
key_sec_prot_int_t *data = key_sec_prot_get(prot);
switch (sec_prot_state_get(&data->common)) {
case KEY_STATE_INIT:
tr_info("Initial-key init");
sec_prot_state_set(prot, &data->common, KEY_STATE_INITIAL_KEY_RECEIVED);
prot->timer_start(prot);
break;
case KEY_STATE_INITIAL_KEY_RECEIVED:
if (!sec_prot_result_ok_check(&data->common)) {
// Goes right away to finished
sec_prot_state_set(prot, &data->common, KEY_STATE_FINISHED);
return;
}
// Send KMP-CREATE.indication
prot->create_ind(prot);
sec_prot_state_set(prot, &data->common, KEY_STATE_CREATE_RESP);
break;
case KEY_STATE_CREATE_RESP:
// Goes to finish state right away
sec_prot_state_set(prot, &data->common, KEY_STATE_FINISH);
break;
case KEY_STATE_FINISH:
// KMP-FINISHED.indication,
prot->finished_ind(prot, sec_prot_result_get(&data->common), 0);
sec_prot_state_set(prot, &data->common, KEY_STATE_FINISHED);
data->common.ticks = KEY_SEC_FINISHED_TIMEOUT;
break;
case KEY_STATE_FINISHED: {
tr_info("Initial-key finished, eui-64: %s", trace_array(sec_prot_remote_eui_64_addr_get(prot), 8));
prot->finished(prot);
break;
}
default:
break;
}
}
#endif /* HAVE_WS */