diff options
Diffstat (limited to 'contrib/pgcrypto/pgp-armor.c')
-rw-r--r-- | contrib/pgcrypto/pgp-armor.c | 488 |
1 files changed, 488 insertions, 0 deletions
diff --git a/contrib/pgcrypto/pgp-armor.c b/contrib/pgcrypto/pgp-armor.c new file mode 100644 index 0000000..679779a --- /dev/null +++ b/contrib/pgcrypto/pgp-armor.c @@ -0,0 +1,488 @@ +/* + * 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 "pgp.h" +#include "px.h" + +/* + * BASE64 - duplicated :( + */ + +static const unsigned char _base64[] = +"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + +static int +pg_base64_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 +pg_base64_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 +pg_base64_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 +pg_base64_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 pg_base64_encode() */ + b64len = pg_base64_enc_len(len); + enlargeStringInfo(dst, (int) b64len); + + res = pg_base64_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 (pg_base64_decode(p + 1, 4, buf) != 3) + goto out; + crc = (((long) buf[0]) << 16) + (((long) buf[1]) << 8) + (long) buf[2]; + + /* decode data */ + blen = (int) pg_base64_dec_len(len); + enlargeStringInfo(dst, blen); + res = pg_base64_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; +} |