From d6aafd205ed377bc2728e581a7635bb131d42623 Mon Sep 17 00:00:00 2001 From: Jehan-Guillaume de Rorthais Date: Wed, 14 Dec 2022 18:17:22 +0100 Subject: [PATCH 01/13] Create v3.1.5 extension full SQL script --- pgsodium.control | 2 +- sql/pgsodium--3.1.4--3.1.5.sql | 45 + sql/pgsodium--3.1.5.sql | 2698 ++++++++++++++++++++++++++++++++ 3 files changed, 2744 insertions(+), 1 deletion(-) create mode 100644 sql/pgsodium--3.1.4--3.1.5.sql create mode 100644 sql/pgsodium--3.1.5.sql diff --git a/pgsodium.control b/pgsodium.control index 78ee885..14e22b9 100644 --- a/pgsodium.control +++ b/pgsodium.control @@ -1,5 +1,5 @@ # pgsodium extension comment = 'Postgres extension for libsodium functions' -default_version = '3.1.4' +default_version = '3.1.5' relocatable = false schema = pgsodium diff --git a/sql/pgsodium--3.1.4--3.1.5.sql b/sql/pgsodium--3.1.4--3.1.5.sql new file mode 100644 index 0000000..497b047 --- /dev/null +++ b/sql/pgsodium--3.1.4--3.1.5.sql @@ -0,0 +1,45 @@ +/* + * 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); + +/* + * 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; + +/* + * change: remove useless "mask_schema" variable + */ +CREATE OR REPLACE FUNCTION pgsodium.mask_role(masked_role regrole, source_name text, view_name text) + RETURNS void AS $$ + DECLARE + source_schema REGNAMESPACE = (regexp_split_to_array(source_name, '\.'))[1]; + BEGIN + EXECUTE format( + 'GRANT SELECT ON pgsodium.key TO %s', + masked_role); + + EXECUTE format( + 'GRANT pgsodium_keyiduser 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'; diff --git a/sql/pgsodium--3.1.5.sql b/sql/pgsodium--3.1.5.sql new file mode 100644 index 0000000..d2752b9 --- /dev/null +++ b/sql/pgsodium--3.1.5.sql @@ -0,0 +1,2698 @@ +-- 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; + +--============================================================================== +-- TRIGGERS +--============================================================================== + + +/* + * trg_mask_update() + */ +CREATE FUNCTION pgsodium.trg_mask_update() + RETURNS EVENT_TRIGGER + AS $$ + DECLARE + r record; + BEGIN + -- FIXME: should we filter out all extensions BUT pgsodium? + -- This would allow the extension to generate the decrypted_key + -- view when creating the security label without explicitly call + -- "update_masks()" at the end of this script. + 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 ON pgsodium.key TO pgsodium_keyiduser; +GRANT INSERT, UPDATE, DELETE ON pgsodium.key 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', ''); + +--============================================================================== +-- VIEWS +--============================================================================== + +/* */ +CREATE OR REPLACE 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; + +/* */ +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 + ), + 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 + 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 SELECT ON pgsodium.masking_rule TO PUBLIC; + +/* */ +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? + +--============================================================================== +-- 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; + BEGIN + SELECT * INTO STRICT rule + FROM pgsodium.masking_rule + WHERE attrelid = relid + AND attnum = subid; + + source_name := relid::regclass; + + -- FIXME: missing grant/revoke DDL? + body = format( + $c$ + DROP VIEW IF EXISTS %s; + CREATE VIEW %s AS SELECT %s + FROM %s; + ALTER VIEW %s OWNER TO %s; + $c$, + rule.view_name, + rule.view_name, + pgsodium.decrypted_columns(relid), + source_name, + rule.view_name, + view_owner + ); + + IF debug THEN + RAISE NOTICE '%', body; + END IF; + + EXECUTE body; + + 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 %s."%s_encrypt_secret_%s"() CASCADE; + + CREATE FUNCTION %s."%s_encrypt_secret_%s"() + RETURNS TRIGGER + LANGUAGE plpgsql + AS $t$ + BEGIN + %s; + RETURN new; + END; + $t$; + + ALTER FUNCTION %s."%s_encrypt_secret_%s"() OWNER TO %s; + + DROP TRIGGER IF EXISTS "%s_encrypt_secret_trigger_%s" ON %s; + + CREATE TRIGGER "%s_encrypt_secret_trigger_%s" + BEFORE INSERT OR UPDATE OF "%s" ON %s + FOR EACH ROW + EXECUTE FUNCTION %s."%s_encrypt_secret_%s" (); + $c$, + rule.relnamespace, + rule.relname, + m.attname, + + rule.relnamespace, + rule.relname, + m.attname, + + pgsodium.encrypted_column(relid, m), + + rule.relnamespace, + rule.relname, + m.attname, + view_owner, + + rule.relname, + m.attname, + source_name, + + rule.relname, + m.attname, + m.attname, + source_name, + rule.relnamespace, + rule.relname, + m.attname + ); + + if debug THEN + RAISE NOTICE '%', body; + END IF; + + EXECUTE body; + + END IF; + END LOOP; + + PERFORM pgsodium.mask_role(oid::regrole, source_name, rule.view_name) + FROM pg_catalog.pg_roles + WHERE pgsodium.has_mask(oid::regrole, source_name); + + RETURN; + END + $$ + LANGUAGE plpgsql + VOLATILE + SET search_path=''; + +-- FIXME no OWNER +-- FIXME no REVOKE ? +-- FIXME no GRANT + +/* + * crypto_aead_det_decrypt(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_keymaker; + +/* + * 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 STRICT; + +-- 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? + +/* + * 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? +-- FIXME: revoke? +-- FIXME: grant? + +/* + * 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? +REVOKE ALL ON FUNCTION pgsodium.crypto_shorthash(bytea, bigint, bytea) FROM PUBLIC; +GRANT EXECUTE ON FUNCTION pgsodium.crypto_shorthash(bytea, bigint, bytea) TO pgsodium_keyiduser; + +/* + * 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 + STRICT 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 OR REPLACE 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 OR REPLACE 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 $$ + DECLARE + source_schema REGNAMESPACE = (regexp_split_to_array(source_name, '\.'))[1]; + BEGIN + EXECUTE format( + 'GRANT SELECT ON pgsodium.key TO %s', + masked_role); + + EXECUTE format( + 'GRANT pgsodium_keyiduser 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; + +/* + * 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_catalog.pg_extension + WHERE extname = 'pgsodium' + $$ + LANGUAGE sql + SET search_path=''; + +-- FIXME: owner? +-- FIXME: revoke? +-- FIXME: grant? + +--============================================================================== +-- 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 GRANT ALL ON TABLES TO pgsodium_keyiduser; +ALTER DEFAULT PRIVILEGES IN SCHEMA pgsodium GRANT ALL ON SEQUENCES TO pgsodium_keyiduser; + +-- 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 SEQUENCES FROM pgsodium_keyiduser; + +ALTER DEFAULT PRIVILEGES IN SCHEMA pgsodium GRANT ALL ON TABLES TO pgsodium_keyholder; +ALTER DEFAULT PRIVILEGES IN SCHEMA pgsodium GRANT ALL ON SEQUENCES TO pgsodium_keyholder; + +REVOKE ALL ON ALL TABLES IN SCHEMA pgsodium FROM pgsodium_keyiduser; +REVOKE ALL ON ALL SEQUENCES IN SCHEMA pgsodium FROM pgsodium_keyiduser; + +GRANT ALL ON ALL TABLES IN SCHEMA pgsodium TO pgsodium_keymaker; +GRANT ALL ON ALL SEQUENCES IN SCHEMA pgsodium TO pgsodium_keymaker; + +--============================================================================== +-- 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: why revoke not generated ? +-- FIXME: why grant not generated ? + +GRANT SELECT ON pgsodium.decrypted_key TO pgsodium_keymaker; From 9abb53603a585826e6a0f89bb3a2f883eab68da8 Mon Sep 17 00:00:00 2001 From: Jehan-Guillaume de Rorthais Date: Thu, 15 Dec 2022 16:20:57 +0100 Subject: [PATCH 02/13] Remove useless pgsodium.encrypted_columns --- sql/pgsodium--3.1.4--3.1.5.sql | 5 +++ sql/pgsodium--3.1.5.sql | 67 ---------------------------------- 2 files changed, 5 insertions(+), 67 deletions(-) diff --git a/sql/pgsodium--3.1.4--3.1.5.sql b/sql/pgsodium--3.1.4--3.1.5.sql index 497b047..df74755 100644 --- a/sql/pgsodium--3.1.4--3.1.5.sql +++ b/sql/pgsodium--3.1.4--3.1.5.sql @@ -8,6 +8,11 @@ DROP FUNCTION IF EXISTS pgsodium.create_mask_view(oid, boolean); */ DROP FUNCTION IF EXISTS pgsodium.mask_columns(oid); +/* + * change: useless code since 3.1.1 and the introduction of encrypted_column(oid) + */ +DROP FUNCTION IF EXISTS pgsodium.encrypted_columns(oid); + /* * change: schema "pgsodium_masks" removed in 3.0.4 * FIXME: how the extension handle bw compatibility when a table having a view diff --git a/sql/pgsodium--3.1.5.sql b/sql/pgsodium--3.1.5.sql index d2752b9..6019afa 100644 --- a/sql/pgsodium--3.1.5.sql +++ b/sql/pgsodium--3.1.5.sql @@ -2301,73 +2301,6 @@ CREATE FUNCTION pgsodium.encrypted_column(relid OID, m record) -- 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) */ From a4486ea378353d5485b557d3c0376ac9c2b1c477 Mon Sep 17 00:00:00 2001 From: Jehan-Guillaume de Rorthais Date: Fri, 16 Dec 2022 15:41:01 +0100 Subject: [PATCH 03/13] Remove some useless vars --- sql/pgsodium--3.1.4--3.1.5.sql | 72 +++++++++++++++++++++++++++++++ sql/pgsodium--3.1.5.sql | 78 +++++++++++++++------------------- 2 files changed, 107 insertions(+), 43 deletions(-) diff --git a/sql/pgsodium--3.1.4--3.1.5.sql b/sql/pgsodium--3.1.4--3.1.5.sql index df74755..46d91b1 100644 --- a/sql/pgsodium--3.1.4--3.1.5.sql +++ b/sql/pgsodium--3.1.4--3.1.5.sql @@ -22,6 +22,28 @@ DROP FUNCTION IF EXISTS pgsodium.encrypted_columns(oid); */ DROP SCHEMA IF EXISTS pgsodium_masks; +/* + * change: remove useless "r" variable + */ +CREATE OR REPLACE FUNCTION pgsodium.trg_mask_update() + RETURNS EVENT_TRIGGER + AS $$ + BEGIN + -- FIXME: should we filter out all extensions BUT pgsodium? + -- This would allow the extension to generate the decrypted_key + -- view when creating the security label without explicitly call + -- "update_masks()" at the end of this script. + 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=''; + /* * change: remove useless "mask_schema" variable */ @@ -48,3 +70,53 @@ CREATE OR REPLACE FUNCTION pgsodium.mask_role(masked_role regrole, source_name t LANGUAGE plpgsql SECURITY DEFINER SET search_path='pg_catalog'; + +/* + * change: remove useless "comma" and "expression" variables + */ +CREATE OR REPLACE FUNCTION pgsodium.encrypted_column(relid OID, m record) + RETURNS TEXT AS $$ + BEGIN + IF m.format_type = 'text' THEN + RETURN 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 + RETURN 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; + RAISE 'Not supported type % for encryoted column %', + m.format_type, m.attname; + END + $$ + LANGUAGE plpgsql + VOLATILE + SET search_path=''; diff --git a/sql/pgsodium--3.1.5.sql b/sql/pgsodium--3.1.5.sql index 6019afa..0d9d0c9 100644 --- a/sql/pgsodium--3.1.5.sql +++ b/sql/pgsodium--3.1.5.sql @@ -52,8 +52,6 @@ GRANT pgsodium_keyiduser TO pgsodium_keyholder; CREATE FUNCTION pgsodium.trg_mask_update() RETURNS EVENT_TRIGGER AS $$ - DECLARE - r record; BEGIN -- FIXME: should we filter out all extensions BUT pgsodium? -- This would allow the extension to generate the decrypted_key @@ -2246,51 +2244,45 @@ CREATE FUNCTION pgsodium.enable_security_label_trigger() RETURNS void AS */ 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') - ); + RETURN 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') - ); + RETURN 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; + RAISE 'Not supported type % for encryoted column %', + m.format_type, m.attname; END $$ LANGUAGE plpgsql From 9d3803f7435f4d348e0ba470f27add8e34bbaa4b Mon Sep 17 00:00:00 2001 From: Jehan-Guillaume de Rorthais Date: Fri, 16 Dec 2022 16:31:11 +0100 Subject: [PATCH 04/13] Spacing and indent --- sql/pgsodium--3.1.5.sql | 264 ++++++++++++++++++++-------------------- 1 file changed, 135 insertions(+), 129 deletions(-) diff --git a/sql/pgsodium--3.1.5.sql b/sql/pgsodium--3.1.5.sql index 0d9d0c9..2929d88 100644 --- a/sql/pgsodium--3.1.5.sql +++ b/sql/pgsodium--3.1.5.sql @@ -13,28 +13,28 @@ COMMENT ON EXTENSION pgsodium IS /* */ DO $$ -DECLARE - new_role text; -BEGIN - FOREACH new_role IN ARRAY - ARRAY['pgsodium_keyiduser', - 'pgsodium_keyholder', + 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 + 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 @@ -740,8 +740,9 @@ CREATE FUNCTION pgsodium.crypto_aead_ietf_encrypt(message bytea, additional byte key pgsodium.decrypted_key; BEGIN SELECT * INTO STRICT key - FROM pgsodium.decrypted_key v - WHERE id = key_uuid AND key_type = 'aead-ietf'; + 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); @@ -819,8 +820,9 @@ CREATE FUNCTION pgsodium.crypto_auth(message bytea, key_uuid uuid) key pgsodium.decrypted_key; BEGIN SELECT * INTO STRICT key - FROM pgsodium.decrypted_key v - WHERE id = key_uuid AND key_type = 'auth'; + 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); @@ -872,8 +874,9 @@ CREATE FUNCTION pgsodium.crypto_auth_hmacsha256(message bytea, key_uuid uuid) key pgsodium.decrypted_key; BEGIN SELECT * INTO STRICT key - FROM pgsodium.decrypted_key v - WHERE id = key_uuid AND key_type = 'hmacsha256'; + 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); @@ -938,8 +941,9 @@ CREATE FUNCTION pgsodium.crypto_auth_hmacsha256_verify(signature bytea, message key pgsodium.decrypted_key; BEGIN SELECT * INTO STRICT key - FROM pgsodium.decrypted_key v - WHERE id = key_uuid AND key_type = 'hmacsha256'; + 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); @@ -991,8 +995,9 @@ CREATE FUNCTION pgsodium.crypto_auth_hmacsha512(message bytea, key_uuid uuid) key pgsodium.decrypted_key; BEGIN SELECT * INTO STRICT key - FROM pgsodium.decrypted_key v - WHERE id = key_uuid AND key_type = 'hmacsha512'; + 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); @@ -1057,8 +1062,9 @@ CREATE FUNCTION pgsodium.crypto_auth_hmacsha512_verify(signature bytea, message key pgsodium.decrypted_key; BEGIN SELECT * INTO STRICT key - FROM pgsodium.decrypted_key v - WHERE id = key_uuid AND key_type = 'hmacsha512'; + 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); @@ -1123,8 +1129,9 @@ CREATE FUNCTION pgsodium.crypto_auth_verify(mac bytea, message bytea, key_uuid u key pgsodium.decrypted_key; BEGIN SELECT * INTO STRICT key - FROM pgsodium.decrypted_key v - WHERE id = key_uuid AND key_type = 'auth'; + 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); @@ -1294,8 +1301,9 @@ CREATE FUNCTION pgsodium.crypto_generichash(message bytea, key_uuid uuid) key pgsodium.decrypted_key; BEGIN SELECT * INTO STRICT key - FROM pgsodium.decrypted_key v - WHERE id = key_uuid AND key_type = 'generichash'; + 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); @@ -1365,20 +1373,20 @@ GRANT EXECUTE ON FUNCTION pgsodium.crypto_kdf_derive_from_key(bigint, bigint, by * 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 + 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'; + 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 + 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 @@ -1540,20 +1548,20 @@ GRANT EXECUTE ON FUNCTION pgsodium.crypto_secretbox(bytea, bytea, bigint, bytea) * 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 + 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'; + 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; + 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 @@ -1616,20 +1624,20 @@ GRANT EXECUTE ON FUNCTION pgsodium.crypto_secretbox_open(bytea, bytea, bigint, b * 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 + 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'; + 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; + 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 @@ -1682,21 +1690,20 @@ GRANT EXECUTE ON FUNCTION pgsodium.crypto_shorthash(bytea, bigint, bytea) TO pgs * 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 + 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; + 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 STRICT STABLE @@ -1855,10 +1862,11 @@ 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); + RETURNS bytea AS $$ + SELECT pgsodium.crypto_sign_update( + COALESCE(state, pgsodium.crypto_sign_init()), + message + ); $$ LANGUAGE SQL IMMUTABLE; @@ -1876,7 +1884,7 @@ initializes state if it has not already been initialized.'; */ CREATE FUNCTION pgsodium.crypto_sign_update_agg2(cur_state bytea, initial_state bytea, - message bytea) + message bytea) RETURNS bytea AS $$ SELECT pgsodium.crypto_sign_update( @@ -1897,10 +1905,11 @@ initializes state to the state passed to the aggregate as a parameter, if it has not already been initialized.'; CREATE OR REPLACE AGGREGATE pgsodium.crypto_sign_update_agg(message bytea) - ( +( SFUNC = pgsodium.crypto_sign_update_agg1, STYPE = bytea, - PARALLEL = unsafe); + PARALLEL = unsafe +); COMMENT ON AGGREGATE pgsodium.crypto_sign_update_agg(bytea) IS 'Multi-part message signing aggregate that returns a state which can @@ -1913,10 +1922,11 @@ in which message parts is processed is critical. You *must* ensure that the order of messages passed to the aggregate is invariant.'; CREATE OR REPLACE AGGREGATE pgsodium.crypto_sign_update_agg(state bytea, message bytea) - ( +( SFUNC = pgsodium.crypto_sign_update_agg2, STYPE = bytea, - PARALLEL = unsafe); + PARALLEL = unsafe +); COMMENT ON AGGREGATE pgsodium.crypto_sign_update_agg(bytea, bytea) IS 'Multi-part message signing aggregate that returns a state which can @@ -2212,8 +2222,8 @@ GRANT EXECUTE ON FUNCTION pgsodium.derive_key TO pgsodium_keymaker; /* * disable_security_label_trigger() */ -CREATE FUNCTION pgsodium.disable_security_label_trigger() RETURNS void AS - $$ +CREATE FUNCTION pgsodium.disable_security_label_trigger() + RETURNS void AS $$ ALTER EVENT TRIGGER pgsodium_trg_mask_update DISABLE; $$ LANGUAGE sql @@ -2227,8 +2237,8 @@ CREATE FUNCTION pgsodium.disable_security_label_trigger() RETURNS void AS /* * enable_security_label_trigger() */ -CREATE FUNCTION pgsodium.enable_security_label_trigger() RETURNS void AS - $$ +CREATE FUNCTION pgsodium.enable_security_label_trigger() + RETURNS void AS $$ ALTER EVENT TRIGGER pgsodium_trg_mask_update ENABLE; $$ LANGUAGE sql @@ -2297,9 +2307,8 @@ CREATE FUNCTION pgsodium.encrypted_column(relid OID, m record) * 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; + RETURNS pgsodium.valid_key AS $$ + SELECT * FROM pgsodium.valid_key WHERE id = $1; $$ SECURITY DEFINER LANGUAGE sql @@ -2313,8 +2322,7 @@ CREATE FUNCTION pgsodium.get_key_by_id(uuid) * get_key_by_name(text) */ CREATE FUNCTION pgsodium.get_key_by_name(text) - RETURNS pgsodium.valid_key - AS $$ + RETURNS pgsodium.valid_key AS $$ SELECT * from pgsodium.valid_key WHERE name = $1; $$ SECURITY DEFINER @@ -2346,13 +2354,14 @@ CREATE FUNCTION pgsodium.get_named_keys(filter 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; + 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? @@ -2363,7 +2372,7 @@ CREATE FUNCTION pgsodium.has_mask(role regrole, source_name text) */ CREATE FUNCTION pgsodium.mask_role(masked_role regrole, source_name text, view_name text) RETURNS void AS $$ - DECLARE + DECLARE source_schema REGNAMESPACE = (regexp_split_to_array(source_name, '\.'))[1]; BEGIN EXECUTE format( @@ -2513,19 +2522,18 @@ CREATE FUNCTION pgsodium.sodium_bin2base64(bin bytea) RETURNS text * update_mask(oid, boolean) */ CREATE FUNCTION pgsodium.update_mask(target oid, debug boolean = false) -RETURNS void AS + 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 $$ -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=''; @@ -2538,8 +2546,7 @@ $$ * update_masks(boolean) */ CREATE FUNCTION pgsodium.update_masks(debug boolean = false) - RETURNS void AS - $$ + RETURNS void AS $$ BEGIN PERFORM pgsodium.update_mask(objoid, debug) FROM pg_catalog.pg_seclabel sl @@ -2562,8 +2569,7 @@ CREATE FUNCTION pgsodium.update_masks(debug boolean = false) * version() */ CREATE FUNCTION pgsodium.version() - RETURNS text - AS $$ + RETURNS text AS $$ SELECT extversion FROM pg_catalog.pg_extension WHERE extname = 'pgsodium' @@ -2613,7 +2619,7 @@ GRANT ALL ON ALL SEQUENCES IN SCHEMA pgsodium TO pgsodium_keymaker; 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'; + IS 'ENCRYPT WITH KEY COLUMN parent_key ASSOCIATED (id, associated_data) NONCE raw_key_nonce'; SELECT * FROM pgsodium.update_mask('pgsodium.key'::regclass::oid); From 93c696a3dcc94b77c836bc02df453003fd843cff Mon Sep 17 00:00:00 2001 From: Jehan-Guillaume de Rorthais Date: Fri, 16 Dec 2022 16:36:26 +0100 Subject: [PATCH 05/13] Set version to 3.2.0 --- pgsodium.control | 2 +- sql/{pgsodium--3.1.4--3.1.5.sql => pgsodium--3.1.5--3.2.0.sql} | 0 sql/{pgsodium--3.1.5.sql => pgsodium--3.2.0.sql} | 0 3 files changed, 1 insertion(+), 1 deletion(-) rename sql/{pgsodium--3.1.4--3.1.5.sql => pgsodium--3.1.5--3.2.0.sql} (100%) rename sql/{pgsodium--3.1.5.sql => pgsodium--3.2.0.sql} (100%) diff --git a/pgsodium.control b/pgsodium.control index 14e22b9..3f89d95 100644 --- a/pgsodium.control +++ b/pgsodium.control @@ -1,5 +1,5 @@ # pgsodium extension comment = 'Postgres extension for libsodium functions' -default_version = '3.1.5' +default_version = '3.2.0' relocatable = false schema = pgsodium diff --git a/sql/pgsodium--3.1.4--3.1.5.sql b/sql/pgsodium--3.1.5--3.2.0.sql similarity index 100% rename from sql/pgsodium--3.1.4--3.1.5.sql rename to sql/pgsodium--3.1.5--3.2.0.sql diff --git a/sql/pgsodium--3.1.5.sql b/sql/pgsodium--3.2.0.sql similarity index 100% rename from sql/pgsodium--3.1.5.sql rename to sql/pgsodium--3.2.0.sql From 22f8adb97578220b54f16bc568c1fd21856305b7 Mon Sep 17 00:00:00 2001 From: Jehan-Guillaume de Rorthais Date: Fri, 16 Dec 2022 16:59:25 +0100 Subject: [PATCH 06/13] merge 3.1.5 in 3.2.0 --- sql/pgsodium--3.1.5--3.2.0.sql | 140 +++++++++++++++++++++++++++++++++ sql/pgsodium--3.2.0.sql | 48 ++++++++--- 2 files changed, 177 insertions(+), 11 deletions(-) diff --git a/sql/pgsodium--3.1.5--3.2.0.sql b/sql/pgsodium--3.1.5--3.2.0.sql index 46d91b1..9b4a385 100644 --- a/sql/pgsodium--3.1.5--3.2.0.sql +++ b/sql/pgsodium--3.1.5--3.2.0.sql @@ -120,3 +120,143 @@ CREATE OR REPLACE FUNCTION pgsodium.encrypted_column(relid OID, m record) LANGUAGE plpgsql VOLATILE SET search_path=''; + +/* + * change: keep user privs on regenerated views. (#58) + */ +CREATE OR REPLACE 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 %s; + CREATE VIEW %s AS SELECT %s + FROM %s; + ALTER VIEW %s OWNER TO %s; + $c$, + rule.view_name, + rule.view_name, + pgsodium.decrypted_columns(relid), + source_name, + rule.view_name, + view_owner + ); + + IF debug THEN + RAISE NOTICE '%', body; + END IF; + + EXECUTE body; + + FOR priv IN SELECT * FROM pg_catalog.aclexplode(privs) LOOP + body = format( + 'GRANT %s ON %s TO %s', + 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 %s."%s_encrypt_secret_%s"() CASCADE; + + CREATE OR REPLACE FUNCTION %s."%s_encrypt_secret_%s"() + RETURNS TRIGGER + LANGUAGE plpgsql + AS $t$ + BEGIN + %s; + RETURN new; + END; + $t$; + + ALTER FUNCTION %s."%s_encrypt_secret_%s"() OWNER TO %s; + + DROP TRIGGER IF EXISTS "%s_encrypt_secret_trigger_%s" ON %s; + + CREATE TRIGGER "%s_encrypt_secret_trigger_%s" + BEFORE INSERT OR UPDATE OF "%s" ON %s + FOR EACH ROW + EXECUTE FUNCTION %s."%s_encrypt_secret_%s" (); + $c$, + rule.relnamespace, + rule.relname, + m.attname, + rule.relnamespace, + rule.relname, + m.attname, + pgsodium.encrypted_column(relid, m), + rule.relnamespace, + rule.relname, + m.attname, + view_owner, + rule.relname, + m.attname, + source_name, + rule.relname, + m.attname, + m.attname, + source_name, + rule.relnamespace, + rule.relname, + m.attname + ); + + IF debug THEN + RAISE NOTICE '%', body; + END IF; + + EXECUTE body; + + END IF; + END LOOP; + + PERFORM pgsodium.mask_role(oid::regrole, source_name, rule.view_name) + FROM pg_catalog.pg_roles + WHERE pgsodium.has_mask(oid::regrole, source_name); + + RETURN; + END + $$ + LANGUAGE plpgsql + VOLATILE + SET search_path=''; diff --git a/sql/pgsodium--3.2.0.sql b/sql/pgsodium--3.2.0.sql index 2929d88..aef6a85 100644 --- a/sql/pgsodium--3.2.0.sql +++ b/sql/pgsodium--3.2.0.sql @@ -337,6 +337,7 @@ GRANT EXECUTE ON FUNCTION pgsodium.create_key(pgsodium.key_type, text, bytea, by * create_mask_view(oid, integer, boolean) * * - drop and create view "decrypted_" for given relation + * - restore privileges on view if applicable * - 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) @@ -347,15 +348,25 @@ CREATE FUNCTION pgsodium.create_mask_view(relid oid, subid integer, debug boolea source_name text; view_owner regrole = session_user; rule pgsodium.masking_rule; + privs aclitem[]; + priv record; BEGIN - SELECT * INTO STRICT rule + SELECT DISTINCT * INTO STRICT rule FROM pgsodium.masking_rule WHERE attrelid = relid AND attnum = subid; - source_name := relid::regclass; + 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; - -- FIXME: missing grant/revoke DDL? body = format( $c$ DROP VIEW IF EXISTS %s; @@ -377,7 +388,27 @@ CREATE FUNCTION pgsodium.create_mask_view(relid oid, subid integer, debug boolea EXECUTE body; - FOR m IN SELECT * FROM pgsodium.mask_columns WHERE attrelid = relid LOOP + -- FIXME: missing revoke DDL? + FOR priv IN SELECT * FROM pg_catalog.aclexplode(privs) LOOP + body = format( + 'GRANT %s ON %s TO %s', + 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 @@ -385,7 +416,7 @@ CREATE FUNCTION pgsodium.create_mask_view(relid oid, subid integer, debug boolea $c$ DROP FUNCTION IF EXISTS %s."%s_encrypt_secret_%s"() CASCADE; - CREATE FUNCTION %s."%s_encrypt_secret_%s"() + CREATE OR REPLACE FUNCTION %s."%s_encrypt_secret_%s"() RETURNS TRIGGER LANGUAGE plpgsql AS $t$ @@ -407,22 +438,17 @@ CREATE FUNCTION pgsodium.create_mask_view(relid oid, subid integer, debug boolea rule.relnamespace, rule.relname, m.attname, - rule.relnamespace, rule.relname, m.attname, - pgsodium.encrypted_column(relid, m), - rule.relnamespace, rule.relname, m.attname, view_owner, - rule.relname, m.attname, source_name, - rule.relname, m.attname, m.attname, @@ -432,7 +458,7 @@ CREATE FUNCTION pgsodium.create_mask_view(relid oid, subid integer, debug boolea m.attname ); - if debug THEN + IF debug THEN RAISE NOTICE '%', body; END IF; From c094564dd3318f4b2a886bd605cddc76defea05a Mon Sep 17 00:00:00 2001 From: Jehan-Guillaume de Rorthais Date: Tue, 3 Jan 2023 10:52:13 +0100 Subject: [PATCH 07/13] first functional refactored version --- sql/pgsodium--3.1.5--3.2.0.sql | 551 +++++++++++++++++--------- sql/pgsodium--3.2.0.sql | 684 ++++++++++++++++----------------- src/pgsodium.c | 4 + src/pgsodium.h | 4 + src/tce.c | 321 ++++++++++++++++ 5 files changed, 1033 insertions(+), 531 deletions(-) create mode 100644 src/tce.c diff --git a/sql/pgsodium--3.1.5--3.2.0.sql b/sql/pgsodium--3.1.5--3.2.0.sql index 9b4a385..76a1b97 100644 --- a/sql/pgsodium--3.1.5--3.2.0.sql +++ b/sql/pgsodium--3.1.5--3.2.0.sql @@ -13,6 +13,26 @@ DROP FUNCTION IF EXISTS pgsodium.mask_columns(oid); */ DROP FUNCTION IF EXISTS pgsodium.encrypted_columns(oid); +/* + * change: useless code since 3.2.0 + */ +DROP FUNCTION IF EXISTS pgsodium.encrypted_column(oid, record); + +/* + * change: useless code since 3.2.0 + */ +DROP FUNCTION IF EXISTS pgsodium.decrypted_columns(oid); + +/* + * change: useless code since 3.2.0 + */ +DROP FUNCTION IF EXISTS pgsodium.create_mask_view(oid, integer, boolean); + +/* + * change: useless code since 3.2.0 + */ +DROP VIEW IF EXISTS pgsodium.mask_columns; + /* * change: schema "pgsodium_masks" removed in 3.0.4 * FIXME: how the extension handle bw compatibility when a table having a view @@ -23,240 +43,419 @@ DROP FUNCTION IF EXISTS pgsodium.encrypted_columns(oid); DROP SCHEMA IF EXISTS pgsodium_masks; /* - * change: remove useless "r" variable + * change: replaced with "tg_tce_update()" */ -CREATE OR REPLACE FUNCTION pgsodium.trg_mask_update() +DROP FUNCTION pgsodium.trg_mask_update(); + +/* + * change: creation in 3.2.0 + */ +CREATE FUNCTION pgsodium.tg_tce_update() RETURNS EVENT_TRIGGER AS $$ + DECLARE r record; BEGIN - -- FIXME: should we filter out all extensions BUT pgsodium? - -- This would allow the extension to generate the decrypted_key - -- view when creating the security label without explicitly call - -- "update_masks()" at the end of this script. 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(); + + 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 + ) + LOOP + IF pg_catalog.current_setting('pgsodium.debug')::bool 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; + + /* + * Create/update encryption trigger for given field. This triggers + * the creation/update of the related decrypting view as well. + */ + PERFORM pgsodium.tce_update_tg(r.objid, r.objsubid); + END LOOP; END $$ LANGUAGE plpgsql SET search_path=''; /* - * change: remove useless "mask_schema" variable + * change: creation in 3.2.0 */ -CREATE OR REPLACE FUNCTION pgsodium.mask_role(masked_role regrole, source_name text, view_name text) - RETURNS void AS $$ - DECLARE - source_schema REGNAMESPACE = (regexp_split_to_array(source_name, '\.'))[1]; +CREATE FUNCTION pgsodium.tce_update_attr_tg(relid oid, attnum integer) + RETURNS void AS $tce_update_tg$ + DECLARE + body text; + rule pgsodium.masking_rule; + raw_key bytea; + key_id bigint; + tgname text; + tgf text; + tgargs text; + attname text; BEGIN - EXECUTE format( - 'GRANT SELECT ON pgsodium.key TO %s', - masked_role); + -- get encryption rules for given field + SELECT DISTINCT * INTO STRICT rule + FROM pgsodium.masking_rule AS mr + WHERE mr.attrelid = relid + AND mr.attnum = tce_update_tg.attnum; - EXECUTE format( - 'GRANT pgsodium_keyiduser TO %s', - masked_role); + tgname = 'encrypt_' || rule.attname; + tgargs = pg_catalog.quote_literal(rule.attname); - EXECUTE format( - 'GRANT ALL ON %s TO %s', - view_name, - masked_role); - RETURN; - END - $$ - LANGUAGE plpgsql - SECURITY DEFINER - SET search_path='pg_catalog'; + IF rule.key_id_column IS NOT NULL THEN + tgf = 'tg_tce_encrypt_using_key_col'; + tgargs = pg_catalog.format('%s, %L', tgargs, rule.key_id_column); + ELSIF rule.key_id IS NOT NULL THEN + tgf = 'tg_tce_encrypt_using_key_id'; + tgargs = pg_catalog.format('%s, %L', tgargs, rule.key_id); + ELSE + -- FIXME trigger set col to NULL + END IF; -/* - * change: remove useless "comma" and "expression" variables - */ -CREATE OR REPLACE FUNCTION pgsodium.encrypted_column(relid OID, m record) - RETURNS TEXT AS $$ - BEGIN - IF m.format_type = 'text' THEN - RETURN 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 - RETURN 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') - ); + IF rule.nonce_column IS NOT NULL THEN + tgargs = pg_catalog.format('%s, %L', tgargs, rule.nonce_column); + END IF; + + IF rule.associated_columns IS NOT NULL THEN + IF rule.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(rule.associated_columns, + '\s*,\s*') + LOOP + tgargs = pg_catalog.format('%s, %L', tgargs, attname); + END LOOP; + END IF; + + body = pg_catalog.format(' + 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);', + tgname, -- 1 + rule.attname, -- 2 + rule.relnamespace, -- 3 + rule.relname, -- 4 + tgf, -- 5 + tgargs -- 6 + ); + + IF pg_catalog.current_setting('pgsodium.debug')::bool THEN + RAISE NOTICE '%', body; END IF; - RAISE 'Not supported type % for encryoted column %', - m.format_type, m.attname; + + EXECUTE body; + + PERFORM pgsodium.disable_security_label_trigger(); + PERFORM pgsodium.tce_update_view(relid); + PERFORM pgsodium.enable_security_label_trigger(); + + RETURN; END - $$ + $tce_update_tg$ LANGUAGE plpgsql - VOLATILE SET search_path=''; /* - * change: keep user privs on regenerated views. (#58) + * change: creation in 3.2.0 */ -CREATE OR REPLACE FUNCTION pgsodium.create_mask_view(relid oid, subid integer, debug boolean = false) - RETURNS void AS $$ +CREATE FUNCTION pgsodium.tce_update_view(relid oid) + RETURNS void AS $tce_update_view$ DECLARE - m record; - body text; - source_name text; + r record; + origrelpath text; + viewname text; + body text = ''; + enc_cols text[]; + dec_col text; + dec_col_alias text; + ad 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; + SELECT + pg_catalog.format('%I.%I', relnamespace::regnamespace::text, relname) + INTO STRICT origrelpath + FROM pg_catalog.pg_class + WHERE oid = relid; - source_name := relid::regclass::text; + FOR r IN + SELECT * + FROM pgsodium.masking_rule AS mr + WHERE mr.attrelid = tce_update_view.relid + LOOP + IF viewname IS NULL THEN + viewname = r.view_name; + END IF; - 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; + dec_col = pg_catalog.format('%I', r.attname); + dec_col_alias = 'decrypted_' || r.attname; - body = format( - $c$ + IF r.key_id IS NOT NULL THEN + dec_col = pg_catalog.format('%s, UUID %L', dec_col, r.key_id); + ELSIF r.key_id_column IS NOT NULL THEN + dec_col = pg_catalog.format('%s, %I', dec_col, r.key_id_column); + ELSE + enc_cols = enc_cols || ARRAY[ + pg_catalog.format('NULL AS %I', dec_col_alias) + ]; + CONTINUE; + END IF; + + IF r.nonce_column IS NOT NULL THEN + dec_col = pg_catalog.format('%s, %I', dec_col, r.nonce_column); + END IF; + + IF r.associated_columns IS NOT NULL THEN + WITH a AS ( + SELECT pg_catalog.array_agg( format('%I::text', trim(v)) ) AS r + FROM pg_catalog.regexp_split_to_table(r.associated_columns, + '\s*,\s*') as v + ) + SELECT pg_catalog.array_to_string(a.r, ' || ') INTO ad + FROM a; + + dec_col = pg_catalog.format('%s, ad => %s', dec_col, ad); + END IF; + + enc_cols = enc_cols + || pg_catalog.format(E'pgsodium.tce_decrypt_col(%s) AS %s', + dec_col, dec_col_alias); + END LOOP; + + IF (viewname IS NULL) THEN + RAISE 'relation % has no encrypted columns', relid::regclass; + END IF; + + body = pg_catalog.format(' DROP VIEW IF EXISTS %s; - CREATE VIEW %s AS SELECT %s + CREATE VIEW %1$s AS + SELECT *, + %s FROM %s; - ALTER VIEW %s OWNER TO %s; - $c$, - rule.view_name, - rule.view_name, - pgsodium.decrypted_columns(relid), - source_name, - rule.view_name, + ALTER VIEW %1$s OWNER TO %4$I; + REVOKE ALL ON %1$s FROM public; + ', + viewname, -- supposed to be already escaped + array_to_string(enc_cols, E',\n'), + origrelpath, view_owner ); - IF debug THEN + IF pg_catalog.current_setting('pgsodium.debug')::bool THEN RAISE NOTICE '%', body; END IF; EXECUTE body; - FOR priv IN SELECT * FROM pg_catalog.aclexplode(privs) LOOP + 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; + + -- FIXME: missing revoke DDL? + FOR r IN SELECT * FROM pg_catalog.aclexplode(privs) LOOP body = format( - 'GRANT %s ON %s TO %s', - priv.privilege_type, - rule.view_name, - priv.grantee::regrole::text + 'GRANT %s ON %s TO %I', + r.privilege_type, + viewname, -- supposed to be already escaped + r.grantee::regrole::text ); - IF debug THEN + IF pg_catalog.current_setting('pgsodium.debug')::bool 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 %s."%s_encrypt_secret_%s"() CASCADE; - - CREATE OR REPLACE FUNCTION %s."%s_encrypt_secret_%s"() - RETURNS TRIGGER - LANGUAGE plpgsql - AS $t$ - BEGIN - %s; - RETURN new; - END; - $t$; - - ALTER FUNCTION %s."%s_encrypt_secret_%s"() OWNER TO %s; - - DROP TRIGGER IF EXISTS "%s_encrypt_secret_trigger_%s" ON %s; - - CREATE TRIGGER "%s_encrypt_secret_trigger_%s" - BEFORE INSERT OR UPDATE OF "%s" ON %s - FOR EACH ROW - EXECUTE FUNCTION %s."%s_encrypt_secret_%s" (); - $c$, - rule.relnamespace, - rule.relname, - m.attname, - rule.relnamespace, - rule.relname, - m.attname, - pgsodium.encrypted_column(relid, m), - rule.relnamespace, - rule.relname, - m.attname, - view_owner, - rule.relname, - m.attname, - source_name, - rule.relname, - m.attname, - m.attname, - source_name, - rule.relnamespace, - rule.relname, - m.attname - ); - - IF debug THEN - RAISE NOTICE '%', body; - END IF; - - EXECUTE body; + PERFORM pgsodium.mask_role(oid::regrole, origrelpath, rule.view_name) + FROM pg_catalog.pg_roles + WHERE pgsodium.has_mask(oid::regrole, origrelpath); + END + $tce_update_view$ + LANGUAGE plpgsql + SET search_path=''; - END IF; - END LOOP; +/* + * change: remove useless "mask_schema" variable + */ +CREATE OR REPLACE FUNCTION pgsodium.mask_role(masked_role regrole, source_name text, view_name text) + RETURNS void AS $$ + DECLARE + source_schema REGNAMESPACE = (regexp_split_to_array(source_name, '\.'))[1]; + BEGIN + EXECUTE format( + 'GRANT SELECT ON pgsodium.key TO %s', + masked_role); - PERFORM pgsodium.mask_role(oid::regrole, source_name, rule.view_name) - FROM pg_catalog.pg_roles - WHERE pgsodium.has_mask(oid::regrole, source_name); + EXECUTE format( + 'GRANT pgsodium_keyiduser TO %s', + masked_role); + EXECUTE format( + 'GRANT ALL ON %s TO %s', + view_name, + masked_role); RETURN; END $$ LANGUAGE plpgsql - VOLATILE + SECURITY DEFINER + SET search_path='pg_catalog'; + +/* + * change: schema-qualify "pg_shseclabel" + search_path. + */ +CREATE OR REPLACE FUNCTION pgsodium.has_mask(role regrole, source_name text) + RETURNS boolean AS $$ + SELECT EXISTS( + SELECT 1 + FROM pg_catalog.pg_shseclabel + WHERE objoid = role + AND provider = 'pgsodium' + AND label ilike 'ACCESS%' || source_name || '%') + $$ + LANGUAGE sql + SET search_path='pg_catalog'; + +CREATE OR REPLACE FUNCTION pgsodium.tce_decrypt_col( + message bytea, + keyid uuid, + nonce bytea DEFAULT NULL, + ad text DEFAULT '') + RETURNS bytea AS $$ + DECLARE + BEGIN + IF message IS NULL OR keyid IS NULL THEN + RETURN NULL; + END IF; + + RETURN pgsodium.crypto_aead_det_decrypt( + message, + pg_catalog.convert_to(ad, 'utf8'), + keyid, + nonce + ); + END + $$ + LANGUAGE plpgsql + SET search_path=''; + +-- FIXME: owner? +-- FIXME: revoke? +-- FIXME: grant? + +CREATE OR REPLACE FUNCTION pgsodium.tce_decrypt_col( + message text, + keyid uuid, + nonce bytea DEFAULT NULL, + ad text DEFAULT '') + RETURNS text AS $tce_decrypt_col$ + DECLARE + BEGIN + IF message IS NULL OR keyid IS NULL THEN + RETURN NULL; + END IF; + + RETURN pg_catalog.convert_from( + pgsodium.crypto_aead_det_decrypt( + pg_catalog.decode(message, 'base64'), + pg_catalog.convert_to(ad, 'utf8'), + keyid, + nonce + ), 'utf8' -- FIXME: this should use the column encoding? + ); + END + $tce_decrypt_col$ + LANGUAGE plpgsql SET search_path=''; + +-- FIXME: owner? +-- FIXME: revoke? +-- FIXME: grant? + +/* + * change: quote_ident() the schema name of the generated view path + * FIXME: bug: the view path given by the user in the security label might not + * be quoted correctly. + */ +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 + ), + 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], + quote_ident(c.relnamespace::regnamespace::text) || '.' || quote_ident('decrypted_' || c.relname)) AS view_name, + 100 AS priority + 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; + + +DROP EVENT TRIGGER pgsodium_trg_mask_update; +CREATE EVENT TRIGGER pgsodium_tg_tce_update + ON ddl_command_end + WHEN TAG IN ( + 'SECURITY LABEL', + 'ALTER TABLE' + ) + EXECUTE PROCEDURE pgsodium.tg_tce_update(); diff --git a/sql/pgsodium--3.2.0.sql b/sql/pgsodium--3.2.0.sql index aef6a85..97ddd33 100644 --- a/sql/pgsodium--3.2.0.sql +++ b/sql/pgsodium--3.2.0.sql @@ -45,24 +45,280 @@ GRANT pgsodium_keyiduser TO pgsodium_keyholder; -- TRIGGERS --============================================================================== +CREATE FUNCTION pgsodium.tg_tce_encrypt_using_key_col() + RETURNS trigger + AS '$libdir/pgsodium' + LANGUAGE C; + +CREATE FUNCTION pgsodium.tg_tce_encrypt_using_key_id() + RETURNS trigger + AS '$libdir/pgsodium' + LANGUAGE C; + +-- FIXME: owner? +-- FIXME: revoke? +-- FIXME: grant? /* - * trg_mask_update() + * tce_update_view(oid) + * + * Build if needed: + * - decrypting view */ -CREATE FUNCTION pgsodium.trg_mask_update() +CREATE FUNCTION pgsodium.tce_update_view(relid oid) + RETURNS void AS $$ + DECLARE + r record; + origrelpath text; + viewname text; + body text = ''; + enc_cols text[]; + dec_col text; + dec_col_alias text; + ad text; + view_owner regrole = session_user; + privs aclitem[]; + BEGIN + SELECT + pg_catalog.format('%I.%I', relnamespace::regnamespace::text, relname) + INTO STRICT origrelpath + FROM pg_catalog.pg_class + WHERE oid = relid; + + FOR r IN + SELECT * + FROM pgsodium.masking_rule AS mr + WHERE mr.attrelid = tce_update_view.relid + LOOP + IF viewname IS NULL THEN + viewname = r.view_name; + END IF; + + dec_col = pg_catalog.format('%I', r.attname); + dec_col_alias = 'decrypted_' || r.attname; + + IF r.key_id IS NOT NULL THEN + dec_col = pg_catalog.format('%s, UUID %L', dec_col, r.key_id); + ELSIF r.key_id_column IS NOT NULL THEN + dec_col = pg_catalog.format('%s, %I', dec_col, r.key_id_column); + ELSE + enc_cols = enc_cols || ARRAY[ + pg_catalog.format('NULL AS %I', dec_col_alias) + ]; + CONTINUE; + END IF; + + IF r.nonce_column IS NOT NULL THEN + dec_col = pg_catalog.format('%s, %I', dec_col, r.nonce_column); + END IF; + + IF r.associated_columns IS NOT NULL THEN + WITH a AS ( + SELECT pg_catalog.array_agg( format('%I::text', trim(v)) ) AS r + FROM pg_catalog.regexp_split_to_table(r.associated_columns, + '\s*,\s*') as v + ) + SELECT pg_catalog.array_to_string(a.r, ' || ') INTO ad + FROM a; + + dec_col = pg_catalog.format('%s, ad => %s', dec_col, ad); + END IF; + + enc_cols = enc_cols + || pg_catalog.format(E'pgsodium.tce_decrypt_col(%s) AS %s', + dec_col, dec_col_alias); + END LOOP; + + IF (viewname IS NULL) THEN + RAISE 'relation % has no encrypted columns', relid::regclass; + END IF; + + body = pg_catalog.format(' + DROP VIEW IF EXISTS %s; + CREATE VIEW %1$s AS + SELECT *, + %s + FROM %s; + ALTER VIEW %1$s OWNER TO %4$I; + REVOKE ALL ON %1$s FROM public; + ', + viewname, -- supposed to be already escaped + array_to_string(enc_cols, E',\n'), + origrelpath, + view_owner + ); + + IF pg_catalog.current_setting('pgsodium.debug')::bool THEN + RAISE NOTICE '%', body; + END IF; + + EXECUTE body; + + 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; + + -- FIXME: missing revoke DDL? + FOR r IN SELECT * FROM pg_catalog.aclexplode(privs) LOOP + body = format( + 'GRANT %s ON %s TO %I', + r.privilege_type, + viewname, -- supposed to be already escaped + r.grantee::regrole::text + ); + + IF pg_catalog.current_setting('pgsodium.debug')::bool THEN + RAISE NOTICE '%', body; + END IF; + + EXECUTE body; + END LOOP; + + PERFORM pgsodium.mask_role(oid::regrole, origrelpath, viewname) + FROM pg_catalog.pg_roles + WHERE pgsodium.has_mask(oid::regrole, origrelpath); + END + $$ + LANGUAGE plpgsql + SET search_path=''; + +-- FIXME: owner? +-- FIXME: revoke? +-- FIXME: grant? + +/* + * tce_update_attr_tg(oid, integer) + * + * Build if needed: + * - encrypting triggers on tables with encrypted cols + * - decrypting view + */ +CREATE FUNCTION pgsodium.tce_update_attr_tg(relid oid, attnum integer) + RETURNS void AS $$ + DECLARE + body text; + rule pgsodium.masking_rule; + raw_key bytea; + key_id bigint; + tgname text; + tgf text; + tgargs text; + attname text; + BEGIN + -- get encryption rules for given field + SELECT DISTINCT * INTO STRICT rule + FROM pgsodium.masking_rule AS mr + WHERE mr.attrelid = relid + AND mr.attnum = tce_update_attr_tg.attnum; + + tgname = 'encrypt_' || rule.attname; + tgargs = pg_catalog.quote_literal(rule.attname); + + IF rule.key_id_column IS NOT NULL THEN + tgf = 'tg_tce_encrypt_using_key_col'; + tgargs = pg_catalog.format('%s, %L', tgargs, rule.key_id_column); + ELSIF rule.key_id IS NOT NULL THEN + tgf = 'tg_tce_encrypt_using_key_id'; + tgargs = pg_catalog.format('%s, %L', tgargs, rule.key_id); + ELSE + -- FIXME trigger set col to NULL + END IF; + + IF rule.nonce_column IS NOT NULL THEN + tgargs = pg_catalog.format('%s, %L', tgargs, rule.nonce_column); + END IF; + + IF rule.associated_columns IS NOT NULL THEN + IF rule.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(rule.associated_columns, + '\s*,\s*') + LOOP + tgargs = pg_catalog.format('%s, %L', tgargs, attname); + END LOOP; + END IF; + + body = pg_catalog.format(' + 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);', + tgname, -- 1 + rule.attname, -- 2 + rule.relnamespace, -- 3 + rule.relname, -- 4 + tgf, -- 5 + tgargs -- 6 + ); + + IF pg_catalog.current_setting('pgsodium.debug')::bool THEN + RAISE NOTICE '%', body; + END IF; + + EXECUTE body; + + PERFORM pgsodium.disable_security_label_trigger(); + PERFORM pgsodium.tce_update_view(relid); + PERFORM pgsodium.enable_security_label_trigger(); + + RETURN; + END + $$ + LANGUAGE plpgsql + SET search_path=''; + +/* + * tg_tce_update() + */ +CREATE FUNCTION pgsodium.tg_tce_update() RETURNS EVENT_TRIGGER AS $$ + DECLARE r record; BEGIN - -- FIXME: should we filter out all extensions BUT pgsodium? - -- This would allow the extension to generate the decrypted_key - -- view when creating the security label without explicitly call - -- "update_masks()" at the end of this script. 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(); + + 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 + ) + LOOP + IF pg_catalog.current_setting('pgsodium.debug')::bool 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; + + /* + * Create/update encryption trigger for given attribute. This triggers + * the creation/update of the related decrypting view as well. + */ + PERFORM pgsodium.tce_update_attr_tg(r.objid, r.objsubid); + END LOOP; END $$ LANGUAGE plpgsql @@ -73,13 +329,13 @@ CREATE FUNCTION pgsodium.trg_mask_update() -- FIXME: grant? /* */ -CREATE EVENT TRIGGER pgsodium_trg_mask_update +CREATE EVENT TRIGGER pgsodium_tg_tce_update ON ddl_command_end WHEN TAG IN ( 'SECURITY LABEL', 'ALTER TABLE' ) - EXECUTE PROCEDURE pgsodium.trg_mask_update(); + EXECUTE PROCEDURE pgsodium.tg_tce_update(); --============================================================================== -- TYPES @@ -186,7 +442,7 @@ SELECT pg_catalog.pg_extension_config_dump('pgsodium.key', ''); --============================================================================== /* */ -CREATE OR REPLACE VIEW pgsodium.valid_key AS +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') @@ -197,7 +453,7 @@ CREATE OR REPLACE VIEW pgsodium.valid_key AS GRANT SELECT ON pgsodium.valid_key TO pgsodium_keyiduser; /* */ -CREATE OR REPLACE VIEW pgsodium.masking_rule AS +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})' @@ -225,7 +481,7 @@ CREATE OR REPLACE VIEW pgsodium.masking_rule AS (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, + quote_ident(c.relnamespace::regnamespace::text) || '.' || quote_ident('decrypted_' || c.relname)) AS view_name, 100 AS priority FROM const k, pg_catalog.pg_seclabel sl @@ -247,26 +503,6 @@ CREATE OR REPLACE VIEW pgsodium.masking_rule AS -- FIXME: revoke? GRANT SELECT ON pgsodium.masking_rule TO PUBLIC; -/* */ -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? - --============================================================================== -- FUNCTIONS --============================================================================== @@ -332,156 +568,6 @@ ALTER FUNCTION pgsodium.create_key(pgsodium.key_type, text, bytea, by -- 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 - * - restore privileges on view if applicable - * - 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 %s; - CREATE VIEW %s AS SELECT %s - FROM %s; - ALTER VIEW %s OWNER TO %s; - $c$, - rule.view_name, - rule.view_name, - pgsodium.decrypted_columns(relid), - source_name, - rule.view_name, - view_owner - ); - - IF debug THEN - RAISE NOTICE '%', body; - END IF; - - EXECUTE body; - - -- FIXME: missing revoke DDL? - FOR priv IN SELECT * FROM pg_catalog.aclexplode(privs) LOOP - body = format( - 'GRANT %s ON %s TO %s', - 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 %s."%s_encrypt_secret_%s"() CASCADE; - - CREATE OR REPLACE FUNCTION %s."%s_encrypt_secret_%s"() - RETURNS TRIGGER - LANGUAGE plpgsql - AS $t$ - BEGIN - %s; - RETURN new; - END; - $t$; - - ALTER FUNCTION %s."%s_encrypt_secret_%s"() OWNER TO %s; - - DROP TRIGGER IF EXISTS "%s_encrypt_secret_trigger_%s" ON %s; - - CREATE TRIGGER "%s_encrypt_secret_trigger_%s" - BEFORE INSERT OR UPDATE OF "%s" ON %s - FOR EACH ROW - EXECUTE FUNCTION %s."%s_encrypt_secret_%s" (); - $c$, - rule.relnamespace, - rule.relname, - m.attname, - rule.relnamespace, - rule.relname, - m.attname, - pgsodium.encrypted_column(relid, m), - rule.relnamespace, - rule.relname, - m.attname, - view_owner, - rule.relname, - m.attname, - source_name, - rule.relname, - m.attname, - m.attname, - source_name, - rule.relnamespace, - rule.relname, - m.attname - ); - - IF debug THEN - RAISE NOTICE '%', body; - END IF; - - EXECUTE body; - - END IF; - END LOOP; - - PERFORM pgsodium.mask_role(oid::regrole, source_name, rule.view_name) - FROM pg_catalog.pg_roles - WHERE pgsodium.has_mask(oid::regrole, source_name); - - RETURN; - END - $$ - LANGUAGE plpgsql - VOLATILE - SET search_path=''; - --- FIXME no OWNER --- FIXME no REVOKE ? --- FIXME no GRANT - /* * crypto_aead_det_decrypt(bytea, bytea, bytea, bytea) */ @@ -1930,7 +2016,7 @@ COMMENT ON FUNCTION pgsodium.crypto_sign_update_agg2(bytea, bytea, bytea) IS initializes state to the state passed to the aggregate as a parameter, if it has not already been initialized.'; -CREATE OR REPLACE AGGREGATE pgsodium.crypto_sign_update_agg(message bytea) +CREATE AGGREGATE pgsodium.crypto_sign_update_agg(message bytea) ( SFUNC = pgsodium.crypto_sign_update_agg1, STYPE = bytea, @@ -1947,7 +2033,7 @@ 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 OR REPLACE AGGREGATE pgsodium.crypto_sign_update_agg(state bytea, message bytea) +CREATE AGGREGATE pgsodium.crypto_sign_update_agg(state bytea, message bytea) ( SFUNC = pgsodium.crypto_sign_update_agg2, STYPE = bytea, @@ -2158,80 +2244,6 @@ CREATE FUNCTION pgsodium.crypto_stream_xchacha20_xor_ic(bytea, bytea, bigint, bi -- 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) */ @@ -2250,7 +2262,7 @@ GRANT EXECUTE ON FUNCTION pgsodium.derive_key TO pgsodium_keymaker; */ CREATE FUNCTION pgsodium.disable_security_label_trigger() RETURNS void AS $$ - ALTER EVENT TRIGGER pgsodium_trg_mask_update DISABLE; + ALTER EVENT TRIGGER pgsodium_tg_tce_update DISABLE; $$ LANGUAGE sql SECURITY DEFINER @@ -2265,7 +2277,7 @@ CREATE FUNCTION pgsodium.disable_security_label_trigger() */ CREATE FUNCTION pgsodium.enable_security_label_trigger() RETURNS void AS $$ - ALTER EVENT TRIGGER pgsodium_trg_mask_update ENABLE; + ALTER EVENT TRIGGER pgsodium_tg_tce_update ENABLE; $$ LANGUAGE sql SECURITY DEFINER @@ -2275,60 +2287,6 @@ CREATE FUNCTION pgsodium.enable_security_label_trigger() -- FIXME: revoke? -- FIXME: grant? -/* - * encrypted_column(oid, record) - */ -CREATE FUNCTION pgsodium.encrypted_column(relid OID, m record) - RETURNS TEXT AS $$ - BEGIN - IF m.format_type = 'text' THEN - RETURN 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 - RETURN 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; - RAISE 'Not supported type % for encryoted column %', - m.format_type, m.attname; - END - $$ - LANGUAGE plpgsql - VOLATILE - SET search_path=''; - --- FIXME: owner? --- FIXME: revoke? --- FIXME: grant? - /* * get_key_by_id(uuid) */ @@ -2377,16 +2335,19 @@ CREATE FUNCTION pgsodium.get_named_keys(filter text='%') /* * has_mask(regrole, text) + * FIXME: matching on 'ACCESS%%' could match other table where + * is a sub-string */ CREATE FUNCTION pgsodium.has_mask(role regrole, source_name text) RETURNS boolean AS $$ SELECT EXISTS( SELECT 1 - FROM pg_shseclabel + FROM pg_catalog.pg_shseclabel WHERE objoid = role AND provider = 'pgsodium' AND label ilike 'ACCESS%' || source_name || '%') $$ + SET search_path='pg_catalog' LANGUAGE sql; -- FIXME: owner? @@ -2545,43 +2506,39 @@ CREATE FUNCTION pgsodium.sodium_bin2base64(bin bytea) RETURNS text -- FIXME: grant? /* - * update_mask(oid, boolean) + * version() */ -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 +CREATE FUNCTION pgsodium.version() + RETURNS text AS $$ + SELECT extversion + FROM pg_catalog.pg_extension + WHERE extname = 'pgsodium' $$ - LANGUAGE plpgsql - SECURITY DEFINER + LANGUAGE sql SET search_path=''; -- FIXME: owner? -- FIXME: revoke? -- FIXME: grant? -/* - * update_masks(boolean) - */ -CREATE FUNCTION pgsodium.update_masks(debug boolean = false) - RETURNS void AS $$ +CREATE FUNCTION pgsodium.tce_decrypt_col( + message bytea, + keyid uuid, + nonce bytea DEFAULT NULL, + ad text DEFAULT '') + RETURNS bytea AS $$ + DECLARE 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; + IF message IS NULL OR keyid IS NULL THEN + RETURN NULL; + END IF; + + RETURN pgsodium.crypto_aead_det_decrypt( + message, + pg_catalog.convert_to(ad, 'utf8'), + keyid, + nonce + ); END $$ LANGUAGE plpgsql @@ -2591,16 +2548,29 @@ CREATE FUNCTION pgsodium.update_masks(debug boolean = false) -- FIXME: revoke? -- FIXME: grant? -/* - * version() - */ -CREATE FUNCTION pgsodium.version() - RETURNS text AS $$ - SELECT extversion - FROM pg_catalog.pg_extension - WHERE extname = 'pgsodium' - $$ - LANGUAGE sql +CREATE FUNCTION pgsodium.tce_decrypt_col( + message text, + keyid uuid, + nonce bytea DEFAULT NULL, + ad text DEFAULT '') + RETURNS text AS $tce_decrypt_col$ + DECLARE + BEGIN + IF message IS NULL OR keyid IS NULL THEN + RETURN NULL; + END IF; + + RETURN pg_catalog.convert_from( + pgsodium.crypto_aead_det_decrypt( + pg_catalog.decode(message, 'base64'), + pg_catalog.convert_to(ad, 'utf8'), + keyid, + nonce + ), 'utf8' -- FIXME: this should use the column encoding? + ); + END + $tce_decrypt_col$ + LANGUAGE plpgsql SET search_path=''; -- FIXME: owner? @@ -2647,9 +2617,13 @@ GRANT ALL ON ALL SEQUENCES IN SCHEMA pgsodium TO pgsodium_keymaker; 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); +-- TT SELECT * FROM pgsodium.update_mask('pgsodium.key'::regclass::oid); -- FIXME: why revoke not generated ? -- FIXME: why grant not generated ? -GRANT SELECT ON pgsodium.decrypted_key TO pgsodium_keymaker; +-- GRANT SELECT ON pgsodium.decrypted_key TO pgsodium_keymaker; +SELECT pgsodium.tce_update_attr_tg(a.attrelid, a.attnum) +FROM pg_catalog.pg_attribute a +WHERE a.attrelid = 'pgsodium.key'::regclass + AND a.attname = 'raw_key'; diff --git a/src/pgsodium.c b/src/pgsodium.c index 996b2b5..ffa398a 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 @@ -102,6 +103,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/src/pgsodium.h b/src/pgsodium.h index 52162b0..81f0345 100644 --- a/src/pgsodium.h +++ b/src/pgsodium.h @@ -265,4 +265,8 @@ Datum pgsodium_cmp (PG_FUNCTION_ARGS); Datum pgsodium_sodium_bin2base64 (PG_FUNCTION_ARGS); Datum pgsodium_sodium_base642bin (PG_FUNCTION_ARGS); +/* Triggers */ + +Datum tg_aead_det_encrypt_by_uuid_text(PG_FUNCTION_ARGS); + #endif /* PGSODIUM_H */ diff --git a/src/tce.c b/src/tce.c new file mode 100644 index 0000000..c79a8eb --- /dev/null +++ b/src/tce.c @@ -0,0 +1,321 @@ +#include "pgsodium.h" +#include "executor/spi.h" +#include "parser/parse_type.h" +#include "lib/stringinfo.h" +#include "utils/lsyscache.h" + +/**** Trigger related code ****/ + +/* + * Common code between tg_tce_encrypt_using_key_id() and + * tg_tce_encrypt_using_key_col(). + */ +static Datum tg_tce_encrypt(PG_FUNCTION_ARGS, Datum keyuuid) +{ + 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); /* trigg'd relname */ + 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 */ + + Oid uuidtype; /* uuidtype */ + + int ret; /* return codes from SPI */ + + bool isnull = false; + bool istext = false; + + /* 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")); + + /* + * Connect to SPI manager. + * Every operations now occurs in the SPI memory context! + */ + if ((ret = SPI_connect()) < 0) + /* internal error */ + elog(ERROR, "%s on %s: SPI_connect returned %d", tgname, relname, ret); + + // FIXME: error when not found + parseTypeString("uuid", &uuidtype, NULL, false); + + /* Fetch key_id and key_context from pgsodium key table */ + ret = SPI_execute_with_args( + "SELECT key_id, key_context " + "FROM pgsodium.valid_key v " // FIXME: use decrypted_key? + "WHERE id = $1 " + " AND key_type = 'aead-det' ", + 1, &uuidtype, &keyuuid, NULL, true, 1 + ); + + if (ret < 0) + elog(ERROR, "%s on %s: SPI_execute_with_args returned %d", tgname, + relname, ret); + + if ((ret == SPI_OK_SELECT) && (SPI_processed > 1)) + elog(ERROR, "%s on %s: more than one key found for uuid %s", tgname, + relname, + TextDatumGetCString(DirectFunctionCall1(uuid_out, keyuuid))); + + if ((ret == SPI_OK_SELECT) && (SPI_processed == 0)) + elog(ERROR, "%s on %s: no key found for uuid %s", tgname, relname, + TextDatumGetCString(DirectFunctionCall1(uuid_out, keyuuid))); + + if (ret != SPI_OK_SELECT) + elog(ERROR, "%s on %s: unexpected query result (return: %d)", + tgname, relname, ret); + + /* Get key_id Datum from the query result */ + key_id = SPI_getbinval(SPI_tuptable->vals[0], SPI_tuptable->tupdesc, 1, + &isnull); + if (isnull) + elog(ERROR, "%s on %s: key found for uuid %s is NULL", tgname, relname, + TextDatumGetCString(DirectFunctionCall1(uuid_out, keyuuid))); + + /* Get key_context Datum from the query result */ + key_context = SPI_getbinval(SPI_tuptable->vals[0], + SPI_tuptable->tupdesc, 2, &isnull); + if (isnull) + elog(ERROR, "%s on %s: key context for uuid %s is NULL", tgname, + relname, + TextDatumGetCString(DirectFunctionCall1(uuid_out, keyuuid))); + + /* 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); + + /* + * copy the resulting row to the trigger memory context before losing + * it during memory context destruction in SPI_finish() + */ + rettuple = SPI_copytuple(rettuple); + + /* releasing spi db connection and its memory context */ + SPI_finish(); + + 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(tg_tce_encrypt_using_key_col); +Datum +tg_tce_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, + "tg_tce_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 tg_tce_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(tg_tce_encrypt_using_key_id); +Datum +tg_tce_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, + "tg_tce_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 tg_tce_encrypt(fcinfo, keyuuid); +} From 295a00fb766048867d6c7ea9d7a2c21064dff76e Mon Sep 17 00:00:00 2001 From: Jehan-Guillaume de Rorthais Date: Wed, 4 Jan 2023 18:44:23 +0100 Subject: [PATCH 08/13] Fix --- sql/pgsodium--3.1.5--3.2.0.sql | 129 +++++++++++++++++++-------- sql/pgsodium--3.2.0.sql | 157 +++++++++++++++++---------------- test/tce.sql | 5 +- 3 files changed, 177 insertions(+), 114 deletions(-) diff --git a/sql/pgsodium--3.1.5--3.2.0.sql b/sql/pgsodium--3.1.5--3.2.0.sql index 76a1b97..cd064f1 100644 --- a/sql/pgsodium--3.1.5--3.2.0.sql +++ b/sql/pgsodium--3.1.5--3.2.0.sql @@ -28,6 +28,19 @@ DROP FUNCTION IF EXISTS pgsodium.decrypted_columns(oid); */ DROP FUNCTION IF EXISTS pgsodium.create_mask_view(oid, integer, boolean); +/* + * change: replaced by tce_update_views + */ +DROP FUNCTION IF EXISTS pgsodium.update_masks(boolean); + + +/* + * change: + * redundant with + * pgsodium.crypto_aead_det_encrypt(bytea, bytea, uuid, bytea DEFAULT NULL); + */ +DROP FUNCTION pgsodium.crypto_aead_det_encrypt(bytea, bytea, uuid); + /* * change: useless code since 3.2.0 */ @@ -48,7 +61,7 @@ DROP SCHEMA IF EXISTS pgsodium_masks; DROP FUNCTION pgsodium.trg_mask_update(); /* - * change: creation in 3.2.0 + * change: created in 3.2.0 */ CREATE FUNCTION pgsodium.tg_tce_update() RETURNS EVENT_TRIGGER @@ -90,7 +103,7 @@ CREATE FUNCTION pgsodium.tg_tce_update() SET search_path=''; /* - * change: creation in 3.2.0 + * change: created in 3.2.0 */ CREATE FUNCTION pgsodium.tce_update_attr_tg(relid oid, attnum integer) RETURNS void AS $tce_update_tg$ @@ -175,7 +188,7 @@ CREATE FUNCTION pgsodium.tce_update_attr_tg(relid oid, attnum integer) SET search_path=''; /* - * change: creation in 3.2.0 + * change: created in 3.2.0 */ CREATE FUNCTION pgsodium.tce_update_view(relid oid) RETURNS void AS $tce_update_view$ @@ -192,10 +205,11 @@ CREATE FUNCTION pgsodium.tce_update_view(relid oid) privs aclitem[]; BEGIN SELECT - pg_catalog.format('%I.%I', relnamespace::regnamespace::text, relname) + pg_catalog.format('%I.%I', n.nspname, c.relname) INTO STRICT origrelpath - FROM pg_catalog.pg_class - WHERE oid = relid; + FROM pg_catalog.pg_class c + JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid + WHERE c.oid = tce_update_view.relid; FOR r IN SELECT * @@ -237,12 +251,13 @@ CREATE FUNCTION pgsodium.tce_update_view(relid oid) END IF; enc_cols = enc_cols - || pg_catalog.format(E'pgsodium.tce_decrypt_col(%s) AS %s', + || pg_catalog.format(E'pgsodium.tce_decrypt_col(%s) AS %I', dec_col, dec_col_alias); END LOOP; - IF (viewname IS NULL) THEN - RAISE 'relation % has no encrypted columns', relid::regclass; + IF viewname IS NULL THEN + RAISE NOTICE 'skip decrypting view: relation % has no encrypted columns', relid::regclass; + RETURN; END IF; body = pg_catalog.format(' @@ -302,25 +317,41 @@ CREATE FUNCTION pgsodium.tce_update_view(relid oid) SET search_path=''; /* - * change: remove useless "mask_schema" variable + * change: created in 3.2.0 */ -CREATE OR REPLACE FUNCTION pgsodium.mask_role(masked_role regrole, source_name text, view_name text) +CREATE OR REPLACE FUNCTION pgsodium.tce_update_views() RETURNS void AS $$ - DECLARE - source_schema REGNAMESPACE = (regexp_split_to_array(source_name, '\.'))[1]; + SELECT pgsodium.tce_update_view(objoid) + FROM pg_catalog.pg_seclabel sl + JOIN pg_catalog.pg_class cl ON (cl.oid = sl.objoid) + WHERE sl.label ILIKE 'ENCRYPT%' + AND sl.provider = 'pgsodium' + AND cl.relowner = session_user::regrole::oid + AND sl.objoid::regclass != 'pgsodium.key'::regclass; + $$ + LANGUAGE SQL + SET search_path=''; + +/* + * change: remove useless "mask_schema" and "source_schema" variables + * change: support debug + */ +CREATE FUNCTION pgsodium.mask_role(masked_role regrole, source_name text, view_name text) + RETURNS void AS $$ + DECLARE + body text; BEGIN - EXECUTE format( - 'GRANT SELECT ON pgsodium.key TO %s', - masked_role); - - EXECUTE format( - 'GRANT pgsodium_keyiduser TO %s', - masked_role); - - EXECUTE format( - 'GRANT ALL ON %s TO %s', - view_name, - masked_role); + body = format(' + GRANT SELECT ON pgsodium.key TO %I; + GRANT pgsodium_keyiduser TO %1$I; + GRANT ALL ON %I TO %1$I;', + masked_role, view_name); + + IF pg_catalog.current_setting('pgsodium.debug')::bool THEN + RAISE NOTICE '%', body; + END IF; + + EXECUTE body; RETURN; END $$ @@ -336,9 +367,9 @@ CREATE OR REPLACE FUNCTION pgsodium.has_mask(role regrole, source_name text) SELECT EXISTS( SELECT 1 FROM pg_catalog.pg_shseclabel - WHERE objoid = role + WHERE objoid = role AND provider = 'pgsodium' - AND label ilike 'ACCESS%' || source_name || '%') + AND label ILIKE 'ACCESS%' || source_name || '%') $$ LANGUAGE sql SET search_path='pg_catalog'; @@ -400,9 +431,36 @@ CREATE OR REPLACE FUNCTION pgsodium.tce_decrypt_col( -- FIXME: grant? /* - * change: quote_ident() the schema name of the generated view path - * FIXME: bug: the view path given by the user in the security label might not - * be quoted correctly. + * change: makes "nonce" argument "DEFAULT NULL" + */ +CREATE OR REPLACE FUNCTION pgsodium.crypto_aead_det_encrypt(message bytea, additional bytea, key_uuid uuid, nonce bytea DEFAULT NULL) + 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; + +/* + * change: add some comments */ CREATE OR REPLACE VIEW pgsodium.masking_rule AS WITH const AS ( @@ -422,8 +480,9 @@ CREATE OR REPLACE VIEW pgsodium.masking_rule AS SELECT sl.objoid AS attrelid, sl.objsubid AS attnum, - c.relnamespace::regnamespace, + c.relnamespace, c.relname, + n.nspname, a.attname, pg_catalog.format_type(a.atttypid, a.atttypmod), sl.label AS col_description, @@ -432,17 +491,18 @@ CREATE OR REPLACE VIEW pgsodium.masking_rule AS (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], - quote_ident(c.relnamespace::regnamespace::text) || '.' || quote_ident('decrypted_' || c.relname)) AS view_name, + quote_ident(nspname) || '.' || quote_ident('decrypted_' || c.relname)) AS view_name, 100 AS priority 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 n.nspname <> 'pg_catalog' AND NOT a.attisdropped - AND sl.label ilike 'ENCRYPT%' + AND sl.label ILIKE 'ENCRYPT%' AND sl.provider = 'pgsodium' ) SELECT @@ -450,7 +510,6 @@ CREATE OR REPLACE VIEW pgsodium.masking_rule AS FROM rules_from_seclabels ORDER BY attrelid, attnum, priority DESC; - DROP EVENT TRIGGER pgsodium_trg_mask_update; CREATE EVENT TRIGGER pgsodium_tg_tce_update ON ddl_command_end diff --git a/sql/pgsodium--3.2.0.sql b/sql/pgsodium--3.2.0.sql index 97ddd33..82b2bd2 100644 --- a/sql/pgsodium--3.2.0.sql +++ b/sql/pgsodium--3.2.0.sql @@ -22,7 +22,7 @@ DO $$ 'pgsodium_keymaker'] LOOP IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = new_role) THEN - EXECUTE format($i$ + EXECUTE pg_catalog.format($i$ CREATE ROLE %I WITH NOLOGIN NOSUPERUSER @@ -48,12 +48,14 @@ GRANT pgsodium_keyiduser TO pgsodium_keyholder; CREATE FUNCTION pgsodium.tg_tce_encrypt_using_key_col() RETURNS trigger AS '$libdir/pgsodium' - LANGUAGE C; + LANGUAGE C + SECURITY DEFINER; CREATE FUNCTION pgsodium.tg_tce_encrypt_using_key_id() RETURNS trigger AS '$libdir/pgsodium' - LANGUAGE C; + LANGUAGE C + SECURITY DEFINER; -- FIXME: owner? -- FIXME: revoke? @@ -80,10 +82,11 @@ CREATE FUNCTION pgsodium.tce_update_view(relid oid) privs aclitem[]; BEGIN SELECT - pg_catalog.format('%I.%I', relnamespace::regnamespace::text, relname) + pg_catalog.format('%I.%I', n.nspname, c.relname) INTO STRICT origrelpath - FROM pg_catalog.pg_class - WHERE oid = relid; + FROM pg_catalog.pg_class c + JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid + WHERE c.oid = tce_update_view.relid; FOR r IN SELECT * @@ -125,23 +128,23 @@ CREATE FUNCTION pgsodium.tce_update_view(relid oid) END IF; enc_cols = enc_cols - || pg_catalog.format(E'pgsodium.tce_decrypt_col(%s) AS %s', + || pg_catalog.format(E'pgsodium.tce_decrypt_col(%s) AS %I', dec_col, dec_col_alias); END LOOP; IF (viewname IS NULL) THEN - RAISE 'relation % has no encrypted columns', relid::regclass; + RAISE NOTICE 'skip decrypting view: relation % has no encrypted columns', relid::regclass; + RETURN; END IF; body = pg_catalog.format(' DROP VIEW IF EXISTS %s; CREATE VIEW %1$s AS - SELECT *, - %s - FROM %s; + SELECT *, + %s + FROM %s; ALTER VIEW %1$s OWNER TO %4$I; - REVOKE ALL ON %1$s FROM public; - ', + REVOKE ALL ON %1$s FROM public;', viewname, -- supposed to be already escaped array_to_string(enc_cols, E',\n'), origrelpath, @@ -167,8 +170,7 @@ CREATE FUNCTION pgsodium.tce_update_view(relid oid) -- FIXME: missing revoke DDL? FOR r IN SELECT * FROM pg_catalog.aclexplode(privs) LOOP - body = format( - 'GRANT %s ON %s TO %I', + body = pg_catalog.format( 'GRANT %s ON %s TO %I', r.privilege_type, viewname, -- supposed to be already escaped r.grantee::regrole::text @@ -258,12 +260,12 @@ CREATE FUNCTION pgsodium.tce_update_attr_tg(relid oid, attnum integer) 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);', - tgname, -- 1 - rule.attname, -- 2 - rule.relnamespace, -- 3 - rule.relname, -- 4 - tgf, -- 5 - tgargs -- 6 + tgname, -- 1 + rule.attname, -- 2 + rule.nspname, -- 3 + rule.relname, -- 4 + tgf, -- 5 + tgargs -- 6 ); IF pg_catalog.current_setting('pgsodium.debug')::bool THEN @@ -313,11 +315,18 @@ CREATE FUNCTION pgsodium.tg_tce_update() r.schema_name, r.object_identity, r.in_extension; END IF; - /* - * Create/update encryption trigger for given attribute. This triggers - * the creation/update of the related decrypting view as well. - */ - PERFORM pgsodium.tce_update_attr_tg(r.objid, r.objsubid); + 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.tce_update_attr_tg(r.objid, r.objsubid); + ELSIF r.object_type = 'table' AND r.objsubid = 0 THEN + /* + * Create/update the view on given table + */ + PERFORM pgsodium.tce_update_view(r.objid); + END IF; END LOOP; END $$ @@ -452,7 +461,10 @@ CREATE VIEW pgsodium.valid_key AS -- FIXME: revoke? GRANT SELECT ON pgsodium.valid_key TO pgsodium_keyiduser; -/* */ +/* + * FIXME: bug: the view path given by the user in the security label might not + * be quoted correctly. + */ CREATE VIEW pgsodium.masking_rule AS WITH const AS ( SELECT @@ -471,8 +483,9 @@ CREATE VIEW pgsodium.masking_rule AS SELECT sl.objoid AS attrelid, sl.objsubid AS attnum, - c.relnamespace::regnamespace, + c.relnamespace, c.relname, + n.nspname, a.attname, pg_catalog.format_type(a.atttypid, a.atttypmod), sl.label AS col_description, @@ -481,17 +494,18 @@ CREATE VIEW pgsodium.masking_rule AS (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], - quote_ident(c.relnamespace::regnamespace::text) || '.' || quote_ident('decrypted_' || c.relname)) AS view_name, + quote_ident(nspname) || '.' || quote_ident('decrypted_' || c.relname)) AS view_name, 100 AS priority 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 n.nspname <> 'pg_catalog' AND NOT a.attisdropped - AND sl.label ilike 'ENCRYPT%' + AND sl.label ILIKE 'ENCRYPT%' AND sl.provider = 'pgsodium' ) SELECT @@ -678,39 +692,10 @@ CREATE FUNCTION pgsodium.crypto_aead_det_encrypt(message bytea, additional bytea 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) +CREATE FUNCTION pgsodium.crypto_aead_det_encrypt(message bytea, additional bytea, key_uuid uuid, nonce bytea DEFAULT NULL) RETURNS bytea AS $$ DECLARE key pgsodium.decrypted_key; @@ -2356,24 +2341,26 @@ CREATE FUNCTION pgsodium.has_mask(role regrole, source_name text) /* * mask_role(regrole, text, text) + * WARNING: view_name is supposed to be schema qualified and properly quoted. */ CREATE FUNCTION pgsodium.mask_role(masked_role regrole, source_name text, view_name text) RETURNS void AS $$ DECLARE - source_schema REGNAMESPACE = (regexp_split_to_array(source_name, '\.'))[1]; + body text; BEGIN - EXECUTE format( - 'GRANT SELECT ON pgsodium.key TO %s', - masked_role); - - EXECUTE format( - 'GRANT pgsodium_keyiduser TO %s', - masked_role); - - EXECUTE format( - 'GRANT ALL ON %s TO %s', - view_name, - masked_role); + body = pg_catalog.format( + 'GRANT SELECT ON pgsodium.key TO %I; + GRANT pgsodium_keyiduser TO %1$I; + GRANT ALL ON %s TO %1$I', + masked_role, + view_name -- this one is supposed to be already quoted correctly. + ); + + IF pg_catalog.current_setting('pgsodium.debug')::bool THEN + RAISE NOTICE '%', body; + END IF; + + EXECUTE body; RETURN; END $$ @@ -2527,7 +2514,6 @@ CREATE FUNCTION pgsodium.tce_decrypt_col( nonce bytea DEFAULT NULL, ad text DEFAULT '') RETURNS bytea AS $$ - DECLARE BEGIN IF message IS NULL OR keyid IS NULL THEN RETURN NULL; @@ -2554,7 +2540,6 @@ CREATE FUNCTION pgsodium.tce_decrypt_col( nonce bytea DEFAULT NULL, ad text DEFAULT '') RETURNS text AS $tce_decrypt_col$ - DECLARE BEGIN IF message IS NULL OR keyid IS NULL THEN RETURN NULL; @@ -2577,6 +2562,23 @@ CREATE FUNCTION pgsodium.tce_decrypt_col( -- FIXME: revoke? -- FIXME: grant? +CREATE OR REPLACE FUNCTION pgsodium.tce_update_views() + RETURNS void AS $$ + SELECT pgsodium.tce_update_view(objoid) + FROM pg_catalog.pg_seclabel sl + JOIN pg_catalog.pg_class cl ON (cl.oid = sl.objoid) + WHERE sl.label ILIKE 'ENCRYPT%' + AND sl.provider = 'pgsodium' + AND cl.relowner = session_user::regrole::oid + AND sl.objoid::regclass != 'pgsodium.key'::regclass; + $$ + LANGUAGE SQL + SET search_path=''; + +-- FIXME: owner? +-- FIXME: revoke? +-- FIXME: grant? + --============================================================================== -- PRIVILEGES --============================================================================== @@ -2622,8 +2624,9 @@ SECURITY LABEL FOR pgsodium ON COLUMN pgsodium.key.raw_key -- FIXME: why revoke not generated ? -- FIXME: why grant not generated ? --- GRANT SELECT ON pgsodium.decrypted_key TO pgsodium_keymaker; SELECT pgsodium.tce_update_attr_tg(a.attrelid, a.attnum) FROM pg_catalog.pg_attribute a WHERE a.attrelid = 'pgsodium.key'::regclass AND a.attname = 'raw_key'; + +GRANT SELECT ON pgsodium.decrypted_key TO pgsodium_keymaker; diff --git a/test/tce.sql b/test/tce.sql index f76b932..756299e 100644 --- a/test/tce.sql +++ b/test/tce.sql @@ -122,7 +122,8 @@ GRANT USAGE ON ALL SEQUENCES IN SCHEMA "private-test" TO bobo; SELECT * FROM finish(); COMMIT; -select pgsodium.update_masks(); +-- force regeneration +SELECT pgsodium.tce_update_views(); select ok(has_table_privilege('bobo', 'private.bar', 'SELECT'), 'user keeps privs after regeneration'); @@ -260,7 +261,7 @@ SELECT results_eq( SELECT lives_ok( $test$ - select pgsodium.update_masks() + SELECT pgsodium.tce_update_views() $test$, 'can update only objects owned by session user'); From c00a27d90e981c8d2b96cad34d4edb20a7b34cce Mon Sep 17 00:00:00 2001 From: Jehan-Guillaume de Rorthais Date: Tue, 3 Jan 2023 15:35:09 +0100 Subject: [PATCH 09/13] Move fetching key meta data in its own func --- src/tce.c | 137 ++++++++++++++++++++++++++---------------------------- 1 file changed, 67 insertions(+), 70 deletions(-) diff --git a/src/tce.c b/src/tce.c index c79a8eb..502022a 100644 --- a/src/tce.c +++ b/src/tce.c @@ -4,6 +4,68 @@ #include "lib/stringinfo.h" #include "utils/lsyscache.h" +static void fetch_key_meta_using_uuid(Datum keyuuid, Datum *key_id, + Datum *key_context) +{ + int ret; + Oid uuidtype; /* uuidtype */ + 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); + + // FIXME: error when not found + parseTypeString("uuid", &uuidtype, NULL, false); + + /* Fetch key_id and key_context from pgsodium key table */ + ret = SPI_execute_with_args( + "SELECT key_id, key_context " + "FROM pgsodium.decrypted_key v " + "WHERE id = $1 " + " AND key_type = 'aead-det' ", + 1, &uuidtype, &keyuuid, NULL, true, 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 ****/ /* @@ -15,24 +77,18 @@ static Datum tg_tce_encrypt(PG_FUNCTION_ARGS, Datum keyuuid) 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); /* trigg'd relname */ 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 */ + 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_id; /* key_id from pgsodium.key */ Datum key_context; /* key context from pgsodium.key */ - Oid uuidtype; /* uuidtype */ - - int ret; /* return codes from SPI */ - bool isnull = false; bool istext = false; @@ -58,57 +114,7 @@ static Datum tg_tce_encrypt(PG_FUNCTION_ARGS, Datum keyuuid) message = DirectFunctionCall2(pg_convert_to, message, CStringGetDatum("utf8")); - /* - * Connect to SPI manager. - * Every operations now occurs in the SPI memory context! - */ - if ((ret = SPI_connect()) < 0) - /* internal error */ - elog(ERROR, "%s on %s: SPI_connect returned %d", tgname, relname, ret); - - // FIXME: error when not found - parseTypeString("uuid", &uuidtype, NULL, false); - - /* Fetch key_id and key_context from pgsodium key table */ - ret = SPI_execute_with_args( - "SELECT key_id, key_context " - "FROM pgsodium.valid_key v " // FIXME: use decrypted_key? - "WHERE id = $1 " - " AND key_type = 'aead-det' ", - 1, &uuidtype, &keyuuid, NULL, true, 1 - ); - - if (ret < 0) - elog(ERROR, "%s on %s: SPI_execute_with_args returned %d", tgname, - relname, ret); - - if ((ret == SPI_OK_SELECT) && (SPI_processed > 1)) - elog(ERROR, "%s on %s: more than one key found for uuid %s", tgname, - relname, - TextDatumGetCString(DirectFunctionCall1(uuid_out, keyuuid))); - - if ((ret == SPI_OK_SELECT) && (SPI_processed == 0)) - elog(ERROR, "%s on %s: no key found for uuid %s", tgname, relname, - TextDatumGetCString(DirectFunctionCall1(uuid_out, keyuuid))); - - if (ret != SPI_OK_SELECT) - elog(ERROR, "%s on %s: unexpected query result (return: %d)", - tgname, relname, ret); - - /* Get key_id Datum from the query result */ - key_id = SPI_getbinval(SPI_tuptable->vals[0], SPI_tuptable->tupdesc, 1, - &isnull); - if (isnull) - elog(ERROR, "%s on %s: key found for uuid %s is NULL", tgname, relname, - TextDatumGetCString(DirectFunctionCall1(uuid_out, keyuuid))); - - /* Get key_context Datum from the query result */ - key_context = SPI_getbinval(SPI_tuptable->vals[0], - SPI_tuptable->tupdesc, 2, &isnull); - if (isnull) - elog(ERROR, "%s on %s: key context for uuid %s is NULL", tgname, - relname, - TextDatumGetCString(DirectFunctionCall1(uuid_out, keyuuid))); + fetch_key_meta_using_uuid(keyuuid, &key_id, &key_context); /* init fields of the function call structs */ InitFunctionCallInfoData(*fcencinfo, NULL, 5, InvalidOid, NULL, NULL); @@ -190,15 +196,6 @@ static Datum tg_tce_encrypt(PG_FUNCTION_ARGS, Datum keyuuid) rettuple = heap_modify_tuple_by_cols(rettuple, tupdesc, 1, &msgattnum, &encmsg, &isnull); - /* - * copy the resulting row to the trigger memory context before losing - * it during memory context destruction in SPI_finish() - */ - rettuple = SPI_copytuple(rettuple); - - /* releasing spi db connection and its memory context */ - SPI_finish(); - return PointerGetDatum(rettuple); } From ef63b3e114dfac19c649833f637fc211c5a79024 Mon Sep 17 00:00:00 2001 From: Jehan-Guillaume de Rorthais Date: Tue, 3 Jan 2023 15:35:43 +0100 Subject: [PATCH 10/13] Fix row visibility when calling create_key() from with an INSERT The create_key() function is calling itself to create a new parent_key when inserting a raw_key without existing parent_key to encrypt it. During the encryption trigger call, the newly created key was not visible from SPI query to gather its meta datas, even though the parent_key command is finished before the trigger fires. This visibility mechanism is explained in this page: https://www.postgresql.org/docs/current/spi-visibility.html --- src/tce.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tce.c b/src/tce.c index 502022a..c806fc0 100644 --- a/src/tce.c +++ b/src/tce.c @@ -29,7 +29,7 @@ static void fetch_key_meta_using_uuid(Datum keyuuid, Datum *key_id, "FROM pgsodium.decrypted_key v " "WHERE id = $1 " " AND key_type = 'aead-det' ", - 1, &uuidtype, &keyuuid, NULL, true, 1 + 1, &uuidtype, &keyuuid, NULL, false, 1 ); if (ret < 0) From 0e9c21a99280d52af6c9510478867828f4dc230b Mon Sep 17 00:00:00 2001 From: Jehan-Guillaume de Rorthais Date: Wed, 4 Jan 2023 21:14:47 +0100 Subject: [PATCH 11/13] Fix compilation error with versions < 15 --- src/tce.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/tce.c b/src/tce.c index c806fc0..a1c0468 100644 --- a/src/tce.c +++ b/src/tce.c @@ -1,8 +1,10 @@ #include "pgsodium.h" #include "executor/spi.h" #include "parser/parse_type.h" -#include "lib/stringinfo.h" #include "utils/lsyscache.h" +#if PG_VERSION_NUM < 150000 +#include "utils/rel.h" +#endif static void fetch_key_meta_using_uuid(Datum keyuuid, Datum *key_id, Datum *key_context) From 7a9af14b2c8f49ceca62d3f622b6e82db4809af5 Mon Sep 17 00:00:00 2001 From: Jehan-Guillaume de Rorthais Date: Thu, 5 Jan 2023 09:44:13 +0100 Subject: [PATCH 12/13] Fix handling pgsodium.debug GUC in v13 --- sql/pgsodium--3.2.0.sql | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/sql/pgsodium--3.2.0.sql b/sql/pgsodium--3.2.0.sql index 82b2bd2..bd9b993 100644 --- a/sql/pgsodium--3.2.0.sql +++ b/sql/pgsodium--3.2.0.sql @@ -151,7 +151,7 @@ CREATE FUNCTION pgsodium.tce_update_view(relid oid) view_owner ); - IF pg_catalog.current_setting('pgsodium.debug')::bool THEN + IF pg_catalog.current_setting('pgsodium.debug', true)::bool THEN RAISE NOTICE '%', body; END IF; @@ -176,7 +176,7 @@ CREATE FUNCTION pgsodium.tce_update_view(relid oid) r.grantee::regrole::text ); - IF pg_catalog.current_setting('pgsodium.debug')::bool THEN + IF pg_catalog.current_setting('pgsodium.debug', true)::bool THEN RAISE NOTICE '%', body; END IF; @@ -268,7 +268,7 @@ CREATE FUNCTION pgsodium.tce_update_attr_tg(relid oid, attnum integer) tgargs -- 6 ); - IF pg_catalog.current_setting('pgsodium.debug')::bool THEN + IF pg_catalog.current_setting('pgsodium.debug', true)::bool THEN RAISE NOTICE '%', body; END IF; @@ -309,7 +309,7 @@ CREATE FUNCTION pgsodium.tg_tce_update() AND e.objid = c.oid ) LOOP - IF pg_catalog.current_setting('pgsodium.debug')::bool THEN + IF pg_catalog.current_setting('pgsodium.debug', true)::bool 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; @@ -2356,7 +2356,7 @@ CREATE FUNCTION pgsodium.mask_role(masked_role regrole, source_name text, view_n view_name -- this one is supposed to be already quoted correctly. ); - IF pg_catalog.current_setting('pgsodium.debug')::bool THEN + IF pg_catalog.current_setting('pgsodium.debug', true)::bool THEN RAISE NOTICE '%', body; END IF; From 73f4da852c55e3e6223c533ff58b2b2458d201d8 Mon Sep 17 00:00:00 2001 From: Jehan-Guillaume de Rorthais Date: Thu, 5 Jan 2023 11:40:43 +0100 Subject: [PATCH 13/13] Fix pgsodium_crypto_auth_hmacsha256_verify strictiness --- sql/pgsodium--3.2.0.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sql/pgsodium--3.2.0.sql b/sql/pgsodium--3.2.0.sql index bd9b993..4850b08 100644 --- a/sql/pgsodium--3.2.0.sql +++ b/sql/pgsodium--3.2.0.sql @@ -1010,7 +1010,7 @@ CREATE FUNCTION pgsodium.crypto_auth_hmacsha256_verify(hash bytea, message bytea RETURNS bool AS '$libdir/pgsodium', 'pgsodium_crypto_auth_hmacsha256_verify' LANGUAGE C - IMMUTABLE STRICT; + IMMUTABLE; -- FIXME: owner? REVOKE ALL ON FUNCTION pgsodium.crypto_auth_hmacsha256_verify(bytea, bytea, bytea) FROM PUBLIC;