Skip to content

Commit

Permalink
[tools] add tcat ble client (openthread#9739)
Browse files Browse the repository at this point in the history
Adds TCAT client implementation for BLE transport.

Signed-off-by: Piotr Jasinski <[email protected]>
Co-authored-by: Przemyslaw Bida <[email protected]>
  • Loading branch information
pjasinski990 and Przemyslaw Bida authored Feb 7, 2024
1 parent 869c2de commit 905a22e
Show file tree
Hide file tree
Showing 21 changed files with 2,202 additions and 0 deletions.
91 changes: 91 additions & 0 deletions tools/tcat_ble_client/GENERATING_CERTIFICATES.md
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
```
56 changes: 56 additions & 0 deletions tools/tcat_ble_client/README.md
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.
13 changes: 13 additions & 0 deletions tools/tcat_ble_client/auth/ca_cert.pem
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-----
5 changes: 5 additions & 0 deletions tools/tcat_ble_client/auth/ca_key.pem
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-----
11 changes: 11 additions & 0 deletions tools/tcat_ble_client/auth/commissioner_cert.pem
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-----
8 changes: 8 additions & 0 deletions tools/tcat_ble_client/auth/commissioner_key.pem
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-----
112 changes: 112 additions & 0 deletions tools/tcat_ble_client/bbtc.py
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
32 changes: 32 additions & 0 deletions tools/tcat_ble_client/ble/ble_connection_constants.py
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'
52 changes: 52 additions & 0 deletions tools/tcat_ble_client/ble/ble_scanner.py
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
Loading

0 comments on commit 905a22e

Please sign in to comment.