Contiki-NG
Loading...
Searching...
No Matches
cose.c
Go to the documentation of this file.
1/*
2 * Copyright (c) 2024, RISE Research Institutes of Sweden AB
3 * Copyright (c) 2020, Industrial Systems Institute (ISI), Patras, Greece
4 * All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 * 1. Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 * 3. Neither the name of the copyright holder nor the names of its contributors
15 * may be used to endorse or promote products derived from this software
16 * without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' AND
19 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
22 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
24 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
25 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
26 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
27 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
28 * SUCH DAMAGE.
29 *
30 */
31
32/**
33 * \file
34 * Stateless COSE helpers (RFC 9052) used by EDHOC.
35 * \author
36 * Lidia Pocero <pocero@isi.gr>
37 * Peter A Jonsson
38 * Rikard Höglund
39 * Marco Tiloca
40 * Niclas Finne <niclas.finne@ri.se>
41 * Nicolas Tsiftes <nicolas.tsiftes@ri.se>
42 */
43
44#include "contiki.h"
45#include "cose.h"
46#include "ecdh.h"
47#include "lib/cbor.h"
48#include "lib/ccm-star.h"
49#include "lib/sha-256.h"
50#include <string.h>
51
52#include "sys/log.h"
53#define LOG_MODULE "COSE"
54#define LOG_LEVEL LOG_LEVEL_EDHOC
55
56/*
57 * Maximum size of the serialized Enc_structure / Sig_structure used as
58 * AAD or signed input. The structures contain three or four CBOR
59 * elements: a short context string, an empty (or short) protected
60 * header, the external AAD, and (for Sig_structure) the payload. The
61 * largest contributor in EDHOC is the external AAD, which is bounded by
62 * the EDHOC payload buffer.
63 */
64#define COSE_STRUCTURE_BUF_LEN (2 * EDHOC_MAX_PAYLOAD_LEN)
65
66/*---------------------------------------------------------------------------*/
67static char enc_header[] = "Encrypt0";
68static char sig_header[] = "Signature1";
69/*---------------------------------------------------------------------------*/
70static size_t
71encode_enc_structure(const uint8_t *external_aad, size_t external_aad_len,
72 uint8_t *out, size_t out_len)
73{
75 cbor_init_writer(&writer, out, out_len);
76 cbor_open_array(&writer);
77 cbor_write_text(&writer, enc_header, strlen(enc_header));
78 /* Empty protected header. */
79 cbor_write_data(&writer, NULL, 0);
80 cbor_write_data(&writer, external_aad, external_aad_len);
81 cbor_close_array(&writer);
82 return cbor_end_writer(&writer);
83}
84/*---------------------------------------------------------------------------*/
85static size_t
86encode_sig_structure(const uint8_t *protected_hdr, size_t protected_hdr_len,
87 const uint8_t *external_aad, size_t external_aad_len,
88 const uint8_t *payload, size_t payload_len,
89 uint8_t *out, size_t out_len)
90{
92 cbor_init_writer(&writer, out, out_len);
93 cbor_open_array(&writer);
94 cbor_write_text(&writer, sig_header, strlen(sig_header));
95 cbor_write_data(&writer, protected_hdr, protected_hdr_len);
96 cbor_write_data(&writer, external_aad, external_aad_len);
97 cbor_write_data(&writer, payload, payload_len);
98 cbor_close_array(&writer);
99 return cbor_end_writer(&writer);
100}
101/*---------------------------------------------------------------------------*/
102size_t
104 const uint8_t *key, const uint8_t *nonce,
105 const uint8_t *external_aad, size_t external_aad_len,
106 uint8_t *buf, size_t plaintext_len,
107 size_t buf_capacity)
108{
109 uint8_t tag_len = cose_get_tag_len(alg);
110 if(tag_len == 0 || cose_get_key_len(alg) == 0 || cose_get_iv_len(alg) == 0) {
111 LOG_ERR("Unsupported COSE AEAD algorithm %u\n", alg);
112 return 0;
113 }
114
115 /* The tag is appended in place, so buf must hold plaintext + tag. */
116 if(plaintext_len > buf_capacity || buf_capacity - plaintext_len < tag_len) {
117 LOG_ERR("COSE_Encrypt0 output buffer too small for plaintext + tag\n");
118 return 0;
119 }
120
121 uint8_t enc_struct[COSE_STRUCTURE_BUF_LEN];
122 size_t enc_struct_len = encode_enc_structure(external_aad, external_aad_len,
123 enc_struct, sizeof(enc_struct));
124 if(enc_struct_len == 0) {
125 LOG_ERR("Failed to encode Enc_structure for COSE_Encrypt0\n");
126 return 0;
127 }
128
129 /* The AEAD interface takes uint16_t lengths; reject anything that
130 would be silently truncated. */
131 if(plaintext_len > UINT16_MAX || enc_struct_len > UINT16_MAX) {
132 LOG_ERR("COSE_Encrypt0 input too large for the AEAD interface\n");
133 return 0;
134 }
135
136 /* CCM_STAR is a shared, stateful primitive: set_key() and the aead() that
137 consumes it must run without an intervening yield or reentrant use. This
138 holds under Contiki-NG cooperative scheduling and EDHOC's single handshake. */
139 CCM_STAR.set_key(key);
140 CCM_STAR.aead(nonce,
141 buf, plaintext_len,
142 enc_struct, enc_struct_len,
143 buf + plaintext_len, tag_len, 1);
144
145 return plaintext_len + tag_len;
146}
147/*---------------------------------------------------------------------------*/
148size_t
150 const uint8_t *key, const uint8_t *nonce,
151 const uint8_t *external_aad, size_t external_aad_len,
152 uint8_t *buf, size_t ciphertext_len)
153{
154 uint8_t tag_len = cose_get_tag_len(alg);
155 if(tag_len == 0 || cose_get_key_len(alg) == 0 || cose_get_iv_len(alg) == 0) {
156 LOG_ERR("Unsupported COSE AEAD algorithm %u\n", alg);
157 return 0;
158 }
159 if(ciphertext_len < tag_len) {
160 LOG_ERR("Ciphertext (%zu) shorter than COSE tag length (%u)\n",
161 ciphertext_len, tag_len);
162 return 0;
163 }
164
165 uint8_t enc_struct[COSE_STRUCTURE_BUF_LEN];
166 size_t enc_struct_len = encode_enc_structure(external_aad, external_aad_len,
167 enc_struct, sizeof(enc_struct));
168 if(enc_struct_len == 0) {
169 LOG_ERR("Failed to encode Enc_structure for COSE_Encrypt0\n");
170 return 0;
171 }
172
173 size_t plaintext_len = ciphertext_len - tag_len;
174
175 /* The AEAD interface takes uint16_t lengths; reject anything that
176 would be silently truncated. */
177 if(plaintext_len > UINT16_MAX || enc_struct_len > UINT16_MAX) {
178 LOG_ERR("COSE_Encrypt0 input too large for the AEAD interface\n");
179 return 0;
180 }
181
182 uint8_t expected_tag[COSE_MAX_TAG_LEN];
183
184 /*
185 * CCM_STAR is a shared, stateful primitive (see cose_encrypt0_seal). The
186 * buffer is decrypted in place here before the tag below is checked, so on
187 * authentication failure buf holds unauthenticated data and must be discarded
188 * by the caller.
189 */
190 CCM_STAR.set_key(key);
191 CCM_STAR.aead(nonce,
192 buf, plaintext_len,
193 enc_struct, enc_struct_len,
194 expected_tag, tag_len, 0);
195
196 /* Constant-time comparison to prevent timing attacks. */
197 uint8_t diff = 0;
198 for(uint8_t i = 0; i < tag_len; i++) {
199 diff |= expected_tag[i] ^ buf[plaintext_len + i];
200 }
201 if(diff != 0) {
202 LOG_ERR("COSE_Encrypt0 authentication tag verification failed\n");
203 return 0;
204 }
205 return plaintext_len;
206}
207/*---------------------------------------------------------------------------*/
208size_t
209cose_sign1_sign(int8_t alg, const uint8_t *private_key,
210 const uint8_t *protected_hdr, size_t protected_hdr_len,
211 const uint8_t *external_aad, size_t external_aad_len,
212 const uint8_t *payload, size_t payload_len,
213 uint8_t *signature)
214{
215 if(alg != ES256) {
216 LOG_ERR("Unsupported COSE_Sign1 algorithm %d (only ES256=%d is supported)\n",
217 alg, ES256);
218 return 0;
219 }
220
221 uint8_t sig_struct[COSE_STRUCTURE_BUF_LEN];
222 size_t sig_struct_len = encode_sig_structure(protected_hdr, protected_hdr_len,
223 external_aad, external_aad_len,
224 payload, payload_len,
225 sig_struct, sizeof(sig_struct));
226 if(sig_struct_len == 0) {
227 LOG_ERR("Failed to encode Sig_structure for COSE_Sign1 signing\n");
228 return 0;
229 }
230
231 uint8_t hash[HASH_LEN];
232 sha_256_hash(sig_struct, sig_struct_len, hash);
233
234 if(!ecc_sign_hash(EDHOC_CURVE_P256, hash, private_key, signature)) {
235 LOG_ERR("COSE_Sign1 signature generation failed\n");
236 return 0;
237 }
238 return P256_SIGNATURE_LEN;
239}
240/*---------------------------------------------------------------------------*/
241bool
242cose_sign1_verify(int8_t alg, const uint8_t *public_key,
243 const uint8_t *protected_hdr, size_t protected_hdr_len,
244 const uint8_t *external_aad, size_t external_aad_len,
245 const uint8_t *payload, size_t payload_len,
246 const uint8_t *signature, size_t signature_len)
247{
248 if(alg != ES256) {
249 LOG_ERR("Unsupported COSE_Sign1 algorithm %d (only ES256=%d is supported)\n",
250 alg, ES256);
251 return false;
252 }
253 if(signature_len != P256_SIGNATURE_LEN) {
254 LOG_ERR("COSE_Sign1: unexpected signature length %zu (expected %d)\n",
255 signature_len, P256_SIGNATURE_LEN);
256 return false;
257 }
258
259 uint8_t sig_struct[COSE_STRUCTURE_BUF_LEN];
260 size_t sig_struct_len = encode_sig_structure(protected_hdr, protected_hdr_len,
261 external_aad, external_aad_len,
262 payload, payload_len,
263 sig_struct, sizeof(sig_struct));
264 if(sig_struct_len == 0) {
265 LOG_ERR("Failed to encode Sig_structure for COSE_Sign1 verification\n");
266 return false;
267 }
268
269 uint8_t hash[HASH_LEN];
270 sha_256_hash(sig_struct, sig_struct_len, hash);
271
272 if(!ecc_verify_hash(EDHOC_CURVE_P256, hash, public_key, signature)) {
273 LOG_ERR("COSE_Sign1 signature verification failed\n");
274 return false;
275 }
276 return true;
277}
278/*---------------------------------------------------------------------------*/
279uint8_t
280cose_get_key_len(uint8_t alg_id)
281{
282 switch(alg_id) {
283 case COSE_ALG_AES_CCM_16_64_128:
284 return COSE_ALG_AES_CCM_16_64_128_KEY_LEN;
285 case COSE_ALG_AES_CCM_16_128_128:
286 return COSE_ALG_AES_CCM_16_128_128_KEY_LEN;
287 default:
288 LOG_ERR("Invalid COSE algorithm %d specified\n", alg_id);
289 return 0;
290 }
291}
292/*---------------------------------------------------------------------------*/
293uint8_t
294cose_get_iv_len(uint8_t alg_id)
295{
296 switch(alg_id) {
297 case COSE_ALG_AES_CCM_16_64_128:
298 return COSE_ALG_AES_CCM_16_64_128_IV_LEN;
299 case COSE_ALG_AES_CCM_16_128_128:
300 return COSE_ALG_AES_CCM_16_128_128_IV_LEN;
301 default:
302 LOG_ERR("Invalid COSE algorithm %d specified\n", alg_id);
303 return 0;
304 }
305}
306/*---------------------------------------------------------------------------*/
307uint8_t
308cose_get_tag_len(uint8_t alg_id)
309{
310 switch(alg_id) {
311 case COSE_ALG_AES_CCM_16_64_128:
312 return COSE_ALG_AES_CCM_16_64_128_TAG_LEN;
313 case COSE_ALG_AES_CCM_16_128_128:
314 return COSE_ALG_AES_CCM_16_128_128_TAG_LEN;
315 default:
316 LOG_ERR("Invalid COSE algorithm %d specified\n", alg_id);
317 return 0;
318 }
319}
320/*---------------------------------------------------------------------------*/
CBOR API.
CCM* header file.
Public API declarations for COSE (RFC 9052).
bool ecc_sign_hash(uint8_t curve_id, const uint8_t *hash, const uint8_t *private_key, uint8_t *signature)
Generates an ECDSA signature for a message hash.
Definition ecdh.c:174
bool ecc_verify_hash(uint8_t curve_id, const uint8_t *hash, const uint8_t *public_key, const uint8_t *signature)
Verifies an ECDSA signature of a message hash.
Definition ecdh.c:203
ECDH interface for the EDHOC implementation.
uint8_t cose_get_key_len(uint8_t alg_id)
Get the symmetric key length for a COSE AEAD algorithm.
Definition cose.c:280
size_t cose_encrypt0_open(uint8_t alg, const uint8_t *key, const uint8_t *nonce, const uint8_t *external_aad, size_t external_aad_len, uint8_t *buf, size_t ciphertext_len)
AEAD-decrypt a buffer in place as a COSE_Encrypt0.
Definition cose.c:149
uint8_t cose_get_iv_len(uint8_t alg_id)
Get the nonce/IV length for a COSE AEAD algorithm.
Definition cose.c:294
uint8_t cose_get_tag_len(uint8_t alg_id)
Get the authentication tag length for a COSE AEAD algorithm.
Definition cose.c:308
size_t cose_encrypt0_seal(uint8_t alg, const uint8_t *key, const uint8_t *nonce, const uint8_t *external_aad, size_t external_aad_len, uint8_t *buf, size_t plaintext_len, size_t buf_capacity)
AEAD-encrypt a buffer in place as a COSE_Encrypt0.
Definition cose.c:103
size_t cose_sign1_sign(int8_t alg, const uint8_t *private_key, const uint8_t *protected_hdr, size_t protected_hdr_len, const uint8_t *external_aad, size_t external_aad_len, const uint8_t *payload, size_t payload_len, uint8_t *signature)
Produce a COSE_Sign1 signature.
Definition cose.c:209
bool cose_sign1_verify(int8_t alg, const uint8_t *public_key, const uint8_t *protected_hdr, size_t protected_hdr_len, const uint8_t *external_aad, size_t external_aad_len, const uint8_t *payload, size_t payload_len, const uint8_t *signature, size_t signature_len)
Verify a COSE_Sign1 signature.
Definition cose.c:242
size_t cbor_end_writer(cbor_writer_state_t *state)
Finishes writing CBOR output.
Definition cbor.c:54
void cbor_write_text(cbor_writer_state_t *state, const char *text, size_t text_size)
Appends a text string.
Definition cbor.c:196
void cbor_open_array(cbor_writer_state_t *state)
Adds subsequent CBOR objects to an array.
Definition cbor.c:260
void cbor_init_writer(cbor_writer_state_t *state, uint8_t *buffer, size_t buffer_size)
Prepares for writing CBOR output.
Definition cbor.c:44
void cbor_close_array(cbor_writer_state_t *state)
Stops adding subsequent CBOR objects to the innermost array.
Definition cbor.c:266
void cbor_write_data(cbor_writer_state_t *state, const uint8_t *data, size_t data_size)
Appends a byte string.
Definition cbor.c:187
#define P256_SIGNATURE_LEN
Length of signatures.
Header file for the logging system.
Platform-independent SHA-256 API.
Structure of the internal state of a CBOR writer.
Definition cbor.h:119