From 84281a522bedc1b83bf4fbea5390b8b1f1591152 Mon Sep 17 00:00:00 2001 From: Peter Eisentraut Date: Fri, 3 Dec 2021 22:07:52 +0100 Subject: [PATCH v1] Transparent column encryption --- doc/src/sgml/catalogs.sgml | 227 +++++++++++ doc/src/sgml/datatype.sgml | 47 +++ doc/src/sgml/ddl.sgml | 65 ++++ doc/src/sgml/libpq.sgml | 79 ++++ doc/src/sgml/protocol.sgml | 220 ++++++++++- doc/src/sgml/ref/create_table.sgml | 42 ++- src/backend/access/common/printtup.c | 150 ++++++++ src/backend/access/common/tupdesc.c | 8 + src/backend/access/hash/hashvalidate.c | 2 +- src/backend/catalog/Makefile | 3 +- src/backend/catalog/heap.c | 3 + src/backend/commands/prepare.c | 51 ++- src/backend/commands/tablecmds.c | 62 +++ src/backend/executor/spi.c | 4 + src/backend/nodes/copyfuncs.c | 1 + src/backend/nodes/equalfuncs.c | 1 + src/backend/nodes/nodeFuncs.c | 2 + src/backend/nodes/outfuncs.c | 1 + src/backend/parser/analyze.c | 5 +- src/backend/parser/gram.y | 13 +- src/backend/parser/parse_param.c | 43 ++- src/backend/parser/parse_target.c | 6 + src/backend/tcop/postgres.c | 58 ++- src/backend/utils/cache/plancache.c | 10 + src/backend/utils/cache/syscache.c | 29 ++ src/include/access/printtup.h | 2 + src/include/catalog/pg_amop.dat | 5 + src/include/catalog/pg_amproc.dat | 5 + src/include/catalog/pg_attribute.h | 9 + src/include/catalog/pg_cast.dat | 6 + src/include/catalog/pg_colenckey.h | 18 + src/include/catalog/pg_colenckeydata.h | 25 ++ src/include/catalog/pg_colmasterkey.h | 24 ++ src/include/catalog/pg_opclass.dat | 2 + src/include/catalog/pg_operator.dat | 10 + src/include/catalog/pg_opfamily.dat | 2 + src/include/catalog/pg_proc.dat | 13 +- src/include/catalog/pg_type.dat | 12 + src/include/catalog/pg_type.h | 1 + src/include/nodes/parsenodes.h | 1 + src/include/parser/analyze.h | 3 +- src/include/parser/parse_node.h | 2 + src/include/parser/parse_param.h | 3 +- src/include/utils/plancache.h | 4 + src/include/utils/syscache.h | 3 + src/interfaces/libpq/exports.txt | 2 + src/interfaces/libpq/fe-connect.c | 5 + src/interfaces/libpq/fe-exec.c | 352 +++++++++++++++++- src/interfaces/libpq/fe-protocol3.c | 83 ++++- src/interfaces/libpq/fe-trace.c | 2 + src/interfaces/libpq/libpq-fe.h | 16 + src/interfaces/libpq/libpq-int.h | 13 + src/test/Makefile | 3 +- src/test/column_encryption/.gitignore | 3 + src/test/column_encryption/Makefile | 20 + .../t/001_column_encryption.pl | 135 +++++++ src/test/column_encryption/test_client.c | 155 ++++++++ .../libpq_pipeline/traces/prepared.trace | 2 +- .../regress/expected/column_encryption.out | 47 +++ src/test/regress/expected/oidjoins.out | 4 + src/test/regress/expected/opr_sanity.out | 12 +- src/test/regress/expected/prepare.out | 41 +- src/test/regress/expected/rules.out | 6 +- src/test/regress/expected/sanity_check.out | 3 + src/test/regress/expected/type_sanity.out | 20 +- src/test/regress/parallel_schedule | 3 + src/test/regress/sql/column_encryption.sql | 44 +++ src/test/regress/sql/prepare.sql | 6 +- src/test/regress/sql/type_sanity.sql | 4 +- 69 files changed, 2196 insertions(+), 67 deletions(-) create mode 100644 src/include/catalog/pg_colenckey.h create mode 100644 src/include/catalog/pg_colenckeydata.h create mode 100644 src/include/catalog/pg_colmasterkey.h create mode 100644 src/test/column_encryption/.gitignore create mode 100644 src/test/column_encryption/Makefile create mode 100644 src/test/column_encryption/t/001_column_encryption.pl create mode 100644 src/test/column_encryption/test_client.c create mode 100644 src/test/regress/expected/column_encryption.out create mode 100644 src/test/regress/sql/column_encryption.sql diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml index c1d11be73f..a220aa4dd2 100644 --- a/doc/src/sgml/catalogs.sgml +++ b/doc/src/sgml/catalogs.sgml @@ -105,6 +105,21 @@ System Catalogs collations (locale information) + + pg_colenckey + column encryption keys + + + + pg_colenckeydata + column encryption key data + + + + pg_colmasterkey + column master keys + + pg_constraint check constraints, unique constraints, primary key constraints, foreign key constraints @@ -2423,6 +2438,218 @@ <structname>pg_collation</structname> Columns + + <structname>pg_colenckey</structname> + + + pg_colenckey + + + + The catalog pg_colenckey contains information + about the column encryption keys in the database. The actual key material + of the column encryption keys is in the catalog pg_colenckeydata. + + + + <structname>pg_colenckey</structname> Columns + + + + + Column Type + + + Description + + + + + + + + oid oid + + + Row identifier + + + + + + cekname name + + + Column encryption key name + + + + +
+
+ + + <structname>pg_colenckeydata</structname> + + + pg_colenckeydata + + + + The catalog pg_colenckeydata contains the key + material of column encryption keys. Each column encryption key object can + contain several versions of the key material, each encrypted with a + different column master key. That allows the gradual rotation of the + column master keys. Thus, (ckdcekid, ckdcmkid) is a + unique key of this table. + + + + The key material of column encryption keys should never be decrypted inside + the database instance. It is meant to be sent as-is to the client, where + it is decrypted using the associated column master key, and then used to + encrypt or decrypt column values. + + + + <structname>pg_colenckeydata</structname> Columns + + + + + Column Type + + + Description + + + + + + + + oid oid + + + Row identifier + + + + + + ckdcekid oid + (references pg_colenckey.oid) + + + The column encryption key this entry belongs to + + + + + + ckdcmkid oid + (references pg_colmasterkey.oid) + + + The column master key that the key material is encrypted with + + + + + + ckdcmkalg text + + + The encryption algorithm used for encrypting the key material + + + + + + ckdencval bytea + + + The key material of this column encryption key, encrypted using the + referenced column master key + + + + +
+
+ + + <structname>pg_colmasterkey</structname> + + + pg_colmasterkey + + + + The catalog pg_colmasterkey contains information + about column master keys. The keys themselves are not stored in the + database. The catalog entry only contains information that is used by + clients to locate the keys, for example in a file or in a key management + system. + + + + <structname>pg_colmasterkey</structname> Columns + + + + + Column Type + + + Description + + + + + + + + oid oid + + + Row identifier + + + + + + cmkname name + + + Column master key name + + + + + + cmkprovider text + + + The provider associated with this column master key. This is used by + clients to determine how to look up the key. + + + + + + cmkoptions text[] + + + Provider-specific options, such as a file name or a URL. + + + + +
+
+ <structname>pg_constraint</structname> diff --git a/doc/src/sgml/datatype.sgml b/doc/src/sgml/datatype.sgml index 6929f3bb18..bcda249179 100644 --- a/doc/src/sgml/datatype.sgml +++ b/doc/src/sgml/datatype.sgml @@ -5341,4 +5341,51 @@ Pseudo-Types + + Types Related to Encryption + + + An encrypted column value (see ) is + internally stored using the types + encryptedr (for randomized encryption) or + encryptedd (for deterministic encryption); see . Most of the database system treats + this as normal types. For example, the type encryptedd has + an equals operator that allows lookup of encrypted values. The external + representation of these types is the same as the bytea type. + Thus, clients that don't support transparent column encryption or have + disabled it will see the encrypted values as byte arrays. Clients that + support transparent data encryption will not see these types in result + sets, as the protocol layer will translate them back to declared + underlying type in the table definition. + + + + Types Related to Encryption + + + + + + + Name + Storage Size + Description + + + + + encryptedd + 1 or 4 bytes plus the actual binary string + encrypted column value, deterministic encryption + + + encryptedr + 1 or 4 bytes plus the actual binary string + encrypted column value, randomized encryption + + + +
+
diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml index 642ea2a70d..b0c26958a7 100644 --- a/doc/src/sgml/ddl.sgml +++ b/doc/src/sgml/ddl.sgml @@ -1161,6 +1161,71 @@ Exclusion Constraints + + Transparent Column Encryption + + + With transparent column encryption, columns can be + stored encrypted in the database. The encryption and decryption happens on + the client, so that the plaintext value is never seen in the database + instance or on the server hosting the database. The drawback is that most + operations, such as function calls or sorting, are not possible on + encrypted values. + + + + Tranparent column encryption uses two levels of cryptographic keys. The + actual column value is encrypted using a symmetric algorithm, such as AES, + using a column encryption key + (CEK). The column encryption key is in turn encrypted + using an assymmetric algorithm, such as RSA, using a column + master key (CMK). The encrypted CEK is + stored in the database system. The CMK is not stored in the database + system; it is stored on the client or somewhere where the client can access + it, such as in a local file or in a key management system. The database + system only records where the CMK is stored and provides this information + to the client. When rows containing encrypted columns are sent to the + client, the server first sends any necessary CMK information, followed by + any required CEK. The client then looks up the CMK and uses that to + decrypt the CEK. Then it decrypts incoming row data using the CEK and + provides the decrypted row data to the application. + + + + Here is an example declaring a column as encrypted: + +CREATE TABLE customers ( + id int PRIMARY KEY, + name text NOT NULL, + ... + creditcard_num text ENCRYPTED WITH (column_encryption_key = cek1) +); + + + + + Column encryption supports randomized and + deterministic encryption. The above example uses + randomized encryption, which is the default. Randomized encryption uses a + random initialization vector for each encryption, so that even if the + plaintext of two rows is equal, the encrypted values will be different. + This prevents someone with direct access to the database server to make + computations such as distinct counts on the encrypted values. + Deterministic encryption uses a fixed initialization vector. This reduces + security, but it allows equality searches on encrypted values. The + following example declares a column with deterministic encryption: + +CREATE TABLE employees ( + id int PRIMARY KEY, + name text NOT NULL, + ... + ssn text ENCRYPTED WITH ( + column_encryption_key = cek1, encryption_type = deterministic) +); + + + + System Columns diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml index c17d33a54f..f1bb5e9df5 100644 --- a/doc/src/sgml/libpq.sgml +++ b/doc/src/sgml/libpq.sgml @@ -3012,6 +3012,44 @@ Main Functions + + PQexecPrepared2PQexecPrepared2 + + + + Sends a request to execute a prepared statement with given + parameters, and waits for the result, with support for encrypted columns. + +PGresult *PQexecPrepared2(PGconn *conn, + const char *stmtName, + int nParams, + const char * const *paramValues, + const int *paramLengths, + const int *paramFormats, + int resultFormat, + PGresult *paramDesc); + + + + + is like with additional support for encrypted + columns. The parameter paramDesc must be a + result set obtained from on + the same prepared statement. + + + + This function must be used if a statement parameter corresponds to an + underlying encrypted column. In that situation, the prepared + statement needs to be described first so that libpq can obtain the + necessary key and other information from the server. When that is + done, the parameters corresponding to encrypted columns are + automatically encrypted appropriately before being sent to the server. + + + + PQdescribePreparedPQdescribePrepared @@ -4563,6 +4601,7 @@ Asynchronous Command Processing , , , + , , and , which can be used with to duplicate @@ -4570,6 +4609,7 @@ Asynchronous Command Processing , , , + , , and respectively. @@ -4681,6 +4721,44 @@ Asynchronous Command Processing + + PQsendQueryPrepared2PQsendQueryPrepared2 + + + + Sends a request to execute a prepared statement with given + parameters, without waiting for the result(s), with support for encrypted columns. + +int PQsendQueryPrepared(PGconn *conn, + const char *stmtName, + int nParams, + const char * const *paramValues, + const int *paramLengths, + const int *paramFormats, + int resultFormat, + PGresult *paramDesc); + + + + + is like with additional support for encrypted + columns. The parameter paramDesc must be a + result set obtained from on + the same prepared statement. + + + + This function must be used if a statement parameter corresponds to an + underlying encrypted column. In that situation, the prepared + statement needs to be described first so that libpq can obtain the + necessary key and other information from the server. When that is + done, the parameters corresponding to encrypted columns are + automatically encrypted appropriately before being sent to the server. + + + + PQsendDescribePreparedPQsendDescribePrepared @@ -4731,6 +4809,7 @@ Asynchronous Command Processing , , , + , , , or diff --git a/doc/src/sgml/protocol.sgml b/doc/src/sgml/protocol.sgml index 34a7034282..9cb8aad8d6 100644 --- a/doc/src/sgml/protocol.sgml +++ b/doc/src/sgml/protocol.sgml @@ -1050,6 +1050,47 @@ Extended Query + + Transparent Column Encryption + + + When transparent column encryption is enabled, the messages + ColumnMasterKey and ColumnEncryptionKey can appear before RowDescription + and ParameterDescription messages. Clients should collect the information + in these messages and keep them for the duration of the connection. A + server is not required to resend the key information for each statement + cycle if it was already sent during this connection. If a server resends + a key that the client has already stored (that is, a key having a name + equal to one already stored), the new information should replace the old. + (This could happen, for example, if the key was altered by server-side DDL + commands.) + + + + A client supporting transparent column encryption should automatically + decrypt the column value fields of DataRow messages corresponding to + encrypted columns, and it should automatically encrypt the parameter value + fields of Bind messages corresponding to encrypted columns. + + + + When column encryption is used, the plaintext is always in text format + (not binary format). + + + + When deterministic encryption is used, clients need to take care to + represent plaintext to be encrypted in a consistent form. For example, + encrypting an integer represented by the string 100 and + an integer represented by the string +100 would result + in two different ciphertexts, thus defeating the main point of + deterministic encryption. This protocol specification requires the + plaintext to be in canonical form, which is the form that + is produced by the server when it outputs a particular value in text + format. + + + Function Call @@ -4083,6 +4124,159 @@ Message Formats + + +ColumnEncryptionKey (B) + + + + + + + + Byte1('Y') + + + + Identifies the message as a column encryption key message. + + + + + + Int32 + + + + Length of message contents in bytes, including self. + + + + + + String + + + + The name of the key. + + + + + + Int32 + + + + The length of the following key material. + + + + + + Byten + + + + The key material. + + + + + + + + + + + + +ColumnMasterKey (B) + + + + + + + + Byte1('y') + + + + Identifies the message as a column master key message. + + + + + + Int32 + + + + Length of message contents in bytes, including self. + + + + + + String + + + + The name of the key. + + + + + + String + + + + The name of the key provider. + + + + + + Int16 + + + + The number of options to follow. + + + + + Next, the following fields follow for each option: + + + + String + + + + Option name + + + + + + String + + + + Option value + + + + + + + + + + CommandComplete (B) @@ -5408,6 +5602,23 @@ Message Formats + + And then, for each parameter, there is the following: + + + + Int16 + + + + A format code. This is used as a bit field. If bit 0x10 is + set, the parameter corresponds to an encrypted column and must + be sent encrypted. If bit 0x20 is set in addition, the column + uses deterministic encryption, otherwise randomized + encryption. + + + @@ -5876,10 +6087,11 @@ Message Formats - The format code being used for the field. Currently will - be zero (text) or one (binary). In a RowDescription - returned from the statement variant of Describe, the - format code is not yet known and will always be zero. + The format code being used for the field. The lower byte will + be zero (text) or one (binary). The upper byte will be 1 if + the column is encrypted. In a RowDescription returned from + the statement variant of Describe, the format code is not yet + known, so the lower byte will always be zero. diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml index 57d51a676a..d72043d2b7 100644 --- a/doc/src/sgml/ref/create_table.sgml +++ b/doc/src/sgml/ref/create_table.sgml @@ -22,7 +22,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXISTS ] table_name ( [ - { column_name data_type [ COMPRESSION compression_method ] [ COLLATE collation ] [ column_constraint [ ... ] ] + { column_name data_type [ COMPRESSION compression_method ] [ ENCRYPTED WITH ( encryption_options ) ] [ COLLATE collation ] [ column_constraint [ ... ] ] | table_constraint | LIKE source_table [ like_option ... ] } [, ... ] @@ -317,6 +317,46 @@ Parameters + + ENCRYPTED WITH ( encryption_options ) + + + Enables transparent column encryption for the column. + encryption_options are comma-separated + key=value specifications. The following options are + available: + + + column_encryption_key + + + Specifies the name of the column encryption key to use. Specifying + this is mandatory. + + + + + encryption_type + + + randomized (the default) or deterministic + + + + + algorithm + + + The encryption algorithm to use. The default is + AES-CBC-128. + + + + + + + + INHERITS ( parent_table [, ... ] ) diff --git a/src/backend/access/common/printtup.c b/src/backend/access/common/printtup.c index 54b539f6fb..19a27325d8 100644 --- a/src/backend/access/common/printtup.c +++ b/src/backend/access/common/printtup.c @@ -15,13 +15,25 @@ */ #include "postgres.h" +#include "access/genam.h" #include "access/printtup.h" +#include "access/skey.h" +#include "access/table.h" +#include "catalog/pg_colenckey.h" +#include "catalog/pg_colenckeydata.h" +#include "catalog/pg_colmasterkey.h" #include "libpq/libpq.h" #include "libpq/pqformat.h" #include "tcop/pquery.h" +#include "utils/array.h" +#include "utils/arrayaccess.h" +#include "utils/builtins.h" +#include "utils/fmgroids.h" #include "utils/lsyscache.h" #include "utils/memdebug.h" #include "utils/memutils.h" +#include "utils/rel.h" +#include "utils/syscache.h" static void printtup_startup(DestReceiver *self, int operation, @@ -151,6 +163,127 @@ printtup_startup(DestReceiver *self, int operation, TupleDesc typeinfo) */ } +List *cmk_sent = NIL; + +static void +MaybeSendColumnMasterKeyMessage(Oid cmkid) +{ + HeapTuple tuple; + Form_pg_colmasterkey cmkform; + Datum datum; + bool isnull; + int n; + AnyArrayType *arr; + array_iter iter; + StringInfoData buf; + MemoryContext oldcontext; + + if (list_member_oid(cmk_sent, cmkid)) + return; + + tuple = SearchSysCache1(CMKOID, ObjectIdGetDatum(cmkid)); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "cache lookup failed for column master key %u", cmkid); + cmkform = (Form_pg_colmasterkey) GETSTRUCT(tuple); + + pq_beginmessage(&buf, 'y'); /* ColumnMasterKey */ + pq_sendstring(&buf, NameStr(cmkform->cmkname)); + + datum = SysCacheGetAttr(CMKOID, tuple, Anum_pg_colmasterkey_cmkprovider, &isnull); + Assert(!isnull); + pq_sendstring(&buf, TextDatumGetCString(datum)); + + datum = SysCacheGetAttr(CMKOID, tuple, Anum_pg_colmasterkey_cmkoptions, &isnull); + Assert(!isnull); + arr = DatumGetAnyArrayP(datum); + Assert(ARR_NDIM((ArrayType *) arr) == 1); + n = ARR_DIMS((ArrayType *) arr)[0]; + Assert(n % 2 == 0); + pq_sendint16(&buf, n / 2); + + array_iter_setup(&iter, arr); + + for (int i = 0; i < n; i++) + { + datum = array_iter_next(&iter, &isnull, i, -1, false, 'i'); + Assert(!isnull); + pq_sendstring(&buf, TextDatumGetCString(datum)); + } + + pq_endmessage(&buf); + + ReleaseSysCache(tuple); + + oldcontext = MemoryContextSwitchTo(TopMemoryContext); + cmk_sent = lappend_oid(cmk_sent, cmkid); + MemoryContextSwitchTo(oldcontext); +} + +List *cek_sent = NIL; + +void +MaybeSendColumnEncryptionKeyMessage(Oid attcek) +{ + HeapTuple tuple; + HeapTuple tuple2; + Form_pg_colenckey cekform; + ScanKeyData skey[1]; + SysScanDesc sd; + Relation rel; + bool found = false; + MemoryContext oldcontext; + + if (list_member_oid(cek_sent, attcek)) + return; + + tuple = SearchSysCache1(CEKOID, ObjectIdGetDatum(attcek)); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "cache lookup failed for column encryption key %u", attcek); + cekform = (Form_pg_colenckey) GETSTRUCT(tuple); + + ScanKeyInit(&skey[0], + Anum_pg_colenckeydata_ckdcekid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(attcek)); + rel = table_open(ColumnEncKeyDataRelationId, AccessShareLock); + sd = systable_beginscan(rel, ColumnEncKeyCekidCmkidIndexId, true, NULL, 1, skey); + + while ((tuple2 = systable_getnext(sd))) + { + Form_pg_colenckeydata ckdform = (Form_pg_colenckeydata) GETSTRUCT(tuple2); + Datum datum; + bool isnull; + bytea *ba; + StringInfoData buf; + + MaybeSendColumnMasterKeyMessage(ckdform->ckdcmkid); + + datum = heap_getattr(tuple2, Anum_pg_colenckeydata_ckdencval, RelationGetDescr(rel), &isnull); + Assert(!isnull); + ba = pg_detoast_datum_packed((bytea *) DatumGetPointer(datum)); + + pq_beginmessage(&buf, 'Y'); /* ColumnEncryptionKey */ + pq_sendstring(&buf, NameStr(cekform->cekname)); + pq_sendint32(&buf, VARSIZE_ANY_EXHDR(ba)); + pq_sendbytes(&buf, VARDATA_ANY(ba), VARSIZE_ANY_EXHDR(ba)); + pq_endmessage(&buf); + + found = true; + } + + if (!found) + elog(ERROR, "lookup failed for column encryption key data %u", attcek); + + systable_endscan(sd); + table_close(rel, NoLock); + + ReleaseSysCache(tuple); + + oldcontext = MemoryContextSwitchTo(TopMemoryContext); + cek_sent = lappend_oid(cek_sent, attcek); + MemoryContextSwitchTo(oldcontext); +} + /* * SendRowDescriptionMessage --- send a RowDescription message to the frontend * @@ -231,6 +364,23 @@ SendRowDescriptionMessage(StringInfo buf, TupleDesc typeinfo, else format = 0; + if (get_typtype(atttypid) == TYPTYPE_ENCRYPTED) + { + HeapTuple tp; + Form_pg_attribute orig_att; + + tp = SearchSysCache2(ATTNUM, ObjectIdGetDatum(resorigtbl), Int16GetDatum(resorigcol)); + if (!HeapTupleIsValid(tp)) + elog(ERROR, "cache lookup failed for attribute %d of relation %u", resorigcol, resorigtbl); + orig_att = (Form_pg_attribute) GETSTRUCT(tp); + MaybeSendColumnEncryptionKeyMessage(orig_att->attcek); + atttypid = orig_att->attrealtypid; + + ReleaseSysCache(tp); + + format |= 0x10; + } + pq_writestring(buf, NameStr(att->attname)); pq_writeint32(buf, resorigtbl); pq_writeint16(buf, resorigcol); diff --git a/src/backend/access/common/tupdesc.c b/src/backend/access/common/tupdesc.c index 9b506471fe..602e9e10a7 100644 --- a/src/backend/access/common/tupdesc.c +++ b/src/backend/access/common/tupdesc.c @@ -459,6 +459,10 @@ equalTupleDescs(TupleDesc tupdesc1, TupleDesc tupdesc2) return false; if (attr1->attislocal != attr2->attislocal) return false; + if (attr1->attcek != attr2->attcek) + return false; + if (attr1->attrealtypid != attr2->attrealtypid) + return false; if (attr1->attinhcount != attr2->attinhcount) return false; if (attr1->attcollation != attr2->attcollation) @@ -629,6 +633,8 @@ TupleDescInitEntry(TupleDesc desc, att->attgenerated = '\0'; att->attisdropped = false; att->attislocal = true; + att->attcek = 0; + att->attrealtypid = 0; att->attinhcount = 0; /* variable-length fields are not present in tupledescs */ @@ -690,6 +696,8 @@ TupleDescInitBuiltinEntry(TupleDesc desc, att->attgenerated = '\0'; att->attisdropped = false; att->attislocal = true; + att->attcek = 0; + att->attrealtypid = 0; att->attinhcount = 0; /* variable-length fields are not present in tupledescs */ diff --git a/src/backend/access/hash/hashvalidate.c b/src/backend/access/hash/hashvalidate.c index 1e343df0af..3c3dee5c36 100644 --- a/src/backend/access/hash/hashvalidate.c +++ b/src/backend/access/hash/hashvalidate.c @@ -331,7 +331,7 @@ check_hash_func_signature(Oid funcid, int16 amprocnum, Oid argtype) argtype == BOOLOID) /* okay, allowed use of hashchar() */ ; else if ((funcid == F_HASHVARLENA || funcid == F_HASHVARLENAEXTENDED) && - argtype == BYTEAOID) + (argtype == BYTEAOID || argtype == ENCRYPTEDDOID)) /* okay, allowed use of hashvarlena() */ ; else result = false; diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile index 4e6efda97f..85d6ee76e6 100644 --- a/src/backend/catalog/Makefile +++ b/src/backend/catalog/Makefile @@ -69,7 +69,8 @@ CATALOG_HEADERS := \ pg_default_acl.h pg_init_privs.h pg_seclabel.h pg_shseclabel.h \ pg_collation.h pg_partitioned_table.h pg_range.h pg_transform.h \ pg_sequence.h pg_publication.h pg_publication_namespace.h \ - pg_publication_rel.h pg_subscription.h pg_subscription_rel.h + pg_publication_rel.h pg_subscription.h pg_subscription_rel.h \ + pg_colmasterkey.h pg_colenckey.h pg_colenckeydata.h GENERATED_HEADERS := $(CATALOG_HEADERS:%.h=%_d.h) schemapg.h system_fk_info.h diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c index 8bbf23e452..3b8e0b92a1 100644 --- a/src/backend/catalog/heap.c +++ b/src/backend/catalog/heap.c @@ -792,6 +792,9 @@ InsertPgAttributeTuples(Relation pg_attribute_rel, slot[slotCount]->tts_values[Anum_pg_attribute_attgenerated - 1] = CharGetDatum(attrs->attgenerated); slot[slotCount]->tts_values[Anum_pg_attribute_attisdropped - 1] = BoolGetDatum(attrs->attisdropped); slot[slotCount]->tts_values[Anum_pg_attribute_attislocal - 1] = BoolGetDatum(attrs->attislocal); + slot[slotCount]->tts_values[Anum_pg_attribute_attcek - 1] = ObjectIdGetDatum(attrs->attcek); + slot[slotCount]->tts_values[Anum_pg_attribute_attrealtypid - 1] = ObjectIdGetDatum(attrs->attrealtypid); + slot[slotCount]->tts_values[Anum_pg_attribute_attencalg - 1] = Int32GetDatum(attrs->attencalg); slot[slotCount]->tts_values[Anum_pg_attribute_attinhcount - 1] = Int32GetDatum(attrs->attinhcount); slot[slotCount]->tts_values[Anum_pg_attribute_attcollation - 1] = ObjectIdGetDatum(attrs->attcollation); if (attoptions && attoptions[natts] != (Datum) 0) diff --git a/src/backend/commands/prepare.c b/src/backend/commands/prepare.c index 5e03c7c5aa..7925007c41 100644 --- a/src/backend/commands/prepare.c +++ b/src/backend/commands/prepare.c @@ -61,6 +61,8 @@ PrepareQuery(ParseState *pstate, PrepareStmt *stmt, RawStmt *rawstmt; CachedPlanSource *plansource; Oid *argtypes = NULL; + Oid *argorigtbls = NULL; + AttrNumber *argorigcols = NULL; int nargs; Query *query; List *query_list; @@ -108,6 +110,9 @@ PrepareQuery(ParseState *pstate, PrepareStmt *stmt, argtypes[i++] = toid; } + + argorigtbls = (Oid *) palloc0(nargs * sizeof(Oid)); + argorigcols = (AttrNumber *) palloc0(nargs * sizeof(AttrNumber)); } /* @@ -116,7 +121,7 @@ PrepareQuery(ParseState *pstate, PrepareStmt *stmt, * information about unknown parameters to be deduced from context. */ query = parse_analyze_varparams(rawstmt, pstate->p_sourcetext, - &argtypes, &nargs); + &argtypes, &nargs, &argorigtbls, &argorigcols); /* * Check that all parameter types were determined. @@ -159,6 +164,8 @@ PrepareQuery(ParseState *pstate, PrepareStmt *stmt, NULL, argtypes, nargs, + argorigtbls, + argorigcols, NULL, NULL, CURSOR_OPT_PARALLEL_OK, /* allow parallel mode */ @@ -724,7 +731,7 @@ pg_prepared_statement(PG_FUNCTION_ARGS) * build tupdesc for result tuples. This must match the definition of the * pg_prepared_statements view in system_views.sql */ - tupdesc = CreateTemplateTupleDesc(7); + tupdesc = CreateTemplateTupleDesc(9); TupleDescInitEntry(tupdesc, (AttrNumber) 1, "name", TEXTOID, -1, 0); TupleDescInitEntry(tupdesc, (AttrNumber) 2, "statement", @@ -739,6 +746,10 @@ pg_prepared_statement(PG_FUNCTION_ARGS) INT8OID, -1, 0); TupleDescInitEntry(tupdesc, (AttrNumber) 7, "custom_plans", INT8OID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 8, "parameter_orig_tables", + REGCLASSARRAYOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 9, "parameter_orig_columns", + INT2ARRAYOID, -1, 0); /* * We put all the tuples into a tuplestore in one scan of the hashtable. @@ -760,8 +771,8 @@ pg_prepared_statement(PG_FUNCTION_ARGS) hash_seq_init(&hash_seq, prepared_queries); while ((prep_stmt = hash_seq_search(&hash_seq)) != NULL) { - Datum values[7]; - bool nulls[7]; + Datum values[9]; + bool nulls[9]; MemSet(nulls, 0, sizeof(nulls)); @@ -773,6 +784,38 @@ pg_prepared_statement(PG_FUNCTION_ARGS) values[4] = BoolGetDatum(prep_stmt->from_sql); values[5] = Int64GetDatumFast(prep_stmt->plansource->num_generic_plans); values[6] = Int64GetDatumFast(prep_stmt->plansource->num_custom_plans); + { + int num_params = prep_stmt->plansource->num_params; + Datum *tmp_ary; + ArrayType *result; + int i; + + tmp_ary = (Datum *) palloc(num_params * sizeof(Datum)); + + for (i = 0; i < num_params; i++) + tmp_ary[i] = ObjectIdGetDatum(prep_stmt->plansource->param_origtbls[i]); + + /* XXX: this hardcodes assumptions about the regclass type */ + result = construct_array(tmp_ary, num_params, REGCLASSOID, + 4, true, TYPALIGN_INT); + values[7] = PointerGetDatum(result); + } + { + int num_params = prep_stmt->plansource->num_params; + Datum *tmp_ary; + ArrayType *result; + int i; + + tmp_ary = (Datum *) palloc(num_params * sizeof(Datum)); + + for (i = 0; i < num_params; i++) + tmp_ary[i] = Int16GetDatum(prep_stmt->plansource->param_origcols[i]); + + /* XXX: this hardcodes assumptions about the int2 type */ + result = construct_array(tmp_ary, num_params, INT2OID, + 2, true, TYPALIGN_SHORT); + values[8] = PointerGetDatum(result); + } tuplestore_putvalues(tupstore, tupdesc, values, nulls); } diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index c35f09998c..8dea6fafd3 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -33,6 +33,7 @@ #include "catalog/objectaccess.h" #include "catalog/partition.h" #include "catalog/pg_am.h" +#include "catalog/pg_colenckey.h" #include "catalog/pg_collation.h" #include "catalog/pg_constraint.h" #include "catalog/pg_depend.h" @@ -900,6 +901,64 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId, if (colDef->compression) attr->attcompression = GetAttributeCompression(attr->atttypid, colDef->compression); + + if (colDef->encryption) + { + ListCell *lc; + char *cek = NULL; + Oid cekoid; + bool encdet = false; + int alg = 1; + + foreach(lc, colDef->encryption) + { + DefElem *el = lfirst_node(DefElem, lc); + + if (strcmp(el->defname, "column_encryption_key") == 0) + cek = strVal(linitial(castNode(TypeName, el->arg)->names)); + else if (strcmp(el->defname, "encryption_type") == 0) + { + char *val = strVal(linitial(castNode(TypeName, el->arg)->names)); + + if (strcmp(val, "deterministic") == 0) + encdet = true; + else if (strcmp(val, "randomized") == 0) + encdet = false; + else + elog(ERROR, "unrecognized encryption type: %s", val); + } + else if (strcmp(el->defname, "algorithm") == 0) + { + char *val = strVal(linitial(castNode(TypeName, el->arg)->names)); + + if (strcmp(val, "AES-CBC-128") == 0) + alg = 1; + if (strcmp(val, "AES-CBC-192") == 0) + alg = 2; + if (strcmp(val, "AES-CBC-256") == 0) + alg = 3; + else + elog(ERROR, "unrecognized encryption algorithm: %s", val); + } + else + elog(ERROR, "unrecognized column encryption parameter: %s", el->defname); + } + + if (!cek) + elog(ERROR, "column encryption key must be specified"); + + cekoid = GetSysCacheOid1(CEKNAME, Anum_pg_colenckey_oid, PointerGetDatum(cek)); + if (!cekoid) + elog(ERROR, "column encryption key \"%s\" does not exist", cek); + + attr->attcek = cekoid; + attr->attrealtypid = attr->atttypid; + if (encdet) + attr->atttypid = ENCRYPTEDDOID; + else + attr->atttypid = ENCRYPTEDROID; + attr->attencalg = alg; + } } /* @@ -6758,6 +6817,9 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel, attribute.attgenerated = colDef->generated; attribute.attisdropped = false; attribute.attislocal = colDef->is_local; + attribute.attcek = 0; // TODO + attribute.attrealtypid = 0; // TODO + attribute.attencalg = 0; // TODO attribute.attinhcount = colDef->inhcount; attribute.attcollation = collOid; diff --git a/src/backend/executor/spi.c b/src/backend/executor/spi.c index 0568ae123f..b1bfe12eab 100644 --- a/src/backend/executor/spi.c +++ b/src/backend/executor/spi.c @@ -2186,6 +2186,8 @@ _SPI_prepare_plan(const char *src, SPIPlanPtr plan) NULL, plan->argtypes, plan->nargs, + NULL, + NULL, plan->parserSetup, plan->parserSetupArg, plan->cursor_options, @@ -2423,6 +2425,8 @@ _SPI_execute_plan(SPIPlanPtr plan, const SPIExecuteOptions *options, NULL, plan->argtypes, plan->nargs, + NULL, + NULL, plan->parserSetup, plan->parserSetupArg, plan->cursor_options, diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index 297b6ee715..4f6255baa9 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -3035,6 +3035,7 @@ _copyColumnDef(const ColumnDef *from) COPY_STRING_FIELD(colname); COPY_NODE_FIELD(typeName); COPY_STRING_FIELD(compression); + COPY_NODE_FIELD(encryption); COPY_SCALAR_FIELD(inhcount); COPY_SCALAR_FIELD(is_local); COPY_SCALAR_FIELD(is_not_null); diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c index f537d3eb96..cf0a23a2c4 100644 --- a/src/backend/nodes/equalfuncs.c +++ b/src/backend/nodes/equalfuncs.c @@ -2679,6 +2679,7 @@ _equalColumnDef(const ColumnDef *a, const ColumnDef *b) COMPARE_STRING_FIELD(colname); COMPARE_NODE_FIELD(typeName); COMPARE_STRING_FIELD(compression); + COMPARE_NODE_FIELD(encryption); COMPARE_SCALAR_FIELD(inhcount); COMPARE_SCALAR_FIELD(is_local); COMPARE_SCALAR_FIELD(is_not_null); diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c index e276264882..33621141f1 100644 --- a/src/backend/nodes/nodeFuncs.c +++ b/src/backend/nodes/nodeFuncs.c @@ -3917,6 +3917,8 @@ raw_expression_tree_walker(Node *node, return true; if (walker(coldef->compression, context)) return true; + if (walker(coldef->encryption, context)) + return true; if (walker(coldef->raw_default, context)) return true; if (walker(coldef->collClause, context)) diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index 3600c9fa36..ecd2e86abf 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -2953,6 +2953,7 @@ _outColumnDef(StringInfo str, const ColumnDef *node) WRITE_STRING_FIELD(colname); WRITE_NODE_FIELD(typeName); WRITE_STRING_FIELD(compression); + WRITE_NODE_FIELD(encryption); WRITE_INT_FIELD(inhcount); WRITE_BOOL_FIELD(is_local); WRITE_BOOL_FIELD(is_not_null); diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c index 146ee8dd1e..411c474a89 100644 --- a/src/backend/parser/analyze.c +++ b/src/backend/parser/analyze.c @@ -148,7 +148,8 @@ parse_analyze(RawStmt *parseTree, const char *sourceText, */ Query * parse_analyze_varparams(RawStmt *parseTree, const char *sourceText, - Oid **paramTypes, int *numParams) + Oid **paramTypes, int *numParams, + Oid **paramOrigTbls, AttrNumber **paramOrigCols) { ParseState *pstate = make_parsestate(NULL); Query *query; @@ -158,7 +159,7 @@ parse_analyze_varparams(RawStmt *parseTree, const char *sourceText, pstate->p_sourcetext = sourceText; - parse_variable_parameters(pstate, paramTypes, numParams); + parse_variable_parameters(pstate, paramTypes, numParams, paramOrigTbls, paramOrigCols); query = transformTopLevelStmt(pstate, parseTree); diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 86ce33bd97..449dc4e9b1 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -568,6 +568,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); %type TableConstraint TableLikeClause %type TableLikeOptionList TableLikeOption %type column_compression opt_column_compression +%type opt_column_encryption %type ColQualList %type ColConstraint ColConstraintElem ConstraintAttr %type key_actions key_delete key_match key_update key_action @@ -3476,12 +3477,13 @@ TypedTableElement: | TableConstraint { $$ = $1; } ; -columnDef: ColId Typename opt_column_compression create_generic_options ColQualList +columnDef: ColId Typename opt_column_compression opt_column_encryption create_generic_options ColQualList { ColumnDef *n = makeNode(ColumnDef); n->colname = $1; n->typeName = $2; n->compression = $3; + n->encryption = $4; n->inhcount = 0; n->is_local = true; n->is_not_null = false; @@ -3490,8 +3492,8 @@ columnDef: ColId Typename opt_column_compression create_generic_options ColQualL n->raw_default = NULL; n->cooked_default = NULL; n->collOid = InvalidOid; - n->fdwoptions = $4; - SplitColQualList($5, &n->constraints, &n->collClause, + n->fdwoptions = $5; + SplitColQualList($6, &n->constraints, &n->collClause, yyscanner); n->location = @1; $$ = (Node *)n; @@ -3546,6 +3548,11 @@ opt_column_compression: | /*EMPTY*/ { $$ = NULL; } ; +opt_column_encryption: + ENCRYPTED WITH '(' def_list ')' { $$ = $4; } + | /*EMPTY*/ { $$ = NULL; } + ; + ColQualList: ColQualList ColConstraint { $$ = lappend($1, $2); } | /*EMPTY*/ { $$ = NIL; } diff --git a/src/backend/parser/parse_param.c b/src/backend/parser/parse_param.c index 68a5534393..4fd5cb1443 100644 --- a/src/backend/parser/parse_param.c +++ b/src/backend/parser/parse_param.c @@ -49,6 +49,8 @@ typedef struct VarParamState { Oid **paramTypes; /* array of parameter type OIDs */ int *numParams; /* number of array entries */ + Oid **paramOrigTbls; + AttrNumber **paramOrigCols; } VarParamState; static Node *fixed_paramref_hook(ParseState *pstate, ParamRef *pref); @@ -56,6 +58,7 @@ static Node *variable_paramref_hook(ParseState *pstate, ParamRef *pref); static Node *variable_coerce_param_hook(ParseState *pstate, Param *param, Oid targetTypeId, int32 targetTypeMod, int location); +static void variable_param_assign_orig_hook(ParseState *pstate, Param *param, Oid origtbl, AttrNumber origcol); static bool check_parameter_resolution_walker(Node *node, ParseState *pstate); static bool query_contains_extern_params_walker(Node *node, void *context); @@ -81,15 +84,19 @@ parse_fixed_parameters(ParseState *pstate, */ void parse_variable_parameters(ParseState *pstate, - Oid **paramTypes, int *numParams) + Oid **paramTypes, int *numParams, + Oid **paramOrigTbls, AttrNumber **paramOrigCols) { VarParamState *parstate = palloc(sizeof(VarParamState)); parstate->paramTypes = paramTypes; parstate->numParams = numParams; + parstate->paramOrigTbls = paramOrigTbls; + parstate->paramOrigCols = paramOrigCols; pstate->p_ref_hook_state = (void *) parstate; pstate->p_paramref_hook = variable_paramref_hook; pstate->p_coerce_param_hook = variable_coerce_param_hook; + pstate->p_param_assign_orig_hook = variable_param_assign_orig_hook; } /* @@ -145,14 +152,36 @@ variable_paramref_hook(ParseState *pstate, ParamRef *pref) { /* Need to enlarge param array */ if (*parstate->paramTypes) + { *parstate->paramTypes = (Oid *) repalloc(*parstate->paramTypes, paramno * sizeof(Oid)); + if (parstate->paramOrigTbls) + *parstate->paramOrigTbls = repalloc(*parstate->paramOrigTbls, + paramno * sizeof(Oid)); + if (parstate->paramOrigCols) + *parstate->paramOrigCols = repalloc(*parstate->paramOrigCols, + paramno * sizeof(AttrNumber)); + } else + { *parstate->paramTypes = (Oid *) palloc(paramno * sizeof(Oid)); + if (parstate->paramOrigTbls) + *parstate->paramOrigTbls = palloc(paramno * sizeof(Oid)); + if (parstate->paramOrigCols) + *parstate->paramOrigCols = palloc(paramno * sizeof(AttrNumber)); + } /* Zero out the previously-unreferenced slots */ MemSet(*parstate->paramTypes + *parstate->numParams, 0, (paramno - *parstate->numParams) * sizeof(Oid)); + if (parstate->paramOrigTbls) + MemSet(*parstate->paramOrigTbls + *parstate->numParams, + 0, + (paramno - *parstate->numParams) * sizeof(Oid)); + if (parstate->paramOrigCols) + MemSet(*parstate->paramOrigCols + *parstate->numParams, + 0, + (paramno - *parstate->numParams) * sizeof(AttrNumber)); *parstate->numParams = paramno; } @@ -260,6 +289,18 @@ variable_coerce_param_hook(ParseState *pstate, Param *param, return NULL; } +static void +variable_param_assign_orig_hook(ParseState *pstate, Param *param, Oid origtbl, AttrNumber origcol) +{ + VarParamState *parstate = (VarParamState *) pstate->p_ref_hook_state; + int paramno = param->paramid; + + if (parstate->paramOrigTbls) + (*parstate->paramOrigTbls)[paramno - 1] = origtbl; + if (parstate->paramOrigCols) + (*parstate->paramOrigCols)[paramno - 1] = origcol; +} + /* * Check for consistent assignment of variable parameters after completion * of parsing with parse_variable_parameters. diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c index 9ce3a0de96..5d0093d7ac 100644 --- a/src/backend/parser/parse_target.c +++ b/src/backend/parser/parse_target.c @@ -595,6 +595,12 @@ transformAssignedExpr(ParseState *pstate, parser_errposition(pstate, exprLocation(orig_expr)))); } + if (IsA(expr, Param)) + { + if (pstate->p_param_assign_orig_hook) + pstate->p_param_assign_orig_hook(pstate, castNode(Param, expr), RelationGetRelid(pstate->p_target_relation), attrno); + } + pstate->p_expr_kind = sv_expr_kind; return expr; diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c index 82de01cdc6..da7ab9d68b 100644 --- a/src/backend/tcop/postgres.c +++ b/src/backend/tcop/postgres.c @@ -80,6 +80,7 @@ #include "utils/memutils.h" #include "utils/ps_status.h" #include "utils/snapmgr.h" +#include "utils/syscache.h" #include "utils/timeout.h" #include "utils/timestamp.h" @@ -1336,6 +1337,8 @@ exec_parse_message(const char *query_string, /* string to execute */ bool is_named; bool save_log_statement_stats = log_statement_stats; char msec_str[32]; + Oid *paramOrigTbls = palloc(numParams * sizeof(Oid)); + AttrNumber *paramOrigCols = palloc(numParams * sizeof(AttrNumber)); /* * Report query to various monitoring facilities. @@ -1459,7 +1462,9 @@ exec_parse_message(const char *query_string, /* string to execute */ query = parse_analyze_varparams(raw_parse_tree, query_string, ¶mTypes, - &numParams); + &numParams, + ¶mOrigTbls, + ¶mOrigCols); /* * Check all parameter types got determined. @@ -1508,6 +1513,8 @@ exec_parse_message(const char *query_string, /* string to execute */ unnamed_stmt_context, paramTypes, numParams, + paramOrigTbls, + paramOrigCols, NULL, NULL, CURSOR_OPT_PARALLEL_OK, /* allow parallel mode */ @@ -1805,6 +1812,16 @@ exec_bind_message(StringInfo input_message) else pformat = 0; /* default = text */ + if (get_typtype(ptype) == TYPTYPE_ENCRYPTED) + { + if (pformat & 0xF0) + pformat &= ~0xF0; + else + ereport(ERROR, + (errcode(ERRCODE_PROTOCOL_VIOLATION), + errmsg("parameter corresponds to an encrypted column, but the parameter value was not encrypted"))); + } + if (pformat == 0) /* text mode */ { Oid typinput; @@ -2602,8 +2619,47 @@ exec_describe_statement_message(const char *stmt_name) { Oid ptype = psrc->param_types[i]; + if (get_typtype(ptype) == TYPTYPE_ENCRYPTED) + { + Oid porigtbl = psrc->param_origtbls[i]; + AttrNumber porigcol = psrc->param_origcols[i]; + HeapTuple tp; + Form_pg_attribute orig_att; + + if (porigtbl == InvalidOid || porigcol <= 0) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("parameter %d corresponds to an encrypted column, but an underlying table and column could not be determined", i))); + + tp = SearchSysCache2(ATTNUM, ObjectIdGetDatum(porigtbl), Int16GetDatum(porigcol)); + if (!HeapTupleIsValid(tp)) + elog(ERROR, "cache lookup failed for attribute %d of relation %u", porigcol, porigtbl); + orig_att = (Form_pg_attribute) GETSTRUCT(tp); + MaybeSendColumnEncryptionKeyMessage(orig_att->attcek); + ptype = orig_att->attrealtypid; + + ReleaseSysCache(tp); + } + pq_sendint32(&row_description_buf, (int) ptype); } + + for (int i = 0; i < psrc->num_params; i++) + { + int16 pformat; + + if (get_typtype(psrc->param_types[i]) == TYPTYPE_ENCRYPTED) + { + pformat = 0x10; + if (psrc->param_types[i] == ENCRYPTEDDOID) + pformat |= 0x20; + } + else + pformat = 0; + + pq_sendint16(&row_description_buf, pformat); + } + pq_endmessage_reuse(&row_description_buf); /* diff --git a/src/backend/utils/cache/plancache.c b/src/backend/utils/cache/plancache.c index 6767eae8f2..869f37b26e 100644 --- a/src/backend/utils/cache/plancache.c +++ b/src/backend/utils/cache/plancache.c @@ -340,6 +340,8 @@ CompleteCachedPlan(CachedPlanSource *plansource, MemoryContext querytree_context, Oid *param_types, int num_params, + Oid *param_origtbls, + AttrNumber *param_origcols, ParserSetupHook parserSetup, void *parserSetupArg, int cursor_options, @@ -419,6 +421,14 @@ CompleteCachedPlan(CachedPlanSource *plansource, { plansource->param_types = (Oid *) palloc(num_params * sizeof(Oid)); memcpy(plansource->param_types, param_types, num_params * sizeof(Oid)); + + plansource->param_origtbls = (Oid *) palloc0(num_params * sizeof(Oid)); + if (param_origtbls) + memcpy(plansource->param_origtbls, param_origtbls, num_params * sizeof(Oid)); + + plansource->param_origcols = (AttrNumber *) palloc0(num_params * sizeof(AttrNumber)); + if (param_origcols) + memcpy(plansource->param_origcols, param_origcols, num_params * sizeof(AttrNumber)); } else plansource->param_types = NULL; diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c index 56870b46e4..5c3f9b0055 100644 --- a/src/backend/utils/cache/syscache.c +++ b/src/backend/utils/cache/syscache.c @@ -29,7 +29,9 @@ #include "catalog/pg_auth_members.h" #include "catalog/pg_authid.h" #include "catalog/pg_cast.h" +#include "catalog/pg_colenckey.h" #include "catalog/pg_collation.h" +#include "catalog/pg_colmasterkey.h" #include "catalog/pg_constraint.h" #include "catalog/pg_conversion.h" #include "catalog/pg_database.h" @@ -266,6 +268,24 @@ static const struct cachedesc cacheinfo[] = { }, 256 }, + { + ColumnEncKeyRelationId, /* CEKNAME */ + ColumnEncKeyNameIndexId, + 1, + { + Anum_pg_colenckey_cekname, + }, + 8 + }, + { + ColumnEncKeyRelationId, /* CEKOID */ + ColumnEncKeyOidIndexId, + 1, + { + Anum_pg_colenckey_oid, + }, + 8 + }, {OperatorClassRelationId, /* CLAAMNAMENSP */ OpclassAmNameNspIndexId, 3, @@ -288,6 +308,15 @@ static const struct cachedesc cacheinfo[] = { }, 8 }, + { + ColumnMasterKeyRelationId, /* CMKOID */ + ColumnMasterKeyOidIndexId, + 1, + { + Anum_pg_colmasterkey_oid, + }, + 8 + }, {CollationRelationId, /* COLLNAMEENCNSP */ CollationNameEncNspIndexId, 3, diff --git a/src/include/access/printtup.h b/src/include/access/printtup.h index c9b37538a0..90d8c93564 100644 --- a/src/include/access/printtup.h +++ b/src/include/access/printtup.h @@ -20,6 +20,8 @@ extern DestReceiver *printtup_create_DR(CommandDest dest); extern void SetRemoteDestReceiverParams(DestReceiver *self, Portal portal); +extern void MaybeSendColumnEncryptionKeyMessage(Oid attcek); + extern void SendRowDescriptionMessage(StringInfo buf, TupleDesc typeinfo, List *targetlist, int16 *formats); diff --git a/src/include/catalog/pg_amop.dat b/src/include/catalog/pg_amop.dat index 8135854163..815cdac0af 100644 --- a/src/include/catalog/pg_amop.dat +++ b/src/include/catalog/pg_amop.dat @@ -1028,6 +1028,11 @@ amoprighttype => 'bytea', amopstrategy => '1', amopopr => '=(bytea,bytea)', amopmethod => 'hash' }, +# encryptedd_ops +{ amopfamily => 'hash/encryptedd_ops', amoplefttype => 'encryptedd', + amoprighttype => 'encryptedd', amopstrategy => '1', amopopr => '=(encryptedd,encryptedd)', + amopmethod => 'hash' }, + # xid_ops { amopfamily => 'hash/xid_ops', amoplefttype => 'xid', amoprighttype => 'xid', amopstrategy => '1', amopopr => '=(xid,xid)', amopmethod => 'hash' }, diff --git a/src/include/catalog/pg_amproc.dat b/src/include/catalog/pg_amproc.dat index 5460aa2422..a3698eea0f 100644 --- a/src/include/catalog/pg_amproc.dat +++ b/src/include/catalog/pg_amproc.dat @@ -402,6 +402,11 @@ { amprocfamily => 'hash/bytea_ops', amproclefttype => 'bytea', amprocrighttype => 'bytea', amprocnum => '2', amproc => 'hashvarlenaextended' }, +{ amprocfamily => 'hash/encryptedd_ops', amproclefttype => 'encryptedd', + amprocrighttype => 'encryptedd', amprocnum => '1', amproc => 'hashvarlena' }, +{ amprocfamily => 'hash/encryptedd_ops', amproclefttype => 'encryptedd', + amprocrighttype => 'encryptedd', amprocnum => '2', + amproc => 'hashvarlenaextended' }, { amprocfamily => 'hash/xid_ops', amproclefttype => 'xid', amprocrighttype => 'xid', amprocnum => '1', amproc => 'hashint4' }, { amprocfamily => 'hash/xid_ops', amproclefttype => 'xid', diff --git a/src/include/catalog/pg_attribute.h b/src/include/catalog/pg_attribute.h index 5c1ec9313e..48c774c053 100644 --- a/src/include/catalog/pg_attribute.h +++ b/src/include/catalog/pg_attribute.h @@ -164,6 +164,15 @@ CATALOG(pg_attribute,1249,AttributeRelationId) BKI_BOOTSTRAP BKI_ROWTYPE_OID(75, */ bool attislocal BKI_DEFAULT(t); + /* column encryption key */ + Oid attcek BKI_DEFAULT(0) BKI_LOOKUP_OPT(pg_colenckey); + + /* real type if encrypted */ + Oid attrealtypid BKI_DEFAULT(0) BKI_LOOKUP_OPT(pg_type); + + /* encryption algorithm */ + int32 attencalg BKI_DEFAULT(0); + /* Number of times inherited from direct parent relation(s) */ int32 attinhcount BKI_DEFAULT(0); diff --git a/src/include/catalog/pg_cast.dat b/src/include/catalog/pg_cast.dat index 67f73cb6fb..22ff9d5c0b 100644 --- a/src/include/catalog/pg_cast.dat +++ b/src/include/catalog/pg_cast.dat @@ -548,4 +548,10 @@ { castsource => 'tstzrange', casttarget => 'tstzmultirange', castfunc => 'tstzmultirange(tstzrange)', castcontext => 'e', castmethod => 'f' }, + +{ castsource => 'bytea', casttarget => 'encryptedd', castfunc => '0', + castcontext => 'e', castmethod => 'b' }, +{ castsource => 'bytea', casttarget => 'encryptedr', castfunc => '0', + castcontext => 'e', castmethod => 'b' }, + ] diff --git a/src/include/catalog/pg_colenckey.h b/src/include/catalog/pg_colenckey.h new file mode 100644 index 0000000000..a59abc3a16 --- /dev/null +++ b/src/include/catalog/pg_colenckey.h @@ -0,0 +1,18 @@ +#ifndef PG_COLENCKEY_H +#define PG_COLENCKEY_H + +#include "catalog/genbki.h" +#include "catalog/pg_colenckey_d.h" + +CATALOG(pg_colenckey,8234,ColumnEncKeyRelationId) +{ + Oid oid; + NameData cekname; +} FormData_pg_colenckey; + +typedef FormData_pg_colenckey *Form_pg_colenckey; + +DECLARE_UNIQUE_INDEX_PKEY(pg_colenckey_oid_index, 8240, ColumnEncKeyOidIndexId, on pg_colenckey using btree(oid oid_ops)); +DECLARE_UNIQUE_INDEX(pg_colenckey_cekname_index, 8242, ColumnEncKeyNameIndexId, on pg_colenckey using btree(cekname name_ops)); + +#endif diff --git a/src/include/catalog/pg_colenckeydata.h b/src/include/catalog/pg_colenckeydata.h new file mode 100644 index 0000000000..cd19b0501b --- /dev/null +++ b/src/include/catalog/pg_colenckeydata.h @@ -0,0 +1,25 @@ +#ifndef PG_COLENCKEYDATA_H +#define PG_COLENCKEYDATA_H + +#include "catalog/genbki.h" +#include "catalog/pg_colenckeydata_d.h" + +CATALOG(pg_colenckeydata,8250,ColumnEncKeyDataRelationId) +{ + Oid oid; + Oid ckdcekid BKI_LOOKUP(pg_colenckey); + Oid ckdcmkid BKI_LOOKUP(pg_colmasterkey); +#ifdef CATALOG_VARLEN /* variable-length fields start here */ + text ckdcmkalg BKI_FORCE_NOT_NULL; + bytea ckdencval BKI_FORCE_NOT_NULL; +#endif +} FormData_pg_colenckeydata; + +typedef FormData_pg_colenckeydata *Form_pg_colenckeydata; + +DECLARE_TOAST(pg_colenckeydata, 8237, 8238); + +DECLARE_UNIQUE_INDEX_PKEY(pg_colenckeydata_oid_index, 8251, ColumnEncKeyDataOidIndexId, on pg_colenckeydata using btree(oid oid_ops)); +DECLARE_UNIQUE_INDEX(pg_colenckeydata_ckdcekid_ckdcmkid_index, 8252, ColumnEncKeyCekidCmkidIndexId, on pg_colenckeydata using btree(ckdcekid oid_ops, ckdcmkid oid_ops)); + +#endif diff --git a/src/include/catalog/pg_colmasterkey.h b/src/include/catalog/pg_colmasterkey.h new file mode 100644 index 0000000000..15292b0b61 --- /dev/null +++ b/src/include/catalog/pg_colmasterkey.h @@ -0,0 +1,24 @@ +#ifndef PG_COLMASTERKEY_H +#define PG_COLMASTERKEY_H + +#include "catalog/genbki.h" +#include "catalog/pg_colmasterkey_d.h" + +CATALOG(pg_colmasterkey,8233,ColumnMasterKeyRelationId) +{ + Oid oid; + NameData cmkname; +#ifdef CATALOG_VARLEN /* variable-length fields start here */ + text cmkprovider BKI_FORCE_NOT_NULL; + text cmkoptions[1] BKI_FORCE_NOT_NULL; +#endif +} FormData_pg_colmasterkey; + +typedef FormData_pg_colmasterkey *Form_pg_colmasterkey; + +DECLARE_TOAST(pg_colmasterkey, 8235, 8236); + +DECLARE_UNIQUE_INDEX_PKEY(pg_colmasterkey_oid_index, 8239, ColumnMasterKeyOidIndexId, on pg_colmasterkey using btree(oid oid_ops)); +DECLARE_UNIQUE_INDEX(pg_colmasterkey_cmkname_index, 8241, ColumnMasterKeyNameIndexId, on pg_colmasterkey using btree(cmkname name_ops)); + +#endif diff --git a/src/include/catalog/pg_opclass.dat b/src/include/catalog/pg_opclass.dat index 484727a2fc..d41e2aac2a 100644 --- a/src/include/catalog/pg_opclass.dat +++ b/src/include/catalog/pg_opclass.dat @@ -166,6 +166,8 @@ opcintype => 'bool' }, { opcmethod => 'hash', opcname => 'bytea_ops', opcfamily => 'hash/bytea_ops', opcintype => 'bytea' }, +{ opcmethod => 'hash', opcname => 'encryptedd_ops', opcfamily => 'hash/encryptedd_ops', + opcintype => 'encryptedd' }, { opcmethod => 'btree', opcname => 'tid_ops', opcfamily => 'btree/tid_ops', opcintype => 'tid' }, { opcmethod => 'hash', opcname => 'xid_ops', opcfamily => 'hash/xid_ops', diff --git a/src/include/catalog/pg_operator.dat b/src/include/catalog/pg_operator.dat index 0075a02f32..9fb9890522 100644 --- a/src/include/catalog/pg_operator.dat +++ b/src/include/catalog/pg_operator.dat @@ -3475,4 +3475,14 @@ oprcode => 'multirange_after_multirange', oprrest => 'multirangesel', oprjoin => 'scalargtjoinsel' }, +{ oid => '8247', descr => 'equal', + oprname => '=', oprcanmerge => 'f', oprcanhash => 't', oprleft => 'encryptedd', + oprright => 'encryptedd', oprresult => 'bool', oprcom => '=(encryptedd,encryptedd)', + oprnegate => '<>(encryptedd,encryptedd)', oprcode => 'encrypteddeq', oprrest => 'eqsel', + oprjoin => 'eqjoinsel' }, +{ oid => '8248', descr => 'not equal', + oprname => '<>', oprleft => 'encryptedd', oprright => 'encryptedd', oprresult => 'bool', + oprcom => '<>(encryptedd,encryptedd)', oprnegate => '=(encryptedd,encryptedd)', + oprcode => 'encrypteddne', oprrest => 'neqsel', oprjoin => 'neqjoinsel' }, + ] diff --git a/src/include/catalog/pg_opfamily.dat b/src/include/catalog/pg_opfamily.dat index 8e480efd28..dcf9839e42 100644 --- a/src/include/catalog/pg_opfamily.dat +++ b/src/include/catalog/pg_opfamily.dat @@ -108,6 +108,8 @@ opfmethod => 'hash', opfname => 'bool_ops' }, { oid => '2223', opfmethod => 'hash', opfname => 'bytea_ops' }, +{ oid => '8249', + opfmethod => 'hash', opfname => 'encryptedd_ops' }, { oid => '2789', opfmethod => 'btree', opfname => 'tid_ops' }, { oid => '2225', diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 79d787cd26..44a822aca5 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -7992,9 +7992,9 @@ proname => 'pg_prepared_statement', prorows => '1000', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'record', proargtypes => '', - proallargtypes => '{text,text,timestamptz,_regtype,bool,int8,int8}', - proargmodes => '{o,o,o,o,o,o,o}', - proargnames => '{name,statement,prepare_time,parameter_types,from_sql,generic_plans,custom_plans}', + proallargtypes => '{text,text,timestamptz,_regtype,bool,int8,int8,_regclass,_int2}', + proargmodes => '{o,o,o,o,o,o,o,o,o}', + proargnames => '{name,statement,prepare_time,parameter_types,from_sql,generic_plans,custom_plans,parameter_orig_tables,parameter_orig_columns}', prosrc => 'pg_prepared_statement' }, { oid => '2511', descr => 'get the open cursors for this session', proname => 'pg_cursor', prorows => '1000', proretset => 't', @@ -11733,4 +11733,11 @@ prorettype => 'bytea', proargtypes => 'pg_brin_minmax_multi_summary', prosrc => 'brin_minmax_multi_summary_send' }, +{ oid => '8245', + proname => 'encrypteddeq', proleakproof => 't', prorettype => 'bool', + proargtypes => 'encryptedd encryptedd', prosrc => 'byteaeq' }, +{ oid => '8246', + proname => 'encrypteddne', proleakproof => 't', prorettype => 'bool', + proargtypes => 'encryptedd encryptedd', prosrc => 'byteane' }, + ] diff --git a/src/include/catalog/pg_type.dat b/src/include/catalog/pg_type.dat index 41074c994b..36e2e2c263 100644 --- a/src/include/catalog/pg_type.dat +++ b/src/include/catalog/pg_type.dat @@ -692,4 +692,16 @@ typreceive => 'brin_minmax_multi_summary_recv', typsend => 'brin_minmax_multi_summary_send', typalign => 'i', typstorage => 'x', typcollation => 'default' }, + +{ oid => '8243', descr => 'encrypted column (deterministic)', + typname => 'encryptedd', typlen => '-1', typbyval => 'f', typtype => 'y', + typcategory => 'Y', typinput => 'byteain', typoutput => 'byteaout', + typreceive => 'bytearecv', typsend => 'byteasend', typalign => 'i', + typstorage => 'x' }, +{ oid => '8244', descr => 'encrypted column (randomized)', + typname => 'encryptedr', typlen => '-1', typbyval => 'f', typtype => 'y', + typcategory => 'Y', typinput => 'byteain', typoutput => 'byteaout', + typreceive => 'bytearecv', typsend => 'byteasend', typalign => 'i', + typstorage => 'x' }, + ] diff --git a/src/include/catalog/pg_type.h b/src/include/catalog/pg_type.h index e568e21dee..999720fb54 100644 --- a/src/include/catalog/pg_type.h +++ b/src/include/catalog/pg_type.h @@ -277,6 +277,7 @@ DECLARE_UNIQUE_INDEX(pg_type_typname_nsp_index, 2704, TypeNameNspIndexId, on pg_ #define TYPTYPE_MULTIRANGE 'm' /* multirange type */ #define TYPTYPE_PSEUDO 'p' /* pseudo-type */ #define TYPTYPE_RANGE 'r' /* range type */ +#define TYPTYPE_ENCRYPTED 'y' /* encrypted column value */ #define TYPCATEGORY_INVALID '\0' /* not an allowed category */ #define TYPCATEGORY_ARRAY 'A' diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 067138e6b5..04e6e310ed 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -670,6 +670,7 @@ typedef struct ColumnDef char *colname; /* name of column */ TypeName *typeName; /* type of column */ char *compression; /* compression method for column */ + List *encryption; /* encryption info for column */ int inhcount; /* number of times column is inherited */ bool is_local; /* column has local (non-inherited) def'n */ bool is_not_null; /* NOT NULL constraint specified? */ diff --git a/src/include/parser/analyze.h b/src/include/parser/analyze.h index a0f0bd38d7..c1b8f7617a 100644 --- a/src/include/parser/analyze.h +++ b/src/include/parser/analyze.h @@ -27,7 +27,8 @@ extern PGDLLIMPORT post_parse_analyze_hook_type post_parse_analyze_hook; extern Query *parse_analyze(RawStmt *parseTree, const char *sourceText, Oid *paramTypes, int numParams, QueryEnvironment *queryEnv); extern Query *parse_analyze_varparams(RawStmt *parseTree, const char *sourceText, - Oid **paramTypes, int *numParams); + Oid **paramTypes, int *numParams, + Oid **paramOrigTbls, AttrNumber **paramOrigCols); extern Query *parse_sub_analyze(Node *parseTree, ParseState *parentParseState, CommonTableExpr *parentCTE, diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h index ee179082ce..011162a63f 100644 --- a/src/include/parser/parse_node.h +++ b/src/include/parser/parse_node.h @@ -92,6 +92,7 @@ typedef Node *(*ParseParamRefHook) (ParseState *pstate, ParamRef *pref); typedef Node *(*CoerceParamHook) (ParseState *pstate, Param *param, Oid targetTypeId, int32 targetTypeMod, int location); +typedef void (*ParamAssignOrigHook) (ParseState *pstate, Param *param, Oid origtbl, AttrNumber origcol); /* @@ -221,6 +222,7 @@ struct ParseState PostParseColumnRefHook p_post_columnref_hook; ParseParamRefHook p_paramref_hook; CoerceParamHook p_coerce_param_hook; + ParamAssignOrigHook p_param_assign_orig_hook; void *p_ref_hook_state; /* common passthrough link for above */ }; diff --git a/src/include/parser/parse_param.h b/src/include/parser/parse_param.h index b42fff296c..b90a315e9c 100644 --- a/src/include/parser/parse_param.h +++ b/src/include/parser/parse_param.h @@ -18,7 +18,8 @@ extern void parse_fixed_parameters(ParseState *pstate, Oid *paramTypes, int numParams); extern void parse_variable_parameters(ParseState *pstate, - Oid **paramTypes, int *numParams); + Oid **paramTypes, int *numParams, + Oid **paramOrigTbls, AttrNumber **paramOrigCols); extern void check_variable_parameters(ParseState *pstate, Query *query); extern bool query_contains_extern_params(Query *query); diff --git a/src/include/utils/plancache.h b/src/include/utils/plancache.h index ff09c63a02..eac90aa821 100644 --- a/src/include/utils/plancache.h +++ b/src/include/utils/plancache.h @@ -101,6 +101,8 @@ typedef struct CachedPlanSource CommandTag commandTag; /* 'nuff said */ Oid *param_types; /* array of parameter type OIDs, or NULL */ int num_params; /* length of param_types array */ + Oid *param_origtbls; + AttrNumber *param_origcols; ParserSetupHook parserSetup; /* alternative parameter spec method */ void *parserSetupArg; int cursor_options; /* cursor options used for planning */ @@ -199,6 +201,8 @@ extern void CompleteCachedPlan(CachedPlanSource *plansource, MemoryContext querytree_context, Oid *param_types, int num_params, + Oid *param_origtbls, + AttrNumber *param_origcols, ParserSetupHook parserSetup, void *parserSetupArg, int cursor_options, diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h index c8cfbc30f6..2420c9a818 100644 --- a/src/include/utils/syscache.h +++ b/src/include/utils/syscache.h @@ -44,8 +44,11 @@ enum SysCacheIdentifier AUTHNAME, AUTHOID, CASTSOURCETARGET, + CEKNAME, + CEKOID, CLAAMNAMENSP, CLAOID, + CMKOID, COLLNAMEENCNSP, COLLOID, CONDEFAULT, diff --git a/src/interfaces/libpq/exports.txt b/src/interfaces/libpq/exports.txt index e8bcc88370..7dacc93e0b 100644 --- a/src/interfaces/libpq/exports.txt +++ b/src/interfaces/libpq/exports.txt @@ -186,3 +186,5 @@ PQpipelineStatus 183 PQsetTraceFlags 184 PQmblenBounded 185 PQsendFlushRequest 186 +PQexecPrepared2 187 +PQsendQueryPrepared2 188 diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 9b6a6939f0..bc1b492557 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -4151,6 +4151,11 @@ freePGconn(PGconn *conn) free(conn->gsslib); if (conn->connip) free(conn->connip); + if (conn->cekdata) + { + explicit_bzero(conn->cekdata, conn->cekdatalen); + free(conn->cekdata); + } /* Note that conn->Pfdebug is not ours to close or free */ if (conn->write_err_msg) free(conn->write_err_msg); diff --git a/src/interfaces/libpq/fe-exec.c b/src/interfaces/libpq/fe-exec.c index 6c7b3df012..0e9431f360 100644 --- a/src/interfaces/libpq/fe-exec.c +++ b/src/interfaces/libpq/fe-exec.c @@ -28,6 +28,9 @@ #include "libpq-int.h" #include "mb/pg_wchar.h" +/* TODO: move OpenSSL-specific parts to separate file */ +#include + /* keep this in same order as ExecStatusType in libpq-fe.h */ char *const pgresStatus[] = { "PGRES_EMPTY_QUERY", @@ -65,7 +68,8 @@ static int PQsendQueryGuts(PGconn *conn, const char *const *paramValues, const int *paramLengths, const int *paramFormats, - int resultFormat); + int resultFormat, + PGresult *paramDesc); static void parseInput(PGconn *conn); static PGresult *getCopyResult(PGconn *conn, ExecStatusType copytype); static bool PQexecStart(PGconn *conn); @@ -1089,6 +1093,113 @@ pqSaveParameterStatus(PGconn *conn, const char *name, const char *value) } } +void +pqSaveColumnMasterKey(PGconn *conn, const char *name, const char *provider, + const char *optname1, const char *optval1) +{ + free(conn->cmkname); + conn->cmkname = strdup(name); + free(conn->cmkprovider); + conn->cmkprovider = strdup(provider); + free(conn->cmkoptname1); + conn->cmkoptname1 = strdup(optname1); + free(conn->cmkoptval1); + conn->cmkoptval1 = strdup(optval1); +} + +/* + * a different key provider would replace this function + */ +static unsigned char * +decrypt_cek_file(PGconn *conn, + int fromlen, const unsigned char *from, + int *tolen) +{ + RSA *rsa; + char *cmkfilename = conn->cmkoptval1; + FILE *fp; + unsigned char *to; + int rv; + + /* + * setup, load keys + */ + + if (!cmkfilename) + { + appendPQExpBuffer(&conn->errorMessage, + libpq_gettext("no CMK file set\n")); + goto fail; + } + rsa = RSA_new(); + fp = fopen(cmkfilename, "rb"); + if (!fp) + { + appendPQExpBuffer(&conn->errorMessage, + libpq_gettext("could not open file \"%s\": %m\n"), cmkfilename); + goto fail; + } + rsa = PEM_read_RSAPrivateKey(fp, &rsa, NULL, NULL); + if (!rsa) + { + appendPQExpBuffer(&conn->errorMessage, + libpq_gettext("PEM_read_RSAPrivateKey() failed: %s\n"), + ERR_reason_error_string(ERR_get_error())); + goto fail; + } + fclose(fp); + + /* + * decrypt + */ + + to = malloc(RSA_size(rsa)); + + rv = RSA_private_decrypt(fromlen, from, to, rsa, RSA_PKCS1_OAEP_PADDING); + if (rv < 0) + { + appendPQExpBuffer(&conn->errorMessage, + libpq_gettext("RSA_private_decrypt() failed: %s\n"), + ERR_reason_error_string(ERR_get_error())); + goto fail; + } + + RSA_free(rsa); + + *tolen = rv; + return to; + +fail: + return false; +} + +static unsigned char * +decrypt_cek(PGconn *conn, + int fromlen, const unsigned char *from, + int *tolen) +{ + if (strcmp(conn->cmkprovider, "file") == 0) + return decrypt_cek_file(conn, fromlen, from, tolen); + else + { + appendPQExpBuffer(&conn->errorMessage, + libpq_gettext("CMK provider \"%s\" not recognized\n"), conn->cmkprovider); + return NULL; + } +} + +void +pqSaveColumnEncryptionKey(PGconn *conn, const char *name, const unsigned char *value, int len) +{ + unsigned char *newval = NULL; + int newvallen = 0; + + newval = decrypt_cek(conn, len, value, &newvallen); + + free(conn->cekdata); + conn->cekdata = newval; + conn->cekdatalen = newvallen; +} /* * pqRowProcessor @@ -1153,9 +1264,103 @@ pqRowProcessor(PGconn *conn, const char **errmsgp) } else { - bool isbinary = (res->attDescs[i].format != 0); + bool isbinary = ((res->attDescs[i].format & 0x0F) != 0); + bool isencrypted = ((res->attDescs[i].format & 0xF0) != 0); char *val; + if (isencrypted) + { + unsigned char *v; + size_t l; + unsigned char *iv; + size_t ivlen; + + EVP_CIPHER_CTX *evp_ctx; + const EVP_CIPHER *cipher; + unsigned char *decr; + int decrlen, decrlen2; + + cipher = EVP_get_cipherbyname("AES-128-CBC"); + if (!cipher) + { + *errmsgp = libpq_gettext("EVP_get_cipherbyname() failed"); + goto fail; + } + + evp_ctx = EVP_CIPHER_CTX_new(); + + if (!isbinary) + { + unsigned char *x; + x = malloc(clen + 1); + memcpy(x, columns[i].value, clen); + x[clen] = '\0'; + v = PQunescapeBytea(x, &l); + } + else + { + v = unconstify(unsigned char *, (const unsigned char *) columns[i].value); + l = clen; + } + + if (!EVP_DecryptInit_ex(evp_ctx, cipher, NULL, NULL, NULL)) + { + static char errmsg[100]; + snprintf(errmsg, sizeof(errmsg), libpq_gettext("EVP_DecryptInit_ex() failed: %s"), + ERR_reason_error_string(ERR_get_error())); + *errmsgp = errmsg; + goto fail; + } + + if (conn->cekdatalen != EVP_CIPHER_CTX_key_length(evp_ctx)) + { + static char errmsg[100]; + snprintf(errmsg, sizeof(errmsg), libpq_gettext("column encryption key has wrong key length for cipher (has: %zu, required: %d)"), + conn->cekdatalen, EVP_CIPHER_CTX_key_length(evp_ctx)); + *errmsgp = errmsg; + goto fail; + } + + ivlen = EVP_CIPHER_CTX_iv_length(evp_ctx); + iv = v; + v += ivlen; + l -= ivlen; + if (!EVP_DecryptInit_ex(evp_ctx, NULL, NULL, conn->cekdata, iv)) + { + static char errmsg[100]; + snprintf(errmsg, sizeof(errmsg), libpq_gettext("EVP_DecryptInit_ex() failed: %s"), + ERR_reason_error_string(ERR_get_error())); + *errmsgp = errmsg; + goto fail; + } + + decr = malloc(l + EVP_CIPHER_CTX_block_size(evp_ctx) + 1); + if (!EVP_DecryptUpdate(evp_ctx, decr, &decrlen, v, l)) + { + static char errmsg[100]; + snprintf(errmsg, sizeof(errmsg), libpq_gettext("EVP_DecryptUpdate() failed: %s"), + ERR_reason_error_string(ERR_get_error())); + *errmsgp = errmsg; + goto fail; + } + if (!EVP_DecryptFinal_ex(evp_ctx, decr + decrlen, &decrlen2)) + { + static char errmsg[100]; + snprintf(errmsg, sizeof(errmsg), libpq_gettext("EVP_DecryptFinal_ex() failed: %s"), + ERR_reason_error_string(ERR_get_error())); + *errmsgp = errmsg; + goto fail; + } + decrlen += decrlen2; + decr[decrlen] = '\0'; + val = (char *) decr; + clen = decrlen + 1; + + EVP_CIPHER_CTX_free(evp_ctx); + free(iv); + } + else + { val = (char *) pqResultAlloc(res, clen + 1, isbinary); if (val == NULL) goto fail; @@ -1163,6 +1368,7 @@ pqRowProcessor(PGconn *conn, const char **errmsgp) /* copy and zero-terminate the data (even if it's binary) */ memcpy(val, columns[i].value, clen); val[clen] = '\0'; + } tup[i].len = clen; tup[i].value = val; @@ -1470,7 +1676,8 @@ PQsendQueryParams(PGconn *conn, paramValues, paramLengths, paramFormats, - resultFormat); + resultFormat, + NULL); } /* @@ -1588,6 +1795,19 @@ PQsendQueryPrepared(PGconn *conn, const int *paramLengths, const int *paramFormats, int resultFormat) +{ + return PQsendQueryPrepared2(conn, stmtName, nParams, paramValues, paramLengths, paramFormats, resultFormat, NULL); +} + +int +PQsendQueryPrepared2(PGconn *conn, + const char *stmtName, + int nParams, + const char *const *paramValues, + const int *paramLengths, + const int *paramFormats, + int resultFormat, + PGresult *paramDesc) { if (!PQsendQueryStart(conn, true)) return 0; @@ -1615,7 +1835,8 @@ PQsendQueryPrepared(PGconn *conn, paramValues, paramLengths, paramFormats, - resultFormat); + resultFormat, + paramDesc); } /* @@ -1712,7 +1933,8 @@ PQsendQueryGuts(PGconn *conn, const char *const *paramValues, const int *paramLengths, const int *paramFormats, - int resultFormat) + int resultFormat, + PGresult *paramDesc) { int i; PGcmdQueueEntry *entry; @@ -1760,13 +1982,24 @@ PQsendQueryGuts(PGconn *conn, goto sendFailed; /* Send parameter formats */ - if (nParams > 0 && paramFormats) + if (nParams > 0 && (paramFormats || (paramDesc && paramDesc->paramDescs))) { if (pqPutInt(nParams, 2, conn) < 0) goto sendFailed; + for (i = 0; i < nParams; i++) { - if (pqPutInt(paramFormats[i], 2, conn) < 0) + int format = paramFormats ? paramFormats[i] : 0; + + if (paramDesc && paramDesc->paramDescs) + { + PGresParamDesc *pd = ¶mDesc->paramDescs[i]; + + if (pd->format & 0x10) + format |= 0x10; + } + + if (pqPutInt(format, 2, conn) < 0) goto sendFailed; } } @@ -1785,6 +2018,7 @@ PQsendQueryGuts(PGconn *conn, if (paramValues && paramValues[i]) { int nbytes; + const char *paramValue; if (paramFormats && paramFormats[i] != 0) { @@ -1803,8 +2037,93 @@ PQsendQueryGuts(PGconn *conn, /* text parameter, do not use paramLengths */ nbytes = strlen(paramValues[i]); } + + paramValue = paramValues[i]; + + if (paramDesc && paramDesc->paramDescs && paramDesc->paramDescs[i].format & 0x10) + { + bool enc_det = (paramDesc->paramDescs[i].format & 0x20) != 0; + unsigned char *v; + unsigned char *iv; + size_t ivlen; + EVP_CIPHER_CTX *evp_ctx; + const EVP_CIPHER *cipher; + unsigned char *encr; + int encrlen, encrlen2; + unsigned char *esc; + size_t esclen; + + v = unconstify(unsigned char *, (const unsigned char *) paramValue); + + cipher = EVP_get_cipherbyname("AES-128-CBC"); + if (!cipher) + { + appendPQExpBuffer(&conn->errorMessage, + libpq_gettext("EVP_get_cipherbyname() failed\n")); + goto sendFailed; + } + + evp_ctx = EVP_CIPHER_CTX_new(); + + if (!EVP_EncryptInit_ex(evp_ctx, cipher, NULL, NULL, NULL)) + { + appendPQExpBuffer(&conn->errorMessage, + libpq_gettext("EVP_EncryptInit_ex() failed: %s\n"), + ERR_reason_error_string(ERR_get_error())); + goto sendFailed; + } + + if (conn->cekdatalen != EVP_CIPHER_CTX_key_length(evp_ctx)) + { + appendPQExpBuffer(&conn->errorMessage, + libpq_gettext("column encryption key has wrong key length for cipher (has: %zu, required: %d)"), + conn->cekdatalen, EVP_CIPHER_CTX_key_length(evp_ctx)); + goto sendFailed; + } + + ivlen = EVP_CIPHER_CTX_iv_length(evp_ctx); + iv = malloc(ivlen); + if (enc_det) + memset(iv, ivlen, 0); + else + pg_strong_random(iv, ivlen); + if (!EVP_EncryptInit_ex(evp_ctx, NULL, NULL, conn->cekdata, iv)) + { + appendPQExpBuffer(&conn->errorMessage, + libpq_gettext("EVP_EncryptInit_ex() failed: %s\n"), + ERR_reason_error_string(ERR_get_error())); + goto sendFailed; + } + + encr = malloc(ivlen + nbytes + EVP_CIPHER_CTX_block_size(evp_ctx) + 1); + memcpy(encr, iv, ivlen); + encr += ivlen; + if (!EVP_EncryptUpdate(evp_ctx, encr, &encrlen, v, nbytes)) + { + appendPQExpBuffer(&conn->errorMessage, + libpq_gettext("EVP_EncryptUpdate() failed: %s\n"), + ERR_reason_error_string(ERR_get_error())); + goto sendFailed; + } + if (!EVP_EncryptFinal_ex(evp_ctx, encr + encrlen, &encrlen2)) + { + appendPQExpBuffer(&conn->errorMessage, + libpq_gettext("EVP_EncryptFinal_ex() failed: %s\n"), + ERR_reason_error_string(ERR_get_error())); + goto sendFailed; + } + encrlen += encrlen2; + + EVP_CIPHER_CTX_free(evp_ctx); + free(iv); + + esc = PQescapeByteaConn(conn, encr - ivlen, encrlen + ivlen, &esclen); + nbytes = esclen - 1; + paramValue = (char *) esc; + } + if (pqPutInt(nbytes, 4, conn) < 0 || - pqPutnchar(paramValues[i], nbytes, conn) < 0) + pqPutnchar(paramValue, nbytes, conn) < 0) goto sendFailed; } else @@ -2258,12 +2577,25 @@ PQexecPrepared(PGconn *conn, const int *paramLengths, const int *paramFormats, int resultFormat) +{ + return PQexecPrepared2(conn, stmtName, nParams, paramValues, paramLengths, paramFormats, resultFormat, NULL); +} + +PGresult * +PQexecPrepared2(PGconn *conn, + const char *stmtName, + int nParams, + const char *const *paramValues, + const int *paramLengths, + const int *paramFormats, + int resultFormat, + PGresult *paramDesc) { if (!PQexecStart(conn)) return NULL; - if (!PQsendQueryPrepared(conn, stmtName, + if (!PQsendQueryPrepared2(conn, stmtName, nParams, paramValues, paramLengths, - paramFormats, resultFormat)) + paramFormats, resultFormat, paramDesc)) return NULL; return PQexecFinish(conn); } diff --git a/src/interfaces/libpq/fe-protocol3.c b/src/interfaces/libpq/fe-protocol3.c index 9ab3bf1fcb..c40550cdf6 100644 --- a/src/interfaces/libpq/fe-protocol3.c +++ b/src/interfaces/libpq/fe-protocol3.c @@ -45,6 +45,8 @@ static int getRowDescriptions(PGconn *conn, int msgLength); static int getParamDescriptions(PGconn *conn, int msgLength); static int getAnotherTuple(PGconn *conn, int msgLength); static int getParameterStatus(PGconn *conn); +static int getColumnMasterKey(PGconn *conn); +static int getColumnEncryptionKey(PGconn *conn); static int getNotify(PGconn *conn); static int getCopyStart(PGconn *conn, ExecStatusType copytype); static int getReadyForQuery(PGconn *conn); @@ -315,6 +317,14 @@ pqParseInput3(PGconn *conn) if (pqGetInt(&(conn->be_key), 4, conn)) return; break; + case 'y': /* Column Master Key */ + if (getColumnMasterKey(conn)) + return; + break; + case 'Y': /* Column Encryption Key */ + if (getColumnEncryptionKey(conn)) + return; + break; case 'T': /* Row Description */ if (conn->result != NULL && conn->result->resultStatus == PGRES_FATAL_ERROR) @@ -606,7 +616,7 @@ getRowDescriptions(PGconn *conn, int msgLength) result->attDescs[i].typlen = typlen; result->attDescs[i].atttypmod = atttypmod; - if (format != 1) + if ((format & 0x0F) != 1) result->binary = 0; } @@ -713,6 +723,14 @@ getParamDescriptions(PGconn *conn, int msgLength) goto not_enough_data; result->paramDescs[i].typid = typid; } + for (i = 0; i < nparams; i++) + { + int format; + + if (pqGetInt(&format, 2, conn)) + goto not_enough_data; + result->paramDescs[i].format = format; + } /* Success! */ conn->result = result; @@ -1429,6 +1447,69 @@ getParameterStatus(PGconn *conn) return 0; } +static int +getColumnMasterKey(PGconn *conn) +{ + char *keyname; + char *keyprov; + int noptions; + char *optname = NULL; + char *optval = NULL; + + /* Get the key name */ + if (pqGets(&conn->workBuffer, conn) != 0) + return EOF; + keyname = strdup(conn->workBuffer.data); + /* Get the key provider */ + if (pqGets(&conn->workBuffer, conn) != 0) + return EOF; + keyprov = strdup(conn->workBuffer.data); + /* Get the options */ + if (pqGetInt(&noptions, 2, conn) != 0) + return EOF; + for (int i = 0; i < noptions; i++) + { + if (pqGets(&conn->workBuffer, conn) != 0) + return EOF; + optname = strdup(conn->workBuffer.data); + if (pqGets(&conn->workBuffer, conn) != 0) + return EOF; + optval = strdup(conn->workBuffer.data); + } + /* And save it */ + pqSaveColumnMasterKey(conn, keyname, keyprov, optname, optval); + + free(keyname); + free(keyprov); + free(optname); + free(optval); + return 0; +} + +static int +getColumnEncryptionKey(PGconn *conn) +{ + char *buf; + int vallen; + + /* Get the key name */ + if (pqGets(&conn->workBuffer, conn) != 0) + return EOF; + /* Get the key data len */ + if (pqGetInt(&vallen, 4, conn) != 0) + return EOF; + /* Get the key data */ + buf = malloc(vallen); + if (pqGetnchar(buf, vallen, conn) != 0) + { + free(buf); + return EOF; + } + /* And save it */ + pqSaveColumnEncryptionKey(conn, conn->workBuffer.data, (unsigned char *) buf, vallen); + free(buf); + return 0; +} /* * Attempt to read a Notify response message. diff --git a/src/interfaces/libpq/fe-trace.c b/src/interfaces/libpq/fe-trace.c index 8660d27926..e99a5d15af 100644 --- a/src/interfaces/libpq/fe-trace.c +++ b/src/interfaces/libpq/fe-trace.c @@ -459,6 +459,8 @@ pqTraceOutputt(FILE *f, const char *message, int *cursor, bool regress) for (int i = 0; i < nfields; i++) pqTraceOutputInt32(f, message, cursor, regress); + for (int i = 0; i < nfields; i++) + pqTraceOutputInt16(f, message, cursor); } /* RowDescription */ diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h index a6fd69aceb..a6c8ec974a 100644 --- a/src/interfaces/libpq/libpq-fe.h +++ b/src/interfaces/libpq/libpq-fe.h @@ -436,6 +436,14 @@ extern PGresult *PQexecPrepared(PGconn *conn, const int *paramLengths, const int *paramFormats, int resultFormat); +extern PGresult *PQexecPrepared2(PGconn *conn, + const char *stmtName, + int nParams, + const char *const *paramValues, + const int *paramLengths, + const int *paramFormats, + int resultFormat, + PGresult *paramDesc); /* Interface for multiple-result or asynchronous queries */ #define PQ_QUERY_PARAM_MAX_LIMIT 65535 @@ -459,6 +467,14 @@ extern int PQsendQueryPrepared(PGconn *conn, const int *paramLengths, const int *paramFormats, int resultFormat); +extern int PQsendQueryPrepared2(PGconn *conn, + const char *stmtName, + int nParams, + const char *const *paramValues, + const int *paramLengths, + const int *paramFormats, + int resultFormat, + PGresult *paramDesc); extern int PQsetSingleRowMode(PGconn *conn); extern PGresult *PQgetResult(PGconn *conn); diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 490458adef..aa0f2eec2c 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -111,6 +111,7 @@ union pgresult_data typedef struct pgresParamDesc { Oid typid; /* type id */ + int format; /* encrypted parameter? */ } PGresParamDesc; /* @@ -475,6 +476,14 @@ struct pg_conn PGContextVisibility show_context; /* whether to show CONTEXT field */ PGlobjfuncs *lobjfuncs; /* private state for large-object access fns */ + /* Encryption stuff */ + char *cmkname; + char *cmkprovider; + char *cmkoptname1; /* XXX only one option right now */ + char *cmkoptval1; + unsigned char *cekdata; /* column encryption key (decrypted) */ + size_t cekdatalen; + /* Buffer for data received from backend and not yet processed */ char *inBuffer; /* currently allocated buffer */ int inBufSize; /* allocated size of buffer */ @@ -648,6 +657,10 @@ extern void pqSaveMessageField(PGresult *res, char code, const char *value); extern void pqSaveParameterStatus(PGconn *conn, const char *name, const char *value); +extern void pqSaveColumnMasterKey(PGconn *conn, const char *name, const char *provider, + const char *optname1, const char *optval1); +extern void pqSaveColumnEncryptionKey(PGconn *conn, const char *name, + const unsigned char *value, int len); extern int pqRowProcessor(PGconn *conn, const char **errmsgp); extern void pqCommandQueueAdvance(PGconn *conn); extern int PQsendQueryContinue(PGconn *conn, const char *query); diff --git a/src/test/Makefile b/src/test/Makefile index 46275915ff..e2c819b047 100644 --- a/src/test/Makefile +++ b/src/test/Makefile @@ -28,6 +28,7 @@ SUBDIRS += ldap endif endif ifeq ($(with_ssl),openssl) +SUBDIRS += column_encryption ifneq (,$(filter ssl,$(PG_TEST_EXTRA))) SUBDIRS += ssl endif @@ -37,7 +38,7 @@ endif # clean" etc to recurse into them. (We must filter out those that we # have conditionally included into SUBDIRS above, else there will be # make confusion.) -ALWAYS_SUBDIRS = $(filter-out $(SUBDIRS),examples kerberos ldap ssl) +ALWAYS_SUBDIRS = $(filter-out $(SUBDIRS),examples kerberos ldap ssl column_encryption) # We want to recurse to all subdirs for all standard targets, except that # installcheck and install should not recurse into the subdirectory "modules". diff --git a/src/test/column_encryption/.gitignore b/src/test/column_encryption/.gitignore new file mode 100644 index 0000000000..456dbf69d2 --- /dev/null +++ b/src/test/column_encryption/.gitignore @@ -0,0 +1,3 @@ +/test_client +# Generated by test suite +/tmp_check/ diff --git a/src/test/column_encryption/Makefile b/src/test/column_encryption/Makefile new file mode 100644 index 0000000000..1b9a5c72be --- /dev/null +++ b/src/test/column_encryption/Makefile @@ -0,0 +1,20 @@ +subdir = src/test/column_encryption +top_builddir = ../../.. +include $(top_builddir)/src/Makefile.global + +override CPPFLAGS += -I$(libpq_srcdir) +LDFLAGS_INTERNAL += -L$(top_builddir)/src/fe_utils -lpgfeutils $(libpq_pgport) + +all: test_client + +EXTRA_INSTALL = contrib/pgcrypto + +check: all + $(prove_check) + +installcheck: + $(prove_installcheck) + +clean distclean maintainer-clean: + rm -f test_client.o test_client + rm -rf tmp_check diff --git a/src/test/column_encryption/t/001_column_encryption.pl b/src/test/column_encryption/t/001_column_encryption.pl new file mode 100644 index 0000000000..b4eb7323b5 --- /dev/null +++ b/src/test/column_encryption/t/001_column_encryption.pl @@ -0,0 +1,135 @@ +use strict; +use warnings; +use PostgreSQL::Test::Cluster; +use PostgreSQL::Test::Utils; +use Test::More tests => 13; + +my $node = PostgreSQL::Test::Cluster->new('node'); +$node->init; +$node->start; + +$node->safe_psql('postgres', qq{CREATE EXTENSION pgcrypto}); + +my $cmkfilename = "${PostgreSQL::Test::Utils::tmp_check}/cmk1.pem"; + +# generate CMK +system_or_bail 'openssl', 'genrsa', '-out', $cmkfilename; + +# generate 16 random bytes for 128-bit key +system_or_bail 'openssl', 'rand', '-out', "${PostgreSQL::Test::Utils::tmp_check}/cek1.bin", 16; + +# encrypt CEK using CMK +system_or_bail 'openssl', 'rsautl', '-encrypt', '-oaep', + '-in', "${PostgreSQL::Test::Utils::tmp_check}/cek1.bin", + '-inkey', $cmkfilename, + '-out', "${PostgreSQL::Test::Utils::tmp_check}/cek1.bin.enc"; + +# XXX might as well capture stdout, not go through file +my $cekbin = slurp_file "${PostgreSQL::Test::Utils::tmp_check}/cek1.bin"; +my $cekhex = unpack('H*', $cekbin); +my $cekencbin = slurp_file "${PostgreSQL::Test::Utils::tmp_check}/cek1.bin.enc"; +my $cekenchex = unpack('H*', $cekencbin); + +# create CMK in database +$node->safe_psql('postgres', qq{ +INSERT INTO pg_colmasterkey (oid, cmkname, cmkprovider, cmkoptions) VALUES ( + pg_nextoid('pg_catalog.pg_colmasterkey', 'oid', 'pg_catalog.pg_colmasterkey_oid_index'), + 'cmk1', 'file', '{"filename", "$cmkfilename"}'::text[] +); +}); + +# create CEK in database +$node->safe_psql('postgres', qq{ +INSERT INTO pg_colenckey (oid, cekname) VALUES ( + pg_nextoid('pg_catalog.pg_colenckey', 'oid', 'pg_catalog.pg_colenckey_oid_index'), + 'cek1' +); +}); +$node->safe_psql('postgres', qq{ +INSERT INTO pg_colenckeydata (oid, ckdcekid, ckdcmkid, ckdcmkalg, ckdencval) VALUES ( + pg_nextoid('pg_catalog.pg_colenckeydata', 'oid', 'pg_catalog.pg_colenckeydata_oid_index'), + (SELECT oid FROM pg_colenckey WHERE cekname = 'cek1'), + (SELECT oid FROM pg_colmasterkey WHERE cmkname = 'cmk1'), + 'RSA-OAEP', + '\\x${cekenchex}' +); +}); + +$node->safe_psql('postgres', qq{ +CREATE TABLE tbl1 ( + a int, + b text ENCRYPTED WITH (column_encryption_key = cek1) +); +}); + +$node->safe_psql('postgres', qq{ +CREATE TABLE tbl2 ( + a int, + b text ENCRYPTED WITH (encryption_type = deterministic, column_encryption_key = cek1) +); +}); + +my $ivhex = '00' x 16; +$node->safe_psql('postgres', qq{ +INSERT INTO tbl1 (a, b) VALUES (1, ('\\x${ivhex}'::bytea || encrypt_iv('val1', '\\x${cekhex}', '\\x${ivhex}', 'aes'))::encryptedr); +INSERT INTO tbl1 (a, b) VALUES (2, ('\\x${ivhex}'::bytea || encrypt_iv('val2', '\\x${cekhex}', '\\x${ivhex}', 'aes'))::encryptedr); +}); + +my $result; + +$result = $node->safe_psql('postgres', q{SELECT a, b FROM tbl1 \gdesc}); +is($result, + q(a|integer +b|text), + 'query result description has original type'); + +$result = $node->safe_psql('postgres', q{SELECT a, b FROM tbl1}); +is($result, + q(1|val1 +2|val2), + 'decrypted query result'); + + +$node->command_fails_like(['test_client', 'test1'], qr/not encrypted/, 'test client fails because parameters not encrypted'); + +$result = $node->safe_psql('postgres', q{SELECT a, b FROM tbl1}); +is($result, + q(1|val1 +2|val2), + 'decrypted query result after test client insert'); + +$node->command_ok(['test_client', 'test2'], 'test client test 2'); + +$result = $node->safe_psql('postgres', q{SELECT a, b FROM tbl1}); +is($result, + q(1|val1 +2|val2 +3|val3), + 'decrypted query result after test client insert 2'); + +like($node->safe_psql('postgres', q{COPY (SELECT * FROM tbl1 WHERE a = 3) TO STDOUT}), + qr/3\t\\\\x[0-9a-z]{32}/, + 'inserted data is encrypted'); + +$node->command_ok(['test_client', 'test3'], 'test client test 3'); + +$result = $node->safe_psql('postgres', q{SELECT a, b FROM tbl1}); +is($result, + q(1|val1 +2|val2 +3|val3upd), + 'decrypted query result after test client insert 3'); + +$node->command_ok(['test_client', 'test4'], 'test client test 4'); + +$result = $node->safe_psql('postgres', q{SELECT a, b FROM tbl2}); +is($result, + q(1|valA +2|valB +3|valA), + 'decrypted query result after test client insert 4'); + +is($node->safe_psql('postgres', q{SELECT b, count(*) FROM tbl2 GROUP BY b ORDER BY 2}), + q(valB|1 +valA|2), + 'group by deterministically encrypted column'); diff --git a/src/test/column_encryption/test_client.c b/src/test/column_encryption/test_client.c new file mode 100644 index 0000000000..be37b51305 --- /dev/null +++ b/src/test/column_encryption/test_client.c @@ -0,0 +1,155 @@ +#include "postgres_fe.h" + +#include "libpq-fe.h" + + +static int +test1(PGconn *conn) +{ + PGresult *res; + const char *values[] = {"3", "val3"}; + + res = PQexecParams(conn, "INSERT INTO tbl1 (a, b) VALUES ($1, $2)", + 2, NULL, values, NULL, NULL, 0); + if (PQresultStatus(res) != PGRES_COMMAND_OK) + { + fprintf(stderr, "PQexecParams() failed: %s\n", + PQerrorMessage(conn)); + return 1; + } + + return 0; +} + +static int +test2(PGconn *conn) +{ + PGresult *res, *res2; + const char *values[] = {"3", "val3"}; + + res = PQprepare(conn, "", "INSERT INTO tbl1 (a, b) VALUES ($1, $2)", + 2, NULL); + if (PQresultStatus(res) != PGRES_COMMAND_OK) + { + fprintf(stderr, "PQprepare() failed: %s\n", + PQerrorMessage(conn)); + return 1; + } + + res2 = PQdescribePrepared(conn, ""); + if (PQresultStatus(res2) != PGRES_COMMAND_OK) + { + fprintf(stderr, "PQdescribePrepared() failed: %s\n", + PQerrorMessage(conn)); + return 1; + } + + res = PQexecPrepared2(conn, "", 2, values, NULL, NULL, 0, res2); + if (PQresultStatus(res) != PGRES_COMMAND_OK) + { + fprintf(stderr, "PQexecPrepared() failed: %s\n", + PQerrorMessage(conn)); + return 1; + } + + return 0; +} + +static int +test3(PGconn *conn) +{ + PGresult *res, *res2; + const char *values[] = {"3", "val3upd"}; + + res = PQprepare(conn, "", "UPDATE tbl1 SET b = $2 WHERE a = $1", + 2, NULL); + if (PQresultStatus(res) != PGRES_COMMAND_OK) + { + fprintf(stderr, "PQprepare() failed: %s\n", + PQerrorMessage(conn)); + return 1; + } + + res2 = PQdescribePrepared(conn, ""); + if (PQresultStatus(res2) != PGRES_COMMAND_OK) + { + fprintf(stderr, "PQdescribePrepared() failed: %s\n", + PQerrorMessage(conn)); + return 1; + } + + res = PQexecPrepared2(conn, "", 2, values, NULL, NULL, 0, res2); + if (PQresultStatus(res) != PGRES_COMMAND_OK) + { + fprintf(stderr, "PQexecPrepared() failed: %s\n", + PQerrorMessage(conn)); + return 1; + } + + return 0; +} + +static int +test4(PGconn *conn) +{ + PGresult *res, *res2; + const char *values[] = {"1", "valA", "2", "valB", "3", "valA"}; + + res = PQprepare(conn, "", "INSERT INTO tbl2 (a, b) VALUES ($1, $2), ($3, $4), ($5, $6)", + 6, NULL); + if (PQresultStatus(res) != PGRES_COMMAND_OK) + { + fprintf(stderr, "PQprepare() failed: %s\n", + PQerrorMessage(conn)); + return 1; + } + + res2 = PQdescribePrepared(conn, ""); + if (PQresultStatus(res2) != PGRES_COMMAND_OK) + { + fprintf(stderr, "PQdescribePrepared() failed: %s\n", + PQerrorMessage(conn)); + return 1; + } + + res = PQexecPrepared2(conn, "", 6, values, NULL, NULL, 0, res2); + if (PQresultStatus(res) != PGRES_COMMAND_OK) + { + fprintf(stderr, "PQexecPrepared() failed: %s\n", + PQerrorMessage(conn)); + return 1; + } + + return 0; +} + +int +main(int argc, char **argv) +{ + PGconn *conn; + int ret = 0; + + conn = PQconnectdb(""); + if (PQstatus(conn) != CONNECTION_OK) + { + fprintf(stderr, "Connection to database failed: %s\n", + PQerrorMessage(conn)); + return 1; + } + + if (argc < 2 || argv[1] == NULL) + return 87; + else if (strcmp(argv[1], "test1") == 0) + ret = test1(conn); + else if (strcmp(argv[1], "test2") == 0) + ret = test2(conn); + else if (strcmp(argv[1], "test3") == 0) + ret = test3(conn); + else if (strcmp(argv[1], "test4") == 0) + ret = test4(conn); + else + ret = 88; + + PQfinish(conn); + return ret; +} diff --git a/src/test/modules/libpq_pipeline/traces/prepared.trace b/src/test/modules/libpq_pipeline/traces/prepared.trace index 1a7de5c3e6..ce84f1955b 100644 --- a/src/test/modules/libpq_pipeline/traces/prepared.trace +++ b/src/test/modules/libpq_pipeline/traces/prepared.trace @@ -2,7 +2,7 @@ F 68 Parse "select_one" "SELECT $1, '42', $1::numeric, interval '1 sec'" 1 NNNN F 16 Describe S "select_one" F 4 Sync B 4 ParseComplete -B 10 ParameterDescription 1 NNNN +B 12 ParameterDescription 1 NNNN 0 B 113 RowDescription 4 "?column?" NNNN 0 NNNN 4 -1 0 "?column?" NNNN 0 NNNN 65535 -1 0 "numeric" NNNN 0 NNNN 65535 -1 0 "interval" NNNN 0 NNNN 16 -1 0 B 5 ReadyForQuery I F 10 Query "BEGIN" diff --git a/src/test/regress/expected/column_encryption.out b/src/test/regress/expected/column_encryption.out new file mode 100644 index 0000000000..1fec9f8903 --- /dev/null +++ b/src/test/regress/expected/column_encryption.out @@ -0,0 +1,47 @@ +/* Imagine: +CREATE COLUMN MASTER KEY cmk1 ( + provider = 'test' +); +*/ +INSERT INTO pg_colmasterkey (oid, cmkname, cmkprovider, cmkoptions) VALUES ( + pg_nextoid('pg_catalog.pg_colmasterkey', 'oid', 'pg_catalog.pg_colmasterkey_oid_index'), + 'cmk1', 'test', '{}'::text[] +); +/* Imagine: +CREATE COLUMN ENCRYPTION KEY cek1 ( + column_master_key = cmk1, + algorithm = '...', + encrypted_value = '...' +); +*/ +INSERT INTO pg_colenckey (oid, cekname) VALUES ( + pg_nextoid('pg_catalog.pg_colenckey', 'oid', 'pg_catalog.pg_colenckey_oid_index'), + 'cek1' +); +INSERT INTO pg_colenckeydata (oid, ckdcekid, ckdcmkid, ckdcmkalg, ckdencval) VALUES ( + pg_nextoid('pg_catalog.pg_colenckeydata', 'oid', 'pg_catalog.pg_colenckeydata_oid_index'), + (SELECT oid FROM pg_colenckey WHERE cekname = 'cek1'), + (SELECT oid FROM pg_colmasterkey WHERE cmkname = 'cmk1'), + '2ROT13', + '\xDEADBEEF' +); +CREATE TABLE tbl_fail ( + a int, + b text, + c text ENCRYPTED WITH (column_encryption_key = notexist) +); +ERROR: column encryption key "notexist" does not exist +CREATE TABLE tbl_29f3 ( + a int, + b text, + c text ENCRYPTED WITH (column_encryption_key = cek1) +); +\d tbl_29f3 + Table "public.tbl_29f3" + Column | Type | Collation | Nullable | Default +--------+------------+-----------+----------+--------- + a | integer | | | + b | text | | | + c | encryptedr | default | | + +DROP TABLE tbl_29f3; -- FIXME: needs pg_dump support for pg_upgrade tests diff --git a/src/test/regress/expected/oidjoins.out b/src/test/regress/expected/oidjoins.out index 215eb899be..ab6405a6c3 100644 --- a/src/test/regress/expected/oidjoins.out +++ b/src/test/regress/expected/oidjoins.out @@ -73,6 +73,8 @@ NOTICE: checking pg_type {typbasetype} => pg_type {oid} NOTICE: checking pg_type {typcollation} => pg_collation {oid} NOTICE: checking pg_attribute {attrelid} => pg_class {oid} NOTICE: checking pg_attribute {atttypid} => pg_type {oid} +NOTICE: checking pg_attribute {attcek} => pg_colenckey {oid} +NOTICE: checking pg_attribute {attrealtypid} => pg_type {oid} NOTICE: checking pg_attribute {attcollation} => pg_collation {oid} NOTICE: checking pg_class {relnamespace} => pg_namespace {oid} NOTICE: checking pg_class {reltype} => pg_type {oid} @@ -266,3 +268,5 @@ NOTICE: checking pg_subscription {subdbid} => pg_database {oid} NOTICE: checking pg_subscription {subowner} => pg_authid {oid} NOTICE: checking pg_subscription_rel {srsubid} => pg_subscription {oid} NOTICE: checking pg_subscription_rel {srrelid} => pg_class {oid} +NOTICE: checking pg_colenckeydata {ckdcekid} => pg_colenckey {oid} +NOTICE: checking pg_colenckeydata {ckdcmkid} => pg_colmasterkey {oid} diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out index 562b586d8e..fc2a07971d 100644 --- a/src/test/regress/expected/opr_sanity.out +++ b/src/test/regress/expected/opr_sanity.out @@ -175,13 +175,14 @@ WHERE p1.oid != p2.oid AND ORDER BY 1, 2; proargtypes | proargtypes -----------------------------+-------------------------- + bytea | encryptedd bigint | xid8 text | character text | character varying timestamp without time zone | timestamp with time zone bit | bit varying txid_snapshot | pg_snapshot -(6 rows) +(7 rows) SELECT DISTINCT p1.proargtypes[1]::regtype, p2.proargtypes[1]::regtype FROM pg_proc AS p1, pg_proc AS p2 @@ -197,11 +198,12 @@ WHERE p1.oid != p2.oid AND ORDER BY 1, 2; proargtypes | proargtypes -----------------------------+-------------------------- + bytea | encryptedd integer | xid timestamp without time zone | timestamp with time zone bit | bit varying txid_snapshot | pg_snapshot -(4 rows) +(5 rows) SELECT DISTINCT p1.proargtypes[2]::regtype, p2.proargtypes[2]::regtype FROM pg_proc AS p1, pg_proc AS p2 @@ -840,6 +842,8 @@ xid8ge(xid8,xid8) xid8eq(xid8,xid8) xid8ne(xid8,xid8) xid8cmp(xid8,xid8) +encrypteddeq(encryptedd,encryptedd) +encrypteddne(encryptedd,encryptedd) -- restore normal output mode \a\t -- List of functions used by libpq's fe-lobj.c @@ -987,7 +991,9 @@ WHERE c.castmethod = 'b' AND xml | text | 0 | a xml | character varying | 0 | a xml | character | 0 | a -(10 rows) + bytea | encryptedd | 0 | e + bytea | encryptedr | 0 | e +(12 rows) -- **************** pg_conversion **************** -- Look for illegal values in pg_conversion fields. diff --git a/src/test/regress/expected/prepare.out b/src/test/regress/expected/prepare.out index 3306c696b1..675556c7d1 100644 --- a/src/test/regress/expected/prepare.out +++ b/src/test/regress/expected/prepare.out @@ -159,25 +159,30 @@ PREPARE q6 AS SELECT * FROM tenk1 WHERE unique1 = $1 AND stringu1 = $2; PREPARE q7(unknown) AS SELECT * FROM road WHERE thepath = $1; -SELECT name, statement, parameter_types FROM pg_prepared_statements +-- DML statements +PREPARE q8 AS + UPDATE tenk1 SET stringu1 = $2 WHERE unique1 = $1; +SELECT name, statement, parameter_types, parameter_orig_tables, parameter_orig_columns FROM pg_prepared_statements ORDER BY name; - name | statement | parameter_types -------+------------------------------------------------------------------+---------------------------------------------------- - q2 | PREPARE q2(text) AS +| {text} - | SELECT datname, datistemplate, datallowconn +| - | FROM pg_database WHERE datname = $1; | - q3 | PREPARE q3(text, int, float, boolean, smallint) AS +| {text,integer,"double precision",boolean,smallint} - | SELECT * FROM tenk1 WHERE string4 = $1 AND (four = $2 OR+| - | ten = $3::bigint OR true = $4 OR odd = $5::int) +| - | ORDER BY unique1; | - q5 | PREPARE q5(int, text) AS +| {integer,text} - | SELECT * FROM tenk1 WHERE unique1 = $1 OR stringu1 = $2 +| - | ORDER BY unique1; | - q6 | PREPARE q6 AS +| {integer,name} - | SELECT * FROM tenk1 WHERE unique1 = $1 AND stringu1 = $2; | - q7 | PREPARE q7(unknown) AS +| {path} - | SELECT * FROM road WHERE thepath = $1; | -(5 rows) + name | statement | parameter_types | parameter_orig_tables | parameter_orig_columns +------+------------------------------------------------------------------+----------------------------------------------------+-----------------------+------------------------ + q2 | PREPARE q2(text) AS +| {text} | {-} | {0} + | SELECT datname, datistemplate, datallowconn +| | | + | FROM pg_database WHERE datname = $1; | | | + q3 | PREPARE q3(text, int, float, boolean, smallint) AS +| {text,integer,"double precision",boolean,smallint} | {-,-,-,-,-} | {0,0,0,0,0} + | SELECT * FROM tenk1 WHERE string4 = $1 AND (four = $2 OR+| | | + | ten = $3::bigint OR true = $4 OR odd = $5::int) +| | | + | ORDER BY unique1; | | | + q5 | PREPARE q5(int, text) AS +| {integer,text} | {-,-} | {0,0} + | SELECT * FROM tenk1 WHERE unique1 = $1 OR stringu1 = $2 +| | | + | ORDER BY unique1; | | | + q6 | PREPARE q6 AS +| {integer,name} | {-,-} | {0,0} + | SELECT * FROM tenk1 WHERE unique1 = $1 AND stringu1 = $2; | | | + q7 | PREPARE q7(unknown) AS +| {path} | {-} | {0} + | SELECT * FROM road WHERE thepath = $1; | | | + q8 | PREPARE q8 AS +| {integer,name} | {-,tenk1} | {0,14} + | UPDATE tenk1 SET stringu1 = $2 WHERE unique1 = $1; | | | +(6 rows) -- test DEALLOCATE ALL; DEALLOCATE ALL; diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index b58b062b10..7883baf4ab 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1441,8 +1441,10 @@ pg_prepared_statements| SELECT p.name, p.parameter_types, p.from_sql, p.generic_plans, - p.custom_plans - FROM pg_prepared_statement() p(name, statement, prepare_time, parameter_types, from_sql, generic_plans, custom_plans); + p.custom_plans, + p.parameter_orig_tables, + p.parameter_orig_columns + FROM pg_prepared_statement() p(name, statement, prepare_time, parameter_types, from_sql, generic_plans, custom_plans, parameter_orig_tables, parameter_orig_columns); pg_prepared_xacts| SELECT p.transaction, p.gid, p.prepared, diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out index 63706a28cc..67a0fcc9a3 100644 --- a/src/test/regress/expected/sanity_check.out +++ b/src/test/regress/expected/sanity_check.out @@ -112,7 +112,10 @@ pg_auth_members|t pg_authid|t pg_cast|t pg_class|t +pg_colenckey|t +pg_colenckeydata|t pg_collation|t +pg_colmasterkey|t pg_constraint|t pg_conversion|t pg_database|t diff --git a/src/test/regress/expected/type_sanity.out b/src/test/regress/expected/type_sanity.out index 257b6cac12..403e9f5c1e 100644 --- a/src/test/regress/expected/type_sanity.out +++ b/src/test/regress/expected/type_sanity.out @@ -17,7 +17,7 @@ SELECT p1.oid, p1.typname FROM pg_type as p1 WHERE p1.typnamespace = 0 OR (p1.typlen <= 0 AND p1.typlen != -1 AND p1.typlen != -2) OR - (p1.typtype not in ('b', 'c', 'd', 'e', 'p', 'r', 'm')) OR + (p1.typtype not in ('b', 'c', 'd', 'e', 'm', 'p', 'r', 'y')) OR NOT p1.typisdefined OR (p1.typalign not in ('c', 's', 'i', 'd')) OR (p1.typstorage not in ('p', 'x', 'e', 'm')); @@ -75,7 +75,9 @@ ORDER BY p1.oid; 4600 | pg_brin_bloom_summary 4601 | pg_brin_minmax_multi_summary 5017 | pg_mcv_list -(6 rows) + 8243 | encryptedd + 8244 | encryptedr +(8 rows) -- Make sure typarray points to a "true" array type of our own base SELECT p1.oid, p1.typname as basetype, p2.typname as arraytype, @@ -210,7 +212,8 @@ ORDER BY 1; e | enum_in m | multirange_in r | range_in -(5 rows) + y | byteain +(6 rows) -- Check for bogus typoutput routines -- As of 8.0, this check finds refcursor, which is borrowing @@ -255,7 +258,8 @@ ORDER BY 1; e | enum_out m | multirange_out r | range_out -(4 rows) + y | byteaout +(5 rows) -- Domains should have same typoutput as their base types SELECT p1.oid, p1.typname, p2.oid, p2.typname @@ -335,7 +339,8 @@ ORDER BY 1; e | enum_recv m | multirange_recv r | range_recv -(5 rows) + y | bytearecv +(6 rows) -- Check for bogus typsend routines -- As of 7.4, this check finds refcursor, which is borrowing @@ -380,7 +385,8 @@ ORDER BY 1; e | enum_send m | multirange_send r | range_send -(4 rows) + y | byteasend +(5 rows) -- Domains should have same typsend as their base types SELECT p1.oid, p1.typname, p2.oid, p2.typname @@ -707,6 +713,8 @@ CREATE TABLE tab_core_types AS SELECT 'txt'::text, true::bool, E'\\xDEADBEEF'::bytea, + E'\\xDEADBEEF'::encryptedr, + E'\\xDEADBEEF'::encryptedd, B'10001'::bit, B'10001'::varbit AS varbit, '12.34'::money, diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule index 017e962fed..cfc7c4e69a 100644 --- a/src/test/regress/parallel_schedule +++ b/src/test/regress/parallel_schedule @@ -123,6 +123,9 @@ test: plancache limit plpgsql copy2 temp domain rangefuncs prepare conversion tr # ---------- test: partition_join partition_prune reloptions hash_part indexing partition_aggregate partition_info tuplesort explain compression memoize +# WIP +test: column_encryption + # event triggers cannot run concurrently with any test that runs DDL # oidjoins is read-only, though, and should run late for best coverage test: event_trigger oidjoins diff --git a/src/test/regress/sql/column_encryption.sql b/src/test/regress/sql/column_encryption.sql new file mode 100644 index 0000000000..745d6c2ede --- /dev/null +++ b/src/test/regress/sql/column_encryption.sql @@ -0,0 +1,44 @@ +/* Imagine: +CREATE COLUMN MASTER KEY cmk1 ( + provider = 'test' +); +*/ +INSERT INTO pg_colmasterkey (oid, cmkname, cmkprovider, cmkoptions) VALUES ( + pg_nextoid('pg_catalog.pg_colmasterkey', 'oid', 'pg_catalog.pg_colmasterkey_oid_index'), + 'cmk1', 'test', '{}'::text[] +); + +/* Imagine: +CREATE COLUMN ENCRYPTION KEY cek1 ( + column_master_key = cmk1, + algorithm = '...', + encrypted_value = '...' +); +*/ +INSERT INTO pg_colenckey (oid, cekname) VALUES ( + pg_nextoid('pg_catalog.pg_colenckey', 'oid', 'pg_catalog.pg_colenckey_oid_index'), + 'cek1' +); +INSERT INTO pg_colenckeydata (oid, ckdcekid, ckdcmkid, ckdcmkalg, ckdencval) VALUES ( + pg_nextoid('pg_catalog.pg_colenckeydata', 'oid', 'pg_catalog.pg_colenckeydata_oid_index'), + (SELECT oid FROM pg_colenckey WHERE cekname = 'cek1'), + (SELECT oid FROM pg_colmasterkey WHERE cmkname = 'cmk1'), + '2ROT13', + '\xDEADBEEF' +); + +CREATE TABLE tbl_fail ( + a int, + b text, + c text ENCRYPTED WITH (column_encryption_key = notexist) +); + +CREATE TABLE tbl_29f3 ( + a int, + b text, + c text ENCRYPTED WITH (column_encryption_key = cek1) +); + +\d tbl_29f3 + +DROP TABLE tbl_29f3; -- FIXME: needs pg_dump support for pg_upgrade tests diff --git a/src/test/regress/sql/prepare.sql b/src/test/regress/sql/prepare.sql index 985d0f05c9..b2aa96d370 100644 --- a/src/test/regress/sql/prepare.sql +++ b/src/test/regress/sql/prepare.sql @@ -71,7 +71,11 @@ CREATE TEMPORARY TABLE q5_prep_nodata AS EXECUTE q5(200, 'DTAAAA') PREPARE q7(unknown) AS SELECT * FROM road WHERE thepath = $1; -SELECT name, statement, parameter_types FROM pg_prepared_statements +-- DML statements +PREPARE q8 AS + UPDATE tenk1 SET stringu1 = $2 WHERE unique1 = $1; + +SELECT name, statement, parameter_types, parameter_orig_tables, parameter_orig_columns FROM pg_prepared_statements ORDER BY name; -- test DEALLOCATE ALL; diff --git a/src/test/regress/sql/type_sanity.sql b/src/test/regress/sql/type_sanity.sql index 8281076423..f31f9da139 100644 --- a/src/test/regress/sql/type_sanity.sql +++ b/src/test/regress/sql/type_sanity.sql @@ -20,7 +20,7 @@ FROM pg_type as p1 WHERE p1.typnamespace = 0 OR (p1.typlen <= 0 AND p1.typlen != -1 AND p1.typlen != -2) OR - (p1.typtype not in ('b', 'c', 'd', 'e', 'p', 'r', 'm')) OR + (p1.typtype not in ('b', 'c', 'd', 'e', 'm', 'p', 'r', 'y')) OR NOT p1.typisdefined OR (p1.typalign not in ('c', 's', 'i', 'd')) OR (p1.typstorage not in ('p', 'x', 'e', 'm')); @@ -529,6 +529,8 @@ CREATE TABLE tab_core_types AS SELECT 'txt'::text, true::bool, E'\\xDEADBEEF'::bytea, + E'\\xDEADBEEF'::encryptedr, + E'\\xDEADBEEF'::encryptedd, B'10001'::bit, B'10001'::varbit AS varbit, '12.34'::money, base-commit: 49422ad0cc88c91a38522b2a7b222c2f2c939f82 -- 2.34.1