1 /*
2  * Copyright 2025 NXP
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 #include <platform-zephyr.h>
8 #include <openthread/border_agent.h>
9 #include <openthread/cli.h>
10 #include <openthread/dns.h>
11 #include <openthread/thread.h>
12 #include <openthread/verhoeff_checksum.h>
13 #include <openthread/platform/entropy.h>
14 #include "common/code_utils.hpp"
15 #include "openthread_border_router.h"
16 #include <zephyr/sys/byteorder.h>
17 #include <string.h>
18 #include <stdlib.h>
19 #include <inttypes.h>
20 #include <zephyr/kernel.h>
21 #include <zephyr/shell/shell.h>
22 
23 static struct otInstance *ot_instance_ptr;
24 static bool border_agent_is_init;
25 
26 #if defined(CONFIG_OPENTHREAD_BORDER_AGENT_EPHEMERAL_KEY_ENABLE)
27 /* Byte values, 9 bytes for the key, one for null terminator. */
28 static uint8_t ephemeral_key_string[10];
29 
30 static uint32_t ephemeral_key_timeout;
31 static bool epskc_active;
32 
33 static otError generate_ephemeral_key(void);
34 static void handle_border_agent_ephemeral_key_callback(void *context);
35 static otError border_agent_enable_epskc_service(uint32_t timeout);
36 
37 /* shell related functions */
38 static int border_agent_eph_key_shell_cmd(const struct shell *sh, size_t argc, char **argv);
39 static int border_agent_eph_key_run(size_t argc, char **argv);
40 
41 #endif /* CONFIG_OPENTHREAD_BORDER_AGENT_EPHEMERAL_KEY_ENABLE */
42 
43 static void append_vendor_txt_data(uint8_t *txt_data, uint16_t *txt_data_len);
44 
border_agent_init(otInstance * instance)45 otError border_agent_init(otInstance *instance)
46 {
47 	otError error = OT_ERROR_NONE;
48 
49 	ot_instance_ptr = instance;
50 
51 	if (!border_agent_is_init) {
52 		uint8_t txt_buffer[128] = {0};
53 		uint16_t txt_buffer_len = 0;
54 
55 		VerifyOrExit(otBorderAgentSetMeshCoPServiceBaseName(instance,
56 								    otbr_base_service_instance_name)
57 			     == OT_ERROR_NONE, error = OT_ERROR_FAILED);
58 		append_vendor_txt_data(txt_buffer, &txt_buffer_len);
59 		otBorderAgentSetVendorTxtData(instance, txt_buffer, txt_buffer_len);
60 #if defined(CONFIG_OPENTHREAD_BORDER_AGENT_EPHEMERAL_KEY_ENABLE)
61 		otBorderAgentEphemeralKeySetEnabled(instance, true);
62 		otBorderAgentEphemeralKeySetCallback(instance,
63 						     handle_border_agent_ephemeral_key_callback,
64 						     instance);
65 #endif /* CONFIG_OPENTHREAD_BORDER_AGENT_EPHEMERAL_KEY_ENABLE */
66 
67 		border_agent_is_init = true;
68 	}
69 exit:
70 	return error;
71 }
72 
border_agent_deinit(void)73 void border_agent_deinit(void)
74 {
75 #if defined(CONFIG_OPENTHREAD_BORDER_AGENT_EPHEMERAL_KEY_ENABLE)
76 	otBorderAgentEphemeralKeySetEnabled(ot_instance_ptr, false);
77 	epskc_active = false;
78 #endif /* CONFIG_OPENTHREAD_BORDER_AGENT_EPHEMERAL_KEY_ENABLE */
79 	border_agent_is_init = false;
80 }
81 
append_vendor_txt_data(uint8_t * txt_data,uint16_t * txt_data_len)82 static void append_vendor_txt_data(uint8_t *txt_data, uint16_t *txt_data_len)
83 {
84 	*txt_data_len = 0;
85 	size_t i = 0;
86 	otDnsTxtEntry txt_entries[] = {
87 				       {.mKey = "vn", .mValue = (uint8_t *)otbr_vendor_name,
88 					.mValueLength = strlen(otbr_vendor_name)},
89 				       {.mKey = "mn", .mValue = (uint8_t *)otbr_model_name,
90 					.mValueLength = strlen(otbr_model_name)}};
91 
92 	for (i = 0; i < ARRAY_SIZE(txt_entries); ++i) {
93 		const otDnsTxtEntry *entry    = &txt_entries[i];
94 		uint8_t key_len   = (uint8_t)strlen(entry->mKey);
95 		uint8_t total_len = key_len + 1 + entry->mValueLength; /* +1 for '=' */
96 
97 		txt_data[(*txt_data_len)++] = total_len;
98 		memcpy(txt_data + *txt_data_len, entry->mKey, key_len);
99 		*txt_data_len += key_len;
100 
101 		txt_data[(*txt_data_len)++] = '=';
102 		memcpy(txt_data + *txt_data_len, entry->mValue, entry->mValueLength);
103 		*txt_data_len += entry->mValueLength;
104 	}
105 
106 }
107 
108 #if defined(CONFIG_OPENTHREAD_BORDER_AGENT_EPHEMERAL_KEY_ENABLE)
109 
110 /** Defining functions below as weak allows applications to implement their specific behaviour,
111  *  i.e., custom messages/used print function.
112  */
print_ephemeral_key(const char * ephemeral_key,uint32_t timeout)113 __weak void print_ephemeral_key(const char *ephemeral_key, uint32_t timeout)
114 {
115 	otCliOutputFormat("\r\n Use this passcode to enable an additional device to administer "
116 			  "and manage your Thread network, including adding new devices to it. "
117 			  "\r\nThis passcode is not required for an app to communicate with "
118 			  "existing devices on your Thread network.");
119 	otCliOutputFormat("\r\n\nePSKc : %s", ephemeral_key);
120 	otCliOutputFormat("\r\n\nValid for %" PRIu32 " seconds.\r\n", timeout);
121 }
122 
print_ephemeral_key_expired_message(void)123 __weak void print_ephemeral_key_expired_message(void)
124 {
125 	otCliOutputFormat("\r\nEphemeral Key disabled.\r\n");
126 }
127 
generate_ephemeral_key(void)128 static otError generate_ephemeral_key(void)
129 {
130 	otError error = OT_ERROR_NONE;
131 	uint8_t i = 0;
132 	uint32_t random;
133 	char verhoeff_checksum;
134 
135 	memset(ephemeral_key_string, 0, sizeof(ephemeral_key_string));
136 
137 	VerifyOrExit(otPlatEntropyGet((uint8_t *)&random, sizeof(random)) == OT_ERROR_NONE,
138 		     error = OT_ERROR_FAILED);
139 	random %= 100000000;
140 	i += snprintf((char *)ephemeral_key_string, sizeof(ephemeral_key_string),
141 		      "%08u", random);
142 	VerifyOrExit(otVerhoeffChecksumCalculate((const char *)ephemeral_key_string,
143 		     &verhoeff_checksum) == OT_ERROR_NONE,
144 		     error = OT_ERROR_FAILED);
145 	snprintf((char *)&ephemeral_key_string[i], sizeof(ephemeral_key_string) - i,
146 		 "%c", verhoeff_checksum);
147 exit:
148 	return error;
149 }
150 
handle_border_agent_ephemeral_key_callback(void * context)151 static void handle_border_agent_ephemeral_key_callback(void *context)
152 {
153 	char formatted_epskc[12] = {0};
154 	otBorderAgentEphemeralKeyState eph_key_state;
155 
156 	eph_key_state = otBorderAgentEphemeralKeyGetState((otInstance *)context);
157 
158 	switch (eph_key_state) {
159 	case OT_BORDER_AGENT_STATE_STOPPED:
160 		if (epskc_active) {
161 			epskc_active = false;
162 			print_ephemeral_key_expired_message();
163 		}
164 		break;
165 
166 	case OT_BORDER_AGENT_STATE_STARTED:
167 		snprintf(formatted_epskc, sizeof(formatted_epskc), "%.3s %.3s %.3s",
168 			 ephemeral_key_string, ephemeral_key_string + 3,
169 			 ephemeral_key_string + 6);
170 		print_ephemeral_key(formatted_epskc, (uint32_t)(ephemeral_key_timeout / 1000UL));
171 		epskc_active = true;
172 		break;
173 
174 	case OT_BORDER_AGENT_STATE_CONNECTED:
175 		/* connected to */
176 	case OT_BORDER_AGENT_STATE_ACCEPTED:
177 	default:
178 		break;
179 	}
180 }
border_agent_enable_epskc_service(uint32_t timeout)181 static otError border_agent_enable_epskc_service(uint32_t timeout)
182 {
183 	otError error = OT_ERROR_NONE;
184 
185 	VerifyOrExit(border_agent_is_init, error = OT_ERROR_INVALID_STATE);
186 
187 	ephemeral_key_timeout = (timeout &&
188 				   timeout >= OT_BORDER_AGENT_DEFAULT_EPHEMERAL_KEY_TIMEOUT &&
189 				   timeout <= OT_BORDER_AGENT_MAX_EPHEMERAL_KEY_TIMEOUT) ?
190 				   timeout : OT_BORDER_AGENT_DEFAULT_EPHEMERAL_KEY_TIMEOUT;
191 
192 	VerifyOrExit((generate_ephemeral_key() == OT_ERROR_NONE), error = OT_ERROR_FAILED);
193 	error = otBorderAgentEphemeralKeyStart(ot_instance_ptr,
194 					       (const char *)ephemeral_key_string,
195 					       ephemeral_key_timeout, 0);
196 
197 exit:
198 	return error;
199 }
200 
border_agent_eph_key_run(size_t argc,char ** argv)201 static int border_agent_eph_key_run(size_t argc, char **argv)
202 {
203 	if (strcmp(argv[1], "enable") == 0) {
204 		uint32_t timeout = (argc >= 1) ? strtoul(argv[2], NULL, 10) : 0;
205 
206 		if (border_agent_enable_epskc_service(timeout) != OT_ERROR_NONE) {
207 			otCliOutputFormat("Invalid state\r\n");
208 		}
209 		return 0;
210 	}
211 
212 	if (strcmp(argv[1], "help") == 0) {
213 		otCliOutputFormat("ephkey enable [timeout-in-msec]\r\n");
214 	}
215 
216 	return 0;
217 }
218 
border_agent_eph_key_shell_cmd(const struct shell * sh,size_t argc,char ** argv)219 static int border_agent_eph_key_shell_cmd(const struct shell *sh, size_t argc, char **argv)
220 {
221 	if (argc < 2) {
222 		shell_help(sh);
223 		return -ENOEXEC;
224 	}
225 
226 	border_agent_eph_key_run(argc, argv);
227 
228 	return 0;
229 }
230 #endif /* CONFIG_OPENTHREAD_BORDER_AGENT_EPHEMERAL_KEY_ENABLE */
231 
232 #if defined(CONFIG_OPENTHREAD_BORDER_AGENT_EPHEMERAL_KEY_ENABLE)
233 SHELL_CMD_ARG_REGISTER(ephkey, NULL, "Ephemeral key support", border_agent_eph_key_shell_cmd, 2,
234 		       CONFIG_SHELL_ARGC_MAX);
235 #endif /* CONFIG_OPENTHREAD_BORDER_AGENT_EPHEMERAL_KEY_ENABLE */
236