summaryrefslogtreecommitdiffstats
path: root/src/lib/b64_decode.c
blob: b4dd3f8c0a1c29f998d937fbfa5eeb6e70516658 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
/* SPDX-License-Identifier: GPL-2.0-only */

#include <b64_decode.h>
#include <console/console.h>

/*
 * Translation Table to decode base64 ASCII stream into binary. Borrowed from
 *
 * http://base64.sourceforge.net/b64.c.
 *
 */
static const char cd64[] = "|$$$}rstuvwxyz{$$$$$$$>?@ABCDEFGHIJKLMN"
	"OPQRSTUVW$$$$$$XYZ[\\]^_`abcdefghijklmnopq";

struct buffer_descriptor {
	const uint8_t *input_buffer;
	size_t data_size;
	size_t input_index;
};

#define isalnum(c) ((((c) >= 'a') && ((c) <= 'z')) || \
		    (((c) >= 'A') && ((c) <= 'Z')) || \
		    (((c) >= '0') && ((c) <= '9')))

/*
 * On each invocation this function returns the next valid base64 character
 * from the encoded message, ignoring padding and line breaks.
 *
 * Once all input is consumed, 0 is returned on all following invocations. In
 * case any other than expected characters is found in the encoded message, -1
 * is returned for error.
 */
static int get_next_char(struct buffer_descriptor *bd)
{
	uint8_t c;

	/*
	 * The canonical base64 encoded messages include the following
	 * characters:
	 * - '0..9A..Za..z+/' to represent 64 values
	 * - '=' for padding
	 * - '<CR><LF>' to split the message into lines.
	 */
	while (bd->input_index < bd->data_size) {
		c = bd->input_buffer[bd->input_index++];

		switch (c) {
		case '=':
		case 0xa:
		case 0xd:
			continue;

		default:
			break;
		}

		if (!isalnum(c) && (c != '+') && (c != '/'))
			return -1;

		return c;
	}

	return 0;
}

/*
** decode
**
** decode a base64 encoded stream discarding padding and line breaks.
*/
size_t b64_decode(const uint8_t *input_data,
		  size_t input_length,
		  uint8_t *output_data)
{
	struct buffer_descriptor bd;
	unsigned int interim = 0;
	size_t output_size = 0;
	/* count of processed input bits, modulo log2(64) */
	unsigned int bit_count = 0;

	/*
	 * Keep the context on the stack to make things easier if this needs
	 * to run with CAR.
	 */
	bd.input_buffer = input_data;
	bd.data_size = input_length;
	bd.input_index = 0;

	while (1) { /* Until input is exhausted. */
		int v = get_next_char(&bd);

		if (v < 0) {
			printk(BIOS_ERR,
			       "Incompatible character at offset %zd.\n",
			       bd.input_index);
			return 0;
		}

		if (!v)
			break;

		/*
		 * v is guaranteed to be in the proper range for cd64, the
		 * result is a 6 bit number.
		 */
		v = cd64[v - 43] - 62;

		if (bit_count >= 2) {
			/*
			 * Once 6 more bits are added to the output, there is
			 * going to be at least a full byte.
			 *
			 * 'remaining_bits' is the exact number of bits which
			 * need to be added to the output to have another full
			 * byte ready.
			 */
			int remaining_bits = 8 - bit_count;

			interim <<= remaining_bits;
			interim |= v >> (6 - remaining_bits);

			/* Pass the new full byte to the output. */
			output_data[output_size++] = interim & 0xff;

			interim = v;
			bit_count -= 2;
		} else {
			interim <<= 6;
			interim |= v;
			bit_count += 6;
		}
	}

	return output_size;
}