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 kUrl[] = "dummy_url";
97
IsDeviceIdSet(void)98 static bool IsDeviceIdSet(void)
99 {
100 bool ret = false;
101 for (const otTcatAdvertisedDeviceId &vendorDeviceId : sAdvertisedDeviceIds)
102 {
103 if (vendorDeviceId.mDeviceIdType != OT_TCAT_DEVICE_ID_EMPTY)
104 {
105 ExitNow(ret = true);
106 }
107 }
108 exit:
109 return ret;
110 }
111
HandleBleSecureReceive(otInstance * aInstance,const otMessage * aMessage,int32_t aOffset,otTcatApplicationProtocol aTcatApplicationProtocol,const char * aServiceName,void * aContext)112 static void HandleBleSecureReceive(otInstance *aInstance,
113 const otMessage *aMessage,
114 int32_t aOffset,
115 otTcatApplicationProtocol aTcatApplicationProtocol,
116 const char *aServiceName,
117 void *aContext)
118 {
119 OT_UNUSED_VARIABLE(aContext);
120 OT_UNUSED_VARIABLE(aTcatApplicationProtocol);
121 OT_UNUSED_VARIABLE(aServiceName);
122
123 static constexpr int kTextMaxLen = 100;
124 static constexpr uint8_t kBufPrefixLen = 5;
125
126 uint16_t nLen;
127 uint8_t buf[kTextMaxLen];
128
129 nLen =
130 otMessageRead(aMessage, static_cast<uint16_t>(aOffset), buf + kBufPrefixLen, sizeof(buf) - kBufPrefixLen - 1);
131
132 memcpy(buf, "RECV:", kBufPrefixLen);
133
134 buf[nLen + kBufPrefixLen] = 0;
135
136 IgnoreReturnValue(otBleSecureSendApplicationTlv(aInstance, buf, (uint16_t)strlen((char *)buf)));
137 IgnoreReturnValue(otBleSecureFlush(aInstance));
138 }
139
Process(Arg aArgs[])140 template <> otError Tcat::Process<Cmd("advid")>(Arg aArgs[])
141 {
142 otError error = OT_ERROR_NONE;
143 otTcatAdvertisedDeviceId devId;
144 static const char *const kVendorIdTypes[] = {"clear", "oui24", "oui36", "discriminator", "ianapen"};
145
146 mVendorInfo.mAdvertisedDeviceIds = sAdvertisedDeviceIds;
147
148 if (aArgs[0].IsEmpty())
149 {
150 if (mVendorInfo.mAdvertisedDeviceIds[0].mDeviceIdType != OT_TCAT_DEVICE_ID_EMPTY)
151 {
152 OutputLine("Set advertisedIds:");
153 for (size_t i = 0; mVendorInfo.mAdvertisedDeviceIds[i].mDeviceIdType != OT_TCAT_DEVICE_ID_EMPTY; i++)
154 {
155 OutputFormat("type %s, value: ", kVendorIdTypes[mVendorInfo.mAdvertisedDeviceIds[i].mDeviceIdType]);
156 OutputBytesLine(const_cast<uint8_t *>(mVendorInfo.mAdvertisedDeviceIds[i].mDeviceId),
157 mVendorInfo.mAdvertisedDeviceIds[i].mDeviceIdLen);
158 }
159 }
160 ExitNow();
161 }
162
163 if (aArgs[0] == kVendorIdTypes[OT_TCAT_DEVICE_ID_OUI24])
164 {
165 devId.mDeviceIdType = OT_TCAT_DEVICE_ID_OUI24;
166 }
167 else if (aArgs[0] == kVendorIdTypes[OT_TCAT_DEVICE_ID_OUI36])
168 {
169 devId.mDeviceIdType = OT_TCAT_DEVICE_ID_OUI36;
170 }
171 else if (aArgs[0] == kVendorIdTypes[OT_TCAT_DEVICE_ID_DISCRIMINATOR])
172 {
173 devId.mDeviceIdType = OT_TCAT_DEVICE_ID_DISCRIMINATOR;
174 }
175 else if (aArgs[0] == kVendorIdTypes[OT_TCAT_DEVICE_ID_IANAPEN])
176 {
177 devId.mDeviceIdType = OT_TCAT_DEVICE_ID_IANAPEN;
178 }
179 else if (aArgs[0] == kVendorIdTypes[OT_TCAT_DEVICE_ID_EMPTY])
180 {
181 for (otTcatAdvertisedDeviceId &vendorDeviceId : sAdvertisedDeviceIds)
182 {
183 vendorDeviceId.mDeviceIdType = OT_TCAT_DEVICE_ID_EMPTY;
184 vendorDeviceId.mDeviceIdLen = 0;
185 }
186 ExitNow();
187 }
188 else
189 {
190 ExitNow(error = OT_ERROR_INVALID_ARGS);
191 }
192
193 if (!aArgs[1].IsEmpty() && aArgs[1].GetLength() < (OT_TCAT_MAX_ADVERTISED_DEVICEID_SIZE * 2 + 1))
194 {
195 devId.mDeviceIdLen = OT_TCAT_MAX_ADVERTISED_DEVICEID_SIZE;
196 SuccessOrExit(error = aArgs[1].ParseAsHexString(devId.mDeviceIdLen, devId.mDeviceId));
197 for (otTcatAdvertisedDeviceId &vendorDeviceId : sAdvertisedDeviceIds)
198 {
199 if (vendorDeviceId.mDeviceIdType == devId.mDeviceIdType ||
200 vendorDeviceId.mDeviceIdType == OT_TCAT_DEVICE_ID_EMPTY)
201 {
202 vendorDeviceId = devId;
203 break;
204 }
205 }
206 }
207 else
208 {
209 ExitNow(error = OT_ERROR_INVALID_ARGS);
210 }
211 exit:
212 return error;
213 }
214
Process(Arg aArgs[])215 template <> otError Tcat::Process<Cmd("devid")>(Arg aArgs[])
216 {
217 otError error = OT_ERROR_NONE;
218
219 if (aArgs[0].IsEmpty())
220 {
221 if (sGeneralDeviceId.mDeviceIdLen != 0)
222 {
223 OutputLine("TCAT DeviceId:");
224 OutputBytesLine(sGeneralDeviceId.mDeviceId, sGeneralDeviceId.mDeviceIdLen);
225 }
226 ExitNow();
227 }
228
229 if (aArgs[0] == "clear")
230 {
231 ClearAllBytes(sGeneralDeviceId);
232 }
233 else
234 {
235 VerifyOrExit(aArgs[0].GetLength() < (OT_TCAT_MAX_DEVICEID_SIZE * 2 + 1), error = OT_ERROR_INVALID_ARGS);
236 sGeneralDeviceId.mDeviceIdLen = OT_TCAT_MAX_DEVICEID_SIZE;
237 SuccessOrExit(error = aArgs[0].ParseAsHexString(sGeneralDeviceId.mDeviceIdLen, sGeneralDeviceId.mDeviceId));
238 }
239
240 exit:
241 return error;
242 }
243
Process(Arg aArgs[])244 template <> otError Tcat::Process<Cmd("start")>(Arg aArgs[])
245 {
246 OT_UNUSED_VARIABLE(aArgs);
247
248 otError error = OT_ERROR_NONE;
249
250 ClearAllBytes(mVendorInfo);
251 mVendorInfo.mPskdString = kPskdVendor;
252 mVendorInfo.mProvisioningUrl = kUrl;
253
254 if (IsDeviceIdSet())
255 {
256 mVendorInfo.mAdvertisedDeviceIds = sAdvertisedDeviceIds;
257 }
258
259 if (sGeneralDeviceId.mDeviceIdLen != 0)
260 {
261 mVendorInfo.mGeneralDeviceId = &sGeneralDeviceId;
262 }
263
264 otBleSecureSetCertificate(GetInstancePtr(), reinterpret_cast<const uint8_t *>(OT_CLI_TCAT_X509_CERT),
265 sizeof(OT_CLI_TCAT_X509_CERT), reinterpret_cast<const uint8_t *>(OT_CLI_TCAT_PRIV_KEY),
266 sizeof(OT_CLI_TCAT_PRIV_KEY));
267
268 otBleSecureSetCaCertificateChain(GetInstancePtr(),
269 reinterpret_cast<const uint8_t *>(OT_CLI_TCAT_TRUSTED_ROOT_CERTIFICATE),
270 sizeof(OT_CLI_TCAT_TRUSTED_ROOT_CERTIFICATE));
271
272 otBleSecureSetSslAuthMode(GetInstancePtr(), true);
273
274 SuccessOrExit(error = otBleSecureSetTcatVendorInfo(GetInstancePtr(), &mVendorInfo));
275 SuccessOrExit(error = otBleSecureStart(GetInstancePtr(), nullptr, HandleBleSecureReceive, true, nullptr));
276 SuccessOrExit(error = otBleSecureTcatStart(GetInstancePtr(), nullptr));
277
278 exit:
279 return error;
280 }
281
Process(Arg aArgs[])282 template <> otError Tcat::Process<Cmd("stop")>(Arg aArgs[])
283 {
284 OT_UNUSED_VARIABLE(aArgs);
285
286 otBleSecureStop(GetInstancePtr());
287
288 return OT_ERROR_NONE;
289 }
290
Process(Arg aArgs[])291 otError Tcat::Process(Arg aArgs[])
292 {
293 #define CmdEntry(aCommandString) \
294 { \
295 aCommandString, &Tcat::Process<Cmd(aCommandString)> \
296 }
297
298 static constexpr Command kCommands[] = {CmdEntry("advid"), CmdEntry("devid"), CmdEntry("start"), CmdEntry("stop")};
299
300 static_assert(BinarySearch::IsSorted(kCommands), "kCommands is not sorted");
301
302 #undef CmdEntry
303
304 otError error = OT_ERROR_NONE;
305 const Command *command;
306
307 if (aArgs[0].IsEmpty() || (aArgs[0] == "help"))
308 {
309 OutputCommandTable(kCommands);
310 ExitNow(error = aArgs[0].IsEmpty() ? error : OT_ERROR_NONE);
311 }
312
313 command = BinarySearch::Find(aArgs[0].GetCString(), kCommands);
314 VerifyOrExit(command != nullptr);
315
316 error = (this->*command->mHandler)(aArgs + 1);
317
318 exit:
319 return error;
320 }
321
322 } // namespace Cli
323 } // namespace ot
324 #endif // OPENTHREAD_CONFIG_BLE_TCAT_ENABLE && OPENTHREAD_CONFIG_CLI_BLE_SECURE_ENABLE
325