1 #include "base64.h"
2
3 #include <openssl/err.h>
4 #include <openssl/evp.h>
5 #include <openssl/buffer.h>
6 #include <errno.h>
7 #include <string.h>
8 #include "log.h"
9
10 /**
11 * Converts error from libcrypto representation to this project's
12 * representation.
13 */
14 static int
error_ul2i(unsigned long error)15 error_ul2i(unsigned long error)
16 {
17 /* I'm assuming int has at least 32 bits. Don't mess with the sign. */
18 int interror = error & 0x7FFFFFFFul;
19 return interror ? interror : -EINVAL;
20 }
21
22 /*
23 * Reference: openbsd/src/usr.bin/openssl/enc.c
24 *
25 * @in: The BIO that will stream the base64 encoded string you want to decode.
26 * @out: Buffer where this function will write the decoded string.
27 * @has_nl: Indicate if the encoded string has newline char.
28 * @out_len: Total allocated size of @out. It's supposed to be the result of
29 * EVP_DECODE_LENGTH(<size of the encoded string>).
30 * @out_written: This function will write the actual number of decoded bytes
31 * here.
32 *
33 * Returns error status. (Nonzero = error code, zero = success)
34 *
35 * If this returns error, do visit ERR_print_errors(), but also print an
36 * additional error message anyway. Functions such as BIO_new() don't always
37 * register a libcrypto stack error.
38 */
39 int
base64_decode(BIO * in,unsigned char * out,bool has_nl,size_t out_len,size_t * out_written)40 base64_decode(BIO *in, unsigned char *out, bool has_nl, size_t out_len,
41 size_t *out_written)
42 {
43 BIO *b64;
44 size_t offset = 0;
45 int written = 0;
46 unsigned long error;
47
48 /*
49 * BTW: The libcrypto API was clearly designed by fucking idiots.
50 * Peeking at the error stack is the only way I found to figure out
51 * whether some of the functions error'd.
52 * But since it's not documented that it's supposed to work this way,
53 * there's no guarantee that it will catch all errors.
54 * But it will have to do. It's better than nothing.
55 */
56
57 /* Assume that the caller took care of handling any previous errors. */
58 ERR_clear_error();
59
60 /*
61 * BIO_f_base64() cannot fail because it's dead-simple by definition.
62 * BIO_new() can, and it will lead to NULL. But only *some* errors will
63 * populate the error stack.
64 */
65 b64 = BIO_new(BIO_f_base64());
66 if (b64 == NULL) {
67 error = ERR_peek_last_error();
68 return error ? error_ul2i(error) : pr_enomem();
69 }
70
71 /*
72 * BIO_push() can technically fail through BIO_ctrl(), but it ignores
73 * the error. This will not cause it to revert the push, so we have to
74 * do it ourselves.
75 * Should we ignore this error? BIO_ctrl(BIO_CTRL_PUSH) performs some
76 * "internal, used to signify change" thing, whose importance is
77 * undefined due to BIO_ctrl()'s callback spaghetti.
78 * I'm not risking it.
79 */
80 in = BIO_push(b64, in);
81 if (!has_nl)
82 BIO_set_flags(in, BIO_FLAGS_BASE64_NO_NL);
83
84 error = ERR_peek_last_error();
85 if (error)
86 goto end;
87
88 do {
89 /*
90 * Do not move this after BIO_read().
91 * BIO_read() can return negative, which does not necessarily
92 * imply error, and which ruins the counter.
93 */
94 offset += written;
95 /*
96 * According to the documentation, the first argument should
97 * be b64, not in.
98 * But this is how it's written in enc.c.
99 * It doesn't seem to make a difference either way.
100 */
101 written = BIO_read(in, out + offset, out_len - offset);
102 } while (written > 0);
103
104 /* BIO_read() can fail. It does not return status. */
105 error = ERR_peek_last_error();
106 *out_written = offset;
107
108 end:
109 /*
110 * BIO_pop() can also fail due to BIO_ctrl(), but we will ignore this
111 * because whatever "signify change" crap happens, it can't possibly be
112 * damaging enough to prevent us from releasing b64. I hope.
113 */
114 BIO_pop(b64);
115 /* Returns 0 on failure, but that's only if b64 is NULL. Meaningless. */
116 BIO_free(b64);
117
118 return error ? error_ul2i(error) : 0;
119 }
120
121 /*
122 * Decode a base64 encoded string (@str_encoded), the decoded value is
123 * allocated at @result with a length of @result_len.
124 *
125 * Return 0 on success, or the error code if something went wrong. Don't forget
126 * to free @result after a successful decoding.
127 */
128 int
base64url_decode(char const * str_encoded,unsigned char ** result,size_t * result_len)129 base64url_decode(char const *str_encoded, unsigned char **result,
130 size_t *result_len)
131 {
132 BIO *encoded; /* base64 encoded. */
133 char *str_copy;
134 size_t encoded_len, alloc_size, dec_len;
135 int error, pad, i;
136
137 /*
138 * Apparently there isn't a base64url decoder, and there isn't
139 * much difference between base64 codification and base64url, just as
140 * stated in RFC 4648 section 5: "This encoding is technically
141 * identical to the previous one, except for the 62:nd and 63:rd
142 * alphabet character, as indicated in Table 2".
143 *
144 * The existing base64 can be used if the 62:nd and 63:rd base64url
145 * alphabet chars are replaced with the corresponding base64 chars, and
146 * also if we add the optional padding that the member should have.
147 */
148 encoded_len = strlen(str_encoded);
149 pad = (encoded_len % 4) > 0 ? 4 - (encoded_len % 4) : 0;
150
151 str_copy = malloc(encoded_len + pad + 1);
152 if (str_copy == NULL)
153 return pr_enomem();
154 /* Set all with pad char, then replace with the original string */
155 memset(str_copy, '=', encoded_len + pad);
156 memcpy(str_copy, str_encoded, encoded_len);
157 str_copy[encoded_len + pad] = '\0';
158
159 for (i = 0; i < encoded_len; i++) {
160 if (str_copy[i] == '-')
161 str_copy[i] = '+';
162 else if (str_copy[i] == '_')
163 str_copy[i] = '/';
164 }
165
166 /* Now decode as regular base64 */
167 encoded = BIO_new_mem_buf(str_copy, -1);
168 if (encoded == NULL) {
169 error = -EINVAL;
170 goto free_copy;
171 }
172
173 alloc_size = EVP_DECODE_LENGTH(strlen(str_copy));
174 *result = malloc(alloc_size + 1);
175 if (*result == NULL) {
176 error = pr_enomem();
177 goto free_enc;
178 }
179 memset(*result, 0, alloc_size);
180 (*result)[alloc_size] = '\0';
181
182 error = base64_decode(encoded, *result, false, alloc_size, &dec_len);
183 if (error)
184 goto free_all;
185
186 if (dec_len == 0) {
187 error = -EINVAL;
188 goto free_all;
189 }
190 *result_len = dec_len;
191
192 free(str_copy);
193 BIO_free(encoded);
194 return 0;
195 free_all:
196 free(*result);
197 free_enc:
198 BIO_free(encoded);
199 free_copy:
200 free(str_copy);
201 return error;
202 }
203
204 static int
to_base64url(char * base,size_t base_len,char ** out)205 to_base64url(char *base, size_t base_len, char **out)
206 {
207 char *pad, *tmp;
208 size_t len;
209 int i;
210
211 /* Remove padding, if present */
212 len = base_len;
213 do {
214 pad = strchr(base, '=');
215 if (pad == NULL)
216 break;
217 len = pad - base;
218 } while(0);
219
220 tmp = malloc(len + 1);
221 if (tmp == NULL)
222 return pr_enomem();
223
224 memcpy(tmp, base, len);
225 tmp[len] = '\0';
226
227 for (i = 0; i < len; i++) {
228 if (tmp[i] == '+')
229 tmp[i] = '-';
230 else if (tmp[i] == '/')
231 tmp[i] = '_';
232 }
233
234 *out = tmp;
235 return 0;
236 }
237
238 /*
239 * Encode @in (with size @in_len) as base64url without trailing pad, and
240 * allocate at @result.
241 */
242 int
base64url_encode(unsigned char const * in,int in_len,char ** result)243 base64url_encode(unsigned char const *in, int in_len, char **result)
244 {
245 BIO *b64, *mem;
246 BUF_MEM *mem_buf;
247 int error;
248
249 ERR_clear_error();
250
251 mem = BIO_new(BIO_s_mem());
252 if (mem == NULL) {
253 error = ERR_peek_last_error();
254 return error ? error_ul2i(error) : pr_enomem();
255 }
256
257 b64 = BIO_new(BIO_f_base64());
258 if (b64 == NULL) {
259 error = ERR_peek_last_error();
260 goto free_mem;
261 }
262 mem = BIO_push(b64, mem);
263 BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
264
265 BIO_write(b64, in, in_len);
266 BIO_flush(b64);
267 BIO_get_mem_ptr(mem, &mem_buf);
268
269 error = to_base64url(mem_buf->data, mem_buf->length, result);
270 if (error)
271 goto free_mem;
272
273 BIO_free_all(b64);
274 return 0;
275 free_mem:
276 BIO_free_all(b64);
277 return error ? error_ul2i(error) : pr_enomem();
278 }
279