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