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