1 /*
2    Copyright (c) 2023 Assa Abloy. See the COPYRIGHT
3    file at the top-level directory of this distribution.
4 
5    Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6    http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7    <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8    option. This file may not be copied, modified, or distributed
9    except according to those terms.
10 */
11 
12 #include <stdbool.h>
13 #include <stdint.h>
14 #include <string.h>
15 
16 #include "oscore/oscore_interactions.h"
17 #include "common/byte_array.h"
18 #include "common/print_util.h"
19 
20 #ifdef DEBUG_PRINT
21 static const char msg_interaction_not_found[] =
22 	"Couldn't find the interaction with given key.\n";
23 static const char msg_token_already_used[] =
24 	"Given token is already used by other interaction (index=%u).\n";
25 
26 /**
27  * @brief Print single interaction field.
28  *
29  * @param msg Field name, null char included.
30  * @param buffer Field buffer.
31  * @param len Buffer size in bytes.
32  */
print_interaction_field(const char * name,uint8_t * buffer,uint32_t len)33 static void print_interaction_field(const char *name, uint8_t *buffer,
34 				    uint32_t len)
35 {
36 	PRINTF("   %s: ", name);
37 	if (NULL != buffer) {
38 		for (uint32_t index = 0; index < len; index++) {
39 			PRINTF("%02x ", buffer[index]);
40 		}
41 	}
42 	PRINTF("\n");
43 }
44 
45 /**
46  * @brief Print interactions array.
47  *
48  * @param interactions Input interactions array.
49  */
print_interactions(struct oscore_interaction_t * interactions)50 static void print_interactions(struct oscore_interaction_t *interactions)
51 {
52 	for (uint8_t index = 0; index < OSCORE_INTERACTIONS_COUNT; index++) {
53 		struct oscore_interaction_t *record = &interactions[index];
54 		PRINTF("record %02u:\n", index);
55 		PRINTF("   type     : %d\n", record->request_type);
56 		print_interaction_field("uri paths", record->uri_paths,
57 					record->uri_paths_len);
58 		print_interaction_field("token    ", record->token,
59 					record->token_len);
60 		print_interaction_field("req_piv  ", record->request_piv,
61 					record->request_piv_len);
62 		print_interaction_field("req_kid  ", record->request_kid,
63 					record->request_kid_len);
64 		PRINTF("   occupied : %s\n",
65 		       record->is_occupied ? "true" : "false");
66 	}
67 }
68 
69 #define PRINT_INTERACTIONS(table) print_interactions(table)
70 
71 #else
72 #define PRINT_INTERACTIONS(table)                                              \
73 	{                                                                      \
74 	}
75 #endif
76 
77 /**
78  * @brief Securely compares two memory buffers.
79  *
80  * @param actual Actual value.
81  * @param expected Expected value.
82  * @param expected_size Number of bytes to be compared.
83  * @return True if memory buffers are identical, false otherwise.
84  */
compare_memory(uint8_t * actual,uint32_t actual_size,uint8_t * expected,uint32_t expected_size)85 static bool compare_memory(uint8_t *actual, uint32_t actual_size,
86 			   uint8_t *expected, uint32_t expected_size)
87 {
88 	if (actual_size != expected_size) {
89 		return false;
90 	}
91 
92 	if ((NULL != actual) && (NULL != expected)) {
93 		return (0 == memcmp(actual, expected, expected_size));
94 	} else if ((NULL == actual) && (0 == expected_size)) {
95 		return true;
96 	}
97 
98 	return false;
99 }
100 
101 /**
102  * @brief Searches given interactions array for a first free slot.
103  * @param interactions Interactions array, MUST have exactly OSCORE_INTERACTIONS_COUNT elements.
104  * @return Index of the first unoccupied slot (or OSCORE_INTERACTIONS_COUNT if the array is full).
105  */
find_unoccupied_index(struct oscore_interaction_t * interactions)106 static uint32_t find_unoccupied_index(struct oscore_interaction_t *interactions)
107 {
108 	uint32_t index;
109 	for (index = 0; index < OSCORE_INTERACTIONS_COUNT; index++) {
110 		if (false == interactions[index].is_occupied) {
111 			break;
112 		}
113 	}
114 	return index;
115 }
116 
117 /**
118  * @brief Searches given interactions array for a record that matches given resource and request type.
119  * @param interactions Interactions array, MUST have exactly OSCORE_INTERACTIONS_COUNT elements.
120  * @param uri_paths Resource path buffer to match.
121  * @param uri_paths_len Resource path buffer size.
122  * @param request_type Request type to match.
123  * @return Index of the record (of OSCORE_INTERACTIONS_COUNT if not found).
124  */
125 static uint32_t
find_record_index_by_resource(struct oscore_interaction_t * interactions,uint8_t * uri_paths,uint8_t uri_paths_len,enum o_coap_msg request_type)126 find_record_index_by_resource(struct oscore_interaction_t *interactions,
127 			      uint8_t *uri_paths, uint8_t uri_paths_len,
128 			      enum o_coap_msg request_type)
129 {
130 	uint32_t index;
131 	for (index = 0; index < OSCORE_INTERACTIONS_COUNT; index++) {
132 		bool is_occupied = interactions[index].is_occupied;
133 		bool request_type_ok =
134 			(interactions[index].request_type == request_type);
135 		bool uri_path_ok =
136 			compare_memory(uri_paths, uri_paths_len,
137 				       interactions[index].uri_paths,
138 				       interactions[index].uri_paths_len);
139 		if (is_occupied && request_type_ok && uri_path_ok) {
140 			break;
141 		}
142 	}
143 	return index;
144 }
145 
146 /**
147  * @brief Searches given interactions array for a record that matches given token.
148  * @param interactions Interactions array, MUST have exactly OSCORE_INTERACTIONS_COUNT elements.
149  * @param token Token buffer to match.
150  * @param token_len Token buffer size.
151  * @return Index of the record (if found), or OSCORE_INTERACTIONS_COUNT (if not found).
152  */
153 static uint32_t
find_record_index_by_token(struct oscore_interaction_t * interactions,uint8_t * token,uint8_t token_len)154 find_record_index_by_token(struct oscore_interaction_t *interactions,
155 			   uint8_t *token, uint8_t token_len)
156 {
157 	uint32_t index;
158 	for (index = 0; index < OSCORE_INTERACTIONS_COUNT; index++) {
159 		bool is_occupied = interactions[index].is_occupied;
160 		bool token_ok = compare_memory(token, token_len,
161 					       interactions[index].token,
162 					       interactions[index].token_len);
163 		if (is_occupied && token_ok) {
164 			break;
165 		}
166 	}
167 	return index;
168 }
169 
oscore_interactions_init(struct oscore_interaction_t * interactions)170 enum err oscore_interactions_init(struct oscore_interaction_t *interactions)
171 {
172 	if (NULL == interactions) {
173 		return wrong_parameter;
174 	}
175 
176 	memset(interactions, 0,
177 	       sizeof(struct oscore_interaction_t) * OSCORE_INTERACTIONS_COUNT);
178 	return ok;
179 }
180 
181 enum err
oscore_interactions_set_record(struct oscore_interaction_t * interactions,struct oscore_interaction_t * record)182 oscore_interactions_set_record(struct oscore_interaction_t *interactions,
183 			       struct oscore_interaction_t *record)
184 {
185 	if ((NULL == interactions) || (NULL == record) ||
186 	    (record->token_len > MAX_TOKEN_LEN) ||
187 	    (record->uri_paths_len > OSCORE_MAX_URI_PATH_LEN) ||
188 	    (record->request_piv_len > MAX_PIV_LEN) ||
189 	    (record->request_kid_len > MAX_KID_LEN)) {
190 		return wrong_parameter;
191 	}
192 
193 	// Find the entry at which the record will be stored.
194 	uint32_t index_by_uri =
195 		find_record_index_by_resource(interactions, record->uri_paths,
196 					      record->uri_paths_len,
197 					      record->request_type);
198 	if (index_by_uri >= OSCORE_INTERACTIONS_COUNT) {
199 		index_by_uri = find_unoccupied_index(interactions);
200 		if (index_by_uri >= OSCORE_INTERACTIONS_COUNT) {
201 			return oscore_max_interactions;
202 		}
203 	}
204 
205 	// Prevent from using the same token twice, as it would be impossible to find the proper record with get_record.
206 	uint32_t index_by_token = find_record_index_by_token(
207 		interactions, record->token, record->token_len);
208 	if ((index_by_token < OSCORE_INTERACTIONS_COUNT) &&
209 	    (index_by_token != index_by_uri)) {
210 		PRINTF(msg_token_already_used, index_by_token);
211 		return oscore_interaction_duplicated_token;
212 	}
213 
214 	record->is_occupied = true;
215 
216 	// Memmove is used to avoid overlapping issues when get_record output is used as the record.
217 	memmove(&interactions[index_by_uri], record, sizeof(*record));
218 	PRINT_MSG("set record:\n");
219 	PRINT_INTERACTIONS(interactions);
220 	return ok;
221 }
222 
223 enum err
oscore_interactions_get_record(struct oscore_interaction_t * interactions,uint8_t * token,uint8_t token_len,struct oscore_interaction_t ** record)224 oscore_interactions_get_record(struct oscore_interaction_t *interactions,
225 			       uint8_t *token, uint8_t token_len,
226 			       struct oscore_interaction_t **record)
227 {
228 	if ((NULL == interactions) || (NULL == record) ||
229 	    (token_len > MAX_TOKEN_LEN)) {
230 		return wrong_parameter;
231 	}
232 	*record = NULL;
233 
234 	PRINT_MSG("get record:\n");
235 	PRINT_INTERACTIONS(interactions);
236 
237 	uint32_t index =
238 		find_record_index_by_token(interactions, token, token_len);
239 	if (index >= OSCORE_INTERACTIONS_COUNT) {
240 		PRINT_MSG(msg_interaction_not_found);
241 		PRINT_ARRAY("token", token, token_len);
242 		return oscore_interaction_not_found;
243 	}
244 
245 	*record = &interactions[index];
246 	return ok;
247 }
248 
249 enum err
oscore_interactions_remove_record(struct oscore_interaction_t * interactions,uint8_t * token,uint8_t token_len)250 oscore_interactions_remove_record(struct oscore_interaction_t *interactions,
251 				  uint8_t *token, uint8_t token_len)
252 {
253 	if ((NULL == interactions) || (token_len > MAX_TOKEN_LEN)) {
254 		return wrong_parameter;
255 	}
256 
257 	PRINT_MSG("remove record (before):\n");
258 	PRINT_INTERACTIONS(interactions);
259 
260 	uint32_t index =
261 		find_record_index_by_token(interactions, token, token_len);
262 	if (index >= OSCORE_INTERACTIONS_COUNT) {
263 		PRINT_MSG(msg_interaction_not_found);
264 		PRINT_ARRAY("token", token, token_len);
265 		return oscore_interaction_not_found;
266 	}
267 
268 	memset(&interactions[index], 0, sizeof(struct oscore_interaction_t));
269 	PRINT_MSG("remove record (after):\n");
270 	PRINT_INTERACTIONS(interactions);
271 	return ok;
272 }
273 
oscore_interactions_read_wrapper(enum o_coap_msg msg_type,struct byte_array * token,struct oscore_interaction_t * interactions,struct byte_array * request_piv,struct byte_array * request_kid)274 enum err oscore_interactions_read_wrapper(
275 	enum o_coap_msg msg_type, struct byte_array *token,
276 	struct oscore_interaction_t *interactions,
277 	struct byte_array *request_piv, struct byte_array *request_kid)
278 {
279 	if ((NULL == token) || (NULL == interactions) ||
280 	    (NULL == request_piv) || (NULL == request_kid)) {
281 		return wrong_parameter;
282 	}
283 
284 	if ((COAP_MSG_RESPONSE == msg_type) ||
285 	    (COAP_MSG_NOTIFICATION == msg_type)) {
286 		/* Server sends / Client receives any response (notification included) - read the record from interactions array and update request_piv and request_kid. */
287 		struct oscore_interaction_t *record;
288 		TRY(oscore_interactions_get_record(interactions, token->ptr,
289 						   token->len, &record));
290 		request_piv->ptr = record->request_piv;
291 		request_piv->len = record->request_piv_len;
292 		request_kid->ptr = record->request_kid;
293 		request_kid->len = record->request_kid_len;
294 	}
295 
296 	return ok;
297 }
298 
oscore_interactions_update_wrapper(enum o_coap_msg msg_type,struct byte_array * token,struct byte_array * uri_paths,struct oscore_interaction_t * interactions,struct byte_array * request_piv,struct byte_array * request_kid)299 enum err oscore_interactions_update_wrapper(
300 	enum o_coap_msg msg_type, struct byte_array *token,
301 	struct byte_array *uri_paths, struct oscore_interaction_t *interactions,
302 	struct byte_array *request_piv, struct byte_array *request_kid)
303 {
304 	if ((NULL == token) || (NULL == uri_paths) || (NULL == interactions) ||
305 	    (NULL == request_piv) || (NULL == request_kid)) {
306 		return wrong_parameter;
307 	}
308 
309 	// cancellation must be interpreted as a registration, to properly match the corresponding record from the interactions table.
310 	if (COAP_MSG_CANCELLATION == msg_type) {
311 		msg_type = COAP_MSG_REGISTRATION;
312 	}
313 
314 	if ((COAP_MSG_REQUEST == msg_type) ||
315 	    (COAP_MSG_REGISTRATION == msg_type)) {
316 		/* Server receives / client sends any request (including registration and cancellation) - add the record to the interactions array.
317 		   Request_piv and request_kid not updated - current values of PIV and KID (Sender ID) are used. */
318 		struct oscore_interaction_t record = {
319 			.request_piv_len = request_piv->len,
320 			.request_kid_len = request_kid->len,
321 			.token_len = token->len,
322 			.uri_paths_len = uri_paths->len,
323 			.request_type = msg_type
324 		};
325 		TRY(_memcpy_s(record.request_piv, MAX_PIV_LEN, request_piv->ptr,
326 			      request_piv->len));
327 		TRY(_memcpy_s(record.request_kid, MAX_KID_LEN, request_kid->ptr,
328 			      request_kid->len));
329 		TRY(_memcpy_s(record.token, MAX_TOKEN_LEN, token->ptr,
330 			      token->len));
331 		TRY(_memcpy_s(record.uri_paths, OSCORE_MAX_URI_PATH_LEN,
332 			      uri_paths->ptr, uri_paths->len));
333 		TRY(oscore_interactions_set_record(interactions, &record));
334 	} else if (COAP_MSG_RESPONSE == msg_type) {
335 		/* Server sends / client receives a regular response - remove the record. */
336 		//TODO removing records must be taken into account when No-Response support will be added.
337 		TRY(oscore_interactions_remove_record(interactions, token->ptr,
338 						      token->len));
339 	}
340 
341 	return ok;
342 }
343