1 /*
2  * pgp-armor.c
3  *		PGP ascii-armor.
4  *
5  * Copyright (c) 2005 Marko Kreen
6  * All rights reserved.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
11  * 1. Redistributions of source code must retain the above copyright
12  *	  notice, this list of conditions and the following disclaimer.
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *	  notice, this list of conditions and the following disclaimer in the
15  *	  documentation and/or other materials provided with the distribution.
16  *
17  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
18  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
21  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27  * SUCH DAMAGE.
28  *
29  * contrib/pgcrypto/pgp-armor.c
30  */
31 
32 #include "postgres.h"
33 
34 #include "px.h"
35 #include "pgp.h"
36 
37 /*
38  * BASE64 - duplicated :(
39  */
40 
41 static const unsigned char _base64[] =
42 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
43 
44 static int
pg_base64_encode(const uint8 * src,unsigned len,uint8 * dst)45 pg_base64_encode(const uint8 *src, unsigned len, uint8 *dst)
46 {
47 	uint8	   *p,
48 			   *lend = dst + 76;
49 	const uint8 *s,
50 			   *end = src + len;
51 	int			pos = 2;
52 	unsigned long buf = 0;
53 
54 	s = src;
55 	p = dst;
56 
57 	while (s < end)
58 	{
59 		buf |= *s << (pos << 3);
60 		pos--;
61 		s++;
62 
63 		/*
64 		 * write it out
65 		 */
66 		if (pos < 0)
67 		{
68 			*p++ = _base64[(buf >> 18) & 0x3f];
69 			*p++ = _base64[(buf >> 12) & 0x3f];
70 			*p++ = _base64[(buf >> 6) & 0x3f];
71 			*p++ = _base64[buf & 0x3f];
72 
73 			pos = 2;
74 			buf = 0;
75 		}
76 		if (p >= lend)
77 		{
78 			*p++ = '\n';
79 			lend = p + 76;
80 		}
81 	}
82 	if (pos != 2)
83 	{
84 		*p++ = _base64[(buf >> 18) & 0x3f];
85 		*p++ = _base64[(buf >> 12) & 0x3f];
86 		*p++ = (pos == 0) ? _base64[(buf >> 6) & 0x3f] : '=';
87 		*p++ = '=';
88 	}
89 
90 	return p - dst;
91 }
92 
93 /* probably should use lookup table */
94 static int
pg_base64_decode(const uint8 * src,unsigned len,uint8 * dst)95 pg_base64_decode(const uint8 *src, unsigned len, uint8 *dst)
96 {
97 	const uint8 *srcend = src + len,
98 			   *s = src;
99 	uint8	   *p = dst;
100 	char		c;
101 	unsigned	b = 0;
102 	unsigned long buf = 0;
103 	int			pos = 0,
104 				end = 0;
105 
106 	while (s < srcend)
107 	{
108 		c = *s++;
109 		if (c >= 'A' && c <= 'Z')
110 			b = c - 'A';
111 		else if (c >= 'a' && c <= 'z')
112 			b = c - 'a' + 26;
113 		else if (c >= '0' && c <= '9')
114 			b = c - '0' + 52;
115 		else if (c == '+')
116 			b = 62;
117 		else if (c == '/')
118 			b = 63;
119 		else if (c == '=')
120 		{
121 			/*
122 			 * end sequence
123 			 */
124 			if (!end)
125 			{
126 				if (pos == 2)
127 					end = 1;
128 				else if (pos == 3)
129 					end = 2;
130 				else
131 					return PXE_PGP_CORRUPT_ARMOR;
132 			}
133 			b = 0;
134 		}
135 		else if (c == ' ' || c == '\t' || c == '\n' || c == '\r')
136 			continue;
137 		else
138 			return PXE_PGP_CORRUPT_ARMOR;
139 
140 		/*
141 		 * add it to buffer
142 		 */
143 		buf = (buf << 6) + b;
144 		pos++;
145 		if (pos == 4)
146 		{
147 			*p++ = (buf >> 16) & 255;
148 			if (end == 0 || end > 1)
149 				*p++ = (buf >> 8) & 255;
150 			if (end == 0 || end > 2)
151 				*p++ = buf & 255;
152 			buf = 0;
153 			pos = 0;
154 		}
155 	}
156 
157 	if (pos != 0)
158 		return PXE_PGP_CORRUPT_ARMOR;
159 	return p - dst;
160 }
161 
162 static unsigned
pg_base64_enc_len(unsigned srclen)163 pg_base64_enc_len(unsigned srclen)
164 {
165 	/*
166 	 * 3 bytes will be converted to 4, linefeed after 76 chars
167 	 */
168 	return (srclen + 2) * 4 / 3 + srclen / (76 * 3 / 4);
169 }
170 
171 static unsigned
pg_base64_dec_len(unsigned srclen)172 pg_base64_dec_len(unsigned srclen)
173 {
174 	return (srclen * 3) >> 2;
175 }
176 
177 /*
178  * PGP armor
179  */
180 
181 static const char *armor_header = "-----BEGIN PGP MESSAGE-----\n";
182 static const char *armor_footer = "\n-----END PGP MESSAGE-----\n";
183 
184 /* CRC24 implementation from rfc2440 */
185 #define CRC24_INIT 0x00b704ceL
186 #define CRC24_POLY 0x01864cfbL
187 static long
crc24(const uint8 * data,unsigned len)188 crc24(const uint8 *data, unsigned len)
189 {
190 	unsigned	crc = CRC24_INIT;
191 	int			i;
192 
193 	while (len--)
194 	{
195 		crc ^= (*data++) << 16;
196 		for (i = 0; i < 8; i++)
197 		{
198 			crc <<= 1;
199 			if (crc & 0x1000000)
200 				crc ^= CRC24_POLY;
201 		}
202 	}
203 	return crc & 0xffffffL;
204 }
205 
206 void
pgp_armor_encode(const uint8 * src,unsigned len,StringInfo dst,int num_headers,char ** keys,char ** values)207 pgp_armor_encode(const uint8 *src, unsigned len, StringInfo dst,
208 				 int num_headers, char **keys, char **values)
209 {
210 	int			n;
211 	int			res;
212 	unsigned	b64len;
213 	unsigned	crc = crc24(src, len);
214 
215 	appendStringInfoString(dst, armor_header);
216 
217 	for (n = 0; n < num_headers; n++)
218 		appendStringInfo(dst, "%s: %s\n", keys[n], values[n]);
219 	appendStringInfoChar(dst, '\n');
220 
221 	/* make sure we have enough room to pg_base64_encode() */
222 	b64len = pg_base64_enc_len(len);
223 	enlargeStringInfo(dst, (int) b64len);
224 
225 	res = pg_base64_encode(src, len, (uint8 *) dst->data + dst->len);
226 	if (res > b64len)
227 		elog(FATAL, "overflow - encode estimate too small");
228 	dst->len += res;
229 
230 	if (*(dst->data + dst->len - 1) != '\n')
231 		appendStringInfoChar(dst, '\n');
232 
233 	appendStringInfoChar(dst, '=');
234 	appendStringInfoChar(dst, _base64[(crc >> 18) & 0x3f]);
235 	appendStringInfoChar(dst, _base64[(crc >> 12) & 0x3f]);
236 	appendStringInfoChar(dst, _base64[(crc >> 6) & 0x3f]);
237 	appendStringInfoChar(dst, _base64[crc & 0x3f]);
238 
239 	appendStringInfoString(dst, armor_footer);
240 }
241 
242 static const uint8 *
find_str(const uint8 * data,const uint8 * data_end,const char * str,int strlen)243 find_str(const uint8 *data, const uint8 *data_end, const char *str, int strlen)
244 {
245 	const uint8 *p = data;
246 
247 	if (!strlen)
248 		return NULL;
249 	if (data_end - data < strlen)
250 		return NULL;
251 	while (p < data_end)
252 	{
253 		p = memchr(p, str[0], data_end - p);
254 		if (p == NULL)
255 			return NULL;
256 		if (p + strlen > data_end)
257 			return NULL;
258 		if (memcmp(p, str, strlen) == 0)
259 			return p;
260 		p++;
261 	}
262 	return NULL;
263 }
264 
265 static int
find_header(const uint8 * data,const uint8 * datend,const uint8 ** start_p,int is_end)266 find_header(const uint8 *data, const uint8 *datend,
267 			const uint8 **start_p, int is_end)
268 {
269 	const uint8 *p = data;
270 	static const char *start_sep = "-----BEGIN";
271 	static const char *end_sep = "-----END";
272 	const char *sep = is_end ? end_sep : start_sep;
273 
274 	/* find header line */
275 	while (1)
276 	{
277 		p = find_str(p, datend, sep, strlen(sep));
278 		if (p == NULL)
279 			return PXE_PGP_CORRUPT_ARMOR;
280 		/* it must start at beginning of line */
281 		if (p == data || *(p - 1) == '\n')
282 			break;
283 		p += strlen(sep);
284 	}
285 	*start_p = p;
286 	p += strlen(sep);
287 
288 	/* check if header text ok */
289 	for (; p < datend && *p != '-'; p++)
290 	{
291 		/* various junk can be there, but definitely not line-feed	*/
292 		if (*p >= ' ')
293 			continue;
294 		return PXE_PGP_CORRUPT_ARMOR;
295 	}
296 	if (datend - p < 5 || memcmp(p, sep, 5) != 0)
297 		return PXE_PGP_CORRUPT_ARMOR;
298 	p += 5;
299 
300 	/* check if at end of line */
301 	if (p < datend)
302 	{
303 		if (*p != '\n' && *p != '\r')
304 			return PXE_PGP_CORRUPT_ARMOR;
305 		if (*p == '\r')
306 			p++;
307 		if (p < datend && *p == '\n')
308 			p++;
309 	}
310 	return p - *start_p;
311 }
312 
313 int
pgp_armor_decode(const uint8 * src,int len,StringInfo dst)314 pgp_armor_decode(const uint8 *src, int len, StringInfo dst)
315 {
316 	const uint8 *p = src;
317 	const uint8 *data_end = src + len;
318 	long		crc;
319 	const uint8 *base64_start,
320 			   *armor_end;
321 	const uint8 *base64_end = NULL;
322 	uint8		buf[4];
323 	int			hlen;
324 	int			blen;
325 	int			res = PXE_PGP_CORRUPT_ARMOR;
326 
327 	/* armor start */
328 	hlen = find_header(src, data_end, &p, 0);
329 	if (hlen <= 0)
330 		goto out;
331 	p += hlen;
332 
333 	/* armor end */
334 	hlen = find_header(p, data_end, &armor_end, 1);
335 	if (hlen <= 0)
336 		goto out;
337 
338 	/* skip comments - find empty line */
339 	while (p < armor_end && *p != '\n' && *p != '\r')
340 	{
341 		p = memchr(p, '\n', armor_end - p);
342 		if (!p)
343 			goto out;
344 
345 		/* step to start of next line */
346 		p++;
347 	}
348 	base64_start = p;
349 
350 	/* find crc pos */
351 	for (p = armor_end; p >= base64_start; p--)
352 		if (*p == '=')
353 		{
354 			base64_end = p - 1;
355 			break;
356 		}
357 	if (base64_end == NULL)
358 		goto out;
359 
360 	/* decode crc */
361 	if (pg_base64_decode(p + 1, 4, buf) != 3)
362 		goto out;
363 	crc = (((long) buf[0]) << 16) + (((long) buf[1]) << 8) + (long) buf[2];
364 
365 	/* decode data */
366 	blen = (int) pg_base64_dec_len(len);
367 	enlargeStringInfo(dst, blen);
368 	res = pg_base64_decode(base64_start, base64_end - base64_start, (uint8 *) dst->data);
369 	if (res > blen)
370 		elog(FATAL, "overflow - decode estimate too small");
371 	if (res >= 0)
372 	{
373 		if (crc24((uint8 *) dst->data, res) == crc)
374 			dst->len += res;
375 		else
376 			res = PXE_PGP_CORRUPT_ARMOR;
377 	}
378 out:
379 	return res;
380 }
381 
382 /*
383  * Extracts all armor headers from an ASCII-armored input.
384  *
385  * Returns 0 on success, or PXE_* error code on error. On success, the
386  * number of headers and their keys and values are returned in *nheaders,
387  * *nkeys and *nvalues.
388  */
389 int
pgp_extract_armor_headers(const uint8 * src,unsigned len,int * nheaders,char *** keys,char *** values)390 pgp_extract_armor_headers(const uint8 *src, unsigned len,
391 						  int *nheaders, char ***keys, char ***values)
392 {
393 	const uint8 *data_end = src + len;
394 	const uint8 *p;
395 	const uint8 *base64_start;
396 	const uint8 *armor_start;
397 	const uint8 *armor_end;
398 	Size		armor_len;
399 	char	   *line;
400 	char	   *nextline;
401 	char	   *eol,
402 			   *colon;
403 	int			hlen;
404 	char	   *buf;
405 	int			hdrlines;
406 	int			n;
407 
408 	/* armor start */
409 	hlen = find_header(src, data_end, &armor_start, 0);
410 	if (hlen <= 0)
411 		return PXE_PGP_CORRUPT_ARMOR;
412 	armor_start += hlen;
413 
414 	/* armor end */
415 	hlen = find_header(armor_start, data_end, &armor_end, 1);
416 	if (hlen <= 0)
417 		return PXE_PGP_CORRUPT_ARMOR;
418 
419 	/* Count the number of armor header lines. */
420 	hdrlines = 0;
421 	p = armor_start;
422 	while (p < armor_end && *p != '\n' && *p != '\r')
423 	{
424 		p = memchr(p, '\n', armor_end - p);
425 		if (!p)
426 			return PXE_PGP_CORRUPT_ARMOR;
427 
428 		/* step to start of next line */
429 		p++;
430 		hdrlines++;
431 	}
432 	base64_start = p;
433 
434 	/*
435 	 * Make a modifiable copy of the part of the input that contains the
436 	 * headers. The returned key/value pointers will point inside the buffer.
437 	 */
438 	armor_len = base64_start - armor_start;
439 	buf = palloc(armor_len + 1);
440 	memcpy(buf, armor_start, armor_len);
441 	buf[armor_len] = '\0';
442 
443 	/* Allocate return arrays */
444 	*keys = (char **) palloc(hdrlines * sizeof(char *));
445 	*values = (char **) palloc(hdrlines * sizeof(char *));
446 
447 	/*
448 	 * Split the header lines at newlines and ": " separators, and collect
449 	 * pointers to the keys and values in the return arrays.
450 	 */
451 	n = 0;
452 	line = buf;
453 	for (;;)
454 	{
455 		/* find end of line */
456 		eol = strchr(line, '\n');
457 		if (!eol)
458 			break;
459 		nextline = eol + 1;
460 		/* if the line ends in CR + LF, strip the CR */
461 		if (eol > line && *(eol - 1) == '\r')
462 			eol--;
463 		*eol = '\0';
464 
465 		/* find colon+space separating the key and value */
466 		colon = strstr(line, ": ");
467 		if (!colon)
468 			return PXE_PGP_CORRUPT_ARMOR;
469 		*colon = '\0';
470 
471 		/* shouldn't happen, we counted the number of lines beforehand */
472 		if (n >= hdrlines)
473 			elog(ERROR, "unexpected number of armor header lines");
474 
475 		(*keys)[n] = line;
476 		(*values)[n] = colon + 2;
477 		n++;
478 
479 		/* step to start of next line */
480 		line = nextline;
481 	}
482 
483 	if (n != hdrlines)
484 		elog(ERROR, "unexpected number of armor header lines");
485 
486 	*nheaders = n;
487 	return 0;
488 }
489