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