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.
This commit is contained in:
Heikki Linnakangas 2014-10-01 15:56:26 +03:00
parent 0ef3c29a4b
commit 32984d8fc3
11 changed files with 804 additions and 7 deletions

1
.gitattributes vendored
View File

@ -14,6 +14,7 @@ README.* conflict-marker-size=32
# Certain data files that contain special whitespace, and other special cases
*.data -whitespace
contrib/tsearch2/sql/tsearch2.sql whitespace=space-before-tab,blank-at-eof,-blank-at-eol
contrib/pgcrypto/sql/pgp-armor.sql whitespace=-blank-at-eol
doc/bug.template whitespace=space-before-tab,-blank-at-eof,blank-at-eol
src/backend/catalog/sql_features.txt whitespace=space-before-tab,blank-at-eof,-blank-at-eol
src/backend/tsearch/hunspell_sample.affix whitespace=-blank-at-eof

View File

@ -26,7 +26,8 @@ MODULE_big = pgcrypto
OBJS = $(SRCS:.c=.o) $(WIN32RES)
EXTENSION = pgcrypto
DATA = pgcrypto--1.1.sql pgcrypto--1.0--1.1.sql pgcrypto--unpackaged--1.0.sql
DATA = pgcrypto--1.2.sql pgcrypto--1.1--1.2.sql pgcrypto--1.0--1.1.sql \
pgcrypto--unpackaged--1.0.sql
PGFILEDESC = "pgcrypto - cryptographic functions"
REGRESS = init md5 sha1 hmac-md5 hmac-sha1 blowfish rijndael \

View File

@ -102,3 +102,271 @@ em9va2E=
-----END PGP MESSAGE-----
');
ERROR: Corrupt ascii-armor
-- corrupt (no space after the colon)
select * from pgp_armor_headers('
-----BEGIN PGP MESSAGE-----
foo:
em9va2E=
=ZZZZ
-----END PGP MESSAGE-----
');
ERROR: Corrupt ascii-armor
-- corrupt (no empty line)
select * from pgp_armor_headers('
-----BEGIN PGP MESSAGE-----
em9va2E=
=ZZZZ
-----END PGP MESSAGE-----
');
ERROR: Corrupt ascii-armor
-- no headers
select * from pgp_armor_headers('
-----BEGIN PGP MESSAGE-----
em9va2E=
=ZZZZ
-----END PGP MESSAGE-----
');
key | value
-----+-------
(0 rows)
-- header with empty value
select * from pgp_armor_headers('
-----BEGIN PGP MESSAGE-----
foo:
em9va2E=
=ZZZZ
-----END PGP MESSAGE-----
');
key | value
-----+-------
foo |
(1 row)
-- simple
select * from pgp_armor_headers('
-----BEGIN PGP MESSAGE-----
fookey: foovalue
barkey: barvalue
em9va2E=
=ZZZZ
-----END PGP MESSAGE-----
');
key | value
--------+----------
fookey | foovalue
barkey | barvalue
(2 rows)
-- insane keys, part 1
select * from pgp_armor_headers('
-----BEGIN PGP MESSAGE-----
insane:key :
em9va2E=
=ZZZZ
-----END PGP MESSAGE-----
');
key | value
-------------+-------
insane:key |
(1 row)
-- insane keys, part 2
select * from pgp_armor_headers('
-----BEGIN PGP MESSAGE-----
insane:key : text value here
em9va2E=
=ZZZZ
-----END PGP MESSAGE-----
');
key | value
-------------+-----------------
insane:key | text value here
(1 row)
-- long value
select * from pgp_armor_headers('
-----BEGIN PGP MESSAGE-----
long: this value is more than 76 characters long, but it should still parse correctly as that''s permitted by RFC 4880
em9va2E=
=ZZZZ
-----END PGP MESSAGE-----
');
key | value
------+-----------------------------------------------------------------------------------------------------------------
long | this value is more than 76 characters long, but it should still parse correctly as that's permitted by RFC 4880
(1 row)
-- long value, split up
select * from pgp_armor_headers('
-----BEGIN PGP MESSAGE-----
long: this value is more than 76 characters long, but it should still
long: parse correctly as that''s permitted by RFC 4880
em9va2E=
=ZZZZ
-----END PGP MESSAGE-----
');
key | value
------+------------------------------------------------------------------
long | this value is more than 76 characters long, but it should still
long | parse correctly as that's permitted by RFC 4880
(2 rows)
-- long value, split up, part 2
select * from pgp_armor_headers('
-----BEGIN PGP MESSAGE-----
long: this value is more than
long: 76 characters long, but it should still
long: parse correctly as that''s permitted by RFC 4880
em9va2E=
=ZZZZ
-----END PGP MESSAGE-----
');
key | value
------+-------------------------------------------------
long | this value is more than
long | 76 characters long, but it should still
long | parse correctly as that's permitted by RFC 4880
(3 rows)
-- long value, split up, part 3
select * from pgp_armor_headers('
-----BEGIN PGP MESSAGE-----
emptykey:
long: this value is more than
emptykey:
long: 76 characters long, but it should still
emptykey:
long: parse correctly as that''s permitted by RFC 4880
emptykey:
em9va2E=
=ZZZZ
-----END PGP MESSAGE-----
');
key | value
----------+-------------------------------------------------
emptykey |
long | this value is more than
emptykey |
long | 76 characters long, but it should still
emptykey |
long | parse correctly as that's permitted by RFC 4880
emptykey |
(7 rows)
select * from pgp_armor_headers('
-----BEGIN PGP MESSAGE-----
Comment: dat1.blowfish.sha1.mdc.s2k3.z0
jA0EBAMCfFNwxnvodX9g0jwB4n4s26/g5VmKzVab1bX1SmwY7gvgvlWdF3jKisvS
yA6Ce1QTMK3KdL2MPfamsTUSAML8huCJMwYQFfE=
=JcP+
-----END PGP MESSAGE-----
');
key | value
---------+--------------------------------
Comment | dat1.blowfish.sha1.mdc.s2k3.z0
(1 row)
-- test CR+LF line endings
select * from pgp_armor_headers(replace('
-----BEGIN PGP MESSAGE-----
fookey: foovalue
barkey: barvalue
em9va2E=
=ZZZZ
-----END PGP MESSAGE-----
', E'\n', E'\r\n'));
key | value
--------+----------
fookey | foovalue
barkey | barvalue
(2 rows)
-- test header generation
select armor('zooka', array['foo'], array['bar']);
armor
-----------------------------
-----BEGIN PGP MESSAGE-----+
foo: bar +
+
em9va2E= +
=D5cR +
-----END PGP MESSAGE----- +
(1 row)
select armor('zooka', array['Version', 'Comment'], array['Created by pgcrypto', 'PostgreSQL, the world''s most advanced open source database']);
armor
---------------------------------------------------------------------
-----BEGIN PGP MESSAGE----- +
Version: Created by pgcrypto +
Comment: PostgreSQL, the world's most advanced open source database+
+
em9va2E= +
=D5cR +
-----END PGP MESSAGE----- +
(1 row)
select * from pgp_armor_headers(
armor('zooka', array['Version', 'Comment'],
array['Created by pgcrypto', 'PostgreSQL, the world''s most advanced open source database']));
key | value
---------+------------------------------------------------------------
Version | Created by pgcrypto
Comment | PostgreSQL, the world's most advanced open source database
(2 rows)
-- error/corner cases
select armor('', array['foo'], array['too', 'many']);
ERROR: mismatched array dimensions
select armor('', array['too', 'many'], array['foo']);
ERROR: mismatched array dimensions
select armor('', array[['']], array['foo']);
ERROR: wrong number of array subscripts
select armor('', array['foo'], array[['']]);
ERROR: wrong number of array subscripts
select armor('', array[null], array['foo']);
ERROR: null value not allowed for header key
select armor('', array['foo'], array[null]);
ERROR: null value not allowed for header value
select armor('', '[0:0]={"foo"}', array['foo']);
armor
-----------------------------
-----BEGIN PGP MESSAGE-----+
foo: foo +
+
=twTO +
-----END PGP MESSAGE----- +
(1 row)
select armor('', array['foo'], '[0:0]={"foo"}');
armor
-----------------------------
-----BEGIN PGP MESSAGE-----+
foo: foo +
+
=twTO +
-----END PGP MESSAGE----- +
(1 row)
select armor('', array[E'embedded\nnewline'], array['foo']);
ERROR: header key must not contain newlines
select armor('', array['foo'], array[E'embedded\nnewline']);
ERROR: header value must not contain newlines
select armor('', array['embedded: colon+space'], array['foo']);
ERROR: header key must not contain ": "

View File

@ -0,0 +1,14 @@
/* contrib/pgcrypto/pgcrypto--1.1--1.2.sql */
-- complain if script is sourced in psql, rather than via ALTER EXTENSION
\echo Use "ALTER EXTENSION pgcrypto UPDATE TO '1.2'" to load this file. \quit
CREATE FUNCTION armor(bytea, text[], text[])
RETURNS text
AS 'MODULE_PATHNAME', 'pg_armor'
LANGUAGE C IMMUTABLE STRICT;
CREATE FUNCTION pgp_armor_headers(text, key OUT text, value OUT text)
RETURNS SETOF record
AS 'MODULE_PATHNAME', 'pgp_armor_headers'
LANGUAGE C IMMUTABLE STRICT;

View File

@ -201,7 +201,17 @@ RETURNS text
AS 'MODULE_PATHNAME', 'pg_armor'
LANGUAGE C IMMUTABLE STRICT;
CREATE FUNCTION armor(bytea, text[], text[])
RETURNS text
AS 'MODULE_PATHNAME', 'pg_armor'
LANGUAGE C IMMUTABLE STRICT;
CREATE FUNCTION dearmor(text)
RETURNS bytea
AS 'MODULE_PATHNAME', 'pg_dearmor'
LANGUAGE C IMMUTABLE STRICT;
CREATE FUNCTION pgp_armor_headers(text, key OUT text, value OUT text)
RETURNS SETOF record
AS 'MODULE_PATHNAME', 'pgp_armor_headers'
LANGUAGE C IMMUTABLE STRICT;

View File

@ -1,5 +1,5 @@
# pgcrypto extension
comment = 'cryptographic functions'
default_version = '1.1'
default_version = '1.2'
module_pathname = '$libdir/pgcrypto'
relocatable = true

View File

@ -178,7 +178,7 @@ b64_dec_len(unsigned srclen)
* PGP armor
*/
static const char *armor_header = "-----BEGIN PGP MESSAGE-----\n\n";
static const char *armor_header = "-----BEGIN PGP MESSAGE-----\n";
static const char *armor_footer = "\n-----END PGP MESSAGE-----\n";
/* CRC24 implementation from rfc2440 */
@ -204,17 +204,24 @@ crc24(const uint8 *data, unsigned len)
}
void
pgp_armor_encode(const uint8 *src, int len, StringInfo dst)
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");
@ -371,3 +378,111 @@ pgp_armor_decode(const uint8 *src, int len, StringInfo dst)
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;
}

View File

@ -32,8 +32,11 @@
#include "postgres.h"
#include "lib/stringinfo.h"
#include "catalog/pg_type.h"
#include "mb/pg_wchar.h"
#include "utils/builtins.h"
#include "utils/array.h"
#include "funcapi.h"
#include "mbuf.h"
#include "px.h"
@ -56,6 +59,7 @@ PG_FUNCTION_INFO_V1(pgp_key_id_w);
PG_FUNCTION_INFO_V1(pg_armor);
PG_FUNCTION_INFO_V1(pg_dearmor);
PG_FUNCTION_INFO_V1(pgp_armor_headers);
/*
* Mix a block of data into RNG.
@ -148,6 +152,19 @@ convert_to_utf8(text *src)
return convert_charset(src, GetDatabaseEncoding(), PG_UTF8);
}
static bool
string_is_ascii(const char *str)
{
const char *p;
for (p = str; *p; p++)
{
if (IS_HIGHBIT_SET(*p))
return false;
}
return true;
}
static void
clear_and_pfree(text *p)
{
@ -816,6 +833,100 @@ pgp_pub_decrypt_text(PG_FUNCTION_ARGS)
* Wrappers for PGP ascii armor
*/
/*
* Helper function for pgp_armor. Converts arrays of keys and values into
* plain C arrays, and checks that they don't contain invalid characters.
*/
static int
parse_key_value_arrays(ArrayType *key_array, ArrayType *val_array,
char ***p_keys, char ***p_values)
{
int nkdims = ARR_NDIM(key_array);
int nvdims = ARR_NDIM(val_array);
char **keys,
**values;
Datum *key_datums,
*val_datums;
bool *key_nulls,
*val_nulls;
int key_count,
val_count;
int i;
if (nkdims > 1 || nkdims != nvdims)
ereport(ERROR,
(errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
errmsg("wrong number of array subscripts")));
if (nkdims == 0)
return 0;
deconstruct_array(key_array,
TEXTOID, -1, false, 'i',
&key_datums, &key_nulls, &key_count);
deconstruct_array(val_array,
TEXTOID, -1, false, 'i',
&val_datums, &val_nulls, &val_count);
if (key_count != val_count)
ereport(ERROR,
(errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
errmsg("mismatched array dimensions")));
keys = (char **) palloc(sizeof(char *) * key_count);
values = (char **) palloc(sizeof(char *) * val_count);
for (i = 0; i < key_count; i++)
{
char *v;
/* Check that the key doesn't contain anything funny */
if (key_nulls[i])
ereport(ERROR,
(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
errmsg("null value not allowed for header key")));
v = TextDatumGetCString(key_datums[i]);
if (!string_is_ascii(v))
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("header key must not contain non-ASCII characters")));
if (strstr(v, ": "))
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("header key must not contain \": \"")));
if (strchr(v, '\n'))
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("header key must not contain newlines")));
keys[i] = v;
/* And the same for the value */
if (val_nulls[i])
ereport(ERROR,
(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
errmsg("null value not allowed for header value")));
v = TextDatumGetCString(val_datums[i]);
if (!string_is_ascii(v))
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("header value must not contain non-ASCII characters")));
if (strchr(v, '\n'))
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("header value must not contain newlines")));
values[i] = v;
}
*p_keys = keys;
*p_values = values;
return key_count;
}
Datum
pg_armor(PG_FUNCTION_ARGS)
{
@ -823,13 +934,27 @@ pg_armor(PG_FUNCTION_ARGS)
text *res;
int data_len;
StringInfoData buf;
int num_headers;
char **keys = NULL,
**values = NULL;
data = PG_GETARG_BYTEA_P(0);
data_len = VARSIZE(data) - VARHDRSZ;
if (PG_NARGS() == 3)
{
num_headers = parse_key_value_arrays(PG_GETARG_ARRAYTYPE_P(1),
PG_GETARG_ARRAYTYPE_P(2),
&keys, &values);
}
else if (PG_NARGS() == 1)
num_headers = 0;
else
elog(ERROR, "unexpected number of arguments %d", PG_NARGS());
initStringInfo(&buf);
pgp_armor_encode((uint8 *) VARDATA(data), data_len, &buf);
pgp_armor_encode((uint8 *) VARDATA(data), data_len, &buf,
num_headers, keys, values);
res = palloc(VARHDRSZ + buf.len);
SET_VARSIZE(res, VARHDRSZ + buf.len);
@ -868,6 +993,82 @@ pg_dearmor(PG_FUNCTION_ARGS)
PG_RETURN_TEXT_P(res);
}
/* cross-call state for pgp_armor_headers */
typedef struct
{
int nheaders;
char **keys;
char **values;
} pgp_armor_headers_state;
Datum
pgp_armor_headers(PG_FUNCTION_ARGS)
{
FuncCallContext *funcctx;
pgp_armor_headers_state *state;
char *utf8key;
char *utf8val;
HeapTuple tuple;
TupleDesc tupdesc;
AttInMetadata *attinmeta;
if (SRF_IS_FIRSTCALL())
{
text *data = PG_GETARG_TEXT_PP(0);
int res;
MemoryContext oldcontext;
funcctx = SRF_FIRSTCALL_INIT();
/* we need the state allocated in the multi call context */
oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
/* Build a tuple descriptor for our result type */
if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
elog(ERROR, "return type must be a row type");
attinmeta = TupleDescGetAttInMetadata(tupdesc);
funcctx->attinmeta = attinmeta;
state = (pgp_armor_headers_state *) palloc(sizeof(pgp_armor_headers_state));
res = pgp_extract_armor_headers((uint8 *) VARDATA_ANY(data),
VARSIZE_ANY_EXHDR(data),
&state->nheaders, &state->keys,
&state->values);
if (res < 0)
ereport(ERROR,
(errcode(ERRCODE_EXTERNAL_ROUTINE_INVOCATION_EXCEPTION),
errmsg("%s", px_strerror(res))));
MemoryContextSwitchTo(oldcontext);
funcctx->user_fctx = state;
}
funcctx = SRF_PERCALL_SETUP();
state = (pgp_armor_headers_state *) funcctx->user_fctx;
if (funcctx->call_cntr >= state->nheaders)
SRF_RETURN_DONE(funcctx);
else
{
char *values[2];
/* we assume that the keys (and values) are in UTF-8. */
utf8key = state->keys[funcctx->call_cntr];
utf8val = state->values[funcctx->call_cntr];
values[0] = pg_any_to_server(utf8key, strlen(utf8key), PG_UTF8);
values[1] = pg_any_to_server(utf8val, strlen(utf8val), PG_UTF8);
/* build a tuple */
tuple = BuildTupleFromCStrings(funcctx->attinmeta, values);
SRF_RETURN_NEXT(funcctx, HeapTupleGetDatum(tuple));
}
}
/*
* Wrappers for PGP key id
*/

View File

@ -276,8 +276,11 @@ void pgp_cfb_free(PGP_CFB *ctx);
int pgp_cfb_encrypt(PGP_CFB *ctx, const uint8 *data, int len, uint8 *dst);
int pgp_cfb_decrypt(PGP_CFB *ctx, const uint8 *data, int len, uint8 *dst);
void pgp_armor_encode(const uint8 *src, int len, StringInfo dst);
void pgp_armor_encode(const uint8 *src, unsigned len, StringInfo dst,
int num_headers, char **keys, char **values);
int pgp_armor_decode(const uint8 *src, int len, StringInfo dst);
int pgp_extract_armor_headers(const uint8 *src, unsigned len,
int *nheaders, char ***keys, char ***values);
int pgp_compress_filter(PushFilter **res, PGP_Context *ctx, PushFilter *dst);
int pgp_decompress_filter(PullFilter **res, PGP_Context *ctx, PullFilter *src);

View File

@ -56,3 +56,161 @@ em9va2E=
=ZZZZ
-----END PGP MESSAGE-----
');
-- corrupt (no space after the colon)
select * from pgp_armor_headers('
-----BEGIN PGP MESSAGE-----
foo:
em9va2E=
=ZZZZ
-----END PGP MESSAGE-----
');
-- corrupt (no empty line)
select * from pgp_armor_headers('
-----BEGIN PGP MESSAGE-----
em9va2E=
=ZZZZ
-----END PGP MESSAGE-----
');
-- no headers
select * from pgp_armor_headers('
-----BEGIN PGP MESSAGE-----
em9va2E=
=ZZZZ
-----END PGP MESSAGE-----
');
-- header with empty value
select * from pgp_armor_headers('
-----BEGIN PGP MESSAGE-----
foo:
em9va2E=
=ZZZZ
-----END PGP MESSAGE-----
');
-- simple
select * from pgp_armor_headers('
-----BEGIN PGP MESSAGE-----
fookey: foovalue
barkey: barvalue
em9va2E=
=ZZZZ
-----END PGP MESSAGE-----
');
-- insane keys, part 1
select * from pgp_armor_headers('
-----BEGIN PGP MESSAGE-----
insane:key :
em9va2E=
=ZZZZ
-----END PGP MESSAGE-----
');
-- insane keys, part 2
select * from pgp_armor_headers('
-----BEGIN PGP MESSAGE-----
insane:key : text value here
em9va2E=
=ZZZZ
-----END PGP MESSAGE-----
');
-- long value
select * from pgp_armor_headers('
-----BEGIN PGP MESSAGE-----
long: this value is more than 76 characters long, but it should still parse correctly as that''s permitted by RFC 4880
em9va2E=
=ZZZZ
-----END PGP MESSAGE-----
');
-- long value, split up
select * from pgp_armor_headers('
-----BEGIN PGP MESSAGE-----
long: this value is more than 76 characters long, but it should still
long: parse correctly as that''s permitted by RFC 4880
em9va2E=
=ZZZZ
-----END PGP MESSAGE-----
');
-- long value, split up, part 2
select * from pgp_armor_headers('
-----BEGIN PGP MESSAGE-----
long: this value is more than
long: 76 characters long, but it should still
long: parse correctly as that''s permitted by RFC 4880
em9va2E=
=ZZZZ
-----END PGP MESSAGE-----
');
-- long value, split up, part 3
select * from pgp_armor_headers('
-----BEGIN PGP MESSAGE-----
emptykey:
long: this value is more than
emptykey:
long: 76 characters long, but it should still
emptykey:
long: parse correctly as that''s permitted by RFC 4880
emptykey:
em9va2E=
=ZZZZ
-----END PGP MESSAGE-----
');
select * from pgp_armor_headers('
-----BEGIN PGP MESSAGE-----
Comment: dat1.blowfish.sha1.mdc.s2k3.z0
jA0EBAMCfFNwxnvodX9g0jwB4n4s26/g5VmKzVab1bX1SmwY7gvgvlWdF3jKisvS
yA6Ce1QTMK3KdL2MPfamsTUSAML8huCJMwYQFfE=
=JcP+
-----END PGP MESSAGE-----
');
-- test CR+LF line endings
select * from pgp_armor_headers(replace('
-----BEGIN PGP MESSAGE-----
fookey: foovalue
barkey: barvalue
em9va2E=
=ZZZZ
-----END PGP MESSAGE-----
', E'\n', E'\r\n'));
-- test header generation
select armor('zooka', array['foo'], array['bar']);
select armor('zooka', array['Version', 'Comment'], array['Created by pgcrypto', 'PostgreSQL, the world''s most advanced open source database']);
select * from pgp_armor_headers(
armor('zooka', array['Version', 'Comment'],
array['Created by pgcrypto', 'PostgreSQL, the world''s most advanced open source database']));
-- error/corner cases
select armor('', array['foo'], array['too', 'many']);
select armor('', array['too', 'many'], array['foo']);
select armor('', array[['']], array['foo']);
select armor('', array['foo'], array[['']]);
select armor('', array[null], array['foo']);
select armor('', array['foo'], array[null]);
select armor('', '[0:0]={"foo"}', array['foo']);
select armor('', array['foo'], '[0:0]={"foo"}');
select armor('', array[E'embedded\nnewline'], array['foo']);
select armor('', array['foo'], array[E'embedded\nnewline']);
select armor('', array['embedded: colon+space'], array['foo']);

View File

@ -691,13 +691,39 @@ pgp_key_id(bytea) returns text
</indexterm>
<synopsis>
armor(data bytea) returns text
armor(data bytea [ , keys text[], values text[] ]) returns text
dearmor(data text) returns bytea
</synopsis>
<para>
These functions wrap/unwrap binary data into PGP ASCII-armor format,
which is basically Base64 with CRC and additional formatting.
</para>
<para>
If the <parameter>keys</> and <parameter>values</> arrays are specified,
an <firstterm>armor header</> is added to the armored format for each
key/value pair. Both arrays must be single-dimensional, and they must
be of the same length. The keys and values cannot contain any non-ASCII
characters.
</para>
</sect3>
<sect3>
<title><function>pgp_armor_headers</function></title>
<indexterm>
<primary>pgp_armor_headers</primary>
</indexterm>
<synopsis>
pgp_armor_headers(data text, key out text, value out text) returns setof record
</synopsis>
<para>
<function>pgp_armor_headers()</> extracts the armor headers from
<parameter>data</>. The return value is a set of rows with two columns,
key and value. If the keys or values contain any non-ASCII characters,
they are treated as UTF-8.
</para>
</sect3>
<sect3>