1 /*
2  * Copyright (C) 2018 Linaro Ltd
3  * Copyright (C) 2024 BayLibre SAS
4  *
5  * SPDX-License-Identifier: Apache-2.0
6  */
7 
8 #include <string.h>
9 #include <zephyr/types.h>
10 #include <errno.h>
11 
12 #include <zephyr/data/jwt.h>
13 #include <zephyr/data/json.h>
14 
15 #include "jwt.h"
16 
17 #if defined(CONFIG_JWT_SIGN_RSA_PSA) || defined(JWT_SIGN_RSA_LEGACY)
18 #define JWT_SIGNATURE_LEN 256
19 #else /* CONFIG_JWT_SIGN_ECDSA_PSA */
20 #define JWT_SIGNATURE_LEN 64
21 #endif
22 
23 /*
24  * Base64URL encoding is typically done by lookup into a 64-byte static
25  * 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
base64_char(int value)34 static 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
49 static const char b64_table[] =
50 	"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
base64_char(int value)51 static 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  */
base64_outch(struct jwt_builder * st,char ch)61 static 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  */
base64_flush(struct jwt_builder * st)82 static 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 
base64_addbyte(struct jwt_builder * st,uint8_t byte)104 static 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 
base64_append_bytes(const char * bytes,size_t len,void * data)112 static 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 
124 struct jwt_payload {
125 	int32_t exp;
126 	int32_t iat;
127 	const char *aud;
128 };
129 
130 static 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  */
jwt_add_header(struct jwt_builder * builder)139 static int jwt_add_header(struct jwt_builder *builder)
140 {
141 	/*
142 	 * Pre-computed JWT header
143 	 * Use https://www.base64encode.org/ for update
144 	 */
145 	const char jwt_header[] =
146 #if defined(CONFIG_JWT_SIGN_RSA_PSA) || defined(CONFIG_JWT_SIGN_RSA_LEGACY)
147 		/* {"alg":"RS256","typ":"JWT"} */
148 		"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9";
149 #else /* CONFIG_JWT_SIGN_ECDSA_PSA */
150 		/* {"alg":"ES256","typ":"JWT"} */
151 		"eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9";
152 #endif
153 	int jwt_header_len = ARRAY_SIZE(jwt_header);
154 
155 	if (jwt_header_len > builder->len) {
156 		builder->overflowed = true;
157 		return -ENOSPC;
158 	}
159 	strcpy(builder->buf, jwt_header);
160 	builder->buf += jwt_header_len - 1;
161 	builder->len -= jwt_header_len - 1;
162 	return 0;
163 }
164 
jwt_add_payload(struct jwt_builder * builder,int32_t exp,int32_t iat,const char * aud)165 int jwt_add_payload(struct jwt_builder *builder,
166 		     int32_t exp,
167 		     int32_t iat,
168 		     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 
jwt_sign(struct jwt_builder * builder,const char * der_key,size_t der_key_len)185 int jwt_sign(struct jwt_builder *builder,
186 	     const char *der_key,
187 	     size_t der_key_len)
188 {
189 	int ret;
190 	unsigned char sig[JWT_SIGNATURE_LEN];
191 
192 	ret = jwt_sign_impl(builder, der_key, der_key_len, sig, sizeof(sig));
193 	if (ret < 0) {
194 		return ret;
195 	}
196 
197 	base64_outch(builder, '.');
198 	base64_append_bytes(sig, sizeof(sig), builder);
199 	base64_flush(builder);
200 
201 	return builder->overflowed ? -ENOMEM : 0;
202 }
203 
jwt_init_builder(struct jwt_builder * builder,char * buffer,size_t buffer_size)204 int 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 
214 	return jwt_add_header(builder);
215 }
216