forked from openthread/openthread
-
Notifications
You must be signed in to change notification settings - Fork 9
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[tools] add tcat ble client (openthread#9739)
Adds TCAT client implementation for BLE transport. Signed-off-by: Piotr Jasinski <[email protected]> Co-authored-by: Przemyslaw Bida <[email protected]>
- Loading branch information
1 parent
869c2de
commit 905a22e
Showing
21 changed files
with
2,202 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
# BBTC X.509 certificates generation | ||
|
||
--- | ||
|
||
TCAT uses X.509 Certificate Extensions to provide permissions with certificates. | ||
|
||
## Extensions | ||
|
||
Extensions were introduced in version 3 of the X.509 standard for certificates. They allow certificates to be customised to applications by supporting the addition of arbitrary fields in the certificate. Each extension, identified by its OID (Object Identifier), is marked as "Critical" or "Non-Critical", and includes the extension-specific data. | ||
|
||
## Certificates generation | ||
|
||
Thread uses Elliptic Curve Cryptography (ECC), so we use the `ecparam` `openssl` argument to generate the keys. | ||
|
||
### Root certificate | ||
|
||
1. Generate the private key: | ||
|
||
``` | ||
openssl ecparam -genkey -name prime256v1 -out ca_key.pem | ||
``` | ||
|
||
1. We can then generate the **.csr** (certificate signing request) file, which will contain all the parameters of our final certificate: | ||
|
||
``` | ||
openssl req -new -sha256 -key ca_key.pem -out ca.csr | ||
``` | ||
|
||
1. Finally, we can generate the certificate itself: | ||
|
||
``` | ||
openssl req -x509 -sha256 -days 365 -key ca_key.pem -in ca.csr -out ca_cert.pem | ||
``` | ||
|
||
1. See the generated certificate using | ||
|
||
``` | ||
openssl x509 -in ca_cert.pem -text -noout | ||
``` | ||
|
||
### Commissioner (client) certificate | ||
|
||
1. Generate the key: | ||
|
||
``` | ||
openssl ecparam -genkey -name prime256v1 -out commissioner_key.pem | ||
``` | ||
|
||
1. Specify additional extensions when generating the .csr (see [sample configuration](#Configurations)): | ||
|
||
``` | ||
openssl req -new -sha256 -key commissioner_key.pem -out commissioner.csr -config commissioner.cnf | ||
``` | ||
|
||
1. Generate the certificate: | ||
|
||
``` | ||
openssl x509 -req -in commissioner.csr -CA ca_cert.pem -CAkey ca_key.pem -out commissioner_cert.pem -days 365 -sha256 -copy_extensions copy | ||
``` | ||
|
||
1. View the generated certificate using: | ||
|
||
``` | ||
openssl x509 -in commissioner_cert.pem -text -noout | ||
``` | ||
|
||
1. View parsed certificate extensions using: | ||
|
||
``` | ||
openssl asn1parse -inform PEM -in commissioner_cert.pem | ||
``` | ||
|
||
## Configurations | ||
|
||
file: `commissioner.cnf` (line `1.3.6.1.4.1.44970.3 = DER:21:01:01:01:01` specifies permissions (all)) | ||
|
||
``` | ||
[ req ] | ||
default_bits = 2048 | ||
distinguished_name = req_distinguished_name | ||
prompt = no | ||
req_extensions = v3_req | ||
[ req_distinguished_name ] | ||
CN = Commissioner | ||
[v3_req] | ||
1.3.6.1.4.1.44970.3 = DER:21:01:01:01:01 | ||
authorityKeyIdentifier = none | ||
subjectKeyIdentifier = none | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
# BBTC Client | ||
|
||
## Overview | ||
|
||
This is a Python implementation of Bluetooth-Based Thread Commissioning client, based on Thread's TCAT (Thread Commissioning over Authenticated TLS) functionality. | ||
|
||
## Installation | ||
|
||
If you don't have the poetry module installed (check with `poetry --version`), install it first using: | ||
|
||
```bash | ||
python3 -m pip install poetry | ||
``` | ||
|
||
Thread uses Elliptic Curve Cryptography (ECC), so we use the `ecparam` `openssl` argument to generate the keys. | ||
|
||
``` | ||
poetry install | ||
``` | ||
|
||
This will install all the required modules to a virtual environment, which can be used by calling `poetry run <COMMAND>` from the project directory. | ||
|
||
## Usage | ||
|
||
In order to connect to a TCAT device, enter the project directory and run: | ||
|
||
```bash | ||
poetry run python3 bbtc.py {<device specifier> | --scan} | ||
``` | ||
|
||
where `<device specifier>` can be: | ||
|
||
- `--name <NAME>` - name advertised by the device | ||
- `--mac <ADDRESS>` - physical address of the device's Bluetooth interface | ||
|
||
Using the `--scan` option will scan for every TCAT device and display them in a list, to allow selection of the target. | ||
|
||
For example: | ||
|
||
``` | ||
poetry run python3 bbtc.py --name 'Thread BLE' | ||
``` | ||
|
||
The application will connect to the first matching device discovered and set up a secure TLS channel. The user is then presented with the CLI. | ||
|
||
## Commands | ||
|
||
The application supports the following interactive CLI commands: | ||
|
||
- `help` - Display available commands. | ||
- `commission` - Commission the device with current dataset. | ||
- `thread start` - Enable Thread interface. | ||
- `thread stop` - Disable Thread interface. | ||
- `hello` - Send "hello world" application data and read the response. | ||
- `exit` - Close the connection and exit. | ||
- `dataset` - View and manipulate current dataset. See `dataset help` for more information. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
-----BEGIN CERTIFICATE----- | ||
MIICCDCCAa2gAwIBAgIJAIKxygBXoH+5MAoGCCqGSM49BAMCMG8xCzAJBgNVBAYT | ||
AlhYMRAwDgYDVQQIEwdNeVN0YXRlMQ8wDQYDVQQHEwZNeUNpdHkxDzANBgNVBAsT | ||
Bk15VW5pdDERMA8GA1UEChMITXlWZW5kb3IxGTAXBgNVBAMTEHd3dy5teXZlbmRv | ||
ci5jb20wHhcNMjMxMDE2MTAzMzE1WhcNMjYxMDE2MTAzMzE1WjBvMQswCQYDVQQG | ||
EwJYWDEQMA4GA1UECBMHTXlTdGF0ZTEPMA0GA1UEBxMGTXlDaXR5MQ8wDQYDVQQL | ||
EwZNeVVuaXQxETAPBgNVBAoTCE15VmVuZG9yMRkwFwYDVQQDExB3d3cubXl2ZW5k | ||
b3IuY29tMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEWdyzPAXGKeZY94OhHAWX | ||
HzJfQIjGSyaOzlgL9OEFw2SoUDncLKPGwfPAUSfuMyEkzszNDM0HHkBsDLqu4n25 | ||
/6MyMDAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU4EynoSw9eDKZEVPkums2 | ||
IWLAJCowCgYIKoZIzj0EAwIDSQAwRgIhAMYGGL9xShyE6P9wEU+MAYF6W3CzdrwV | ||
kuerX1encIH2AiEA5rq490NUobM1Au43roxJq1T6Z43LscPVbGZfULD1Jq0= | ||
-----END CERTIFICATE----- |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
-----BEGIN EC PRIVATE KEY----- | ||
MHcCAQEEII/jxkoUczPvlYM3ayhneif63B64lan3SubrXiVEbhoVoAoGCCqGSM49 | ||
AwEHoUQDQgAEWdyzPAXGKeZY94OhHAWXHzJfQIjGSyaOzlgL9OEFw2SoUDncLKPG | ||
wfPAUSfuMyEkzszNDM0HHkBsDLqu4n25/w== | ||
-----END EC PRIVATE KEY----- |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
-----BEGIN CERTIFICATE----- | ||
MIIBnzCCAUSgAwIBAgIUQ5RUJMc95ssHQybR6pcx7LXSBzcwCgYIKoZIzj0EAwIw | ||
bzELMAkGA1UEBhMCWFgxEDAOBgNVBAgTB015U3RhdGUxDzANBgNVBAcTBk15Q2l0 | ||
eTEPMA0GA1UECxMGTXlVbml0MREwDwYDVQQKEwhNeVZlbmRvcjEZMBcGA1UEAxMQ | ||
d3d3Lm15dmVuZG9yLmNvbTAeFw0yMzEwMTgxNTIyMTZaFw0zMzEwMTUxNTIyMTZa | ||
MBcxFTATBgNVBAMMDENvbW1pc3Npb25lcjBZMBMGByqGSM49AgEGCCqGSM49AwEH | ||
A0IABDU90Qpae5c+5Diou072S6MMHNv9+Ah9Kmo+mZTT6gQUyRScFey3+6wE08o7 | ||
wl6/8EKRgS8TFSihK4mYGxYoN06jFjAUMBIGCSsGAQQBgt8qAwQFIQEBAQEwCgYI | ||
KoZIzj0EAwIDSQAwRgIhAOgQH8wFSe3JtGSmEFLy4fbMhOg+5Mfhqoq95vu2ML/u | ||
AiEAt4BjuFo7GTxQxXl1e8TvMGESPGBKnR7cIT/BCnn2fto= | ||
-----END CERTIFICATE----- |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
-----BEGIN EC PARAMETERS----- | ||
BggqhkjOPQMBBw== | ||
-----END EC PARAMETERS----- | ||
-----BEGIN EC PRIVATE KEY----- | ||
MHcCAQEEIFOGszqvbs62fRwd3Rnd79wf6fpWxkXLO5YzhEuJ9EV1oAoGCCqGSM49 | ||
AwEHoUQDQgAENT3RClp7lz7kOKi7TvZLowwc2/34CH0qaj6ZlNPqBBTJFJwV7Lf7 | ||
rATTyjvCXr/wQpGBLxMVKKEriZgbFig3Tg== | ||
-----END EC PRIVATE KEY----- |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
""" | ||
Copyright (c) 2024, The OpenThread Authors. | ||
All rights reserved. | ||
Redistribution and use in source and binary forms, with or without | ||
modification, are permitted provided that the following conditions are met: | ||
1. Redistributions of source code must retain the above copyright | ||
notice, this list of conditions and the following disclaimer. | ||
2. Redistributions in binary form must reproduce the above copyright | ||
notice, this list of conditions and the following disclaimer in the | ||
documentation and/or other materials provided with the distribution. | ||
3. Neither the name of the copyright holder nor the | ||
names of its contributors may be used to endorse or promote products | ||
derived from this software without specific prior written permission. | ||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | ||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | ||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | ||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE | ||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | ||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | ||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | ||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN | ||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | ||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | ||
POSSIBILITY OF SUCH DAMAGE. | ||
""" | ||
|
||
import asyncio | ||
import argparse | ||
from os import path | ||
import logging | ||
|
||
from ble.ble_connection_constants import BBTC_SERVICE_UUID, BBTC_TX_CHAR_UUID, \ | ||
BBTC_RX_CHAR_UUID, SERVER_COMMON_NAME | ||
from ble.ble_stream import BleStream | ||
from ble.ble_stream_secure import BleStreamSecure | ||
from ble import ble_scanner | ||
from cli.cli import CLI | ||
from dataset.dataset import ThreadDataset | ||
from cli.command import CommandResult | ||
from utils import select_device_by_user_input | ||
|
||
|
||
async def main(): | ||
logging.basicConfig(level=logging.WARNING) | ||
|
||
parser = argparse.ArgumentParser(description='Device parameters') | ||
parser.add_argument('--debug', help='Enable debug logs', action='store_true') | ||
group = parser.add_mutually_exclusive_group() | ||
group.add_argument('--mac', type=str, help='Device MAC address', action='store') | ||
group.add_argument('--name', type=str, help='Device name', action='store') | ||
group.add_argument('--scan', help='Scan all available devices', action='store_true') | ||
args = parser.parse_args() | ||
|
||
if args.debug: | ||
logging.getLogger('ble_stream').setLevel(logging.DEBUG) | ||
logging.getLogger('ble_stream_secure').setLevel(logging.DEBUG) | ||
|
||
device = await get_device_by_args(args) | ||
|
||
ble_sstream = None | ||
|
||
if not (device is None): | ||
print(f'Connecting to {device}') | ||
ble_stream = await BleStream.create(device.address, BBTC_SERVICE_UUID, BBTC_TX_CHAR_UUID, BBTC_RX_CHAR_UUID) | ||
ble_sstream = BleStreamSecure(ble_stream) | ||
ble_sstream.load_cert( | ||
certfile=path.join('auth', 'commissioner_cert.pem'), | ||
keyfile=path.join('auth', 'commissioner_key.pem'), | ||
cafile=path.join('auth', 'ca_cert.pem'), | ||
) | ||
|
||
print('Setting up secure channel...') | ||
await ble_sstream.do_handshake(hostname=SERVER_COMMON_NAME) | ||
print('Done') | ||
|
||
ds = ThreadDataset() | ||
cli = CLI(ds, ble_sstream) | ||
loop = asyncio.get_running_loop() | ||
print('Enter \'help\' to see available commands' ' or \'exit\' to exit the application.') | ||
while True: | ||
user_input = await loop.run_in_executor(None, lambda: input('> ')) | ||
if user_input.lower() == 'exit': | ||
print('Disconnecting...') | ||
break | ||
try: | ||
result: CommandResult = await cli.evaluate_input(user_input) | ||
if result: | ||
result.pretty_print() | ||
except Exception as e: | ||
print(e) | ||
|
||
|
||
async def get_device_by_args(args): | ||
device = None | ||
if args.mac: | ||
device = await ble_scanner.find_first_by_mac(args.mac) | ||
elif args.name: | ||
device = await ble_scanner.find_first_by_name(args.name) | ||
elif args.scan: | ||
tcat_devices = await ble_scanner.scan_tcat_devices() | ||
device = select_device_by_user_input(tcat_devices) | ||
|
||
return device | ||
|
||
|
||
if __name__ == '__main__': | ||
try: | ||
asyncio.run(main()) | ||
except asyncio.CancelledError: | ||
pass # device disconnected |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
""" | ||
Copyright (c) 2024, The OpenThread Authors. | ||
All rights reserved. | ||
Redistribution and use in source and binary forms, with or without | ||
modification, are permitted provided that the following conditions are met: | ||
1. Redistributions of source code must retain the above copyright | ||
notice, this list of conditions and the following disclaimer. | ||
2. Redistributions in binary form must reproduce the above copyright | ||
notice, this list of conditions and the following disclaimer in the | ||
documentation and/or other materials provided with the distribution. | ||
3. Neither the name of the copyright holder nor the | ||
names of its contributors may be used to endorse or promote products | ||
derived from this software without specific prior written permission. | ||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | ||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | ||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | ||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE | ||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | ||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | ||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | ||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN | ||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | ||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | ||
POSSIBILITY OF SUCH DAMAGE. | ||
""" | ||
|
||
BBTC_SERVICE_UUID = 'FFFB' | ||
BBTC_RX_CHAR_UUID = '6BD10D8B-85A7-4E5A-BA2D-C83558A5F220' | ||
BBTC_TX_CHAR_UUID = '7FDDF61F-280A-4773-B448-BA1B8FE0DD69' | ||
SERVER_COMMON_NAME = 'myvendor.com/tcat/mydev' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
""" | ||
Copyright (c) 2024, The OpenThread Authors. | ||
All rights reserved. | ||
Redistribution and use in source and binary forms, with or without | ||
modification, are permitted provided that the following conditions are met: | ||
1. Redistributions of source code must retain the above copyright | ||
notice, this list of conditions and the following disclaimer. | ||
2. Redistributions in binary form must reproduce the above copyright | ||
notice, this list of conditions and the following disclaimer in the | ||
documentation and/or other materials provided with the distribution. | ||
3. Neither the name of the copyright holder nor the | ||
names of its contributors may be used to endorse or promote products | ||
derived from this software without specific prior written permission. | ||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | ||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | ||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | ||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE | ||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | ||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | ||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | ||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN | ||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | ||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | ||
POSSIBILITY OF SUCH DAMAGE. | ||
""" | ||
|
||
from bleak import BleakScanner | ||
from bbtc import BBTC_SERVICE_UUID | ||
|
||
|
||
async def find_first_by_name(name): | ||
match_name = lambda dev, adv_data: name == dev.name | ||
device = await BleakScanner.find_device_by_filter(match_name) | ||
return device | ||
|
||
|
||
async def find_first_by_mac(mac): | ||
match_mac = lambda dev, adv_data: mac.upper() == dev.address | ||
device = await BleakScanner.find_device_by_filter(match_mac) | ||
return device | ||
|
||
|
||
async def scan_tcat_devices(): | ||
scanner = BleakScanner() | ||
tcat_devices = [] | ||
devices_dict = await scanner.discover(return_adv=True, service_uuids=[BBTC_SERVICE_UUID.lower()]) | ||
for _, (device, _) in devices_dict.items(): | ||
tcat_devices.append(device) | ||
|
||
return tcat_devices |
Oops, something went wrong.