From d0c777b2e1ded49f59d4d2b41e7f85eb32c7292a Mon Sep 17 00:00:00 2001 From: Amey Inamdar Date: Mon, 30 Jul 2018 21:33:10 +0530 Subject: [PATCH] Protocomm : Added component core for protocol communication * This manages secure sessions and provides framework for multiple transports. * The application can use protocomm layer directly to have application specific extensions for provisioning (or non-provisioning) use cases. * Following features are available for provisioning : * Security - Security0 (no security), Security1 (curve25519 key exchange + AES-CTR encryption) * Proof-of-possession support for Security1 * Protocomm requires specific protocol buffer modules for compilation which can be generated from the `.proto` files in the `proto` directory using make. Co-Authored-By: Amey Inamdar Co-Authored-By: Anurag Kar --- components/protocomm/component.mk | 3 + .../protocomm/include/common/protocomm.h | 226 ++++++++ .../include/security/protocomm_security.h | 93 +++ .../include/security/protocomm_security0.h | 25 + .../include/security/protocomm_security1.h | 25 + components/protocomm/proto/constants.proto | 14 + components/protocomm/proto/makefile | 7 + components/protocomm/proto/sec0.proto | 28 + components/protocomm/proto/sec1.proto | 45 ++ components/protocomm/proto/session.proto | 21 + components/protocomm/src/common/protocomm.c | 383 +++++++++++++ .../protocomm/src/common/protocomm_priv.h | 79 +++ components/protocomm/src/security/security0.c | 111 ++++ components/protocomm/src/security/security1.c | 535 ++++++++++++++++++ 14 files changed, 1595 insertions(+) create mode 100644 components/protocomm/component.mk create mode 100644 components/protocomm/include/common/protocomm.h create mode 100644 components/protocomm/include/security/protocomm_security.h create mode 100644 components/protocomm/include/security/protocomm_security0.h create mode 100644 components/protocomm/include/security/protocomm_security1.h create mode 100644 components/protocomm/proto/constants.proto create mode 100644 components/protocomm/proto/makefile create mode 100644 components/protocomm/proto/sec0.proto create mode 100644 components/protocomm/proto/sec1.proto create mode 100644 components/protocomm/proto/session.proto create mode 100644 components/protocomm/src/common/protocomm.c create mode 100644 components/protocomm/src/common/protocomm_priv.h create mode 100644 components/protocomm/src/security/security0.c create mode 100644 components/protocomm/src/security/security1.c diff --git a/components/protocomm/component.mk b/components/protocomm/component.mk new file mode 100644 index 000000000..ed8c4b7c0 --- /dev/null +++ b/components/protocomm/component.mk @@ -0,0 +1,3 @@ +COMPONENT_ADD_INCLUDEDIRS := include/common include/security +COMPONENT_PRIV_INCLUDEDIRS := proto-c src/common +COMPONENT_SRCDIRS := src/common src/security proto-c diff --git a/components/protocomm/include/common/protocomm.h b/components/protocomm/include/common/protocomm.h new file mode 100644 index 000000000..f5f67da64 --- /dev/null +++ b/components/protocomm/include/common/protocomm.h @@ -0,0 +1,226 @@ +// Copyright 2018 Espressif Systems (Shanghai) PTE LTD +// +// 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. + +#pragma once + +#include +#include + +/** + * @brief Function prototype for protocomm endpoint handler + */ +typedef esp_err_t (*protocomm_req_handler_t)( + uint32_t session_id, /*!< Session ID for identifying protocomm client */ + const uint8_t *inbuf, /*!< Pointer to user provided input data buffer */ + ssize_t inlen, /*!< Length o the input buffer */ + uint8_t **outbuf, /*!< Pointer to output buffer allocated by handler */ + ssize_t *outlen, /*!< Length of the allocated output buffer */ + void *priv_data /*!< Private data passed to the handler (NULL if not used) */ +); + +/** + * @brief This structure corresponds to a unique instance of protocomm + * returned when the API `protocomm_new()` is called. The remaining + * Protocomm APIs require this object as the first parameter. + * + * @note Structure of the protocomm object is kept private + */ +typedef struct protocomm protocomm_t; + +/** + * @brief Create a new protocomm instance + * + * This API will return a new dynamically allocated protocomm instance + * with all elements of the protocomm_t structure initialised to NULL. + * + * @return + * - protocomm_t* : On success + * - NULL : No memory for allocating new instance + */ +protocomm_t *protocomm_new(); + +/** + * @brief Delete a protocomm instance + * + * This API will deallocate a protocomm instance that was created + * using `protocomm_new()`. + * + * @param[in] pc Pointer to the protocomm instance to be deleted + */ +void protocomm_delete(protocomm_t *pc); + +/** + * @brief Add endpoint request handler for a protocomm instance + * + * This API will bind an endpoint handler function to the specified + * endpoint name, along with any private data that needs to be pass to + * the handler at the time of call. + * + * @note + * - An endpoint must be bound to a valid protocomm instance, + * created using `protocomm_new()`. + * - This function internally calls the registered `add_endpoint()` + * function which is a member of the protocomm_t instance structure. + * + * @param[in] pc Pointer to the protocomm instance + * @param[in] ep_name Endpoint identifier(name) string + * @param[in] h Endpoint handler function + * @param[in] priv_data Pointer to private data to be passed as a + * parameter to the handler function on call. + * Pass NULL if not needed. + * + * @return + * - ESP_OK : Added new endpoint succesfully + * - ESP_FAIL : Error adding endpoint / Endpoint with this name already exists + * - ESP_ERR_NO_MEM : Error allocating endpoint resource + * - ESP_ERR_INVALID_ARG : Null instance/name/handler arguments + */ +esp_err_t protocomm_add_endpoint(protocomm_t *pc, const char *ep_name, + protocomm_req_handler_t h, void *priv_data); + +/** + * @brief Remove endpoint request handler for a protocomm instance + * + * This API will remove a registered endpoint handler identified by + * an endpoint name. + * + * @note + * - This function internally calls the registered `remove_endpoint()` + * function which is a member of the protocomm_t instance structure. + * + * @param[in] pc Pointer to the protocomm instance + * @param[in] ep_name Endpoint identifier(name) string + * + * @return + * - ESP_OK : Added new endpoint succesfully + * - ESP_ERR_NOT_FOUND : Endpoint with specified name doesn't exist + * - ESP_ERR_INVALID_ARG : Null instance/name arguments + */ +esp_err_t protocomm_remove_endpoint(protocomm_t *pc, const char *ep_name); + +/** + * @brief Calls the registered handler of an endpoint session + * for processing incoming data and giving the output + * + * @note + * - An endpoint must be bound to a valid protocomm instance, + * created using `protocomm_new()`. + * - Resulting output buffer must be deallocated by the user. + * + * @param[in] pc Pointer to the protocomm instance + * @param[in] ep_name Endpoint identifier(name) string + * @param[in] session_id Unique ID for a communication session + * @param[in] inbuf Input buffer contains input request data which is to be + * processed by the registered handler + * @param[in] inlen Length of the input buffer + * @param[out] outbuf Pointer to internally allocated output buffer, + * where the resulting response data output from + * the registered handler is to be stored + * @param[out] outlen Buffer length of the allocated output buffer + * + * @return + * - ESP_OK : Request handled succesfully + * - ESP_FAIL : Internal error in execution of registered handler + * - ESP_ERR_NO_MEM : Error allocating internal resource + * - ESP_ERR_NOT_FOUND : Endpoint with specified name doesn't exist + * - ESP_ERR_INVALID_ARG : Null instance/name arguments + */ +esp_err_t protocomm_req_handle(protocomm_t *pc, const char *ep_name, uint32_t session_id, + const uint8_t *inbuf, ssize_t inlen, + uint8_t **outbuf, ssize_t *outlen); + +/** + * @brief Add endpoint security for a protocomm instance + * + * This API will bind a security session establisher to the specified + * endpoint name, along with any proof of possession that may be required + * for authenticating a session client. + * + * @note + * - An endpoint must be bound to a valid protocomm instance, + * created using `protocomm_new()`. + * - The choice of security can be any `protocomm_security_t` instance. + * Choices `protocomm_security0` and `protocomm_security1` are readily available. + * + * @param[in] pc Pointer to the protocomm instance + * @param[in] ep_name Endpoint identifier(name) string + * @param[in] sec Pointer to endpoint security instance + * @param[in] pop Pointer to proof of possession for authenticating a client + * + * @return + * - ESP_OK : Added new security endpoint succesfully + * - ESP_FAIL : Error adding endpoint / Endpoint with this name already exists + * - ESP_ERR_INVALID_STATE : Security endpoint already set + * - ESP_ERR_NO_MEM : Error allocating endpoint resource + * - ESP_ERR_INVALID_ARG : Null instance/name/handler arguments + */ +esp_err_t protocomm_set_security(protocomm_t *pc, const char *ep_name, + const protocomm_security_t *sec, + const protocomm_security_pop_t *pop); + +/** + * @brief Remove endpoint security for a protocomm instance + * + * This API will remove a registered security endpoint identified by + * an endpoint name. + * + * @param[in] pc Pointer to the protocomm instance + * @param[in] ep_name Endpoint identifier(name) string + * + * @return + * - ESP_OK : Added new endpoint succesfully + * - ESP_ERR_NOT_FOUND : Endpoint with specified name doesn't exist + * - ESP_ERR_INVALID_ARG : Null instance/name arguments + */ +esp_err_t protocomm_unset_security(protocomm_t *pc, const char *ep_name); + +/** + * @brief Set endpoint for version verification + * + * This API can be used for setting an application specific protocol + * version which can be verfied by clients through the endpoint. + * + * @note + * - An endpoint must be bound to a valid protocomm instance, + * created using `protocomm_new()`. + + * @param[in] pc Pointer to the protocomm instance + * @param[in] ep_name Endpoint identifier(name) string + * @param[in] version Version identifier(name) string + * + * @return + * - ESP_OK : Added new security endpoint succesfully + * - ESP_FAIL : Error adding endpoint / Endpoint with this name already exists + * - ESP_ERR_INVALID_STATE : Version endpoint already set + * - ESP_ERR_NO_MEM : Error allocating endpoint resource + * - ESP_ERR_INVALID_ARG : Null instance/name/handler arguments + */ +esp_err_t protocomm_set_version(protocomm_t *pc, const char *ep_name, + const char *version); + +/** + * @brief Remove version verification endpoint from a protocomm instance + * + * This API will remove a registered version endpoint identified by + * an endpoint name. + * + * @param[in] pc Pointer to the protocomm instance + * @param[in] ep_name Endpoint identifier(name) string + * + * @return + * - ESP_OK : Added new endpoint succesfully + * - ESP_ERR_NOT_FOUND : Endpoint with specified name doesn't exist + * - ESP_ERR_INVALID_ARG : Null instance/name arguments + */ +esp_err_t protocomm_unset_version(protocomm_t *pc, const char *ep_name); diff --git a/components/protocomm/include/security/protocomm_security.h b/components/protocomm/include/security/protocomm_security.h new file mode 100644 index 000000000..7eeecc16f --- /dev/null +++ b/components/protocomm/include/security/protocomm_security.h @@ -0,0 +1,93 @@ +// Copyright 2018 Espressif Systems (Shanghai) PTE LTD +// +// 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. + +#pragma once + +#include + +/** + * @brief Proof Of Possession for authenticating a secure session + */ +typedef struct protocomm_security_pop { + /** + * Pointer to buffer containing the proof of possession data + */ + const uint8_t *data; + + /** + * Length (in bytes) of the proof of possession data + */ + uint16_t len; +} protocomm_security_pop_t; + +/** + * @brief Protocomm security object structure. + * + * The member functions are used for implementing secure + * protocomm sessions. + * + * @note This structure should not have any dynamic + * members to allow re-entrancy + */ +typedef struct protocomm_security { + /** + * Unique version number of security implmentation + */ + int ver; + + /** + * Function for initialising/allocating security + * infrastructure + */ + esp_err_t (*init)(); + + /** + * Function for deallocating security infrastructure + */ + esp_err_t (*cleanup)(); + + /** + * Starts new secure transport session with specified ID + */ + esp_err_t (*new_transport_session)(uint32_t session_id); + + /** + * Closes a secure transport session with specified ID + */ + esp_err_t (*close_transport_session)(uint32_t session_id); + + /** + * Handler function for authenticating connection + * request and establishing secure session + */ + esp_err_t (*security_req_handler)(const protocomm_security_pop_t *pop, + uint32_t session_id, + const uint8_t *inbuf, ssize_t inlen, + uint8_t **outbuf, ssize_t *outlen, + void *priv_data); + + /** + * Function which implements the encryption algorithm + */ + esp_err_t (*encrypt)(uint32_t session_id, + const uint8_t *inbuf, ssize_t inlen, + uint8_t *outbuf, ssize_t *outlen); + + /** + * Function which implements the decryption algorithm + */ + esp_err_t (*decrypt)(uint32_t session_id, + const uint8_t *inbuf, ssize_t inlen, + uint8_t *outbuf, ssize_t *outlen); +} protocomm_security_t; diff --git a/components/protocomm/include/security/protocomm_security0.h b/components/protocomm/include/security/protocomm_security0.h new file mode 100644 index 000000000..7d0462d8e --- /dev/null +++ b/components/protocomm/include/security/protocomm_security0.h @@ -0,0 +1,25 @@ +// Copyright 2018 Espressif Systems (Shanghai) PTE LTD +// +// 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. + +#pragma once + +#include + +/** + * @brief Protocomm security version 0 implementation + * + * This is a simple implementation to be used when no + * security is required for the protocomm instance + */ +extern const protocomm_security_t protocomm_security0; diff --git a/components/protocomm/include/security/protocomm_security1.h b/components/protocomm/include/security/protocomm_security1.h new file mode 100644 index 000000000..098d61e6a --- /dev/null +++ b/components/protocomm/include/security/protocomm_security1.h @@ -0,0 +1,25 @@ +// Copyright 2018 Espressif Systems (Shanghai) PTE LTD +// +// 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. + +#pragma once + +#include + +/** + * @brief Protocomm security version 1 implementation + * + * This is a full fledged security implmentation using + * Curve25519 key exchange and AES-256-CTR encryption + */ +extern const protocomm_security_t protocomm_security1; diff --git a/components/protocomm/proto/constants.proto b/components/protocomm/proto/constants.proto new file mode 100644 index 000000000..c80d250e5 --- /dev/null +++ b/components/protocomm/proto/constants.proto @@ -0,0 +1,14 @@ +syntax = "proto3"; + +/* Allowed values for the status + * of a protocomm instance */ +enum Status { + Success = 0; + InvalidSecScheme = 1; + InvalidProto = 2; + TooManySessions = 3; + InvalidArgument = 4; + InternalError = 5; + CryptoError = 6; + InvalidSession = 7; +} diff --git a/components/protocomm/proto/makefile b/components/protocomm/proto/makefile new file mode 100644 index 000000000..154e591b5 --- /dev/null +++ b/components/protocomm/proto/makefile @@ -0,0 +1,7 @@ +all: c_proto python_proto + +c_proto: *.proto + @protoc-c --c_out=../proto-c/ -I . *.proto + +python_proto: *.proto + @protoc --python_out=../python/ -I . *.proto diff --git a/components/protocomm/proto/sec0.proto b/components/protocomm/proto/sec0.proto new file mode 100644 index 000000000..299fce7c8 --- /dev/null +++ b/components/protocomm/proto/sec0.proto @@ -0,0 +1,28 @@ +syntax = "proto3"; + +import "constants.proto"; + +/* Data structure of Session command/request packet */ +message S0SessionCmd { + +} + +/* Data structure of Session response packet */ +message S0SessionResp { + Status status = 1; +} + +/* A message must be of type Cmd or Resp */ +enum Sec0MsgType { + S0_Session_Command = 0; + S0_Session_Response = 1; +} + +/* Payload structure of session data */ +message Sec0Payload { + Sec0MsgType msg = 1; /*!< Type of message */ + oneof payload { + S0SessionCmd sc = 20; /*!< Payload data interpreted as Cmd */ + S0SessionResp sr = 21; /*!< Payload data interpreted as Resp */ + } +} diff --git a/components/protocomm/proto/sec1.proto b/components/protocomm/proto/sec1.proto new file mode 100644 index 000000000..97e05ea6d --- /dev/null +++ b/components/protocomm/proto/sec1.proto @@ -0,0 +1,45 @@ +syntax = "proto3"; + +import "constants.proto"; + +/* Data structure of Session command1 packet */ +message SessionCmd1 { + bytes client_verify_data = 2; +} + +/* Data structure of Session response1 packet */ +message SessionResp1 { + Status status = 1; + bytes device_verify_data = 3; +} + +/* Data structure of Session command0 packet */ +message SessionCmd0 { + bytes client_pubkey = 1; +} + +/* Data structure of Session response0 packet */ +message SessionResp0 { + Status status = 1; + bytes device_pubkey = 2; + bytes device_random = 3; +} + +/* A message must be of type Cmd0 / Cmd1 / Resp0 / Resp1 */ +enum Sec1MsgType { + Session_Command0 = 0; + Session_Response0 = 1; + Session_Command1 = 2; + Session_Response1 = 3; +} + +/* Payload structure of session data */ +message Sec1Payload { + Sec1MsgType msg = 1; /*!< Type of message */ + oneof payload { + SessionCmd0 sc0 = 20; /*!< Payload data interpreted as Cmd0 */ + SessionResp0 sr0 = 21; /*!< Payload data interpreted as Resp0 */ + SessionCmd1 sc1 = 22; /*!< Payload data interpreted as Cmd1 */ + SessionResp1 sr1 = 23; /*!< Payload data interpreted as Resp1 */ + } +} diff --git a/components/protocomm/proto/session.proto b/components/protocomm/proto/session.proto new file mode 100644 index 000000000..de267b1f9 --- /dev/null +++ b/components/protocomm/proto/session.proto @@ -0,0 +1,21 @@ +syntax = "proto3"; + +import "sec0.proto"; +import "sec1.proto"; + +/* Allowed values for the type of security + * being used in a protocomm session */ +enum SecSchemeVersion { + SecScheme0 = 0; /*!< Unsecured - plaintext communication */ + SecScheme1 = 1; /*!< Security scheme 1 - Curve25519 + AES-256-CTR*/ +} + +/* Data structure exchanged when establishing + * secure session between Host and Client */ +message SessionData { + SecSchemeVersion sec_ver = 2; /*!< Type of security */ + oneof proto { + Sec0Payload sec0 = 10; /*!< Payload data in case of security 0 */ + Sec1Payload sec1 = 11; /*!< Payload data in case of security 1 */ + } +} diff --git a/components/protocomm/src/common/protocomm.c b/components/protocomm/src/common/protocomm.c new file mode 100644 index 000000000..94ebc8e81 --- /dev/null +++ b/components/protocomm/src/common/protocomm.c @@ -0,0 +1,383 @@ +// Copyright 2018 Espressif Systems (Shanghai) PTE LTD +// +// 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 +#include + +#include +#include +#include + +#include +#include + +#include "protocomm_priv.h" + +static const char *TAG = "protocomm"; + +protocomm_t *protocomm_new() +{ + protocomm_t *pc; + + pc = (protocomm_t *) calloc(1, sizeof(protocomm_t)); + if (!pc) { + ESP_LOGE(TAG, "Error allocating protocomm"); + return NULL; + } + SLIST_INIT(&pc->endpoints); + + return pc; +} + +void protocomm_delete(protocomm_t *pc) +{ + if (pc == NULL) { + return; + } + + protocomm_ep_t *it, *tmp; + /* Remove endpoints first */ + SLIST_FOREACH_SAFE(it, &pc->endpoints, next, tmp) { + free(it); + } + + free(pc); +} + +static protocomm_ep_t *search_endpoint(protocomm_t *pc, const char *ep_name) +{ + protocomm_ep_t *it; + SLIST_FOREACH(it, &pc->endpoints, next) { + if (strcmp(it->ep_name, ep_name) == 0) { + return it; + } + } + return NULL; +} + +static esp_err_t protocomm_add_endpoint_internal(protocomm_t *pc, const char *ep_name, + protocomm_req_handler_t h, void *priv_data, + uint32_t flag) +{ + if ((pc == NULL) || (ep_name == NULL) || (h == NULL)) { + return ESP_ERR_INVALID_ARG; + } + + protocomm_ep_t *ep; + esp_err_t ret; + + ep = search_endpoint(pc, ep_name); + if (ep) { + ESP_LOGE(TAG, "Endpoint with this name already exists"); + return ESP_FAIL; + } + + if (pc->add_endpoint) { + ret = pc->add_endpoint(ep_name, h, priv_data); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Error adding endpoint"); + return ret; + } + } + + ep = (protocomm_ep_t *) calloc(1, sizeof(protocomm_ep_t)); + if (!ep) { + ESP_LOGE(TAG, "Error allocating endpoint resource"); + return ESP_ERR_NO_MEM; + } + + /* Initialize ep handler */ + ep->ep_name = ep_name; + ep->req_handler = h; + ep->priv_data = priv_data; + ep->flag = flag; + + /* Add endpoint to the head of the singly linked list */ + SLIST_INSERT_HEAD(&pc->endpoints, ep, next); + + return ESP_OK; +} + +esp_err_t protocomm_add_endpoint(protocomm_t *pc, const char *ep_name, + protocomm_req_handler_t h, void *priv_data) +{ + return protocomm_add_endpoint_internal(pc, ep_name, h, priv_data, REQ_EP); +} + +esp_err_t protocomm_remove_endpoint(protocomm_t *pc, const char *ep_name) +{ + if ((pc == NULL) || (ep_name == NULL)) { + return ESP_ERR_INVALID_ARG; + } + + if (pc->remove_endpoint) { + pc->remove_endpoint(ep_name); + } + + protocomm_ep_t *it, *tmp; + SLIST_FOREACH_SAFE(it, &pc->endpoints, next, tmp) { + if (strcmp(ep_name, it->ep_name) == 0) { + SLIST_REMOVE(&pc->endpoints, it, protocomm_ep, next); + free(it); + return ESP_OK; + } + } + return ESP_ERR_NOT_FOUND; +} + +esp_err_t protocomm_req_handle(protocomm_t *pc, const char *ep_name, uint32_t session_id, + const uint8_t *inbuf, ssize_t inlen, + uint8_t **outbuf, ssize_t *outlen) +{ + if ((pc == NULL) || (ep_name == NULL)) { + ESP_LOGE(TAG, "Invalid params %p %p", pc, ep_name); + return ESP_ERR_INVALID_ARG; + } + + protocomm_ep_t *ep = search_endpoint(pc, ep_name); + if (!ep) { + ESP_LOGE(TAG, "No registered endpoint for %s", ep_name); + return ESP_ERR_NOT_FOUND; + } + + esp_err_t ret = ESP_FAIL; + if (ep->flag & SEC_EP) { + /* Call the registered endpoint handler for establishing secure session */ + ret = ep->req_handler(session_id, inbuf, inlen, outbuf, outlen, ep->priv_data); + ESP_LOGD(TAG, "SEC_EP Req handler returned %d", ret); + } else if (ep->flag & REQ_EP) { + if (pc->sec && pc->sec->decrypt) { + /* Decrypt the data first */ + uint8_t *dec_inbuf = (uint8_t *) malloc(inlen); + if (!dec_inbuf) { + ESP_LOGE(TAG, "Failed to allocate decrypt buf len %d", inlen); + return ESP_ERR_NO_MEM; + } + + ssize_t dec_inbuf_len = inlen; + pc->sec->decrypt(session_id, inbuf, inlen, dec_inbuf, &dec_inbuf_len); + + /* Invoke the request handler */ + uint8_t *plaintext_resp; + ssize_t plaintext_resp_len; + ret = ep->req_handler(session_id, + dec_inbuf, dec_inbuf_len, + &plaintext_resp, &plaintext_resp_len, + ep->priv_data); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Request handler for %s failed", ep_name); + *outbuf = NULL; + *outlen = 0; + free(dec_inbuf); + return ret; + } + /* We don't need decrypted data anymore */ + free(dec_inbuf); + + /* Encrypt response to be sent back */ + uint8_t *enc_resp = (uint8_t *) malloc(plaintext_resp_len); + if (!enc_resp) { + ESP_LOGE(TAG, "Failed to allocate decrypt buf len %d", inlen); + return ESP_ERR_NO_MEM; + } + + ssize_t enc_resp_len = plaintext_resp_len; + pc->sec->encrypt(session_id, plaintext_resp, plaintext_resp_len, + enc_resp, &enc_resp_len); + + /* We no more need plaintext response */ + free(plaintext_resp); + + /* Set outbuf and outlen appropriately */ + *outbuf = enc_resp; + *outlen = enc_resp_len; + } else { + /* No encryption */ + ret = ep->req_handler(session_id, + inbuf, inlen, + outbuf, outlen, + ep->priv_data); + ESP_LOGD(TAG, "No encrypt ret %d", ret); + } + } else if (ep->flag & VER_EP) { + ret = ep->req_handler(session_id, inbuf, inlen, outbuf, outlen, ep->priv_data); + ESP_LOGD(TAG, "VER_EP Req handler returned %d", ret); + } + return ret; +} + +static int protocomm_common_security_handler(uint32_t session_id, + const uint8_t *inbuf, ssize_t inlen, + uint8_t **outbuf, ssize_t *outlen, + void *priv_data) +{ + protocomm_t *pc = (protocomm_t *) priv_data; + + if (pc->sec && pc->sec->security_req_handler) { + return pc->sec->security_req_handler(pc->pop, session_id, + inbuf, inlen, + outbuf, outlen, + priv_data); + } + + return ESP_OK; +} + +esp_err_t protocomm_set_security(protocomm_t *pc, const char *ep_name, + const protocomm_security_t *sec, + const protocomm_security_pop_t *pop) +{ + if ((pc == NULL) || (ep_name == NULL) || (sec == NULL)) { + return ESP_ERR_INVALID_ARG; + } + + if (pc->sec) { + return ESP_ERR_INVALID_STATE; + } + + esp_err_t ret = protocomm_add_endpoint_internal(pc, ep_name, + protocomm_common_security_handler, + (void *) pc, SEC_EP); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Error adding security endpoint"); + return ret; + } + + if (sec->init) { + ret = sec->init(); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Error initialising security"); + protocomm_remove_endpoint(pc, ep_name); + return ret; + } + } + pc->sec = sec; + + if (pop) { + pc->pop = malloc(sizeof(protocomm_security_pop_t)); + if (pc->pop == NULL) { + ESP_LOGE(TAG, "Error allocating Proof of Possession"); + if (pc->sec && pc->sec->cleanup) { + pc->sec->cleanup(); + pc->sec = NULL; + } + + protocomm_remove_endpoint(pc, ep_name); + return ESP_ERR_NO_MEM; + } + memcpy((void *)pc->pop, pop, sizeof(protocomm_security_pop_t)); + } + return ESP_OK; +} + +esp_err_t protocomm_unset_security(protocomm_t *pc, const char *ep_name) +{ + if ((pc == NULL) || (ep_name == NULL)) { + return ESP_FAIL; + } + + if (pc->sec && pc->sec->cleanup) { + pc->sec->cleanup(); + pc->sec = NULL; + } + + if (pc->pop) { + free(pc->pop); + pc->pop = NULL; + } + + return protocomm_remove_endpoint(pc, ep_name); +} + +static int protocomm_version_handler(uint32_t session_id, + const uint8_t *inbuf, ssize_t inlen, + uint8_t **outbuf, ssize_t *outlen, + void *priv_data) +{ + const char *resp_match = "SUCCESS"; + const char *resp_fail = "FAIL"; + bool match = false; + char *version = strndup((const char *)inbuf, inlen); + protocomm_t *pc = (protocomm_t *) priv_data; + *outbuf = NULL; + *outlen = 0; + + if ((pc->ver != NULL) && (version != NULL)) { + ESP_LOGV(TAG, "Protocol version of device : %s", pc->ver); + ESP_LOGV(TAG, "Protocol version of client : %s", version); + if (strcmp(pc->ver, version) == 0) { + match = true; + } + } else if ((pc->ver == NULL) && (version == NULL)) { + match = true; + } + free(version); + + if (!match) { + ESP_LOGE(TAG, "Protocol version mismatch"); + } + + const char *result_msg = match ? resp_match : resp_fail; + + /* Output is a non null terminated string with length specified */ + *outlen = strlen(result_msg); + *outbuf = malloc(strlen(result_msg)); + if (outbuf == NULL) { + ESP_LOGE(TAG, "Failed to allocate memory for version check response"); + return ESP_ERR_NO_MEM; + } + + memcpy(*outbuf, result_msg, *outlen); + return ESP_OK; +} + +esp_err_t protocomm_set_version(protocomm_t *pc, const char *ep_name, const char *version) +{ + if ((pc == NULL) || (ep_name == NULL) || (version == NULL)) { + return ESP_ERR_INVALID_ARG; + } + + if (pc->ver) { + return ESP_ERR_INVALID_STATE; + } + + pc->ver = strdup(version); + if (pc->ver == NULL) { + ESP_LOGE(TAG, "Error allocating version string"); + return ESP_ERR_NO_MEM; + } + + esp_err_t ret = protocomm_add_endpoint_internal(pc, ep_name, + protocomm_version_handler, + (void *) pc, VER_EP); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Error adding version endpoint"); + return ret; + } + return ESP_OK; +} + +esp_err_t protocomm_unset_version(protocomm_t *pc, const char *ep_name) +{ + if ((pc == NULL) || (ep_name == NULL)) { + return ESP_ERR_INVALID_ARG; + } + + if (pc->ver) { + free((char *)pc->ver); + pc->ver = NULL; + } + + return protocomm_remove_endpoint(pc, ep_name); +} diff --git a/components/protocomm/src/common/protocomm_priv.h b/components/protocomm/src/common/protocomm_priv.h new file mode 100644 index 000000000..e956c3fd0 --- /dev/null +++ b/components/protocomm/src/common/protocomm_priv.h @@ -0,0 +1,79 @@ +// Copyright 2018 Espressif Systems (Shanghai) PTE LTD +// +// 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. + +#pragma once + +#include +#include +#include + +#define PROTOCOMM_NO_SESSION_ID UINT32_MAX + +/* Bit Flags for indicating intended functionality of handler to either + * process request or establish secure session */ +#define REQ_EP (1 << 0) /*!< Flag indicating request handling endpoint */ +#define SEC_EP (1 << 1) /*!< Flag indicating security handling endpoint */ +#define VER_EP (1 << 2) /*!< Flag indicating version handling endpoint */ + +/** + * @brief Protocomm endpoint table entry prototype + * + * The structure of an entry stored in the endpoint table. + */ +typedef struct protocomm_ep { + const char *ep_name; /*!< Unique endpoint name */ + protocomm_req_handler_t req_handler; /*!< Request handler function */ + + /* Pointer to private data to be passed as a parameter to the handler + * function at the time of call. Set to NULL if not used. */ + void *priv_data; + + uint32_t flag; /*!< Flag indicating endpoint functionality */ + + /* Next endpoint entry in the singly linked list for storing endpoint handlers */ + SLIST_ENTRY(protocomm_ep) next; +} protocomm_ep_t; + +/** + * @brief Prototype structure of a Protocomm instance + * + * This structure corresponds to a unique instance of protocomm returned, + * when the API protocomm_new() is called. The remaining Protocomm + * APIs require this object as the first parameter. + */ +struct protocomm { + /* Function Prototype of transport specific function, which is called + * internally when protocomm_add_endpoint() is invoked. */ + int (*add_endpoint)(const char *ep_name, protocomm_req_handler_t h, void *priv_data); + + /* Function Prototype of transport specific function, which is called + * internally when protocomm_remove_endpoint() is invoked. */ + int (*remove_endpoint)(const char *ep_name); + + /* Pointer to security layer instance to be used internally for + * establising secure sessions */ + const protocomm_security_t *sec; + + /* Pointer to proof of possession object */ + protocomm_security_pop_t *pop; + + /* Head of the singly linked list for storing endpoint handlers */ + SLIST_HEAD(eptable_t, protocomm_ep) endpoints; + + /* Private data to be used internally by the protocomm instance */ + void* priv; + + /* Application specific version string */ + const char* ver; +}; diff --git a/components/protocomm/src/security/security0.c b/components/protocomm/src/security/security0.c new file mode 100644 index 000000000..bca4d18c3 --- /dev/null +++ b/components/protocomm/src/security/security0.c @@ -0,0 +1,111 @@ +// Copyright 2018 Espressif Systems (Shanghai) PTE LTD +// +// 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 +#include +#include + +#include +#include + +#include +#include + +#include "session.pb-c.h" +#include "sec0.pb-c.h" +#include "constants.pb-c.h" + +static const char* TAG = "security0"; + +static esp_err_t sec0_session_setup(uint32_t session_id, + SessionData *req, SessionData *resp, + const protocomm_security_pop_t *pop) +{ + Sec0Payload *out = (Sec0Payload *) malloc(sizeof(Sec0Payload)); + S0SessionResp *s0resp = (S0SessionResp *) malloc(sizeof(S0SessionResp)); + if (!out || !s0resp) { + ESP_LOGE(TAG, "Error allocating response"); + return ESP_ERR_NO_MEM; + } + sec0_payload__init(out); + s0_session_resp__init(s0resp); + s0resp->status = STATUS__Success; + + out->msg = SEC0_MSG_TYPE__S0_Session_Response; + out->payload_case = SEC0_PAYLOAD__PAYLOAD_SR; + out->sr = s0resp; + + resp->proto_case = SESSION_DATA__PROTO_SEC0; + resp->sec0 = out; + + return ESP_OK; +} + +static void sec0_session_setup_cleanup(uint32_t session_id, SessionData *resp) +{ + if (!resp) { + return; + } + + free(resp->sec0->sr); + free(resp->sec0); + return; +} + +static esp_err_t sec0_req_handler(const protocomm_security_pop_t *pop, uint32_t session_id, + const uint8_t *inbuf, ssize_t inlen, + uint8_t **outbuf, ssize_t *outlen, + void *priv_data) +{ + SessionData *req; + SessionData resp; + esp_err_t ret; + + req = session_data__unpack(NULL, inlen, inbuf); + if (!req) { + ESP_LOGE(TAG, "Unable to unpack setup_req"); + return ESP_ERR_INVALID_ARG; + } + if (req->sec_ver != protocomm_security0.ver) { + ESP_LOGE(TAG, "Security version mismatch. Closing connection"); + return ESP_ERR_INVALID_ARG; + } + + session_data__init(&resp); + ret = sec0_session_setup(session_id, req, &resp, pop); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Session setup error %d", ret); + return ESP_FAIL; + } + + session_data__free_unpacked(req, NULL); + + resp.sec_ver = req->sec_ver; + + *outlen = session_data__get_packed_size(&resp); + *outbuf = (uint8_t *) malloc(*outlen); + if (!*outbuf) { + ESP_LOGE(TAG, "System out of memory"); + return ESP_ERR_NO_MEM; + } + session_data__pack(&resp, *outbuf); + + sec0_session_setup_cleanup(session_id, &resp); + return ESP_OK; +} + +const protocomm_security_t protocomm_security0 = { + .ver = 0, + .security_req_handler = sec0_req_handler, +}; diff --git a/components/protocomm/src/security/security1.c b/components/protocomm/src/security/security1.c new file mode 100644 index 000000000..76b9a25f5 --- /dev/null +++ b/components/protocomm/src/security/security1.c @@ -0,0 +1,535 @@ +// Copyright 2018 Espressif Systems (Shanghai) PTE LTD +// +// 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 +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "session.pb-c.h" +#include "sec1.pb-c.h" +#include "constants.pb-c.h" + +static const char* TAG = "security1"; + +#define PUBLIC_KEY_LEN 32 +#define SZ_RANDOM 16 + +#define SESSION_STATE_1 1 /* Session in state 1 */ +#define SESSION_STATE_SETUP 2 /* Session setup successful */ + +typedef struct session { + /* Session data */ + uint32_t id; + uint8_t state; + uint8_t device_pubkey[PUBLIC_KEY_LEN]; + uint8_t client_pubkey[PUBLIC_KEY_LEN]; + uint8_t sym_key[PUBLIC_KEY_LEN]; + uint8_t rand[SZ_RANDOM]; + + /* mbedtls context data for AES */ + mbedtls_aes_context ctx_aes; + unsigned char stb[16]; + size_t nc_off; +} session_t; + +static session_t *cur_session; + +static void flip_endian(uint8_t *data, size_t len) +{ + uint8_t swp_buf; + for (int i = 0; i < len/2; i++) { + swp_buf = data[i]; + data[i] = data[len - i - 1]; + data[len - i - 1] = swp_buf; + } +} + +static void hexdump(const char *msg, uint8_t *buf, int len) +{ + ESP_LOGD(TAG, "%s:", msg); + ESP_LOG_BUFFER_HEX_LEVEL(TAG, buf, len, ESP_LOG_DEBUG); +} + +static esp_err_t handle_session_command1(uint32_t session_id, + SessionData *req, SessionData *resp) +{ + ESP_LOGD(TAG, "Request to handle setup1_command"); + Sec1Payload *in = (Sec1Payload *) req->sec1; + uint8_t check_buf[PUBLIC_KEY_LEN]; + int mbed_err; + + if (!cur_session) { + ESP_LOGE(TAG, "Data on session endpoint without session establishment"); + return ESP_ERR_INVALID_STATE; + } + + if (session_id != cur_session->id) { + ESP_LOGE(TAG, "Invalid session"); + return ESP_ERR_INVALID_STATE; + } + + if (!in) { + ESP_LOGE(TAG, "Empty session data"); + return ESP_ERR_INVALID_ARG; + } + + /* Initialise crypto context */ + mbedtls_aes_init(&cur_session->ctx_aes); + memset(cur_session->stb, 0, sizeof(cur_session->stb)); + cur_session->nc_off = 0; + + hexdump("Client verifier", in->sc1->client_verify_data.data, + in->sc1->client_verify_data.len); + + mbed_err = mbedtls_aes_setkey_enc(&cur_session->ctx_aes, cur_session->sym_key, + sizeof(cur_session->sym_key)*8); + if (mbed_err != 0) { + ESP_LOGE(TAG, "Failure at mbedtls_aes_setkey_enc with error code : -0x%x", -mbed_err); + return ESP_FAIL; + } + + mbed_err = mbedtls_aes_crypt_ctr(&cur_session->ctx_aes, + PUBLIC_KEY_LEN, &cur_session->nc_off, + cur_session->rand, cur_session->stb, + in->sc1->client_verify_data.data, check_buf); + if (mbed_err != 0) { + ESP_LOGE(TAG, "Failure at mbedtls_aes_crypt_ctr with error code : -0x%x", -mbed_err); + return ESP_FAIL; + } + + hexdump("Dec Client verifier", check_buf, sizeof(check_buf)); + + /* constant time memcmp */ + if (mbedtls_ssl_safer_memcmp(check_buf, cur_session->device_pubkey, + sizeof(cur_session->device_pubkey)) != 0) { + ESP_LOGE(TAG, "Key mismatch. Close connection"); + return ESP_FAIL; + } + + Sec1Payload *out = (Sec1Payload *) malloc(sizeof(Sec1Payload)); + sec1_payload__init(out); + SessionResp1 *out_resp = (SessionResp1 *) malloc(sizeof(SessionResp1)); + session_resp1__init(out_resp); + + out_resp->status = STATUS__Success; + + uint8_t *outbuf = (uint8_t *) malloc(PUBLIC_KEY_LEN); + if (!outbuf) { + ESP_LOGE(TAG, "Error allocating ciphertext buffer"); + return ESP_ERR_NO_MEM; + } + + mbed_err = mbedtls_aes_crypt_ctr(&cur_session->ctx_aes, + PUBLIC_KEY_LEN, &cur_session->nc_off, + cur_session->rand, cur_session->stb, + cur_session->client_pubkey, outbuf); + if (mbed_err != 0) { + ESP_LOGE(TAG, "Failure at mbedtls_aes_crypt_ctr with error code : -0x%x", -mbed_err); + return ESP_FAIL; + } + + out_resp->device_verify_data.data = outbuf; + out_resp->device_verify_data.len = PUBLIC_KEY_LEN; + hexdump("Device verify data", outbuf, PUBLIC_KEY_LEN); + + out->msg = SEC1_MSG_TYPE__Session_Response1; + out->payload_case = SEC1_PAYLOAD__PAYLOAD_SR1; + out->sr1 = out_resp; + + resp->proto_case = SESSION_DATA__PROTO_SEC1; + resp->sec1 = out; + + ESP_LOGD(TAG, "Session successful"); + + return ESP_OK; +} + +static esp_err_t handle_session_command0(uint32_t session_id, + SessionData *req, SessionData *resp, + const protocomm_security_pop_t *pop) +{ + Sec1Payload *in = (Sec1Payload *) req->sec1; + esp_err_t ret; + int mbed_err; + + if (!cur_session) { + ESP_LOGE(TAG, "Data on session endpoint without session establishment"); + return ESP_ERR_INVALID_STATE; + } + + if (session_id != cur_session->id) { + ESP_LOGE(TAG, "Invalid session"); + return ESP_ERR_INVALID_STATE; + } + + if (!in) { + ESP_LOGE(TAG, "Empty session data"); + return ESP_ERR_INVALID_ARG; + } + + if (in->sc0->client_pubkey.len != PUBLIC_KEY_LEN) { + ESP_LOGE(TAG, "Invalid public key length"); + return ESP_ERR_INVALID_ARG; + } + + cur_session->state = SESSION_STATE_1; + + mbedtls_ecdh_context *ctx_server = malloc(sizeof(mbedtls_ecdh_context)); + mbedtls_entropy_context *entropy = malloc(sizeof(mbedtls_entropy_context)); + mbedtls_ctr_drbg_context *ctr_drbg = malloc(sizeof(mbedtls_ctr_drbg_context)); + if (!ctx_server || !ctx_server || !ctr_drbg) { + ESP_LOGE(TAG, "Failed to allocate memory for mbedtls context"); + free(ctx_server); + free(entropy); + free(ctr_drbg); + return ESP_ERR_NO_MEM; + } + + mbedtls_ecdh_init(ctx_server); + mbedtls_ctr_drbg_init(ctr_drbg); + mbedtls_entropy_init(entropy); + + mbed_err = mbedtls_ctr_drbg_seed(ctr_drbg, mbedtls_entropy_func, + entropy, NULL, 0); + if (mbed_err != 0) { + ESP_LOGE(TAG, "Failed at mbedtls_ctr_drbg_seed with error code : -0x%x", -mbed_err); + ret = ESP_FAIL; + goto exit_cmd0; + } + + mbed_err = mbedtls_ecp_group_load(&ctx_server->grp, MBEDTLS_ECP_DP_CURVE25519); + if (mbed_err != 0) { + ESP_LOGE(TAG, "Failed at mbedtls_ecp_group_load with error code : -0x%x", -mbed_err); + ret = ESP_FAIL; + goto exit_cmd0; + } + + mbed_err = mbedtls_ecdh_gen_public(&ctx_server->grp, &ctx_server->d, &ctx_server->Q, + mbedtls_ctr_drbg_random, ctr_drbg); + if (mbed_err != 0) { + ESP_LOGE(TAG, "Failed at mbedtls_ecdh_gen_public with error code : -0x%x", -mbed_err); + ret = ESP_FAIL; + goto exit_cmd0; + } + + mbed_err = mbedtls_mpi_write_binary(&ctx_server->Q.X, + cur_session->device_pubkey, + PUBLIC_KEY_LEN); + if (mbed_err != 0) { + ESP_LOGE(TAG, "Failed at mbedtls_mpi_write_binary with error code : -0x%x", -mbed_err); + ret = ESP_FAIL; + goto exit_cmd0; + } + flip_endian(cur_session->device_pubkey, PUBLIC_KEY_LEN); + + memcpy(cur_session->client_pubkey, in->sc0->client_pubkey.data, PUBLIC_KEY_LEN); + + uint8_t *dev_pubkey = cur_session->device_pubkey; + uint8_t *cli_pubkey = cur_session->client_pubkey; + hexdump("Device pubkey", dev_pubkey, PUBLIC_KEY_LEN); + hexdump("Client pubkey", cli_pubkey, PUBLIC_KEY_LEN); + + mbed_err = mbedtls_mpi_lset(&ctx_server->Qp.Z, 1); + if (mbed_err != 0) { + ESP_LOGE(TAG, "Failed at mbedtls_mpi_lset with error code : -0x%x", -mbed_err); + ret = ESP_FAIL; + goto exit_cmd0; + } + + flip_endian(cur_session->client_pubkey, PUBLIC_KEY_LEN); + mbed_err = mbedtls_mpi_read_binary(&ctx_server->Qp.X, cli_pubkey, PUBLIC_KEY_LEN); + flip_endian(cur_session->client_pubkey, PUBLIC_KEY_LEN); + if (mbed_err != 0) { + ESP_LOGE(TAG, "Failed at mbedtls_mpi_read_binary with error code : -0x%x", -mbed_err); + ret = ESP_FAIL; + goto exit_cmd0; + } + + mbed_err = mbedtls_ecdh_compute_shared(&ctx_server->grp, &ctx_server->z, &ctx_server->Qp, + &ctx_server->d, mbedtls_ctr_drbg_random, ctr_drbg); + if (mbed_err != 0) { + ESP_LOGE(TAG, "Failed at mbedtls_ecdh_compute_shared with error code : -0x%x", -mbed_err); + ret = ESP_FAIL; + goto exit_cmd0; + } + + mbed_err = mbedtls_mpi_write_binary(&ctx_server->z, cur_session->sym_key, PUBLIC_KEY_LEN); + if (mbed_err != 0) { + ESP_LOGE(TAG, "Failed at mbedtls_mpi_write_binary with error code : -0x%x", -mbed_err); + ret = ESP_FAIL; + goto exit_cmd0; + } + flip_endian(cur_session->sym_key, PUBLIC_KEY_LEN); + + if (pop != NULL && pop->data != NULL && pop->len != 0) { + ESP_LOGD(TAG, "Adding proof of possession"); + uint8_t sha_out[PUBLIC_KEY_LEN]; + + mbed_err = mbedtls_sha256_ret((const unsigned char *) pop->data, pop->len, sha_out, 0); + if (mbed_err != 0) { + ESP_LOGE(TAG, "Failed at mbedtls_sha256_ret with error code : -0x%x", -mbed_err); + ret = ESP_FAIL; + goto exit_cmd0; + } + + for (int i = 0; i < PUBLIC_KEY_LEN; i++) { + cur_session->sym_key[i] ^= sha_out[i]; + } + } + + hexdump("Shared key", cur_session->sym_key, PUBLIC_KEY_LEN); + + mbed_err = mbedtls_ctr_drbg_random(ctr_drbg, cur_session->rand, SZ_RANDOM); + if (mbed_err != 0) { + ESP_LOGE(TAG, "Failed at mbedtls_ctr_drbg_random with error code : -0x%x", -mbed_err); + ret = ESP_FAIL; + goto exit_cmd0; + } + + hexdump("Device random", cur_session->rand, SZ_RANDOM); + + Sec1Payload *out = (Sec1Payload *) malloc(sizeof(Sec1Payload)); + SessionResp0 *out_resp = (SessionResp0 *) malloc(sizeof(SessionResp0)); + if (!out || !out_resp) { + ESP_LOGE(TAG, "Error allocating memory for response"); + ret = ESP_FAIL; + goto exit_cmd0; + } + + sec1_payload__init(out); + session_resp0__init(out_resp); + + out_resp->status = STATUS__Success; + + out_resp->device_pubkey.data = dev_pubkey; + out_resp->device_pubkey.len = PUBLIC_KEY_LEN; + + out_resp->device_random.data = (uint8_t *) cur_session->rand; + out_resp->device_random.len = SZ_RANDOM; + + out->msg = SEC1_MSG_TYPE__Session_Response0; + out->payload_case = SEC1_PAYLOAD__PAYLOAD_SR0; + out->sr0 = out_resp; + + resp->proto_case = SESSION_DATA__PROTO_SEC1; + resp->sec1 = out; + + ESP_LOGD(TAG, "Session setup phase1 done"); + ret = ESP_OK; + +exit_cmd0: + mbedtls_ecdh_free(ctx_server); + free(ctx_server); + + mbedtls_ctr_drbg_free(ctr_drbg); + free(ctr_drbg); + + mbedtls_entropy_free(entropy); + free(entropy); + + return ret; +} + +static esp_err_t sec1_session_setup(uint32_t session_id, + SessionData *req, SessionData *resp, + const protocomm_security_pop_t *pop) +{ + Sec1Payload *in = (Sec1Payload *) req->sec1; + esp_err_t ret; + + switch (in->msg) { + case SEC1_MSG_TYPE__Session_Command0: + ret = handle_session_command0(session_id, req, resp, pop); + break; + case SEC1_MSG_TYPE__Session_Command1: + ret = handle_session_command1(session_id, req, resp); + break; + default: + ESP_LOGE(TAG, "Invalid security message type"); + ret = ESP_ERR_INVALID_ARG; + } + + return ret; + +} + +static void sec1_session_setup_cleanup(uint32_t session_id, SessionData *resp) +{ + Sec1Payload *out = resp->sec1; + + if (!out) { + return; + } + + switch (out->msg) { + case SEC1_MSG_TYPE__Session_Response0: + { + SessionResp0 *out_resp0 = out->sr0; + if (out_resp0) { + free(out_resp0); + } + break; + } + case SEC1_MSG_TYPE__Session_Response1: + { + SessionResp1 *out_resp1 = out->sr1; + if (out_resp1) { + free(out_resp1->device_verify_data.data); + free(out_resp1); + } + break; + } + default: + break; + } + free(out); + + return; +} + +static esp_err_t sec1_init() +{ + return ESP_OK; +} + +static esp_err_t sec1_cleanup() +{ + if (cur_session) { + ESP_LOGD(TAG, "Closing current session with id %u", cur_session->id); + mbedtls_aes_free(&cur_session->ctx_aes); + bzero(cur_session, sizeof(session_t)); + free(cur_session); + cur_session = NULL; + } + return ESP_OK; +} + +static esp_err_t sec1_new_session(uint32_t session_id) +{ + if (cur_session && cur_session->id != session_id) { + ESP_LOGE(TAG, "Closing old session with id %u", cur_session->id); + sec1_cleanup(); + } else if (cur_session && cur_session->id == session_id) { + return ESP_OK; + } + + cur_session = (session_t *) calloc(1, sizeof(session_t)); + if (!cur_session) { + ESP_LOGE(TAG, "Error allocating session structure"); + return ESP_ERR_NO_MEM; + } + + cur_session->id = session_id; + return ESP_OK; +} + +static esp_err_t sec1_close_session(uint32_t session_id) +{ + if (!cur_session || cur_session->id != session_id) { + ESP_LOGE(TAG, "Attempt to close invalid session"); + return ESP_ERR_INVALID_ARG; + } + + bzero(cur_session, sizeof(session_t)); + free(cur_session); + cur_session = NULL; + return ESP_OK; +} + +static esp_err_t sec1_decrypt(uint32_t session_id, + const uint8_t *inbuf, ssize_t inlen, + uint8_t *outbuf, ssize_t *outlen) +{ + if (*outlen < inlen) { + return ESP_ERR_INVALID_ARG; + } + + if (!cur_session || cur_session->id != session_id) { + return ESP_ERR_INVALID_STATE; + } + *outlen = inlen; + int ret = mbedtls_aes_crypt_ctr(&cur_session->ctx_aes, inlen, &cur_session->nc_off, + cur_session->rand, cur_session->stb, inbuf, outbuf); + if (ret != 0) { + ESP_LOGE(TAG, "Failed at mbedtls_aes_crypt_ctr with error code : %d", ret); + return ESP_FAIL; + } + return ESP_OK; +} + +static esp_err_t sec1_req_handler(const protocomm_security_pop_t *pop, uint32_t session_id, + const uint8_t *inbuf, ssize_t inlen, + uint8_t **outbuf, ssize_t *outlen, + void *priv_data) +{ + SessionData *req; + SessionData resp; + esp_err_t ret; + + req = session_data__unpack(NULL, inlen, inbuf); + if (!req) { + ESP_LOGE(TAG, "Unable to unpack setup_req"); + return ESP_ERR_INVALID_ARG; + } + if (req->sec_ver != protocomm_security1.ver) { + ESP_LOGE(TAG, "Security version mismatch. Closing connection"); + return ESP_ERR_INVALID_ARG; + } + + session_data__init(&resp); + ret = sec1_session_setup(session_id, req, &resp, pop); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Session setup error %d", ret); + return ESP_FAIL; + } + + session_data__free_unpacked(req, NULL); + + resp.sec_ver = req->sec_ver; + + *outlen = session_data__get_packed_size(&resp); + *outbuf = (uint8_t *) malloc(*outlen); + if (!*outbuf) { + ESP_LOGE(TAG, "System out of memory"); + return ESP_ERR_NO_MEM; + } + session_data__pack(&resp, *outbuf); + + sec1_session_setup_cleanup(session_id, &resp); + return ESP_OK; +} + +const protocomm_security_t protocomm_security1 = { + .ver = 1, + .init = sec1_init, + .cleanup = sec1_cleanup, + .new_transport_session = sec1_new_session, + .close_transport_session = sec1_close_session, + .security_req_handler = sec1_req_handler, + .encrypt = sec1_decrypt, /* Encrypt == decrypt for AES-CTR */ + .decrypt = sec1_decrypt, +};