/src/suricata7/src/util-base64.c
Line | Count | Source |
1 | | /* Copyright (C) 2007-2012 Open Information Security Foundation |
2 | | * |
3 | | * You can copy, redistribute or modify this Program under the terms of |
4 | | * the GNU General Public License version 2 as published by the Free |
5 | | * Software Foundation. |
6 | | * |
7 | | * This program is distributed in the hope that it will be useful, |
8 | | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
9 | | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
10 | | * GNU General Public License for more details. |
11 | | * |
12 | | * You should have received a copy of the GNU General Public License |
13 | | * version 2 along with this program; if not, write to the Free Software |
14 | | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA |
15 | | * 02110-1301, USA. |
16 | | */ |
17 | | |
18 | | /** |
19 | | * \file |
20 | | * |
21 | | * \author David Abarbanel <david.abarbanel@baesystems.com> |
22 | | * |
23 | | */ |
24 | | |
25 | | #include "util-base64.h" |
26 | | #include "util-debug.h" |
27 | | #include "util-unittest.h" |
28 | | /* Constants */ |
29 | 41.2M | #define BASE64_TABLE_MAX 122 |
30 | | |
31 | | /* Base64 character to index conversion table */ |
32 | | /* Characters are mapped as "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" */ |
33 | | static const int b64table[] = { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, |
34 | | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, |
35 | | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, |
36 | | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, |
37 | | -1, -1, -1, 62, -1, -1, -1, 63, 52, 53, |
38 | | 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, |
39 | | -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, |
40 | | 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, |
41 | | 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, |
42 | | 25, -1, -1, -1, -1, -1, -1, 26, 27, 28, |
43 | | 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, |
44 | | 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, |
45 | | 49, 50, 51 }; |
46 | | |
47 | | /** |
48 | | * \brief Gets a base64-decoded value from an encoded character |
49 | | * |
50 | | * \param c The encoded character |
51 | | * |
52 | | * \return The decoded value (0 or above), or -1 if the parameter is invalid |
53 | | */ |
54 | | static inline int GetBase64Value(uint8_t c) |
55 | 41.2M | { |
56 | 41.2M | int val = -1; |
57 | | |
58 | | /* Pull from conversion table */ |
59 | 41.2M | if (c <= BASE64_TABLE_MAX) { |
60 | 33.6M | val = b64table[(int) c]; |
61 | 33.6M | } |
62 | | |
63 | 41.2M | return val; |
64 | 41.2M | } |
65 | | |
66 | | /** |
67 | | * \brief Checks if the given char in a byte array is Base64 alphabet |
68 | | * |
69 | | * \param Char that needs to be checked |
70 | | * |
71 | | * \return True if the char was Base64 alphabet, False otherwise |
72 | | */ |
73 | | bool IsBase64Alphabet(uint8_t encoded_byte) |
74 | 6.16M | { |
75 | 6.16M | if (GetBase64Value(encoded_byte) < 0 && encoded_byte != '=') { |
76 | 4.30M | return false; |
77 | 4.30M | } |
78 | 1.85M | return true; |
79 | 6.16M | } |
80 | | |
81 | | /** |
82 | | * \brief Decodes a 4-byte base64-encoded block into a 3-byte ascii-encoded block |
83 | | * |
84 | | * \param ascii the 3-byte ascii output block |
85 | | * \param b64 the 4-byte base64 input block |
86 | | * |
87 | | * \return none |
88 | | */ |
89 | | static inline void DecodeBase64Block(uint8_t ascii[ASCII_BLOCK], uint8_t b64[B64_BLOCK]) |
90 | 6.25M | { |
91 | 6.25M | ascii[0] = (uint8_t) (b64[0] << 2) | (b64[1] >> 4); |
92 | 6.25M | ascii[1] = (uint8_t) (b64[1] << 4) | (b64[2] >> 2); |
93 | 6.25M | ascii[2] = (uint8_t) (b64[2] << 6) | (b64[3]); |
94 | 6.25M | } |
95 | | |
96 | | /** |
97 | | * \brief Decodes a base64-encoded string buffer into an ascii-encoded byte buffer |
98 | | * |
99 | | * \param dest The destination byte buffer |
100 | | * \param dest_size The destination byte buffer size |
101 | | * \param src The source string |
102 | | * \param len The length of the source string |
103 | | * \param consumed_bytes The bytes that were actually processed/consumed |
104 | | * \param decoded_bytes The bytes that were decoded |
105 | | * \param mode The mode in which decoding should happen |
106 | | * |
107 | | * \return Error code indicating success or failures with parsing |
108 | | */ |
109 | | Base64Ecode DecodeBase64(uint8_t *dest, uint32_t dest_size, const uint8_t *src, uint32_t len, |
110 | | uint32_t *consumed_bytes, uint32_t *decoded_bytes, Base64Mode mode) |
111 | 507k | { |
112 | 507k | int val; |
113 | 507k | uint32_t padding = 0, bbidx = 0, sp = 0, leading_sp = 0; |
114 | 507k | uint8_t *dptr = dest; |
115 | 507k | uint8_t b64[B64_BLOCK] = { 0,0,0,0 }; |
116 | 507k | bool valid = true; |
117 | 507k | Base64Ecode ecode = BASE64_ECODE_OK; |
118 | 507k | *decoded_bytes = 0; |
119 | | |
120 | | /* Traverse through each alpha-numeric letter in the source array */ |
121 | 35.6M | for (uint32_t i = 0; i < len; i++) { |
122 | | /* Get decimal representation */ |
123 | 35.1M | val = GetBase64Value(src[i]); |
124 | 35.1M | if (val < 0) { |
125 | 10.1M | if (mode == BASE64_MODE_RFC2045 && src[i] != '=') { |
126 | 9.86M | if (bbidx == 0) { |
127 | | /* Special case where last block of data has a leading space or invalid char */ |
128 | 1.73M | leading_sp++; |
129 | 1.73M | } |
130 | 9.86M | sp++; |
131 | 9.86M | continue; |
132 | 9.86M | } |
133 | | /* Invalid character found, so decoding fails */ |
134 | 239k | if (src[i] != '=') { |
135 | 6.59k | valid = false; |
136 | 6.59k | ecode = BASE64_ECODE_ERR; |
137 | 6.59k | if (mode == BASE64_MODE_STRICT) { |
138 | 0 | *decoded_bytes = 0; |
139 | 0 | } |
140 | 6.59k | break; |
141 | 6.59k | } |
142 | 233k | padding++; |
143 | 233k | } |
144 | | |
145 | | /* For each alpha-numeric letter in the source array, find the numeric |
146 | | * value */ |
147 | 25.2M | b64[bbidx++] = (val > 0 ? (uint8_t)val : 0); |
148 | | |
149 | | /* Decode every 4 base64 bytes into 3 ascii bytes */ |
150 | 25.2M | if (bbidx == B64_BLOCK) { |
151 | | |
152 | | /* For every 4 bytes, add 3 bytes but deduct the '=' padded blocks */ |
153 | 6.25M | uint32_t numDecoded_blk = ASCII_BLOCK - (padding < B64_BLOCK ? padding : ASCII_BLOCK); |
154 | 6.25M | if (dest_size < *decoded_bytes + numDecoded_blk) { |
155 | 2.24k | SCLogDebug("Destination buffer full"); |
156 | 2.24k | ecode = BASE64_ECODE_BUF; |
157 | 2.24k | break; |
158 | 2.24k | } |
159 | 6.25M | if (dest_size - *decoded_bytes < ASCII_BLOCK) |
160 | 194 | return BASE64_ECODE_BUF; |
161 | | |
162 | | /* Decode base-64 block into ascii block and move pointer */ |
163 | 6.25M | DecodeBase64Block(dptr, b64); |
164 | 6.25M | dptr += numDecoded_blk; |
165 | 6.25M | *decoded_bytes += numDecoded_blk; |
166 | | /* Reset base-64 block and index */ |
167 | 6.25M | bbidx = 0; |
168 | 6.25M | padding = 0; |
169 | 6.25M | *consumed_bytes += B64_BLOCK + sp; |
170 | 6.25M | sp = 0; |
171 | 6.25M | leading_sp = 0; |
172 | 6.25M | memset(&b64, 0, sizeof(b64)); |
173 | 6.25M | } |
174 | 25.2M | } |
175 | | |
176 | 507k | if (bbidx > 0 && bbidx < 4 && ((!valid && mode == BASE64_MODE_RFC4648))) { |
177 | | /* Decoded bytes for 1 or 2 base64 encoded bytes is 1 */ |
178 | 1.58k | padding = bbidx > 1 ? B64_BLOCK - bbidx : 2; |
179 | 1.58k | uint32_t numDecoded_blk = ASCII_BLOCK - (padding < B64_BLOCK ? padding : ASCII_BLOCK); |
180 | 1.58k | if (dest_size < *decoded_bytes + numDecoded_blk) { |
181 | 0 | SCLogDebug("Destination buffer full"); |
182 | 0 | ecode = BASE64_ECODE_BUF; |
183 | 0 | return ecode; |
184 | 0 | } |
185 | | /* if the destination size is not at least 3 Bytes long, it'll give a dynamic |
186 | | * buffer overflow while decoding, so, return and let the caller take care of the |
187 | | * remaining bytes to be decoded which should always be < 4 at this stage */ |
188 | 1.58k | if (dest_size - *decoded_bytes < ASCII_BLOCK) |
189 | 0 | return BASE64_ECODE_BUF; |
190 | 1.58k | *decoded_bytes += numDecoded_blk; |
191 | 1.58k | DecodeBase64Block(dptr, b64); |
192 | 1.58k | *consumed_bytes += bbidx; |
193 | 1.58k | } |
194 | | |
195 | | /* Finish remaining b64 bytes by padding */ |
196 | 507k | if (valid && bbidx > 0 && (mode != BASE64_MODE_RFC2045)) { |
197 | | /* Decode remaining */ |
198 | 749 | if (dest_size - *decoded_bytes < ASCII_BLOCK) |
199 | 0 | return BASE64_ECODE_BUF; |
200 | 749 | *decoded_bytes += ASCII_BLOCK - (B64_BLOCK - bbidx); |
201 | 749 | DecodeBase64Block(dptr, b64); |
202 | 749 | } |
203 | | |
204 | 507k | if (*decoded_bytes == 0) { |
205 | 15.1k | SCLogDebug("base64 decoding failed"); |
206 | 15.1k | } |
207 | | |
208 | 507k | *consumed_bytes += leading_sp; |
209 | 507k | return ecode; |
210 | 507k | } |
211 | | |
212 | | #ifdef UNITTESTS |
213 | | |
214 | | #define TEST_RFC2045(src, fin_str, dest_size, exp_decoded, exp_consumed, ecode) \ |
215 | | { \ |
216 | | uint32_t consumed_bytes = 0, num_decoded = 0; \ |
217 | | uint8_t dst[dest_size]; \ |
218 | | Base64Ecode code = DecodeBase64(dst, dest_size, (const uint8_t *)src, strlen(src), \ |
219 | | &consumed_bytes, &num_decoded, BASE64_MODE_RFC2045); \ |
220 | | FAIL_IF(code != ecode); \ |
221 | | FAIL_IF(memcmp(dst, fin_str, strlen(fin_str)) != 0); \ |
222 | | FAIL_IF(num_decoded != exp_decoded); \ |
223 | | FAIL_IF(consumed_bytes != exp_consumed); \ |
224 | | } |
225 | | |
226 | | #define TEST_RFC4648(src, fin_str, dest_size, exp_decoded, exp_consumed, ecode) \ |
227 | | { \ |
228 | | uint32_t consumed_bytes = 0, num_decoded = 0; \ |
229 | | uint8_t dst[dest_size]; \ |
230 | | Base64Ecode code = DecodeBase64(dst, dest_size, (const uint8_t *)src, strlen(src), \ |
231 | | &consumed_bytes, &num_decoded, BASE64_MODE_RFC4648); \ |
232 | | FAIL_IF(code != ecode); \ |
233 | | FAIL_IF(memcmp(dst, fin_str, strlen(fin_str)) != 0); \ |
234 | | FAIL_IF(num_decoded != exp_decoded); \ |
235 | | FAIL_IF(consumed_bytes != exp_consumed); \ |
236 | | } |
237 | | |
238 | | static int B64DecodeCompleteString(void) |
239 | | { |
240 | | /* |
241 | | * SGVsbG8gV29ybGR6 : Hello Worldz |
242 | | * */ |
243 | | const char *src = "SGVsbG8gV29ybGR6"; |
244 | | const char *fin_str = "Hello Worldz"; |
245 | | TEST_RFC2045(src, fin_str, strlen(fin_str), strlen(fin_str), strlen(src), BASE64_ECODE_OK); |
246 | | PASS; |
247 | | } |
248 | | |
249 | | static int B64DecodeInCompleteString(void) |
250 | | { |
251 | | /* |
252 | | * SGVsbG8gV29ybGR6 : Hello Worldz |
253 | | * */ |
254 | | const char *src = "SGVsbG8gV29ybGR"; |
255 | | const char *fin_str = "Hello Wor"; |
256 | | TEST_RFC2045(src, fin_str, strlen(fin_str), strlen(fin_str), strlen(src) - 3, BASE64_ECODE_OK); |
257 | | PASS; |
258 | | } |
259 | | |
260 | | static int B64DecodeCompleteStringWSp(void) |
261 | | { |
262 | | /* |
263 | | * SGVsbG8gV29ybGQ= : Hello World |
264 | | * */ |
265 | | |
266 | | const char *src = "SGVs bG8 gV29y bGQ="; |
267 | | const char *fin_str = "Hello World"; |
268 | | TEST_RFC2045(src, fin_str, strlen(fin_str) + 3, strlen(fin_str), strlen(src), BASE64_ECODE_OK); |
269 | | PASS; |
270 | | } |
271 | | |
272 | | static int B64DecodeInCompleteStringWSp(void) |
273 | | { |
274 | | /* |
275 | | * SGVsbG8gV29ybGQ= : Hello World |
276 | | * Special handling for this case (sp in remainder) done in ProcessBase64Remainder |
277 | | * */ |
278 | | |
279 | | const char *src = "SGVs bG8 gV29y bGQ"; |
280 | | const char *fin_str = "Hello Wor"; |
281 | | TEST_RFC2045(src, fin_str, strlen(fin_str) + 1 /* 12 B in dest_size */, strlen(fin_str), |
282 | | strlen(src) - 3, BASE64_ECODE_OK); |
283 | | PASS; |
284 | | } |
285 | | |
286 | | static int B64DecodeStringBiggerThanBuffer(void) |
287 | | { |
288 | | /* |
289 | | * SGVsbG8gV29ybGQ= : Hello World |
290 | | * */ |
291 | | |
292 | | const char *src = "SGVs bG8 gV29y bGQ="; |
293 | | const char *fin_str = "Hello Wor"; |
294 | | TEST_RFC2045( |
295 | | src, fin_str, strlen(fin_str) + 1, strlen(fin_str), strlen(src) - 4, BASE64_ECODE_BUF); |
296 | | PASS; |
297 | | } |
298 | | |
299 | | static int B64DecodeStringEndingSpaces(void) |
300 | | { |
301 | | const char *src = "0YPhA d H"; |
302 | | uint32_t consumed_bytes = 0, num_decoded = 0; |
303 | | uint8_t dst[10]; |
304 | | Base64Ecode code = DecodeBase64(dst, sizeof(dst), (const uint8_t *)src, strlen(src), |
305 | | &consumed_bytes, &num_decoded, BASE64_MODE_RFC2045); |
306 | | FAIL_IF(code != BASE64_ECODE_OK); |
307 | | FAIL_IF(num_decoded != 3); |
308 | | FAIL_IF(consumed_bytes != 4); |
309 | | PASS; |
310 | | } |
311 | | |
312 | | static int B64TestVectorsRFC2045(void) |
313 | | { |
314 | | const char *src1 = ""; |
315 | | const char *fin_str1 = ""; |
316 | | |
317 | | const char *src2 = "Zg=="; |
318 | | const char *fin_str2 = "f"; |
319 | | |
320 | | const char *src3 = "Zm8="; |
321 | | const char *fin_str3 = "fo"; |
322 | | |
323 | | const char *src4 = "Zm9v"; |
324 | | const char *fin_str4 = "foo"; |
325 | | |
326 | | const char *src5 = "Zm9vYg=="; |
327 | | const char *fin_str5 = "foob"; |
328 | | |
329 | | const char *src6 = "Zm9vYmE="; |
330 | | const char *fin_str6 = "fooba"; |
331 | | |
332 | | const char *src7 = "Zm9vYmFy"; |
333 | | const char *fin_str7 = "foobar"; |
334 | | |
335 | | const char *src8 = "Zm 9v Ym Fy"; |
336 | | const char *fin_str8 = "foobar"; |
337 | | |
338 | | const char *src9 = "Zm$9vYm.Fy"; |
339 | | const char *fin_str9 = "foobar"; |
340 | | |
341 | | const char *src10 = "Y21Wd2IzSjBaVzFoYVd4bWNtRjFaRUJoZEc4dVoyOTJMbUYxOmpqcHh4b3Rhb2w%5"; |
342 | | const char *fin_str10 = "cmVwb3J0ZW1haWxmcmF1ZEBhdG8uZ292LmF1:jjpxxotaol9"; |
343 | | |
344 | | TEST_RFC2045(src1, fin_str1, ASCII_BLOCK * 2, strlen(fin_str1), strlen(src1), BASE64_ECODE_OK); |
345 | | TEST_RFC2045(src2, fin_str2, ASCII_BLOCK * 2, strlen(fin_str2), strlen(src2), BASE64_ECODE_OK); |
346 | | TEST_RFC2045(src3, fin_str3, ASCII_BLOCK * 2, strlen(fin_str3), strlen(src3), BASE64_ECODE_OK); |
347 | | TEST_RFC2045(src4, fin_str4, ASCII_BLOCK * 2, strlen(fin_str4), strlen(src4), BASE64_ECODE_OK); |
348 | | TEST_RFC2045(src5, fin_str5, ASCII_BLOCK * 2, strlen(fin_str5), strlen(src5), BASE64_ECODE_OK); |
349 | | TEST_RFC2045(src6, fin_str6, ASCII_BLOCK * 2, strlen(fin_str6), strlen(src6), BASE64_ECODE_OK); |
350 | | TEST_RFC2045(src7, fin_str7, ASCII_BLOCK * 2, strlen(fin_str7), strlen(src7), BASE64_ECODE_OK); |
351 | | TEST_RFC2045(src8, fin_str8, ASCII_BLOCK * 2, strlen(fin_str8), strlen(src8), BASE64_ECODE_OK); |
352 | | TEST_RFC2045(src9, fin_str9, ASCII_BLOCK * 2, strlen(fin_str9), strlen(src9), BASE64_ECODE_OK); |
353 | | TEST_RFC2045(src10, fin_str10, strlen(fin_str10) + 2, strlen(fin_str10), strlen(src10), |
354 | | BASE64_ECODE_OK); |
355 | | PASS; |
356 | | } |
357 | | |
358 | | static int B64TestVectorsRFC4648(void) |
359 | | { |
360 | | const char *src1 = ""; |
361 | | const char *fin_str1 = ""; |
362 | | |
363 | | const char *src2 = "Zg=="; |
364 | | const char *fin_str2 = "f"; |
365 | | |
366 | | const char *src3 = "Zm8="; |
367 | | const char *fin_str3 = "fo"; |
368 | | |
369 | | const char *src4 = "Zm9v"; |
370 | | const char *fin_str4 = "foo"; |
371 | | |
372 | | const char *src5 = "Zm9vYg=="; |
373 | | const char *fin_str5 = "foob"; |
374 | | |
375 | | const char *src6 = "Zm9vYmE="; |
376 | | const char *fin_str6 = "fooba"; |
377 | | |
378 | | const char *src7 = "Zm9vYmFy"; |
379 | | const char *fin_str7 = "foobar"; |
380 | | |
381 | | const char *src8 = "Zm 9v Ym Fy"; |
382 | | const char *fin_str8 = "f"; |
383 | | |
384 | | const char *src9 = "Zm$9vYm.Fy"; |
385 | | const char *fin_str9 = "f"; |
386 | | |
387 | | const char *src10 = "Y21Wd2IzSjBaVzFoYVd4bWNtRjFaRUJoZEc4dVoyOTJMbUYxOmpqcHh4b3Rhb2w%3D"; |
388 | | const char *fin_str10 = "cmVwb3J0ZW1haWxmcmF1ZEBhdG8uZ292LmF1:jjpxxotaol"; |
389 | | |
390 | | TEST_RFC4648(src1, fin_str1, ASCII_BLOCK * 2, strlen(fin_str1), strlen(src1), BASE64_ECODE_OK); |
391 | | TEST_RFC4648(src2, fin_str2, ASCII_BLOCK * 2, strlen(fin_str2), strlen(src2), BASE64_ECODE_OK); |
392 | | TEST_RFC4648(src3, fin_str3, ASCII_BLOCK * 2, strlen(fin_str3), strlen(src3), BASE64_ECODE_OK); |
393 | | TEST_RFC4648(src4, fin_str4, ASCII_BLOCK * 2, strlen(fin_str4), strlen(src4), BASE64_ECODE_OK); |
394 | | TEST_RFC4648(src5, fin_str5, ASCII_BLOCK * 2, strlen(fin_str5), strlen(src5), BASE64_ECODE_OK); |
395 | | TEST_RFC4648(src6, fin_str6, ASCII_BLOCK * 2, strlen(fin_str6), strlen(src6), BASE64_ECODE_OK); |
396 | | TEST_RFC4648(src7, fin_str7, ASCII_BLOCK * 2, strlen(fin_str7), strlen(src7), BASE64_ECODE_OK); |
397 | | TEST_RFC4648(src8, fin_str8, ASCII_BLOCK * 2, 1 /* f */, 2 /* Zm */, BASE64_ECODE_ERR); |
398 | | TEST_RFC4648(src9, fin_str9, ASCII_BLOCK * 2, 1 /* f */, 2 /* Zm */, BASE64_ECODE_ERR); |
399 | | TEST_RFC4648(src10, fin_str10, strlen(fin_str10) + 1, strlen(fin_str10), strlen(src10) - 3, |
400 | | BASE64_ECODE_ERR); |
401 | | PASS; |
402 | | } |
403 | | |
404 | | void Base64RegisterTests(void) |
405 | | { |
406 | | UtRegisterTest("B64DecodeCompleteStringWSp", B64DecodeCompleteStringWSp); |
407 | | UtRegisterTest("B64DecodeInCompleteStringWSp", B64DecodeInCompleteStringWSp); |
408 | | UtRegisterTest("B64DecodeCompleteString", B64DecodeCompleteString); |
409 | | UtRegisterTest("B64DecodeInCompleteString", B64DecodeInCompleteString); |
410 | | UtRegisterTest("B64DecodeStringBiggerThanBuffer", B64DecodeStringBiggerThanBuffer); |
411 | | UtRegisterTest("B64DecodeStringEndingSpaces", B64DecodeStringEndingSpaces); |
412 | | UtRegisterTest("B64TestVectorsRFC2045", B64TestVectorsRFC2045); |
413 | | UtRegisterTest("B64TestVectorsRFC4648", B64TestVectorsRFC4648); |
414 | | } |
415 | | #endif |