*** a/contrib/pgcrypto/Makefile --- b/contrib/pgcrypto/Makefile *************** *** 26,32 **** 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 PGFILEDESC = "pgcrypto - cryptographic functions" REGRESS = init md5 sha1 hmac-md5 hmac-sha1 blowfish rijndael \ --- 26,32 ---- OBJS = $(SRCS:.c=.o) $(WIN32RES) EXTENSION = pgcrypto ! 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 \ *** a/contrib/pgcrypto/expected/pgp-armor.out --- b/contrib/pgcrypto/expected/pgp-armor.out *************** *** 102,104 **** em9va2E= --- 102,423 ---- -----END PGP MESSAGE----- '); ERROR: Corrupt ascii-armor + -- corrupt + select pgp_armor_header(' + -----BEGIN PGP MESSAGE----- + foo: + + em9va2E= + =ZZZZ + -----END PGP MESSAGE----- + ', 'foo'); + ERROR: Corrupt ascii-armor + -- empty + select pgp_armor_header(' + -----BEGIN PGP MESSAGE----- + foo: + + em9va2E= + =ZZZZ + -----END PGP MESSAGE----- + ', 'foo'); + pgp_armor_header + ------------------ + + (1 row) + + -- simple + select pgp_armor_header(' + -----BEGIN PGP MESSAGE----- + foo: bar + + em9va2E= + =ZZZZ + -----END PGP MESSAGE----- + ', 'foo'); + pgp_armor_header + ------------------ + bar + (1 row) + + -- uninteresting keys, part 1 + select pgp_armor_header(' + -----BEGIN PGP MESSAGE----- + foo: found + bar: ignored + + em9va2E= + =ZZZZ + -----END PGP MESSAGE----- + ', 'foo'); + pgp_armor_header + ------------------ + found + (1 row) + + -- uninteresting keys, part 2 + select pgp_armor_header(' + -----BEGIN PGP MESSAGE----- + bar: ignored + foo: found + + em9va2E= + =ZZZZ + -----END PGP MESSAGE----- + ', 'foo'); + pgp_armor_header + ------------------ + found + (1 row) + + -- uninteresting keys, part 3 + select pgp_armor_header(' + -----BEGIN PGP MESSAGE----- + bar: ignored + foo: found + bar: ignored + + em9va2E= + =ZZZZ + -----END PGP MESSAGE----- + ', 'foo'); + pgp_armor_header + ------------------ + found + (1 row) + + -- insane keys, part 1 + select pgp_armor_header(' + -----BEGIN PGP MESSAGE----- + insane:key : + + em9va2E= + =ZZZZ + -----END PGP MESSAGE----- + ', 'insane:key '); + pgp_armor_header + ------------------ + + (1 row) + + -- insane keys, part 2 + select pgp_armor_header(' + -----BEGIN PGP MESSAGE----- + insane:key : text value here + + em9va2E= + =ZZZZ + -----END PGP MESSAGE----- + ', 'insane:key '); + pgp_armor_header + ------------------ + text value here + (1 row) + + -- long value + select pgp_armor_header(' + -----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'); + pgp_armor_header + ----------------------------------------------------------------------------------------------------------------- + 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 pgp_armor_header(' + -----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'); + pgp_armor_header + ----------------------------------------------------------------------------------------------------------------- + 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, part 2 + select pgp_armor_header(' + -----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'); + pgp_armor_header + ----------------------------------------------------------------------------------------------------------------- + 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, part 3 + select pgp_armor_header(' + -----BEGIN PGP MESSAGE----- + ignored: + long: this value is more than + ignored: + long: 76 characters long, but it should still + ignored: + long: parse correctly as that''s permitted by RFC 4880 + ignored: + + em9va2E= + =ZZZZ + -----END PGP MESSAGE----- + ', 'long'); + pgp_armor_header + ----------------------------------------------------------------------------------------------------------------- + this value is more than 76 characters long, but it should still parse correctly as that's permitted by RFC 4880 + (1 row) + + select pgp_armor_header(' + -----BEGIN PGP MESSAGE----- + Comment: dat1.blowfish.sha1.mdc.s2k3.z0 + + jA0EBAMCfFNwxnvodX9g0jwB4n4s26/g5VmKzVab1bX1SmwY7gvgvlWdF3jKisvS + yA6Ce1QTMK3KdL2MPfamsTUSAML8huCJMwYQFfE= + =JcP+ + -----END PGP MESSAGE----- + ', 'Comment'); + pgp_armor_header + -------------------------------- + dat1.blowfish.sha1.mdc.s2k3.z0 + (1 row) + + -- corrupt + select pgp_armor_header_keys(' + -----BEGIN PGP MESSAGE----- + foo: + + em9va2E= + =ZZZZ + -----END PGP MESSAGE----- + '); + ERROR: Corrupt ascii-armor + -- empty + select pgp_armor_header_keys(' + -----BEGIN PGP MESSAGE----- + foo: + + em9va2E= + =ZZZZ + -----END PGP MESSAGE----- + '); + pgp_armor_header_keys + ----------------------- + foo + (1 row) + + -- simple + select pgp_armor_header_keys(' + -----BEGIN PGP MESSAGE----- + foo: bar + + em9va2E= + =ZZZZ + -----END PGP MESSAGE----- + '); + pgp_armor_header_keys + ----------------------- + foo + (1 row) + + -- duplicates should be eliminated + select pgp_armor_header_keys(' + -----BEGIN PGP MESSAGE----- + nodups: + long: this value is more than + nodups: + long: 76 characters long, but it should still + nodups: + long: parse correctly as that''s permitted by RFC 4880 + nodups: + reallynodups: + + em9va2E= + =ZZZZ + -----END PGP MESSAGE----- + '); + pgp_armor_header_keys + ----------------------- + nodups + long + reallynodups + (3 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 most advanced open source database']); + armor + -------------------------------------------------------------------------- + -----BEGIN PGP MESSAGE----- + + Version: Created by pgcrypto + + Comment: PostgreSQL, the world's most most advanced open source database+ + + + em9va2E= + + =D5cR + + -----END PGP MESSAGE----- + + + (1 row) + + select pgp_armor_header(armor('zooka', array['Version', 'Comment'], array['Created by pgcrypto', 'PostgreSQL, the world''s most most advanced open source database']), 'Comment'); + pgp_armor_header + ----------------------------------------------------------------- + PostgreSQL, the world's most most advanced open source database + (1 row) + + -- 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) + *** /dev/null --- b/contrib/pgcrypto/pgcrypto--1.1--1.2.sql *************** *** 0 **** --- 1,19 ---- + /* 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_header(text, text) + RETURNS text + AS 'MODULE_PATHNAME', 'pgp_armor_header' + LANGUAGE C IMMUTABLE STRICT; + + CREATE FUNCTION pgp_armor_header_keys(text) + RETURNS text + AS 'MODULE_PATHNAME', 'pgp_armor_header_keys' + LANGUAGE C IMMUTABLE STRICT; *** /dev/null --- b/contrib/pgcrypto/pgcrypto--1.2.sql *************** *** 0 **** --- 1,222 ---- + /* contrib/pgcrypto/pgcrypto--1.1.sql */ + + -- complain if script is sourced in psql, rather than via CREATE EXTENSION + \echo Use "CREATE EXTENSION pgcrypto" to load this file. \quit + + CREATE FUNCTION digest(text, text) + RETURNS bytea + AS 'MODULE_PATHNAME', 'pg_digest' + LANGUAGE C IMMUTABLE STRICT; + + CREATE FUNCTION digest(bytea, text) + RETURNS bytea + AS 'MODULE_PATHNAME', 'pg_digest' + LANGUAGE C IMMUTABLE STRICT; + + CREATE FUNCTION hmac(text, text, text) + RETURNS bytea + AS 'MODULE_PATHNAME', 'pg_hmac' + LANGUAGE C IMMUTABLE STRICT; + + CREATE FUNCTION hmac(bytea, bytea, text) + RETURNS bytea + AS 'MODULE_PATHNAME', 'pg_hmac' + LANGUAGE C IMMUTABLE STRICT; + + CREATE FUNCTION crypt(text, text) + RETURNS text + AS 'MODULE_PATHNAME', 'pg_crypt' + LANGUAGE C IMMUTABLE STRICT; + + CREATE FUNCTION gen_salt(text) + RETURNS text + AS 'MODULE_PATHNAME', 'pg_gen_salt' + LANGUAGE C VOLATILE STRICT; + + CREATE FUNCTION gen_salt(text, int4) + RETURNS text + AS 'MODULE_PATHNAME', 'pg_gen_salt_rounds' + LANGUAGE C VOLATILE STRICT; + + CREATE FUNCTION encrypt(bytea, bytea, text) + RETURNS bytea + AS 'MODULE_PATHNAME', 'pg_encrypt' + LANGUAGE C IMMUTABLE STRICT; + + CREATE FUNCTION decrypt(bytea, bytea, text) + RETURNS bytea + AS 'MODULE_PATHNAME', 'pg_decrypt' + LANGUAGE C IMMUTABLE STRICT; + + CREATE FUNCTION encrypt_iv(bytea, bytea, bytea, text) + RETURNS bytea + AS 'MODULE_PATHNAME', 'pg_encrypt_iv' + LANGUAGE C IMMUTABLE STRICT; + + CREATE FUNCTION decrypt_iv(bytea, bytea, bytea, text) + RETURNS bytea + AS 'MODULE_PATHNAME', 'pg_decrypt_iv' + LANGUAGE C IMMUTABLE STRICT; + + CREATE FUNCTION gen_random_bytes(int4) + RETURNS bytea + AS 'MODULE_PATHNAME', 'pg_random_bytes' + LANGUAGE C VOLATILE STRICT; + + CREATE FUNCTION gen_random_uuid() + RETURNS uuid + AS 'MODULE_PATHNAME', 'pg_random_uuid' + LANGUAGE C VOLATILE; + + -- + -- pgp_sym_encrypt(data, key) + -- + CREATE FUNCTION pgp_sym_encrypt(text, text) + RETURNS bytea + AS 'MODULE_PATHNAME', 'pgp_sym_encrypt_text' + LANGUAGE C STRICT; + + CREATE FUNCTION pgp_sym_encrypt_bytea(bytea, text) + RETURNS bytea + AS 'MODULE_PATHNAME', 'pgp_sym_encrypt_bytea' + LANGUAGE C STRICT; + + -- + -- pgp_sym_encrypt(data, key, args) + -- + CREATE FUNCTION pgp_sym_encrypt(text, text, text) + RETURNS bytea + AS 'MODULE_PATHNAME', 'pgp_sym_encrypt_text' + LANGUAGE C STRICT; + + CREATE FUNCTION pgp_sym_encrypt_bytea(bytea, text, text) + RETURNS bytea + AS 'MODULE_PATHNAME', 'pgp_sym_encrypt_bytea' + LANGUAGE C STRICT; + + -- + -- pgp_sym_decrypt(data, key) + -- + CREATE FUNCTION pgp_sym_decrypt(bytea, text) + RETURNS text + AS 'MODULE_PATHNAME', 'pgp_sym_decrypt_text' + LANGUAGE C IMMUTABLE STRICT; + + CREATE FUNCTION pgp_sym_decrypt_bytea(bytea, text) + RETURNS bytea + AS 'MODULE_PATHNAME', 'pgp_sym_decrypt_bytea' + LANGUAGE C IMMUTABLE STRICT; + + -- + -- pgp_sym_decrypt(data, key, args) + -- + CREATE FUNCTION pgp_sym_decrypt(bytea, text, text) + RETURNS text + AS 'MODULE_PATHNAME', 'pgp_sym_decrypt_text' + LANGUAGE C IMMUTABLE STRICT; + + CREATE FUNCTION pgp_sym_decrypt_bytea(bytea, text, text) + RETURNS bytea + AS 'MODULE_PATHNAME', 'pgp_sym_decrypt_bytea' + LANGUAGE C IMMUTABLE STRICT; + + -- + -- pgp_pub_encrypt(data, key) + -- + CREATE FUNCTION pgp_pub_encrypt(text, bytea) + RETURNS bytea + AS 'MODULE_PATHNAME', 'pgp_pub_encrypt_text' + LANGUAGE C STRICT; + + CREATE FUNCTION pgp_pub_encrypt_bytea(bytea, bytea) + RETURNS bytea + AS 'MODULE_PATHNAME', 'pgp_pub_encrypt_bytea' + LANGUAGE C STRICT; + + -- + -- pgp_pub_encrypt(data, key, args) + -- + CREATE FUNCTION pgp_pub_encrypt(text, bytea, text) + RETURNS bytea + AS 'MODULE_PATHNAME', 'pgp_pub_encrypt_text' + LANGUAGE C STRICT; + + CREATE FUNCTION pgp_pub_encrypt_bytea(bytea, bytea, text) + RETURNS bytea + AS 'MODULE_PATHNAME', 'pgp_pub_encrypt_bytea' + LANGUAGE C STRICT; + + -- + -- pgp_pub_decrypt(data, key) + -- + CREATE FUNCTION pgp_pub_decrypt(bytea, bytea) + RETURNS text + AS 'MODULE_PATHNAME', 'pgp_pub_decrypt_text' + LANGUAGE C IMMUTABLE STRICT; + + CREATE FUNCTION pgp_pub_decrypt_bytea(bytea, bytea) + RETURNS bytea + AS 'MODULE_PATHNAME', 'pgp_pub_decrypt_bytea' + LANGUAGE C IMMUTABLE STRICT; + + -- + -- pgp_pub_decrypt(data, key, psw) + -- + CREATE FUNCTION pgp_pub_decrypt(bytea, bytea, text) + RETURNS text + AS 'MODULE_PATHNAME', 'pgp_pub_decrypt_text' + LANGUAGE C IMMUTABLE STRICT; + + CREATE FUNCTION pgp_pub_decrypt_bytea(bytea, bytea, text) + RETURNS bytea + AS 'MODULE_PATHNAME', 'pgp_pub_decrypt_bytea' + LANGUAGE C IMMUTABLE STRICT; + + -- + -- pgp_pub_decrypt(data, key, psw, arg) + -- + CREATE FUNCTION pgp_pub_decrypt(bytea, bytea, text, text) + RETURNS text + AS 'MODULE_PATHNAME', 'pgp_pub_decrypt_text' + LANGUAGE C IMMUTABLE STRICT; + + CREATE FUNCTION pgp_pub_decrypt_bytea(bytea, bytea, text, text) + RETURNS bytea + AS 'MODULE_PATHNAME', 'pgp_pub_decrypt_bytea' + LANGUAGE C IMMUTABLE STRICT; + + -- + -- PGP key ID + -- + CREATE FUNCTION pgp_key_id(bytea) + RETURNS text + AS 'MODULE_PATHNAME', 'pgp_key_id_w' + LANGUAGE C IMMUTABLE STRICT; + + -- + -- pgp armor + -- + CREATE FUNCTION armor(bytea) + 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_header(text, text) + RETURNS text + AS 'MODULE_PATHNAME', 'pgp_armor_header' + LANGUAGE C IMMUTABLE STRICT; + + CREATE FUNCTION pgp_armor_header_keys(text) + RETURNS SETOF text + AS 'MODULE_PATHNAME', 'pgp_armor_header_keys' + LANGUAGE C IMMUTABLE STRICT; *** a/contrib/pgcrypto/pgcrypto.control --- b/contrib/pgcrypto/pgcrypto.control *************** *** 1,5 **** # pgcrypto extension comment = 'cryptographic functions' ! default_version = '1.1' module_pathname = '$libdir/pgcrypto' relocatable = true --- 1,5 ---- # pgcrypto extension comment = 'cryptographic functions' ! default_version = '1.2' module_pathname = '$libdir/pgcrypto' relocatable = true *** a/contrib/pgcrypto/pgp-armor.c --- b/contrib/pgcrypto/pgp-armor.c *************** *** 178,184 **** b64_dec_len(unsigned srclen) * PGP armor */ ! static const char *armor_header = "-----BEGIN PGP MESSAGE-----\n\n"; static const char *armor_footer = "\n-----END PGP MESSAGE-----\n"; /* CRC24 implementation from rfc2440 */ --- 178,184 ---- * PGP armor */ ! static const char *armor_header = "-----BEGIN PGP MESSAGE-----\n"; static const char *armor_footer = "\n-----END PGP MESSAGE-----\n"; /* CRC24 implementation from rfc2440 */ *************** *** 204,217 **** crc24(const uint8 *data, unsigned len) } void ! pgp_armor_encode(const uint8 *src, int len, StringInfo dst) { int res; unsigned b64len; unsigned crc = crc24(src, len); appendStringInfoString(dst, armor_header); /* make sure we have enough room to b64_encode() */ b64len = b64_enc_len(len); enlargeStringInfo(dst, (int) b64len); --- 204,229 ---- } void ! pgp_armor_encode(const uint8 *src, unsigned len, StringInfo dst, ! int num_headers, char **keys, char **values) { + int n; int res; unsigned b64len; unsigned crc = crc24(src, len); appendStringInfoString(dst, armor_header); + for (n = 0; n < num_headers; n++) + { + appendStringInfoString(dst, keys[n]); + appendStringInfoChar(dst, ':'); + appendStringInfoChar(dst, ' '); + appendStringInfoString(dst, values[n]); + appendStringInfoChar(dst, '\n'); + } + appendStringInfoChar(dst, '\n'); + /* make sure we have enough room to b64_encode() */ b64len = b64_enc_len(len); enlargeStringInfo(dst, (int) b64len); *************** *** 371,373 **** pgp_armor_decode(const uint8 *src, int len, StringInfo dst) --- 383,516 ---- out: return res; } + + /* + * pgp_extract_armor_headers can be used in two different ways: + * + * 1) If key and valuedst are not NULL, this function finds the value for the + * key specified by *key, and copies it to valuedst. key_len should be + * set to the length of the string in *key, which need not be null + * terminated. The return value is 1 if the key was found, 0 if it was + * not found, or negative if an error occurred. + * 2) If keylistdst is not NULL, *keylistdst is set to a list containing the + * set of armor header keys present in the armor. The return value is 0, + * or negative if an error occurred. + */ + int + pgp_extract_armor_headers(const uint8 *src, unsigned len, const char *key, + unsigned key_len, StringInfo valuedst, + List **keylistdst) + { + const uint8 *p = src; + const uint8 *data_end = src + len; + const uint8 *armor_end; + const uint8 *eol, + *colon; + int hlen; + int res = PXE_PGP_CORRUPT_ARMOR; + bool found = false; + List *keys = NIL; + + Assert((valuedst != NULL) != (keylistdst != NULL)); + Assert((key == NULL) == (valuedst == NULL)); + + /* armor start */ + hlen = find_header(src, data_end, &p, 0); + if (hlen <= 0) + goto out; + p += hlen; + + /* armor end */ + hlen = find_header(p, data_end, &armor_end, 1); + if (hlen <= 0) + goto out; + + /* read comments until an empty line or the end of data */ + while (p < armor_end) + { + res = PXE_PGP_CORRUPT_ARMOR; + + if (*p == '\n' || *p == '\r') + { + res = 0; + break; + } + + eol = memchr(p, '\n', armor_end - p); + if (!eol) + goto out; + + /* find the next key */ + colon = p; + while (1) + { + colon = memchr(colon, ':', eol - colon); + if (!colon) + goto out; + if (colon == eol) + goto out; + + /* if it's not followed by a space, this isn't the full key */ + if (*(colon + 1) == ' ') + break; + colon = colon + 1; + } + + /* + * See if this the key we're looking for. Note that even if it is, we + * need to keep scanning the headers since it might be split into + * multiple lines. + */ + if (valuedst != NULL && + key_len == colon - p && + memcmp(p, key, key_len) == 0) + { + appendBinaryStringInfo(valuedst, (const char *) colon + 2, (int) (eol - colon - 2)); + found = true; + } + else if (keylistdst != NULL) + { + char *key; + size_t len = colon - p; + ListCell *lc; + + key = palloc(len + 1); + memcpy(key, p, len); + key[len] = '\0'; + + /* check for duplicates */ + found = false; + foreach(lc, keys) + { + if (strcmp(key, lfirst(lc)) == 0) + { + found = true; + break; + } + } + if (!found) + keys = lappend(keys, key); + else + pfree(key); + } + /* step to start of next line */ + p = eol + 1; + } + + out: + if (res < 0) + { + if (keys != NIL) + list_free_deep(keys); + return res; + } + if (keylistdst != NULL) + { + *keylistdst = keys; + return 0; + } + else if (valuedst != NULL) + return found ? 1 : 0; + else + elog(ERROR, "unexpected input arguments to pgp_extract_armor_headers"); + } *** a/contrib/pgcrypto/pgp-pgsql.c --- b/contrib/pgcrypto/pgp-pgsql.c *************** *** 32,39 **** --- 32,42 ---- #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,61 **** PG_FUNCTION_INFO_V1(pgp_key_id_w); --- 59,66 ---- PG_FUNCTION_INFO_V1(pg_armor); PG_FUNCTION_INFO_V1(pg_dearmor); + PG_FUNCTION_INFO_V1(pgp_armor_header); + PG_FUNCTION_INFO_V1(pgp_armor_header_keys); /* * Mix a block of data into RNG. *************** *** 816,821 **** pgp_pub_decrypt_text(PG_FUNCTION_ARGS) --- 821,888 ---- * Wrappers for PGP ascii armor */ + 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; + + if (key_nulls[i]) + ereport(ERROR, + (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), + errmsg("null value not allowed for header key"))); + if (val_nulls[i]) + ereport(ERROR, + (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), + errmsg("null value not allowed for header value"))); + v = TextDatumGetCString(key_datums[i]); + keys[i] = pg_server_to_any(v, strlen(v), PG_UTF8); + v = TextDatumGetCString(val_datums[i]); + values[i] = pg_server_to_any(v, strlen(v), PG_UTF8); + } + + *p_keys = keys; + *p_values = values; + return key_count; + } + Datum pg_armor(PG_FUNCTION_ARGS) { *************** *** 823,835 **** pg_armor(PG_FUNCTION_ARGS) text *res; int data_len; StringInfoData buf; data = PG_GETARG_BYTEA_P(0); data_len = VARSIZE(data) - VARHDRSZ; initStringInfo(&buf); ! pgp_armor_encode((uint8 *) VARDATA(data), data_len, &buf); res = palloc(VARHDRSZ + buf.len); SET_VARSIZE(res, VARHDRSZ + buf.len); --- 890,914 ---- text *res; int data_len; StringInfoData buf; + int num_headers = 0; + 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) + elog(ERROR, "unexpected number of arguments %d", PG_NARGS()); initStringInfo(&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,873 **** pg_dearmor(PG_FUNCTION_ARGS) --- 947,1052 ---- PG_RETURN_TEXT_P(res); } + Datum + pgp_armor_header(PG_FUNCTION_ARGS) + { + bytea *data; + int data_len, + res; + text *key, + *utf8key; + StringInfoData buf; + + data = PG_GETARG_BYTEA_P(0); + data_len = VARSIZE(data) - VARHDRSZ; + + key = PG_GETARG_TEXT_P(1); + utf8key = convert_to_utf8(key); + + initStringInfo(&buf); + res = pgp_extract_armor_headers((uint8 *) VARDATA(data), data_len, + VARDATA(utf8key), VARSIZE(utf8key) - VARHDRSZ, + &buf, NULL); + if (res < 0) + ereport(ERROR, + (errcode(ERRCODE_EXTERNAL_ROUTINE_INVOCATION_EXCEPTION), + errmsg("%s", px_strerror(res)))); + + PG_FREE_IF_COPY(data, 0); + if (utf8key != key) + pfree(utf8key); + PG_FREE_IF_COPY(key, 1); + if (res == 0) + { + pfree(buf.data); + PG_RETURN_NULL(); + } + else + { + /* assume it's UTF-8 */ + char *utf; + text *result; + + /* 0-terminate the string for cstring_to_text */ + appendStringInfoChar(&buf, '\x00'); + utf = pg_any_to_server(buf.data, buf.len - 1, PG_UTF8); + result = cstring_to_text(utf); + pfree(buf.data); + PG_RETURN_TEXT_P(result); + } + } + + Datum + pgp_armor_header_keys(PG_FUNCTION_ARGS) + { + FuncCallContext *funcctx; + List *keys; + + if (SRF_IS_FIRSTCALL()) + { + bytea *data; + int data_len, + res; + MemoryContext oldcontext; + + data = PG_GETARG_BYTEA_P(0); + data_len = VARSIZE(data) - VARHDRSZ; + + funcctx = SRF_FIRSTCALL_INIT(); + + /* we need the resulting list allocated in the multi call context */ + oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); + + res = pgp_extract_armor_headers((uint8 *) VARDATA(data), data_len, + NULL, 0, NULL, &keys); + if (res < 0) + ereport(ERROR, + (errcode(ERRCODE_EXTERNAL_ROUTINE_INVOCATION_EXCEPTION), + errmsg("%s", px_strerror(res)))); + + MemoryContextSwitchTo(oldcontext); + funcctx->user_fctx = keys; + } + + funcctx = SRF_PERCALL_SETUP(); + keys = (List *) funcctx->user_fctx; + if (keys == NIL) + SRF_RETURN_DONE(funcctx); + else + { + /* we assume the keys are UTF-8 */ + char *utf; + text *result; + + utf = pg_any_to_server(linitial(keys), strlen(linitial(keys)), PG_UTF8); + result = cstring_to_text(utf); + funcctx->user_fctx = list_delete_first(keys); + SRF_RETURN_NEXT(funcctx, PointerGetDatum(result)); + } + } + + + /* * Wrappers for PGP key id */ *** a/contrib/pgcrypto/pgp.h --- b/contrib/pgcrypto/pgp.h *************** *** 30,35 **** --- 30,36 ---- */ #include "lib/stringinfo.h" + #include "nodes/pg_list.h" #include "mbuf.h" #include "px.h" *************** *** 276,283 **** 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); int pgp_armor_decode(const uint8 *src, int len, StringInfo dst); int pgp_compress_filter(PushFilter **res, PGP_Context *ctx, PushFilter *dst); int pgp_decompress_filter(PullFilter **res, PGP_Context *ctx, PullFilter *src); --- 277,288 ---- 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, 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, + const char *key, unsigned key_len, + StringInfo valuedst, List **keylistdst); int pgp_compress_filter(PushFilter **res, PGP_Context *ctx, PushFilter *dst); int pgp_decompress_filter(PullFilter **res, PGP_Context *ctx, PullFilter *src); *** a/contrib/pgcrypto/sql/pgp-armor.sql --- b/contrib/pgcrypto/sql/pgp-armor.sql *************** *** 56,58 **** em9va2E= --- 56,263 ---- =ZZZZ -----END PGP MESSAGE----- '); + + -- corrupt + select pgp_armor_header(' + -----BEGIN PGP MESSAGE----- + foo: + + em9va2E= + =ZZZZ + -----END PGP MESSAGE----- + ', 'foo'); + + -- empty + select pgp_armor_header(' + -----BEGIN PGP MESSAGE----- + foo: + + em9va2E= + =ZZZZ + -----END PGP MESSAGE----- + ', 'foo'); + + -- simple + select pgp_armor_header(' + -----BEGIN PGP MESSAGE----- + foo: bar + + em9va2E= + =ZZZZ + -----END PGP MESSAGE----- + ', 'foo'); + + -- uninteresting keys, part 1 + select pgp_armor_header(' + -----BEGIN PGP MESSAGE----- + foo: found + bar: ignored + + em9va2E= + =ZZZZ + -----END PGP MESSAGE----- + ', 'foo'); + + -- uninteresting keys, part 2 + select pgp_armor_header(' + -----BEGIN PGP MESSAGE----- + bar: ignored + foo: found + + em9va2E= + =ZZZZ + -----END PGP MESSAGE----- + ', 'foo'); + + -- uninteresting keys, part 3 + select pgp_armor_header(' + -----BEGIN PGP MESSAGE----- + bar: ignored + foo: found + bar: ignored + + em9va2E= + =ZZZZ + -----END PGP MESSAGE----- + ', 'foo'); + + -- insane keys, part 1 + select pgp_armor_header(' + -----BEGIN PGP MESSAGE----- + insane:key : + + em9va2E= + =ZZZZ + -----END PGP MESSAGE----- + ', 'insane:key '); + + -- insane keys, part 2 + select pgp_armor_header(' + -----BEGIN PGP MESSAGE----- + insane:key : text value here + + em9va2E= + =ZZZZ + -----END PGP MESSAGE----- + ', 'insane:key '); + + -- long value + select pgp_armor_header(' + -----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'); + + -- long value, split up + select pgp_armor_header(' + -----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'); + + -- long value, split up, part 2 + select pgp_armor_header(' + -----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'); + + -- long value, split up, part 3 + select pgp_armor_header(' + -----BEGIN PGP MESSAGE----- + ignored: + long: this value is more than + ignored: + long: 76 characters long, but it should still + ignored: + long: parse correctly as that''s permitted by RFC 4880 + ignored: + + em9va2E= + =ZZZZ + -----END PGP MESSAGE----- + ', 'long'); + + select pgp_armor_header(' + -----BEGIN PGP MESSAGE----- + Comment: dat1.blowfish.sha1.mdc.s2k3.z0 + + jA0EBAMCfFNwxnvodX9g0jwB4n4s26/g5VmKzVab1bX1SmwY7gvgvlWdF3jKisvS + yA6Ce1QTMK3KdL2MPfamsTUSAML8huCJMwYQFfE= + =JcP+ + -----END PGP MESSAGE----- + ', 'Comment'); + + -- corrupt + select pgp_armor_header_keys(' + -----BEGIN PGP MESSAGE----- + foo: + + em9va2E= + =ZZZZ + -----END PGP MESSAGE----- + '); + + -- empty + select pgp_armor_header_keys(' + -----BEGIN PGP MESSAGE----- + foo: + + em9va2E= + =ZZZZ + -----END PGP MESSAGE----- + '); + + -- simple + select pgp_armor_header_keys(' + -----BEGIN PGP MESSAGE----- + foo: bar + + em9va2E= + =ZZZZ + -----END PGP MESSAGE----- + '); + + -- duplicates should be eliminated + select pgp_armor_header_keys(' + -----BEGIN PGP MESSAGE----- + nodups: + long: this value is more than + nodups: + long: 76 characters long, but it should still + nodups: + long: parse correctly as that''s permitted by RFC 4880 + nodups: + reallynodups: + + em9va2E= + =ZZZZ + -----END PGP MESSAGE----- + '); + + -- 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 most advanced open source database']); + select pgp_armor_header(armor('zooka', array['Version', 'Comment'], array['Created by pgcrypto', 'PostgreSQL, the world''s most most advanced open source database']), 'Comment'); + + -- 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"}'); *** a/doc/src/sgml/pgcrypto.sgml --- b/doc/src/sgml/pgcrypto.sgml *************** *** 691,703 **** pgp_key_id(bytea) returns text ! armor(data bytea) returns text dearmor(data text) returns bytea These functions wrap/unwrap binary data into PGP ASCII-armor format, which is basically Base64 with CRC and additional formatting. --- 691,751 ---- ! armor(data bytea [ , keys text[], values text[] ]) returns text dearmor(data text) returns bytea These functions wrap/unwrap binary data into PGP ASCII-armor format, which is basically Base64 with CRC and additional formatting. + + + For armor, if the keys and values + arrays are specified, their members are written into the armored data as + armor headers. For each member in keys, the + value in values with the corresponding ordinal is used as + the value for that key. Both arrays must be single-dimensional, and they + must be of the same length. All text is converted into UTF-8. + + + + + <function>pgp_armor_header</function> + + + pgp_armor_header + + + + pgp_armor_header(data text, key text) returns text + + + pgp_armor_header() extracts the armor header with + the key key from data. Before matching, + key is converted into UTF-8. Also all data in the armored + text is assumed to be UTF-8. If part of the data is not valid UTF-8 or + key can not be converted to UTF-8, an error is raised. + If the key key appears multiple times in the armored text, + all values are concatenated into the return value. If the key does not + appear in the armored text, the return value is NULL. + + + + + <function>pgp_armor_header_keys</function> + + + pgp_armor_header_keys + + + + pgp_armor_header_keys(data text) returns setof text + + + pgp_armor_header_keys() extracts the list of armor + header keys from data. The keys are all assumed to be in + UTF-8. If any of the keys is not valid UTF-8, an error is raised. +