blob: 4487e557096af1fbe60aab144464afb0f79ea173 [file] [log] [blame]
David Brownf8b838d2018-09-20 12:15:20 -07001/*
2 * Copyright (C) 2018 Linaro Ltd
Valerio Settiec3dea22024-09-10 16:42:50 +02003 * Copyright (C) 2024 BayLibre SAS
David Brownf8b838d2018-09-20 12:15:20 -07004 *
5 * SPDX-License-Identifier: Apache-2.0
6 */
7
8#include <string.h>
9#include <zephyr/types.h>
10#include <errno.h>
11
Gerard Marull-Paretas5113c142022-05-06 11:12:04 +020012#include <zephyr/data/jwt.h>
13#include <zephyr/data/json.h>
David Brownf8b838d2018-09-20 12:15:20 -070014
Valerio Settiec3dea22024-09-10 16:42:50 +020015#include "jwt.h"
David Brownf8b838d2018-09-20 12:15:20 -070016
Valerio Settiec3dea22024-09-10 16:42:50 +020017#if defined(CONFIG_JWT_SIGN_RSA)
18#define JWT_SIGNATURE_LEN 256
19#else /* CONFIG_JWT_SIGN_ECDSA */
20#define JWT_SIGNATURE_LEN 64
David Brownf8b838d2018-09-20 12:15:20 -070021#endif
22
23/*
Benjamin Lemouzy635e41e2024-07-15 14:19:14 +020024 * Base64URL encoding is typically done by lookup into a 64-byte static
David Brownf8b838d2018-09-20 12:15:20 -070025 * array. As an experiment, lets look at both code size and time for
26 * one that does the character encoding computationally. Like the
27 * array version, this doesn't do bounds checking, and assumes the
28 * passed value has been masked.
29 *
30 * On Cortex-M, this function is 34 bytes of code, which is only a
31 * little more than half of the size of the lookup table.
32 */
33#if 1
34static int base64_char(int value)
35{
36 if (value < 26) {
37 return value + 'A';
38 } else if (value < 52) {
39 return value + 'a' - 26;
40 } else if (value < 62) {
41 return value + '0' - 52;
42 } else if (value == 62) {
43 return '-';
44 } else {
45 return '_';
46 }
47}
48#else
49static const char b64_table[] =
50 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
51static inline int base64_char(int value)
52{
53 return b64_table[value];
54}
55#endif
56
57/*
58 * Add a single character to the jwt buffer. Detects overflow, and
59 * always keeps the buffer null terminated.
60 */
61static void base64_outch(struct jwt_builder *st, char ch)
62{
63 if (st->overflowed) {
64 return;
65 }
66
67 if (st->len < 2) {
68 st->overflowed = true;
69 return;
70 }
71
72 *st->buf++ = ch;
73 st->len--;
74 *st->buf = 0;
75}
76
77/*
78 * Flush any pending base64 character data out. If we have all three
79 * bytes are present, this will generate 4 characters, otherwise it
80 * may generate fewer.
81 */
82static void base64_flush(struct jwt_builder *st)
83{
84 if (st->pending < 1) {
85 return;
86 }
87
88 base64_outch(st, base64_char(st->wip[0] >> 2));
89 base64_outch(st, base64_char(((st->wip[0] & 0x03) << 4) |
90 (st->wip[1] >> 4)));
91
92 if (st->pending >= 2) {
93 base64_outch(st, base64_char(((st->wip[1] & 0x0f) << 2) |
94 (st->wip[2] >> 6)));
95 }
96 if (st->pending >= 3) {
97 base64_outch(st, base64_char(st->wip[2] & 0x3f));
98 }
99
100 st->pending = 0;
101 memset(st->wip, 0, 3);
102}
103
104static void base64_addbyte(struct jwt_builder *st, uint8_t byte)
105{
106 st->wip[st->pending++] = byte;
107 if (st->pending == 3) {
108 base64_flush(st);
109 }
110}
111
112static int base64_append_bytes(const char *bytes, size_t len,
113 void *data)
114{
115 struct jwt_builder *st = data;
116
117 while (len-- > 0) {
118 base64_addbyte(st, *bytes++);
119 }
120
121 return 0;
122}
123
David Brownf8b838d2018-09-20 12:15:20 -0700124struct jwt_payload {
Kumar Galaa1b77fd2020-05-27 11:26:57 -0500125 int32_t exp;
126 int32_t iat;
David Brownf8b838d2018-09-20 12:15:20 -0700127 const char *aud;
128};
129
130static struct json_obj_descr jwt_payload_desc[] = {
131 JSON_OBJ_DESCR_PRIM(struct jwt_payload, aud, JSON_TOK_STRING),
132 JSON_OBJ_DESCR_PRIM(struct jwt_payload, exp, JSON_TOK_NUMBER),
133 JSON_OBJ_DESCR_PRIM(struct jwt_payload, iat, JSON_TOK_NUMBER),
134};
135
136/*
137 * Add the JWT header to the buffer.
138 */
Alexey Markevich5ee67932020-10-05 16:35:58 +0300139static int jwt_add_header(struct jwt_builder *builder)
David Brownf8b838d2018-09-20 12:15:20 -0700140{
Alexey Markevich5ee67932020-10-05 16:35:58 +0300141 /*
142 * Pre-computed JWT header
143 * Use https://www.base64encode.org/ for update
144 */
145 const char jwt_header[] =
David Brownf8b838d2018-09-20 12:15:20 -0700146#ifdef CONFIG_JWT_SIGN_RSA
Alexey Markevich5ee67932020-10-05 16:35:58 +0300147 /* {"alg":"RS256","typ":"JWT"} */
148 "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9";
Valerio Settiec3dea22024-09-10 16:42:50 +0200149#else /* CONFIG_JWT_SIGN_ECDSA */
Alexey Markevich5ee67932020-10-05 16:35:58 +0300150 /* {"alg":"ES256","typ":"JWT"} */
151 "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9";
David Brownf8b838d2018-09-20 12:15:20 -0700152#endif
Alexey Markevich5ee67932020-10-05 16:35:58 +0300153 int jwt_header_len = ARRAY_SIZE(jwt_header);
David Brownf8b838d2018-09-20 12:15:20 -0700154
Alexey Markevich5ee67932020-10-05 16:35:58 +0300155 if (jwt_header_len > builder->len) {
156 builder->overflowed = true;
157 return -ENOSPC;
David Brownf8b838d2018-09-20 12:15:20 -0700158 }
Alexey Markevich5ee67932020-10-05 16:35:58 +0300159 strcpy(builder->buf, jwt_header);
160 builder->buf += jwt_header_len - 1;
161 builder->len -= jwt_header_len - 1;
162 return 0;
David Brownf8b838d2018-09-20 12:15:20 -0700163}
164
165int jwt_add_payload(struct jwt_builder *builder,
Kumar Galaa1b77fd2020-05-27 11:26:57 -0500166 int32_t exp,
167 int32_t iat,
David Brownf8b838d2018-09-20 12:15:20 -0700168 const char *aud)
169{
170 struct jwt_payload payload = {
171 .exp = exp,
172 .iat = iat,
173 .aud = aud,
174 };
175
176 base64_outch(builder, '.');
177 int res = json_obj_encode(jwt_payload_desc,
178 ARRAY_SIZE(jwt_payload_desc),
179 &payload, base64_append_bytes, builder);
180
181 base64_flush(builder);
182 return res;
183}
184
David Brownf8b838d2018-09-20 12:15:20 -0700185int jwt_sign(struct jwt_builder *builder,
186 const char *der_key,
187 size_t der_key_len)
188{
Valerio Settiec3dea22024-09-10 16:42:50 +0200189 int ret;
190 unsigned char sig[JWT_SIGNATURE_LEN];
David Brownf8b838d2018-09-20 12:15:20 -0700191
Valerio Settiec3dea22024-09-10 16:42:50 +0200192 ret = jwt_sign_impl(builder, der_key, der_key_len, sig, sizeof(sig));
193 if (ret < 0) {
194 return ret;
David Brownf8b838d2018-09-20 12:15:20 -0700195 }
196
197 base64_outch(builder, '.');
198 base64_append_bytes(sig, sizeof(sig), builder);
199 base64_flush(builder);
200
Valerio Settiec3dea22024-09-10 16:42:50 +0200201 return builder->overflowed ? -ENOMEM : 0;
David Brownf8b838d2018-09-20 12:15:20 -0700202}
David Brownf8b838d2018-09-20 12:15:20 -0700203
204int jwt_init_builder(struct jwt_builder *builder,
205 char *buffer,
206 size_t buffer_size)
207{
208 builder->base = buffer;
209 builder->buf = buffer;
210 builder->len = buffer_size;
211 builder->overflowed = false;
212 builder->pending = 0;
213
Alexey Markevich5ee67932020-10-05 16:35:58 +0300214 return jwt_add_header(builder);
David Brownf8b838d2018-09-20 12:15:20 -0700215}