1 /*
2 Copyright (c) 2021 Fraunhofer AISEC. 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 <stdio.h>
15 #include <string.h>
16
17 #include "oscore.h"
18
19 #include "oscore/aad.h"
20 #include "oscore/oscore_coap.h"
21 #include "oscore/nonce.h"
22 #include "oscore/option.h"
23 #include "oscore/oscore_cose.h"
24 #include "oscore/security_context.h"
25 #include "oscore/replay_protection.h"
26
27 #include "common/byte_array.h"
28 #include "common/oscore_edhoc_error.h"
29 #include "common/memcpy_s.h"
30 #include "common/print_util.h"
31 #include "common/unit_test.h"
32
33 /**
34 * @brief Parse all received options to find the OSCORE option. If it doesn't
35 * have OSCORE option, then this packet is a normal CoAP. If it does
36 * have, it's an OSCORE packet, and then parse the compressed OSCORE
37 * option value to get value of PIV, KID and KID context of the client.
38 * @param opt: input array of options
39 * @param opt_cnt: number of elements in the array
40 * @param out: pointer output compressed OSCORE_option
41 * @return error code
42 */
oscore_option_parser(const struct o_coap_option * opt,uint8_t opt_cnt,struct compressed_oscore_option * out)43 STATIC enum err oscore_option_parser(const struct o_coap_option *opt,
44 uint8_t opt_cnt,
45 struct compressed_oscore_option *out)
46 {
47 uint8_t *val_ptr;
48 uint16_t temp_kid_len = 0;
49
50 enum err r = not_oscore_pkt;
51
52 for (uint8_t i = 0; i < opt_cnt; i++) {
53 temp_kid_len = opt[i].len;
54
55 /* Check current option is OSCORE_option or not */
56 if (opt[i].option_number == OSCORE) {
57 if (opt[i].len == 0) {
58 /* No OSCORE option value*/
59 out->h = 0;
60 out->k = 0;
61 out->n = 0;
62 out->piv.ptr = NULL;
63 out->piv.len = 0;
64 out->kid.ptr = NULL;
65 out->kid.len = 0;
66 out->kid_context.ptr = NULL;
67 out->kid_context.len = 0;
68 } else {
69 /* Get address of current option value*/
70 val_ptr = opt[i].value;
71 /* Parse first byte of OSCORE value*/
72 out->h = ((*val_ptr) &
73 COMP_OSCORE_OPT_KIDC_H_MASK) >>
74 COMP_OSCORE_OPT_KIDC_H_OFFSET;
75 out->k = ((*val_ptr) &
76 COMP_OSCORE_OPT_KID_K_MASK) >>
77 COMP_OSCORE_OPT_KID_K_OFFSET;
78 out->n = ((*val_ptr) &
79 COMP_OSCORE_OPT_PIV_N_MASK) >>
80 COMP_OSCORE_OPT_PIV_N_OFFSET;
81 val_ptr++;
82 temp_kid_len--;
83
84 /* Get PIV */
85 switch (out->n) {
86 case 0:
87 /* NO PIV in COSE object*/
88 out->piv.ptr = NULL;
89 out->piv.len = 0;
90 break;
91 case 6:
92 case 7:
93 /* ERROR: Byte length of PIV not right, max. 5 bytes */
94 return oscore_inpkt_invalid_piv;
95 break;
96 default:
97 out->piv.ptr = val_ptr;
98 out->piv.len = out->n;
99 val_ptr += out->n;
100 temp_kid_len = (uint8_t)(temp_kid_len -
101 out->n);
102 break;
103 }
104
105 /* Get KID context */
106 if (out->h == 0) {
107 out->kid_context.len = 0;
108 out->kid_context.ptr = NULL;
109 } else {
110 out->kid_context.len = *val_ptr;
111 out->kid_context.ptr = ++val_ptr;
112 val_ptr += out->kid_context.len;
113 temp_kid_len = (uint8_t)(
114 temp_kid_len -
115 (out->kid_context.len + 1));
116 }
117
118 /* Get KID */
119 if (out->k == 0) {
120 out->kid.len = 0;
121 out->kid.ptr = NULL;
122 } else {
123 out->kid.len = temp_kid_len;
124 out->kid.ptr = val_ptr;
125 }
126 }
127
128 r = ok;
129 }
130 }
131
132 return r;
133 }
134
135 /**
136 * @brief Reorder E-options and other U-options, and update their delta, and combine them all to normal CoAP packet
137 * @param oscore_pkt: input OSCORE, which contains U-options
138 * @param E_options: input pointer to E-options array
139 * @param E_options_cnt: count number of input E-options
140 * @param out: output pointer to CoAP packet, which will have all reordered options
141 * @return ok or error code
142 */
143
144 STATIC enum err
options_reorder(struct o_coap_option * U_options,uint8_t U_options_cnt,struct o_coap_option * E_options,uint8_t E_options_cnt,struct o_coap_option * out_options,uint8_t * out_options_cnt)145 options_reorder(struct o_coap_option *U_options, uint8_t U_options_cnt,
146 struct o_coap_option *E_options, uint8_t E_options_cnt,
147 struct o_coap_option *out_options, uint8_t *out_options_cnt)
148 {
149 /*the maximum amount of options for the CoAP packet
150 is the amount of all options -1 (for the OSCORE option)*/
151 uint8_t max_coap_opt_cnt = (uint8_t)(U_options_cnt + E_options_cnt - 1);
152
153 TRY(check_buffer_size(MAX_OPTION_COUNT, max_coap_opt_cnt));
154 *out_options_cnt = 0;
155 memset(out_options, 0, sizeof(struct o_coap_option) * max_coap_opt_cnt);
156
157 /*Get the all outer options. Discard OSCORE and outer OBSERVE as specified in 8.2 and 8.4 */
158 for (uint8_t i = 0; i < U_options_cnt; i++) {
159 if ((U_options[i].option_number != OSCORE) &&
160 (U_options[i].option_number != OBSERVE)) {
161 out_options[*out_options_cnt] = U_options[i];
162 *out_options_cnt += 1;
163 }
164 }
165
166 /*Get the inner options.*/
167 for (uint8_t i = 0; i < E_options_cnt; i++) {
168 out_options[*out_options_cnt] = E_options[i];
169 *out_options_cnt += 1;
170 }
171
172 uint16_t delta = 0;
173 /* Order the options starting with minimum option number to maximum */
174 for (uint8_t i = 0; i < *out_options_cnt; i++) {
175 uint8_t ipp = (uint8_t)(i + 1);
176 for (uint8_t k = ipp; k < *out_options_cnt; k++) {
177 if (out_options[i].option_number >
178 out_options[k].option_number) {
179 struct o_coap_option tmp;
180 tmp = out_options[i];
181 out_options[i] = out_options[k];
182 out_options[k] = tmp;
183 }
184 }
185 /*update the delta*/
186 out_options[i].delta = out_options[i].option_number - delta;
187 delta = out_options[i].option_number;
188 }
189
190 return ok;
191 }
192
193 /**
194 * @brief Generate CoAP packet from OSCORE packet
195 * @param decrypted_payload: decrypted OSCORE payload, which contains code, E-options and original unprotected CoAP payload
196 * @param oscore_pkt: input OSCORE packet
197 * @param out: pointer to output CoAP packet
198 * @return
199 */
o_coap_pkg_generate(struct byte_array * decrypted_payload,struct o_coap_packet * oscore_pkt,struct o_coap_packet * out)200 static inline enum err o_coap_pkg_generate(struct byte_array *decrypted_payload,
201 struct o_coap_packet *oscore_pkt,
202 struct o_coap_packet *out)
203 {
204 uint8_t code = 0;
205 struct byte_array unprotected_o_coap_payload = BYTE_ARRAY_INIT(NULL, 0);
206 struct o_coap_option E_options[MAX_E_OPTION_COUNT];
207 uint8_t E_options_cnt = 0;
208
209 /* Parse decrypted payload: code + options + unprotected CoAP payload*/
210 TRY(oscore_decrypted_payload_parser(decrypted_payload, &code, E_options,
211 &E_options_cnt,
212 &unprotected_o_coap_payload));
213
214 /* Copy each items from OSCORE packet to CoAP packet */
215 /* Header */
216 out->header.ver = oscore_pkt->header.ver;
217 out->header.type = oscore_pkt->header.type;
218 out->header.TKL = oscore_pkt->header.TKL;
219 out->token = oscore_pkt->token;
220 out->header.code = code; //decrypted code must be used, see 8.2 p.7
221 out->header.MID = oscore_pkt->header.MID;
222
223 /* Payload */
224 out->payload.len = unprotected_o_coap_payload.len;
225 if (unprotected_o_coap_payload.len == 0) {
226 out->payload.ptr = NULL;
227 } else {
228 out->payload.ptr = unprotected_o_coap_payload.ptr;
229 }
230
231 /* reorder all options, and copy it to output coap packet */
232 TRY(options_reorder(oscore_pkt->options, oscore_pkt->options_cnt,
233 E_options, E_options_cnt, out->options,
234 &out->options_cnt));
235 return ok;
236 }
237
238 /**
239 * @brief Wrapper function with common operations for decrypting the payload.
240 * These operations are shared in all possible scenarios.
241 * For more info, see RFC8616 8.2 and 8.4.
242 *
243 * @param ciphertext Input encrypted payload.
244 * @param plaintext Output decrypted payload.
245 * @param c Security context.
246 * @param new_nonce_oscore_option Input OSCORE option from the packet.
247 * Use proper pointer for cases when new nonce are generated, or
248 * NULL if data from corresponding request should be used.
249 * @param input_oscore Input OSCORE packet.
250 * @param output_coap Output decrypted coap packet.
251 * @return enum err
252 */
253 static enum err
decrypt_wrapper(struct byte_array * ciphertext,struct byte_array * plaintext,struct context * c,struct compressed_oscore_option * new_nonce_oscore_option,struct o_coap_packet * input_oscore,struct o_coap_packet * output_coap)254 decrypt_wrapper(struct byte_array *ciphertext, struct byte_array *plaintext,
255 struct context *c,
256 struct compressed_oscore_option *new_nonce_oscore_option,
257 struct o_coap_packet *input_oscore,
258 struct o_coap_packet *output_coap)
259 {
260 BYTE_ARRAY_NEW(new_nonce, NONCE_LEN, NONCE_LEN);
261 struct byte_array nonce;
262
263 /* Read necessary fields from the input packet. */
264 enum o_coap_msg msg_type_oscore;
265 TRY(coap_get_message_type(input_oscore, &msg_type_oscore));
266 struct byte_array token =
267 BYTE_ARRAY_INIT(input_oscore->token, input_oscore->header.TKL);
268
269 /* Read Request PIV and KID fields from OSCORE option, if available. Update using interactions wrapper. */
270 struct byte_array request_piv;
271 struct byte_array request_kid;
272 if (NULL != new_nonce_oscore_option) {
273 request_piv = new_nonce_oscore_option->piv;
274 request_kid = new_nonce_oscore_option->kid;
275 }
276 TRY(oscore_interactions_read_wrapper(msg_type_oscore, &token,
277 c->rrc.interactions, &request_piv,
278 &request_kid));
279 /* Message type read from encrypted packet can be invalid due to external OBSERVE option change,
280 but it is sufficient enough for the interactions read wrapper to work properly,
281 as it only need to know whether the packet is any kind of response. */
282
283 /* Calculate new nonce from oscore option - only if required by the usecase.
284 If not, nonce from the corresponding request (rcc.nonce) is used. */
285 if (NULL != new_nonce_oscore_option) {
286 TRY(create_nonce(&new_nonce_oscore_option->kid,
287 &new_nonce_oscore_option->piv,
288 &c->cc.common_iv, &new_nonce));
289 nonce = new_nonce;
290 } else {
291 nonce = c->rrc.nonce;
292 }
293
294 /* compute AAD */
295 uint8_t aad_buf[MAX_AAD_LEN];
296 struct byte_array aad = BYTE_ARRAY_INIT(aad_buf, sizeof(aad_buf));
297 TRY(create_aad(NULL, 0, c->cc.aead_alg, &request_kid, &request_piv,
298 &aad));
299
300 /* Decrypt the ciphertext */
301 TRY(oscore_cose_decrypt(ciphertext, plaintext, &nonce, &aad,
302 &c->rc.recipient_key));
303
304 /* Update nonce only after successful decryption (for handling future responses) */
305 if (NULL != new_nonce_oscore_option) {
306 TRY(byte_array_cpy(&c->rrc.nonce, &nonce, NONCE_LEN));
307 }
308
309 /* Generate corresponding CoAP packet */
310 TRY(o_coap_pkg_generate(plaintext, input_oscore, output_coap));
311
312 /* Handle OSCORE interactions after successful decryption.
313 Decrypted packet is used for URI Paths and message type, as original values are modified while encrypting. */
314 enum o_coap_msg msg_type;
315 TRY(coap_get_message_type(output_coap, &msg_type));
316 BYTE_ARRAY_NEW(uri_paths, OSCORE_MAX_URI_PATH_LEN,
317 OSCORE_MAX_URI_PATH_LEN);
318 TRY(uri_path_create(output_coap->options, output_coap->options_cnt,
319 uri_paths.ptr, &(uri_paths.len)));
320 TRY(oscore_interactions_update_wrapper(msg_type, &token, &uri_paths,
321 c->rrc.interactions,
322 &request_piv, &request_kid));
323
324 return ok;
325 }
326
oscore2coap(uint8_t * buf_in,uint32_t buf_in_len,uint8_t * buf_out,uint32_t * buf_out_len,struct context * c)327 enum err oscore2coap(uint8_t *buf_in, uint32_t buf_in_len, uint8_t *buf_out,
328 uint32_t *buf_out_len, struct context *c)
329 {
330 struct o_coap_packet oscore_packet;
331 struct compressed_oscore_option oscore_option;
332 struct byte_array buf;
333
334 PRINT_MSG("\n\n\noscore2coap***************************************\n");
335 PRINT_ARRAY("Input OSCORE packet", buf_in, buf_in_len);
336
337 buf.ptr = buf_in;
338 buf.len = buf_in_len;
339
340 /* Make sure that given context is fresh enough to process the message. */
341 TRY(check_context_freshness(c));
342
343 /*Parse the incoming message (buf_in) into a CoAP struct*/
344 memset(&oscore_packet, 0, sizeof(oscore_packet));
345 TRY(coap_deserialize(&buf, &oscore_packet));
346
347 /* Check if the packet is OSCORE packet and if so parse the OSCORE option */
348 TRY(oscore_option_parser(oscore_packet.options,
349 oscore_packet.options_cnt, &oscore_option));
350
351 /* Encrypted packet payload */
352 struct byte_array *ciphertext = &oscore_packet.payload;
353
354 /* Setup buffer for the plaintext. The plaintext is shorter than the
355 ciphertext because of the authentication tag*/
356 uint32_t plaintext_bytes_len = ciphertext->len - AUTH_TAG_LEN;
357 BYTE_ARRAY_NEW(plaintext, MAX_PLAINTEXT_LEN, plaintext_bytes_len);
358 /* TODO plaintext can be moved inside decrypt_wrapper to simplify the code.
359 To do so, refactor of echo_val_is_fresh is needed, to operate on o_coap_packet. */
360
361 /* Helper structure for decrypted coap packet */
362 struct o_coap_packet output_coap;
363
364 /*In requests the OSCORE packet contains at least a KID = sender ID
365 and eventually sender sequence number*/
366 if (is_request(&oscore_packet)) {
367 /*Check that the recipient context c->rc has a Recipient ID that
368 matches the received with the oscore option KID (Sender ID).
369 If this is not true return an error which indicates the caller
370 application to tray another context. This is useful when the caller
371 app doesn't know in advance to which context an incoming packet
372 belongs.*/
373 if (!array_equals(&c->rc.recipient_id, &oscore_option.kid)) {
374 return oscore_kid_recipient_id_mismatch;
375 }
376
377 /* Check if the packet is replayed - in case of normal operation (replay window already synchronized).
378 It must be performed before decrypting the packet (see RFC 8613 p. 7.4). */
379 if (ECHO_SYNCHRONIZED == c->rrc.echo_state_machine) {
380 uint64_t ssn;
381 piv2ssn(&oscore_option.piv, &ssn);
382 if (!server_is_sequence_number_valid(
383 ssn, &c->rc.replay_window)) {
384 PRINT_MSG("Replayed message detected!\n");
385 return oscore_replay_window_protection_error;
386 }
387 }
388
389 /* Decrypt packet using new nonce based on the packet */
390 TRY(decrypt_wrapper(ciphertext, &plaintext, c, &oscore_option,
391 &oscore_packet, &output_coap));
392
393 if (ECHO_REBOOT == c->rrc.echo_state_machine) {
394 /* Abort the execution if this is the the first request after reboot.
395 Let the application layer know that it should prepare a special response with ECHO option
396 and prepare for verifying ECHO of the next request. */
397 PRINT_MSG("Abort -- first request after reboot!\n");
398 c->rrc.echo_state_machine = ECHO_VERIFY;
399 return first_request_after_reboot;
400 } else if (ECHO_VERIFY == c->rrc.echo_state_machine) {
401 /* Next request should already have proper ECHO option for proving freshness.
402 If so, perform replay window reinitialization and start normal operation.
403 If not, repeat the whole process until normal operation can be started. */
404 if (ok == echo_val_is_fresh(&c->rrc.echo_opt_val,
405 &plaintext)) {
406 uint64_t ssn;
407 piv2ssn(&oscore_option.piv, &ssn);
408 TRY(server_replay_window_reinit(
409 ssn, &c->rc.replay_window));
410 c->rrc.echo_state_machine = ECHO_SYNCHRONIZED;
411 } else {
412 PRINT_MSG(
413 "Abort -- ECHO validation failed! Repeating the challenge.\n");
414 return echo_validation_failed;
415 }
416 } else {
417 /* Normal operation - update replay window. */
418 TRY_EXPECT(c->rrc.echo_state_machine,
419 ECHO_SYNCHRONIZED);
420 server_replay_window_update(*oscore_option.piv.ptr,
421 &c->rc.replay_window);
422 }
423 } else {
424 /* received any kind of response */
425 if (is_observe(oscore_packet.options,
426 oscore_packet.options_cnt)) {
427 if (oscore_option.piv.len != 0) {
428 /*Notification with PIV received*/
429 PRINT_MSG(
430 "Observe notification with PIV received\n");
431
432 TRY(replay_protection_check_notification(
433 c->rc.notification_num,
434 c->rc.notification_num_initialized,
435 &oscore_option.piv));
436
437 /* Decrypt packet using new nonce based on the packet */
438 TRY(decrypt_wrapper(ciphertext, &plaintext, c,
439 &oscore_option,
440 &oscore_packet,
441 &output_coap));
442
443 /*update replay protection value in context*/
444 TRY(notification_number_update(
445 &c->rc.notification_num,
446 &c->rc.notification_num_initialized,
447 &oscore_option.piv));
448 } else {
449 /*Notification without PIV received -- Currently not supported*/
450 return not_supported_feature; //LCOV_EXCL_LINE
451 }
452 } else {
453 /*regular response received*/
454 if (oscore_option.piv.len != 0) {
455 /*response with PIV*/
456 TRY(decrypt_wrapper(ciphertext, &plaintext, c,
457 &oscore_option,
458 &oscore_packet,
459 &output_coap));
460 } else {
461 /*response without PIV*/
462 TRY(decrypt_wrapper(ciphertext, &plaintext, c,
463 NULL, &oscore_packet,
464 &output_coap));
465 }
466 }
467 }
468
469 /*Convert to byte string*/
470 return coap_serialize(&output_coap, buf_out, buf_out_len);
471 }
472