mirror of
				https://github.com/postgres/postgres.git
				synced 2025-11-03 09:13:20 +03:00 
			
		
		
		
	This add a new pgp_armor_headers function to extract armor headers from an ASCII-armored blob, and a new overloaded variant of the armor function, for constructing an ASCII-armor with extra headers. Marko Tiikkaja and me.
		
			
				
	
	
		
			489 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			489 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
/*
 | 
						|
 * pgp-armor.c
 | 
						|
 *		PGP ascii-armor.
 | 
						|
 *
 | 
						|
 * Copyright (c) 2005 Marko Kreen
 | 
						|
 * All rights reserved.
 | 
						|
 *
 | 
						|
 * Redistribution and use in source and binary forms, with or without
 | 
						|
 * modification, are permitted provided that the following conditions
 | 
						|
 * are met:
 | 
						|
 * 1. Redistributions of source code must retain the above copyright
 | 
						|
 *	  notice, this list of conditions and the following disclaimer.
 | 
						|
 * 2. Redistributions in binary form must reproduce the above copyright
 | 
						|
 *	  notice, this list of conditions and the following disclaimer in the
 | 
						|
 *	  documentation and/or other materials provided with the distribution.
 | 
						|
 *
 | 
						|
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 | 
						|
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 | 
						|
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 | 
						|
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 | 
						|
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 | 
						|
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 | 
						|
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 | 
						|
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 | 
						|
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 | 
						|
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 | 
						|
 * SUCH DAMAGE.
 | 
						|
 *
 | 
						|
 * contrib/pgcrypto/pgp-armor.c
 | 
						|
 */
 | 
						|
 | 
						|
#include "postgres.h"
 | 
						|
 | 
						|
#include "px.h"
 | 
						|
#include "pgp.h"
 | 
						|
 | 
						|
/*
 | 
						|
 * BASE64 - duplicated :(
 | 
						|
 */
 | 
						|
 | 
						|
static const unsigned char _base64[] =
 | 
						|
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
 | 
						|
 | 
						|
static int
 | 
						|
b64_encode(const uint8 *src, unsigned len, uint8 *dst)
 | 
						|
{
 | 
						|
	uint8	   *p,
 | 
						|
			   *lend = dst + 76;
 | 
						|
	const uint8 *s,
 | 
						|
			   *end = src + len;
 | 
						|
	int			pos = 2;
 | 
						|
	unsigned long buf = 0;
 | 
						|
 | 
						|
	s = src;
 | 
						|
	p = dst;
 | 
						|
 | 
						|
	while (s < end)
 | 
						|
	{
 | 
						|
		buf |= *s << (pos << 3);
 | 
						|
		pos--;
 | 
						|
		s++;
 | 
						|
 | 
						|
		/*
 | 
						|
		 * write it out
 | 
						|
		 */
 | 
						|
		if (pos < 0)
 | 
						|
		{
 | 
						|
			*p++ = _base64[(buf >> 18) & 0x3f];
 | 
						|
			*p++ = _base64[(buf >> 12) & 0x3f];
 | 
						|
			*p++ = _base64[(buf >> 6) & 0x3f];
 | 
						|
			*p++ = _base64[buf & 0x3f];
 | 
						|
 | 
						|
			pos = 2;
 | 
						|
			buf = 0;
 | 
						|
		}
 | 
						|
		if (p >= lend)
 | 
						|
		{
 | 
						|
			*p++ = '\n';
 | 
						|
			lend = p + 76;
 | 
						|
		}
 | 
						|
	}
 | 
						|
	if (pos != 2)
 | 
						|
	{
 | 
						|
		*p++ = _base64[(buf >> 18) & 0x3f];
 | 
						|
		*p++ = _base64[(buf >> 12) & 0x3f];
 | 
						|
		*p++ = (pos == 0) ? _base64[(buf >> 6) & 0x3f] : '=';
 | 
						|
		*p++ = '=';
 | 
						|
	}
 | 
						|
 | 
						|
	return p - dst;
 | 
						|
}
 | 
						|
 | 
						|
/* probably should use lookup table */
 | 
						|
static int
 | 
						|
b64_decode(const uint8 *src, unsigned len, uint8 *dst)
 | 
						|
{
 | 
						|
	const uint8 *srcend = src + len,
 | 
						|
			   *s = src;
 | 
						|
	uint8	   *p = dst;
 | 
						|
	char		c;
 | 
						|
	unsigned	b = 0;
 | 
						|
	unsigned long buf = 0;
 | 
						|
	int			pos = 0,
 | 
						|
				end = 0;
 | 
						|
 | 
						|
	while (s < srcend)
 | 
						|
	{
 | 
						|
		c = *s++;
 | 
						|
		if (c >= 'A' && c <= 'Z')
 | 
						|
			b = c - 'A';
 | 
						|
		else if (c >= 'a' && c <= 'z')
 | 
						|
			b = c - 'a' + 26;
 | 
						|
		else if (c >= '0' && c <= '9')
 | 
						|
			b = c - '0' + 52;
 | 
						|
		else if (c == '+')
 | 
						|
			b = 62;
 | 
						|
		else if (c == '/')
 | 
						|
			b = 63;
 | 
						|
		else if (c == '=')
 | 
						|
		{
 | 
						|
			/*
 | 
						|
			 * end sequence
 | 
						|
			 */
 | 
						|
			if (!end)
 | 
						|
			{
 | 
						|
				if (pos == 2)
 | 
						|
					end = 1;
 | 
						|
				else if (pos == 3)
 | 
						|
					end = 2;
 | 
						|
				else
 | 
						|
					return PXE_PGP_CORRUPT_ARMOR;
 | 
						|
			}
 | 
						|
			b = 0;
 | 
						|
		}
 | 
						|
		else if (c == ' ' || c == '\t' || c == '\n' || c == '\r')
 | 
						|
			continue;
 | 
						|
		else
 | 
						|
			return PXE_PGP_CORRUPT_ARMOR;
 | 
						|
 | 
						|
		/*
 | 
						|
		 * add it to buffer
 | 
						|
		 */
 | 
						|
		buf = (buf << 6) + b;
 | 
						|
		pos++;
 | 
						|
		if (pos == 4)
 | 
						|
		{
 | 
						|
			*p++ = (buf >> 16) & 255;
 | 
						|
			if (end == 0 || end > 1)
 | 
						|
				*p++ = (buf >> 8) & 255;
 | 
						|
			if (end == 0 || end > 2)
 | 
						|
				*p++ = buf & 255;
 | 
						|
			buf = 0;
 | 
						|
			pos = 0;
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if (pos != 0)
 | 
						|
		return PXE_PGP_CORRUPT_ARMOR;
 | 
						|
	return p - dst;
 | 
						|
}
 | 
						|
 | 
						|
static unsigned
 | 
						|
b64_enc_len(unsigned srclen)
 | 
						|
{
 | 
						|
	/*
 | 
						|
	 * 3 bytes will be converted to 4, linefeed after 76 chars
 | 
						|
	 */
 | 
						|
	return (srclen + 2) * 4 / 3 + srclen / (76 * 3 / 4);
 | 
						|
}
 | 
						|
 | 
						|
static unsigned
 | 
						|
b64_dec_len(unsigned srclen)
 | 
						|
{
 | 
						|
	return (srclen * 3) >> 2;
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * PGP armor
 | 
						|
 */
 | 
						|
 | 
						|
static const char *armor_header = "-----BEGIN PGP MESSAGE-----\n";
 | 
						|
static const char *armor_footer = "\n-----END PGP MESSAGE-----\n";
 | 
						|
 | 
						|
/* CRC24 implementation from rfc2440 */
 | 
						|
#define CRC24_INIT 0x00b704ceL
 | 
						|
#define CRC24_POLY 0x01864cfbL
 | 
						|
static long
 | 
						|
crc24(const uint8 *data, unsigned len)
 | 
						|
{
 | 
						|
	unsigned	crc = CRC24_INIT;
 | 
						|
	int			i;
 | 
						|
 | 
						|
	while (len--)
 | 
						|
	{
 | 
						|
		crc ^= (*data++) << 16;
 | 
						|
		for (i = 0; i < 8; i++)
 | 
						|
		{
 | 
						|
			crc <<= 1;
 | 
						|
			if (crc & 0x1000000)
 | 
						|
				crc ^= CRC24_POLY;
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return crc & 0xffffffL;
 | 
						|
}
 | 
						|
 | 
						|
void
 | 
						|
pgp_armor_encode(const uint8 *src, unsigned len, StringInfo dst,
 | 
						|
				 int num_headers, char **keys, char **values)
 | 
						|
{
 | 
						|
	int			n;
 | 
						|
	int			res;
 | 
						|
	unsigned	b64len;
 | 
						|
	unsigned	crc = crc24(src, len);
 | 
						|
 | 
						|
	appendStringInfoString(dst, armor_header);
 | 
						|
 | 
						|
	for (n = 0; n < num_headers; n++)
 | 
						|
		appendStringInfo(dst, "%s: %s\n", keys[n], values[n]);
 | 
						|
	appendStringInfoChar(dst, '\n');
 | 
						|
 | 
						|
	/* make sure we have enough room to b64_encode() */
 | 
						|
	b64len = b64_enc_len(len);
 | 
						|
	enlargeStringInfo(dst, (int) b64len);
 | 
						|
 | 
						|
	res = b64_encode(src, len, (uint8 *) dst->data + dst->len);
 | 
						|
	if (res > b64len)
 | 
						|
		elog(FATAL, "overflow - encode estimate too small");
 | 
						|
	dst->len += res;
 | 
						|
 | 
						|
	if (*(dst->data + dst->len - 1) != '\n')
 | 
						|
		appendStringInfoChar(dst, '\n');
 | 
						|
 | 
						|
	appendStringInfoChar(dst, '=');
 | 
						|
	appendStringInfoChar(dst, _base64[(crc >> 18) & 0x3f]);
 | 
						|
	appendStringInfoChar(dst, _base64[(crc >> 12) & 0x3f]);
 | 
						|
	appendStringInfoChar(dst, _base64[(crc >> 6) & 0x3f]);
 | 
						|
	appendStringInfoChar(dst, _base64[crc & 0x3f]);
 | 
						|
 | 
						|
	appendStringInfoString(dst, armor_footer);
 | 
						|
}
 | 
						|
 | 
						|
static const uint8 *
 | 
						|
find_str(const uint8 *data, const uint8 *data_end, const char *str, int strlen)
 | 
						|
{
 | 
						|
	const uint8 *p = data;
 | 
						|
 | 
						|
	if (!strlen)
 | 
						|
		return NULL;
 | 
						|
	if (data_end - data < strlen)
 | 
						|
		return NULL;
 | 
						|
	while (p < data_end)
 | 
						|
	{
 | 
						|
		p = memchr(p, str[0], data_end - p);
 | 
						|
		if (p == NULL)
 | 
						|
			return NULL;
 | 
						|
		if (p + strlen > data_end)
 | 
						|
			return NULL;
 | 
						|
		if (memcmp(p, str, strlen) == 0)
 | 
						|
			return p;
 | 
						|
		p++;
 | 
						|
	}
 | 
						|
	return NULL;
 | 
						|
}
 | 
						|
 | 
						|
static int
 | 
						|
find_header(const uint8 *data, const uint8 *datend,
 | 
						|
			const uint8 **start_p, int is_end)
 | 
						|
{
 | 
						|
	const uint8 *p = data;
 | 
						|
	static const char *start_sep = "-----BEGIN";
 | 
						|
	static const char *end_sep = "-----END";
 | 
						|
	const char *sep = is_end ? end_sep : start_sep;
 | 
						|
 | 
						|
	/* find header line */
 | 
						|
	while (1)
 | 
						|
	{
 | 
						|
		p = find_str(p, datend, sep, strlen(sep));
 | 
						|
		if (p == NULL)
 | 
						|
			return PXE_PGP_CORRUPT_ARMOR;
 | 
						|
		/* it must start at beginning of line */
 | 
						|
		if (p == data || *(p - 1) == '\n')
 | 
						|
			break;
 | 
						|
		p += strlen(sep);
 | 
						|
	}
 | 
						|
	*start_p = p;
 | 
						|
	p += strlen(sep);
 | 
						|
 | 
						|
	/* check if header text ok */
 | 
						|
	for (; p < datend && *p != '-'; p++)
 | 
						|
	{
 | 
						|
		/* various junk can be there, but definitely not line-feed	*/
 | 
						|
		if (*p >= ' ')
 | 
						|
			continue;
 | 
						|
		return PXE_PGP_CORRUPT_ARMOR;
 | 
						|
	}
 | 
						|
	if (datend - p < 5 || memcmp(p, sep, 5) != 0)
 | 
						|
		return PXE_PGP_CORRUPT_ARMOR;
 | 
						|
	p += 5;
 | 
						|
 | 
						|
	/* check if at end of line */
 | 
						|
	if (p < datend)
 | 
						|
	{
 | 
						|
		if (*p != '\n' && *p != '\r')
 | 
						|
			return PXE_PGP_CORRUPT_ARMOR;
 | 
						|
		if (*p == '\r')
 | 
						|
			p++;
 | 
						|
		if (p < datend && *p == '\n')
 | 
						|
			p++;
 | 
						|
	}
 | 
						|
	return p - *start_p;
 | 
						|
}
 | 
						|
 | 
						|
int
 | 
						|
pgp_armor_decode(const uint8 *src, int len, StringInfo dst)
 | 
						|
{
 | 
						|
	const uint8 *p = src;
 | 
						|
	const uint8 *data_end = src + len;
 | 
						|
	long		crc;
 | 
						|
	const uint8 *base64_start,
 | 
						|
			   *armor_end;
 | 
						|
	const uint8 *base64_end = NULL;
 | 
						|
	uint8		buf[4];
 | 
						|
	int			hlen;
 | 
						|
	int			blen;
 | 
						|
	int			res = PXE_PGP_CORRUPT_ARMOR;
 | 
						|
 | 
						|
	/* armor start */
 | 
						|
	hlen = find_header(src, data_end, &p, 0);
 | 
						|
	if (hlen <= 0)
 | 
						|
		goto out;
 | 
						|
	p += hlen;
 | 
						|
 | 
						|
	/* armor end */
 | 
						|
	hlen = find_header(p, data_end, &armor_end, 1);
 | 
						|
	if (hlen <= 0)
 | 
						|
		goto out;
 | 
						|
 | 
						|
	/* skip comments - find empty line */
 | 
						|
	while (p < armor_end && *p != '\n' && *p != '\r')
 | 
						|
	{
 | 
						|
		p = memchr(p, '\n', armor_end - p);
 | 
						|
		if (!p)
 | 
						|
			goto out;
 | 
						|
 | 
						|
		/* step to start of next line */
 | 
						|
		p++;
 | 
						|
	}
 | 
						|
	base64_start = p;
 | 
						|
 | 
						|
	/* find crc pos */
 | 
						|
	for (p = armor_end; p >= base64_start; p--)
 | 
						|
		if (*p == '=')
 | 
						|
		{
 | 
						|
			base64_end = p - 1;
 | 
						|
			break;
 | 
						|
		}
 | 
						|
	if (base64_end == NULL)
 | 
						|
		goto out;
 | 
						|
 | 
						|
	/* decode crc */
 | 
						|
	if (b64_decode(p + 1, 4, buf) != 3)
 | 
						|
		goto out;
 | 
						|
	crc = (((long) buf[0]) << 16) + (((long) buf[1]) << 8) + (long) buf[2];
 | 
						|
 | 
						|
	/* decode data */
 | 
						|
	blen = (int) b64_dec_len(len);
 | 
						|
	enlargeStringInfo(dst, blen);
 | 
						|
	res = b64_decode(base64_start, base64_end - base64_start, (uint8 *) dst->data);
 | 
						|
	if (res > blen)
 | 
						|
		elog(FATAL, "overflow - decode estimate too small");
 | 
						|
	if (res >= 0)
 | 
						|
	{
 | 
						|
		if (crc24((uint8 *) dst->data, res) == crc)
 | 
						|
			dst->len += res;
 | 
						|
		else
 | 
						|
			res = PXE_PGP_CORRUPT_ARMOR;
 | 
						|
	}
 | 
						|
out:
 | 
						|
	return res;
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * Extracts all armor headers from an ASCII-armored input.
 | 
						|
 *
 | 
						|
 * Returns 0 on success, or PXE_* error code on error. On success, the
 | 
						|
 * number of headers and their keys and values are returned in *nheaders,
 | 
						|
 * *nkeys and *nvalues.
 | 
						|
 */
 | 
						|
int
 | 
						|
pgp_extract_armor_headers(const uint8 *src, unsigned len,
 | 
						|
						  int *nheaders, char ***keys, char ***values)
 | 
						|
{
 | 
						|
	const uint8 *data_end = src + len;
 | 
						|
	const uint8 *p;
 | 
						|
	const uint8 *base64_start;
 | 
						|
	const uint8 *armor_start;
 | 
						|
	const uint8 *armor_end;
 | 
						|
	Size		armor_len;
 | 
						|
	char	   *line;
 | 
						|
	char	   *nextline;
 | 
						|
	char	   *eol,
 | 
						|
				*colon;
 | 
						|
	int			hlen;
 | 
						|
	char	   *buf;
 | 
						|
	int			hdrlines;
 | 
						|
	int			n;
 | 
						|
 | 
						|
	/* armor start */
 | 
						|
	hlen = find_header(src, data_end, &armor_start, 0);
 | 
						|
	if (hlen <= 0)
 | 
						|
		return PXE_PGP_CORRUPT_ARMOR;
 | 
						|
	armor_start += hlen;
 | 
						|
 | 
						|
	/* armor end */
 | 
						|
	hlen = find_header(armor_start, data_end, &armor_end, 1);
 | 
						|
	if (hlen <= 0)
 | 
						|
		return PXE_PGP_CORRUPT_ARMOR;
 | 
						|
 | 
						|
	/* Count the number of armor header lines. */
 | 
						|
	hdrlines = 0;
 | 
						|
	p = armor_start;
 | 
						|
	while (p < armor_end && *p != '\n' && *p != '\r')
 | 
						|
	{
 | 
						|
		p = memchr(p, '\n', armor_end - p);
 | 
						|
		if (!p)
 | 
						|
			return PXE_PGP_CORRUPT_ARMOR;
 | 
						|
 | 
						|
		/* step to start of next line */
 | 
						|
		p++;
 | 
						|
		hdrlines++;
 | 
						|
	}
 | 
						|
	base64_start = p;
 | 
						|
 | 
						|
	/*
 | 
						|
	 * Make a modifiable copy of the part of the input that contains the
 | 
						|
	 * headers. The returned key/value pointers will point inside the buffer.
 | 
						|
	 */
 | 
						|
	armor_len = base64_start - armor_start;
 | 
						|
	buf = palloc(armor_len + 1);
 | 
						|
	memcpy(buf, armor_start, armor_len);
 | 
						|
	buf[armor_len] = '\0';
 | 
						|
 | 
						|
	/* Allocate return arrays */
 | 
						|
	*keys = (char **) palloc(hdrlines * sizeof(char *));
 | 
						|
	*values = (char **) palloc(hdrlines * sizeof(char *));
 | 
						|
 | 
						|
	/*
 | 
						|
	 * Split the header lines at newlines and ": " separators, and collect
 | 
						|
	 * pointers to the keys and values in the return arrays.
 | 
						|
	 */
 | 
						|
	n = 0;
 | 
						|
	line = buf;
 | 
						|
	for (;;)
 | 
						|
	{
 | 
						|
		/* find end of line */
 | 
						|
		eol = strchr(line, '\n');
 | 
						|
		if (!eol)
 | 
						|
			break;
 | 
						|
		nextline = eol + 1;
 | 
						|
		/* if the line ends in CR + LF, strip the CR */
 | 
						|
		if (eol > line && *(eol - 1) == '\r')
 | 
						|
			eol--;
 | 
						|
		*eol = '\0';
 | 
						|
 | 
						|
		/* find colon+space separating the key and value */
 | 
						|
		colon = strstr(line, ": ");
 | 
						|
		if (!colon)
 | 
						|
			return PXE_PGP_CORRUPT_ARMOR;
 | 
						|
		*colon = '\0';
 | 
						|
 | 
						|
		/* shouldn't happen, we counted the number of lines beforehand */
 | 
						|
		if (n >= hdrlines)
 | 
						|
			elog(ERROR, "unexpected number of armor header lines");
 | 
						|
 | 
						|
		(*keys)[n] = line;
 | 
						|
		(*values)[n] = colon + 2;
 | 
						|
		n++;
 | 
						|
 | 
						|
		/* step to start of next line */
 | 
						|
		line = nextline;
 | 
						|
	}
 | 
						|
 | 
						|
	if (n != hdrlines)
 | 
						|
		elog(ERROR, "unexpected number of armor header lines");
 | 
						|
 | 
						|
	*nheaders = n;
 | 
						|
	return 0;
 | 
						|
}
 |