Hi, On 8/8/14 3:18 PM, I wrote:
Currently there's no way to generate or extract armor headers from the PGP armored format in pgcrypto. I've written a patch to add the support.
Latest version of the patch here, having fixed some small coding issues. .marko
*** 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,362 ---- -----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) + + -- 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,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_header(text, text) + RETURNS text + AS 'MODULE_PATHNAME', 'pgp_armor_header_w' + LANGUAGE C IMMUTABLE STRICT; *** /dev/null --- b/contrib/pgcrypto/pgcrypto--1.2.sql *************** *** 0 **** --- 1,217 ---- + /* 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_w' + 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,210 **** crc24(const uint8 *data, unsigned len) } int ! pgp_armor_encode(const uint8 *src, unsigned len, uint8 *dst) { int n; uint8 *pos = dst; --- 204,211 ---- } int ! pgp_armor_encode(const uint8 *src, unsigned len, uint8 *dst, ! int num_headers, char **keys, char **values) { int n; uint8 *pos = dst; *************** *** 214,219 **** pgp_armor_encode(const uint8 *src, unsigned len, uint8 *dst) --- 215,236 ---- memcpy(pos, armor_header, n); pos += n; + for (n = 0; n < num_headers; n++) + { + size_t keylen = strlen(keys[n]); + size_t valuelen = strlen(values[n]); + + memcpy(pos, keys[n], keylen); + pos += keylen; + *pos++ = ':'; + *pos++ = ' '; + memcpy(pos, values[n], valuelen); + pos += valuelen; + *pos++ = '\n'; + } + + *pos++ = '\n'; + n = b64_encode(src, len, pos); pos += n; *************** *** 369,378 **** out: return res; } unsigned ! pgp_armor_enc_len(unsigned len) { ! return b64_enc_len(len) + strlen(armor_header) + strlen(armor_footer) + 16; } unsigned --- 386,501 ---- return res; } + int + pgp_armor_header(const uint8 *src, unsigned len, const char *key, + unsigned key_len, uint8 **dst, int *out_len) + { + const uint8 *p = src; + const uint8 *data_end = src + len; + const uint8 *armor_end; + const uint8 *eol, + *colon; + MBuf *buf = NULL; + int hlen; + int res = PXE_PGP_CORRUPT_ARMOR; + + /* 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; + } + + if (key_len == colon - p && + memcmp(p, key, key_len) == 0) + { + size_t valuelen; + valuelen = eol - colon - 2; + if (!buf) + buf = mbuf_create(valuelen + 1); + + res = mbuf_append(buf, colon + 2, (int) valuelen); + if (res < 0) + goto out; + } + /* step to start of next line */ + p = eol + 1; + } + + out: + if (res < 0) + { + if (buf) + px_free(buf); + *dst = NULL; + *out_len = -1; + return res; + } + else + { + if (buf) + { + /* 0-terminate the string for the caller */ + res = mbuf_append(buf, (uint8 *) "\x00", 1); + if (res < 0) + return res; + *out_len = mbuf_steal_data(buf, dst) - 1; + } + else + { + *dst = NULL; + *out_len = 0; + } + return 0; + } + } + unsigned ! pgp_armor_enc_len(unsigned len, int num_headers, char **keys, char **values) { ! int i; ! unsigned header_length = 0; ! ! for (i = 0; i < num_headers; i++) ! header_length += strlen(keys[i]) + strlen(values[i]) + 2 + 1; ! ! return strlen(armor_header) + header_length + 1 + ! b64_enc_len(len) + strlen(armor_footer) + 16; } unsigned *** a/contrib/pgcrypto/pgp-pgsql.c --- b/contrib/pgcrypto/pgp-pgsql.c *************** *** 31,38 **** --- 31,40 ---- #include "postgres.h" + #include "catalog/pg_type.h" #include "mb/pg_wchar.h" #include "utils/builtins.h" + #include "utils/array.h" #include "mbuf.h" #include "px.h" *************** *** 55,60 **** PG_FUNCTION_INFO_V1(pgp_key_id_w); --- 57,63 ---- PG_FUNCTION_INFO_V1(pg_armor); PG_FUNCTION_INFO_V1(pg_dearmor); + PG_FUNCTION_INFO_V1(pgp_armor_header_w); /* * Mix a block of data into RNG. *************** *** 815,820 **** pgp_pub_decrypt_text(PG_FUNCTION_ARGS) --- 818,885 ---- * 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,837 **** pg_armor(PG_FUNCTION_ARGS) int data_len, res_len, guess_len; data = PG_GETARG_BYTEA_P(0); data_len = VARSIZE(data) - VARHDRSZ; ! guess_len = pgp_armor_enc_len(data_len); res = palloc(VARHDRSZ + guess_len); res_len = pgp_armor_encode((uint8 *) VARDATA(data), data_len, ! (uint8 *) VARDATA(res)); if (res_len > guess_len) ereport(ERROR, (errcode(ERRCODE_EXTERNAL_ROUTINE_INVOCATION_EXCEPTION), --- 888,914 ---- int data_len, res_len, guess_len; + 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()); ! guess_len = pgp_armor_enc_len(data_len, num_headers, keys, values); res = palloc(VARHDRSZ + guess_len); res_len = pgp_armor_encode((uint8 *) VARDATA(data), data_len, ! (uint8 *) VARDATA(res), ! num_headers, keys, values); if (res_len > guess_len) ereport(ERROR, (errcode(ERRCODE_EXTERNAL_ROUTINE_INVOCATION_EXCEPTION), *************** *** 873,878 **** pg_dearmor(PG_FUNCTION_ARGS) --- 950,999 ---- PG_RETURN_TEXT_P(res); } + Datum + pgp_armor_header_w(PG_FUNCTION_ARGS) + { + bytea *data; + int data_len, + res; + char *buf; + text *key, + *utf8key; + int buflen; + + data = PG_GETARG_BYTEA_P(0); + data_len = VARSIZE(data) - VARHDRSZ; + + key = PG_GETARG_TEXT_P(1); + utf8key = convert_to_utf8(key); + res = pgp_armor_header((uint8 *) VARDATA(data), data_len, + VARDATA(utf8key), VARSIZE(utf8key) - VARHDRSZ, + (uint8 **) &buf, &buflen); + 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 (!buf) + PG_RETURN_NULL(); + else + { + /* assume it's UTF-8 */ + char *utf; + text *result; + + utf = pg_any_to_server(buf, buflen, PG_UTF8); + result = cstring_to_text(utf); + PG_RETURN_TEXT_P(result); + } + } + + + /* * Wrappers for PGP key id */ *** a/contrib/pgcrypto/pgp.h --- b/contrib/pgcrypto/pgp.h *************** *** 274,282 **** 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); ! int pgp_armor_encode(const uint8 *src, unsigned len, uint8 *dst); int pgp_armor_decode(const uint8 *src, unsigned len, uint8 *dst); ! unsigned pgp_armor_enc_len(unsigned len); unsigned pgp_armor_dec_len(unsigned len); int pgp_compress_filter(PushFilter **res, PGP_Context *ctx, PushFilter *dst); --- 274,286 ---- 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); ! int pgp_armor_encode(const uint8 *src, unsigned len, uint8 *dst, ! int num_headers, char **keys, char **values); int pgp_armor_decode(const uint8 *src, unsigned len, uint8 *dst); ! int pgp_armor_header(const uint8 *src, unsigned len, ! const char *key, unsigned key_len, ! uint8 **dst, int *out_len); ! unsigned pgp_armor_enc_len(unsigned len, int num_headers, char **keys, char **values); unsigned pgp_armor_dec_len(unsigned len); int pgp_compress_filter(PushFilter **res, PGP_Context *ctx, PushFilter *dst); *** a/contrib/pgcrypto/sql/pgp-armor.sql --- b/contrib/pgcrypto/sql/pgp-armor.sql *************** *** 56,58 **** em9va2E= --- 56,216 ---- =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'); + + -- 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,706 **** pgp_key_id(bytea) returns text </indexterm> <synopsis> ! armor(data bytea) 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> </sect3> <sect3> <title>Options for PGP Functions</title> <para> --- 691,738 ---- </indexterm> <synopsis> ! 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> + For <function>armor</>, if the <parameter>keys</> and <parameter>values</> + arrays are specified, their members are written into the armored data as + <literal>armor headers</>. For each member in <parameter>keys</>, the + value in <parameter>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. + </para> </sect3> <sect3> + <title><function>pgp_armor_header</function></title> + + <indexterm> + <primary>pgp_armor_header</primary> + </indexterm> + + <synopsis> + pgp_armor_header(data text, key text) returns text + </synopsis> + <para> + <function>pgp_armor_header()</> extracts the <literal>armor header</> with + the key <parameter>key</> from <parameter>data</>. Before matching, + <parameter>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 + <parameter>key</> can not be converted to UTF-8, an error is returned. + If the key <parameter>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. + </para> + </sect3> + + + <sect3> <title>Options for PGP Functions</title> <para>
-- Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org) To make changes to your subscription: http://www.postgresql.org/mailpref/pgsql-hackers