1 /*
2 * Copyright (c) 2023, The OpenThread Authors.
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 * 3. Neither the name of the copyright holder nor the
13 * names of its contributors may be used to endorse or promote products
14 * derived from this software without specific prior written permission.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
20 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26 * POSSIBILITY OF SUCH DAMAGE.
27 */
28
29 #include "openthread-core-config.h"
30
31 #include "cli/cli_utils.hpp"
32
33 #include "cli/cli_tcat.hpp"
34 #include "common/code_utils.hpp"
35 #include "common/error.hpp"
36
37 #include <openthread/ble_secure.h>
38
39 #include <mbedtls/oid.h>
40 #include <openthread/error.h>
41 #include <openthread/tcat.h>
42 #include <openthread/platform/ble.h>
43
44 #if OPENTHREAD_CONFIG_BLE_TCAT_ENABLE && OPENTHREAD_CONFIG_CLI_BLE_SECURE_ENABLE
45
46 // DeviceCert1 default identity for TCAT certification testing.
47 // WARNING: storage of private keys in code or program memory MUST NOT be used in production.
48 // The below code is for testing purposes only. For production, secure key storage must be
49 // used to store private keys.
50 #define OT_CLI_TCAT_X509_CERT \
51 "-----BEGIN CERTIFICATE-----\n" \
52 "MIIB6TCCAZCgAwIBAgICNekwCgYIKoZIzj0EAwIwcTEmMCQGA1UEAwwdVGhyZWFk\n" \
53 "IENlcnRpZmljYXRpb24gRGV2aWNlQ0ExGTAXBgNVBAoMEFRocmVhZCBHcm91cCBJ\n" \
54 "bmMxEjAQBgNVBAcMCVNhbiBSYW1vbjELMAkGA1UECAwCQ0ExCzAJBgNVBAYTAlVT\n" \
55 "MCAXDTI0MDUwNzA5Mzk0NVoYDzI5OTkxMjMxMDkzOTQ1WjA8MSEwHwYDVQQDDBhU\n" \
56 "Q0FUIEV4YW1wbGUgRGV2aWNlQ2VydDExFzAVBgNVBAUTDjQ3MjMtOTgzMy0wMDAx\n" \
57 "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE11h/4vKZXVXv+1GDZo066spItloT\n" \
58 "dpCi0bux0jvpQSHLdQBIc+40zVCxMDRUvbX//vJKGsSJKOVUlCojQ2wIdqNLMEkw\n" \
59 "HwYDVR0jBBgwFoAUX6sbKWiIodS0MaiGYefnZlnt+BkwEAYJKwYBBAGC3yoCBAMC\n" \
60 "AQUwFAYJKwYBBAGC3yoDBAcEBSABAQEBMAoGCCqGSM49BAMCA0cAMEQCIHWu+Rd1\n" \
61 "VRlzrD8KbuyJcJFTXh2sQ9UIrFIA7+4e/GVcAiAVBdGqTxbt3TGkBBllpafAUB2/\n" \
62 "s0GJj7E33oblqy5eHQ==\n" \
63 "-----END CERTIFICATE-----\n"
64
65 #define OT_CLI_TCAT_PRIV_KEY \
66 "-----BEGIN EC PRIVATE KEY-----\n" \
67 "MHcCAQEEIIqKM1QTlNaquV74W6Viz/ggXoLqlPOP6LagSyaFO3oUoAoGCCqGSM49\n" \
68 "AwEHoUQDQgAE11h/4vKZXVXv+1GDZo066spItloTdpCi0bux0jvpQSHLdQBIc+40\n" \
69 "zVCxMDRUvbX//vJKGsSJKOVUlCojQ2wIdg==\n" \
70 "-----END EC PRIVATE KEY-----\n"
71
72 #define OT_CLI_TCAT_TRUSTED_ROOT_CERTIFICATE \
73 "-----BEGIN CERTIFICATE-----\n" \
74 "MIICOzCCAeGgAwIBAgIJAKOc2hehOGoBMAoGCCqGSM49BAMCMHExJjAkBgNVBAMM\n" \
75 "HVRocmVhZCBDZXJ0aWZpY2F0aW9uIERldmljZUNBMRkwFwYDVQQKDBBUaHJlYWQg\n" \
76 "R3JvdXAgSW5jMRIwEAYDVQQHDAlTYW4gUmFtb24xCzAJBgNVBAgMAkNBMQswCQYD\n" \
77 "VQQGEwJVUzAeFw0yNDA1MDMyMDAyMThaFw00NDA0MjgyMDAyMThaMHExJjAkBgNV\n" \
78 "BAMMHVRocmVhZCBDZXJ0aWZpY2F0aW9uIERldmljZUNBMRkwFwYDVQQKDBBUaHJl\n" \
79 "YWQgR3JvdXAgSW5jMRIwEAYDVQQHDAlTYW4gUmFtb24xCzAJBgNVBAgMAkNBMQsw\n" \
80 "CQYDVQQGEwJVUzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABGy850VBIPTkN3oL\n" \
81 "x++zIUsZk2k26w4fuieFz9oNvjdb5W14+Yf3mvGWsl4NHyLxqhmamVAR4h7zWRlZ\n" \
82 "0XyMVpKjYjBgMB4GA1UdEQQXMBWBE3RvbUB0aHJlYWRncm91cC5vcmcwDgYDVR0P\n" \
83 "AQH/BAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFF+rGyloiKHUtDGo\n" \
84 "hmHn52ZZ7fgZMAoGCCqGSM49BAMCA0gAMEUCIQCTq1qjPZs9fAJB6ppTXs588Pnu\n" \
85 "eVFOwC8bd//D99KiHAIgU84kwFHIyDvFqu6y+u1hFqBGsiuTmKwZ2PHhVe/xK1k=\n" \
86 "-----END CERTIFICATE-----\n"
87
88 namespace ot {
89
90 namespace Cli {
91
92 otTcatAdvertisedDeviceId sAdvertisedDeviceIds[OT_TCAT_DEVICE_ID_MAX];
93 otTcatGeneralDeviceId sGeneralDeviceId;
94
95 const char kPskdVendor[] = "JJJJJJ";
96 const char kInstallVendor[] = "InstallCode";
97 const char kUrl[] = "dummy_url";
98
IsDeviceIdSet(void)99 static bool IsDeviceIdSet(void)
100 {
101 bool ret = false;
102 for (const otTcatAdvertisedDeviceId &vendorDeviceId : sAdvertisedDeviceIds)
103 {
104 if (vendorDeviceId.mDeviceIdType != OT_TCAT_DEVICE_ID_EMPTY)
105 {
106 ExitNow(ret = true);
107 }
108 }
109 exit:
110 return ret;
111 }
112
HandleBleSecureReceive(otInstance * aInstance,const otMessage * aMessage,int32_t aOffset,otTcatApplicationProtocol aTcatApplicationProtocol,const char * aServiceName,void * aContext)113 static void HandleBleSecureReceive(otInstance *aInstance,
114 const otMessage *aMessage,
115 int32_t aOffset,
116 otTcatApplicationProtocol aTcatApplicationProtocol,
117 const char *aServiceName,
118 void *aContext)
119 {
120 OT_UNUSED_VARIABLE(aContext);
121 OT_UNUSED_VARIABLE(aTcatApplicationProtocol);
122 OT_UNUSED_VARIABLE(aServiceName);
123
124 static constexpr int kTextMaxLen = 100;
125 static constexpr uint8_t kBufPrefixLen = 5;
126
127 uint16_t nLen;
128 uint8_t buf[kTextMaxLen];
129
130 nLen =
131 otMessageRead(aMessage, static_cast<uint16_t>(aOffset), buf + kBufPrefixLen, sizeof(buf) - kBufPrefixLen - 1);
132
133 memcpy(buf, "RECV:", kBufPrefixLen);
134
135 buf[nLen + kBufPrefixLen] = 0;
136
137 IgnoreReturnValue(otBleSecureSendApplicationTlv(aInstance, buf, (uint16_t)strlen((char *)buf)));
138 IgnoreReturnValue(otBleSecureFlush(aInstance));
139 }
140
141 /**
142 * @cli tcat advid
143 * @code
144 * tcat advid ianapen f378aabb
145 * Done
146 * @endcode
147 * @cparam tcat advid [@ca{id_type}] [@ca{value}]
148 * * The `id_type` has five possible values:
149 * * `clear` - removes all previously set advertised IDs.
150 * * `oui24` - sets OUI24 ID type.
151 * * `oui36` - sets OUI36 ID type.
152 * * `discriminator` - sets discriminator ID type.
153 * * `ianapen` - sets IANA PEN ID type.
154 * * The `value` hexstring value of the ID.
155 * @par
156 * Sets/clears advertised ID type and value.
157 */
Process(Arg aArgs[])158 template <> otError Tcat::Process<Cmd("advid")>(Arg aArgs[])
159 {
160 otError error = OT_ERROR_NONE;
161 otTcatAdvertisedDeviceId devId;
162 static const char *const kVendorIdTypes[] = {"clear", "oui24", "oui36", "discriminator", "ianapen"};
163
164 mVendorInfo.mAdvertisedDeviceIds = sAdvertisedDeviceIds;
165
166 if (aArgs[0].IsEmpty())
167 {
168 if (mVendorInfo.mAdvertisedDeviceIds[0].mDeviceIdType != OT_TCAT_DEVICE_ID_EMPTY)
169 {
170 OutputLine("Set advertisedIds:");
171 for (size_t i = 0; mVendorInfo.mAdvertisedDeviceIds[i].mDeviceIdType != OT_TCAT_DEVICE_ID_EMPTY; i++)
172 {
173 OutputFormat("type %s, value: ", kVendorIdTypes[mVendorInfo.mAdvertisedDeviceIds[i].mDeviceIdType]);
174 OutputBytesLine(const_cast<uint8_t *>(mVendorInfo.mAdvertisedDeviceIds[i].mDeviceId),
175 mVendorInfo.mAdvertisedDeviceIds[i].mDeviceIdLen);
176 }
177 }
178 ExitNow();
179 }
180
181 if (aArgs[0] == kVendorIdTypes[OT_TCAT_DEVICE_ID_OUI24])
182 {
183 devId.mDeviceIdType = OT_TCAT_DEVICE_ID_OUI24;
184 }
185 else if (aArgs[0] == kVendorIdTypes[OT_TCAT_DEVICE_ID_OUI36])
186 {
187 devId.mDeviceIdType = OT_TCAT_DEVICE_ID_OUI36;
188 }
189 else if (aArgs[0] == kVendorIdTypes[OT_TCAT_DEVICE_ID_DISCRIMINATOR])
190 {
191 devId.mDeviceIdType = OT_TCAT_DEVICE_ID_DISCRIMINATOR;
192 }
193 else if (aArgs[0] == kVendorIdTypes[OT_TCAT_DEVICE_ID_IANAPEN])
194 {
195 devId.mDeviceIdType = OT_TCAT_DEVICE_ID_IANAPEN;
196 }
197 else if (aArgs[0] == kVendorIdTypes[OT_TCAT_DEVICE_ID_EMPTY])
198 {
199 for (otTcatAdvertisedDeviceId &vendorDeviceId : sAdvertisedDeviceIds)
200 {
201 vendorDeviceId.mDeviceIdType = OT_TCAT_DEVICE_ID_EMPTY;
202 vendorDeviceId.mDeviceIdLen = 0;
203 }
204 ExitNow();
205 }
206 else
207 {
208 ExitNow(error = OT_ERROR_INVALID_ARGS);
209 }
210
211 if (!aArgs[1].IsEmpty() && aArgs[1].GetLength() < (OT_TCAT_MAX_ADVERTISED_DEVICEID_SIZE * 2 + 1))
212 {
213 devId.mDeviceIdLen = OT_TCAT_MAX_ADVERTISED_DEVICEID_SIZE;
214 SuccessOrExit(error = aArgs[1].ParseAsHexString(devId.mDeviceIdLen, devId.mDeviceId));
215 for (otTcatAdvertisedDeviceId &vendorDeviceId : sAdvertisedDeviceIds)
216 {
217 if (vendorDeviceId.mDeviceIdType == devId.mDeviceIdType ||
218 vendorDeviceId.mDeviceIdType == OT_TCAT_DEVICE_ID_EMPTY)
219 {
220 vendorDeviceId = devId;
221 break;
222 }
223 }
224 }
225 else
226 {
227 ExitNow(error = OT_ERROR_INVALID_ARGS);
228 }
229 exit:
230 return error;
231 }
232
233 /**
234 * @cli tcat devid
235 * @code
236 * tcat devid ianapen f378aabb
237 * Done
238 * @endcode
239 * @cparam tcat devid [@ca{value}|clear]
240 * * The `value` hexstring value of the ID. `clear` is a special value removing previously set ID.
241 * @par
242 * Sets/clears vendor specific device ID.
243 */
Process(Arg aArgs[])244 template <> otError Tcat::Process<Cmd("devid")>(Arg aArgs[])
245 {
246 otError error = OT_ERROR_NONE;
247
248 if (aArgs[0].IsEmpty())
249 {
250 if (sGeneralDeviceId.mDeviceIdLen != 0)
251 {
252 OutputLine("TCAT DeviceId:");
253 OutputBytesLine(sGeneralDeviceId.mDeviceId, sGeneralDeviceId.mDeviceIdLen);
254 }
255 ExitNow();
256 }
257
258 if (aArgs[0] == "clear")
259 {
260 ClearAllBytes(sGeneralDeviceId);
261 }
262 else
263 {
264 VerifyOrExit(aArgs[0].GetLength() < (OT_TCAT_MAX_DEVICEID_SIZE * 2 + 1), error = OT_ERROR_INVALID_ARGS);
265 sGeneralDeviceId.mDeviceIdLen = OT_TCAT_MAX_DEVICEID_SIZE;
266 SuccessOrExit(error = aArgs[0].ParseAsHexString(sGeneralDeviceId.mDeviceIdLen, sGeneralDeviceId.mDeviceId));
267 }
268
269 exit:
270 return error;
271 }
272
273 /**
274 * @cli tcat start
275 * @code
276 * tcat start
277 * Done
278 * @endcode
279 * @par
280 * Starts TCAT operation.
281 * @sa otBleSecureSetCertificate
282 * @sa otBleSecureSetCaCertificateChain
283 * @sa otBleSecureSetSslAuthMode
284 * @sa otBleSecureSetTcatVendorInfo
285 * @sa otBleSecureStart
286 * @sa otBleSecureTcatStart
287 */
Process(Arg aArgs[])288 template <> otError Tcat::Process<Cmd("start")>(Arg aArgs[])
289 {
290 OT_UNUSED_VARIABLE(aArgs);
291
292 otError error = OT_ERROR_NONE;
293
294 ClearAllBytes(mVendorInfo);
295 mVendorInfo.mPskdString = kPskdVendor;
296 mVendorInfo.mProvisioningUrl = kUrl;
297 mVendorInfo.mInstallCode = kInstallVendor;
298
299 if (IsDeviceIdSet())
300 {
301 mVendorInfo.mAdvertisedDeviceIds = sAdvertisedDeviceIds;
302 }
303
304 if (sGeneralDeviceId.mDeviceIdLen != 0)
305 {
306 mVendorInfo.mGeneralDeviceId = &sGeneralDeviceId;
307 }
308
309 otBleSecureSetCertificate(GetInstancePtr(), reinterpret_cast<const uint8_t *>(OT_CLI_TCAT_X509_CERT),
310 sizeof(OT_CLI_TCAT_X509_CERT), reinterpret_cast<const uint8_t *>(OT_CLI_TCAT_PRIV_KEY),
311 sizeof(OT_CLI_TCAT_PRIV_KEY));
312
313 otBleSecureSetCaCertificateChain(GetInstancePtr(),
314 reinterpret_cast<const uint8_t *>(OT_CLI_TCAT_TRUSTED_ROOT_CERTIFICATE),
315 sizeof(OT_CLI_TCAT_TRUSTED_ROOT_CERTIFICATE));
316
317 otBleSecureSetSslAuthMode(GetInstancePtr(), true);
318
319 SuccessOrExit(error = otBleSecureSetTcatVendorInfo(GetInstancePtr(), &mVendorInfo));
320 SuccessOrExit(error = otBleSecureStart(GetInstancePtr(), nullptr, HandleBleSecureReceive, true, nullptr));
321 SuccessOrExit(error = otBleSecureTcatStart(GetInstancePtr(), nullptr));
322
323 exit:
324 return error;
325 }
326
327 /**
328 * @cli tcat stop
329 * @code
330 * tcat stop
331 * Done
332 * @endcode
333 * @par
334 * Stops TCAT operation.
335 * @sa otBleSecureStop
336 */
Process(Arg aArgs[])337 template <> otError Tcat::Process<Cmd("stop")>(Arg aArgs[])
338 {
339 OT_UNUSED_VARIABLE(aArgs);
340
341 otBleSecureStop(GetInstancePtr());
342
343 return OT_ERROR_NONE;
344 }
345
Process(Arg aArgs[])346 otError Tcat::Process(Arg aArgs[])
347 {
348 #define CmdEntry(aCommandString) \
349 { \
350 aCommandString, &Tcat::Process<Cmd(aCommandString)> \
351 }
352
353 static constexpr Command kCommands[] = {CmdEntry("advid"), CmdEntry("devid"), CmdEntry("start"), CmdEntry("stop")};
354
355 static_assert(BinarySearch::IsSorted(kCommands), "kCommands is not sorted");
356
357 #undef CmdEntry
358
359 otError error = OT_ERROR_NONE;
360 const Command *command;
361
362 if (aArgs[0].IsEmpty() || (aArgs[0] == "help"))
363 {
364 OutputCommandTable(kCommands);
365 ExitNow(error = aArgs[0].IsEmpty() ? error : OT_ERROR_NONE);
366 }
367
368 command = BinarySearch::Find(aArgs[0].GetCString(), kCommands);
369 VerifyOrExit(command != nullptr);
370
371 error = (this->*command->mHandler)(aArgs + 1);
372
373 exit:
374 return error;
375 }
376
377 } // namespace Cli
378 } // namespace ot
379 #endif // OPENTHREAD_CONFIG_BLE_TCAT_ENABLE && OPENTHREAD_CONFIG_CLI_BLE_SECURE_ENABLE
380