1 /*
2 * Copyright (c) 2018-2019, Laurence Lundblade. All rights reserved.
3 * Copyright (c) 2020, Arm Limited. All rights reserved.
4 *
5 * SPDX-License-Identifier: BSD-3-Clause
6 *
7 * See BSD-3-Clause license in README.md
8 */
9
10 #include "qcbor.h"
11 #include "t_cose_crypto.h"
12 #include "t_cose_mac0_sign.h"
13 #include "t_cose_util.h"
14
15 /**
16 * \file t_cose_mac0_sign.c
17 *
18 * \brief This creates t_cose Mac authentication structure without a recipient
19 * structure.
20 * Only HMAC is supported so far.
21 */
22
23 /**
24 * \brief Makes the protected header parameters for COSE.
25 *
26 * \param[in] cose_algorithm_id The COSE algorithm ID to put in the
27 * header parameters.
28 * \param[in] buffer_for_parameters Pointer and length of buffer into which
29 * the resulting encoded protected
30 * parameters is put. See return value.
31 *
32 * \return The pointer and length of the encoded protected
33 * parameters is returned, or \c NULL_Q_USEFUL_BUF_C if this fails.
34 * This will have the same pointer as \c buffer_for_parameters,
35 * but the pointer is conts and the length is that of the valid
36 * data, not of the size of the buffer.
37 *
38 * The protected parameters are returned in fully encoded CBOR format as
39 * they are added to the \c COSE_Mac0 message as a binary string. This is
40 * different from the unprotected parameters which are not handled this
41 * way.
42 *
43 * This returns \c NULL_Q_USEFUL_BUF_C if buffer_for_parameters was too
44 * small. See also definition of \c T_COSE_MAC0_MAX_SIZE_PROTECTED_PARAMETERS
45 */
46 static inline struct q_useful_buf_c
encode_protected_parameters(int32_t cose_algorithm_id,struct q_useful_buf buffer_for_parameters)47 encode_protected_parameters(int32_t cose_algorithm_id,
48 struct q_useful_buf buffer_for_parameters)
49 {
50 /* approximate stack use on 32-bit machine:
51 * CBOR encode context 148
52 * local use: 20
53 * total: 168
54 */
55 struct q_useful_buf_c protected_parameters;
56 QCBORError qcbor_result;
57 QCBOREncodeContext cbor_encode_ctx;
58 struct q_useful_buf_c return_value;
59
60 QCBOREncode_Init(&cbor_encode_ctx, buffer_for_parameters);
61 QCBOREncode_OpenMap(&cbor_encode_ctx);
62 QCBOREncode_AddInt64ToMapN(&cbor_encode_ctx,
63 COSE_HEADER_PARAM_ALG,
64 cose_algorithm_id);
65 QCBOREncode_CloseMap(&cbor_encode_ctx);
66 qcbor_result = QCBOREncode_Finish(&cbor_encode_ctx, &protected_parameters);
67
68 if(qcbor_result == QCBOR_SUCCESS) {
69 return_value = protected_parameters;
70 } else {
71 return_value = NULL_Q_USEFUL_BUF_C;
72 }
73
74 return return_value;
75 }
76
77 /**
78 * \brief Add the unprotected parameters to a CBOR encoding context
79 *
80 * \param[in] me The t_cose signing context.
81 * \param[in] kid The key ID.
82 * \param[in] cbor_encode_ctx CBOR encoding context to output to
83 *
84 * No error is returned. If an error occurred it will be returned when
85 * \c QCBOR_Finish() is called on \c cbor_encode_ctx.
86 *
87 * The unprotected parameters added by this are the kid and content type.
88 */
89 static inline enum t_cose_err_t
add_unprotected_parameters(const struct t_cose_mac0_sign_ctx * me,const struct q_useful_buf_c kid,QCBOREncodeContext * cbor_encode_ctx)90 add_unprotected_parameters(const struct t_cose_mac0_sign_ctx *me,
91 const struct q_useful_buf_c kid,
92 QCBOREncodeContext *cbor_encode_ctx)
93 {
94 QCBOREncode_OpenMap(cbor_encode_ctx);
95
96 if(!q_useful_buf_c_is_null_or_empty(kid)) {
97 QCBOREncode_AddBytesToMapN(cbor_encode_ctx,
98 COSE_HEADER_PARAM_KID,
99 kid);
100 }
101
102 #ifndef T_COSE_DISABLE_CONTENT_TYPE
103 if(me->content_type_uint != T_COSE_EMPTY_UINT_CONTENT_TYPE &&
104 me->content_type_tstr != NULL) {
105 /* Both the string and int content types are not allowed */
106 return T_COSE_ERR_DUPLICATE_PARAMETER;
107 }
108
109 if(me->content_type_uint != T_COSE_EMPTY_UINT_CONTENT_TYPE) {
110 QCBOREncode_AddUInt64ToMapN(cbor_encode_ctx,
111 COSE_HEADER_PARAM_CONTENT_TYPE,
112 me->content_type_uint);
113 }
114
115 if(me->content_type_tstr != NULL) {
116 QCBOREncode_AddSZStringToMapN(cbor_encode_ctx,
117 COSE_HEADER_PARAM_CONTENT_TYPE,
118 me->content_type_tstr);
119 }
120 #else
121 (void)me; /* avoid unused parameter warning */
122 #endif
123
124 QCBOREncode_CloseMap(cbor_encode_ctx);
125
126 return T_COSE_SUCCESS;
127 }
128
129 #ifndef T_COSE_DISABLE_SHORT_CIRCUIT_SIGN
130 /**
131 * \brief Create a short-circuit tag
132 *
133 * \param[in] cose_alg_id Algorithm ID. This is used only to make
134 * the short-circuit tag the same size as the
135 * real tag would be for the particular algorithm.
136 * \param[in] header The Header of COSE_Mac0.
137 * \param[in] payload The payload of COSE_Mac0
138 * \param[in] tag_buffer Pointer and length of buffer into which
139 * the resulting tag is put.
140 * \param[out] tag Pointer and length of the tag returned.
141 *
142 * \return This returns one of the error codes defined by \ref t_cose_err_t.
143 *
144 * This creates the short-circuit tag that is actually a hash of input bytes.
145 * This is a test mode only has it has no security value. This is retained in
146 * commercial production code as a useful test or demo that can run
147 * even if key material is not set up or accessible.
148 */
149 static inline enum t_cose_err_t
short_circuit_tag(int32_t cose_alg_id,struct q_useful_buf_c header,struct q_useful_buf_c payload,struct q_useful_buf tag_buffer,struct q_useful_buf_c * tag)150 short_circuit_tag(int32_t cose_alg_id,
151 struct q_useful_buf_c header,
152 struct q_useful_buf_c payload,
153 struct q_useful_buf tag_buffer,
154 struct q_useful_buf_c *tag)
155 {
156 /* approximate stack use on 32-bit machine: local use: 16 bytes */
157 enum t_cose_err_t return_value;
158 struct t_cose_crypto_hash hash_ctx;
159 size_t tag_size;
160 int32_t hash_alg_id;
161
162 /*
163 * The length of Hash result equals that of HMAC result
164 * with the same Hash algorithm.
165 */
166 tag_size = t_cose_tag_size(cose_alg_id);
167
168 /* Check the tag length against buffer size */
169 if(tag_size == INT32_MAX) {
170 return_value = T_COSE_ERR_UNSUPPORTED_SIGNING_ALG;
171 goto Done;
172 }
173
174 if(tag_size > tag_buffer.len) {
175 /* Buffer too small for this tag */
176 return_value = T_COSE_ERR_SIG_BUFFER_SIZE;
177 goto Done;
178 }
179
180 hash_alg_id = t_cose_hmac_to_hash_alg_id(cose_alg_id);
181 if(hash_alg_id == INT32_MAX) {
182 return_value = T_COSE_ERR_UNSUPPORTED_SIGNING_ALG;
183 goto Done;
184 }
185
186 return_value = t_cose_crypto_hash_start(&hash_ctx, hash_alg_id);
187 if(return_value != T_COSE_SUCCESS) {
188 goto Done;
189 }
190
191 /* Hash the Header */
192 t_cose_crypto_hash_update(&hash_ctx, q_useful_buf_head(header, header.len));
193
194 /* Hash the payload */
195 t_cose_crypto_hash_update(&hash_ctx, payload);
196
197 return_value = t_cose_crypto_hash_finish(&hash_ctx, tag_buffer, tag);
198
199 Done:
200 return return_value;
201 }
202 #endif /* T_COSE_DISABLE_SHORT_CIRCUIT_SIGN */
203
204 /*
205 * Public function. See t_cose_mac0.h
206 */
207 enum t_cose_err_t
t_cose_mac0_encode_parameters(struct t_cose_mac0_sign_ctx * me,QCBOREncodeContext * cbor_encode_ctx)208 t_cose_mac0_encode_parameters(struct t_cose_mac0_sign_ctx *me,
209 QCBOREncodeContext *cbor_encode_ctx)
210
211 {
212 size_t tag_len;
213 enum t_cose_err_t return_value;
214 struct q_useful_buf buffer_for_protected_parameters;
215 struct q_useful_buf_c kid;
216
217 /*
218 * Check the algorithm now by getting the algorithm as an early
219 * error check even though it is not used until later.
220 */
221 tag_len = t_cose_tag_size(me->cose_algorithm_id);
222 if(tag_len == INT32_MAX) {
223 return T_COSE_ERR_UNSUPPORTED_SIGNING_ALG;
224 }
225
226 /* Add the CBOR tag indicating COSE_Mac0 */
227 if(!(me->option_flags & T_COSE_OPT_OMIT_CBOR_TAG)) {
228 QCBOREncode_AddTag(cbor_encode_ctx, CBOR_TAG_COSE_MAC0);
229 }
230
231 /* Get started with the tagged array that holds the parts of
232 * a COSE_Mac0 message
233 */
234 QCBOREncode_OpenArray(cbor_encode_ctx);
235
236 /* The protected headers, which are added as a wrapped bstr */
237 buffer_for_protected_parameters =
238 Q_USEFUL_BUF_FROM_BYTE_ARRAY(me->protected_parameters_buffer);
239 me->protected_parameters =
240 encode_protected_parameters(me->cose_algorithm_id,
241 buffer_for_protected_parameters);
242 if(q_useful_buf_c_is_null(me->protected_parameters)) {
243 /* The sizing of storage for protected headers is
244 * off (should never happen in tested, released code)
245 */
246 return_value = T_COSE_ERR_MAKING_PROTECTED;
247 goto Done;
248 }
249 /* The use of _AddBytes here achieves the bstr wrapping */
250 QCBOREncode_AddBytes(cbor_encode_ctx, me->protected_parameters);
251
252 /* The Unprotected parameters */
253 if(me->option_flags & T_COSE_OPT_SHORT_CIRCUIT_TAG) {
254 #ifndef T_COSE_DISABLE_SHORT_CIRCUIT_SIGN
255 kid = NULL_Q_USEFUL_BUF_C;
256 #else
257 return_value = T_COSE_ERR_SHORT_CIRCUIT_SIG_DISABLED;
258 goto Done;
259 #endif
260 } else {
261 /* Get the kid because it goes into the parameters that are about
262 * to be made.
263 */
264 kid = me->kid;
265 }
266
267 return_value = add_unprotected_parameters(me, kid, cbor_encode_ctx);
268 if(return_value != T_COSE_SUCCESS) {
269 goto Done;
270 }
271
272 QCBOREncode_BstrWrap(cbor_encode_ctx);
273
274 /*
275 * Any failures in CBOR encoding will be caught in finish
276 * when the CBOR encoding is closed off. No need to track
277 * here as the CBOR encoder tracks it internally.
278 */
279
280 Done:
281 return return_value;
282 }
283
284 /*
285 * Public function. See t_cose_mac0.h
286 */
287 enum t_cose_err_t
t_cose_mac0_encode_tag(struct t_cose_mac0_sign_ctx * me,QCBOREncodeContext * cbor_encode_ctx)288 t_cose_mac0_encode_tag(struct t_cose_mac0_sign_ctx *me,
289 QCBOREncodeContext *cbor_encode_ctx)
290
291 {
292 enum t_cose_err_t return_value;
293 QCBORError cbor_err;
294 /* Pointer and length of the completed tag */
295 struct q_useful_buf_c tag;
296 /* Buffer for the actual tag */
297 Q_USEFUL_BUF_MAKE_STACK_UB( tag_buf,
298 T_COSE_CRYPTO_HMAC_TAG_MAX_SIZE);
299 struct q_useful_buf_c tbm_first_part;
300 /* Buffer for the ToBeMaced */
301 UsefulBuf_MAKE_STACK_UB( tbm_first_part_buf,
302 T_COSE_SIZE_OF_TBM);
303 struct t_cose_crypto_hmac hmac_ctx;
304 struct q_useful_buf_c maced_payload;
305
306 QCBOREncode_CloseBstrWrap(cbor_encode_ctx, &maced_payload);
307
308 /* Check that there are no CBOR encoding errors before proceeding
309 * with hashing and tagging. This is not actually necessary as the
310 * errors will be caught correctly later, but it does make it a
311 * bit easier for the caller to debug problems.
312 */
313 cbor_err = QCBOREncode_GetErrorState(cbor_encode_ctx);
314 if(cbor_err == QCBOR_ERR_BUFFER_TOO_SMALL) {
315 return_value = T_COSE_ERR_TOO_SMALL;
316 goto Done;
317 } else if(cbor_err != QCBOR_SUCCESS) {
318 return_value = T_COSE_ERR_CBOR_FORMATTING;
319 goto Done;
320 }
321
322 if(QCBOREncode_IsBufferNULL(cbor_encode_ctx)) {
323 /* Just calculating sizes. All that is needed is the tag size. */
324 tag.ptr = NULL;
325 tag.len = t_cose_tag_size(me->cose_algorithm_id);
326
327 return_value = T_COSE_SUCCESS;
328 goto CloseArray;
329 }
330
331 /* Create the hash of the ToBeMaced bytes. Inputs to the
332 * MAC are the protected parameters, the payload that is
333 * getting MACed.
334 */
335 return_value = create_tbm(tbm_first_part_buf,
336 me->protected_parameters,
337 &tbm_first_part,
338 T_COSE_TBM_PAYLOAD_IS_BSTR_WRAPPED,
339 maced_payload);
340 if(return_value) {
341 goto Done;
342 }
343
344 /*
345 * Start the HMAC.
346 * Calculate the tag of the first part of ToBeMaced and the wrapped
347 * payload, to save a bigger buffer containing the entire ToBeMaced.
348 *
349 * Short-circuit tagging is invoked if requested. It does no HMAC operation
350 * and requires no key. It is just a test mode that works without accessing
351 * any device asset.
352 */
353 if(me->option_flags & T_COSE_OPT_SHORT_CIRCUIT_TAG) {
354 #ifndef T_COSE_DISABLE_SHORT_CIRCUIT_SIGN
355 /* Short-circuit tag. Hash is used to generated tag instead of HMAC */
356 return_value = short_circuit_tag(me->cose_algorithm_id,
357 tbm_first_part,
358 maced_payload,
359 tag_buf,
360 &tag);
361 if(return_value) {
362 goto Done;
363 }
364
365 goto CloseArray;
366 #else
367 return_value = T_COSE_ERR_SHORT_CIRCUIT_SIG_DISABLED;
368 goto Done;
369 #endif
370 }
371
372 return_value = t_cose_crypto_hmac_sign_setup(&hmac_ctx,
373 me->signing_key,
374 me->cose_algorithm_id);
375 if(return_value) {
376 goto Done;
377 }
378
379 /* Compute the tag of the first part. */
380 return_value = t_cose_crypto_hmac_update(&hmac_ctx,
381 q_useful_buf_head(tbm_first_part,
382 tbm_first_part.len));
383 if(return_value) {
384 goto Done;
385 }
386
387 /*
388 * It is assumed that the context payload has been wrapped in a byte
389 * string in CBOR format.
390 */
391 return_value = t_cose_crypto_hmac_update(&hmac_ctx, maced_payload);
392 if(return_value) {
393 goto Done;
394 }
395
396 return_value = t_cose_crypto_hmac_sign_finish(&hmac_ctx, tag_buf, &tag);
397 if(return_value) {
398 goto Done;
399 }
400
401 CloseArray:
402 /* Add tag to CBOR and close out the array */
403 QCBOREncode_AddBytes(cbor_encode_ctx, tag);
404 QCBOREncode_CloseArray(cbor_encode_ctx);
405
406 /* CBOR encoding errors are tracked in the CBOR encoding context
407 * and handled in the layer above this
408 */
409
410 Done:
411 return return_value;
412 }
413