Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 32984d8

Browse files
committedOct 1, 2014
Add functions for dealing with PGP armor header lines to pgcrypto.
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.
1 parent 0ef3c29 commit 32984d8

File tree

11 files changed

+804
-7
lines changed

11 files changed

+804
-7
lines changed
 

‎.gitattributes

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ README.* conflict-marker-size=32
1414
# Certain data files that contain special whitespace, and other special cases
1515
*.data -whitespace
1616
contrib/tsearch2/sql/tsearch2.sql whitespace=space-before-tab,blank-at-eof,-blank-at-eol
17+
contrib/pgcrypto/sql/pgp-armor.sql whitespace=-blank-at-eol
1718
doc/bug.template whitespace=space-before-tab,-blank-at-eof,blank-at-eol
1819
src/backend/catalog/sql_features.txt whitespace=space-before-tab,blank-at-eof,-blank-at-eol
1920
src/backend/tsearch/hunspell_sample.affix whitespace=-blank-at-eof

‎contrib/pgcrypto/Makefile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@ MODULE_big = pgcrypto
2626
OBJS = $(SRCS:.c=.o) $(WIN32RES)
2727

2828
EXTENSION = pgcrypto
29-
DATA = pgcrypto--1.1.sql pgcrypto--1.0--1.1.sql pgcrypto--unpackaged--1.0.sql
29+
DATA = pgcrypto--1.2.sql pgcrypto--1.1--1.2.sql pgcrypto--1.0--1.1.sql \
30+
pgcrypto--unpackaged--1.0.sql
3031
PGFILEDESC = "pgcrypto - cryptographic functions"
3132

3233
REGRESS = init md5 sha1 hmac-md5 hmac-sha1 blowfish rijndael \

‎contrib/pgcrypto/expected/pgp-armor.out

Lines changed: 268 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,3 +102,271 @@ em9va2E=
102102
-----END PGP MESSAGE-----
103103
');
104104
ERROR: Corrupt ascii-armor
105+
-- corrupt (no space after the colon)
106+
select * from pgp_armor_headers('
107+
-----BEGIN PGP MESSAGE-----
108+
foo:
109+
110+
em9va2E=
111+
=ZZZZ
112+
-----END PGP MESSAGE-----
113+
');
114+
ERROR: Corrupt ascii-armor
115+
-- corrupt (no empty line)
116+
select * from pgp_armor_headers('
117+
-----BEGIN PGP MESSAGE-----
118+
em9va2E=
119+
=ZZZZ
120+
-----END PGP MESSAGE-----
121+
');
122+
ERROR: Corrupt ascii-armor
123+
-- no headers
124+
select * from pgp_armor_headers('
125+
-----BEGIN PGP MESSAGE-----
126+
127+
em9va2E=
128+
=ZZZZ
129+
-----END PGP MESSAGE-----
130+
');
131+
key | value
132+
-----+-------
133+
(0 rows)
134+
135+
-- header with empty value
136+
select * from pgp_armor_headers('
137+
-----BEGIN PGP MESSAGE-----
138+
foo:
139+
140+
em9va2E=
141+
=ZZZZ
142+
-----END PGP MESSAGE-----
143+
');
144+
key | value
145+
-----+-------
146+
foo |
147+
(1 row)
148+
149+
-- simple
150+
select * from pgp_armor_headers('
151+
-----BEGIN PGP MESSAGE-----
152+
fookey: foovalue
153+
barkey: barvalue
154+
155+
em9va2E=
156+
=ZZZZ
157+
-----END PGP MESSAGE-----
158+
');
159+
key | value
160+
--------+----------
161+
fookey | foovalue
162+
barkey | barvalue
163+
(2 rows)
164+
165+
-- insane keys, part 1
166+
select * from pgp_armor_headers('
167+
-----BEGIN PGP MESSAGE-----
168+
insane:key :
169+
170+
em9va2E=
171+
=ZZZZ
172+
-----END PGP MESSAGE-----
173+
');
174+
key | value
175+
-------------+-------
176+
insane:key |
177+
(1 row)
178+
179+
-- insane keys, part 2
180+
select * from pgp_armor_headers('
181+
-----BEGIN PGP MESSAGE-----
182+
insane:key : text value here
183+
184+
em9va2E=
185+
=ZZZZ
186+
-----END PGP MESSAGE-----
187+
');
188+
key | value
189+
-------------+-----------------
190+
insane:key | text value here
191+
(1 row)
192+
193+
-- long value
194+
select * from pgp_armor_headers('
195+
-----BEGIN PGP MESSAGE-----
196+
long: this value is more than 76 characters long, but it should still parse correctly as that''s permitted by RFC 4880
197+
198+
em9va2E=
199+
=ZZZZ
200+
-----END PGP MESSAGE-----
201+
');
202+
key | value
203+
------+-----------------------------------------------------------------------------------------------------------------
204+
long | this value is more than 76 characters long, but it should still parse correctly as that's permitted by RFC 4880
205+
(1 row)
206+
207+
-- long value, split up
208+
select * from pgp_armor_headers('
209+
-----BEGIN PGP MESSAGE-----
210+
long: this value is more than 76 characters long, but it should still
211+
long: parse correctly as that''s permitted by RFC 4880
212+
213+
em9va2E=
214+
=ZZZZ
215+
-----END PGP MESSAGE-----
216+
');
217+
key | value
218+
------+------------------------------------------------------------------
219+
long | this value is more than 76 characters long, but it should still
220+
long | parse correctly as that's permitted by RFC 4880
221+
(2 rows)
222+
223+
-- long value, split up, part 2
224+
select * from pgp_armor_headers('
225+
-----BEGIN PGP MESSAGE-----
226+
long: this value is more than
227+
long: 76 characters long, but it should still
228+
long: parse correctly as that''s permitted by RFC 4880
229+
230+
em9va2E=
231+
=ZZZZ
232+
-----END PGP MESSAGE-----
233+
');
234+
key | value
235+
------+-------------------------------------------------
236+
long | this value is more than
237+
long | 76 characters long, but it should still
238+
long | parse correctly as that's permitted by RFC 4880
239+
(3 rows)
240+
241+
-- long value, split up, part 3
242+
select * from pgp_armor_headers('
243+
-----BEGIN PGP MESSAGE-----
244+
emptykey:
245+
long: this value is more than
246+
emptykey:
247+
long: 76 characters long, but it should still
248+
emptykey:
249+
long: parse correctly as that''s permitted by RFC 4880
250+
emptykey:
251+
252+
em9va2E=
253+
=ZZZZ
254+
-----END PGP MESSAGE-----
255+
');
256+
key | value
257+
----------+-------------------------------------------------
258+
emptykey |
259+
long | this value is more than
260+
emptykey |
261+
long | 76 characters long, but it should still
262+
emptykey |
263+
long | parse correctly as that's permitted by RFC 4880
264+
emptykey |
265+
(7 rows)
266+
267+
select * from pgp_armor_headers('
268+
-----BEGIN PGP MESSAGE-----
269+
Comment: dat1.blowfish.sha1.mdc.s2k3.z0
270+
271+
jA0EBAMCfFNwxnvodX9g0jwB4n4s26/g5VmKzVab1bX1SmwY7gvgvlWdF3jKisvS
272+
yA6Ce1QTMK3KdL2MPfamsTUSAML8huCJMwYQFfE=
273+
=JcP+
274+
-----END PGP MESSAGE-----
275+
');
276+
key | value
277+
---------+--------------------------------
278+
Comment | dat1.blowfish.sha1.mdc.s2k3.z0
279+
(1 row)
280+
281+
-- test CR+LF line endings
282+
select * from pgp_armor_headers(replace('
283+
-----BEGIN PGP MESSAGE-----
284+
fookey: foovalue
285+
barkey: barvalue
286+
287+
em9va2E=
288+
=ZZZZ
289+
-----END PGP MESSAGE-----
290+
', E'\n', E'\r\n'));
291+
key | value
292+
--------+----------
293+
fookey | foovalue
294+
barkey | barvalue
295+
(2 rows)
296+
297+
-- test header generation
298+
select armor('zooka', array['foo'], array['bar']);
299+
armor
300+
-----------------------------
301+
-----BEGIN PGP MESSAGE-----+
302+
foo: bar +
303+
+
304+
em9va2E= +
305+
=D5cR +
306+
-----END PGP MESSAGE----- +
307+
308+
(1 row)
309+
310+
select armor('zooka', array['Version', 'Comment'], array['Created by pgcrypto', 'PostgreSQL, the world''s most advanced open source database']);
311+
armor
312+
---------------------------------------------------------------------
313+
-----BEGIN PGP MESSAGE----- +
314+
Version: Created by pgcrypto +
315+
Comment: PostgreSQL, the world's most advanced open source database+
316+
+
317+
em9va2E= +
318+
=D5cR +
319+
-----END PGP MESSAGE----- +
320+
321+
(1 row)
322+
323+
select * from pgp_armor_headers(
324+
armor('zooka', array['Version', 'Comment'],
325+
array['Created by pgcrypto', 'PostgreSQL, the world''s most advanced open source database']));
326+
key | value
327+
---------+------------------------------------------------------------
328+
Version | Created by pgcrypto
329+
Comment | PostgreSQL, the world's most advanced open source database
330+
(2 rows)
331+
332+
-- error/corner cases
333+
select armor('', array['foo'], array['too', 'many']);
334+
ERROR: mismatched array dimensions
335+
select armor('', array['too', 'many'], array['foo']);
336+
ERROR: mismatched array dimensions
337+
select armor('', array[['']], array['foo']);
338+
ERROR: wrong number of array subscripts
339+
select armor('', array['foo'], array[['']]);
340+
ERROR: wrong number of array subscripts
341+
select armor('', array[null], array['foo']);
342+
ERROR: null value not allowed for header key
343+
select armor('', array['foo'], array[null]);
344+
ERROR: null value not allowed for header value
345+
select armor('', '[0:0]={"foo"}', array['foo']);
346+
armor
347+
-----------------------------
348+
-----BEGIN PGP MESSAGE-----+
349+
foo: foo +
350+
+
351+
=twTO +
352+
-----END PGP MESSAGE----- +
353+
354+
(1 row)
355+
356+
select armor('', array['foo'], '[0:0]={"foo"}');
357+
armor
358+
-----------------------------
359+
-----BEGIN PGP MESSAGE-----+
360+
foo: foo +
361+
+
362+
=twTO +
363+
-----END PGP MESSAGE----- +
364+
365+
(1 row)
366+
367+
select armor('', array[E'embedded\nnewline'], array['foo']);
368+
ERROR: header key must not contain newlines
369+
select armor('', array['foo'], array[E'embedded\nnewline']);
370+
ERROR: header value must not contain newlines
371+
select armor('', array['embedded: colon+space'], array['foo']);
372+
ERROR: header key must not contain ": "
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
/* contrib/pgcrypto/pgcrypto--1.1--1.2.sql */
2+
3+
-- complain if script is sourced in psql, rather than via ALTER EXTENSION
4+
\echo Use "ALTER EXTENSION pgcrypto UPDATE TO '1.2'" to load this file. \quit
5+
6+
CREATE FUNCTION armor(bytea, text[], text[])
7+
RETURNS text
8+
AS 'MODULE_PATHNAME', 'pg_armor'
9+
LANGUAGE C IMMUTABLE STRICT;
10+
11+
CREATE FUNCTION pgp_armor_headers(text, key OUT text, value OUT text)
12+
RETURNS SETOF record
13+
AS 'MODULE_PATHNAME', 'pgp_armor_headers'
14+
LANGUAGE C IMMUTABLE STRICT;

‎contrib/pgcrypto/pgcrypto--1.1.sql renamed to ‎contrib/pgcrypto/pgcrypto--1.2.sql

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,17 @@ RETURNS text
201201
AS 'MODULE_PATHNAME', 'pg_armor'
202202
LANGUAGE C IMMUTABLE STRICT;
203203

204+
CREATE FUNCTION armor(bytea, text[], text[])
205+
RETURNS text
206+
AS 'MODULE_PATHNAME', 'pg_armor'
207+
LANGUAGE C IMMUTABLE STRICT;
208+
204209
CREATE FUNCTION dearmor(text)
205210
RETURNS bytea
206211
AS 'MODULE_PATHNAME', 'pg_dearmor'
207212
LANGUAGE C IMMUTABLE STRICT;
213+
214+
CREATE FUNCTION pgp_armor_headers(text, key OUT text, value OUT text)
215+
RETURNS SETOF record
216+
AS 'MODULE_PATHNAME', 'pgp_armor_headers'
217+
LANGUAGE C IMMUTABLE STRICT;

‎contrib/pgcrypto/pgcrypto.control

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# pgcrypto extension
22
comment = 'cryptographic functions'
3-
default_version = '1.1'
3+
default_version = '1.2'
44
module_pathname = '$libdir/pgcrypto'
55
relocatable = true

‎contrib/pgcrypto/pgp-armor.c

Lines changed: 117 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,7 @@ b64_dec_len(unsigned srclen)
178178
* PGP armor
179179
*/
180180

181-
static const char *armor_header = "-----BEGIN PGP MESSAGE-----\n\n";
181+
static const char *armor_header = "-----BEGIN PGP MESSAGE-----\n";
182182
static const char *armor_footer = "\n-----END PGP MESSAGE-----\n";
183183

184184
/* CRC24 implementation from rfc2440 */
@@ -204,17 +204,24 @@ crc24(const uint8 *data, unsigned len)
204204
}
205205

206206
void
207-
pgp_armor_encode(const uint8 *src, int len, StringInfo dst)
207+
pgp_armor_encode(const uint8 *src, unsigned len, StringInfo dst,
208+
int num_headers, char **keys, char **values)
208209
{
210+
int n;
209211
int res;
210212
unsigned b64len;
211213
unsigned crc = crc24(src, len);
212214

213215
appendStringInfoString(dst, armor_header);
214216

217+
for (n = 0; n < num_headers; n++)
218+
appendStringInfo(dst, "%s: %s\n", keys[n], values[n]);
219+
appendStringInfoChar(dst, '\n');
220+
215221
/* make sure we have enough room to b64_encode() */
216222
b64len = b64_enc_len(len);
217223
enlargeStringInfo(dst, (int) b64len);
224+
218225
res = b64_encode(src, len, (uint8 *) dst->data + dst->len);
219226
if (res > b64len)
220227
elog(FATAL, "overflow - encode estimate too small");
@@ -371,3 +378,111 @@ pgp_armor_decode(const uint8 *src, int len, StringInfo dst)
371378
out:
372379
return res;
373380
}
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+
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+
}

‎contrib/pgcrypto/pgp-pgsql.c

Lines changed: 202 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,11 @@
3232
#include "postgres.h"
3333

3434
#include "lib/stringinfo.h"
35+
#include "catalog/pg_type.h"
3536
#include "mb/pg_wchar.h"
3637
#include "utils/builtins.h"
38+
#include "utils/array.h"
39+
#include "funcapi.h"
3740

3841
#include "mbuf.h"
3942
#include "px.h"
@@ -56,6 +59,7 @@ PG_FUNCTION_INFO_V1(pgp_key_id_w);
5659

5760
PG_FUNCTION_INFO_V1(pg_armor);
5861
PG_FUNCTION_INFO_V1(pg_dearmor);
62+
PG_FUNCTION_INFO_V1(pgp_armor_headers);
5963

6064
/*
6165
* Mix a block of data into RNG.
@@ -148,6 +152,19 @@ convert_to_utf8(text *src)
148152
return convert_charset(src, GetDatabaseEncoding(), PG_UTF8);
149153
}
150154

155+
static bool
156+
string_is_ascii(const char *str)
157+
{
158+
const char *p;
159+
160+
for (p = str; *p; p++)
161+
{
162+
if (IS_HIGHBIT_SET(*p))
163+
return false;
164+
}
165+
return true;
166+
}
167+
151168
static void
152169
clear_and_pfree(text *p)
153170
{
@@ -816,20 +833,128 @@ pgp_pub_decrypt_text(PG_FUNCTION_ARGS)
816833
* Wrappers for PGP ascii armor
817834
*/
818835

836+
/*
837+
* Helper function for pgp_armor. Converts arrays of keys and values into
838+
* plain C arrays, and checks that they don't contain invalid characters.
839+
*/
840+
static int
841+
parse_key_value_arrays(ArrayType *key_array, ArrayType *val_array,
842+
char ***p_keys, char ***p_values)
843+
{
844+
int nkdims = ARR_NDIM(key_array);
845+
int nvdims = ARR_NDIM(val_array);
846+
char **keys,
847+
**values;
848+
Datum *key_datums,
849+
*val_datums;
850+
bool *key_nulls,
851+
*val_nulls;
852+
int key_count,
853+
val_count;
854+
int i;
855+
856+
if (nkdims > 1 || nkdims != nvdims)
857+
ereport(ERROR,
858+
(errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
859+
errmsg("wrong number of array subscripts")));
860+
if (nkdims == 0)
861+
return 0;
862+
863+
deconstruct_array(key_array,
864+
TEXTOID, -1, false, 'i',
865+
&key_datums, &key_nulls, &key_count);
866+
867+
deconstruct_array(val_array,
868+
TEXTOID, -1, false, 'i',
869+
&val_datums, &val_nulls, &val_count);
870+
871+
if (key_count != val_count)
872+
ereport(ERROR,
873+
(errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
874+
errmsg("mismatched array dimensions")));
875+
876+
keys = (char **) palloc(sizeof(char *) * key_count);
877+
values = (char **) palloc(sizeof(char *) * val_count);
878+
879+
for (i = 0; i < key_count; i++)
880+
{
881+
char *v;
882+
883+
/* Check that the key doesn't contain anything funny */
884+
if (key_nulls[i])
885+
ereport(ERROR,
886+
(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
887+
errmsg("null value not allowed for header key")));
888+
889+
v = TextDatumGetCString(key_datums[i]);
890+
891+
if (!string_is_ascii(v))
892+
ereport(ERROR,
893+
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
894+
errmsg("header key must not contain non-ASCII characters")));
895+
if (strstr(v, ": "))
896+
ereport(ERROR,
897+
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
898+
errmsg("header key must not contain \": \"")));
899+
if (strchr(v, '\n'))
900+
ereport(ERROR,
901+
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
902+
errmsg("header key must not contain newlines")));
903+
keys[i] = v;
904+
905+
/* And the same for the value */
906+
if (val_nulls[i])
907+
ereport(ERROR,
908+
(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
909+
errmsg("null value not allowed for header value")));
910+
911+
v = TextDatumGetCString(val_datums[i]);
912+
913+
if (!string_is_ascii(v))
914+
ereport(ERROR,
915+
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
916+
errmsg("header value must not contain non-ASCII characters")));
917+
if (strchr(v, '\n'))
918+
ereport(ERROR,
919+
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
920+
errmsg("header value must not contain newlines")));
921+
922+
values[i] = v;
923+
}
924+
925+
*p_keys = keys;
926+
*p_values = values;
927+
return key_count;
928+
}
929+
819930
Datum
820931
pg_armor(PG_FUNCTION_ARGS)
821932
{
822933
bytea *data;
823934
text *res;
824935
int data_len;
825936
StringInfoData buf;
937+
int num_headers;
938+
char **keys = NULL,
939+
**values = NULL;
826940

827941
data = PG_GETARG_BYTEA_P(0);
828942
data_len = VARSIZE(data) - VARHDRSZ;
943+
if (PG_NARGS() == 3)
944+
{
945+
num_headers = parse_key_value_arrays(PG_GETARG_ARRAYTYPE_P(1),
946+
PG_GETARG_ARRAYTYPE_P(2),
947+
&keys, &values);
948+
}
949+
else if (PG_NARGS() == 1)
950+
num_headers = 0;
951+
else
952+
elog(ERROR, "unexpected number of arguments %d", PG_NARGS());
829953

830954
initStringInfo(&buf);
831955

832-
pgp_armor_encode((uint8 *) VARDATA(data), data_len, &buf);
956+
pgp_armor_encode((uint8 *) VARDATA(data), data_len, &buf,
957+
num_headers, keys, values);
833958

834959
res = palloc(VARHDRSZ + buf.len);
835960
SET_VARSIZE(res, VARHDRSZ + buf.len);
@@ -868,6 +993,82 @@ pg_dearmor(PG_FUNCTION_ARGS)
868993
PG_RETURN_TEXT_P(res);
869994
}
870995

996+
/* cross-call state for pgp_armor_headers */
997+
typedef struct
998+
{
999+
int nheaders;
1000+
char **keys;
1001+
char **values;
1002+
} pgp_armor_headers_state;
1003+
1004+
Datum
1005+
pgp_armor_headers(PG_FUNCTION_ARGS)
1006+
{
1007+
FuncCallContext *funcctx;
1008+
pgp_armor_headers_state *state;
1009+
char *utf8key;
1010+
char *utf8val;
1011+
HeapTuple tuple;
1012+
TupleDesc tupdesc;
1013+
AttInMetadata *attinmeta;
1014+
1015+
if (SRF_IS_FIRSTCALL())
1016+
{
1017+
text *data = PG_GETARG_TEXT_PP(0);
1018+
int res;
1019+
MemoryContext oldcontext;
1020+
1021+
funcctx = SRF_FIRSTCALL_INIT();
1022+
1023+
/* we need the state allocated in the multi call context */
1024+
oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
1025+
1026+
/* Build a tuple descriptor for our result type */
1027+
if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
1028+
elog(ERROR, "return type must be a row type");
1029+
1030+
attinmeta = TupleDescGetAttInMetadata(tupdesc);
1031+
funcctx->attinmeta = attinmeta;
1032+
1033+
state = (pgp_armor_headers_state *) palloc(sizeof(pgp_armor_headers_state));
1034+
1035+
res = pgp_extract_armor_headers((uint8 *) VARDATA_ANY(data),
1036+
VARSIZE_ANY_EXHDR(data),
1037+
&state->nheaders, &state->keys,
1038+
&state->values);
1039+
if (res < 0)
1040+
ereport(ERROR,
1041+
(errcode(ERRCODE_EXTERNAL_ROUTINE_INVOCATION_EXCEPTION),
1042+
errmsg("%s", px_strerror(res))));
1043+
1044+
MemoryContextSwitchTo(oldcontext);
1045+
funcctx->user_fctx = state;
1046+
}
1047+
1048+
funcctx = SRF_PERCALL_SETUP();
1049+
state = (pgp_armor_headers_state *) funcctx->user_fctx;
1050+
1051+
if (funcctx->call_cntr >= state->nheaders)
1052+
SRF_RETURN_DONE(funcctx);
1053+
else
1054+
{
1055+
char *values[2];
1056+
1057+
/* we assume that the keys (and values) are in UTF-8. */
1058+
utf8key = state->keys[funcctx->call_cntr];
1059+
utf8val = state->values[funcctx->call_cntr];
1060+
1061+
values[0] = pg_any_to_server(utf8key, strlen(utf8key), PG_UTF8);
1062+
values[1] = pg_any_to_server(utf8val, strlen(utf8val), PG_UTF8);
1063+
1064+
/* build a tuple */
1065+
tuple = BuildTupleFromCStrings(funcctx->attinmeta, values);
1066+
SRF_RETURN_NEXT(funcctx, HeapTupleGetDatum(tuple));
1067+
}
1068+
}
1069+
1070+
1071+
8711072
/*
8721073
* Wrappers for PGP key id
8731074
*/

‎contrib/pgcrypto/pgp.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -276,8 +276,11 @@ void pgp_cfb_free(PGP_CFB *ctx);
276276
int pgp_cfb_encrypt(PGP_CFB *ctx, const uint8 *data, int len, uint8 *dst);
277277
int pgp_cfb_decrypt(PGP_CFB *ctx, const uint8 *data, int len, uint8 *dst);
278278

279-
void pgp_armor_encode(const uint8 *src, int len, StringInfo dst);
279+
void pgp_armor_encode(const uint8 *src, unsigned len, StringInfo dst,
280+
int num_headers, char **keys, char **values);
280281
int pgp_armor_decode(const uint8 *src, int len, StringInfo dst);
282+
int pgp_extract_armor_headers(const uint8 *src, unsigned len,
283+
int *nheaders, char ***keys, char ***values);
281284

282285
int pgp_compress_filter(PushFilter **res, PGP_Context *ctx, PushFilter *dst);
283286
int pgp_decompress_filter(PullFilter **res, PGP_Context *ctx, PullFilter *src);

‎contrib/pgcrypto/sql/pgp-armor.sql

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,3 +56,161 @@ em9va2E=
5656
=ZZZZ
5757
-----END PGP MESSAGE-----
5858
');
59+
60+
-- corrupt (no space after the colon)
61+
select * from pgp_armor_headers('
62+
-----BEGIN PGP MESSAGE-----
63+
foo:
64+
65+
em9va2E=
66+
=ZZZZ
67+
-----END PGP MESSAGE-----
68+
');
69+
70+
-- corrupt (no empty line)
71+
select * from pgp_armor_headers('
72+
-----BEGIN PGP MESSAGE-----
73+
em9va2E=
74+
=ZZZZ
75+
-----END PGP MESSAGE-----
76+
');
77+
78+
-- no headers
79+
select * from pgp_armor_headers('
80+
-----BEGIN PGP MESSAGE-----
81+
82+
em9va2E=
83+
=ZZZZ
84+
-----END PGP MESSAGE-----
85+
');
86+
87+
-- header with empty value
88+
select * from pgp_armor_headers('
89+
-----BEGIN PGP MESSAGE-----
90+
foo:
91+
92+
em9va2E=
93+
=ZZZZ
94+
-----END PGP MESSAGE-----
95+
');
96+
97+
-- simple
98+
select * from pgp_armor_headers('
99+
-----BEGIN PGP MESSAGE-----
100+
fookey: foovalue
101+
barkey: barvalue
102+
103+
em9va2E=
104+
=ZZZZ
105+
-----END PGP MESSAGE-----
106+
');
107+
108+
-- insane keys, part 1
109+
select * from pgp_armor_headers('
110+
-----BEGIN PGP MESSAGE-----
111+
insane:key :
112+
113+
em9va2E=
114+
=ZZZZ
115+
-----END PGP MESSAGE-----
116+
');
117+
118+
-- insane keys, part 2
119+
select * from pgp_armor_headers('
120+
-----BEGIN PGP MESSAGE-----
121+
insane:key : text value here
122+
123+
em9va2E=
124+
=ZZZZ
125+
-----END PGP MESSAGE-----
126+
');
127+
128+
-- long value
129+
select * from pgp_armor_headers('
130+
-----BEGIN PGP MESSAGE-----
131+
long: this value is more than 76 characters long, but it should still parse correctly as that''s permitted by RFC 4880
132+
133+
em9va2E=
134+
=ZZZZ
135+
-----END PGP MESSAGE-----
136+
');
137+
138+
-- long value, split up
139+
select * from pgp_armor_headers('
140+
-----BEGIN PGP MESSAGE-----
141+
long: this value is more than 76 characters long, but it should still
142+
long: parse correctly as that''s permitted by RFC 4880
143+
144+
em9va2E=
145+
=ZZZZ
146+
-----END PGP MESSAGE-----
147+
');
148+
149+
-- long value, split up, part 2
150+
select * from pgp_armor_headers('
151+
-----BEGIN PGP MESSAGE-----
152+
long: this value is more than
153+
long: 76 characters long, but it should still
154+
long: parse correctly as that''s permitted by RFC 4880
155+
156+
em9va2E=
157+
=ZZZZ
158+
-----END PGP MESSAGE-----
159+
');
160+
161+
-- long value, split up, part 3
162+
select * from pgp_armor_headers('
163+
-----BEGIN PGP MESSAGE-----
164+
emptykey:
165+
long: this value is more than
166+
emptykey:
167+
long: 76 characters long, but it should still
168+
emptykey:
169+
long: parse correctly as that''s permitted by RFC 4880
170+
emptykey:
171+
172+
em9va2E=
173+
=ZZZZ
174+
-----END PGP MESSAGE-----
175+
');
176+
177+
select * from pgp_armor_headers('
178+
-----BEGIN PGP MESSAGE-----
179+
Comment: dat1.blowfish.sha1.mdc.s2k3.z0
180+
181+
jA0EBAMCfFNwxnvodX9g0jwB4n4s26/g5VmKzVab1bX1SmwY7gvgvlWdF3jKisvS
182+
yA6Ce1QTMK3KdL2MPfamsTUSAML8huCJMwYQFfE=
183+
=JcP+
184+
-----END PGP MESSAGE-----
185+
');
186+
187+
-- test CR+LF line endings
188+
select * from pgp_armor_headers(replace('
189+
-----BEGIN PGP MESSAGE-----
190+
fookey: foovalue
191+
barkey: barvalue
192+
193+
em9va2E=
194+
=ZZZZ
195+
-----END PGP MESSAGE-----
196+
', E'\n', E'\r\n'));
197+
198+
-- test header generation
199+
select armor('zooka', array['foo'], array['bar']);
200+
select armor('zooka', array['Version', 'Comment'], array['Created by pgcrypto', 'PostgreSQL, the world''s most advanced open source database']);
201+
select * from pgp_armor_headers(
202+
armor('zooka', array['Version', 'Comment'],
203+
array['Created by pgcrypto', 'PostgreSQL, the world''s most advanced open source database']));
204+
205+
-- error/corner cases
206+
select armor('', array['foo'], array['too', 'many']);
207+
select armor('', array['too', 'many'], array['foo']);
208+
select armor('', array[['']], array['foo']);
209+
select armor('', array['foo'], array[['']]);
210+
select armor('', array[null], array['foo']);
211+
select armor('', array['foo'], array[null]);
212+
select armor('', '[0:0]={"foo"}', array['foo']);
213+
select armor('', array['foo'], '[0:0]={"foo"}');
214+
select armor('', array[E'embedded\nnewline'], array['foo']);
215+
select armor('', array['foo'], array[E'embedded\nnewline']);
216+
select armor('', array['embedded: colon+space'], array['foo']);

‎doc/src/sgml/pgcrypto.sgml

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -691,13 +691,39 @@ pgp_key_id(bytea) returns text
691691
</indexterm>
692692

693693
<synopsis>
694-
armor(data bytea) returns text
694+
armor(data bytea [ , keys text[], values text[] ]) returns text
695695
dearmor(data text) returns bytea
696696
</synopsis>
697697
<para>
698698
These functions wrap/unwrap binary data into PGP ASCII-armor format,
699699
which is basically Base64 with CRC and additional formatting.
700700
</para>
701+
702+
<para>
703+
If the <parameter>keys</> and <parameter>values</> arrays are specified,
704+
an <firstterm>armor header</> is added to the armored format for each
705+
key/value pair. Both arrays must be single-dimensional, and they must
706+
be of the same length. The keys and values cannot contain any non-ASCII
707+
characters.
708+
</para>
709+
</sect3>
710+
711+
<sect3>
712+
<title><function>pgp_armor_headers</function></title>
713+
714+
<indexterm>
715+
<primary>pgp_armor_headers</primary>
716+
</indexterm>
717+
718+
<synopsis>
719+
pgp_armor_headers(data text, key out text, value out text) returns setof record
720+
</synopsis>
721+
<para>
722+
<function>pgp_armor_headers()</> extracts the armor headers from
723+
<parameter>data</>. The return value is a set of rows with two columns,
724+
key and value. If the keys or values contain any non-ASCII characters,
725+
they are treated as UTF-8.
726+
</para>
701727
</sect3>
702728

703729
<sect3>

0 commit comments

Comments
 (0)
Please sign in to comment.