Branch data Line data Source code
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 : : */
43 : 20 : 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 : 20 : uint16_t temp_kid_len = 0;
49 : :
50 : 20 : enum err r = not_oscore_pkt;
51 : :
52 [ + + ]: 43 : for (uint8_t i = 0; i < opt_cnt; i++) {
53 : 25 : temp_kid_len = opt[i].len;
54 : :
55 : : /* Check current option is OSCORE_option or not */
56 [ + + ]: 25 : if (opt[i].option_number == OSCORE) {
57 [ + + ]: 20 : if (opt[i].len == 0) {
58 : : /* No OSCORE option value*/
59 : 2 : out->h = 0;
60 : 2 : out->k = 0;
61 : 2 : out->n = 0;
62 : 2 : out->piv.ptr = NULL;
63 : 2 : out->piv.len = 0;
64 : 2 : out->kid.ptr = NULL;
65 : 2 : out->kid.len = 0;
66 : 2 : out->kid_context.ptr = NULL;
67 : 2 : out->kid_context.len = 0;
68 : : } else {
69 : : /* Get address of current option value*/
70 : 18 : val_ptr = opt[i].value;
71 : : /* Parse first byte of OSCORE value*/
72 : 18 : out->h = ((*val_ptr) &
73 : 18 : COMP_OSCORE_OPT_KIDC_H_MASK) >>
74 : : COMP_OSCORE_OPT_KIDC_H_OFFSET;
75 : 18 : out->k = ((*val_ptr) &
76 : 18 : COMP_OSCORE_OPT_KID_K_MASK) >>
77 : : COMP_OSCORE_OPT_KID_K_OFFSET;
78 : 18 : out->n = ((*val_ptr) &
79 : 18 : COMP_OSCORE_OPT_PIV_N_MASK) >>
80 : : COMP_OSCORE_OPT_PIV_N_OFFSET;
81 : 18 : val_ptr++;
82 : 18 : temp_kid_len--;
83 : :
84 : : /* Get PIV */
85 [ + + + ]: 18 : switch (out->n) {
86 : 1 : case 0:
87 : : /* NO PIV in COSE object*/
88 : 1 : out->piv.ptr = NULL;
89 : 1 : out->piv.len = 0;
90 : 1 : break;
91 : 2 : case 6:
92 : : case 7:
93 : : /* ERROR: Byte length of PIV not right, max. 5 bytes */
94 : 2 : return oscore_inpkt_invalid_piv;
95 : : break;
96 : 15 : default:
97 : 15 : out->piv.ptr = val_ptr;
98 : 15 : out->piv.len = out->n;
99 : 15 : val_ptr += out->n;
100 : 15 : temp_kid_len = (uint8_t)(temp_kid_len -
101 : 15 : out->n);
102 : 15 : break;
103 : : }
104 : :
105 : : /* Get KID context */
106 [ + + ]: 16 : if (out->h == 0) {
107 : 14 : out->kid_context.len = 0;
108 : 14 : out->kid_context.ptr = NULL;
109 : : } else {
110 : 2 : out->kid_context.len = *val_ptr;
111 : 2 : out->kid_context.ptr = ++val_ptr;
112 : 2 : val_ptr += out->kid_context.len;
113 : 2 : temp_kid_len = (uint8_t)(
114 : 2 : temp_kid_len -
115 : 2 : (out->kid_context.len + 1));
116 : : }
117 : :
118 : : /* Get KID */
119 [ + + ]: 16 : if (out->k == 0) {
120 : 1 : out->kid.len = 0;
121 : 1 : out->kid.ptr = NULL;
122 : : } else {
123 : 15 : out->kid.len = temp_kid_len;
124 : 15 : out->kid.ptr = val_ptr;
125 : : }
126 : : }
127 : :
128 : 18 : r = ok;
129 : : }
130 : : }
131 : :
132 : 18 : 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
145 : 14 : 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 : 14 : uint8_t max_coap_opt_cnt = (uint8_t)(U_options_cnt + E_options_cnt - 1);
152 : :
153 [ - + ]: 14 : TRY(check_buffer_size(MAX_OPTION_COUNT, max_coap_opt_cnt));
154 : 14 : *out_options_cnt = 0;
155 : 14 : 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 [ + + ]: 32 : for (uint8_t i = 0; i < U_options_cnt; i++) {
159 [ + + ]: 18 : if ((U_options[i].option_number != OSCORE) &&
160 [ + + ]: 5 : (U_options[i].option_number != OBSERVE)) {
161 : 3 : out_options[*out_options_cnt] = U_options[i];
162 : 3 : *out_options_cnt += 1;
163 : : }
164 : : }
165 : :
166 : : /*Get the inner options.*/
167 [ + + ]: 29 : for (uint8_t i = 0; i < E_options_cnt; i++) {
168 : 15 : out_options[*out_options_cnt] = E_options[i];
169 : 15 : *out_options_cnt += 1;
170 : : }
171 : :
172 : 14 : uint16_t delta = 0;
173 : : /* Order the options starting with minimum option number to maximum */
174 [ + + ]: 32 : for (uint8_t i = 0; i < *out_options_cnt; i++) {
175 : 18 : uint8_t ipp = (uint8_t)(i + 1);
176 [ + + ]: 28 : for (uint8_t k = ipp; k < *out_options_cnt; k++) {
177 : 10 : if (out_options[i].option_number >
178 [ + + ]: 10 : out_options[k].option_number) {
179 : : struct o_coap_option tmp;
180 : 4 : tmp = out_options[i];
181 : 4 : out_options[i] = out_options[k];
182 : 4 : out_options[k] = tmp;
183 : : }
184 : : }
185 : : /*update the delta*/
186 : 18 : out_options[i].delta = out_options[i].option_number - delta;
187 : 18 : delta = out_options[i].option_number;
188 : : }
189 : :
190 : 14 : 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 : : */
200 : 13 : 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 : 13 : uint8_t code = 0;
205 : 13 : struct byte_array unprotected_o_coap_payload = BYTE_ARRAY_INIT(NULL, 0);
206 : : struct o_coap_option E_options[MAX_E_OPTION_COUNT];
207 : 13 : uint8_t E_options_cnt = 0;
208 : :
209 : : /* Parse decrypted payload: code + options + unprotected CoAP payload*/
210 [ - + ]: 13 : 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 : 13 : out->header.ver = oscore_pkt->header.ver;
217 : 13 : out->header.type = oscore_pkt->header.type;
218 : 13 : out->header.TKL = oscore_pkt->header.TKL;
219 : 13 : out->token = oscore_pkt->token;
220 : 13 : out->header.code = code; //decrypted code must be used, see 8.2 p.7
221 : 13 : out->header.MID = oscore_pkt->header.MID;
222 : :
223 : : /* Payload */
224 : 13 : out->payload.len = unprotected_o_coap_payload.len;
225 [ + + ]: 13 : if (unprotected_o_coap_payload.len == 0) {
226 : 11 : out->payload.ptr = NULL;
227 : : } else {
228 : 2 : out->payload.ptr = unprotected_o_coap_payload.ptr;
229 : : }
230 : :
231 : : /* reorder all options, and copy it to output coap packet */
232 [ - + ]: 13 : TRY(options_reorder(oscore_pkt->options, oscore_pkt->options_cnt,
233 : : E_options, E_options_cnt, out->options,
234 : : &out->options_cnt));
235 : 13 : 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
254 : 13 : 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 [ - + ]: 13 : 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 [ - + ]: 13 : TRY(coap_get_message_type(input_oscore, &msg_type_oscore));
266 : 13 : struct byte_array token =
267 : 13 : 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 [ + + ]: 13 : if (NULL != new_nonce_oscore_option) {
273 : 11 : request_piv = new_nonce_oscore_option->piv;
274 : 11 : request_kid = new_nonce_oscore_option->kid;
275 : : }
276 [ - + ]: 13 : 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 [ + + ]: 13 : if (NULL != new_nonce_oscore_option) {
286 [ - + ]: 11 : TRY(create_nonce(&new_nonce_oscore_option->kid,
287 : : &new_nonce_oscore_option->piv,
288 : : &c->cc.common_iv, &new_nonce));
289 : 11 : nonce = new_nonce;
290 : : } else {
291 : 2 : nonce = c->rrc.nonce;
292 : : }
293 : :
294 : : /* compute AAD */
295 : : uint8_t aad_buf[MAX_AAD_LEN];
296 : 13 : struct byte_array aad = BYTE_ARRAY_INIT(aad_buf, sizeof(aad_buf));
297 [ - + ]: 13 : TRY(create_aad(NULL, 0, c->cc.aead_alg, &request_kid, &request_piv,
298 : : &aad));
299 : :
300 : : /* Decrypt the ciphertext */
301 [ - + ]: 13 : 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 [ + + ]: 13 : if (NULL != new_nonce_oscore_option) {
306 [ - + ]: 11 : TRY(byte_array_cpy(&c->rrc.nonce, &nonce, NONCE_LEN));
307 : : }
308 : :
309 : : /* Generate corresponding CoAP packet */
310 [ - + ]: 13 : 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 [ - + ]: 13 : TRY(coap_get_message_type(output_coap, &msg_type));
316 [ - + ]: 13 : BYTE_ARRAY_NEW(uri_paths, OSCORE_MAX_URI_PATH_LEN,
317 : : OSCORE_MAX_URI_PATH_LEN);
318 [ - + ]: 13 : TRY(uri_path_create(output_coap->options, output_coap->options_cnt,
319 : : uri_paths.ptr, &(uri_paths.len)));
320 [ - + ]: 13 : TRY(oscore_interactions_update_wrapper(msg_type, &token, &uri_paths,
321 : : c->rrc.interactions,
322 : : &request_piv, &request_kid));
323 : :
324 : 13 : return ok;
325 : : }
326 : :
327 : 16 : 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 : 16 : PRINT_MSG("\n\n\noscore2coap***************************************\n");
335 : 16 : PRINT_ARRAY("Input OSCORE packet", buf_in, buf_in_len);
336 : :
337 : 16 : buf.ptr = buf_in;
338 : 16 : buf.len = buf_in_len;
339 : :
340 : : /* Make sure that given context is fresh enough to process the message. */
341 [ - + ]: 16 : TRY(check_context_freshness(c));
342 : :
343 : : /*Parse the incoming message (buf_in) into a CoAP struct*/
344 : 16 : memset(&oscore_packet, 0, sizeof(oscore_packet));
345 [ - + ]: 16 : TRY(coap_deserialize(&buf, &oscore_packet));
346 : :
347 : : /* Check if the packet is OSCORE packet and if so parse the OSCORE option */
348 [ - + ]: 16 : TRY(oscore_option_parser(oscore_packet.options,
349 : : oscore_packet.options_cnt, &oscore_option));
350 : :
351 : : /* Encrypted packet payload */
352 : 16 : 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 : 16 : uint32_t plaintext_bytes_len = ciphertext->len - AUTH_TAG_LEN;
357 [ - + - + ]: 16 : 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 [ + + ]: 16 : 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 [ + + ]: 9 : if (!array_equals(&c->rc.recipient_id, &oscore_option.kid)) {
374 : 1 : 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 [ + + ]: 8 : if (ECHO_SYNCHRONIZED == c->rrc.echo_state_machine) {
380 : : uint64_t ssn;
381 : 4 : piv2ssn(&oscore_option.piv, &ssn);
382 [ + + ]: 4 : if (!server_is_sequence_number_valid(
383 : : ssn, &c->rc.replay_window)) {
384 : 1 : PRINT_MSG("Replayed message detected!\n");
385 : 1 : return oscore_replay_window_protection_error;
386 : : }
387 : : }
388 : :
389 : : /* Decrypt packet using new nonce based on the packet */
390 [ - + ]: 7 : TRY(decrypt_wrapper(ciphertext, &plaintext, c, &oscore_option,
391 : : &oscore_packet, &output_coap));
392 : :
393 [ + + ]: 7 : 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 : 1 : PRINT_MSG("Abort -- first request after reboot!\n");
398 : 1 : c->rrc.echo_state_machine = ECHO_VERIFY;
399 : 1 : return first_request_after_reboot;
400 [ + + ]: 6 : } 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 [ + + ]: 3 : if (ok == echo_val_is_fresh(&c->rrc.echo_opt_val,
405 : : &plaintext)) {
406 : : uint64_t ssn;
407 : 1 : piv2ssn(&oscore_option.piv, &ssn);
408 [ - + ]: 1 : TRY(server_replay_window_reinit(
409 : : ssn, &c->rc.replay_window));
410 : 1 : c->rrc.echo_state_machine = ECHO_SYNCHRONIZED;
411 : : } else {
412 : 2 : PRINT_MSG(
413 : : "Abort -- ECHO validation failed! Repeating the challenge.\n");
414 : 2 : return echo_validation_failed;
415 : : }
416 : : } else {
417 : : /* Normal operation - update replay window. */
418 [ - + ]: 3 : TRY_EXPECT(c->rrc.echo_state_machine,
419 : : ECHO_SYNCHRONIZED);
420 : 3 : server_replay_window_update(*oscore_option.piv.ptr,
421 : : &c->rc.replay_window);
422 : : }
423 : : } else {
424 : : /* received any kind of response */
425 [ + + ]: 7 : if (is_observe(oscore_packet.options,
426 : 7 : oscore_packet.options_cnt)) {
427 [ + - ]: 2 : if (oscore_option.piv.len != 0) {
428 : : /*Notification with PIV received*/
429 : 2 : PRINT_MSG(
430 : : "Observe notification with PIV received\n");
431 : :
432 [ + + ]: 2 : 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 [ - + ]: 1 : TRY(decrypt_wrapper(ciphertext, &plaintext, c,
439 : : &oscore_option,
440 : : &oscore_packet,
441 : : &output_coap));
442 : :
443 : : /*update replay protection value in context*/
444 [ - + ]: 1 : 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 [ + + ]: 5 : if (oscore_option.piv.len != 0) {
455 : : /*response with PIV*/
456 [ - + ]: 3 : TRY(decrypt_wrapper(ciphertext, &plaintext, c,
457 : : &oscore_option,
458 : : &oscore_packet,
459 : : &output_coap));
460 : : } else {
461 : : /*response without PIV*/
462 [ - + ]: 2 : TRY(decrypt_wrapper(ciphertext, &plaintext, c,
463 : : NULL, &oscore_packet,
464 : : &output_coap));
465 : : }
466 : : }
467 : : }
468 : :
469 : : /*Convert to byte string*/
470 : 10 : return coap_serialize(&output_coap, buf_out, buf_out_len);
471 : : }
|