mirror of
https://github.com/espressif/esp-idf.git
synced 2024-10-05 20:47:46 -04:00
Merge branch 'feature/local_control_sec1' into 'master'
Added support for security1 in local control See merge request espressif/esp-idf!13684
This commit is contained in:
commit
3850eba152
@ -228,6 +228,37 @@ typedef union {
|
||||
esp_local_ctrl_transport_config_httpd_t *httpd;
|
||||
} esp_local_ctrl_transport_config_t;
|
||||
|
||||
/**
|
||||
* @brief Security types for esp_local_control
|
||||
*/
|
||||
typedef enum esp_local_ctrl_proto_sec {
|
||||
PROTOCOM_SEC0 = 0,
|
||||
PROTOCOM_SEC1,
|
||||
PROTOCOM_SEC_CUSTOM,
|
||||
} esp_local_ctrl_proto_sec_t;
|
||||
|
||||
/**
|
||||
* Protocom security configs
|
||||
*/
|
||||
typedef struct esp_local_ctrl_proto_sec_cfg {
|
||||
/**
|
||||
* This sets protocom security version, sec0/sec1 or custom
|
||||
* If custom, user must provide handle via `proto_sec_custom_handle` below
|
||||
*/
|
||||
esp_local_ctrl_proto_sec_t version;
|
||||
|
||||
/**
|
||||
* Custom security handle if security is set custom via `proto_sec` above
|
||||
* This handle must follow `protocomm_security_t` signature
|
||||
*/
|
||||
void *custom_handle;
|
||||
|
||||
/**
|
||||
* Proof of possession to be used for local control. Could be NULL.
|
||||
*/
|
||||
void *pop;
|
||||
} esp_local_ctrl_proto_sec_cfg_t;
|
||||
|
||||
/**
|
||||
* @brief Configuration structure to pass to `esp_local_ctrl_start()`
|
||||
*/
|
||||
@ -242,6 +273,11 @@ typedef struct esp_local_ctrl_config {
|
||||
*/
|
||||
esp_local_ctrl_transport_config_t transport_config;
|
||||
|
||||
/**
|
||||
* Security version and POP
|
||||
*/
|
||||
esp_local_ctrl_proto_sec_cfg_t proto_sec;
|
||||
|
||||
/**
|
||||
* Register handlers for responding to get/set requests on properties
|
||||
*/
|
||||
|
@ -19,6 +19,7 @@
|
||||
|
||||
#include <protocomm.h>
|
||||
#include <protocomm_security0.h>
|
||||
#include <protocomm_security1.h>
|
||||
|
||||
#include <esp_local_ctrl.h>
|
||||
#include "esp_local_ctrl_priv.h"
|
||||
@ -149,8 +150,21 @@ esp_err_t esp_local_ctrl_start(const esp_local_ctrl_config_t *config)
|
||||
return ret;
|
||||
}
|
||||
|
||||
protocomm_security_t *proto_sec_handle;
|
||||
switch (local_ctrl_inst_ctx->config.proto_sec.version) {
|
||||
case PROTOCOM_SEC_CUSTOM:
|
||||
proto_sec_handle = local_ctrl_inst_ctx->config.proto_sec.custom_handle;
|
||||
break;
|
||||
case PROTOCOM_SEC1:
|
||||
proto_sec_handle = (protocomm_security_t *) &protocomm_security1;
|
||||
break;
|
||||
case PROTOCOM_SEC0:
|
||||
default:
|
||||
proto_sec_handle = (protocomm_security_t *) &protocomm_security0;
|
||||
break;
|
||||
}
|
||||
ret = protocomm_set_security(local_ctrl_inst_ctx->pc, "esp_local_ctrl/session",
|
||||
&protocomm_security0, NULL);
|
||||
proto_sec_handle, local_ctrl_inst_ctx->config.proto_sec.pop);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to set session endpoint");
|
||||
esp_local_ctrl_stop();
|
||||
|
@ -24,6 +24,11 @@ Initialization of the **esp_local_ctrl** service over BLE transport is performed
|
||||
}
|
||||
}
|
||||
},
|
||||
.proto_sec = {
|
||||
.version = PROTOCOM_SEC0,
|
||||
.custom_handle = NULL,
|
||||
.pop = NULL,
|
||||
},
|
||||
.handlers = {
|
||||
/* User defined handler functions */
|
||||
.get_prop_values = get_property_values,
|
||||
@ -65,6 +70,11 @@ Similarly for HTTPS transport:
|
||||
.transport_config = {
|
||||
.httpd = &https_conf
|
||||
},
|
||||
.proto_sec = {
|
||||
.version = PROTOCOM_SEC0,
|
||||
.custom_handle = NULL,
|
||||
.pop = NULL,
|
||||
},
|
||||
.handlers = {
|
||||
/* User defined handler functions */
|
||||
.get_prop_values = get_property_values,
|
||||
@ -79,6 +89,11 @@ Similarly for HTTPS transport:
|
||||
/* Start esp_local_ctrl service */
|
||||
ESP_ERROR_CHECK(esp_local_ctrl_start(&config));
|
||||
|
||||
You may set security for transport in ESP local control using following options:
|
||||
|
||||
1. `PROTOCOM_SEC1`: specifies that end to end encryption is used.
|
||||
2. `PROTOCOM_SEC0`: specifies that data will be exchanged as a plain text.
|
||||
3. `PROTOCOM_SEC_CUSTOM`: you can define your own security requirement. Please note that you will also have to provide `custom_handle` of type `protocomm_security_t *` in this context.
|
||||
|
||||
Creating a property
|
||||
-------------------
|
||||
|
@ -28,12 +28,12 @@ Sample output:
|
||||
After you've tested the name resolution, run:
|
||||
|
||||
```
|
||||
python scripts/esp_local_ctrl.py
|
||||
python scripts/esp_local_ctrl.py --sec_ver 0
|
||||
```
|
||||
Sample output:
|
||||
|
||||
```
|
||||
python scripts/esp_local_ctrl.py
|
||||
python scripts/esp_local_ctrl.py --sec_ver 0
|
||||
|
||||
==== Acquiring properties information ====
|
||||
|
||||
|
@ -29,6 +29,7 @@ def test_examples_esp_local_ctrl(env, extra_data):
|
||||
# Running mDNS services in docker is not a trivial task. Therefore, the script won't connect to the host name but
|
||||
# to IP address. However, the certificates were generated for the host name and will be rejected.
|
||||
cmd = ' '.join([sys.executable, os.path.join(idf_path, rel_project_path, 'scripts/esp_local_ctrl.py'),
|
||||
'--sec_ver 0',
|
||||
'--name', dut_ip,
|
||||
'--dont-check-hostname']) # don't reject the certificate because of the hostname
|
||||
esp_local_ctrl_log = os.path.join(idf_path, rel_project_path, 'esp_local_ctrl.log')
|
||||
|
@ -178,6 +178,11 @@ void start_esp_local_ctrl_service(void)
|
||||
.transport_config = {
|
||||
.httpd = &https_conf
|
||||
},
|
||||
.proto_sec = {
|
||||
.version = 0,
|
||||
.custom_handle = NULL,
|
||||
.pop = NULL,
|
||||
},
|
||||
.handlers = {
|
||||
/* User defined handler functions */
|
||||
.get_prop_values = get_property_values,
|
||||
|
@ -18,19 +18,28 @@
|
||||
from __future__ import print_function
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import ssl
|
||||
import struct
|
||||
import sys
|
||||
import textwrap
|
||||
from builtins import input
|
||||
|
||||
import proto
|
||||
import proto_lc
|
||||
from future.utils import tobytes
|
||||
|
||||
# The tools directory is already in the PATH in environment prepared by install.sh which would allow to import
|
||||
# esp_prov as file but not as complete module.
|
||||
sys.path.insert(0, os.path.join(os.environ['IDF_PATH'], 'tools/esp_prov'))
|
||||
import esp_prov # noqa: E402
|
||||
try:
|
||||
import esp_prov
|
||||
import security
|
||||
|
||||
except ImportError:
|
||||
idf_path = os.environ['IDF_PATH']
|
||||
sys.path.insert(0, idf_path + '/components/protocomm/python')
|
||||
sys.path.insert(1, idf_path + '/tools/esp_prov')
|
||||
|
||||
import esp_prov
|
||||
import security
|
||||
|
||||
# Set this to true to allow exceptions to be thrown
|
||||
config_throw_except = False
|
||||
@ -118,6 +127,14 @@ def on_except(err):
|
||||
print(err)
|
||||
|
||||
|
||||
def get_security(secver, pop=None, verbose=False):
|
||||
if secver == 1:
|
||||
return security.Security1(pop, verbose)
|
||||
elif secver == 0:
|
||||
return security.Security0(verbose)
|
||||
return None
|
||||
|
||||
|
||||
def get_transport(sel_transport, service_name, check_hostname):
|
||||
try:
|
||||
tp = None
|
||||
@ -140,29 +157,99 @@ def get_transport(sel_transport, service_name, check_hostname):
|
||||
return None
|
||||
|
||||
|
||||
def version_match(tp, expected, verbose=False):
|
||||
def version_match(tp, protover, verbose=False):
|
||||
try:
|
||||
response = tp.send_data('esp_local_ctrl/version', expected)
|
||||
return (response.lower() == expected.lower())
|
||||
response = tp.send_data('proto-ver', protover)
|
||||
|
||||
if verbose:
|
||||
print('proto-ver response : ', response)
|
||||
|
||||
# First assume this to be a simple version string
|
||||
if response.lower() == protover.lower():
|
||||
return True
|
||||
|
||||
try:
|
||||
# Else interpret this as JSON structure containing
|
||||
# information with versions and capabilities of both
|
||||
# provisioning service and application
|
||||
info = json.loads(response)
|
||||
if info['prov']['ver'].lower() == protover.lower():
|
||||
return True
|
||||
|
||||
except ValueError:
|
||||
# If decoding as JSON fails, it means that capabilities
|
||||
# are not supported
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
on_except(e)
|
||||
return None
|
||||
|
||||
|
||||
def get_all_property_values(tp):
|
||||
def has_capability(tp, capability='none', verbose=False):
|
||||
# Note : default value of `capability` argument cannot be empty string
|
||||
# because protocomm_httpd expects non zero content lengths
|
||||
try:
|
||||
response = tp.send_data('proto-ver', capability)
|
||||
|
||||
if verbose:
|
||||
print('proto-ver response : ', response)
|
||||
|
||||
try:
|
||||
# Interpret this as JSON structure containing
|
||||
# information with versions and capabilities of both
|
||||
# provisioning service and application
|
||||
info = json.loads(response)
|
||||
supported_capabilities = info['prov']['cap']
|
||||
if capability.lower() == 'none':
|
||||
# No specific capability to check, but capabilities
|
||||
# feature is present so return True
|
||||
return True
|
||||
elif capability in supported_capabilities:
|
||||
return True
|
||||
return False
|
||||
|
||||
except ValueError:
|
||||
# If decoding as JSON fails, it means that capabilities
|
||||
# are not supported
|
||||
return False
|
||||
|
||||
except RuntimeError as e:
|
||||
on_except(e)
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def establish_session(tp, sec):
|
||||
try:
|
||||
response = None
|
||||
while True:
|
||||
request = sec.security_session(response)
|
||||
if request is None:
|
||||
break
|
||||
response = tp.send_data('esp_local_ctrl/session', request)
|
||||
if (response is None):
|
||||
return False
|
||||
return True
|
||||
except RuntimeError as e:
|
||||
on_except(e)
|
||||
return None
|
||||
|
||||
|
||||
def get_all_property_values(tp, security_ctx):
|
||||
try:
|
||||
props = []
|
||||
message = proto.get_prop_count_request()
|
||||
message = proto_lc.get_prop_count_request(security_ctx)
|
||||
response = tp.send_data('esp_local_ctrl/control', message)
|
||||
count = proto.get_prop_count_response(response)
|
||||
count = proto_lc.get_prop_count_response(security_ctx, response)
|
||||
if count == 0:
|
||||
raise RuntimeError('No properties found!')
|
||||
indices = [i for i in range(count)]
|
||||
message = proto.get_prop_vals_request(indices)
|
||||
message = proto_lc.get_prop_vals_request(security_ctx, indices)
|
||||
response = tp.send_data('esp_local_ctrl/control', message)
|
||||
props = proto.get_prop_vals_response(response)
|
||||
props = proto_lc.get_prop_vals_response(security_ctx, response)
|
||||
if len(props) != count:
|
||||
raise RuntimeError('Incorrect count of properties!')
|
||||
raise RuntimeError('Incorrect count of properties!', len(props), count)
|
||||
for p in props:
|
||||
p['value'] = decode_prop_value(p, p['value'])
|
||||
return props
|
||||
@ -171,20 +258,27 @@ def get_all_property_values(tp):
|
||||
return []
|
||||
|
||||
|
||||
def set_property_values(tp, props, indices, values, check_readonly=False):
|
||||
def set_property_values(tp, security_ctx, props, indices, values, check_readonly=False):
|
||||
try:
|
||||
if check_readonly:
|
||||
for index in indices:
|
||||
if prop_is_readonly(props[index]):
|
||||
raise RuntimeError('Cannot set value of Read-Only property')
|
||||
message = proto.set_prop_vals_request(indices, values)
|
||||
message = proto_lc.set_prop_vals_request(security_ctx, indices, values)
|
||||
response = tp.send_data('esp_local_ctrl/control', message)
|
||||
return proto.set_prop_vals_response(response)
|
||||
return proto_lc.set_prop_vals_response(security_ctx, response)
|
||||
except RuntimeError as e:
|
||||
on_except(e)
|
||||
return False
|
||||
|
||||
|
||||
def desc_format(*args):
|
||||
desc = ''
|
||||
for arg in args:
|
||||
desc += textwrap.fill(replace_whitespace=False, text=arg) + '\n'
|
||||
return desc
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = argparse.ArgumentParser(add_help=False)
|
||||
|
||||
@ -199,6 +293,22 @@ if __name__ == '__main__':
|
||||
parser.add_argument('--name', dest='service_name', type=str,
|
||||
help='BLE Device Name / HTTP Server hostname or IP', default='')
|
||||
|
||||
parser.add_argument('--sec_ver', dest='secver', type=int, default=None,
|
||||
help=desc_format(
|
||||
'Protocomm security scheme used by the provisioning service for secure '
|
||||
'session establishment. Accepted values are :',
|
||||
'\t- 0 : No security',
|
||||
'\t- 1 : X25519 key exchange + AES-CTR encryption',
|
||||
'\t + Authentication using Proof of Possession (PoP)',
|
||||
'In case device side application uses IDF\'s provisioning manager, '
|
||||
'the compatible security version is automatically determined from '
|
||||
'capabilities retrieved via the version endpoint'))
|
||||
|
||||
parser.add_argument('--pop', dest='pop', type=str, default='',
|
||||
help=desc_format(
|
||||
'This specifies the Proof of possession (PoP) when security scheme 1 '
|
||||
'is used'))
|
||||
|
||||
parser.add_argument('--dont-check-hostname', action='store_true',
|
||||
# If enabled, the certificate won't be rejected for hostname mismatch.
|
||||
# This option is hidden because it should be used only for testing purposes.
|
||||
@ -220,6 +330,31 @@ if __name__ == '__main__':
|
||||
print('---- Invalid transport ----')
|
||||
exit(1)
|
||||
|
||||
# If security version not specified check in capabilities
|
||||
if args.secver is None:
|
||||
# First check if capabilities are supported or not
|
||||
if not has_capability(obj_transport):
|
||||
print('Security capabilities could not be determined. Please specify \'--sec_ver\' explicitly')
|
||||
print('---- Invalid Security Version ----')
|
||||
exit(2)
|
||||
|
||||
# When no_sec is present, use security 0, else security 1
|
||||
args.secver = int(not has_capability(obj_transport, 'no_sec'))
|
||||
print('Security scheme determined to be :', args.secver)
|
||||
|
||||
if (args.secver != 0) and not has_capability(obj_transport, 'no_pop'):
|
||||
if len(args.pop) == 0:
|
||||
print('---- Proof of Possession argument not provided ----')
|
||||
exit(2)
|
||||
elif len(args.pop) != 0:
|
||||
print('---- Proof of Possession will be ignored ----')
|
||||
args.pop = ''
|
||||
|
||||
obj_security = get_security(args.secver, args.pop, False)
|
||||
if obj_security is None:
|
||||
print('---- Invalid Security Version ----')
|
||||
exit(2)
|
||||
|
||||
if args.version != '':
|
||||
print('\n==== Verifying protocol version ====')
|
||||
if not version_match(obj_transport, args.version, args.verbose):
|
||||
@ -227,8 +362,15 @@ if __name__ == '__main__':
|
||||
exit(2)
|
||||
print('==== Verified protocol version successfully ====')
|
||||
|
||||
print('\n==== Starting Session ====')
|
||||
if not establish_session(obj_transport, obj_security):
|
||||
print('Failed to establish session. Ensure that security scheme and proof of possession are correct')
|
||||
print('---- Error in establishing session ----')
|
||||
exit(3)
|
||||
print('==== Session Established ====')
|
||||
|
||||
while True:
|
||||
properties = get_all_property_values(obj_transport)
|
||||
properties = get_all_property_values(obj_transport, obj_security)
|
||||
if len(properties) == 0:
|
||||
print('---- Error in reading property values ----')
|
||||
exit(4)
|
||||
@ -245,7 +387,7 @@ if __name__ == '__main__':
|
||||
select = 0
|
||||
while True:
|
||||
try:
|
||||
inval = input("\nSelect properties to set (0 to re-read, 'q' to quit) : ")
|
||||
inval = input('\nSelect properties to set (0 to re-read, \'q\' to quit) : ')
|
||||
if inval.lower() == 'q':
|
||||
print('Quitting...')
|
||||
exit(5)
|
||||
@ -274,5 +416,5 @@ if __name__ == '__main__':
|
||||
set_values += [value]
|
||||
set_indices += [select - 1]
|
||||
|
||||
if not set_property_values(obj_transport, properties, set_indices, set_values):
|
||||
if not set_property_values(obj_transport, obj_security, properties, set_indices, set_values):
|
||||
print('Failed to set values!')
|
||||
|
@ -36,35 +36,39 @@ constants_pb2 = _load_source('constants_pb2', idf_path + '/components/protocomm/
|
||||
local_ctrl_pb2 = _load_source('esp_local_ctrl_pb2', idf_path + '/components/esp_local_ctrl/python/esp_local_ctrl_pb2.py')
|
||||
|
||||
|
||||
def get_prop_count_request():
|
||||
def get_prop_count_request(security_ctx):
|
||||
req = local_ctrl_pb2.LocalCtrlMessage()
|
||||
req.msg = local_ctrl_pb2.TypeCmdGetPropertyCount
|
||||
payload = local_ctrl_pb2.CmdGetPropertyCount()
|
||||
req.cmd_get_prop_count.MergeFrom(payload)
|
||||
return req.SerializeToString()
|
||||
enc_cmd = security_ctx.encrypt_data(req.SerializeToString())
|
||||
return enc_cmd
|
||||
|
||||
|
||||
def get_prop_count_response(response_data):
|
||||
def get_prop_count_response(security_ctx, response_data):
|
||||
decrypt = security_ctx.decrypt_data(tobytes(response_data))
|
||||
resp = local_ctrl_pb2.LocalCtrlMessage()
|
||||
resp.ParseFromString(tobytes(response_data))
|
||||
resp.ParseFromString(decrypt)
|
||||
if (resp.resp_get_prop_count.status == 0):
|
||||
return resp.resp_get_prop_count.count
|
||||
else:
|
||||
return 0
|
||||
|
||||
|
||||
def get_prop_vals_request(indices):
|
||||
def get_prop_vals_request(security_ctx, indices):
|
||||
req = local_ctrl_pb2.LocalCtrlMessage()
|
||||
req.msg = local_ctrl_pb2.TypeCmdGetPropertyValues
|
||||
payload = local_ctrl_pb2.CmdGetPropertyValues()
|
||||
payload.indices.extend(indices)
|
||||
req.cmd_get_prop_vals.MergeFrom(payload)
|
||||
return req.SerializeToString()
|
||||
enc_cmd = security_ctx.encrypt_data(req.SerializeToString())
|
||||
return enc_cmd
|
||||
|
||||
|
||||
def get_prop_vals_response(response_data):
|
||||
def get_prop_vals_response(security_ctx, response_data):
|
||||
decrypt = security_ctx.decrypt_data(tobytes(response_data))
|
||||
resp = local_ctrl_pb2.LocalCtrlMessage()
|
||||
resp.ParseFromString(tobytes(response_data))
|
||||
resp.ParseFromString(decrypt)
|
||||
results = []
|
||||
if (resp.resp_get_prop_vals.status == 0):
|
||||
for prop in resp.resp_get_prop_vals.props:
|
||||
@ -77,7 +81,7 @@ def get_prop_vals_response(response_data):
|
||||
return results
|
||||
|
||||
|
||||
def set_prop_vals_request(indices, values):
|
||||
def set_prop_vals_request(security_ctx, indices, values):
|
||||
req = local_ctrl_pb2.LocalCtrlMessage()
|
||||
req.msg = local_ctrl_pb2.TypeCmdSetPropertyValues
|
||||
payload = local_ctrl_pb2.CmdSetPropertyValues()
|
||||
@ -86,10 +90,12 @@ def set_prop_vals_request(indices, values):
|
||||
prop.index = i
|
||||
prop.value = v
|
||||
req.cmd_set_prop_vals.MergeFrom(payload)
|
||||
return req.SerializeToString()
|
||||
enc_cmd = security_ctx.encrypt_data(req.SerializeToString())
|
||||
return enc_cmd
|
||||
|
||||
|
||||
def set_prop_vals_response(response_data):
|
||||
def set_prop_vals_response(security_ctx, response_data):
|
||||
decrypt = security_ctx.decrypt_data(tobytes(response_data))
|
||||
resp = local_ctrl_pb2.LocalCtrlMessage()
|
||||
resp.ParseFromString(tobytes(response_data))
|
||||
resp.ParseFromString(decrypt)
|
||||
return (resp.resp_set_prop_vals.status == 0)
|
@ -73,7 +73,7 @@ examples/protocols/cbor/example_test.py
|
||||
examples/protocols/esp_http_client/esp_http_client_test.py
|
||||
examples/protocols/esp_local_ctrl/example_test.py
|
||||
examples/protocols/esp_local_ctrl/scripts/esp_local_ctrl.py
|
||||
examples/protocols/esp_local_ctrl/scripts/proto.py
|
||||
examples/protocols/esp_local_ctrl/scripts/proto_lc.py
|
||||
examples/protocols/http_server/advanced_tests/http_server_advanced_test.py
|
||||
examples/protocols/http_server/advanced_tests/scripts/test.py
|
||||
examples/protocols/http_server/persistent_sockets/http_server_persistence_test.py
|
||||
|
Loading…
Reference in New Issue
Block a user