From 420c071e90f1f306db7a914dc0d0d3acd111dbe3 Mon Sep 17 00:00:00 2001 From: Jehan-Guillaume de Rorthais Date: Fri, 9 Jun 2023 11:48:54 +0200 Subject: [PATCH 01/16] Set version 3.2.0 --- META.json | 4 ++-- pgsodium.control | 2 +- pgsodium_tapgen.pl | 2 +- sql/pgsodium--3.1.7--3.2.0.sql | 1 + test/pgsodium_schema.sql | 2 +- 5 files changed, 6 insertions(+), 5 deletions(-) create mode 100644 sql/pgsodium--3.1.7--3.2.0.sql diff --git a/META.json b/META.json index cacea64..3ad9892 100644 --- a/META.json +++ b/META.json @@ -2,7 +2,7 @@ "name": "pgsodium", "abstract": "Postgres extension for libsodium functions", "description": "pgsodium is a PostgreSQL extension that exposes modern libsodium based cryptographic functions to SQL.", - "version": "3.1.7", + "version": "3.2.0", "maintainer": [ "Michel Pelletier " ], @@ -13,7 +13,7 @@ "abstract": "Postgres extension for libsodium functions", "file": "src/pgsodium.h", "docfile": "README.md", - "version": "3.1.7" + "version": "3.2.0" } }, "prereqs": { diff --git a/pgsodium.control b/pgsodium.control index c4db242..3f89d95 100644 --- a/pgsodium.control +++ b/pgsodium.control @@ -1,5 +1,5 @@ # pgsodium extension comment = 'Postgres extension for libsodium functions' -default_version = '3.1.7' +default_version = '3.2.0' relocatable = false schema = pgsodium diff --git a/pgsodium_tapgen.pl b/pgsodium_tapgen.pl index 4429215..57ec545 100755 --- a/pgsodium_tapgen.pl +++ b/pgsodium_tapgen.pl @@ -7,7 +7,7 @@ use Getopt::Long; use File::Spec; -my $PGSODIUM_VERSION = '3.1.7'; +my $PGSODIUM_VERSION = '3.2.0'; my $curr; my $rs; diff --git a/sql/pgsodium--3.1.7--3.2.0.sql b/sql/pgsodium--3.1.7--3.2.0.sql new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/sql/pgsodium--3.1.7--3.2.0.sql @@ -0,0 +1 @@ + diff --git a/test/pgsodium_schema.sql b/test/pgsodium_schema.sql index d500c85..3e8953b 100644 --- a/test/pgsodium_schema.sql +++ b/test/pgsodium_schema.sql @@ -8,7 +8,7 @@ SELECT cmp_ok(current_setting('server_version_num')::int, '>=', 130000, format(' ---- EXTENSION VERSION -SELECT results_eq('SELECT pgsodium.version()', $$VALUES ('3.1.7'::text)$$, 'Version of pgsodium is 3.1.7'); +SELECT results_eq('SELECT pgsodium.version()', $$VALUES ('3.2.0'::text)$$, 'Version of pgsodium is 3.2.0'); ---- EXTENSION OBJECTS From fec4f1cc660b0f5575a0e29e65118b43d357ea7f Mon Sep 17 00:00:00 2001 From: Jehan-Guillaume de Rorthais Date: Fri, 9 Jun 2023 11:51:06 +0200 Subject: [PATCH 02/16] Remove useless pgsodium.create_mask_view(oid,boolean) --- sql/pgsodium--3.1.7--3.2.0.sql | 5 ++++- test/pgsodium_schema.sql | 36 ---------------------------------- 2 files changed, 4 insertions(+), 37 deletions(-) diff --git a/sql/pgsodium--3.1.7--3.2.0.sql b/sql/pgsodium--3.1.7--3.2.0.sql index 8b13789..a778ca8 100644 --- a/sql/pgsodium--3.1.7--3.2.0.sql +++ b/sql/pgsodium--3.1.7--3.2.0.sql @@ -1 +1,4 @@ - +/* + * change: replaced in 3.0.5 with "create_mask_view(oid, integer, boolean)". + */ +DROP FUNCTION IF EXISTS pgsodium.create_mask_view(oid, boolean); diff --git a/test/pgsodium_schema.sql b/test/pgsodium_schema.sql index 3e8953b..e0b0f60 100644 --- a/test/pgsodium_schema.sql +++ b/test/pgsodium_schema.sql @@ -24,7 +24,6 @@ SELECT bag_eq($$ $$ VALUES ('event trigger pgsodium_trg_mask_update' ::text), ('function pgsodium.create_key(pgsodium.key_type,text,bytea,bytea,uuid,bytea,timestamp with time zone,text)' ::text), - ('function pgsodium.create_mask_view(oid,boolean)' ::text), ('function pgsodium.create_mask_view(oid,integer,boolean)' ::text), ('function pgsodium.crypto_aead_det_decrypt(bytea,bytea,bigint,bytea,bytea)' ::text), ('function pgsodium.crypto_aead_det_decrypt(bytea,bytea,bytea,bytea)' ::text), @@ -971,41 +970,6 @@ SELECT function_privs_are('pgsodium'::name, proname, proargtypes::regtype[]::tex AND proname = 'create_key' AND oidvectortypes(proargtypes) = 'pgsodium.key_type, text, bytea, bytea, uuid, bytea, timestamp with time zone, text'; -SELECT unnest(ARRAY[ - is(md5(prosrc), 'a34e96732392101c6e438288325151c0', - format('Function pgsodium.%s(%s) body should match checksum', - proname, pg_get_function_identity_arguments(oid)) - ), - function_owner_is( - 'pgsodium'::name, proname, - proargtypes::regtype[]::name[], 'postgres'::name, - format('Function pgsodium.%s(%s) owner is %s', - proname, pg_get_function_identity_arguments(oid), 'postgres') - ), - function_lang_is('pgsodium'::name, proname, proargtypes::regtype[]::name[], 'plpgsql'::name ), - function_returns('pgsodium'::name, proname, proargtypes::regtype[]::name[], 'void' ), - volatility_is('pgsodium'::name, proname, proargtypes::regtype[]::name[], 'volatile'), - isnt_definer('pgsodium'::name, proname, proargtypes::regtype[]::name[]), - isnt_strict('pgsodium'::name, proname, proargtypes::regtype[]::name[]), - is_normal_function('pgsodium'::name, proname, proargtypes::regtype[]::name[]) -]) - FROM pg_catalog.pg_proc - WHERE pronamespace = 'pgsodium'::regnamespace - AND proname = 'create_mask_view' - AND oidvectortypes(proargtypes) = 'oid, boolean'; - -SELECT function_privs_are('pgsodium'::name, proname, proargtypes::regtype[]::text[], 'postgres', '{EXECUTE}'::text[]) - FROM pg_catalog.pg_proc - WHERE pronamespace = 'pgsodium'::regnamespace - AND proname = 'create_mask_view' - AND oidvectortypes(proargtypes) = 'oid, boolean'; - -SELECT function_privs_are('pgsodium'::name, proname, proargtypes::regtype[]::text[], 'public', '{EXECUTE}'::text[]) - FROM pg_catalog.pg_proc - WHERE pronamespace = 'pgsodium'::regnamespace - AND proname = 'create_mask_view' - AND oidvectortypes(proargtypes) = 'oid, boolean'; - SELECT unnest(ARRAY[ is(md5(prosrc), 'fb42e03b118baa4eec1ff6fd3773ef3e', format('Function pgsodium.%s(%s) body should match checksum', From d072843f8eda14374e6b17963d7b456c9f0d545b Mon Sep 17 00:00:00 2001 From: Jehan-Guillaume de Rorthais Date: Fri, 9 Jun 2023 12:17:58 +0200 Subject: [PATCH 03/16] Remove useless pgsodium.mask_columns(oid) --- sql/pgsodium--3.1.7--3.2.0.sql | 5 +++++ test/pgsodium_schema.sql | 37 ---------------------------------- 2 files changed, 5 insertions(+), 37 deletions(-) diff --git a/sql/pgsodium--3.1.7--3.2.0.sql b/sql/pgsodium--3.1.7--3.2.0.sql index a778ca8..c9ea5c8 100644 --- a/sql/pgsodium--3.1.7--3.2.0.sql +++ b/sql/pgsodium--3.1.7--3.2.0.sql @@ -2,3 +2,8 @@ * change: replaced in 3.0.5 with "create_mask_view(oid, integer, boolean)". */ DROP FUNCTION IF EXISTS pgsodium.create_mask_view(oid, boolean); + +/* + * change: replaced in 3.0.5 by the "pgsodium.mask_columns" view. + */ +DROP FUNCTION IF EXISTS pgsodium.mask_columns(oid); diff --git a/test/pgsodium_schema.sql b/test/pgsodium_schema.sql index e0b0f60..9f7ef70 100644 --- a/test/pgsodium_schema.sql +++ b/test/pgsodium_schema.sql @@ -144,7 +144,6 @@ SELECT bag_eq($$ ('function pgsodium.get_named_keys(text)' ::text), ('function pgsodium.has_mask(regrole,text)' ::text), ('function pgsodium.key_encrypt_secret_raw_key()' ::text), - ('function pgsodium.mask_columns(oid)' ::text), ('function pgsodium.mask_role(regrole,text,text)' ::text), ('function pgsodium.pgsodium_derive(bigint,integer,bytea)' ::text), ('function pgsodium.quote_assoc(text,boolean)' ::text), @@ -912,7 +911,6 @@ SELECT functions_are('pgsodium', ARRAY[ 'get_named_keys', 'has_mask', 'key_encrypt_secret_raw_key', - 'mask_columns', 'mask_role', 'pgsodium_derive', 'quote_assoc', @@ -5176,41 +5174,6 @@ SELECT function_privs_are('pgsodium'::name, proname, proargtypes::regtype[]::tex AND proname = 'key_encrypt_secret_raw_key' AND oidvectortypes(proargtypes) = ''; -SELECT unnest(ARRAY[ - is(md5(prosrc), 'dad5c5f648d4aec8e8142213de721039', - format('Function pgsodium.%s(%s) body should match checksum', - proname, pg_get_function_identity_arguments(oid)) - ), - function_owner_is( - 'pgsodium'::name, proname, - proargtypes::regtype[]::name[], 'postgres'::name, - format('Function pgsodium.%s(%s) owner is %s', - proname, pg_get_function_identity_arguments(oid), 'postgres') - ), - function_lang_is('pgsodium'::name, proname, proargtypes::regtype[]::name[], 'sql'::name ), - function_returns('pgsodium'::name, proname, proargtypes::regtype[]::name[], 'setof record' ), - volatility_is('pgsodium'::name, proname, proargtypes::regtype[]::name[], 'volatile'), - isnt_definer('pgsodium'::name, proname, proargtypes::regtype[]::name[]), - isnt_strict('pgsodium'::name, proname, proargtypes::regtype[]::name[]), - is_normal_function('pgsodium'::name, proname, proargtypes::regtype[]::name[]) -]) - FROM pg_catalog.pg_proc - WHERE pronamespace = 'pgsodium'::regnamespace - AND proname = 'mask_columns' - AND oidvectortypes(proargtypes) = 'oid'; - -SELECT function_privs_are('pgsodium'::name, proname, proargtypes::regtype[]::text[], 'postgres', '{EXECUTE}'::text[]) - FROM pg_catalog.pg_proc - WHERE pronamespace = 'pgsodium'::regnamespace - AND proname = 'mask_columns' - AND oidvectortypes(proargtypes) = 'oid'; - -SELECT function_privs_are('pgsodium'::name, proname, proargtypes::regtype[]::text[], 'public', '{EXECUTE}'::text[]) - FROM pg_catalog.pg_proc - WHERE pronamespace = 'pgsodium'::regnamespace - AND proname = 'mask_columns' - AND oidvectortypes(proargtypes) = 'oid'; - SELECT unnest(ARRAY[ is(md5(prosrc), '1b1d814a258347381f8989c6874dc01c', format('Function pgsodium.%s(%s) body should match checksum', From e3821050232e68d36b077105535b4767ca67942b Mon Sep 17 00:00:00 2001 From: Jehan-Guillaume de Rorthais Date: Fri, 9 Jun 2023 12:23:51 +0200 Subject: [PATCH 04/16] Remove useless schema pgsodium_masks --- sql/pgsodium--3.1.7--3.2.0.sql | 9 +++++++++ test/pgsodium_schema.sql | 4 ---- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/sql/pgsodium--3.1.7--3.2.0.sql b/sql/pgsodium--3.1.7--3.2.0.sql index c9ea5c8..e33dd84 100644 --- a/sql/pgsodium--3.1.7--3.2.0.sql +++ b/sql/pgsodium--3.1.7--3.2.0.sql @@ -7,3 +7,12 @@ DROP FUNCTION IF EXISTS pgsodium.create_mask_view(oid, boolean); * change: replaced in 3.0.5 by the "pgsodium.mask_columns" view. */ DROP FUNCTION IF EXISTS pgsodium.mask_columns(oid); + +/* + * change: schema "pgsodium_masks" removed in 3.0.4 + * FIXME: how the extension handle bw compatibility when a table having a view + * in pgsodium_masks is update or has a seclabel added/changed? A new + * view is created outside of pgsodium_masks? What about the client app + * and the old view? + */ +DROP SCHEMA IF EXISTS pgsodium_masks; diff --git a/test/pgsodium_schema.sql b/test/pgsodium_schema.sql index 9f7ef70..02f98b5 100644 --- a/test/pgsodium_schema.sql +++ b/test/pgsodium_schema.sql @@ -158,7 +158,6 @@ SELECT bag_eq($$ ('function pgsodium.update_mask(oid,boolean)' ::text), ('function pgsodium.update_masks(boolean)' ::text), ('function pgsodium.version()' ::text), - ('schema pgsodium_masks' ::text), ('sequence pgsodium.key_key_id_seq' ::text), ('table pgsodium.key' ::text), ('type pgsodium._key_id_context' ::text), @@ -195,9 +194,6 @@ SELECT is_member_of( 'pgsodium_keyiduser', 'pgsodium_keymaker' ); SELECT has_schema('pgsodium'); SELECT schema_owner_is('pgsodium', 'postgres'); -SELECT has_schema('pgsodium_masks'); -SELECT schema_owner_is('pgsodium_masks', 'postgres'); - From b56d91470ad12f455c11b27a6598cb74d5c68c93 Mon Sep 17 00:00:00 2001 From: Jehan-Guillaume de Rorthais Date: Fri, 9 Jun 2023 13:42:53 +0200 Subject: [PATCH 05/16] Add 3.2.0 full script --- sql/pgsodium--3.2.0.sql | 2672 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 2672 insertions(+) create mode 100644 sql/pgsodium--3.2.0.sql diff --git a/sql/pgsodium--3.2.0.sql b/sql/pgsodium--3.2.0.sql new file mode 100644 index 0000000..bf62272 --- /dev/null +++ b/sql/pgsodium--3.2.0.sql @@ -0,0 +1,2672 @@ +-- TODO: check namespaces in funcs body +-- TODO: check strictiness of the functions + +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "CREATE EXTENSION pgsodium" to load this file. \quit + +COMMENT ON EXTENSION pgsodium IS +'Pgsodium is a modern cryptography library for Postgres.'; + +--============================================================================== +-- ROLES +--============================================================================== + +/* */ +DO $$ +DECLARE + new_role text; +BEGIN + FOREACH new_role IN ARRAY + ARRAY['pgsodium_keyiduser', + 'pgsodium_keyholder', + 'pgsodium_keymaker'] + LOOP + IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = new_role) THEN + EXECUTE format($i$ + CREATE ROLE %I WITH + NOLOGIN + NOSUPERUSER + NOCREATEDB + NOCREATEROLE + INHERIT + NOREPLICATION + CONNECTION LIMIT -1; + $i$, new_role); + END IF; + END LOOP; +END +$$; + + +GRANT pgsodium_keyholder TO pgsodium_keymaker; -- deprecating keyholder +GRANT pgsodium_keyiduser TO pgsodium_keymaker; +GRANT pgsodium_keyiduser TO pgsodium_keyholder; + + +--============================================================================== +-- PRIVILEGES +--============================================================================== + +-- All roles cannot execute functions in pgsodium +REVOKE ALL ON SCHEMA pgsodium FROM PUBLIC; + +-- But they can see the objects in pgsodium +GRANT USAGE ON SCHEMA pgsodium TO PUBLIC; + +-- By default, public can't use any table, functions, or sequences +ALTER DEFAULT PRIVILEGES IN SCHEMA pgsodium REVOKE ALL ON TABLES FROM PUBLIC; +ALTER DEFAULT PRIVILEGES IN SCHEMA pgsodium REVOKE ALL ON FUNCTIONS FROM PUBLIC; +ALTER DEFAULT PRIVILEGES IN SCHEMA pgsodium REVOKE ALL ON SEQUENCES FROM PUBLIC; + +-- pgsodium_keyiduser can use all tables and sequences (functions are granted individually) +ALTER DEFAULT PRIVILEGES IN SCHEMA pgsodium REVOKE ALL ON TABLES FROM pgsodium_keyiduser; +ALTER DEFAULT PRIVILEGES IN SCHEMA pgsodium REVOKE ALL ON FUNCTIONS FROM pgsodium_keyiduser; +ALTER DEFAULT PRIVILEGES IN SCHEMA pgsodium REVOKE ALL ON SEQUENCES FROM pgsodium_keyiduser; + + +--============================================================================== +-- TRIGGERS +--============================================================================== + + +/* + * trg_mask_update() + */ +CREATE FUNCTION pgsodium.trg_mask_update() + RETURNS EVENT_TRIGGER + AS $$ +DECLARE + r record; +BEGIN + IF (select bool_or(in_extension) FROM pg_event_trigger_ddl_commands()) THEN + RAISE NOTICE 'skipping pgsodium mask regeneration in extension'; + RETURN; + END IF; + PERFORM pgsodium.update_masks(); +END +$$ + LANGUAGE plpgsql + SET search_path=''; + +-- FIXME: owner? +-- FIXME: revoke? +-- FIXME: grant? + +/* */ +CREATE EVENT TRIGGER pgsodium_trg_mask_update + ON ddl_command_end + WHEN TAG IN ( + 'SECURITY LABEL', + 'ALTER TABLE' + ) + EXECUTE PROCEDURE pgsodium.trg_mask_update(); + +--============================================================================== +-- TYPES +--============================================================================== + +/* */ +CREATE TYPE pgsodium.crypto_box_keypair AS (public bytea, secret bytea); + +/* */ +CREATE TYPE pgsodium.crypto_sign_keypair AS (public bytea, secret bytea); + +/* */ +CREATE TYPE pgsodium.crypto_kx_keypair AS (public bytea, secret bytea); + +/* */ +CREATE TYPE pgsodium.crypto_kx_session AS (rx bytea, tx bytea); + +/* */ +CREATE TYPE pgsodium.crypto_signcrypt_state_key AS (state bytea, shared_key bytea); + +/* */ +CREATE TYPE pgsodium.crypto_signcrypt_keypair AS (public bytea, secret bytea); + +/* Internal Key Management */ +CREATE TYPE pgsodium.key_status AS ENUM ( + 'default', + 'valid', + 'invalid', + 'expired' +); + +/* */ +CREATE TYPE pgsodium.key_type AS ENUM ( + 'aead-ietf', + 'aead-det', + 'hmacsha512', + 'hmacsha256', + 'auth', + 'shorthash', + 'generichash', + 'kdf', + 'secretbox', + 'secretstream', + 'stream_xchacha20' +); + +/* Deterministic AEAD functions by key uuid */ +CREATE TYPE pgsodium._key_id_context AS ( + key_id bigint, + key_context bytea +); + + +--============================================================================== +-- TABLE +--============================================================================== + +/* */ +CREATE TABLE pgsodium.key ( + id uuid PRIMARY KEY DEFAULT gen_random_uuid(), + status pgsodium.key_status DEFAULT 'valid', + created timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP, + expires timestamptz, + key_type pgsodium.key_type, + key_id bigserial, + key_context bytea DEFAULT 'pgsodium' CHECK (length(key_context) = 8), + "name" text UNIQUE, + associated_data text DEFAULT 'associated', + raw_key bytea, + raw_key_nonce bytea, + parent_key uuid REFERENCES pgsodium.key(id), + -- This is for bw compat with old dumps that don't go through the UPDATE TO process + "comment" text, + -- deprecated for b/w compat with <= 3.0.4 + user_data text, + CHECK ( + CASE WHEN raw_key IS NOT NULL + THEN key_id IS NULL AND key_context IS NULL AND parent_key IS NOT NULL + ELSE key_id IS NOT NULL AND key_context IS NOT NULL AND parent_key IS NULL + END + ) +); + +-- serial pseudo-type force NOT NULL but we allow them. +ALTER TABLE pgsodium.key ALTER COLUMN key_id DROP NOT NULL; + +-- FIXME: owner? +-- FIXME: revoke? +GRANT SELECT, INSERT, UPDATE, DELETE ON pgsodium.key TO pgsodium_keymaker; + +GRANT ALL ON SEQUENCE pgsodium.key_key_id_seq TO pgsodium_keymaker; + +CREATE INDEX ON pgsodium.key (status) WHERE status IN ('valid', 'default'); +CREATE UNIQUE INDEX ON pgsodium.key (status) WHERE status = 'default'; +CREATE UNIQUE INDEX ON pgsodium.key (key_id, key_context, key_type); + +COMMENT ON TABLE pgsodium.key IS + 'This table holds metadata for derived keys given a key_id ' + 'and key_context. The raw key is never stored.'; + +SELECT pg_catalog.pg_extension_config_dump('pgsodium.key', ''); +SELECT pg_catalog.pg_extension_config_dump('pgsodium.key_key_id_seq', ''); + +--============================================================================== +-- VIEWS +--============================================================================== + +/* */ +CREATE VIEW pgsodium.valid_key AS + SELECT id, name, status, key_type, key_id, key_context, created, expires, associated_data + FROM pgsodium.key + WHERE status IN ('valid', 'default') + AND CASE WHEN expires IS NULL THEN true ELSE expires > now() END; + +-- FIXME: owner? +-- FIXME: revoke? +GRANT SELECT ON pgsodium.valid_key TO pgsodium_keyiduser; +GRANT ALL ON pgsodium.valid_key TO pgsodium_keyholder; + +/* */ +CREATE VIEW pgsodium.masking_rule AS + WITH const AS ( + SELECT + 'encrypt +with +key +id +([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})' + AS pattern_key_id, + 'encrypt +with +key +column +([\w\"\-$]+)' + AS pattern_key_id_column, + '(?<=associated) +\(([\w\"\-$, ]+)\)' + AS pattern_associated_columns, + '(?<=nonce) +([\w\"\-$]+)' + AS pattern_nonce_column, + '(?<=decrypt with view) +([\w\"\-$]+\.[\w\"\-$]+)' + AS pattern_view_name, + '(?<=security invoker)' + AS pattern_security_invoker + ), + rules_from_seclabels AS ( + SELECT + sl.objoid AS attrelid, + sl.objsubid AS attnum, + c.relnamespace::regnamespace, + c.relname, + a.attname, + pg_catalog.format_type(a.atttypid, a.atttypmod), + sl.label AS col_description, + (regexp_match(sl.label, k.pattern_key_id_column, 'i'))[1] AS key_id_column, + (regexp_match(sl.label, k.pattern_key_id, 'i'))[1] AS key_id, + (regexp_match(sl.label, k.pattern_associated_columns, 'i'))[1] AS associated_columns, + (regexp_match(sl.label, k.pattern_nonce_column, 'i'))[1] AS nonce_column, + coalesce((regexp_match(sl2.label, k.pattern_view_name, 'i'))[1], + c.relnamespace::regnamespace || '.' || quote_ident('decrypted_' || c.relname)) AS view_name, + 100 AS priority, + (regexp_match(sl.label, k.pattern_security_invoker, 'i'))[1] IS NOT NULL AS security_invoker + FROM const k, + pg_catalog.pg_seclabel sl + JOIN pg_catalog.pg_class c ON sl.classoid = c.tableoid AND sl.objoid = c.oid + JOIN pg_catalog.pg_attribute a ON a.attrelid = c.oid AND sl.objsubid = a.attnum + LEFT JOIN pg_catalog.pg_seclabel sl2 ON sl2.objoid = c.oid AND sl2.objsubid = 0 + WHERE a.attnum > 0 + AND c.relnamespace::regnamespace != 'pg_catalog'::regnamespace + AND NOT a.attisdropped + AND sl.label ilike 'ENCRYPT%' + AND sl.provider = 'pgsodium' + ) + SELECT + DISTINCT ON (attrelid, attnum) * + FROM rules_from_seclabels + ORDER BY attrelid, attnum, priority DESC; + +-- FIXME: owner? +-- FIXME: revoke? +GRANT ALL ON pgsodium.masking_rule TO pgsodium_keyholder; + +/* */ +CREATE VIEW pgsodium.mask_columns AS SELECT + a.attname, + a.attrelid, + m.key_id, + m.key_id_column, + m.associated_columns, + m.nonce_column, + m.format_type +FROM pg_attribute a +LEFT JOIN pgsodium.masking_rule m ON m.attrelid = a.attrelid + AND m.attname = a.attname +WHERE a.attnum > 0 -- exclude ctid, cmin, cmax + AND NOT a.attisdropped +ORDER BY a.attnum; + +-- FIXME: owner? +-- FIXME: revoke? +-- FIXME: grant? +GRANT ALL ON pgsodium.mask_columns TO pgsodium_keyholder; + +--============================================================================== +-- FUNCTIONS +--============================================================================== + +/* + * create_key(pgsodium.key_type, text, bytea, bytea, uuid, bytea, timestamptz, text) + * + * Insert new key in "pgsodium.valid_key" table. + */ +CREATE FUNCTION pgsodium.create_key( + key_type pgsodium.key_type = 'aead-det', + name text = NULL, + raw_key bytea = NULL, + raw_key_nonce bytea = NULL, + parent_key uuid = NULL, + key_context bytea = 'pgsodium', + expires timestamptz = NULL, + associated_data text = '' + ) + RETURNS pgsodium.valid_key + AS $$ +DECLARE + new_key pgsodium.key; + valid_key pgsodium.valid_key; +BEGIN + INSERT INTO pgsodium.key (key_id, key_context, key_type, raw_key, + raw_key_nonce, parent_key, expires, name, associated_data) + VALUES ( + CASE WHEN raw_key IS NULL THEN + NEXTVAL('pgsodium.key_key_id_seq'::REGCLASS) + ELSE NULL END, + CASE WHEN raw_key IS NULL THEN + key_context + ELSE NULL END, + key_type, + raw_key, + CASE WHEN raw_key IS NOT NULL THEN + COALESCE(raw_key_nonce, pgsodium.crypto_aead_det_noncegen()) + ELSE NULL END, + CASE WHEN parent_key IS NULL and raw_key IS NOT NULL THEN + (pgsodium.create_key('aead-det')).id + ELSE parent_key END, + expires, + name, + associated_data) + RETURNING * INTO new_key; + SELECT * INTO valid_key FROM pgsodium.valid_key WHERE id = new_key.id; + RETURN valid_key; +END; +$$ + LANGUAGE plpgsql + SECURITY DEFINER + SET search_path = ''; + +ALTER FUNCTION pgsodium.create_key(pgsodium.key_type, text, bytea, bytea, uuid, bytea, timestamptz, text) OWNER TO pgsodium_keymaker; +-- FIXME: REVOKE? +GRANT EXECUTE ON FUNCTION pgsodium.create_key(pgsodium.key_type, text, bytea, bytea, uuid, bytea, timestamptz, text) TO pgsodium_keyiduser; + + +/* + * create_mask_view(oid, integer, boolean) + * + * - drop and create view "decrypted_" for given relation + * - drop and create associated triggers to encrypt data on INSERT OR UPDATE for given relation + */ +CREATE FUNCTION pgsodium.create_mask_view(relid oid, subid integer, debug boolean = false) + RETURNS void AS $$ +DECLARE + m record; + body text; + source_name text; + view_owner regrole = session_user; + rule pgsodium.masking_rule; + privs aclitem[]; + priv record; +BEGIN + SELECT DISTINCT * INTO STRICT rule FROM pgsodium.masking_rule WHERE attrelid = relid AND attnum = subid; + + source_name := relid::regclass::text; + + BEGIN + SELECT relacl INTO STRICT privs FROM pg_catalog.pg_class WHERE oid = rule.view_name::regclass::oid; + EXCEPTION + WHEN undefined_table THEN + SELECT relacl INTO STRICT privs FROM pg_catalog.pg_class WHERE oid = relid; + END; + + body = format( + $c$ + DROP VIEW IF EXISTS %1$s; + CREATE VIEW %1$s %5$s AS SELECT %2$s + FROM %3$s; + ALTER VIEW %1$s OWNER TO %4$s; + $c$, + rule.view_name, + pgsodium.decrypted_columns(relid), + source_name, + view_owner, + CASE WHEN rule.security_invoker THEN 'WITH (security_invoker=true)' ELSE '' END + ); + IF debug THEN + RAISE NOTICE '%', body; + END IF; + EXECUTE body; + + FOR priv IN SELECT * FROM pg_catalog.aclexplode(privs) LOOP + body = format( + $c$ + GRANT %s ON %s TO %s; + $c$, + priv.privilege_type, + rule.view_name, + priv.grantee::regrole::text + ); + IF debug THEN + RAISE NOTICE '%', body; + END IF; + EXECUTE body; + END LOOP; + + FOR m IN SELECT * FROM pgsodium.mask_columns where attrelid = relid LOOP + IF m.key_id IS NULL AND m.key_id_column is NULL THEN + CONTINUE; + ELSE + body = format( + $c$ + DROP FUNCTION IF EXISTS %1$s."%2$s_encrypt_secret_%3$s"() CASCADE; + + CREATE OR REPLACE FUNCTION %1$s."%2$s_encrypt_secret_%3$s"() + RETURNS TRIGGER + LANGUAGE plpgsql + AS $t$ + BEGIN + %4$s; + RETURN new; + END; + $t$; + + ALTER FUNCTION %1$s."%2$s_encrypt_secret_%3$s"() OWNER TO %5$s; + + DROP TRIGGER IF EXISTS "%2$s_encrypt_secret_trigger_%3$s" ON %6$s; + + CREATE TRIGGER "%2$s_encrypt_secret_trigger_%3$s" + BEFORE INSERT OR UPDATE OF "%3$s" ON %6$s + FOR EACH ROW + EXECUTE FUNCTION %1$s."%2$s_encrypt_secret_%3$s" (); + $c$, + rule.relnamespace, + rule.relname, + m.attname, + pgsodium.encrypted_column(relid, m), + view_owner, + source_name + ); + if debug THEN + RAISE NOTICE '%', body; + END IF; + EXECUTE body; + END IF; + END LOOP; + + raise notice 'about to masking role % %', source_name, rule.view_name; + PERFORM pgsodium.mask_role(oid::regrole, source_name, rule.view_name) + FROM pg_roles WHERE pgsodium.has_mask(oid::regrole, source_name); + + RETURN; +END + $$ + LANGUAGE plpgsql + VOLATILE + SET search_path='pg_catalog'; + +-- FIXME no OWNER +-- FIXME no REVOKE ? +-- FIXME no GRANT + +/* + * (bytea, bytea, bytea, bytea) + */ +CREATE FUNCTION pgsodium.crypto_aead_det_decrypt(message bytea, additional bytea, key bytea, nonce bytea = NULL) + RETURNS bytea + AS '$libdir/pgsodium', 'pgsodium_crypto_aead_det_decrypt' + LANGUAGE C + IMMUTABLE; + +-- FIXME: owner? +REVOKE ALL ON FUNCTION pgsodium.crypto_aead_det_decrypt(bytea, bytea, bytea, bytea) FROM PUBLIC; +GRANT EXECUTE ON FUNCTION pgsodium.crypto_aead_det_decrypt(bytea, bytea, bytea, bytea) TO pgsodium_keyholder; + +/* + * crypto_aead_det_decrypt(bytea, bytea, bigint, bytea, bytea) + */ +CREATE FUNCTION pgsodium.crypto_aead_det_decrypt(message bytea, additional bytea, key_id bigint, context bytea = 'pgsodium', nonce bytea = NULL) + RETURNS bytea + AS '$libdir/pgsodium', 'pgsodium_crypto_aead_det_decrypt_by_id' + LANGUAGE C + IMMUTABLE; + +-- FIXME: owner? +REVOKE ALL ON FUNCTION crypto_aead_det_decrypt(bytea, bytea, bigint, bytea, bytea) FROM PUBLIC; +GRANT EXECUTE ON FUNCTION crypto_aead_det_decrypt(bytea, bytea, bigint, bytea, bytea) TO pgsodium_keyiduser; + +/* + * crypto_aead_det_decrypt(bytea, bytea, uuid) + */ +CREATE FUNCTION pgsodium.crypto_aead_det_decrypt(message bytea, additional bytea, key_uuid uuid) + RETURNS bytea AS $$ +DECLARE + key pgsodium.decrypted_key; +BEGIN + SELECT * INTO STRICT key + FROM pgsodium.decrypted_key v + WHERE id = key_uuid AND key_type = 'aead-det'; + + IF key.decrypted_raw_key IS NOT NULL THEN + RETURN pgsodium.crypto_aead_det_decrypt(message, additional, key.decrypted_raw_key); + END IF; + RETURN pgsodium.crypto_aead_det_decrypt(message, additional, key.key_id, key.key_context); +END; + $$ + LANGUAGE plpgsql + SECURITY DEFINER + STABLE + SET search_path=''; + +ALTER FUNCTION pgsodium.crypto_aead_det_decrypt(bytea, bytea, uuid) OWNER TO pgsodium_keymaker; +REVOKE ALL ON FUNCTION pgsodium.crypto_aead_det_decrypt(bytea, bytea, uuid) FROM PUBLIC; +GRANT EXECUTE ON FUNCTION pgsodium.crypto_aead_det_decrypt(bytea, bytea, uuid) TO pgsodium_keyiduser; + +/* + * crypto_aead_det_decrypt(bytea, bytea, uuid, bytea) + */ +CREATE FUNCTION pgsodium.crypto_aead_det_decrypt(message bytea, additional bytea, key_uuid uuid, nonce bytea) + RETURNS bytea AS $$ +DECLARE + key pgsodium.decrypted_key; +BEGIN + SELECT * INTO STRICT key + FROM pgsodium.decrypted_key v + WHERE id = key_uuid AND key_type = 'aead-det'; + + IF key.decrypted_raw_key IS NOT NULL THEN + RETURN pgsodium.crypto_aead_det_decrypt(message, additional, key.decrypted_raw_key, nonce); + END IF; + RETURN pgsodium.crypto_aead_det_decrypt(message, additional, key.key_id, key.key_context, nonce); +END; + $$ + LANGUAGE plpgsql + SECURITY DEFINER + STABLE + SET search_path=''; + +ALTER FUNCTION pgsodium.crypto_aead_det_decrypt(bytea, bytea, uuid, bytea) OWNER TO pgsodium_keymaker; +REVOKE ALL ON FUNCTION pgsodium.crypto_aead_det_decrypt(bytea, bytea, uuid, bytea) FROM PUBLIC; +GRANT EXECUTE ON FUNCTION pgsodium.crypto_aead_det_decrypt(bytea, bytea, uuid, bytea) TO pgsodium_keyiduser; + +/* + * crypto_aead_det_encrypt(bytea, bytea, bytea, bytea) + */ +CREATE FUNCTION pgsodium.crypto_aead_det_encrypt(message bytea, additional bytea, key bytea, nonce bytea = NULL) + RETURNS bytea + AS '$libdir/pgsodium', 'pgsodium_crypto_aead_det_encrypt' + LANGUAGE C + IMMUTABLE; + +-- FIXME: owner? +REVOKE ALL ON FUNCTION crypto_aead_det_encrypt(bytea, bytea, bytea, bytea) FROM PUBLIC; +GRANT EXECUTE ON FUNCTION crypto_aead_det_encrypt(bytea, bytea, bytea, bytea) TO pgsodium_keyholder; + +/* + * crypto_aead_det_encrypt(bytea, bytea, bigint, bytea, bytea) + */ +CREATE FUNCTION pgsodium.crypto_aead_det_encrypt(message bytea, additional bytea, key_id bigint, context bytea = 'pgsodium', nonce bytea = NULL) + RETURNS bytea + AS '$libdir/pgsodium', 'pgsodium_crypto_aead_det_encrypt_by_id' + LANGUAGE C + IMMUTABLE; + +-- FIXME: owner? +REVOKE ALL ON FUNCTION crypto_aead_det_encrypt(bytea, bytea, bigint, bytea, bytea) FROM PUBLIC; +GRANT EXECUTE ON FUNCTION crypto_aead_det_encrypt(bytea, bytea, bigint, bytea, bytea) TO pgsodium_keyiduser; + +/* + * crypto_aead_det_encrypt(bytea, bytea, uuid) + */ +CREATE FUNCTION pgsodium.crypto_aead_det_encrypt(message bytea, additional bytea, key_uuid uuid) + RETURNS bytea AS $$ +DECLARE + key pgsodium.decrypted_key; +BEGIN + SELECT * INTO STRICT key + FROM pgsodium.decrypted_key v + WHERE id = key_uuid AND key_type = 'aead-det'; + + IF key.decrypted_raw_key IS NOT NULL THEN + RETURN pgsodium.crypto_aead_det_encrypt(message, additional, key.decrypted_raw_key); + END IF; + RETURN pgsodium.crypto_aead_det_encrypt(message, additional, key.key_id, key.key_context); +END; + $$ + LANGUAGE plpgsql + SECURITY DEFINER + STABLE + SET search_path=''; + +ALTER FUNCTION pgsodium.crypto_aead_det_encrypt(bytea, bytea, uuid) OWNER TO pgsodium_keymaker; +REVOKE ALL ON FUNCTION pgsodium.crypto_aead_det_encrypt(bytea, bytea, uuid) FROM PUBLIC; +GRANT EXECUTE ON FUNCTION pgsodium.crypto_aead_det_encrypt(bytea, bytea, uuid) TO pgsodium_keyiduser; + +/* + * crypto_aead_det_encrypt(bytea, bytea, uuid, bytea) + */ +CREATE FUNCTION pgsodium.crypto_aead_det_encrypt(message bytea, additional bytea, key_uuid uuid, nonce bytea) + RETURNS bytea AS $$ +DECLARE + key pgsodium.decrypted_key; +BEGIN + SELECT * INTO STRICT key + FROM pgsodium.decrypted_key v + WHERE id = key_uuid AND key_type = 'aead-det'; + + IF key.decrypted_raw_key IS NOT NULL THEN + RETURN pgsodium.crypto_aead_det_encrypt(message, additional, key.decrypted_raw_key, nonce); + END IF; + RETURN pgsodium.crypto_aead_det_encrypt(message, additional, key.key_id, key.key_context, nonce); +END; + $$ + LANGUAGE plpgsql + SECURITY DEFINER + STABLE + SET search_path=''; + +ALTER FUNCTION pgsodium.crypto_aead_det_encrypt(bytea, bytea, uuid, bytea) OWNER TO pgsodium_keymaker; +REVOKE ALL ON FUNCTION pgsodium.crypto_aead_det_encrypt(bytea, bytea, uuid, bytea) FROM PUBLIC; +GRANT EXECUTE ON FUNCTION pgsodium.crypto_aead_det_encrypt(bytea, bytea, uuid, bytea) TO pgsodium_keyiduser; + +/* + * crypto_aead_det_keygen() + */ +CREATE FUNCTION pgsodium.crypto_aead_det_keygen() + RETURNS bytea + AS '$libdir/pgsodium', 'pgsodium_crypto_aead_det_keygen' + LANGUAGE C + VOLATILE; + +-- FIXME: owner? +REVOKE ALL ON FUNCTION crypto_aead_det_keygen FROM PUBLIC; +GRANT EXECUTE ON FUNCTION crypto_aead_det_keygen TO pgsodium_keymaker; + +/* + * crypto_aead_det_noncegen() + */ +CREATE FUNCTION pgsodium.crypto_aead_det_noncegen() + RETURNS bytea + AS '$libdir/pgsodium', 'pgsodium_crypto_aead_det_noncegen' + LANGUAGE C + VOLATILE; + +-- FIXME: owner? +-- FIXME: revoke? +GRANT EXECUTE ON FUNCTION pgsodium.crypto_aead_det_noncegen() TO pgsodium_keyiduser; + +/* + * crypto_aead_ietf_decrypt(bytea, bytea, bytea, bytea) + */ +CREATE FUNCTION pgsodium.crypto_aead_ietf_decrypt(message bytea, additional bytea, nonce bytea, key bytea) + RETURNS bytea + AS '$libdir/pgsodium', 'pgsodium_crypto_aead_ietf_decrypt' + LANGUAGE C + IMMUTABLE; + +-- FIXME: owner? +REVOKE ALL ON FUNCTION crypto_aead_ietf_decrypt(bytea, bytea, bytea, bytea) FROM PUBLIC; +GRANT EXECUTE ON FUNCTION crypto_aead_ietf_decrypt(bytea, bytea, bytea, bytea) TO pgsodium_keyholder; + +/* + * crypto_aead_ietf_decrypt(bytea, bytea, bytea, bigint, bytea) + */ +CREATE FUNCTION pgsodium.crypto_aead_ietf_decrypt(message bytea, additional bytea, nonce bytea, key_id bigint, context bytea = 'pgsodium') + RETURNS bytea + AS '$libdir/pgsodium', 'pgsodium_crypto_aead_ietf_decrypt_by_id' + LANGUAGE C + IMMUTABLE; + +-- FIXME: owner? +REVOKE ALL ON FUNCTION crypto_aead_ietf_decrypt(bytea, bytea, bytea, bigint, bytea) FROM PUBLIC; +GRANT EXECUTE ON FUNCTION crypto_aead_ietf_decrypt(bytea, bytea, bytea, bigint, bytea) TO pgsodium_keyiduser; + +/* + * crypto_aead_ietf_decrypt(bytea, bytea, bytea, uuid) + */ +CREATE FUNCTION pgsodium.crypto_aead_ietf_decrypt(message bytea, additional bytea, nonce bytea, key_uuid uuid) + RETURNS bytea AS $$ +DECLARE + key pgsodium.decrypted_key; +BEGIN + SELECT * INTO STRICT key + FROM pgsodium.decrypted_key v + WHERE id = key_uuid AND key_type = 'aead-ietf'; + + IF key.decrypted_raw_key IS NOT NULL THEN + RETURN pgsodium.crypto_aead_ietf_decrypt(message, additional, nonce, key.decrypted_raw_key); + END IF; + RETURN pgsodium.crypto_aead_ietf_decrypt(message, additional, nonce, key.key_id, key.key_context); +END; + $$ + LANGUAGE plpgsql + SECURITY DEFINER + STABLE + SET search_path=''; + +ALTER FUNCTION pgsodium.crypto_aead_ietf_decrypt(bytea, bytea, bytea, uuid) OWNER TO pgsodium_keymaker; +REVOKE ALL ON FUNCTION pgsodium.crypto_aead_ietf_decrypt(bytea, bytea, bytea, uuid) FROM PUBLIC; +GRANT EXECUTE ON FUNCTION pgsodium.crypto_aead_ietf_decrypt(bytea, bytea, bytea, uuid) TO pgsodium_keyiduser; + +/* + * crypto_aead_ietf_encrypt(bytea, bytea, bytea, bytea) + */ +CREATE FUNCTION pgsodium.crypto_aead_ietf_encrypt(bytea, bytea, bytea, bytea) + RETURNS bytea + AS '$libdir/pgsodium', 'pgsodium_crypto_aead_ietf_encrypt' + LANGUAGE C + IMMUTABLE; + +-- FIXME: owner? +REVOKE ALL ON FUNCTION pgsodium.crypto_aead_ietf_encrypt(bytea, bytea, bytea, bytea) FROM PUBLIC; +GRANT EXECUTE ON FUNCTION pgsodium.crypto_aead_ietf_encrypt(bytea, bytea, bytea, bytea) TO pgsodium_keyholder; + +/* + * crypto_aead_ietf_encrypt(bytea, bytea, bytea, bigint, bytea) + */ +CREATE FUNCTION pgsodium.crypto_aead_ietf_encrypt(message bytea, additional bytea, nonce bytea, key_id bigint, context bytea = 'pgsodium') + RETURNS bytea + AS '$libdir/pgsodium', 'pgsodium_crypto_aead_ietf_encrypt_by_id' + LANGUAGE C + IMMUTABLE; + +-- FIXME: owner? +REVOKE ALL ON FUNCTION pgsodium.crypto_aead_ietf_encrypt(bytea, bytea, bytea, bigint, bytea) FROM PUBLIC; +GRANT EXECUTE ON FUNCTION pgsodium.crypto_aead_ietf_encrypt(bytea, bytea, bytea, bigint, bytea) TO pgsodium_keyiduser; + +/* + * crypto_aead_ietf_encrypt(bytea, bytea, bytea, uuid) + */ +CREATE FUNCTION pgsodium.crypto_aead_ietf_encrypt(message bytea, additional bytea, nonce bytea, key_uuid uuid) + RETURNS bytea AS $$ +DECLARE + key pgsodium.decrypted_key; +BEGIN + SELECT * INTO STRICT key + FROM pgsodium.decrypted_key v + WHERE id = key_uuid AND key_type = 'aead-ietf'; + + IF key.decrypted_raw_key IS NOT NULL THEN + RETURN pgsodium.crypto_aead_ietf_encrypt(message, additional, nonce, key.decrypted_raw_key); + END IF; + RETURN pgsodium.crypto_aead_ietf_encrypt(message, additional, nonce, key.key_id, key.key_context); +END; + $$ + LANGUAGE plpgsql + SECURITY DEFINER + STABLE + SET search_path=''; + +ALTER FUNCTION pgsodium.crypto_aead_ietf_encrypt(bytea, bytea, bytea, uuid) OWNER TO pgsodium_keymaker; +REVOKE ALL ON FUNCTION pgsodium.crypto_aead_ietf_encrypt(bytea, bytea, bytea, uuid) FROM PUBLIC; +GRANT EXECUTE ON FUNCTION pgsodium.crypto_aead_ietf_encrypt(bytea, bytea, bytea, uuid) TO pgsodium_keyiduser; + +/* + * crypto_aead_ietf_keygen() + */ +CREATE FUNCTION pgsodium.crypto_aead_ietf_keygen() + RETURNS bytea + AS '$libdir/pgsodium', 'pgsodium_crypto_aead_ietf_keygen' + LANGUAGE C + VOLATILE; + +-- FIXME: owner? +REVOKE ALL ON FUNCTION pgsodium.crypto_aead_ietf_keygen FROM PUBLIC; +GRANT EXECUTE ON FUNCTION pgsodium.crypto_aead_ietf_keygen TO pgsodium_keymaker; + +/* + * crypto_aead_ietf_noncegen() + */ +CREATE FUNCTION pgsodium.crypto_aead_ietf_noncegen() + RETURNS bytea + AS '$libdir/pgsodium', 'pgsodium_crypto_aead_ietf_noncegen' + LANGUAGE C + VOLATILE; + +-- FIXME: owner? +REVOKE ALL ON FUNCTION pgsodium.crypto_aead_ietf_noncegen FROM PUBLIC; +GRANT EXECUTE ON FUNCTION pgsodium.crypto_aead_ietf_noncegen TO pgsodium_keyiduser; + +/* + * crypto_auth(bytea, bytea) + */ +CREATE FUNCTION pgsodium.crypto_auth(message bytea, key bytea) + RETURNS bytea + AS '$libdir/pgsodium', 'pgsodium_crypto_auth' + LANGUAGE C + IMMUTABLE; + +-- FIXME: owner? +REVOKE ALL ON FUNCTION pgsodium.crypto_auth(bytea, bytea) FROM PUBLIC; +GRANT EXECUTE ON FUNCTION pgsodium.crypto_auth(bytea, bytea) TO pgsodium_keyholder; + +/* + * crypto_auth(bytea, bigint, bytea) + */ +CREATE FUNCTION pgsodium.crypto_auth(message bytea, key_id bigint, context bytea = 'pgsodium') + RETURNS bytea + AS '$libdir/pgsodium', 'pgsodium_crypto_auth_by_id' + LANGUAGE C + IMMUTABLE; + +-- FIXME: owner? +REVOKE ALL ON FUNCTION pgsodium.crypto_auth(bytea, bigint, bytea) FROM PUBLIC; +GRANT EXECUTE ON FUNCTION pgsodium.crypto_auth(bytea, bigint, bytea) TO pgsodium_keyiduser; + +/* + * crypto_auth(bytea, uuid) + */ +CREATE FUNCTION pgsodium.crypto_auth(message bytea, key_uuid uuid) + RETURNS bytea AS $$ +DECLARE + key pgsodium.decrypted_key; +BEGIN + SELECT * INTO STRICT key + FROM pgsodium.decrypted_key v + WHERE id = key_uuid AND key_type = 'auth'; + + IF key.decrypted_raw_key IS NOT NULL THEN + RETURN pgsodium.crypto_auth(message, key.decrypted_raw_key); + END IF; + RETURN pgsodium.crypto_auth(message, key.key_id, key.key_context); +END; + +$$ + LANGUAGE plpgsql + STABLE + SECURITY DEFINER + SET search_path = ''; + +ALTER FUNCTION pgsodium.crypto_auth(bytea, uuid) OWNER TO pgsodium_keymaker; +REVOKE ALL ON FUNCTION pgsodium.crypto_auth(bytea, uuid) FROM PUBLIC; +GRANT EXECUTE ON FUNCTION pgsodium.crypto_auth(bytea, uuid) TO pgsodium_keyiduser; + +/* + * crypto_auth_hmacsha256(bytea, bytea) + */ +CREATE FUNCTION pgsodium.crypto_auth_hmacsha256(message bytea, secret bytea) + RETURNS bytea + AS '$libdir/pgsodium', 'pgsodium_crypto_auth_hmacsha256' + LANGUAGE C + IMMUTABLE; + +-- FIXME: owner? +REVOKE ALL ON FUNCTION pgsodium.crypto_auth_hmacsha256(bytea, bytea) FROM PUBLIC; +GRANT EXECUTE ON FUNCTION pgsodium.crypto_auth_hmacsha256(bytea, bytea) TO pgsodium_keyholder; + +/* + * crypto_auth_hmacsha256(bytea, bigint, bytea) + */ +CREATE FUNCTION pgsodium.crypto_auth_hmacsha256(message bytea, key_id bigint, context bytea = 'pgsodium') + RETURNS bytea + AS '$libdir/pgsodium', 'pgsodium_crypto_auth_hmacsha256_by_id' + LANGUAGE C + IMMUTABLE; + +-- FIXME: owner? +REVOKE ALL ON FUNCTION pgsodium.crypto_auth_hmacsha256(bytea, bigint, bytea) FROM PUBLIC; +GRANT EXECUTE ON FUNCTION pgsodium.crypto_auth_hmacsha256(bytea, bigint, bytea) TO pgsodium_keyiduser; + +/* + * crypto_auth_hmacsha256(bytea, uuid) + */ +CREATE FUNCTION pgsodium.crypto_auth_hmacsha256(message bytea, key_uuid uuid) + RETURNS bytea AS $$ +DECLARE + key pgsodium.decrypted_key; +BEGIN + SELECT * INTO STRICT key + FROM pgsodium.decrypted_key v + WHERE id = key_uuid AND key_type = 'hmacsha256'; + + IF key.decrypted_raw_key IS NOT NULL THEN + RETURN pgsodium.crypto_auth_hmacsha256(message, key.decrypted_raw_key); + END IF; + RETURN pgsodium.crypto_auth_hmacsha256(message, key.key_id, key.key_context); +END; + +$$ + LANGUAGE plpgsql + STABLE + SECURITY DEFINER + SET search_path = ''; + +ALTER FUNCTION pgsodium.crypto_auth_hmacsha256(bytea, uuid) OWNER TO pgsodium_keymaker; +REVOKE ALL ON FUNCTION pgsodium.crypto_auth_hmacsha256(bytea, uuid) FROM PUBLIC; +GRANT EXECUTE ON FUNCTION pgsodium.crypto_auth_hmacsha256(bytea, uuid) TO pgsodium_keyiduser; + +/* + * crypto_auth_hmacsha256_keygen() + */ +CREATE FUNCTION pgsodium.crypto_auth_hmacsha256_keygen() + RETURNS bytea + AS '$libdir/pgsodium', 'pgsodium_crypto_auth_hmacsha256_keygen' + LANGUAGE C + VOLATILE; + +-- FIXME: owner? +REVOKE ALL ON FUNCTION pgsodium.crypto_auth_hmacsha256_keygen FROM PUBLIC; +GRANT EXECUTE ON FUNCTION pgsodium.crypto_auth_hmacsha256_keygen TO pgsodium_keymaker; + +/* + * crypto_auth_hmacsha256_verify(bytea, bytea, bytea) + */ +CREATE FUNCTION pgsodium.crypto_auth_hmacsha256_verify(hash bytea, message bytea, secret bytea) + RETURNS bool + AS '$libdir/pgsodium', 'pgsodium_crypto_auth_hmacsha256_verify' + LANGUAGE C + IMMUTABLE; + +-- FIXME: owner? +REVOKE ALL ON FUNCTION pgsodium.crypto_auth_hmacsha256_verify(bytea, bytea, bytea) FROM PUBLIC; +GRANT EXECUTE ON FUNCTION pgsodium.crypto_auth_hmacsha256_verify(bytea, bytea, bytea) TO pgsodium_keyholder; + +/* + * crypto_auth_hmacsha256_verify(bytea, bytea, bigint, bytea) + */ +CREATE FUNCTION pgsodium.crypto_auth_hmacsha256_verify(hash bytea, message bytea, key_id bigint, context bytea = 'pgsodium') + RETURNS bool + AS '$libdir/pgsodium', 'pgsodium_crypto_auth_hmacsha256_verify_by_id' + LANGUAGE C + IMMUTABLE; + +-- FIXME: owner? +REVOKE ALL ON FUNCTION pgsodium.crypto_auth_hmacsha256_verify(bytea, bytea, bigint, bytea) FROM PUBLIC; +GRANT EXECUTE ON FUNCTION pgsodium.crypto_auth_hmacsha256_verify(bytea, bytea, bigint, bytea) TO pgsodium_keyiduser; + +/* + * crypto_auth_hmacsha256_verify(bytea, bytea, uuid) + */ +CREATE FUNCTION pgsodium.crypto_auth_hmacsha256_verify(signature bytea, message bytea, key_uuid uuid) + RETURNS boolean AS $$ +DECLARE + key pgsodium.decrypted_key; +BEGIN + SELECT * INTO STRICT key + FROM pgsodium.decrypted_key v + WHERE id = key_uuid AND key_type = 'hmacsha256'; + + IF key.decrypted_raw_key IS NOT NULL THEN + RETURN pgsodium.crypto_auth_hmacsha256_verify(signature, message, key.decrypted_raw_key); + END IF; + RETURN pgsodium.crypto_auth_hmacsha256_verify(signature, message, key.key_id, key.key_context); +END; + +$$ + LANGUAGE plpgsql + STABLE + SECURITY DEFINER + SET search_path = ''; + +ALTER FUNCTION pgsodium.crypto_auth_hmacsha256_verify(bytea, bytea, uuid) OWNER TO pgsodium_keymaker; +REVOKE ALL ON FUNCTION pgsodium.crypto_auth_hmacsha256_verify(bytea, bytea, uuid) FROM PUBLIC; +GRANT EXECUTE ON FUNCTION pgsodium.crypto_auth_hmacsha256_verify(bytea, bytea, uuid) TO pgsodium_keyiduser; + +/* + * crypto_auth_hmacsha512(bytea, bytea) + */ +CREATE FUNCTION pgsodium.crypto_auth_hmacsha512(message bytea, secret bytea) + RETURNS bytea + AS '$libdir/pgsodium', 'pgsodium_crypto_auth_hmacsha512' + LANGUAGE C + IMMUTABLE; + +-- FIXME: owner? +REVOKE ALL ON FUNCTION pgsodium.crypto_auth_hmacsha512(bytea, bytea) FROM PUBLIC; +GRANT EXECUTE ON FUNCTION pgsodium.crypto_auth_hmacsha512(bytea, bytea) TO pgsodium_keyholder; + +/* + * crypto_auth_hmacsha512(bytea, bigint, bytea) + */ +CREATE FUNCTION pgsodium.crypto_auth_hmacsha512(message bytea, key_id bigint, context bytea = 'pgsodium') + RETURNS bytea + AS '$libdir/pgsodium', 'pgsodium_crypto_auth_hmacsha512_by_id' + LANGUAGE C + IMMUTABLE; + +-- FIXME: owner? +REVOKE ALL ON FUNCTION pgsodium.crypto_auth_hmacsha512(bytea, bigint, bytea) FROM PUBLIC; +GRANT EXECUTE ON FUNCTION pgsodium.crypto_auth_hmacsha512(bytea, bigint, bytea) TO pgsodium_keyiduser; + +/* + * crypto_auth_hmacsha512(bytea, uuid) + */ +CREATE FUNCTION pgsodium.crypto_auth_hmacsha512(message bytea, key_uuid uuid) + RETURNS bytea AS $$ +DECLARE + key pgsodium.decrypted_key; +BEGIN + SELECT * INTO STRICT key + FROM pgsodium.decrypted_key v + WHERE id = key_uuid AND key_type = 'hmacsha512'; + + IF key.decrypted_raw_key IS NOT NULL THEN + RETURN pgsodium.crypto_auth_hmacsha512(message, key.decrypted_raw_key); + END IF; + RETURN pgsodium.crypto_auth_hmacsha512(message, key.key_id, key.key_context); +END; + +$$ + LANGUAGE plpgsql + STABLE + SECURITY DEFINER + SET search_path = ''; + +ALTER FUNCTION pgsodium.crypto_auth_hmacsha512(bytea, uuid) OWNER TO pgsodium_keymaker; +REVOKE ALL ON FUNCTION pgsodium.crypto_auth_hmacsha512(bytea, uuid) FROM PUBLIC; +GRANT EXECUTE ON FUNCTION pgsodium.crypto_auth_hmacsha512(bytea, uuid) TO pgsodium_keyiduser; + +/* + * crypto_auth_hmacsha512_keygen() + */ +CREATE FUNCTION pgsodium.crypto_auth_hmacsha512_keygen() + RETURNS bytea + AS '$libdir/pgsodium', 'pgsodium_crypto_auth_hmacsha512_keygen' + LANGUAGE C + VOLATILE; + +-- FIXME: owner? +-- FIXME: revoke? +-- FIXME: grant? + +/* + * crypto_auth_hmacsha512_verify(bytea, bytea, bytea) + */ +CREATE FUNCTION pgsodium.crypto_auth_hmacsha512_verify(hash bytea, message bytea, secret bytea) + RETURNS bool + AS '$libdir/pgsodium', 'pgsodium_crypto_auth_hmacsha512_verify' + LANGUAGE C + IMMUTABLE; + +-- FIXME: owner? +REVOKE ALL ON FUNCTION pgsodium.crypto_auth_hmacsha512_verify(bytea, bytea, bytea) FROM PUBLIC; +GRANT EXECUTE ON FUNCTION pgsodium.crypto_auth_hmacsha512_verify(bytea, bytea, bytea) TO pgsodium_keyholder; + +/* + * crypto_auth_hmacsha512_verify(bytea, bytea, bigint, bytea) + */ +CREATE FUNCTION pgsodium.crypto_auth_hmacsha512_verify(hash bytea, message bytea, key_id bigint, context bytea = 'pgsodium') + RETURNS bool + AS '$libdir/pgsodium', 'pgsodium_crypto_auth_hmacsha512_verify_by_id' + LANGUAGE C + IMMUTABLE; + +-- FIXME: owner? +REVOKE ALL ON FUNCTION pgsodium.crypto_auth_hmacsha512_verify(bytea, bytea, bigint, bytea) FROM PUBLIC; +GRANT EXECUTE ON FUNCTION pgsodium.crypto_auth_hmacsha512_verify(bytea, bytea, bigint, bytea) TO pgsodium_keyiduser; + +/* + * crypto_auth_hmacsha512_verify(bytea, bytea, uuid) + */ +CREATE FUNCTION pgsodium.crypto_auth_hmacsha512_verify(signature bytea, message bytea, key_uuid uuid) + RETURNS boolean AS $$ +DECLARE + key pgsodium.decrypted_key; +BEGIN + SELECT * INTO STRICT key + FROM pgsodium.decrypted_key v + WHERE id = key_uuid AND key_type = 'hmacsha512'; + + IF key.decrypted_raw_key IS NOT NULL THEN + RETURN pgsodium.crypto_auth_hmacsha512_verify(signature, message, key.decrypted_raw_key); + END IF; + RETURN pgsodium.crypto_auth_hmacsha512_verify(signature, message, key.key_id, key.key_context); +END; + +$$ + LANGUAGE plpgsql + STABLE + SECURITY DEFINER + SET search_path = ''; + +ALTER FUNCTION pgsodium.crypto_auth_hmacsha512_verify(bytea, bytea, uuid) OWNER TO pgsodium_keymaker; +REVOKE ALL ON FUNCTION pgsodium.crypto_auth_hmacsha512_verify(bytea, bytea, uuid) FROM PUBLIC; +GRANT EXECUTE ON FUNCTION pgsodium.crypto_auth_hmacsha512_verify(bytea, bytea, uuid) TO pgsodium_keyiduser; + +/* + * crypto_auth_keygen() + */ +CREATE FUNCTION pgsodium.crypto_auth_keygen() + RETURNS bytea + AS '$libdir/pgsodium', 'pgsodium_crypto_auth_keygen' + LANGUAGE C + VOLATILE; + +-- FIXME: owner? +REVOKE ALL ON FUNCTION pgsodium.crypto_auth_keygen FROM PUBLIC; +GRANT EXECUTE ON FUNCTION pgsodium.crypto_auth_keygen TO pgsodium_keymaker; + +/* + * crypto_auth_verify(bytea, bytea, bytea) + */ +CREATE FUNCTION pgsodium.crypto_auth_verify(mac bytea, message bytea, key bytea) + RETURNS boolean + AS '$libdir/pgsodium', 'pgsodium_crypto_auth_verify' + LANGUAGE C + IMMUTABLE; + +-- FIXME: owner? +REVOKE ALL ON FUNCTION pgsodium.crypto_auth_verify(bytea, bytea, bytea) FROM PUBLIC; +GRANT EXECUTE ON FUNCTION pgsodium.crypto_auth_verify(bytea, bytea, bytea) TO pgsodium_keyholder; + +/* + * crypto_auth_verify(bytea, bytea, bigint, bytea) + */ +CREATE FUNCTION pgsodium.crypto_auth_verify(mac bytea, message bytea, key_id bigint, context bytea = 'pgsodium') + RETURNS boolean + AS '$libdir/pgsodium', 'pgsodium_crypto_auth_verify_by_id' + LANGUAGE C + IMMUTABLE; + +-- FIXME: owner? +REVOKE ALL ON FUNCTION pgsodium.crypto_auth_verify(bytea, bytea, bigint, bytea) FROM PUBLIC; +GRANT EXECUTE ON FUNCTION pgsodium.crypto_auth_verify(bytea, bytea, bigint, bytea) TO pgsodium_keyiduser; + +/* + * crypto_auth_verify(bytea, bytea, uuid) + */ +CREATE FUNCTION pgsodium.crypto_auth_verify(mac bytea, message bytea, key_uuid uuid) + RETURNS boolean AS $$ +DECLARE + key pgsodium.decrypted_key; +BEGIN + SELECT * INTO STRICT key + FROM pgsodium.decrypted_key v + WHERE id = key_uuid AND key_type = 'auth'; + + IF key.decrypted_raw_key IS NOT NULL THEN + RETURN pgsodium.crypto_auth_verify(mac, message, key.decrypted_raw_key); + END IF; + RETURN pgsodium.crypto_auth_verify(mac, message, key.key_id, key.key_context); +END; + +$$ + LANGUAGE plpgsql + STABLE + SECURITY DEFINER + SET search_path = ''; + +ALTER FUNCTION pgsodium.crypto_auth_verify(bytea, bytea, uuid) OWNER TO pgsodium_keymaker; +REVOKE ALL ON FUNCTION pgsodium.crypto_auth_verify(bytea, bytea, uuid) FROM PUBLIC; +GRANT EXECUTE ON FUNCTION pgsodium.crypto_auth_verify(bytea, bytea, uuid) TO pgsodium_keyiduser; + +/* + * crypto_box(bytea, bytea, bytea, bytea) + */ +CREATE FUNCTION pgsodium.crypto_box(message bytea, nonce bytea, public bytea, secret bytea) + RETURNS bytea + AS '$libdir/pgsodium', 'pgsodium_crypto_box' + LANGUAGE C + IMMUTABLE; + +-- FIXME: owner? +REVOKE ALL ON FUNCTION pgsodium.crypto_box FROM PUBLIC; +GRANT EXECUTE ON FUNCTION pgsodium.crypto_box TO pgsodium_keyholder; + +/* + * crypto_box_new_keypair() + */ +CREATE FUNCTION pgsodium.crypto_box_new_keypair() + RETURNS crypto_box_keypair + AS '$libdir/pgsodium', 'pgsodium_crypto_box_keypair' + LANGUAGE C + VOLATILE; + +-- FIXME: owner? +REVOKE ALL ON FUNCTION pgsodium.crypto_box_new_keypair FROM PUBLIC; +GRANT EXECUTE ON FUNCTION pgsodium.crypto_box_new_keypair TO pgsodium_keymaker; + +/* + * crypto_box_new_seed() + */ +CREATE FUNCTION pgsodium.crypto_box_new_seed() + RETURNS bytea + AS '$libdir/pgsodium', 'pgsodium_crypto_box_new_seed' + LANGUAGE C + VOLATILE; + +-- FIXME: owner? +-- FIXME: revoke? +-- FIXME: grant? + +/* + * crypto_box_noncegen() + */ +CREATE FUNCTION pgsodium.crypto_box_noncegen() + RETURNS bytea + AS '$libdir/pgsodium', 'pgsodium_crypto_box_noncegen' + LANGUAGE C + VOLATILE; + +-- FIXME: owner? +REVOKE ALL ON FUNCTION pgsodium.crypto_box_noncegen FROM PUBLIC; +GRANT EXECUTE ON FUNCTION pgsodium.crypto_box_noncegen TO pgsodium_keymaker; + +/* + * crypto_box_open(bytea, bytea, bytea, bytea) + */ +CREATE FUNCTION pgsodium.crypto_box_open(ciphertext bytea, nonce bytea, public bytea, secret bytea) + RETURNS bytea + AS '$libdir/pgsodium', 'pgsodium_crypto_box_open' + LANGUAGE C + IMMUTABLE; + +-- FIXME: owner? +REVOKE ALL ON FUNCTION pgsodium.crypto_box_open FROM PUBLIC; +GRANT EXECUTE ON FUNCTION pgsodium.crypto_box_open TO pgsodium_keyholder; + +/* + * crypto_box_seal(bytea, bytea) + */ +CREATE FUNCTION pgsodium.crypto_box_seal(message bytea, public_key bytea) + RETURNS bytea + AS '$libdir/pgsodium', 'pgsodium_crypto_box_seal' + LANGUAGE C + IMMUTABLE; + +-- FIXME: owner? +-- FIXME: revoke? +-- FIXME: grant? + +/* + * crypto_box_seal_open(bytea, bytea, bytea) + */ +CREATE FUNCTION pgsodium.crypto_box_seal_open(ciphertext bytea, public_key bytea, secret_key bytea) + RETURNS bytea + AS '$libdir/pgsodium', 'pgsodium_crypto_box_seal_open' + LANGUAGE C + IMMUTABLE; + +-- FIXME: owner? +-- FIXME: revoke? +-- FIXME: grant? + +/* + * crypto_box_seed_new_keypair(bytea) + */ +CREATE FUNCTION pgsodium.crypto_box_seed_new_keypair(seed bytea) + RETURNS pgsodium.crypto_box_keypair + AS '$libdir/pgsodium', 'pgsodium_crypto_box_seed_keypair' + LANGUAGE C + IMMUTABLE; + +-- FIXME: owner? +REVOKE ALL ON FUNCTION pgsodium.crypto_box_seed_new_keypair FROM PUBLIC; +GRANT EXECUTE ON FUNCTION pgsodium.crypto_box_seed_new_keypair TO pgsodium_keymaker; + +/* + * crypto_cmp(text, text) + */ +CREATE FUNCTION pgsodium.crypto_cmp(text, text) + RETURNS bool + AS '$libdir/pgsodium', 'pgsodium_cmp' + LANGUAGE C + IMMUTABLE + STRICT; + +-- FIXME: owner? +-- FIXME: revoke? +-- FIXME: grant? + +/* + * crypto_generichash(bytea, bytea) + */ +CREATE FUNCTION pgsodium.crypto_generichash(message bytea, key bytea DEFAULT NULL) + RETURNS bytea + AS '$libdir/pgsodium', 'pgsodium_crypto_generichash' + LANGUAGE C + IMMUTABLE; + +-- FIXME: owner? +-- FIXME: revoke? +-- FIXME: grant? + +REVOKE ALL ON FUNCTION pgsodium.crypto_generichash(bytea, bytea) FROM PUBLIC; +GRANT EXECUTE ON FUNCTION pgsodium.crypto_generichash(bytea, bytea) TO pgsodium_keyiduser; + +/* + * crypto_generichash(bytea, bigint, bytea) + */ +CREATE FUNCTION pgsodium.crypto_generichash(message bytea, key bigint, context bytea = 'pgsodium') + RETURNS bytea + AS '$libdir/pgsodium', 'pgsodium_crypto_generichash_by_id' + LANGUAGE C + IMMUTABLE; + +-- FIXME: owner? +-- FIXME: revoke? +-- FIXME: grant? + +/* + * crypto_generichash(bytea, uuid) + */ +CREATE FUNCTION pgsodium.crypto_generichash(message bytea, key_uuid uuid) + RETURNS bytea AS $$ +DECLARE + key pgsodium.decrypted_key; +BEGIN + SELECT * INTO STRICT key + FROM pgsodium.decrypted_key v + WHERE id = key_uuid AND key_type = 'generichash'; + + IF key.decrypted_raw_key IS NOT NULL THEN + RETURN pgsodium.crypto_generichash(message, key.decrypted_raw_key); + END IF; + RETURN pgsodium.crypto_generichash(message, key.key_id, key.key_context); +END; + +$$ + LANGUAGE plpgsql + STABLE + SECURITY DEFINER + SET search_path = ''; + +ALTER FUNCTION pgsodium.crypto_generichash(bytea, uuid) OWNER TO pgsodium_keymaker; +REVOKE ALL ON FUNCTION pgsodium.crypto_generichash(bytea, uuid) FROM PUBLIC; +GRANT EXECUTE ON FUNCTION pgsodium.crypto_generichash(bytea, uuid) TO pgsodium_keyiduser; + +/* + * crypto_generichash_keygen() + */ +CREATE FUNCTION pgsodium.crypto_generichash_keygen() + RETURNS bytea + AS '$libdir/pgsodium', 'pgsodium_crypto_generichash_keygen' + LANGUAGE C + VOLATILE; + +-- FIXME: owner? +REVOKE ALL ON FUNCTION pgsodium.crypto_generichash_keygen FROM PUBLIC; +GRANT EXECUTE ON FUNCTION pgsodium.crypto_generichash_keygen TO pgsodium_keymaker; + +/* + * crypto_hash_sha256(bytea) + */ +CREATE FUNCTION pgsodium.crypto_hash_sha256(message bytea) + RETURNS bytea + AS '$libdir/pgsodium', 'pgsodium_crypto_hash_sha256' + LANGUAGE C IMMUTABLE; + +-- FIXME: owner? +-- FIXME: revoke? +-- FIXME: grant? + +/* + * crypto_hash_sha512(bytea) + */ +CREATE FUNCTION pgsodium.crypto_hash_sha512(message bytea) + RETURNS bytea + AS '$libdir/pgsodium', 'pgsodium_crypto_hash_sha512' + LANGUAGE C IMMUTABLE; + +-- FIXME: owner? +-- FIXME: revoke? +-- FIXME: grant? + +/* + * crypto_kdf_derive_from_key(bigint, bigint, bytea, bytea) + */ +CREATE FUNCTION pgsodium.crypto_kdf_derive_from_key(subkey_size bigint, subkey_id bigint, context bytea, primary_key bytea) + RETURNS bytea + AS '$libdir/pgsodium', 'pgsodium_crypto_kdf_derive_from_key' + LANGUAGE C IMMUTABLE; + +-- FIXME: owner? +REVOKE ALL ON FUNCTION pgsodium.crypto_kdf_derive_from_key(bigint, bigint, bytea, bytea) FROM PUBLIC; +GRANT EXECUTE ON FUNCTION pgsodium.crypto_kdf_derive_from_key(bigint, bigint, bytea, bytea) TO pgsodium_keymaker; + +/* + * crypto_kdf_derive_from_key(integer, bigint, bytea, uuid) + */ +CREATE FUNCTION pgsodium.crypto_kdf_derive_from_key(subkey_size integer, subkey_id bigint, context bytea, primary_key uuid) + RETURNS bytea + AS $$ +DECLARE + key pgsodium.decrypted_key; +BEGIN + SELECT * INTO STRICT key + FROM pgsodium.decrypted_key v + WHERE id = primary_key AND key_type = 'kdf'; + + IF key.decrypted_raw_key IS NOT NULL THEN + RETURN pgsodium.crypto_kdf_derive_from_key(subkey_size, subkey_id, context, key.decrypted_raw_key); + END IF; + RETURN pgsodium.derive_key(key.key_id, subkey_size, key.key_context); +END; + +$$ + LANGUAGE plpgsql + STRICT STABLE + SECURITY DEFINER + SET search_path = ''; + +ALTER FUNCTION pgsodium.crypto_kdf_derive_from_key(integer, bigint, bytea, uuid) OWNER TO pgsodium_keymaker; +REVOKE ALL ON FUNCTION pgsodium.crypto_kdf_derive_from_key(integer, bigint, bytea, uuid) FROM PUBLIC; +GRANT EXECUTE ON FUNCTION pgsodium.crypto_kdf_derive_from_key(integer, bigint, bytea, uuid) TO pgsodium_keyiduser; + + +/* + * crypto_kdf_keygen() + */ +CREATE FUNCTION pgsodium.crypto_kdf_keygen() + RETURNS bytea + AS '$libdir/pgsodium', 'pgsodium_crypto_kdf_keygen' + LANGUAGE C VOLATILE; + +-- FIXME: owner? +REVOKE ALL ON FUNCTION pgsodium.crypto_kdf_keygen FROM PUBLIC; +GRANT EXECUTE ON FUNCTION pgsodium.crypto_kdf_keygen TO pgsodium_keymaker; + +/* + * crypto_kx_client_session_keys(bytea, bytea, bytea) + */ +CREATE FUNCTION pgsodium.crypto_kx_client_session_keys(client_pk bytea, client_sk bytea, server_pk bytea) + RETURNS crypto_kx_session + AS '$libdir/pgsodium', 'pgsodium_crypto_kx_client_session_keys' + LANGUAGE C VOLATILE; + +-- FIXME: owner? +-- FIXME: revoke? +-- FIXME: grant? + +/* + * crypto_kx_new_keypair() + */ +CREATE FUNCTION pgsodium.crypto_kx_new_keypair() + RETURNS crypto_kx_keypair + AS '$libdir/pgsodium', 'pgsodium_crypto_kx_keypair' + LANGUAGE C VOLATILE; + +-- FIXME: owner? +REVOKE ALL ON FUNCTION pgsodium.crypto_kx_new_keypair FROM PUBLIC; +GRANT EXECUTE ON FUNCTION pgsodium.crypto_kx_new_keypair TO pgsodium_keymaker; + +/* + * crypto_kx_new_seed() + */ +CREATE FUNCTION pgsodium.crypto_kx_new_seed() + RETURNS bytea + AS '$libdir/pgsodium', 'pgsodium_crypto_kx_new_seed' + LANGUAGE C VOLATILE; + +-- FIXME: owner? +REVOKE ALL ON FUNCTION pgsodium.crypto_kx_new_seed FROM PUBLIC; +GRANT EXECUTE ON FUNCTION pgsodium.crypto_kx_new_seed TO pgsodium_keymaker; + +/* + * crypto_kx_seed_new_keypair(bytea) + */ +CREATE FUNCTION pgsodium.crypto_kx_seed_new_keypair(seed bytea) + RETURNS crypto_kx_keypair + AS '$libdir/pgsodium', 'pgsodium_crypto_kx_seed_keypair' + LANGUAGE C IMMUTABLE + STRICT; + +-- FIXME: owner? +REVOKE ALL ON FUNCTION pgsodium.crypto_kx_seed_new_keypair FROM PUBLIC; +GRANT EXECUTE ON FUNCTION pgsodium.crypto_kx_seed_new_keypair TO pgsodium_keymaker; + +/* + * crypto_kx_server_session_keys(bytea, bytea, bytea) + */ +CREATE FUNCTION pgsodium.crypto_kx_server_session_keys(server_pk bytea, server_sk bytea, client_pk bytea) + RETURNS crypto_kx_session + AS '$libdir/pgsodium', 'pgsodium_crypto_kx_server_session_keys' + LANGUAGE C + VOLATILE; + +-- FIXME: owner? +-- FIXME: revoke? +-- FIXME: grant? + +/* + * crypto_pwhash(bytea, bytea) + */ +CREATE FUNCTION pgsodium.crypto_pwhash(password bytea, salt bytea) + RETURNS bytea + AS '$libdir/pgsodium', 'pgsodium_crypto_pwhash' + LANGUAGE C IMMUTABLE; + +-- FIXME: owner? +-- FIXME: revoke? +-- FIXME: grant? + +/* + * crypto_pwhash_saltgen() + */ +CREATE FUNCTION pgsodium.crypto_pwhash_saltgen() + RETURNS bytea + AS '$libdir/pgsodium', 'pgsodium_crypto_pwhash_saltgen' + LANGUAGE C VOLATILE; + +-- FIXME: owner? +-- FIXME: revoke? +-- FIXME: grant? + +/* + * crypto_pwhash_str(bytea) + */ +CREATE FUNCTION pgsodium.crypto_pwhash_str(password bytea) + RETURNS bytea + AS '$libdir/pgsodium', 'pgsodium_crypto_pwhash_str' + LANGUAGE C VOLATILE; + +-- FIXME: owner? +-- FIXME: revoke? +-- FIXME: grant? + +/* + * crypto_pwhash_str_verify(bytea, bytea) + */ +CREATE FUNCTION pgsodium.crypto_pwhash_str_verify(hashed_password bytea, password bytea) + RETURNS bool + AS '$libdir/pgsodium', 'pgsodium_crypto_pwhash_str_verify' + LANGUAGE C IMMUTABLE; + +-- FIXME: owner? +-- FIXME: revoke? +-- FIXME: grant? + +/* + * crypto_secretbox(bytea, bytea, bytea) + */ +CREATE FUNCTION pgsodium.crypto_secretbox(message bytea, nonce bytea, key bytea) + RETURNS bytea + AS '$libdir/pgsodium', 'pgsodium_crypto_secretbox' + LANGUAGE C IMMUTABLE; + +-- FIXME: owner? +REVOKE ALL ON FUNCTION pgsodium.crypto_secretbox(bytea, bytea, bytea) FROM PUBLIC; +GRANT EXECUTE ON FUNCTION pgsodium.crypto_secretbox(bytea, bytea, bytea) TO pgsodium_keyholder; + +/* + * crypto_secretbox(bytea, bytea, bigint, bytea) + */ +CREATE FUNCTION pgsodium.crypto_secretbox(message bytea, nonce bytea, key_id bigint, context bytea = 'pgsodium') + RETURNS bytea + AS '$libdir/pgsodium', 'pgsodium_crypto_secretbox_by_id' + LANGUAGE C IMMUTABLE; + +-- FIXME: owner? +REVOKE ALL ON FUNCTION pgsodium.crypto_secretbox(bytea, bytea, bigint, bytea) FROM PUBLIC; +GRANT EXECUTE ON FUNCTION pgsodium.crypto_secretbox(bytea, bytea, bigint, bytea) TO pgsodium_keyiduser; + +/* + * crypto_secretbox(bytea, bytea, uuid) + */ +CREATE FUNCTION pgsodium.crypto_secretbox(message bytea, nonce bytea, key_uuid uuid) + RETURNS bytea AS + $$ +DECLARE + key pgsodium.decrypted_key; +BEGIN + SELECT * INTO STRICT key + FROM pgsodium.decrypted_key v + WHERE id = key_uuid AND key_type = 'secretbox'; + + IF key.decrypted_raw_key IS NOT NULL THEN + RETURN pgsodium.crypto_secretbox(message, nonce, key.decrypted_raw_key); + END IF; + RETURN pgsodium.crypto_secretbox(message, nonce, key.key_id, key.key_context); +END; +$$ + LANGUAGE plpgsql + STABLE + SECURITY DEFINER + SET search_path = ''; + +ALTER FUNCTION pgsodium.crypto_secretbox(bytea, bytea, uuid) OWNER TO pgsodium_keymaker; +REVOKE ALL ON FUNCTION pgsodium.crypto_secretbox(bytea, bytea, uuid) FROM PUBLIC; +GRANT EXECUTE ON FUNCTION pgsodium.crypto_secretbox(bytea, bytea, uuid) TO pgsodium_keyiduser; + +/* + * crypto_secretbox_keygen() + */ +CREATE FUNCTION pgsodium.crypto_secretbox_keygen() + RETURNS bytea + AS '$libdir/pgsodium', 'pgsodium_crypto_secretbox_keygen' + LANGUAGE C VOLATILE; + +-- FIXME: owner? +REVOKE ALL ON FUNCTION pgsodium.crypto_secretbox_keygen FROM PUBLIC; +GRANT EXECUTE ON FUNCTION pgsodium.crypto_secretbox_keygen TO pgsodium_keymaker; + +/* + * crypto_secretbox_noncegen() + */ +CREATE FUNCTION pgsodium.crypto_secretbox_noncegen() + RETURNS bytea + AS '$libdir/pgsodium', 'pgsodium_crypto_secretbox_noncegen' + LANGUAGE C VOLATILE; + +-- FIXME: owner? +REVOKE ALL ON FUNCTION pgsodium.crypto_secretbox_noncegen FROM PUBLIC; +GRANT EXECUTE ON FUNCTION pgsodium.crypto_secretbox_noncegen TO pgsodium_keyiduser; + +/* + * crypto_secretbox_open(bytea, bytea, bytea) + */ +CREATE FUNCTION pgsodium.crypto_secretbox_open(ciphertext bytea, nonce bytea, key bytea) + RETURNS bytea + AS '$libdir/pgsodium', 'pgsodium_crypto_secretbox_open' + LANGUAGE C IMMUTABLE; + +-- FIXME: owner? +REVOKE ALL ON FUNCTION pgsodium.crypto_secretbox_open(bytea, bytea, bytea) FROM PUBLIC; +GRANT EXECUTE ON FUNCTION pgsodium.crypto_secretbox_open(bytea, bytea, bytea) TO pgsodium_keyholder; + +/* + * crypto_secretbox_open(bytea, bytea, bigint, bytea) + */ +CREATE FUNCTION pgsodium.crypto_secretbox_open(message bytea, nonce bytea, key_id bigint, context bytea = 'pgsodium') + RETURNS bytea + AS '$libdir/pgsodium', 'pgsodium_crypto_secretbox_open_by_id' + LANGUAGE C IMMUTABLE; + +-- FIXME: owner? +REVOKE ALL ON FUNCTION pgsodium.crypto_secretbox_open(bytea, bytea, bigint, bytea) FROM PUBLIC; +GRANT EXECUTE ON FUNCTION pgsodium.crypto_secretbox_open(bytea, bytea, bigint, bytea) TO pgsodium_keyiduser; + +/* + * crypto_secretbox_open(bytea, bytea, uuid) + */ +CREATE FUNCTION pgsodium.crypto_secretbox_open(message bytea, nonce bytea, key_uuid uuid) + RETURNS bytea AS + $$ +DECLARE + key pgsodium.decrypted_key; +BEGIN + SELECT * INTO STRICT key + FROM pgsodium.decrypted_key v + WHERE id = key_uuid AND key_type = 'secretbox'; + + IF key.decrypted_raw_key IS NOT NULL THEN + RETURN pgsodium.crypto_secretbox_open(message, nonce, key.decrypted_raw_key); + END IF; + RETURN pgsodium.crypto_secretbox_open(message, nonce, key.key_id, key.key_context); +END; +$$ + LANGUAGE plpgsql + STABLE + SECURITY DEFINER + SET search_path = ''; + +ALTER FUNCTION pgsodium.crypto_secretbox_open(bytea, bytea, uuid) OWNER TO pgsodium_keymaker; +REVOKE ALL ON FUNCTION pgsodium.crypto_secretbox_open(bytea, bytea, uuid) FROM PUBLIC; +GRANT EXECUTE ON FUNCTION pgsodium.crypto_secretbox_open(bytea, bytea, uuid) TO pgsodium_keyiduser; + +/* + * crypto_secretstream_keygen() + */ +CREATE FUNCTION pgsodium.crypto_secretstream_keygen() + RETURNS bytea + AS '$libdir/pgsodium', 'pgsodium_crypto_secretstream_xchacha20poly1305_keygen' + LANGUAGE C + VOLATILE; + +-- FIXME: owner? +-- FIXME: revoke? +-- FIXME: grant? + +/* + * crypto_shorthash(bytea, bytea) + */ +CREATE FUNCTION pgsodium.crypto_shorthash(message bytea, key bytea) + RETURNS bytea + AS '$libdir/pgsodium', 'pgsodium_crypto_shorthash' + LANGUAGE C + IMMUTABLE; + +-- FIXME: owner? +REVOKE ALL ON FUNCTION pgsodium.crypto_shorthash(bytea, bytea) FROM PUBLIC; +GRANT EXECUTE ON FUNCTION pgsodium.crypto_shorthash(bytea, bytea) TO pgsodium_keyiduser; + +/* + * crypto_shorthash(bytea, bigint, bytea) + */ +CREATE FUNCTION pgsodium.crypto_shorthash(message bytea, key bigint, context bytea = 'pgsodium') + RETURNS bytea + AS '$libdir/pgsodium', 'pgsodium_crypto_shorthash_by_id' + LANGUAGE C + IMMUTABLE; + +-- FIXME: owner? +-- FIXME: revoke? +-- FIXME: grant? + +/* + * crypto_shorthash(bytea, uuid) + */ +CREATE FUNCTION pgsodium.crypto_shorthash(message bytea, key_uuid uuid) + RETURNS bytea AS + $$ +DECLARE + key pgsodium.decrypted_key; +BEGIN + SELECT * INTO STRICT key + FROM pgsodium.decrypted_key v + WHERE id = key_uuid AND key_type = 'shorthash'; + + IF key.decrypted_raw_key IS NOT NULL THEN + RETURN pgsodium.crypto_shorthash(message, key.decrypted_raw_key); + END IF; + RETURN pgsodium.crypto_shorthash(message, key.key_id, key.key_context); +END; + +$$ + LANGUAGE plpgsql + STABLE + SECURITY DEFINER + SET search_path = ''; + +ALTER FUNCTION pgsodium.crypto_shorthash(bytea, uuid) OWNER TO pgsodium_keymaker; +REVOKE ALL ON FUNCTION pgsodium.crypto_shorthash(bytea, uuid) FROM PUBLIC; +GRANT EXECUTE ON FUNCTION pgsodium.crypto_shorthash(bytea, uuid) TO pgsodium_keyiduser; + +/* + * crypto_shorthash_keygen() + */ +CREATE FUNCTION pgsodium.crypto_shorthash_keygen() + RETURNS bytea + AS '$libdir/pgsodium', 'pgsodium_crypto_shorthash_keygen' + LANGUAGE C + VOLATILE; + +-- FIXME: owner? +REVOKE ALL ON FUNCTION pgsodium.crypto_shorthash_keygen FROM PUBLIC; +GRANT EXECUTE ON FUNCTION pgsodium.crypto_shorthash_keygen TO pgsodium_keymaker; + +/* + * crypto_sign(bytea, bytea) + */ +CREATE FUNCTION pgsodium.crypto_sign(message bytea, key bytea) + RETURNS bytea + AS '$libdir/pgsodium', 'pgsodium_crypto_sign' + LANGUAGE C + IMMUTABLE; + +-- FIXME: owner? +-- FIXME: revoke? +-- FIXME: grant? + +/* + * crypto_sign_detached(bytea, bytea) + */ +CREATE FUNCTION pgsodium.crypto_sign_detached(message bytea, key bytea) + RETURNS bytea + AS '$libdir/pgsodium', 'pgsodium_crypto_sign_detached' + LANGUAGE C + IMMUTABLE; + +-- FIXME: owner? +-- FIXME: revoke? +-- FIXME: grant? + +/* + * crypto_sign_final_create(bytea, bytea) + */ +CREATE FUNCTION pgsodium.crypto_sign_final_create(state bytea, key bytea) + RETURNS bytea + AS '$libdir/pgsodium', 'pgsodium_crypto_sign_final_create' + LANGUAGE C + IMMUTABLE; + +-- FIXME: owner? +REVOKE ALL ON FUNCTION pgsodium.crypto_sign_final_create FROM PUBLIC; +GRANT EXECUTE ON FUNCTION pgsodium.crypto_sign_final_create TO pgsodium_keyholder; + +/* + * crypto_sign_final_verify(bytea, bytea, bytea) + */ +CREATE FUNCTION pgsodium.crypto_sign_final_verify(state bytea, signature bytea, key bytea) + RETURNS boolean + AS '$libdir/pgsodium', 'pgsodium_crypto_sign_final_verify' + LANGUAGE C + IMMUTABLE; + +-- FIXME: owner? +REVOKE ALL ON FUNCTION pgsodium.crypto_sign_final_verify FROM PUBLIC; +GRANT EXECUTE ON FUNCTION pgsodium.crypto_sign_final_verify TO pgsodium_keyholder; + +/* + * crypto_sign_init() + */ +CREATE FUNCTION pgsodium.crypto_sign_init() + RETURNS bytea + AS '$libdir/pgsodium', 'pgsodium_crypto_sign_init' + LANGUAGE C + IMMUTABLE + STRICT; + +-- FIXME: owner? +REVOKE ALL ON FUNCTION pgsodium.crypto_sign_init FROM PUBLIC; +GRANT EXECUTE ON FUNCTION pgsodium.crypto_sign_init TO pgsodium_keyholder; + +/* + * crypto_sign_new_keypair() + */ +CREATE FUNCTION pgsodium.crypto_sign_new_keypair() + RETURNS crypto_sign_keypair + AS '$libdir/pgsodium', 'pgsodium_crypto_sign_keypair' + LANGUAGE C + VOLATILE; + +-- FIXME: owner? +REVOKE ALL ON FUNCTION pgsodium.crypto_sign_new_keypair FROM PUBLIC; +GRANT EXECUTE ON FUNCTION pgsodium.crypto_sign_new_keypair TO pgsodium_keymaker; + +/* + * crypto_sign_new_seed() + */ +CREATE FUNCTION pgsodium.crypto_sign_new_seed() + RETURNS bytea + AS '$libdir/pgsodium', 'pgsodium_crypto_sign_new_seed' + LANGUAGE C + VOLATILE; + +-- FIXME: owner? +-- FIXME: revoke? +-- FIXME: grant? + +/* + * crypto_sign_open(bytea, bytea) + */ +CREATE FUNCTION pgsodium.crypto_sign_open(signed_message bytea, key bytea) + RETURNS bytea + AS '$libdir/pgsodium', 'pgsodium_crypto_sign_open' + LANGUAGE C + IMMUTABLE; + +-- FIXME: owner? +-- FIXME: revoke? +-- FIXME: grant? + +/* + * crypto_sign_seed_new_keypair(bytea) + */ +CREATE FUNCTION pgsodium.crypto_sign_seed_new_keypair(seed bytea) + RETURNS crypto_sign_keypair + AS '$libdir/pgsodium', 'pgsodium_crypto_sign_seed_keypair' + LANGUAGE C + IMMUTABLE; + +-- FIXME: owner? +-- FIXME: revoke? +-- FIXME: grant? + +/* + * crypto_sign_update(bytea, bytea) + */ +CREATE FUNCTION pgsodium.crypto_sign_update(state bytea, message bytea) + RETURNS bytea + AS '$libdir/pgsodium', 'pgsodium_crypto_sign_update' + LANGUAGE C + IMMUTABLE; + +-- FIXME: owner? +REVOKE ALL ON FUNCTION pgsodium.crypto_sign_update FROM PUBLIC; +GRANT EXECUTE ON FUNCTION pgsodium.crypto_sign_update TO pgsodium_keyholder; + +/* + * crypto_sign_update_agg1(bytea, bytea) + */ +CREATE FUNCTION pgsodium.crypto_sign_update_agg1(state bytea, message bytea) + RETURNS bytea + AS $$ + SELECT pgsodium.crypto_sign_update(COALESCE(state, pgsodium.crypto_sign_init()), message); +$$ + LANGUAGE SQL + IMMUTABLE; + +-- FIXME: owner? +REVOKE ALL ON FUNCTION pgsodium.crypto_sign_update_agg1 FROM PUBLIC; +GRANT EXECUTE ON FUNCTION pgsodium.crypto_sign_update_agg1 TO pgsodium_keyholder; + +COMMENT ON FUNCTION pgsodium.crypto_sign_update_agg1(bytea, bytea) IS +'Internal helper function for crypto_sign_update_agg(bytea). This +initializes state if it has not already been initialized.'; + +/* + * pgsodium.crypto_sign_update_agg2(cur_state bytea, bytea, bytea) + */ +CREATE FUNCTION pgsodium.crypto_sign_update_agg2(cur_state bytea, + initial_state bytea, + message bytea) + RETURNS bytea + AS $$ + SELECT pgsodium.crypto_sign_update( + COALESCE(cur_state, initial_state), + message) +$$ + LANGUAGE SQL + IMMUTABLE; + +-- FIXME: owner? +REVOKE ALL ON FUNCTION pgsodium.crypto_sign_update_agg2 FROM PUBLIC; +GRANT EXECUTE ON FUNCTION pgsodium.crypto_sign_update_agg2 TO pgsodium_keyholder; + +COMMENT ON FUNCTION pgsodium.crypto_sign_update_agg2(bytea, bytea, bytea) IS +'Internal helper function for crypto_sign_update_agg(bytea, bytea). This +initializes state to the state passed to the aggregate as a parameter, +if it has not already been initialized.'; + +CREATE AGGREGATE pgsodium.crypto_sign_update_agg(message bytea) + ( + SFUNC = pgsodium.crypto_sign_update_agg1, + STYPE = bytea, + PARALLEL = unsafe); + +COMMENT ON AGGREGATE pgsodium.crypto_sign_update_agg(bytea) IS +'Multi-part message signing aggregate that returns a state which can +then be finalised using crypto_sign_final() or to which other parts +can be added crypto_sign_update() or another message signing aggregate +function. + +Note that when signing mutli-part messages using aggregates, the order +in which message parts is processed is critical. You *must* ensure +that the order of messages passed to the aggregate is invariant.'; + +CREATE AGGREGATE pgsodium.crypto_sign_update_agg(state bytea, message bytea) + ( + SFUNC = pgsodium.crypto_sign_update_agg2, + STYPE = bytea, + PARALLEL = unsafe); + +COMMENT ON AGGREGATE pgsodium.crypto_sign_update_agg(bytea, bytea) IS +'Multi-part message signing aggregate that returns a state which can +then be finalised using crypto_sign_final() or to which other parts +can be added crypto_sign_update() or another message signing aggregate +function. + +The first argument to this aggregate is the input state. This may be +the result of a previous crypto_sign_update_agg(), a previous +crypto_sign_update(). + +Note that when signing mutli-part messages using aggregates, the order +in which message parts is processed is critical. You *must* ensure +that the order of messages passed to the aggregate is invariant.'; + +/* + * crypto_sign_verify_detached(bytea, bytea, bytea) + */ +CREATE FUNCTION pgsodium.crypto_sign_verify_detached(sig bytea, message bytea, key bytea) + RETURNS boolean + AS '$libdir/pgsodium', 'pgsodium_crypto_sign_verify_detached' + LANGUAGE C + IMMUTABLE; + +-- FIXME: owner? +-- FIXME: revoke? +-- FIXME: grant? + +/* + * crypto_signcrypt_new_keypair() + */ +CREATE FUNCTION pgsodium.crypto_signcrypt_new_keypair() + RETURNS pgsodium.crypto_signcrypt_keypair + AS '$libdir/pgsodium', 'pgsodium_crypto_signcrypt_keypair' + LANGUAGE C + VOLATILE; + +-- FIXME: owner? +REVOKE ALL ON FUNCTION pgsodium.crypto_signcrypt_new_keypair FROM PUBLIC; +GRANT EXECUTE ON FUNCTION pgsodium.crypto_signcrypt_new_keypair TO pgsodium_keymaker; + +/* + * crypto_signcrypt_sign_after(bytea, bytea, bytea) + */ +CREATE FUNCTION pgsodium.crypto_signcrypt_sign_after(state bytea, sender_sk bytea, ciphertext bytea) + RETURNS bytea + AS '$libdir/pgsodium', 'pgsodium_crypto_signcrypt_sign_after' + LANGUAGE C; + +-- FIXME: owner? +REVOKE ALL ON FUNCTION pgsodium.crypto_signcrypt_sign_after FROM PUBLIC; +GRANT EXECUTE ON FUNCTION pgsodium.crypto_signcrypt_sign_after TO pgsodium_keyholder; + +/* + * crypto_signcrypt_sign_before(bytea, bytea, bytea, bytea, bytea) + */ +CREATE FUNCTION pgsodium.crypto_signcrypt_sign_before(sender bytea, recipient bytea, sender_sk bytea, recipient_pk bytea, additional bytea) + RETURNS crypto_signcrypt_state_key + AS '$libdir/pgsodium', 'pgsodium_crypto_signcrypt_sign_before' + LANGUAGE C; + +-- FIXME: owner? +REVOKE ALL ON FUNCTION pgsodium.crypto_signcrypt_sign_before FROM PUBLIC; +GRANT EXECUTE ON FUNCTION pgsodium.crypto_signcrypt_sign_before TO pgsodium_keyholder; + +/* + * crypto_signcrypt_verify_after(bytea, bytea, bytea, bytea) + */ +CREATE FUNCTION pgsodium.crypto_signcrypt_verify_after(state bytea, signature bytea, sender_pk bytea, ciphertext bytea) + RETURNS bool + AS '$libdir/pgsodium', 'pgsodium_crypto_signcrypt_verify_after' + LANGUAGE C; + +-- FIXME: owner? +REVOKE ALL ON FUNCTION pgsodium.crypto_signcrypt_verify_after FROM PUBLIC; +GRANT EXECUTE ON FUNCTION pgsodium.crypto_signcrypt_verify_after TO pgsodium_keyholder; + +/* + * crypto_signcrypt_verify_before(bytea, bytea, bytea, bytea, bytea, bytea) + */ +CREATE FUNCTION pgsodium.crypto_signcrypt_verify_before(signature bytea, sender bytea, recipient bytea, additional bytea, sender_pk bytea, recipient_sk bytea) + RETURNS pgsodium.crypto_signcrypt_state_key + AS '$libdir/pgsodium', 'pgsodium_crypto_signcrypt_verify_before' + LANGUAGE C; + +-- FIXME: owner? +REVOKE ALL ON FUNCTION pgsodium.crypto_signcrypt_verify_before FROM PUBLIC; +GRANT EXECUTE ON FUNCTION pgsodium.crypto_signcrypt_verify_before TO pgsodium_keyholder; + +/* + * crypto_signcrypt_verify_public(bytea, bytea, bytea, bytea, bytea, bytea) + */ +CREATE FUNCTION pgsodium.crypto_signcrypt_verify_public(signature bytea, sender bytea, recipient bytea, additional bytea, sender_pk bytea, ciphertext bytea) + RETURNS bool + AS '$libdir/pgsodium', 'pgsodium_crypto_signcrypt_verify_public' + LANGUAGE C; + +-- FIXME: owner? +REVOKE ALL ON FUNCTION pgsodium.crypto_signcrypt_verify_public FROM PUBLIC; +GRANT EXECUTE ON FUNCTION pgsodium.crypto_signcrypt_verify_public TO pgsodium_keyholder; + +/* + * crypto_stream_xchacha20(bigint, bytea, bytea) + */ +CREATE FUNCTION pgsodium.crypto_stream_xchacha20(bigint, bytea, bytea) + RETURNS bytea + AS '$libdir/pgsodium', 'pgsodium_crypto_stream_xchacha20' + LANGUAGE C + IMMUTABLE; + +-- FIXME: owner? +-- FIXME: revoke? +-- FIXME: grant? + +/* + * crypto_stream_xchacha20(bigint, bytea, bigint, bytea) + */ +CREATE FUNCTION pgsodium.crypto_stream_xchacha20(bigint, bytea, bigint, context bytea = 'pgsodium') + RETURNS bytea + AS '$libdir/pgsodium', 'pgsodium_crypto_stream_xchacha20_by_id' + LANGUAGE C + IMMUTABLE; + +-- FIXME: owner? +-- FIXME: revoke? +-- FIXME: grant? + +/* + * crypto_stream_xchacha20_keygen() + */ +CREATE FUNCTION pgsodium.crypto_stream_xchacha20_keygen() + RETURNS bytea + AS '$libdir/pgsodium', 'pgsodium_crypto_stream_xchacha20_keygen' + LANGUAGE C + VOLATILE; + +-- FIXME: owner? +-- FIXME: revoke? +-- FIXME: grant? + +/* + * crypto_stream_xchacha20_noncegen() + */ +CREATE FUNCTION pgsodium.crypto_stream_xchacha20_noncegen() + RETURNS bytea + AS '$libdir/pgsodium', 'pgsodium_crypto_stream_xchacha20_noncegen' + LANGUAGE C + VOLATILE; + +-- FIXME: owner? +-- FIXME: revoke? +-- FIXME: grant? + +/* + * crypto_stream_xchacha20_xor(bytea, bytea, bytea) + */ +CREATE FUNCTION pgsodium.crypto_stream_xchacha20_xor(bytea, bytea, bytea) + RETURNS bytea + AS '$libdir/pgsodium', 'pgsodium_crypto_stream_xchacha20_xor' + LANGUAGE C + IMMUTABLE; + +-- FIXME: owner? +-- FIXME: revoke? +-- FIXME: grant? + +/* + * crypto_stream_xchacha20_xor(bytea, bytea, bigint, bytea) + */ +CREATE FUNCTION pgsodium.crypto_stream_xchacha20_xor(bytea, bytea, bigint, context bytea = 'pgosdium') + RETURNS bytea + AS '$libdir/pgsodium', 'pgsodium_crypto_stream_xchacha20_xor_by_id' + LANGUAGE C + IMMUTABLE; + +-- FIXME: owner? +-- FIXME: revoke? +-- FIXME: grant? + +/* + * crypto_stream_xchacha20_xor_ic(bytea, bytea, bigint, bytea) + */ +CREATE FUNCTION pgsodium.crypto_stream_xchacha20_xor_ic(bytea, bytea, bigint, bytea) + RETURNS bytea + AS '$libdir/pgsodium', 'pgsodium_crypto_stream_xchacha20_xor_ic' + LANGUAGE C + IMMUTABLE; + +-- FIXME: owner? +-- FIXME: revoke? +-- FIXME: grant? + +/* + * crypto_stream_xchacha20_xor_ic(bytea, bytea, bigint, bigint, bytea) + */ +CREATE FUNCTION pgsodium.crypto_stream_xchacha20_xor_ic(bytea, bytea, bigint, bigint, context bytea = 'pgsodium') + RETURNS bytea + AS '$libdir/pgsodium', 'pgsodium_crypto_stream_xchacha20_xor_ic_by_id' + LANGUAGE C + IMMUTABLE; + +-- FIXME: owner? +-- FIXME: revoke? +-- FIXME: grant? + +/* + * decrypted_columns(oid) + */ +CREATE FUNCTION pgsodium.decrypted_columns(relid OID) + RETURNS TEXT AS $$ +DECLARE + m RECORD; + expression TEXT; + comma TEXT; + padding text = ' '; +BEGIN + expression := E'\n'; + comma := padding; + FOR m IN SELECT * FROM pgsodium.mask_columns where attrelid = relid LOOP + expression := expression || comma; + IF m.key_id IS NULL AND m.key_id_column IS NULL THEN + expression := expression || padding || quote_ident(m.attname); + ELSE + expression := expression || padding || quote_ident(m.attname) || E',\n'; + IF m.format_type = 'text' THEN + expression := expression || format( + $f$ + CASE WHEN %s IS NULL THEN NULL ELSE + CASE WHEN %s IS NULL THEN NULL ELSE pg_catalog.convert_from( + pgsodium.crypto_aead_det_decrypt( + pg_catalog.decode(%s, 'base64'), + pg_catalog.convert_to((%s)::text, 'utf8'), + %s::uuid, + %s + ), + 'utf8') END + END AS %s$f$, + quote_ident(m.attname), + coalesce(quote_ident(m.key_id_column), quote_literal(m.key_id)), + quote_ident(m.attname), + coalesce(pgsodium.quote_assoc(m.associated_columns), quote_literal('')), + coalesce(quote_ident(m.key_id_column), quote_literal(m.key_id)), + coalesce(quote_ident(m.nonce_column), 'NULL'), + quote_ident('decrypted_' || m.attname) + ); + ELSIF m.format_type = 'bytea' THEN + expression := expression || format( + $f$ + CASE WHEN %s IS NULL THEN NULL ELSE + CASE WHEN %s IS NULL THEN NULL ELSE pgsodium.crypto_aead_det_decrypt( + %s::bytea, + pg_catalog.convert_to((%s)::text, 'utf8'), + %s::uuid, + %s + ) END + END AS %s$f$, + quote_ident(m.attname), + coalesce(quote_ident(m.key_id_column), quote_literal(m.key_id)), + quote_ident(m.attname), + coalesce(pgsodium.quote_assoc(m.associated_columns), quote_literal('')), + coalesce(quote_ident(m.key_id_column), quote_literal(m.key_id)), + coalesce(quote_ident(m.nonce_column), 'NULL'), + 'decrypted_' || quote_ident(m.attname) + ); + END IF; + END IF; + comma := E', \n'; + END LOOP; + RETURN expression; +END +$$ + LANGUAGE plpgsql + VOLATILE + SET search_path=''; + +-- FIXME: owner? +-- FIXME: revoke? +-- FIXME: grant? + +/* + * derive_key(bigint, integer, bytea) + */ +CREATE FUNCTION pgsodium.derive_key(key_id bigint, key_len integer = 32, context bytea = 'pgsodium') + RETURNS bytea + AS '$libdir/pgsodium', 'pgsodium_derive' + LANGUAGE C + VOLATILE; + +-- FIXME: owner? +REVOKE ALL ON FUNCTION pgsodium.derive_key FROM PUBLIC; +GRANT EXECUTE ON FUNCTION pgsodium.derive_key TO pgsodium_keymaker; + +/* + * disable_security_label_trigger() + */ +CREATE FUNCTION pgsodium.disable_security_label_trigger() RETURNS void AS + $$ + ALTER EVENT TRIGGER pgsodium_trg_mask_update DISABLE; + $$ + LANGUAGE sql + SECURITY DEFINER + SET search_path=''; + +-- FIXME: owner? +-- FIXME: revoke? +-- FIXME: grant? + +/* + * enable_security_label_trigger() + */ +CREATE FUNCTION pgsodium.enable_security_label_trigger() RETURNS void AS + $$ + ALTER EVENT TRIGGER pgsodium_trg_mask_update ENABLE; + $$ + LANGUAGE sql + SECURITY DEFINER + SET search_path=''; + +-- FIXME: owner? +-- FIXME: revoke? +-- FIXME: grant? + +/* + * encrypted_column(oid, record) + */ +CREATE FUNCTION pgsodium.encrypted_column(relid OID, m record) + RETURNS TEXT AS $$ +DECLARE + expression TEXT; + comma TEXT; +BEGIN + expression := ''; + comma := E' '; + expression := expression || comma; + IF m.format_type = 'text' THEN + expression := expression || format( + $f$%s = CASE WHEN %s IS NULL THEN NULL ELSE + CASE WHEN %s IS NULL THEN NULL ELSE pg_catalog.encode( + pgsodium.crypto_aead_det_encrypt( + pg_catalog.convert_to(%s, 'utf8'), + pg_catalog.convert_to((%s)::text, 'utf8'), + %s::uuid, + %s + ), + 'base64') END END$f$, + 'new.' || quote_ident(m.attname), + 'new.' || quote_ident(m.attname), + COALESCE('new.' || quote_ident(m.key_id_column), quote_literal(m.key_id)), + 'new.' || quote_ident(m.attname), + COALESCE(pgsodium.quote_assoc(m.associated_columns, true), quote_literal('')), + COALESCE('new.' || quote_ident(m.key_id_column), quote_literal(m.key_id)), + COALESCE('new.' || quote_ident(m.nonce_column), 'NULL') + ); + ELSIF m.format_type = 'bytea' THEN + expression := expression || format( + $f$%s = CASE WHEN %s IS NULL THEN NULL ELSE + CASE WHEN %s IS NULL THEN NULL ELSE + pgsodium.crypto_aead_det_encrypt(%s::bytea, pg_catalog.convert_to((%s)::text, 'utf8'), + %s::uuid, + %s + ) END END$f$, + 'new.' || quote_ident(m.attname), + 'new.' || quote_ident(m.attname), + COALESCE('new.' || quote_ident(m.key_id_column), quote_literal(m.key_id)), + 'new.' || quote_ident(m.attname), + COALESCE(pgsodium.quote_assoc(m.associated_columns, true), quote_literal('')), + COALESCE('new.' || quote_ident(m.key_id_column), quote_literal(m.key_id)), + COALESCE('new.' || quote_ident(m.nonce_column), 'NULL') + ); + END IF; + comma := E';\n '; + RETURN expression; +END +$$ + LANGUAGE plpgsql + VOLATILE + SET search_path=''; + +-- FIXME: owner? +-- FIXME: revoke? +-- FIXME: grant? + +/* + * encrypted_columns(oid) + */ +CREATE FUNCTION pgsodium.encrypted_columns(relid OID) + RETURNS TEXT AS $$ +DECLARE + m RECORD; + expression TEXT; + comma TEXT; +BEGIN + expression := ''; + comma := E' '; + FOR m IN SELECT * FROM pgsodium.mask_columns where attrelid = relid LOOP + IF m.key_id IS NULL AND m.key_id_column is NULL THEN + CONTINUE; + ELSE + expression := expression || comma; + IF m.format_type = 'text' THEN + expression := expression || format( + $f$%s = CASE WHEN %s IS NULL THEN NULL ELSE + CASE WHEN %s IS NULL THEN NULL ELSE pg_catalog.encode( + pgsodium.crypto_aead_det_encrypt( + pg_catalog.convert_to(%s, 'utf8'), + pg_catalog.convert_to((%s)::text, 'utf8'), + %s::uuid, + %s + ), + 'base64') END END$f$, + 'new.' || quote_ident(m.attname), + 'new.' || quote_ident(m.attname), + COALESCE('new.' || quote_ident(m.key_id_column), quote_literal(m.key_id)), + 'new.' || quote_ident(m.attname), + COALESCE(pgsodium.quote_assoc(m.associated_columns, true), quote_literal('')), + COALESCE('new.' || quote_ident(m.key_id_column), quote_literal(m.key_id)), + COALESCE('new.' || quote_ident(m.nonce_column), 'NULL') + ); + ELSIF m.format_type = 'bytea' THEN + expression := expression || format( + $f$%s = CASE WHEN %s IS NULL THEN NULL ELSE + CASE WHEN %s IS NULL THEN NULL ELSE + pgsodium.crypto_aead_det_encrypt(%s::bytea, pg_catalog.convert_to((%s)::text, 'utf8'), + %s::uuid, + %s + ) END END$f$, + 'new.' || quote_ident(m.attname), + 'new.' || quote_ident(m.attname), + COALESCE('new.' || quote_ident(m.key_id_column), quote_literal(m.key_id)), + 'new.' || quote_ident(m.attname), + COALESCE(pgsodium.quote_assoc(m.associated_columns, true), quote_literal('')), + COALESCE('new.' || quote_ident(m.key_id_column), quote_literal(m.key_id)), + COALESCE('new.' || quote_ident(m.nonce_column), 'NULL') + ); + END IF; + END IF; + comma := E';\n '; + END LOOP; + RETURN expression; +END +$$ + LANGUAGE plpgsql + VOLATILE + SET search_path=''; + +-- FIXME: owner? +-- FIXME: revoke? +-- FIXME: grant? + +/* + * get_key_by_id(uuid) + */ +CREATE FUNCTION pgsodium.get_key_by_id(uuid) + RETURNS pgsodium.valid_key + AS $$ + SELECT * from pgsodium.valid_key WHERE id = $1; +$$ + SECURITY DEFINER + LANGUAGE sql + SET search_path = ''; + +-- FIXME: owner? +-- FIXME: revoke? +-- FIXME: grant? + +/* + * get_key_by_name(text) + */ +CREATE FUNCTION pgsodium.get_key_by_name(text) + RETURNS pgsodium.valid_key + AS $$ + SELECT * from pgsodium.valid_key WHERE name = $1; +$$ + SECURITY DEFINER + LANGUAGE sql + SET search_path = ''; + +-- FIXME: owner? +-- FIXME: revoke? +-- FIXME: grant? + +/* + * get_named_keys(text) + */ +CREATE FUNCTION pgsodium.get_named_keys(filter text='%') + RETURNS SETOF pgsodium.valid_key + AS $$ + SELECT * from pgsodium.valid_key vk WHERE vk.name ILIKE filter; +$$ + SECURITY DEFINER + LANGUAGE sql + SET search_path = ''; + +-- FIXME: owner? +-- FIXME: revoke? +-- FIXME: grant? + +/* + * has_mask(regrole, text) + */ +CREATE FUNCTION pgsodium.has_mask(role regrole, source_name text) + RETURNS boolean AS $$ + SELECT EXISTS( + SELECT 1 + FROM pg_shseclabel + WHERE objoid = role + AND provider = 'pgsodium' + AND label ilike 'ACCESS%' || source_name || '%') + $$ LANGUAGE sql; + +-- FIXME: owner? +-- FIXME: revoke? +-- FIXME: grant? + +/* + * mask_role(regrole, text, text) + */ +CREATE FUNCTION pgsodium.mask_role(masked_role regrole, source_name text, view_name text) + RETURNS void AS $$ +BEGIN + EXECUTE format( + 'GRANT SELECT ON pgsodium.key TO %s', + masked_role); + + EXECUTE format( + 'GRANT pgsodium_keyiduser, pgsodium_keyholder TO %s', + masked_role); + + EXECUTE format( + 'GRANT ALL ON %s TO %s', + view_name, + masked_role); + RETURN; +END +$$ + LANGUAGE plpgsql + SECURITY DEFINER + SET search_path='pg_catalog'; + +-- FIXME: owner? +-- FIXME: revoke? +-- FIXME: grant? + +/* + * pgsodium_derive(bigint, integer, bytea + */ +CREATE FUNCTION pgsodium.pgsodium_derive(key_id bigint, key_len integer = 32, context bytea = decode('pgsodium', 'escape')) + RETURNS bytea + AS '$libdir/pgsodium', 'pgsodium_derive' + LANGUAGE C + VOLATILE; + +-- FIXME: owner? +REVOKE ALL ON FUNCTION pgsodium.pgsodium_derive FROM PUBLIC; +GRANT EXECUTE ON FUNCTION pgsodium.pgsodium_derive TO pgsodium_keymaker; + +/* + * quote_assoc(text, boolean) + */ +CREATE FUNCTION pgsodium.quote_assoc(text, boolean = false) + RETURNS text + AS $$ + WITH a AS (SELECT array_agg(CASE WHEN $2 THEN + 'new.' || quote_ident(trim(v)) + ELSE quote_ident(trim(v)) END) as r + FROM regexp_split_to_table($1, '\s*,\s*') as v) + SELECT array_to_string(a.r, '::text || ') || '::text' FROM a; +$$ + LANGUAGE sql; + +-- FIXME: owner? +-- FIXME: revoke? +-- FIXME: grant? + +/* + * randombytes_buf(integer) + */ +CREATE FUNCTION pgsodium.randombytes_buf(size integer) + RETURNS bytea + AS '$libdir/pgsodium', 'pgsodium_randombytes_buf' + LANGUAGE C + VOLATILE; + +-- FIXME: owner? +REVOKE ALL ON FUNCTION pgsodium.randombytes_buf FROM PUBLIC; +GRANT EXECUTE ON FUNCTION pgsodium.randombytes_buf TO pgsodium_keyiduser; + +/* + * randombytes_buf_deterministic(integer, bytea) + */ +CREATE FUNCTION pgsodium.randombytes_buf_deterministic(size integer, seed bytea) + RETURNS bytea + AS '$libdir/pgsodium', 'pgsodium_randombytes_buf_deterministic' + LANGUAGE C + IMMUTABLE; + +-- FIXME: owner? +REVOKE ALL ON FUNCTION pgsodium.randombytes_buf_deterministic FROM PUBLIC; +GRANT EXECUTE ON FUNCTION pgsodium.randombytes_buf_deterministic TO pgsodium_keyiduser, pgsodium_keymaker; + +/* + * randombytes_new_seed() + */ +CREATE FUNCTION pgsodium.randombytes_new_seed() + RETURNS bytea + AS '$libdir/pgsodium', 'pgsodium_randombytes_new_seed' + LANGUAGE C + VOLATILE; + +-- FIXME: owner? +REVOKE ALL ON FUNCTION pgsodium.randombytes_new_seed FROM PUBLIC; +GRANT EXECUTE ON FUNCTION pgsodium.randombytes_new_seed TO pgsodium_keymaker; + +/* + * randombytes_random() + */ +CREATE FUNCTION pgsodium.randombytes_random() + RETURNS integer + AS '$libdir/pgsodium', 'pgsodium_randombytes_random' + LANGUAGE C + VOLATILE; + +-- FIXME: owner? +REVOKE ALL ON FUNCTION pgsodium.randombytes_random FROM PUBLIC; +GRANT EXECUTE ON FUNCTION pgsodium.randombytes_random TO pgsodium_keyiduser; + +/* + * randombytes_uniform(integer) + */ +CREATE FUNCTION pgsodium.randombytes_uniform(upper_bound integer) + RETURNS integer + AS '$libdir/pgsodium', 'pgsodium_randombytes_uniform' + LANGUAGE C + VOLATILE; + +-- FIXME: owner? +REVOKE ALL ON FUNCTION pgsodium.randombytes_uniform FROM PUBLIC; +GRANT EXECUTE ON FUNCTION pgsodium.randombytes_uniform TO pgsodium_keyiduser; + +/* + * sodium_base642bin(text) + */ +CREATE FUNCTION pgsodium.sodium_base642bin(base64 text) RETURNS bytea + AS '$libdir/pgsodium', 'pgsodium_sodium_base642bin' + LANGUAGE C + IMMUTABLE; + +-- FIXME: owner? +-- FIXME: revoke? +-- FIXME: grant? + +/* + * sodium_bin2base64(bytea) + */ +CREATE FUNCTION pgsodium.sodium_bin2base64(bin bytea) RETURNS text + AS '$libdir/pgsodium', 'pgsodium_sodium_bin2base64' + LANGUAGE C + IMMUTABLE; + +-- FIXME: owner? +-- FIXME: revoke? +-- FIXME: grant? + +/* + * update_mask(oid, boolean) + */ +CREATE FUNCTION pgsodium.update_mask(target oid, debug boolean = false) +RETURNS void AS + $$ +BEGIN + PERFORM pgsodium.disable_security_label_trigger(); + PERFORM pgsodium.create_mask_view(objoid, objsubid, debug) + FROM pg_catalog.pg_seclabel sl + WHERE sl.objoid = target + AND sl.label ILIKE 'ENCRYPT%' + AND sl.provider = 'pgsodium'; + PERFORM pgsodium.enable_security_label_trigger(); + RETURN; +END +$$ + LANGUAGE plpgsql + SECURITY DEFINER + SET search_path=''; + +-- FIXME: owner? +-- FIXME: revoke? +-- FIXME: grant? + +/* + * update_masks(boolean) + */ +CREATE FUNCTION pgsodium.update_masks(debug boolean = false) + RETURNS void AS + $$ +BEGIN + PERFORM pgsodium.update_mask(objoid, debug) + FROM pg_catalog.pg_seclabel sl + JOIN pg_catalog.pg_class cl ON (cl.oid = sl.objoid) + WHERE label ilike 'ENCRYPT%' + AND cl.relowner = session_user::regrole::oid + AND provider = 'pgsodium' + AND objoid::regclass != 'pgsodium.key'::regclass + ; + RETURN; +END +$$ + LANGUAGE plpgsql + SET search_path=''; + +-- FIXME: owner? +-- FIXME: revoke? +-- FIXME: grant? + +/* + * version() + */ +CREATE FUNCTION pgsodium.version() + RETURNS text + AS $$ SELECT extversion FROM pg_extension WHERE extname = 'pgsodium' $$ + LANGUAGE sql + SET search_path=''; + +-- FIXME: owner? +-- FIXME: revoke? +-- FIXME: grant? + +--============================================================================== +-- MAINTENANCES +--============================================================================== + + +SECURITY LABEL FOR pgsodium ON COLUMN pgsodium.key.raw_key + IS 'ENCRYPT WITH KEY COLUMN parent_key ASSOCIATED (id, associated_data) NONCE raw_key_nonce'; + +SELECT * FROM pgsodium.update_mask('pgsodium.key'::regclass::oid); + +-- FIXME: should we really keep it as keyholder has been deprecated in the same +-- release decrypted_key appeared?? +GRANT SELECT, INSERT, UPDATE, DELETE ON pgsodium.decrypted_key TO pgsodium_keyholder; From 990c48e0fd9995842e1f4cf9f14035ff5656ba8a Mon Sep 17 00:00:00 2001 From: Jehan-Guillaume de Rorthais Date: Fri, 9 Jun 2023 15:41:51 +0200 Subject: [PATCH 06/16] Rename constraint name to match the one from 3.2.0 full script --- sql/pgsodium--3.1.7--3.2.0.sql | 7 +++++ test/pgsodium_schema.sql | 50 +++++++++++++++++----------------- 2 files changed, 32 insertions(+), 25 deletions(-) diff --git a/sql/pgsodium--3.1.7--3.2.0.sql b/sql/pgsodium--3.1.7--3.2.0.sql index e33dd84..41fbf82 100644 --- a/sql/pgsodium--3.1.7--3.2.0.sql +++ b/sql/pgsodium--3.1.7--3.2.0.sql @@ -16,3 +16,10 @@ DROP FUNCTION IF EXISTS pgsodium.mask_columns(oid); * and the old view? */ DROP SCHEMA IF EXISTS pgsodium_masks; + +/* + * change: constraint names generated by the create table pgsodium.key in + * pgsodium--3.2.0.sql are different from the older ones. + */ +ALTER TABLE pgsodium.key RENAME CONSTRAINT "pgsodium_raw" TO "key_check"; +ALTER INDEX pgsodium.pgsodium_key_unique_name RENAME TO key_name_key; diff --git a/test/pgsodium_schema.sql b/test/pgsodium_schema.sql index 02f98b5..1e22498 100644 --- a/test/pgsodium_schema.sql +++ b/test/pgsodium_schema.sql @@ -330,31 +330,31 @@ SELECT results_eq( WHERE n.nspname = 'pgsodium' AND r.relname = 'key' ORDER BY c.contype, c.conname $q$, ARRAY[ + 'key_check', 'key_key_context_check', - 'pgsodium_raw', 'key_parent_key_fkey', 'key_pkey', - 'pgsodium_key_unique_name' + 'key_name_key' ]::name[], $$Event trigger list is ok$$); --- constraint 'key_key_context_check' on 'key' -SELECT is(pg_catalog.pg_get_constraintdef(c.oid, true),'CHECK (length(key_context) = 8)', $$Definition of constraint 'key_key_context_check'$$) -FROM pg_catalog.pg_constraint c -JOIN pg_catalog.pg_class r ON c.conrelid = r.oid -JOIN pg_catalog.pg_namespace n ON n.oid = r.relnamespace -WHERE n.nspname = 'pgsodium' AND r.relname = 'key' AND c.conname = 'key_key_context_check'; - --- constraint 'pgsodium_raw' on 'key' +-- constraint 'key_check' on 'key' SELECT is(pg_catalog.pg_get_constraintdef(c.oid, true),'CHECK ( CASE WHEN raw_key IS NOT NULL THEN key_id IS NULL AND key_context IS NULL AND parent_key IS NOT NULL ELSE key_id IS NOT NULL AND key_context IS NOT NULL AND parent_key IS NULL -END)', $$Definition of constraint 'pgsodium_raw'$$) +END)', $$Definition of constraint 'key_check'$$) +FROM pg_catalog.pg_constraint c +JOIN pg_catalog.pg_class r ON c.conrelid = r.oid +JOIN pg_catalog.pg_namespace n ON n.oid = r.relnamespace +WHERE n.nspname = 'pgsodium' AND r.relname = 'key' AND c.conname = 'key_check'; + +-- constraint 'key_key_context_check' on 'key' +SELECT is(pg_catalog.pg_get_constraintdef(c.oid, true),'CHECK (length(key_context) = 8)', $$Definition of constraint 'key_key_context_check'$$) FROM pg_catalog.pg_constraint c JOIN pg_catalog.pg_class r ON c.conrelid = r.oid JOIN pg_catalog.pg_namespace n ON n.oid = r.relnamespace -WHERE n.nspname = 'pgsodium' AND r.relname = 'key' AND c.conname = 'pgsodium_raw'; +WHERE n.nspname = 'pgsodium' AND r.relname = 'key' AND c.conname = 'key_key_context_check'; -- constraint 'key_parent_key_fkey' on 'key' SELECT is(pg_catalog.pg_get_constraintdef(c.oid, true),'FOREIGN KEY (parent_key) REFERENCES pgsodium.key(id)', $$Definition of constraint 'key_parent_key_fkey'$$) @@ -370,20 +370,20 @@ JOIN pg_catalog.pg_class r ON c.conrelid = r.oid JOIN pg_catalog.pg_namespace n ON n.oid = r.relnamespace WHERE n.nspname = 'pgsodium' AND r.relname = 'key' AND c.conname = 'key_pkey'; --- constraint 'pgsodium_key_unique_name' on 'key' -SELECT is(pg_catalog.pg_get_constraintdef(c.oid, true),'UNIQUE (name)', $$Definition of constraint 'pgsodium_key_unique_name'$$) +-- constraint 'key_name_key' on 'key' +SELECT is(pg_catalog.pg_get_constraintdef(c.oid, true),'UNIQUE (name)', $$Definition of constraint 'key_name_key'$$) FROM pg_catalog.pg_constraint c JOIN pg_catalog.pg_class r ON c.conrelid = r.oid JOIN pg_catalog.pg_namespace n ON n.oid = r.relnamespace -WHERE n.nspname = 'pgsodium' AND r.relname = 'key' AND c.conname = 'pgsodium_key_unique_name'; +WHERE n.nspname = 'pgsodium' AND r.relname = 'key' AND c.conname = 'key_name_key'; -- indexes of table key SELECT indexes_are('pgsodium'::name, 'key'::name, ARRAY[ 'key_key_id_key_context_key_type_idx', + 'key_name_key', 'key_pkey', 'key_status_idx', - 'key_status_idx1', - 'pgsodium_key_unique_name' + 'key_status_idx1' ]::name[]); -- index 'key_key_id_key_context_key_type_idx' on key @@ -394,6 +394,14 @@ JOIN pg_catalog.pg_class r ON i.indrelid = r.oid JOIN pg_catalog.pg_namespace n ON n.oid = r.relnamespace WHERE n.nspname = 'pgsodium' AND r.relname = 'key' AND c.relname = 'key_key_id_key_context_key_type_idx'; +-- index 'key_name_key' on key +SELECT is(pg_catalog.pg_get_indexdef(i.indexrelid, 0, true),'CREATE UNIQUE INDEX key_name_key ON pgsodium.key USING btree (name)', $$Definition of index 'key_name_key'$$) +FROM pg_catalog.pg_class c +JOIN pg_catalog.pg_index i ON c.oid = i.indexrelid +JOIN pg_catalog.pg_class r ON i.indrelid = r.oid +JOIN pg_catalog.pg_namespace n ON n.oid = r.relnamespace +WHERE n.nspname = 'pgsodium' AND r.relname = 'key' AND c.relname = 'key_name_key'; + -- index 'key_pkey' on key SELECT is(pg_catalog.pg_get_indexdef(i.indexrelid, 0, true),'CREATE UNIQUE INDEX key_pkey ON pgsodium.key USING btree (id)', $$Definition of index 'key_pkey'$$) FROM pg_catalog.pg_class c @@ -419,14 +427,6 @@ JOIN pg_catalog.pg_class r ON i.indrelid = r.oid JOIN pg_catalog.pg_namespace n ON n.oid = r.relnamespace WHERE n.nspname = 'pgsodium' AND r.relname = 'key' AND c.relname = 'key_status_idx1'; --- index 'pgsodium_key_unique_name' on key -SELECT is(pg_catalog.pg_get_indexdef(i.indexrelid, 0, true),'CREATE UNIQUE INDEX pgsodium_key_unique_name ON pgsodium.key USING btree (name)', $$Definition of index 'pgsodium_key_unique_name'$$) -FROM pg_catalog.pg_class c -JOIN pg_catalog.pg_index i ON c.oid = i.indexrelid -JOIN pg_catalog.pg_class r ON i.indrelid = r.oid -JOIN pg_catalog.pg_namespace n ON n.oid = r.relnamespace -WHERE n.nspname = 'pgsodium' AND r.relname = 'key' AND c.relname = 'pgsodium_key_unique_name'; - -- triggers of relation key SELECT triggers_are('pgsodium', 'key', ARRAY[ 'key_encrypt_secret_trigger_raw_key' From df73592c75f00f15f90c58b5f2da46a8040f1729 Mon Sep 17 00:00:00 2001 From: Jehan-Guillaume de Rorthais Date: Fri, 9 Jun 2023 16:33:06 +0200 Subject: [PATCH 07/16] Add missing column user_data to the decrypted_key view --- sql/pgsodium--3.1.7--3.2.0.sql | 6 ++++++ test/pgsodium_schema.sql | 8 +++++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/sql/pgsodium--3.1.7--3.2.0.sql b/sql/pgsodium--3.1.7--3.2.0.sql index 41fbf82..162aecf 100644 --- a/sql/pgsodium--3.1.7--3.2.0.sql +++ b/sql/pgsodium--3.1.7--3.2.0.sql @@ -23,3 +23,9 @@ DROP SCHEMA IF EXISTS pgsodium_masks; */ ALTER TABLE pgsodium.key RENAME CONSTRAINT "pgsodium_raw" TO "key_check"; ALTER INDEX pgsodium.pgsodium_key_unique_name RENAME TO key_name_key; + +/* + * change: force regenerating the decrypted_key view to add the missing column + * "user_data" to the view. + */ +SELECT * FROM pgsodium.update_mask('pgsodium.key'::regclass::oid); diff --git a/test/pgsodium_schema.sql b/test/pgsodium_schema.sql index 1e22498..39c4db2 100644 --- a/test/pgsodium_schema.sql +++ b/test/pgsodium_schema.sql @@ -474,7 +474,8 @@ SELECT columns_are('pgsodium'::name, 'decrypted_key'::name, ARRAY[ 'decrypted_raw_key', 'raw_key_nonce', 'parent_key', - 'comment' + 'comment', + 'user_data' ]::name[]); SELECT has_column( 'pgsodium', 'decrypted_key', 'id' , 'has column decrypted_key.id'); @@ -547,6 +548,11 @@ SELECT col_type_is( 'pgsodium', 'decrypted_key', 'comment' , 'text', SELECT col_is_null( 'pgsodium', 'decrypted_key', 'comment' , 'col_is_null( decrypted_key.comment )'); SELECT col_hasnt_default('pgsodium', 'decrypted_key', 'comment' , 'col_hasnt_default( decrypted_key.comment )'); +SELECT has_column( 'pgsodium', 'decrypted_key', 'user_data' , 'has column decrypted_key.user_data'); +SELECT col_type_is( 'pgsodium', 'decrypted_key', 'user_data' , 'text', 'type of column decrypted_key.user_data is text'); +SELECT col_is_null( 'pgsodium', 'decrypted_key', 'user_data' , 'col_is_null( decrypted_key.user_data )'); +SELECT col_hasnt_default('pgsodium', 'decrypted_key', 'user_data' , 'col_hasnt_default( decrypted_key.user_data )'); + -- owner of view decrypted_key SELECT view_owner_is('pgsodium'::name, 'decrypted_key'::name, 'postgres'::name); From 6ea0ad2f7ef3493e7cc078f6bd7fb9ba89dccf2e Mon Sep 17 00:00:00 2001 From: Jehan-Guillaume de Rorthais Date: Fri, 9 Jun 2023 20:55:13 +0200 Subject: [PATCH 08/16] Fix privileges on key and decrypted_keys --- sql/pgsodium--3.1.7--3.2.0.sql | 14 ++++++++++++++ test/pgsodium_schema.sql | 6 +++--- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/sql/pgsodium--3.1.7--3.2.0.sql b/sql/pgsodium--3.1.7--3.2.0.sql index 162aecf..8e55ebb 100644 --- a/sql/pgsodium--3.1.7--3.2.0.sql +++ b/sql/pgsodium--3.1.7--3.2.0.sql @@ -29,3 +29,17 @@ ALTER INDEX pgsodium.pgsodium_key_unique_name RENAME TO key_name_key; * "user_data" to the view. */ SELECT * FROM pgsodium.update_mask('pgsodium.key'::regclass::oid); + +/* + * Fix privileges + */ + +REVOKE ALL ON pgsodium.key FROM pgsodium_keyiduser; + +REVOKE ALL ON pgsodium.key FROM pgsodium_keymaker; +GRANT SELECT, INSERT, UPDATE, DELETE ON pgsodium.key TO pgsodium_keymaker; +REVOKE ALL ON pgsodium.decrypted_key FROM pgsodium_keymaker; +GRANT SELECT, INSERT, UPDATE, DELETE ON pgsodium.decrypted_key TO pgsodium_keymaker; + +REVOKE ALL ON pgsodium.decrypted_key FROM pgsodium_keyholder; +GRANT SELECT, INSERT, UPDATE, DELETE ON pgsodium.decrypted_key TO pgsodium_keyholder; diff --git a/test/pgsodium_schema.sql b/test/pgsodium_schema.sql index 39c4db2..cb2a5e3 100644 --- a/test/pgsodium_schema.sql +++ b/test/pgsodium_schema.sql @@ -440,7 +440,7 @@ SELECT table_owner_is('pgsodium'::name, 'key'::name, 'postgres'::name); -- privs of relation key -SELECT table_privs_are('pgsodium'::name, 'key'::name, 'pgsodium_keymaker' ::name, '{DELETE,INSERT,REFERENCES,SELECT,TRIGGER,TRUNCATE,UPDATE}'::text[]); +SELECT table_privs_are('pgsodium'::name, 'key'::name, 'pgsodium_keymaker' ::name, '{DELETE,INSERT,SELECT,UPDATE}'::text[]); SELECT table_privs_are('pgsodium'::name, 'key'::name, 'postgres' ::name, '{DELETE,INSERT,REFERENCES,SELECT,TRIGGER,TRUNCATE,UPDATE}'::text[]); SELECT table_privs_are('pgsodium'::name, 'key'::name, rolname, '{}'::text[]) FROM pg_catalog.pg_roles @@ -559,8 +559,8 @@ SELECT view_owner_is('pgsodium'::name, 'decrypted_key'::name, 'postgres'::name); -- privs of relation decrypted_key -SELECT table_privs_are('pgsodium'::name, 'decrypted_key'::name, 'pgsodium_keyholder' ::name, '{DELETE,INSERT,REFERENCES,SELECT,TRIGGER,TRUNCATE,UPDATE}'::text[]); -SELECT table_privs_are('pgsodium'::name, 'decrypted_key'::name, 'pgsodium_keymaker' ::name, '{DELETE,INSERT,REFERENCES,SELECT,TRIGGER,TRUNCATE,UPDATE}'::text[]); +SELECT table_privs_are('pgsodium'::name, 'decrypted_key'::name, 'pgsodium_keyholder' ::name, '{DELETE,INSERT,SELECT,UPDATE}'::text[]); +SELECT table_privs_are('pgsodium'::name, 'decrypted_key'::name, 'pgsodium_keymaker' ::name, '{DELETE,INSERT,SELECT,UPDATE}'::text[]); SELECT table_privs_are('pgsodium'::name, 'decrypted_key'::name, 'postgres' ::name, '{DELETE,INSERT,REFERENCES,SELECT,TRIGGER,TRUNCATE,UPDATE}'::text[]); SELECT table_privs_are('pgsodium'::name, 'decrypted_key'::name, rolname, '{}'::text[]) FROM pg_catalog.pg_roles From aad3d33b2b534986c5cd09ee0f3216af0dcf5c73 Mon Sep 17 00:00:00 2001 From: Jehan-Guillaume de Rorthais Date: Fri, 9 Jun 2023 23:38:59 +0200 Subject: [PATCH 09/16] Revoke default permission on pgsodium from pgsodium_keyholder --- pgsodium_tapgen.pl | 39 ++++++++++++++++++++++++++++++++++ sql/pgsodium--3.1.7--3.2.0.sql | 3 +++ 2 files changed, 42 insertions(+) diff --git a/pgsodium_tapgen.pl b/pgsodium_tapgen.pl index 57ec545..c87d0a6 100755 --- a/pgsodium_tapgen.pl +++ b/pgsodium_tapgen.pl @@ -115,6 +115,45 @@ printf "SELECT is_member_of( %s, %s );\n", $r->[0], $r->[1]; } +$rs = $dbh->selectcol_arrayref(q{ +SELECT format('(%L::text, %L::text, %L::text, %L::text)', + pg_catalog.pg_get_userbyid(d.defaclrole), n.nspname, + CASE d.defaclobjtype + WHEN 'r' THEN 'table' + WHEN 'S' THEN 'sequence' + WHEN 'f' THEN 'function' + WHEN 'T' THEN 'type' + WHEN 'n' THEN 'schema' + END, + pg_catalog.array_to_string(d.defaclacl, E'\n')) +FROM pg_catalog.pg_default_acl d +LEFT JOIN pg_catalog.pg_namespace n ON n.oid = d.defaclnamespace +ORDER BY 1 +}) or die; + +if (scalar @$rs) { + print "\n\n\n---- DEFAULT PRIVS\n\n"; + print q{SELECT result_eq($$ + SELECT pg_catalog.pg_get_userbyid(d.defaclrole)::text, n.nspname::text, + CASE d.defaclobjtype + WHEN 'r' THEN 'table' + WHEN 'S' THEN 'sequence' + WHEN 'f' THEN 'function' + WHEN 'T' THEN 'type' + WHEN 'n' THEN 'schema' + END, + pg_catalog.array_to_string(d.defaclacl, E'\n')::text + FROM pg_catalog.pg_default_acl d + LEFT JOIN pg_catalog.pg_namespace n ON n.oid = d.defaclnamespace + ORDER BY 1, 2, 3$$, + $$ VALUES + }, + join(",\n ", @$rs), q{ + $$, + 'Check default privileges'); +}; +} + print "\n\n\n---- SCHEMAS\n\n"; $rs = $dbh->selectall_arrayref(q{ diff --git a/sql/pgsodium--3.1.7--3.2.0.sql b/sql/pgsodium--3.1.7--3.2.0.sql index 8e55ebb..9331e08 100644 --- a/sql/pgsodium--3.1.7--3.2.0.sql +++ b/sql/pgsodium--3.1.7--3.2.0.sql @@ -43,3 +43,6 @@ GRANT SELECT, INSERT, UPDATE, DELETE ON pgsodium.decrypted_key TO pgsodium_keyma REVOKE ALL ON pgsodium.decrypted_key FROM pgsodium_keyholder; GRANT SELECT, INSERT, UPDATE, DELETE ON pgsodium.decrypted_key TO pgsodium_keyholder; + +ALTER DEFAULT PRIVILEGES IN SCHEMA pgsodium REVOKE ALL ON TABLES FROM pgsodium_keyholder; +ALTER DEFAULT PRIVILEGES IN SCHEMA pgsodium REVOKE ALL ON SEQUENCES FROM pgsodium_keyholder; From e702c98bacd46313341daa2dc3fb519f883c8b5b Mon Sep 17 00:00:00 2001 From: Jehan-Guillaume de Rorthais Date: Sat, 10 Jun 2023 00:40:41 +0200 Subject: [PATCH 10/16] Makes Dockerfile less chatty --- Dockerfile | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/Dockerfile b/Dockerfile index 08de39f..e8a2065 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,9 +3,9 @@ ARG version ARG DEBIAN_FRONTEND=noninteractive # install base dependences -RUN apt-get update && \ - apt-get install -y make cmake git curl build-essential m4 sudo gdbserver \ - gdb libreadline-dev bison flex zlib1g-dev tmux zile zip vim gawk wget +RUN apt-get -qq update && \ + apt-get -qq install -y make cmake git curl build-essential m4 sudo gdbserver \ + gdb libreadline-dev bison flex zlib1g-dev tmux zile zip vim gawk wget > /dev/null # add postgres user and make data dir RUN groupadd -r postgres && useradd --no-log-init -r -m -s /bin/bash -g postgres -G sudo postgres @@ -15,27 +15,29 @@ WORKDIR "/home/postgres" # get postgres source and compile with debug and no optimization RUN git clone --branch REL_${version}_STABLE https://github.com/postgres/postgres.git --depth=1 && \ - cd postgres && ./configure \ + cd postgres && echo "Installing pgsql..." && ( ./configure \ --prefix=/usr/ \ --enable-debug \ --enable-depend --enable-cassert --enable-profiling \ CFLAGS="-ggdb -Og -g3 -fno-omit-frame-pointer" \ -# CFLAGS="-O3" \ - && make -j 4 && make install + && make -j 4 && make install ) > /dev/null +# CFLAGS="-O3" RUN chown postgres:postgres /home/postgres -RUN curl -s -L https://github.com/theory/pgtap/archive/v1.2.0.tar.gz | tar zxvf - && cd pgtap-1.2.0 && make && make install -RUN curl -s -L https://download.libsodium.org/libsodium/releases/libsodium-1.0.18.tar.gz | tar zxvf - && cd libsodium-1.0.18 && ./configure && make check && make -j 4 install -RUN cpan App::cpanminus && cpan TAP::Parser::SourceHandler::pgTAP && cpan App::prove +RUN curl -s -L https://github.com/theory/pgtap/archive/v1.2.0.tar.gz | tar zxf - && cd pgtap-1.2.0 && (make && make install) > /dev/null +RUN curl -s -L https://download.libsodium.org/libsodium/releases/libsodium-1.0.18.tar.gz | tar zxf - && cd libsodium-1.0.18 && (./configure && make check && make -j 4 install) > /dev/null +RUN cpan App::cpanminus > /dev/null +RUN cpan TAP::Parser::SourceHandler::pgTAP > /dev/null +RUN cpan App::prove > /dev/null -RUN git clone --depth 1 https://github.com/lacanoid/pgddl.git -RUN cd pgddl && make && make install && cd .. +RUN git clone --depth 1 https://github.com/lacanoid/pgddl.git && \ + (cd pgddl && make && make install && cd ..) > /dev/null RUN mkdir "/home/postgres/pgsodium" WORKDIR "/home/postgres/pgsodium" COPY . . -RUN make -j 4 && make install +RUN echo "Installing pgsodium..." && (make -j 4 && make install) > /dev/null RUN ldconfig RUN cd `pg_config --sharedir`/extension/ RUN cp getkey_scripts/pgsodium_getkey_urandom.sh `pg_config --sharedir`/extension/pgsodium_getkey @@ -53,6 +55,6 @@ RUN echo "postgres ALL=(root) NOPASSWD:ALL" > /etc/sudoers.d/user && \ # start the database USER postgres -RUN initdb -D "$PGDATA" +RUN initdb -D "$PGDATA" > /dev/null EXPOSE 5432 CMD ["/usr/bin/postgres"] From eba57867e64dad71836b887db4d4edcc58cee185 Mon Sep 17 00:00:00 2001 From: Jehan-Guillaume de Rorthais Date: Sat, 10 Jun 2023 00:41:48 +0200 Subject: [PATCH 11/16] Test both full script and incremental script --- test.sh | 11 +++++++++-- test/test.sql | 26 +++++++++++++++++++++++++- 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/test.sh b/test.sh index 9a6d7e6..68e88ab 100755 --- a/test.sh +++ b/test.sh @@ -26,8 +26,15 @@ do echo waiting for database to accept connections sleep 3; echo running tests - - $EXEC pg_prove -U "$SU" /home/postgres/pgsodium/test/test.sql + + # test using inscremental script + $EXEC createdb inc + $EXEC psql -d inc -c 'CREATE EXTENSION pgsodium VERSION "3.1.0"' + $EXEC pg_prove -d inc -U "$SU" /home/postgres/pgsodium/test/test.sql + + # test using full script + $EXEC createdb full + $EXEC pg_prove -d full -U "$SU" /home/postgres/pgsodium/test/test.sql echo destroying test container and image docker rm --force "$DB_HOST" diff --git a/test/test.sql b/test/test.sql index b18e81b..b37f764 100644 --- a/test/test.sql +++ b/test/test.sql @@ -16,7 +16,31 @@ SELECT EXISTS (SELECT * FROM pg_settings CREATE EXTENSION IF NOT EXISTS pgtap; -CREATE EXTENSION IF NOT EXISTS pgsodium; +SELECT diag('Existing pgsodium version: '|| extversion) +FROM pg_catalog.pg_extension +WHERE extname = 'pgsodium'; + +DO $$ +BEGIN + IF NOT EXISTS (SELECT 1 FROM pg_catalog.pg_extension WHERE extname = 'pgsodium') THEN + EXECUTE 'CREATE EXTENSION pgsodium'; + ELSE + EXECUTE 'ALTER EXTENSION pgsodium UPDATE'; + END IF; +END +$$; + +SELECT diag('Installed or updated version of pgsodium: '|| extversion) +FROM pg_catalog.pg_extension +WHERE extname = 'pgsodium'; + +SELECT diag('Running tests on ' || pg_catalog.version()); + +SELECT diag(format('Parameter %s = %s', name, setting)) +FROM pg_settings +WHERE name IN ('shared_preload_libraries', 'pgsodium.getkey_script'); + +SELECT diag('Running tests in database ' || current_database()); BEGIN; CREATE ROLE bobo with login password 'foo'; From b5da330c7003e2a2bc44178697334b0a6ed45630 Mon Sep 17 00:00:00 2001 From: Jehan-Guillaume de Rorthais Date: Sat, 10 Jun 2023 01:09:32 +0200 Subject: [PATCH 12/16] Avoid rebuilding the docker image when possible --- test.sh | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/test.sh b/test.sh index 68e88ab..cbcdab7 100755 --- a/test.sh +++ b/test.sh @@ -6,16 +6,17 @@ versions=${1:-13 14 15} for version in $versions do + TAG="pgsodium/test-$version" + DB_HOST="pgsodium-test-db-$version" + + echo building test image $DB_HOST + docker build . -t $TAG --build-arg "version=$version" + for config in '-c shared_preload_libraries=pgsodium' '-c shared_preload_libraries=pgsodium -c pgsodium.getkey_script=/getkey' '' do - DB_HOST="pgsodium-test-db-$version" DB_NAME="postgres" SU="postgres" EXEC="docker exec -i $DB_HOST" - TAG="pgsodium/test-$version" - - echo building test image $DB_HOST - docker build . -t $TAG --build-arg "version=$version" echo running test container docker run --rm -e POSTGRES_HOST_AUTH_METHOD=trust -d \ From 389c1318d4c6366fff5730f4cdef5eb19fc534c1 Mon Sep 17 00:00:00 2001 From: Jehan-Guillaume de Rorthais Date: Wed, 21 Jun 2023 12:30:02 +0200 Subject: [PATCH 13/16] Introduce pgsodium.debug GUC --- sql/pgsodium--3.1.7--3.2.0.sql | 25 +++++++++++++++++++++++++ sql/pgsodium--3.2.0.sql | 7 ++++++- src/pgsodium.c | 4 ++++ test/pgsodium_schema.sql | 2 +- 4 files changed, 36 insertions(+), 2 deletions(-) diff --git a/sql/pgsodium--3.1.7--3.2.0.sql b/sql/pgsodium--3.1.7--3.2.0.sql index 9331e08..48e0845 100644 --- a/sql/pgsodium--3.1.7--3.2.0.sql +++ b/sql/pgsodium--3.1.7--3.2.0.sql @@ -17,6 +17,31 @@ DROP FUNCTION IF EXISTS pgsodium.mask_columns(oid); */ DROP SCHEMA IF EXISTS pgsodium_masks; +/* + * change: read debug GUC from settings. + */ +CREATE OR REPLACE FUNCTION pgsodium.trg_mask_update() +RETURNS EVENT_TRIGGER +AS $$ +DECLARE + r record; + debug bool := CASE WHEN count(1) > 0 + THEN pg_catalog.current_setting('pgsodium.debug', false)::bool + ELSE false::bool + END + FROM pg_catalog.pg_settings WHERE name ~ 'pgsodium.debug'; +BEGIN + IF (select bool_or(in_extension) FROM pg_event_trigger_ddl_commands()) THEN + RAISE NOTICE 'skipping pgsodium mask regeneration in extension'; + RETURN; + END IF; + PERFORM pgsodium.update_masks(debug); +END +$$ +LANGUAGE plpgsql +SET search_path=''; + + /* * change: constraint names generated by the create table pgsodium.key in * pgsodium--3.2.0.sql are different from the older ones. diff --git a/sql/pgsodium--3.2.0.sql b/sql/pgsodium--3.2.0.sql index bf62272..95147fc 100644 --- a/sql/pgsodium--3.2.0.sql +++ b/sql/pgsodium--3.2.0.sql @@ -77,12 +77,17 @@ CREATE FUNCTION pgsodium.trg_mask_update() AS $$ DECLARE r record; + debug bool := CASE WHEN count(1) > 0 + THEN pg_catalog.current_setting('pgsodium.debug', false)::bool + ELSE false::bool + END + FROM pg_catalog.pg_settings WHERE name ~ 'pgsodium.debug'; BEGIN IF (select bool_or(in_extension) FROM pg_event_trigger_ddl_commands()) THEN RAISE NOTICE 'skipping pgsodium mask regeneration in extension'; RETURN; END IF; - PERFORM pgsodium.update_masks(); + PERFORM pgsodium.update_masks(debug); END $$ LANGUAGE plpgsql diff --git a/src/pgsodium.c b/src/pgsodium.c index ff969b4..525ed2c 100644 --- a/src/pgsodium.c +++ b/src/pgsodium.c @@ -4,6 +4,7 @@ PG_MODULE_MAGIC; bytea *pgsodium_secret_key; static char *getkey_script = NULL; +static bool pgsodium_debug = false; /* * Checking the syntax of the masking rules @@ -108,6 +109,9 @@ _PG_init (void) get_share_path (my_exec_path, sharepath); snprintf (path, MAXPGPATH, "%s/extension/%s", sharepath, PG_GETKEY_EXEC); + DefineCustomBoolVariable("pgsodium.debug", "show pgsodium debug messages", + NULL, &pgsodium_debug, false, PGC_SUSET, 0, NULL, NULL, NULL); + DefineCustomStringVariable ("pgsodium.getkey_script", "path to script that returns pgsodium root key", NULL, &getkey_script, path, PGC_POSTMASTER, 0, NULL, NULL, NULL); diff --git a/test/pgsodium_schema.sql b/test/pgsodium_schema.sql index cb2a5e3..dd5de83 100644 --- a/test/pgsodium_schema.sql +++ b/test/pgsodium_schema.sql @@ -5533,7 +5533,7 @@ SELECT function_privs_are('pgsodium'::name, proname, proargtypes::regtype[]::tex AND oidvectortypes(proargtypes) = 'bytea'; SELECT unnest(ARRAY[ - is(md5(prosrc), '4db22ce073ebb842d305c906da664e72', + is(md5(prosrc), '677fce118ea17f94576d6d2bd67b0540', format('Function pgsodium.%s(%s) body should match checksum', proname, pg_get_function_identity_arguments(oid)) ), From 458394d0c47e85f6413df9685d5499c9a9395a92 Mon Sep 17 00:00:00 2001 From: Jehan-Guillaume de Rorthais Date: Wed, 21 Jun 2023 14:56:19 +0200 Subject: [PATCH 14/16] Split trigger and view creation in different functions Before this commit, triggers and views were all recreated each time a matching event trigger was exectued. Now, create_mask_view(...) only create or replace the old view on top of a table with some encrypted fields. It doesn't drop/create encryption trigger anymore. The previous code generating encryption triggers has been moved to a new dedicated function called create_mask_colum(...). The event trigger used to call update_masks() which was rebuilding all the view and all the triggers. thanks of the split of create_mask_view() and create_mask_colum(), it can now calls either of these function based on the object type of the event. --- sql/pgsodium--3.1.7--3.2.0.sql | 218 ++++++++++++++++++++++++++++++++- sql/pgsodium--3.2.0.sql | 181 +++++++++++++++++++-------- test/pgsodium_schema.sql | 51 ++++++-- 3 files changed, 392 insertions(+), 58 deletions(-) diff --git a/sql/pgsodium--3.1.7--3.2.0.sql b/sql/pgsodium--3.1.7--3.2.0.sql index 48e0845..404f0cf 100644 --- a/sql/pgsodium--3.1.7--3.2.0.sql +++ b/sql/pgsodium--3.1.7--3.2.0.sql @@ -18,7 +18,10 @@ DROP FUNCTION IF EXISTS pgsodium.mask_columns(oid); DROP SCHEMA IF EXISTS pgsodium_masks; /* - * change: read debug GUC from settings. + * changes: + * - read debug GUC from settings. + * - either (re)create the trigger if the event apply on a column or + * recreate the view if the event apply on a table. */ CREATE OR REPLACE FUNCTION pgsodium.trg_mask_update() RETURNS EVENT_TRIGGER @@ -35,13 +38,224 @@ BEGIN RAISE NOTICE 'skipping pgsodium mask regeneration in extension'; RETURN; END IF; - PERFORM pgsodium.update_masks(debug); + + /* + * Loop on each event to either rebuilt the view or setup column + * encryption. + */ + FOR r IN + SELECT e.* + FROM pg_event_trigger_ddl_commands() e + WHERE EXISTS ( + SELECT FROM pg_catalog.pg_class c + JOIN pg_catalog.pg_seclabel s ON s.classoid = c.tableoid + AND s.objoid = c.oid + WHERE c.tableoid = e.classid + AND e.objid = c.oid + AND s.provider = 'pgsodium' + ) + LOOP + IF debug + THEN + RAISE NOTICE 'trg_mask_update: classid: %, objid: %, objsubid: %, tag: %, obj_type: %, schema: %, identity: %, in_ext: %', + r.classid, r.objid, r.objsubid, r.command_tag, r.object_type, + r.schema_name, r.object_identity, r.in_extension; + END IF; + + IF r.object_type = 'table column' AND r.objsubid <> 0 + THEN + /* + * Create/update encryption trigger for given attribute. This triggers + * the creation/update of the related decrypting view as well. + */ + PERFORM pgsodium.create_mask_column(r.objid, r.objsubid, debug); + ELSIF r.object_type = 'table' AND r.objsubid = 0 + THEN + /* + * Create/update the view on given table + */ + PERFORM pgsodium.create_mask_view(r.objid, debug); + END IF; + END LOOP; END $$ LANGUAGE plpgsql SET search_path=''; +/* + * change: create_mask_view(oid,integer,boolean) replaced by a new version + * of create_mask_view(oid,boolean) + */ +DROP FUNCTION pgsodium.create_mask_view(oid,integer,boolean); + +/* + * new: new version of create_mask_view(oid,boolean). Only creates or replace + * decrypting view. Doesn't generate trigger anymore, exits when no + * encrypted cols found. + */ +CREATE FUNCTION pgsodium.create_mask_view(relid oid, debug bool) +RETURNS void +AS $$ +DECLARE + m record; + body text; + source_name text; + view_owner regrole = session_user; + rule pgsodium.masking_rule; + privs aclitem[]; + priv record; +BEGIN + SELECT * INTO rule + FROM pgsodium.masking_rule AS mr + WHERE mr.attrelid = create_mask_view.relid + LIMIT 1; + + IF rule.view_name IS NULL + THEN + RAISE NOTICE 'skip decrypting view: relation % has no encrypted columns', relid::regclass; + RETURN; + END IF; + + source_name := relid::regclass::text; + + BEGIN + SELECT relacl INTO STRICT privs FROM pg_catalog.pg_class WHERE oid = rule.view_name::regclass::oid; + EXCEPTION + WHEN undefined_table THEN + SELECT relacl INTO STRICT privs FROM pg_catalog.pg_class WHERE oid = relid; + END; + + body = format( + $c$ + DROP VIEW IF EXISTS %1$s; + CREATE VIEW %1$s %5$s AS SELECT %2$s + FROM %3$s; + ALTER VIEW %1$s OWNER TO %4$s; + $c$, + rule.view_name, + pgsodium.decrypted_columns(relid), + source_name, + view_owner, + CASE WHEN rule.security_invoker THEN 'WITH (security_invoker=true)' ELSE '' END + ); + IF debug THEN + RAISE NOTICE '%', body; + END IF; + EXECUTE body; + + FOR priv IN SELECT * FROM pg_catalog.aclexplode(privs) LOOP + body = format( + $c$ + GRANT %s ON %s TO %s; + $c$, + priv.privilege_type, + rule.view_name, + priv.grantee::regrole::text + ); + IF debug THEN + RAISE NOTICE '%', body; + END IF; + EXECUTE body; + END LOOP; + + raise notice 'about to masking role % %', source_name, rule.view_name; + PERFORM pgsodium.mask_role(oid::regrole, source_name, rule.view_name) + FROM pg_roles WHERE pgsodium.has_mask(oid::regrole, source_name); + + RETURN; +END + $$ +LANGUAGE plpgsql +VOLATILE +SET search_path='pg_catalog'; + +/* + * change: fix call to new create_mask_view() + */ +CREATE OR REPLACE FUNCTION pgsodium.update_mask(target oid, debug boolean = false) +RETURNS void +AS $$ +BEGIN + PERFORM pgsodium.disable_security_label_trigger(); + PERFORM pgsodium.create_mask_view(objoid, debug) + FROM pg_catalog.pg_seclabel sl + WHERE sl.objoid = target + AND sl.label ILIKE 'ENCRYPT%' + AND sl.provider = 'pgsodium'; + PERFORM pgsodium.enable_security_label_trigger(); + RETURN; +END +$$ +LANGUAGE plpgsql +SECURITY DEFINER +SET search_path=''; + +/* + * new: this function creates encryption trigger for given column + */ +CREATE FUNCTION pgsodium.create_mask_column(relid oid, attnum integer, debug bool) +RETURNS void +AS $$ +DECLARE + body text; + m pgsodium.masking_rule; +BEGIN + -- get encryption rules for given field + SELECT * INTO STRICT m + FROM pgsodium.masking_rule AS mr + WHERE mr.attrelid = relid + AND mr.attnum = create_mask_column.attnum; + + IF m.key_id IS NULL AND m.key_id_column IS NULL + THEN + RETURN; + END IF; + + body = format( + $c$ + DROP FUNCTION IF EXISTS %1$s."%2$s_encrypt_secret_%3$s"() CASCADE; + + CREATE OR REPLACE FUNCTION %1$s."%2$s_encrypt_secret_%3$s"() + RETURNS TRIGGER + LANGUAGE plpgsql + AS $t$ + BEGIN + %4$s; + RETURN new; + END; + $t$; + + ALTER FUNCTION %1$s."%2$s_encrypt_secret_%3$s"() OWNER TO %5$s; + + DROP TRIGGER IF EXISTS "%2$s_encrypt_secret_trigger_%3$s" ON %6$s; + + CREATE TRIGGER "%2$s_encrypt_secret_trigger_%3$s" + BEFORE INSERT OR UPDATE OF "%3$s" ON %6$s + FOR EACH ROW + EXECUTE FUNCTION %1$s."%2$s_encrypt_secret_%3$s" (); + $c$, + m.relnamespace, + m.relname, + m.attname, + pgsodium.encrypted_column(relid, m), + session_user, + relid::regclass::text + ); + IF debug THEN + RAISE NOTICE '%', body; + END IF; + EXECUTE body; + + -- update related view + PERFORM pgsodium.create_mask_view(relid, debug); + + RETURN; +END +$$ +LANGUAGE plpgsql +SET search_path=''; + /* * change: constraint names generated by the create table pgsodium.key in * pgsodium--3.2.0.sql are different from the older ones. diff --git a/sql/pgsodium--3.2.0.sql b/sql/pgsodium--3.2.0.sql index 95147fc..6c576d1 100644 --- a/sql/pgsodium--3.2.0.sql +++ b/sql/pgsodium--3.2.0.sql @@ -87,7 +87,45 @@ BEGIN RAISE NOTICE 'skipping pgsodium mask regeneration in extension'; RETURN; END IF; - PERFORM pgsodium.update_masks(debug); + + /* + * Loop on each event to either rebuilt the view or setup column + * encryption. + */ + FOR r IN + SELECT e.* + FROM pg_event_trigger_ddl_commands() e + WHERE EXISTS ( + SELECT FROM pg_catalog.pg_class c + JOIN pg_catalog.pg_seclabel s ON s.classoid = c.tableoid + AND s.objoid = c.oid + WHERE c.tableoid = e.classid + AND e.objid = c.oid + AND s.provider = 'pgsodium' + ) + LOOP + IF debug + THEN + RAISE NOTICE 'trg_mask_update: classid: %, objid: %, objsubid: %, tag: %, obj_type: %, schema: %, identity: %, in_ext: %', + r.classid, r.objid, r.objsubid, r.command_tag, r.object_type, + r.schema_name, r.object_identity, r.in_extension; + END IF; + + IF r.object_type = 'table column' AND r.objsubid <> 0 + THEN + /* + * Create/update encryption trigger for given attribute. This triggers + * the creation/update of the related decrypting view as well. + */ + PERFORM pgsodium.create_mask_column(r.objid, r.objsubid, debug); + ELSIF r.object_type = 'table' AND r.objsubid = 0 + THEN + /* + * Create/update the view on given table + */ + PERFORM pgsodium.create_mask_view(r.objid, debug); + END IF; + END LOOP; END $$ LANGUAGE plpgsql @@ -359,12 +397,11 @@ GRANT EXECUTE ON FUNCTION pgsodium.create_key(pgsodium.key_type, text, bytea, by /* - * create_mask_view(oid, integer, boolean) + * create_mask_view(oid, boolean) * - * - drop and create view "decrypted_" for given relation - * - drop and create associated triggers to encrypt data on INSERT OR UPDATE for given relation + * Create or replace decrypting view for given relation */ -CREATE FUNCTION pgsodium.create_mask_view(relid oid, subid integer, debug boolean = false) +CREATE FUNCTION pgsodium.create_mask_view(relid oid, debug bool) RETURNS void AS $$ DECLARE m record; @@ -375,7 +412,16 @@ DECLARE privs aclitem[]; priv record; BEGIN - SELECT DISTINCT * INTO STRICT rule FROM pgsodium.masking_rule WHERE attrelid = relid AND attnum = subid; + SELECT * INTO rule + FROM pgsodium.masking_rule AS mr + WHERE mr.attrelid = create_mask_view.relid + LIMIT 1; + + IF rule.view_name IS NULL + THEN + RAISE NOTICE 'skip decrypting view: relation % has no encrypted columns', relid::regclass; + RETURN; + END IF; source_name := relid::regclass::text; @@ -419,47 +465,6 @@ BEGIN EXECUTE body; END LOOP; - FOR m IN SELECT * FROM pgsodium.mask_columns where attrelid = relid LOOP - IF m.key_id IS NULL AND m.key_id_column is NULL THEN - CONTINUE; - ELSE - body = format( - $c$ - DROP FUNCTION IF EXISTS %1$s."%2$s_encrypt_secret_%3$s"() CASCADE; - - CREATE OR REPLACE FUNCTION %1$s."%2$s_encrypt_secret_%3$s"() - RETURNS TRIGGER - LANGUAGE plpgsql - AS $t$ - BEGIN - %4$s; - RETURN new; - END; - $t$; - - ALTER FUNCTION %1$s."%2$s_encrypt_secret_%3$s"() OWNER TO %5$s; - - DROP TRIGGER IF EXISTS "%2$s_encrypt_secret_trigger_%3$s" ON %6$s; - - CREATE TRIGGER "%2$s_encrypt_secret_trigger_%3$s" - BEFORE INSERT OR UPDATE OF "%3$s" ON %6$s - FOR EACH ROW - EXECUTE FUNCTION %1$s."%2$s_encrypt_secret_%3$s" (); - $c$, - rule.relnamespace, - rule.relname, - m.attname, - pgsodium.encrypted_column(relid, m), - view_owner, - source_name - ); - if debug THEN - RAISE NOTICE '%', body; - END IF; - EXECUTE body; - END IF; - END LOOP; - raise notice 'about to masking role % %', source_name, rule.view_name; PERFORM pgsodium.mask_role(oid::regrole, source_name, rule.view_name) FROM pg_roles WHERE pgsodium.has_mask(oid::regrole, source_name); @@ -475,6 +480,81 @@ END -- FIXME no REVOKE ? -- FIXME no GRANT +/* + * create_mask_column(oid, integer) + * + * Build if needed: + * - encrypting triggers on tables with encrypted cols + * - decrypting view + * + * FIXME: add `WHERE col IS NOT NULL` to trigger definition? + */ +CREATE FUNCTION pgsodium.create_mask_column(relid oid, attnum integer, debug bool) +RETURNS void +AS $$ +DECLARE + body text; + m pgsodium.masking_rule; +BEGIN + -- get encryption rules for given field + SELECT * INTO STRICT m + FROM pgsodium.masking_rule AS mr + WHERE mr.attrelid = relid + AND mr.attnum = create_mask_column.attnum; + + IF m.key_id IS NULL AND m.key_id_column IS NULL + THEN + RETURN; + END IF; + + body = format( + $c$ + DROP FUNCTION IF EXISTS %1$s."%2$s_encrypt_secret_%3$s"() CASCADE; + + CREATE OR REPLACE FUNCTION %1$s."%2$s_encrypt_secret_%3$s"() + RETURNS TRIGGER + LANGUAGE plpgsql + AS $t$ + BEGIN + %4$s; + RETURN new; + END; + $t$; + + ALTER FUNCTION %1$s."%2$s_encrypt_secret_%3$s"() OWNER TO %5$s; + + DROP TRIGGER IF EXISTS "%2$s_encrypt_secret_trigger_%3$s" ON %6$s; + + CREATE TRIGGER "%2$s_encrypt_secret_trigger_%3$s" + BEFORE INSERT OR UPDATE OF "%3$s" ON %6$s + FOR EACH ROW + EXECUTE FUNCTION %1$s."%2$s_encrypt_secret_%3$s" (); + $c$, + m.relnamespace, + m.relname, + m.attname, + pgsodium.encrypted_column(relid, m), + session_user, + relid::regclass::text + ); + IF debug THEN + RAISE NOTICE '%', body; + END IF; + EXECUTE body; + + -- update related view + PERFORM pgsodium.create_mask_view(relid, debug); + + RETURN; +END +$$ +LANGUAGE plpgsql +SET search_path=''; + +-- FIXME no OWNER +-- FIXME no REVOKE ? +-- FIXME no GRANT + /* * (bytea, bytea, bytea, bytea) */ @@ -2607,7 +2687,7 @@ RETURNS void AS $$ BEGIN PERFORM pgsodium.disable_security_label_trigger(); - PERFORM pgsodium.create_mask_view(objoid, objsubid, debug) + PERFORM pgsodium.create_mask_view(objoid, debug) FROM pg_catalog.pg_seclabel sl WHERE sl.objoid = target AND sl.label ILIKE 'ENCRYPT%' @@ -2670,7 +2750,10 @@ CREATE FUNCTION pgsodium.version() SECURITY LABEL FOR pgsodium ON COLUMN pgsodium.key.raw_key IS 'ENCRYPT WITH KEY COLUMN parent_key ASSOCIATED (id, associated_data) NONCE raw_key_nonce'; -SELECT * FROM pgsodium.update_mask('pgsodium.key'::regclass::oid); +SELECT pgsodium.create_mask_column(a.attrelid, a.attnum, true) +FROM pg_catalog.pg_attribute a +WHERE a.attrelid = 'pgsodium.key'::regclass + AND a.attname = 'raw_key'; -- FIXME: should we really keep it as keyholder has been deprecated in the same -- release decrypted_key appeared?? diff --git a/test/pgsodium_schema.sql b/test/pgsodium_schema.sql index dd5de83..1e1d6d3 100644 --- a/test/pgsodium_schema.sql +++ b/test/pgsodium_schema.sql @@ -24,7 +24,8 @@ SELECT bag_eq($$ $$ VALUES ('event trigger pgsodium_trg_mask_update' ::text), ('function pgsodium.create_key(pgsodium.key_type,text,bytea,bytea,uuid,bytea,timestamp with time zone,text)' ::text), - ('function pgsodium.create_mask_view(oid,integer,boolean)' ::text), + ('function pgsodium.create_mask_column(oid,integer,boolean)' ::text), + ('function pgsodium.create_mask_view(oid,boolean)' ::text), ('function pgsodium.crypto_aead_det_decrypt(bytea,bytea,bigint,bytea,bytea)' ::text), ('function pgsodium.crypto_aead_det_decrypt(bytea,bytea,bytea,bytea)' ::text), ('function pgsodium.crypto_aead_det_decrypt(bytea,bytea,uuid)' ::text), @@ -828,6 +829,7 @@ WHERE rolname NOT IN ('pg_read_all_data','pg_write_all_data','pgsodium_keymaker' SELECT functions_are('pgsodium', ARRAY[ 'create_key', + 'create_mask_column', 'create_mask_view', 'crypto_aead_det_decrypt', 'crypto_aead_det_encrypt', @@ -971,7 +973,7 @@ SELECT function_privs_are('pgsodium'::name, proname, proargtypes::regtype[]::tex AND oidvectortypes(proargtypes) = 'pgsodium.key_type, text, bytea, bytea, uuid, bytea, timestamp with time zone, text'; SELECT unnest(ARRAY[ - is(md5(prosrc), 'fb42e03b118baa4eec1ff6fd3773ef3e', + is(md5(prosrc), '1e789b3ce330898b7a537e71e010de37', format('Function pgsodium.%s(%s) body should match checksum', proname, pg_get_function_identity_arguments(oid)) ), @@ -990,21 +992,56 @@ SELECT unnest(ARRAY[ ]) FROM pg_catalog.pg_proc WHERE pronamespace = 'pgsodium'::regnamespace - AND proname = 'create_mask_view' + AND proname = 'create_mask_column' AND oidvectortypes(proargtypes) = 'oid, integer, boolean'; SELECT function_privs_are('pgsodium'::name, proname, proargtypes::regtype[]::text[], 'postgres', '{EXECUTE}'::text[]) FROM pg_catalog.pg_proc WHERE pronamespace = 'pgsodium'::regnamespace - AND proname = 'create_mask_view' + AND proname = 'create_mask_column' AND oidvectortypes(proargtypes) = 'oid, integer, boolean'; SELECT function_privs_are('pgsodium'::name, proname, proargtypes::regtype[]::text[], 'public', '{EXECUTE}'::text[]) FROM pg_catalog.pg_proc WHERE pronamespace = 'pgsodium'::regnamespace - AND proname = 'create_mask_view' + AND proname = 'create_mask_column' AND oidvectortypes(proargtypes) = 'oid, integer, boolean'; +SELECT unnest(ARRAY[ + is(md5(prosrc), 'f36b8a4331cd67cd2f2644d0b01dacd7', + format('Function pgsodium.%s(%s) body should match checksum', + proname, pg_get_function_identity_arguments(oid)) + ), + function_owner_is( + 'pgsodium'::name, proname, + proargtypes::regtype[]::name[], 'postgres'::name, + format('Function pgsodium.%s(%s) owner is %s', + proname, pg_get_function_identity_arguments(oid), 'postgres') + ), + function_lang_is('pgsodium'::name, proname, proargtypes::regtype[]::name[], 'plpgsql'::name ), + function_returns('pgsodium'::name, proname, proargtypes::regtype[]::name[], 'void' ), + volatility_is('pgsodium'::name, proname, proargtypes::regtype[]::name[], 'volatile'), + isnt_definer('pgsodium'::name, proname, proargtypes::regtype[]::name[]), + isnt_strict('pgsodium'::name, proname, proargtypes::regtype[]::name[]), + is_normal_function('pgsodium'::name, proname, proargtypes::regtype[]::name[]) +]) + FROM pg_catalog.pg_proc + WHERE pronamespace = 'pgsodium'::regnamespace + AND proname = 'create_mask_view' + AND oidvectortypes(proargtypes) = 'oid, boolean'; + +SELECT function_privs_are('pgsodium'::name, proname, proargtypes::regtype[]::text[], 'postgres', '{EXECUTE}'::text[]) + FROM pg_catalog.pg_proc + WHERE pronamespace = 'pgsodium'::regnamespace + AND proname = 'create_mask_view' + AND oidvectortypes(proargtypes) = 'oid, boolean'; + +SELECT function_privs_are('pgsodium'::name, proname, proargtypes::regtype[]::text[], 'public', '{EXECUTE}'::text[]) + FROM pg_catalog.pg_proc + WHERE pronamespace = 'pgsodium'::regnamespace + AND proname = 'create_mask_view' + AND oidvectortypes(proargtypes) = 'oid, boolean'; + SELECT unnest(ARRAY[ is(md5(prosrc), '27fbda23b76401e3f3013342ead60241', format('Function pgsodium.%s(%s) body should match checksum', @@ -5533,7 +5570,7 @@ SELECT function_privs_are('pgsodium'::name, proname, proargtypes::regtype[]::tex AND oidvectortypes(proargtypes) = 'bytea'; SELECT unnest(ARRAY[ - is(md5(prosrc), '677fce118ea17f94576d6d2bd67b0540', + is(md5(prosrc), 'acb5451db837a533bc4aa933b6dc1e54', format('Function pgsodium.%s(%s) body should match checksum', proname, pg_get_function_identity_arguments(oid)) ), @@ -5568,7 +5605,7 @@ SELECT function_privs_are('pgsodium'::name, proname, proargtypes::regtype[]::tex AND oidvectortypes(proargtypes) = ''; SELECT unnest(ARRAY[ - is(md5(prosrc), '382a14e794ccad16439301eb9f8592b0', + is(md5(prosrc), '761f590790076abed9f7836036af6740', format('Function pgsodium.%s(%s) body should match checksum', proname, pg_get_function_identity_arguments(oid)) ), From 04e18df55233fc8b1be8422efb802d9c815b4d12 Mon Sep 17 00:00:00 2001 From: Jehan-Guillaume de Rorthais Date: Wed, 21 Jun 2023 16:22:03 +0200 Subject: [PATCH 15/16] Implement generic encryption trigger in C This commit creates C trigger functions "trg_encrypt_using_key_col()" and "trg_encrypt_using_key_id()" able to encrypt any column in any table no matter the name or position of the column. That means we now pass to the trigger the column name we need to encrypt and other infos about the encryption key, additional data, nonce, etc. In consequence, we now use the same two functions for all our encryption triggers. This avoids the generation of a dedicated plpgsql trigger for each column we need to encrypt. Creating generic triggers for any table/column is not possible in plpgsql because you can not reference a record's field using a variable. Plpgsql is not able to dereference a column inside the "new" record. --- sql/pgsodium--3.1.7--3.2.0.sql | 216 ++++++++++++++++++--- sql/pgsodium--3.2.0.sql | 229 +++++++--------------- src/tce.c | 339 +++++++++++++++++++++++++++++++++ test/pgsodium_schema.sql | 195 ++++++++----------- 4 files changed, 681 insertions(+), 298 deletions(-) create mode 100644 src/tce.c diff --git a/sql/pgsodium--3.1.7--3.2.0.sql b/sql/pgsodium--3.1.7--3.2.0.sql index 404f0cf..62725da 100644 --- a/sql/pgsodium--3.1.7--3.2.0.sql +++ b/sql/pgsodium--3.1.7--3.2.0.sql @@ -89,6 +89,105 @@ SET search_path=''; */ DROP FUNCTION pgsodium.create_mask_view(oid,integer,boolean); +/* + * change: droped from 3.2.0 + */ +DROP FUNCTION pgsodium.encrypted_columns(oid); +DROP FUNCTION pgsodium.encrypted_column(oid, record); + +/* + * change: add nspname column to pgsodium.masking_rule. We need to DROP the + * view and mask_columns as it depends on it. + */ +DROP VIEW pgsodium.mask_columns; +DROP VIEW pgsodium.masking_rule; + +CREATE OR REPLACE VIEW pgsodium.masking_rule AS + WITH const AS ( + SELECT + 'encrypt +with +key +id +([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})' + AS pattern_key_id, + 'encrypt +with +key +column +([\w\"\-$]+)' + AS pattern_key_id_column, + '(?<=associated) +\(([\w\"\-$, ]+)\)' + AS pattern_associated_columns, + '(?<=nonce) +([\w\"\-$]+)' + AS pattern_nonce_column, + '(?<=decrypt with view) +([\w\"\-$]+\.[\w\"\-$]+)' + AS pattern_view_name, + '(?<=security invoker)' + AS pattern_security_invoker + ), + rules_from_seclabels AS ( + SELECT + sl.objoid AS attrelid, + sl.objsubid AS attnum, + c.relnamespace::regnamespace, + c.relname, + n.nspname, + a.attname, + pg_catalog.format_type(a.atttypid, a.atttypmod), + sl.label AS col_description, + (regexp_match(sl.label, k.pattern_key_id_column, 'i'))[1] AS key_id_column, + (regexp_match(sl.label, k.pattern_key_id, 'i'))[1] AS key_id, + (regexp_match(sl.label, k.pattern_associated_columns, 'i'))[1] AS associated_columns, + (regexp_match(sl.label, k.pattern_nonce_column, 'i'))[1] AS nonce_column, + coalesce((regexp_match(sl2.label, k.pattern_view_name, 'i'))[1], + c.relnamespace::regnamespace || '.' || quote_ident('decrypted_' || c.relname)) AS view_name, + 100 AS priority, + (regexp_match(sl.label, k.pattern_security_invoker, 'i'))[1] IS NOT NULL AS security_invoker + FROM const k, + pg_catalog.pg_seclabel sl + JOIN pg_catalog.pg_class c ON sl.classoid = c.tableoid AND sl.objoid = c.oid + JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid AND sl.objoid = c.oid + JOIN pg_catalog.pg_attribute a ON a.attrelid = c.oid AND sl.objsubid = a.attnum + LEFT JOIN pg_catalog.pg_seclabel sl2 ON sl2.objoid = c.oid AND sl2.objsubid = 0 + WHERE a.attnum > 0 + AND c.relnamespace::regnamespace != 'pg_catalog'::regnamespace + AND NOT a.attisdropped + AND sl.label ilike 'ENCRYPT%' + AND sl.provider = 'pgsodium' + ) + SELECT + DISTINCT ON (attrelid, attnum) * + FROM rules_from_seclabels + ORDER BY attrelid, attnum, priority DESC; + +CREATE VIEW pgsodium.mask_columns AS SELECT + a.attname, + a.attrelid, + m.key_id, + m.key_id_column, + m.associated_columns, + m.nonce_column, + m.format_type +FROM pg_attribute a +LEFT JOIN pgsodium.masking_rule m ON m.attrelid = a.attrelid + AND m.attname = a.attname +WHERE a.attnum > 0 -- exclude ctid, cmin, cmax + AND NOT a.attisdropped +ORDER BY a.attnum; + + +/* + * new: common trigger to encrypt any column using a key column + */ +CREATE FUNCTION pgsodium.trg_encrypt_using_key_col() +RETURNS trigger +AS '$libdir/pgsodium' +LANGUAGE C +SECURITY DEFINER; + + +/* + * new: common trigger to encrypt any column using a key id + */ +CREATE FUNCTION pgsodium.trg_encrypt_using_key_id() +RETURNS trigger +AS '$libdir/pgsodium' +LANGUAGE C +SECURITY DEFINER; + /* * new: new version of create_mask_view(oid,boolean). Only creates or replace * decrypting view. Doesn't generate trigger anymore, exits when no @@ -200,6 +299,10 @@ AS $$ DECLARE body text; m pgsodium.masking_rule; + tgname text; + tgf text; + tgargs text; + attname text; BEGIN -- get encryption rules for given field SELECT * INTO STRICT m @@ -207,40 +310,58 @@ BEGIN WHERE mr.attrelid = relid AND mr.attnum = create_mask_column.attnum; - IF m.key_id IS NULL AND m.key_id_column IS NULL + tgname = m.relname || '_encrypt_secret_trigger_' || m.attname; + tgargs = pg_catalog.quote_literal(m.attname); -- FIXME: test? + + IF m.key_id_column IS NOT NULL THEN - RETURN; + tgf = 'trg_encrypt_using_key_col'; + tgargs = pg_catalog.format('%s, %L', tgargs, m.key_id_column); + ELSIF m.key_id IS NOT NULL + THEN + tgf = 'trg_encrypt_using_key_id'; + tgargs = pg_catalog.format('%s, %L', tgargs, m.key_id); + ELSE + -- FIXME trigger set col to NULL + END IF; + + IF m.nonce_column IS NOT NULL + THEN tgargs = pg_catalog.format('%s, %L', tgargs, m.nonce_column); + END IF; + + IF m.associated_columns IS NOT NULL + THEN + IF m.nonce_column IS NULL + THEN + /* + * empty nonce is required because associated cols starts at + * the 4th argument. + */ + tgargs = pg_catalog.format('%s, %L', tgargs, ''); + END IF; + + FOR attname IN + SELECT pg_catalog.regexp_split_to_table(m.associated_columns, + '\s*,\s*') + LOOP + tgargs = pg_catalog.format('%s, %L', tgargs, attname); + END LOOP; END IF; body = format( $c$ - DROP FUNCTION IF EXISTS %1$s."%2$s_encrypt_secret_%3$s"() CASCADE; - - CREATE OR REPLACE FUNCTION %1$s."%2$s_encrypt_secret_%3$s"() - RETURNS TRIGGER - LANGUAGE plpgsql - AS $t$ - BEGIN - %4$s; - RETURN new; - END; - $t$; - - ALTER FUNCTION %1$s."%2$s_encrypt_secret_%3$s"() OWNER TO %5$s; - - DROP TRIGGER IF EXISTS "%2$s_encrypt_secret_trigger_%3$s" ON %6$s; - - CREATE TRIGGER "%2$s_encrypt_secret_trigger_%3$s" - BEFORE INSERT OR UPDATE OF "%3$s" ON %6$s - FOR EACH ROW - EXECUTE FUNCTION %1$s."%2$s_encrypt_secret_%3$s" (); - $c$, - m.relnamespace, - m.relname, - m.attname, - pgsodium.encrypted_column(relid, m), - session_user, - relid::regclass::text + DROP TRIGGER IF EXISTS %1$I ON %3$I.%4$I; + + CREATE TRIGGER %1$I BEFORE INSERT OR UPDATE OF %2$I + ON %3$I.%4$I FOR EACH ROW EXECUTE FUNCTION + pgsodium.%5$I(%6$s); + $c$, + tgname, -- 1 + m.attname, -- 2 + m.nspname, -- 3 + m.relname, -- 4 + tgf, -- 5 + tgargs -- 6 ); IF debug THEN RAISE NOTICE '%', body; @@ -263,6 +384,43 @@ SET search_path=''; ALTER TABLE pgsodium.key RENAME CONSTRAINT "pgsodium_raw" TO "key_check"; ALTER INDEX pgsodium.pgsodium_key_unique_name RENAME TO key_name_key; +/* + * change: replace old triggers + */ +DO $$ +DECLARE rs record; +BEGIN + FOR rs IN + SELECT r.oid AS reloid, r.relname, rn.nspname AS relnsp, + t.tgname, + tn.nspname AS pronsp, tp.proname, + a.attnum, a.attname + FROM pg_catalog.pg_depend d + JOIN pg_catalog.pg_trigger t ON d.objid = t.oid + JOIN pg_catalog.pg_class r ON t.tgrelid = r.oid + JOIN pg_catalog.pg_namespace rn ON r.relnamespace = rn.oid + JOIN pg_catalog.pg_proc tp ON t.tgfoid = tp.oid + JOIN pg_catalog.pg_namespace tn ON tp.pronamespace = tn.oid + JOIN pg_catalog.pg_attribute a ON d.refobjsubid = a.attnum AND d.refobjid = a.attrelid + JOIN pg_catalog.pg_seclabel l ON d.refobjsubid = l.objsubid AND d.refobjid = l.objoid + WHERE classid = 'pg_trigger'::regclass + AND l.provider = 'pgsodium' + AND l.label ILIKE 'ENCRYPT %' + AND t.tgname ~ '_encrypt_secret_trigger_' + AND tp.proname ~ '_encrypt_secret_' + AND tp.prorettype = 'trigger'::regtype + LOOP + -- DROP them all + RAISE NOTICE 'DROP TRIGGER/FUNCTION %.%.%/%.%', + rs.relnsp, rs.relname, rs.tgname, rs.pronsp, rs.proname; + EXECUTE format('DROP TRIGGER %I ON %I.%I', rs.tgname, rs.relnsp, rs.relname); + EXECUTE format('DROP FUNCTION %I.%I', rs.pronsp, rs.proname); + -- create them + PERFORM pgsodium.create_mask_column(rs.reloid, rs.attnum, true); + END LOOP; + END +$$; + /* * change: force regenerating the decrypted_key view to add the missing column * "user_data" to the view. diff --git a/sql/pgsodium--3.2.0.sql b/sql/pgsodium--3.2.0.sql index 6c576d1..0a49ee7 100644 --- a/sql/pgsodium--3.2.0.sql +++ b/sql/pgsodium--3.2.0.sql @@ -285,6 +285,7 @@ CREATE VIEW pgsodium.masking_rule AS sl.objsubid AS attnum, c.relnamespace::regnamespace, c.relname, + n.nspname, a.attname, pg_catalog.format_type(a.atttypid, a.atttypmod), sl.label AS col_description, @@ -299,6 +300,7 @@ CREATE VIEW pgsodium.masking_rule AS FROM const k, pg_catalog.pg_seclabel sl JOIN pg_catalog.pg_class c ON sl.classoid = c.tableoid AND sl.objoid = c.oid + JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid AND sl.objoid = c.oid JOIN pg_catalog.pg_attribute a ON a.attrelid = c.oid AND sl.objsubid = a.attnum LEFT JOIN pg_catalog.pg_seclabel sl2 ON sl2.objoid = c.oid AND sl2.objsubid = 0 WHERE a.attnum > 0 @@ -495,6 +497,10 @@ AS $$ DECLARE body text; m pgsodium.masking_rule; + tgname text; + tgf text; + tgargs text; + attname text; BEGIN -- get encryption rules for given field SELECT * INTO STRICT m @@ -502,40 +508,58 @@ BEGIN WHERE mr.attrelid = relid AND mr.attnum = create_mask_column.attnum; - IF m.key_id IS NULL AND m.key_id_column IS NULL + tgname = m.relname || '_encrypt_secret_trigger_' || m.attname; + tgargs = pg_catalog.quote_literal(m.attname); -- FIXME: test? + + IF m.key_id_column IS NOT NULL THEN - RETURN; + tgf = 'trg_encrypt_using_key_col'; + tgargs = pg_catalog.format('%s, %L', tgargs, m.key_id_column); + ELSIF m.key_id IS NOT NULL + THEN + tgf = 'trg_encrypt_using_key_id'; + tgargs = pg_catalog.format('%s, %L', tgargs, m.key_id); + ELSE + -- FIXME trigger set col to NULL + END IF; + + IF m.nonce_column IS NOT NULL + THEN tgargs = pg_catalog.format('%s, %L', tgargs, m.nonce_column); + END IF; + + IF m.associated_columns IS NOT NULL + THEN + IF m.nonce_column IS NULL + THEN + /* + * empty nonce is required because associated cols starts at + * the 4th argument. + */ + tgargs = pg_catalog.format('%s, %L', tgargs, ''); + END IF; + + FOR attname IN + SELECT pg_catalog.regexp_split_to_table(m.associated_columns, + '\s*,\s*') + LOOP + tgargs = pg_catalog.format('%s, %L', tgargs, attname); + END LOOP; END IF; body = format( $c$ - DROP FUNCTION IF EXISTS %1$s."%2$s_encrypt_secret_%3$s"() CASCADE; - - CREATE OR REPLACE FUNCTION %1$s."%2$s_encrypt_secret_%3$s"() - RETURNS TRIGGER - LANGUAGE plpgsql - AS $t$ - BEGIN - %4$s; - RETURN new; - END; - $t$; - - ALTER FUNCTION %1$s."%2$s_encrypt_secret_%3$s"() OWNER TO %5$s; - - DROP TRIGGER IF EXISTS "%2$s_encrypt_secret_trigger_%3$s" ON %6$s; - - CREATE TRIGGER "%2$s_encrypt_secret_trigger_%3$s" - BEFORE INSERT OR UPDATE OF "%3$s" ON %6$s - FOR EACH ROW - EXECUTE FUNCTION %1$s."%2$s_encrypt_secret_%3$s" (); - $c$, - m.relnamespace, - m.relname, - m.attname, - pgsodium.encrypted_column(relid, m), - session_user, - relid::regclass::text + DROP TRIGGER IF EXISTS %1$I ON %3$I.%4$I; + + CREATE TRIGGER %1$I BEFORE INSERT OR UPDATE OF %2$I + ON %3$I.%4$I FOR EACH ROW EXECUTE FUNCTION + pgsodium.%5$I(%6$s); + $c$, + tgname, -- 1 + m.attname, -- 2 + m.nspname, -- 3 + m.relname, -- 4 + tgf, -- 5 + tgargs -- 6 ); IF debug THEN RAISE NOTICE '%', body; @@ -555,6 +579,26 @@ SET search_path=''; -- FIXME no REVOKE ? -- FIXME no GRANT +CREATE FUNCTION pgsodium.trg_encrypt_using_key_col() + RETURNS trigger + AS '$libdir/pgsodium' + LANGUAGE C + SECURITY DEFINER; + +-- FIXME: owner? +-- FIXME: revoke? +-- FIXME: grant? + +CREATE FUNCTION pgsodium.trg_encrypt_using_key_id() + RETURNS trigger + AS '$libdir/pgsodium' + LANGUAGE C + SECURITY DEFINER; + +-- FIXME: owner? +-- FIXME: revoke? +-- FIXME: grant? + /* * (bytea, bytea, bytea, bytea) */ @@ -2338,133 +2382,6 @@ CREATE FUNCTION pgsodium.enable_security_label_trigger() RETURNS void AS -- FIXME: revoke? -- FIXME: grant? -/* - * encrypted_column(oid, record) - */ -CREATE FUNCTION pgsodium.encrypted_column(relid OID, m record) - RETURNS TEXT AS $$ -DECLARE - expression TEXT; - comma TEXT; -BEGIN - expression := ''; - comma := E' '; - expression := expression || comma; - IF m.format_type = 'text' THEN - expression := expression || format( - $f$%s = CASE WHEN %s IS NULL THEN NULL ELSE - CASE WHEN %s IS NULL THEN NULL ELSE pg_catalog.encode( - pgsodium.crypto_aead_det_encrypt( - pg_catalog.convert_to(%s, 'utf8'), - pg_catalog.convert_to((%s)::text, 'utf8'), - %s::uuid, - %s - ), - 'base64') END END$f$, - 'new.' || quote_ident(m.attname), - 'new.' || quote_ident(m.attname), - COALESCE('new.' || quote_ident(m.key_id_column), quote_literal(m.key_id)), - 'new.' || quote_ident(m.attname), - COALESCE(pgsodium.quote_assoc(m.associated_columns, true), quote_literal('')), - COALESCE('new.' || quote_ident(m.key_id_column), quote_literal(m.key_id)), - COALESCE('new.' || quote_ident(m.nonce_column), 'NULL') - ); - ELSIF m.format_type = 'bytea' THEN - expression := expression || format( - $f$%s = CASE WHEN %s IS NULL THEN NULL ELSE - CASE WHEN %s IS NULL THEN NULL ELSE - pgsodium.crypto_aead_det_encrypt(%s::bytea, pg_catalog.convert_to((%s)::text, 'utf8'), - %s::uuid, - %s - ) END END$f$, - 'new.' || quote_ident(m.attname), - 'new.' || quote_ident(m.attname), - COALESCE('new.' || quote_ident(m.key_id_column), quote_literal(m.key_id)), - 'new.' || quote_ident(m.attname), - COALESCE(pgsodium.quote_assoc(m.associated_columns, true), quote_literal('')), - COALESCE('new.' || quote_ident(m.key_id_column), quote_literal(m.key_id)), - COALESCE('new.' || quote_ident(m.nonce_column), 'NULL') - ); - END IF; - comma := E';\n '; - RETURN expression; -END -$$ - LANGUAGE plpgsql - VOLATILE - SET search_path=''; - --- FIXME: owner? --- FIXME: revoke? --- FIXME: grant? - -/* - * encrypted_columns(oid) - */ -CREATE FUNCTION pgsodium.encrypted_columns(relid OID) - RETURNS TEXT AS $$ -DECLARE - m RECORD; - expression TEXT; - comma TEXT; -BEGIN - expression := ''; - comma := E' '; - FOR m IN SELECT * FROM pgsodium.mask_columns where attrelid = relid LOOP - IF m.key_id IS NULL AND m.key_id_column is NULL THEN - CONTINUE; - ELSE - expression := expression || comma; - IF m.format_type = 'text' THEN - expression := expression || format( - $f$%s = CASE WHEN %s IS NULL THEN NULL ELSE - CASE WHEN %s IS NULL THEN NULL ELSE pg_catalog.encode( - pgsodium.crypto_aead_det_encrypt( - pg_catalog.convert_to(%s, 'utf8'), - pg_catalog.convert_to((%s)::text, 'utf8'), - %s::uuid, - %s - ), - 'base64') END END$f$, - 'new.' || quote_ident(m.attname), - 'new.' || quote_ident(m.attname), - COALESCE('new.' || quote_ident(m.key_id_column), quote_literal(m.key_id)), - 'new.' || quote_ident(m.attname), - COALESCE(pgsodium.quote_assoc(m.associated_columns, true), quote_literal('')), - COALESCE('new.' || quote_ident(m.key_id_column), quote_literal(m.key_id)), - COALESCE('new.' || quote_ident(m.nonce_column), 'NULL') - ); - ELSIF m.format_type = 'bytea' THEN - expression := expression || format( - $f$%s = CASE WHEN %s IS NULL THEN NULL ELSE - CASE WHEN %s IS NULL THEN NULL ELSE - pgsodium.crypto_aead_det_encrypt(%s::bytea, pg_catalog.convert_to((%s)::text, 'utf8'), - %s::uuid, - %s - ) END END$f$, - 'new.' || quote_ident(m.attname), - 'new.' || quote_ident(m.attname), - COALESCE('new.' || quote_ident(m.key_id_column), quote_literal(m.key_id)), - 'new.' || quote_ident(m.attname), - COALESCE(pgsodium.quote_assoc(m.associated_columns, true), quote_literal('')), - COALESCE('new.' || quote_ident(m.key_id_column), quote_literal(m.key_id)), - COALESCE('new.' || quote_ident(m.nonce_column), 'NULL') - ); - END IF; - END IF; - comma := E';\n '; - END LOOP; - RETURN expression; -END -$$ - LANGUAGE plpgsql - VOLATILE - SET search_path=''; - --- FIXME: owner? --- FIXME: revoke? --- FIXME: grant? - /* * get_key_by_id(uuid) */ diff --git a/src/tce.c b/src/tce.c new file mode 100644 index 0000000..fc41ce1 --- /dev/null +++ b/src/tce.c @@ -0,0 +1,339 @@ +#include "pgsodium.h" +#include "executor/spi.h" +#include "parser/parse_type.h" +#include "utils/lsyscache.h" +#if PG_VERSION_NUM < 150000 +#include "utils/rel.h" +#endif + +/* keep plan to get key_id/key_context from its uuid in memory */ +static SPIPlanPtr pplan = NULL; + +static void fetch_key_meta_using_uuid(Datum keyuuid, Datum *key_id, + Datum *key_context) +{ + int ret; + bool isnull; + HeapTuple rettuple = NULL; + + /* + * Connect to SPI manager. + * Every operations now occurs in the SPI memory context! + */ + if ((ret = SPI_connect()) < 0) + /* internal error */ + elog(ERROR, "fetch_key_meta_using_uuid: SPI_connect returned %d", ret); + + /* + * First time we called this trigger, plan the query to get the + * key_id/key_context. + */ + if (pplan == NULL) + { + Oid uuidtype; + + parseTypeString("uuid", &uuidtype, NULL, false); + pplan = SPI_prepare( + "SELECT key_id, key_context " + "FROM pgsodium.decrypted_key v " + "WHERE id = $1 " + " AND key_type = 'aead-det' ", + 1, &uuidtype + ); + + if (pplan == NULL) + /* internal error */ + elog(ERROR, "fetch_key_meta_using_uuid: SPI_prepare returned %s", SPI_result_code_string(SPI_result)); + + if (SPI_keepplan(pplan)) + /* internal error */ + elog(ERROR, "fetch_key_meta_using_uuid: SPI_keepplan failed"); + } + + ret = SPI_execute_plan( pplan, &keyuuid, NULL, false, 1 ); + + if (ret < 0) + elog(ERROR, + "fetch_key_meta_using_uuid: SPI_execute_with_args returned %d", + ret); + + if (ret != SPI_OK_SELECT) + elog(ERROR, + "fetch_key_meta_using_uuid: unexpected query result (return: %d)", + ret); + + if (SPI_processed > 1) + elog(ERROR, "more than one key found for uuid %s", + DatumGetCString(DirectFunctionCall1(uuid_out, keyuuid))); + + if (SPI_processed == 0) + elog(ERROR, "no key found for uuid %s", + DatumGetCString(DirectFunctionCall1(uuid_out, keyuuid))); + + rettuple = SPI_copytuple(SPI_tuptable->vals[0]); + + /* Get key_id Datum from the query result */ + *key_id = SPI_getbinval(rettuple, SPI_tuptable->tupdesc, 1, &isnull); + if (isnull) + elog(ERROR, "key found for uuid %s is NULL", + DatumGetCString(DirectFunctionCall1(uuid_out, keyuuid))); + + /* Get key_context Datum from the query result */ + *key_context = SPI_getbinval(rettuple, SPI_tuptable->tupdesc, 2, &isnull); + if (isnull) + elog(ERROR, "key context for uuid %s is NULL", + DatumGetCString(DirectFunctionCall1(uuid_out, keyuuid))); + + SPI_finish(); +} + +/**** Trigger related code ****/ + +/* + * Common code between trg_encrypt_using_key_id() and + * trg_encrypt_using_key_col(). + */ +static Datum trg_encrypt(PG_FUNCTION_ARGS, Datum keyuuid) +{ + TriggerData *trigdata = (TriggerData *) fcinfo->context; + + Trigger *trigger = trigdata->tg_trigger; /* to get trigger name */ + Relation rel = trigdata->tg_relation; /* triggered relation */ + char **tgargs = trigger->tgargs; /* trigger arguments */ + TupleDesc tupdesc = rel->rd_att; /* tuple description */ + HeapTuple rettuple = NULL; + + int msgattnum; /* message attribute position in row */ + Datum message; /* non encrypted message */ + Datum encmsg; /* encrypted message */ + + Datum key_id; /* key_id from pgsodium.key */ + Datum key_context; /* key context from pgsodium.key */ + + bool isnull = false; + bool istext = false; + + /* prepare func call info for pgsodium_crypto_aead_det_encrypt_by_id */ + LOCAL_FCINFO(fcencinfo, 5); + + if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event)) + rettuple = trigdata->tg_trigtuple; + else if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event)) + rettuple = trigdata->tg_newtuple; + + /* get message Datum from the tuple */ + msgattnum = SPI_fnumber(tupdesc, tgargs[0]); // FIXME test return value + message = SPI_getbinval(rettuple, tupdesc, msgattnum, &isnull); + /* No encryption if the field to encrypt is NULL */ + if (isnull) + return PointerGetDatum(rettuple); + + istext = (SPI_gettypeid(tupdesc, msgattnum) == TEXTOID); + + /* if the message type is text, convert it to bytea */ + if (istext) + message = DirectFunctionCall2(pg_convert_to, message, + CStringGetDatum("utf8")); + + fetch_key_meta_using_uuid(keyuuid, &key_id, &key_context); + + /* init fields of the function call structs */ + InitFunctionCallInfoData(*fcencinfo, NULL, 5, InvalidOid, NULL, NULL); + + /* set function call args */ + /* arg 1: message to encrypt */ + fcencinfo->args[0].value = message; + fcencinfo->args[0].isnull = false; + + /* arg 2: associated data if any */ + if (trigger->tgnargs > 3) + { + int i; + StringInfoData assocdata; + + initStringInfo(&assocdata); + + for (i=3; i < trigger->tgnargs; i++) + { + int assocattnum = SPI_fnumber(tupdesc, tgargs[i]); // FIXME: test return value + Oid assocatttyp = SPI_gettypeid(tupdesc, assocattnum); // FIXME: test return value + Oid assocattfout; + Datum value; + + getTypeOutputInfo(assocatttyp, &assocattfout, &isnull); // FIXME: test if fout is valid + + value = SPI_getbinval(rettuple, tupdesc, assocattnum, &isnull); + + if (isnull) + continue; + + appendStringInfoString(&assocdata, + OidOutputFunctionCall(assocattfout, value)); + } + + fcencinfo->args[1].value = PointerGetDatum( + cstring_to_text_with_len(assocdata.data, assocdata.len)); + fcencinfo->args[1].isnull = false; + } + else + { + fcencinfo->args[1].value = (Datum) 0; + fcencinfo->args[1].isnull = true; + } + + /* arg 3: key id */ + fcencinfo->args[2].value = key_id; + fcencinfo->args[2].isnull = false; + + /* arg 4: key context */ + fcencinfo->args[3].value = key_context; + fcencinfo->args[3].isnull = false; + + /* arg 5: nonce */ + if (trigger->tgnargs > 2 && *tgargs[2] != '\0') + { + int nonceattnum = SPI_fnumber(tupdesc, tgargs[2]); // FIXME test return value + fcencinfo->args[4].value = SPI_getbinval(rettuple, tupdesc, nonceattnum, &isnull); + fcencinfo->args[4].isnull = isnull; + } + else + { + fcencinfo->args[4].value = (Datum) 0; + fcencinfo->args[4].isnull = true; + } + + /* encrypt the message */ + encmsg = pgsodium_crypto_aead_det_encrypt_by_id(fcencinfo); + + /* if the field type is text, convert the encrypted message to base64 */ + if (istext) + { + encmsg = DirectFunctionCall2(binary_encode, encmsg, + CStringGetTextDatum("base64")); + } + + /* update the row to store with the encrypted message */ + isnull = false; + rettuple = heap_modify_tuple_by_cols(rettuple, tupdesc, 1, &msgattnum, + &encmsg, &isnull); + + return PointerGetDatum(rettuple); +} + +/* + * This triggers arguments are: + * - the field name to encrypt + * - the field name holding the key uuid + * - optionally the field name holding the nonce + */ +PG_FUNCTION_INFO_V1(trg_encrypt_using_key_col); +Datum +trg_encrypt_using_key_col(PG_FUNCTION_ARGS) +{ + TriggerData *trigdata = (TriggerData *) fcinfo->context; + + Trigger *trigger = trigdata->tg_trigger; /* to get trigger name */ + char *tgname = trigger->tgname; /* trigger name */ + Relation rel = trigdata->tg_relation; /* triggered relation */ + char *relname = RelationGetRelationName(rel); /* trig'ed relname */ + char **tgargs = trigger->tgargs; /* trigger arguments */ + TupleDesc tupdesc = rel->rd_att; /* tuple description */ + HeapTuple rettuple = NULL; + + Datum keyuuid; + int keyattnum; + + bool isnull = false; + + if (!CALLED_AS_TRIGGER(fcinfo)) + /* internal error */ + elog(ERROR, + "trg_encrypt_using_key_col: not fired by trigger manager"); + + if (!TRIGGER_FIRED_FOR_ROW(trigdata->tg_event)) + /* internal error */ + elog(ERROR, "%s on %s: must be fired for row", tgname, relname); + + if (!TRIGGER_FIRED_BEFORE(trigdata->tg_event)) + /* internal error */ + elog(ERROR, "%s on %s: must be fired before event", tgname, relname); + + if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event)) + rettuple = trigdata->tg_trigtuple; + else if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event)) + rettuple = trigdata->tg_newtuple; + else + /* internal error */ + elog(ERROR, "%s on %s: cannot process DELETE events", tgname, relname); + + if (trigger->tgnargs < 2) + /* internal error */ + elog(ERROR, "%s on %s: at least two arguments are expected", + tgname, relname); + + /* get key uuid Datum from the row */ + keyattnum = SPI_fnumber(tupdesc, tgargs[1]); // FIXME test return value + keyuuid = SPI_getbinval(rettuple, tupdesc, keyattnum, &isnull); + + /* + * Set field to NULL if the key uuid is NULL. + * FIXME: shouldn't we raise an ERROR instead? + */ + if (isnull) + { + Datum encmsg = (Datum) 0; + int msgattnum = SPI_fnumber(tupdesc, tgargs[0]); // FIXME test return value; + + rettuple = heap_modify_tuple_by_cols(rettuple, tupdesc, + 1, &msgattnum, + &encmsg, &isnull); + return PointerGetDatum(rettuple); + } + + return trg_encrypt(fcinfo, keyuuid); +} + + +/* + * This triggers arguments are: + * - the field name to encrypt + * - the key uuid + * - optionally the field name holding the nonce + */ +PG_FUNCTION_INFO_V1(trg_encrypt_using_key_id); +Datum +trg_encrypt_using_key_id(PG_FUNCTION_ARGS) +{ + TriggerData *trigdata = (TriggerData *) fcinfo->context; + + Trigger *trigger = trigdata->tg_trigger; /* to get trigger name */ + char *tgname = trigger->tgname; /* trigger name */ + Relation rel = trigdata->tg_relation; /* triggered relation */ + char *relname = RelationGetRelationName(rel); /* relation name */ + char **tgargs = trigger->tgargs; /* trigger arguments */ + + Datum keyuuid; + + if (!CALLED_AS_TRIGGER(fcinfo)) + /* internal error */ + elog(ERROR, + "trg_encrypt_using_key_id: not fired by trigger manager"); + + if (!TRIGGER_FIRED_FOR_ROW(trigdata->tg_event)) + /* internal error */ + elog(ERROR, "%s on %s: must be fired for row", tgname, relname); + + if (!TRIGGER_FIRED_BEFORE(trigdata->tg_event)) + /* internal error */ + elog(ERROR, "%s on %s: must be fired before event", tgname, relname); + + if (trigger->tgnargs < 2) + /* internal error */ + elog(ERROR, "%s on %s: at least two arguments are expected", + tgname, relname); + + /* this raise an error if the uuid is invalid */ + keyuuid = DirectFunctionCall1(uuid_in, CStringGetDatum(tgargs[1])); + + return trg_encrypt(fcinfo, keyuuid); +} diff --git a/test/pgsodium_schema.sql b/test/pgsodium_schema.sql index 1e1d6d3..f07be9a 100644 --- a/test/pgsodium_schema.sql +++ b/test/pgsodium_schema.sql @@ -138,13 +138,10 @@ SELECT bag_eq($$ ('function pgsodium.derive_key(bigint,integer,bytea)' ::text), ('function pgsodium.disable_security_label_trigger()' ::text), ('function pgsodium.enable_security_label_trigger()' ::text), - ('function pgsodium.encrypted_column(oid,record)' ::text), - ('function pgsodium.encrypted_columns(oid)' ::text), ('function pgsodium.get_key_by_id(uuid)' ::text), ('function pgsodium.get_key_by_name(text)' ::text), ('function pgsodium.get_named_keys(text)' ::text), ('function pgsodium.has_mask(regrole,text)' ::text), - ('function pgsodium.key_encrypt_secret_raw_key()' ::text), ('function pgsodium.mask_role(regrole,text,text)' ::text), ('function pgsodium.pgsodium_derive(bigint,integer,bytea)' ::text), ('function pgsodium.quote_assoc(text,boolean)' ::text), @@ -155,6 +152,8 @@ SELECT bag_eq($$ ('function pgsodium.randombytes_uniform(integer)' ::text), ('function pgsodium.sodium_base642bin(text)' ::text), ('function pgsodium.sodium_bin2base64(bytea)' ::text), + ('function pgsodium.trg_encrypt_using_key_col()' ::text), + ('function pgsodium.trg_encrypt_using_key_id()' ::text), ('function pgsodium.trg_mask_update()' ::text), ('function pgsodium.update_mask(oid,boolean)' ::text), ('function pgsodium.update_masks(boolean)' ::text), @@ -434,7 +433,7 @@ SELECT triggers_are('pgsodium', 'key', ARRAY[ ]); SELECT has_trigger( 'pgsodium', 'key', 'key_encrypt_secret_trigger_raw_key'::name); -SELECT trigger_is( 'pgsodium', 'key', 'key_encrypt_secret_trigger_raw_key'::name, 'pgsodium', 'key_encrypt_secret_raw_key'); +SELECT trigger_is( 'pgsodium', 'key', 'key_encrypt_secret_trigger_raw_key'::name, 'pgsodium', 'trg_encrypt_using_key_col'); -- owner of table key SELECT table_owner_is('pgsodium'::name, 'key'::name, 'postgres'::name); @@ -634,6 +633,7 @@ SELECT columns_are('pgsodium'::name, 'masking_rule'::name, ARRAY[ 'attnum', 'relnamespace', 'relname', + 'nspname', 'attname', 'format_type', 'col_description', @@ -666,6 +666,11 @@ SELECT col_type_is( 'pgsodium', 'masking_rule', 'relname' , 'name', SELECT col_is_null( 'pgsodium', 'masking_rule', 'relname' , 'col_is_null( masking_rule.relname )'); SELECT col_hasnt_default('pgsodium', 'masking_rule', 'relname' , 'col_hasnt_default( masking_rule.relname )'); +SELECT has_column( 'pgsodium', 'masking_rule', 'nspname' , 'has column masking_rule.nspname'); +SELECT col_type_is( 'pgsodium', 'masking_rule', 'nspname' , 'name', 'type of column masking_rule.nspname is name'); +SELECT col_is_null( 'pgsodium', 'masking_rule', 'nspname' , 'col_is_null( masking_rule.nspname )'); +SELECT col_hasnt_default('pgsodium', 'masking_rule', 'nspname' , 'col_hasnt_default( masking_rule.nspname )'); + SELECT has_column( 'pgsodium', 'masking_rule', 'attname' , 'has column masking_rule.attname'); SELECT col_type_is( 'pgsodium', 'masking_rule', 'attname' , 'name', 'type of column masking_rule.attname is name'); SELECT col_is_null( 'pgsodium', 'masking_rule', 'attname' , 'col_is_null( masking_rule.attname )'); @@ -908,13 +913,10 @@ SELECT functions_are('pgsodium', ARRAY[ 'derive_key', 'disable_security_label_trigger', 'enable_security_label_trigger', - 'encrypted_column', - 'encrypted_columns', 'get_key_by_id', 'get_key_by_name', 'get_named_keys', 'has_mask', - 'key_encrypt_secret_raw_key', 'mask_role', 'pgsodium_derive', 'quote_assoc', @@ -925,6 +927,8 @@ SELECT functions_are('pgsodium', ARRAY[ 'randombytes_uniform', 'sodium_base642bin', 'sodium_bin2base64', + 'trg_encrypt_using_key_col', + 'trg_encrypt_using_key_id', 'trg_mask_update', 'update_mask', 'update_masks', @@ -973,7 +977,7 @@ SELECT function_privs_are('pgsodium'::name, proname, proargtypes::regtype[]::tex AND oidvectortypes(proargtypes) = 'pgsodium.key_type, text, bytea, bytea, uuid, bytea, timestamp with time zone, text'; SELECT unnest(ARRAY[ - is(md5(prosrc), '1e789b3ce330898b7a537e71e010de37', + is(md5(prosrc), '6dd7c0bc23499441ca059fe902f1d235', format('Function pgsodium.%s(%s) body should match checksum', proname, pg_get_function_identity_arguments(oid)) ), @@ -4968,76 +4972,6 @@ SELECT function_privs_are('pgsodium'::name, proname, proargtypes::regtype[]::tex AND proname = 'enable_security_label_trigger' AND oidvectortypes(proargtypes) = ''; -SELECT unnest(ARRAY[ - is(md5(prosrc), 'b58694d2602515d557e8637d43b6df1a', - format('Function pgsodium.%s(%s) body should match checksum', - proname, pg_get_function_identity_arguments(oid)) - ), - function_owner_is( - 'pgsodium'::name, proname, - proargtypes::regtype[]::name[], 'postgres'::name, - format('Function pgsodium.%s(%s) owner is %s', - proname, pg_get_function_identity_arguments(oid), 'postgres') - ), - function_lang_is('pgsodium'::name, proname, proargtypes::regtype[]::name[], 'plpgsql'::name ), - function_returns('pgsodium'::name, proname, proargtypes::regtype[]::name[], 'text' ), - volatility_is('pgsodium'::name, proname, proargtypes::regtype[]::name[], 'volatile'), - isnt_definer('pgsodium'::name, proname, proargtypes::regtype[]::name[]), - isnt_strict('pgsodium'::name, proname, proargtypes::regtype[]::name[]), - is_normal_function('pgsodium'::name, proname, proargtypes::regtype[]::name[]) -]) - FROM pg_catalog.pg_proc - WHERE pronamespace = 'pgsodium'::regnamespace - AND proname = 'encrypted_column' - AND oidvectortypes(proargtypes) = 'oid, record'; - -SELECT function_privs_are('pgsodium'::name, proname, proargtypes::regtype[]::text[], 'postgres', '{EXECUTE}'::text[]) - FROM pg_catalog.pg_proc - WHERE pronamespace = 'pgsodium'::regnamespace - AND proname = 'encrypted_column' - AND oidvectortypes(proargtypes) = 'oid, record'; - -SELECT function_privs_are('pgsodium'::name, proname, proargtypes::regtype[]::text[], 'public', '{EXECUTE}'::text[]) - FROM pg_catalog.pg_proc - WHERE pronamespace = 'pgsodium'::regnamespace - AND proname = 'encrypted_column' - AND oidvectortypes(proargtypes) = 'oid, record'; - -SELECT unnest(ARRAY[ - is(md5(prosrc), 'f0c7d467712320fda2f6dcafb2041fc7', - format('Function pgsodium.%s(%s) body should match checksum', - proname, pg_get_function_identity_arguments(oid)) - ), - function_owner_is( - 'pgsodium'::name, proname, - proargtypes::regtype[]::name[], 'postgres'::name, - format('Function pgsodium.%s(%s) owner is %s', - proname, pg_get_function_identity_arguments(oid), 'postgres') - ), - function_lang_is('pgsodium'::name, proname, proargtypes::regtype[]::name[], 'plpgsql'::name ), - function_returns('pgsodium'::name, proname, proargtypes::regtype[]::name[], 'text' ), - volatility_is('pgsodium'::name, proname, proargtypes::regtype[]::name[], 'volatile'), - isnt_definer('pgsodium'::name, proname, proargtypes::regtype[]::name[]), - isnt_strict('pgsodium'::name, proname, proargtypes::regtype[]::name[]), - is_normal_function('pgsodium'::name, proname, proargtypes::regtype[]::name[]) -]) - FROM pg_catalog.pg_proc - WHERE pronamespace = 'pgsodium'::regnamespace - AND proname = 'encrypted_columns' - AND oidvectortypes(proargtypes) = 'oid'; - -SELECT function_privs_are('pgsodium'::name, proname, proargtypes::regtype[]::text[], 'postgres', '{EXECUTE}'::text[]) - FROM pg_catalog.pg_proc - WHERE pronamespace = 'pgsodium'::regnamespace - AND proname = 'encrypted_columns' - AND oidvectortypes(proargtypes) = 'oid'; - -SELECT function_privs_are('pgsodium'::name, proname, proargtypes::regtype[]::text[], 'public', '{EXECUTE}'::text[]) - FROM pg_catalog.pg_proc - WHERE pronamespace = 'pgsodium'::regnamespace - AND proname = 'encrypted_columns' - AND oidvectortypes(proargtypes) = 'oid'; - SELECT unnest(ARRAY[ is(md5(prosrc), '74de169dbf6e9283728f28292f6ab6c3', format('Function pgsodium.%s(%s) body should match checksum', @@ -5178,41 +5112,6 @@ SELECT function_privs_are('pgsodium'::name, proname, proargtypes::regtype[]::tex AND proname = 'has_mask' AND oidvectortypes(proargtypes) = 'regrole, text'; -SELECT unnest(ARRAY[ - is(md5(prosrc), '52760b5073c9e61a42f29ea5c23bfe52', - format('Function pgsodium.%s(%s) body should match checksum', - proname, pg_get_function_identity_arguments(oid)) - ), - function_owner_is( - 'pgsodium'::name, proname, - proargtypes::regtype[]::name[], 'postgres'::name, - format('Function pgsodium.%s(%s) owner is %s', - proname, pg_get_function_identity_arguments(oid), 'postgres') - ), - function_lang_is('pgsodium'::name, proname, proargtypes::regtype[]::name[], 'plpgsql'::name ), - function_returns('pgsodium'::name, proname, proargtypes::regtype[]::name[], 'trigger' ), - volatility_is('pgsodium'::name, proname, proargtypes::regtype[]::name[], 'volatile'), - isnt_definer('pgsodium'::name, proname, proargtypes::regtype[]::name[]), - isnt_strict('pgsodium'::name, proname, proargtypes::regtype[]::name[]), - is_normal_function('pgsodium'::name, proname, proargtypes::regtype[]::name[]) -]) - FROM pg_catalog.pg_proc - WHERE pronamespace = 'pgsodium'::regnamespace - AND proname = 'key_encrypt_secret_raw_key' - AND oidvectortypes(proargtypes) = ''; - -SELECT function_privs_are('pgsodium'::name, proname, proargtypes::regtype[]::text[], 'postgres', '{EXECUTE}'::text[]) - FROM pg_catalog.pg_proc - WHERE pronamespace = 'pgsodium'::regnamespace - AND proname = 'key_encrypt_secret_raw_key' - AND oidvectortypes(proargtypes) = ''; - -SELECT function_privs_are('pgsodium'::name, proname, proargtypes::regtype[]::text[], 'public', '{EXECUTE}'::text[]) - FROM pg_catalog.pg_proc - WHERE pronamespace = 'pgsodium'::regnamespace - AND proname = 'key_encrypt_secret_raw_key' - AND oidvectortypes(proargtypes) = ''; - SELECT unnest(ARRAY[ is(md5(prosrc), '1b1d814a258347381f8989c6874dc01c', format('Function pgsodium.%s(%s) body should match checksum', @@ -5569,6 +5468,76 @@ SELECT function_privs_are('pgsodium'::name, proname, proargtypes::regtype[]::tex AND proname = 'sodium_bin2base64' AND oidvectortypes(proargtypes) = 'bytea'; +SELECT unnest(ARRAY[ + is(md5(prosrc), '063224624e84b8e9113b9ea077ecef0b', + format('Function pgsodium.%s(%s) body should match checksum', + proname, pg_get_function_identity_arguments(oid)) + ), + function_owner_is( + 'pgsodium'::name, proname, + proargtypes::regtype[]::name[], 'postgres'::name, + format('Function pgsodium.%s(%s) owner is %s', + proname, pg_get_function_identity_arguments(oid), 'postgres') + ), + function_lang_is('pgsodium'::name, proname, proargtypes::regtype[]::name[], 'c'::name ), + function_returns('pgsodium'::name, proname, proargtypes::regtype[]::name[], 'trigger' ), + volatility_is('pgsodium'::name, proname, proargtypes::regtype[]::name[], 'volatile'), + is_definer('pgsodium'::name, proname, proargtypes::regtype[]::name[]), + isnt_strict('pgsodium'::name, proname, proargtypes::regtype[]::name[]), + is_normal_function('pgsodium'::name, proname, proargtypes::regtype[]::name[]) +]) + FROM pg_catalog.pg_proc + WHERE pronamespace = 'pgsodium'::regnamespace + AND proname = 'trg_encrypt_using_key_col' + AND oidvectortypes(proargtypes) = ''; + +SELECT function_privs_are('pgsodium'::name, proname, proargtypes::regtype[]::text[], 'postgres', '{EXECUTE}'::text[]) + FROM pg_catalog.pg_proc + WHERE pronamespace = 'pgsodium'::regnamespace + AND proname = 'trg_encrypt_using_key_col' + AND oidvectortypes(proargtypes) = ''; + +SELECT function_privs_are('pgsodium'::name, proname, proargtypes::regtype[]::text[], 'public', '{EXECUTE}'::text[]) + FROM pg_catalog.pg_proc + WHERE pronamespace = 'pgsodium'::regnamespace + AND proname = 'trg_encrypt_using_key_col' + AND oidvectortypes(proargtypes) = ''; + +SELECT unnest(ARRAY[ + is(md5(prosrc), '9396a0be0a1a95b32240de4b44d90ae5', + format('Function pgsodium.%s(%s) body should match checksum', + proname, pg_get_function_identity_arguments(oid)) + ), + function_owner_is( + 'pgsodium'::name, proname, + proargtypes::regtype[]::name[], 'postgres'::name, + format('Function pgsodium.%s(%s) owner is %s', + proname, pg_get_function_identity_arguments(oid), 'postgres') + ), + function_lang_is('pgsodium'::name, proname, proargtypes::regtype[]::name[], 'c'::name ), + function_returns('pgsodium'::name, proname, proargtypes::regtype[]::name[], 'trigger' ), + volatility_is('pgsodium'::name, proname, proargtypes::regtype[]::name[], 'volatile'), + is_definer('pgsodium'::name, proname, proargtypes::regtype[]::name[]), + isnt_strict('pgsodium'::name, proname, proargtypes::regtype[]::name[]), + is_normal_function('pgsodium'::name, proname, proargtypes::regtype[]::name[]) +]) + FROM pg_catalog.pg_proc + WHERE pronamespace = 'pgsodium'::regnamespace + AND proname = 'trg_encrypt_using_key_id' + AND oidvectortypes(proargtypes) = ''; + +SELECT function_privs_are('pgsodium'::name, proname, proargtypes::regtype[]::text[], 'postgres', '{EXECUTE}'::text[]) + FROM pg_catalog.pg_proc + WHERE pronamespace = 'pgsodium'::regnamespace + AND proname = 'trg_encrypt_using_key_id' + AND oidvectortypes(proargtypes) = ''; + +SELECT function_privs_are('pgsodium'::name, proname, proargtypes::regtype[]::text[], 'public', '{EXECUTE}'::text[]) + FROM pg_catalog.pg_proc + WHERE pronamespace = 'pgsodium'::regnamespace + AND proname = 'trg_encrypt_using_key_id' + AND oidvectortypes(proargtypes) = ''; + SELECT unnest(ARRAY[ is(md5(prosrc), 'acb5451db837a533bc4aa933b6dc1e54', format('Function pgsodium.%s(%s) body should match checksum', From 73acc11fd6480aeed1d249efd210b751d158935f Mon Sep 17 00:00:00 2001 From: Jehan-Guillaume de Rorthais Date: Wed, 21 Jun 2023 18:01:55 +0200 Subject: [PATCH 16/16] Revoke all privilege from public on decrypting views --- sql/pgsodium--3.1.7--3.2.0.sql | 1 + sql/pgsodium--3.2.0.sql | 1 + test/pgsodium_schema.sql | 2 +- 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/sql/pgsodium--3.1.7--3.2.0.sql b/sql/pgsodium--3.1.7--3.2.0.sql index 62725da..e8f17cd 100644 --- a/sql/pgsodium--3.1.7--3.2.0.sql +++ b/sql/pgsodium--3.1.7--3.2.0.sql @@ -231,6 +231,7 @@ BEGIN CREATE VIEW %1$s %5$s AS SELECT %2$s FROM %3$s; ALTER VIEW %1$s OWNER TO %4$s; + REVOKE ALL ON %1$s FROM public; $c$, rule.view_name, pgsodium.decrypted_columns(relid), diff --git a/sql/pgsodium--3.2.0.sql b/sql/pgsodium--3.2.0.sql index 0a49ee7..d1bdb9d 100644 --- a/sql/pgsodium--3.2.0.sql +++ b/sql/pgsodium--3.2.0.sql @@ -440,6 +440,7 @@ BEGIN CREATE VIEW %1$s %5$s AS SELECT %2$s FROM %3$s; ALTER VIEW %1$s OWNER TO %4$s; + REVOKE ALL ON %1$s FROM public; $c$, rule.view_name, pgsodium.decrypted_columns(relid), diff --git a/test/pgsodium_schema.sql b/test/pgsodium_schema.sql index f07be9a..f390557 100644 --- a/test/pgsodium_schema.sql +++ b/test/pgsodium_schema.sql @@ -1012,7 +1012,7 @@ SELECT function_privs_are('pgsodium'::name, proname, proargtypes::regtype[]::tex AND oidvectortypes(proargtypes) = 'oid, integer, boolean'; SELECT unnest(ARRAY[ - is(md5(prosrc), 'f36b8a4331cd67cd2f2644d0b01dacd7', + is(md5(prosrc), '7289f138c951e90b3125b4f92e347b17', format('Function pgsodium.%s(%s) body should match checksum', proname, pg_get_function_identity_arguments(oid)) ),