mirror of
https://git.postgresql.org/git/postgresql.git
synced 2025-03-19 20:00:51 +08:00
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:
parent
0ef3c29a4b
commit
32984d8fc3
1
.gitattributes
vendored
1
.gitattributes
vendored
@ -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
|
||||
|
@ -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 \
|
||||
|
@ -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 ": "
|
||||
|
14
contrib/pgcrypto/pgcrypto--1.1--1.2.sql
Normal file
14
contrib/pgcrypto/pgcrypto--1.1--1.2.sql
Normal 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;
|
@ -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;
|
@ -1,5 +1,5 @@
|
||||
# pgcrypto extension
|
||||
comment = 'cryptographic functions'
|
||||
default_version = '1.1'
|
||||
default_version = '1.2'
|
||||
module_pathname = '$libdir/pgcrypto'
|
||||
relocatable = true
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
*/
|
||||
|
@ -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);
|
||||
|
@ -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']);
|
||||
|
@ -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>
|
||||
|
Loading…
x
Reference in New Issue
Block a user