LCOV - code coverage report
Current view: top level - contrib/pgcrypto - pgp-armor.c (source / functions) Hit Total Coverage
Test: PostgreSQL 17devel Lines: 206 231 89.2 %
Date: 2024-04-20 12:11:36 Functions: 10 10 100.0 %
Legend: Lines: hit not hit

          Line data    Source code
       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 "pgp.h"
      35             : #include "px.h"
      36             : 
      37             : /*
      38             :  * BASE64 - duplicated :(
      39             :  */
      40             : 
      41             : static const unsigned char _base64[] =
      42             : "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
      43             : 
      44             : static int
      45          20 : pg_base64_encode(const uint8 *src, unsigned len, uint8 *dst)
      46             : {
      47             :     uint8      *p,
      48          20 :                *lend = dst + 76;
      49             :     const uint8 *s,
      50          20 :                *end = src + len;
      51          20 :     int         pos = 2;
      52          20 :     unsigned long buf = 0;
      53             : 
      54          20 :     s = src;
      55          20 :     p = dst;
      56             : 
      57         262 :     while (s < end)
      58             :     {
      59         242 :         buf |= *s << (pos << 3);
      60         242 :         pos--;
      61         242 :         s++;
      62             : 
      63             :         /*
      64             :          * write it out
      65             :          */
      66         242 :         if (pos < 0)
      67             :         {
      68          74 :             *p++ = _base64[(buf >> 18) & 0x3f];
      69          74 :             *p++ = _base64[(buf >> 12) & 0x3f];
      70          74 :             *p++ = _base64[(buf >> 6) & 0x3f];
      71          74 :             *p++ = _base64[buf & 0x3f];
      72             : 
      73          74 :             pos = 2;
      74          74 :             buf = 0;
      75             :         }
      76         242 :         if (p >= lend)
      77             :         {
      78           2 :             *p++ = '\n';
      79           2 :             lend = p + 76;
      80             :         }
      81             :     }
      82          20 :     if (pos != 2)
      83             :     {
      84          12 :         *p++ = _base64[(buf >> 18) & 0x3f];
      85          12 :         *p++ = _base64[(buf >> 12) & 0x3f];
      86          12 :         *p++ = (pos == 0) ? _base64[(buf >> 6) & 0x3f] : '=';
      87          12 :         *p++ = '=';
      88             :     }
      89             : 
      90          20 :     return p - dst;
      91             : }
      92             : 
      93             : /* probably should use lookup table */
      94             : static int
      95         356 : pg_base64_decode(const uint8 *src, unsigned len, uint8 *dst)
      96             : {
      97         356 :     const uint8 *srcend = src + len,
      98         356 :                *s = src;
      99         356 :     uint8      *p = dst;
     100             :     char        c;
     101         356 :     unsigned    b = 0;
     102         356 :     unsigned long buf = 0;
     103         356 :     int         pos = 0,
     104         356 :                 end = 0;
     105             : 
     106      170260 :     while (s < srcend)
     107             :     {
     108      169904 :         c = *s++;
     109      169904 :         if (c >= 'A' && c <= 'Z')
     110       71272 :             b = c - 'A';
     111       98632 :         else if (c >= 'a' && c <= 'z')
     112       65524 :             b = c - 'a' + 26;
     113       33108 :         else if (c >= '0' && c <= '9')
     114       24920 :             b = c - '0' + 52;
     115        8188 :         else if (c == '+')
     116        2604 :             b = 62;
     117        5584 :         else if (c == '/')
     118        2688 :             b = 63;
     119        2896 :         else if (c == '=')
     120             :         {
     121             :             /*
     122             :              * end sequence
     123             :              */
     124         232 :             if (!end)
     125             :             {
     126         134 :                 if (pos == 2)
     127          98 :                     end = 1;
     128          36 :                 else if (pos == 3)
     129          36 :                     end = 2;
     130             :                 else
     131           0 :                     return PXE_PGP_CORRUPT_ARMOR;
     132             :             }
     133         232 :             b = 0;
     134             :         }
     135        2664 :         else if (c == ' ' || c == '\t' || c == '\n' || c == '\r')
     136        2664 :             continue;
     137             :         else
     138           0 :             return PXE_PGP_CORRUPT_ARMOR;
     139             : 
     140             :         /*
     141             :          * add it to buffer
     142             :          */
     143      167240 :         buf = (buf << 6) + b;
     144      167240 :         pos++;
     145      167240 :         if (pos == 4)
     146             :         {
     147       41810 :             *p++ = (buf >> 16) & 255;
     148       41810 :             if (end == 0 || end > 1)
     149       41712 :                 *p++ = (buf >> 8) & 255;
     150       41810 :             if (end == 0 || end > 2)
     151       41676 :                 *p++ = buf & 255;
     152       41810 :             buf = 0;
     153       41810 :             pos = 0;
     154             :         }
     155             :     }
     156             : 
     157         356 :     if (pos != 0)
     158           0 :         return PXE_PGP_CORRUPT_ARMOR;
     159         356 :     return p - dst;
     160             : }
     161             : 
     162             : static unsigned
     163          20 : pg_base64_enc_len(unsigned srclen)
     164             : {
     165             :     /*
     166             :      * 3 bytes will be converted to 4, linefeed after 76 chars
     167             :      */
     168          20 :     return (srclen + 2) / 3 * 4 + srclen / (76 * 3 / 4);
     169             : }
     170             : 
     171             : static unsigned
     172         178 : pg_base64_dec_len(unsigned srclen)
     173             : {
     174         178 :     return (srclen * 3) >> 2;
     175             : }
     176             : 
     177             : /*
     178             :  * PGP armor
     179             :  */
     180             : 
     181             : static const char *const armor_header = "-----BEGIN PGP MESSAGE-----\n";
     182             : static const char *const 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
     188         198 : crc24(const uint8 *data, unsigned len)
     189             : {
     190         198 :     unsigned    crc = CRC24_INIT;
     191             :     int         i;
     192             : 
     193      125104 :     while (len--)
     194             :     {
     195      124906 :         crc ^= (*data++) << 16;
     196     1124154 :         for (i = 0; i < 8; i++)
     197             :         {
     198      999248 :             crc <<= 1;
     199      999248 :             if (crc & 0x1000000)
     200      500460 :                 crc ^= CRC24_POLY;
     201             :         }
     202             :     }
     203         198 :     return crc & 0xffffffL;
     204             : }
     205             : 
     206             : void
     207          20 : 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          20 :     unsigned    crc = crc24(src, len);
     214             : 
     215          20 :     appendStringInfoString(dst, armor_header);
     216             : 
     217          34 :     for (n = 0; n < num_headers; n++)
     218          14 :         appendStringInfo(dst, "%s: %s\n", keys[n], values[n]);
     219          20 :     appendStringInfoChar(dst, '\n');
     220             : 
     221             :     /* make sure we have enough room to pg_base64_encode() */
     222          20 :     b64len = pg_base64_enc_len(len);
     223          20 :     enlargeStringInfo(dst, (int) b64len);
     224             : 
     225          20 :     res = pg_base64_encode(src, len, (uint8 *) dst->data + dst->len);
     226          20 :     if (res > b64len)
     227           0 :         elog(FATAL, "overflow - encode estimate too small");
     228          20 :     dst->len += res;
     229             : 
     230          20 :     if (*(dst->data + dst->len - 1) != '\n')
     231          12 :         appendStringInfoChar(dst, '\n');
     232             : 
     233          20 :     appendStringInfoChar(dst, '=');
     234          20 :     appendStringInfoChar(dst, _base64[(crc >> 18) & 0x3f]);
     235          20 :     appendStringInfoChar(dst, _base64[(crc >> 12) & 0x3f]);
     236          20 :     appendStringInfoChar(dst, _base64[(crc >> 6) & 0x3f]);
     237          20 :     appendStringInfoChar(dst, _base64[crc & 0x3f]);
     238             : 
     239          20 :     appendStringInfoString(dst, armor_footer);
     240          20 : }
     241             : 
     242             : static const uint8 *
     243         414 : find_str(const uint8 *data, const uint8 *data_end, const char *str, int strlen)
     244             : {
     245         414 :     const uint8 *p = data;
     246             : 
     247         414 :     if (!strlen)
     248           0 :         return NULL;
     249         414 :     if (data_end - data < strlen)
     250           0 :         return NULL;
     251         444 :     while (p < data_end)
     252             :     {
     253         444 :         p = memchr(p, str[0], data_end - p);
     254         444 :         if (p == NULL)
     255           0 :             return NULL;
     256         444 :         if (p + strlen > data_end)
     257           0 :             return NULL;
     258         444 :         if (memcmp(p, str, strlen) == 0)
     259         414 :             return p;
     260          30 :         p++;
     261             :     }
     262           0 :     return NULL;
     263             : }
     264             : 
     265             : static int
     266         412 : find_header(const uint8 *data, const uint8 *datend,
     267             :             const uint8 **start_p, int is_end)
     268             : {
     269         412 :     const uint8 *p = data;
     270             :     static const char *start_sep = "-----BEGIN";
     271             :     static const char *end_sep = "-----END";
     272         412 :     const char *sep = is_end ? end_sep : start_sep;
     273             : 
     274             :     /* find header line */
     275             :     while (1)
     276             :     {
     277         414 :         p = find_str(p, datend, sep, strlen(sep));
     278         414 :         if (p == NULL)
     279           0 :             return PXE_PGP_CORRUPT_ARMOR;
     280             :         /* it must start at beginning of line */
     281         414 :         if (p == data || *(p - 1) == '\n')
     282             :             break;
     283           2 :         p += strlen(sep);
     284             :     }
     285         412 :     *start_p = p;
     286         412 :     p += strlen(sep);
     287             : 
     288             :     /* check if header text ok */
     289        6824 :     for (; p < datend && *p != '-'; p++)
     290             :     {
     291             :         /* various junk can be there, but definitely not line-feed  */
     292        6412 :         if (*p >= ' ')
     293        6412 :             continue;
     294           0 :         return PXE_PGP_CORRUPT_ARMOR;
     295             :     }
     296         412 :     if (datend - p < 5 || memcmp(p, sep, 5) != 0)
     297           0 :         return PXE_PGP_CORRUPT_ARMOR;
     298         412 :     p += 5;
     299             : 
     300             :     /* check if at end of line */
     301         412 :     if (p < datend)
     302             :     {
     303         410 :         if (*p != '\n' && *p != '\r')
     304           0 :             return PXE_PGP_CORRUPT_ARMOR;
     305         410 :         if (*p == '\r')
     306           4 :             p++;
     307         410 :         if (p < datend && *p == '\n')
     308         410 :             p++;
     309             :     }
     310         412 :     return p - *start_p;
     311             : }
     312             : 
     313             : int
     314         178 : pgp_armor_decode(const uint8 *src, int len, StringInfo dst)
     315             : {
     316         178 :     const uint8 *p = src;
     317         178 :     const uint8 *data_end = src + len;
     318             :     long        crc;
     319             :     const uint8 *base64_start,
     320             :                *armor_end;
     321         178 :     const uint8 *base64_end = NULL;
     322             :     uint8       buf[4];
     323             :     int         hlen;
     324             :     int         blen;
     325         178 :     int         res = PXE_PGP_CORRUPT_ARMOR;
     326             : 
     327             :     /* armor start */
     328         178 :     hlen = find_header(src, data_end, &p, 0);
     329         178 :     if (hlen <= 0)
     330           0 :         goto out;
     331         178 :     p += hlen;
     332             : 
     333             :     /* armor end */
     334         178 :     hlen = find_header(p, data_end, &armor_end, 1);
     335         178 :     if (hlen <= 0)
     336           0 :         goto out;
     337             : 
     338             :     /* skip comments - find empty line */
     339         336 :     while (p < armor_end && *p != '\n' && *p != '\r')
     340             :     {
     341         158 :         p = memchr(p, '\n', armor_end - p);
     342         158 :         if (!p)
     343           0 :             goto out;
     344             : 
     345             :         /* step to start of next line */
     346         158 :         p++;
     347             :     }
     348         178 :     base64_start = p;
     349             : 
     350             :     /* find crc pos */
     351        1248 :     for (p = armor_end; p >= base64_start; p--)
     352        1248 :         if (*p == '=')
     353             :         {
     354         178 :             base64_end = p - 1;
     355         178 :             break;
     356             :         }
     357         178 :     if (base64_end == NULL)
     358           0 :         goto out;
     359             : 
     360             :     /* decode crc */
     361         178 :     if (pg_base64_decode(p + 1, 4, buf) != 3)
     362           0 :         goto out;
     363         178 :     crc = (((long) buf[0]) << 16) + (((long) buf[1]) << 8) + (long) buf[2];
     364             : 
     365             :     /* decode data */
     366         178 :     blen = (int) pg_base64_dec_len(len);
     367         178 :     enlargeStringInfo(dst, blen);
     368         178 :     res = pg_base64_decode(base64_start, base64_end - base64_start, (uint8 *) dst->data);
     369         178 :     if (res > blen)
     370           0 :         elog(FATAL, "overflow - decode estimate too small");
     371         178 :     if (res >= 0)
     372             :     {
     373         178 :         if (crc24((uint8 *) dst->data, res) == crc)
     374         176 :             dst->len += res;
     375             :         else
     376           2 :             res = PXE_PGP_CORRUPT_ARMOR;
     377             :     }
     378           0 : out:
     379         178 :     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
     390          28 : pgp_extract_armor_headers(const uint8 *src, unsigned len,
     391             :                           int *nheaders, char ***keys, char ***values)
     392             : {
     393          28 :     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          28 :     hlen = find_header(src, data_end, &armor_start, 0);
     410          28 :     if (hlen <= 0)
     411           0 :         return PXE_PGP_CORRUPT_ARMOR;
     412          28 :     armor_start += hlen;
     413             : 
     414             :     /* armor end */
     415          28 :     hlen = find_header(armor_start, data_end, &armor_end, 1);
     416          28 :     if (hlen <= 0)
     417           0 :         return PXE_PGP_CORRUPT_ARMOR;
     418             : 
     419             :     /* Count the number of armor header lines. */
     420          28 :     hdrlines = 0;
     421          28 :     p = armor_start;
     422          80 :     while (p < armor_end && *p != '\n' && *p != '\r')
     423             :     {
     424          52 :         p = memchr(p, '\n', armor_end - p);
     425          52 :         if (!p)
     426           0 :             return PXE_PGP_CORRUPT_ARMOR;
     427             : 
     428             :         /* step to start of next line */
     429          52 :         p++;
     430          52 :         hdrlines++;
     431             :     }
     432          28 :     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          28 :     armor_len = base64_start - armor_start;
     439          28 :     buf = palloc(armor_len + 1);
     440          28 :     memcpy(buf, armor_start, armor_len);
     441          28 :     buf[armor_len] = '\0';
     442             : 
     443             :     /* Allocate return arrays */
     444          28 :     *keys = (char **) palloc(hdrlines * sizeof(char *));
     445          28 :     *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          28 :     n = 0;
     452          28 :     line = buf;
     453             :     for (;;)
     454             :     {
     455             :         /* find end of line */
     456         120 :         eol = strchr(line, '\n');
     457          74 :         if (!eol)
     458          24 :             break;
     459          50 :         nextline = eol + 1;
     460             :         /* if the line ends in CR + LF, strip the CR */
     461          50 :         if (eol > line && *(eol - 1) == '\r')
     462           4 :             eol--;
     463          50 :         *eol = '\0';
     464             : 
     465             :         /* find colon+space separating the key and value */
     466          50 :         colon = strstr(line, ": ");
     467          50 :         if (!colon)
     468           4 :             return PXE_PGP_CORRUPT_ARMOR;
     469          46 :         *colon = '\0';
     470             : 
     471             :         /* shouldn't happen, we counted the number of lines beforehand */
     472          46 :         if (n >= hdrlines)
     473           0 :             elog(ERROR, "unexpected number of armor header lines");
     474             : 
     475          46 :         (*keys)[n] = line;
     476          46 :         (*values)[n] = colon + 2;
     477          46 :         n++;
     478             : 
     479             :         /* step to start of next line */
     480          46 :         line = nextline;
     481             :     }
     482             : 
     483          24 :     if (n != hdrlines)
     484           0 :         elog(ERROR, "unexpected number of armor header lines");
     485             : 
     486          24 :     *nheaders = n;
     487          24 :     return 0;
     488             : }

Generated by: LCOV version 1.14