public inbox for [email protected]  
help / color / mirror / Atom feed
[PATCH] WIP Support libnss for as TLS backend v7
5+ messages / 4 participants
[nested] [flat]

* [PATCH] WIP Support libnss for as TLS backend v7
@ 2020-08-03 16:32 Andrew Dunstan <[email protected]>
  0 siblings, 0 replies; 5+ messages in thread

From: Andrew Dunstan @ 2020-08-03 16:32 UTC (permalink / raw)

---
 configure                                     |  211 ++++
 configure.ac                                  |   30 +
 contrib/Makefile                              |    2 +-
 .../postgres_fdw/expected/postgres_fdw.out    |    2 +-
 contrib/sslinfo/sslinfo.c                     |  164 ++-
 doc/src/sgml/sslinfo.sgml                     |   14 +-
 src/Makefile.global.in                        |   10 +
 src/backend/libpq/Makefile                    |    4 +
 src/backend/libpq/auth.c                      |    7 +
 src/backend/libpq/be-secure-nss.c             | 1032 +++++++++++++++++
 src/backend/libpq/be-secure-openssl.c         |   16 +-
 src/backend/libpq/be-secure.c                 |    3 +
 src/backend/utils/misc/guc.c                  |   20 +-
 src/include/common/pg_nss.h                   |  141 +++
 src/include/libpq/libpq-be.h                  |    9 +-
 src/include/libpq/libpq.h                     |    3 +
 src/include/pg_config.h.in                    |    3 +
 src/include/pg_config_manual.h                |    5 +-
 src/interfaces/libpq/Makefile                 |    4 +
 src/interfaces/libpq/fe-connect.c             |    4 +
 src/interfaces/libpq/fe-secure-nss.c          |  975 ++++++++++++++++
 src/interfaces/libpq/fe-secure.c              |    5 +-
 src/interfaces/libpq/libpq-fe.h               |   11 +
 src/interfaces/libpq/libpq-int.h              |    5 +
 src/test/Makefile                             |    2 +-
 src/test/ssl/Makefile                         |  172 +++
 .../cert9.db                                  |  Bin 0 -> 36864 bytes
 .../key4.db                                   |  Bin 0 -> 45056 bytes
 .../pkcs11.txt                                |    5 +
 src/test/ssl/ssl/nss/client-encrypted-pem.pfx |  Bin 0 -> 3149 bytes
 .../cert9.db                                  |  Bin 0 -> 28672 bytes
 .../key4.db                                   |  Bin 0 -> 45056 bytes
 .../pkcs11.txt                                |    5 +
 src/test/ssl/ssl/nss/client-revoked.pfx       |  Bin 0 -> 3149 bytes
 src/test/ssl/ssl/nss/client.crl               |  Bin 0 -> 418 bytes
 ...ient.crt__client-encrypted-pem.key.db.pass |    1 +
 .../cert9.db                                  |  Bin 0 -> 36864 bytes
 .../key4.db                                   |  Bin 0 -> 45056 bytes
 .../pkcs11.txt                                |    5 +
 .../nss/client.crt__client.key.db/cert9.db    |  Bin 0 -> 36864 bytes
 .../ssl/nss/client.crt__client.key.db/key4.db |  Bin 0 -> 45056 bytes
 .../nss/client.crt__client.key.db/pkcs11.txt  |    5 +
 src/test/ssl/ssl/nss/client.pfx               |  Bin 0 -> 3149 bytes
 .../ssl/ssl/nss/client_ca.crt.db/cert9.db     |  Bin 0 -> 28672 bytes
 src/test/ssl/ssl/nss/client_ca.crt.db/key4.db |  Bin 0 -> 36864 bytes
 .../ssl/ssl/nss/client_ca.crt.db/pkcs11.txt   |    5 +
 src/test/ssl/ssl/nss/root+client.crl          |  Bin 0 -> 393 bytes
 .../ssl/nss/root+client_ca.crt.db/cert9.db    |  Bin 0 -> 28672 bytes
 .../ssl/ssl/nss/root+client_ca.crt.db/key4.db |  Bin 0 -> 36864 bytes
 .../ssl/nss/root+client_ca.crt.db/pkcs11.txt  |    5 +
 .../ssl/nss/root+server_ca.crt.db/cert9.db    |  Bin 0 -> 28672 bytes
 .../ssl/ssl/nss/root+server_ca.crt.db/key4.db |  Bin 0 -> 36864 bytes
 .../ssl/nss/root+server_ca.crt.db/pkcs11.txt  |    5 +
 .../cert9.db                                  |  Bin 0 -> 28672 bytes
 .../key4.db                                   |  Bin 0 -> 36864 bytes
 .../pkcs11.txt                                |    5 +
 .../cert9.db                                  |  Bin 0 -> 28672 bytes
 .../root+server_ca.crt__server.crl.db/key4.db |  Bin 0 -> 36864 bytes
 .../pkcs11.txt                                |    5 +
 src/test/ssl/ssl/nss/root.crl                 |  Bin 0 -> 393 bytes
 .../cert9.db                                  |  Bin 0 -> 36864 bytes
 .../key4.db                                   |  Bin 0 -> 45056 bytes
 .../pkcs11.txt                                |    5 +
 .../ssl/ssl/nss/server-cn-and-alt-names.pfx   |  Bin 0 -> 3349 bytes
 .../cert9.db                                  |  Bin 0 -> 28672 bytes
 .../key4.db                                   |  Bin 0 -> 45056 bytes
 .../pkcs11.txt                                |    5 +
 .../cert9.db                                  |  Bin 0 -> 36864 bytes
 .../key4.db                                   |  Bin 0 -> 45056 bytes
 .../pkcs11.txt                                |    5 +
 src/test/ssl/ssl/nss/server-cn-only.pfx       |  Bin 0 -> 3197 bytes
 .../cert9.db                                  |  Bin 0 -> 36864 bytes
 .../key4.db                                   |  Bin 0 -> 45056 bytes
 .../pkcs11.txt                                |    5 +
 .../ssl/ssl/nss/server-multiple-alt-names.pfx |  Bin 0 -> 3325 bytes
 .../cert9.db                                  |  Bin 0 -> 36864 bytes
 .../key4.db                                   |  Bin 0 -> 45056 bytes
 .../pkcs11.txt                                |    5 +
 src/test/ssl/ssl/nss/server-no-names.pfx      |  Bin 0 -> 3109 bytes
 src/test/ssl/ssl/nss/server-password.pfx      |  Bin 0 -> 3197 bytes
 .../cert9.db                                  |  Bin 0 -> 36864 bytes
 .../key4.db                                   |  Bin 0 -> 45056 bytes
 .../pkcs11.txt                                |    5 +
 src/test/ssl/ssl/nss/server-revoked.pfx       |  Bin 0 -> 3181 bytes
 .../cert9.db                                  |  Bin 0 -> 36864 bytes
 .../key4.db                                   |  Bin 0 -> 45056 bytes
 .../pkcs11.txt                                |    5 +
 .../ssl/ssl/nss/server-single-alt-name.pfx    |  Bin 0 -> 3213 bytes
 src/test/ssl/ssl/nss/server.crl               |  Bin 0 -> 418 bytes
 .../ssl/ssl/nss/server_ca.crt.db/cert9.db     |  Bin 0 -> 28672 bytes
 src/test/ssl/ssl/nss/server_ca.crt.db/key4.db |  Bin 0 -> 36864 bytes
 .../ssl/ssl/nss/server_ca.crt.db/pkcs11.txt   |    5 +
 src/test/ssl/t/001_ssltests.pl                |  289 ++---
 src/test/ssl/t/002_scram.pl                   |    4 +-
 src/test/ssl/t/SSL/Backend/NSS.pm             |   64 +
 src/test/ssl/t/SSL/Backend/OpenSSL.pm         |  103 ++
 .../ssl/t/{SSLServer.pm => SSL/Server.pm}     |   80 +-
 src/tools/msvc/Install.pm                     |    3 +-
 src/tools/msvc/Mkvcbuild.pm                   |   29 +-
 src/tools/msvc/Solution.pm                    |   20 +
 src/tools/msvc/config_default.pl              |    1 +
 101 files changed, 3276 insertions(+), 257 deletions(-)
 create mode 100644 src/backend/libpq/be-secure-nss.c
 create mode 100644 src/include/common/pg_nss.h
 create mode 100644 src/interfaces/libpq/fe-secure-nss.c
 create mode 100644 src/test/ssl/ssl/nss/client+client_ca.crt__client.key.db/cert9.db
 create mode 100644 src/test/ssl/ssl/nss/client+client_ca.crt__client.key.db/key4.db
 create mode 100644 src/test/ssl/ssl/nss/client+client_ca.crt__client.key.db/pkcs11.txt
 create mode 100644 src/test/ssl/ssl/nss/client-encrypted-pem.pfx
 create mode 100644 src/test/ssl/ssl/nss/client-revoked.crt__client-revoked.key.db/cert9.db
 create mode 100644 src/test/ssl/ssl/nss/client-revoked.crt__client-revoked.key.db/key4.db
 create mode 100644 src/test/ssl/ssl/nss/client-revoked.crt__client-revoked.key.db/pkcs11.txt
 create mode 100644 src/test/ssl/ssl/nss/client-revoked.pfx
 create mode 100644 src/test/ssl/ssl/nss/client.crl
 create mode 100644 src/test/ssl/ssl/nss/client.crt__client-encrypted-pem.key.db.pass
 create mode 100644 src/test/ssl/ssl/nss/client.crt__client-encrypted-pem.key.db/cert9.db
 create mode 100644 src/test/ssl/ssl/nss/client.crt__client-encrypted-pem.key.db/key4.db
 create mode 100644 src/test/ssl/ssl/nss/client.crt__client-encrypted-pem.key.db/pkcs11.txt
 create mode 100644 src/test/ssl/ssl/nss/client.crt__client.key.db/cert9.db
 create mode 100644 src/test/ssl/ssl/nss/client.crt__client.key.db/key4.db
 create mode 100644 src/test/ssl/ssl/nss/client.crt__client.key.db/pkcs11.txt
 create mode 100644 src/test/ssl/ssl/nss/client.pfx
 create mode 100644 src/test/ssl/ssl/nss/client_ca.crt.db/cert9.db
 create mode 100644 src/test/ssl/ssl/nss/client_ca.crt.db/key4.db
 create mode 100644 src/test/ssl/ssl/nss/client_ca.crt.db/pkcs11.txt
 create mode 100644 src/test/ssl/ssl/nss/root+client.crl
 create mode 100644 src/test/ssl/ssl/nss/root+client_ca.crt.db/cert9.db
 create mode 100644 src/test/ssl/ssl/nss/root+client_ca.crt.db/key4.db
 create mode 100644 src/test/ssl/ssl/nss/root+client_ca.crt.db/pkcs11.txt
 create mode 100644 src/test/ssl/ssl/nss/root+server_ca.crt.db/cert9.db
 create mode 100644 src/test/ssl/ssl/nss/root+server_ca.crt.db/key4.db
 create mode 100644 src/test/ssl/ssl/nss/root+server_ca.crt.db/pkcs11.txt
 create mode 100644 src/test/ssl/ssl/nss/root+server_ca.crt__root+server.crl.db/cert9.db
 create mode 100644 src/test/ssl/ssl/nss/root+server_ca.crt__root+server.crl.db/key4.db
 create mode 100644 src/test/ssl/ssl/nss/root+server_ca.crt__root+server.crl.db/pkcs11.txt
 create mode 100644 src/test/ssl/ssl/nss/root+server_ca.crt__server.crl.db/cert9.db
 create mode 100644 src/test/ssl/ssl/nss/root+server_ca.crt__server.crl.db/key4.db
 create mode 100644 src/test/ssl/ssl/nss/root+server_ca.crt__server.crl.db/pkcs11.txt
 create mode 100644 src/test/ssl/ssl/nss/root.crl
 create mode 100644 src/test/ssl/ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db/cert9.db
 create mode 100644 src/test/ssl/ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db/key4.db
 create mode 100644 src/test/ssl/ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db/pkcs11.txt
 create mode 100644 src/test/ssl/ssl/nss/server-cn-and-alt-names.pfx
 create mode 100644 src/test/ssl/ssl/nss/server-cn-only.crt__server-cn-only.key.db/cert9.db
 create mode 100644 src/test/ssl/ssl/nss/server-cn-only.crt__server-cn-only.key.db/key4.db
 create mode 100644 src/test/ssl/ssl/nss/server-cn-only.crt__server-cn-only.key.db/pkcs11.txt
 create mode 100644 src/test/ssl/ssl/nss/server-cn-only.crt__server-password.key.db/cert9.db
 create mode 100644 src/test/ssl/ssl/nss/server-cn-only.crt__server-password.key.db/key4.db
 create mode 100644 src/test/ssl/ssl/nss/server-cn-only.crt__server-password.key.db/pkcs11.txt
 create mode 100644 src/test/ssl/ssl/nss/server-cn-only.pfx
 create mode 100644 src/test/ssl/ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db/cert9.db
 create mode 100644 src/test/ssl/ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db/key4.db
 create mode 100644 src/test/ssl/ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db/pkcs11.txt
 create mode 100644 src/test/ssl/ssl/nss/server-multiple-alt-names.pfx
 create mode 100644 src/test/ssl/ssl/nss/server-no-names.crt__server-no-names.key.db/cert9.db
 create mode 100644 src/test/ssl/ssl/nss/server-no-names.crt__server-no-names.key.db/key4.db
 create mode 100644 src/test/ssl/ssl/nss/server-no-names.crt__server-no-names.key.db/pkcs11.txt
 create mode 100644 src/test/ssl/ssl/nss/server-no-names.pfx
 create mode 100644 src/test/ssl/ssl/nss/server-password.pfx
 create mode 100644 src/test/ssl/ssl/nss/server-revoked.crt__server-revoked.key.db/cert9.db
 create mode 100644 src/test/ssl/ssl/nss/server-revoked.crt__server-revoked.key.db/key4.db
 create mode 100644 src/test/ssl/ssl/nss/server-revoked.crt__server-revoked.key.db/pkcs11.txt
 create mode 100644 src/test/ssl/ssl/nss/server-revoked.pfx
 create mode 100644 src/test/ssl/ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db/cert9.db
 create mode 100644 src/test/ssl/ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db/key4.db
 create mode 100644 src/test/ssl/ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db/pkcs11.txt
 create mode 100644 src/test/ssl/ssl/nss/server-single-alt-name.pfx
 create mode 100644 src/test/ssl/ssl/nss/server.crl
 create mode 100644 src/test/ssl/ssl/nss/server_ca.crt.db/cert9.db
 create mode 100644 src/test/ssl/ssl/nss/server_ca.crt.db/key4.db
 create mode 100644 src/test/ssl/ssl/nss/server_ca.crt.db/pkcs11.txt
 create mode 100644 src/test/ssl/t/SSL/Backend/NSS.pm
 create mode 100644 src/test/ssl/t/SSL/Backend/OpenSSL.pm
 rename src/test/ssl/t/{SSLServer.pm => SSL/Server.pm} (78%)

diff --git a/configure b/configure
index cb8fbe1051..8b7d98c2ab 100755
--- a/configure
+++ b/configure
@@ -711,6 +711,7 @@ with_uuid
 with_readline
 with_systemd
 with_selinux
+with_nss
 with_openssl
 with_ldap
 with_krb_srvnam
@@ -856,6 +857,7 @@ with_bsd_auth
 with_ldap
 with_bonjour
 with_openssl
+with_nss
 with_selinux
 with_systemd
 with_readline
@@ -1558,6 +1560,7 @@ Optional Packages:
   --with-ldap             build with LDAP support
   --with-bonjour          build with Bonjour support
   --with-openssl          build with OpenSSL support
+  --with-nss              build with NSS support
   --with-selinux          build with SELinux support
   --with-systemd          build with systemd support
   --without-readline      do not use GNU Readline nor BSD Libedit for editing
@@ -8100,6 +8103,41 @@ fi
 $as_echo "$with_openssl" >&6; }
 
 
+#
+# LibNSS
+#
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether to build with NSS support" >&5
+$as_echo_n "checking whether to build with NSS support... " >&6; }
+
+
+
+# Check whether --with-nss was given.
+if test "${with_nss+set}" = set; then :
+  withval=$with_nss;
+  case $withval in
+    yes)
+
+$as_echo "#define USE_NSS 1" >>confdefs.h
+
+      ;;
+    no)
+      :
+      ;;
+    *)
+      as_fn_error $? "no argument expected for --with-nss option" "$LINENO" 5
+      ;;
+  esac
+
+else
+  with_nss=no
+
+fi
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $with_nss" >&5
+$as_echo "$with_nss" >&6; }
+
+
 #
 # SELinux
 #
@@ -12174,6 +12212,9 @@ fi
 fi
 
 if test "$with_openssl" = yes ; then
+  if test x"$with_nss" = x"yes" ; then
+    as_fn_error $? "multiple SSL backends cannot be enabled simultaneously\"" "$LINENO" 5
+  fi
     # Minimum required OpenSSL version is 1.0.1
 
 $as_echo "#define OPENSSL_API_COMPAT 0x10001000L" >>confdefs.h
@@ -12436,6 +12477,157 @@ done
 
 fi
 
+if test "$with_nss" = yes ; then
+  if test x"$with_openssl" = x"yes" ; then
+    as_fn_error $? "multiple SSL backends cannot be enabled simultaneously\"" "$LINENO" 5
+  fi
+  CLEANLDFLAGS="$LDFLAGS"
+  # TODO: document this set of LDFLAGS
+  LDFLAGS="-lssl3 -lsmime3 -lnss3 -lplds4 -lplc4 -lnspr4 $LDFLAGS"
+  { $as_echo "$as_me:${as_lineno-$LINENO}: checking for SSL_VersionRangeSet in -lnss3" >&5
+$as_echo_n "checking for SSL_VersionRangeSet in -lnss3... " >&6; }
+if ${ac_cv_lib_nss3_SSL_VersionRangeSet+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  ac_check_lib_save_LIBS=$LIBS
+LIBS="-lnss3  $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+/* Override any GCC internal prototype to avoid an error.
+   Use char because int might match the return type of a GCC
+   builtin and then its argument prototype would still apply.  */
+#ifdef __cplusplus
+extern "C"
+#endif
+char SSL_VersionRangeSet ();
+int
+main ()
+{
+return SSL_VersionRangeSet ();
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+  ac_cv_lib_nss3_SSL_VersionRangeSet=yes
+else
+  ac_cv_lib_nss3_SSL_VersionRangeSet=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+    conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_nss3_SSL_VersionRangeSet" >&5
+$as_echo "$ac_cv_lib_nss3_SSL_VersionRangeSet" >&6; }
+if test "x$ac_cv_lib_nss3_SSL_VersionRangeSet" = xyes; then :
+  cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBNSS3 1
+_ACEOF
+
+  LIBS="-lnss3 $LIBS"
+
+else
+  as_fn_error $? "library 'nss3' is required for NSS" "$LINENO" 5
+fi
+
+  { $as_echo "$as_me:${as_lineno-$LINENO}: checking for PR_GetDefaultIOMethods in -lnspr4" >&5
+$as_echo_n "checking for PR_GetDefaultIOMethods in -lnspr4... " >&6; }
+if ${ac_cv_lib_nspr4_PR_GetDefaultIOMethods+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  ac_check_lib_save_LIBS=$LIBS
+LIBS="-lnspr4  $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+/* Override any GCC internal prototype to avoid an error.
+   Use char because int might match the return type of a GCC
+   builtin and then its argument prototype would still apply.  */
+#ifdef __cplusplus
+extern "C"
+#endif
+char PR_GetDefaultIOMethods ();
+int
+main ()
+{
+return PR_GetDefaultIOMethods ();
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+  ac_cv_lib_nspr4_PR_GetDefaultIOMethods=yes
+else
+  ac_cv_lib_nspr4_PR_GetDefaultIOMethods=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+    conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_nspr4_PR_GetDefaultIOMethods" >&5
+$as_echo "$ac_cv_lib_nspr4_PR_GetDefaultIOMethods" >&6; }
+if test "x$ac_cv_lib_nspr4_PR_GetDefaultIOMethods" = xyes; then :
+  cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBNSPR4 1
+_ACEOF
+
+  LIBS="-lnspr4 $LIBS"
+
+else
+  as_fn_error $? "library 'nspr4' is required for NSS" "$LINENO" 5
+fi
+
+  { $as_echo "$as_me:${as_lineno-$LINENO}: checking for SSL_GetImplementedCiphers in -lssl3" >&5
+$as_echo_n "checking for SSL_GetImplementedCiphers in -lssl3... " >&6; }
+if ${ac_cv_lib_ssl3_SSL_GetImplementedCiphers+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  ac_check_lib_save_LIBS=$LIBS
+LIBS="-lssl3  $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+/* Override any GCC internal prototype to avoid an error.
+   Use char because int might match the return type of a GCC
+   builtin and then its argument prototype would still apply.  */
+#ifdef __cplusplus
+extern "C"
+#endif
+char SSL_GetImplementedCiphers ();
+int
+main ()
+{
+return SSL_GetImplementedCiphers ();
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+  ac_cv_lib_ssl3_SSL_GetImplementedCiphers=yes
+else
+  ac_cv_lib_ssl3_SSL_GetImplementedCiphers=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+    conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_ssl3_SSL_GetImplementedCiphers" >&5
+$as_echo "$ac_cv_lib_ssl3_SSL_GetImplementedCiphers" >&6; }
+if test "x$ac_cv_lib_ssl3_SSL_GetImplementedCiphers" = xyes; then :
+  cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBSSL3 1
+_ACEOF
+
+  LIBS="-lssl3 $LIBS"
+
+else
+  as_fn_error $? "library 'ssl3' is required for NSS" "$LINENO" 5
+fi
+
+  LDFLAGS="$CLEANLDFLAGS"
+fi
+
 if test "$with_pam" = yes ; then
   { $as_echo "$as_me:${as_lineno-$LINENO}: checking for pam_start in -lpam" >&5
 $as_echo_n "checking for pam_start in -lpam... " >&6; }
@@ -13338,6 +13530,25 @@ else
 fi
 
 
+fi
+
+if test "$with_nss" = yes ; then
+  ac_fn_c_check_header_mongrel "$LINENO" "ssl.h" "ac_cv_header_ssl_h" "$ac_includes_default"
+if test "x$ac_cv_header_ssl_h" = xyes; then :
+
+else
+  as_fn_error $? "header file <ssl.h> is required for NSS" "$LINENO" 5
+fi
+
+
+  ac_fn_c_check_header_mongrel "$LINENO" "nss.h" "ac_cv_header_nss_h" "$ac_includes_default"
+if test "x$ac_cv_header_nss_h" = xyes; then :
+
+else
+  as_fn_error $? "header file <nss.h> is required for NSS" "$LINENO" 5
+fi
+
+
 fi
 
 if test "$with_pam" = yes ; then
diff --git a/configure.ac b/configure.ac
index eb2c731b58..23c07cabce 100644
--- a/configure.ac
+++ b/configure.ac
@@ -856,6 +856,15 @@ PGAC_ARG_BOOL(with, openssl, no, [build with OpenSSL support],
 AC_MSG_RESULT([$with_openssl])
 AC_SUBST(with_openssl)
 
+#
+# LibNSS
+#
+AC_MSG_CHECKING([whether to build with NSS support])
+PGAC_ARG_BOOL(with, nss, no, [build with NSS support],
+              [AC_DEFINE([USE_NSS], 1, [Define to build with NSS support. (--with-nss)])])
+AC_MSG_RESULT([$with_nss])
+AC_SUBST(with_nss)
+
 #
 # SELinux
 #
@@ -1205,6 +1214,9 @@ if test "$with_gssapi" = yes ; then
 fi
 
 if test "$with_openssl" = yes ; then
+  if test x"$with_nss" = x"yes" ; then
+    AC_MSG_ERROR([multiple SSL backends cannot be enabled simultaneously"])
+  fi
   dnl Order matters!
   # Minimum required OpenSSL version is 1.0.1
   AC_DEFINE(OPENSSL_API_COMPAT, [0x10001000L],
@@ -1230,6 +1242,19 @@ if test "$with_openssl" = yes ; then
   AC_CHECK_FUNCS([CRYPTO_lock])
 fi
 
+if test "$with_nss" = yes ; then
+  if test x"$with_openssl" = x"yes" ; then
+    AC_MSG_ERROR([multiple SSL backends cannot be enabled simultaneously"])
+  fi
+  CLEANLDFLAGS="$LDFLAGS"
+  # TODO: document this set of LDFLAGS
+  LDFLAGS="-lssl3 -lsmime3 -lnss3 -lplds4 -lplc4 -lnspr4 $LDFLAGS"
+  AC_CHECK_LIB(nss3, SSL_VersionRangeSet, [], [AC_MSG_ERROR([library 'nss3' is required for NSS])])
+  AC_CHECK_LIB(nspr4, PR_GetDefaultIOMethods, [], [AC_MSG_ERROR([library 'nspr4' is required for NSS])])
+  AC_CHECK_LIB(ssl3, SSL_GetImplementedCiphers, [], [AC_MSG_ERROR([library 'ssl3' is required for NSS])])
+  LDFLAGS="$CLEANLDFLAGS"
+fi
+
 if test "$with_pam" = yes ; then
   AC_CHECK_LIB(pam,    pam_start, [], [AC_MSG_ERROR([library 'pam' is required for PAM])])
 fi
@@ -1405,6 +1430,11 @@ if test "$with_openssl" = yes ; then
   AC_CHECK_HEADER(openssl/err.h, [], [AC_MSG_ERROR([header file <openssl/err.h> is required for OpenSSL])])
 fi
 
+if test "$with_nss" = yes ; then
+  AC_CHECK_HEADER(ssl.h, [], [AC_MSG_ERROR([header file <ssl.h> is required for NSS])])
+  AC_CHECK_HEADER(nss.h, [], [AC_MSG_ERROR([header file <nss.h> is required for NSS])])
+fi
+
 if test "$with_pam" = yes ; then
   AC_CHECK_HEADERS(security/pam_appl.h, [],
                    [AC_CHECK_HEADERS(pam/pam_appl.h, [],
diff --git a/contrib/Makefile b/contrib/Makefile
index 1846d415b6..cef7bf7f61 100644
--- a/contrib/Makefile
+++ b/contrib/Makefile
@@ -50,7 +50,7 @@ SUBDIRS = \
 		unaccent	\
 		vacuumlo
 
-ifeq ($(with_openssl),yes)
+ifeq ($(with_ssl),yes)
 SUBDIRS += sslinfo
 else
 ALWAYS_SUBDIRS += sslinfo
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index 90db550b92..961cb56358 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -8898,7 +8898,7 @@ DO $d$
     END;
 $d$;
 ERROR:  invalid option "password"
-HINT:  Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, fetch_size
+HINT:  Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, cert_database, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, fetch_size
 CONTEXT:  SQL statement "ALTER SERVER loopback_nopw OPTIONS (ADD password 'dummypw')"
 PL/pgSQL function inline_code_block line 3 at EXECUTE
 -- If we add a password for our user mapping instead, we should get a different
diff --git a/contrib/sslinfo/sslinfo.c b/contrib/sslinfo/sslinfo.c
index 5ba3988e27..84bb2c65b8 100644
--- a/contrib/sslinfo/sslinfo.c
+++ b/contrib/sslinfo/sslinfo.c
@@ -9,9 +9,11 @@
 
 #include "postgres.h"
 
+#ifdef USE_OPENSSL
 #include <openssl/x509.h>
 #include <openssl/x509v3.h>
 #include <openssl/asn1.h>
+#endif
 
 #include "access/htup_details.h"
 #include "funcapi.h"
@@ -21,8 +23,8 @@
 
 PG_MODULE_MAGIC;
 
+#ifdef USE_OPENSSL
 static Datum X509_NAME_field_to_text(X509_NAME *name, text *fieldName);
-static Datum X509_NAME_to_text(X509_NAME *name);
 static Datum ASN1_STRING_to_text(ASN1_STRING *str);
 
 /*
@@ -32,6 +34,7 @@ typedef struct
 {
 	TupleDesc	tupdesc;
 } SSLExtensionInfoContext;
+#endif
 
 /*
  * Indicates whether current session uses SSL
@@ -54,9 +57,16 @@ PG_FUNCTION_INFO_V1(ssl_version);
 Datum
 ssl_version(PG_FUNCTION_ARGS)
 {
-	if (MyProcPort->ssl == NULL)
+	const char *version;
+
+	if (!MyProcPort->ssl_in_use)
+		PG_RETURN_NULL();
+
+	version = be_tls_get_version(MyProcPort);
+	if (version == NULL)
 		PG_RETURN_NULL();
-	PG_RETURN_TEXT_P(cstring_to_text(SSL_get_version(MyProcPort->ssl)));
+
+	PG_RETURN_TEXT_P(cstring_to_text(version));
 }
 
 
@@ -67,9 +77,16 @@ PG_FUNCTION_INFO_V1(ssl_cipher);
 Datum
 ssl_cipher(PG_FUNCTION_ARGS)
 {
-	if (MyProcPort->ssl == NULL)
+	const char *cipher;
+
+	if (!MyProcPort->ssl_in_use)
 		PG_RETURN_NULL();
-	PG_RETURN_TEXT_P(cstring_to_text(SSL_get_cipher(MyProcPort->ssl)));
+
+	cipher = be_tls_get_cipher(MyProcPort);
+	if (cipher == NULL)
+		PG_RETURN_NULL();
+
+	PG_RETURN_TEXT_P(cstring_to_text(cipher));
 }
 
 
@@ -83,7 +100,7 @@ PG_FUNCTION_INFO_V1(ssl_client_cert_present);
 Datum
 ssl_client_cert_present(PG_FUNCTION_ARGS)
 {
-	PG_RETURN_BOOL(MyProcPort->peer != NULL);
+	PG_RETURN_BOOL(MyProcPort->peer_cert_valid);
 }
 
 
@@ -99,29 +116,26 @@ PG_FUNCTION_INFO_V1(ssl_client_serial);
 Datum
 ssl_client_serial(PG_FUNCTION_ARGS)
 {
+	char decimal[NAMEDATALEN];
 	Datum		result;
-	Port	   *port = MyProcPort;
-	X509	   *peer = port->peer;
-	ASN1_INTEGER *serial = NULL;
-	BIGNUM	   *b;
-	char	   *decimal;
 
-	if (!peer)
+	if (!MyProcPort->ssl_in_use || !MyProcPort->peer_cert_valid)
+		PG_RETURN_NULL();
+
+	be_tls_get_peer_serial(MyProcPort, decimal, NAMEDATALEN);
+
+	if (!*decimal)
 		PG_RETURN_NULL();
-	serial = X509_get_serialNumber(peer);
-	b = ASN1_INTEGER_to_BN(serial, NULL);
-	decimal = BN_bn2dec(b);
 
-	BN_free(b);
 	result = DirectFunctionCall3(numeric_in,
 								 CStringGetDatum(decimal),
 								 ObjectIdGetDatum(0),
 								 Int32GetDatum(-1));
-	OPENSSL_free(decimal);
 	return result;
 }
 
 
+#ifdef USE_OPENSSL
 /*
  * Converts OpenSSL ASN1_STRING structure into text
  *
@@ -228,7 +242,7 @@ ssl_client_dn_field(PG_FUNCTION_ARGS)
 	text	   *fieldname = PG_GETARG_TEXT_PP(0);
 	Datum		result;
 
-	if (!(MyProcPort->peer))
+	if (!MyProcPort->ssl_in_use || !MyProcPort->peer_cert_valid)
 		PG_RETURN_NULL();
 
 	result = X509_NAME_field_to_text(X509_get_subject_name(MyProcPort->peer), fieldname);
@@ -273,76 +287,23 @@ ssl_issuer_field(PG_FUNCTION_ARGS)
 	else
 		return result;
 }
+#endif							/* USE_OPENSSL */
 
-
-/*
- * Equivalent of X509_NAME_oneline that respects encoding
- *
- * This function converts X509_NAME structure to the text variable
- * converting all textual data into current database encoding.
- *
- * Parameter: X509_NAME *name X509_NAME structure to be converted
- *
- * Returns: text datum which contains string representation of
- * X509_NAME
- */
-static Datum
-X509_NAME_to_text(X509_NAME *name)
+#ifdef USE_NSS
+PG_FUNCTION_INFO_V1(ssl_client_dn_field);
+Datum
+ssl_client_dn_field(PG_FUNCTION_ARGS)
 {
-	BIO		   *membuf = BIO_new(BIO_s_mem());
-	int			i,
-				nid,
-				count = X509_NAME_entry_count(name);
-	X509_NAME_ENTRY *e;
-	ASN1_STRING *v;
-	const char *field_name;
-	size_t		size;
-	char		nullterm;
-	char	   *sp;
-	char	   *dp;
-	text	   *result;
-
-	if (membuf == NULL)
-		ereport(ERROR,
-				(errcode(ERRCODE_OUT_OF_MEMORY),
-				 errmsg("could not create OpenSSL BIO structure")));
-
-	(void) BIO_set_close(membuf, BIO_CLOSE);
-	for (i = 0; i < count; i++)
-	{
-		e = X509_NAME_get_entry(name, i);
-		nid = OBJ_obj2nid(X509_NAME_ENTRY_get_object(e));
-		if (nid == NID_undef)
-			ereport(ERROR,
-					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
-					 errmsg("could not get NID for ASN1_OBJECT object")));
-		v = X509_NAME_ENTRY_get_data(e);
-		field_name = OBJ_nid2sn(nid);
-		if (field_name == NULL)
-			field_name = OBJ_nid2ln(nid);
-		if (field_name == NULL)
-			ereport(ERROR,
-					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
-					 errmsg("could not convert NID %d to an ASN1_OBJECT structure", nid)));
-		BIO_printf(membuf, "/%s=", field_name);
-		ASN1_STRING_print_ex(membuf, v,
-							 ((ASN1_STRFLGS_RFC2253 & ~ASN1_STRFLGS_ESC_MSB)
-							  | ASN1_STRFLGS_UTF8_CONVERT));
-	}
-
-	/* ensure null termination of the BIO's content */
-	nullterm = '\0';
-	BIO_write(membuf, &nullterm, 1);
-	size = BIO_get_mem_data(membuf, &sp);
-	dp = pg_any_to_server(sp, size - 1, PG_UTF8);
-	result = cstring_to_text(dp);
-	if (dp != sp)
-		pfree(dp);
-	if (BIO_free(membuf) != 1)
-		elog(ERROR, "could not free OpenSSL BIO structure");
+	PG_RETURN_NULL();
+}
 
-	PG_RETURN_TEXT_P(result);
+PG_FUNCTION_INFO_V1(ssl_issuer_field);
+Datum
+ssl_issuer_field(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_NULL();
 }
+#endif							/* USE_NSS */
 
 
 /*
@@ -358,9 +319,17 @@ PG_FUNCTION_INFO_V1(ssl_client_dn);
 Datum
 ssl_client_dn(PG_FUNCTION_ARGS)
 {
-	if (!(MyProcPort->peer))
+	char		subject[NAMEDATALEN];
+
+	if (!MyProcPort->ssl_in_use || !MyProcPort->peer_cert_valid)
+		PG_RETURN_NULL();
+
+	be_tls_get_peer_subject_name(MyProcPort, subject, NAMEDATALEN);
+
+	if (!*subject)
 		PG_RETURN_NULL();
-	return X509_NAME_to_text(X509_get_subject_name(MyProcPort->peer));
+
+	PG_RETURN_TEXT_P(cstring_to_text(subject));
 }
 
 
@@ -377,12 +346,21 @@ PG_FUNCTION_INFO_V1(ssl_issuer_dn);
 Datum
 ssl_issuer_dn(PG_FUNCTION_ARGS)
 {
-	if (!(MyProcPort->peer))
+	char		issuer[NAMEDATALEN];
+
+	if (!MyProcPort->ssl_in_use || !MyProcPort->peer_cert_valid)
+		PG_RETURN_NULL();
+
+	be_tls_get_peer_issuer_name(MyProcPort, issuer, NAMEDATALEN);
+
+	if (!*issuer)
 		PG_RETURN_NULL();
-	return X509_NAME_to_text(X509_get_issuer_name(MyProcPort->peer));
+
+	PG_RETURN_TEXT_P(cstring_to_text(issuer));
 }
 
 
+#ifdef USE_OPENSSL
 /*
  * Returns information about available SSL extensions.
  *
@@ -516,3 +494,13 @@ ssl_extension_info(PG_FUNCTION_ARGS)
 	/* All done */
 	SRF_RETURN_DONE(funcctx);
 }
+#endif							/* USE_OPENSSL */
+
+#ifdef USE_NSS
+PG_FUNCTION_INFO_V1(ssl_extension_info);
+Datum
+ssl_extension_info(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_NULL();
+}
+#endif							/* USE_NSS */
diff --git a/doc/src/sgml/sslinfo.sgml b/doc/src/sgml/sslinfo.sgml
index c237d4ba95..d15a206d2d 100644
--- a/doc/src/sgml/sslinfo.sgml
+++ b/doc/src/sgml/sslinfo.sgml
@@ -22,7 +22,8 @@
 
  <para>
   This extension won't build at all unless the installation was
-  configured with <literal>--with-openssl</literal>.
+  configured with SSL support, such as <literal>--with-openssl</literal>
+  or <literal>--with-nss</literal>.
  </para>
 
  <sect2>
@@ -54,7 +55,7 @@
     <listitem>
     <para>
      Returns the name of the protocol used for the SSL connection (e.g. TLSv1.0
-     TLSv1.1, or TLSv1.2).
+     TLSv1.1, TLSv1.2 or TLSv1.3).
     </para>
     </listitem>
    </varlistentry>
@@ -208,6 +209,9 @@ emailAddress
      the X.500 and X.509 standards, so you cannot just assign arbitrary
      meaning to them.
     </para>
+    <para>
+     This function is only available when using OpenSSL.
+    </para>
     </listitem>
    </varlistentry>
 
@@ -223,6 +227,9 @@ emailAddress
      Same as <function>ssl_client_dn_field</function>, but for the certificate issuer
      rather than the certificate subject.
     </para>
+    <para>
+     This function is only available when using OpenSSL.
+    </para>
     </listitem>
    </varlistentry>
 
@@ -238,6 +245,9 @@ emailAddress
      Provide information about extensions of client certificate: extension name,
      extension value, and if it is a critical extension.
     </para>
+    <para>
+     This function is only available when using OpenSSL.
+    </para>
     </listitem>
    </varlistentry>
   </variablelist>
diff --git a/src/Makefile.global.in b/src/Makefile.global.in
index 9a6265b3a0..2f25c51c6c 100644
--- a/src/Makefile.global.in
+++ b/src/Makefile.global.in
@@ -184,6 +184,7 @@ with_perl	= @with_perl@
 with_python	= @with_python@
 with_tcl	= @with_tcl@
 with_openssl	= @with_openssl@
+with_nss        = @with_nss@
 with_readline	= @with_readline@
 with_selinux	= @with_selinux@
 with_systemd	= @with_systemd@
@@ -232,6 +233,15 @@ CLANG = @CLANG@
 BITCODE_CFLAGS = @BITCODE_CFLAGS@
 BITCODE_CXXFLAGS = @BITCODE_CXXFLAGS@
 
+ifeq ($(with_openssl),yes)
+with_ssl = yes
+else ifeq ($(with_nss),yes)
+with_ssl = yes
+else
+with_ssl = no
+endif
+
+
 ##########################################################################
 #
 # Programs and flags
diff --git a/src/backend/libpq/Makefile b/src/backend/libpq/Makefile
index efc5ef760a..191266a426 100644
--- a/src/backend/libpq/Makefile
+++ b/src/backend/libpq/Makefile
@@ -30,6 +30,10 @@ OBJS = \
 
 ifeq ($(with_openssl),yes)
 OBJS += be-secure-openssl.o
+else
+ifeq ($(with_nss),yes)
+OBJS += be-secure-nss.o
+endif
 endif
 
 ifeq ($(with_gssapi),yes)
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index 02b6c3f127..8f4197d002 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -2870,7 +2870,14 @@ CheckCertAuth(Port *port)
 {
 	int			status_check_usermap = STATUS_ERROR;
 
+#if defined(USE_OPENSSL)
 	Assert(port->ssl);
+#elif defined(USE_NSS)
+	/* TODO: should we rename pr_fd to ssl, to keep consistency? */
+	Assert(port->pr_fd);
+#else
+	Assert(false);
+#endif
 
 	/* Make sure we have received a username in the certificate */
 	if (port->peer_cn == NULL ||
diff --git a/src/backend/libpq/be-secure-nss.c b/src/backend/libpq/be-secure-nss.c
new file mode 100644
index 0000000000..00ae054920
--- /dev/null
+++ b/src/backend/libpq/be-secure-nss.c
@@ -0,0 +1,1032 @@
+/*-------------------------------------------------------------------------
+ *
+ * be-secure-nss.c
+ *	  functions for supporting NSS as a TLS backend
+ *
+ *
+ * Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ *	  src/backend/libpq/be-secure-nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include <sys/stat.h>
+
+/*
+ * BITS_PER_BYTE is also defined in the NSPR header fils, so we need to undef
+ * our version to avoid compiler warnings on redefinition.
+ */
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+
+/*
+ * The nspr/obsolete/protypes.h NSPR header typedefs uint64 and int64 with
+ * colliding definitions from ours, causing a much expected compiler error.
+ * The definitions are however not actually used in NSPR at all, and are only
+ * intended for what seems to be backwards compatibility for apps written
+ * against old versions of NSPR.  The following comment is in the referenced
+ * file, and was added in 1998:
+ *
+ *		This section typedefs the old 'native' types to the new PR<type>s.
+ *		These definitions are scheduled to be eliminated at the earliest
+ *		possible time. The NSPR API is implemented and documented using
+ *		the new definitions.
+ *
+ * As there is no opt-out from pulling in these typedefs, we define the guard
+ * for the file to exclude it. This is incredibly ugly, but seems to be about
+ * the only way around it.
+ */
+#define PROTYPES_H
+#include <nspr.h>
+#undef PROTYPES_H
+#include <nss.h>
+#include <prio.h>
+#include <ssl.h>
+#include <sslerr.h>
+#include <secerr.h>
+#include <sslproto.h>
+#include <prtypes.h>
+#include <pk11pub.h>
+#include <secitem.h>
+#include <secport.h>
+#include <secder.h>
+#include <certdb.h>
+#include <base64.h>
+#include <cert.h>
+#include <prerror.h>
+#include <keyhi.h>
+
+typedef struct
+{
+	enum
+	{
+		PW_NONE = 0,
+		PW_FROMFILE = 1,
+		PW_PLAINTEXT = 2,
+		PW_EXTERNAL = 3
+	} source;
+	char	   *data;
+}			secuPWData;
+
+/*
+ * Ensure that the colliding definitions match, else throw an error. In case
+ * NSPR has removed the definition for some reasone, make sure to put ours
+ * back again.
+ */
+#if defined(BITS_PER_BYTE)
+#if BITS_PER_BYTE != pg_BITS_PER_BYTE
+#error "incompatible byte widths between NSPR and postgres"
+#endif
+#else
+#define BITS_PER_BYTE pg_BITS_PER_BYTE
+#endif
+#undef pg_BITS_PER_BYTE
+
+#include "common/pg_nss.h"
+#include "lib/stringinfo.h"
+#include "libpq/libpq.h"
+#include "nodes/pg_list.h"
+#include "miscadmin.h"
+#include "storage/fd.h"
+#include "utils/guc.h"
+#include "utils/memutils.h"
+
+static PRDescIdentity pr_id;
+
+static PRIOMethods pr_iomethods;
+static NSSInitContext * nss_context = NULL;
+static SSLVersionRange desired_sslver;
+
+/*
+ * PR_ImportTCPSocket() is a private API, but very widely used, as it's the
+ * only way to make NSS use an already set up POSIX file descriptor rather
+ * than opening one itself. To quote the NSS documentation:
+ *
+ *		"In theory, code that uses PR_ImportTCPSocket may break when NSPR's
+ *		implementation changes. In practice, this is unlikely to happen because
+ *		NSPR's implementation has been stable for years and because of NSPR's
+ *		strong commitment to backward compatibility."
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR/Reference/PR_ImportTCPSocket
+ *
+ * The function is declared in <private/pprio.h>, but as it is a header marked
+ * private we declare it here rather than including it.
+ */
+NSPR_API(PRFileDesc *) PR_ImportTCPSocket(int);
+
+/* NSS IO layer callback overrides */
+static PRInt32 pg_ssl_read(PRFileDesc * fd, void *buf, PRInt32 amount,
+						   PRIntn flags, PRIntervalTime timeout);
+static PRInt32 pg_ssl_write(PRFileDesc * fd, const void *buf, PRInt32 amount,
+							PRIntn flags, PRIntervalTime timeout);
+/* Utility functions */
+static PRFileDesc * init_iolayer(Port *port, int loglevel);
+static uint16 ssl_protocol_version_to_nss(int v, const char *guc_name);
+
+static char *pg_SSLerrmessage(PRErrorCode errcode);
+static char *ssl_protocol_version_to_string(int v);
+static SECStatus pg_cert_auth_handler(void *arg, PRFileDesc * fd,
+									  PRBool checksig, PRBool isServer);
+static SECStatus pg_bad_cert_handler(void *arg, PRFileDesc * fd);
+
+/* ------------------------------------------------------------ */
+/*						 Public interface						*/
+/* ------------------------------------------------------------ */
+
+static char *
+ssl_passphrase_callback(PK11SlotInfo * slot, PRBool retry, void *arg)
+{
+	return pstrdup("");
+}
+
+/*
+ * be_tls_init
+ *			Initialize the nss TLS library in the postmaster
+ *
+ * The majority of the setup needs to happen in be_tls_open_server since the
+ * NSPR initialization must happen after the forking of the backend. We could
+ * potentially move some parts in under !isServerStart, but so far this is the
+ * separation chosen.
+ */
+int
+be_tls_init(bool isServerStart)
+{
+	SECStatus	status;
+	SSLVersionRange supported_sslver;
+
+	/*
+	 * Set up the connection cache for multi-processing application behavior.
+	 * If we are in ServerStart then we initialize the cache. If the server is
+	 * already started, we inherit the cache such that it can be used for
+	 * connections. Calling SSL_ConfigMPServerSIDCache sets an environment
+	 * variable which contains enough information for the forked child to know
+	 * how to access it.  Passing NULL to SSL_InheritMPServerSIDCache will
+	 * make the forked child look it up by the default name SSL_INHERITANCE,
+	 * if env vars aren't inherited then the contents of the variable can be
+	 * passed instead.
+	 */
+	if (isServerStart)
+	{
+		/*
+		 * SSLv2 and SSLv3 are disabled in this TLS backend, but when setting
+		 * up the required session cache for NSS we still must supply timeout
+		 * values for v2 and The minimum allowed value for both is 5 seconds,
+		 * so opt for that in both cases (the defaults being 100 seconds and
+		 * 24 hours).
+		 *
+		 * Passing NULL as the directory for the session cache will default to
+		 * using /tmp on UNIX and \\temp on Windows.  Deciding if we want to
+		 * keep closer control on this directory is left as a TODO.
+		 */
+		status = SSL_ConfigMPServerSIDCache(MaxConnections, 5, 5, NULL);
+		if (status != SECSuccess)
+			ereport(FATAL,
+					(errmsg("unable to set up TLS connection cache: %s",
+							pg_SSLerrmessage(PR_GetError()))));
+
+	}
+	else
+	{
+		status = SSL_InheritMPServerSIDCache(NULL);
+		if (status != SECSuccess)
+		{
+			ereport(LOG,
+					(errmsg("unable to connect to TLS connection cache: %s",
+							pg_SSLerrmessage(PR_GetError()))));
+			return -1;
+		}
+	}
+
+	if (!ssl_database || strlen(ssl_database) == 0)
+	{
+		ereport(isServerStart ? FATAL : LOG,
+				(errmsg("no certificate database specified")));
+		goto error;
+	}
+
+	/*
+	 * We check for the desired TLS version range here, even though we cannot
+	 * set it until be_open_server such that we can be compatible with how the
+	 * OpenSSL backend reports errors for incompatible range configurations.
+	 * Set either the default supported TLS version range, or the configured
+	 * range from ssl_min_protocol_version and ssl_max_protocol version. In
+	 * case the user hasn't defined the maximum allowed version we fall back
+	 * to the highest version TLS that the library supports.
+	 */
+	if (SSL_VersionRangeGetSupported(ssl_variant_stream, &supported_sslver) != SECSuccess)
+	{
+		ereport(isServerStart ? FATAL : LOG,
+				(errmsg("unable to get default protocol support from NSS")));
+		goto error;
+	}
+
+	/*
+	 * Set the fallback versions for the TLS protocol version range to a
+	 * combination of our minimal requirement and the library maximum.
+	 */
+	desired_sslver.min = SSL_LIBRARY_VERSION_TLS_1_0;
+	desired_sslver.max = supported_sslver.max;
+
+	if (ssl_min_protocol_version)
+	{
+		int			ver = ssl_protocol_version_to_nss(ssl_min_protocol_version,
+													  "ssl_min_protocol_version");
+
+		if (ver == -1)
+		{
+			ereport(isServerStart ? FATAL : LOG,
+					(errmsg("\"%s\" setting \"%s\" not supported by this build",
+							"ssl_min_protocol_version",
+							GetConfigOption("ssl_min_protocol_version",
+											false, false))));
+			goto error;
+		}
+
+		if (ver > 0)
+			desired_sslver.min = ver;
+	}
+
+	if (ssl_max_protocol_version)
+	{
+		int			ver = ssl_protocol_version_to_nss(ssl_max_protocol_version,
+													  "ssl_max_protocol_version");
+
+		if (ver == -1)
+		{
+			ereport(isServerStart ? FATAL : LOG,
+					(errmsg("\"%s\" setting \"%s\" not supported by this build",
+							"ssl_max_protocol_version",
+							GetConfigOption("ssl_max_protocol_version",
+											false, false))));
+			goto error;
+		}
+		if (ver > 0)
+			desired_sslver.max = ver;
+
+		if (ver < desired_sslver.min)
+		{
+			ereport(isServerStart ? FATAL : LOG,
+					(errmsg("could not set SSL protocol version range"),
+					 errdetail("\"%s\" cannot be higher than \"%s\"",
+							   "ssl_min_protocol_version",
+							   "ssl_max_protocol_version")));
+			goto error;
+		}
+	}
+
+	return 0;
+error:
+	return -1;
+}
+
+int
+be_tls_open_server(Port *port)
+{
+	SECStatus	status;
+	PRFileDesc *model;
+	PRFileDesc *pr_fd;
+	PRFileDesc *layer;
+	CERTCertificate *server_cert;
+	SECKEYPrivateKey *private_key;
+	CERTSignedCrl *crl;
+	SECItem		crlname;
+	secuPWData	pwdata = {PW_NONE, 0};	/* TODO: This is a bogus callback */
+	char	   *cert_database;
+	NSSInitParameters params;
+
+	/*
+	 * The NSPR documentation states that runtime initialization via PR_Init
+	 * is no longer required, as the first caller into NSPR will perform the
+	 * initialization implicitly. The documentation doesn't however clarify
+	 * from which version this is holds true, so let's perform the potentially
+	 * superfluous initialization anyways to avoid crashing on older versions
+	 * of NSPR, as there is no difference in overhead.  The NSS documentation
+	 * still states that PR_Init must be called in some way (implicitly or
+	 * explicitly).
+	 *
+	 * The below parameters are what the implicit initialization would've done
+	 * for us, and should work even for older versions where it might not be
+	 * done automatically. The last parameter, maxPTDs, is set to various
+	 * values in other codebases, but has been unused since NSPR 2.1 which was
+	 * released sometime in 1998.
+	 */
+	PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0 /* maxPTDs */ );
+
+	/*
+	 * The certificate path (configdir) must contain a valid NSS database. If
+	 * the certificate path isn't a valid directory, NSS will fall back on the
+	 * system certificate database. If the certificate path is a directory but
+	 * is empty then the initialization will fail. On the client side this can
+	 * be allowed for any sslmode but the verify-xxx ones.
+	 * https://bugzilla.redhat.com/show_bug.cgi?id=728562 For the server side
+	 * we wont allow this to fail however, as we require the certificate and
+	 * key to exist.
+	 *
+	 * The original design of NSS was for a single application to use a single
+	 * copy of it, initialized with NSS_Initialize() which isn't returning any
+	 * handle with which to refer to NSS. NSS initialization and shutdown are
+	 * global for the application, so a shutdown in another NSS enabled
+	 * library would cause NSS to be stopped for libpq as well.  The fix has
+	 * been to introduce NSS_InitContext which returns a context handle to
+	 * pass to NSS_ShutdownContext.  NSS_InitContext was introduced in NSS
+	 * 3.12, but the use of it is not very well documented.
+	 * https://bugzilla.redhat.com/show_bug.cgi?id=738456
+	 *
+	 * The InitParameters struct passed can be used to override internal
+	 * values in NSS, but the usage is not documented at all. When using
+	 * NSS_Init initializations, the values are instead set via PK11_Configure
+	 * calls so the PK11_Configure documentation can be used to glean some
+	 * details on these.
+	 *
+	 * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/PKCS11/Module_Specs
+	 */
+	memset(&params, '\0', sizeof(params));
+	params.length = sizeof(params);
+
+	if (!ssl_database || strlen(ssl_database) == 0)
+		ereport(FATAL,
+				(errmsg("no certificate database specified")));
+
+	cert_database = psprintf("sql:%s", ssl_database);
+	nss_context = NSS_InitContext(cert_database, "", "", "",
+								  &params,
+								  NSS_INIT_READONLY | NSS_INIT_PK11RELOAD);
+	pfree(cert_database);
+
+	if (!nss_context)
+		ereport(FATAL,
+				(errmsg("unable to read certificate database \"%s\": %s",
+						ssl_database, pg_SSLerrmessage(PR_GetError()))));
+
+	/*
+	 * Set the passphrase callback which will be used both to obtain the
+	 * passphrase from the user, as well as by NSS to obtain the phrase
+	 * repeatedly.
+	 *
+	 * TODO: Figure this out - do note that we are setting another password
+	 * callback below for cert/key as well. Need to make sense of all these.
+	 */
+	PK11_SetPasswordFunc(ssl_passphrase_callback);
+
+	/*
+	 * Import the already opened socket as we don't want to use NSPR functions
+	 * for opening the network socket due to how the PostgreSQL protocol works
+	 * with TLS connections. This function is not part of the NSPR public API,
+	 * see the comment at the top of the file for the rationale of still using
+	 * it.
+	 */
+	pr_fd = PR_ImportTCPSocket(port->sock);
+	if (!pr_fd)
+		ereport(ERROR,
+				(errmsg("unable to connect to socket")));
+
+	/*
+	 * Most of the documentation available, and implementations of, NSS/NSPR
+	 * use the PR_NewTCPSocket() function here, which has the drawback that it
+	 * can only create IPv4 sockets. Instead use PR_OpenTCPSocket() which
+	 * copes with IPv6 as well.
+	 */
+	model = PR_OpenTCPSocket(port->laddr.addr.ss_family);
+	if (!model)
+		ereport(ERROR,
+				(errmsg("unable to open socket")));
+
+	/*
+	 * Convert the NSPR socket to an SSL socket. Ensuring the success of this
+	 * operation is critical as NSS SSL_* functions may return SECSuccess on
+	 * the socket even though SSL hasn't been enabled, which introduce a risk
+	 * of silent downgrades.
+	 */
+	model = SSL_ImportFD(NULL, model);
+	if (!model)
+		ereport(ERROR,
+				(errmsg("unable to enable TLS on socket")));
+
+	/*
+	 * Configure basic settings for the connection over the SSL socket in
+	 * order to set it up as a server.
+	 */
+	if (SSL_OptionSet(model, SSL_SECURITY, PR_TRUE) != SECSuccess)
+		ereport(ERROR,
+				(errmsg("unable to configure TLS connection")));
+
+	if (SSL_OptionSet(model, SSL_HANDSHAKE_AS_SERVER, PR_TRUE) != SECSuccess ||
+		SSL_OptionSet(model, SSL_HANDSHAKE_AS_CLIENT, PR_FALSE) != SECSuccess)
+		ereport(ERROR,
+				(errmsg("unable to configure TLS connection as server")));
+
+	/*
+	 * SSLv2 is disabled by default, and SSLv3 will be excluded from the range
+	 * of allowed protocols further down. Since we really don't want these to
+	 * ever be enabled, let's use belts and suspenders and explicitly turn
+	 * them off as well.
+	 */
+	SSL_OptionSet(model, SSL_ENABLE_SSL2, PR_FALSE);
+	SSL_OptionSet(model, SSL_ENABLE_SSL3, PR_FALSE);
+
+#ifdef SSL_CBC_RANDOM_IV
+
+	/*
+	 * Enable protection against the BEAST attack in case the NSS server has
+	 * support for that. While SSLv3 is disabled, we may still allow TLSv1
+	 * which is affected. The option isn't documented as an SSL option, but as
+	 * an NSS environment variable.
+	 */
+	SSL_OptionSet(model, SSL_CBC_RANDOM_IV, PR_TRUE);
+#endif
+
+	/*
+	 * Configure the allowed cipher. If there are no user preferred suites,
+	 * set the domestic policy. TODO: while this code works, the set of
+	 * ciphers which can be set and still end up with a working socket is
+	 * woefully underdocumented for anything more recent than SSLv3 (the code
+	 * for TLS actually calls ssl3 functions under the hood for
+	 * SSL_CipherPrefSet), so it's unclear if this is helpful or not. Using
+	 * the policies works, but may be too coarsely grained.
+	 *
+	 * Another TODO: The SSL_ImplementedCiphers table returned with calling
+	 * SSL_GetImplementedCiphers is sorted in server preference order. Sorting
+	 * SSLCipherSuites according to the order of the ciphers therein could be
+	 * a way to implement ssl_prefer_server_ciphers - if we at all want to use
+	 * cipher selection for NSS like how we do it for OpenSSL that is.
+	 */
+
+	/*
+	 * If no ciphers are specified, we use the domestic policy
+	 */
+	if (!SSLCipherSuites || strlen(SSLCipherSuites) == 0)
+	{
+		status = NSS_SetDomesticPolicy();
+		if (status != SECSuccess)
+			ereport(ERROR,
+					(errmsg("unable to set cipher policy: %s",
+							pg_SSLerrmessage(PR_GetError()))));
+	}
+	else
+	{
+		char	   *ciphers,
+				   *c;
+
+		char	   *sep = ":;, ";
+		PRUint16	ciphercode;
+		const		PRUint16 *nss_ciphers;
+
+		/*
+		 * If the user has specified a set of preferred cipher suites we start
+		 * by turning off all the existing suites to avoid the risk of down-
+		 * grades to a weaker cipher than expected.
+		 */
+		nss_ciphers = SSL_GetImplementedCiphers();
+		for (int i = 0; i < SSL_GetNumImplementedCiphers(); i++)
+			SSL_CipherPrefSet(model, nss_ciphers[i], PR_FALSE);
+
+		ciphers = pstrdup(SSLCipherSuites);
+
+		for (c = strtok(ciphers, sep); c; c = strtok(NULL, sep))
+		{
+			ciphercode = pg_find_cipher(c);
+			if (ciphercode != INVALID_CIPHER)
+			{
+				status = SSL_CipherPrefSet(model, ciphercode, PR_TRUE);
+				if (status != SECSuccess)
+					ereport(ERROR,
+							(errmsg("invalid cipher-suite specified: %s", c)));
+			}
+		}
+
+		pfree(ciphers);
+	}
+
+	if (SSL_VersionRangeSet(model, &desired_sslver) != SECSuccess)
+		ereport(ERROR,
+				(errmsg("unable to set requested SSL protocol version range")));
+
+	/*
+	 * Set up the custom IO layer.
+	 */
+	layer = init_iolayer(port, ERROR);
+	if (!layer)
+		goto error;
+
+	/* Store the Port as private data available in callbacks */
+	layer->secret = (void *) port;
+
+	if (PR_PushIOLayer(pr_fd, PR_TOP_IO_LAYER, layer) != PR_SUCCESS)
+	{
+		PR_Close(layer);
+		ereport(ERROR,
+				(errmsg("unable to push IO layer")));
+	}
+
+	/* TODO: set the postgres password callback param as callback function */
+	server_cert = PK11_FindCertFromNickname(ssl_cert_file, &pwdata /* password callback */ );
+	if (!server_cert)
+		ereport(ERROR,
+				(errmsg("unable to find certificate for \"%s\": %s",
+						ssl_cert_file, pg_SSLerrmessage(PR_GetError()))));
+
+	/* TODO: set the postgres password callback param as callback function */
+	private_key = PK11_FindKeyByAnyCert(server_cert, &pwdata /* password callback */ );
+	if (!private_key)
+		ereport(ERROR,
+				(errmsg("unable to find private key for \"%s\": %s",
+						ssl_cert_file, pg_SSLerrmessage(PR_GetError()))));
+
+	/*
+	 * NSS doesn't use CRL files on disk, so we use the ssl_crl_file guc to
+	 * contain the CRL nickname for the current server certificate in the NSS
+	 * certificate database. The main difference from the OpenSSL backend is
+	 * that NSS will use the CRL regardless, but being able to make sure the
+	 * CRL is loaded seems like a good feature.
+	 */
+	if (ssl_crl_file[0])
+	{
+		SECITEM_CopyItem(NULL, &crlname, &server_cert->derSubject);
+		crl = SEC_FindCrlByName(CERT_GetDefaultCertDB(), &crlname, SEC_CRL_TYPE);
+		if (!crl)
+			ereport(ERROR,
+					(errmsg("specified CRL not found in database")));
+		SEC_DestroyCrl(crl);
+	}
+
+	/*
+	 * Finally we must configure the socket for being a server by setting the
+	 * certificate and key.
+	 */
+	status = SSL_ConfigSecureServer(model, server_cert, private_key, kt_rsa);
+	if (status != SECSuccess)
+		ereport(ERROR,
+				(errmsg("unable to configure secure server: %s",
+						pg_SSLerrmessage(PR_GetError()))));
+	status = SSL_ConfigServerCert(model, server_cert, private_key, NULL, 0);
+	if (status != SECSuccess)
+		ereport(ERROR,
+				(errmsg("unable to configure server for TLS server connections: %s",
+						pg_SSLerrmessage(PR_GetError()))));
+
+	ssl_loaded_verify_locations = true;
+
+	/*
+	 * At this point, we no longer have use for the certificate and private
+	 * key as they have been copied into the context by NSS. Destroy our
+	 * copies explicitly to clean out the memory as best we can.
+	 */
+	CERT_DestroyCertificate(server_cert);
+	SECKEY_DestroyPrivateKey(private_key);
+
+	status = SSL_AuthCertificateHook(model, pg_cert_auth_handler, (void *) port);
+	if (status != SECSuccess)
+		ereport(ERROR,
+				(errmsg("unable to install authcert hook: %s",
+						pg_SSLerrmessage(PR_GetError()))));
+	SSL_BadCertHook(model, pg_bad_cert_handler, (void *) port);
+	SSL_OptionSet(model, SSL_REQUEST_CERTIFICATE, PR_TRUE);
+	SSL_OptionSet(model, SSL_REQUIRE_CERTIFICATE, PR_FALSE);
+
+	port->pr_fd = SSL_ImportFD(model, pr_fd);
+	if (!port->pr_fd)
+		ereport(ERROR,
+				(errmsg("unable to initialize")));
+
+	PR_Close(model);
+
+	/*
+	 * Force a handshake on the next I/O request, the second parameter means
+	 * that we are a server, PR_FALSE would indicate being a client. NSPR
+	 * requires us to call SSL_ResetHandshake since we imported an already
+	 * established socket.
+	 */
+	status = SSL_ResetHandshake(port->pr_fd, PR_TRUE);
+	if (status != SECSuccess)
+		ereport(ERROR,
+				(errmsg("unable to initiate handshake: %s",
+						pg_SSLerrmessage(PR_GetError()))));
+	status = SSL_ForceHandshake(port->pr_fd);
+	if (status != SECSuccess)
+		ereport(ERROR,
+				(errmsg("unable to handshake: %s",
+						pg_SSLerrmessage(PR_GetError()))));
+
+	port->ssl_in_use = true;
+	return 0;
+
+error:
+	return 1;
+}
+
+ssize_t
+be_tls_read(Port *port, void *ptr, size_t len, int *waitfor)
+{
+	ssize_t		n_read;
+	PRErrorCode err;
+
+	n_read = PR_Read(port->pr_fd, ptr, len);
+
+	if (n_read < 0)
+	{
+		err = PR_GetError();
+
+		/* XXX: This logic seems potentially bogus? */
+		if (err == PR_WOULD_BLOCK_ERROR)
+			*waitfor = WL_SOCKET_READABLE;
+		else
+			*waitfor = WL_SOCKET_WRITEABLE;
+	}
+
+	return n_read;
+}
+
+ssize_t
+be_tls_write(Port *port, void *ptr, size_t len, int *waitfor)
+{
+	ssize_t		n_write;
+	PRErrorCode err;
+
+	n_write = PR_Send(port->pr_fd, ptr, len, 0, PR_INTERVAL_NO_WAIT);
+
+	if (n_write < 0)
+	{
+		err = PR_GetError();
+
+		if (err == PR_WOULD_BLOCK_ERROR)
+			*waitfor = WL_SOCKET_WRITEABLE;
+		else
+			*waitfor = WL_SOCKET_READABLE;
+	}
+
+	return n_write;
+}
+
+void
+be_tls_close(Port *port)
+{
+	if (!port)
+		return;
+
+	if (port->peer_cn)
+	{
+		SSL_InvalidateSession(port->pr_fd);
+		pfree(port->peer_cn);
+		port->peer_cn = NULL;
+	}
+
+	PR_Close(port->pr_fd);
+	port->pr_fd = NULL;
+	port->ssl_in_use = false;
+
+	if (nss_context)
+	{
+		NSS_ShutdownContext(nss_context);
+		nss_context = NULL;
+	}
+}
+
+void
+be_tls_destroy(void)
+{
+	/*
+	 * It reads a bit odd to clear a session cache when we are destroying the
+	 * context altogether, but if the session cache isn't cleared before
+	 * shutting down the context it will fail with SEC_ERROR_BUSY.
+	 */
+	SSL_ClearSessionCache();
+}
+
+int
+be_tls_get_cipher_bits(Port *port)
+{
+	SECStatus	status;
+	SSLChannelInfo channel;
+	SSLCipherSuiteInfo suite;
+
+	status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+	if (status != SECSuccess)
+		goto error;
+
+	status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+	if (status != SECSuccess)
+		goto error;
+
+	return suite.effectiveKeyBits;
+
+error:
+	ereport(WARNING,
+			(errmsg("unable to extract TLS session information: %s",
+					pg_SSLerrmessage(PR_GetError()))));
+	return 0;
+}
+
+/*
+ * be_tls_get_compression
+ *
+ * NSS disabled support for TLS compression in version 3.33 and removed the
+ * code in a subsequent release. The API for retrieving information about
+ * compression as well as enabling it is kept for backwards compatibility, but
+ * we don't need to consult it since it was only available for SSLv3 which we
+ * don't support.
+ *
+ * https://bugzilla.mozilla.org/show_bug.cgi?id=1409587
+ */
+bool
+be_tls_get_compression(Port *port)
+{
+	return false;
+}
+
+const char *
+be_tls_get_version(Port *port)
+{
+	SECStatus	status;
+	SSLChannelInfo channel;
+
+	status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+	if (status != SECSuccess)
+	{
+		ereport(WARNING,
+				(errmsg("unable to extract TLS session information: %s",
+						pg_SSLerrmessage(PR_GetError()))));
+		return NULL;
+	}
+
+	return ssl_protocol_version_to_string(channel.protocolVersion);
+}
+
+const char *
+be_tls_get_cipher(Port *port)
+{
+	SECStatus	status;
+	SSLChannelInfo channel;
+	SSLCipherSuiteInfo suite;
+
+	status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+	if (status != SECSuccess)
+		goto error;
+
+	status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+	if (status != SECSuccess)
+		goto error;
+
+	return suite.cipherSuiteName;
+
+error:
+	ereport(WARNING,
+			(errmsg("unable to extract TLS session information: %s",
+					pg_SSLerrmessage(PR_GetError()))));
+	return NULL;
+}
+
+void
+be_tls_get_peer_subject_name(Port *port, char *ptr, size_t len)
+{
+	CERTCertificate *certificate;
+
+	certificate = SSL_PeerCertificate(port->pr_fd);
+	if (certificate)
+		strlcpy(ptr, CERT_NameToAscii(&certificate->subject), len);
+	else
+		ptr[0] = '\0';
+}
+
+void
+be_tls_get_peer_issuer_name(Port *port, char *ptr, size_t len)
+{
+	CERTCertificate *certificate;
+
+	certificate = SSL_PeerCertificate(port->pr_fd);
+	if (certificate)
+		strlcpy(ptr, CERT_NameToAscii(&certificate->issuer), len);
+	else
+		ptr[0] = '\0';
+}
+
+void
+be_tls_get_peer_serial(Port *port, char *ptr, size_t len)
+{
+	CERTCertificate *certificate;
+
+	certificate = SSL_PeerCertificate(port->pr_fd);
+	if (certificate)
+		snprintf(ptr, len, "%li", DER_GetInteger(&(certificate->serialNumber)));
+	else
+		ptr[0] = '\0';
+}
+
+static SECStatus
+pg_bad_cert_handler(void *arg, PRFileDesc * fd)
+{
+	Port	   *port = (Port *) arg;
+
+	port->peer_cert_valid = false;
+	return SECFailure;
+}
+
+static SECStatus
+pg_cert_auth_handler(void *arg, PRFileDesc * fd, PRBool checksig, PRBool isServer)
+{
+	SECStatus	status;
+	Port	   *port = (Port *) arg;
+	CERTCertificate *cert;
+	char	   *peer_cn;
+	int			len;
+
+	status = SSL_AuthCertificate(CERT_GetDefaultCertDB(), port->pr_fd, checksig, PR_TRUE);
+	if (status == SECSuccess)
+	{
+		cert = SSL_PeerCertificate(port->pr_fd);
+		len = strlen(cert->subjectName);
+		peer_cn = MemoryContextAllocZero(TopMemoryContext, len + 1);
+		if (strncmp(cert->subjectName, "CN=", 3) == 0)
+			strlcpy(peer_cn, cert->subjectName + strlen("CN="), len + 1);
+		else
+			strlcpy(peer_cn, cert->subjectName, len + 1);
+		CERT_DestroyCertificate(cert);
+
+		port->peer_cn = peer_cn;
+		port->peer_cert_valid = true;
+	}
+
+	return status;
+}
+
+/* ------------------------------------------------------------ */
+/*						Internal functions						*/
+/* ------------------------------------------------------------ */
+
+static PRInt32
+pg_ssl_read(PRFileDesc * fd, void *buf, PRInt32 amount, PRIntn flags,
+			PRIntervalTime timeout)
+{
+	PRRecvFN	read_fn;
+	PRInt32		n_read;
+
+	read_fn = fd->lower->methods->recv;
+	n_read = read_fn(fd->lower, buf, amount, flags, timeout);
+
+	return n_read;
+}
+
+static PRInt32
+pg_ssl_write(PRFileDesc * fd, const void *buf, PRInt32 amount, PRIntn flags,
+			 PRIntervalTime timeout)
+{
+	PRSendFN	send_fn;
+	PRInt32		n_write;
+
+	send_fn = fd->lower->methods->send;
+	n_write = send_fn(fd->lower, buf, amount, flags, timeout);
+
+	return n_write;
+}
+
+static PRFileDesc *
+init_iolayer(Port *port, int loglevel)
+{
+	const		PRIOMethods *default_methods;
+	PRFileDesc *layer;
+
+	/*
+	 * Start by initializing our layer with all the default methods so that we
+	 * can selectively override the ones we want while still ensuring that we
+	 * have a complete layer specification.
+	 */
+	default_methods = PR_GetDefaultIOMethods();
+	memcpy(&pr_iomethods, default_methods, sizeof(PRIOMethods));
+
+	pr_iomethods.recv = pg_ssl_read;
+	pr_iomethods.send = pg_ssl_write;
+
+	/*
+	 * Each IO layer must be identified by a unique name, where uniqueness is
+	 * per connection. Each connection in a postgres cluster can generate the
+	 * identity from the same string as they will create their IO layers on
+	 * different sockets. Only one layer per socket can have the same name.
+	 */
+	pr_id = PR_GetUniqueIdentity("PostgreSQL");
+	if (pr_id == PR_INVALID_IO_LAYER)
+	{
+		ereport(loglevel,
+				(errmsg("out of memory when setting up TLS connection")));
+		return NULL;
+	}
+
+	/*
+	 * Create the actual IO layer as a stub such that it can be pushed onto
+	 * the layer stack. The step via a stub is required as we define custom
+	 * callbacks.
+	 */
+	layer = PR_CreateIOLayerStub(pr_id, &pr_iomethods);
+	if (!layer)
+	{
+		ereport(loglevel,
+				(errmsg("unable to create NSS I/O layer")));
+		return NULL;
+	}
+
+	return layer;
+}
+
+static char *
+ssl_protocol_version_to_string(int v)
+{
+	switch (v)
+	{
+			/* SSL v2 and v3 are not supported */
+		case SSL_LIBRARY_VERSION_2:
+		case SSL_LIBRARY_VERSION_3_0:
+			Assert(false);
+			break;
+
+		case SSL_LIBRARY_VERSION_TLS_1_0:
+			return pstrdup("TLSv1.0");
+		case SSL_LIBRARY_VERSION_TLS_1_1:
+			return pstrdup("TLSv1.1");
+		case SSL_LIBRARY_VERSION_TLS_1_2:
+			return pstrdup("TLSv1.2");
+		case SSL_LIBRARY_VERSION_TLS_1_3:
+			return pstrdup("TLSv1.3");
+	}
+
+	return pstrdup("unknown");
+}
+
+
+/*
+ * ssl_protocol_version_to_nss
+ *			Translate PostgreSQL TLS version to NSS version
+ *
+ * Returns zero in case the requested TLS version is undefined (PG_ANY) and
+ * should be set by the caller, or -1 on failure.
+ */
+static uint16
+ssl_protocol_version_to_nss(int v, const char *guc_name)
+{
+	switch (v)
+	{
+			/*
+			 * There is no SSL_LIBRARY_ macro defined in NSS with the value
+			 * zero, so we use this to signal the caller that the highest
+			 * useful version should be set on the connection.
+			 */
+		case PG_TLS_ANY:
+			return 0;
+
+			/*
+			 * No guard is required here as there are no versions of NSS
+			 * without support for TLS1.
+			 */
+		case PG_TLS1_VERSION:
+			return SSL_LIBRARY_VERSION_TLS_1_0;
+		case PG_TLS1_1_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+			return SSL_LIBRARY_VERSION_TLS_1_1;
+#else
+			break;
+#endif
+		case PG_TLS1_2_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+			return SSL_LIBRARY_VERSION_TLS_1_2;
+#else
+			break;
+#endif
+		case PG_TLS1_3_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+			return SSL_LIBRARY_VERSION_TLS_1_3;
+#else
+			break;
+#endif
+		default:
+			break;
+	}
+
+	return -1;
+}
+
+/*
+ * pg_SSLerrmessage
+ *		Create and return a human readable error message given
+ *		the specified error code
+ *
+ * PR_ErrorToName only converts the enum identifier of the error to string,
+ * but that can be quite useful for debugging (and in case PR_ErrorToString is
+ * unable to render a message then we at least have something).
+ */
+static char *
+pg_SSLerrmessage(PRErrorCode errcode)
+{
+	char		error[128];
+	int			ret;
+
+	/* TODO: this should perhaps use a StringInfo instead.. */
+	ret = pg_snprintf(error, sizeof(error), "%s (%s)",
+					  PR_ErrorToString(errcode, PR_LANGUAGE_I_DEFAULT),
+					  PR_ErrorToName(errcode));
+	if (ret)
+		return pstrdup(error);
+
+	return pstrdup(_("unknown TLS error"));
+}
diff --git a/src/backend/libpq/be-secure-openssl.c b/src/backend/libpq/be-secure-openssl.c
index 8b21ff4065..5962cffc0c 100644
--- a/src/backend/libpq/be-secure-openssl.c
+++ b/src/backend/libpq/be-secure-openssl.c
@@ -1298,15 +1298,28 @@ X509_NAME_to_cstring(X509_NAME *name)
 	char	   *dp;
 	char	   *result;
 
+	if (membuf == NULL)
+		ereport(ERROR,
+				(errcode(ERRCODE_OUT_OF_MEMORY),
+				 errmsg("failed to create BIO")));
+
 	(void) BIO_set_close(membuf, BIO_CLOSE);
 	for (i = 0; i < count; i++)
 	{
 		e = X509_NAME_get_entry(name, i);
 		nid = OBJ_obj2nid(X509_NAME_ENTRY_get_object(e));
+		if (nid == NID_undef)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+					 errmsg("could not get NID for ASN1_OBJECT object")));
 		v = X509_NAME_ENTRY_get_data(e);
 		field_name = OBJ_nid2sn(nid);
 		if (!field_name)
 			field_name = OBJ_nid2ln(nid);
+		if (field_name == NULL)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+					 errmsg("could not convert NID %d to an ASN1_OBJECT structure", nid)));
 		BIO_printf(membuf, "/%s=", field_name);
 		ASN1_STRING_print_ex(membuf, v,
 							 ((ASN1_STRFLGS_RFC2253 & ~ASN1_STRFLGS_ESC_MSB)
@@ -1322,7 +1335,8 @@ X509_NAME_to_cstring(X509_NAME *name)
 	result = pstrdup(dp);
 	if (dp != sp)
 		pfree(dp);
-	BIO_free(membuf);
+	if (BIO_free(membuf) != 1)
+		elog(ERROR, "could not free OpenSSL BIO structure");
 
 	return result;
 }
diff --git a/src/backend/libpq/be-secure.c b/src/backend/libpq/be-secure.c
index 2ae507a902..f39977b80c 100644
--- a/src/backend/libpq/be-secure.c
+++ b/src/backend/libpq/be-secure.c
@@ -49,6 +49,9 @@ bool		ssl_passphrase_command_supports_reload;
 #ifdef USE_SSL
 bool		ssl_loaded_verify_locations = false;
 #endif
+#ifdef USE_NSS
+char	   *ssl_database;
+#endif
 
 /* GUC variable controlling SSL cipher list */
 char	   *SSLCipherSuites = NULL;
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index de87ad6ef7..33c3eebf48 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -4262,7 +4262,11 @@ static struct config_string ConfigureNamesString[] =
 		},
 		&ssl_library,
 #ifdef USE_SSL
+#if defined(USE_OPENSSL)
 		"OpenSSL",
+#elif defined(USE_NSS)
+		"NSS",
+#endif
 #else
 		"",
 #endif
@@ -4320,6 +4324,18 @@ static struct config_string ConfigureNamesString[] =
 		check_canonical_path, assign_pgstat_temp_directory, NULL
 	},
 
+#ifdef USE_NSS
+	{
+		{"ssl_database", PGC_SIGHUP, CONN_AUTH_SSL,
+			gettext_noop("Location of the NSS certificate database."),
+			NULL
+		},
+		&ssl_database,
+		"",
+		NULL, NULL, NULL
+	},
+#endif
+
 	{
 		{"synchronous_standby_names", PGC_SIGHUP, REPLICATION_PRIMARY,
 			gettext_noop("Number of synchronous standbys and list of names of potential synchronous ones."),
@@ -4348,8 +4364,10 @@ static struct config_string ConfigureNamesString[] =
 			GUC_SUPERUSER_ONLY
 		},
 		&SSLCipherSuites,
-#ifdef USE_OPENSSL
+#if defined(USE_OPENSSL)
 		"HIGH:MEDIUM:+3DES:!aNULL",
+#elif defined (USE_NSS)
+		"",
 #else
 		"none",
 #endif
diff --git a/src/include/common/pg_nss.h b/src/include/common/pg_nss.h
new file mode 100644
index 0000000000..74298c8bb1
--- /dev/null
+++ b/src/include/common/pg_nss.h
@@ -0,0 +1,141 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_nss.h
+ *	  Support for NSS as a TLS backend
+ *
+ * These definitions are used by both frontend and backend code.
+ *
+ * Copyright (c) 2020, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *        src/include/common/pg_nss.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_NSS_H
+#define PG_NSS_H
+
+#ifdef USE_NSS
+
+#include <sslproto.h>
+
+PRUint16	pg_find_cipher(char *name);
+
+typedef struct
+{
+	const char *name;
+	PRUint16	number;
+}			NSSCiphers;
+
+#define INVALID_CIPHER	0xFFFF
+
+/*
+ * This list is a partial copy of the ciphers in NSS files lib/ssl/sslproto.h
+ * in order to provide a human readable version of the ciphers. It would be
+ * nice to not have to have this, but NSS doesn't provide any API addressing
+ * the ciphers by name. TODO: do we want more of the ciphers, or perhaps less?
+ */
+static const NSSCiphers NSS_CipherList[] = {
+
+	{"TLS_NULL_WITH_NULL_NULL", TLS_NULL_WITH_NULL_NULL},
+
+	{"TLS_RSA_WITH_NULL_MD5", TLS_RSA_WITH_NULL_MD5},
+	{"TLS_RSA_WITH_NULL_SHA", TLS_RSA_WITH_NULL_SHA},
+	{"TLS_RSA_WITH_RC4_128_MD5", TLS_RSA_WITH_RC4_128_MD5},
+	{"TLS_RSA_WITH_RC4_128_SHA", TLS_RSA_WITH_RC4_128_SHA},
+	{"TLS_RSA_WITH_IDEA_CBC_SHA", TLS_RSA_WITH_IDEA_CBC_SHA},
+	{"TLS_RSA_WITH_DES_CBC_SHA", TLS_RSA_WITH_DES_CBC_SHA},
+	{"TLS_RSA_WITH_3DES_EDE_CBC_SHA", TLS_RSA_WITH_3DES_EDE_CBC_SHA},
+
+	{"TLS_DH_DSS_WITH_DES_CBC_SHA", TLS_DH_DSS_WITH_DES_CBC_SHA},
+	{"TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA", TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA},
+	{"TLS_DH_RSA_WITH_DES_CBC_SHA", TLS_DH_RSA_WITH_DES_CBC_SHA},
+	{"TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA", TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA},
+
+	{"TLS_DHE_DSS_WITH_DES_CBC_SHA", TLS_DHE_DSS_WITH_DES_CBC_SHA},
+	{"TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA", TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA},
+	{"TLS_DHE_RSA_WITH_DES_CBC_SHA", TLS_DHE_RSA_WITH_DES_CBC_SHA},
+	{"TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA", TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA},
+
+	{"TLS_DH_anon_WITH_RC4_128_MD5", TLS_DH_anon_WITH_RC4_128_MD5},
+	{"TLS_DH_anon_WITH_DES_CBC_SHA", TLS_DH_anon_WITH_DES_CBC_SHA},
+	{"TLS_DH_anon_WITH_3DES_EDE_CBC_SHA", TLS_DH_anon_WITH_3DES_EDE_CBC_SHA},
+
+	{"TLS_RSA_WITH_AES_128_CBC_SHA", TLS_RSA_WITH_AES_128_CBC_SHA},
+	{"TLS_DH_DSS_WITH_AES_128_CBC_SHA", TLS_DH_DSS_WITH_AES_128_CBC_SHA},
+	{"TLS_DH_RSA_WITH_AES_128_CBC_SHA", TLS_DH_RSA_WITH_AES_128_CBC_SHA},
+	{"TLS_DHE_DSS_WITH_AES_128_CBC_SHA", TLS_DHE_DSS_WITH_AES_128_CBC_SHA},
+	{"TLS_DHE_RSA_WITH_AES_128_CBC_SHA", TLS_DHE_RSA_WITH_AES_128_CBC_SHA},
+	{"TLS_DH_anon_WITH_AES_128_CBC_SHA", TLS_DH_anon_WITH_AES_128_CBC_SHA},
+
+	{"TLS_RSA_WITH_AES_256_CBC_SHA", TLS_RSA_WITH_AES_256_CBC_SHA},
+	{"TLS_DH_DSS_WITH_AES_256_CBC_SHA", TLS_DH_DSS_WITH_AES_256_CBC_SHA},
+	{"TLS_DH_RSA_WITH_AES_256_CBC_SHA", TLS_DH_RSA_WITH_AES_256_CBC_SHA},
+	{"TLS_DHE_DSS_WITH_AES_256_CBC_SHA", TLS_DHE_DSS_WITH_AES_256_CBC_SHA},
+	{"TLS_DHE_RSA_WITH_AES_256_CBC_SHA", TLS_DHE_RSA_WITH_AES_256_CBC_SHA},
+	{"TLS_DH_anon_WITH_AES_256_CBC_SHA", TLS_DH_anon_WITH_AES_256_CBC_SHA},
+	{"TLS_RSA_WITH_NULL_SHA256", TLS_RSA_WITH_NULL_SHA256},
+	{"TLS_RSA_WITH_AES_128_CBC_SHA256", TLS_RSA_WITH_AES_128_CBC_SHA256},
+	{"TLS_RSA_WITH_AES_256_CBC_SHA256", TLS_RSA_WITH_AES_256_CBC_SHA256},
+
+	{"TLS_DHE_DSS_WITH_AES_128_CBC_SHA256", TLS_DHE_DSS_WITH_AES_128_CBC_SHA256},
+	{"TLS_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_RSA_WITH_CAMELLIA_128_CBC_SHA},
+	{"TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA},
+	{"TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA},
+	{"TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA", TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA},
+	{"TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA},
+	{"TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA},
+
+	{"TLS_DHE_DSS_WITH_RC4_128_SHA", TLS_DHE_DSS_WITH_RC4_128_SHA},
+	{"TLS_DHE_RSA_WITH_AES_128_CBC_SHA256", TLS_DHE_RSA_WITH_AES_128_CBC_SHA256},
+	{"TLS_DHE_DSS_WITH_AES_256_CBC_SHA256", TLS_DHE_DSS_WITH_AES_256_CBC_SHA256},
+	{"TLS_DHE_RSA_WITH_AES_256_CBC_SHA256", TLS_DHE_RSA_WITH_AES_256_CBC_SHA256},
+
+	{"TLS_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_RSA_WITH_CAMELLIA_256_CBC_SHA},
+	{"TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA},
+	{"TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA},
+	{"TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA", TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA},
+	{"TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA},
+	{"TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA},
+
+	{"TLS_RSA_WITH_SEED_CBC_SHA", TLS_RSA_WITH_SEED_CBC_SHA},
+
+	{"TLS_RSA_WITH_AES_128_GCM_SHA256", TLS_RSA_WITH_AES_128_GCM_SHA256},
+	{"TLS_RSA_WITH_AES_256_GCM_SHA384", TLS_RSA_WITH_AES_256_GCM_SHA384},
+	{"TLS_DHE_RSA_WITH_AES_128_GCM_SHA256", TLS_DHE_RSA_WITH_AES_128_GCM_SHA256},
+	{"TLS_DHE_RSA_WITH_AES_256_GCM_SHA384", TLS_DHE_RSA_WITH_AES_256_GCM_SHA384},
+	{"TLS_DHE_DSS_WITH_AES_128_GCM_SHA256", TLS_DHE_DSS_WITH_AES_128_GCM_SHA256},
+	{"TLS_DHE_DSS_WITH_AES_256_GCM_SHA384", TLS_DHE_DSS_WITH_AES_256_GCM_SHA384},
+
+	{"TLS_AES_128_GCM_SHA256", TLS_AES_128_GCM_SHA256},
+	{"TLS_AES_256_GCM_SHA384", TLS_AES_256_GCM_SHA384},
+	{"TLS_CHACHA20_POLY1305_SHA256", TLS_CHACHA20_POLY1305_SHA256},
+	{NULL, 0}
+};
+
+/*
+ * pg_find_cipher
+ *			Translate an NSS ciphername to the cipher code
+ *
+ * Searches the configured ciphers for the corresponding cipher code to the
+ * name. Search is performed case insensitive.
+ */
+PRUint16
+pg_find_cipher(char *name)
+{
+	const		NSSCiphers *cipher_list = NSS_CipherList;
+
+	while (cipher_list->name)
+	{
+		if (pg_strcasecmp(cipher_list->name, name) == 0)
+			return cipher_list->number;
+
+		cipher_list++;
+	}
+
+	return 0xFFFF;
+}
+
+#endif							/* USE_NSS */
+
+#endif							/* PG_NSS_H */
diff --git a/src/include/libpq/libpq-be.h b/src/include/libpq/libpq-be.h
index 179ebaa104..6211510fab 100644
--- a/src/include/libpq/libpq-be.h
+++ b/src/include/libpq/libpq-be.h
@@ -192,13 +192,18 @@ typedef struct Port
 	bool		peer_cert_valid;
 
 	/*
-	 * OpenSSL structures. (Keep these last so that the locations of other
-	 * fields are the same whether or not you build with OpenSSL.)
+	 * SSL backend specific structures. (Keep these last so that the locations
+	 * of other fields are the same whether or not you build with SSL
+	 * enabled.)
 	 */
 #ifdef USE_OPENSSL
 	SSL		   *ssl;
 	X509	   *peer;
 #endif
+
+#ifdef USE_NSS
+	void	   *pr_fd;
+#endif
 } Port;
 
 #ifdef USE_SSL
diff --git a/src/include/libpq/libpq.h b/src/include/libpq/libpq.h
index b1152475ac..298d87ecae 100644
--- a/src/include/libpq/libpq.h
+++ b/src/include/libpq/libpq.h
@@ -88,6 +88,9 @@ extern PGDLLIMPORT bool ssl_passphrase_command_supports_reload;
 #ifdef USE_SSL
 extern bool ssl_loaded_verify_locations;
 #endif
+#ifdef USE_NSS
+extern char *ssl_database;
+#endif
 
 extern int	secure_initialize(bool isServerStart);
 extern bool secure_loaded_verify_locations(void);
diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in
index fb270df678..31f808398c 100644
--- a/src/include/pg_config.h.in
+++ b/src/include/pg_config.h.in
@@ -893,6 +893,9 @@
 /* Define to 1 to build with PAM support. (--with-pam) */
 #undef USE_PAM
 
+/* Define to build with NSS support (--with-nss) */
+#undef USE_NSS
+
 /* Define to 1 to use software CRC-32C implementation (slicing-by-8). */
 #undef USE_SLICING_BY_8_CRC32C
 
diff --git a/src/include/pg_config_manual.h b/src/include/pg_config_manual.h
index 705dc69c06..c28b84126d 100644
--- a/src/include/pg_config_manual.h
+++ b/src/include/pg_config_manual.h
@@ -176,10 +176,9 @@
 
 /*
  * USE_SSL code should be compiled only when compiling with an SSL
- * implementation.  (Currently, only OpenSSL is supported, but we might add
- * more implementations in the future.)
+ * implementation.
  */
-#ifdef USE_OPENSSL
+#if defined(USE_OPENSSL) || defined(USE_NSS)
 #define USE_SSL
 #endif
 
diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile
index d4919970f8..97821fb39b 100644
--- a/src/interfaces/libpq/Makefile
+++ b/src/interfaces/libpq/Makefile
@@ -57,6 +57,10 @@ OBJS += \
 	fe-secure-gssapi.o
 endif
 
+ifeq ($(with_nss), yes)
+OBJS += fe-secure-nss.o
+endif
+
 ifeq ($(PORTNAME), cygwin)
 override shlib = cyg$(NAME)$(DLSUFFIX)
 endif
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index 7bee9dd201..2814eb8ddd 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -354,6 +354,10 @@ static const internalPQconninfoOption PQconninfoOptions[] = {
 		"Target-Session-Attrs", "", 11, /* sizeof("read-write") = 11 */
 	offsetof(struct pg_conn, target_session_attrs)},
 
+	{"cert_database", NULL, NULL, NULL,
+		"CertificateDatabase", "", 64,
+	offsetof(struct pg_conn, cert_database)},
+
 	/* Terminating entry --- MUST BE LAST */
 	{NULL, NULL, NULL, NULL,
 	NULL, NULL, 0}
diff --git a/src/interfaces/libpq/fe-secure-nss.c b/src/interfaces/libpq/fe-secure-nss.c
new file mode 100644
index 0000000000..6401949136
--- /dev/null
+++ b/src/interfaces/libpq/fe-secure-nss.c
@@ -0,0 +1,975 @@
+/*-------------------------------------------------------------------------
+ *
+ * fe-secure-nss.c
+ *	  functions for supporting NSS as a TLS backend for frontend libpq
+ *
+ * Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ *	  src/interfaces/libpq/fe-secure-nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres_fe.h"
+
+#include "libpq-fe.h"
+#include "fe-auth.h"
+#include "libpq-int.h"
+
+/*
+ * BITS_PER_BYTE is also defined in the NSPR header fils, so we need to undef
+ * our version to avoid compiler warnings on redefinition.
+ */
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+
+/*
+ * The nspr/obsolete/protypes.h NSPR header typedefs uint64 and int64 with
+ * colliding definitions from ours, causing a much expected compiler error.
+ * The definitions are however not actually used in NSPR at all, and are only
+ * intended for what seems to be backwards compatibility for apps written
+ * against old versions of NSPR.  The following comment is in the referenced
+ * file, and was added in 1998:
+ *
+ *		This section typedefs the old 'native' types to the new PR<type>s.
+ *		These definitions are scheduled to be eliminated at the earliest
+ *		possible time. The NSPR API is implemented and documented using
+ *		the new definitions.
+ *
+ * As there is no opt-out from pulling in these typedefs, we define the guard
+ * for the file to exclude it. This is incredibly ugly, but seems to be about
+ * the only way around it.
+ */
+#define PROTYPES_H
+#include <nspr.h>
+#undef PROTYPES_H
+#include <nss.h>
+#include <ssl.h>
+#include <sslproto.h>
+#include <pk11func.h>
+#include <prerror.h>
+#include <prinit.h>
+#include <prio.h>
+#include <secerr.h>
+#include <secmod.h>
+
+/*
+ * Ensure that the colliding definitions match, else throw an error. In case
+ * NSPR remove the definition in a future version (however unlikely that may
+ * be, make sure to put ours back again.
+ */
+#if defined(BITS_PER_BYTE)
+#if BITS_PER_BYTE != pg_BITS_PER_BYTE
+#error "incompatible byte widths between NSPR and PostgreSQL"
+#endif
+#else
+#define BITS_PER_BYTE pg_BITS_PER_BYTE
+#endif
+#undef pg_BITS_PER_BYTE
+
+static SECStatus pg_load_nss_module(SECMODModule * *module, const char *library, const char *name);
+static SECStatus pg_bad_cert_handler(void *arg, PRFileDesc * fd);
+static char *pg_SSLerrmessage(PRErrorCode errcode);
+static SECStatus pg_client_auth_handler(void *arg, PRFileDesc * socket, CERTDistNames * caNames,
+										CERTCertificate * *pRetCert, SECKEYPrivateKey * *pRetKey);
+static SECStatus pg_cert_auth_handler(void *arg, PRFileDesc * fd, PRBool checksig, PRBool isServer);
+static int	ssl_protocol_version_to_nss(const char *protocol);
+static bool cert_database_has_CA(PGconn *conn);
+
+static char *PQssl_passwd_cb(PK11SlotInfo * slot, PRBool retry, void *arg);
+
+/*
+ * PR_ImportTCPSocket() is a private API, but very widely used, as it's the
+ * only way to make NSS use an already set up POSIX file descriptor rather
+ * than opening one itself. To quote the NSS documentation:
+ *
+ *		"In theory, code that uses PR_ImportTCPSocket may break when NSPR's
+ *		implementation changes. In practice, this is unlikely to happen because
+ *		NSPR's implementation has been stable for years and because of NSPR's
+ *		strong commitment to backward compatibility."
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR/Reference/PR_ImportTCPSocket
+ *
+ * The function is declared in <private/pprio.h>, but as it is a header marked
+ * private we declare it here rather than including it.
+ */
+NSPR_API(PRFileDesc *) PR_ImportTCPSocket(int);
+
+static SECMODModule * ca_trust = NULL;
+static NSSInitContext * nss_context = NULL;
+
+/*
+ * Track whether the NSS database has a password set or not. There is no API
+ * function for retrieving password status, so we simply flip this to true in
+ * case NSS invoked the password callback - as that will only happen in case
+ * there is a password. The reason for tracking this is that there are calls
+ * which require a password parameter, but doesn't use the callbacks provided,
+ * so we must call the callback on behalf of these.
+ */
+static bool has_password = false;
+
+#if defined(WIN32)
+static const char *ca_trust_name = "nssckbi.dll";
+#elif defined(__darwin__)
+static const char *ca_trust_name = "libnssckbi.dylib";
+#else
+static const char *ca_trust_name = "libnssckbi.so";
+#endif
+
+static PQsslKeyPassHook_nss_type PQsslKeyPassHook = NULL;
+
+/* ------------------------------------------------------------ */
+/*			 Procedures common to all secure sessions			*/
+/* ------------------------------------------------------------ */
+
+void
+pgtls_init_library(bool do_ssl, int do_crypto)
+{
+	/* TODO: implement me .. */
+}
+
+int
+pgtls_init(PGconn *conn)
+{
+	conn->ssl_in_use = false;
+
+	return 0;
+}
+
+void
+pgtls_close(PGconn *conn)
+{
+	if (nss_context)
+	{
+		NSS_ShutdownContext(nss_context);
+		nss_context = NULL;
+	}
+}
+
+PostgresPollingStatusType
+pgtls_open_client(PGconn *conn)
+{
+	SECStatus	status;
+	PRFileDesc *pr_fd;
+	PRFileDesc *model;
+	NSSInitParameters params;
+	SSLVersionRange desired_range;
+
+	/*
+	 * The NSPR documentation states that runtime initialization via PR_Init
+	 * is no longer required, as the first caller into NSPR will perform the
+	 * initialization implicitly. The documentation doesn't however clarify
+	 * from which version this is holds true, so let's perform the potentially
+	 * superfluous initialization anyways to avoid crashing on older versions
+	 * of NSPR, as there is no difference in overhead.  The NSS documentation
+	 * still states that PR_Init must be called in some way (implicitly or
+	 * explicitly).
+	 *
+	 * The below parameters are what the implicit initialization would've done
+	 * for us, and should work even for older versions where it might not be
+	 * done automatically. The last parameter, maxPTDs, is set to various
+	 * values in other codebases, but has been unused since NSPR 2.1 which was
+	 * released sometime in 1998.
+	 */
+	PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0);
+
+	/*
+	 * The original design of NSS was for a single application to use a single
+	 * copy of it, initialized with NSS_Initialize() which isn't returning any
+	 * handle with which to refer to NSS. NSS initialization and shutdown are
+	 * global for the application, so a shutdown in another NSS enabled
+	 * library would cause NSS to be stopped for libpq as well.  The fix has
+	 * been to introduce NSS_InitContext which returns a context handle to
+	 * pass to NSS_ShutdownContext.  NSS_InitContext was introduced in NSS
+	 * 3.12, but the use of it is not very well documented.
+	 * https://bugzilla.redhat.com/show_bug.cgi?id=738456
+	 *
+	 * The InitParameters struct passed can be used to override internal
+	 * values in NSS, but the usage is not documented at all. When using
+	 * NSS_Init initializations, the values are instead set via PK11_Configure
+	 * calls so the PK11_Configure documentation can be used to glean some
+	 * details on these.
+	 *
+	 * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/PKCS11/Module_Specs
+	 */
+	memset(&params, 0, sizeof(params));
+	params.length = sizeof(params);
+
+	if (conn->cert_database && strlen(conn->cert_database) > 0)
+	{
+		char	   *cert_database_path = psprintf("sql:%s", conn->cert_database);
+
+		nss_context = NSS_InitContext(cert_database_path, "", "", "",
+									  &params,
+									  NSS_INIT_READONLY | NSS_INIT_PK11RELOAD);
+		pfree(cert_database_path);
+	}
+	else
+		nss_context = NSS_InitContext("", "", "", "", &params,
+									  NSS_INIT_READONLY | NSS_INIT_NOCERTDB |
+									  NSS_INIT_NOMODDB | NSS_INIT_FORCEOPEN |
+									  NSS_INIT_NOROOTINIT | NSS_INIT_PK11RELOAD);
+
+	if (!nss_context)
+	{
+		char	   *err = pg_SSLerrmessage(PR_GetError());
+
+		printfPQExpBuffer(&conn->errorMessage,
+						  libpq_gettext("unable to %s certificate database: %s"),
+						  conn->cert_database ? "open" : "create",
+						  err);
+		free(err);
+		return PGRES_POLLING_FAILED;
+	}
+
+	/*
+	 * Configure cipher policy.
+	 */
+	status = NSS_SetDomesticPolicy();
+	if (status != SECSuccess)
+	{
+		char	   *err = pg_SSLerrmessage(PR_GetError());
+
+		printfPQExpBuffer(&conn->errorMessage,
+						  libpq_gettext("unable to configure cipher policy: %s"),
+						  err);
+		free(err);
+		return PGRES_POLLING_FAILED;
+	}
+
+	/*
+	 * If we don't have a certificate database, the system trust store is the
+	 * fallback we can use. If we fail to initialize that as well, we can
+	 * still attempt a connection as long as the sslmode isn't verify*.
+	 */
+	if (!conn->cert_database && conn->sslmode[0] == 'v')
+	{
+		status = pg_load_nss_module(&ca_trust, ca_trust_name, "\"Root Certificates\"");
+		/* status = pg_load_nss_module(&ca_trust, ca_trust_name, "trust"); */
+		if (status != SECSuccess)
+		{
+			char	   *err = pg_SSLerrmessage(PR_GetError());
+
+			printfPQExpBuffer(&conn->errorMessage,
+							  libpq_gettext("WARNING: unable to load NSS trust module \"%s\" : %s"), ca_trust_name, err);
+			return PGRES_POLLING_FAILED;
+		}
+	}
+
+
+	PK11_SetPasswordFunc(PQssl_passwd_cb);
+
+	/*
+	 * Import the already opened socket as we don't want to use NSPR functions
+	 * for opening the network socket due to how the PostgreSQL protocol works
+	 * with TLS connections. This function is not part of the NSPR public API,
+	 * see the comment at the top of the file for the rationale of still using
+	 * it.
+	 */
+	pr_fd = PR_ImportTCPSocket(conn->sock);
+	if (!pr_fd)
+	{
+		printfPQExpBuffer(&conn->errorMessage,
+						  libpq_gettext("unable to attach to socket: %s"),
+						  pg_SSLerrmessage(PR_GetError()));
+		return PGRES_POLLING_FAILED;
+	}
+
+	/*
+	 * Most of the documentation available, and implementations of, NSS/NSPR
+	 * use the PR_NewTCPSocket() function here, which has the drawback that it
+	 * can only create IPv4 sockets. Instead use PR_OpenTCPSocket() which
+	 * copes with IPv6 as well.
+	 */
+	model = SSL_ImportFD(NULL, PR_OpenTCPSocket(conn->laddr.addr.ss_family));
+	if (!model)
+	{
+		printfPQExpBuffer(&conn->errorMessage,
+						  libpq_gettext("unable to enable TLS: %s"),
+						  pg_SSLerrmessage(PR_GetError()));
+		return PGRES_POLLING_FAILED;
+	}
+
+	/* Disable old protocol versions (SSLv2 and SSLv3) */
+	SSL_OptionSet(model, SSL_ENABLE_SSL2, PR_FALSE);
+	SSL_OptionSet(model, SSL_V2_COMPATIBLE_HELLO, PR_FALSE);
+	SSL_OptionSet(model, SSL_ENABLE_SSL3, PR_FALSE);
+
+#ifdef SSL_CBC_RANDOM_IV
+
+	/*
+	 * Enable protection against the BEAST attack in case the NSS library has
+	 * support for that. While SSLv3 is disabled, we may still allow TLSv1
+	 * which is affected. The option isn't documented as an SSL option, but as
+	 * an NSS environment variable.
+	 */
+	SSL_OptionSet(model, SSL_CBC_RANDOM_IV, PR_TRUE);
+#endif
+
+	/* Set us up as a TLS client for the handshake */
+	SSL_OptionSet(model, SSL_HANDSHAKE_AS_CLIENT, PR_TRUE);
+
+	/*
+	 * When setting the available protocols, we either use the user defined
+	 * configuration values, and if missing we accept whatever is the highest
+	 * version supported by the library as the max and only limit the range in
+	 * the other end at TLSv1.0. ssl_variant_stream is a ProtocolVariant enum
+	 * for Stream protocols, rather than datagram.
+	 */
+	SSL_VersionRangeGetSupported(ssl_variant_stream, &desired_range);
+	desired_range.min = SSL_LIBRARY_VERSION_TLS_1_0;
+
+	if (conn->ssl_min_protocol_version && strlen(conn->ssl_min_protocol_version) > 0)
+	{
+		int			ssl_min_ver = ssl_protocol_version_to_nss(conn->ssl_min_protocol_version);
+
+		if (ssl_min_ver == -1)
+		{
+			printfPQExpBuffer(&conn->errorMessage,
+							  libpq_gettext("invalid value \"%s\" for minimum version of SSL protocol\n"),
+							  conn->ssl_min_protocol_version);
+			return -1;
+		}
+
+		desired_range.min = ssl_min_ver;
+	}
+
+	if (conn->ssl_max_protocol_version && strlen(conn->ssl_max_protocol_version) > 0)
+	{
+		int			ssl_max_ver = ssl_protocol_version_to_nss(conn->ssl_max_protocol_version);
+
+		if (ssl_max_ver == -1)
+		{
+			printfPQExpBuffer(&conn->errorMessage,
+							  libpq_gettext("invalid value \"%s\" for maximum version of SSL protocol\n"),
+							  conn->ssl_max_protocol_version);
+			return -1;
+		}
+
+		desired_range.max = ssl_max_ver;
+	}
+
+	if (SSL_VersionRangeSet(model, &desired_range) != SECSuccess)
+	{
+		printfPQExpBuffer(&conn->errorMessage,
+						  libpq_gettext("unable to set allowed SSL protocol version range: %s"),
+						  pg_SSLerrmessage(PR_GetError()));
+		return PGRES_POLLING_FAILED;
+	}
+
+	/*
+	 * Set up callback for verifying server certificates, as well as for how
+	 * to handle failed verifications.
+	 */
+	SSL_AuthCertificateHook(model, pg_cert_auth_handler, (void *) conn);
+	SSL_BadCertHook(model, pg_bad_cert_handler, (void *) conn);
+
+	/*
+	 * Convert the NSPR socket to an SSL socket. Ensuring the success of this
+	 * operation is critical as NSS SSL_* functions may return SECSuccess on
+	 * the socket even though SSL hasn't been enabled, which introduce a risk
+	 * of silent downgrades.
+	 */
+	conn->pr_fd = SSL_ImportFD(model, pr_fd);
+	if (!conn->pr_fd)
+	{
+		printfPQExpBuffer(&conn->errorMessage,
+						  libpq_gettext("unable to configure client for TLS: %s"),
+						  pg_SSLerrmessage(PR_GetError()));
+		return PGRES_POLLING_FAILED;
+	}
+
+	/*
+	 * The model can now we closed as we've applied the settings of the model
+	 * onto the real socket. From hereon we should only use conn->pr_fd.
+	 */
+	PR_Close(model);
+
+	/* Set the private data to be passed to the password callback */
+	SSL_SetPKCS11PinArg(conn->pr_fd, (void *) conn);
+
+	/*
+	 * If a CRL file has been specified, verify if it exists in the database
+	 * but don't fail in case it doesn't.
+	 */
+	if (conn->sslcrl && strlen(conn->sslcrl) > 0)
+	{
+		/* XXX: Implement me.. */
+	}
+
+	status = SSL_ResetHandshake(conn->pr_fd, PR_FALSE);
+	if (status != SECSuccess)
+	{
+		printfPQExpBuffer(&conn->errorMessage,
+						  libpq_gettext("unable to initiate handshake: %s"),
+						  pg_SSLerrmessage(PR_GetError()));
+		return PGRES_POLLING_FAILED;
+	}
+
+	/*
+	 * Set callback for client authentication when requested by the server.
+	 */
+	SSL_GetClientAuthDataHook(conn->pr_fd, pg_client_auth_handler, (void *) conn);
+
+	/*
+	 * Specify which hostname we are expecting to talk to. This is required,
+	 * albeit mostly applies to when opening a connection to a traditional
+	 * http server it seems.
+	 */
+	SSL_SetURL(conn->pr_fd, (conn->connhost[conn->whichhost]).host);
+
+	do
+	{
+		status = SSL_ForceHandshake(conn->pr_fd);
+	}
+	while (status != SECSuccess && PR_GetError() == PR_WOULD_BLOCK_ERROR);
+
+	if (status != SECSuccess)
+	{
+		printfPQExpBuffer(&conn->errorMessage,
+						  libpq_gettext("SSL error: %s"),
+						  pg_SSLerrmessage(PR_GetError()));
+		return PGRES_POLLING_FAILED;
+	}
+
+	conn->ssl_in_use = true;
+	return PGRES_POLLING_OK;
+}
+
+ssize_t
+pgtls_read(PGconn *conn, void *ptr, size_t len)
+{
+	PRInt32		nread;
+	PRErrorCode status;
+	int			read_errno = 0;
+
+	nread = PR_Recv(conn->pr_fd, ptr, len, 0, PR_INTERVAL_NO_WAIT);
+
+	/*
+	 * PR_Recv blocks until there is data to read or the timeout expires. Zero
+	 * is returned for closed connections, while -1 indicates an error within
+	 * the ongoing connection.
+	 */
+	if (nread == 0)
+	{
+		read_errno = ECONNRESET;
+		return -1;
+	}
+
+	if (nread == -1)
+	{
+		status = PR_GetError();
+
+		switch (status)
+		{
+			case PR_WOULD_BLOCK_ERROR:
+				read_errno = EINTR;
+				break;
+
+			case PR_IO_TIMEOUT_ERROR:
+				break;
+
+				/*
+				 * The error cases for PR_Recv are not documented, but can be
+				 * reverse engineered from _MD_unix_map_default_error() in the
+				 * NSPR code, defined in pr/src/md/unix/unix_errors.c.
+				 */
+			default:
+				printfPQExpBuffer(&conn->errorMessage,
+								  libpq_gettext("TLS read error: %s"),
+								  pg_SSLerrmessage(status));
+				break;
+		}
+	}
+
+	SOCK_ERRNO_SET(read_errno);
+	return (ssize_t) nread;
+}
+
+/*
+ * pgtls_read_pending
+ *		Check for the existence of data to be read.
+ *
+ * This is part of the PostgreSQL TLS backend API.
+ */
+bool
+pgtls_read_pending(PGconn *conn)
+{
+	unsigned char c;
+	int			n;
+
+	/*
+	 * PR_Recv peeks into the stream with the timeount turned off, to see if
+	 * there is another byte to read off the wire. There is an NSS function
+	 * SSL_DataPending() which might seem like a better fit, but it will only
+	 * check already encrypted data in the SSL buffer, not still unencrypted
+	 * data, thus it doesn't guarantee that a subsequent call to
+	 * PR_Read/PR_Recv wont block.
+	 */
+	n = PR_Recv(conn->pr_fd, &c, 1, PR_MSG_PEEK, PR_INTERVAL_NO_WAIT);
+	return (n > 0);
+}
+
+ssize_t
+pgtls_write(PGconn *conn, const void *ptr, size_t len)
+{
+	PRInt32		n;
+	PRErrorCode status;
+	int			write_errno = 0;
+
+	n = PR_Write(conn->pr_fd, ptr, len);
+
+	if (n < 0)
+	{
+		status = PR_GetError();
+
+		switch (status)
+		{
+			case PR_WOULD_BLOCK_ERROR:
+#ifdef EAGAIN
+				write_errno = EAGAIN;
+#else
+				write_errno = EINTR;
+#endif
+				break;
+
+			default:
+				printfPQExpBuffer(&conn->errorMessage,
+								  libpq_gettext("TLS write error: %s"),
+								  pg_SSLerrmessage(status));
+				write_errno = ECONNRESET;
+				break;
+		}
+	}
+
+	SOCK_ERRNO_SET(write_errno);
+	return (ssize_t) n;
+}
+
+/*
+ *	Verify that the server certificate matches the hostname we connected to.
+ *
+ * The certificate's Common Name and Subject Alternative Names are considered.
+ */
+int
+pgtls_verify_peer_name_matches_certificate_guts(PGconn *conn,
+												int *names_examined,
+												char **first_name)
+{
+	return 1;
+}
+
+/* ------------------------------------------------------------ */
+/*			PostgreSQL specific TLS support functions			*/
+/* ------------------------------------------------------------ */
+
+/*
+ * TODO: this a 99% copy of the same function in the backend, make these share
+ * a single implementation instead.
+ */
+static char *
+pg_SSLerrmessage(PRErrorCode errcode)
+{
+	const char *error;
+
+	error = PR_ErrorToName(errcode);
+	if (error)
+		return strdup(error);
+
+	return strdup("unknown TLS error");
+}
+
+static SECStatus
+pg_load_nss_module(SECMODModule * *module, const char *library, const char *name)
+{
+	SECMODModule *mod;
+	char	   *modulespec;
+
+	modulespec = psprintf("library=\"%s\", name=\"%s\"", library, name);
+
+	/*
+	 * Attempt to load the specified module. The second parameter is "parent"
+	 * which should always be NULL for application code. The third parameter
+	 * defines if loading should recurse which is only applicable when loading
+	 * a module from within another module. This hierarchy would have to be
+	 * defined in the modulespec, and since we don't support anything but
+	 * directly addressed modules we should pass PR_FALSE.
+	 */
+	mod = SECMOD_LoadUserModule(modulespec, NULL, PR_FALSE);
+	pfree(modulespec);
+
+	if (mod && mod->loaded)
+	{
+		*module = mod;
+		return SECSuccess;
+	}
+
+	SECMOD_DestroyModule(mod);
+	return SECFailure;
+}
+
+/* ------------------------------------------------------------ */
+/*						NSS Callbacks							*/
+/* ------------------------------------------------------------ */
+
+/*
+ * pg_cert_auth_handler
+ *			Callback for authenticating server certificate
+ *
+ * This is pretty much the same procedure as the SSL_AuthCertificate function
+ * provided by NSS, with the difference being server hostname validation. With
+ * SSL_AuthCertificate there is no way to do verify-ca, it only does the -full
+ * flavor of our sslmodes, so we need our own implementation.
+ */
+static SECStatus
+pg_cert_auth_handler(void *arg, PRFileDesc * fd, PRBool checksig, PRBool isServer)
+{
+	SECStatus	status;
+	PGconn	   *conn = (PGconn *) arg;
+	char	   *server_hostname = NULL;
+	CERTCertificate *server_cert;
+	void	   *pin;
+
+	Assert(!isServer);
+
+	pin = SSL_RevealPinArg(conn->pr_fd);
+	server_cert = SSL_PeerCertificate(conn->pr_fd);
+
+	status = CERT_VerifyCertificateNow((CERTCertDBHandle *) CERT_GetDefaultCertDB(), server_cert,
+									   checksig, certificateUsageSSLServer,
+									   pin, NULL);
+
+	/*
+	 * If we've already failed validation then there is no point in also
+	 * performing the hostname check for verify-full.
+	 */
+	if (status != SECSuccess)
+	{
+		printfPQExpBuffer(&conn->errorMessage,
+						  libpq_gettext("unable to verify certificate: %s"),
+						  pg_SSLerrmessage(PR_GetError()));
+		goto done;
+	}
+
+	if (strcmp(conn->sslmode, "verify-full") == 0)
+	{
+		server_hostname = SSL_RevealURL(conn->pr_fd);
+		if (!server_hostname || server_hostname[0] == '\0')
+			goto done;
+
+		/*
+		 * CERT_VerifyCertName will internally perform RFC 2818 SubjectAltName
+		 * verification.
+		 */
+		status = CERT_VerifyCertName(server_cert, server_hostname);
+		if (status != SECSuccess)
+			printfPQExpBuffer(&conn->errorMessage,
+							  libpq_gettext("unable to verify server hostname: %s"),
+							  pg_SSLerrmessage(PR_GetError()));
+
+	}
+
+done:
+	if (server_hostname)
+		PR_Free(server_hostname);
+
+	CERT_DestroyCertificate(server_cert);
+	return status;
+}
+
+/*
+ * pg_client_auth_handler
+ *		Callback for client certificate validation
+ *
+ * The client auth callback is not on by default in NSS, so we need to invoke
+ * it ourselves to ensure we can do cert authentication. A TODO is to support
+ * running without a specified sslcert parameter. By retrieving all the certs
+ * via nickname from the cert database and see if we find one which apply with
+ * NSS_CmpCertChainWCANames() and PK11_FindKeyByAnyCert() we could support
+ * just running with a ssl database specified.
+ *
+ * For now, we use the default client certificate validation which requires a
+ * defined nickname to identify the cert in the database.
+ */
+static SECStatus
+pg_client_auth_handler(void *arg, PRFileDesc * socket, CERTDistNames * caNames,
+					   CERTCertificate * *pRetCert, SECKEYPrivateKey * *pRetKey)
+{
+	PGconn	   *conn = (PGconn *) arg;
+
+	return NSS_GetClientAuthData(conn->sslcert, socket, caNames, pRetCert, pRetKey);
+}
+
+/*
+ * pg_bad_cert_handler
+ *		Callback for failed certificate validation
+ *
+ * The TLS handshake will call this function iff the server certificate failed
+ * validation. Depending on the sslmode, we allow the connection anyways.
+ */
+static SECStatus
+pg_bad_cert_handler(void *arg, PRFileDesc * fd)
+{
+	PGconn	   *conn = (PGconn *) arg;
+	PRErrorCode err;
+
+	/*
+	 * This really shouldn't happen, as we've the the PGconn object as our
+	 * callback data, and at the callsite we know it will be populated. That
+	 * being said, the NSS code itself performs this check even when it should
+	 * not be required so let's use the same belts with our suspenders.
+	 */
+	if (!arg)
+		return SECFailure;
+
+	/*
+	 * For sslmodes other than verify-full and verify-ca we don't perform peer
+	 * validation, so return immediately.  sslmode require with a database
+	 * specified which contains a CA certificate will work like verify-ca to
+	 * be compatible with the OpenSSL implementation.
+	 */
+	if (strcmp(conn->sslmode, "require") == 0)
+	{
+		if (conn->cert_database && strlen(conn->cert_database) > 0 && cert_database_has_CA(conn))
+			return SECFailure;
+	}
+	if (conn->sslmode[0] == 'v')
+		return SECFailure;
+
+	err = PORT_GetError();
+
+	/*
+	 * TODO: these are relevant error codes that can occur in certificate
+	 * validation, figure out which we dont want for require/prefer etc.
+	 */
+	switch (err)
+	{
+		case SEC_ERROR_INVALID_AVA:
+		case SEC_ERROR_INVALID_TIME:
+		case SEC_ERROR_BAD_SIGNATURE:
+		case SEC_ERROR_EXPIRED_CERTIFICATE:
+		case SEC_ERROR_UNKNOWN_ISSUER:
+		case SEC_ERROR_UNTRUSTED_ISSUER:
+		case SEC_ERROR_UNTRUSTED_CERT:
+		case SEC_ERROR_CERT_VALID:
+		case SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE:
+		case SEC_ERROR_CRL_EXPIRED:
+		case SEC_ERROR_CRL_BAD_SIGNATURE:
+		case SEC_ERROR_EXTENSION_VALUE_INVALID:
+		case SEC_ERROR_CA_CERT_INVALID:
+		case SEC_ERROR_CERT_USAGES_INVALID:
+		case SEC_ERROR_UNKNOWN_CRITICAL_EXTENSION:
+			return SECSuccess;
+			break;
+		default:
+			return SECFailure;
+			break;
+	}
+
+	/* Unreachable */
+	return SECSuccess;
+}
+
+/* ------------------------------------------------------------ */
+/*					SSL information functions					*/
+/* ------------------------------------------------------------ */
+
+void *
+PQgetssl(PGconn *conn)
+{
+	/*
+	 * Always return NULL as this is legacy and defined to be equal to
+	 * PQsslStruct(conn, "OpenSSL"); This should ideally trigger a logged
+	 * warning somewhere as it's nonsensical to run in a non-OpenSSL build,
+	 * but the color of said bikeshed hasn't yet been determined.
+	 */
+	return NULL;
+}
+
+void *
+PQsslStruct(PGconn *conn, const char *struct_name)
+{
+	if (!conn)
+		return NULL;
+
+	/*
+	 * Return the underlying PRFileDesc which can be used to access
+	 * information on the connection details. There is no SSL context per se.
+	 */
+	if (strcmp(struct_name, "NSS") == 0)
+		return conn->pr_fd;
+	return NULL;
+}
+
+const char *const *
+PQsslAttributeNames(PGconn *conn)
+{
+	static const char *const result[] = {
+		"library",
+		"cipher",
+		"protocol",
+		"key_bits",
+		"compression",
+		NULL
+	};
+
+	return result;
+}
+
+const char *
+PQsslAttribute(PGconn *conn, const char *attribute_name)
+{
+	SECStatus	status;
+	SSLChannelInfo channel;
+	SSLCipherSuiteInfo suite;
+
+	if (!conn || !conn->pr_fd)
+		return NULL;
+
+	if (strcmp(attribute_name, "library") == 0)
+		return "NSS";
+
+	status = SSL_GetChannelInfo(conn->pr_fd, &channel, sizeof(channel));
+	if (status != SECSuccess)
+		return NULL;
+
+	status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+	if (status != SECSuccess)
+		return NULL;
+
+	if (strcmp(attribute_name, "cipher") == 0)
+		return suite.cipherSuiteName;
+
+	if (strcmp(attribute_name, "key_bits") == 0)
+	{
+		static char key_bits_str[8];
+
+		snprintf(key_bits_str, sizeof(key_bits_str), "%i", suite.effectiveKeyBits);
+		return key_bits_str;
+	}
+
+	if (strcmp(attribute_name, "protocol") == 0)
+	{
+		switch (channel.protocolVersion)
+		{
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+			case SSL_LIBRARY_VERSION_TLS_1_3:
+				return "TLSv1.3";
+#endif
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+			case SSL_LIBRARY_VERSION_TLS_1_2:
+				return "TLSv1.2";
+#endif
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+			case SSL_LIBRARY_VERSION_TLS_1_1:
+				return "TLSv1.1";
+#endif
+			case SSL_LIBRARY_VERSION_TLS_1_0:
+				return "TLSv1.0";
+			default:
+				return "unknown";
+		}
+	}
+
+	/*
+	 * NSS disabled support for compression in version 3.33, and it was only
+	 * available for SSLv3 at that point anyways, so we can safely return off
+	 * here without checking.
+	 */
+	if (strcmp(attribute_name, "compression") == 0)
+		return "off";
+
+	return NULL;
+}
+
+static int
+ssl_protocol_version_to_nss(const char *protocol)
+{
+	if (pg_strcasecmp("TLSv1", protocol) == 0)
+		return SSL_LIBRARY_VERSION_TLS_1_0;
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+	if (pg_strcasecmp("TLSv1.1", protocol) == 0)
+		return SSL_LIBRARY_VERSION_TLS_1_1;
+#endif
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+	if (pg_strcasecmp("TLSv1.2", protocol) == 0)
+		return SSL_LIBRARY_VERSION_TLS_1_2;
+#endif
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+	if (pg_strcasecmp("TLSv1.3", protocol) == 0)
+		return SSL_LIBRARY_VERSION_TLS_1_3;
+#endif
+
+	return -1;
+}
+
+static bool
+cert_database_has_CA(PGconn *conn)
+{
+	CERTCertList *certificates;
+	bool		hasCA;
+
+	/*
+	 * If the certificate database has a password we must provide it, since
+	 * this API doesn't invoke the standard password callback.
+	 */
+	if (has_password)
+		certificates = PK11_ListCerts(PK11CertListCA, PQssl_passwd_cb(NULL, PR_FALSE, (void *) conn));
+	else
+		certificates = PK11_ListCerts(PK11CertListCA, NULL);
+	hasCA = !CERT_LIST_EMPTY(certificates);
+	CERT_DestroyCertList(certificates);
+
+	return hasCA;
+}
+
+PQsslKeyPassHook_nss_type
+PQgetSSLKeyPassHook_nss(void)
+{
+	return PQsslKeyPassHook;
+}
+
+void
+PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook)
+{
+	PQsslKeyPassHook = hook;
+}
+
+/*
+ * Supply a password to decrypt a client certificate.
+ *
+ * This must match NSS type PK11PasswordFunc.
+ */
+static char *
+PQssl_passwd_cb(PK11SlotInfo * slot, PRBool retry, void *arg)
+{
+	has_password = true;
+
+	if (PQsslKeyPassHook)
+		return PQsslKeyPassHook(slot, (PRBool) retry, arg);
+	else
+		return PQdefaultSSLKeyPassHook_nss(slot, retry, arg);
+}
+
+/*
+ * The default password handler callback.
+ */
+char *
+PQdefaultSSLKeyPassHook_nss(PK11SlotInfo * slot, PRBool retry, void *arg)
+{
+	PGconn	   *conn = (PGconn *) arg;
+
+	/*
+	 * If the password didn't work the first time there is no point in
+	 * retrying as it hasn't changed.
+	 */
+	if (retry != PR_TRUE && conn->sslpassword && strlen(conn->sslpassword) > 0)
+		return PORT_Strdup(conn->sslpassword);
+
+	return NULL;
+}
diff --git a/src/interfaces/libpq/fe-secure.c b/src/interfaces/libpq/fe-secure.c
index 3311fd7a5b..b6c92ece11 100644
--- a/src/interfaces/libpq/fe-secure.c
+++ b/src/interfaces/libpq/fe-secure.c
@@ -430,6 +430,9 @@ PQsslAttributeNames(PGconn *conn)
 
 	return result;
 }
+#endif /* USE_SSL */
+
+#ifndef USE_OPENSSL
 
 PQsslKeyPassHook_OpenSSL_type
 PQgetSSLKeyPassHook_OpenSSL(void)
@@ -448,7 +451,7 @@ PQdefaultSSLKeyPassHook_OpenSSL(char *buf, int size, PGconn *conn)
 {
 	return 0;
 }
-#endif							/* USE_SSL */
+#endif							/* USE_OPENSSL */
 
 /* Dummy version of GSSAPI information functions, when built without GSS support */
 #ifndef ENABLE_GSS
diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h
index 3b6a9fbce3..27c16e187f 100644
--- a/src/interfaces/libpq/libpq-fe.h
+++ b/src/interfaces/libpq/libpq-fe.h
@@ -625,6 +625,17 @@ extern PQsslKeyPassHook_OpenSSL_type PQgetSSLKeyPassHook_OpenSSL(void);
 extern void PQsetSSLKeyPassHook_OpenSSL(PQsslKeyPassHook_OpenSSL_type hook);
 extern int	PQdefaultSSLKeyPassHook_OpenSSL(char *buf, int size, PGconn *conn);
 
+/* == in fe-secure-nss.c === */
+typedef struct PK11SlotInfoStr PK11SlotInfo;
+typedef int PRIntn;
+typedef PRIntn PRBool;
+
+/* Support for overriding sslpassword handling with a callback. */
+typedef char *(*PQsslKeyPassHook_nss_type) (PK11SlotInfo * slot, PRBool retry, void *arg);
+extern PQsslKeyPassHook_nss_type PQgetSSLKeyPassHook_nss(void);
+extern void PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook);
+extern char *PQdefaultSSLKeyPassHook_nss(PK11SlotInfo * slot, PRBool retry, void *arg);
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index 1de91ae295..12717ca720 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -362,6 +362,7 @@ struct pg_conn
 	char	   *sslpassword;	/* client key file password */
 	char	   *sslrootcert;	/* root certificate filename */
 	char	   *sslcrl;			/* certificate revocation list filename */
+	char	   *cert_database;
 	char	   *requirepeer;	/* required peer credentials for local sockets */
 	char	   *gssencmode;		/* GSS mode (require,prefer,disable) */
 	char	   *krbsrvname;		/* Kerberos service name */
@@ -485,6 +486,10 @@ struct pg_conn
 								 * OpenSSL version changes */
 #endif
 #endif							/* USE_OPENSSL */
+
+#ifdef USE_NSS
+	void	   *pr_fd;
+#endif							/* USE_NSS */
 #endif							/* USE_SSL */
 
 #ifdef ENABLE_GSS
diff --git a/src/test/Makefile b/src/test/Makefile
index efb206aa75..d18f5a083b 100644
--- a/src/test/Makefile
+++ b/src/test/Makefile
@@ -27,7 +27,7 @@ ifneq (,$(filter ldap,$(PG_TEST_EXTRA)))
 SUBDIRS += ldap
 endif
 endif
-ifeq ($(with_openssl),yes)
+ifeq ($(with_ssl),yes)
 ifneq (,$(filter ssl,$(PG_TEST_EXTRA)))
 SUBDIRS += ssl
 endif
diff --git a/src/test/ssl/Makefile b/src/test/ssl/Makefile
index 777ee39413..fe265e2dbd 100644
--- a/src/test/ssl/Makefile
+++ b/src/test/ssl/Makefile
@@ -14,6 +14,7 @@ top_builddir = ../../..
 include $(top_builddir)/src/Makefile.global
 
 export with_openssl
+export with_nss
 
 CERTIFICATES := server_ca server-cn-and-alt-names \
 	server-cn-only server-single-alt-name server-multiple-alt-names \
@@ -30,6 +31,32 @@ SSLFILES := $(CERTIFICATES:%=ssl/%.key) $(CERTIFICATES:%=ssl/%.crt) \
 	ssl/client+client_ca.crt ssl/client-der.key \
 	ssl/client-encrypted-pem.key ssl/client-encrypted-der.key
 
+# Even though we in practice could get away with far fewer NSS databases, they
+# are generated to mimick the setup for the OpenSSL tests in order to ensure
+# we isolate the same behavior between the backends. The database name should
+# contain the files included for easier test suite code reading.
+NSSFILES := ssl/nss/client_ca.crt.db \
+	ssl/nss/server_ca.crt.db \
+	ssl/nss/root+server_ca.crt.db \
+	ssl/nss/root+client_ca.crt.db \
+	ssl/nss/client.crt__client.key.db \
+	ssl/nss/client-revoked.crt__client-revoked.key.db \
+	ssl/nss/server-cn-only.crt__server-password.key.db \
+	ssl/nss/server-cn-only.crt__server-cn-only.key.db \
+	ssl/nss/root.crl \
+	ssl/nss/server.crl \
+	ssl/nss/client.crl \
+	ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db \
+	ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db \
+	ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db \
+	ssl/nss/server-no-names.crt__server-no-names.key.db \
+	ssl/nss/server-revoked.crt__server-revoked.key.db \
+	ssl/nss/root+client.crl \
+	ssl/nss/client+client_ca.crt__client.key.db \
+	ssl/nss/client.crt__client-encrypted-pem.key.db \
+	ssl/nss/root+server_ca.crt__server.crl.db \
+	ssl/nss/root+server_ca.crt__root+server.crl.db
+
 # This target re-generates all the key and certificate files. Usually we just
 # use the ones that are committed to the tree without rebuilding them.
 #
@@ -37,6 +64,10 @@ SSLFILES := $(CERTIFICATES:%=ssl/%.key) $(CERTIFICATES:%=ssl/%.crt) \
 #
 sslfiles: $(SSLFILES)
 
+# Generate NSS certificate databases corresponding to the OpenSSL certificates.
+# This target will fail unless preceded by nssfiles-clean.
+nssfiles: $(NSSFILES)
+
 # OpenSSL requires a directory to put all generated certificates in. We don't
 # use this for anything, but we need a location.
 ssl/new_certs_dir:
@@ -64,6 +95,24 @@ ssl/%_ca.crt: ssl/%_ca.key %_ca.config ssl/root_ca.crt ssl/new_certs_dir
 	rm ssl/temp_ca.crt ssl/temp_ca_signed.crt
 	echo "01" > ssl/$*_ca.srl
 
+ssl/nss/%_ca.crt.db: ssl/%_ca.crt
+	$(MKDIR_P) $@
+	certutil -d "sql:$@" -N --empty-password
+	certutil -d "sql:$@" -A -n $*_ca.crt -i ssl/$*_ca.crt -t "CT,C,C"
+
+ssl/nss/root+server_ca.crt__server.crl.db: ssl/root+server_ca.crt ssl/nss/server.crl
+	$(MKDIR_P) $@
+	certutil -d "sql:$@" -N --empty-password
+	certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+	crlutil -I -i ssl/nss/server.crl -d $@ -B
+
+ssl/nss/root+server_ca.crt__root+server.crl.db: ssl/root+server_ca.crt ssl/nss/root.crl ssl/nss/server.crl
+	$(MKDIR_P) $@
+	certutil -d "sql:$@" -N --empty-password
+	certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+	crlutil -I -i ssl/nss/root.crl -d $@ -B
+	crlutil -I -i ssl/nss/server.crl -d $@ -B
+
 # Server certificates, signed by server CA:
 ssl/server-%.crt: ssl/server-%.key ssl/server_ca.crt server-%.config
 	openssl req -new -key ssl/server-$*.key -out ssl/server-$*.csr -config server-$*.config
@@ -77,6 +126,74 @@ ssl/server-ss.crt: ssl/server-cn-only.key ssl/server-cn-only.crt server-cn-only.
 	openssl x509 -req -days 10000 -in ssl/server-ss.csr -signkey ssl/server-cn-only.key -out ssl/server-ss.crt  -extensions v3_req -extfile server-cn-only.config
 	rm ssl/server-ss.csr
 
+ssl/nss/server-cn-only.crt__server-password.key.db: ssl/server-cn-only.crt
+	$(MKDIR_P) $@
+	certutil -d "sql:$@" -N --empty-password
+	certutil -d "sql:$@" -A -n ssl/server-cn-only.crt -i ssl/server-cn-only.crt -t "CT,C,C"
+	certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+	certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+	certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+	openssl pkcs12 -export -out ssl/nss/server-password.pfx -inkey ssl/server-password.key -in ssl/server-cn-only.crt -certfile ssl/server_ca.crt -passin 'pass:secret1' -passout pass:
+	pk12util -i ssl/nss/server-password.pfx -d $@ -W ''
+
+ssl/nss/server-cn-only.crt__server-cn-only.key.db: ssl/server-cn-only.crt ssl/server-cn-only.key
+	$(MKDIR_P) $@
+	certutil -d "sql:$@" -N --empty-password
+	certutil -d "sql:$@" -A -n ssl/server-cn-only.crt -i ssl/server-cn-only.crt -t "CT,C,C"
+	certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+	openssl pkcs12 -export -out ssl/nss/server-cn-only.pfx -inkey ssl/server-cn-only.key -in ssl/server-cn-only.crt -certfile ssl/server_ca.crt -passout pass:
+	pk12util -i ssl/nss/server-cn-only.pfx -d $@ -W ''
+
+ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db: ssl/server-multiple-alt-names.crt
+	$(MKDIR_P) $@
+	certutil -d "sql:$@" -N --empty-password
+	certutil -d "sql:$@" -A -n ssl/server-multiple-alt-names.crt -i ssl/server-multiple-alt-names.crt -t "CT,C,C"
+	certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+	certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+	certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+	openssl pkcs12 -export -out ssl/nss/server-multiple-alt-names.pfx -inkey ssl/server-multiple-alt-names.key -in ssl/server-multiple-alt-names.crt -certfile ssl/server-multiple-alt-names.crt -passout pass:
+	pk12util -i ssl/nss/server-multiple-alt-names.pfx -d $@ -W ''
+
+ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db: ssl/server-single-alt-name.crt
+	$(MKDIR_P) $@
+	certutil -d "sql:$@" -N --empty-password
+	certutil -d "sql:$@" -A -n ssl/server-single-alt-name.crt -i ssl/server-single-alt-name.crt -t "CT,C,C"
+	certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+	certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+	certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+	openssl pkcs12 -export -out ssl/nss/server-single-alt-name.pfx -inkey ssl/server-single-alt-name.key -in ssl/server-single-alt-name.crt -certfile ssl/server-single-alt-name.crt -passout pass:
+	pk12util -i ssl/nss/server-single-alt-name.pfx -d $@ -W ''
+
+ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db: ssl/server-cn-and-alt-names.crt
+	$(MKDIR_P) $@
+	certutil -d "sql:$@" -N --empty-password
+	certutil -d "sql:$@" -A -n ssl/server-cn-and-alt-names.crt -i ssl/server-cn-and-alt-names.crt -t "CT,C,C"
+	certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+	certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+	certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+	openssl pkcs12 -export -out ssl/nss/server-cn-and-alt-names.pfx -inkey ssl/server-cn-and-alt-names.key -in ssl/server-cn-and-alt-names.crt -certfile ssl/server-cn-and-alt-names.crt -passout pass:
+	pk12util -i ssl/nss/server-cn-and-alt-names.pfx -d $@ -W ''
+
+ssl/nss/server-no-names.crt__server-no-names.key.db: ssl/server-no-names.crt
+	$(MKDIR_P) $@
+	certutil -d "sql:$@" -N --empty-password
+	certutil -d "sql:$@" -A -n ssl/server-no-names.crt -i ssl/server-no-names.crt -t "CT,C,C"
+	certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+	certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+	certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+	openssl pkcs12 -export -out ssl/nss/server-no-names.pfx -inkey ssl/server-no-names.key -in ssl/server-no-names.crt -certfile ssl/server-no-names.crt -passout pass:
+	pk12util -i ssl/nss/server-no-names.pfx -d $@ -W ''
+
+ssl/nss/server-revoked.crt__server-revoked.key.db: ssl/server-revoked.crt
+	$(MKDIR_P) $@
+	certutil -d "sql:$@" -N --empty-password
+	certutil -d "sql:$@" -A -n ssl/server-revoked.crt -i ssl/server-revoked.crt -t "CT,C,C"
+	certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+	certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+	certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+	openssl pkcs12 -export -out ssl/nss/server-revoked.pfx -inkey ssl/server-revoked.key -in ssl/server-revoked.crt -certfile ssl/server-revoked.crt -passout pass:
+	pk12util -i ssl/nss/server-revoked.pfx -d $@ -W ''
+
 # Password-protected version of server-cn-only.key
 ssl/server-password.key: ssl/server-cn-only.key
 	openssl rsa -aes256 -in $< -out $@ -passout 'pass:secret1'
@@ -88,6 +205,27 @@ ssl/client.crt: ssl/client.key ssl/client_ca.crt
 	openssl x509 -in ssl/temp.crt -out ssl/client.crt # to keep just the PEM cert
 	rm ssl/client.csr ssl/temp.crt
 
+# Client certificate, signed by client CA
+ssl/nss/client.crt__client.key.db: ssl/client.crt
+	$(MKDIR_P) $@
+	certutil -d "sql:$@" -N --empty-password
+	certutil -d "sql:$@" -A -n ssl/client.crt -i ssl/client.crt -t "CT,C,C"
+	certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+	certutil -d "sql:$@" -A -n root+client_ca.crt -i ssl/root+client_ca.crt -t "CT,C,C"
+	openssl pkcs12 -export -out ssl/nss/client.pfx -inkey ssl/client.key -in ssl/client.crt -certfile ssl/client_ca.crt -passout pass:
+	pk12util -i ssl/nss/client.pfx -d $@ -W ''
+
+# Client certificate with encrypted key, signed by client CA
+ssl/nss/client.crt__client-encrypted-pem.key.db: ssl/client.crt
+	$(MKDIR_P) $@
+	echo 'dUmmyP^#+' > [email protected]
+	certutil -d "sql:$@" -N -f [email protected]
+	certutil -d "sql:$@" -A -f [email protected] -n ssl/client.crt -i ssl/client.crt -t "CT,C,C"
+	certutil -d "sql:$@" -A -f [email protected] -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+	certutil -d "sql:$@" -A -f [email protected] -n root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+	openssl pkcs12 -export -out ssl/nss/client-encrypted-pem.pfx -inkey ssl/client-encrypted-pem.key -in ssl/client.crt -certfile ssl/client_ca.crt -passin pass:'dUmmyP^#+' -passout pass:'dUmmyP^#+'
+	pk12util -i ssl/nss/client-encrypted-pem.pfx -d $@ -W 'dUmmyP^#+' -k [email protected]
+
 # Another client certificate, signed by the client CA. This one is revoked.
 ssl/client-revoked.crt: ssl/client-revoked.key ssl/client_ca.crt client.config
 	openssl req -new -key ssl/client-revoked.key -out ssl/client-revoked.csr -config client.config
@@ -95,6 +233,14 @@ ssl/client-revoked.crt: ssl/client-revoked.key ssl/client_ca.crt client.config
 	openssl x509 -in ssl/temp.crt -out ssl/client-revoked.crt # to keep just the PEM cert
 	rm ssl/client-revoked.csr ssl/temp.crt
 
+ssl/nss/client-revoked.crt__client-revoked.key.db: ssl/client-revoked.crt
+	$(MKDIR_P) $@
+	certutil -d "sql:$@" -N --empty-password
+	certutil -d "sql:$@" -A -n ssl/client-revoked.crt -i ssl/client-revoked.crt -t "CT,C,C"
+	certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+	openssl pkcs12 -export -out ssl/nss/client-revoked.pfx -inkey ssl/client-revoked.key -in ssl/client-revoked.crt -certfile ssl/client_ca.crt -passout pass:
+	pk12util -i ssl/nss/client-revoked.pfx -d $@ -W ''
+
 # Convert the key to DER, to test our behaviour there too
 ssl/client-der.key: ssl/client.key
 	openssl rsa -in ssl/client.key -outform DER -out ssl/client-der.key
@@ -127,19 +273,40 @@ ssl/root+client_ca.crt: ssl/root_ca.crt ssl/client_ca.crt
 ssl/client+client_ca.crt: ssl/client.crt ssl/client_ca.crt
 	cat $^ > $@
 
+# Client certificate, signed by client CA
+ssl/nss/client+client_ca.crt__client.key.db: ssl/client+client_ca.crt
+	$(MKDIR_P) $@
+	certutil -d "sql:$@" -N --empty-password
+	certutil -d "sql:$@" -A -n ssl/client+client_ca.crt -i ssl/client+client_ca.crt -t "CT,C,C"
+	certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+	openssl pkcs12 -export -out ssl/nss/client.pfx -inkey ssl/client.key -in ssl/client.crt -certfile ssl/client_ca.crt -passout pass:
+	pk12util -i ssl/nss/client.pfx -d $@ -W ''
+
 #### CRLs
 
 ssl/client.crl: ssl/client-revoked.crt
 	openssl ca -config cas.config -name client_ca -revoke ssl/client-revoked.crt
 	openssl ca -config cas.config -name client_ca -gencrl -out ssl/client.crl
 
+ssl/nss/client.crl: ssl/client.crl
+	openssl crl -in $^ -outform der -out $@
+
 ssl/server.crl: ssl/server-revoked.crt
 	openssl ca -config cas.config -name server_ca -revoke ssl/server-revoked.crt
 	openssl ca -config cas.config -name server_ca -gencrl -out ssl/server.crl
 
+ssl/nss/server.crl: ssl/server.crl
+	openssl crl -in $^ -outform der -out $@
+
 ssl/root.crl: ssl/root_ca.crt
 	openssl ca -config cas.config -name root_ca -gencrl -out ssl/root.crl
 
+ssl/nss/root.crl: ssl/root.crl
+	openssl crl -in $^ -outform der -out $@
+
+ssl/nss/root+client.crl: ssl/root+client.crl
+	openssl crl -in $^ -outform der -out $@
+
 # If a CRL is used, OpenSSL requires a CRL file for *all* the CAs in the
 # chain, even if some of them are empty.
 ssl/root+server.crl: ssl/root.crl ssl/server.crl
@@ -151,9 +318,14 @@ ssl/root+client.crl: ssl/root.crl ssl/client.crl
 sslfiles-clean:
 	rm -f $(SSLFILES) ssl/client_ca.srl ssl/server_ca.srl ssl/client_ca-certindex* ssl/server_ca-certindex* ssl/root_ca-certindex* ssl/root_ca.srl ssl/temp_ca.crt ssl/temp_ca_signed.crt
 
+.PHONY: nssfiles-clean
+nssfiles-clean:
+	rm -rf ssl/nss
+
 clean distclean maintainer-clean:
 	rm -rf tmp_check
 	rm -rf ssl/*.old ssl/new_certs_dir ssl/client*_tmp.key
+	rm -rf ssl/nss
 
 # Doesn't depend on $(SSLFILES) because we don't rebuild them by default
 check:
diff --git a/src/test/ssl/ssl/nss/client+client_ca.crt__client.key.db/cert9.db b/src/test/ssl/ssl/nss/client+client_ca.crt__client.key.db/cert9.db
new file mode 100644
index 0000000000000000000000000000000000000000..2e541cdfc9c6da2ce489ad74bc2f32e25028361d
GIT binary patch
literal 36864
zcmeI530xCL`^R^45st73C`c_tML|H_To3`3Lqsq_I7CqqLx>pUP?8{6ixN>hS`mD`
zUbF~kv4Vw)2wqiMvGsbbR;*g7Sgqw%3W}}bRo|ISIOIeB{rgJa&-=Eskjb-i{AT96
zyH7Hc$tEmBqEsumbCXjNWooVfGsAEk^Wt(b3^UX}z!SAGLCDbC@Col`|CVWpjq<q9
zA=)vPQ4?nTkf<}RFpf8N0y}6!1P}p401-e05CKF05kLg~KmuMYmX(zq9;r-<Riw%1
z%apN6DwR|dlc1Cv_=fs<hx>5@f+qM)=Td3h;2>^iI@eLgBl#o7F)4+qltO|mMv>5&
zgY}96mDW>1Fk%9OW%ZUF9z~T`X=36Pa&@Ny^A!bsYEKOw$s6HKXITyG=!a6J(kN0o
zMHsJ$=n{Ksh<PKjgK+~ZJ3D+`qFNS{py({#Y0&gK;od$HzwR8Av0Q0rz!dM$NbY36
zNbc02fRL$vum<1Ypox+I-*B$CPjG0s3s**h2M;{>;30rE6#-*-FiH$}#c)?l?ZPZ#
zAq+$?aD{;z48$-P0|OZhVn8s4k|K2>rAR^z9;vGbs_;lC!y};%kAy-z5-RaXD8(b8
z7LSBtJW>uree-nfqm=nNn^NXe%6v+hPbu>$Wj>|Mr<D1WvVbZppvnrUvI45CfGX=s
zrMgn7u2iZkmFlWXrIcMMWj87(MTtjY;#2+QldvqG>Mx&avw(^hQ1JpPUP#3VsTiR?
z2G$}ZVJ$+6jF2KDr1XW9zK}W&BC4#2Dl4MOiYTf;lO*{(Sh?PWtrx>slFBSshy+4`
z2xicmFoWKNwd+mT1icA+t2ben^(G{$Hz8J?N%7H}UBxI;`q-{wuHA~kL!!&z*3|~L
zjyAY<wHKHj?FD8>USM|1+fgxy?PxDBJ1PcdN5#PGs2G^=RD*2*COqHtCOql%W^9+K
z=rZSan$$_7%Nx_xoEX@gSdom3jUmYqJQ~Z&&ko0Qu4Q2Y7|}<|H0oq(Kf3d}j^=fm
zbl_^)(A~m8Jx)A>rlIZ$nrK4=5CKF05kLeG0Ym^1Km-s0L;w*$1Q3Bg4*@pQ2y});
zoWY28qLsKqG!U1-4%!d_L;w*$1P}p401-e05CKF05kLeG0Yu=xl>nR0!0iQ3PO$XG
zXH3fAXHH`14Szai@MfFQ4Co9)d){n<8vmQF$4svf!%Q1Y*Avx5;(u!gQM(WUL;w*$
z1P}p401-e05CKF05kLg~JOun1nPxcY#T!R@vglJ8bQ4jyLZ#-WBqyu6zTWVA>Rf5E
zN*$M?2n&&L!@?xo6b0B(sg%h{Ts6$9(tzdfECwCJnSCV2=p!9^03*{9Cw+Kc<TzdD
z+^Ej||N13QP%4ttT)84et)fi=N``%*r2G8e3nOk2dBkmEGvP(7`SYAjs3(X3B7g`W
z0*C-2fCwN0hyWsh2p|H8K;IDPvmrgpfNqZO!e%hx%tfEgI6yJlTXauDasbbgG}p~K
z7^_qXaH>S3Ql#j{{~C<YfYE;~ah*6we9|{$kP0Gz2p|H803v`0AOeU0B7g`W0*C-2
z@E0fGL0^pjo1gr5P7~;-3m7(ZF%HJ_qu~k(XE2~oRis4AWuxRNYPvoPHU5WLbSns)
zbx-<Cy7B)8jJQSA5vPeJqKw$^7e6|v`-lJ{fCwN0hyWsh2p|H803v`0AOeWMe}aHD
zU5JM#^KP9=KO3Yx`P0ddn6!}Vp`WI*V1n1~2UmSRHA<I6;0B@(PS>*5o$=Ex8Gxy*
zGD)znw^RRSKicPa*}GpXd{3^TQ)>XM=t9HZ%5p%DnDnnO6KmsstnJJdz(gA&fC&7L
z5?Ib>E@oJonws})zooelGX;MbW(rcxvrMp_S*Wz0#a?S4+<oLTP=JPGU?CGM37&d$
zIyl&18u;J8cE_l~w&PxmWA>5ObzZqwMQ4W}w2a6Rd&_LOCM9@~S>}+N&bJ>;$n*Pg
zc-m&iMI#ejt}*vqI-Z<AeV#jOa747~{Oyy;E=ea&yi5tIxV4CVx_FEsyFRa&Ggza&
zb~148Uafl>KRsBwm1+L@%>kzGPk9j;%8T*-A#2k}JFo4>Hnf}_nzg?=(APdM;^V@!
z%_Vu-R_6*Et658G#1(?OW2?)RWBTX2+dZAf%}HNfp4HaxNZ`J?ymHU&JEKZ#{AZqX
zjr(ZAN_B*A{9coJKV8_gVd<a@V~ey<@mopDSFirLEjQ%WVKU^K?*%T4c!lfNj-NJE
zd1;Se+{}Lr3A4HIP>zG2QN^|W8C?f0(@gwY(7^$H&HNZ<2{u^BROo`)gB!&BBg{99
zIwEiW%{!W<8QaRCJ^%Ba^-}^J|1M4N{GvW63<ouYc7I@#9p@^)TV9>KePToCqUzjZ
z{CmoQaSppbjBnxPp4_`^;xv(!@2Z9LCGcbxWA8~Z%U8i<nhAfu+pkZiyCxvx5B9;i
z+WQ1MsioVJS{iEyorcqT?>EU|89J?;wDK_r$BmiTo8u*ItvKK(p?G{Zo&fxqlW>L=
zN9allc|!eeAJm}5hjec?&cx_ioWMveZbZ`p7zdVIZd=3Hx=`ym%2j))c^%v0`i)5k
zeojkmjAJe2%xc&&tr)lYVx!Wn{j<ZO(q|L)uYTNMlmG6+<N}&xk%w%Bg0<bF{kd_p
z=SLqY2g{z?U%F$R<M>&bw0WPK;n1`Y!wnP8H(OUE@71hW{ge^x^+Q4SL_3f8y37S^
zvS!8IS)0zim@PiP?)^1&FPbZBD{Y1cu;$<A$Cf>=-I7x}>Fa~q`YjnI&Xu@(on7F3
zeBaWhY<nMH!MLKR(LOVs4^4l5==8kp7N2KdZEU=sKE|<M>*XO^S#jXTNe<S3J93;}
zR`g_aQN?I;#koUU{3kxyQnSQyP_0jF;dS<-cVax^X5Z8l)9B!mEjA-fNfWT1@@)$S
zlceML%kIp;p$5!wY%=LV+tlK;blCb)Ki_3czHy-XhW*vJ)3@TX<BmATH~lkl<FZrt
zS2q-0%y)U$T<XR9__56Z)yQV%i(OWaD&_<x<_J_aIT>djPHmd=MW#*t0Q-umHQC?Y
z%896&P`Xo6nkDugvg^o#;=nU??X8-UM{C`;W#t>&Ty9#hGWtTr^HkRpj|6Ks*f!5Q
z>bZL~!>Ibjp>J*9`Tmqqopr($>o2t<Ek^B&NMG@MS><ER$bxBXzo%QvBPCZ>9&mX%
zn=_{9pxxYO4+XimlaJ&!ZM$~CK7E$fc>17a<uOi!AGaTOu^L`mP&mauC!(w-t1;8z
zyC;TeHyp##$eoy`agkZeZ00h01H78v{EYmKOY6<QB;3opWn9akb@RoRJzpfd`-k4_
z*Y`ND_+oHtt}b8P7he<xue9CWF0?V$wH8esTkH7Te|%|eI=}U-te@wpFO8~*TSpEs
zwC-=0dd#?*t-#-buGbS=>jGTwD;zxkc+U&&Oa|!cJ#TNHTLQm(7I>unG#u}7;q}G0
zqI-B*(W_ooG~>5cQ-c=$-@fy&{&t7*ojnd1<tYbo;}e<&Hv13c+&L{<Ubv>3Y%Phg
z{PNzx!bSsi-q*KtBeRMsv&eR*q2*6BmFh+2w5SLC9ZuV(g-V=LKmBIh$5X`b42!U*
zwJqI41n=!%kk3?>DS|i|2T~<$mu-ZF`^NK6i<UH&?6@>NUa*N#n0AGBsyMt=6IS!(
z+fUDbwf6&mqobyttjEe&>6x0Tid!YCzM3|p@)&z$UhQ*1i^wgt?RaD+C-jlRBKEMY
znw1i=YS_`|yQL+2?&WFaM^BCYd6crE<lWmpJ}!6rhU0ZMT(zul&hWn{+}s?pBfyRC
zHK*3n)^2xuWus+OkwL)PW~t?xf@%8%MIHvuN%u^CB@RGe(idkgydHjIseiBU#NqfK
zH+~-j|7b%55CKF05kLeG0Ym^1Km-th|0ID`3s}^A=VI)=QE<Mq`{YBW<sU!l1~(4}
z-36oWe{*p%m=NWFA@DD{2~nKZW1#ouGeN&QF7JaWliwY~AO6v{?I0&(S^3cV;zNY;
z<mtpuuH1}U>!B#HB{nKHt!Z<PnU!+SY;Z<ZR1W+49r;MHQ@vAUf_ik`CI7pIe{1=s
ziFv_c;t#bg4zY>L*9@uHzvA1(<X`6#;;(G#zs@)6U*=%=!NaN<1NQxDkWjSim#VQ6
z$uA$jEQ$7+U`6-+{@lzv%u=kM!Qhjnv+Uw8s&<~Yh-%$zu8y90ar5}pgX4BCUi?7n
zA-;~kT+i4(81s*kilgRu<yVH3(l$4nEl8?O%~ua@zdV-tQnD@gOo}G1cC%vU)`sSc
zT8XXO>?cEHrGxg|v(6h`ATO{T=rlj>m+*D6Pd;Qgcx$wW{oEXe8NYaq`A^%*?6w6@
z8>+vJ5(azBZ>7z1TuL83JuWcuyO!14bNqIVc(%3HeR<8<+Ups{V@6!0Pq8W)idoZq
z?%B&HpH6(b<;v~+#YMOGEG^@d2CTR!Ud3q=ZFum$xp>DHKe2;8Jh9Oyy~$w24hxwF
zCKucLWowi6<zC&bX_Q{JFn6GBtjgH9&5_g^)wisW4e;H4X7aNPR@HjikofY6zE9$3
ztY2OeAU&r_5Jg`r$(Lu_1-6*J>v724bWPbY+TjoH`p50PQ2uq&6@U3yskLjr!tjgZ
z*KXR8EUDkrOn-FFB5qI0{-IAUrqN>9<tK-=#osS}5E<BR>vBpeui_l9qF0+7_3?qu
T<IT@{@aaNdObzt5rU3sJIhEY#

literal 0
HcmV?d00001

diff --git a/src/test/ssl/ssl/nss/client+client_ca.crt__client.key.db/key4.db b/src/test/ssl/ssl/nss/client+client_ca.crt__client.key.db/key4.db
new file mode 100644
index 0000000000000000000000000000000000000000..2a6cb5d9248a87e322d39172bd495eb428b2a035
GIT binary patch
literal 45056
zcmeI53pi9;|HtRTG&9VMYebk46-9e)4AHnGa>*s62s2Dl?nWtfG;Up`)S-K%qC^)Z
z-6)q7M_1KBDRD}tlS)ZS_0HZn&gE@p{?Gq;pXYzx|Jr7+`R(;v>$|_}vu4)V_Jg^e
zuHixfby0L&Bu_|XqLff*G|GlbMWIkc@vb1=M>d=|kQ+G=KcoK;e=j7WOsBsgQ9h#Z
zL@Sg+52Z%ohyqVRkJv`EA|Cx-Cr}Rv00MvjAOHve0)PM@@PCs)#F#M}Iyz|46d{iv
zA&3+Rc{6!J-pDiF-pj$(+kxtBYv<}f9jT>G4iBYzc+GaR^_ow0ahOl_nKRqd$ARiT
zhidOWXO`=1dvB_jgNLiFy~9+h_&d```p^@@Xc``;VXuQmg-3-7660ed#K|CDf-o96
z4jR!9qK`xbI~-0!gNF7;8iXw6g@;DP$9u$uFXIUXV}{Mmo-@<IkBTH=3!@FFlX-NA
zVPqqZXv{-2B6xg3#Ap-TpEQu<-!zy;v*d6Z+B9^~knIHi5<!S?R73tx8u3Hvzp2n6
zx{*B=r!j71G~w~_34*v$8M!~n3}yeO!l4_j^+J!)pwZBqqmd~a6&Q^$!%{=jHd-&s
z#M#=pdwEZ#@*wdpo;Mytyffh#seo(fa1{qW<iLj<<PdJcVZj9(F3jM<94<I;VF4FB
zxZsNm3q%SUDiK8p)}TW}=Ydt|5G+H7U>!OH3(+B1i4MV1bO_d>L$DYf3W3XEdqe&Z
zWyVm9C^Hac2BOSBlo^OJ15sum$_zxAiS%V6eVIsKCeoLQ^fg1WW=Pfy$(kWqv!N`a
zY=$VCBP|h1bOaLv87~8Z`!bO6G7!&9q@Ib?Gm&~0Qo}-OSi?2&Sy&K!78XK=g^*z(
z`Yc4Bh0FsR>B~m?vXQ=QgsNB*f*5r8<ijECo&(oHNaGLzo5^Cb;ReGY++a9_&psT&
z7KTH3w8J4h<lzt|Ivm1SheCwUa5&lxAvIh(+Rf|_-QX!1YG6L<!F<Gn*{EM}IO10v
zj<gkre`q^$VsY(=UvW5cVsSWfVsSWfVsQw+)L;+d5PrQ4hwzJTI1C*P1*74j-$Ud@
zGt`zpYKITo31#!3P(B11F)=s|N80xm#?g0h5PdYpia$~C@TUq%yrXe3DDeYqfB+x>
z2mk_rz<-RuHWFHdky9g+t^RrnH7gbwCH^9lQ7E#CN}3`{B||GsbL4BJxi}9u`(JEn
ziU!PqD`EjG8XX@WF)bt_To5IkGV~M_!ZQtt6T<S)Sh^adg2VdAVHFj`4TXYuVM4qh
z&Zg*Xi+v?=g{v=m40~V072M`V2Q<8)4xtu^AE!6wndbLiwvnqcn6Fu9v$ci&)5LRX
zzF8bwo-S3f5Isjpq~9^6>%+`!hvyR$b0;r1iJ00df3*2>^lrb!mN=SkQ2foVtI<=V
zYHPp6%{kGz9N$o2LBQY3E+EkoQd+M%FDgl~JjPhz?y*x|rKV#nIn(XSd@nlR_LsCA
z6Fr-~Roi-gnviz-th2pAwr^>EVs2q}%Ihg7v@`-%Rd7x)f3-e)JlsNkx24{v#nh}7
z>yM}PDqnOywTOP)YVW~?#TAbJO=e+7W@ZR|S=<uE#jkGXY)c;hgMw<J7e_lPb^ZGH
zy<0syFF>9To-(H{r{{0k$elMKy!j||hW`!yxmvg1g`iPrC<UDgMdAp?8BQ7fB(yvU
z3WjV)4;3Mn95pU#{J|TJs;ksp@@?eTFqlc>fA&v*6+Ed`Z^8`>>P;2~jmDs<5D|wT
zQo#@)Z5$yu!wD_G%At+r$ZvYFY)*YjS<}JXJEbJK6toh5iM;o&l<;z6!U0wHrMrW;
zCrO+7oW8a9yxxUsQ#zod@pNZr-Si`vYNOKZXK5!6?-;v9Z$K%iPNC4Mqk?NN^GMjO
zX(l!40nKfS?<bjblfpbxc&C2bq3m(u7N`38q&oz|UjnPwPDV{Q<i7Db`pZ1}iHcp-
zs9xNQcMDC@(Y$RI*d0&aYbE^>mr|+t^o{RQIm?>)Hz)EEt#Sr!V+JPr`dT-J9rMP7
z1skRP8aepz^cKMkp>Ft>&X5~p+x&Js{rc+VRx0suQ~v#G!IFpb9TMxG^t3on&X3%)
z*+1k+Zfs%T_0p6p^~-~8zK*wPs6Tb`i`~-HuWzAE)PYAu&o4}?y72b%qu5Kj=gcQv
zR#?YPB8aB}BW)V03U55#Fom3UvAgG|+UM2y)2o?fPjyQfnY~$^rF92Aq)vnGtFoS!
z0k!Kcvtl?OXmX-0rgCFDwDxdk1egx^CFvDBw9vdbr93UqRf|$%Us%37CwxHd#_7Qs
zy;Q&W7@wb2GAn&M)|+-TmM;(B=F})^JDjlCkoU<;%aF|@a7*$+9O5h&Qn<%<ihO(B
zW5%{DB-~o){@yCoDy=eLQ@rZz?xL}t_-_+VQ92neTN@lV?<D4ub;o{u?^F<NJuCa(
zz6)eoDEQ3sym+TI=B05RjdUc-X1n!{8%lQXniSW*F0Jb0_*hD%;Ilu<*L(Wa*8#^`
zPdyqhrVy=tUsLmO;O2L%sgp19k`FbS1}5n4YQIul{gcYPd7plC`l4ju>6v}oty}$I
zZd!a$K5=Esz=938Clw52dD1qY*K~I^^l43hAygMC4KgurededHU3Id##?_OtZG7CT
zS&KdHW#z8cQ$q6>Ej*iMmhxDO?f6n*$0VWh!nw98YdXgnm<w9)?6Qq}eFPRaFFcWj
z0_6CqeSK%s@|J^ZYvb=*Cofz31EoX9&+8c}p1UY3q*p41IiirHw{nfQF)Q2l%y}iI
za42<d+0nKi%sL4LmaUVbelhE@<MU25;jY?`%kRsYR;;bQ!V=24RG}=tO`&9;$sS)1
zyT%X_E=U36$vfZlaB9Cj^x)#kqIurS?*-mqZS?Cn@>BTE&H{hZt!V`mhr+Fg7nXM`
z8Fc38e(c>OC_z0-sj}&wewyc&qEr8Z_RP{rFFmJPHRWA`YrEC!%ow-rvQV&3IGb!f
zf65$c8UL1|uW%u%SnY|?a(ZTK_)fL64de8sQm`3FElFN=H#5SJ(}>wCrqJ)ZjO%~P
z-C!jzC&O|7%p>@D?WQ^INqpw6LGN1r2fJU5P27L|RR3z6)yZ@oH&ItkUc2hW<pq;x
z*qlG*j1LZ6)w=R<!}DCV>tC#B>?@7t%eu<8EDBp%6rO9?;8B5}bS5vtQ`j-Ha5=Ht
zYGV3yr;g?*VOo2aTLPBoHY2(AZT)?%<b5sfu^-w@d!5{jFUUfn)+E1%-)v*zcIfzY
zGuFYu-KOZo<k#w}rmH9J^4)cyN-Bj5Dk<0YFHMi+Cmbtlf!`7PF>k$my_Ih0MtQ~B
zR37cvx4)^G{QBaJq5-R*CA`?h_hJ(Q91Xe8>ICFaOYN%*izaDScK7If3iKaKYvUx?
zPSmHNlC8^QLfb>qgBrq)7wSN%dGZ(E2$uyci1|4GaFOGt^#xBiJc~JT*Q0p#H1kVX
z&f0m~g1bbM`<w7<vUMY$Y|oM3_ul>7!QA#w_8!gf{cH69_YZ6MCO=wye_s3m8z2A(
z00MvjAOHve0)PM@00;mAfB+x>2>h237=x7~z_&q$uKx#7l)?Y90fONG0)PM@00;mA
zfB+x>2mk_r03ZMe00MvjLI8_J5r(e+yHJ!a00j^L1ONd*01yBK00BS%5C8-K0YCr{
z00jQY1hlbMgzx<_Be?$mC(kVK4hR4OfB+x>2mk_r03ZMe00MvjAOHxA5`eG&zxRiZ
zhOYm+QIzh{_8<ZT00BS%5C8-K0YCr{00aO5KmZT`1OS15CIJmBi}3IMaIw62IB3de
zcu>Tlr+?=7fNMYi5C8-K0YCr{00aO5KmZT`1ONd*01)^d0R=7i{{J|V8jA9sVoA}a
zlv5HZ_b9oP`D7m1j{JzcpFBveB(EVcNou4Rk{79))Id5$+DtYe<G!Z}>Hz^j01yBK
z00BS%5C8-K0YCr{_~#JN#oD6d!^46?1aZP328+pDWD&v$8v1wl42HJEG^C}2<s!Y8
z3RaACrvKhqLsAwRD=BNHE-A}alayttO3E@-BxO0ulCtJXf5pMz&{-TYJLJFKTTmpX
zIb=y`KAR*d&E_jgO7mF?lG1D*QBv9rBK$Q?XA9`i?*qe2OmoIaO7qz`Noh7;UQ(LR
zl9QBX^RSZAW)MaJt3!z4#m6s?jteD;-<D{KKT7-n8z2A(00MvjAOHve0)PM@00;mA
zfB+x>2>j0pi1fAPZBtZG-~Uk(Bw7On;0VSUP8t0qv^*Jdhipd=6(N=!b>=Urj5EiQ
zW|ZwR!Siz-?!<o!8oc!8o45T>GsoqpQo1o{GzLwDh&cR^3Wo5<SdN-@9v{2^?1AMQ
z=)YZSJn?k^y3+pJl~ZwO4O2ZQ=D3-^!Pozqs7w^ak6cD3lfo5i74;NW67LZq!bbcv
zyzQ7hxPF|Me5pK9ZV|Q`tBYBQzK>>zJNzd$k-nB>Vvr2PmbrR!xxoXeh55Hj6%P=!
zHnb?$wQrAol=U2{@LAPIkVY)#V+O0+H&V5{LbUG1-TYkPPU<R*w#Y=Da7(#&!P5p2
zO+%hLJgxusXM8evOl&pCP+QzDoNiWaxjE+ZgR*9u_F~&tKN`GyXUdB>c21hdt!P>a
zRaHLkrZY4n4&A(yP}n%-Dr4#UUs#!%igR|mh-hOadX$A@DCY9&xyh;i#dW5SdoXw%
z3(JNVPxS6vw7Ps*VKKe%t~8I<k#PoR=%jb`l!>?l8pH){-s_sX*dG}04<G-=SiAr&
zqNz*tXeI+k^VIuV8=q@taSzqHZVu22)0sfI%L!9))X`l$@!iI{d}$u<_L)ToZ?c-V
zeTMq+`(8KC9pfe`Yu(;(KxD}oXL@qxIT1}wqDQt29*tkEdBD4Ny`U#BPxaP0i_JH}
ztMs5NFKR6V-+mi^FVaw&$MBBy+_DF^r{84k{-N2x`LqJ2-?6H=?>b)<(_Hv#><JM~
zRia0h3?7}F7gSlt#wen*p+mD%wR`(3sI%j*eJwR!6ENmxkCmS^k9yT#Zj*dtN;~LH
zRe7B^bvy2Czerp5=6Tqr8hXK?L!OAHBGDsL29Hl|`r3Ar7Af6xACperGT1t6&k6zl
zX=2&j=SBnPZnqMpd3-Qw%e;y@i~JdNo>qnr@$DfoweN%d$q@!G%Zsxf4U~vz$`U<t
zWbjDaa=*mtH>~m5%Na#xaqhI)E50RZqLt5BU#)7KVt2n%n#V$Y)b)>RgG$Yt9$cmH
zKHrn?ZLWQrIq};%zxf+?Gn0!&G$n~1&1LZDH(u-gL6$|rr41H>CC-JjP2T3vZdDi=
z_dSbamNu0fmgdp2>f|@Y^?j7&z_#i|g&h+r7hJtOS3u!5PSH%6dOmfXNT2ezMf}hA
z1Y|Hdant<8TMpH2z4PHjhkCvG*X-4fCzY?o%@#gSy3Bo~YA4O)8_U24UbM{P#Yw9M
z?bl{Br#{p}g|HrO_2{^td@^g_RFOVevPoHH`_SV#HC~yEm8Mpl8gE^m&~%cwjA-|I
z!P`@Mp}Wabn|4Vv*_pb!%MXQ~o11Rf6KvGJ^?`F>o!)cQ?8p=%1J`CKC(<WLG|87`
z`I)uF3bU;zqinP90~aB`JH1@q$14>3@oinQ(GK&-SPf|=->=TwBJ>F@nqF7)oa}47
zaPiIZgt^XWwlgvAMnT<^<syAWi6+^68K(QlwAy}62hk+Tk`07J&m#}=E<D|h7gZj<
zLbfxe>{pGFW->CZ_)Y%=TSl<L9M8J}h0S+$&c%qDTC4hE($=MfXQzqu6(pME%d$wC
z+D<#{s#iM2=;hs&9UE5W6s>N&{y9wT-ZQq5!;A5&tE8D!p5PsL9YuY4th}yl>c#%O
zTWt@jtO}bprt8As8RAkMKaoCBqDeMS29ns>aq8`y2)C8yAI+7UUe7A(DvT)7$88mb
zO~1;G3}}~Ta##AbeAmj;j<?RZOlmu%-DiBA_GNJG(}BY!hQ?o=oUKLr1c@fiWck)2
zdowTpU6Xr-d+?gir`K5*9X(t&p=I6V;?<g2O%E)%t<p?>)Ln$z-g|nmJ}zzP>#e-X
z$t!h}I(4*YCR?Iw@td6Xi1hJ)Gx_JccQTMX@q*>@S;;i<XM-63b^Uvmr46*sy-)O8
zlDm3f;?Y?B25Ba_@td?gId?3}G+i>UMSdEr%Fc8#SKG1TLUPIJ9=)5rBK<LvP0F$a
z-G5CikKo$ob~y1LXSgP_S1Tu73U2&T|1z*C$cCa&C(UH>#n)X0YIEI`UELO|=iJ;I
zLeyK3V6@O_yJqHl!HP}eMfy02CS_WJic))b$p5T$-L|OT+J5OY`(-6b7c^_Qhf+u`
zzE8f??~`UyW#Ovx_K)JH&=P}9&sW;rT=u5(WUEu_<EM$re$PlpCy4arC7P6J2`W6d
zbI+di&lO2j!`J|?;ywP!b}b1OY@2(+W2Pk)g=NxAvZB70_{JQd8*%Qens*WNJo-Z4
zt*eF^`}aRsSA2JQakxldPNGSfmLNy<c}k~VeVqSoteZZ4X31HXAN}tfo|}*z;@h2I
z5fUfW<cBePy9%a6t&echt@<#rrlp*?!Rt=VbT6|bj2!Q|9<N0DScxWOT7q6a<bGRp
z3S~*ynW*o7cJiq!SNhahKQQo$eZ9suIZYnYNEV(>bISYCy1Xm7?Um~BqD@@Y-25Jl
zheFhv?dqFJXZMNpF%nJ6v;=L>IQZ?_J<5yQhY|=%sLyX&j4N!$`9wtDS$iP;v0Kn1
zX(nI3I`gxWQ^B!l7ah|q90h9_ma@On*F)7LI`u|-=N=1@AzEICm7+FspWTYz?BlQb
zSYg)JBmsTzRW$`?m3BxZf+9z+_eoIj-!A`y7^gaO<+E+`zDDLADtPTAI!s*BU=zGy
zy(qul)8~h6|Hh^^ju4#Tgce}s(8hA)myetm7v|LELYf~ub$#WOo@LI6Hu~CJ@4TA(
KtL=5`OaB6Y_1OIY

literal 0
HcmV?d00001

diff --git a/src/test/ssl/ssl/nss/client+client_ca.crt__client.key.db/pkcs11.txt b/src/test/ssl/ssl/nss/client+client_ca.crt__client.key.db/pkcs11.txt
new file mode 100644
index 0000000000..dfffe92b90
--- /dev/null
+++ b/src/test/ssl/ssl/nss/client+client_ca.crt__client.key.db/pkcs11.txt
@@ -0,0 +1,5 @@
+library=
+name=NSS Internal PKCS #11 Module
+parameters=configdir='sql:ssl/nss/client+client_ca.crt__client.key.db' certPrefix='' keyPrefix='' secmod='secmod.db' flags= updatedir='' updateCertPrefix='' updateKeyPrefix='' updateid='' updateTokenDescription='' 
+NSS=Flags=internal,critical trustOrder=75 cipherOrder=100 slotParams=(1={slotFlags=[ECC,RSA,DSA,DH,RC2,RC4,DES,RANDOM,SHA1,MD5,MD2,SSL,TLS,AES,Camellia,SEED,SHA256,SHA512] askpw=any timeout=30})
+
diff --git a/src/test/ssl/ssl/nss/client-encrypted-pem.pfx b/src/test/ssl/ssl/nss/client-encrypted-pem.pfx
new file mode 100644
index 0000000000000000000000000000000000000000..940af9f2967aeb1d3be1c41db6cb2d1e21c757ba
GIT binary patch
literal 3149
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

literal 0
HcmV?d00001

diff --git a/src/test/ssl/ssl/nss/client-revoked.crt__client-revoked.key.db/cert9.db b/src/test/ssl/ssl/nss/client-revoked.crt__client-revoked.key.db/cert9.db
new file mode 100644
index 0000000000000000000000000000000000000000..78bd1528d61535531d652ea1fae1e1a0b749d696
GIT binary patch
literal 28672
zcmeI42~-o;8pmfcNdS=yLTX$pM5rQ*I+KJDWhsk6QI@b-QIVH~NLiyvuvIH4F1WO6
zrAjGQX{}X33${|NRDHas^hxoxqO>m9O{+XN)C$E?%e(g`EOO}awZ(T%Pw&9oe0TZ&
zbMNoo`I4C|vC-kFCY^Y$-jJy_iKT=+L6StESWFOvHQ24d-nsFB!R^d|N3w_gYo;|Z
z&i9TT-A3>SG!eEx&?jswY}0H#Kn8D^049J5U;>x`CV&ZG0+_%PBoN5wi$u<3LTXmB
z?nQ07IyE`VXjEluGE%ixAu-CJIHfo&a*}d}7}bfRBE?<x;xTHLk+=u&kU~6C$WUu^
z8C@;-k0_wJ-U?FpNnE~YxHFlE{EgX~G@aJerNDbc!BX2>gJoFvAc`*<+BuF?qcK}&
z=n~;RB4V!Wt)XDu^P@;Bk+U<oKGUSuWazrwy9|y+CoU*BT-lRDYO+`r6BZE^lOUd^
zOc2LMhDFCK;SoZjB16N&LgK_h!BH`BUSc%^b{6asuuGwh3SbEfixe=cfLR5~!X^qC
zOuS(thlvkN6fl_p6E#dUAen%q7;}mg8K}WB=HoyWmVq)X19eyi3b71SVi_pKGEj?U
zpcu<&VQT4~xqnDmVz!a81Sw09vIHqhkg^0ROOUbzDNB*B6!}V#uN3)8k*^%p%2BNx
z)yh$=++2&4<w)5FwM3Lygh_(ND`B9o1dUgM`Yc7|QdBNQ<uX(vLnSgx2|S97fk%-c
zGBQL)hV*4fUxwzv8~J)8UvK2=ji>@mhLN!F;1(11UI9xP)L5(Ymdd2wuz|&d4J;-+
zy2XTDu$XYP784HHVnU)86Jj-+h>ykWc0;5rrQL4w9&T_-%nf|H`{2{r2YGjYf!Wz#
zV0N|zW{<X=2L`2`{RL*{fq~h1U|@D07?^OW!9D;Jt~ZMb7oEjS?lyJZ=G-n5EgExM
zO?P)Rusg}#Y9?93FzzgeFH$;_ggLa#&j45SN$PBq9_5p|&g&$$%cMZ4h1d6l9oTtX
zdt%@)TStB^?*uUMh6!K-e^CO<CA>vkp+Ml!d;CHN86g0F1R(&m4l4!{y_=xA-fq7s
z@7lV<9XN1EVka>ZdV&XTp@eW7p;%jRwe=NG*6&kwb^2uzY_JaemxxAd=jQAw->Gt%
z5w@mg+}DeyeYk(q(q>o5$pTK+(zJ?($X04kV6@*-|Abrb&;7QfHpJd=xORqL?6Q9}
z9#Llr_Y6K%FjDeD6x-Hr72+IIq=}UmOmW^@dZl7((XIBt+HdrALfumpb86N`x5T-Y
z660hghS7DSOxF&0Qunvb?T9@4a7*CWTQA<2dSf9w`Gcntnxh667gwna;+pp39*&_u
zzqaJmrP#uR&3Oxrr_1?Gz6&mJQpPP_x#06lgcmpJ0YAb)HS^{hM+=WO$R~Ui8sWQM
zzT)PcRnz0Q2UxrBe8cPPz{>qc)~wj)(7GkP*5vr`q)+LfL&4weF{F0z4j%h~1h-+y
zJPx;e(w5oxo|wnY0i3wswh6%oJp?i@!nI{$)4yXwV$qB+fouTd$EV`C)IjezozWyV
z==COXND#ckELQ1_rWAuNHac7!8yhY*=s?D3Ox0(JO|Ypk8$5AwNDc|)VtVexLs25_
zF(<IJqh&qirI`5h<qbLhE|QAfBkIydBp1B1N~2q}WOXR@5?r~XiI>L_!bd4A6UrVx
zz~7FcrzXH~4dlVX)MljWvP?-@^*F7;#N<(SOdi=%LV<ySf5wh)<FRVWsvqr0(w0a3
zabAp*$PT<lWF<bV6ui3go++}E$r%|dvt*fIHjoK0&xjA2k^gNn%%c!%bq13W48Wyl
z$4MSR<&iW2$_H@rz>ur1Io<onE@x@EYr~GibZSj)=KgcvrmXYTZFizK>$a@D=c$=(
zxM4psr!sMab>mg-ScOM}M?!{a{HC*$uiLzK`^zTYX}8ett8cp{XD(msQgLAA#Z3J#
z>9peWVGU=B2Ryac&1TyVl`{vw|BF?|+dEq-{lmjs-s{+v6g){pg?x8v)>U2^G019U
zP1$Vcw0h$^pE)Kr7dn`dX4MxyyWpdMcNQ(WtMXMek{uhlZ;m7;C#n>Qe+w-BAi9iG
zc+-AfR`r5n)5x|j{COSWTVMUukeyOpsGC)E>1IxKxRcME2QKQer%P`P*))E;cDvJ1
zkMxw5xb^D&?{eLOvh%8xK5nCIA95&g7VfZT1k6A{dW0jF$515M(UTdNY`}~poIc3E
zH}9uQ2QMbdqI}bvInR&DrN+-lnVR|4?KN+1Q0{bZFRGrn{7_wWV~*_v_j)Qqw9Azk
z!U?|djCNXW=1;GmyHdR9?JK3Zd+o}?R@N(4+ckL?+<nDCQS!+x>&SPHZw_AAWaVDs
zsP-kaif5Ghd0FqjdVY8IH>xij9o#sZD|0q)9mC`eXt=#nJve0dr_<VV_>~(uE@|bV
zArI1KZd`sSOm)hb;hl70SFtwVdFpM!65qoU1#9;n<5azSeR9ga)8%Kf&P~?(tA@x2
zy%ksg?7A%_`tXJ=H>uWBjwz*v1FjG1U*u@4%WFp6OS`k<Zo<?yC$Ez#ZKd7cE2)D6
zj|2z9bLNk)dUz)F!@`c!vzJdUtcy&oxmB}=$BAx?Il6$%<8pdLQ{n$Pn%);a+Z|2&
zVTgUk)3RAl+YjgZ&N(whw_WXD^m_Z3xyny6auk;(;xShbv}On<J{Om-B^SeZsvy>R
zfq41|2frsDPVWl?%;D5%%ouMDMq>@S1^RSdvN^7D>OlB8&__t^2WXECs)Jb}<6sV|
zwxEOX=_}i)Gdwm>{?%Wupg`GISTuzB349ytg`P>F@{3ujyTrbI2l7sz-t^qIpD)XO
zSG;%p;hyg=l%?mc);GT~=)r8og@r9Ayj-pi{!g<(u+`N+(8-=@+vJw(?h`RJBsJ&d
z)43iSk`#;Ah)zA{*JyR2@z91VMK0ycrekk;?k4Y?k5A0paz2)F4m>=4#LPm^&!Y5)
ztA31bi~p9r?{c^D%?(qJtg^YF%#5`;@_Ez%`&Q+xK>LHk+3lK%`<AM{{-&mCvD18?
z>|y-i$feI*YhE>Wo9@K))m2j>+(raMq^SQnf0)|~Jl)aC`itw6x1EbW^^zYM*`A-j
z(SKU*lBA;Ctrc#|pD!5d`LE*Gl9+iPzLh?vU_QDQ?~jNE&xc=GoPEBM9so`<9(|Ue
z+vsNcDt!r<c*6uR0ZafBzyvS>OaK$W1TX<i029CjFo8b-0c(m&!ne|R$N+E8a4p6)
z3K`%QBgcy3+PH#yZ0P!5aE=h1qelrY(Ff_wKfwTT7cl`$029CjFab;e6Tk#80ZafB
zzyvS>3jr=gki5y<W%lGB|F99apLD1&?lK|SFYoeC0ZKOgp=5sle>FjWO>d;H(3|Pi
zcnM(wm;fe#319-4049J5U;>x`CV&ZG0+_&)B49;1kl^dAJb2%N+i&*?C@9AnPWjpV
z+Lr>%*Z&_AbRGEg|5x;J`r{`xLad7kU;>x`CV&ZG0+;|MfC*p%m;fe#319-hhd}2&
z1Ir_K5H*^7<j037OATizrLgJycQnk`|9c7gGJS$>qW99Zzo#eI2NS>qFab;e6Tk#8
t0ZafBzyvS>OaK$W1p14>5K2b&^&@`f4-LTDzJAZt{22fdC9~<v_rI&8?9~7O

literal 0
HcmV?d00001

diff --git a/src/test/ssl/ssl/nss/client-revoked.crt__client-revoked.key.db/key4.db b/src/test/ssl/ssl/nss/client-revoked.crt__client-revoked.key.db/key4.db
new file mode 100644
index 0000000000000000000000000000000000000000..5eff0aae8ca3c4fa615a1ae29580f9cc407581b9
GIT binary patch
literal 45056
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
VuUpY+#=ZPFXt>|`T2$G?e*uT81eX8+

literal 0
HcmV?d00001

diff --git a/src/test/ssl/ssl/nss/client-revoked.crt__client-revoked.key.db/pkcs11.txt b/src/test/ssl/ssl/nss/client-revoked.crt__client-revoked.key.db/pkcs11.txt
new file mode 100644
index 0000000000..67ce598fd3
--- /dev/null
+++ b/src/test/ssl/ssl/nss/client-revoked.crt__client-revoked.key.db/pkcs11.txt
@@ -0,0 +1,5 @@
+library=
+name=NSS Internal PKCS #11 Module
+parameters=configdir='sql:ssl/nss/client-revoked.crt__client-revoked.key.db' certPrefix='' keyPrefix='' secmod='secmod.db' flags= updatedir='' updateCertPrefix='' updateKeyPrefix='' updateid='' updateTokenDescription='' 
+NSS=Flags=internal,critical trustOrder=75 cipherOrder=100 slotParams=(1={slotFlags=[ECC,RSA,DSA,DH,RC2,RC4,DES,RANDOM,SHA1,MD5,MD2,SSL,TLS,AES,Camellia,SEED,SHA256,SHA512] askpw=any timeout=30})
+
diff --git a/src/test/ssl/ssl/nss/client-revoked.pfx b/src/test/ssl/ssl/nss/client-revoked.pfx
new file mode 100644
index 0000000000000000000000000000000000000000..2ab711880535eb3cec99c033365797487765f042
GIT binary patch
literal 3149
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

literal 0
HcmV?d00001

diff --git a/src/test/ssl/ssl/nss/client.crl b/src/test/ssl/ssl/nss/client.crl
new file mode 100644
index 0000000000000000000000000000000000000000..ec061fd17bd11aad4eea2cb4980cdd9620be5f9e
GIT binary patch
literal 418
zcmXqLVw`8t*lxhf#;Mij(e|B}k&&B~!NAGT!N87<Ih2K&$2=spxJ1F(Q6VkANFgA<
zxFo$OH8{{mAvoAap(qu|DK5^;&r>J?sY=etOw9vQsYNBl;=G0yhK5GwhQ=larlwKi
zye4J_CWa<Zu7QYw5ECO4nj$o3FgGzWGQ=5uWWFNvib1cOm7QIfWA#LK*3-R$b#Zr+
zQzt9Of9Pf2QgXg#^7|Q#`?^;~od5q&E_3mbwX*KnbDH;uHx}8yc;&n*Cg`u>*0_IV
z2Y54&+qGrPUC4dt$>ZO*)%q{4x14vAo8!GyW3<{9)zs`N>D*O<h4!Wm&zYD%)*XAW
z?8h?G^mEIM=O3^Yk(m1JZpp$KRu`9gue$prUuR!%Rb1VVhy91s9M8Qw^)xx^n<TUM
zv3Uho7B_OK8=h5cdh)~l<(Z{@Hw(mAHc$Slv&lrgXD46g|EyT1`^j4q@6F3L%d)b)
nrK%&VUs_%AV!{6+)9&7a`}PORtmIRl{k+g!;Q6Fz-w|N|QYfe5

literal 0
HcmV?d00001

diff --git a/src/test/ssl/ssl/nss/client.crt__client-encrypted-pem.key.db.pass b/src/test/ssl/ssl/nss/client.crt__client-encrypted-pem.key.db.pass
new file mode 100644
index 0000000000..3ac5759692
--- /dev/null
+++ b/src/test/ssl/ssl/nss/client.crt__client-encrypted-pem.key.db.pass
@@ -0,0 +1 @@
+dUmmyP^#+
diff --git a/src/test/ssl/ssl/nss/client.crt__client-encrypted-pem.key.db/cert9.db b/src/test/ssl/ssl/nss/client.crt__client-encrypted-pem.key.db/cert9.db
new file mode 100644
index 0000000000000000000000000000000000000000..2a50e21d0e379f2b24547cf52127b06329c6bd67
GIT binary patch
literal 36864
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

literal 0
HcmV?d00001

diff --git a/src/test/ssl/ssl/nss/client.crt__client-encrypted-pem.key.db/key4.db b/src/test/ssl/ssl/nss/client.crt__client-encrypted-pem.key.db/key4.db
new file mode 100644
index 0000000000000000000000000000000000000000..3884ab59f5568f08e5c65bd753fe7a24c4bb2ae7
GIT binary patch
literal 45056
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
OLx$%;!>EIuY5xMt<fVxK

literal 0
HcmV?d00001

diff --git a/src/test/ssl/ssl/nss/client.crt__client-encrypted-pem.key.db/pkcs11.txt b/src/test/ssl/ssl/nss/client.crt__client-encrypted-pem.key.db/pkcs11.txt
new file mode 100644
index 0000000000..190f880c0a
--- /dev/null
+++ b/src/test/ssl/ssl/nss/client.crt__client-encrypted-pem.key.db/pkcs11.txt
@@ -0,0 +1,5 @@
+library=
+name=NSS Internal PKCS #11 Module
+parameters=configdir='sql:ssl/nss/client.crt__client-encrypted-pem.key.db' certPrefix='' keyPrefix='' secmod='secmod.db' flags= updatedir='' updateCertPrefix='' updateKeyPrefix='' updateid='' updateTokenDescription='' 
+NSS=Flags=internal,critical trustOrder=75 cipherOrder=100 slotParams=(1={slotFlags=[ECC,RSA,DSA,DH,RC2,RC4,DES,RANDOM,SHA1,MD5,MD2,SSL,TLS,AES,Camellia,SEED,SHA256,SHA512] askpw=any timeout=30})
+
diff --git a/src/test/ssl/ssl/nss/client.crt__client.key.db/cert9.db b/src/test/ssl/ssl/nss/client.crt__client.key.db/cert9.db
new file mode 100644
index 0000000000000000000000000000000000000000..96ef52b347114a4403c97567215841487f8d9d29
GIT binary patch
literal 36864
zcmeI530xCb9>-^L5sokjXpkyIKtVvATo3`3LqrfE9HOX*Aw&#vSdu7MMTsaLtq3lt
zMT>wIDp;t9;8mq9)&tjiVAV>+VlAr_6szJ@citpi)~@v<-R)=lMrQJV$N%@<`)1~E
z=FLnJ667ye$~f~9lj0>x4j(Z>P!w_Ja1aF1BUW8v?ON!BLF-BouV^p(_e?!xwCi0K
z)`>9mS`hvFSe<@_eyqMdk$?*%00}?>kN_kA2|xmn03`4y5^!fQ*lbHQT%HguOO`H>
z$fFY!3b86GPA=8)3ikF0_2&2nO!S_?A<H;{0i5n~j-7;y^G1%RlM2&Fg*Zu+EUvo-
zqmKev_Ob$h<U|^S{f;FXLHa9HQL!?qvRi@PM?qWqvIZCDj`W~1*n_*qAy+6=vZQVi
zS|1Tj;maB#?#PTlREKS8iEfBjN}}Rq-R|86MXM9);py+)n}a->BM$bR>Jc2ync^MJ
znHJz1G|d~d;1w7!$=}y2l;hzU7#!-zk>JG2B~~7>@_|iefE+H!5`m-$Bt>KrR1pcl
zMhG@eVB-unBCr_?HWILjA~s`5DO|H96>*@!#Wn2!6)p~BxH!<^;y{Rt10^mFq_{ZH
z;^IJzi%Y>)+dWPHNM)YJCY5=lGLKZ|k;*($nMW$~NM#<W%qM;Mq%WWJ<&(aA($|SB
zbs|fh$WkY=)JaoHDm#(N&SXuJ5|_lpBgf0bfiI67FOTdqpUmfz`Ft{8K;{U@9Dz0m
zv?9PkD*}>?fFvUz^#!E9fP5c>q_2?l6_UO}k}9Ez<2){CTx){fi$E?;R+h?yd;wnw
zDrikmL2H86wI=9-)&!%~nqbIU6A;y!0IS9%`Do1^H<FY#x5v$?mm7FXG!>kC`rzEv
z2dAF?5@uI_3A3v%VfL!q)i9CU)nCHwYM3y)8YaxHh6xkwYM>8<3HCRw33fWI8Qo*b
zddzv<Cb`pS>PGc+Ckk{YS}4Jzqi}pAm%?CsTcU_2w7eTfoY6;0RLVp$9oc<cM{>JO
zDiLa#Al!nJdK7yKOkK?jFyR6TKmw2eBmfCO0+0YC00}?>kN_kA2|xn>KLnTzaFJgJ
z`vSo_v3Beh)`(riP7_JEKmw2eBmfCO0+0YC00}?>kN_kA2|xmnz+Z|0lS)Ia@1Cg8
zA&qv?eumblqLW6OX)aA`<WNbY^+GO1hf346ZoQC0p8p$8Mhquo!wnk^a}1|q)mZ#r
zYJjjakN_kA2|xmn03-kjKmw2eBmfCO0)HU_G%AAX_|VdfP~4q69(Q9<r_rbe!cdt)
z$w^8~RC2sLz(eL7aiT&QlOziX^5=wv_;ZqEL_(pECnj)|psGSe+`G=`Cmnh}>CpN~
zhw4j9GevPvt~)+n!^fHA^T*Ig<K(ggB}XbtQYt8u2_@bBP|{rgcSW%4*hb6?yNON4
zaxmAw(2fPW0tr9@kN_kA2|xmn03-kjKmw2eBmfEgZwcs7jnQ4mOdT-w(Qi{0K#bOJ
z^8{2NM|p?pri%~an&QTqSqFWEA`Z;9s1&j!&H4W>gt+g&3Tq>t0I0|IV7vaeU}5W!
z03-kjKmw2eBmfCO0+0YC00}?>kig%PK-Wxz_LVV+ilgAB3J1+S0g+P4Xlas?t}Uhw
zppHQMlu)%LlmS#bUE<__jHVPgY0m#QAXqb2hZSRGSPQlRJN37u3<nJfKmw2eBmfCO
z0+0YC00}?>kN_kA3H+A`m{A3&d&#{kUZ3lw_=ll7!jDeXGB%v{*4$$NN*QJTfnFZ=
z16#c*U*2YxJb(9nse(@4GyqCN6S>VNQ|bfc$A3Ql37ryjE%@_AntK4)RDtfRlv%{c
z7>GAwV5R>K<2?Ng8bKu9bWsmvETNei8X5z~G$YX)`WwDseFQNj7T{zka73($uRQo?
zh*t=T5I1AI{lg~|QzItZwlnX`oefj{>^>C7xqaOb5P}jd1ovK%DR%SZ-><Ar+&-x>
zcyV>sQQjT-;27JIk7L`oS;zORm^59;_F5B9UkbS3$opc%v@e)6Bf;y3{pMtPYZ7Ft
zh&1toj=!BO!qrp@Tuou@pi)rEtH+G97`pbWC$E0QLQ#D>^7d$P3pR`RITJ3=nad}B
zh(s`x%@R1_0<J)t?1vWAs8#Q7qjZF-Mll3eqk0rI0b>8sOC9TITi;c?jdoHWY~8>#
zxpsZ>{$G+8HN`OAWzBBfGQ9{j|2jwR-1)^JVd>L}`_?{cG|yXhKQW);zt~l>O2*jk
z+WAaB((RK^<ijLSY%bn1%e4EVOx(KHS=Tx_NO#l3v#n+oiF;J5);^&Hy8oD;G0D<3
zwk~ZU6R%lydv@-b=W|47H+-<J?s;ovZKe4LU&eyFyy&t=wOcYvCto<AZrGA);82OW
z)LHs1K=&?h$*}SC;*T$k7~?t1;oyvC2T#r4Zt`Wum8Pb<DP!#hv|qB?%7{U2__k*M
zIDCv+R`_^KVZ|6@*_ne|d?r2KQnS=<NUdjd!8PW?_o7^5=G;&fQK%?_78&7&xB+o5
z&`=W^9jBt`i{8wLx0+yvB2#czN^YC|^5Gjt|8ko)<@)~W>o!+nPBq75#2j{rZTVMx
z&We+F*ESZO&vRU6EOzI9`pA5cVpJ>r`7ZXuin)IAnS6zLX6ott(^}?!oo3!J$fjai
zO~&`lnPF8EOLzL0ri(nRb{$?=<afHRvt3pEaJ|d6^gMm@ODzjmN1m&Aw#ezLhy3-M
zEL!IuaVr@^)2n`d@H>n5emJRDXBKza?1Xxh$>_adDXX5XsC=Xvl|P;7{bXx-xc}wV
z`yF4*VT~<3U^(yUeSX%>#KT!F+peCoNtvzIpD|=bd6fOIN1ewU*&}N63#R&HhLzQ%
zH>KHr|5!Kqx?M;zz7tV3EjDVKLtjB{1V_@_?~%WAXubV2a<BM*098|Hz2h($j(Z1$
z*Vu0!ZTiGv;)`bbGY+Ga-v-_1><G+$j{EKx4>nL8MJ~^pGgHS|@hVD&*2fNw-n4Ud
zlx)eeHIt}h&>exiKN<wx-W1YI|NNlaW|~8ACJvxJQ}9H*Wf4*6fhOLfl$VaUx8KoT
z3a-Mw!Bsf3KOPn&2JoN5>!F`4Iu5W>SCm^f6dlCm$4|w7cKJrkdRJM#1(qYrUDx3d
zH9P5!(XiC2h)m|SThdV?`v&{)IOUk^i$1q?|Izks3;mq!q#tYBY@_2>uCuDxx9Yq2
z#9tR+qHoL_F68MAEVI@9=zi7AL3@AIi7VXoplY1I|AS9o6i0eaWK+F<I5X=Oy%ZUs
zGwgWjY|Gg5ik)XoBHA|_D<fx}-#lT_f$=++EV(Ck6<tGLY@}@;hWJE?MG<q|^D2W%
zDVtl37ADj#%2N*Oyflvf!hc)V=_FN5?Pl4mt&Odzwf+{)a~@ksN{8&eW0pN8Uz%?*
z*nUCGgU}6<&pxKvdZ^Tgyq#@_>py>k@M=++(XsGJWA%3tf<V^=?Uea;%c)~##Q4R3
z-?nypruVLqPq)^(tgNZ8y_Tv!cI0{LRCcj7Vn*@2V<Vk%D*nlq%Qy3u6yDsuyo^=q
zyXw4X4XZ`C>D~v%q8(rV%nbPWs~pdi7M+nhOeC&|RAl3wp-$MFb)`hrB)()~Y)i?h
zO3m43hpY7(+Ez&hd6k@=@-&rEwUJ^KTRzF_aqP^ED{FkkXB2V5$g9P9(hN(#Hp6AE
z2VD%;l^vxV`uMg_%${@Q7ZNV}NXLoIoCXwxo}aKjcSoXsLvAbe;Te;d-AVhbAD>UA
zL@~>c5ATS*TXZknuhYWuq*z+TI#xxkHaOzx31ZRP@Abrom;TtXU-h{eRnsWFqN!=`
zIP#i6N*2FmG$p>It0$WF$0Nfi`rCNQxZ0+wW2)_*`AjISP2siIO9r@|JfT;GH6Pwj
zQ@ac;b)9)7V;1@z_z-J{tal{hX&(-5e||V6zN3u5NrcnB*DZ<9uJuGX+DAdrmqTek
zz<O;k{hhn1UmGCzt=}=apyQZ3?Wj$-S)F^<6=D5|1Eyh_A`giL$DkMuFiNw!;c)Ze
z#BA@MMkH^xTRbYx@hW}y#bb$iGv>Q6hJ{5c&fYwp=$P=;S1*zRDw-EFPZf>TWj16N
zv4*MCSC9M6+oN_V<D~?Ox6+NjyfMh|gQ?HMgSk;2Kc?r7vUJ~mbW>Zsb^5+)KQEi?
zuult;Hy3BC+Z`&**|V3{h${HE$5ofh#}3SMv3xS0lbN!%JiTMUVZXiexaDr!cSe-f
z_{=)v6!Xc%)ygozggplHe?FJHY59;;eUs#1(K`t%*RK7gBP*!+5FYgH4}8bP+=7kk
zCrr1NU);?fKkJ`XA?D}qOUVHF(i=v8+8n}(jcXa)>NA*i>y%_=!MbX^y*SGB#GL~L
zO*+c#3pcaE(+exp@lJc|^2e%5<ziz>#68{)`)$*M{T&v4{_Xfrr;6Si9%e)7SiTzz
z+%qsgk1j8h1+Y^0FY;$PZo^Dma?U;}T-sE;<Km21elD#b`7-5XQE0m=q~^rXCuhIe
z^O29<5koh|BYCv=bj>tbbMcyQrq8TA${dwl`;6ZvbY9ePEIf@B{7_~ReaJ$|ND5jr
z{K&Hsaq;du*=p&LljD9FEw3nEcJrr4<<8%--0MRXD+=b0_%QCq=Aa$E&OG<IwWb!9
zC7qQ`rV)iYzUy1Xrt9*j@8uV|>Nq6aG58hR4?;$NL^5zZ{Laq#s?&cIod2VSQxM`0
zE|35u00}?>kN_kA2|xmn03-kjKmw4!A0e=kr_(cE*4x3<Sb!K3|A_0g#5H2$bOYq&
sxiqq@x62#b;2ZD>CklBL7<dyGgI{^VOH6}-GUD&=J{Ln=2}IC;1Gj|?5dZ)H

literal 0
HcmV?d00001

diff --git a/src/test/ssl/ssl/nss/client.crt__client.key.db/key4.db b/src/test/ssl/ssl/nss/client.crt__client.key.db/key4.db
new file mode 100644
index 0000000000000000000000000000000000000000..bd26c68db1e1d12b48ae7b38be0a2df3661cc094
GIT binary patch
literal 45056
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
MpZrikc6yKh3rY97u>b%7

literal 0
HcmV?d00001

diff --git a/src/test/ssl/ssl/nss/client.crt__client.key.db/pkcs11.txt b/src/test/ssl/ssl/nss/client.crt__client.key.db/pkcs11.txt
new file mode 100644
index 0000000000..3f0a9ff5b5
--- /dev/null
+++ b/src/test/ssl/ssl/nss/client.crt__client.key.db/pkcs11.txt
@@ -0,0 +1,5 @@
+library=
+name=NSS Internal PKCS #11 Module
+parameters=configdir='sql:ssl/nss/client.crt__client.key.db' certPrefix='' keyPrefix='' secmod='secmod.db' flags= updatedir='' updateCertPrefix='' updateKeyPrefix='' updateid='' updateTokenDescription='' 
+NSS=Flags=internal,critical trustOrder=75 cipherOrder=100 slotParams=(1={slotFlags=[ECC,RSA,DSA,DH,RC2,RC4,DES,RANDOM,SHA1,MD5,MD2,SSL,TLS,AES,Camellia,SEED,SHA256,SHA512] askpw=any timeout=30})
+
diff --git a/src/test/ssl/ssl/nss/client.pfx b/src/test/ssl/ssl/nss/client.pfx
new file mode 100644
index 0000000000000000000000000000000000000000..ba13f0cafff3a3d64ab5c1b26e98033614e71fcb
GIT binary patch
literal 3149
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

literal 0
HcmV?d00001

diff --git a/src/test/ssl/ssl/nss/client_ca.crt.db/cert9.db b/src/test/ssl/ssl/nss/client_ca.crt.db/cert9.db
new file mode 100644
index 0000000000000000000000000000000000000000..e0a8a94d56e8c8fae91d40d6c67ba5778f0e122a
GIT binary patch
literal 28672
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
HHvs$=4>)Rp

literal 0
HcmV?d00001

diff --git a/src/test/ssl/ssl/nss/client_ca.crt.db/key4.db b/src/test/ssl/ssl/nss/client_ca.crt.db/key4.db
new file mode 100644
index 0000000000000000000000000000000000000000..5793b879513b18bb5467717532e575556422341a
GIT binary patch
literal 36864
zcmeI5drVVT9LIb6raVfft|}szDo&K)xwp5aAahheAuv>g0fP*0p$f`F+lt_ZQjBP_
zsdFkujjxQkOvR}9*rp6{n3*_rb0NB^G1D-!5uY(SlcBrkwB5xx3(5Z3$gfFHfA{x0
z=X<{Ab8au(lF-?+(o3B-dTyn?oOjX&Nw9>XBndPvkw|31(@%JMA4(yRdk=(Hs!#l~
zP$r3<d@oS@heWN3m-ye&e&heSKkpx=xuS{J?0wlF$O8c&00e*l5C8%|00{gy36!Z+
zy3kO{HPOji%WUN~C!fSSdGD(_F*Dhcl}u+@rlu#;-Yh-7w3wcmnfjI`GmlP7&ZDz4
zQfFl+(=#&Y#2FdW(o++&=*;Aq>6XOgC|dZ<w48Cju#uD0N?l?oB`K{awpBY8l?lZ{
zezCKX92a`^3mI?ZH&v<B>B6ae(xGSpUs_z@aLlxqR`E`o%404yBPltTCPi#P>_Cs_
z8N@~=D2PT5(J14swlcAc@;MDsd_ltyIZdwAg@jXuZrh8k^KC^=QA6>ZhQnQcL4`p~
zWTH%|8{u6|sl&0@W*24T&&jx}FQ}NA$W@t?N*5kZt*s<mCJKm=)Fb8IHZebtjk8Rh
zk(m`m^GJ9K`^K}v(|}`=z!?Uo%=plZ56$EdcQJFgG2+IAn;6`faTALh9yeB@i6v6V
z-4aEFH5lZc2dgj$%P<J*FbE4V2rDrNOECy*F$jw>s0g<ndv1S3nRUlRnI+0BQD%uU
zOO#on%o1gmC>zLF0~u=|V+~}ifs8egs)<xhq-r8nle<ckO+-0{^dyuRf{7*TWf30B
zlJ&C0vw`FdByS*jj$}BJ;XD~U3y1J593jIIG91z8h(1U5!AQm$$yg&9Ya~<!O@vqm
zPwok^do#`=(z(cHG;jtZ?%)Y=2TzD+_k`GjC&a7ugm}rG5EJ!;7^^!Zd_18zjF9qV
z#bKsC!|;~4JH&_{V!R$qqF*8O`V~TNUm@(%*E_M0_4*Y;@5DmromdFH6AK|e)vyO4
z#OIqQ#3!96EEYqX7|#7WBqxo#uT`{T#deB~JSw&#6lstub<@LNc3~9X!G(+%<9mBG
z{;3j(n_%3)0|)>CAOHk_01yBIKmZ5;0U!VbfB+B}tOPW8VQ~LHShp9}1q6Tq5C8%|
z00;m9AOHk_01yBIK!6Z{`+qnQfB+Bx0zd!=00AHX1b_e#00KY&2n>D#aQ{E}_ZU_P
z1b_e#00KY&2mk>f00e*l5C8%|0Pg?c8~_4900;m9AOHk_01yBIKmZ5;0U$8=3BdjT
z;NN3dArJrpKmZ5;0U!VbfB+Bx0zd!=00I2}|J9l<i8eQ=B`7GcG@vse%zvqnfCmr&
z0zd!=00AHX1b_e#_`eWv>D3BL{VNhj>Ab=so84K+8f>i9%&@`{`7*9p<f=mjO1}vW
zDGiSUDMb*Pfh^)-0OI6yNmJX}<|Fqjp6v>_Hga#0am>12uI2Hvoc!MUht2b9<D?WN
zrD&v4s@*D5zrL}Yt}40VQb)AtS9X*~jZNRZ@5iy<r5!(CIX`Lp%!sW`LHk{Lm2Z>g
z0hp}2(mG_=7~7-pqq~<seq;2iQ7b-M^K{LnMH5UL0zUr!)!F@-+<(7uo1tMIvwh8_
zs+7#?*8Cl5s>opJnUR}gcm14uGs&e_`ZQ@Z4#4EqP2<j0tLrRJM%`_GCwh3su}>q*
zPj2s=wH7I^AFS+A_h)j!(avj^Gq0{hKjn6p?T>yIdOJQ$yJf-pB5O-esKm6>rC0bg
zX|xW&<ehI{`!aiDec<X(hN`ZFwM0vwD9SW#L*q*FPlcUaeep<tCWo3*)}0Qo*i%y*
zUvRj2G~E?wzh2j29--{H@kqC7GwssLeVVj#128%Ic8>b>-pg@Y_SJT``2}|6Hr9>o
zuACSmtGge!>S@`A{!IR`nhAO{^pt(_q#<X{XB>EM;__kRmJi$bB+8bvf@Zg*xb!lg
zCWWt@>;H{&JYhss^qH2nO;g6rkJwO?{>8EM-&+ryWlmpc>}Xn8^q@bJ2kk}rZ9yAX
z*3MoSUyx@^YkIeR>ZNo1#;Ww#hFjZCl(_U#pC(Oc04Cqw&N!F-mQ~gDbpPR!A+0rs
zPER@T^`YbMkN72FX__W`dVeP4e%(5M$L#{uPVHn#{fUYv9rk;{?Mu!EU(v?b#3bJ6
zc12K%P_BOHLf+w6QfV(<Ds8#iG@RKYiH<qFa_pkpQ^s7(MlKgB7q2>jXwC~4A#1OR
zzVPRv#?D0vuBzbLlwR({`1WolqBF!_xBi3nYyV&~MCmuDA%(KZ<kSRtP>=1wN4FlD
zKU^r^yJvWlJh*&XLDS8z;%@9R-3?RKPK?HX|5s~6B-)4CIBkgbn6_HmrQNK}`@dZ3
z!6y&^0zd!=00AHX1b_e#00KY&2mpb9Nno_hLcRPGM77VdS>>y2H7b3TjaG%PvXztj
UDjRv3ud)eA{biwk-p?ie0)~o{+yDRo

literal 0
HcmV?d00001

diff --git a/src/test/ssl/ssl/nss/client_ca.crt.db/pkcs11.txt b/src/test/ssl/ssl/nss/client_ca.crt.db/pkcs11.txt
new file mode 100644
index 0000000000..212e72edeb
--- /dev/null
+++ b/src/test/ssl/ssl/nss/client_ca.crt.db/pkcs11.txt
@@ -0,0 +1,5 @@
+library=
+name=NSS Internal PKCS #11 Module
+parameters=configdir='sql:ssl/nss/client_ca.crt.db' certPrefix='' keyPrefix='' secmod='secmod.db' flags= updatedir='' updateCertPrefix='' updateKeyPrefix='' updateid='' updateTokenDescription='' 
+NSS=Flags=internal,critical trustOrder=75 cipherOrder=100 slotParams=(1={slotFlags=[ECC,RSA,DSA,DH,RC2,RC4,DES,RANDOM,SHA1,MD5,MD2,SSL,TLS,AES,Camellia,SEED,SHA256,SHA512] askpw=any timeout=30})
+
diff --git a/src/test/ssl/ssl/nss/root+client.crl b/src/test/ssl/ssl/nss/root+client.crl
new file mode 100644
index 0000000000000000000000000000000000000000..1d345a098f4488bc747deaa0ef788d7168beb7c7
GIT binary patch
literal 393
zcmXqLVr(_YH{fOC)N1o+`_9YA$j!=N;9zKHV8g~7%EHWJ8j@OEqEM8dU!vgbsF0Rl
zq!5r_T#{at8XV}O5FG5IP?QSf6c=aa=P8tclopp}mZXaF8d?|{8krj!n;4jyMv3#9
zm>HNDnn1Z|)-g9RGBVUyDtwyVe{1D7zqPZ^SxI)Sy>~!W<pJaJ+>*6S@zp;+c5W9+
z^Lod^S|Z)bS@}Ni`Tpa})~8e$KbpomWoGw<j^>Bllh2*f__60v-OkD9uJ?Uw<TTk{
zmS=us-bBmDYa3p6Z@O9bv+~Z)sT{tk*KH0LCdCP#6Le5|<94^kM*Ttl{XHHp&#Ok?
zW?0u_DpK*4O>)g*jb;m5?;Xo@d|q+aiUfUo7iT?{_rZm4@7YV92Ulqo$lkcVF*#r2
zXz>Ca$%>3A*Vis^H2dH0V&i6PQTN~OPHL)m2G=s5-}_AU9$7i>`SIuP^gnIp_kxXH
dbZjc_b^6{}Z@>M}ghR)eg7;aw?eLl=1OPoRrTqW^

literal 0
HcmV?d00001

diff --git a/src/test/ssl/ssl/nss/root+client_ca.crt.db/cert9.db b/src/test/ssl/ssl/nss/root+client_ca.crt.db/cert9.db
new file mode 100644
index 0000000000000000000000000000000000000000..54a75ba497fed1145ef8b0987417e5de6449496a
GIT binary patch
literal 28672
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
N0Ryi@{@4QmzXywqjwJv9

literal 0
HcmV?d00001

diff --git a/src/test/ssl/ssl/nss/root+client_ca.crt.db/key4.db b/src/test/ssl/ssl/nss/root+client_ca.crt.db/key4.db
new file mode 100644
index 0000000000000000000000000000000000000000..55494e66c0b0aae452fa1e58ff6daa0ee72341f1
GIT binary patch
literal 36864
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
Yl|@byuFMPSaAhlsh*2j+hd!713q1s>zyJUM

literal 0
HcmV?d00001

diff --git a/src/test/ssl/ssl/nss/root+client_ca.crt.db/pkcs11.txt b/src/test/ssl/ssl/nss/root+client_ca.crt.db/pkcs11.txt
new file mode 100644
index 0000000000..bdbedc732b
--- /dev/null
+++ b/src/test/ssl/ssl/nss/root+client_ca.crt.db/pkcs11.txt
@@ -0,0 +1,5 @@
+library=
+name=NSS Internal PKCS #11 Module
+parameters=configdir='sql:ssl/nss/root+client_ca.crt.db' certPrefix='' keyPrefix='' secmod='secmod.db' flags= updatedir='' updateCertPrefix='' updateKeyPrefix='' updateid='' updateTokenDescription='' 
+NSS=Flags=internal,critical trustOrder=75 cipherOrder=100 slotParams=(1={slotFlags=[ECC,RSA,DSA,DH,RC2,RC4,DES,RANDOM,SHA1,MD5,MD2,SSL,TLS,AES,Camellia,SEED,SHA256,SHA512] askpw=any timeout=30})
+
diff --git a/src/test/ssl/ssl/nss/root+server_ca.crt.db/cert9.db b/src/test/ssl/ssl/nss/root+server_ca.crt.db/cert9.db
new file mode 100644
index 0000000000000000000000000000000000000000..faf4480a51f08ed862464fc42753d1002c886912
GIT binary patch
literal 28672
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
H4*>if6nm11

literal 0
HcmV?d00001

diff --git a/src/test/ssl/ssl/nss/root+server_ca.crt.db/key4.db b/src/test/ssl/ssl/nss/root+server_ca.crt.db/key4.db
new file mode 100644
index 0000000000000000000000000000000000000000..391fefa234b49726959a94205089a04167c44fb7
GIT binary patch
literal 36864
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
YD|-ZWys}AD#VebUGD(%082en}FC><sZvX%Q

literal 0
HcmV?d00001

diff --git a/src/test/ssl/ssl/nss/root+server_ca.crt.db/pkcs11.txt b/src/test/ssl/ssl/nss/root+server_ca.crt.db/pkcs11.txt
new file mode 100644
index 0000000000..5d04ea23ce
--- /dev/null
+++ b/src/test/ssl/ssl/nss/root+server_ca.crt.db/pkcs11.txt
@@ -0,0 +1,5 @@
+library=
+name=NSS Internal PKCS #11 Module
+parameters=configdir='sql:ssl/nss/root+server_ca.crt.db' certPrefix='' keyPrefix='' secmod='secmod.db' flags= updatedir='' updateCertPrefix='' updateKeyPrefix='' updateid='' updateTokenDescription='' 
+NSS=Flags=internal,critical trustOrder=75 cipherOrder=100 slotParams=(1={slotFlags=[ECC,RSA,DSA,DH,RC2,RC4,DES,RANDOM,SHA1,MD5,MD2,SSL,TLS,AES,Camellia,SEED,SHA256,SHA512] askpw=any timeout=30})
+
diff --git a/src/test/ssl/ssl/nss/root+server_ca.crt__root+server.crl.db/cert9.db b/src/test/ssl/ssl/nss/root+server_ca.crt__root+server.crl.db/cert9.db
new file mode 100644
index 0000000000000000000000000000000000000000..1bef7b32e1a48253dcded78d36eb0a22062511fb
GIT binary patch
literal 28672
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

literal 0
HcmV?d00001

diff --git a/src/test/ssl/ssl/nss/root+server_ca.crt__root+server.crl.db/key4.db b/src/test/ssl/ssl/nss/root+server_ca.crt__root+server.crl.db/key4.db
new file mode 100644
index 0000000000000000000000000000000000000000..633f26005c9734b998e0ec8ec699cc1c0164be48
GIT binary patch
literal 36864
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
YvV)U{D;s!OxUvyRqhv|y(B~3=1DlPWL;wH)

literal 0
HcmV?d00001

diff --git a/src/test/ssl/ssl/nss/root+server_ca.crt__root+server.crl.db/pkcs11.txt b/src/test/ssl/ssl/nss/root+server_ca.crt__root+server.crl.db/pkcs11.txt
new file mode 100644
index 0000000000..9e34f7d8ea
--- /dev/null
+++ b/src/test/ssl/ssl/nss/root+server_ca.crt__root+server.crl.db/pkcs11.txt
@@ -0,0 +1,5 @@
+library=
+name=NSS Internal PKCS #11 Module
+parameters=configdir='sql:ssl/nss/root+server_ca.crt__root+server.crl.db' certPrefix='' keyPrefix='' secmod='secmod.db' flags= updatedir='' updateCertPrefix='' updateKeyPrefix='' updateid='' updateTokenDescription='' 
+NSS=Flags=internal,critical trustOrder=75 cipherOrder=100 slotParams=(1={slotFlags=[ECC,RSA,DSA,DH,RC2,RC4,DES,RANDOM,SHA1,MD5,MD2,SSL,TLS,AES,Camellia,SEED,SHA256,SHA512] askpw=any timeout=30})
+
diff --git a/src/test/ssl/ssl/nss/root+server_ca.crt__server.crl.db/cert9.db b/src/test/ssl/ssl/nss/root+server_ca.crt__server.crl.db/cert9.db
new file mode 100644
index 0000000000000000000000000000000000000000..6dcb06f709163abaa8ff8779475330009cb10925
GIT binary patch
literal 28672
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
I=mmiP1_=>&y8r+H

literal 0
HcmV?d00001

diff --git a/src/test/ssl/ssl/nss/root+server_ca.crt__server.crl.db/key4.db b/src/test/ssl/ssl/nss/root+server_ca.crt__server.crl.db/key4.db
new file mode 100644
index 0000000000000000000000000000000000000000..572042e848fb394849dfc9a4aae5f811603ca074
GIT binary patch
literal 36864
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
X_VTJ&WkFQND%((;UTM}wKbQCmpCXce

literal 0
HcmV?d00001

diff --git a/src/test/ssl/ssl/nss/root+server_ca.crt__server.crl.db/pkcs11.txt b/src/test/ssl/ssl/nss/root+server_ca.crt__server.crl.db/pkcs11.txt
new file mode 100644
index 0000000000..b54121fc4e
--- /dev/null
+++ b/src/test/ssl/ssl/nss/root+server_ca.crt__server.crl.db/pkcs11.txt
@@ -0,0 +1,5 @@
+library=
+name=NSS Internal PKCS #11 Module
+parameters=configdir='sql:ssl/nss/root+server_ca.crt__server.crl.db' certPrefix='' keyPrefix='' secmod='secmod.db' flags= updatedir='' updateCertPrefix='' updateKeyPrefix='' updateid='' updateTokenDescription='' 
+NSS=Flags=internal,critical trustOrder=75 cipherOrder=100 slotParams=(1={slotFlags=[ECC,RSA,DSA,DH,RC2,RC4,DES,RANDOM,SHA1,MD5,MD2,SSL,TLS,AES,Camellia,SEED,SHA256,SHA512] askpw=any timeout=30})
+
diff --git a/src/test/ssl/ssl/nss/root.crl b/src/test/ssl/ssl/nss/root.crl
new file mode 100644
index 0000000000000000000000000000000000000000..1d345a098f4488bc747deaa0ef788d7168beb7c7
GIT binary patch
literal 393
zcmXqLVr(_YH{fOC)N1o+`_9YA$j!=N;9zKHV8g~7%EHWJ8j@OEqEM8dU!vgbsF0Rl
zq!5r_T#{at8XV}O5FG5IP?QSf6c=aa=P8tclopp}mZXaF8d?|{8krj!n;4jyMv3#9
zm>HNDnn1Z|)-g9RGBVUyDtwyVe{1D7zqPZ^SxI)Sy>~!W<pJaJ+>*6S@zp;+c5W9+
z^Lod^S|Z)bS@}Ni`Tpa})~8e$KbpomWoGw<j^>Bllh2*f__60v-OkD9uJ?Uw<TTk{
zmS=us-bBmDYa3p6Z@O9bv+~Z)sT{tk*KH0LCdCP#6Le5|<94^kM*Ttl{XHHp&#Ok?
zW?0u_DpK*4O>)g*jb;m5?;Xo@d|q+aiUfUo7iT?{_rZm4@7YV92Ulqo$lkcVF*#r2
zXz>Ca$%>3A*Vis^H2dH0V&i6PQTN~OPHL)m2G=s5-}_AU9$7i>`SIuP^gnIp_kxXH
dbZjc_b^6{}Z@>M}ghR)eg7;aw?eLl=1OPoRrTqW^

literal 0
HcmV?d00001

diff --git a/src/test/ssl/ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db/cert9.db b/src/test/ssl/ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db/cert9.db
new file mode 100644
index 0000000000000000000000000000000000000000..1ea3e8b64630be9ffe7463c1713556679c180e77
GIT binary patch
literal 36864
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

literal 0
HcmV?d00001

diff --git a/src/test/ssl/ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db/key4.db b/src/test/ssl/ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db/key4.db
new file mode 100644
index 0000000000000000000000000000000000000000..c834a04c75e060c037de9d211fe5588aa18d7198
GIT binary patch
literal 45056
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
AYXATM

literal 0
HcmV?d00001

diff --git a/src/test/ssl/ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db/pkcs11.txt b/src/test/ssl/ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db/pkcs11.txt
new file mode 100644
index 0000000000..bdd10448f3
--- /dev/null
+++ b/src/test/ssl/ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db/pkcs11.txt
@@ -0,0 +1,5 @@
+library=
+name=NSS Internal PKCS #11 Module
+parameters=configdir='sql:ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db' certPrefix='' keyPrefix='' secmod='secmod.db' flags= updatedir='' updateCertPrefix='' updateKeyPrefix='' updateid='' updateTokenDescription='' 
+NSS=Flags=internal,critical trustOrder=75 cipherOrder=100 slotParams=(1={slotFlags=[ECC,RSA,DSA,DH,RC2,RC4,DES,RANDOM,SHA1,MD5,MD2,SSL,TLS,AES,Camellia,SEED,SHA256,SHA512] askpw=any timeout=30})
+
diff --git a/src/test/ssl/ssl/nss/server-cn-and-alt-names.pfx b/src/test/ssl/ssl/nss/server-cn-and-alt-names.pfx
new file mode 100644
index 0000000000000000000000000000000000000000..7a6b23bb14fe20ca32e9b1055a9c783e9ef63ca7
GIT binary patch
literal 3349
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

literal 0
HcmV?d00001

diff --git a/src/test/ssl/ssl/nss/server-cn-only.crt__server-cn-only.key.db/cert9.db b/src/test/ssl/ssl/nss/server-cn-only.crt__server-cn-only.key.db/cert9.db
new file mode 100644
index 0000000000000000000000000000000000000000..03d88f8838bede656dbb48a5b56f42d64ee38d43
GIT binary patch
literal 28672
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

literal 0
HcmV?d00001

diff --git a/src/test/ssl/ssl/nss/server-cn-only.crt__server-cn-only.key.db/key4.db b/src/test/ssl/ssl/nss/server-cn-only.crt__server-cn-only.key.db/key4.db
new file mode 100644
index 0000000000000000000000000000000000000000..4c91baa85e018d37071a3566256c88dbf5d53f09
GIT binary patch
literal 45056
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
Ifg7Iv7nl1PP5=M^

literal 0
HcmV?d00001

diff --git a/src/test/ssl/ssl/nss/server-cn-only.crt__server-cn-only.key.db/pkcs11.txt b/src/test/ssl/ssl/nss/server-cn-only.crt__server-cn-only.key.db/pkcs11.txt
new file mode 100644
index 0000000000..1e4d3f5fef
--- /dev/null
+++ b/src/test/ssl/ssl/nss/server-cn-only.crt__server-cn-only.key.db/pkcs11.txt
@@ -0,0 +1,5 @@
+library=
+name=NSS Internal PKCS #11 Module
+parameters=configdir='sql:ssl/nss/server-cn-only.crt__server-cn-only.key.db' certPrefix='' keyPrefix='' secmod='secmod.db' flags= updatedir='' updateCertPrefix='' updateKeyPrefix='' updateid='' updateTokenDescription='' 
+NSS=Flags=internal,critical trustOrder=75 cipherOrder=100 slotParams=(1={slotFlags=[ECC,RSA,DSA,DH,RC2,RC4,DES,RANDOM,SHA1,MD5,MD2,SSL,TLS,AES,Camellia,SEED,SHA256,SHA512] askpw=any timeout=30})
+
diff --git a/src/test/ssl/ssl/nss/server-cn-only.crt__server-password.key.db/cert9.db b/src/test/ssl/ssl/nss/server-cn-only.crt__server-password.key.db/cert9.db
new file mode 100644
index 0000000000000000000000000000000000000000..0d2d3e5bd060df6e7b5fe4f574a14ba91389dad9
GIT binary patch
literal 36864
zcmeI530M=?7RP6@u`_H6B0>a|MS%%PAc_l%0TEOXHx!5gB8D9T)|DlSpdyME6>&ve
zK|~P+)K;`m_f{-ci!5%4N<l$IS>Mbg2wMAWAN2KoeRp&wXYTU9_x@()d~;7GgNM7T
zFiJpQ7#11AkD@an6$pkQTRI(rAVqAI!`7aKiW!ui9qbeCWB;D12$|cwR6^cCH2Hc+
zp#>>b$WsVZFvWJj0tf&CfB+x>2mk_r03ZMe{DlN;X*5kuJ=jwi>MvO07sMC(hl)gQ
z(Z0b#KN*J^T)UZEy7RP2+_`ijk3M}G{X;(8n9o2hO(swYg;|6`FyB`Y{GkNxBLyPw
zQw65UBnnM)s2=Q1)E7nj1`7P5J}6K>Qjq3;s=+`RCU#_+=8&Fo2t}f3LF5M!%10uS
z%uh8q43os^u#Bdj9-I;q#rF*se5n4xAW3y*+S$8u`*INa)7@q`PqmxjNq6CT(q~O`
zcAv$?TX2{@&C%7_VJ6+qe)^1=qv?DUTN&7DiLFfBCKB)z2A;&hcRBbjhuFo7a9H?;
zjc=^*jWxdE;F~e{hL3N2vCSAl3YBaLMHJUypptfQ6$XmSFi>2Ff#O086jx%PxD*4$
zwHPQa#z6h>t+acR{t?QS5}Q!AB$O=)WlKWYl2EoJlr0HmOG24R)MXNNnM7SCQI|>7
zwIXt@h+Hco*NVuslH?M~R)n%OQIep<ATU`H<F!Qbx|YOvEr~ueiF78B&Lq-VL<);Y
zVM$Z)R#+(B3X34aBFL}^eHNk5B8~%_sLLkmvWdEEf-0toqLvK2ajA*-o`a{NL}5Px
zo5^Cb@d8p4FCaDX)}<!i1*wUTR%+rymYO(GsflBim;@iG*;|buB~9(EX4R(}en=z*
ztb6-l-O~rF-u_}{Pk%A9rz~dnDcjR9mfF)_%<O3xGkY4w%$|lZ6F=4PK42z(zDZ5|
zq?4Ndy{4eoT=>BxP8vyB-`?){;@$CQ^HG0a6g6RxXqsF-7?QY_h+u3+&*Mi&g%SI_
z57#x1@xdfxu2vRzxA>$UM%r;xPV#}9U;zXG0YCr{00aO5KmZT`1ONd*01yBK0D*rF
z0a>a%HW)c14?^A{O-LP5h5UqELoOmkSQ1zO0YCr{00aO5KmZT`1ONd*01yBK00BVY
zZz3Q|roaZ>dsoX5Mi-MOD>Zy%2&1EGJWXnhqY_4Y=~{}^NFozPoA@S4hD?z&XcONc
z=Ko5CkWwKsLb*ygO?j#^Rp~r(1_}9_1_+u31ONd*01yBK00BS%5C8-K0YCr{_=o_7
z48d|vlz0^wwPj2|$I-~MC}c(UOo1qh9vK!EMR%~nf7DHP3ll{JL<&6IUFjYkuJlL&
zwj&Y=!$Rp%cu`R__N=(<0O`mKkPdBtbf^QQLm40)vNI)K14iu`w&(=O@v<h4*B?Ao
zg2=^!NV=aOGD<|6j48<tgc33TS3V0Nxd;zYQ@(^~DqlwYk$fx(EPwzY00;mAfB+x>
z2mk_r03ZMe00Mx({{jISvKo8<nkR>cLS*|jwe|}Z3PPhKQ)?MqZoncCxEQ5heG~PW
z_LFfrk}D-%M-Fvl=%PA!6hT38P@f=AZ&Zh2i0VtC3`70GLPEkq%|iJh0`u?yGm$75
zFJ_K!B+vh^gODA_Ddal#3xI0mJ0u6$jI8@#IK-e2fB+x>2mk_r03ZMe00MvjAOHve
z0)W6jjX+O0L;5MJN*)PI0uwwxzPVpyl#DcohLH7P{232vI#rrZ!D~pu1=1X{G>0^V
z%#p)p{T7m3Grv%?u+ZS8xTs|Q9}XdpkW%CjvKE{Bry%u6ICAx$#utnS5C8-K0YCr{
z00aO5KmZT`1ONd*01)`^5zr#D;G*57F3rgeu_cFewhlkG4Px;_#!8-Iz;kDYF&<r6
zdVQL(?ANj*RFeDs8Q(0HJd}Xv(sEp<JJ^{HYT%MCJ(oS)8IkNKq7qLT;JIhFbcQwm
z=)q%qX=;RDrtd#gzY>x>G@wal$$hR}C2Z(as2x%$l0QikQvEUW|7A&9NL@kEP*&E5
zlHDMUcoj~cfyb+``t1FVsr)<Xe=OOcqiY;i68m$DePTJNn5>J6NwmFW5=<g#sK{y>
zz00_iRWWsn@ea4(ao<-=^FWnoa;9r0uW40+VFfDG|DXS$x|&MZUm0OoS~Hl~-v?pM
z(^O(vp)3YVx;p?Zh++M{-G-?USqvi(Du(4rVvNA)6?dA~QPLyC<IJtZ#SJO4>i26V
zpMJe$acuxCLdmOY`<(r-_V;N*>vtE=va{PKom$&krJb>|C2SAL^(z~Gf`GQm=3R#Z
zZ`_e1!r}Zj!=LK3HX2{dacekfEoZRAU2e;y9~!js!j45JtZk!Aw|%lF(NWJPurxkg
z7A;J8?zQzsrw`|clnv`jI~(##^0h}g(}G@F`scKkY~PqY`F4@GVtb6@sC;;Asos<z
z_+)H-qM^M5b3&%Kh5h_d#dAA~uP)l9ekt)@ZSBjYV~kas?&zn}0$@X?k=EDeE|7CF
z-&<tnS*Qta6mNHOe80VLg|SYFz5l-ZvTv9B+64GKh~7^k!w|e*1yx2Bu}5}>sZ*#Z
z8HT(2GQ$ov#tepBP#e<LM$_04o6KK7r?}LdK2u|OFW~B<fW&}vqXO%ng`~w@dAYVK
zvpi$;N;NlIMsBP2V3An^wex`H+q?x+LN+o*+8bl8waltt@O`{?#bCp{S%rz!k2cOO
zn3SF6n!TE1r+?sF`2H!^O5Zg_A9}lf?9SC03fgz-!`JX`<#jB!`tB`r{TAJZMd!yI
zwxGzL=`60)UH<Tje5qFOU9HPvGj;QmvzI1x#O1d}o9&q+%WX?P?&*4W&FRtIK1yRU
zi}V(@w=h#5hn-8U-}%ce!=+wgg}FL$$9+wQx4yeDT61K{o_$lDHqOo|TwNP)RQ+CV
zNsY0`5;O~nuKh}-(T5sGuEH;_{-2S*b7}Sar*xll&#;(8S%y0rHN5ZfD#QnF{=wzX
zyBQ5^!NrwXqag$A{Vz8?96VqMdsc1$z`-z#$<64yLZyqu5tWBF-~Cnjp2Z8j&0js)
z=hNVL!7w$hG&j7~B{tb$4drx_Bbo5FBcWt-+}rl05U=sqd)xNXB#a1l0euu9$Na4l
z=7ru!{4ETBa>ezZ*B71Z0Q?#d8^FJJuMejz9ugWb{+mt5On(hF;R#+GyCU~}Zm2r?
zauNGEH!2~58vD9d%bdA+q>@f`*U*%?NPb|st$p@aPG^D<xtlwizcx0=9Tu~r;_!Qm
zn~4Q;wLRG{{N2Y`m3NLy{y8<Mpo)9S{e?_HeuZP#j%P2ITkYUKS8Z-IxED5cJo$$w
zuIaBPzdZA{>1x-d=P3oAi%8sdsNwKvdjZ=FwhNo<d8^ZUo0*V%edrOFIeu{|KWx2o
z<%mh!<g5#EcRb3zb`EWt9JzaK_b!SBrK{cfwTWO3YuP1s^OzwAzj0AHK)Teh%xwSY
zqYIp}s0u5LYRXn-mp_8%i{H)Kx3?-ZA@Ojo5ry|B+^eUTiPhJgldT;l2i@<S>2PTF
z<y`IAu|fGsO|xDJZXHwImAB|(=4pk7A7^p5PrY1k95rdSb#a}%!85O0GDnQN@5MRn
zzc=ynts*U+d{Eta!-?qu3nsEsPfT9Pl%-_+Y<2F=^OBsWs?&A&>DRKBq`q#P(bTqe
zzUICS>m#<L4z1nAdX_YT|LW<QQ{0g)mFtf1=nwSI@2(v_r)kmSO7k%rezCu~$69bP
zt!$&tg4)XS3kTgyXnVGeZNYti^!$8sQgo%e{xII71i_B%7h?ILi9yrFg|w=?;w`JM
z#0(i+s_9*mC_ZlOEKGZ(Sd(^jp|<_S5xXv`TszmfU`!D-Cz@(}(;ask{XgqT*p)N@
zC-&!VXJIjg)W@4@^z|cn=fAY{)0<-N&g%81129DFHt)CobXi5yD(9&;UWfP;?#pZ}
zs@}QE>(vc@az)3`Cu#8>f#(_2olZ%UV8WkrpkzhNpMJz4Ugxj(r`Ws0Fx0p|{p|OO
z*iCE)=9eC0<NMU74gj?;des4XI{J&AwIY9Ji(#WV?|q1_>LGXa$G1t9vZJPxHsx#V
zwz0XM&e*-Qc+!G76LpV|3O9t3zPS*pmA>Te#E4wW5x<y^xsxblQjIQi6*q*i9_-f0
ztqy+#dm_t+s^xTKnwlgO-X+J_?SAr;e9({dd!y1k)>pk5$>3bjr_UfgtmL2tTSs&`
zbhPe^9<}pUqwDgfXsg97t<Fyt-yEUazRIa?-L7;G-GJdWG7o&Zp0G(H={hwRz1vJ`
zD(4Q<gSgxizdjTQGvhNSzMiqK`%O~zhTE)j0WU6z?UR$2zbf0X=~m|o<LVP*Q<T3=
zQdFBavh&Kpngz`nM~|)9>s2gVdr&QVhcc;=S44Q$PYy6~o4TcJ$`Mv&%FjDShdpe*
zv~ZK7iPl2HwGSV9aguk`kKN2S;I+utYF)G)HPK_!@YO?4<y_B;bIwX~X`gUnSWNKL
zm9cvx_6~WL_2yjRTJ-{jI&CelxuSv^rbd2Q-KyK$8?Kcu*ycv^Ke{q8!)>H$Q`F(H
zcWo^Otw^k^pTFmoNO1k9+ZhM0ZB9U9&zjWTD{oaTzLC;u?A2i2lFLo^&|Bf$+0L2N
zGGxi7izy)rCDrG1PnjGb`ML<F?oNiDMp(>!uv3+h`>L(AX_;N@;O6hV9S_)DcoK6u
zs_gnio!qCl-%!1~c<#&2tfA5(B8GW}c3(j`)%zos-m3|Edi|)o==5{q?Rf*?0`co-
zfKKMmP5)th{!fv40x8$a?~&Vq8DIef00BS%5C8-Kfqw>pRhDwSQ>MN{(okbT%Gf_F
zRD^}6)K)7(pH8QUyuMZbw2i;57^_4g0#JBuEDrVATM;a>gy&)Z|KIPbv0yNSMGa+p
z58B7^vOjVR`Z>?V-Ynfa&+T_u^s}?vPEBvb)W`N{_nQ>*XwKM<M;l|t>s#g>9(Fx&
znE#fnHNJvnE0Y{${?@2r7t;rWjh&buJ&F5C)bKYm+<x==WPZ#3_$wLqy#I7&(f5bW
zU!2>XeX84Bq!bf(+@NBAF(NFx8gk<9gMjrmf<3xOnqcd?=25<0kuOw+#}s&Pl)Yc)
zXT~wDF!c<Mve^8S({s768-K2+-ZFB0QqpMTAF^tle%`5s%8;<GAcXUycE#-s`9V2G
za=Tj!<_$jCB@>)^;7!4JSJyYW-G_MglQhW=4{yw`qh>>@GQ-QVz4QXhMOi<ndpB)U
zi{i~M-!^e^(S)pJ%U-$JaPGt1n<%@6Lr&gq9Pb6T8Tsznq-_l<;h`mqGopsSyEC5J
z?YcAdT4Z!U$u`0K^s0uK5?5VopZEIwY@MSov^HDp@!O+2#55@2&CC@3iGvg)yJ+!Q
zuC>t!h0Z?_HPp>dYz}X$I#cP*nr;)+L|SAVOSYIBFeRkAaqX^++yf@<=_O-V6<#m7
zAEPkFq?|le^N<0gMY4Zk=;v}Zq;31%#~I5qA0Lg)QOb5sDCZ<8)w8#}+MveS`~9!7
z(++-@X1}yv#$>NL-v;vI7;+QEp(j)C9gePbyQ8jVL`o}&N!w|RisdUB6ZnH24qtO=
zkD(Q8BIySncXW6kIB(OcLT9%dqF^@fmqQtTiF#8Sl~>vnk5yim^DXJ@!RJl^$8H_J
z9eUTvZ@in9mFm8k<rCL$-5cgwv9*Ex_J(@E(a2K<@5`5vd}WW9jc5*hx&M{tly|zL
zuekXYC|xKZpHVz-Z;v~qfruKC2I9p2+!yGuco40xH|_gf?|;FYVsGUiNN@Vt?}dLG
sMszV=D6C#}CTy2u)r_ysq<(ApLO3MA=<vb7Mn-Dcu{g&$Y)yxM0o=!f!~g&Q

literal 0
HcmV?d00001

diff --git a/src/test/ssl/ssl/nss/server-cn-only.crt__server-password.key.db/key4.db b/src/test/ssl/ssl/nss/server-cn-only.crt__server-password.key.db/key4.db
new file mode 100644
index 0000000000000000000000000000000000000000..4db1426c3a88f0bd27dd3dd1593038e228c82540
GIT binary patch
literal 45056
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

literal 0
HcmV?d00001

diff --git a/src/test/ssl/ssl/nss/server-cn-only.crt__server-password.key.db/pkcs11.txt b/src/test/ssl/ssl/nss/server-cn-only.crt__server-password.key.db/pkcs11.txt
new file mode 100644
index 0000000000..142748c1c9
--- /dev/null
+++ b/src/test/ssl/ssl/nss/server-cn-only.crt__server-password.key.db/pkcs11.txt
@@ -0,0 +1,5 @@
+library=
+name=NSS Internal PKCS #11 Module
+parameters=configdir='sql:ssl/nss/server-cn-only.crt__server-password.key.db' certPrefix='' keyPrefix='' secmod='secmod.db' flags= updatedir='' updateCertPrefix='' updateKeyPrefix='' updateid='' updateTokenDescription='' 
+NSS=Flags=internal,critical trustOrder=75 cipherOrder=100 slotParams=(1={slotFlags=[ECC,RSA,DSA,DH,RC2,RC4,DES,RANDOM,SHA1,MD5,MD2,SSL,TLS,AES,Camellia,SEED,SHA256,SHA512] askpw=any timeout=30})
+
diff --git a/src/test/ssl/ssl/nss/server-cn-only.pfx b/src/test/ssl/ssl/nss/server-cn-only.pfx
new file mode 100644
index 0000000000000000000000000000000000000000..052a8847cb8f8feef763a7220540f540d8016f69
GIT binary patch
literal 3197
zcmY+FcQhM{-^NKJgxnI1d0Ww{y<%@_#NPDQ9yP9-MQm*~wQAL>nHoXSqLkRG_6~y9
zti8qF^VfUM?|tw4{_#EEb3V^G&!5i+frbPCNN*v~5CssJCtN-J<PTDE(i}7d0YpRi
zeq$~K8Z`TF1j<2!hJK@d04d4u*YR&cint4+{BHw<6oj}9q?kKD&Xc1s0s#ONBq3-}
z+D9^w9;pvM-gbbWelghQ@BEZfFFNiYVTonS?Ow7O@k*o4MQ~*>%}kGQ=+c8UIY;U3
zo?g4|1=U&BVNEi*;+BLs#9VfIe0`br(O#Q$@R+B(Eza2CUc__~T#U<CYB`j<^c2@4
z*=-(=p`DY64YT(FX5O4)q(^^{Ht9|EyhQ<;dfl4)d7nsrj{V@rctm*cn1@BjF!z4M
zRUo#2HIzP%I5QV^UqPRI+FFHSQ+(;#n<{U_w2*k)aQV^A1Xb$%!$vb(i5`syacI>g
z`)@+-gAqp6YprGEC1xUB)I^m^WUq;4WbMH4fTTk!H0o6OP4QM~eG<z1C>sfmjuVak
z8MHEXkuHuWM?DF76H5Kp{7f1--*@YB%9|+}m2odQZau42mzUhszKt1%r>_3q?e88@
zi==qw=oU{LRKb#*2MU!{x_1Qp%mxGb1X5!6CAyqAaIf-G>&=HM@uDc9m$+wC<)xPV
zp}3+k>BSGgcrlUOphe|QX7Lq7$dRLsjnGYXb#0+a25!-jM@QFH;HAL}GxiHp4x9D<
z@-L$^;HK#z*$_@6d<`Sjq}ho2S__to<x(YC<gF;k{_uHfb3=n@UEVyM&R5|`=c~f?
zxb4H%Gn;0ghtB7sKl;kDrcRz=tl$#a=&f(jnJw$IWCOV(Nj;H@if*iz^RH^rRgwp!
z8Icn*r=QXyC2zo%#vXB|4w)GkQvDASd=W03A$&m!M;ph{Izm}{i)v1s##GJFA#kK~
zJjZF%7mF_A_^Xje?Ahvo7y0sKK)P_Hjm={roo%_gTTn~AM8gh9D*ST$mWoU^l|D<D
z9*f&@YNu8u@xd9%{>1g2`5BVd=*m0kG}`NUFf7Z59{EKp>;COyVZWzF3uSa#V_g^Y
zcoo?&bJ!|xlI`Folzw1<@HfqAY}z=q+v)+I{>zXFI}9C%Wv;}cyH}@bmS>Y)s&T{&
zPoxO?)1<3`juhYC=Rb=rIzR3+$;lcpaQG0C!`^+RqPUpsW^z#iZ+tt3+G|i7!P*o}
z#p7zT%j`Z0NX8+roSCc^E`~mD-C6(c%bQw#s9<FNS~9gPI((IdkU~y<=hygx{EVO5
z>LE7`+7l|c54seej4u**lr$eU8ri_<z$z|O8uw{`*?IF9^dNV!(lt^NhrCiA>XWw4
z#;z7@O|=*xJuS_WI>L7%K<n8gxzo}>+Tx0b$fcoWdTOP)U3WTS_Yp8CG72n_&c(Pr
zsYh1FJ<_)PAh4x&4h5Cx`&_$l#c>*DTgjFpLDu$~wvQDe(SO@7a?Ms%XDh%ZwlmnP
z;u0HnxSdPL+|TX*r5(isOA<RFCwgpmWxfBbkS*g6^S=Sn!#q<QP&-U>r+(aBWBqO^
z)mITFP<#wO23NX{91h0pm!Xs-nid+DO&l})I5`uJ7A0FBw*$x;)e4WKw#{5C3}yn&
z#?J{F*iTK>u7_j4ELD`O#tpm9g{5-UXU2pRc=OUxa}7VjO}C!KOr79Jaszml$R@vL
z!j%k@m-gU=86WT>{!yR#t2hXSgDN7^->rWcDzp<vb=Qr9F=1_;f4DgqVg|x5C=N`H
zeuhLgQ~ho6mBwN`k7B(MG2Dj^4>OR$?h>MqNA*;?M5@u=IW~+GB@;3tRMtEoy6fH#
zlY4{8&t6<!2$D0g=;kzBshW2LM=U2=6Bj!b#D!&CwFHiSdb&5MJ)xV#A+u%M*wg##
z$sxj;4k*`9V>?Ri8l)zlh`q`#PeB@EejZXMjA4Fl(nQNr4U805&-7&`>Qb)`RO0rn
z1<6%<<sw@D3TuC?l-P!}+wt>bXbO(ml_Hir1e2sacK(Y0zRFD5u4|gg@6*_b|Di77
zbYqut$Xry}1s(-aPaOvqoldI3<44!<G3&T_ZSyVCl8JS_xMsFzc`T*DO=GXSy>^ck
zJ(eyuZ#-Ab&iyHv1m~FIDkExG|Kc(iwrXwvV+yI<7O!k@Gi5Ju^QyEw<yfZ~zxVFU
zfaOM)ce%Ir0Ab6RDU1;7qp0Q_dsS<xJ6h{06HMUmJV2?<%<1LQYP(3rYJ5VUvd0zB
z)j0w3|Kyv5T9hze4^uV5%iYR2thcv2AQk6OQPp(7;O-EA3GGeSxD^bKJxpK^cb*F*
z^GBNc78Qk{lYF#`Rm@O#U-Sy>Ub1V;9_>lPc}%_bhF4_TUw&ECH|Xn<dK1Z#UtT$c
zO;(5T!y@Sk=w9SelCOG)a}+tK1%W10{68YgL6Zps(PRR@G0*Sg08#yq)+k8<IcVTE
z0u9{wf3~~zkL_;I{i8neQ)2(v4u}SJCy4nYb2EHi4-WFMDi+*);||Jm8pC@*sP(BS
zx@(UGZP2<LQ~Qv??_K8|#Hy#h$}}HqcC2fT4X@sdzQQ?r?2-*odEe5ATz)wU<}B8V
zk%<93uRJ^0yQUdph5o6r4Rff_nhile9ev;Seqt;H7^6-5x67R^w4j(Qz4H^N!2@P6
zsdBQU_NBN*6wc1>anMnM!L_S7BcLLxE*vnuv(`TMEUUy4=H9dtu1(SN;m7Kag($h)
zxH9FmzTU=3(l*Xj`4hm(852aH9FJJuikfgj2GM86$yFzMrKxRmz}4##oC;qDwK6tV
zP@0)EVmc?^h`9PS+=+d??#<miGU5pQ#HbchMKXKKIA6a(ht<?_d~A(VkH6nT5!I?t
zeKlPJ?=-*O+4{imv#>`LC3yHQAi{XDy|b5BrFsIEf)zZRds7*21>mMN{fXF&o{G)8
z2Gg)V-deRF-pdF!bggK)Tnu~5GPUxDo)HbeJ0iCMoH={aa+MN)T_0eTP_`lP4Ie~n
zu(28wW+-`buGr&f_DSCdEOl<u++;Kv?4*|vNAm=%dA;r)+2}jGJ9Hzf1*r?TM}&1U
zPH3w8SA8p~+&yt#ax_Zk%6LJj+Q%H;%gE`}wB2>b7|Ht!Fs(E{G>+jIUGj0PMAYAt
z=-$wC+>zUT8n|v#(&uQ1&=1Bu#DqgdQNzi<M%<?I*e0#(d7`N5Z>c%v-F?49xT3mC
z%Cz=Qbu$abF9x?Ns7}t)9B7~?w>sxJWCjQ9Ftb+^yKZ-r<yu?uSS;asMe9G)p+|0$
zWJ4`dG<M$hyaFYT628lECn}(4jqB^Zmm?b(DsU!x`9e;Gg__WZIsSzSD;(v_(BwGA
zFavY}JXv4sW_8HWuv+Q4nWFFT>7gI&-njp_I}7j1VuzG`Gt}@!MWU^^*(czU2qmGn
zLDL$u3wv`GRZ0e~`^&OJu%4qekuIq+M;vqb^|_Qr4S(RlWvGlin*(a>=tW1;J56L(
zG~kpb%PeN04oVME*Bm5aK)aMjx+-QpY0>-H+g#La3%9H9pb{}V8wOQZdlbW4*b80`
zs|F&*?t}vcWxG<2I`TDT4)a~(MOYgZ0~qiK4^H<?^$C}+*f_&SZNizV-Uoc%Dxg-j
zl#>7%G$3l9hgnYY*BkK6yJi~NDQpLwUD?-fAcjqoG8aXT5w_k^A&d&phAWqbyBw+$
zE)KcJ!RUMSY)+MK);u_Q1DjA<Fn9B<eTlVM`a6)(PybN?%Pafr<X%+?FbX-F9q`z*
z?75ze5%>Dq)qN%V9-*)q!2X8BDAS-j*;K<xVXk+I2x(@>?oPs~68dag&Skyhv8ca~
zn6I<Tz_q)nZezj35M4zE?WXrx9g+JWfBWg!N2szsyId7~o?k~Ae}9p>UMesg)-gSk
zy!oZbwip6GE8b*MT7~4fuO;o~iwYC=kVKZki=1iT0U_D=lCj4zT>rkb|M25iX9Xpc
zL+xz9Laz7rD&v5}&9&(ve{8C`-}@mVjTG<6z|?^T7sRfw(+2kQ6zR<@A69z1{@`GS
z6{3&~q9}7)#y(uo`4}#i!R2txM`aj8%Em0{bQp_y)3OlHa6QmJogwDdLHV<pMTi~2
z_P^^0rW2wE!UD&3-e~9y9J*Q6=3=+&eqr8N>>-2@_Yq(a8Sm{|0A?~0AS{>?)#S()
fK+p<!9*uI!47T<J@c}8q1m<mmvZG}FS;_wZs?8RU

literal 0
HcmV?d00001

diff --git a/src/test/ssl/ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db/cert9.db b/src/test/ssl/ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db/cert9.db
new file mode 100644
index 0000000000000000000000000000000000000000..d6aa6d7f651c924dc55f8aed978ce69888b4ac68
GIT binary patch
literal 36864
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

literal 0
HcmV?d00001

diff --git a/src/test/ssl/ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db/key4.db b/src/test/ssl/ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db/key4.db
new file mode 100644
index 0000000000000000000000000000000000000000..e740fd73e936386e125ee24cca039e79dcc932be
GIT binary patch
literal 45056
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

literal 0
HcmV?d00001

diff --git a/src/test/ssl/ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db/pkcs11.txt b/src/test/ssl/ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db/pkcs11.txt
new file mode 100644
index 0000000000..1d08f78285
--- /dev/null
+++ b/src/test/ssl/ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db/pkcs11.txt
@@ -0,0 +1,5 @@
+library=
+name=NSS Internal PKCS #11 Module
+parameters=configdir='sql:ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db' certPrefix='' keyPrefix='' secmod='secmod.db' flags= updatedir='' updateCertPrefix='' updateKeyPrefix='' updateid='' updateTokenDescription='' 
+NSS=Flags=internal,critical trustOrder=75 cipherOrder=100 slotParams=(1={slotFlags=[ECC,RSA,DSA,DH,RC2,RC4,DES,RANDOM,SHA1,MD5,MD2,SSL,TLS,AES,Camellia,SEED,SHA256,SHA512] askpw=any timeout=30})
+
diff --git a/src/test/ssl/ssl/nss/server-multiple-alt-names.pfx b/src/test/ssl/ssl/nss/server-multiple-alt-names.pfx
new file mode 100644
index 0000000000000000000000000000000000000000..8ed37c04dafbbb1e34561794ae0f876e0688c115
GIT binary patch
literal 3325
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
H0s;sC!G=jY

literal 0
HcmV?d00001

diff --git a/src/test/ssl/ssl/nss/server-no-names.crt__server-no-names.key.db/cert9.db b/src/test/ssl/ssl/nss/server-no-names.crt__server-no-names.key.db/cert9.db
new file mode 100644
index 0000000000000000000000000000000000000000..bb19429e5560cb66b93edd9fbe4f4e5d93e1153d
GIT binary patch
literal 36864
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
V*0@6&e6Pp6#87nXFD)#9{|D0SSLOf!

literal 0
HcmV?d00001

diff --git a/src/test/ssl/ssl/nss/server-no-names.crt__server-no-names.key.db/key4.db b/src/test/ssl/ssl/nss/server-no-names.crt__server-no-names.key.db/key4.db
new file mode 100644
index 0000000000000000000000000000000000000000..e66a9a26ae087c879a16a4d666c9ac970fe3de48
GIT binary patch
literal 45056
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
RBoyDtmNG0ILbp9Y{tp%$q~QPn

literal 0
HcmV?d00001

diff --git a/src/test/ssl/ssl/nss/server-no-names.crt__server-no-names.key.db/pkcs11.txt b/src/test/ssl/ssl/nss/server-no-names.crt__server-no-names.key.db/pkcs11.txt
new file mode 100644
index 0000000000..78691fd862
--- /dev/null
+++ b/src/test/ssl/ssl/nss/server-no-names.crt__server-no-names.key.db/pkcs11.txt
@@ -0,0 +1,5 @@
+library=
+name=NSS Internal PKCS #11 Module
+parameters=configdir='sql:ssl/nss/server-no-names.crt__server-no-names.key.db' certPrefix='' keyPrefix='' secmod='secmod.db' flags= updatedir='' updateCertPrefix='' updateKeyPrefix='' updateid='' updateTokenDescription='' 
+NSS=Flags=internal,critical trustOrder=75 cipherOrder=100 slotParams=(1={slotFlags=[ECC,RSA,DSA,DH,RC2,RC4,DES,RANDOM,SHA1,MD5,MD2,SSL,TLS,AES,Camellia,SEED,SHA256,SHA512] askpw=any timeout=30})
+
diff --git a/src/test/ssl/ssl/nss/server-no-names.pfx b/src/test/ssl/ssl/nss/server-no-names.pfx
new file mode 100644
index 0000000000000000000000000000000000000000..c35fcdabc7a2a7859bc541547a557ddb7b5cfee0
GIT binary patch
literal 3109
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

literal 0
HcmV?d00001

diff --git a/src/test/ssl/ssl/nss/server-password.pfx b/src/test/ssl/ssl/nss/server-password.pfx
new file mode 100644
index 0000000000000000000000000000000000000000..a6ad1bb869b043715aca18b25791da12403a6be8
GIT binary patch
literal 3197
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

literal 0
HcmV?d00001

diff --git a/src/test/ssl/ssl/nss/server-revoked.crt__server-revoked.key.db/cert9.db b/src/test/ssl/ssl/nss/server-revoked.crt__server-revoked.key.db/cert9.db
new file mode 100644
index 0000000000000000000000000000000000000000..3431d1549ef5c2c073e938de6b106f535747b9a7
GIT binary patch
literal 36864
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
AssI20

literal 0
HcmV?d00001

diff --git a/src/test/ssl/ssl/nss/server-revoked.crt__server-revoked.key.db/key4.db b/src/test/ssl/ssl/nss/server-revoked.crt__server-revoked.key.db/key4.db
new file mode 100644
index 0000000000000000000000000000000000000000..2118ea3a456ae43f7537af0d83c3cfe04959e6dc
GIT binary patch
literal 45056
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
D=yu?q

literal 0
HcmV?d00001

diff --git a/src/test/ssl/ssl/nss/server-revoked.crt__server-revoked.key.db/pkcs11.txt b/src/test/ssl/ssl/nss/server-revoked.crt__server-revoked.key.db/pkcs11.txt
new file mode 100644
index 0000000000..c5e90c7000
--- /dev/null
+++ b/src/test/ssl/ssl/nss/server-revoked.crt__server-revoked.key.db/pkcs11.txt
@@ -0,0 +1,5 @@
+library=
+name=NSS Internal PKCS #11 Module
+parameters=configdir='sql:ssl/nss/server-revoked.crt__server-revoked.key.db' certPrefix='' keyPrefix='' secmod='secmod.db' flags= updatedir='' updateCertPrefix='' updateKeyPrefix='' updateid='' updateTokenDescription='' 
+NSS=Flags=internal,critical trustOrder=75 cipherOrder=100 slotParams=(1={slotFlags=[ECC,RSA,DSA,DH,RC2,RC4,DES,RANDOM,SHA1,MD5,MD2,SSL,TLS,AES,Camellia,SEED,SHA256,SHA512] askpw=any timeout=30})
+
diff --git a/src/test/ssl/ssl/nss/server-revoked.pfx b/src/test/ssl/ssl/nss/server-revoked.pfx
new file mode 100644
index 0000000000000000000000000000000000000000..a8f9b0b15ba6a5006772309d60546ffcea721ac9
GIT binary patch
literal 3181
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
TbYKJshg&M|@u8^*0s;sCT(as-

literal 0
HcmV?d00001

diff --git a/src/test/ssl/ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db/cert9.db b/src/test/ssl/ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db/cert9.db
new file mode 100644
index 0000000000000000000000000000000000000000..7f677ebc3519f47fb1af0393737e3c7751d568a2
GIT binary patch
literal 36864
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
EUr6s&82|tP

literal 0
HcmV?d00001

diff --git a/src/test/ssl/ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db/key4.db b/src/test/ssl/ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db/key4.db
new file mode 100644
index 0000000000000000000000000000000000000000..66d43f59a56e3ac78870738b91363408252785e9
GIT binary patch
literal 45056
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

literal 0
HcmV?d00001

diff --git a/src/test/ssl/ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db/pkcs11.txt b/src/test/ssl/ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db/pkcs11.txt
new file mode 100644
index 0000000000..82255d3f4e
--- /dev/null
+++ b/src/test/ssl/ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db/pkcs11.txt
@@ -0,0 +1,5 @@
+library=
+name=NSS Internal PKCS #11 Module
+parameters=configdir='sql:ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db' certPrefix='' keyPrefix='' secmod='secmod.db' flags= updatedir='' updateCertPrefix='' updateKeyPrefix='' updateid='' updateTokenDescription='' 
+NSS=Flags=internal,critical trustOrder=75 cipherOrder=100 slotParams=(1={slotFlags=[ECC,RSA,DSA,DH,RC2,RC4,DES,RANDOM,SHA1,MD5,MD2,SSL,TLS,AES,Camellia,SEED,SHA256,SHA512] askpw=any timeout=30})
+
diff --git a/src/test/ssl/ssl/nss/server-single-alt-name.pfx b/src/test/ssl/ssl/nss/server-single-alt-name.pfx
new file mode 100644
index 0000000000000000000000000000000000000000..95616f580e4361c493962ca84bee51baacaf7dbb
GIT binary patch
literal 3213
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

literal 0
HcmV?d00001

diff --git a/src/test/ssl/ssl/nss/server.crl b/src/test/ssl/ssl/nss/server.crl
new file mode 100644
index 0000000000000000000000000000000000000000..769196dda6dfffeb4141ec007d6e276d3afb5e84
GIT binary patch
literal 418
zcmXqLVw`8t*lxhf#;Mij(e|B}k&&B~!NAGT!N87<Ih2K&$2=spxJ1F(Q6VkANFgA<
zxFo$OH8{{mAvoAap(qu|DK5^;&r>J?sVYt_DoZU=NKP#(DHi87v@kR@GB-3fF)%fa
z66ZBBGcYkUfpQH*41|~%+0YcBIfJ>0k&z)cx-<6WM&pI2r!*dm3egYCR+DZEcFbs=
zJLCSW&CW;U#5aBOwXHll$=p%k!bPiXhut}MT~u<8-g_X&?EdN?ab}a1JABr;+ig`=
z<67*aH7|L#+SOR4x3|MWJEoZ4vY)6nxx9MOgT+t(bDezlrzuZUzqqQ}z2kL-Q>O0U
zHbE<wjSIh&+P2+I(flFf&)N8NZ@bU8AI59GGNp869L-9#II(HANW;al9|>u9KQFgj
zG1J5K+BS|et`!}!yF)I0+pZ_l{^ZHDOBr?gx7Y$1Zp_zSS614#>}BGLu8NhBoGu3M
o9tXbUKYKSkd*T+M_r-Y*A{D<U7Q|28yuIn;%#d&L*G&^z0V1ELsQ>@~

literal 0
HcmV?d00001

diff --git a/src/test/ssl/ssl/nss/server_ca.crt.db/cert9.db b/src/test/ssl/ssl/nss/server_ca.crt.db/cert9.db
new file mode 100644
index 0000000000000000000000000000000000000000..93d58e2651fdf3614ff265dba5629dbb27e6887a
GIT binary patch
literal 28672
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
EA6+1O;Q#;t

literal 0
HcmV?d00001

diff --git a/src/test/ssl/ssl/nss/server_ca.crt.db/key4.db b/src/test/ssl/ssl/nss/server_ca.crt.db/key4.db
new file mode 100644
index 0000000000000000000000000000000000000000..7493954711430fd0967475631b9380d8399e9ce2
GIT binary patch
literal 36864
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
aNT9M!FAY>S@WFw~MiitB)`a;#m-rj=KB=Yv

literal 0
HcmV?d00001

diff --git a/src/test/ssl/ssl/nss/server_ca.crt.db/pkcs11.txt b/src/test/ssl/ssl/nss/server_ca.crt.db/pkcs11.txt
new file mode 100644
index 0000000000..b81ced09e6
--- /dev/null
+++ b/src/test/ssl/ssl/nss/server_ca.crt.db/pkcs11.txt
@@ -0,0 +1,5 @@
+library=
+name=NSS Internal PKCS #11 Module
+parameters=configdir='sql:ssl/nss/server_ca.crt.db' certPrefix='' keyPrefix='' secmod='secmod.db' flags= updatedir='' updateCertPrefix='' updateKeyPrefix='' updateid='' updateTokenDescription='' 
+NSS=Flags=internal,critical trustOrder=75 cipherOrder=100 slotParams=(1={slotFlags=[ECC,RSA,DSA,DH,RC2,RC4,DES,RANDOM,SHA1,MD5,MD2,SSL,TLS,AES,Camellia,SEED,SHA256,SHA512] askpw=any timeout=30})
+
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index fd2727b568..00530d98af 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -4,15 +4,22 @@ use PostgresNode;
 use TestLib;
 use Test::More;
 
-use File::Copy;
-
 use FindBin;
 use lib $FindBin::RealBin;
 
-use SSLServer;
+use SSL::Server;
+
+my $openssl;
+my $nss;
 
 if ($ENV{with_openssl} eq 'yes')
 {
+	$openssl = 1;
+	plan tests => 93;
+}
+elsif ($ENV{with_nss} eq 'yes')
+{
+	$nss = 1;
 	plan tests => 93;
 }
 else
@@ -32,32 +39,6 @@ my $SERVERHOSTCIDR = '127.0.0.1/32';
 # Allocation of base connection string shared among multiple tests.
 my $common_connstr;
 
-# The client's private key must not be world-readable, so take a copy
-# of the key stored in the code tree and update its permissions.
-#
-# This changes ssl/client.key to ssl/client_tmp.key etc for the rest
-# of the tests.
-my @keys = (
-	"client",     "client-revoked",
-	"client-der", "client-encrypted-pem",
-	"client-encrypted-der");
-foreach my $key (@keys)
-{
-	copy("ssl/${key}.key", "ssl/${key}_tmp.key")
-	  or die
-	  "couldn't copy ssl/${key}.key to ssl/${key}_tmp.key for permissions change: $!";
-	chmod 0600, "ssl/${key}_tmp.key"
-	  or die "failed to change permissions on ssl/${key}_tmp.key: $!";
-}
-
-# Also make a copy of that explicitly world-readable.  We can't
-# necessarily rely on the file in the source tree having those
-# permissions.  Add it to @keys to include it in the final clean
-# up phase.
-copy("ssl/client.key", "ssl/client_wrongperms_tmp.key");
-chmod 0644, "ssl/client_wrongperms_tmp.key";
-push @keys, 'client_wrongperms';
-
 #### Set up the server.
 
 note "setting up data directory";
@@ -72,32 +53,28 @@ $node->start;
 
 # Run this before we lock down access below.
 my $result = $node->safe_psql('postgres', "SHOW ssl_library");
-is($result, 'OpenSSL', 'ssl_library parameter');
+is($result, SSL::Server::ssl_library(), 'ssl_library parameter');
 
 configure_test_server_for_ssl($node, $SERVERHOSTADDR, $SERVERHOSTCIDR,
 	'trust');
 
 note "testing password-protected keys";
 
-open my $sslconf, '>', $node->data_dir . "/sslconfig.conf";
-print $sslconf "ssl=on\n";
-print $sslconf "ssl_cert_file='server-cn-only.crt'\n";
-print $sslconf "ssl_key_file='server-password.key'\n";
-print $sslconf "ssl_passphrase_command='echo wrongpassword'\n";
-close $sslconf;
-
-command_fails(
-	[ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
-	'restart fails with password-protected key file with wrong password');
-$node->_update_pid(0);
-
-open $sslconf, '>', $node->data_dir . "/sslconfig.conf";
-print $sslconf "ssl=on\n";
-print $sslconf "ssl_cert_file='server-cn-only.crt'\n";
-print $sslconf "ssl_key_file='server-password.key'\n";
-print $sslconf "ssl_passphrase_command='echo secret1'\n";
-close $sslconf;
+SKIP:
+{
+	skip "Certificate passphrases aren't checked on server restart in NSS", 1
+	  if ($nss);
+
+	set_server_cert($node, 'server-cn-only', 'root+client_ca',
+					   'server-password', 'echo wrongpassword');
+	command_fails(
+		[ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
+		'restart fails with password-protected key file with wrong password');
+	$node->_update_pid(0);
+}
 
+set_server_cert($node, 'server-cn-only', 'root+client_ca',
+				'server-password', 'echo secret1');
 command_ok(
 	[ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
 	'restart succeeds with password-protected key file');
@@ -149,82 +126,105 @@ test_connect_ok(
 test_connect_fails(
 	$common_connstr,
 	"sslrootcert=invalid sslmode=verify-ca",
-	qr/root certificate file "invalid" does not exist/,
+	qr/root certificate file "invalid" does not exist|could not connect to server/,
 	"connect without server root cert sslmode=verify-ca");
 test_connect_fails(
 	$common_connstr,
 	"sslrootcert=invalid sslmode=verify-full",
-	qr/root certificate file "invalid" does not exist/,
+	qr/root certificate file "invalid" does not exist|could not connect to server/,
 	"connect without server root cert sslmode=verify-full");
 
 # Try with wrong root cert, should fail. (We're using the client CA as the
 # root, but the server's key is signed by the server CA.)
-test_connect_fails($common_connstr,
-	"sslrootcert=ssl/client_ca.crt sslmode=require",
-	qr/SSL error/, "connect with wrong server root cert sslmode=require");
-test_connect_fails($common_connstr,
-	"sslrootcert=ssl/client_ca.crt sslmode=verify-ca",
-	qr/SSL error/, "connect with wrong server root cert sslmode=verify-ca");
-test_connect_fails($common_connstr,
-	"sslrootcert=ssl/client_ca.crt sslmode=verify-full",
-	qr/SSL error/, "connect with wrong server root cert sslmode=verify-full");
-
-# Try with just the server CA's cert. This fails because the root file
-# must contain the whole chain up to the root CA.
-test_connect_fails($common_connstr,
-	"sslrootcert=ssl/server_ca.crt sslmode=verify-ca",
-	qr/SSL error/, "connect with server CA cert, without root CA");
+test_connect_fails(
+	$common_connstr,
+	"sslrootcert=ssl/client_ca.crt sslmode=require cert_database=ssl/nss/client_ca.crt.db",
+	qr/SSL error/,
+	"connect with wrong server root cert sslmode=require");
+test_connect_fails(
+	$common_connstr,
+	"sslrootcert=ssl/client_ca.crt sslmode=verify-ca cert_database=ssl/nss/client_ca.crt.db",
+	qr/SSL error/,
+	"connect with wrong server root cert sslmode=verify-ca");
+test_connect_fails(
+	$common_connstr,
+	"sslrootcert=ssl/client_ca.crt sslmode=verify-full cert_database=ssl/nss/client_ca.crt.db",
+	qr/SSL error/,
+	"connect with wrong server root cert sslmode=verify-full");
+
+SKIP:
+{
+	# NSS supports partial chain validation, so this test doesnt work there.
+	# This is similar to the OpenSSL option X509_V_FLAG_PARTIAL_CHAIN which
+	# we don't allow.
+	skip "NSS support partial chain validation", 2 if ($nss);
+	# Try with just the server CA's cert. This fails because the root file
+	# must contain the whole chain up to the root CA.
+	test_connect_fails($common_connstr,
+		"sslrootcert=ssl/server_ca.crt sslmode=verify-ca",
+		qr/SSL error/, "connect with server CA cert, without root CA");
+}
 
 # And finally, with the correct root cert.
 test_connect_ok(
 	$common_connstr,
-	"sslrootcert=ssl/root+server_ca.crt sslmode=require",
+	"sslrootcert=ssl/root+server_ca.crt sslmode=require cert_database=ssl/nss/root+server_ca.crt.db",
 	"connect with correct server CA cert file sslmode=require");
 test_connect_ok(
 	$common_connstr,
-	"sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca",
+	"sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca cert_database=ssl/nss/root+server_ca.crt.db",
 	"connect with correct server CA cert file sslmode=verify-ca");
 test_connect_ok(
 	$common_connstr,
-	"sslrootcert=ssl/root+server_ca.crt sslmode=verify-full",
+	"sslrootcert=ssl/root+server_ca.crt sslmode=verify-full cert_database=ssl/nss/root+server_ca.crt.db",
 	"connect with correct server CA cert file sslmode=verify-full");
 
-# Test with cert root file that contains two certificates. The client should
-# be able to pick the right one, regardless of the order in the file.
-test_connect_ok(
-	$common_connstr,
-	"sslrootcert=ssl/both-cas-1.crt sslmode=verify-ca",
-	"cert root file that contains two certificates, order 1");
-test_connect_ok(
-	$common_connstr,
-	"sslrootcert=ssl/both-cas-2.crt sslmode=verify-ca",
-	"cert root file that contains two certificates, order 2");
+SKIP:
+{
+	skip "CA ordering is irrelevant in NSS databases", 2 if ($nss);
 
+	# Test with cert root file that contains two certificates. The client should
+	# be able to pick the right one, regardless of the order in the file.
+	test_connect_ok(
+		$common_connstr,
+		"sslrootcert=ssl/both-cas-1.crt sslmode=verify-ca",
+		"cert root file that contains two certificates, order 1");
+
+	# How about import the both-file into a database?
+	test_connect_ok(
+		$common_connstr,
+		"sslrootcert=ssl/both-cas-2.crt sslmode=verify-ca",
+		"cert root file that contains two certificates, order 2");
+}
 # CRL tests
 
 # Invalid CRL filename is the same as no CRL, succeeds
 test_connect_ok(
 	$common_connstr,
-	"sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=invalid",
+	"sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=invalid cert_database=ssl/nss/root+server_ca.crt.db",
 	"sslcrl option with invalid file name");
 
-# A CRL belonging to a different CA is not accepted, fails
-test_connect_fails(
-	$common_connstr,
-	"sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/client.crl",
-	qr/SSL error/,
-	"CRL belonging to a different CA");
+SKIP:
+{
+	skip "CRL's are verified when adding to NSS database", 2 if ($nss);
+	# A CRL belonging to a different CA is not accepted, fails
+	test_connect_fails(
+		$common_connstr,
+		"sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/client.crl",
+		qr/SSL error/,
+		"CRL belonging to a different CA");
+}
 
 # With the correct CRL, succeeds (this cert is not revoked)
 test_connect_ok(
 	$common_connstr,
-	"sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl",
+	"sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl cert_database=ssl/nss/root+server_ca.crt__root+server.crl.db",
 	"CRL with a non-revoked cert");
 
 # Check that connecting with verify-full fails, when the hostname doesn't
 # match the hostname in the server's certificate.
 $common_connstr =
-  "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+  "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/root+server_ca.crt.db";
 
 test_connect_ok(
 	$common_connstr,
@@ -237,14 +237,14 @@ test_connect_ok(
 test_connect_fails(
 	$common_connstr,
 	"sslmode=verify-full host=wronghost.test",
-	qr/\Qserver certificate for "common-name.pg-ssltest.test" does not match host name "wronghost.test"\E/,
+	qr/\Qserver certificate for "common-name.pg-ssltest.test" does not match host name "wronghost.test"\E|SSL_ERROR_BAD_CERT_DOMAIN/,
 	"mismatch between host name and server certificate sslmode=verify-full");
 
 # Test Subject Alternative Names.
 switch_server_cert($node, 'server-multiple-alt-names');
 
 $common_connstr =
-  "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+  "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full cert_database=ssl/nss/root+server_ca.crt.db";
 
 test_connect_ok(
 	$common_connstr,
@@ -262,12 +262,12 @@ test_connect_ok(
 test_connect_fails(
 	$common_connstr,
 	"host=wronghost.alt-name.pg-ssltest.test",
-	qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "wronghost.alt-name.pg-ssltest.test"\E/,
+	qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "wronghost.alt-name.pg-ssltest.test"\E|SSL_ERROR_BAD_CERT_DOMAIN/,
 	"host name not matching with X.509 Subject Alternative Names");
 test_connect_fails(
 	$common_connstr,
 	"host=deep.subdomain.wildcard.pg-ssltest.test",
-	qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E/,
+	qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E|SSL_ERROR_BAD_CERT_DOMAIN/,
 	"host name not matching with X.509 Subject Alternative Names wildcard");
 
 # Test certificate with a single Subject Alternative Name. (this gives a
@@ -275,7 +275,7 @@ test_connect_fails(
 switch_server_cert($node, 'server-single-alt-name');
 
 $common_connstr =
-  "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+  "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full cert_database=ssl/nss/root+server_ca.crt.db";
 
 test_connect_ok(
 	$common_connstr,
@@ -285,12 +285,12 @@ test_connect_ok(
 test_connect_fails(
 	$common_connstr,
 	"host=wronghost.alt-name.pg-ssltest.test",
-	qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "wronghost.alt-name.pg-ssltest.test"\E/,
+	qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "wronghost.alt-name.pg-ssltest.test"\E|SSL_ERROR_BAD_CERT_DOMAIN/,
 	"host name not matching with a single X.509 Subject Alternative Name");
 test_connect_fails(
 	$common_connstr,
 	"host=deep.subdomain.wildcard.pg-ssltest.test",
-	qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E/,
+	qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E|SSL_ERROR_BAD_CERT_DOMAIN/,
 	"host name not matching with a single X.509 Subject Alternative Name wildcard"
 );
 
@@ -299,7 +299,7 @@ test_connect_fails(
 switch_server_cert($node, 'server-cn-and-alt-names');
 
 $common_connstr =
-  "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+  "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full cert_database=ssl/nss/root+server_ca.crt.db";
 
 test_connect_ok(
 	$common_connstr,
@@ -312,14 +312,14 @@ test_connect_ok(
 test_connect_fails(
 	$common_connstr,
 	"host=common-name.pg-ssltest.test",
-	qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 1 other name) does not match host name "common-name.pg-ssltest.test"\E/,
+	qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 1 other name) does not match host name "common-name.pg-ssltest.test"\E|SSL_ERROR_BAD_CERT_DOMAIN/,
 	"certificate with both a CN and SANs ignores CN");
 
 # Finally, test a server certificate that has no CN or SANs. Of course, that's
 # not a very sensible certificate, but libpq should handle it gracefully.
 switch_server_cert($node, 'server-no-names');
 $common_connstr =
-  "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+  "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/root+server_ca.crt.db";
 
 test_connect_ok(
 	$common_connstr,
@@ -328,7 +328,7 @@ test_connect_ok(
 test_connect_fails(
 	$common_connstr,
 	"sslmode=verify-full host=common-name.pg-ssltest.test",
-	qr/could not get server's host name from server certificate/,
+	qr/could not get server's host name from server certificate|SSL_ERROR_BAD_CERT_DOMAIN/,
 	"server certificate without CN or SANs sslmode=verify-full");
 
 # Test that the CRL works
@@ -340,11 +340,11 @@ $common_connstr =
 # Without the CRL, succeeds. With it, fails.
 test_connect_ok(
 	$common_connstr,
-	"sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca",
+	"sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca cert_database=ssl/nss/root+server_ca.crt.db",
 	"connects without client-side CRL");
 test_connect_fails(
 	$common_connstr,
-	"sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl",
+	"sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/server.crl cert_database=ssl/nss/root+server_ca.crt__server.crl.db",
 	qr/SSL error/,
 	"does not connect with client-side CRL");
 
@@ -365,21 +365,21 @@ command_like(
 # Test min/max SSL protocol versions.
 test_connect_ok(
 	$common_connstr,
-	"sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.2",
+	"sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.2 cert_database=ssl/nss/root+server_ca.crt.db",
 	"connection success with correct range of TLS protocol versions");
 test_connect_fails(
 	$common_connstr,
-	"sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.1",
+	"sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.1 cert_database=ssl/nss/root+server_ca.crt.db",
 	qr/invalid SSL protocol version range/,
 	"connection failure with incorrect range of TLS protocol versions");
 test_connect_fails(
 	$common_connstr,
-	"sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=incorrect_tls",
+	"sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=incorrect_tls cert_database=ssl/nss/root+server_ca.crt.db",
 	qr/invalid ssl_min_protocol_version value/,
 	"connection failure with an incorrect SSL protocol minimum bound");
 test_connect_fails(
 	$common_connstr,
-	"sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_max_protocol_version=incorrect_tls",
+	"sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_max_protocol_version=incorrect_tls cert_database=ssl/nss/root+server_ca.crt.db",
 	qr/invalid ssl_max_protocol_version value/,
 	"connection failure with an incorrect SSL protocol maximum bound");
 
@@ -390,7 +390,7 @@ test_connect_fails(
 note "running server tests";
 
 $common_connstr =
-  "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR";
+  "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client.crt__client.key.db";
 
 # no client cert
 test_connect_fails(
@@ -406,32 +406,43 @@ test_connect_ok(
 	"certificate authorization succeeds with correct client cert in PEM format"
 );
 
-# correct client cert in unencrypted DER
-test_connect_ok(
-	$common_connstr,
-	"user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-der_tmp.key",
-	"certificate authorization succeeds with correct client cert in DER format"
-);
+$common_connstr =
+  "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR";
+
+SKIP:
+{
+	skip "NSS database not implemented in the Makefile", 1 if ($nss);
+	# correct client cert in unencrypted DER
+	test_connect_ok(
+		$common_connstr,
+		"user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-der_tmp.key",
+		"certificate authorization succeeds with correct client cert in DER format"
+	);
+}
 
 # correct client cert in encrypted PEM
 test_connect_ok(
 	$common_connstr,
-	"user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='dUmmyP^#+'",
+	"user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='dUmmyP^#+' cert_database=ssl/nss/client.crt__client-encrypted-pem.key.db",
 	"certificate authorization succeeds with correct client cert in encrypted PEM format"
 );
 
-# correct client cert in encrypted DER
-test_connect_ok(
-	$common_connstr,
-	"user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-der_tmp.key sslpassword='dUmmyP^#+'",
-	"certificate authorization succeeds with correct client cert in encrypted DER format"
-);
+SKIP:
+{
+	skip "NSS database not implemented in the Makefile", 1 if ($nss);
+	# correct client cert in encrypted DER
+	test_connect_ok(
+		$common_connstr,
+		"user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-der_tmp.key sslpassword='dUmmyP^#+'",
+		"certificate authorization succeeds with correct client cert in encrypted DER format"
+	);
+}
 
 # correct client cert in encrypted PEM with wrong password
 test_connect_fails(
 	$common_connstr,
-	"user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='wrong'",
-	qr!\Qprivate key file "ssl/client-encrypted-pem_tmp.key": bad decrypt\E!,
+	"user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='wrong' cert_database=ssl/nss/client.crt__client-encrypted-pem.key.db",
+	qr!connection requires a valid client certificate|\Qprivate key file "ssl/client-encrypted-pem_tmp.key": bad decrypt\E!,
 	"certificate authorization fails with correct client cert and wrong password in encrypted PEM format"
 );
 
@@ -471,18 +482,19 @@ command_like(
 		'-P',
 		'null=_null_',
 		'-d',
-		"$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
+		"$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key cert_database=ssl/nss/client.crt__client.key.db",
 		'-c',
 		"SELECT * FROM pg_stat_ssl WHERE pid = pg_backend_pid()"
 	],
 	qr{^pid,ssl,version,cipher,bits,compression,client_dn,client_serial,issuer_dn\r?\n
-				^\d+,t,TLSv[\d.]+,[\w-]+,\d+,f,/CN=ssltestuser,1,\Q/CN=Test CA for PostgreSQL SSL regression test client certs\E\r?$}mx,
+				^\d+,t,TLSv[\d.]+,[\w-]+,\d+,f,/?CN=ssltestuser,1,/?\QCN=Test CA for PostgreSQL SSL regression test client certs\E\r?$}mx,
 	'pg_stat_ssl with client certificate');
 
 # client key with wrong permissions
 SKIP:
 {
 	skip "Permissions check not enforced on Windows", 2 if ($windows_os);
+	skip "Key not on filesystem with NSS",            2 if ($nss);
 
 	test_connect_fails(
 		$common_connstr,
@@ -495,10 +507,13 @@ SKIP:
 test_connect_fails(
 	$common_connstr,
 	"user=anotheruser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
-	qr/certificate authentication failed for user "anotheruser"/,
+	qr/unable to verify certificate|certificate authentication failed for user "anotheruser"/,
 	"certificate authorization fails with client cert belonging to another user"
 );
 
+$common_connstr =
+  "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client-revoked.crt__client-revoked.key.db";
+
 # revoked client cert
 test_connect_fails(
 	$common_connstr,
@@ -510,7 +525,7 @@ test_connect_fails(
 # works, iff username matches Common Name
 # fails, iff username doesn't match Common Name.
 $common_connstr =
-  "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=verifydb hostaddr=$SERVERHOSTADDR";
+  "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=verifydb hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client.crt__client.key.db";
 
 test_connect_ok(
 	$common_connstr,
@@ -536,17 +551,23 @@ test_connect_ok(
 # intermediate client_ca.crt is provided by client, and isn't in server's ssl_ca_file
 switch_server_cert($node, 'server-cn-only', 'root_ca');
 $common_connstr =
-  "user=ssltestuser dbname=certdb sslkey=ssl/client_tmp.key sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+  "user=ssltestuser dbname=certdb sslkey=ssl/client_tmp.key sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client+client_ca.crt__client.key.db";
 
-test_connect_ok(
+TODO:
+{
+	local $TODO = "WIP failure cause currently unknown";
+	test_connect_ok(
+		$common_connstr,
+		"sslmode=require sslcert=ssl/client+client_ca.crt",
+		"intermediate client certificate is provided by client");
+}
+
+test_connect_fails(
 	$common_connstr,
-	"sslmode=require sslcert=ssl/client+client_ca.crt",
-	"intermediate client certificate is provided by client");
-test_connect_fails($common_connstr, "sslmode=require sslcert=ssl/client.crt",
-	qr/SSL error/, "intermediate client certificate is missing");
+	"sslmode=require sslcert=ssl/client.crt",
+	qr/connection requires a valid client certificate|SSL error/,
+	"intermediate client certificate is missing");
 
 # clean up
-foreach my $key (@keys)
-{
-	unlink("ssl/${key}_tmp.key");
-}
+
+SSL::Server::cleanup();
diff --git a/src/test/ssl/t/002_scram.pl b/src/test/ssl/t/002_scram.pl
index 01231f8ba0..4ea81fdbcf 100644
--- a/src/test/ssl/t/002_scram.pl
+++ b/src/test/ssl/t/002_scram.pl
@@ -11,11 +11,11 @@ use File::Copy;
 use FindBin;
 use lib $FindBin::RealBin;
 
-use SSLServer;
+use SSL::Server;
 
 if ($ENV{with_openssl} ne 'yes')
 {
-	plan skip_all => 'SSL not supported by this build';
+	plan skip_all => 'OpenSSL not supported by this build';
 }
 
 # This is the hostname used to connect to the server.
diff --git a/src/test/ssl/t/SSL/Backend/NSS.pm b/src/test/ssl/t/SSL/Backend/NSS.pm
new file mode 100644
index 0000000000..837f0d9891
--- /dev/null
+++ b/src/test/ssl/t/SSL/Backend/NSS.pm
@@ -0,0 +1,64 @@
+package SSL::Backend::NSS;
+
+use strict;
+use warnings;
+use Exporter;
+
+our @ISA       = qw(Exporter);
+our @EXPORT_OK = qw(get_new_nss_backend);
+
+sub new
+{
+	my ($class) = @_;
+
+	my $self = { _library => 'NSS' };
+
+	bless $self, $class;
+
+	return $self;
+}
+
+sub get_new_nss_backend
+{
+	my $class = 'SSL::Backend::NSS';
+
+	return $class->new();
+}
+
+sub init
+{
+	# Make sure the certificate databases are in place?
+}
+
+sub get_library
+{
+	my ($self) = @_;
+
+	return $self->{_library};
+}
+
+sub set_server_cert
+{
+	my $self     = $_[0];
+	my $certfile = $_[1];
+	my $cafile   = $_[2];
+	my $keyfile  = $_[3];
+
+	my $cert_nickname = $certfile . '.crt__' . $keyfile . '.key';
+	my $cert_database = $cert_nickname . '.db';
+
+	my $sslconf =
+	    "ssl_ca_file='$cafile.crt'\n"
+	  . "ssl_cert_file='ssl/$certfile.crt'\n"
+	  . "ssl_crl_file=''\n"
+	  . "ssl_database='nss/$cert_database'\n";
+
+	return $sslconf;
+}
+
+sub cleanup
+{
+	# Something?
+}
+
+1;
diff --git a/src/test/ssl/t/SSL/Backend/OpenSSL.pm b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
new file mode 100644
index 0000000000..62b11b7632
--- /dev/null
+++ b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
@@ -0,0 +1,103 @@
+package SSL::Backend::OpenSSL;
+
+use strict;
+use warnings;
+use Exporter;
+use File::Copy;
+
+our @ISA       = qw(Exporter);
+our @EXPORT_OK = qw(get_new_openssl_backend);
+
+our (@keys);
+
+INIT
+{
+	@keys = (
+		"client",     "client-revoked",
+		"client-der", "client-encrypted-pem",
+		"client-encrypted-der");
+}
+
+sub new
+{
+	my ($class) = @_;
+
+	my $self = { _library => 'OpenSSL' };
+
+	bless $self, $class;
+
+	return $self;
+}
+
+sub get_new_openssl_backend
+{
+	my $class = 'SSL::Backend::OpenSSL';
+
+	my $backend = $class->new();
+
+	return $backend;
+}
+
+sub init
+{
+	# The client's private key must not be world-readable, so take a copy
+	# of the key stored in the code tree and update its permissions.
+	#
+	# This changes ssl/client.key to ssl/client_tmp.key etc for the rest
+	# of the tests.
+	foreach my $key (@keys)
+	{
+		copy("ssl/${key}.key", "ssl/${key}_tmp.key")
+		  or die
+		  "couldn't copy ssl/${key}.key to ssl/${key}_tmp.key for permissions change: $!";
+		chmod 0600, "ssl/${key}_tmp.key"
+		  or die "failed to change permissions on ssl/${key}_tmp.key: $!";
+	}
+
+	# Also make a copy of that explicitly world-readable.  We can't
+	# necessarily rely on the file in the source tree having those
+	# permissions. Add it to @keys to include it in the final clean
+	# up phase.
+	copy("ssl/client.key", "ssl/client_wrongperms_tmp.key")
+	  or die
+	  "couldn't copy ssl/client.key to ssl/client_wrongperms_tmp.key: $!";
+	chmod 0644, "ssl/client_wrongperms_tmp.key"
+	  or die
+	  "failed to change permissions on ssl/client_wrongperms_tmp.key: $!";
+	push @keys, 'client_wrongperms';
+}
+
+# Change the configuration to use given server cert file, and reload
+# the server so that the configuration takes effect.
+sub set_server_cert
+{
+	my $self     = $_[0];
+	my $certfile = $_[1];
+	my $cafile   = $_[2] || "root+client_ca";
+	my $keyfile  = $_[3] || $certfile;
+
+	my $sslconf =
+	    "ssl_ca_file='$cafile.crt'\n"
+	  . "ssl_cert_file='$certfile.crt'\n"
+	  . "ssl_key_file='$keyfile.key'\n"
+	  . "ssl_crl_file='root+client.crl'\n";
+
+	return $sslconf;
+}
+
+sub get_library
+{
+	my ($self) = @_;
+
+	return $self->{_library};
+}
+
+sub cleanup
+{
+	foreach my $key (@keys)
+	{
+		unlink("ssl/${key}_tmp.key");
+	}
+}
+
+1;
diff --git a/src/test/ssl/t/SSLServer.pm b/src/test/ssl/t/SSL/Server.pm
similarity index 78%
rename from src/test/ssl/t/SSLServer.pm
rename to src/test/ssl/t/SSL/Server.pm
index 1e392b8fbf..679969902e 100644
--- a/src/test/ssl/t/SSLServer.pm
+++ b/src/test/ssl/t/SSL/Server.pm
@@ -24,19 +24,39 @@
 # explicitly because an invalid sslcert or sslrootcert, respectively,
 # causes those to be ignored.)
 
-package SSLServer;
+package SSL::Server;
 
 use strict;
 use warnings;
 use PostgresNode;
+use RecursiveCopy;
 use TestLib;
 use File::Basename;
 use File::Copy;
 use Test::More;
+use SSL::Backend::OpenSSL qw(get_new_openssl_backend);
+use SSL::Backend::NSS qw(get_new_nss_backend);
+
+our ($openssl, $nss, $backend);
+
+# The TLS backend which the server is using should be mostly transparent for
+# the user, apart from individual configuration settings, so keep the backend
+# specific things abstracted behind SSL::Server.
+if ($ENV{with_openssl} eq 'yes')
+{
+	$backend = get_new_openssl_backend();
+	$openssl = 1;
+}
+elsif ($ENV{with_nss} eq 'yes')
+{
+	$backend = get_new_nss_backend();
+	$nss     = 1;
+}
 
 use Exporter 'import';
 our @EXPORT = qw(
   configure_test_server_for_ssl
+  set_server_cert
   switch_server_cert
   test_connect_fails
   test_connect_ok
@@ -145,12 +165,19 @@ sub configure_test_server_for_ssl
 	close $sslconf;
 
 	# Copy all server certificates and keys, and client root cert, to the data dir
-	copy_files("ssl/server-*.crt", $pgdata);
-	copy_files("ssl/server-*.key", $pgdata);
-	chmod(0600, glob "$pgdata/server-*.key") or die $!;
-	copy_files("ssl/root+client_ca.crt", $pgdata);
-	copy_files("ssl/root_ca.crt",        $pgdata);
-	copy_files("ssl/root+client.crl",    $pgdata);
+	if (defined($openssl))
+	{
+		copy_files("ssl/server-*.crt", $pgdata);
+		copy_files("ssl/server-*.key", $pgdata);
+		chmod(0600, glob "$pgdata/server-*.key") or die $!;
+		copy_files("ssl/root+client_ca.crt", $pgdata);
+		copy_files("ssl/root_ca.crt",        $pgdata);
+		copy_files("ssl/root+client.crl",    $pgdata);
+	}
+	elsif (defined($nss))
+	{
+		RecursiveCopy::copypath("ssl/nss", $pgdata . "/nss") if -e "ssl/nss";
+	}
 
 	# Stop and restart server to load new listen_addresses.
 	$node->restart;
@@ -158,26 +185,51 @@ sub configure_test_server_for_ssl
 	# Change pg_hba after restart because hostssl requires ssl=on
 	configure_hba_for_ssl($node, $servercidr, $authmethod);
 
+	# Finally, perform backend specific configuration
+	$backend->init();
+
 	return;
 }
 
-# Change the configuration to use given server cert file, and reload
-# the server so that the configuration takes effect.
-sub switch_server_cert
+sub ssl_library
+{
+	return $backend->get_library();
+}
+
+sub cleanup
+{
+	$backend->cleanup();
+}
+
+# Change the configuration to use given server cert file,
+sub set_server_cert
 {
 	my $node     = $_[0];
 	my $certfile = $_[1];
 	my $cafile   = $_[2] || "root+client_ca";
+	my $keyfile  = $_[3] || '';
+	my $pwcmd    = $_[4] || '';
 	my $pgdata   = $node->data_dir;
 
+	$keyfile = $certfile if $keyfile eq '';
+
 	open my $sslconf, '>', "$pgdata/sslconfig.conf";
 	print $sslconf "ssl=on\n";
-	print $sslconf "ssl_ca_file='$cafile.crt'\n";
-	print $sslconf "ssl_cert_file='$certfile.crt'\n";
-	print $sslconf "ssl_key_file='$certfile.key'\n";
-	print $sslconf "ssl_crl_file='root+client.crl'\n";
+	print $sslconf $backend->set_server_cert($certfile, $cafile, $keyfile);
+	print $sslconf "ssl_passphrase_command='$pwcmd'\n"
+	  unless $pwcmd eq '';
 	close $sslconf;
+	return;
+}
 
+# Change the configuration to use given server cert file, and reload
+# the server so that the configuration takes effect.
+# Takes the same arguments as set_server_cert, which it calls to do that
+# piece of the work.
+sub switch_server_cert
+{
+	my $node = $_[0];
+	set_server_cert(@_);
 	$node->restart;
 	return;
 }
diff --git a/src/tools/msvc/Install.pm b/src/tools/msvc/Install.pm
index b6d0cfd39b..c53c59229e 100644
--- a/src/tools/msvc/Install.pm
+++ b/src/tools/msvc/Install.pm
@@ -438,7 +438,8 @@ sub CopyContribFiles
 		{
 			# These configuration-based exclusions must match vcregress.pl
 			next if ($d eq "uuid-ossp"  && !defined($config->{uuid}));
-			next if ($d eq "sslinfo"    && !defined($config->{openssl}));
+			next if ($d eq "sslinfo"    && !defined($config->{openssl})
+			  && !defined($config->{nss}));
 			next if ($d eq "xml2"       && !defined($config->{xml}));
 			next if ($d =~ /_plperl$/   && !defined($config->{perl}));
 			next if ($d =~ /_plpython$/ && !defined($config->{python}));
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index 20da7985c1..818a1922f3 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -192,12 +192,19 @@ sub mkvcbuild
 	$postgres->FullExportDLL('postgres.lib');
 
 	# The OBJS scraper doesn't know about ifdefs, so remove appropriate files
-	# if building without OpenSSL.
-	if (!$solution->{options}->{openssl})
+	# if building without various options.
+	if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
 	{
 		$postgres->RemoveFile('src/backend/libpq/be-secure-common.c');
+	}
+	if (!$solution->{options}->{openssl})
+	{
 		$postgres->RemoveFile('src/backend/libpq/be-secure-openssl.c');
 	}
+	if (!$solution->{options}->{nss})
+	{
+		$postgres->RemoveFile('src/backend/libpq/be-secure-nss.c');
+	}
 	if (!$solution->{options}->{gss})
 	{
 		$postgres->RemoveFile('src/backend/libpq/be-gssapi-common.c');
@@ -255,12 +262,19 @@ sub mkvcbuild
 	$libpq->AddReference($libpgcommon, $libpgport);
 
 	# The OBJS scraper doesn't know about ifdefs, so remove appropriate files
-	# if building without OpenSSL.
-	if (!$solution->{options}->{openssl})
+	# if building without various options
+	if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
 	{
 		$libpq->RemoveFile('src/interfaces/libpq/fe-secure-common.c');
+	}
+	if (!$solution->{options}->{openssl})
+	{
 		$libpq->RemoveFile('src/interfaces/libpq/fe-secure-openssl.c');
 	}
+	if (!$solution->{options}->{nss})
+	{
+		$libpq->RemoveFile('src/interfaces/libpq/fe-secure-nss.c');
+	}
 	if (!$solution->{options}->{gss})
 	{
 		$libpq->RemoveFile('src/interfaces/libpq/fe-gssapi-common.c');
@@ -428,9 +442,14 @@ sub mkvcbuild
 		push @contrib_excludes, 'xml2';
 	}
 
+	if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
+	{
+		push @contrib_excludes, 'sslinfo';
+	}
+
 	if (!$solution->{options}->{openssl})
 	{
-		push @contrib_excludes, 'sslinfo', 'ssl_passphrase_callback';
+		push @contrib_excludes, 'ssl_passphrase_callback';
 	}
 
 	if (!$solution->{options}->{uuid})
diff --git a/src/tools/msvc/Solution.pm b/src/tools/msvc/Solution.pm
index bc8904732f..ac11d9ab26 100644
--- a/src/tools/msvc/Solution.pm
+++ b/src/tools/msvc/Solution.pm
@@ -484,6 +484,7 @@ sub GenerateFiles
 		USE_NAMED_POSIX_SEMAPHORES => undef,
 		USE_OPENSSL                => undef,
 		USE_OPENSSL_RANDOM         => undef,
+		USE_NSS                    => undef,
 		USE_PAM                    => undef,
 		USE_SLICING_BY_8_CRC32C    => undef,
 		USE_SSE42_CRC32C           => undef,
@@ -537,6 +538,10 @@ sub GenerateFiles
 			$define{HAVE_OPENSSL_INIT_SSL}      = 1;
 		}
 	}
+	if ($self->{options}->{nss})
+	{
+		$define{USE_NSS} = 1;
+	}
 
 	$self->GenerateConfigHeader('src/include/pg_config.h',     \%define, 1);
 	$self->GenerateConfigHeader('src/include/pg_config_ext.h', \%define, 0);
@@ -1004,6 +1009,21 @@ sub AddProject
 			}
 		}
 	}
+	if ($self->{options}->{nss})
+	{
+		$proj->AddIncludeDir($self->{options}->{nss} . '\..\public\nss');
+		$proj->AddIncludeDir($self->{options}->{nss} . '\include\nspr');
+		foreach my $lib (qw(plds4 plc4 nspr4))
+		{
+			$proj->AddLibrary($self->{options}->{nss} .
+							  '\lib\lib' . "$lib.lib", 0);
+		}
+		foreach my $lib (qw(ssl3 smime3 nss3))
+		{
+			$proj->AddLibrary($self->{options}->{nss} .
+							  '\lib' . "\\$lib.dll.lib", 0);
+		}
+	}
 	if ($self->{options}->{nls})
 	{
 		$proj->AddIncludeDir($self->{options}->{nls} . '\include');
diff --git a/src/tools/msvc/config_default.pl b/src/tools/msvc/config_default.pl
index 2ef2cfc4e9..49dc4d5864 100644
--- a/src/tools/msvc/config_default.pl
+++ b/src/tools/msvc/config_default.pl
@@ -17,6 +17,7 @@ our $config = {
 	perl      => undef,    # --with-perl=<path>
 	python    => undef,    # --with-python=<path>
 	openssl   => undef,    # --with-openssl=<path>
+	nss       => undef,    # --with-nss=<path>
 	uuid      => undef,    # --with-uuid=<path>
 	xml       => undef,    # --with-libxml=<path>
 	xslt      => undef,    # --with-libxslt=<path>
-- 
2.25.4


--------------C29A190231AE8074F549FB7F--





^ permalink  raw  reply  [nested|flat] 5+ messages in thread

* Re: Transparent column encryption
@ 2023-01-11 16:46 vignesh C <[email protected]>
  2023-01-25 18:44 ` Re: Transparent column encryption Peter Eisentraut <[email protected]>
  0 siblings, 1 reply; 5+ messages in thread

From: vignesh C @ 2023-01-11 16:46 UTC (permalink / raw)
  To: Peter Eisentraut <[email protected]>; +Cc: pgsql-hackers

On Sat, 31 Dec 2022 at 19:47, Peter Eisentraut
<[email protected]> wrote:
>
> On 21.12.22 06:46, Peter Eisentraut wrote:
> > And another update.  The main changes are that I added an 'unspecified'
> > CMK algorithm, which indicates that the external KMS knows what it is
> > but the database system doesn't.  This was discussed a while ago.  I
> > also changed some details about how the "cmklookup" works in libpq. Also
> > added more code comments and documentation and rearranged some code.
> >
> > According to my local todo list, this patch is now complete.
>
> Another update, with some merge conflicts resolved.  I also fixed up the
> remaining TODO markers in the code, which had something to do with Perl
> and Windows.  I did some more work on schema handling, e.g., CREATE
> TABLE / LIKE, views, partitioning etc. on top of encrypted columns,
> mostly tedious and repetitive, nothing interesting.  I also rewrote the
> code that extracts the underlying tables and columns corresponding to
> query parameters.  It's now much simpler and better encapsulated.

The patch does not apply on top of HEAD as in [1], please post a rebased patch:
=== Applying patches on top of PostgreSQL commit ID
5f6401f81cb24bd3930e0dc589fc4aa8b5424cdc ===
=== applying patch ./v14-0001-Transparent-column-encryption.patch
....
Hunk #1 FAILED at 1109.
....
1 out of 5 hunks FAILED -- saving rejects to file doc/src/sgml/protocol.sgml.rej
....
patching file doc/src/sgml/ref/create_table.sgml
Hunk #3 FAILED at 351.
Hunk #4 FAILED at 704.
2 out of 4 hunks FAILED -- saving rejects to file
doc/src/sgml/ref/create_table.sgml.rej
....
Hunk #1 FAILED at 1420.
Hunk #2 FAILED at 4022.
2 out of 2 hunks FAILED -- saving rejects to file
doc/src/sgml/ref/psql-ref.sgml.rej

[1] - http://cfbot.cputube.org/patch_41_3718.log

Regards,
Vignesh






^ permalink  raw  reply  [nested|flat] 5+ messages in thread

* Re: Transparent column encryption
  2023-01-11 16:46 Re: Transparent column encryption vignesh C <[email protected]>
@ 2023-01-25 18:44 ` Peter Eisentraut <[email protected]>
  2023-02-11 21:54   ` Re: Transparent column encryption Mark Dilger <[email protected]>
  0 siblings, 1 reply; 5+ messages in thread

From: Peter Eisentraut @ 2023-01-25 18:44 UTC (permalink / raw)
  To: pgsql-hackers; +Cc: vignesh C <[email protected]>

On 11.01.23 17:46, vignesh C wrote:
> On Sat, 31 Dec 2022 at 19:47, Peter Eisentraut
> <[email protected]> wrote:
>>
>> On 21.12.22 06:46, Peter Eisentraut wrote:
>>> And another update.  The main changes are that I added an 'unspecified'
>>> CMK algorithm, which indicates that the external KMS knows what it is
>>> but the database system doesn't.  This was discussed a while ago.  I
>>> also changed some details about how the "cmklookup" works in libpq. Also
>>> added more code comments and documentation and rearranged some code.
>>>
>>> According to my local todo list, this patch is now complete.
>>
>> Another update, with some merge conflicts resolved.  I also fixed up the
>> remaining TODO markers in the code, which had something to do with Perl
>> and Windows.  I did some more work on schema handling, e.g., CREATE
>> TABLE / LIKE, views, partitioning etc. on top of encrypted columns,
>> mostly tedious and repetitive, nothing interesting.  I also rewrote the
>> code that extracts the underlying tables and columns corresponding to
>> query parameters.  It's now much simpler and better encapsulated.
> 
> The patch does not apply on top of HEAD as in [1], please post a rebased patch:

Here is a new patch.  Changes since v14:

- Fixed some typos (review by Justin Pryzby)
- Fixed backward compat. psql and pg_dump (review by Justin Pryzby)
- Doc additions (review by Jacob Champion)
- Validate column_encryption option in libpq (review by Jacob Champion)
- Handle column encryption in inheritance
- Change CEKs and CMKs to live inside schemas
From 0a40fa00a2426d74519415e9089ce7af7df4e012 Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <[email protected]>
Date: Wed, 25 Jan 2023 19:40:05 +0100
Subject: [PATCH v15] Transparent column encryption

This feature enables the automatic, transparent encryption and
decryption of particular columns in the client.  The data for those
columns then only ever appears in ciphertext on the server, so it is
protected from DBAs, sysadmins, cloud operators, etc. as well as
accidental leakage to server logs, file-system backups, etc.  The
canonical use case for this feature is storing credit card numbers
encrypted, in accordance with PCI DSS, as well as similar situations
involving social security numbers etc.  One can't do any computations
with encrypted values on the server, but for these use cases, that is
not necessary.  This feature does support deterministic encryption as
an alternative to the default randomized encryption, so in that mode
one can do equality lookups, at the cost of some security.

This functionality also exists in other database products, and the
overall concepts were mostly adopted from there.

(Note: This feature has nothing to do with any on-disk encryption
feature.  Both can exist independently.)

You declare a column as encrypted in a CREATE TABLE statement.  The
column value is encrypted by a symmetric key called the column
encryption key (CEK).  The CEK is a catalog object.  The CEK key
material is in turn encrypted by an asymmetric key called the column
master key (CMK).  The CMK is not stored in the database but somewhere
where the client can get to it, for example in a file or in a key
management system.  When a server sends rows containing encrypted
column values to the client, it first sends the required CMK and CEK
information (new protocol messages), which the client needs to record.
Then, the client can use this information to automatically decrypt the
incoming row data and forward it in plaintext to the application.

For the CMKs, libpq has a new connection parameter "cmklookup" that
specifies via a mini-language where to get the keys.  Right now, you
can use "file" to read it from a file, or "run" to run some program,
which could get it from a KMS.

The general idea would be for an application to have one CMK per area
of secret stuff, for example, for credit card data.  The CMK can be
rotated: each CEK can be represented multiple times in the database,
encrypted by a different CMK.  (The CEK can't be rotated easily, since
that would require reading out all the data from a table/column and
reencrypting it.  We could/should add some custom tooling for that,
but it wouldn't be a routine operation.)

Several encryption algorithms are provided.  The CMK process uses
RSAES_OAEP_SHA_1 or _256.  The CEK process uses
AEAD_AES_*_CBC_HMAC_SHA_* with several strengths.

In the server, the encrypted datums are stored in types called
pg_encrypted_rnd and pg_encrypted_det (for randomized and
deterministic encryption).  These are essentially cousins of bytea.
For the rest of the database system below the protocol handling, there
is nothing special about those.  For example, pg_encrypted_rnd has no
operators at all, pg_encrypted_det has only an equality operator.
pg_attribute has a new column attrealtypid that stores the original
type of the data in the column.  This is only used for providing it to
clients, so that higher-level clients can convert the decrypted value
to their appropriate data types in their environments.

The protocol extensions are guarded by a new protocol extension option
"_pq_.column_encryption".  If this is not set, nothing changes, the
protocol stays the same, and no encryption or decryption happens.

To get transparently encrypted data into the database (as opposed to
reading it out), it is required to use protocol-level prepared
statements (i.e., extended query).  The client must first prepare a
statement, then describe the statement to get parameter metadata,
which indicates which parameters are to be encrypted and how.  libpq's
PQexecParams() does this internally.  For the asynchronous interfaces,
additional libpq functions are added to be able to pass the describe
result back into the statement execution function.  (Other client APIs
that have a "statement handle" concept could do this more elegantly
and probably without any API changes.)  psql also supports this
transparently if the \bind command is used.

Another challenge is that the parse analysis must check which
underlying column a parameter corresponds to.  This is similar to
resorigtbl and resorigcol in the opposite direction.

Discussion: https://www.postgresql.org/message-id/flat/89157929-c2b6-817b-6025-8e4b2d89d88f%40enterprisedb.com
---
 doc/src/sgml/acronyms.sgml                    |  18 +
 doc/src/sgml/catalogs.sgml                    | 295 ++++++
 doc/src/sgml/charset.sgml                     |   9 +
 doc/src/sgml/datatype.sgml                    |  55 ++
 doc/src/sgml/ddl.sgml                         | 416 +++++++++
 doc/src/sgml/func.sgml                        |  26 +
 doc/src/sgml/glossary.sgml                    |  23 +
 doc/src/sgml/libpq.sgml                       | 315 +++++++
 doc/src/sgml/protocol.sgml                    | 442 +++++++++
 doc/src/sgml/ref/allfiles.sgml                |   6 +
 .../sgml/ref/alter_column_encryption_key.sgml | 197 ++++
 doc/src/sgml/ref/alter_column_master_key.sgml | 134 +++
 doc/src/sgml/ref/comment.sgml                 |   2 +
 doc/src/sgml/ref/copy.sgml                    |   9 +
 .../ref/create_column_encryption_key.sgml     | 169 ++++
 .../sgml/ref/create_column_master_key.sgml    | 107 +++
 doc/src/sgml/ref/create_table.sgml            |  54 +-
 .../sgml/ref/drop_column_encryption_key.sgml  | 112 +++
 doc/src/sgml/ref/drop_column_master_key.sgml  | 112 +++
 doc/src/sgml/ref/pg_dump.sgml                 |  31 +
 doc/src/sgml/ref/pg_dumpall.sgml              |  27 +
 doc/src/sgml/ref/psql-ref.sgml                |  39 +
 doc/src/sgml/reference.sgml                   |   6 +
 src/backend/access/common/printsimple.c       |   7 +
 src/backend/access/common/printtup.c          | 221 ++++-
 src/backend/access/common/tupdesc.c           |  12 +
 src/backend/access/hash/hashvalidate.c        |   2 +-
 src/backend/catalog/Makefile                  |   3 +-
 src/backend/catalog/aclchk.c                  |  12 +
 src/backend/catalog/dependency.c              |  18 +
 src/backend/catalog/heap.c                    |  42 +-
 src/backend/catalog/namespace.c               | 272 ++++++
 src/backend/catalog/objectaddress.c           | 288 ++++++
 src/backend/commands/Makefile                 |   1 +
 src/backend/commands/alter.c                  |  17 +
 src/backend/commands/colenccmds.c             | 427 +++++++++
 src/backend/commands/createas.c               |  32 +
 src/backend/commands/dropcmds.c               |  15 +
 src/backend/commands/event_trigger.c          |  12 +
 src/backend/commands/meson.build              |   1 +
 src/backend/commands/seclabel.c               |   3 +
 src/backend/commands/tablecmds.c              | 190 +++-
 src/backend/commands/variable.c               |   7 +-
 src/backend/commands/view.c                   |  20 +
 src/backend/nodes/nodeFuncs.c                 |   2 +
 src/backend/parser/gram.y                     | 169 +++-
 src/backend/parser/parse_param.c              | 144 +++
 src/backend/parser/parse_utilcmd.c            |  12 +-
 src/backend/postmaster/postmaster.c           |  19 +-
 src/backend/tcop/postgres.c                   |  64 ++
 src/backend/tcop/utility.c                    |  53 ++
 src/backend/utils/adt/varlena.c               | 106 +++
 src/backend/utils/cache/lsyscache.c           |  83 ++
 src/backend/utils/cache/plancache.c           |   4 +-
 src/backend/utils/cache/syscache.c            |  42 +
 src/backend/utils/mb/mbutils.c                |  18 +-
 src/bin/pg_dump/common.c                      |  44 +
 src/bin/pg_dump/pg_backup.h                   |   1 +
 src/bin/pg_dump/pg_backup_archiver.c          |   2 +
 src/bin/pg_dump/pg_backup_db.c                |   9 +-
 src/bin/pg_dump/pg_dump.c                     | 343 ++++++-
 src/bin/pg_dump/pg_dump.h                     |  33 +
 src/bin/pg_dump/pg_dump_sort.c                |  14 +
 src/bin/pg_dump/pg_dumpall.c                  |   5 +
 src/bin/pg_dump/t/002_pg_dump.pl              |  52 ++
 src/bin/psql/command.c                        |   6 +-
 src/bin/psql/describe.c                       | 179 +++-
 src/bin/psql/describe.h                       |   6 +
 src/bin/psql/help.c                           |   4 +
 src/bin/psql/settings.h                       |   1 +
 src/bin/psql/startup.c                        |  10 +
 src/bin/psql/tab-complete.c                   |  55 +-
 src/common/Makefile                           |   1 +
 src/common/colenc.c                           | 104 +++
 src/common/meson.build                        |   1 +
 src/include/access/printtup.h                 |   2 +
 src/include/catalog/dependency.h              |   3 +
 src/include/catalog/heap.h                    |   1 +
 src/include/catalog/meson.build               |   3 +
 src/include/catalog/namespace.h               |   6 +
 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_colenckey.h            |  41 +
 src/include/catalog/pg_colenckeydata.h        |  46 +
 src/include/catalog/pg_colmasterkey.h         |  46 +
 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               |  41 +
 src/include/catalog/pg_type.dat               |  12 +
 src/include/catalog/pg_type.h                 |   1 +
 src/include/commands/colenccmds.h             |  26 +
 src/include/commands/tablecmds.h              |   2 +
 src/include/common/colenc.h                   |  51 ++
 src/include/libpq/libpq-be.h                  |   1 +
 src/include/nodes/parsenodes.h                |  40 +-
 src/include/parser/kwlist.h                   |   2 +
 src/include/parser/parse_param.h              |   1 +
 src/include/tcop/cmdtaglist.h                 |   6 +
 src/include/utils/lsyscache.h                 |   4 +
 src/include/utils/plancache.h                 |   3 +
 src/include/utils/syscache.h                  |   6 +
 src/interfaces/libpq/Makefile                 |   1 +
 src/interfaces/libpq/exports.txt              |   4 +
 src/interfaces/libpq/fe-connect.c             |  46 +
 src/interfaces/libpq/fe-encrypt-openssl.c     | 842 ++++++++++++++++++
 src/interfaces/libpq/fe-encrypt.h             |  33 +
 src/interfaces/libpq/fe-exec.c                | 665 +++++++++++++-
 src/interfaces/libpq/fe-protocol3.c           | 139 ++-
 src/interfaces/libpq/fe-trace.c               |  55 +-
 src/interfaces/libpq/libpq-fe.h               |  20 +
 src/interfaces/libpq/libpq-int.h              |  36 +
 src/interfaces/libpq/meson.build              |   2 +
 src/interfaces/libpq/nls.mk                   |   2 +-
 src/interfaces/libpq/t/003_encrypt.pl         |  70 ++
 src/interfaces/libpq/test/.gitignore          |   1 +
 src/interfaces/libpq/test/Makefile            |   7 +
 src/interfaces/libpq/test/meson.build         |  23 +
 src/test/Makefile                             |   4 +-
 src/test/column_encryption/.gitignore         |   3 +
 src/test/column_encryption/Makefile           |  31 +
 src/test/column_encryption/meson.build        |  24 +
 .../t/001_column_encryption.pl                | 255 ++++++
 .../column_encryption/t/002_cmk_rotation.pl   | 112 +++
 src/test/column_encryption/test_client.c      | 161 ++++
 .../column_encryption/test_run_decrypt.pl     |  58 ++
 src/test/meson.build                          |   1 +
 .../regress/expected/column_encryption.out    | 322 +++++++
 src/test/regress/expected/object_address.out  |  37 +-
 src/test/regress/expected/oidjoins.out        |   8 +
 src/test/regress/expected/opr_sanity.out      |  12 +-
 src/test/regress/expected/type_sanity.out     |   6 +-
 src/test/regress/parallel_schedule            |   2 +-
 src/test/regress/pg_regress_main.c            |   2 +-
 src/test/regress/sql/column_encryption.sql    | 232 +++++
 src/test/regress/sql/object_address.sql       |  13 +-
 src/test/regress/sql/type_sanity.sql          |   2 +
 138 files changed, 9319 insertions(+), 81 deletions(-)
 create mode 100644 doc/src/sgml/ref/alter_column_encryption_key.sgml
 create mode 100644 doc/src/sgml/ref/alter_column_master_key.sgml
 create mode 100644 doc/src/sgml/ref/create_column_encryption_key.sgml
 create mode 100644 doc/src/sgml/ref/create_column_master_key.sgml
 create mode 100644 doc/src/sgml/ref/drop_column_encryption_key.sgml
 create mode 100644 doc/src/sgml/ref/drop_column_master_key.sgml
 create mode 100644 src/backend/commands/colenccmds.c
 create mode 100644 src/common/colenc.c
 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/include/commands/colenccmds.h
 create mode 100644 src/include/common/colenc.h
 create mode 100644 src/interfaces/libpq/fe-encrypt-openssl.c
 create mode 100644 src/interfaces/libpq/fe-encrypt.h
 create mode 100644 src/interfaces/libpq/t/003_encrypt.pl
 create mode 100644 src/test/column_encryption/.gitignore
 create mode 100644 src/test/column_encryption/Makefile
 create mode 100644 src/test/column_encryption/meson.build
 create mode 100644 src/test/column_encryption/t/001_column_encryption.pl
 create mode 100644 src/test/column_encryption/t/002_cmk_rotation.pl
 create mode 100644 src/test/column_encryption/test_client.c
 create mode 100755 src/test/column_encryption/test_run_decrypt.pl
 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/acronyms.sgml b/doc/src/sgml/acronyms.sgml
index 2df6559acc..bd1a0185ed 100644
--- a/doc/src/sgml/acronyms.sgml
+++ b/doc/src/sgml/acronyms.sgml
@@ -56,6 +56,15 @@ <title>Acronyms</title>
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><acronym>CEK</acronym></term>
+    <listitem>
+     <para>
+      Column Encryption Key
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term><acronym>CIDR</acronym></term>
     <listitem>
@@ -67,6 +76,15 @@ <title>Acronyms</title>
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><acronym>CMK</acronym></term>
+    <listitem>
+     <para>
+      Column Master Key
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term><acronym>CPAN</acronym></term>
     <listitem>
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index c1e4048054..1b8e1326c7 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -105,6 +105,21 @@ <title>System Catalogs</title>
       <entry>collations (locale information)</entry>
      </row>
 
+     <row>
+      <entry><link linkend="catalog-pg-colenckey"><structname>pg_colenckey</structname></link></entry>
+      <entry>column encryption keys</entry>
+     </row>
+
+     <row>
+      <entry><link linkend="catalog-pg-colenckeydata"><structname>pg_colenckeydata</structname></link></entry>
+      <entry>column encryption key data</entry>
+     </row>
+
+     <row>
+      <entry><link linkend="catalog-pg-colmasterkey"><structname>pg_colmasterkey</structname></link></entry>
+      <entry>column master keys</entry>
+     </row>
+
      <row>
       <entry><link linkend="catalog-pg-constraint"><structname>pg_constraint</structname></link></entry>
       <entry>check constraints, unique constraints, primary key constraints, foreign key constraints</entry>
@@ -1360,6 +1375,40 @@ <title><structname>pg_attribute</structname> Columns</title>
       </para></entry>
      </row>
 
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>attcek</structfield> <type>oid</type>
+       (references <link linkend="catalog-pg-colenckey"><structname>pg_colenckey</structname></link>.<structfield>oid</structfield>)
+      </para>
+      <para>
+       If the column is encrypted, a reference to the column encryption key, else 0.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>attrealtypid</structfield> <type>oid</type>
+       (references <link linkend="catalog-pg-type"><structname>pg_type</structname></link>.<structfield>oid</structfield>)
+      </para>
+      <para>
+       If the column is encrypted, then this column indicates the type of the
+       encrypted data that is reported to the client.  For encrypted columns,
+       the field <structfield>atttypid</structfield> is either
+       <type>pg_encrypted_det</type> or <type>pg_encrypted_rnd</type>.  If the
+       column is not encrypted, then 0.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>attencalg</structfield> <type>int4</type>
+      </para>
+      <para>
+       If the column is encrypted, the identifier of the encryption algorithm;
+       see <xref linkend="protocol-cek"/> for possible values.
+       </para></entry>
+     </row>
+
      <row>
       <entry role="catalog_table_entry"><para role="column_definition">
        <structfield>attinhcount</structfield> <type>int4</type>
@@ -2467,6 +2516,252 @@ <title><structname>pg_collation</structname> Columns</title>
   </para>
  </sect1>
 
+ <sect1 id="catalog-pg-colenckey">
+  <title><structname>pg_colenckey</structname></title>
+
+  <indexterm zone="catalog-pg-colenckey">
+   <primary>pg_colenckey</primary>
+  </indexterm>
+
+  <para>
+   The catalog <structname>pg_colenckey</structname> contains information
+   about the column encryption keys in the database.  The actual key material
+   of the column encryption keys is in the catalog <link
+   linkend="catalog-pg-colenckeydata"><structname>pg_colenckeydata</structname></link>.
+  </para>
+
+  <table>
+   <title><structname>pg_colenckey</structname> Columns</title>
+   <tgroup cols="1">
+    <thead>
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       Column Type
+      </para>
+      <para>
+       Description
+      </para></entry>
+     </row>
+    </thead>
+
+    <tbody>
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>oid</structfield> <type>oid</type>
+      </para>
+      <para>
+       Row identifier
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>cekname</structfield> <type>name</type>
+      </para>
+      <para>
+       Column encryption key name
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>ceknamespace</structfield> <type>oid</type>
+       (references <link linkend="catalog-pg-namespace"><structname>pg_namespace</structname></link>.<structfield>oid</structfield>)
+      </para>
+      <para>
+       The OID of the namespace that contains this column encryption key
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>cekowner</structfield> <type>oid</type>
+       (references <link linkend="catalog-pg-authid"><structname>pg_authid</structname></link>.<structfield>oid</structfield>)
+      </para>
+      <para>
+       Owner of the column encryption key
+      </para></entry>
+     </row>
+    </tbody>
+   </tgroup>
+  </table>
+ </sect1>
+
+ <sect1 id="catalog-pg-colenckeydata">
+  <title><structname>pg_colenckeydata</structname></title>
+
+  <indexterm zone="catalog-pg-colenckeydata">
+   <primary>pg_colenckeydata</primary>
+  </indexterm>
+
+  <para>
+   The catalog <structname>pg_colenckeydata</structname> 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, <literal>(ckdcekid, ckdcmkid)</literal> is a
+   unique key of this table.
+  </para>
+
+  <para>
+   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.
+  </para>
+
+  <table>
+   <title><structname>pg_colenckeydata</structname> Columns</title>
+   <tgroup cols="1">
+    <thead>
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       Column Type
+      </para>
+      <para>
+       Description
+      </para></entry>
+     </row>
+    </thead>
+
+    <tbody>
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>oid</structfield> <type>oid</type>
+      </para>
+      <para>
+       Row identifier
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>ckdcekid</structfield> <type>oid</type>
+       (references <link linkend="catalog-pg-colenckey"><structname>pg_colenckey</structname></link>.<structfield>oid</structfield>)
+      </para>
+      <para>
+       The column encryption key this entry belongs to
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>ckdcmkid</structfield> <type>oid</type>
+       (references <link linkend="catalog-pg-colmasterkey"><structname>pg_colmasterkey</structname></link>.<structfield>oid</structfield>)
+      </para>
+      <para>
+       The column master key that the key material is encrypted with
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>ckdcmkalg</structfield> <type>int4</type>
+      </para>
+      <para>
+       The encryption algorithm used for encrypting the key material; see
+       <xref linkend="protocol-cmk"/> for possible values.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>ckdencval</structfield> <type>bytea</type>
+      </para>
+      <para>
+       The key material of this column encryption key, encrypted using the
+       referenced column master key
+      </para></entry>
+     </row>
+    </tbody>
+   </tgroup>
+  </table>
+ </sect1>
+
+ <sect1 id="catalog-pg-colmasterkey">
+  <title><structname>pg_colmasterkey</structname></title>
+
+  <indexterm zone="catalog-pg-colmasterkey">
+   <primary>pg_colmasterkey</primary>
+  </indexterm>
+
+  <para>
+   The catalog <structname>pg_colmasterkey</structname> 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.
+  </para>
+
+  <table>
+   <title><structname>pg_colmasterkey</structname> Columns</title>
+   <tgroup cols="1">
+    <thead>
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       Column Type
+      </para>
+      <para>
+       Description
+      </para></entry>
+     </row>
+    </thead>
+
+    <tbody>
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>oid</structfield> <type>oid</type>
+      </para>
+      <para>
+       Row identifier
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>cmkname</structfield> <type>name</type>
+      </para>
+      <para>
+       Column master key name
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>cmknamespace</structfield> <type>oid</type>
+       (references <link linkend="catalog-pg-namespace"><structname>pg_namespace</structname></link>.<structfield>oid</structfield>)
+      </para>
+      <para>
+       The OID of the namespace that contains this column master key
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>cmkowner</structfield> <type>oid</type>
+       (references <link linkend="catalog-pg-authid"><structname>pg_authid</structname></link>.<structfield>oid</structfield>)
+      </para>
+      <para>
+       Owner of the column master key
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>cmkrealm</structfield> <type>text</type>
+      </para>
+      <para>
+       A <quote>realm</quote> associated with this column master key.  This is
+       a freely chosen string that is used by clients to determine how to look
+       up the key.  A typical configuration would put all CMKs that are looked
+       up in the same way into the same realm.
+      </para></entry>
+     </row>
+    </tbody>
+   </tgroup>
+  </table>
+ </sect1>
+
  <sect1 id="catalog-pg-constraint">
   <title><structname>pg_constraint</structname></title>
 
diff --git a/doc/src/sgml/charset.sgml b/doc/src/sgml/charset.sgml
index 3032392b80..e3d21e66f8 100644
--- a/doc/src/sgml/charset.sgml
+++ b/doc/src/sgml/charset.sgml
@@ -1721,6 +1721,15 @@ <title>Automatic Character Set Conversion Between Server and Client</title>
      Just as for the server, use of <literal>SQL_ASCII</literal> is unwise
      unless you are working with all-ASCII data.
     </para>
+
+    <para>
+     When transparent column encryption is used, then no encoding conversion
+     is possible.  (The encoding conversion happens on the server, and the
+     server cannot look inside any encrypted column values.)  If column
+     encryption is enabled for a session, then the server enforces that the
+     client encoding matches the server encoding, and any attempts to change
+     the client encoding will be rejected by the server.
+    </para>
    </sect2>
 
    <sect2 id="multibyte-conversions-supported">
diff --git a/doc/src/sgml/datatype.sgml b/doc/src/sgml/datatype.sgml
index 467b49b199..7494ec85d5 100644
--- a/doc/src/sgml/datatype.sgml
+++ b/doc/src/sgml/datatype.sgml
@@ -5360,4 +5360,59 @@ <title>Pseudo-Types</title>
 
   </sect1>
 
+  <sect1 id="datatype-encrypted">
+   <title>Types Related to Encryption</title>
+
+   <para>
+    An encrypted column value (see <xref linkend="ddl-column-encryption"/>) is
+    internally stored using the types
+    <type>pg_encrypted_rnd</type> (for randomized encryption) or
+    <type>pg_encrypted_det</type> (for deterministic encryption); see <xref
+    linkend="datatype-encrypted-table"/>.  Most of the database system treats
+    these as normal types.  For example, the type <type>pg_encrypted_det</type> has
+    an equals operator that allows lookup of encrypted values.  It is,
+    however, not allowed to create a table using one of these types directly
+    as a column type.
+   </para>
+
+   <para>
+    The external representation of these types is the string
+    <literal>encrypted$</literal> followed by hexadecimal byte values, for
+    example
+    <literal>encrypted$3aacd063d2d3a1a04119df76874e0b9785ea466177f18fe9c0a1a313eaf09c98</literal>.
+    Clients that don't support transparent column encryption or have disabled
+    it will see the encrypted values in this format.  Clients that support
+    transparent data encryption will not see these types in result sets, as
+    the protocol layer will translate them back to the declared underlying type in
+    the table definition.
+   </para>
+
+    <table id="datatype-encrypted-table">
+     <title>Types Related to Encryption</title>
+     <tgroup cols="3">
+      <colspec colname="col1" colwidth="1*"/>
+      <colspec colname="col2" colwidth="3*"/>
+      <colspec colname="col3" colwidth="2*"/>
+      <thead>
+       <row>
+        <entry>Name</entry>
+        <entry>Storage Size</entry>
+        <entry>Description</entry>
+       </row>
+      </thead>
+      <tbody>
+       <row>
+        <entry><type>pg_encrypted_det</type></entry>
+        <entry>1 or 4 bytes plus the actual binary string</entry>
+        <entry>encrypted column value, deterministic encryption</entry>
+       </row>
+       <row>
+        <entry><type>pg_encrypted_rnd</type></entry>
+        <entry>1 or 4 bytes plus the actual binary string</entry>
+        <entry>encrypted column value, randomized encryption</entry>
+       </row>
+      </tbody>
+     </tgroup>
+    </table>
+  </sect1>
  </chapter>
diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml
index 8dc8d7a0ce..fb3c19b01d 100644
--- a/doc/src/sgml/ddl.sgml
+++ b/doc/src/sgml/ddl.sgml
@@ -1238,6 +1238,422 @@ <title>Exclusion Constraints</title>
   </sect2>
  </sect1>
 
+ <sect1 id="ddl-column-encryption">
+  <title>Transparent Column Encryption</title>
+
+  <para>
+   With <firstterm>transparent column encryption</firstterm>, 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.
+  </para>
+
+  <sect2>
+   <title>Using Transparent Column Encryption</title>
+
+  <para>
+   Transparent column encryption uses two levels of cryptographic keys.  The
+   actual column value is encrypted using a symmetric algorithm, such as AES,
+   using a <firstterm>column encryption key</firstterm>
+   (<acronym>CEK</acronym>).  The column encryption key is in turn encrypted
+   using an asymmetric algorithm, such as RSA, using a <firstterm>column
+   master key</firstterm> (<acronym>CMK</acronym>).  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.
+  </para>
+
+  <para>
+   Here is an example declaring a column as encrypted:
+<programlisting>
+CREATE TABLE customers (
+    id int PRIMARY KEY,
+    name text NOT NULL,
+    ...
+    creditcard_num text <emphasis>ENCRYPTED WITH (column_encryption_key = cek1)</emphasis>
+);
+</programlisting>
+  </para>
+
+  <para>
+   Column encryption supports <firstterm>randomized</firstterm>
+   (also known as <firstterm>probabilistic</firstterm>) and
+   <firstterm>deterministic</firstterm> 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 from making
+   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:
+<programlisting>
+CREATE TABLE employees (
+    id int PRIMARY KEY,
+    name text NOT NULL,
+    ...
+    ssn text ENCRYPTED WITH (
+        column_encryption_key = cek1, <emphasis>encryption_type = deterministic</emphasis>)
+);
+</programlisting>
+  </para>
+
+  <para>
+   Null values are not encrypted by transparent column encryption; null values
+   sent by the client are visible as null values in the database.  If the fact
+   that a value is null needs to be hidden from the server, this information
+   needs to be encoded into a nonnull value in the client somehow.
+  </para>
+  </sect2>
+
+  <sect2>
+   <title>Reading and Writing Encrypted Columns</title>
+
+   <para>
+    Reading and writing encrypted columns is meant to be handled automatically
+    by the client library/driver and should be mostly transparent to the
+    application code, if certain prerequisites are fulfilled:
+
+    <itemizedlist>
+     <listitem>
+      <para>
+       The client library needs to support transparent column encryption.  Not
+       all client libraries do.  Furthermore, the client library might require
+       that transparent column encryption is explicitly enabled at connection
+       time.  See the documentation of the client library for details.
+      </para>
+     </listitem>
+
+     <listitem>
+      <para>
+       Column master keys and column encryption keys have been set up, and the
+       client library has been configured to be able to look up column master
+       keys from the key store or key management system.
+      </para>
+     </listitem>
+    </itemizedlist>
+   </para>
+
+   <para>
+    Reading from encrypted columns will then work automatically.  For example,
+    using the above example,
+<programlisting>
+SELECT ssn FROM employees WHERE id = 5;
+</programlisting>
+    will return the unencrypted value for the <literal>ssn</literal> column in
+    any rows found.
+   </para>
+
+   <para>
+    Writing to encrypted columns requires that the extended query protocol
+    (protocol-level prepared statements) be used, so that the values to be
+    encrypted are supplied separately from the SQL command.  For example,
+    using, say, psql or libpq, the following would not work:
+<programlisting>
+-- WRONG!
+INSERT INTO ssn (id, name, ssn) VALUES (1, 'Someone', '12345');
+</programlisting>
+    This will leak the unencrypted value <literal>12345</literal> to the
+    server, thus defeating the point of column encryption.
+    Note that using server-side prepared statements using the SQL commands
+    <command>PREPARE</command> and <command>EXECUTE</command> is equally
+    incorrect, since that would also leak the parameters provided to
+    <command>EXECUTE</command> to the server.
+   </para>
+
+   <para>
+    This shows a correct invocation in libpq (without error checking):
+<programlisting>
+PGresult   *res;
+const char *values[] = {"1", "Someone", "12345"};
+
+res = PQexecParams(conn, "INSERT INTO employees (id, name, ssn) VALUES ($1, $2, $3)",
+                   3, NULL, values, NULL, NULL, 0);
+</programlisting>
+    Higher-level client libraries might use the protocol-level prepared
+    statements automatically and thus won't require any code changes.
+   </para>
+
+   <para>
+    <application>psql</application> provides the command
+    <literal>\bind</literal> to run statements with parameters like this:
+<programlisting>
+INSERT INTO employees (id, name, ssn) VALUES ($1, $2, $3) \bind '1' 'Someone', '12345' \g
+</programlisting>
+   </para>
+
+   <para>
+    Similarly, if deterministic encryption is used, parameters need to be used
+    in search conditions using encrypted columns:
+<programlisting>
+SELECT * FROM employees WHERE ssn = $1 \bind '12345' \g
+</programlisting>
+   </para>
+  </sect2>
+
+  <sect2>
+   <title>Setting up Transparent Column Encryption</title>
+
+  <para>
+   The steps to set up transparent column encryption for a database are:
+
+   <orderedlist>
+    <listitem>
+     <para>
+      Create the key material for the CMK, for example, using a cryptographic
+      library or toolkit, or a key management system.  Secure access to the
+      key as appropriate, using access control, passwords, etc.
+     </para>
+    </listitem>
+
+    <listitem>
+     <para>
+      Register the CMK in the database using the SQL command <xref
+      linkend="sql-create-column-master-key"/>.
+     </para>
+    </listitem>
+
+    <listitem>
+     <para>
+      Create the (unencrypted) key material for the CEK in a temporary
+      location.  (It will be encrypted in the next step.  Depending on the
+      available tools, it might be possible and sensible to combine these two
+      steps.)
+     </para>
+    </listitem>
+
+    <listitem>
+     <para>
+      Encrypt the created CEK key material using the CMK (created earlier).
+      (The unencrypted version of the CEK key material can now be disposed
+      of.)
+     </para>
+    </listitem>
+
+    <listitem>
+     <para>
+      Register the CEK in the database using the SQL command <xref
+      linkend="sql-create-column-encryption-key"/>.  This command
+      <quote>uploads</quote> the encrypted CEK key material created in the
+      previous step to the database server.  The local copy of the CEK key
+      material can then be removed.
+     </para>
+    </listitem>
+
+    <listitem>
+     <para>
+      Create encrypted columns using the created CEK.
+     </para>
+    </listitem>
+
+    <listitem>
+     <para>
+      Configure the client library/driver to be able to look up the CMK
+      created earlier.
+     </para>
+    </listitem>
+   </orderedlist>
+
+   Once this is done, values can be written to and read from the encrypted
+   columns in a transparent way.
+  </para>
+
+  <para>
+   Note that these steps should not be run on the database server, but on some
+   client machine.  Neither the CMK nor the unencrypted CEK should ever appear
+   on the database server host.
+  </para>
+
+  <para>
+   The specific details of this setup depend on the desired CMK storage
+   mechanism/key management system as well as the client libraries to be used.
+   The following example uses the <command>openssl</command> command-line tool
+   to set up the keys.
+
+   <orderedlist>
+    <listitem>
+     <para>
+      Create the key material for the CMK and write it to a file:
+<programlisting>
+openssl genpkey -algorithm rsa -out cmk1.pem
+</programlisting>
+     </para>
+    </listitem>
+
+    <listitem>
+     <para>
+      Register the CMK in the database:
+<programlisting>
+psql ... -c "CREATE COLUMN MASTER KEY cmk1"
+</programlisting>
+     </para>
+    </listitem>
+
+    <listitem>
+     <para>
+      Create the unencrypted CEK key material in a file:
+<programlisting>
+openssl rand -out cek1.bin 48
+</programlisting>
+      (See <xref linkend="protocol-cek-table"/> for required key lengths.)
+     </para>
+    </listitem>
+
+    <listitem>
+     <para>
+      Encrypt the created CEK key material:
+<programlisting>
+openssl pkeyutl -encrypt -inkey cmk1.pem -pkeyopt rsa_padding_mode:oaep -in cek1.bin -out cek1.bin.enc
+rm cek1.bin
+</programlisting>
+     </para>
+    </listitem>
+
+    <listitem>
+     <para>
+      Register the CEK in the database:
+<programlisting>
+# convert file contents to hex encoding; this is just one possible way
+cekenchex=$(perl -0ne 'print unpack "H*"' cek1.bin.enc)
+psql ... -c "CREATE COLUMN ENCRYPTION KEY cek1 WITH VALUES (column_master_key = cmk1, encrypted_value = '\\x${cekenchex}')"
+rm cek1.bin.enc
+</programlisting>
+     </para>
+    </listitem>
+
+    <listitem>
+     <para>
+      Create encrypted columns as shown in the examples above.
+     </para>
+    </listitem>
+
+    <listitem>
+     <para>
+      Configure the libpq for CMK lookup (see also <xref linkend="libpq-connect-cmklookup"/>):
+<programlisting>
+PGCMKLOOKUP="*=file:$PWD/%k.pem"
+export PGCMKLOOKUP
+</programlisting>
+     </para>
+
+     <para>
+      Additionally, libpq requires that the connection parameter <xref
+      linkend="libpq-connect-column-encryption"/> be set in order to activate
+      the transparent column encryption functionality.  This should be done in
+      the connection parameters of the application, but an environment
+      variable (<envar>PGCOLUMNENCRYPTION</envar>) is also available.
+     </para>
+    </listitem>
+   </orderedlist>
+  </para>
+  </sect2>
+
+  <sect2>
+   <title>Guidance on Using Transparent Column Encryption</title>
+
+   <para>
+    This section contains some information on when it is or is not appropriate
+    to use transparent column encryption, and what precautions need to be
+    taken to maintain its security.
+   </para>
+
+   <para>
+    In general, column encryption is never a replacement for additional
+    security and encryption techniques such as transport encryption
+    (SSL/TLS), storage encryption, strong access control, and password
+    security.  Column encryption only targets specific use cases and should be
+    used in conjunction with additional security measures.
+   </para>
+
+   <para>
+    A typical use case for column encryption is to encrypt specific values
+    with additional security requirements, for example credit card numbers.
+    This would allow you to store that security-sensitive data together with
+    the rest of your data (thus getting various benefits, such as referential
+    integrity, consistent backups), while giving access to that data only to
+    specific clients and preventing accidental leakage on the server side
+    (server logs, file system backups, etc.).
+   </para>
+
+   <para>
+    When using parameters to provide values to insert or search by, care must
+    be taken that values meant to be encrypted are not accidentally leaked to
+    the server.  The server will tell the client which parameters to encrypt,
+    based on the schema definition on the server.  But if the query or client
+    application is faulty, values meant to be encrypted might accidentally be
+    associated with parameters that the server does not think need to be
+    encrypted.  Additional robustness can be achieved by forcing encryption of
+    certain parameters in the client library (see its documentation).
+   </para>
+
+   <para>
+    Column encryption cannot hide the existence or absence of data, it can
+    only disguise the particular data that is known to exist.  For example,
+    storing a cleartext person name and an encrypted credit card number
+    indicates that the person has a credit card.  That might not reveal too
+    much if the database is for an online store and there is other data nearby
+    that shows that the person has recently made purchases.  But in another
+    example, storing a cleartext person name and an encrypted diagnosis in a
+    medical database probably indicates that the person has a medical issue.
+    Depending on the circumstances, that might not by itself be sufficient
+    security.
+   </para>
+
+   <para>
+    Encryption cannot completely hide the length of values.  The encryption
+    methods will pad values to multiples of the underlying cipher's block size
+    (usually 16 bytes), so some length differences will be unified this way.
+    There is no concern if all values are of the same length, but if there are
+    signficant length differences between valid values and that length
+    information is security-sensitive, then application-specific workarounds
+    such as padding would need to be applied.  How to do that securely is
+    beyond the scope of this manual.  Note that column encryption is applied
+    to the text representation of the stored value, so length differences can
+    be leaked even for fixed-length column types (e.g.  <type>bigint</type>,
+    whose largest decimal representation is longer than 16 bytes).
+   </para>
+
+   <para>
+    Column encryption provides only partial protection against a malicious
+    user with write access to the table.  Once encrypted, any modifications to
+    a stored value on the server side will cause a decryption failure on the
+    client.  However, a user with write access can still freely swap encrypted
+    values between rows or columns (or even separate database clusters) as
+    long as they were encrypted with the same key.  Attackers can also remove
+    values by replacing them with nulls, and users with ownership over the
+    table schema can replace encryption keys or strip encryption from the
+    columns entirely.  All of this is to say: Proper access control is still
+    of vital importance when using this feature.
+   </para>
+
+   <para>
+    When using asymmetric CMK algorithms to encrypt CEKs, the
+    <quote>public</quote> half of the CMK can be used to replace existing
+    column encryption keys with keys of an attacker's choosing, compromising
+    confidentiality and authenticity for values encrypted under that CMK.  For
+    this reason, it's important to keep both the private
+    <emphasis>and</emphasis> public halves of the CMK key pair confidential.
+   </para>
+
+   <note>
+    <para>
+     Storing data such credit card data, medical data, and so on is usually
+     subject to government or industry regulations.  This section is not meant
+     to provide complete instructions on how to do this correctly.  Please
+     seek additional advice when engaging in such projects.
+    </para>
+   </note>
+  </sect2>
+ </sect1>
+
  <sect1 id="ddl-system-columns">
   <title>System Columns</title>
 
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index e09e289a43..85b5cb1450 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -23335,6 +23335,32 @@ <title>Schema Visibility Inquiry Functions</title>
      </thead>
 
      <tbody>
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_cek_is_visible</primary>
+        </indexterm>
+        <function>pg_cek_is_visible</function> ( <parameter>cek</parameter> <type>oid</type> )
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is column encryption key visible in search path?
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_cmk_is_visible</primary>
+        </indexterm>
+        <function>pg_cmk_is_visible</function> ( <parameter>cmk</parameter> <type>oid</type> )
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is column master key visible in search path?
+       </para></entry>
+      </row>
+
       <row>
        <entry role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/doc/src/sgml/glossary.sgml b/doc/src/sgml/glossary.sgml
index 7c01a541fe..a9eb49b15f 100644
--- a/doc/src/sgml/glossary.sgml
+++ b/doc/src/sgml/glossary.sgml
@@ -389,6 +389,29 @@ <title>Glossary</title>
    </glossdef>
   </glossentry>
 
+  <glossentry id="glossary-column-encryption-key">
+   <glossterm>Column encryption key</glossterm>
+   <glossdef>
+    <para>
+     A cryptographic key used to encrypt column values when using transparent
+     column encryption.  Column encryption keys are stored in the database
+     encrypted by another key, the column master key.
+    </para>
+   </glossdef>
+  </glossentry>
+
+  <glossentry id="glossary-column-master-key">
+   <glossterm>Column master key</glossterm>
+   <glossdef>
+    <para>
+     A cryptographic key used to encrypt column encryption keys.  (So the
+     column master key is a <firstterm>key encryption key</firstterm>.)
+     Column master keys are stored outside the database system, for example in
+     a key management system.
+    </para>
+   </glossdef>
+  </glossentry>
+
   <glossentry id="glossary-commit">
    <glossterm>Commit</glossterm>
    <glossdef>
diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index 0e7ae70c70..a34d694c2a 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -1964,6 +1964,141 @@ <title>Parameter Key Words</title>
        </para>
       </listitem>
      </varlistentry>
+
+     <varlistentry id="libpq-connect-column-encryption" xreflabel="column_encryption">
+      <term><literal>column_encryption</literal></term>
+      <listitem>
+       <para>
+        If set to <literal>on</literal>, <literal>true</literal>, or
+        <literal>1</literal>, this enables transparent column encryption for
+        the connection.  If encrypted columns are queried and this is not
+        enabled, the encrypted value is returned.  See <xref
+        linkend="ddl-column-encryption"/> for more information about this
+        feature.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry id="libpq-connect-cmklookup" xreflabel="cmklookup">
+      <term><literal>cmklookup</literal></term>
+      <listitem>
+       <para>
+        This specifies how libpq should look up column master keys (CMKs) in
+        order to decrypt the column encryption keys (CEKs).
+        The value is a list of <literal>key=value</literal> entries separated
+        by semicolons.  Each key is the name of a key realm, or
+        <literal>*</literal> to match all realms.  The value is a
+        <literal>scheme:data</literal> specification.  The scheme specifies
+        the method to look up the key, the remaining data is specific to the
+        scheme.  Placeholders are replaced in the remaining data as follows:
+
+        <variablelist>
+         <varlistentry>
+          <term><literal>%a</literal></term>
+          <listitem>
+           <para>
+            The CMK algorithm name (see <xref linkend="protocol-cmk-table"/>)
+           </para>
+          </listitem>
+         </varlistentry>
+
+         <varlistentry>
+          <term><literal>%j</literal></term>
+          <listitem>
+           <para>
+            The CMK algorithm name in JSON Web Algorithms format (see <xref
+            linkend="protocol-cmk-table"/>).  This is useful for interfacing
+            with some key management systems that use these names.
+           </para>
+          </listitem>
+         </varlistentry>
+
+         <varlistentry>
+          <term><literal>%k</literal></term>
+          <listitem>
+           <para>
+            The CMK key name
+           </para>
+          </listitem>
+         </varlistentry>
+
+         <varlistentry>
+          <term><literal>%p</literal></term>
+          <listitem>
+           <para>
+            The name of a temporary file with the encrypted CEK data (only for
+            the <literal>run</literal> scheme)
+           </para>
+          </listitem>
+         </varlistentry>
+
+         <varlistentry>
+          <term><literal>%r</literal></term>
+          <listitem>
+           <para>
+            The realm name
+           </para>
+          </listitem>
+         </varlistentry>
+        </variablelist>
+       </para>
+
+       <para>
+        Available schemes are:
+        <variablelist>
+         <varlistentry>
+          <term><literal>file</literal></term>
+          <listitem>
+           <para>
+            Load the key material from a file.  The remaining data is the file
+            name.  Use this if the CMKs are kept in a file on the file system.
+           </para>
+
+           <para>
+            The file scheme does not support the CMK algorithm
+            <literal>unspecified</literal>.
+           </para>
+          </listitem>
+         </varlistentry>
+
+         <varlistentry>
+          <term><literal>run</literal></term>
+          <listitem>
+           <para>
+            Run the specified command to decrypt the CEK.  The remaining data
+            is a shell command.  Use this with key management systems that
+            perform the decryption themselves.  The command must print the
+            decrypted plaintext on the standard output.
+           </para>
+          </listitem>
+         </varlistentry>
+        </variablelist>
+       </para>
+
+       <para>
+        The default value is empty.
+       </para>
+
+       <para>
+        Example:
+<programlisting>
+cmklookup="r1=file:/some/where/secrets/%k.pem;*=file:/else/where/%r/%k.pem"
+</programlisting>
+        This specification says, for keys in realm <quote>r1</quote>, load
+        them from the specified file, replacing <literal>%k</literal> by the
+        key name.  For keys in other realms, load them from the file,
+        replacing realm and key names as specified.
+       </para>
+
+       <para>
+        An example for interacting with a (hypothetical) key management
+        system:
+<programlisting>
+cmklookup="*=run:acmekms decrypt --key %k --alg %a --infile '%p'"
+</programlisting>
+       </para>
+      </listitem>
+     </varlistentry>
     </variablelist>
    </para>
   </sect2>
@@ -2864,6 +2999,25 @@ <title>Main Functions</title>
             <filename>src/backend/utils/adt/numeric.c::numeric_send()</filename> and
             <filename>src/backend/utils/adt/numeric.c::numeric_recv()</filename>.
            </para>
+
+           <para>
+            When column encryption is enabled, the second-least-significant
+            byte of this parameter specifies whether encryption should be
+            forced for a parameter.  Set this byte to one to force encryption.
+            For example, use the C code literal <literal>0x10</literal> to
+            specify text format with forced encryption.  If the array pointer
+            is null then encryption is not forced for any parameter.
+           </para>
+
+           <para>
+            If encryption is forced for a parameter but the parameter does not
+            correspond to an encrypted column on the server, then the call
+            will fail and the parameter will not be sent.  This can be used
+            for additional security against a compromised server.  (The
+            drawback is that application code then needs to be kept up to date
+            with knowledge about which columns are encrypted rather than
+            letting the server specify this.)
+           </para>
           </listitem>
          </varlistentry>
 
@@ -2876,6 +3030,13 @@ <title>Main Functions</title>
             to obtain different result columns in different formats,
             although that is possible in the underlying protocol.)
            </para>
+
+           <para>
+            If column encryption is used, then encrypted columns will be
+            returned in text format independent of this setting.  Applications
+            can check the format of each result column with <xref
+            linkend="libpq-PQfformat"/> before accessing it.
+           </para>
           </listitem>
          </varlistentry>
         </variablelist>
@@ -3028,6 +3189,44 @@ <title>Main Functions</title>
       </listitem>
      </varlistentry>
 
+     <varlistentry id="libpq-PQexecPreparedDescribed">
+      <term><function>PQexecPreparedDescribed</function><indexterm><primary>PQexecPreparedDescribed</primary></indexterm></term>
+
+      <listitem>
+       <para>
+        Sends a request to execute a prepared statement with given
+        parameters, and waits for the result, with support for encrypted columns.
+<synopsis>
+PGresult *PQexecPreparedDescribed(PGconn *conn,
+                                  const char *stmtName,
+                                  int nParams,
+                                  const char * const *paramValues,
+                                  const int *paramLengths,
+                                  const int *paramFormats,
+                                  int resultFormat,
+                                  PGresult *paramDesc);
+</synopsis>
+       </para>
+
+       <para>
+        <xref linkend="libpq-PQexecPreparedDescribed"/> is like <xref
+        linkend="libpq-PQexecPrepared"/> with additional support for encrypted
+        columns.  The parameter <parameter>paramDesc</parameter> must be a
+        result set obtained from <xref linkend="libpq-PQdescribePrepared"/> on
+        the same prepared statement.
+       </para>
+
+       <para>
+        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.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry id="libpq-PQdescribePrepared">
       <term><function>PQdescribePrepared</function><indexterm><primary>PQdescribePrepared</primary></indexterm></term>
 
@@ -3878,6 +4077,28 @@ <title>Retrieving Query Result Information</title>
      </listitem>
     </varlistentry>
 
+    <varlistentry id="libpq-PQfisencrypted">
+     <term><function>PQfisencrypted</function><indexterm><primary>PQfisencrypted</primary></indexterm></term>
+
+     <listitem>
+      <para>
+       Returns whether the value for the given column came from an encrypted
+       column.  Column numbers start at 0.
+<synopsis>
+int PQfisencrypted(const PGresult *res,
+                   int column_number);
+</synopsis>
+      </para>
+
+      <para>
+       Encrypted column values are automatically decrypted, so this function
+       is not necessary to access the column value.  It can be used for extra
+       security to check whether the value was stored encrypted when one
+       thought it should be.
+      </para>
+     </listitem>
+    </varlistentry>
+
     <varlistentry id="libpq-PQfsize">
      <term><function>PQfsize</function><indexterm><primary>PQfsize</primary></indexterm></term>
 
@@ -4059,6 +4280,31 @@ <title>Retrieving Query Result Information</title>
      </listitem>
     </varlistentry>
 
+    <varlistentry id="libpq-PQparamisencrypted">
+     <term><function>PQparamisencrypted</function><indexterm><primary>PQparamisencrypted</primary></indexterm></term>
+
+     <listitem>
+      <para>
+       Returns whether the value for the given parameter is destined for an
+       encrypted column.  Parameter numbers start at 0.
+<synopsis>
+int PQparamisencrypted(const PGresult *res, int param_number);
+</synopsis>
+      </para>
+
+      <para>
+       Values for parameters destined for encrypted columns are automatically
+       encrypted, so this function is not necessary to prepare the parameter
+       value.  It can be used for extra security to check whether the value
+       will be stored encrypted when one thought it should be.  (But see also
+       at <xref linkend="libpq-PQexecPreparedDescribed"/> for another way to do that.)
+       This function is only useful when inspecting the result of <xref
+       linkend="libpq-PQdescribePrepared"/>.  For other types of results it
+       will return false.
+      </para>
+     </listitem>
+    </varlistentry>
+
     <varlistentry id="libpq-PQprint">
      <term><function>PQprint</function><indexterm><primary>PQprint</primary></indexterm></term>
 
@@ -4584,6 +4830,7 @@ <title>Asynchronous Command Processing</title>
    <xref linkend="libpq-PQsendQueryParams"/>,
    <xref linkend="libpq-PQsendPrepare"/>,
    <xref linkend="libpq-PQsendQueryPrepared"/>,
+   <xref linkend="libpq-PQsendQueryPreparedDescribed"/>,
    <xref linkend="libpq-PQsendDescribePrepared"/>, and
    <xref linkend="libpq-PQsendDescribePortal"/>,
    which can be used with <xref linkend="libpq-PQgetResult"/> to duplicate
@@ -4591,6 +4838,7 @@ <title>Asynchronous Command Processing</title>
    <xref linkend="libpq-PQexecParams"/>,
    <xref linkend="libpq-PQprepare"/>,
    <xref linkend="libpq-PQexecPrepared"/>,
+   <xref linkend="libpq-PQexecPreparedDescribed"/>,
    <xref linkend="libpq-PQdescribePrepared"/>, and
    <xref linkend="libpq-PQdescribePortal"/>
    respectively.
@@ -4647,6 +4895,13 @@ <title>Asynchronous Command Processing</title>
        <xref linkend="libpq-PQexecParams"/>, it allows only one command in the
        query string.
       </para>
+
+      <para>
+       If column encryption is enabled, then this function is not
+       asynchronous.  To get asynchronous behavior, <xref
+       linkend="libpq-PQsendPrepare"/> followed by <xref
+       linkend="libpq-PQsendQueryPreparedDescribed"/> should be called individually.
+      </para>
      </listitem>
     </varlistentry>
 
@@ -4701,6 +4956,45 @@ <title>Asynchronous Command Processing</title>
      </listitem>
     </varlistentry>
 
+    <varlistentry id="libpq-PQsendQueryPreparedDescribed">
+     <term><function>PQsendQueryPreparedDescribed</function><indexterm><primary>PQsendQueryPreparedDescribed</primary></indexterm></term>
+
+     <listitem>
+      <para>
+       Sends a request to execute a prepared statement with given
+       parameters, without waiting for the result(s), with support for encrypted columns.
+<synopsis>
+int PQsendQueryPreparedDescribed(PGconn *conn,
+                                 const char *stmtName,
+                                 int nParams,
+                                 const char * const *paramValues,
+                                 const int *paramLengths,
+                                 const int *paramFormats,
+                                 int resultFormat,
+                                 PGresult *paramDesc);
+</synopsis>
+      </para>
+
+      <para>
+       <xref linkend="libpq-PQsendQueryPreparedDescribed"/> is like <xref
+       linkend="libpq-PQsendQueryPrepared"/> with additional support for encrypted
+       columns.  The parameter <parameter>paramDesc</parameter> must be a
+       result set obtained from <xref linkend="libpq-PQsendDescribePrepared"/> on
+       the same prepared statement.
+      </para>
+
+      <para>
+       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.
+       See also under <xref linkend="libpq-PQexecPreparedDescribed"/>.
+      </para>
+     </listitem>
+    </varlistentry>
+
     <varlistentry id="libpq-PQsendDescribePrepared">
      <term><function>PQsendDescribePrepared</function><indexterm><primary>PQsendDescribePrepared</primary></indexterm></term>
 
@@ -4751,6 +5045,7 @@ <title>Asynchronous Command Processing</title>
        <xref linkend="libpq-PQsendQueryParams"/>,
        <xref linkend="libpq-PQsendPrepare"/>,
        <xref linkend="libpq-PQsendQueryPrepared"/>,
+       <xref linkend="libpq-PQsendQueryPreparedDescribed"/>,
        <xref linkend="libpq-PQsendDescribePrepared"/>,
        <xref linkend="libpq-PQsendDescribePortal"/>, or
        <xref linkend="libpq-PQpipelineSync"/>
@@ -7784,6 +8079,26 @@ <title>Environment Variables</title>
      </para>
     </listitem>
 
+    <listitem>
+     <para>
+      <indexterm>
+       <primary><envar>PGCMKLOOKUP</envar></primary>
+      </indexterm>
+      <envar>PGCMKLOOKUP</envar> behaves the same as the <xref
+      linkend="libpq-connect-cmklookup"/> connection parameter.
+     </para>
+    </listitem>
+
+    <listitem>
+     <para>
+      <indexterm>
+       <primary><envar>PGCOLUMNENCRYPTION</envar></primary>
+      </indexterm>
+      <envar>PGCOLUMNENCRYPTION</envar> behaves the same as the <xref
+      linkend="libpq-connect-column-encryption"/> connection parameter.
+     </para>
+    </listitem>
+
     <listitem>
      <para>
       <indexterm>
diff --git a/doc/src/sgml/protocol.sgml b/doc/src/sgml/protocol.sgml
index 93fc7167d4..c888811ad0 100644
--- a/doc/src/sgml/protocol.sgml
+++ b/doc/src/sgml/protocol.sgml
@@ -1109,6 +1109,75 @@ <title>Pipelining</title>
    </para>
   </sect2>
 
+  <sect2 id="protocol-flow-transparent-column-encryption">
+   <title>Transparent Column Encryption</title>
+
+   <para>
+    Transparent column encryption is enabled by sending the parameter
+    <literal>_pq_.column_encryption</literal> with a value of
+    <literal>1</literal> in the StartupMessage.  This is a protocol extension
+    that enables a few additional protocol messages and adds additional fields
+    to existing protocol messages.  Client drivers should only activate this
+    protocol extension when requested by the user, for example through a
+    connection parameter.
+   </para>
+
+   <para>
+    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 an ID
+    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.)
+   </para>
+
+   <para>
+    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.
+   </para>
+
+   <para>
+    When column encryption is used, format specifications (text/binary) in the
+    various protocol messages apply to the ciphertext.  The plaintext inside
+    the ciphertext is always in text format, but this is invisible to the
+    protocol.  Even though the ciphertext could in theory be sent in either
+    text or binary format, the server will always send it in binary if the
+    column encryption protocol option is enabled.  That way, a client library
+    only needs to support decrypting data sent in binary and does not have to
+    support decoding the text format of the encryption-related types (see
+    <xref linkend="datatype-encrypted"/>).
+   </para>
+
+   <para>
+    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 <literal>100</literal> and
+    an integer represented by the string <literal>+100</literal> would result
+    in two different ciphertexts, thus defeating the main point of
+    deterministic encryption.  This protocol specification requires the
+    plaintext to be in <quote>canonical</quote> form, which is the form that
+    is produced by the server when it outputs a particular value in text
+    format.
+   </para>
+
+   <para>
+    When transparent column encryption is enabled, the client encoding must
+    match the server encoding.  This ensures that all values encrypted or
+    decrypted by the client match the server encoding.
+   </para>
+
+   <para>
+    The cryptographic operations used for transparent column encryption are
+    described in <xref linkend="protocol-transparent-column-encryption-crypto"/>.
+   </para>
+  </sect2>
+
   <sect2 id="protocol-flow-function-call">
    <title>Function Call</title>
 
@@ -4061,6 +4130,140 @@ <title>Message Formats</title>
     </listitem>
    </varlistentry>
 
+   <varlistentry id="protocol-message-formats-ColumnEncryptionKey">
+    <term>ColumnEncryptionKey (B)</term>
+    <listitem>
+     <para>
+      This message can only appear if the protocol extension
+      <literal>_pq_.column_encryption</literal> is enabled.  (See <xref
+      linkend="protocol-flow-transparent-column-encryption"/>.)
+     </para>
+
+     <variablelist>
+      <varlistentry>
+       <term>Byte1('Y')</term>
+       <listitem>
+        <para>
+         Identifies the message as a column encryption key message.
+        </para>
+       </listitem>
+      </varlistentry>
+
+      <varlistentry>
+       <term>Int32</term>
+       <listitem>
+        <para>
+         Length of message contents in bytes, including self.
+        </para>
+       </listitem>
+      </varlistentry>
+
+      <varlistentry>
+       <term>Int32</term>
+       <listitem>
+        <para>
+         The session-specific identifier of the key.
+        </para>
+       </listitem>
+      </varlistentry>
+
+      <varlistentry>
+       <term>Int32</term>
+       <listitem>
+        <para>
+         The identifier of the master key used to encrypt this key.
+        </para>
+       </listitem>
+      </varlistentry>
+
+      <varlistentry>
+       <term>Int32</term>
+       <listitem>
+        <para>
+         The identifier of the algorithm used to encrypt this key.
+        </para>
+       </listitem>
+      </varlistentry>
+
+      <varlistentry>
+       <term>Int32</term>
+       <listitem>
+        <para>
+         The length of the following key material.
+        </para>
+       </listitem>
+      </varlistentry>
+
+      <varlistentry>
+       <term>Byte<replaceable>n</replaceable></term>
+       <listitem>
+        <para>
+         The key material, encrypted with the master key referenced above.
+        </para>
+       </listitem>
+      </varlistentry>
+     </variablelist>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry id="protocol-message-formats-ColumnMasterKey">
+    <term>ColumnMasterKey (B)</term>
+    <listitem>
+     <para>
+      This message can only appear if the protocol extension
+      <literal>_pq_.column_encryption</literal> is enabled.  (See <xref
+      linkend="protocol-flow-transparent-column-encryption"/>.)
+     </para>
+
+     <variablelist>
+      <varlistentry>
+       <term>Byte1('y')</term>
+       <listitem>
+        <para>
+         Identifies the message as a column master key message.
+        </para>
+       </listitem>
+      </varlistentry>
+
+      <varlistentry>
+       <term>Int32</term>
+       <listitem>
+        <para>
+         Length of message contents in bytes, including self.
+        </para>
+       </listitem>
+      </varlistentry>
+
+      <varlistentry>
+       <term>Int32</term>
+       <listitem>
+        <para>
+         The session-specific identifier of the key.
+        </para>
+       </listitem>
+      </varlistentry>
+
+      <varlistentry>
+       <term>String</term>
+       <listitem>
+        <para>
+         The name of the key.
+        </para>
+       </listitem>
+      </varlistentry>
+
+      <varlistentry>
+       <term>String</term>
+       <listitem>
+        <para>
+         The key's realm.
+        </para>
+       </listitem>
+      </varlistentry>
+     </variablelist>
+    </listitem>
+   </varlistentry>
+
    <varlistentry id="protocol-message-formats-CommandComplete">
     <term>CommandComplete (B)</term>
     <listitem>
@@ -5157,6 +5360,46 @@ <title>Message Formats</title>
        </listitem>
       </varlistentry>
      </variablelist>
+
+     <para>
+      If the protocol extension <literal>_pq_.column_encryption</literal> is
+      enabled (see <xref
+      linkend="protocol-flow-transparent-column-encryption"/>), then there is
+      also the following for each parameter:
+     </para>
+
+     <variablelist>
+      <varlistentry>
+       <term>Int32</term>
+       <listitem>
+        <para>
+         If this parameter is to be encrypted, this specifies the
+         identifier of the column encryption key to use, else zero.
+        </para>
+       </listitem>
+      </varlistentry>
+
+      <varlistentry>
+       <term>Int32</term>
+       <listitem>
+        <para>
+         If this parameter is to be encrypted, this specifies the
+         identifier of the encryption algorithm, else zero.
+        </para>
+       </listitem>
+      </varlistentry>
+
+      <varlistentry>
+       <term>Int16</term>
+       <listitem>
+        <para>
+         This is used as a bit field of flags.  If the column is
+         encrypted and bit 0x01 is set, the column uses deterministic
+         encryption, otherwise randomized encryption.
+        </para>
+       </listitem>
+      </varlistentry>
+     </variablelist>
     </listitem>
    </varlistentry>
 
@@ -5545,6 +5788,35 @@ <title>Message Formats</title>
        </listitem>
       </varlistentry>
      </variablelist>
+
+     <para>
+      If the protocol extension <literal>_pq_.column_encryption</literal> is
+      enabled (see <xref
+      linkend="protocol-flow-transparent-column-encryption"/>), then there is
+      also the following for each field:
+     </para>
+
+     <variablelist>
+      <varlistentry>
+       <term>Int32</term>
+       <listitem>
+        <para>
+         If the field is encrypted, this specifies the identifier of the
+         column encryption key to use, else zero.
+        </para>
+       </listitem>
+      </varlistentry>
+
+      <varlistentry>
+       <term>Int32</term>
+       <listitem>
+        <para>
+         If the field is encrypted, this specifies the identifier of the
+         encryption algorithm, else zero.
+        </para>
+       </listitem>
+      </varlistentry>
+     </variablelist>
     </listitem>
    </varlistentry>
 
@@ -7370,6 +7642,176 @@ <title>Logical Replication Message Formats</title>
   </variablelist>
  </sect1>
 
+ <sect1 id="protocol-transparent-column-encryption-crypto">
+  <title>Transparent Column Encryption Cryptography</title>
+
+  <para>
+   This section describes the cryptographic operations used by transparent
+   column encryption.  A client that supports transparent column encryption
+   needs to implement these operations as specified here in order to be able
+   to interoperate with other clients.
+  </para>
+
+  <para>
+   Column encryption key algorithms and column master key algorithms are
+   identified by integers in the protocol messages and the system catalogs.
+   Additional algorithms may be added to this protocol specification without a
+   change in the protocol version number.  Clients should implement support
+   for all the algorithms specified here.  If a client encounters an algorithm
+   identifier it does not recognize or does not support, it must raise an
+   error.  A suitable error message should be provided to the application or
+   user.
+  </para>
+
+  <sect2 id="protocol-cmk">
+   <title>Column Master Keys</title>
+
+   <para>
+    The currently defined algorithms for column master keys are listed in
+    <xref linkend="protocol-cmk-table"/>.
+   </para>
+
+   <!-- see also src/include/common/colenc.h -->
+
+   <table id="protocol-cmk-table">
+    <title>Column Master Key Algorithms</title>
+    <tgroup cols="4">
+     <thead>
+      <row>
+       <entry>PostgreSQL ID</entry>
+       <entry>Name</entry>
+       <entry>JWA (<ulink url="https://tools.ietf.org/html/rfc7518">RFC 7518</ulink>) name</entry>
+       <entry>Description</entry>
+      </row>
+     </thead>
+     <tbody>
+      <row>
+       <entry>1</entry>
+       <entry><literal>unspecified</literal></entry>
+       <entry>(none)</entry>
+       <entry>interpreted by client</entry>
+      </row>
+      <row>
+       <entry>2</entry>
+       <entry><literal>RSAES_OAEP_SHA_1</literal></entry>
+       <entry><literal>RSA-OAEP</literal></entry>
+       <entry>RSAES OAEP using default parameters (<ulink
+       url="https://tools.ietf.org/html/rfc8017">RFC 8017</ulink>/PKCS #1)</entry>
+      </row>
+      <row>
+       <entry>3</entry>
+       <entry><literal>RSAES_OAEP_SHA_256</literal></entry>
+       <entry><literal>RSA-OAEP-256</literal></entry>
+       <entry>RSAES OAEP using SHA-256 and MGF1 with SHA-256 (<ulink
+       url="https://tools.ietf.org/html/rfc8017">RFC
+       8017</ulink>/PKCS #1)</entry>
+      </row>
+     </tbody>
+    </tgroup>
+   </table>
+  </sect2>
+
+  <sect2 id="protocol-cek">
+   <title>Column Encryption Keys</title>
+
+   <para>
+    The currently defined algorithms for column encryption keys are listed in
+    <xref linkend="protocol-cek-table"/>.
+   </para>
+
+   <!-- see also src/include/common/colenc.h -->
+
+   <para>
+    The key material of a column encryption key consists of three components,
+    concatenated in this order: the MAC key, the encryption key, and the IV
+    key.  <xref linkend="protocol-cek-table"/> shows the total length that a
+    key generated for each algorithm is required to have.  The MAC key and the
+    encryption key are used by the referenced encryption algorithms; see there
+    for details.  The IV key is used for computing the static initialization
+    vector for deterministic encryption; it is unused for randomized
+    encryption.
+   </para>
+
+   <!-- see also https://datatracker.ietf.org/doc/html/draft-mcgrew-aead-aes-cbc-hmac-sha2-05#section-2.8 -->
+   <table id="protocol-cek-table">
+    <title>Column Encryption Key Algorithms</title>
+    <tgroup cols="7">
+     <thead>
+      <row>
+       <entry>PostgreSQL ID</entry>
+       <entry>Name</entry>
+       <entry>Description</entry>
+       <entry>MAC key length (octets)</entry>
+       <entry>Encryption key length (octets)</entry>
+       <entry>IV key length (octets)</entry>
+       <entry>Total key length (octets)</entry>
+      </row>
+     </thead>
+     <tbody>
+      <row>
+       <entry>32768</entry>
+       <entry><literal>AEAD_AES_128_CBC_HMAC_SHA_256</literal></entry>
+       <entry><ulink url="https://datatracker.ietf.org/doc/html/draft-mcgrew-aead-aes-cbc-hmac-sha2-05"/></entry;
+       <entry>16</entry>
+       <entry>16</entry>
+       <entry>16</entry>
+       <entry>48</entry>
+      </row>
+      <row>
+       <entry>32769</entry>
+       <entry><literal>AEAD_AES_192_CBC_HMAC_SHA_384</literal></entry>
+       <entry><ulink url="https://datatracker.ietf.org/doc/html/draft-mcgrew-aead-aes-cbc-hmac-sha2-05"/></entry;
+       <entry>24</entry>
+       <entry>24</entry>
+       <entry>24</entry>
+       <entry>72</entry>
+      </row>
+      <row>
+       <entry>32770</entry>
+       <entry><literal>AEAD_AES_256_CBC_HMAC_SHA_384</literal></entry>
+       <entry><ulink url="https://datatracker.ietf.org/doc/html/draft-mcgrew-aead-aes-cbc-hmac-sha2-05"/></entry;
+       <entry>24</entry>
+       <entry>32</entry>
+       <entry>24</entry>
+       <entry>90</entry>
+      </row>
+      <row>
+       <entry>32771</entry>
+       <entry><literal>AEAD_AES_256_CBC_HMAC_SHA_512</literal></entry>
+       <entry><ulink url="https://datatracker.ietf.org/doc/html/draft-mcgrew-aead-aes-cbc-hmac-sha2-05"/></entry;
+       <entry>32</entry>
+       <entry>32</entry>
+       <entry>32</entry>
+       <entry>96</entry>
+      </row>
+     </tbody>
+    </tgroup>
+   </table>
+
+   <para>
+    The <quote>associated data</quote> in these algorithms consists of 4
+    bytes: The ASCII letters <literal>P</literal> and <literal>G</literal>
+    (byte values 80 and 71), followed by the version number as a 16-bit
+    unsigned integer in network byte order.  The version number is currently
+    always 1.  (This is intended to allow for possible incompatible changes or
+    extensions in the future.)
+   </para>
+
+   <para>
+    The length of the initialization vector is 16 octets for all CEK algorithm
+    variants.  For randomized encryption, the initialization vector should be
+    (cryptographically strong) random bytes.  For deterministic encryption,
+    the initialization vector is constructed as
+<programlisting>
+SUBSTRING(<replaceable>HMAC</replaceable>(<replaceable>K</replaceable>, <replaceable>P</replaceable>) FOR <replaceable>IVLEN</replaceable>)
+</programlisting>
+    where <replaceable>HMAC</replaceable> is the HMAC function associated with
+    the algorithm, <replaceable>K</replaceable> is the IV key, and
+    <replaceable>P</replaceable> is the plaintext to be encrypted.
+   </para>
+  </sect2>
+ </sect1>
+
  <sect1 id="protocol-changes">
   <title>Summary of Changes since Protocol 2.0</title>
 
diff --git a/doc/src/sgml/ref/allfiles.sgml b/doc/src/sgml/ref/allfiles.sgml
index e90a0e1f83..331b1f010b 100644
--- a/doc/src/sgml/ref/allfiles.sgml
+++ b/doc/src/sgml/ref/allfiles.sgml
@@ -8,6 +8,8 @@
 <!ENTITY abort              SYSTEM "abort.sgml">
 <!ENTITY alterAggregate     SYSTEM "alter_aggregate.sgml">
 <!ENTITY alterCollation     SYSTEM "alter_collation.sgml">
+<!ENTITY alterColumnEncryptionKey SYSTEM "alter_column_encryption_key.sgml">
+<!ENTITY alterColumnMasterKey SYSTEM "alter_column_master_key.sgml">
 <!ENTITY alterConversion    SYSTEM "alter_conversion.sgml">
 <!ENTITY alterDatabase      SYSTEM "alter_database.sgml">
 <!ENTITY alterDefaultPrivileges SYSTEM "alter_default_privileges.sgml">
@@ -62,6 +64,8 @@
 <!ENTITY createAggregate    SYSTEM "create_aggregate.sgml">
 <!ENTITY createCast         SYSTEM "create_cast.sgml">
 <!ENTITY createCollation    SYSTEM "create_collation.sgml">
+<!ENTITY createColumnEncryptionKey SYSTEM "create_column_encryption_key.sgml">
+<!ENTITY createColumnMasterKey SYSTEM "create_column_master_key.sgml">
 <!ENTITY createConversion   SYSTEM "create_conversion.sgml">
 <!ENTITY createDatabase     SYSTEM "create_database.sgml">
 <!ENTITY createDomain       SYSTEM "create_domain.sgml">
@@ -109,6 +113,8 @@
 <!ENTITY dropAggregate      SYSTEM "drop_aggregate.sgml">
 <!ENTITY dropCast           SYSTEM "drop_cast.sgml">
 <!ENTITY dropCollation      SYSTEM "drop_collation.sgml">
+<!ENTITY dropColumnEncryptionKey SYSTEM "drop_column_encryption_key.sgml">
+<!ENTITY dropColumnMasterKey SYSTEM "drop_column_master_key.sgml">
 <!ENTITY dropConversion     SYSTEM "drop_conversion.sgml">
 <!ENTITY dropDatabase       SYSTEM "drop_database.sgml">
 <!ENTITY dropDomain         SYSTEM "drop_domain.sgml">
diff --git a/doc/src/sgml/ref/alter_column_encryption_key.sgml b/doc/src/sgml/ref/alter_column_encryption_key.sgml
new file mode 100644
index 0000000000..655e1e00d8
--- /dev/null
+++ b/doc/src/sgml/ref/alter_column_encryption_key.sgml
@@ -0,0 +1,197 @@
+<!--
+doc/src/sgml/ref/alter_column_encryption_key.sgml
+PostgreSQL documentation
+-->
+
+<refentry id="sql-alter-column-encryption-key">
+ <indexterm zone="sql-alter-column-encryption-key">
+  <primary>ALTER COLUMN ENCRYPTION KEY</primary>
+ </indexterm>
+
+ <refmeta>
+  <refentrytitle>ALTER COLUMN ENCRYPTION KEY</refentrytitle>
+  <manvolnum>7</manvolnum>
+  <refmiscinfo>SQL - Language Statements</refmiscinfo>
+ </refmeta>
+
+ <refnamediv>
+  <refname>ALTER COLUMN ENCRYPTION KEY</refname>
+  <refpurpose>change the definition of a column encryption key</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+<synopsis>
+ALTER COLUMN ENCRYPTION KEY <replaceable>name</replaceable> ADD VALUE (
+    COLUMN_MASTER_KEY = <replaceable>cmk</replaceable>,
+    [ ALGORITHM = <replaceable>algorithm</replaceable>, ]
+    ENCRYPTED_VALUE = <replaceable>encval</replaceable>
+)
+
+ALTER COLUMN ENCRYPTION KEY <replaceable>name</replaceable> DROP VALUE (
+    COLUMN_MASTER_KEY = <replaceable>cmk</replaceable>
+)
+
+ALTER COLUMN ENCRYPTION KEY <replaceable>name</replaceable> RENAME TO <replaceable>new_name</replaceable>
+ALTER COLUMN ENCRYPTION KEY <replaceable>name</replaceable> OWNER TO { <replaceable>new_owner</replaceable> | CURRENT_ROLE | CURRENT_USER | SESSION_USER }
+ALTER COLUMN ENCRYPTION KEY <replaceable>name</replaceable> SET SCHEMA <replaceable>new_schema</replaceable>
+</synopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+  <title>Description</title>
+
+  <para>
+   <command>ALTER COLUMN ENCRYPTION KEY</command> changes the definition of a
+   column encryption key.
+  </para>
+
+  <para>
+   The first form adds new encrypted key data to a column encryption key,
+   which must be encrypted with a different column master key than the
+   existing key data.  The second form removes a key data entry for a given
+   column master key.  Together, these forms can be used for column master key
+   rotation.
+  </para>
+
+  <para>
+   You must own the column encryption key to use <command>ALTER COLUMN
+   ENCRYPTION KEY</command>.  To alter the owner, you must also be a direct or
+   indirect member of the new owning role, and that role must have
+   <literal>CREATE</literal> privilege on the column encryption key's
+   schema.  (These restrictions enforce that altering the owner doesn't do
+   anything you couldn't do by dropping and recreating the column encryption
+   key.  However, a superuser can alter ownership of any column encryption key
+   anyway.)
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Parameters</title>
+
+  <variablelist>
+   <varlistentry>
+    <term><replaceable class="parameter">name</replaceable></term>
+    <listitem>
+     <para>
+      The name (optionally schema-qualified) of an existing column encryption
+      key.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable>cmk</replaceable></term>
+
+    <listitem>
+     <para>
+      The name of the column master key that was used to encrypt this column
+      encryption key.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable>algorithm</replaceable></term>
+
+    <listitem>
+     <para>
+      The encryption algorithm that was used to encrypt the key material of
+      this column encryption key.  See <xref
+      linkend="sql-create-column-encryption-key"/> for details
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable>encval</replaceable></term>
+
+    <listitem>
+     <para>
+      The key material of this column encryption key, encrypted with the
+      specified column master key using the specified algorithm.  The value
+      must be a <type>bytea</type>-compatible literal.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable class="parameter">new_name</replaceable></term>
+    <listitem>
+     <para>
+      The new name of the column encryption key.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable class="parameter">new_owner</replaceable></term>
+    <listitem>
+     <para>
+      The new owner of the column encryption key.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable class="parameter">new_schema</replaceable></term>
+    <listitem>
+     <para>
+      The new schema for the column encryption key.
+     </para>
+    </listitem>
+   </varlistentry>
+  </variablelist>
+ </refsect1>
+
+ <refsect1>
+  <title>Examples</title>
+
+  <para>
+   To rotate the master keys used to encrypt a given column encryption key,
+   use a command sequence like this:
+<programlisting>
+ALTER COLUMN ENCRYPTION KEY cek1 ADD VALUE (
+    COLUMN_MASTER_KEY = cmk2,
+    ENCRYPTED_VALUE = '\x01020204...'
+);
+
+ALTER COLUMN ENCRYPTION KEY cek1 DROP VALUE (
+    COLUMN_MASTER_KEY = cmk1
+);
+</programlisting>
+  </para>
+
+  <para>
+   To rename the column encryption key <literal>cek1</literal> to
+   <literal>cek2</literal>:
+<programlisting>
+ALTER COLUMN ENCRYPTION KEY cek1 RENAME TO cek2;
+</programlisting>
+  </para>
+
+  <para>
+   To change the owner of the column encryption key <literal>cek1</literal> to
+   <literal>joe</literal>:
+<programlisting>
+ALTER COLUMN ENCRYPTION KEY cek1 OWNER TO joe;
+</programlisting></para>
+ </refsect1>
+
+ <refsect1>
+  <title>Compatibility</title>
+
+  <para>
+   There is no <command>ALTER COLUMN ENCRYPTION KEY</command> statement in the
+   SQL standard.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>See Also</title>
+
+  <simplelist type="inline">
+   <member><xref linkend="sql-create-column-encryption-key"/></member>
+   <member><xref linkend="sql-drop-column-encryption-key"/></member>
+  </simplelist>
+ </refsect1>
+</refentry>
diff --git a/doc/src/sgml/ref/alter_column_master_key.sgml b/doc/src/sgml/ref/alter_column_master_key.sgml
new file mode 100644
index 0000000000..7f0e656ef0
--- /dev/null
+++ b/doc/src/sgml/ref/alter_column_master_key.sgml
@@ -0,0 +1,134 @@
+<!--
+doc/src/sgml/ref/alter_column_master_key.sgml
+PostgreSQL documentation
+-->
+
+<refentry id="sql-alter-column-master-key">
+ <indexterm zone="sql-alter-column-master-key">
+  <primary>ALTER COLUMN MASTER KEY</primary>
+ </indexterm>
+
+ <refmeta>
+  <refentrytitle>ALTER COLUMN MASTER KEY</refentrytitle>
+  <manvolnum>7</manvolnum>
+  <refmiscinfo>SQL - Language Statements</refmiscinfo>
+ </refmeta>
+
+ <refnamediv>
+  <refname>ALTER COLUMN MASTER KEY</refname>
+  <refpurpose>change the definition of a column master key</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+<synopsis>
+ALTER COLUMN MASTER KEY <replaceable>name</replaceable> ( REALM = <replaceable>realm</replaceable> )
+
+ALTER COLUMN MASTER KEY <replaceable>name</replaceable> RENAME TO <replaceable>new_name</replaceable>
+ALTER COLUMN MASTER KEY <replaceable>name</replaceable> OWNER TO { <replaceable>new_owner</replaceable> | CURRENT_ROLE | CURRENT_USER | SESSION_USER }
+ALTER COLUMN MASTER KEY <replaceable>name</replaceable> SET SCHEMA <replaceable>new_schema</replaceable>
+</synopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+  <title>Description</title>
+
+  <para>
+   <command>ALTER COLUMN MASTER KEY</command> changes the definition of a
+   column master key.
+  </para>
+
+  <para>
+   The first form changes the parameters of a column master key.  See <xref
+   linkend="sql-create-column-master-key"/> for details.
+  </para>
+
+  <para>
+   You must own the column master key to use <command>ALTER COLUMN MASTER
+   KEY</command>.  To alter the owner, you must also be a direct or indirect
+   member of the new owning role, and that role must have
+   <literal>CREATE</literal> privilege on the column master key's schema.
+   (These restrictions enforce that altering the owner doesn't do anything you
+   couldn't do by dropping and recreating the column master key.  However, a
+   superuser can alter ownership of any column master key anyway.)
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Parameters</title>
+
+  <variablelist>
+   <varlistentry>
+    <term><replaceable class="parameter">name</replaceable></term>
+    <listitem>
+     <para>
+      The name (optionally schema-qualified) of an existing column master key.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable class="parameter">new_name</replaceable></term>
+    <listitem>
+     <para>
+      The new name of the column master key.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable class="parameter">new_owner</replaceable></term>
+    <listitem>
+     <para>
+      The new owner of the column master key.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable class="parameter">new_schema</replaceable></term>
+    <listitem>
+     <para>
+      The new schema for the column master key.
+     </para>
+    </listitem>
+   </varlistentry>
+  </variablelist>
+ </refsect1>
+
+ <refsect1>
+  <title>Examples</title>
+
+  <para>
+   To rename the column master key <literal>cmk1</literal> to
+   <literal>cmk2</literal>:
+<programlisting>
+ALTER COLUMN MASTER KEY cmk1 RENAME TO cmk2;
+</programlisting>
+  </para>
+
+  <para>
+   To change the owner of the column master key <literal>cmk1</literal> to
+   <literal>joe</literal>:
+<programlisting>
+ALTER COLUMN MASTER KEY cmk1 OWNER TO joe;
+</programlisting></para>
+ </refsect1>
+
+ <refsect1>
+  <title>Compatibility</title>
+
+  <para>
+   There is no <command>ALTER COLUMN MASTER KEY</command> statement in the
+   SQL standard.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>See Also</title>
+
+  <simplelist type="inline">
+   <member><xref linkend="sql-create-column-master-key"/></member>
+   <member><xref linkend="sql-drop-column-master-key"/></member>
+  </simplelist>
+ </refsect1>
+</refentry>
diff --git a/doc/src/sgml/ref/comment.sgml b/doc/src/sgml/ref/comment.sgml
index 7499da1d62..995486baf2 100644
--- a/doc/src/sgml/ref/comment.sgml
+++ b/doc/src/sgml/ref/comment.sgml
@@ -28,6 +28,8 @@
   CAST (<replaceable>source_type</replaceable> AS <replaceable>target_type</replaceable>) |
   COLLATION <replaceable class="parameter">object_name</replaceable> |
   COLUMN <replaceable class="parameter">relation_name</replaceable>.<replaceable class="parameter">column_name</replaceable> |
+  COLUMN ENCRYPTION KEY <replaceable class="parameter">object_name</replaceable> |
+  COLUMN MASTER KEY <replaceable class="parameter">object_name</replaceable> |
   CONSTRAINT <replaceable class="parameter">constraint_name</replaceable> ON <replaceable class="parameter">table_name</replaceable> |
   CONSTRAINT <replaceable class="parameter">constraint_name</replaceable> ON DOMAIN <replaceable class="parameter">domain_name</replaceable> |
   CONVERSION <replaceable class="parameter">object_name</replaceable> |
diff --git a/doc/src/sgml/ref/copy.sgml b/doc/src/sgml/ref/copy.sgml
index c25b52d0cb..8c461b8f15 100644
--- a/doc/src/sgml/ref/copy.sgml
+++ b/doc/src/sgml/ref/copy.sgml
@@ -555,6 +555,15 @@ <title>Notes</title>
     null strings to null values and unquoted null strings to empty strings.
    </para>
 
+   <para>
+    <command>COPY</command> does not support transparent column encryption or
+    decryption; its input or output data will always be the ciphertext.  This
+    is usually suitable for backups (see also <xref linkend="app-pgdump"/>).
+    If transparent encryption or decryption is wanted,
+    <command>INSERT</command> and <command>SELECT</command> need to be used
+    instead to write and read the data.
+   </para>
+
  </refsect1>
 
  <refsect1>
diff --git a/doc/src/sgml/ref/create_column_encryption_key.sgml b/doc/src/sgml/ref/create_column_encryption_key.sgml
new file mode 100644
index 0000000000..e10b61267e
--- /dev/null
+++ b/doc/src/sgml/ref/create_column_encryption_key.sgml
@@ -0,0 +1,169 @@
+<!--
+doc/src/sgml/ref/create_column_encryption_key.sgml
+PostgreSQL documentation
+-->
+
+<refentry id="sql-create-column-encryption-key">
+ <indexterm zone="sql-create-column-encryption-key">
+  <primary>CREATE COLUMN ENCRYPTION KEY</primary>
+ </indexterm>
+
+ <refmeta>
+  <refentrytitle>CREATE COLUMN ENCRYPTION KEY</refentrytitle>
+  <manvolnum>7</manvolnum>
+  <refmiscinfo>SQL - Language Statements</refmiscinfo>
+ </refmeta>
+
+ <refnamediv>
+  <refname>CREATE COLUMN ENCRYPTION KEY</refname>
+  <refpurpose>define a new column encryption key</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+<synopsis>
+CREATE COLUMN ENCRYPTION KEY <replaceable>name</replaceable> WITH VALUES (
+    COLUMN_MASTER_KEY = <replaceable>cmk</replaceable>,
+    [ ALGORITHM = <replaceable>algorithm</replaceable>, ]
+    ENCRYPTED_VALUE = <replaceable>encval</replaceable>
+)
+[ , ... ]
+</synopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+  <title>Description</title>
+
+  <para>
+   <command>CREATE COLUMN ENCRYPTION KEY</command> defines a new column
+   encryption key.  A column encryption key is used for client-side encryption
+   of table columns that have been defined as encrypted.  The key material of
+   a column encryption key is stored in the database's system catalogs,
+   encrypted (wrapped) by a column master key (which in turn is only
+   accessible to the client, not the database server).
+  </para>
+
+  <para>
+   A column encryption key can be associated with more than one column master
+   key.  To specify that, specify more than one parenthesized definition (see
+   also the examples).
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Parameters</title>
+
+  <variablelist>
+   <varlistentry>
+    <term><replaceable>name</replaceable></term>
+
+    <listitem>
+     <para>
+      The name of the new column encryption key.  The name can be
+      schema-qualified.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable>cmk</replaceable></term>
+
+    <listitem>
+     <para>
+      The name of the column master key that was used to encrypt this column
+      encryption key.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable>algorithm</replaceable></term>
+
+    <listitem>
+     <para>
+      The encryption algorithm that was used to encrypt the key material of
+      this column encryption key.  Supported algorithms are:
+      <itemizedlist>
+       <listitem>
+        <para><literal>unspecified</literal></para>
+       </listitem>
+       <listitem>
+        <para><literal>RSAES_OAEP_SHA_1</literal></para>
+       </listitem>
+       <listitem>
+        <para><literal>RSAES_OAEP_SHA_256</literal></para>
+       </listitem>
+      </itemizedlist>
+     </para>
+
+     <para>
+      This is informational only.  The specified value is provided to the
+      client, which may use it for decrypting the column encryption key on the
+      client side.  But a client is also free to ignore this information and
+      figure out how to arrange the decryption in some other way.  In that
+      case, specifying the algorithm as <literal>unspecified</literal> would be
+      appropriate.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable>encval</replaceable></term>
+
+    <listitem>
+     <para>
+      The key material of this column encryption key, encrypted with the
+      specified column master key using the specified algorithm.  The value
+      must be a <type>bytea</type>-compatible literal.
+     </para>
+    </listitem>
+   </varlistentry>
+  </variablelist>
+ </refsect1>
+
+ <refsect1>
+  <title>Examples</title>
+
+  <para>
+<programlisting>
+CREATE COLUMN ENCRYPTION KEY cek1 WITH VALUES (
+    COLUMN_MASTER_KEY = cmk1,
+    ENCRYPTED_VALUE = '\x01020204...'
+);
+</programlisting>
+  </para>
+
+  <para>
+   To specify more than one associated column master key:
+<programlisting>
+CREATE COLUMN ENCRYPTION KEY cek1 WITH VALUES (
+    COLUMN_MASTER_KEY = cmk1,
+    ENCRYPTED_VALUE = '\x01020204...'
+),
+(
+    COLUMN_MASTER_KEY = cmk2,
+    ENCRYPTED_VALUE = '\xF1F2F2F4...'
+);
+</programlisting>
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Compatibility</title>
+
+  <para>
+   There is no <command>CREATE COLUMN ENCRYPTION KEY</command> statement in
+   the SQL standard.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>See Also</title>
+
+  <simplelist type="inline">
+   <member><xref linkend="sql-alter-column-encryption-key"/></member>
+   <member><xref linkend="sql-drop-column-encryption-key"/></member>
+   <member><xref linkend="sql-create-column-master-key"/></member>
+  </simplelist>
+ </refsect1>
+
+</refentry>
diff --git a/doc/src/sgml/ref/create_column_master_key.sgml b/doc/src/sgml/ref/create_column_master_key.sgml
new file mode 100644
index 0000000000..6aaa1088d1
--- /dev/null
+++ b/doc/src/sgml/ref/create_column_master_key.sgml
@@ -0,0 +1,107 @@
+<!--
+doc/src/sgml/ref/create_column_master_key.sgml
+PostgreSQL documentation
+-->
+
+<refentry id="sql-create-column-master-key">
+ <indexterm zone="sql-create-column-master-key">
+  <primary>CREATE COLUMN MASTER KEY</primary>
+ </indexterm>
+
+ <refmeta>
+  <refentrytitle>CREATE COLUMN MASTER KEY</refentrytitle>
+  <manvolnum>7</manvolnum>
+  <refmiscinfo>SQL - Language Statements</refmiscinfo>
+ </refmeta>
+
+ <refnamediv>
+  <refname>CREATE COLUMN MASTER KEY</refname>
+  <refpurpose>define a new column master key</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+<synopsis>
+CREATE COLUMN MASTER KEY <replaceable>name</replaceable> [ WITH (
+    [ REALM = <replaceable>realm</replaceable> ]
+) ]
+</synopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+  <title>Description</title>
+
+  <para>
+   <command>CREATE COLUMN MASTER KEY</command> defines a new column master
+   key.  A column master key is used to encrypt column encryption keys, which
+   are the keys that actually encrypt the column data.  The key material of
+   the column master key is not stored in the database.  The definition of a
+   column master key records information that will allow a client to locate
+   the key material, for example in a file or in a key management system.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Parameters</title>
+
+  <variablelist>
+   <varlistentry>
+    <term><replaceable>name</replaceable></term>
+
+    <listitem>
+     <para>
+      The name of the new column master key.  The name can be
+      schema-qualified.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable>realm</replaceable></term>
+
+    <listitem>
+     <para>
+      This is an optional string that can be used to organize column master
+      keys into groups for lookup by clients.  The intent is that all column
+      master keys that are stored in the same system (file system location,
+      key management system, etc.) should be in the same realm.  A client
+      would then be configured to look up all keys in a given realm in a
+      certain way.  See the documentation of the respective client library for
+      further usage instructions.
+     </para>
+
+     <para>
+      The default is the empty string.
+     </para>
+    </listitem>
+   </varlistentry>
+  </variablelist>
+ </refsect1>
+
+ <refsect1>
+  <title>Examples</title>
+
+<programlisting>
+CREATE COLUMN MASTER KEY cmk1 (realm = 'myrealm');
+</programlisting>
+ </refsect1>
+
+ <refsect1>
+  <title>Compatibility</title>
+
+  <para>
+   There is no <command>CREATE COLUMN MASTER KEY</command> statement in
+   the SQL standard.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>See Also</title>
+
+  <simplelist type="inline">
+   <member><xref linkend="sql-alter-column-master-key"/></member>
+   <member><xref linkend="sql-drop-column-master-key"/></member>
+   <member><xref linkend="sql-create-column-encryption-key"/></member>
+  </simplelist>
+ </refsect1>
+
+</refentry>
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index a03dee4afe..6a9e28c2d7 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -22,7 +22,7 @@
  <refsynopsisdiv>
 <synopsis>
 CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXISTS ] <replaceable class="parameter">table_name</replaceable> ( [
-  { <replaceable class="parameter">column_name</replaceable> <replaceable class="parameter">data_type</replaceable> [ STORAGE { PLAIN | EXTERNAL | EXTENDED | MAIN | DEFAULT } ] [ COMPRESSION <replaceable>compression_method</replaceable> ] [ COLLATE <replaceable>collation</replaceable> ] [ <replaceable class="parameter">column_constraint</replaceable> [ ... ] ]
+  { <replaceable class="parameter">column_name</replaceable> <replaceable class="parameter">data_type</replaceable> [ ENCRYPTED WITH ( <replaceable>encryption_options</replaceable> ) ] [ STORAGE { PLAIN | EXTERNAL | EXTENDED | MAIN | DEFAULT } ] [ COMPRESSION <replaceable>compression_method</replaceable> ] [ COLLATE <replaceable>collation</replaceable> ] [ <replaceable class="parameter">column_constraint</replaceable> [ ... ] ]
     | <replaceable>table_constraint</replaceable>
     | LIKE <replaceable>source_table</replaceable> [ <replaceable>like_option</replaceable> ... ] }
     [, ... ]
@@ -87,7 +87,7 @@
 
 <phrase>and <replaceable class="parameter">like_option</replaceable> is:</phrase>
 
-{ INCLUDING | EXCLUDING } { COMMENTS | COMPRESSION | CONSTRAINTS | DEFAULTS | GENERATED | IDENTITY | INDEXES | STATISTICS | STORAGE | ALL }
+{ INCLUDING | EXCLUDING } { COMMENTS | COMPRESSION | CONSTRAINTS | DEFAULTS | ENCRYPTED | GENERATED | IDENTITY | INDEXES | STATISTICS | STORAGE | ALL }
 
 <phrase>and <replaceable class="parameter">partition_bound_spec</replaceable> is:</phrase>
 
@@ -351,6 +351,46 @@ <title>Parameters</title>
     </listitem>
    </varlistentry>
 
+   <varlistentry id="sql-createtable-parms-encrypted">
+    <term><literal>ENCRYPTED WITH ( <replaceable>encryption_options</replaceable> )</literal></term>
+    <listitem>
+     <para>
+      Enables transparent column encryption for the column.
+      <replaceable>encryption_options</replaceable> are comma-separated
+      <literal>key=value</literal> specifications.  The following options are
+      available:
+      <variablelist>
+       <varlistentry>
+        <term><literal>column_encryption_key</literal></term>
+        <listitem>
+         <para>
+          Specifies the name of the column encryption key to use.  Specifying
+          this is mandatory.
+         </para>
+        </listitem>
+       </varlistentry>
+       <varlistentry>
+        <term><literal>encryption_type</literal></term>
+        <listitem>
+         <para>
+          <literal>randomized</literal> (the default) or <literal>deterministic</literal>
+         </para>
+        </listitem>
+       </varlistentry>
+       <varlistentry>
+        <term><literal>algorithm</literal></term>
+        <listitem>
+         <para>
+          The encryption algorithm to use.  The default is
+          <literal>AEAD_AES_128_CBC_HMAC_SHA_256</literal>.
+         </para>
+        </listitem>
+       </varlistentry>
+      </variablelist>
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry id="sql-createtable-parms-inherits">
     <term><literal>INHERITS ( <replaceable>parent_table</replaceable> [, ... ] )</literal></term>
     <listitem>
@@ -704,6 +744,16 @@ <title>Parameters</title>
         </listitem>
        </varlistentry>
 
+       <varlistentry id="sql-createtable-parms-like-opt-encrypted">
+        <term><literal>INCLUDING ENCRYPTED</literal></term>
+        <listitem>
+         <para>
+          Column encryption specifications for the copied column definitions
+          will be copied.  By default, new columns will be unencrypted.
+         </para>
+        </listitem>
+       </varlistentry>
+
        <varlistentry id="sql-createtable-parms-like-opt-generated">
         <term><literal>INCLUDING GENERATED</literal></term>
         <listitem>
diff --git a/doc/src/sgml/ref/drop_column_encryption_key.sgml b/doc/src/sgml/ref/drop_column_encryption_key.sgml
new file mode 100644
index 0000000000..f2ac1beb08
--- /dev/null
+++ b/doc/src/sgml/ref/drop_column_encryption_key.sgml
@@ -0,0 +1,112 @@
+<!--
+doc/src/sgml/ref/drop_column_encryption_key.sgml
+PostgreSQL documentation
+-->
+
+<refentry id="sql-drop-column-encryption-key">
+ <indexterm zone="sql-drop-column-encryption-key">
+  <primary>DROP COLUMN ENCRYPTION KEY</primary>
+ </indexterm>
+
+ <refmeta>
+  <refentrytitle>DROP COLUMN ENCRYPTION KEY</refentrytitle>
+  <manvolnum>7</manvolnum>
+  <refmiscinfo>SQL - Language Statements</refmiscinfo>
+ </refmeta>
+
+ <refnamediv>
+  <refname>DROP COLUMN ENCRYPTION KEY</refname>
+  <refpurpose>remove a column encryption key</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+<synopsis>
+DROP COLUMN ENCRYPTION KEY [ IF EXISTS ] <replaceable>name</replaceable> [ CASCADE | RESTRICT ]
+</synopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+  <title>Description</title>
+
+  <para>
+   <command>DROP COLUMN ENCRYPTION KEY</command> removes a previously defined
+   column encryption key.  To be able to drop a column encryption key, you
+   must be its owner.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Parameters</title>
+
+   <variablelist>
+    <varlistentry>
+     <term><literal>IF EXISTS</literal></term>
+     <listitem>
+      <para>
+       Do not throw an error if the column encryption key does not exist.
+       A notice is issued in this case.
+      </para>
+     </listitem>
+    </varlistentry>
+
+    <varlistentry>
+     <term><replaceable>name</replaceable></term>
+
+     <listitem>
+      <para>
+       The name (optionally schema-qualified) of the column encryption key.
+      </para>
+     </listitem>
+    </varlistentry>
+
+    <varlistentry>
+     <term><literal>CASCADE</literal></term>
+     <listitem>
+      <para>
+       Automatically drop objects that depend on the column encryption key,
+       and in turn all objects that depend on those objects
+       (see <xref linkend="ddl-depend"/>).
+      </para>
+     </listitem>
+    </varlistentry>
+
+    <varlistentry>
+     <term><literal>RESTRICT</literal></term>
+     <listitem>
+      <para>
+       Refuse to drop the column encryption key if any objects depend on it.  This
+       is the default.
+      </para>
+     </listitem>
+    </varlistentry>
+   </variablelist>
+ </refsect1>
+
+ <refsect1>
+  <title>Examples</title>
+
+  <para>
+<programlisting>
+DROP COLUMN ENCRYPTION KEY cek1;
+</programlisting></para>
+ </refsect1>
+
+ <refsect1>
+  <title>Compatibility</title>
+
+  <para>
+   There is no <command>DROP COLUMN ENCRYPTION KEY</command> statement in
+   the SQL standard.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>See Also</title>
+
+    <simplelist type="inline">
+   <member><xref linkend="sql-alter-column-encryption-key"/></member>
+   <member><xref linkend="sql-create-column-encryption-key"/></member>
+  </simplelist>
+ </refsect1>
+
+</refentry>
diff --git a/doc/src/sgml/ref/drop_column_master_key.sgml b/doc/src/sgml/ref/drop_column_master_key.sgml
new file mode 100644
index 0000000000..fae95e09d1
--- /dev/null
+++ b/doc/src/sgml/ref/drop_column_master_key.sgml
@@ -0,0 +1,112 @@
+<!--
+doc/src/sgml/ref/drop_column_master_key.sgml
+PostgreSQL documentation
+-->
+
+<refentry id="sql-drop-column-master-key">
+ <indexterm zone="sql-drop-column-master-key">
+  <primary>DROP COLUMN MASTER KEY</primary>
+ </indexterm>
+
+ <refmeta>
+  <refentrytitle>DROP COLUMN MASTER KEY</refentrytitle>
+  <manvolnum>7</manvolnum>
+  <refmiscinfo>SQL - Language Statements</refmiscinfo>
+ </refmeta>
+
+ <refnamediv>
+  <refname>DROP COLUMN MASTER KEY</refname>
+  <refpurpose>remove a column master key</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+<synopsis>
+DROP COLUMN MASTER KEY [ IF EXISTS ] <replaceable>name</replaceable> [ CASCADE | RESTRICT ]
+</synopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+  <title>Description</title>
+
+  <para>
+   <command>DROP COLUMN MASTER KEY</command> removes a previously defined
+   column master key.  To be able to drop a column master key, you
+   must be its owner.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Parameters</title>
+
+   <variablelist>
+    <varlistentry>
+     <term><literal>IF EXISTS</literal></term>
+     <listitem>
+      <para>
+       Do not throw an error if the column master key does not exist.
+       A notice is issued in this case.
+      </para>
+     </listitem>
+    </varlistentry>
+
+    <varlistentry>
+     <term><replaceable>name</replaceable></term>
+
+     <listitem>
+      <para>
+       The name (optionally schema-qualified) of the column master key.
+      </para>
+     </listitem>
+    </varlistentry>
+
+    <varlistentry>
+     <term><literal>CASCADE</literal></term>
+     <listitem>
+      <para>
+       Automatically drop objects that depend on the column master key,
+       and in turn all objects that depend on those objects
+       (see <xref linkend="ddl-depend"/>).
+      </para>
+     </listitem>
+    </varlistentry>
+
+    <varlistentry>
+     <term><literal>RESTRICT</literal></term>
+     <listitem>
+      <para>
+       Refuse to drop the column master key if any objects depend on it.  This
+       is the default.
+      </para>
+     </listitem>
+    </varlistentry>
+   </variablelist>
+ </refsect1>
+
+ <refsect1>
+  <title>Examples</title>
+
+  <para>
+<programlisting>
+DROP COLUMN MASTER KEY cek1;
+</programlisting></para>
+ </refsect1>
+
+ <refsect1>
+  <title>Compatibility</title>
+
+  <para>
+   There is no <command>DROP COLUMN MASTER KEY</command> statement in
+   the SQL standard.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>See Also</title>
+
+    <simplelist type="inline">
+   <member><xref linkend="sql-alter-column-master-key"/></member>
+   <member><xref linkend="sql-create-column-master-key"/></member>
+  </simplelist>
+ </refsect1>
+
+</refentry>
diff --git a/doc/src/sgml/ref/pg_dump.sgml b/doc/src/sgml/ref/pg_dump.sgml
index 2c938cd7e1..5b3cbf919c 100644
--- a/doc/src/sgml/ref/pg_dump.sgml
+++ b/doc/src/sgml/ref/pg_dump.sgml
@@ -715,6 +715,37 @@ <title>Options</title>
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>--decrypt-encrypted-columns</option></term>
+      <listitem>
+       <para>
+        This option causes the values of all encrypted columns to be decrypted
+        and written to the output in plaintext.  By default, the values of
+        encrypted columns are written to the dump in ciphertext (that is, they
+        are not decrypted).
+       </para>
+
+       <para>
+        This option requires that <option>--inserts</option>,
+        <option>--column-inserts</option> or
+        <option>--rows-per-insert</option> is also specified.
+        (<command>COPY</command> does not support column decryption.)
+       </para>
+
+       <para>
+        For routine backups, the default behavior is appropriate and most
+        efficient.  This option is suitable if the data is meant to be
+        inspected or exported for other purposes.  Note that a dump created
+        with this option cannot be restored into a database with column
+        encryption.
+       </para>
+       <!--
+           XXX: The latter would require another pg_dump option to put out
+           INSERT ... \bind ... \g commands.
+       -->
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>--disable-dollar-quoting</option></term>
       <listitem>
diff --git a/doc/src/sgml/ref/pg_dumpall.sgml b/doc/src/sgml/ref/pg_dumpall.sgml
index e62d05e5ab..4bf60c729f 100644
--- a/doc/src/sgml/ref/pg_dumpall.sgml
+++ b/doc/src/sgml/ref/pg_dumpall.sgml
@@ -259,6 +259,33 @@ <title>Options</title>
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>--decrypt-encrypted-columns</option></term>
+      <listitem>
+       <para>
+        This option causes the values of all encrypted columns to be decrypted
+        and written to the output in plaintext.  By default, the values of
+        encrypted columns are written to the dump in ciphertext (that is, they
+        are not decrypted).
+       </para>
+
+       <para>
+        This option requires that <option>--inserts</option>,
+        <option>--column-inserts</option> or
+        <option>--rows-per-insert</option> is also specified.
+        (<command>COPY</command> does not support column decryption.)
+       </para>
+
+       <para>
+        For routine backups, the default behavior is appropriate and most
+        efficient.  This option is suitable if the data is meant to be
+        inspected or exported for other purposes.  Note that a dump created
+        with this option cannot be restored into a database with column
+        encryption.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>--disable-dollar-quoting</option></term>
       <listitem>
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index dc6528dc11..e68b8440be 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -1420,6 +1420,34 @@ <title>Meta-Commands</title>
       </varlistentry>
 
 
+      <varlistentry id="app-psql-meta-command-dcek">
+        <term><literal>\dcek[+] [ <link linkend="app-psql-patterns"><replaceable class="parameter">pattern</replaceable></link> ]</literal></term>
+        <listitem>
+        <para>
+        Lists column encryption keys.  If <replaceable
+        class="parameter">pattern</replaceable> is specified, only column
+        encryption keys whose names match the pattern are listed.  If
+        <literal>+</literal> is appended to the command name, each object is
+        listed with its associated description.
+        </para>
+        </listitem>
+      </varlistentry>
+
+
+      <varlistentry id="app-psql-meta-command-dcmk">
+        <term><literal>\dcmk[+] [ <link linkend="app-psql-patterns"><replaceable class="parameter">pattern</replaceable></link> ]</literal></term>
+        <listitem>
+        <para>
+        Lists column master keys.  If <replaceable
+        class="parameter">pattern</replaceable> is specified, only column
+        master keys whose names match the pattern are listed.  If
+        <literal>+</literal> is appended to the command name, each object is
+        listed with its associated description.
+        </para>
+        </listitem>
+      </varlistentry>
+
+
       <varlistentry id="app-psql-meta-command-dconfig">
         <term><literal>\dconfig[+] [ <link linkend="app-psql-patterns"><replaceable class="parameter">pattern</replaceable></link> ]</literal></term>
         <listitem>
@@ -4026,6 +4054,17 @@ <title>Variables</title>
         </listitem>
       </varlistentry>
 
+      <varlistentry id="app-psql-variables-hide-column-encryption">
+        <term><varname>HIDE_COLUMN_ENCRYPTION</varname></term>
+        <listitem>
+        <para>
+         If this variable is set to <literal>true</literal>, column encryption
+         details are not displayed. This is mainly useful for regression
+         tests.
+        </para>
+        </listitem>
+      </varlistentry>
+
       <varlistentry id="app-psql-variables-hide-toast-compression">
         <term><varname>HIDE_TOAST_COMPRESSION</varname></term>
         <listitem>
diff --git a/doc/src/sgml/reference.sgml b/doc/src/sgml/reference.sgml
index a3b743e8c1..e1425c222f 100644
--- a/doc/src/sgml/reference.sgml
+++ b/doc/src/sgml/reference.sgml
@@ -36,6 +36,8 @@ <title>SQL Commands</title>
    &abort;
    &alterAggregate;
    &alterCollation;
+   &alterColumnEncryptionKey;
+   &alterColumnMasterKey;
    &alterConversion;
    &alterDatabase;
    &alterDefaultPrivileges;
@@ -90,6 +92,8 @@ <title>SQL Commands</title>
    &createAggregate;
    &createCast;
    &createCollation;
+   &createColumnEncryptionKey;
+   &createColumnMasterKey;
    &createConversion;
    &createDatabase;
    &createDomain;
@@ -137,6 +141,8 @@ <title>SQL Commands</title>
    &dropAggregate;
    &dropCast;
    &dropCollation;
+   &dropColumnEncryptionKey;
+   &dropColumnMasterKey;
    &dropConversion;
    &dropDatabase;
    &dropDomain;
diff --git a/src/backend/access/common/printsimple.c b/src/backend/access/common/printsimple.c
index ef818228ac..a00545080d 100644
--- a/src/backend/access/common/printsimple.c
+++ b/src/backend/access/common/printsimple.c
@@ -20,7 +20,9 @@
 
 #include "access/printsimple.h"
 #include "catalog/pg_type.h"
+#include "libpq/libpq-be.h"
 #include "libpq/pqformat.h"
+#include "miscadmin.h"
 #include "utils/builtins.h"
 
 /*
@@ -46,6 +48,11 @@ printsimple_startup(DestReceiver *self, int operation, TupleDesc tupdesc)
 		pq_sendint16(&buf, attr->attlen);
 		pq_sendint32(&buf, attr->atttypmod);
 		pq_sendint16(&buf, 0);	/* format code */
+		if (MyProcPort->column_encryption_enabled)
+		{
+			pq_sendint32(&buf, 0);	/* CEK */
+			pq_sendint32(&buf, 0);	/* CEK alg */
+		}
 	}
 
 	pq_endmessage(&buf);
diff --git a/src/backend/access/common/printtup.c b/src/backend/access/common/printtup.c
index 72faeb5dfa..eb93c4bb3f 100644
--- a/src/backend/access/common/printtup.c
+++ b/src/backend/access/common/printtup.c
@@ -15,13 +15,28 @@
  */
 #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/libpq-be.h"
 #include "libpq/pqformat.h"
+#include "miscadmin.h"
 #include "tcop/pquery.h"
+#include "utils/array.h"
+#include "utils/arrayaccess.h"
+#include "utils/builtins.h"
+#include "utils/fmgroids.h"
+#include "utils/inval.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 +166,156 @@ printtup_startup(DestReceiver *self, int operation, TupleDesc typeinfo)
 	 */
 }
 
+/*
+ * Send ColumnMasterKey message, unless it's already been sent in this session
+ * for this key.
+ */
+List	   *cmk_sent = NIL;
+
+static void
+cmk_change_cb(Datum arg, int cacheid, uint32 hashvalue)
+{
+	list_free(cmk_sent);
+	cmk_sent = NIL;
+}
+
+static void
+MaybeSendColumnMasterKeyMessage(Oid cmkid)
+{
+	HeapTuple	tuple;
+	Form_pg_colmasterkey cmkform;
+	Datum		datum;
+	bool		isnull;
+	StringInfoData buf;
+	static bool registered_inval = false;
+	MemoryContext oldcontext;
+
+	Assert(MyProcPort->column_encryption_enabled);
+
+	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_sendint32(&buf, cmkform->oid);
+	pq_sendstring(&buf, NameStr(cmkform->cmkname));
+	datum = SysCacheGetAttr(CMKOID, tuple, Anum_pg_colmasterkey_cmkrealm, &isnull);
+	Assert(!isnull);
+	pq_sendstring(&buf, TextDatumGetCString(datum));
+	pq_endmessage(&buf);
+
+	ReleaseSysCache(tuple);
+
+	if (!registered_inval)
+	{
+		CacheRegisterSyscacheCallback(CMKOID, cmk_change_cb, (Datum) 0);
+		registered_inval = true;
+	}
+
+	oldcontext = MemoryContextSwitchTo(TopMemoryContext);
+	cmk_sent = lappend_oid(cmk_sent, cmkid);
+	MemoryContextSwitchTo(oldcontext);
+}
+
+/*
+ * Send ColumnEncryptionKey message, unless it's already been sent in this
+ * session for this key.
+ */
+List	   *cek_sent = NIL;
+
+static void
+cek_change_cb(Datum arg, int cacheid, uint32 hashvalue)
+{
+	list_free(cek_sent);
+	cek_sent = NIL;
+}
+
+void
+MaybeSendColumnEncryptionKeyMessage(Oid attcek)
+{
+	HeapTuple	tuple;
+	ScanKeyData skey[1];
+	SysScanDesc sd;
+	Relation	rel;
+	bool		found = false;
+	static bool registered_inval = false;
+	MemoryContext oldcontext;
+
+	Assert(MyProcPort->column_encryption_enabled);
+
+	if (list_member_oid(cek_sent, attcek))
+		return;
+
+	/*
+	 * We really only need data from pg_colenckeydata, but before we scan
+	 * that, let's check that an entry exists in pg_colenckey, so that if
+	 * there are catalog inconsistencies, we can locate them better.
+	 */
+	if (!SearchSysCacheExists1(CEKOID, ObjectIdGetDatum(attcek)))
+		elog(ERROR, "cache lookup failed for column encryption key %u", attcek);
+
+	/*
+	 * Now scan pg_colenckeydata.
+	 */
+	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 ((tuple = systable_getnext(sd)))
+	{
+		Form_pg_colenckeydata ckdform = (Form_pg_colenckeydata) GETSTRUCT(tuple);
+		Datum		datum;
+		bool		isnull;
+		bytea	   *ba;
+		StringInfoData buf;
+
+		MaybeSendColumnMasterKeyMessage(ckdform->ckdcmkid);
+
+		datum = heap_getattr(tuple, Anum_pg_colenckeydata_ckdencval, RelationGetDescr(rel), &isnull);
+		Assert(!isnull);
+		ba = pg_detoast_datum_packed((bytea *) DatumGetPointer(datum));
+
+		pq_beginmessage(&buf, 'Y'); /* ColumnEncryptionKey */
+		pq_sendint32(&buf, ckdform->ckdcekid);
+		pq_sendint32(&buf, ckdform->ckdcmkid);
+		pq_sendint32(&buf, ckdform->ckdcmkalg);
+		pq_sendint32(&buf, VARSIZE_ANY_EXHDR(ba));
+		pq_sendbytes(&buf, VARDATA_ANY(ba), VARSIZE_ANY_EXHDR(ba));
+		pq_endmessage(&buf);
+
+		found = true;
+	}
+
+	/*
+	 * This is a user-facing message, because with ALTER it is possible to
+	 * delete all data entries for a CEK.
+	 */
+	if (!found)
+		ereport(ERROR,
+				errcode(ERRCODE_UNDEFINED_OBJECT),
+				errmsg("no data for column encryption key \"%s\"", get_cek_name(attcek, false)));
+
+	systable_endscan(sd);
+	table_close(rel, NoLock);
+
+	if (!registered_inval)
+	{
+		CacheRegisterSyscacheCallback(CEKDATAOID, cek_change_cb, (Datum) 0);
+		registered_inval = true;
+	}
+
+	oldcontext = MemoryContextSwitchTo(TopMemoryContext);
+	cek_sent = lappend_oid(cek_sent, attcek);
+	MemoryContextSwitchTo(oldcontext);
+}
+
 /*
  * SendRowDescriptionMessage --- send a RowDescription message to the frontend
  *
@@ -167,6 +332,7 @@ SendRowDescriptionMessage(StringInfo buf, TupleDesc typeinfo,
 						  List *targetlist, int16 *formats)
 {
 	int			natts = typeinfo->natts;
+	size_t		sz;
 	int			i;
 	ListCell   *tlist_item = list_head(targetlist);
 
@@ -183,14 +349,17 @@ SendRowDescriptionMessage(StringInfo buf, TupleDesc typeinfo,
 	 * Have to overestimate the size of the column-names, to account for
 	 * character set overhead.
 	 */
-	enlargeStringInfo(buf, (NAMEDATALEN * MAX_CONVERSION_GROWTH /* attname */
-							+ sizeof(Oid)	/* resorigtbl */
-							+ sizeof(AttrNumber)	/* resorigcol */
-							+ sizeof(Oid)	/* atttypid */
-							+ sizeof(int16) /* attlen */
-							+ sizeof(int32) /* attypmod */
-							+ sizeof(int16) /* format */
-							) * natts);
+	sz = (NAMEDATALEN * MAX_CONVERSION_GROWTH /* attname */
+		  + sizeof(Oid)	/* resorigtbl */
+		  + sizeof(AttrNumber)	/* resorigcol */
+		  + sizeof(Oid)	/* atttypid */
+		  + sizeof(int16) /* attlen */
+		  + sizeof(int32) /* attypmod */
+		  + sizeof(int16)); /* format */
+	if (MyProcPort->column_encryption_enabled)
+		sz += (sizeof(int32)		/* attcekid */
+			   + sizeof(int32));	/* attencalg */
+	enlargeStringInfo(buf, sz * natts);
 
 	for (i = 0; i < natts; ++i)
 	{
@@ -200,6 +369,8 @@ SendRowDescriptionMessage(StringInfo buf, TupleDesc typeinfo,
 		Oid			resorigtbl;
 		AttrNumber	resorigcol;
 		int16		format;
+		Oid			attcekid = InvalidOid;
+		int32		attencalg = 0;
 
 		/*
 		 * If column is a domain, send the base type and typmod instead.
@@ -231,6 +402,28 @@ SendRowDescriptionMessage(StringInfo buf, TupleDesc typeinfo,
 		else
 			format = 0;
 
+		if (MyProcPort->column_encryption_enabled && type_is_encrypted(atttypid))
+		{
+			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;
+			attcekid = orig_att->attcek;
+			attencalg = orig_att->attencalg;
+			ReleaseSysCache(tp);
+
+			/*
+			 * Encrypted types are always sent in binary when column
+			 * encryption is enabled.
+			 */
+			format = 1;
+		}
+
 		pq_writestring(buf, NameStr(att->attname));
 		pq_writeint32(buf, resorigtbl);
 		pq_writeint16(buf, resorigcol);
@@ -238,6 +431,11 @@ SendRowDescriptionMessage(StringInfo buf, TupleDesc typeinfo,
 		pq_writeint16(buf, att->attlen);
 		pq_writeint32(buf, atttypmod);
 		pq_writeint16(buf, format);
+		if (MyProcPort->column_encryption_enabled)
+		{
+			pq_writeint32(buf, attcekid);
+			pq_writeint32(buf, attencalg);
+		}
 	}
 
 	pq_endmessage_reuse(buf);
@@ -271,6 +469,13 @@ printtup_prepare_info(DR_printtup *myState, TupleDesc typeinfo, int numAttrs)
 		int16		format = (formats ? formats[i] : 0);
 		Form_pg_attribute attr = TupleDescAttr(typeinfo, i);
 
+		/*
+		 * Encrypted types are always sent in binary when column encryption is
+		 * enabled.
+		 */
+		if (MyProcPort->column_encryption_enabled && type_is_encrypted(attr->atttypid))
+			format = 1;
+
 		thisState->format = format;
 		if (format == 0)
 		{
diff --git a/src/backend/access/common/tupdesc.c b/src/backend/access/common/tupdesc.c
index 72a2c3d3db..b869739b43 100644
--- a/src/backend/access/common/tupdesc.c
+++ b/src/backend/access/common/tupdesc.c
@@ -459,6 +459,12 @@ 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->attencalg != attr2->attencalg)
+			return false;
 		if (attr1->attinhcount != attr2->attinhcount)
 			return false;
 		if (attr1->attcollation != attr2->attcollation)
@@ -629,6 +635,9 @@ TupleDescInitEntry(TupleDesc desc,
 	att->attgenerated = '\0';
 	att->attisdropped = false;
 	att->attislocal = true;
+	att->attcek = 0;
+	att->attrealtypid = 0;
+	att->attencalg = 0;
 	att->attinhcount = 0;
 	/* variable-length fields are not present in tupledescs */
 
@@ -690,6 +699,9 @@ TupleDescInitBuiltinEntry(TupleDesc desc,
 	att->attgenerated = '\0';
 	att->attisdropped = false;
 	att->attislocal = true;
+	att->attcek = 0;
+	att->attrealtypid = 0;
+	att->attencalg = 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 24bab58499..cc48069932 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 == PG_ENCRYPTED_DETOID))
 			 /* okay, allowed use of hashvarlena() */ ;
 		else
 			result = false;
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index a60107bf94..7b9575635b 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -72,7 +72,8 @@ CATALOG_HEADERS := \
 	pg_collation.h pg_parameter_acl.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/aclchk.c b/src/backend/catalog/aclchk.c
index c4232344aa..d835e84134 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -33,7 +33,9 @@
 #include "catalog/pg_authid.h"
 #include "catalog/pg_cast.h"
 #include "catalog/pg_class.h"
+#include "catalog/pg_colenckey.h"
 #include "catalog/pg_collation.h"
+#include "catalog/pg_colmasterkey.h"
 #include "catalog/pg_conversion.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_default_acl.h"
@@ -2798,6 +2800,9 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
 					case OBJECT_AMPROC:
 					case OBJECT_ATTRIBUTE:
 					case OBJECT_CAST:
+					case OBJECT_CEK:
+					case OBJECT_CEKDATA:
+					case OBJECT_CMK:
 					case OBJECT_DEFAULT:
 					case OBJECT_DEFACL:
 					case OBJECT_DOMCONSTRAINT:
@@ -2828,6 +2833,12 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
 					case OBJECT_AGGREGATE:
 						msg = gettext_noop("must be owner of aggregate %s");
 						break;
+					case OBJECT_CEK:
+						msg = gettext_noop("must be owner of column encryption key %s");
+						break;
+					case OBJECT_CMK:
+						msg = gettext_noop("must be owner of column master key %s");
+						break;
 					case OBJECT_COLLATION:
 						msg = gettext_noop("must be owner of collation %s");
 						break;
@@ -2938,6 +2949,7 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
 					case OBJECT_AMPROC:
 					case OBJECT_ATTRIBUTE:
 					case OBJECT_CAST:
+					case OBJECT_CEKDATA:
 					case OBJECT_DEFAULT:
 					case OBJECT_DEFACL:
 					case OBJECT_DOMCONSTRAINT:
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 7acf654bf8..b963e660e9 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -30,7 +30,10 @@
 #include "catalog/pg_authid.h"
 #include "catalog/pg_auth_members.h"
 #include "catalog/pg_cast.h"
+#include "catalog/pg_colenckey.h"
+#include "catalog/pg_colenckeydata.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"
@@ -153,6 +156,9 @@ static const Oid object_classes[] = {
 	TypeRelationId,				/* OCLASS_TYPE */
 	CastRelationId,				/* OCLASS_CAST */
 	CollationRelationId,		/* OCLASS_COLLATION */
+	ColumnEncKeyRelationId,		/* OCLASS_CEK */
+	ColumnEncKeyDataRelationId,	/* OCLASS_CEKDATA */
+	ColumnMasterKeyRelationId,	/* OCLASS_CMK */
 	ConstraintRelationId,		/* OCLASS_CONSTRAINT */
 	ConversionRelationId,		/* OCLASS_CONVERSION */
 	AttrDefaultRelationId,		/* OCLASS_DEFAULT */
@@ -1493,6 +1499,9 @@ doDeletion(const ObjectAddress *object, int flags)
 
 		case OCLASS_CAST:
 		case OCLASS_COLLATION:
+		case OCLASS_CEK:
+		case OCLASS_CEKDATA:
+		case OCLASS_CMK:
 		case OCLASS_CONVERSION:
 		case OCLASS_LANGUAGE:
 		case OCLASS_OPCLASS:
@@ -2859,6 +2868,15 @@ getObjectClass(const ObjectAddress *object)
 		case CollationRelationId:
 			return OCLASS_COLLATION;
 
+		case ColumnEncKeyRelationId:
+			return OCLASS_CEK;
+
+		case ColumnEncKeyDataRelationId:
+			return OCLASS_CEKDATA;
+
+		case ColumnMasterKeyRelationId:
+			return OCLASS_CMK;
+
 		case ConstraintRelationId:
 			return OCLASS_CONSTRAINT;
 
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 4f006820b8..63b1e180c0 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -42,6 +42,7 @@
 #include "catalog/partition.h"
 #include "catalog/pg_am.h"
 #include "catalog/pg_attrdef.h"
+#include "catalog/pg_colenckey.h"
 #include "catalog/pg_collation.h"
 #include "catalog/pg_constraint.h"
 #include "catalog/pg_foreign_table.h"
@@ -511,7 +512,14 @@ CheckAttributeNamesTypes(TupleDesc tupdesc, char relkind,
 						   TupleDescAttr(tupdesc, i)->atttypid,
 						   TupleDescAttr(tupdesc, i)->attcollation,
 						   NIL, /* assume we're creating a new rowtype */
-						   flags);
+						   flags |
+						   /*
+							* Allow encrypted types if CEK has been provided,
+							* which means this type has been internally
+							* generated.  We don't want to allow explicitly
+							* using these types.
+							*/
+						   (TupleDescAttr(tupdesc, i)->attcek ? CHKATYPE_ENCRYPTED : 0));
 	}
 }
 
@@ -653,6 +661,21 @@ CheckAttributeType(const char *attname,
 						   flags);
 	}
 
+	/*
+	 * Encrypted types are not allowed explictly as column types.  Most
+	 * callers run this check before transforming the column definition to use
+	 * the encrypted types.  Some callers call it again after; those should
+	 * set the CHKATYPE_ENCRYPTED to let this pass.
+	 */
+	if (type_is_encrypted(atttypid) && !(flags & CHKATYPE_ENCRYPTED))
+	{
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+				 errbacktrace(),
+				 errmsg("column \"%s\" has internal type %s",
+						attname, format_type_be(atttypid))));
+	}
+
 	/*
 	 * This might not be strictly invalid per SQL standard, but it is pretty
 	 * useless, and it cannot be dumped, so we must disallow it.
@@ -749,6 +772,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)
@@ -840,6 +866,20 @@ AddNewAttributeTuples(Oid new_rel_oid,
 							 tupdesc->attrs[i].attcollation);
 			recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
 		}
+
+		if (OidIsValid(tupdesc->attrs[i].attcek))
+		{
+			ObjectAddressSet(referenced, ColumnEncKeyRelationId,
+							 tupdesc->attrs[i].attcek);
+			recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+		}
+
+		if (OidIsValid(tupdesc->attrs[i].attrealtypid))
+		{
+			ObjectAddressSet(referenced, TypeRelationId,
+							 tupdesc->attrs[i].attrealtypid);
+			recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+		}
 	}
 
 	/*
diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index 14e57adee2..00f914bc5f 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -26,6 +26,8 @@
 #include "catalog/dependency.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_authid.h"
+#include "catalog/pg_colenckey.h"
+#include "catalog/pg_colmasterkey.h"
 #include "catalog/pg_collation.h"
 #include "catalog/pg_conversion.h"
 #include "catalog/pg_database.h"
@@ -1997,6 +1999,254 @@ OpfamilyIsVisible(Oid opfid)
 	return visible;
 }
 
+/*
+ * get_cek_oid - find a CEK by possibly qualified name
+ */
+Oid
+get_cek_oid(List *names, bool missing_ok)
+{
+	char	   *schemaname;
+	char	   *cekname;
+	Oid			namespaceId;
+	Oid			cekoid = InvalidOid;
+	ListCell   *l;
+
+	/* deconstruct the name list */
+	DeconstructQualifiedName(names, &schemaname, &cekname);
+
+	if (schemaname)
+	{
+		/* use exact schema given */
+		namespaceId = LookupExplicitNamespace(schemaname, missing_ok);
+		if (missing_ok && !OidIsValid(namespaceId))
+			cekoid = InvalidOid;
+		else
+			cekoid = GetSysCacheOid2(CEKNAMENSP, Anum_pg_colenckey_oid,
+									 PointerGetDatum(cekname),
+									 ObjectIdGetDatum(namespaceId));
+	}
+	else
+	{
+		/* search for it in search path */
+		recomputeNamespacePath();
+
+		foreach(l, activeSearchPath)
+		{
+			namespaceId = lfirst_oid(l);
+
+			if (namespaceId == myTempNamespace)
+				continue;		/* do not look in temp namespace */
+
+			cekoid = GetSysCacheOid2(CEKNAMENSP, Anum_pg_colenckey_oid,
+									 PointerGetDatum(cekname),
+									 ObjectIdGetDatum(namespaceId));
+			if (OidIsValid(cekoid))
+				return cekoid;
+		}
+	}
+
+	/* Not found in path */
+	if (!OidIsValid(cekoid) && !missing_ok)
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_OBJECT),
+				 errmsg("column encryption key \"%s\" does not exist",
+						NameListToString(names))));
+	return cekoid;
+}
+
+/*
+ * CEKIsVisible
+ *		Determine whether a CEK (identified by OID) is visible in the
+ *		current search path.  Visible means "would be found by searching
+ *		for the unqualified parser name".
+ */
+bool
+CEKIsVisible(Oid cekid)
+{
+	HeapTuple	tup;
+	Form_pg_colenckey form;
+	Oid			namespace;
+	bool		visible;
+
+	tup = SearchSysCache1(CEKOID, ObjectIdGetDatum(cekid));
+	if (!HeapTupleIsValid(tup))
+		elog(ERROR, "cache lookup failed for column encryption key %u", cekid);
+	form = (Form_pg_colenckey) GETSTRUCT(tup);
+
+	recomputeNamespacePath();
+
+	/*
+	 * Quick check: if it ain't in the path at all, it ain't visible. Items in
+	 * the system namespace are surely in the path and so we needn't even do
+	 * list_member_oid() for them.
+	 */
+	namespace = form->ceknamespace;
+	if (namespace != PG_CATALOG_NAMESPACE &&
+		!list_member_oid(activeSearchPath, namespace))
+		visible = false;
+	else
+	{
+		/*
+		 * If it is in the path, it might still not be visible; it could be
+		 * hidden by another parser of the same name earlier in the path. So
+		 * we must do a slow check for conflicting CEKs.
+		 */
+		char	   *name = NameStr(form->cekname);
+		ListCell   *l;
+
+		visible = false;
+		foreach(l, activeSearchPath)
+		{
+			Oid			namespaceId = lfirst_oid(l);
+
+			if (namespaceId == myTempNamespace)
+				continue;		/* do not look in temp namespace */
+
+			if (namespaceId == namespace)
+			{
+				/* Found it first in path */
+				visible = true;
+				break;
+			}
+			if (SearchSysCacheExists2(CEKNAMENSP,
+									  PointerGetDatum(name),
+									  ObjectIdGetDatum(namespaceId)))
+			{
+				/* Found something else first in path */
+				break;
+			}
+		}
+	}
+
+	ReleaseSysCache(tup);
+
+	return visible;
+}
+
+/*
+ * get_cmk_oid - find a CMK by possibly qualified name
+ */
+Oid
+get_cmk_oid(List *names, bool missing_ok)
+{
+	char	   *schemaname;
+	char	   *cmkname;
+	Oid			namespaceId;
+	Oid			cmkoid = InvalidOid;
+	ListCell   *l;
+
+	/* deconstruct the name list */
+	DeconstructQualifiedName(names, &schemaname, &cmkname);
+
+	if (schemaname)
+	{
+		/* use exact schema given */
+		namespaceId = LookupExplicitNamespace(schemaname, missing_ok);
+		if (missing_ok && !OidIsValid(namespaceId))
+			cmkoid = InvalidOid;
+		else
+			cmkoid = GetSysCacheOid2(CMKNAMENSP, Anum_pg_colmasterkey_oid,
+									 PointerGetDatum(cmkname),
+									 ObjectIdGetDatum(namespaceId));
+	}
+	else
+	{
+		/* search for it in search path */
+		recomputeNamespacePath();
+
+		foreach(l, activeSearchPath)
+		{
+			namespaceId = lfirst_oid(l);
+
+			if (namespaceId == myTempNamespace)
+				continue;		/* do not look in temp namespace */
+
+			cmkoid = GetSysCacheOid2(CMKNAMENSP, Anum_pg_colmasterkey_oid,
+									 PointerGetDatum(cmkname),
+									 ObjectIdGetDatum(namespaceId));
+			if (OidIsValid(cmkoid))
+				return cmkoid;
+		}
+	}
+
+	/* Not found in path */
+	if (!OidIsValid(cmkoid) && !missing_ok)
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_OBJECT),
+				 errmsg("column master key \"%s\" does not exist",
+						NameListToString(names))));
+	return cmkoid;
+}
+
+/*
+ * CMKIsVisible
+ *		Determine whether a CMK (identified by OID) is visible in the
+ *		current search path.  Visible means "would be found by searching
+ *		for the unqualified parser name".
+ */
+bool
+CMKIsVisible(Oid cmkid)
+{
+	HeapTuple	tup;
+	Form_pg_colmasterkey form;
+	Oid			namespace;
+	bool		visible;
+
+	tup = SearchSysCache1(CMKOID, ObjectIdGetDatum(cmkid));
+	if (!HeapTupleIsValid(tup))
+		elog(ERROR, "cache lookup failed for column master key %u", cmkid);
+	form = (Form_pg_colmasterkey) GETSTRUCT(tup);
+
+	recomputeNamespacePath();
+
+	/*
+	 * Quick check: if it ain't in the path at all, it ain't visible. Items in
+	 * the system namespace are surely in the path and so we needn't even do
+	 * list_member_oid() for them.
+	 */
+	namespace = form->cmknamespace;
+	if (namespace != PG_CATALOG_NAMESPACE &&
+		!list_member_oid(activeSearchPath, namespace))
+		visible = false;
+	else
+	{
+		/*
+		 * If it is in the path, it might still not be visible; it could be
+		 * hidden by another parser of the same name earlier in the path. So
+		 * we must do a slow check for conflicting CMKs.
+		 */
+		char	   *name = NameStr(form->cmkname);
+		ListCell   *l;
+
+		visible = false;
+		foreach(l, activeSearchPath)
+		{
+			Oid			namespaceId = lfirst_oid(l);
+
+			if (namespaceId == myTempNamespace)
+				continue;		/* do not look in temp namespace */
+
+			if (namespaceId == namespace)
+			{
+				/* Found it first in path */
+				visible = true;
+				break;
+			}
+			if (SearchSysCacheExists2(CMKNAMENSP,
+									  PointerGetDatum(name),
+									  ObjectIdGetDatum(namespaceId)))
+			{
+				/* Found something else first in path */
+				break;
+			}
+		}
+	}
+
+	ReleaseSysCache(tup);
+
+	return visible;
+}
+
 /*
  * lookup_collation
  *		If there's a collation of the given name/namespace, and it works
@@ -4567,6 +4817,28 @@ pg_opfamily_is_visible(PG_FUNCTION_ARGS)
 	PG_RETURN_BOOL(OpfamilyIsVisible(oid));
 }
 
+Datum
+pg_cek_is_visible(PG_FUNCTION_ARGS)
+{
+	Oid			oid = PG_GETARG_OID(0);
+
+	if (!SearchSysCacheExists1(CEKOID, ObjectIdGetDatum(oid)))
+		PG_RETURN_NULL();
+
+	PG_RETURN_BOOL(CEKIsVisible(oid));
+}
+
+Datum
+pg_cmk_is_visible(PG_FUNCTION_ARGS)
+{
+	Oid			oid = PG_GETARG_OID(0);
+
+	if (!SearchSysCacheExists1(CMKOID, ObjectIdGetDatum(oid)))
+		PG_RETURN_NULL();
+
+	PG_RETURN_BOOL(CMKIsVisible(oid));
+}
+
 Datum
 pg_collation_is_visible(PG_FUNCTION_ARGS)
 {
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 25c50d66fd..35dfc6fb3c 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -29,7 +29,10 @@
 #include "catalog/pg_authid.h"
 #include "catalog/pg_auth_members.h"
 #include "catalog/pg_cast.h"
+#include "catalog/pg_colenckey.h"
+#include "catalog/pg_colenckeydata.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"
@@ -191,6 +194,48 @@ static const ObjectPropertyType ObjectProperty[] =
 		OBJECT_COLLATION,
 		true
 	},
+	{
+		"column encryption key",
+		ColumnEncKeyRelationId,
+		ColumnEncKeyOidIndexId,
+		CEKOID,
+		CEKNAMENSP,
+		Anum_pg_colenckey_oid,
+		Anum_pg_colenckey_cekname,
+		Anum_pg_colenckey_ceknamespace,
+		Anum_pg_colenckey_cekowner,
+		InvalidAttrNumber,
+		OBJECT_CEK,
+		true
+	},
+	{
+		"column encryption key data",
+		ColumnEncKeyDataRelationId,
+		ColumnEncKeyDataOidIndexId,
+		CEKDATAOID,
+		-1,
+		Anum_pg_colenckeydata_oid,
+		InvalidAttrNumber,
+		InvalidAttrNumber,
+		InvalidAttrNumber,
+		InvalidAttrNumber,
+		-1,
+		false
+	},
+	{
+		"column master key",
+		ColumnMasterKeyRelationId,
+		ColumnMasterKeyOidIndexId,
+		CMKOID,
+		CMKNAMENSP,
+		Anum_pg_colmasterkey_oid,
+		Anum_pg_colmasterkey_cmkname,
+		Anum_pg_colmasterkey_cmknamespace,
+		Anum_pg_colmasterkey_cmkowner,
+		InvalidAttrNumber,
+		OBJECT_CMK,
+		true
+	},
 	{
 		"constraint",
 		ConstraintRelationId,
@@ -723,6 +768,18 @@ static const struct object_type_map
 	{
 		"collation", OBJECT_COLLATION
 	},
+	/* OCLASS_CEK */
+	{
+		"column encryption key", OBJECT_CEK
+	},
+	/* OCLASS_CEKDATA */
+	{
+		"column encryption key data", OBJECT_CEKDATA
+	},
+	/* OCLASS_CMK */
+	{
+		"column master key", OBJECT_CMK
+	},
 	/* OCLASS_CONSTRAINT */
 	{
 		"table constraint", OBJECT_TABCONSTRAINT
@@ -1029,6 +1086,16 @@ get_object_address(ObjectType objtype, Node *object,
 					address.objectSubId = 0;
 				}
 				break;
+			case OBJECT_CEK:
+				address.classId = ColumnEncKeyRelationId;
+				address.objectId = get_cek_oid(castNode(List, object), missing_ok);
+				address.objectSubId = 0;
+				break;
+			case OBJECT_CMK:
+				address.classId = ColumnMasterKeyRelationId;
+				address.objectId = get_cmk_oid(castNode(List, object), missing_ok);
+				address.objectSubId = 0;
+				break;
 			case OBJECT_DATABASE:
 			case OBJECT_EXTENSION:
 			case OBJECT_TABLESPACE:
@@ -1108,6 +1175,21 @@ get_object_address(ObjectType objtype, Node *object,
 					address.objectSubId = 0;
 				}
 				break;
+			case OBJECT_CEKDATA:
+				{
+					List	   *cekname = linitial_node(List, castNode(List, object));
+					List	   *cmkname = lsecond_node(List, castNode(List, object));
+					Oid			cekid;
+					Oid			cmkid;
+
+					cekid = get_cek_oid(cekname, missing_ok);
+					cmkid = get_cmk_oid(cmkname, missing_ok);
+
+					address.classId = ColumnEncKeyDataRelationId;
+					address.objectId = get_cekdata_oid(cekid, cmkid, missing_ok);
+					address.objectSubId = 0;
+				}
+				break;
 			case OBJECT_TRANSFORM:
 				{
 					TypeName   *typename = linitial_node(TypeName, castNode(List, object));
@@ -2311,6 +2393,8 @@ pg_get_object_address(PG_FUNCTION_ARGS)
 		case OBJECT_FOREIGN_TABLE:
 		case OBJECT_COLUMN:
 		case OBJECT_ATTRIBUTE:
+		case OBJECT_CEK:
+		case OBJECT_CMK:
 		case OBJECT_COLLATION:
 		case OBJECT_CONVERSION:
 		case OBJECT_STATISTIC_EXT:
@@ -2367,6 +2451,7 @@ pg_get_object_address(PG_FUNCTION_ARGS)
 			break;
 		case OBJECT_AMOP:
 		case OBJECT_AMPROC:
+		case OBJECT_CEKDATA:
 			objnode = (Node *) list_make2(name, args);
 			break;
 		case OBJECT_FUNCTION:
@@ -2489,6 +2574,8 @@ check_object_ownership(Oid roleid, ObjectType objtype, ObjectAddress address,
 				aclcheck_error(ACLCHECK_NOT_OWNER, objtype,
 							   strVal(object));
 			break;
+		case OBJECT_CEK:
+		case OBJECT_CMK:
 		case OBJECT_COLLATION:
 		case OBJECT_CONVERSION:
 		case OBJECT_OPCLASS:
@@ -2575,6 +2662,7 @@ check_object_ownership(Oid roleid, ObjectType objtype, ObjectAddress address,
 			break;
 		case OBJECT_AMOP:
 		case OBJECT_AMPROC:
+		case OBJECT_CEKDATA:
 		case OBJECT_DEFAULT:
 		case OBJECT_DEFACL:
 		case OBJECT_PUBLICATION_NAMESPACE:
@@ -3040,6 +3128,92 @@ getObjectDescription(const ObjectAddress *object, bool missing_ok)
 				break;
 			}
 
+		case OCLASS_CEK:
+			{
+				HeapTuple	tup;
+				Form_pg_colenckey form;
+				char	   *nspname;
+
+				tup = SearchSysCache1(CEKOID,
+									  ObjectIdGetDatum(object->objectId));
+				if (!HeapTupleIsValid(tup))
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for column encryption key %u",
+							 object->objectId);
+					break;
+				}
+
+				form = (Form_pg_colenckey) GETSTRUCT(tup);
+
+				/* Qualify the name if not visible in search path */
+				if (CEKIsVisible(object->objectId))
+					nspname = NULL;
+				else
+					nspname = get_namespace_name(form->ceknamespace);
+
+				appendStringInfo(&buffer, _("column encryption key %s"),
+								 quote_qualified_identifier(nspname,
+															NameStr(form->cekname)));
+				ReleaseSysCache(tup);
+				break;
+			}
+
+		case OCLASS_CEKDATA:
+			{
+				HeapTuple	tup;
+				Form_pg_colenckeydata cekdata;
+
+				tup = SearchSysCache1(CEKDATAOID, ObjectIdGetDatum(object->objectId));
+				if (!HeapTupleIsValid(tup))
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for column encryption key data %u",
+							 object->objectId);
+					break;
+				}
+
+				cekdata = (Form_pg_colenckeydata) GETSTRUCT(tup);
+
+				appendStringInfo(&buffer, _("column encryption key data of %s for %s"),
+								 getObjectDescription(&(ObjectAddress){ColumnEncKeyRelationId, cekdata->ckdcekid}, false),
+								 getObjectDescription(&(ObjectAddress){ColumnMasterKeyRelationId, cekdata->ckdcmkid}, false));
+
+				ReleaseSysCache(tup);
+				break;
+			}
+
+		case OCLASS_CMK:
+			{
+				HeapTuple	tup;
+				Form_pg_colmasterkey form;
+				char	   *nspname;
+
+				tup = SearchSysCache1(CMKOID,
+									  ObjectIdGetDatum(object->objectId));
+				if (!HeapTupleIsValid(tup))
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for column encryption key %u",
+							 object->objectId);
+					break;
+				}
+
+				form = (Form_pg_colmasterkey) GETSTRUCT(tup);
+
+				/* Qualify the name if not visible in search path */
+				if (CMKIsVisible(object->objectId))
+					nspname = NULL;
+				else
+					nspname = get_namespace_name(form->cmknamespace);
+
+				appendStringInfo(&buffer, _("column master key %s"),
+								 quote_qualified_identifier(nspname,
+															NameStr(form->cmkname)));
+				ReleaseSysCache(tup);
+				break;
+			}
+
 		case OCLASS_CONSTRAINT:
 			{
 				HeapTuple	conTup;
@@ -4440,6 +4614,18 @@ getObjectTypeDescription(const ObjectAddress *object, bool missing_ok)
 			appendStringInfoString(&buffer, "collation");
 			break;
 
+		case OCLASS_CEK:
+			appendStringInfoString(&buffer, "column encryption key");
+			break;
+
+		case OCLASS_CEKDATA:
+			appendStringInfoString(&buffer, "column encryption key data");
+			break;
+
+		case OCLASS_CMK:
+			appendStringInfoString(&buffer, "column master key");
+			break;
+
 		case OCLASS_CONSTRAINT:
 			getConstraintTypeDescription(&buffer, object->objectId,
 										 missing_ok);
@@ -4905,6 +5091,108 @@ getObjectIdentityParts(const ObjectAddress *object,
 				break;
 			}
 
+		case OCLASS_CEK:
+			{
+				HeapTuple	tup;
+				Form_pg_colenckey form;
+				char	   *schema;
+
+				tup = SearchSysCache1(CEKOID, ObjectIdGetDatum(object->objectId));
+				if (!HeapTupleIsValid(tup))
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for column encryption key %u",
+							 object->objectId);
+					break;
+				}
+				form = (Form_pg_colenckey) GETSTRUCT(tup);
+				schema = get_namespace_name_or_temp(form->ceknamespace);
+				appendStringInfoString(&buffer,
+									   quote_qualified_identifier(schema,
+																  NameStr(form->cekname)));
+				if (objname)
+					*objname = list_make2(schema, pstrdup(NameStr(form->cekname)));
+				ReleaseSysCache(tup);
+				break;
+			}
+
+		case OCLASS_CEKDATA:
+			{
+				HeapTuple	tup;
+				Form_pg_colenckeydata form;
+
+				tup = SearchSysCache1(CEKDATAOID, ObjectIdGetDatum(object->objectId));
+				if (!HeapTupleIsValid(tup))
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for column encryption key data %u",
+							 object->objectId);
+					break;
+				}
+				form = (Form_pg_colenckeydata) GETSTRUCT(tup);
+				appendStringInfo(&buffer,
+								 "of %s for %s",
+								 getObjectIdentityParts(&(ObjectAddress){ColumnEncKeyRelationId, form->ckdcekid}, NULL, NULL, false),
+								 getObjectIdentityParts(&(ObjectAddress){ColumnMasterKeyRelationId, form->ckdcmkid}, NULL, NULL, false));
+
+				if (objname)
+				{
+					HeapTuple	tup2;
+					Form_pg_colenckey form2;
+					char	   *schema;
+
+					tup2 = SearchSysCache1(CEKOID, ObjectIdGetDatum(form->ckdcekid));
+					if (!HeapTupleIsValid(tup2))
+						elog(ERROR, "cache lookup failed for column encryption key %u", form->ckdcekid);
+					form2 = (Form_pg_colenckey) GETSTRUCT(tup2);
+					schema = get_namespace_name_or_temp(form2->ceknamespace);
+					*objname = list_make2(schema, pstrdup(NameStr(form2->cekname)));
+					ReleaseSysCache(tup2);
+				}
+				if (objargs)
+				{
+					HeapTuple	tup2;
+					Form_pg_colmasterkey form2;
+					char	   *schema;
+
+					tup2 = SearchSysCache1(CMKOID, ObjectIdGetDatum(form->ckdcmkid));
+					if (!HeapTupleIsValid(tup2))
+						elog(ERROR, "cache lookup failed for column master key %u", form->ckdcmkid);
+					form2 = (Form_pg_colmasterkey) GETSTRUCT(tup2);
+					schema = get_namespace_name_or_temp(form2->cmknamespace);
+					if (objargs)
+						*objargs = list_make2(schema, pstrdup(NameStr(form2->cmkname)));
+					ReleaseSysCache(tup2);
+				}
+				ReleaseSysCache(tup);
+				break;
+			}
+
+		case OCLASS_CMK:
+			{
+				HeapTuple	tup;
+				Form_pg_colmasterkey form;
+				char	   *schema;
+
+				tup = SearchSysCache1(CMKOID, ObjectIdGetDatum(object->objectId));
+				if (!HeapTupleIsValid(tup))
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for column master key %u",
+							 object->objectId);
+					break;
+				}
+				form = (Form_pg_colmasterkey) GETSTRUCT(tup);
+				schema = get_namespace_name_or_temp(form->cmknamespace);
+				appendStringInfoString(&buffer,
+									   quote_qualified_identifier(schema,
+																  NameStr(form->cmkname)));
+				if (objname)
+					*objname = list_make2(schema, pstrdup(NameStr(form->cmkname)));
+				ReleaseSysCache(tup);
+				break;
+			}
+
 		case OCLASS_CONSTRAINT:
 			{
 				HeapTuple	conTup;
diff --git a/src/backend/commands/Makefile b/src/backend/commands/Makefile
index 48f7348f91..69f6175c60 100644
--- a/src/backend/commands/Makefile
+++ b/src/backend/commands/Makefile
@@ -19,6 +19,7 @@ OBJS = \
 	analyze.o \
 	async.o \
 	cluster.o \
+	colenccmds.o \
 	collationcmds.o \
 	comment.o \
 	constraint.o \
diff --git a/src/backend/commands/alter.c b/src/backend/commands/alter.c
index bea51b3af1..59c23e9ef8 100644
--- a/src/backend/commands/alter.c
+++ b/src/backend/commands/alter.c
@@ -22,7 +22,9 @@
 #include "catalog/indexing.h"
 #include "catalog/namespace.h"
 #include "catalog/objectaccess.h"
+#include "catalog/pg_colenckey.h"
 #include "catalog/pg_collation.h"
+#include "catalog/pg_colmasterkey.h"
 #include "catalog/pg_conversion.h"
 #include "catalog/pg_event_trigger.h"
 #include "catalog/pg_foreign_data_wrapper.h"
@@ -118,6 +120,12 @@ report_namespace_conflict(Oid classId, const char *name, Oid nspOid)
 
 	switch (classId)
 	{
+		case ColumnEncKeyRelationId:
+			msgfmt = gettext_noop("column encryption key \"%s\" already exists in schema \"%s\"");
+			break;
+		case ColumnMasterKeyRelationId:
+			msgfmt = gettext_noop("column master key \"%s\" already exists in schema \"%s\"");
+			break;
 		case ConversionRelationId:
 			Assert(OidIsValid(nspOid));
 			msgfmt = gettext_noop("conversion \"%s\" already exists in schema \"%s\"");
@@ -379,6 +387,8 @@ ExecRenameStmt(RenameStmt *stmt)
 			return RenameType(stmt);
 
 		case OBJECT_AGGREGATE:
+		case OBJECT_CEK:
+		case OBJECT_CMK:
 		case OBJECT_COLLATION:
 		case OBJECT_CONVERSION:
 		case OBJECT_EVENT_TRIGGER:
@@ -527,6 +537,8 @@ ExecAlterObjectSchemaStmt(AlterObjectSchemaStmt *stmt,
 
 			/* generic code path */
 		case OBJECT_AGGREGATE:
+		case OBJECT_CEK:
+		case OBJECT_CMK:
 		case OBJECT_COLLATION:
 		case OBJECT_CONVERSION:
 		case OBJECT_FUNCTION:
@@ -643,6 +655,9 @@ AlterObjectNamespace_oid(Oid classId, Oid objid, Oid nspOid,
 			break;
 
 		case OCLASS_CAST:
+		case OCLASS_CEK:
+		case OCLASS_CEKDATA:
+		case OCLASS_CMK:
 		case OCLASS_CONSTRAINT:
 		case OCLASS_DEFAULT:
 		case OCLASS_LANGUAGE:
@@ -876,6 +891,8 @@ ExecAlterOwnerStmt(AlterOwnerStmt *stmt)
 
 			/* Generic cases */
 		case OBJECT_AGGREGATE:
+		case OBJECT_CEK:
+		case OBJECT_CMK:
 		case OBJECT_COLLATION:
 		case OBJECT_CONVERSION:
 		case OBJECT_FUNCTION:
diff --git a/src/backend/commands/colenccmds.c b/src/backend/commands/colenccmds.c
new file mode 100644
index 0000000000..0bb4948672
--- /dev/null
+++ b/src/backend/commands/colenccmds.c
@@ -0,0 +1,427 @@
+/*-------------------------------------------------------------------------
+ *
+ * colenccmds.c
+ *	  column-encryption-related commands support code
+ *
+ * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *	  src/backend/commands/colenccmds.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/htup_details.h"
+#include "access/table.h"
+#include "catalog/catalog.h"
+#include "catalog/dependency.h"
+#include "catalog/indexing.h"
+#include "catalog/namespace.h"
+#include "catalog/objectaccess.h"
+#include "catalog/pg_colenckey.h"
+#include "catalog/pg_colenckeydata.h"
+#include "catalog/pg_colmasterkey.h"
+#include "catalog/pg_namespace.h"
+#include "commands/colenccmds.h"
+#include "commands/dbcommands.h"
+#include "commands/defrem.h"
+#include "common/colenc.h"
+#include "miscadmin.h"
+#include "utils/acl.h"
+#include "utils/builtins.h"
+#include "utils/lsyscache.h"
+#include "utils/rel.h"
+#include "utils/syscache.h"
+
+static void
+parse_cek_attributes(ParseState *pstate, List *definition, Oid *cmkoid_p, int *alg_p, char **encval_p)
+{
+	ListCell   *lc;
+	DefElem    *cmkEl = NULL;
+	DefElem    *algEl = NULL;
+	DefElem    *encvalEl = NULL;
+	Oid			cmkoid = InvalidOid;
+	int			alg = 0;
+	char	   *encval = NULL;
+
+	Assert(cmkoid_p);
+
+	foreach(lc, definition)
+	{
+		DefElem    *defel = lfirst_node(DefElem, lc);
+		DefElem   **defelp;
+
+		if (strcmp(defel->defname, "column_master_key") == 0)
+			defelp = &cmkEl;
+		else if (strcmp(defel->defname, "algorithm") == 0)
+			defelp = &algEl;
+		else if (strcmp(defel->defname, "encrypted_value") == 0)
+			defelp = &encvalEl;
+		else
+		{
+			ereport(ERROR,
+					(errcode(ERRCODE_SYNTAX_ERROR),
+					 errmsg("column encryption key attribute \"%s\" not recognized",
+							defel->defname),
+					 parser_errposition(pstate, defel->location)));
+		}
+		if (*defelp != NULL)
+			errorConflictingDefElem(defel, pstate);
+		*defelp = defel;
+	}
+
+	if (cmkEl)
+	{
+		List	   *val = defGetQualifiedName(cmkEl);
+
+		cmkoid = get_cmk_oid(val, false);
+	}
+	else
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("attribute \"%s\" must be specified",
+						"column_master_key")));
+
+	if (algEl)
+	{
+		char	   *val = defGetString(algEl);
+
+		if (!alg_p)
+			ereport(ERROR,
+					(errcode(ERRCODE_SYNTAX_ERROR),
+					 errmsg("attribute \"%s\" must not be specified",
+							"algorithm")));
+
+		alg = get_cmkalg_num(val);
+		if (!alg)
+			ereport(ERROR,
+					errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+					errmsg("unrecognized encryption algorithm: %s", val));
+	}
+	else
+	{
+		if (alg_p)
+			ereport(ERROR,
+					(errcode(ERRCODE_SYNTAX_ERROR),
+					 errmsg("attribute \"%s\" must be specified",
+							"algorithm")));
+	}
+
+	if (encvalEl)
+	{
+		if (!encval_p)
+			ereport(ERROR,
+					(errcode(ERRCODE_SYNTAX_ERROR),
+					 errmsg("attribute \"%s\" must not be specified",
+							"encrypted_value")));
+
+		encval = defGetString(encvalEl);
+	}
+	else
+	{
+		if (encval_p)
+			ereport(ERROR,
+					(errcode(ERRCODE_SYNTAX_ERROR),
+					 errmsg("attribute \"%s\" must be specified",
+							"encrypted_value")));
+	}
+
+	*cmkoid_p = cmkoid;
+	if (alg_p)
+		*alg_p = alg;
+	if (encval_p)
+		*encval_p = encval;
+}
+
+static void
+insert_cekdata_record(Oid cekoid, Oid cmkoid, int alg, char *encval)
+{
+	Oid			cekdataoid;
+	Relation	rel;
+	Datum		values[Natts_pg_colenckeydata] = {0};
+	bool		nulls[Natts_pg_colenckeydata] = {0};
+	HeapTuple	tup;
+	ObjectAddress myself;
+	ObjectAddress other;
+
+	rel = table_open(ColumnEncKeyDataRelationId, RowExclusiveLock);
+
+	cekdataoid = GetNewOidWithIndex(rel, ColumnEncKeyDataOidIndexId, Anum_pg_colenckeydata_oid);
+	values[Anum_pg_colenckeydata_oid - 1] = ObjectIdGetDatum(cekdataoid);
+	values[Anum_pg_colenckeydata_ckdcekid - 1] = ObjectIdGetDatum(cekoid);
+	values[Anum_pg_colenckeydata_ckdcmkid - 1] = ObjectIdGetDatum(cmkoid);
+	values[Anum_pg_colenckeydata_ckdcmkalg - 1] = Int32GetDatum(alg);
+	values[Anum_pg_colenckeydata_ckdencval - 1] = DirectFunctionCall1(byteain, CStringGetDatum(encval));
+
+	tup = heap_form_tuple(RelationGetDescr(rel), values, nulls);
+	CatalogTupleInsert(rel, tup);
+	heap_freetuple(tup);
+
+	ObjectAddressSet(myself, ColumnEncKeyDataRelationId, cekdataoid);
+
+	/* dependency cekdata -> cek */
+	ObjectAddressSet(other, ColumnEncKeyRelationId, cekoid);
+	recordDependencyOn(&myself, &other, DEPENDENCY_AUTO);
+
+	/* dependency cekdata -> cmk */
+	ObjectAddressSet(other, ColumnMasterKeyRelationId, cmkoid);
+	recordDependencyOn(&myself, &other, DEPENDENCY_NORMAL);
+
+	table_close(rel, NoLock);
+}
+
+ObjectAddress
+CreateCEK(ParseState *pstate, DefineStmt *stmt)
+{
+	Oid			namespaceId;
+	char	   *ceknamestr;
+	AclResult	aclresult;
+	Relation	rel;
+	ObjectAddress myself;
+	Oid			cekoid;
+	ListCell   *lc;
+	NameData	cekname;
+	Datum		values[Natts_pg_colenckey] = {0};
+	bool		nulls[Natts_pg_colenckey] = {0};
+	HeapTuple	tup;
+
+	namespaceId = QualifiedNameGetCreationNamespace(stmt->defnames, &ceknamestr);
+
+	aclresult = object_aclcheck(NamespaceRelationId, namespaceId, GetUserId(), ACL_CREATE);
+	if (aclresult != ACLCHECK_OK)
+		aclcheck_error(aclresult, OBJECT_SCHEMA, get_namespace_name(namespaceId));
+
+	rel = table_open(ColumnEncKeyRelationId, RowExclusiveLock);
+
+	if (SearchSysCacheExists2(CEKNAMENSP, PointerGetDatum(ceknamestr), ObjectIdGetDatum(namespaceId)))
+		ereport(ERROR,
+				errcode(ERRCODE_DUPLICATE_OBJECT),
+				errmsg("column encryption key \"%s\" already exists", ceknamestr));
+
+	cekoid = GetNewOidWithIndex(rel, ColumnEncKeyOidIndexId, Anum_pg_colenckey_oid);
+
+	foreach (lc, stmt->definition)
+	{
+		List	   *definition = lfirst_node(List, lc);
+		Oid			cmkoid = 0;
+		int			alg;
+		char	   *encval;
+
+		parse_cek_attributes(pstate, definition, &cmkoid, &alg, &encval);
+
+		/* pg_colenckeydata */
+		insert_cekdata_record(cekoid, cmkoid, alg, encval);
+	}
+
+	/* pg_colenckey */
+	namestrcpy(&cekname, ceknamestr);
+	values[Anum_pg_colenckey_oid - 1] = ObjectIdGetDatum(cekoid);
+	values[Anum_pg_colenckey_cekname - 1] = NameGetDatum(&cekname);
+	values[Anum_pg_colenckey_ceknamespace - 1] = ObjectIdGetDatum(namespaceId);
+	values[Anum_pg_colenckey_cekowner - 1] = ObjectIdGetDatum(GetUserId());
+
+	tup = heap_form_tuple(RelationGetDescr(rel), values, nulls);
+	CatalogTupleInsert(rel, tup);
+	heap_freetuple(tup);
+
+	ObjectAddressSet(myself, ColumnEncKeyRelationId, cekoid);
+	recordDependencyOnOwner(ColumnEncKeyRelationId, cekoid, GetUserId());
+
+	table_close(rel, RowExclusiveLock);
+
+	InvokeObjectPostCreateHook(ColumnEncKeyRelationId, cekoid, 0);
+
+	return myself;
+}
+
+ObjectAddress
+AlterColumnEncryptionKey(ParseState *pstate, AlterColumnEncryptionKeyStmt *stmt)
+{
+	Oid			cekoid;
+	ObjectAddress address;
+
+	cekoid = get_cek_oid(stmt->cekname, false);
+
+	if (!object_ownercheck(ColumnEncKeyRelationId, cekoid, GetUserId()))
+		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_CEK, NameListToString(stmt->cekname));
+
+	if (stmt->isDrop)
+	{
+		Oid			cmkoid = 0;
+		Oid			cekdataoid;
+		ObjectAddress obj;
+
+		parse_cek_attributes(pstate, stmt->definition, &cmkoid, NULL, NULL);
+		cekdataoid = get_cekdata_oid(cekoid, cmkoid, false);
+		ObjectAddressSet(obj, ColumnEncKeyDataRelationId, cekdataoid);
+		performDeletion(&obj, DROP_CASCADE, 0);
+	}
+	else
+	{
+		Oid			cmkoid = 0;
+		int			alg;
+		char	   *encval;
+
+		parse_cek_attributes(pstate, stmt->definition, &cmkoid, &alg, &encval);
+		if (get_cekdata_oid(cekoid, cmkoid, true))
+			ereport(ERROR,
+					errcode(ERRCODE_DUPLICATE_OBJECT),
+					errmsg("column encryption key \"%s\" already has data for master key \"%s\"",
+						   NameListToString(stmt->cekname), get_cmk_name(cmkoid, false)));
+		insert_cekdata_record(cekoid, cmkoid, alg, encval);
+	}
+
+	InvokeObjectPostAlterHook(ColumnEncKeyRelationId, cekoid, 0);
+	ObjectAddressSet(address, ColumnEncKeyRelationId, cekoid);
+
+	return address;
+}
+
+ObjectAddress
+CreateCMK(ParseState *pstate, DefineStmt *stmt)
+{
+	Oid			namespaceId;
+	char	   *cmknamestr;
+	AclResult	aclresult;
+	Relation	rel;
+	ObjectAddress myself;
+	Oid			cmkoid;
+	ListCell   *lc;
+	DefElem    *realmEl = NULL;
+	char	   *realm;
+	NameData	cmkname;
+	Datum		values[Natts_pg_colmasterkey] = {0};
+	bool		nulls[Natts_pg_colmasterkey] = {0};
+	HeapTuple	tup;
+
+	namespaceId = QualifiedNameGetCreationNamespace(stmt->defnames, &cmknamestr);
+
+	aclresult = object_aclcheck(NamespaceRelationId, namespaceId, GetUserId(), ACL_CREATE);
+	if (aclresult != ACLCHECK_OK)
+		aclcheck_error(aclresult, OBJECT_SCHEMA, get_namespace_name(namespaceId));
+
+	rel = table_open(ColumnMasterKeyRelationId, RowExclusiveLock);
+
+	if (SearchSysCacheExists2(CMKNAMENSP, PointerGetDatum(cmknamestr), ObjectIdGetDatum(namespaceId)))
+		ereport(ERROR,
+				errcode(ERRCODE_DUPLICATE_OBJECT),
+				errmsg("column master key \"%s\" already exists", cmknamestr));
+
+	foreach(lc, stmt->definition)
+	{
+		DefElem    *defel = lfirst_node(DefElem, lc);
+		DefElem   **defelp;
+
+		if (strcmp(defel->defname, "realm") == 0)
+			defelp = &realmEl;
+		else
+		{
+			ereport(ERROR,
+					(errcode(ERRCODE_SYNTAX_ERROR),
+					 errmsg("column master key attribute \"%s\" not recognized",
+							defel->defname),
+					 parser_errposition(pstate, defel->location)));
+		}
+		if (*defelp != NULL)
+			errorConflictingDefElem(defel, pstate);
+		*defelp = defel;
+	}
+
+	if (realmEl)
+		realm = defGetString(realmEl);
+	else
+		realm = "";
+
+	cmkoid = GetNewOidWithIndex(rel, ColumnMasterKeyOidIndexId, Anum_pg_colmasterkey_oid);
+	namestrcpy(&cmkname, cmknamestr);
+	values[Anum_pg_colmasterkey_oid - 1] = ObjectIdGetDatum(cmkoid);
+	values[Anum_pg_colmasterkey_cmkname - 1] = NameGetDatum(&cmkname);
+	values[Anum_pg_colmasterkey_cmknamespace - 1] = ObjectIdGetDatum(namespaceId);
+	values[Anum_pg_colmasterkey_cmkowner - 1] = ObjectIdGetDatum(GetUserId());
+	values[Anum_pg_colmasterkey_cmkrealm - 1] = CStringGetTextDatum(realm);
+
+	tup = heap_form_tuple(RelationGetDescr(rel), values, nulls);
+	CatalogTupleInsert(rel, tup);
+	heap_freetuple(tup);
+
+	recordDependencyOnOwner(ColumnMasterKeyRelationId, cmkoid, GetUserId());
+
+	ObjectAddressSet(myself, ColumnMasterKeyRelationId, cmkoid);
+
+	table_close(rel, RowExclusiveLock);
+
+	InvokeObjectPostCreateHook(ColumnMasterKeyRelationId, cmkoid, 0);
+
+	return myself;
+}
+
+ObjectAddress
+AlterColumnMasterKey(ParseState *pstate, AlterColumnMasterKeyStmt *stmt)
+{
+	Oid			cmkoid;
+	Relation	rel;
+	HeapTuple	tup;
+	HeapTuple	newtup;
+	ObjectAddress address;
+	ListCell   *lc;
+	DefElem    *realmEl = NULL;
+	Datum		values[Natts_pg_colmasterkey] = {0};
+	bool		nulls[Natts_pg_colmasterkey] = {0};
+	bool		replaces[Natts_pg_colmasterkey] = {0};
+
+	cmkoid = get_cmk_oid(stmt->cmkname, false);
+
+	rel = table_open(ColumnMasterKeyRelationId, RowExclusiveLock);
+
+	tup = SearchSysCache1(CMKOID, ObjectIdGetDatum(cmkoid));
+
+	if (!HeapTupleIsValid(tup))
+		elog(ERROR, "cache lookup failed for column master key %u", cmkoid);
+
+	if (!object_ownercheck(ColumnMasterKeyRelationId, cmkoid, GetUserId()))
+		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_CMK, NameListToString(stmt->cmkname));
+
+	foreach(lc, stmt->definition)
+	{
+		DefElem    *defel = lfirst_node(DefElem, lc);
+		DefElem   **defelp;
+
+		if (strcmp(defel->defname, "realm") == 0)
+			defelp = &realmEl;
+		else
+		{
+			ereport(ERROR,
+					(errcode(ERRCODE_SYNTAX_ERROR),
+					 errmsg("column master key attribute \"%s\" not recognized",
+							defel->defname),
+					 parser_errposition(pstate, defel->location)));
+		}
+		if (*defelp != NULL)
+			errorConflictingDefElem(defel, pstate);
+		*defelp = defel;
+	}
+
+	if (realmEl)
+	{
+		values[Anum_pg_colmasterkey_cmkrealm - 1] = CStringGetTextDatum(defGetString(realmEl));
+		replaces[Anum_pg_colmasterkey_cmkrealm - 1] = true;
+	}
+
+	newtup = heap_modify_tuple(tup, RelationGetDescr(rel), values, nulls, replaces);
+
+	CatalogTupleUpdate(rel, &tup->t_self, newtup);
+
+	InvokeObjectPostAlterHook(ColumnMasterKeyRelationId, cmkoid, 0);
+
+	ObjectAddressSet(address, ColumnMasterKeyRelationId, cmkoid);
+
+	heap_freetuple(newtup);
+	ReleaseSysCache(tup);
+
+	table_close(rel, RowExclusiveLock);
+
+	return address;
+}
diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c
index d6c6d514f3..d53c283ad8 100644
--- a/src/backend/commands/createas.c
+++ b/src/backend/commands/createas.c
@@ -50,6 +50,7 @@
 #include "utils/rel.h"
 #include "utils/rls.h"
 #include "utils/snapmgr.h"
+#include "utils/syscache.h"
 
 typedef struct
 {
@@ -205,6 +206,26 @@ create_ctas_nodata(List *tlist, IntoClause *into)
 								format_type_be(col->typeName->typeOid)),
 						 errhint("Use the COLLATE clause to set the collation explicitly.")));
 
+			if (type_is_encrypted(exprType((Node *) tle->expr)))
+			{
+				HeapTuple	tp;
+				Form_pg_attribute orig_att;
+
+				if (!tle->resorigtbl || !tle->resorigcol)
+					ereport(ERROR,
+							errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+							errmsg("underlying table and column could not be determined for encrypted table column"));
+
+				tp = SearchSysCache2(ATTNUM, ObjectIdGetDatum(tle->resorigtbl), Int16GetDatum(tle->resorigcol));
+				if (!HeapTupleIsValid(tp))
+					elog(ERROR, "cache lookup failed for attribute %d of relation %u", tle->resorigcol, tle->resorigtbl);
+				orig_att = (Form_pg_attribute) GETSTRUCT(tp);
+				col->typeName = makeTypeNameFromOid(orig_att->attrealtypid,
+													orig_att->atttypmod);
+				col->encryption = makeColumnEncryption(orig_att);
+				ReleaseSysCache(tp);
+			}
+
 			attrList = lappend(attrList, col);
 		}
 	}
@@ -514,6 +535,17 @@ intorel_startup(DestReceiver *self, int operation, TupleDesc typeinfo)
 							format_type_be(col->typeName->typeOid)),
 					 errhint("Use the COLLATE clause to set the collation explicitly.")));
 
+		if (type_is_encrypted(attribute->atttypid))
+		{
+			/*
+			 * We don't have the required information available here, so
+			 * prevent it for now.
+			 */
+			ereport(ERROR,
+					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+					 errmsg("encrypted columns not yet implemented for this command")));
+		}
+
 		attrList = lappend(attrList, col);
 	}
 
diff --git a/src/backend/commands/dropcmds.c b/src/backend/commands/dropcmds.c
index 82bda15889..b4c681005a 100644
--- a/src/backend/commands/dropcmds.c
+++ b/src/backend/commands/dropcmds.c
@@ -276,6 +276,20 @@ does_not_exist_skipping(ObjectType objtype, Node *object)
 				name = NameListToString(castNode(List, object));
 			}
 			break;
+		case OBJECT_CEK:
+			if (!schema_does_not_exist_skipping(castNode(List, object), &msg, &name))
+			{
+				msg = gettext_noop("column encryption key \"%s\" does not exist, skipping");
+				name = NameListToString(castNode(List, object));
+			}
+			break;
+		case OBJECT_CMK:
+			if (!schema_does_not_exist_skipping(castNode(List, object), &msg, &name))
+			{
+				msg = gettext_noop("column master key \"%s\" does not exist, skipping");
+				name = NameListToString(castNode(List, object));
+			}
+			break;
 		case OBJECT_CONVERSION:
 			if (!schema_does_not_exist_skipping(castNode(List, object), &msg, &name))
 			{
@@ -503,6 +517,7 @@ does_not_exist_skipping(ObjectType objtype, Node *object)
 		case OBJECT_AMOP:
 		case OBJECT_AMPROC:
 		case OBJECT_ATTRIBUTE:
+		case OBJECT_CEKDATA:
 		case OBJECT_DEFAULT:
 		case OBJECT_DEFACL:
 		case OBJECT_DOMCONSTRAINT:
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index d4b00d1a82..4c1628cf7b 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -951,6 +951,9 @@ EventTriggerSupportsObjectType(ObjectType obtype)
 		case OBJECT_AMPROC:
 		case OBJECT_ATTRIBUTE:
 		case OBJECT_CAST:
+		case OBJECT_CEK:
+		case OBJECT_CEKDATA:
+		case OBJECT_CMK:
 		case OBJECT_COLUMN:
 		case OBJECT_COLLATION:
 		case OBJECT_CONVERSION:
@@ -1027,6 +1030,9 @@ EventTriggerSupportsObjectClass(ObjectClass objclass)
 		case OCLASS_TYPE:
 		case OCLASS_CAST:
 		case OCLASS_COLLATION:
+		case OCLASS_CEK:
+		case OCLASS_CEKDATA:
+		case OCLASS_CMK:
 		case OCLASS_CONSTRAINT:
 		case OCLASS_CONVERSION:
 		case OCLASS_DEFAULT:
@@ -2056,6 +2062,9 @@ stringify_grant_objtype(ObjectType objtype)
 		case OBJECT_AMPROC:
 		case OBJECT_ATTRIBUTE:
 		case OBJECT_CAST:
+		case OBJECT_CEK:
+		case OBJECT_CEKDATA:
+		case OBJECT_CMK:
 		case OBJECT_COLLATION:
 		case OBJECT_CONVERSION:
 		case OBJECT_DEFAULT:
@@ -2139,6 +2148,9 @@ stringify_adefprivs_objtype(ObjectType objtype)
 		case OBJECT_AMPROC:
 		case OBJECT_ATTRIBUTE:
 		case OBJECT_CAST:
+		case OBJECT_CEK:
+		case OBJECT_CEKDATA:
+		case OBJECT_CMK:
 		case OBJECT_COLLATION:
 		case OBJECT_CONVERSION:
 		case OBJECT_DEFAULT:
diff --git a/src/backend/commands/meson.build b/src/backend/commands/meson.build
index 42cced9ebe..4b5ac30441 100644
--- a/src/backend/commands/meson.build
+++ b/src/backend/commands/meson.build
@@ -7,6 +7,7 @@ backend_sources += files(
   'analyze.c',
   'async.c',
   'cluster.c',
+  'colenccmds.c',
   'collationcmds.c',
   'comment.c',
   'constraint.c',
diff --git a/src/backend/commands/seclabel.c b/src/backend/commands/seclabel.c
index 7ff16e3276..93d61c96ad 100644
--- a/src/backend/commands/seclabel.c
+++ b/src/backend/commands/seclabel.c
@@ -66,6 +66,9 @@ SecLabelSupportsObjectType(ObjectType objtype)
 		case OBJECT_AMPROC:
 		case OBJECT_ATTRIBUTE:
 		case OBJECT_CAST:
+		case OBJECT_CEK:
+		case OBJECT_CEKDATA:
+		case OBJECT_CMK:
 		case OBJECT_COLLATION:
 		case OBJECT_CONVERSION:
 		case OBJECT_DEFAULT:
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 1293545947..62454bc265 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -35,6 +35,7 @@
 #include "catalog/partition.h"
 #include "catalog/pg_am.h"
 #include "catalog/pg_attrdef.h"
+#include "catalog/pg_colenckey.h"
 #include "catalog/pg_collation.h"
 #include "catalog/pg_constraint.h"
 #include "catalog/pg_depend.h"
@@ -61,6 +62,7 @@
 #include "commands/trigger.h"
 #include "commands/typecmds.h"
 #include "commands/user.h"
+#include "common/colenc.h"
 #include "executor/executor.h"
 #include "foreign/fdwapi.h"
 #include "foreign/foreign.h"
@@ -637,6 +639,7 @@ static List *GetParentedForeignKeyRefs(Relation partition);
 static void ATDetachCheckNoForeignKeyRefs(Relation partition);
 static char GetAttributeCompression(Oid atttypid, char *compression);
 static char GetAttributeStorage(Oid atttypid, const char *storagemode);
+static void GetColumnEncryption(const List *coldefencryption, Form_pg_attribute attr);
 
 
 /* ----------------------------------------------------------------
@@ -936,6 +939,9 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 			attr->attcompression = GetAttributeCompression(attr->atttypid,
 														   colDef->compression);
 
+		if (colDef->encryption)
+			GetColumnEncryption(colDef->encryption, attr);
+
 		if (colDef->storage_name)
 			attr->attstorage = GetAttributeStorage(attr->atttypid, colDef->storage_name);
 	}
@@ -2562,13 +2568,43 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 				Oid			defCollId;
 
 				/*
-				 * Yes, try to merge the two column definitions. They must
-				 * have the same type, typmod, and collation.
+				 * Yes, try to merge the two column definitions.
 				 */
 				ereport(NOTICE,
 						(errmsg("merging multiple inherited definitions of column \"%s\"",
 								attributeName)));
 				def = (ColumnDef *) list_nth(inhSchema, exist_attno - 1);
+
+				/*
+				 * Check encryption parameter.  All parents must have the same
+				 * encryption settings for a column.
+				 */
+				if ((def->encryption && !attribute->attcek) ||
+					(!def->encryption && attribute->attcek))
+				{
+					ereport(ERROR,
+							(errcode(ERRCODE_DATATYPE_MISMATCH),
+							 errmsg("column \"%s\" has an encryption specification conflict",
+									attributeName)));
+				}
+				else if (def->encryption && attribute->attcek)
+				{
+					/*
+					 * Merging the encryption properties of two encrypted
+					 * parent columns is not yet implemented.  Right now, this
+					 * would confuse the checks of the type etc. below (we
+					 * must check the physical and the real types against each
+					 * other, respectively), which might require a larger
+					 * restructuring.  For now, just give up here.
+					 */
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("multiple inheritance of encrypted columns is not implemented")));
+				}
+
+				/*
+				 * Must have the same type, typmod, and collation.
+				 */
 				typenameTypeIdAndMod(NULL, def->typeName, &defTypeId, &deftypmod);
 				if (defTypeId != attribute->atttypid ||
 					deftypmod != attribute->atttypmod)
@@ -2641,6 +2677,12 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 				def->colname = pstrdup(attributeName);
 				def->typeName = makeTypeNameFromOid(attribute->atttypid,
 													attribute->atttypmod);
+				if (type_is_encrypted(attribute->atttypid))
+				{
+					def->typeName = makeTypeNameFromOid(attribute->attrealtypid,
+														attribute->atttypmod);
+					def->encryption = makeColumnEncryption(attribute);
+				}
 				def->inhcount = 1;
 				def->is_local = false;
 				def->is_not_null = attribute->attnotnull;
@@ -2919,6 +2961,34 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 								 errdetail("%s versus %s", def->compression, newdef->compression)));
 				}
 
+				/*
+				 * Check encryption parameter.  All parents and children must
+				 * have the same encryption settings for a column.
+				 */
+				if ((def->encryption && !newdef->encryption) ||
+					(!def->encryption && newdef->encryption))
+				{
+					ereport(ERROR,
+							(errcode(ERRCODE_DATATYPE_MISMATCH),
+							 errmsg("column \"%s\" has an encryption specification conflict",
+									attributeName)));
+				}
+				else if (def->encryption && newdef->encryption)
+				{
+					FormData_pg_attribute a, newa;
+
+					GetColumnEncryption(def->encryption, &a);
+					GetColumnEncryption(newdef->encryption, &newa);
+
+					if (a.atttypid != newa.atttypid ||
+						a.attcek != newa.attcek ||
+						a.attencalg != newa.attencalg)
+						ereport(ERROR,
+								(errcode(ERRCODE_DATATYPE_MISMATCH),
+								 errmsg("column \"%s\" has an encryption specification conflict",
+										attributeName)));
+				}
+
 				/* Mark the column as locally defined */
 				def->is_local = true;
 				/* Merge of NOT NULL constraints = OR 'em together */
@@ -6844,6 +6914,14 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	attribute.attislocal = colDef->is_local;
 	attribute.attinhcount = colDef->inhcount;
 	attribute.attcollation = collOid;
+	if (colDef->encryption)
+		GetColumnEncryption(colDef->encryption, &attribute);
+	else
+	{
+		attribute.attcek = 0;
+		attribute.attrealtypid = 0;
+		attribute.attencalg = 0;
+	}
 
 	ReleaseSysCache(typeTuple);
 
@@ -12675,6 +12753,9 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 			case OCLASS_TYPE:
 			case OCLASS_CAST:
 			case OCLASS_COLLATION:
+			case OCLASS_CEK:
+			case OCLASS_CEKDATA:
+			case OCLASS_CMK:
 			case OCLASS_CONVERSION:
 			case OCLASS_LANGUAGE:
 			case OCLASS_LARGEOBJECT:
@@ -19274,3 +19355,108 @@ GetAttributeStorage(Oid atttypid, const char *storagemode)
 
 	return cstorage;
 }
+
+/*
+ * resolve column encryption specification
+ */
+static void
+GetColumnEncryption(const List *coldefencryption, Form_pg_attribute attr)
+{
+	ListCell   *lc;
+	List	   *cek = NULL;
+	Oid			cekoid;
+	bool		encdet = false;
+	int			alg = PG_CEK_AEAD_AES_128_CBC_HMAC_SHA_256;
+
+	foreach(lc, coldefencryption)
+	{
+		DefElem    *el = lfirst_node(DefElem, lc);
+
+		if (strcmp(el->defname, "column_encryption_key") == 0)
+			cek = defGetQualifiedName(el);
+		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
+				ereport(ERROR,
+						errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						errmsg("unrecognized encryption type: %s", val));
+		}
+		else if (strcmp(el->defname, "algorithm") == 0)
+		{
+			char	   *val = strVal(el->arg);
+
+			alg = get_cekalg_num(val);
+
+			if (!alg)
+				ereport(ERROR,
+						errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						errmsg("unrecognized encryption algorithm: %s", val));
+		}
+		else
+			ereport(ERROR,
+					errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+					errmsg("unrecognized column encryption parameter: %s", el->defname));
+	}
+
+	if (!cek)
+		ereport(ERROR,
+				errcode(ERRCODE_INVALID_COLUMN_DEFINITION),
+				errmsg("column encryption key must be specified"));
+
+	cekoid = get_cek_oid(cek, false);
+
+	attr->attcek = cekoid;
+	attr->attrealtypid = attr->atttypid;
+	attr->attencalg = alg;
+
+	/* override physical type */
+	if (encdet)
+		attr->atttypid = PG_ENCRYPTED_DETOID;
+	else
+		attr->atttypid = PG_ENCRYPTED_RNDOID;
+	get_typlenbyvalalign(attr->atttypid,
+						 &attr->attlen, &attr->attbyval, &attr->attalign);
+	attr->attstorage = get_typstorage(attr->atttypid);
+	attr->attcollation = InvalidOid;
+}
+
+/*
+ * Construct input to GetColumnEncryption(), for synthesizing a column
+ * definition.
+ */
+List *
+makeColumnEncryption(const FormData_pg_attribute *attr)
+{
+	List	   *result;
+	HeapTuple	tup;
+	Form_pg_colenckey form;
+	char	   *nspname;
+
+	tup = SearchSysCache1(CEKOID, ObjectIdGetDatum(attr->attcek));
+	if (!HeapTupleIsValid(tup))
+		elog(ERROR, "cache lookup failed for column encryption key %u", attr->attcek);
+
+	form = (Form_pg_colenckey) GETSTRUCT(tup);
+	nspname = get_namespace_name(form->ceknamespace);
+
+	result = list_make3(makeDefElem("column_encryption_key",
+									(Node *) list_make2(makeString(nspname), makeString(pstrdup(NameStr(form->cekname)))),
+									-1),
+						makeDefElem("encryption_type",
+									(Node *) makeTypeName(attr->atttypid == PG_ENCRYPTED_DETOID ?
+														  "deterministic" : "randomized"),
+									-1),
+						makeDefElem("algorithm",
+									(Node *) makeString(pstrdup(get_cekalg_name(attr->attencalg))),
+									-1));
+
+	ReleaseSysCache(tup);
+
+	return result;
+}
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index bb0f5de4c2..cec2daa9c3 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -25,6 +25,7 @@
 #include "access/xlogprefetcher.h"
 #include "catalog/pg_authid.h"
 #include "common/string.h"
+#include "libpq/libpq-be.h"
 #include "mb/pg_wchar.h"
 #include "miscadmin.h"
 #include "postmaster/postmaster.h"
@@ -706,7 +707,11 @@ check_client_encoding(char **newval, void **extra, GucSource source)
 	 */
 	if (PrepareClientEncoding(encoding) < 0)
 	{
-		if (IsTransactionState())
+		if (MyProcPort->column_encryption_enabled)
+			GUC_check_errdetail("Conversion between %s and %s is not possible when column encryption is enabled.",
+								canonical_name,
+								GetDatabaseEncodingName());
+		else if (IsTransactionState())
 		{
 			/* Must be a genuine no-such-conversion problem */
 			GUC_check_errcode(ERRCODE_FEATURE_NOT_SUPPORTED);
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index ff98c773f5..c93e2faa94 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -88,6 +88,26 @@ DefineVirtualRelation(RangeVar *relation, List *tlist, bool replace,
 			else
 				Assert(!OidIsValid(def->collOid));
 
+			if (type_is_encrypted(exprType((Node *) tle->expr)))
+			{
+				HeapTuple	tp;
+				Form_pg_attribute orig_att;
+
+				if (!tle->resorigtbl || !tle->resorigcol)
+					ereport(ERROR,
+							errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+							errmsg("underlying table and column could not be determined for encrypted view column"));
+
+				tp = SearchSysCache2(ATTNUM, ObjectIdGetDatum(tle->resorigtbl), Int16GetDatum(tle->resorigcol));
+				if (!HeapTupleIsValid(tp))
+					elog(ERROR, "cache lookup failed for attribute %d of relation %u", tle->resorigcol, tle->resorigtbl);
+				orig_att = (Form_pg_attribute) GETSTRUCT(tp);
+				def->typeName = makeTypeNameFromOid(orig_att->attrealtypid,
+													orig_att->atttypmod);
+				def->encryption = makeColumnEncryption(orig_att);
+				ReleaseSysCache(tp);
+			}
+
 			attrList = lappend(attrList, def);
 		}
 	}
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index 8fc24c882b..2cd32b3637 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -3982,6 +3982,8 @@ raw_expression_tree_walker_impl(Node *node,
 					return true;
 				if (WALK(coldef->compression))
 					return true;
+				if (WALK(coldef->encryption))
+					return true;
 				if (WALK(coldef->raw_default))
 					return true;
 				if (WALK(coldef->collClause))
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index a0138382a1..8d0727cac6 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -280,6 +280,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 
 %type <node>	stmt toplevel_stmt schema_stmt routine_body_stmt
 		AlterEventTrigStmt AlterCollationStmt
+		AlterColumnEncryptionKeyStmt AlterColumnMasterKeyStmt
 		AlterDatabaseStmt AlterDatabaseSetStmt AlterDomainStmt AlterEnumStmt
 		AlterFdwStmt AlterForeignServerStmt AlterGroupStmt
 		AlterObjectDependsStmt AlterObjectSchemaStmt AlterOwnerStmt
@@ -419,6 +420,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 
 %type <list>	parse_toplevel stmtmulti routine_body_stmt_list
 				OptTableElementList TableElementList OptInherit definition
+				list_of_definitions
 				OptTypedTableElementList TypedTableElementList
 				reloptions opt_reloptions
 				OptWith opt_definition func_args func_args_list
@@ -592,6 +594,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <node>	TableConstraint TableLikeClause
 %type <ival>	TableLikeOptionList TableLikeOption
 %type <str>		column_compression opt_column_compression column_storage opt_column_storage
+%type <list>	opt_column_encryption
 %type <list>	ColQualList
 %type <node>	ColConstraint ColConstraintElem ConstraintAttr
 %type <ival>	key_match
@@ -690,8 +693,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	DETACH DICTIONARY DISABLE_P DISCARD DISTINCT DO DOCUMENT_P DOMAIN_P
 	DOUBLE_P DROP
 
-	EACH ELSE ENABLE_P ENCODING ENCRYPTED END_P ENUM_P ESCAPE EVENT EXCEPT
-	EXCLUDE EXCLUDING EXCLUSIVE EXECUTE EXISTS EXPLAIN EXPRESSION
+	EACH ELSE ENABLE_P ENCODING ENCRYPTION ENCRYPTED END_P ENUM_P ESCAPE
+	EVENT EXCEPT EXCLUDE EXCLUDING EXCLUSIVE EXECUTE EXISTS EXPLAIN EXPRESSION
 	EXTENSION EXTERNAL EXTRACT
 
 	FALSE_P FAMILY FETCH FILTER FINALIZE FIRST_P FLOAT_P FOLLOWING FOR
@@ -714,7 +717,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	LEADING LEAKPROOF LEAST LEFT LEVEL LIKE LIMIT LISTEN LOAD LOCAL
 	LOCALTIME LOCALTIMESTAMP LOCATION LOCK_P LOCKED LOGGED
 
-	MAPPING MATCH MATCHED MATERIALIZED MAXVALUE MERGE METHOD
+	MAPPING MASTER MATCH MATCHED MATERIALIZED MAXVALUE MERGE METHOD
 	MINUTE_P MINVALUE MODE MONTH_P MOVE
 
 	NAME_P NAMES NATIONAL NATURAL NCHAR NEW NEXT NFC NFD NFKC NFKD NO NONE
@@ -942,6 +945,8 @@ toplevel_stmt:
 stmt:
 			AlterEventTrigStmt
 			| AlterCollationStmt
+			| AlterColumnEncryptionKeyStmt
+			| AlterColumnMasterKeyStmt
 			| AlterDatabaseStmt
 			| AlterDatabaseSetStmt
 			| AlterDefaultPrivilegesStmt
@@ -3700,14 +3705,15 @@ TypedTableElement:
 			| TableConstraint					{ $$ = $1; }
 		;
 
-columnDef:	ColId Typename opt_column_storage opt_column_compression create_generic_options ColQualList
+columnDef:	ColId Typename opt_column_encryption opt_column_storage opt_column_compression create_generic_options ColQualList
 				{
 					ColumnDef *n = makeNode(ColumnDef);
 
 					n->colname = $1;
 					n->typeName = $2;
-					n->storage_name = $3;
-					n->compression = $4;
+					n->encryption = $3;
+					n->storage_name = $4;
+					n->compression = $5;
 					n->inhcount = 0;
 					n->is_local = true;
 					n->is_not_null = false;
@@ -3716,8 +3722,8 @@ columnDef:	ColId Typename opt_column_storage opt_column_compression create_gener
 					n->raw_default = NULL;
 					n->cooked_default = NULL;
 					n->collOid = InvalidOid;
-					n->fdwoptions = $5;
-					SplitColQualList($6, &n->constraints, &n->collClause,
+					n->fdwoptions = $6;
+					SplitColQualList($7, &n->constraints, &n->collClause,
 									 yyscanner);
 					n->location = @1;
 					$$ = (Node *) n;
@@ -3774,6 +3780,11 @@ opt_column_compression:
 			| /*EMPTY*/								{ $$ = NULL; }
 		;
 
+opt_column_encryption:
+			ENCRYPTED WITH '(' def_list ')'			{ $$ = $4; }
+			| /*EMPTY*/								{ $$ = NULL; }
+		;
+
 column_storage:
 			STORAGE ColId							{ $$ = $2; }
 			| STORAGE DEFAULT						{ $$ = pstrdup("default"); }
@@ -4034,6 +4045,7 @@ TableLikeOption:
 				| COMPRESSION		{ $$ = CREATE_TABLE_LIKE_COMPRESSION; }
 				| CONSTRAINTS		{ $$ = CREATE_TABLE_LIKE_CONSTRAINTS; }
 				| DEFAULTS			{ $$ = CREATE_TABLE_LIKE_DEFAULTS; }
+				| ENCRYPTED			{ $$ = CREATE_TABLE_LIKE_ENCRYPTED; }
 				| IDENTITY_P		{ $$ = CREATE_TABLE_LIKE_IDENTITY; }
 				| GENERATED			{ $$ = CREATE_TABLE_LIKE_GENERATED; }
 				| INDEXES			{ $$ = CREATE_TABLE_LIKE_INDEXES; }
@@ -6270,6 +6282,33 @@ DefineStmt:
 					n->if_not_exists = true;
 					$$ = (Node *) n;
 				}
+			| CREATE COLUMN ENCRYPTION KEY any_name WITH VALUES list_of_definitions
+				{
+					DefineStmt *n = makeNode(DefineStmt);
+
+					n->kind = OBJECT_CEK;
+					n->defnames = $5;
+					n->definition = $8;
+					$$ = (Node *) n;
+				}
+			| CREATE COLUMN MASTER KEY any_name
+				{
+					DefineStmt *n = makeNode(DefineStmt);
+
+					n->kind = OBJECT_CMK;
+					n->defnames = $5;
+					n->definition = NIL;
+					$$ = (Node *) n;
+				}
+			| CREATE COLUMN MASTER KEY any_name WITH definition
+				{
+					DefineStmt *n = makeNode(DefineStmt);
+
+					n->kind = OBJECT_CMK;
+					n->defnames = $5;
+					n->definition = $7;
+					$$ = (Node *) n;
+				}
 		;
 
 definition: '(' def_list ')'						{ $$ = $2; }
@@ -6289,6 +6328,10 @@ def_elem:	ColLabel '=' def_arg
 				}
 		;
 
+list_of_definitions: definition						{ $$ = list_make1($1); }
+			| list_of_definitions ',' definition	{ $$ = lappend($1, $3); }
+		;
+
 /* Note: any simple identifier will be returned as a type name! */
 def_arg:	func_type						{ $$ = (Node *) $1; }
 			| reserved_keyword				{ $$ = (Node *) makeString(pstrdup($1)); }
@@ -6800,6 +6843,8 @@ object_type_any_name:
 			| INDEX									{ $$ = OBJECT_INDEX; }
 			| FOREIGN TABLE							{ $$ = OBJECT_FOREIGN_TABLE; }
 			| COLLATION								{ $$ = OBJECT_COLLATION; }
+			| COLUMN ENCRYPTION KEY					{ $$ = OBJECT_CEK; }
+			| COLUMN MASTER KEY						{ $$ = OBJECT_CMK; }
 			| CONVERSION_P							{ $$ = OBJECT_CONVERSION; }
 			| STATISTICS							{ $$ = OBJECT_STATISTIC_EXT; }
 			| TEXT_P SEARCH PARSER					{ $$ = OBJECT_TSPARSER; }
@@ -9140,6 +9185,26 @@ RenameStmt: ALTER AGGREGATE aggregate_with_argtypes RENAME TO name
 					n->missing_ok = false;
 					$$ = (Node *) n;
 				}
+			| ALTER COLUMN ENCRYPTION KEY any_name RENAME TO name
+				{
+					RenameStmt *n = makeNode(RenameStmt);
+
+					n->renameType = OBJECT_CEK;
+					n->object = (Node *) $5;
+					n->newname = $8;
+					n->missing_ok = false;
+					$$ = (Node *) n;
+				}
+			| ALTER COLUMN MASTER KEY any_name RENAME TO name
+				{
+					RenameStmt *n = makeNode(RenameStmt);
+
+					n->renameType = OBJECT_CMK;
+					n->object = (Node *) $5;
+					n->newname = $8;
+					n->missing_ok = false;
+					$$ = (Node *) n;
+				}
 			| ALTER CONVERSION_P any_name RENAME TO name
 				{
 					RenameStmt *n = makeNode(RenameStmt);
@@ -9817,6 +9882,26 @@ AlterObjectSchemaStmt:
 					n->missing_ok = false;
 					$$ = (Node *) n;
 				}
+			| ALTER COLUMN ENCRYPTION KEY any_name SET SCHEMA name
+				{
+					AlterObjectSchemaStmt *n = makeNode(AlterObjectSchemaStmt);
+
+					n->objectType = OBJECT_CEK;
+					n->object = (Node *) $5;
+					n->newschema = $8;
+					n->missing_ok = false;
+					$$ = (Node *) n;
+				}
+			| ALTER COLUMN MASTER KEY any_name SET SCHEMA name
+				{
+					AlterObjectSchemaStmt *n = makeNode(AlterObjectSchemaStmt);
+
+					n->objectType = OBJECT_CMK;
+					n->object = (Node *) $5;
+					n->newschema = $8;
+					n->missing_ok = false;
+					$$ = (Node *) n;
+				}
 			| ALTER CONVERSION_P any_name SET SCHEMA name
 				{
 					AlterObjectSchemaStmt *n = makeNode(AlterObjectSchemaStmt);
@@ -10148,6 +10233,24 @@ AlterOwnerStmt: ALTER AGGREGATE aggregate_with_argtypes OWNER TO RoleSpec
 					n->newowner = $6;
 					$$ = (Node *) n;
 				}
+			| ALTER COLUMN ENCRYPTION KEY any_name OWNER TO RoleSpec
+				{
+					AlterOwnerStmt *n = makeNode(AlterOwnerStmt);
+
+					n->objectType = OBJECT_CEK;
+					n->object = (Node *) $5;
+					n->newowner = $8;
+					$$ = (Node *) n;
+				}
+			| ALTER COLUMN MASTER KEY any_name OWNER TO RoleSpec
+				{
+					AlterOwnerStmt *n = makeNode(AlterOwnerStmt);
+
+					n->objectType = OBJECT_CMK;
+					n->object = (Node *) $5;
+					n->newowner = $8;
+					$$ = (Node *) n;
+				}
 			| ALTER CONVERSION_P any_name OWNER TO RoleSpec
 				{
 					AlterOwnerStmt *n = makeNode(AlterOwnerStmt);
@@ -11304,6 +11407,52 @@ AlterCollationStmt: ALTER COLLATION any_name REFRESH VERSION_P
 		;
 
 
+/*****************************************************************************
+ *
+ *		ALTER COLUMN ENCRYPTION KEY
+ *
+ *****************************************************************************/
+
+AlterColumnEncryptionKeyStmt:
+			ALTER COLUMN ENCRYPTION KEY any_name ADD_P VALUE_P definition
+				{
+					AlterColumnEncryptionKeyStmt *n = makeNode(AlterColumnEncryptionKeyStmt);
+
+					n->cekname = $5;
+					n->isDrop = false;
+					n->definition = $8;
+					$$ = (Node *) n;
+				}
+			| ALTER COLUMN ENCRYPTION KEY any_name DROP VALUE_P definition
+				{
+					AlterColumnEncryptionKeyStmt *n = makeNode(AlterColumnEncryptionKeyStmt);
+
+					n->cekname = $5;
+					n->isDrop = true;
+					n->definition = $8;
+					$$ = (Node *) n;
+				}
+		;
+
+
+/*****************************************************************************
+ *
+ *		ALTER COLUMN MASTER KEY
+ *
+ *****************************************************************************/
+
+AlterColumnMasterKeyStmt:
+			ALTER COLUMN MASTER KEY any_name definition
+				{
+					AlterColumnMasterKeyStmt *n = makeNode(AlterColumnMasterKeyStmt);
+
+					n->cmkname = $5;
+					n->definition = $6;
+					$$ = (Node *) n;
+				}
+		;
+
+
 /*****************************************************************************
  *
  *		ALTER SYSTEM
@@ -16791,6 +16940,7 @@ unreserved_keyword:
 			| ENABLE_P
 			| ENCODING
 			| ENCRYPTED
+			| ENCRYPTION
 			| ENUM_P
 			| ESCAPE
 			| EVENT
@@ -16854,6 +17004,7 @@ unreserved_keyword:
 			| LOCKED
 			| LOGGED
 			| MAPPING
+			| MASTER
 			| MATCH
 			| MATCHED
 			| MATERIALIZED
@@ -17337,6 +17488,7 @@ bare_label_keyword:
 			| ENABLE_P
 			| ENCODING
 			| ENCRYPTED
+			| ENCRYPTION
 			| END_P
 			| ENUM_P
 			| ESCAPE
@@ -17425,6 +17577,7 @@ bare_label_keyword:
 			| LOCKED
 			| LOGGED
 			| MAPPING
+			| MASTER
 			| MATCH
 			| MATCHED
 			| MATERIALIZED
diff --git a/src/backend/parser/parse_param.c b/src/backend/parser/parse_param.c
index 2240284f21..bcfb2d087f 100644
--- a/src/backend/parser/parse_param.c
+++ b/src/backend/parser/parse_param.c
@@ -29,6 +29,7 @@
 #include "catalog/pg_type.h"
 #include "nodes/nodeFuncs.h"
 #include "parser/parse_param.h"
+#include "parser/parsetree.h"
 #include "utils/builtins.h"
 #include "utils/lsyscache.h"
 
@@ -357,3 +358,146 @@ query_contains_extern_params_walker(Node *node, void *context)
 	return expression_tree_walker(node, query_contains_extern_params_walker,
 								  context);
 }
+
+/*
+ * Walk a query tree and find out what tables and columns a parameter is
+ * associated with.
+ *
+ * We need to find 1) parameters written directly into a table column, and 2)
+ * binary predicates relating a parameter to a table column.
+ *
+ * We just need to find Var and Param nodes in appropriate places.  We don't
+ * need to do harder things like looking through casts, since this is used for
+ * column encryption, and encrypted columns can't be usefully cast to
+ * anything.
+ */
+
+struct find_param_origs_context
+{
+	const Query *query;
+	Oid		   *param_orig_tbls;
+	AttrNumber *param_orig_cols;
+};
+
+static bool
+find_param_origs_walker(Node *node, struct find_param_origs_context *context)
+{
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, OpExpr) || IsA(node, DistinctExpr) || IsA(node, NullIfExpr))
+	{
+		OpExpr	   *opexpr = (OpExpr *) node;
+
+		if (list_length(opexpr->args) == 2)
+		{
+			Node	   *lexpr = linitial(opexpr->args);
+			Node	   *rexpr = lsecond(opexpr->args);
+			Var		   *v = NULL;
+			Param	   *p = NULL;
+
+			if (IsA(lexpr, Var) && IsA(rexpr, Param))
+			{
+				v = castNode(Var, lexpr);
+				p = castNode(Param, rexpr);
+			}
+			else if (IsA(rexpr, Var) && IsA(lexpr, Param))
+			{
+				v = castNode(Var, rexpr);
+				p = castNode(Param, lexpr);
+			}
+
+			if (v && p)
+			{
+				RangeTblEntry *rte;
+
+				rte = rt_fetch(v->varno, context->query->rtable);
+				if (rte->rtekind == RTE_RELATION)
+				{
+					context->param_orig_tbls[p->paramid - 1] = rte->relid;
+					context->param_orig_cols[p->paramid - 1] = v->varattno;
+				}
+			}
+		}
+		return false;
+	}
+
+	/*
+	 * TargetEntry in a query with a result relation
+	 */
+	if (IsA(node, TargetEntry) && context->query->resultRelation > 0)
+	{
+		TargetEntry *te = (TargetEntry *) node;
+		RangeTblEntry *resrte;
+
+		resrte = rt_fetch(context->query->resultRelation, context->query->rtable);
+		if (resrte->rtekind == RTE_RELATION)
+		{
+			/*
+			 * Param directly in a target list
+			 */
+			if (IsA(te->expr, Param))
+			{
+				Param	   *p = (Param *) te->expr;
+
+				context->param_orig_tbls[p->paramid - 1] = resrte->relid;
+				context->param_orig_cols[p->paramid - 1] = te->resno;
+			}
+			/*
+			 * If it's a Var, check whether it corresponds to a VALUES list
+			 * with top-level parameters.  This covers multi-row INSERTS.
+			 */
+			else if (IsA(te->expr, Var))
+			{
+				Var	   *v = (Var *) te->expr;
+				RangeTblEntry *srcrte;
+
+				srcrte = rt_fetch(v->varno, context->query->rtable);
+				if (srcrte->rtekind == RTE_VALUES)
+				{
+					ListCell *lc;
+
+					foreach (lc, srcrte->values_lists)
+					{
+						List *values_list = lfirst_node(List, lc);
+						Node *value = list_nth(values_list, v->varattno - 1);
+
+						if (IsA(value, Param))
+						{
+							Param	   *p = (Param *) value;
+
+							context->param_orig_tbls[p->paramid - 1] = resrte->relid;
+							context->param_orig_cols[p->paramid - 1] = te->resno;
+						}
+					}
+				}
+			}
+		}
+		return false;
+	}
+
+	if (IsA(node, Query))
+	{
+		return query_tree_walker((Query *) node, find_param_origs_walker, context, 0);
+	}
+
+	return expression_tree_walker(node, find_param_origs_walker, context);
+}
+
+void
+find_param_origs(List *query_list, Oid **param_orig_tbls, AttrNumber **param_orig_cols)
+{
+	struct find_param_origs_context context;
+	ListCell   *lc;
+
+	context.param_orig_tbls = *param_orig_tbls;
+	context.param_orig_cols = *param_orig_cols;
+
+	foreach (lc, query_list)
+	{
+		Query	   *query = lfirst_node(Query, lc);
+
+		context.query = query;
+		query_tree_walker(query, find_param_origs_walker, &context, 0);
+	}
+}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index f9218f48aa..00492e1eaf 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -1035,8 +1035,16 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla
 		 */
 		def = makeNode(ColumnDef);
 		def->colname = pstrdup(attributeName);
-		def->typeName = makeTypeNameFromOid(attribute->atttypid,
-											attribute->atttypmod);
+		if (type_is_encrypted(attribute->atttypid))
+		{
+			def->typeName = makeTypeNameFromOid(attribute->attrealtypid,
+												attribute->atttypmod);
+			if (table_like_clause->options & CREATE_TABLE_LIKE_ENCRYPTED)
+				def->encryption = makeColumnEncryption(attribute);
+		}
+		else
+			def->typeName = makeTypeNameFromOid(attribute->atttypid,
+												attribute->atttypmod);
 		def->inhcount = 0;
 		def->is_local = true;
 		def->is_not_null = attribute->attnotnull;
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index 5b775cf7d0..958fd65ac4 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -2209,12 +2209,27 @@ ProcessStartupPacket(Port *port, bool ssl_done, bool gss_done)
 									valptr),
 							 errhint("Valid values are: \"false\", 0, \"true\", 1, \"database\".")));
 			}
+			else if (strcmp(nameptr, "_pq_.column_encryption") == 0)
+			{
+				/*
+				 * Right now, the only accepted value is "1".  This gives room
+				 * to expand this into a version number, for example.
+				 */
+				if (strcmp(valptr, "1") == 0)
+					port->column_encryption_enabled = true;
+				else
+					ereport(FATAL,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for parameter \"%s\": \"%s\"",
+									"column_encryption",
+									valptr),
+							 errhint("Valid values are: 1.")));
+			}
 			else if (strncmp(nameptr, "_pq_.", 5) == 0)
 			{
 				/*
 				 * Any option beginning with _pq_. is reserved for use as a
-				 * protocol-level option, but at present no such options are
-				 * defined.
+				 * protocol-level option.
 				 */
 				unrecognized_protocol_options =
 					lappend(unrecognized_protocol_options, pstrdup(nameptr));
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index 470b734e9e..e20ab8966c 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -44,6 +44,7 @@
 #include "nodes/print.h"
 #include "optimizer/optimizer.h"
 #include "parser/analyze.h"
+#include "parser/parse_param.h"
 #include "parser/parser.h"
 #include "pg_getopt.h"
 #include "pg_trace.h"
@@ -71,6 +72,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"
 
@@ -1812,6 +1814,16 @@ exec_bind_message(StringInfo input_message)
 			else
 				pformat = 0;	/* default = text */
 
+			if (type_is_encrypted(ptype))
+			{
+				if (pformat & 0xF0)
+					pformat &= ~0xF0;
+				else
+					ereport(ERROR,
+							(errcode(ERRCODE_PROTOCOL_VIOLATION),
+							 errmsg("parameter %d corresponds to an encrypted column, but the parameter value was not encrypted", paramno + 1)));
+			}
+
 			if (pformat == 0)	/* text mode */
 			{
 				Oid			typinput;
@@ -2553,6 +2565,8 @@ static void
 exec_describe_statement_message(const char *stmt_name)
 {
 	CachedPlanSource *psrc;
+	Oid		   *param_orig_tbls;
+	AttrNumber *param_orig_cols;
 
 	/*
 	 * Start up a transaction command. (Note that this will normally change
@@ -2611,11 +2625,61 @@ exec_describe_statement_message(const char *stmt_name)
 														 * message type */
 	pq_sendint16(&row_description_buf, psrc->num_params);
 
+	/*
+	 * If column encryption is enabled, find the associated tables and columns
+	 * for any parameters, so that we can determine encryption information for
+	 * them.
+	 */
+	if (MyProcPort->column_encryption_enabled && psrc->num_params)
+	{
+		param_orig_tbls = palloc0_array(Oid, psrc->num_params);
+		param_orig_cols = palloc0_array(AttrNumber, psrc->num_params);
+
+		RevalidateCachedQuery(psrc, NULL);
+		find_param_origs(psrc->query_list, &param_orig_tbls, &param_orig_cols);
+	}
+
 	for (int i = 0; i < psrc->num_params; i++)
 	{
 		Oid			ptype = psrc->param_types[i];
+		Oid			pcekid = InvalidOid;
+		int			pcekalg = 0;
+		int16		pflags = 0;
+
+		if (MyProcPort->column_encryption_enabled && type_is_encrypted(ptype))
+		{
+			Oid			porigtbl = param_orig_tbls[i];
+			AttrNumber	porigcol = param_orig_cols[i];
+			HeapTuple	tp;
+			Form_pg_attribute orig_att;
+
+			if (porigtbl == InvalidOid || porigcol == InvalidAttrNumber)
+				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 + 1)));
+
+			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);
+			ptype = orig_att->attrealtypid;
+			pcekid = orig_att->attcek;
+			pcekalg = orig_att->attencalg;
+			ReleaseSysCache(tp);
+
+			if (psrc->param_types[i] == PG_ENCRYPTED_DETOID)
+				pflags |= 0x01;
+
+			MaybeSendColumnEncryptionKeyMessage(pcekid);
+		}
 
 		pq_sendint32(&row_description_buf, (int) ptype);
+		if (MyProcPort->column_encryption_enabled)
+		{
+			pq_sendint32(&row_description_buf, (int) pcekid);
+			pq_sendint32(&row_description_buf, pcekalg);
+			pq_sendint16(&row_description_buf, pflags);
+		}
 	}
 	pq_endmessage_reuse(&row_description_buf);
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index c7d9d96b45..0a17eb4486 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -30,6 +30,7 @@
 #include "commands/alter.h"
 #include "commands/async.h"
 #include "commands/cluster.h"
+#include "commands/colenccmds.h"
 #include "commands/collationcmds.h"
 #include "commands/comment.h"
 #include "commands/conversioncmds.h"
@@ -137,6 +138,8 @@ ClassifyUtilityCommandAsReadOnly(Node *parsetree)
 	switch (nodeTag(parsetree))
 	{
 		case T_AlterCollationStmt:
+		case T_AlterColumnEncryptionKeyStmt:
+		case T_AlterColumnMasterKeyStmt:
 		case T_AlterDatabaseRefreshCollStmt:
 		case T_AlterDatabaseSetStmt:
 		case T_AlterDatabaseStmt:
@@ -1441,6 +1444,14 @@ ProcessUtilitySlow(ParseState *pstate,
 															stmt->definition,
 															&secondaryObject);
 							break;
+						case OBJECT_CEK:
+							Assert(stmt->args == NIL);
+							address = CreateCEK(pstate, stmt);
+							break;
+						case OBJECT_CMK:
+							Assert(stmt->args == NIL);
+							address = CreateCMK(pstate, stmt);
+							break;
 						case OBJECT_COLLATION:
 							Assert(stmt->args == NIL);
 							address = DefineCollation(pstate,
@@ -1903,6 +1914,14 @@ ProcessUtilitySlow(ParseState *pstate,
 				address = AlterCollation((AlterCollationStmt *) parsetree);
 				break;
 
+			case T_AlterColumnEncryptionKeyStmt:
+				address = AlterColumnEncryptionKey(pstate, (AlterColumnEncryptionKeyStmt *) parsetree);
+				break;
+
+			case T_AlterColumnMasterKeyStmt:
+				address = AlterColumnMasterKey(pstate, (AlterColumnMasterKeyStmt *) parsetree);
+				break;
+
 			default:
 				elog(ERROR, "unrecognized node type: %d",
 					 (int) nodeTag(parsetree));
@@ -2225,6 +2244,12 @@ AlterObjectTypeCommandTag(ObjectType objtype)
 		case OBJECT_COLUMN:
 			tag = CMDTAG_ALTER_TABLE;
 			break;
+		case OBJECT_CEK:
+			tag = CMDTAG_ALTER_COLUMN_ENCRYPTION_KEY;
+			break;
+		case OBJECT_CMK:
+			tag = CMDTAG_ALTER_COLUMN_MASTER_KEY;
+			break;
 		case OBJECT_CONVERSION:
 			tag = CMDTAG_ALTER_CONVERSION;
 			break;
@@ -2640,6 +2665,12 @@ CreateCommandTag(Node *parsetree)
 				case OBJECT_STATISTIC_EXT:
 					tag = CMDTAG_DROP_STATISTICS;
 					break;
+				case OBJECT_CEK:
+					tag = CMDTAG_DROP_COLUMN_ENCRYPTION_KEY;
+					break;
+				case OBJECT_CMK:
+					tag = CMDTAG_DROP_COLUMN_MASTER_KEY;
+					break;
 				default:
 					tag = CMDTAG_UNKNOWN;
 			}
@@ -2760,6 +2791,12 @@ CreateCommandTag(Node *parsetree)
 				case OBJECT_COLLATION:
 					tag = CMDTAG_CREATE_COLLATION;
 					break;
+				case OBJECT_CEK:
+					tag = CMDTAG_CREATE_COLUMN_ENCRYPTION_KEY;
+					break;
+				case OBJECT_CMK:
+					tag = CMDTAG_CREATE_COLUMN_MASTER_KEY;
+					break;
 				case OBJECT_ACCESS_METHOD:
 					tag = CMDTAG_CREATE_ACCESS_METHOD;
 					break;
@@ -3063,6 +3100,14 @@ CreateCommandTag(Node *parsetree)
 			tag = CMDTAG_ALTER_COLLATION;
 			break;
 
+		case T_AlterColumnEncryptionKeyStmt:
+			tag = CMDTAG_ALTER_COLUMN_ENCRYPTION_KEY;
+			break;
+
+		case T_AlterColumnMasterKeyStmt:
+			tag = CMDTAG_ALTER_COLUMN_MASTER_KEY;
+			break;
+
 		case T_PrepareStmt:
 			tag = CMDTAG_PREPARE;
 			break;
@@ -3688,6 +3733,14 @@ GetCommandLogLevel(Node *parsetree)
 			lev = LOGSTMT_DDL;
 			break;
 
+		case T_AlterColumnEncryptionKeyStmt:
+			lev = LOGSTMT_DDL;
+			break;
+
+		case T_AlterColumnMasterKeyStmt:
+			lev = LOGSTMT_DDL;
+			break;
+
 			/* already-planned queries */
 		case T_PlannedStmt:
 			{
diff --git a/src/backend/utils/adt/varlena.c b/src/backend/utils/adt/varlena.c
index fd81c47474..af0bca7d3e 100644
--- a/src/backend/utils/adt/varlena.c
+++ b/src/backend/utils/adt/varlena.c
@@ -686,6 +686,112 @@ unknownsend(PG_FUNCTION_ARGS)
 	PG_RETURN_BYTEA_P(pq_endtypsend(&buf));
 }
 
+/*
+ * pg_encrypted_in -
+ *
+ * Input function for pg_encrypted_* types.
+ *
+ * The format and additional checks ensure that one cannot easily insert a
+ * value directly into an encrypted column by accident.  (That's why we don't
+ * just use the bytea format, for example.)  But we still have to support
+ * direct inserts into encrypted columns, for example for restoring backups
+ * made by pg_dump.
+ */
+Datum
+pg_encrypted_in(PG_FUNCTION_ARGS)
+{
+	char	   *inputText = PG_GETARG_CSTRING(0);
+	char	   *ip;
+	size_t		hexlen;
+	int			bc;
+	bytea	   *result;
+
+	if (strncmp(inputText, "encrypted$", 10) != 0)
+		ereport(ERROR,
+				errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+				errmsg("invalid input value for encrypted column value: \"%s\"",
+					   inputText));
+
+	ip = inputText + 10;
+	hexlen = strlen(ip);
+
+	/* sanity check to catch obvious mistakes */
+	if (hexlen / 2 < 32 || (hexlen / 2) % 16 != 0)
+		ereport(ERROR,
+				errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+				errmsg("invalid input value for encrypted column value: \"%s\"",
+					   inputText));
+
+	bc = hexlen / 2 + VARHDRSZ;	/* maximum possible length */
+	result = palloc(bc);
+	bc = hex_decode(ip, hexlen, VARDATA(result));
+	SET_VARSIZE(result, bc + VARHDRSZ); /* actual length */
+
+	PG_RETURN_BYTEA_P(result);
+}
+
+/*
+ * pg_encrypted_out -
+ *
+ * Output function for pg_encrypted_* types.
+ *
+ * This output is seen when reading an encrypted column without column
+ * encryption mode enabled.  Therefore, the output format is chosen so that it
+ * is easily recognizable.
+ */
+Datum
+pg_encrypted_out(PG_FUNCTION_ARGS)
+{
+	bytea	   *vlena = PG_GETARG_BYTEA_PP(0);
+	char	   *result;
+	char	   *rp;
+
+	rp = result = palloc(VARSIZE_ANY_EXHDR(vlena) * 2 + 10 + 1);
+	memcpy(rp, "encrypted$", 10);
+	rp += 10;
+	rp += hex_encode(VARDATA_ANY(vlena), VARSIZE_ANY_EXHDR(vlena), rp);
+	*rp = '\0';
+	PG_RETURN_CSTRING(result);
+}
+
+/*
+ * pg_encrypted_recv -
+ *
+ * Receive function for pg_encrypted_* types.
+ */
+Datum
+pg_encrypted_recv(PG_FUNCTION_ARGS)
+{
+	StringInfo	buf = (StringInfo) PG_GETARG_POINTER(0);
+	bytea	   *result;
+	int			nbytes;
+
+	nbytes = buf->len - buf->cursor;
+	/* sanity check to catch obvious mistakes */
+	if (nbytes < 32)
+		ereport(ERROR,
+				errcode(ERRCODE_INVALID_BINARY_REPRESENTATION),
+				errmsg("invalid binary input value for encrypted column value"));
+	result = (bytea *) palloc(nbytes + VARHDRSZ);
+	SET_VARSIZE(result, nbytes + VARHDRSZ);
+	pq_copymsgbytes(buf, VARDATA(result), nbytes);
+	PG_RETURN_BYTEA_P(result);
+}
+
+/*
+ * pg_encrypted_send -
+ *
+ * Send function for pg_encrypted_* types.
+ */
+Datum
+pg_encrypted_send(PG_FUNCTION_ARGS)
+{
+	bytea	   *vlena = PG_GETARG_BYTEA_P_COPY(0);
+
+	/* just return input */
+	PG_RETURN_BYTEA_P(vlena);
+}
+
 
 /* ========== PUBLIC ROUTINES ========== */
 
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index c07382051d..7ad159110f 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -24,7 +24,10 @@
 #include "catalog/pg_amop.h"
 #include "catalog/pg_amproc.h"
 #include "catalog/pg_cast.h"
+#include "catalog/pg_colenckey.h"
+#include "catalog/pg_colenckeydata.h"
 #include "catalog/pg_collation.h"
+#include "catalog/pg_colmasterkey.h"
 #include "catalog/pg_constraint.h"
 #include "catalog/pg_language.h"
 #include "catalog/pg_namespace.h"
@@ -2658,6 +2661,25 @@ type_is_multirange(Oid typid)
 	return (get_typtype(typid) == TYPTYPE_MULTIRANGE);
 }
 
+bool
+type_is_encrypted(Oid typid)
+{
+	HeapTuple	tp;
+
+	tp = SearchSysCache1(TYPEOID, ObjectIdGetDatum(typid));
+	if (HeapTupleIsValid(tp))
+	{
+		Form_pg_type typtup = (Form_pg_type) GETSTRUCT(tp);
+		bool		result;
+
+		result = (typtup->typcategory == TYPCATEGORY_ENCRYPTED);
+		ReleaseSysCache(tp);
+		return result;
+	}
+	else
+		return false;
+}
+
 /*
  * get_type_category_preferred
  *
@@ -3683,3 +3705,64 @@ get_subscription_name(Oid subid, bool missing_ok)
 
 	return subname;
 }
+
+char *
+get_cek_name(Oid cekid, bool missing_ok)
+{
+	HeapTuple	tup;
+	char	   *cekname;
+
+	tup = SearchSysCache1(CEKOID, ObjectIdGetDatum(cekid));
+
+	if (!HeapTupleIsValid(tup))
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for column encryption key %u", cekid);
+		return NULL;
+	}
+
+	cekname = pstrdup(NameStr(((Form_pg_colenckey) GETSTRUCT(tup))->cekname));
+
+	ReleaseSysCache(tup);
+
+	return cekname;
+}
+
+Oid
+get_cekdata_oid(Oid cekid, Oid cmkid, bool missing_ok)
+{
+	Oid			cekdataid;
+
+	cekdataid = GetSysCacheOid2(CEKDATACEKCMK, Anum_pg_colenckeydata_oid,
+								ObjectIdGetDatum(cekid),
+								ObjectIdGetDatum(cmkid));
+	if (!OidIsValid(cekdataid) && !missing_ok)
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_OBJECT),
+				 errmsg("column encryption key \"%s\" has no data for master key \"%s\"",
+						get_cek_name(cekid, false), get_cmk_name(cmkid, false))));
+
+	return cekdataid;
+}
+
+char *
+get_cmk_name(Oid cmkid, bool missing_ok)
+{
+	HeapTuple	tup;
+	char	   *cmkname;
+
+	tup = SearchSysCache1(CMKOID, ObjectIdGetDatum(cmkid));
+
+	if (!HeapTupleIsValid(tup))
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for column master key %u", cmkid);
+		return NULL;
+	}
+
+	cmkname = pstrdup(NameStr(((Form_pg_colmasterkey) GETSTRUCT(tup))->cmkname));
+
+	ReleaseSysCache(tup);
+
+	return cmkname;
+}
diff --git a/src/backend/utils/cache/plancache.c b/src/backend/utils/cache/plancache.c
index 77c2ba3f8f..d40af13efe 100644
--- a/src/backend/utils/cache/plancache.c
+++ b/src/backend/utils/cache/plancache.c
@@ -97,8 +97,6 @@ static dlist_head saved_plan_list = DLIST_STATIC_INIT(saved_plan_list);
 static dlist_head cached_expression_list = DLIST_STATIC_INIT(cached_expression_list);
 
 static void ReleaseGenericPlan(CachedPlanSource *plansource);
-static List *RevalidateCachedQuery(CachedPlanSource *plansource,
-								   QueryEnvironment *queryEnv);
 static bool CheckCachedPlan(CachedPlanSource *plansource);
 static CachedPlan *BuildCachedPlan(CachedPlanSource *plansource, List *qlist,
 								   ParamListInfo boundParams, QueryEnvironment *queryEnv);
@@ -551,7 +549,7 @@ ReleaseGenericPlan(CachedPlanSource *plansource)
  * had to do re-analysis, and NIL otherwise.  (This is returned just to save
  * a tree copying step in a subsequent BuildCachedPlan call.)
  */
-static List *
+List *
 RevalidateCachedQuery(CachedPlanSource *plansource,
 					  QueryEnvironment *queryEnv)
 {
diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index 94abede512..eb432260da 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -29,7 +29,10 @@
 #include "catalog/pg_auth_members.h"
 #include "catalog/pg_authid.h"
 #include "catalog/pg_cast.h"
+#include "catalog/pg_colenckey.h"
+#include "catalog/pg_colenckeydata.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"
@@ -219,6 +222,32 @@ static const struct cachedesc cacheinfo[] = {
 			Anum_pg_cast_casttarget),
 		256
 	},
+	[CEKDATACEKCMK] = {
+		ColumnEncKeyDataRelationId,
+		ColumnEncKeyCekidCmkidIndexId,
+		KEY(Anum_pg_colenckeydata_ckdcekid,
+			Anum_pg_colenckeydata_ckdcmkid),
+		8
+	},
+	[CEKDATAOID] = {
+		ColumnEncKeyDataRelationId,
+		ColumnEncKeyDataOidIndexId,
+		KEY(Anum_pg_colenckeydata_oid),
+		8
+	},
+	[CEKNAMENSP] = {
+		ColumnEncKeyRelationId,
+		ColumnEncKeyNameNspIndexId,
+		KEY(Anum_pg_colenckey_cekname,
+			Anum_pg_colenckey_ceknamespace),
+		8
+	},
+	[CEKOID] = {
+		ColumnEncKeyRelationId,
+		ColumnEncKeyOidIndexId,
+		KEY(Anum_pg_colenckey_oid),
+		8
+	},
 	[CLAAMNAMENSP] = {
 		OperatorClassRelationId,
 		OpclassAmNameNspIndexId,
@@ -233,6 +262,19 @@ static const struct cachedesc cacheinfo[] = {
 		KEY(Anum_pg_opclass_oid),
 		8
 	},
+	[CMKNAMENSP] = {
+		ColumnMasterKeyRelationId,
+		ColumnMasterKeyNameNspIndexId,
+		KEY(Anum_pg_colmasterkey_cmkname,
+			Anum_pg_colmasterkey_cmknamespace),
+		8
+	},
+	[CMKOID] = {
+		ColumnMasterKeyRelationId,
+		ColumnMasterKeyOidIndexId,
+		KEY(Anum_pg_colmasterkey_oid),
+		8
+	},
 	[COLLNAMEENCNSP] = {
 		CollationRelationId,
 		CollationNameEncNspIndexId,
diff --git a/src/backend/utils/mb/mbutils.c b/src/backend/utils/mb/mbutils.c
index 033647011b..4b6be68f27 100644
--- a/src/backend/utils/mb/mbutils.c
+++ b/src/backend/utils/mb/mbutils.c
@@ -36,7 +36,9 @@
 
 #include "access/xact.h"
 #include "catalog/namespace.h"
+#include "libpq/libpq-be.h"
 #include "mb/pg_wchar.h"
+#include "miscadmin.h"
 #include "utils/builtins.h"
 #include "utils/memutils.h"
 #include "utils/syscache.h"
@@ -130,6 +132,12 @@ PrepareClientEncoding(int encoding)
 		encoding == PG_SQL_ASCII)
 		return 0;
 
+	/*
+	 * Cannot do conversion when column encryption is enabled.
+	 */
+	if (MyProcPort->column_encryption_enabled)
+		return -1;
+
 	if (IsTransactionState())
 	{
 		/*
@@ -237,6 +245,12 @@ SetClientEncoding(int encoding)
 		return 0;
 	}
 
+	/*
+	 * Cannot do conversion when column encryption is enabled.
+	 */
+	if (MyProcPort->column_encryption_enabled)
+		return -1;
+
 	/*
 	 * Search the cache for the entry previously prepared by
 	 * PrepareClientEncoding; if there isn't one, we lose.  While at it,
@@ -297,7 +311,9 @@ InitializeClientEncoding(void)
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				 errmsg("conversion between %s and %s is not supported",
 						pg_enc2name_tbl[pending_client_encoding].name,
-						GetDatabaseEncodingName())));
+						GetDatabaseEncodingName()),
+				 (MyProcPort->column_encryption_enabled) ?
+				 errdetail("Encoding conversion is not possible when column encryption is enabled.") : 0));
 	}
 
 	/*
diff --git a/src/bin/pg_dump/common.c b/src/bin/pg_dump/common.c
index a43f2e5553..e67462c81a 100644
--- a/src/bin/pg_dump/common.c
+++ b/src/bin/pg_dump/common.c
@@ -18,7 +18,9 @@
 #include <ctype.h>
 
 #include "catalog/pg_class_d.h"
+#include "catalog/pg_colenckey_d.h"
 #include "catalog/pg_collation_d.h"
+#include "catalog/pg_colmasterkey_d.h"
 #include "catalog/pg_extension_d.h"
 #include "catalog/pg_namespace_d.h"
 #include "catalog/pg_operator_d.h"
@@ -201,6 +203,12 @@ getSchemaData(Archive *fout, int *numTablesPtr)
 	pg_log_info("reading user-defined collations");
 	(void) getCollations(fout, &numCollations);
 
+	pg_log_info("reading column master keys");
+	getColumnMasterKeys(fout);
+
+	pg_log_info("reading column encryption keys");
+	getColumnEncryptionKeys(fout);
+
 	pg_log_info("reading user-defined conversions");
 	getConversions(fout, &numConversions);
 
@@ -859,6 +867,42 @@ findOprByOid(Oid oid)
 	return (OprInfo *) dobj;
 }
 
+/*
+ * findCekByOid
+ *	  finds the DumpableObject for the CEK with the given oid
+ *	  returns NULL if not found
+ */
+CekInfo *
+findCekByOid(Oid oid)
+{
+	CatalogId	catId;
+	DumpableObject *dobj;
+
+	catId.tableoid = ColumnEncKeyRelationId;
+	catId.oid = oid;
+	dobj = findObjectByCatalogId(catId);
+	Assert(dobj == NULL || dobj->objType == DO_CEK);
+	return (CekInfo *) dobj;
+}
+
+/*
+ * findCmkByOid
+ *	  finds the DumpableObject for the CMK with the given oid
+ *	  returns NULL if not found
+ */
+CmkInfo *
+findCmkByOid(Oid oid)
+{
+	CatalogId	catId;
+	DumpableObject *dobj;
+
+	catId.tableoid = ColumnMasterKeyRelationId;
+	catId.oid = oid;
+	dobj = findObjectByCatalogId(catId);
+	Assert(dobj == NULL || dobj->objType == DO_CMK);
+	return (CmkInfo *) dobj;
+}
+
 /*
  * findCollationByOid
  *	  finds the DumpableObject for the collation with the given oid
diff --git a/src/bin/pg_dump/pg_backup.h b/src/bin/pg_dump/pg_backup.h
index aba780ef4b..afba79b2ea 100644
--- a/src/bin/pg_dump/pg_backup.h
+++ b/src/bin/pg_dump/pg_backup.h
@@ -85,6 +85,7 @@ typedef struct _connParams
 	char	   *pghost;
 	char	   *username;
 	trivalue	promptPassword;
+	int			column_encryption;
 	/* If not NULL, this overrides the dbname obtained from command line */
 	/* (but *only* the DB name, not anything else in the connstring) */
 	char	   *override_dbname;
diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c
index ba5e6acbbb..7f55eb2716 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -3430,6 +3430,8 @@ _getObjectDescription(PQExpBuffer buf, const TocEntry *te)
 
 	/* objects that don't require special decoration */
 	if (strcmp(type, "COLLATION") == 0 ||
+		strcmp(type, "COLUMN ENCRYPTION KEY") == 0 ||
+		strcmp(type, "COLUMN MASTER KEY") == 0 ||
 		strcmp(type, "CONVERSION") == 0 ||
 		strcmp(type, "DOMAIN") == 0 ||
 		strcmp(type, "FOREIGN TABLE") == 0 ||
diff --git a/src/bin/pg_dump/pg_backup_db.c b/src/bin/pg_dump/pg_backup_db.c
index f766b65059..c90c2803fc 100644
--- a/src/bin/pg_dump/pg_backup_db.c
+++ b/src/bin/pg_dump/pg_backup_db.c
@@ -133,8 +133,8 @@ ConnectDatabase(Archive *AHX,
 	 */
 	do
 	{
-		const char *keywords[8];
-		const char *values[8];
+		const char *keywords[9];
+		const char *values[9];
 		int			i = 0;
 
 		/*
@@ -159,6 +159,11 @@ ConnectDatabase(Archive *AHX,
 		}
 		keywords[i] = "fallback_application_name";
 		values[i++] = progname;
+		if (cparams->column_encryption)
+		{
+			keywords[i] = "column_encryption";
+			values[i++] = "1";
+		}
 		keywords[i] = NULL;
 		values[i++] = NULL;
 		Assert(i <= lengthof(keywords));
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 527c7651ab..e09eef540d 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -54,6 +54,7 @@
 #include "catalog/pg_subscription.h"
 #include "catalog/pg_trigger_d.h"
 #include "catalog/pg_type_d.h"
+#include "common/colenc.h"
 #include "common/connect.h"
 #include "common/relpath.h"
 #include "dumputils.h"
@@ -228,6 +229,8 @@ static void dumpAccessMethod(Archive *fout, const AccessMethodInfo *aminfo);
 static void dumpOpclass(Archive *fout, const OpclassInfo *opcinfo);
 static void dumpOpfamily(Archive *fout, const OpfamilyInfo *opfinfo);
 static void dumpCollation(Archive *fout, const CollInfo *collinfo);
+static void dumpColumnEncryptionKey(Archive *fout, const CekInfo *cekinfo);
+static void dumpColumnMasterKey(Archive *fout, const CmkInfo *cekinfo);
 static void dumpConversion(Archive *fout, const ConvInfo *convinfo);
 static void dumpRule(Archive *fout, const RuleInfo *rinfo);
 static void dumpAgg(Archive *fout, const AggInfo *agginfo);
@@ -393,6 +396,7 @@ main(int argc, char **argv)
 		{"attribute-inserts", no_argument, &dopt.column_inserts, 1},
 		{"binary-upgrade", no_argument, &dopt.binary_upgrade, 1},
 		{"column-inserts", no_argument, &dopt.column_inserts, 1},
+		{"decrypt-encrypted-columns", no_argument, &dopt.cparams.column_encryption, 1},
 		{"disable-dollar-quoting", no_argument, &dopt.disable_dollar_quoting, 1},
 		{"disable-triggers", no_argument, &dopt.disable_triggers, 1},
 		{"enable-row-security", no_argument, &dopt.enable_row_security, 1},
@@ -685,6 +689,9 @@ main(int argc, char **argv)
 	 * --inserts are already implied above if --column-inserts or
 	 * --rows-per-insert were specified.
 	 */
+	if (dopt.cparams.column_encryption && dopt.dump_inserts == 0)
+		pg_fatal("option --decrypt-encrypted-columns requires option --inserts, --rows-per-insert, or --column-inserts");
+
 	if (dopt.do_nothing && dopt.dump_inserts == 0)
 		pg_fatal("option --on-conflict-do-nothing requires option --inserts, --rows-per-insert, or --column-inserts");
 
@@ -1057,6 +1064,7 @@ help(const char *progname)
 	printf(_("  -x, --no-privileges          do not dump privileges (grant/revoke)\n"));
 	printf(_("  --binary-upgrade             for use by upgrade utilities only\n"));
 	printf(_("  --column-inserts             dump data as INSERT commands with column names\n"));
+	printf(_("  --decrypt-encrypted-columns  decrypt encrypted columns in the output\n"));
 	printf(_("  --disable-dollar-quoting     disable dollar quoting, use SQL standard quoting\n"));
 	printf(_("  --disable-triggers           disable triggers during data-only restore\n"));
 	printf(_("  --enable-row-security        enable row security (dump only content user has\n"
@@ -5572,6 +5580,144 @@ getCollations(Archive *fout, int *numCollations)
 	return collinfo;
 }
 
+/*
+ * getColumnEncryptionKeys
+ *	  get information about column encryption keys
+ */
+void
+getColumnEncryptionKeys(Archive *fout)
+{
+	PQExpBuffer query;
+	PGresult   *res;
+	int			ntups;
+	CekInfo	   *cekinfo;
+	int			i_tableoid;
+	int			i_oid;
+	int			i_cekname;
+	int			i_ceknamespace;
+	int			i_cekowner;
+
+	if (fout->remoteVersion < 160000)
+		return;
+
+	query = createPQExpBuffer();
+
+	appendPQExpBuffer(query,
+					  "SELECT cek.tableoid, cek.oid, cek.cekname, cek.ceknamespace, cek.cekowner\n"
+					  "FROM pg_colenckey cek");
+
+	res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+	ntups = PQntuples(res);
+
+	i_tableoid = PQfnumber(res, "tableoid");
+	i_oid = PQfnumber(res, "oid");
+	i_cekname = PQfnumber(res, "cekname");
+	i_ceknamespace = PQfnumber(res, "ceknamespace");
+	i_cekowner = PQfnumber(res, "cekowner");
+
+	cekinfo = pg_malloc(ntups * sizeof(CekInfo));
+
+	for (int i = 0; i < ntups; i++)
+	{
+		PGresult	   *res2;
+		int				ntups2;
+
+		cekinfo[i].dobj.objType = DO_CEK;
+		cekinfo[i].dobj.catId.tableoid = atooid(PQgetvalue(res, i, i_tableoid));
+		cekinfo[i].dobj.catId.oid = atooid(PQgetvalue(res, i, i_oid));
+		AssignDumpId(&cekinfo[i].dobj);
+		cekinfo[i].dobj.name = pg_strdup(PQgetvalue(res, i, i_cekname));
+		cekinfo[i].dobj.namespace = findNamespace(atooid(PQgetvalue(res, i, i_ceknamespace)));
+		cekinfo[i].rolname = getRoleName(PQgetvalue(res, i, i_cekowner));
+
+		resetPQExpBuffer(query);
+		appendPQExpBuffer(query,
+						  "SELECT ckdcmkid, ckdcmkalg, ckdencval\n"
+						  "FROM pg_catalog.pg_colenckeydata\n"
+						  "WHERE ckdcekid = %u", cekinfo[i].dobj.catId.oid);
+		res2 = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+		ntups2 = PQntuples(res2);
+		cekinfo[i].numdata = ntups2;
+		cekinfo[i].cekcmks = pg_malloc(sizeof(CmkInfo *) * ntups2);
+		cekinfo[i].cekcmkalgs = pg_malloc(sizeof(int) * ntups2);
+		cekinfo[i].cekencvals = pg_malloc(sizeof(char *) * ntups2);
+		for (int j = 0; j < ntups2; j++)
+		{
+			Oid			ckdcmkid;
+
+			ckdcmkid = atooid(PQgetvalue(res2, j, PQfnumber(res2, "ckdcmkid")));
+			cekinfo[i].cekcmks[j] = findCmkByOid(ckdcmkid);
+			cekinfo[i].cekcmkalgs[j] = atoi(PQgetvalue(res2, j, PQfnumber(res2, "ckdcmkalg")));
+			cekinfo[i].cekencvals[j] = pg_strdup(PQgetvalue(res2, j, PQfnumber(res2, "ckdencval")));
+		}
+		PQclear(res2);
+
+		selectDumpableObject(&(cekinfo[i].dobj), fout);
+	}
+	PQclear(res);
+
+	destroyPQExpBuffer(query);
+}
+
+/*
+ * getColumnMasterKeys
+ *	  get information about column master keys
+ */
+void
+getColumnMasterKeys(Archive *fout)
+{
+	PQExpBuffer query;
+	PGresult   *res;
+	int			ntups;
+	CmkInfo	   *cmkinfo;
+	int			i_tableoid;
+	int			i_oid;
+	int			i_cmkname;
+	int			i_cmknamespace;
+	int			i_cmkowner;
+	int			i_cmkrealm;
+
+	if (fout->remoteVersion < 160000)
+		return;
+
+	query = createPQExpBuffer();
+
+	appendPQExpBuffer(query,
+					  "SELECT cmk.tableoid, cmk.oid, cmk.cmkname, cmk.cmknamespace, cmk.cmkowner, cmk.cmkrealm\n"
+					  "FROM pg_colmasterkey cmk");
+
+	res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+	ntups = PQntuples(res);
+
+	i_tableoid = PQfnumber(res, "tableoid");
+	i_oid = PQfnumber(res, "oid");
+	i_cmkname = PQfnumber(res, "cmkname");
+	i_cmknamespace = PQfnumber(res, "cmknamespace");
+	i_cmkowner = PQfnumber(res, "cmkowner");
+	i_cmkrealm = PQfnumber(res, "cmkrealm");
+
+	cmkinfo = pg_malloc(ntups * sizeof(CmkInfo));
+
+	for (int i = 0; i < ntups; i++)
+	{
+		cmkinfo[i].dobj.objType = DO_CMK;
+		cmkinfo[i].dobj.catId.tableoid = atooid(PQgetvalue(res, i, i_tableoid));
+		cmkinfo[i].dobj.catId.oid = atooid(PQgetvalue(res, i, i_oid));
+		AssignDumpId(&cmkinfo[i].dobj);
+		cmkinfo[i].dobj.name = pg_strdup(PQgetvalue(res, i, i_cmkname));
+		cmkinfo[i].dobj.namespace = findNamespace(atooid(PQgetvalue(res, i, i_cmknamespace)));
+		cmkinfo[i].rolname = getRoleName(PQgetvalue(res, i, i_cmkowner));
+		cmkinfo[i].cmkrealm = pg_strdup(PQgetvalue(res, i, i_cmkrealm));
+
+		selectDumpableObject(&(cmkinfo[i].dobj), fout);
+	}
+	PQclear(res);
+
+	destroyPQExpBuffer(query);
+}
+
 /*
  * getConversions:
  *	  read all conversions in the system catalogs and return them in the
@@ -8188,6 +8334,9 @@ getTableAttrs(Archive *fout, TableInfo *tblinfo, int numTables)
 	int			i_typstorage;
 	int			i_attidentity;
 	int			i_attgenerated;
+	int			i_attcek;
+	int			i_attencalg;
+	int			i_attencdet;
 	int			i_attisdropped;
 	int			i_attlen;
 	int			i_attalign;
@@ -8297,20 +8446,35 @@ getTableAttrs(Archive *fout, TableInfo *tblinfo, int numTables)
 
 	if (fout->remoteVersion >= 120000)
 		appendPQExpBufferStr(q,
-							 "a.attgenerated\n");
+							 "a.attgenerated,\n");
 	else
 		appendPQExpBufferStr(q,
-							 "'' AS attgenerated\n");
+							 "'' AS attgenerated,\n");
+
+	if (fout->remoteVersion >= 160000)
+		appendPQExpBuffer(q,
+						  "a.attcek,\n"
+						  "CASE atttypid WHEN %u THEN true WHEN %u THEN false END AS attencdet,\n"
+						  "attencalg\n",
+						  PG_ENCRYPTED_DETOID, PG_ENCRYPTED_RNDOID);
+	else
+		appendPQExpBufferStr(q,
+							 "NULL AS attcek,\n"
+							 "NULL AS attencdet,\n"
+							 "NULL AS attencalg\n");
 
 	/* need left join to pg_type to not fail on dropped columns ... */
 	appendPQExpBuffer(q,
 					  "FROM unnest('%s'::pg_catalog.oid[]) AS src(tbloid)\n"
 					  "JOIN pg_catalog.pg_attribute a ON (src.tbloid = a.attrelid) "
 					  "LEFT JOIN pg_catalog.pg_type t "
-					  "ON (a.atttypid = t.oid)\n"
+					  "ON (%s = t.oid)\n"
 					  "WHERE a.attnum > 0::pg_catalog.int2\n"
 					  "ORDER BY a.attrelid, a.attnum",
-					  tbloids->data);
+					  tbloids->data,
+					  fout->remoteVersion >= 160000 ?
+					  "CASE WHEN a.attrealtypid <> 0 THEN a.attrealtypid ELSE a.atttypid END" :
+					  "a.atttypid");
 
 	res = ExecuteSqlQuery(fout, q->data, PGRES_TUPLES_OK);
 
@@ -8326,6 +8490,9 @@ getTableAttrs(Archive *fout, TableInfo *tblinfo, int numTables)
 	i_typstorage = PQfnumber(res, "typstorage");
 	i_attidentity = PQfnumber(res, "attidentity");
 	i_attgenerated = PQfnumber(res, "attgenerated");
+	i_attcek = PQfnumber(res, "attcek");
+	i_attencdet = PQfnumber(res, "attencdet");
+	i_attencalg = PQfnumber(res, "attencalg");
 	i_attisdropped = PQfnumber(res, "attisdropped");
 	i_attlen = PQfnumber(res, "attlen");
 	i_attalign = PQfnumber(res, "attalign");
@@ -8387,6 +8554,9 @@ getTableAttrs(Archive *fout, TableInfo *tblinfo, int numTables)
 		tbinfo->typstorage = (char *) pg_malloc(numatts * sizeof(char));
 		tbinfo->attidentity = (char *) pg_malloc(numatts * sizeof(char));
 		tbinfo->attgenerated = (char *) pg_malloc(numatts * sizeof(char));
+		tbinfo->attcek = (CekInfo **) pg_malloc(numatts * sizeof(CekInfo *));
+		tbinfo->attencdet = (bool *) pg_malloc(numatts * sizeof(bool));
+		tbinfo->attencalg = (int *) pg_malloc(numatts * sizeof(int));
 		tbinfo->attisdropped = (bool *) pg_malloc(numatts * sizeof(bool));
 		tbinfo->attlen = (int *) pg_malloc(numatts * sizeof(int));
 		tbinfo->attalign = (char *) pg_malloc(numatts * sizeof(char));
@@ -8415,6 +8585,22 @@ getTableAttrs(Archive *fout, TableInfo *tblinfo, int numTables)
 			tbinfo->attidentity[j] = *(PQgetvalue(res, r, i_attidentity));
 			tbinfo->attgenerated[j] = *(PQgetvalue(res, r, i_attgenerated));
 			tbinfo->needs_override = tbinfo->needs_override || (tbinfo->attidentity[j] == ATTRIBUTE_IDENTITY_ALWAYS);
+			if (!PQgetisnull(res, r, i_attcek))
+			{
+				Oid		attcekid = atooid(PQgetvalue(res, r, i_attcek));
+
+				tbinfo->attcek[j] = findCekByOid(attcekid);
+			}
+			else
+				tbinfo->attcek[j] = NULL;
+			if (!PQgetisnull(res, r, i_attencdet))
+				tbinfo->attencdet[j] = (PQgetvalue(res, r, i_attencdet)[0] == 't');
+			else
+				tbinfo->attencdet[j] = 0;
+			if (!PQgetisnull(res, r, i_attencalg))
+				tbinfo->attencalg[j] = atoi(PQgetvalue(res, r, i_attencalg));
+			else
+				tbinfo->attencalg[j] = 0;
 			tbinfo->attisdropped[j] = (PQgetvalue(res, r, i_attisdropped)[0] == 't');
 			tbinfo->attlen[j] = atoi(PQgetvalue(res, r, i_attlen));
 			tbinfo->attalign[j] = *(PQgetvalue(res, r, i_attalign));
@@ -9933,6 +10119,12 @@ dumpDumpableObject(Archive *fout, DumpableObject *dobj)
 		case DO_OPFAMILY:
 			dumpOpfamily(fout, (const OpfamilyInfo *) dobj);
 			break;
+		case DO_CEK:
+			dumpColumnEncryptionKey(fout, (const CekInfo *) dobj);
+			break;
+		case DO_CMK:
+			dumpColumnMasterKey(fout, (const CmkInfo *) dobj);
+			break;
 		case DO_COLLATION:
 			dumpCollation(fout, (const CollInfo *) dobj);
 			break;
@@ -13326,6 +13518,131 @@ dumpCollation(Archive *fout, const CollInfo *collinfo)
 	free(qcollname);
 }
 
+/*
+ * dumpColumnEncryptionKey
+ *	  dump the definition of the given column encryption key
+ */
+static void
+dumpColumnEncryptionKey(Archive *fout, const CekInfo *cekinfo)
+{
+	DumpOptions *dopt = fout->dopt;
+	PQExpBuffer delq;
+	PQExpBuffer query;
+	char	   *qcekname;
+
+	/* Do nothing in data-only dump */
+	if (dopt->dataOnly)
+		return;
+
+	delq = createPQExpBuffer();
+	query = createPQExpBuffer();
+
+	qcekname = pg_strdup(fmtId(cekinfo->dobj.name));
+
+	appendPQExpBuffer(delq, "DROP COLUMN ENCRYPTION KEY %s;\n",
+					  fmtQualifiedDumpable(cekinfo));
+
+	appendPQExpBuffer(query, "CREATE COLUMN ENCRYPTION KEY %s WITH VALUES ",
+					  fmtQualifiedDumpable(cekinfo));
+
+	for (int i = 0; i < cekinfo->numdata; i++)
+	{
+		appendPQExpBuffer(query, "(");
+
+		appendPQExpBuffer(query, "column_master_key = %s, ", fmtQualifiedDumpable(cekinfo->cekcmks[i]));
+		appendPQExpBuffer(query, "algorithm = '%s', ", get_cmkalg_name(cekinfo->cekcmkalgs[i]));
+		appendPQExpBuffer(query, "encrypted_value = ");
+		appendStringLiteralAH(query, cekinfo->cekencvals[i], fout);
+
+		appendPQExpBuffer(query, ")");
+		if (i < cekinfo->numdata - 1)
+			appendPQExpBuffer(query, ", ");
+	}
+
+	appendPQExpBufferStr(query, ";\n");
+
+	if (cekinfo->dobj.dump & DUMP_COMPONENT_DEFINITION)
+		ArchiveEntry(fout, cekinfo->dobj.catId, cekinfo->dobj.dumpId,
+					 ARCHIVE_OPTS(.tag = cekinfo->dobj.name,
+								  .namespace = cekinfo->dobj.namespace->dobj.name,
+								  .owner = cekinfo->rolname,
+								  .description = "COLUMN ENCRYPTION KEY",
+								  .section = SECTION_PRE_DATA,
+								  .createStmt = query->data,
+								  .dropStmt = delq->data));
+
+	if (cekinfo->dobj.dump & DUMP_COMPONENT_COMMENT)
+		dumpComment(fout, "COLUMN ENCRYPTION KEY", qcekname,
+					cekinfo->dobj.namespace->dobj.name, cekinfo->rolname,
+					cekinfo->dobj.catId, 0, cekinfo->dobj.dumpId);
+
+	if (cekinfo->dobj.dump & DUMP_COMPONENT_SECLABEL)
+		dumpSecLabel(fout, "COLUMN ENCRYPTION KEY", qcekname,
+					 cekinfo->dobj.namespace->dobj.name, cekinfo->rolname,
+					 cekinfo->dobj.catId, 0, cekinfo->dobj.dumpId);
+
+	destroyPQExpBuffer(delq);
+	destroyPQExpBuffer(query);
+	free(qcekname);
+}
+
+/*
+ * dumpColumnMasterKey
+ *	  dump the definition of the given column master key
+ */
+static void
+dumpColumnMasterKey(Archive *fout, const CmkInfo *cmkinfo)
+{
+	DumpOptions *dopt = fout->dopt;
+	PQExpBuffer delq;
+	PQExpBuffer query;
+	char	   *qcmkname;
+
+	/* Do nothing in data-only dump */
+	if (dopt->dataOnly)
+		return;
+
+	delq = createPQExpBuffer();
+	query = createPQExpBuffer();
+
+	qcmkname = pg_strdup(fmtId(cmkinfo->dobj.name));
+
+	appendPQExpBuffer(delq, "DROP COLUMN MASTER KEY %s;\n",
+					  fmtQualifiedDumpable(cmkinfo));
+
+	appendPQExpBuffer(query, "CREATE COLUMN MASTER KEY %s WITH (",
+					  fmtQualifiedDumpable(cmkinfo));
+
+	appendPQExpBuffer(query, "realm = ");
+	appendStringLiteralAH(query, cmkinfo->cmkrealm, fout);
+
+	appendPQExpBufferStr(query, ");\n");
+
+	if (cmkinfo->dobj.dump & DUMP_COMPONENT_DEFINITION)
+		ArchiveEntry(fout, cmkinfo->dobj.catId, cmkinfo->dobj.dumpId,
+					 ARCHIVE_OPTS(.tag = cmkinfo->dobj.name,
+								  .namespace = cmkinfo->dobj.namespace->dobj.name,
+								  .owner = cmkinfo->rolname,
+								  .description = "COLUMN MASTER KEY",
+								  .section = SECTION_PRE_DATA,
+								  .createStmt = query->data,
+								  .dropStmt = delq->data));
+
+	if (cmkinfo->dobj.dump & DUMP_COMPONENT_COMMENT)
+		dumpComment(fout, "COLUMN MASTER KEY", qcmkname,
+					cmkinfo->dobj.namespace->dobj.name, cmkinfo->rolname,
+					cmkinfo->dobj.catId, 0, cmkinfo->dobj.dumpId);
+
+	if (cmkinfo->dobj.dump & DUMP_COMPONENT_SECLABEL)
+		dumpSecLabel(fout, "COLUMN MASTER KEY", qcmkname,
+					 cmkinfo->dobj.namespace->dobj.name, cmkinfo->rolname,
+					 cmkinfo->dobj.catId, 0, cmkinfo->dobj.dumpId);
+
+	destroyPQExpBuffer(delq);
+	destroyPQExpBuffer(query);
+	free(qcmkname);
+}
+
 /*
  * dumpConversion
  *	  write out a single conversion definition
@@ -15409,6 +15726,22 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo)
 										  tbinfo->atttypnames[j]);
 					}
 
+					if (tbinfo->attcek[j])
+					{
+						appendPQExpBuffer(q, " ENCRYPTED WITH (column_encryption_key = %s, ",
+										  fmtQualifiedDumpable(tbinfo->attcek[j]));
+						/*
+						 * To reduce output size, we don't print the default
+						 * of encryption_type, but we do print the default of
+						 * algorithm, since we might want to change to a new
+						 * default algorithm sometime in the future.
+						 */
+						if (tbinfo->attencdet[j])
+							appendPQExpBuffer(q, "encryption_type = deterministic, ");
+						appendPQExpBuffer(q, "algorithm = '%s')",
+										  get_cekalg_name(tbinfo->attencalg[j]));
+					}
+
 					if (print_default)
 					{
 						if (tbinfo->attgenerated[j] == ATTRIBUTE_GENERATED_STORED)
@@ -17972,6 +18305,8 @@ addBoundaryDependencies(DumpableObject **dobjs, int numObjs,
 			case DO_ACCESS_METHOD:
 			case DO_OPCLASS:
 			case DO_OPFAMILY:
+			case DO_CEK:
+			case DO_CMK:
 			case DO_COLLATION:
 			case DO_CONVERSION:
 			case DO_TABLE:
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index e7cbd8d7ed..99b6b20ced 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -47,6 +47,8 @@ typedef enum
 	DO_ACCESS_METHOD,
 	DO_OPCLASS,
 	DO_OPFAMILY,
+	DO_CEK,
+	DO_CMK,
 	DO_COLLATION,
 	DO_CONVERSION,
 	DO_TABLE,
@@ -333,6 +335,9 @@ typedef struct _tableInfo
 	bool	   *attisdropped;	/* true if attr is dropped; don't dump it */
 	char	   *attidentity;
 	char	   *attgenerated;
+	struct _CekInfo **attcek;
+	int		   *attencalg;
+	bool	   *attencdet;
 	int		   *attlen;			/* attribute length, used by binary_upgrade */
 	char	   *attalign;		/* attribute align, used by binary_upgrade */
 	bool	   *attislocal;		/* true if attr has local definition */
@@ -664,6 +669,30 @@ typedef struct _SubscriptionInfo
 	char	   *subpublications;
 } SubscriptionInfo;
 
+/*
+ * The CekInfo struct is used to represent column encryption key.
+ */
+typedef struct _CekInfo
+{
+	DumpableObject dobj;
+	const char *rolname;
+	int			numdata;
+	/* The following are arrays of numdata entries each: */
+	struct _CmkInfo **cekcmks;
+	int		   *cekcmkalgs;
+	char	  **cekencvals;
+} CekInfo;
+
+/*
+ * The CmkInfo struct is used to represent column master key.
+ */
+typedef struct _CmkInfo
+{
+	DumpableObject dobj;
+	const char *rolname;
+	char	   *cmkrealm;
+} CmkInfo;
+
 /*
  *	common utility functions
  */
@@ -684,6 +713,8 @@ extern TableInfo *findTableByOid(Oid oid);
 extern TypeInfo *findTypeByOid(Oid oid);
 extern FuncInfo *findFuncByOid(Oid oid);
 extern OprInfo *findOprByOid(Oid oid);
+extern CekInfo *findCekByOid(Oid oid);
+extern CmkInfo *findCmkByOid(Oid oid);
 extern CollInfo *findCollationByOid(Oid oid);
 extern NamespaceInfo *findNamespaceByOid(Oid oid);
 extern ExtensionInfo *findExtensionByOid(Oid oid);
@@ -711,6 +742,8 @@ extern AccessMethodInfo *getAccessMethods(Archive *fout, int *numAccessMethods);
 extern OpclassInfo *getOpclasses(Archive *fout, int *numOpclasses);
 extern OpfamilyInfo *getOpfamilies(Archive *fout, int *numOpfamilies);
 extern CollInfo *getCollations(Archive *fout, int *numCollations);
+extern void getColumnEncryptionKeys(Archive *fout);
+extern void getColumnMasterKeys(Archive *fout);
 extern ConvInfo *getConversions(Archive *fout, int *numConversions);
 extern TableInfo *getTables(Archive *fout, int *numTables);
 extern void getOwnedSeqs(Archive *fout, TableInfo tblinfo[], int numTables);
diff --git a/src/bin/pg_dump/pg_dump_sort.c b/src/bin/pg_dump/pg_dump_sort.c
index f963b9a449..d56372cd1c 100644
--- a/src/bin/pg_dump/pg_dump_sort.c
+++ b/src/bin/pg_dump/pg_dump_sort.c
@@ -69,6 +69,8 @@ enum dbObjectTypePriorities
 	PRIO_TSTEMPLATE,
 	PRIO_TSDICT,
 	PRIO_TSCONFIG,
+	PRIO_CMK,
+	PRIO_CEK,
 	PRIO_FDW,
 	PRIO_FOREIGN_SERVER,
 	PRIO_TABLE,
@@ -111,6 +113,8 @@ static const int dbObjectTypePriority[] =
 	PRIO_ACCESS_METHOD,			/* DO_ACCESS_METHOD */
 	PRIO_OPFAMILY,				/* DO_OPCLASS */
 	PRIO_OPFAMILY,				/* DO_OPFAMILY */
+	PRIO_CEK,					/* DO_CEK */
+	PRIO_CMK,					/* DO_CMK */
 	PRIO_COLLATION,				/* DO_COLLATION */
 	PRIO_CONVERSION,			/* DO_CONVERSION */
 	PRIO_TABLE,					/* DO_TABLE */
@@ -1322,6 +1326,16 @@ describeDumpableObject(DumpableObject *obj, char *buf, int bufsize)
 					 "OPERATOR FAMILY %s  (ID %d OID %u)",
 					 obj->name, obj->dumpId, obj->catId.oid);
 			return;
+		case DO_CEK:
+			snprintf(buf, bufsize,
+					 "COLUMN ENCRYPTION KEY (ID %d OID %u)",
+					 obj->dumpId, obj->catId.oid);
+			return;
+		case DO_CMK:
+			snprintf(buf, bufsize,
+					 "COLUMN MASTER KEY (ID %d OID %u)",
+					 obj->dumpId, obj->catId.oid);
+			return;
 		case DO_COLLATION:
 			snprintf(buf, bufsize,
 					 "COLLATION %s  (ID %d OID %u)",
diff --git a/src/bin/pg_dump/pg_dumpall.c b/src/bin/pg_dump/pg_dumpall.c
index fbd1c6fc85..f35f3b26ae 100644
--- a/src/bin/pg_dump/pg_dumpall.c
+++ b/src/bin/pg_dump/pg_dumpall.c
@@ -93,6 +93,7 @@ static bool dosync = true;
 
 static int	binary_upgrade = 0;
 static int	column_inserts = 0;
+static int	decrypt_encrypted_columns = 0;
 static int	disable_dollar_quoting = 0;
 static int	disable_triggers = 0;
 static int	if_exists = 0;
@@ -154,6 +155,7 @@ main(int argc, char *argv[])
 		{"attribute-inserts", no_argument, &column_inserts, 1},
 		{"binary-upgrade", no_argument, &binary_upgrade, 1},
 		{"column-inserts", no_argument, &column_inserts, 1},
+		{"decrypt-encrypted-columns", no_argument, &decrypt_encrypted_columns, 1},
 		{"disable-dollar-quoting", no_argument, &disable_dollar_quoting, 1},
 		{"disable-triggers", no_argument, &disable_triggers, 1},
 		{"exclude-database", required_argument, NULL, 6},
@@ -424,6 +426,8 @@ main(int argc, char *argv[])
 		appendPQExpBufferStr(pgdumpopts, " --binary-upgrade");
 	if (column_inserts)
 		appendPQExpBufferStr(pgdumpopts, " --column-inserts");
+	if (decrypt_encrypted_columns)
+		appendPQExpBufferStr(pgdumpopts, " --decrypt-encrypted-columns");
 	if (disable_dollar_quoting)
 		appendPQExpBufferStr(pgdumpopts, " --disable-dollar-quoting");
 	if (disable_triggers)
@@ -649,6 +653,7 @@ help(void)
 	printf(_("  -x, --no-privileges          do not dump privileges (grant/revoke)\n"));
 	printf(_("  --binary-upgrade             for use by upgrade utilities only\n"));
 	printf(_("  --column-inserts             dump data as INSERT commands with column names\n"));
+	printf(_("  --decrypt-encrypted-columns  decrypt encrypted columns in the output\n"));
 	printf(_("  --disable-dollar-quoting     disable dollar quoting, use SQL standard quoting\n"));
 	printf(_("  --disable-triggers           disable triggers during data-only restore\n"));
 	printf(_("  --exclude-database=PATTERN   exclude databases whose name matches PATTERN\n"));
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index d92247c915..dec2a0d305 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -645,6 +645,18 @@
 		unlike    => { %dump_test_schema_runs, no_owner => 1, },
 	},
 
+	'ALTER COLUMN ENCRYPTION KEY cek1 OWNER TO' => {
+		regexp => qr/^ALTER COLUMN ENCRYPTION KEY dump_test.cek1 OWNER TO .+;/m,
+		like   => { %full_runs, %dump_test_schema_runs, section_pre_data => 1, },
+		unlike => { exclude_dump_test_schema => 1, no_owner => 1, },
+	},
+
+	'ALTER COLUMN MASTER KEY cmk1 OWNER TO' => {
+		regexp => qr/^ALTER COLUMN MASTER KEY dump_test.cmk1 OWNER TO .+;/m,
+		like   => { %full_runs, %dump_test_schema_runs, section_pre_data => 1, },
+		unlike => { exclude_dump_test_schema => 1, no_owner => 1, },
+	},
+
 	'ALTER FOREIGN DATA WRAPPER dummy OWNER TO' => {
 		regexp => qr/^ALTER FOREIGN DATA WRAPPER dummy OWNER TO .+;/m,
 		like   => { %full_runs, section_pre_data => 1, },
@@ -1245,6 +1257,26 @@
 		like      => { %full_runs, section_pre_data => 1, },
 	},
 
+	'COMMENT ON COLUMN ENCRYPTION KEY cek1' => {
+		create_order => 55,
+		create_sql   => 'COMMENT ON COLUMN ENCRYPTION KEY dump_test.cek1
+					   IS \'comment on column encryption key\';',
+		regexp =>
+		  qr/^COMMENT ON COLUMN ENCRYPTION KEY dump_test.cek1 IS 'comment on column encryption key';/m,
+		like => { %full_runs, %dump_test_schema_runs, section_pre_data => 1, },
+		unlike => { exclude_dump_test_schema => 1, },
+	},
+
+	'COMMENT ON COLUMN MASTER KEY cmk1' => {
+		create_order => 55,
+		create_sql   => 'COMMENT ON COLUMN MASTER KEY dump_test.cmk1
+					   IS \'comment on column master key\';',
+		regexp =>
+		  qr/^COMMENT ON COLUMN MASTER KEY dump_test.cmk1 IS 'comment on column master key';/m,
+		like => { %full_runs, %dump_test_schema_runs, section_pre_data => 1, },
+		unlike => { exclude_dump_test_schema => 1, },
+	},
+
 	'COMMENT ON LARGE OBJECT ...' => {
 		create_order => 65,
 		create_sql   => 'DO $$
@@ -1663,6 +1695,26 @@
 		like => { %full_runs, section_pre_data => 1, },
 	},
 
+	'CREATE COLUMN ENCRYPTION KEY cek1' => {
+		create_order => 51,
+		create_sql   => "CREATE COLUMN ENCRYPTION KEY dump_test.cek1 WITH VALUES (column_master_key = dump_test.cmk1, algorithm = 'RSAES_OAEP_SHA_1', encrypted_value = '\\xDEADBEEF');",
+		regexp       => qr/^
+			\QCREATE COLUMN ENCRYPTION KEY dump_test.cek1 WITH VALUES (column_master_key = dump_test.cmk1, algorithm = 'RSAES_OAEP_SHA_1', encrypted_value = \E
+			/xm,
+		like => { %full_runs, %dump_test_schema_runs, section_pre_data => 1, },
+		unlike => { exclude_dump_test_schema => 1, },
+	},
+
+	'CREATE COLUMN MASTER KEY cmk1' => {
+		create_order => 50,
+		create_sql   => "CREATE COLUMN MASTER KEY dump_test.cmk1 WITH (realm = 'myrealm');",
+		regexp       => qr/^
+			\QCREATE COLUMN MASTER KEY dump_test.cmk1 WITH (realm = 'myrealm');\E
+			/xm,
+		like => { %full_runs, %dump_test_schema_runs, section_pre_data => 1, },
+		unlike => { exclude_dump_test_schema => 1, },
+	},
+
 	'CREATE DATABASE postgres' => {
 		regexp => qr/^
 			\QCREATE DATABASE postgres WITH TEMPLATE = template0 \E
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index b5201edf55..9e940dca6e 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -815,7 +815,11 @@ exec_command_d(PsqlScanState scan_state, bool active_branch, const char *cmd)
 				success = describeTablespaces(pattern, show_verbose);
 				break;
 			case 'c':
-				if (strncmp(cmd, "dconfig", 7) == 0)
+				if (strncmp(cmd, "dcek", 4) == 0)
+					success = listCEKs(pattern, show_verbose);
+				else if (strncmp(cmd, "dcmk", 4) == 0)
+					success = listCMKs(pattern, show_verbose);
+				else if (strncmp(cmd, "dconfig", 7) == 0)
 					success = describeConfigurationParameters(pattern,
 															  show_verbose,
 															  show_system);
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index c8a0bb7b3a..e004e30d86 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -1530,7 +1530,7 @@ describeOneTableDetails(const char *schemaname,
 	bool		printTableInitialized = false;
 	int			i;
 	char	   *view_def = NULL;
-	char	   *headers[12];
+	char	   *headers[13];
 	PQExpBufferData title;
 	PQExpBufferData tmpbuf;
 	int			cols;
@@ -1546,6 +1546,7 @@ describeOneTableDetails(const char *schemaname,
 				fdwopts_col = -1,
 				attstorage_col = -1,
 				attcompression_col = -1,
+				attcekname_col = -1,
 				attstattarget_col = -1,
 				attdescr_col = -1;
 	int			numrows;
@@ -1568,6 +1569,7 @@ describeOneTableDetails(const char *schemaname,
 		char	   *relam;
 	}			tableinfo;
 	bool		show_column_details = false;
+	const char *attrealtypid;
 
 	myopt.default_footer = false;
 	/* This output looks confusing in expanded mode. */
@@ -1844,7 +1846,11 @@ describeOneTableDetails(const char *schemaname,
 	cols = 0;
 	printfPQExpBuffer(&buf, "SELECT a.attname");
 	attname_col = cols++;
-	appendPQExpBufferStr(&buf, ",\n  pg_catalog.format_type(a.atttypid, a.atttypmod)");
+	if (pset.sversion >= 160000)
+		attrealtypid = "CASE WHEN a.attrealtypid <> 0 THEN a.attrealtypid ELSE a.atttypid END";
+	else
+		attrealtypid = "a.atttypid";
+	appendPQExpBuffer(&buf, ",\n  pg_catalog.format_type(%s, a.atttypmod)", attrealtypid);
 	atttype_col = cols++;
 
 	if (show_column_details)
@@ -1857,8 +1863,10 @@ describeOneTableDetails(const char *schemaname,
 							 ",\n  a.attnotnull");
 		attrdef_col = cols++;
 		attnotnull_col = cols++;
-		appendPQExpBufferStr(&buf, ",\n  (SELECT c.collname FROM pg_catalog.pg_collation c, pg_catalog.pg_type t\n"
-							 "   WHERE c.oid = a.attcollation AND t.oid = a.atttypid AND a.attcollation <> t.typcollation) AS attcollation");
+		appendPQExpBuffer(&buf, ",\n"
+						  "  (SELECT c.collname FROM pg_catalog.pg_collation c, pg_catalog.pg_type t\n"
+						  "   WHERE c.oid = a.attcollation AND t.oid = %s AND a.attcollation <> t.typcollation) AS attcollation",
+						  attrealtypid);
 		attcoll_col = cols++;
 		if (pset.sversion >= 100000)
 			appendPQExpBufferStr(&buf, ",\n  a.attidentity");
@@ -1909,6 +1917,18 @@ describeOneTableDetails(const char *schemaname,
 			attcompression_col = cols++;
 		}
 
+		/* encryption info */
+		if (pset.sversion >= 160000 &&
+			!pset.hide_column_encryption &&
+			(tableinfo.relkind == RELKIND_RELATION ||
+			 tableinfo.relkind == RELKIND_VIEW ||
+			 tableinfo.relkind == RELKIND_MATVIEW ||
+			 tableinfo.relkind == RELKIND_PARTITIONED_TABLE))
+		{
+			appendPQExpBufferStr(&buf, ",\n  (SELECT cekname FROM pg_colenckey cek WHERE cek.oid = a.attcek) AS attcekname");
+			attcekname_col = cols++;
+		}
+
 		/* stats target, if relevant to relkind */
 		if (tableinfo.relkind == RELKIND_RELATION ||
 			tableinfo.relkind == RELKIND_INDEX ||
@@ -2032,6 +2052,8 @@ describeOneTableDetails(const char *schemaname,
 		headers[cols++] = gettext_noop("Storage");
 	if (attcompression_col >= 0)
 		headers[cols++] = gettext_noop("Compression");
+	if (attcekname_col >= 0)
+		headers[cols++] = gettext_noop("Encryption");
 	if (attstattarget_col >= 0)
 		headers[cols++] = gettext_noop("Stats target");
 	if (attdescr_col >= 0)
@@ -2124,6 +2146,17 @@ describeOneTableDetails(const char *schemaname,
 							  false, false);
 		}
 
+		/* Column encryption */
+		if (attcekname_col >= 0)
+		{
+			if (!PQgetisnull(res, i, attcekname_col))
+				printTableAddCell(&cont, PQgetvalue(res, i, attcekname_col),
+								  false, false);
+			else
+				printTableAddCell(&cont, "",
+								  false, false);
+		}
+
 		/* Statistics target, if the relkind supports this feature */
 		if (attstattarget_col >= 0)
 			printTableAddCell(&cont, PQgetvalue(res, i, attstattarget_col),
@@ -4477,6 +4510,144 @@ listConversions(const char *pattern, bool verbose, bool showSystem)
 	return true;
 }
 
+/*
+ * \dcek
+ *
+ * Lists column encryption keys.
+ */
+bool
+listCEKs(const char *pattern, bool verbose)
+{
+	PQExpBufferData buf;
+	PGresult   *res;
+	printQueryOpt myopt = pset.popt;
+
+	if (pset.sversion < 160000)
+	{
+		char		sverbuf[32];
+
+		pg_log_error("The server (version %s) does not support column encryption.",
+					 formatPGVersionNumber(pset.sversion, false,
+										   sverbuf, sizeof(sverbuf)));
+		return true;
+	}
+
+	initPQExpBuffer(&buf);
+
+	printfPQExpBuffer(&buf,
+					  "SELECT "
+					  "n.nspname AS \"%s\", "
+					  "cekname AS \"%s\", "
+					  "pg_catalog.pg_get_userbyid(cekowner) AS \"%s\", "
+					  "cmkname AS \"%s\"",
+					  gettext_noop("Schema"),
+					  gettext_noop("Name"),
+					  gettext_noop("Owner"),
+					  gettext_noop("Master key"));
+	if (verbose)
+		appendPQExpBuffer(&buf,
+						  ", pg_catalog.obj_description(cek.oid, 'pg_colenckey') AS \"%s\"",
+						  gettext_noop("Description"));
+	appendPQExpBufferStr(&buf,
+						 "\nFROM pg_catalog.pg_colenckey cek "
+						 "LEFT JOIN pg_catalog.pg_namespace n ON n.oid = cek.ceknamespace "
+						 "JOIN pg_catalog.pg_colenckeydata ckd ON (cek.oid = ckd.ckdcekid) "
+						 "JOIN pg_catalog.pg_colmasterkey cmk ON (ckd.ckdcmkid = cmk.oid) ");
+
+	if (!validateSQLNamePattern(&buf, pattern, false, false,
+								"n.nspname", "cekname", NULL,
+								"pg_catalog.pg_cek_is_visible(cek.oid)",
+								NULL, 3))
+	{
+		termPQExpBuffer(&buf);
+		return false;
+	}
+
+	appendPQExpBufferStr(&buf, "ORDER BY 1, 2, 4");
+
+	res = PSQLexec(buf.data);
+	termPQExpBuffer(&buf);
+	if (!res)
+		return false;
+
+	myopt.nullPrint = NULL;
+	myopt.title = _("List of column encryption keys");
+	myopt.translate_header = true;
+
+	printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
+
+	PQclear(res);
+	return true;
+}
+
+/*
+ * \dcmk
+ *
+ * Lists column master keys.
+ */
+bool
+listCMKs(const char *pattern, bool verbose)
+{
+	PQExpBufferData buf;
+	PGresult   *res;
+	printQueryOpt myopt = pset.popt;
+
+	if (pset.sversion < 160000)
+	{
+		char		sverbuf[32];
+
+		pg_log_error("The server (version %s) does not support column encryption.",
+					 formatPGVersionNumber(pset.sversion, false,
+										   sverbuf, sizeof(sverbuf)));
+		return true;
+	}
+
+	initPQExpBuffer(&buf);
+
+	printfPQExpBuffer(&buf,
+					  "SELECT "
+					  "n.nspname AS \"%s\", "
+					  "cmkname AS \"%s\", "
+					  "pg_catalog.pg_get_userbyid(cmkowner) AS \"%s\", "
+					  "cmkrealm AS \"%s\"",
+					  gettext_noop("Schema"),
+					  gettext_noop("Name"),
+					  gettext_noop("Owner"),
+					  gettext_noop("Realm"));
+	if (verbose)
+		appendPQExpBuffer(&buf,
+						  ", pg_catalog.obj_description(oid, 'pg_colmasterkey') AS \"%s\"",
+						  gettext_noop("Description"));
+	appendPQExpBufferStr(&buf,
+						 "\nFROM pg_catalog.pg_colmasterkey cmk "
+						 "LEFT JOIN pg_catalog.pg_namespace n ON n.oid = cmk.cmknamespace ");
+
+	if (!validateSQLNamePattern(&buf, pattern, false, false,
+								"n.nspname", "cmkname", NULL,
+								"pg_catalog.pg_cmk_is_visible(cmk.oid)",
+								NULL, 3))
+	{
+		termPQExpBuffer(&buf);
+		return false;
+	}
+
+	appendPQExpBufferStr(&buf, "ORDER BY 1, 2");
+
+	res = PSQLexec(buf.data);
+	termPQExpBuffer(&buf);
+	if (!res)
+		return false;
+
+	myopt.nullPrint = NULL;
+	myopt.title = _("List of column master keys");
+	myopt.translate_header = true;
+
+	printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
+
+	PQclear(res);
+	return true;
+}
+
 /*
  * \dconfig
  *
diff --git a/src/bin/psql/describe.h b/src/bin/psql/describe.h
index 554fe86725..1cf8f72176 100644
--- a/src/bin/psql/describe.h
+++ b/src/bin/psql/describe.h
@@ -76,6 +76,12 @@ extern bool listDomains(const char *pattern, bool verbose, bool showSystem);
 /* \dc */
 extern bool listConversions(const char *pattern, bool verbose, bool showSystem);
 
+/* \dcek */
+extern bool listCEKs(const char *pattern, bool verbose);
+
+/* \dcmk */
+extern bool listCMKs(const char *pattern, bool verbose);
+
 /* \dconfig */
 extern bool describeConfigurationParameters(const char *pattern, bool verbose,
 											bool showSystem);
diff --git a/src/bin/psql/help.c b/src/bin/psql/help.c
index e45c4aaca5..1729966959 100644
--- a/src/bin/psql/help.c
+++ b/src/bin/psql/help.c
@@ -252,6 +252,8 @@ slashUsage(unsigned short int pager)
 	HELP0("  \\dAp[+] [AMPTRN [OPFPTRN]]   list support functions of operator families\n");
 	HELP0("  \\db[+]  [PATTERN]      list tablespaces\n");
 	HELP0("  \\dc[S+] [PATTERN]      list conversions\n");
+	HELP0("  \\dcek[+] [PATTERN]     list column encryption keys\n");
+	HELP0("  \\dcmk[+] [PATTERN]     list column master keys\n");
 	HELP0("  \\dconfig[+] [PATTERN]  list configuration parameters\n");
 	HELP0("  \\dC[+]  [PATTERN]      list casts\n");
 	HELP0("  \\dd[S]  [PATTERN]      show object descriptions not displayed elsewhere\n");
@@ -413,6 +415,8 @@ helpVariables(unsigned short int pager)
 		  "    true if last query failed, else false\n");
 	HELP0("  FETCH_COUNT\n"
 		  "    the number of result rows to fetch and display at a time (0 = unlimited)\n");
+	HELP0("  HIDE_COLUMN_ENCRYPTION\n"
+		  "    if set, column encryption details are not displayed\n");
 	HELP0("  HIDE_TABLEAM\n"
 		  "    if set, table access methods are not displayed\n");
 	HELP0("  HIDE_TOAST_COMPRESSION\n"
diff --git a/src/bin/psql/settings.h b/src/bin/psql/settings.h
index 73d4b393bc..010bc5a6d5 100644
--- a/src/bin/psql/settings.h
+++ b/src/bin/psql/settings.h
@@ -137,6 +137,7 @@ typedef struct _psqlSettings
 	bool		quiet;
 	bool		singleline;
 	bool		singlestep;
+	bool		hide_column_encryption;
 	bool		hide_compression;
 	bool		hide_tableam;
 	int			fetch_count;
diff --git a/src/bin/psql/startup.c b/src/bin/psql/startup.c
index 5a28b6f713..6736505c3a 100644
--- a/src/bin/psql/startup.c
+++ b/src/bin/psql/startup.c
@@ -1188,6 +1188,13 @@ hide_compression_hook(const char *newval)
 							 &pset.hide_compression);
 }
 
+static bool
+hide_column_encryption_hook(const char *newval)
+{
+	return ParseVariableBool(newval, "HIDE_COLUMN_ENCRYPTION",
+							 &pset.hide_column_encryption);
+}
+
 static bool
 hide_tableam_hook(const char *newval)
 {
@@ -1259,6 +1266,9 @@ EstablishVariableSpace(void)
 	SetVariableHooks(pset.vars, "SHOW_CONTEXT",
 					 show_context_substitute_hook,
 					 show_context_hook);
+	SetVariableHooks(pset.vars, "HIDE_COLUMN_ENCRYPTION",
+					 bool_substitute_hook,
+					 hide_column_encryption_hook);
 	SetVariableHooks(pset.vars, "HIDE_TOAST_COMPRESSION",
 					 bool_substitute_hook,
 					 hide_compression_hook);
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 5e1882eaea..fc8ed212b6 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -933,6 +933,20 @@ static const SchemaQuery Query_for_list_of_collations = {
 	.result = "c.collname",
 };
 
+static const SchemaQuery Query_for_list_of_ceks = {
+	.catname = "pg_catalog.pg_colenckey c",
+	.viscondition = "pg_catalog.pg_cek_is_visible(c.oid)",
+	.namespace = "c.ceknamespace",
+	.result = "c.cekname",
+};
+
+static const SchemaQuery Query_for_list_of_cmks = {
+	.catname = "pg_catalog.pg_colmasterkey c",
+	.viscondition = "pg_catalog.pg_cmk_is_visible(c.oid)",
+	.namespace = "c.cmknamespace",
+	.result = "c.cmkname",
+};
+
 static const SchemaQuery Query_for_partition_of_table = {
 	.catname = "pg_catalog.pg_class c1, pg_catalog.pg_class c2, pg_catalog.pg_inherits i",
 	.selcondition = "c1.oid=i.inhparent and i.inhrelid=c2.oid and c2.relispartition",
@@ -1223,6 +1237,8 @@ static const pgsql_thing_t words_after_create[] = {
 	{"CAST", NULL, NULL, NULL}, /* Casts have complex structures for names, so
 								 * skip it */
 	{"COLLATION", NULL, NULL, &Query_for_list_of_collations},
+	{"COLUMN ENCRYPTION KEY", NULL, NULL, NULL},
+	{"COLUMN MASTER KEY KEY", NULL, NULL, NULL},
 
 	/*
 	 * CREATE CONSTRAINT TRIGGER is not supported here because it is designed
@@ -1705,7 +1721,7 @@ psql_completion(const char *text, int start, int end)
 		"\\connect", "\\conninfo", "\\C", "\\cd", "\\copy",
 		"\\copyright", "\\crosstabview",
 		"\\d", "\\da", "\\dA", "\\dAc", "\\dAf", "\\dAo", "\\dAp",
-		"\\db", "\\dc", "\\dconfig", "\\dC", "\\dd", "\\ddp", "\\dD",
+		"\\db", "\\dc", "\\dcek", "\\dcmk", "\\dconfig", "\\dC", "\\dd", "\\ddp", "\\dD",
 		"\\des", "\\det", "\\deu", "\\dew", "\\dE", "\\df",
 		"\\dF", "\\dFd", "\\dFp", "\\dFt", "\\dg", "\\di", "\\dl", "\\dL",
 		"\\dm", "\\dn", "\\do", "\\dO", "\\dp", "\\dP", "\\dPi", "\\dPt",
@@ -1952,6 +1968,22 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches("ALTER", "COLLATION", MatchAny))
 		COMPLETE_WITH("OWNER TO", "REFRESH VERSION", "RENAME TO", "SET SCHEMA");
 
+	/* ALTER/DROP COLUMN ENCRYPTION KEY */
+	else if (Matches("ALTER|DROP", "COLUMN", "ENCRYPTION", "KEY"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_ceks);
+
+	/* ALTER COLUMN ENCRYPTION KEY */
+	else if (Matches("ALTER", "COLUMN", "ENCRYPTION", "KEY", MatchAny))
+		COMPLETE_WITH("ADD VALUE (", "DROP VALUE (", "OWNER TO", "RENAME TO", "SET SCHEMA");
+
+	/* ALTER/DROP COLUMN MASTER KEY */
+	else if (Matches("ALTER|DROP", "COLUMN", "MASTER", "KEY"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_cmks);
+
+	/* ALTER COLUMN MASTER KEY */
+	else if (Matches("ALTER", "COLUMN", "MASTER", "KEY", MatchAny))
+		COMPLETE_WITH("(", "OWNER TO", "RENAME TO", "SET SCHEMA");
+
 	/* ALTER CONVERSION <name> */
 	else if (Matches("ALTER", "CONVERSION", MatchAny))
 		COMPLETE_WITH("OWNER TO", "RENAME TO", "SET SCHEMA");
@@ -2894,6 +2926,26 @@ psql_completion(const char *text, int start, int end)
 			COMPLETE_WITH("true", "false");
 	}
 
+	/* CREATE/ALTER/DROP COLUMN ... KEY */
+	else if (Matches("CREATE|ALTER|DROP", "COLUMN"))
+		COMPLETE_WITH("ENCRYPTION", "MASTER");
+	else if (Matches("CREATE|ALTER|DROP", "COLUMN", "ENCRYPTION|MASTER"))
+		COMPLETE_WITH("KEY");
+
+	/* CREATE COLUMN ENCRYPTION KEY */
+	else if (Matches("CREATE", "COLUMN", "ENCRYPTION", "KEY", MatchAny))
+		COMPLETE_WITH("WITH");
+	else if (Matches("CREATE", "COLUMN", "ENCRYPTION", "KEY", MatchAny, "WITH"))
+		COMPLETE_WITH("VALUES");
+	else if (Matches("CREATE", "COLUMN", "ENCRYPTION", "KEY", MatchAny, "WITH", "VALUES"))
+		COMPLETE_WITH("(");
+
+	/* CREATE COLUMN MASTER KEY */
+	else if (Matches("CREATE", "COLUMN", "MASTER", "KEY", MatchAny))
+		COMPLETE_WITH("WITH");
+	else if (Matches("CREATE", "COLUMN", "MASTER", "KEY", MatchAny, "WITH"))
+		COMPLETE_WITH("(");
+
 	/* CREATE DATABASE */
 	else if (Matches("CREATE", "DATABASE", MatchAny))
 		COMPLETE_WITH("OWNER", "TEMPLATE", "ENCODING", "TABLESPACE",
@@ -3619,6 +3671,7 @@ psql_completion(const char *text, int start, int end)
 			 Matches("DROP", "ACCESS", "METHOD", MatchAny) ||
 			 (Matches("DROP", "AGGREGATE|FUNCTION|PROCEDURE|ROUTINE", MatchAny, MatchAny) &&
 			  ends_with(prev_wd, ')')) ||
+			 Matches("DROP", "COLUMN", "ENCRYPTION|MASTER", "KEY", MatchAny) ||
 			 Matches("DROP", "EVENT", "TRIGGER", MatchAny) ||
 			 Matches("DROP", "FOREIGN", "DATA", "WRAPPER", MatchAny) ||
 			 Matches("DROP", "FOREIGN", "TABLE", MatchAny) ||
diff --git a/src/common/Makefile b/src/common/Makefile
index 2f424a5735..c3ac6cbc35 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -48,6 +48,7 @@ LIBS += $(PTHREAD_LIBS)
 OBJS_COMMON = \
 	base64.o \
 	checksum_helper.o \
+	colenc.o \
 	compression.o \
 	config_info.o \
 	controldata_utils.o \
diff --git a/src/common/colenc.c b/src/common/colenc.c
new file mode 100644
index 0000000000..86c735878e
--- /dev/null
+++ b/src/common/colenc.c
@@ -0,0 +1,104 @@
+/*-------------------------------------------------------------------------
+ *
+ * colenc.c
+ *
+ * Shared code for column encryption algorithms.
+ *
+ * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *		  src/common/colenc.c
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef FRONTEND
+#include "postgres.h"
+#else
+#include "postgres_fe.h"
+#endif
+
+#include "common/colenc.h"
+
+int
+get_cmkalg_num(const char *name)
+{
+	if (strcmp(name, "unspecified") == 0)
+		return PG_CMK_UNSPECIFIED;
+	else if (strcmp(name, "RSAES_OAEP_SHA_1") == 0)
+		return PG_CMK_RSAES_OAEP_SHA_1;
+	else if (strcmp(name, "RSAES_OAEP_SHA_256") == 0)
+		return PG_CMK_RSAES_OAEP_SHA_256;
+	else
+		return 0;
+}
+
+const char *
+get_cmkalg_name(int num)
+{
+	switch (num)
+	{
+		case PG_CMK_UNSPECIFIED:
+			return "unspecified";
+		case PG_CMK_RSAES_OAEP_SHA_1:
+			return "RSAES_OAEP_SHA_1";
+		case PG_CMK_RSAES_OAEP_SHA_256:
+			return "RSAES_OAEP_SHA_256";
+	}
+
+	return NULL;
+}
+
+/*
+ * JSON Web Algorithms (JWA) names (RFC 7518)
+ *
+ * This is useful for some key management systems that use these names
+ * natively.
+ */
+const char *
+get_cmkalg_jwa_name(int num)
+{
+	switch (num)
+	{
+		case PG_CMK_UNSPECIFIED:
+			return NULL;
+		case PG_CMK_RSAES_OAEP_SHA_1:
+			return "RSA-OAEP";
+		case PG_CMK_RSAES_OAEP_SHA_256:
+			return "RSA-OAEP-256";
+	}
+
+	return NULL;
+}
+
+int
+get_cekalg_num(const char *name)
+{
+	if (strcmp(name, "AEAD_AES_128_CBC_HMAC_SHA_256") == 0)
+		return PG_CEK_AEAD_AES_128_CBC_HMAC_SHA_256;
+	else if (strcmp(name, "AEAD_AES_192_CBC_HMAC_SHA_384") == 0)
+		return PG_CEK_AEAD_AES_192_CBC_HMAC_SHA_384;
+	else if (strcmp(name, "AEAD_AES_256_CBC_HMAC_SHA_384") == 0)
+		return PG_CEK_AEAD_AES_256_CBC_HMAC_SHA_384;
+	else if (strcmp(name, "AEAD_AES_256_CBC_HMAC_SHA_512") == 0)
+		return PG_CEK_AEAD_AES_256_CBC_HMAC_SHA_512;
+	else
+		return 0;
+}
+
+const char *
+get_cekalg_name(int num)
+{
+	switch (num)
+	{
+		case PG_CEK_AEAD_AES_128_CBC_HMAC_SHA_256:
+			return "AEAD_AES_128_CBC_HMAC_SHA_256";
+		case PG_CEK_AEAD_AES_192_CBC_HMAC_SHA_384:
+			return "AEAD_AES_192_CBC_HMAC_SHA_384";
+		case PG_CEK_AEAD_AES_256_CBC_HMAC_SHA_384:
+			return "AEAD_AES_256_CBC_HMAC_SHA_384";
+		case PG_CEK_AEAD_AES_256_CBC_HMAC_SHA_512:
+			return "AEAD_AES_256_CBC_HMAC_SHA_512";
+	}
+
+	return NULL;
+}
diff --git a/src/common/meson.build b/src/common/meson.build
index 1caa1fed04..4ef62bddf2 100644
--- a/src/common/meson.build
+++ b/src/common/meson.build
@@ -3,6 +3,7 @@
 common_sources = files(
   'base64.c',
   'checksum_helper.c',
+  'colenc.c',
   'compression.c',
   'controldata_utils.c',
   'encnames.c',
diff --git a/src/include/access/printtup.h b/src/include/access/printtup.h
index 747ecb800d..d199033651 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/dependency.h b/src/include/catalog/dependency.h
index ffd5e9dc82..eb59e73c0a 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -92,6 +92,9 @@ typedef enum ObjectClass
 	OCLASS_TYPE,				/* pg_type */
 	OCLASS_CAST,				/* pg_cast */
 	OCLASS_COLLATION,			/* pg_collation */
+	OCLASS_CEK,					/* pg_colenckey */
+	OCLASS_CEKDATA,				/* pg_colenckeydata */
+	OCLASS_CMK,					/* pg_colmasterkey */
 	OCLASS_CONSTRAINT,			/* pg_constraint */
 	OCLASS_CONVERSION,			/* pg_conversion */
 	OCLASS_DEFAULT,				/* pg_attrdef */
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index d01ab504b6..758696b539 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -23,6 +23,7 @@
 #define CHKATYPE_ANYARRAY		0x01	/* allow ANYARRAY */
 #define CHKATYPE_ANYRECORD		0x02	/* allow RECORD and RECORD[] */
 #define CHKATYPE_IS_PARTKEY		0x04	/* attname is part key # not column */
+#define CHKATYPE_ENCRYPTED		0x08	/* allow internal encrypted types */
 
 typedef struct RawColumnDefault
 {
diff --git a/src/include/catalog/meson.build b/src/include/catalog/meson.build
index 3179be09d3..9e2c5256f7 100644
--- a/src/include/catalog/meson.build
+++ b/src/include/catalog/meson.build
@@ -65,6 +65,9 @@ catalog_headers = [
   'pg_publication_rel.h',
   'pg_subscription.h',
   'pg_subscription_rel.h',
+  'pg_colmasterkey.h',
+  'pg_colenckey.h',
+  'pg_colenckeydata.h',
 ]
 
 bki_data = [
diff --git a/src/include/catalog/namespace.h b/src/include/catalog/namespace.h
index f64a0ec26b..d0b9e8458d 100644
--- a/src/include/catalog/namespace.h
+++ b/src/include/catalog/namespace.h
@@ -115,6 +115,12 @@ extern bool OpclassIsVisible(Oid opcid);
 extern Oid	OpfamilynameGetOpfid(Oid amid, const char *opfname);
 extern bool OpfamilyIsVisible(Oid opfid);
 
+extern Oid	get_cek_oid(List *names, bool missing_ok);
+extern bool CEKIsVisible(Oid cekid);
+
+extern Oid	get_cmk_oid(List *names, bool missing_ok);
+extern bool CMKIsVisible(Oid cmkid);
+
 extern Oid	CollationGetCollid(const char *collname);
 extern bool CollationIsVisible(Oid collid);
 
diff --git a/src/include/catalog/pg_amop.dat b/src/include/catalog/pg_amop.dat
index c4d6adcd3e..c58b79e3a7 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' },
 
+# pg_encrypted_det_ops
+{ amopfamily => 'hash/pg_encrypted_det_ops', amoplefttype => 'pg_encrypted_det',
+  amoprighttype => 'pg_encrypted_det', amopstrategy => '1', amopopr => '=(pg_encrypted_det,pg_encrypted_det)',
+  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 5b950129de..0e9e85ebf3 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/pg_encrypted_det_ops', amproclefttype => 'pg_encrypted_det',
+  amprocrighttype => 'pg_encrypted_det', amprocnum => '1', amproc => 'hashvarlena' },
+{ amprocfamily => 'hash/pg_encrypted_det_ops', amproclefttype => 'pg_encrypted_det',
+  amprocrighttype => 'pg_encrypted_det', 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 b561e17781..4b5b5ad32f 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 (PG_CEK_* values) */
+	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_colenckey.h b/src/include/catalog/pg_colenckey.h
new file mode 100644
index 0000000000..be44c0d70d
--- /dev/null
+++ b/src/include/catalog/pg_colenckey.h
@@ -0,0 +1,41 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_colenckey.h
+ *	  definition of the "column encryption key" system catalog
+ *
+ * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/catalog/pg_colenkey.h
+ *
+ * NOTES
+ *	  The Catalog.pm module reads this file and derives schema
+ *	  information.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_COLENCKEY_H
+#define PG_COLENCKEY_H
+
+#include "catalog/genbki.h"
+#include "catalog/pg_colenckey_d.h"
+
+/* ----------------
+ *		pg_colenckey definition. cpp turns this into
+ *		typedef struct FormData_pg_colenckey
+ * ----------------
+ */
+CATALOG(pg_colenckey,8234,ColumnEncKeyRelationId)
+{
+	Oid			oid;
+	NameData	cekname;
+	Oid			ceknamespace BKI_DEFAULT(pg_catalog) BKI_LOOKUP(pg_namespace);
+	Oid			cekowner BKI_LOOKUP(pg_authid);
+} 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_nsp_index, 8242, ColumnEncKeyNameNspIndexId, on pg_colenckey using btree(cekname name_ops, ceknamespace oid_ops));
+
+#endif
diff --git a/src/include/catalog/pg_colenckeydata.h b/src/include/catalog/pg_colenckeydata.h
new file mode 100644
index 0000000000..c88e7e65ad
--- /dev/null
+++ b/src/include/catalog/pg_colenckeydata.h
@@ -0,0 +1,46 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_colenckeydata.h
+ *	  definition of the "column encryption key data" system catalog
+ *
+ * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/catalog/pg_colenkeydata.h
+ *
+ * NOTES
+ *	  The Catalog.pm module reads this file and derives schema
+ *	  information.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_COLENCKEYDATA_H
+#define PG_COLENCKEYDATA_H
+
+#include "catalog/genbki.h"
+#include "catalog/pg_colenckeydata_d.h"
+
+/* ----------------
+ *		pg_colenckeydata definition. cpp turns this into
+ *		typedef struct FormData_pg_colenckeydata
+ * ----------------
+ */
+CATALOG(pg_colenckeydata,8250,ColumnEncKeyDataRelationId)
+{
+	Oid			oid;
+	Oid			ckdcekid BKI_LOOKUP(pg_colenckey);
+	Oid			ckdcmkid BKI_LOOKUP(pg_colmasterkey);
+	int32		ckdcmkalg;		/* PG_CMK_* values */
+#ifdef CATALOG_VARLEN			/* variable-length fields start here */
+	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..a4a103bbed
--- /dev/null
+++ b/src/include/catalog/pg_colmasterkey.h
@@ -0,0 +1,46 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_colmasterkey.h
+ *	  definition of the "column master key" system catalog
+ *
+ * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/catalog/pg_colmasterkey.h
+ *
+ * NOTES
+ *	  The Catalog.pm module reads this file and derives schema
+ *	  information.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_COLMASTERKEY_H
+#define PG_COLMASTERKEY_H
+
+#include "catalog/genbki.h"
+#include "catalog/pg_colmasterkey_d.h"
+
+/* ----------------
+ *		pg_colmasterkey definition. cpp turns this into
+ *		typedef struct FormData_pg_colmasterkey
+ * ----------------
+ */
+CATALOG(pg_colmasterkey,8233,ColumnMasterKeyRelationId)
+{
+	Oid			oid;
+	NameData	cmkname;
+	Oid			cmknamespace BKI_DEFAULT(pg_catalog) BKI_LOOKUP(pg_namespace);
+	Oid			cmkowner BKI_LOOKUP(pg_authid);
+#ifdef CATALOG_VARLEN			/* variable-length fields start here */
+	text		cmkrealm 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_nsp_index, 8241, ColumnMasterKeyNameNspIndexId, on pg_colmasterkey using btree(cmkname name_ops, cmknamespace oid_ops));
+
+#endif
diff --git a/src/include/catalog/pg_opclass.dat b/src/include/catalog/pg_opclass.dat
index c867d99563..ff06d52fd0 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 => 'pg_encrypted_det_ops', opcfamily => 'hash/pg_encrypted_det_ops',
+  opcintype => 'pg_encrypted_det' },
 { 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 b2cdea66c4..114279fa64 100644
--- a/src/include/catalog/pg_operator.dat
+++ b/src/include/catalog/pg_operator.dat
@@ -3458,4 +3458,14 @@
   oprcode => 'multirange_after_multirange', oprrest => 'multirangesel',
   oprjoin => 'scalargtjoinsel' },
 
+{ oid => '8247', descr => 'equal',
+  oprname => '=', oprcanmerge => 'f', oprcanhash => 't', oprleft => 'pg_encrypted_det',
+  oprright => 'pg_encrypted_det', oprresult => 'bool', oprcom => '=(pg_encrypted_det,pg_encrypted_det)',
+  oprnegate => '<>(pg_encrypted_det,pg_encrypted_det)', oprcode => 'pg_encrypted_det_eq', oprrest => 'eqsel',
+  oprjoin => 'eqjoinsel' },
+{ oid => '8248', descr => 'not equal',
+  oprname => '<>', oprleft => 'pg_encrypted_det', oprright => 'pg_encrypted_det', oprresult => 'bool',
+  oprcom => '<>(pg_encrypted_det,pg_encrypted_det)', oprnegate => '=(pg_encrypted_det,pg_encrypted_det)',
+  oprcode => 'pg_encrypted_det_ne', oprrest => 'neqsel', oprjoin => 'neqjoinsel' },
+
 ]
diff --git a/src/include/catalog/pg_opfamily.dat b/src/include/catalog/pg_opfamily.dat
index 91587b99d0..c21052a3f7 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 => 'pg_encrypted_det_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 c0f2a8a77c..11bf7e05f1 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -6346,6 +6346,14 @@
   proname => 'pg_collation_is_visible', procost => '10', provolatile => 's',
   prorettype => 'bool', proargtypes => 'oid',
   prosrc => 'pg_collation_is_visible' },
+{ oid => '8261', descr => 'is column encryption key visible in search path?',
+  proname => 'pg_cek_is_visible', procost => '10', provolatile => 's',
+  prorettype => 'bool', proargtypes => 'oid',
+  prosrc => 'pg_cek_is_visible' },
+{ oid => '8262', descr => 'is column master key visible in search path?',
+  proname => 'pg_cmk_is_visible', procost => '10', provolatile => 's',
+  prorettype => 'bool', proargtypes => 'oid',
+  prosrc => 'pg_cmk_is_visible' },
 
 { oid => '2854', descr => 'get OID of current session\'s temp schema, if any',
   proname => 'pg_my_temp_schema', provolatile => 's', proparallel => 'r',
@@ -11918,4 +11926,37 @@
   prorettype => 'bytea', proargtypes => 'pg_brin_minmax_multi_summary',
   prosrc => 'brin_minmax_multi_summary_send' },
 
+{ oid => '8253', descr => 'I/O',
+  proname => 'pg_encrypted_det_in', prorettype => 'pg_encrypted_det', proargtypes => 'cstring',
+  prosrc => 'pg_encrypted_in' },
+{ oid => '8254', descr => 'I/O',
+  proname => 'pg_encrypted_det_out', prorettype => 'cstring', proargtypes => 'pg_encrypted_det',
+  prosrc => 'pg_encrypted_out' },
+{ oid => '8255', descr => 'I/O',
+  proname => 'pg_encrypted_det_recv', prorettype => 'pg_encrypted_det', proargtypes => 'internal',
+  prosrc => 'pg_encrypted_recv' },
+{ oid => '8256', descr => 'I/O',
+  proname => 'pg_encrypted_det_send', prorettype => 'bytea', proargtypes => 'pg_encrypted_det',
+  prosrc => 'pg_encrypted_send' },
+
+{ oid => '8257', descr => 'I/O',
+  proname => 'pg_encrypted_rnd_in', prorettype => 'pg_encrypted_rnd', proargtypes => 'cstring',
+  prosrc => 'pg_encrypted_in' },
+{ oid => '8258', descr => 'I/O',
+  proname => 'pg_encrypted_rnd_out', prorettype => 'cstring', proargtypes => 'pg_encrypted_rnd',
+  prosrc => 'pg_encrypted_out' },
+{ oid => '8259', descr => 'I/O',
+  proname => 'pg_encrypted_rnd_recv', prorettype => 'pg_encrypted_rnd', proargtypes => 'internal',
+  prosrc => 'pg_encrypted_recv' },
+{ oid => '8260', descr => 'I/O',
+  proname => 'pg_encrypted_rnd_send', prorettype => 'bytea', proargtypes => 'pg_encrypted_rnd',
+  prosrc => 'pg_encrypted_send' },
+
+{ oid => '8245',
+  proname => 'pg_encrypted_det_eq', proleakproof => 't', prorettype => 'bool',
+  proargtypes => 'pg_encrypted_det pg_encrypted_det', prosrc => 'byteaeq' },
+{ oid => '8246',
+  proname => 'pg_encrypted_det_ne', proleakproof => 't', prorettype => 'bool',
+  proargtypes => 'pg_encrypted_det pg_encrypted_det', prosrc => 'byteane' },
+
 ]
diff --git a/src/include/catalog/pg_type.dat b/src/include/catalog/pg_type.dat
index 92bcaf2c73..85e4b5554e 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 => 'pg_encrypted_det', typlen => '-1', typbyval => 'f', typtype => 'b',
+  typcategory => 'Y', typinput => 'pg_encrypted_det_in', typoutput => 'pg_encrypted_det_out',
+  typreceive => 'pg_encrypted_det_recv', typsend => 'pg_encrypted_det_send', typalign => 'i',
+  typstorage => 'e' },
+{ oid => '8244', descr => 'encrypted column (randomized)',
+  typname => 'pg_encrypted_rnd', typlen => '-1', typbyval => 'f', typtype => 'b',
+  typcategory => 'Y', typinput => 'pg_encrypted_rnd_in', typoutput => 'pg_encrypted_rnd_out',
+  typreceive => 'pg_encrypted_rnd_recv', typsend => 'pg_encrypted_rnd_send', typalign => 'i',
+  typstorage => 'e' },
+
 ]
diff --git a/src/include/catalog/pg_type.h b/src/include/catalog/pg_type.h
index 519e570c8c..3c7ab2a8fe 100644
--- a/src/include/catalog/pg_type.h
+++ b/src/include/catalog/pg_type.h
@@ -294,6 +294,7 @@ DECLARE_UNIQUE_INDEX(pg_type_typname_nsp_index, 2704, TypeNameNspIndexId, on pg_
 #define  TYPCATEGORY_USER		'U'
 #define  TYPCATEGORY_BITSTRING	'V' /* er ... "varbit"? */
 #define  TYPCATEGORY_UNKNOWN	'X'
+#define  TYPCATEGORY_ENCRYPTED	'Y'
 #define  TYPCATEGORY_INTERNAL	'Z'
 
 #define  TYPALIGN_CHAR			'c' /* char alignment (i.e. unaligned) */
diff --git a/src/include/commands/colenccmds.h b/src/include/commands/colenccmds.h
new file mode 100644
index 0000000000..7127e0ca5e
--- /dev/null
+++ b/src/include/commands/colenccmds.h
@@ -0,0 +1,26 @@
+/*-------------------------------------------------------------------------
+ *
+ * colenccmds.h
+ *	  prototypes for colenccmds.c.
+ *
+ *
+ * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/commands/colenccmds.h
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef COLENCCMDS_H
+#define COLENCCMDS_H
+
+#include "catalog/objectaddress.h"
+#include "parser/parse_node.h"
+
+extern ObjectAddress CreateCEK(ParseState *pstate, DefineStmt *stmt);
+extern ObjectAddress AlterColumnEncryptionKey(ParseState *pstate, AlterColumnEncryptionKeyStmt *stmt);
+extern ObjectAddress CreateCMK(ParseState *pstate, DefineStmt *stmt);
+extern ObjectAddress AlterColumnMasterKey(ParseState *pstate, AlterColumnMasterKeyStmt *stmt);
+
+#endif							/* COLENCCMDS_H */
diff --git a/src/include/commands/tablecmds.h b/src/include/commands/tablecmds.h
index e7c2b91a58..11f88c59ce 100644
--- a/src/include/commands/tablecmds.h
+++ b/src/include/commands/tablecmds.h
@@ -105,4 +105,6 @@ extern void RangeVarCallbackOwnsRelation(const RangeVar *relation,
 extern bool PartConstraintImpliedByRelConstraint(Relation scanrel,
 												 List *partConstraint);
 
+extern List *makeColumnEncryption(const FormData_pg_attribute *attr);
+
 #endif							/* TABLECMDS_H */
diff --git a/src/include/common/colenc.h b/src/include/common/colenc.h
new file mode 100644
index 0000000000..212587d222
--- /dev/null
+++ b/src/include/common/colenc.h
@@ -0,0 +1,51 @@
+/*-------------------------------------------------------------------------
+ *
+ * colenc.h
+ *
+ * Shared definitions for column encryption algorithms.
+ *
+ * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *		  src/include/common/colenc.h
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef COMMON_COLENC_H
+#define COMMON_COLENC_H
+
+/*
+ * Constants for CMK and CEK algorithms.  Note that these are part of the
+ * protocol.  In either case, don't assign zero, so that that can be used as
+ * an invalid value.
+ *
+ * Names should use IANA-style capitalization and punctuation ("LIKE_THIS").
+ *
+ * When making changes, also update protocol.sgml.
+ */
+
+#define PG_CMK_UNSPECIFIED				1
+#define PG_CMK_RSAES_OAEP_SHA_1			2
+#define PG_CMK_RSAES_OAEP_SHA_256		3
+
+/*
+ * These algorithms are part of the RFC 5116 realm of AEAD algorithms (even
+ * though they never became an official IETF standard).  So for propriety, we
+ * use "private use" numbers from
+ * <https://www.iana.org/assignments/aead-parameters/aead-parameters.xhtml;.
+ */
+#define PG_CEK_AEAD_AES_128_CBC_HMAC_SHA_256	32768
+#define PG_CEK_AEAD_AES_192_CBC_HMAC_SHA_384	32769
+#define PG_CEK_AEAD_AES_256_CBC_HMAC_SHA_384	32770
+#define PG_CEK_AEAD_AES_256_CBC_HMAC_SHA_512	32771
+
+/*
+ * Functions to convert between names and numbers
+ */
+extern int get_cmkalg_num(const char *name);
+extern const char *get_cmkalg_name(int num);
+extern const char *get_cmkalg_jwa_name(int num);
+extern int get_cekalg_num(const char *name);
+extern const char *get_cekalg_name(int num);
+
+#endif
diff --git a/src/include/libpq/libpq-be.h b/src/include/libpq/libpq-be.h
index 8c70b2fd5b..6cf4b65500 100644
--- a/src/include/libpq/libpq-be.h
+++ b/src/include/libpq/libpq-be.h
@@ -164,6 +164,7 @@ typedef struct Port
 	 */
 	char	   *database_name;
 	char	   *user_name;
+	bool		column_encryption_enabled;
 	char	   *cmdline_options;
 	List	   *guc_options;
 
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 89335d95e7..abf59d0187 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -715,6 +715,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? */
@@ -751,11 +752,12 @@ typedef enum TableLikeOption
 	CREATE_TABLE_LIKE_COMPRESSION = 1 << 1,
 	CREATE_TABLE_LIKE_CONSTRAINTS = 1 << 2,
 	CREATE_TABLE_LIKE_DEFAULTS = 1 << 3,
-	CREATE_TABLE_LIKE_GENERATED = 1 << 4,
-	CREATE_TABLE_LIKE_IDENTITY = 1 << 5,
-	CREATE_TABLE_LIKE_INDEXES = 1 << 6,
-	CREATE_TABLE_LIKE_STATISTICS = 1 << 7,
-	CREATE_TABLE_LIKE_STORAGE = 1 << 8,
+	CREATE_TABLE_LIKE_ENCRYPTED = 1 << 4,
+	CREATE_TABLE_LIKE_GENERATED = 1 << 5,
+	CREATE_TABLE_LIKE_IDENTITY = 1 << 6,
+	CREATE_TABLE_LIKE_INDEXES = 1 << 7,
+	CREATE_TABLE_LIKE_STATISTICS = 1 << 8,
+	CREATE_TABLE_LIKE_STORAGE = 1 << 9,
 	CREATE_TABLE_LIKE_ALL = PG_INT32_MAX
 } TableLikeOption;
 
@@ -1949,6 +1951,9 @@ typedef enum ObjectType
 	OBJECT_CAST,
 	OBJECT_COLUMN,
 	OBJECT_COLLATION,
+	OBJECT_CEK,
+	OBJECT_CEKDATA,
+	OBJECT_CMK,
 	OBJECT_CONVERSION,
 	OBJECT_DATABASE,
 	OBJECT_DEFAULT,
@@ -2136,6 +2141,31 @@ typedef struct AlterCollationStmt
 } AlterCollationStmt;
 
 
+/* ----------------------
+ * Alter Column Encryption Key
+ * ----------------------
+ */
+typedef struct AlterColumnEncryptionKeyStmt
+{
+	NodeTag		type;
+	List	   *cekname;
+	bool		isDrop;			/* ADD or DROP the items? */
+	List	   *definition;
+} AlterColumnEncryptionKeyStmt;
+
+
+/* ----------------------
+ * Alter Column Master Key
+ * ----------------------
+ */
+typedef struct AlterColumnMasterKeyStmt
+{
+	NodeTag		type;
+	List	   *cmkname;
+	List	   *definition;
+} AlterColumnMasterKeyStmt;
+
+
 /* ----------------------
  *	Alter Domain
  *
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index bb36213e6f..c8cf9c7776 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -149,6 +149,7 @@ PG_KEYWORD("else", ELSE, RESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("enable", ENABLE_P, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("encoding", ENCODING, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("encrypted", ENCRYPTED, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("encryption", ENCRYPTION, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("end", END_P, RESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("enum", ENUM_P, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("escape", ESCAPE, UNRESERVED_KEYWORD, BARE_LABEL)
@@ -250,6 +251,7 @@ PG_KEYWORD("lock", LOCK_P, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("locked", LOCKED, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("logged", LOGGED, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("mapping", MAPPING, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("master", MASTER, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("match", MATCH, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("matched", MATCHED, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("materialized", MATERIALIZED, UNRESERVED_KEYWORD, BARE_LABEL)
diff --git a/src/include/parser/parse_param.h b/src/include/parser/parse_param.h
index d4865e50f6..26e1cd617a 100644
--- a/src/include/parser/parse_param.h
+++ b/src/include/parser/parse_param.h
@@ -21,5 +21,6 @@ extern void setup_parse_variable_parameters(ParseState *pstate,
 											Oid **paramTypes, int *numParams);
 extern void check_variable_parameters(ParseState *pstate, Query *query);
 extern bool query_contains_extern_params(Query *query);
+extern void find_param_origs(List *query_list, Oid **param_orig_tbls, AttrNumber **param_orig_cols);
 
 #endif							/* PARSE_PARAM_H */
diff --git a/src/include/tcop/cmdtaglist.h b/src/include/tcop/cmdtaglist.h
index e738ac1c09..c1f6fe1410 100644
--- a/src/include/tcop/cmdtaglist.h
+++ b/src/include/tcop/cmdtaglist.h
@@ -29,6 +29,8 @@ PG_CMDTAG(CMDTAG_ALTER_ACCESS_METHOD, "ALTER ACCESS METHOD", true, false, false)
 PG_CMDTAG(CMDTAG_ALTER_AGGREGATE, "ALTER AGGREGATE", true, false, false)
 PG_CMDTAG(CMDTAG_ALTER_CAST, "ALTER CAST", true, false, false)
 PG_CMDTAG(CMDTAG_ALTER_COLLATION, "ALTER COLLATION", true, false, false)
+PG_CMDTAG(CMDTAG_ALTER_COLUMN_ENCRYPTION_KEY, "ALTER COLUMN ENCRYPTION KEY", true, false, false)
+PG_CMDTAG(CMDTAG_ALTER_COLUMN_MASTER_KEY, "ALTER COLUMN MASTER KEY", true, false, false)
 PG_CMDTAG(CMDTAG_ALTER_CONSTRAINT, "ALTER CONSTRAINT", true, false, false)
 PG_CMDTAG(CMDTAG_ALTER_CONVERSION, "ALTER CONVERSION", true, false, false)
 PG_CMDTAG(CMDTAG_ALTER_DATABASE, "ALTER DATABASE", false, false, false)
@@ -86,6 +88,8 @@ PG_CMDTAG(CMDTAG_CREATE_ACCESS_METHOD, "CREATE ACCESS METHOD", true, false, fals
 PG_CMDTAG(CMDTAG_CREATE_AGGREGATE, "CREATE AGGREGATE", true, false, false)
 PG_CMDTAG(CMDTAG_CREATE_CAST, "CREATE CAST", true, false, false)
 PG_CMDTAG(CMDTAG_CREATE_COLLATION, "CREATE COLLATION", true, false, false)
+PG_CMDTAG(CMDTAG_CREATE_COLUMN_ENCRYPTION_KEY, "CREATE COLUMN ENCRYPTION KEY", true, false, false)
+PG_CMDTAG(CMDTAG_CREATE_COLUMN_MASTER_KEY, "CREATE COLUMN MASTER KEY", true, false, false)
 PG_CMDTAG(CMDTAG_CREATE_CONSTRAINT, "CREATE CONSTRAINT", true, false, false)
 PG_CMDTAG(CMDTAG_CREATE_CONVERSION, "CREATE CONVERSION", true, false, false)
 PG_CMDTAG(CMDTAG_CREATE_DATABASE, "CREATE DATABASE", false, false, false)
@@ -138,6 +142,8 @@ PG_CMDTAG(CMDTAG_DROP_ACCESS_METHOD, "DROP ACCESS METHOD", true, false, false)
 PG_CMDTAG(CMDTAG_DROP_AGGREGATE, "DROP AGGREGATE", true, false, false)
 PG_CMDTAG(CMDTAG_DROP_CAST, "DROP CAST", true, false, false)
 PG_CMDTAG(CMDTAG_DROP_COLLATION, "DROP COLLATION", true, false, false)
+PG_CMDTAG(CMDTAG_DROP_COLUMN_ENCRYPTION_KEY, "DROP COLUMN ENCRYPTION KEY", true, false, false)
+PG_CMDTAG(CMDTAG_DROP_COLUMN_MASTER_KEY, "DROP COLUMN MASTER KEY", true, false, false)
 PG_CMDTAG(CMDTAG_DROP_CONSTRAINT, "DROP CONSTRAINT", true, false, false)
 PG_CMDTAG(CMDTAG_DROP_CONVERSION, "DROP CONVERSION", true, false, false)
 PG_CMDTAG(CMDTAG_DROP_DATABASE, "DROP DATABASE", false, false, false)
diff --git a/src/include/utils/lsyscache.h b/src/include/utils/lsyscache.h
index 4f5418b972..1dac24e4b4 100644
--- a/src/include/utils/lsyscache.h
+++ b/src/include/utils/lsyscache.h
@@ -163,6 +163,7 @@ extern bool type_is_rowtype(Oid typid);
 extern bool type_is_enum(Oid typid);
 extern bool type_is_range(Oid typid);
 extern bool type_is_multirange(Oid typid);
+extern bool type_is_encrypted(Oid typid);
 extern void get_type_category_preferred(Oid typid,
 										char *typcategory,
 										bool *typispreferred);
@@ -202,6 +203,9 @@ extern Oid	get_publication_oid(const char *pubname, bool missing_ok);
 extern char *get_publication_name(Oid pubid, bool missing_ok);
 extern Oid	get_subscription_oid(const char *subname, bool missing_ok);
 extern char *get_subscription_name(Oid subid, bool missing_ok);
+extern char *get_cek_name(Oid cekid, bool missing_ok);
+extern Oid	get_cekdata_oid(Oid cekid, Oid cmkid, bool missing_ok);
+extern char *get_cmk_name(Oid cmkid, bool missing_ok);
 
 #define type_is_array(typid)  (get_element_type(typid) != InvalidOid)
 /* type_is_array_domain accepts both plain arrays and domains over arrays */
diff --git a/src/include/utils/plancache.h b/src/include/utils/plancache.h
index a443181d41..8ecacb29df 100644
--- a/src/include/utils/plancache.h
+++ b/src/include/utils/plancache.h
@@ -207,6 +207,9 @@ extern void CompleteCachedPlan(CachedPlanSource *plansource,
 extern void SaveCachedPlan(CachedPlanSource *plansource);
 extern void DropCachedPlan(CachedPlanSource *plansource);
 
+extern List *RevalidateCachedQuery(CachedPlanSource *plansource,
+								   QueryEnvironment *queryEnv);
+
 extern void CachedPlanSetParentContext(CachedPlanSource *plansource,
 									   MemoryContext newcontext);
 
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index d5d50ceab4..bf1d527c1a 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -44,8 +44,14 @@ enum SysCacheIdentifier
 	AUTHNAME,
 	AUTHOID,
 	CASTSOURCETARGET,
+	CEKDATACEKCMK,
+	CEKDATAOID,
+	CEKNAMENSP,
+	CEKOID,
 	CLAAMNAMENSP,
 	CLAOID,
+	CMKNAMENSP,
+	CMKOID,
 	COLLNAMEENCNSP,
 	COLLOID,
 	CONDEFAULT,
diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile
index c18e914228..10dfda8016 100644
--- a/src/interfaces/libpq/Makefile
+++ b/src/interfaces/libpq/Makefile
@@ -54,6 +54,7 @@ endif
 
 ifeq ($(with_ssl),openssl)
 OBJS += \
+	fe-encrypt-openssl.o \
 	fe-secure-openssl.o
 endif
 
diff --git a/src/interfaces/libpq/exports.txt b/src/interfaces/libpq/exports.txt
index e8bcc88370..8897aa243c 100644
--- a/src/interfaces/libpq/exports.txt
+++ b/src/interfaces/libpq/exports.txt
@@ -186,3 +186,7 @@ PQpipelineStatus          183
 PQsetTraceFlags           184
 PQmblenBounded            185
 PQsendFlushRequest        186
+PQexecPreparedDescribed   187
+PQsendQueryPreparedDescribed 188
+PQfisencrypted            189
+PQparamisencrypted        190
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index 50b5df3490..bf6149e859 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -341,6 +341,14 @@ static const internalPQconninfoOption PQconninfoOptions[] = {
 		"Target-Session-Attrs", "", 15, /* sizeof("prefer-standby") = 15 */
 	offsetof(struct pg_conn, target_session_attrs)},
 
+	{"cmklookup", "PGCMKLOOKUP", "", NULL,
+		"CMK-Lookup", "", 64,
+	offsetof(struct pg_conn, cmklookup)},
+
+	{"column_encryption", "PGCOLUMNENCRYPTION", "0", NULL,
+		"Column-Encryption", "", 1,
+	offsetof(struct pg_conn, column_encryption_setting)},
+
 	/* Terminating entry --- MUST BE LAST */
 	{NULL, NULL, NULL, NULL,
 	NULL, NULL, 0}
@@ -1412,6 +1420,28 @@ connectOptions2(PGconn *conn)
 			goto oom_error;
 	}
 
+	/*
+	 * validate column_encryption option
+	 */
+	if (conn->column_encryption_setting)
+	{
+		if (strcmp(conn->column_encryption_setting, "on") == 0 ||
+			strcmp(conn->column_encryption_setting, "true") == 0 ||
+			strcmp(conn->column_encryption_setting, "1") == 0)
+			conn->column_encryption_enabled = true;
+		else if (strcmp(conn->column_encryption_setting, "off") == 0 ||
+				 strcmp(conn->column_encryption_setting, "false") == 0 ||
+				 strcmp(conn->column_encryption_setting, "0") == 0)
+			conn->column_encryption_enabled = false;
+		else
+		{
+			conn->status = CONNECTION_BAD;
+			libpq_append_conn_error(conn, "invalid %s value: \"%s\"",
+									"column_encryption", conn->column_encryption_setting);
+			return false;
+		}
+	}
+
 	/*
 	 * Only if we get this far is it appropriate to try to connect. (We need a
 	 * state flag, rather than just the boolean result of this function, in
@@ -4029,6 +4059,22 @@ freePGconn(PGconn *conn)
 	free(conn->krbsrvname);
 	free(conn->gsslib);
 	free(conn->connip);
+	free(conn->cmklookup);
+	for (int i = 0; i < conn->ncmks; i++)
+	{
+		free(conn->cmks[i].cmkname);
+		free(conn->cmks[i].cmkrealm);
+	}
+	free(conn->cmks);
+	for (int i = 0; i < conn->nceks; i++)
+	{
+		if (conn->ceks[i].cekdata)
+		{
+			explicit_bzero(conn->ceks[i].cekdata, conn->ceks[i].cekdatalen);
+			free(conn->ceks[i].cekdata);
+		}
+	}
+	free(conn->ceks);
 	/* Note that conn->Pfdebug is not ours to close or free */
 	free(conn->write_err_msg);
 	free(conn->inBuffer);
diff --git a/src/interfaces/libpq/fe-encrypt-openssl.c b/src/interfaces/libpq/fe-encrypt-openssl.c
new file mode 100644
index 0000000000..ab6c70967f
--- /dev/null
+++ b/src/interfaces/libpq/fe-encrypt-openssl.c
@@ -0,0 +1,842 @@
+/*-------------------------------------------------------------------------
+ *
+ * fe-encrypt-openssl.c
+ *
+ * client-side column encryption support using OpenSSL
+ *
+ * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *	  src/interfaces/libpq/fe-encrypt-openssl.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres_fe.h"
+
+#include "fe-encrypt.h"
+#include "libpq-int.h"
+
+#include "common/colenc.h"
+#include "port/pg_bswap.h"
+
+#include <openssl/evp.h>
+
+
+/*
+ * When TEST_ENCRYPT is defined, this file builds a standalone program that
+ * checks encryption test cases against the specification document.
+ *
+ * We have to replace some functions that are not available in that
+ * environment.
+ */
+#ifdef TEST_ENCRYPT
+
+#define libpq_gettext(x) (x)
+#define libpq_append_conn_error(conn, ...) do { fprintf(stderr, __VA_ARGS__); fprintf(stderr, "\n"); exit(1); } while(0)
+
+#endif							/* TEST_ENCRYPT */
+
+
+/*
+ * Decrypt the CEK given by "from" and "fromlen" (data typically sent from the
+ * server) using the CMK in cmkfilename.
+ */
+unsigned char *
+decrypt_cek_from_file(PGconn *conn, const char *cmkfilename, int cmkalg,
+					  int fromlen, const unsigned char *from,
+					  int *tolen)
+{
+	const EVP_MD *md = NULL;
+	EVP_PKEY   *key = NULL;
+	RSA		   *rsa = NULL;
+	BIO		   *bio = NULL;
+	EVP_PKEY_CTX *ctx = NULL;
+	unsigned char *out = NULL;
+	size_t		outlen;
+
+	switch (cmkalg)
+	{
+		case PG_CMK_RSAES_OAEP_SHA_1:
+			md = EVP_sha1();
+			break;
+		case PG_CMK_RSAES_OAEP_SHA_256:
+			md = EVP_sha256();
+			break;
+		case PG_CMK_UNSPECIFIED:
+			libpq_append_conn_error(conn, "unspecified CMK algorithm not supported with file lookup scheme");
+			goto fail;
+		default:
+			libpq_append_conn_error(conn, "unsupported CMK algorithm ID: %d", cmkalg);
+			goto fail;
+	}
+
+	bio = BIO_new_file(cmkfilename, "r");
+	if (!bio)
+	{
+		libpq_append_conn_error(conn, "could not open file \"%s\": %m", cmkfilename);
+		goto fail;
+	}
+
+	rsa = RSA_new();
+	if (!rsa)
+	{
+		libpq_append_conn_error(conn, "could not allocate RSA structure: %s",
+								ERR_reason_error_string(ERR_get_error()));
+		goto fail;
+	}
+	/*
+	 * Note: We must go through BIO and not say use PEM_read_RSAPrivateKey()
+	 * directly on a FILE.  Otherwise, we get into "no OPENSSL_Applink" hell
+	 * on Windows (which happens whenever you pass a stdio handle from the
+	 * application into OpenSSL).
+	 */
+	rsa = PEM_read_bio_RSAPrivateKey(bio, &rsa, NULL, NULL);
+	if (!rsa)
+	{
+		libpq_append_conn_error(conn, "could not read RSA private key: %s",
+								ERR_reason_error_string(ERR_get_error()));
+		goto fail;
+	}
+
+	key = EVP_PKEY_new();
+	if (!key)
+	{
+		libpq_append_conn_error(conn, "could not allocate private key structure: %s",
+								ERR_reason_error_string(ERR_get_error()));
+		goto fail;
+	}
+
+	if (!EVP_PKEY_assign_RSA(key, rsa))
+	{
+		libpq_append_conn_error(conn, "could not assign private key: %s",
+								ERR_reason_error_string(ERR_get_error()));
+		goto fail;
+	}
+
+	ctx = EVP_PKEY_CTX_new(key, NULL);
+	if (!ctx)
+	{
+		libpq_append_conn_error(conn, "could not allocate public key algorithm context: %s",
+								ERR_reason_error_string(ERR_get_error()));
+		goto fail;
+	}
+
+	if (EVP_PKEY_decrypt_init(ctx) <= 0)
+	{
+		libpq_append_conn_error(conn, "decryption initialization failed: %s",
+								ERR_reason_error_string(ERR_get_error()));
+		goto fail;
+	}
+
+	if (EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_OAEP_PADDING) <= 0 ||
+		EVP_PKEY_CTX_set_rsa_mgf1_md(ctx, md) <= 0 ||
+		EVP_PKEY_CTX_set_rsa_oaep_md(ctx, md) <= 0)
+	{
+		libpq_append_conn_error(conn, "could not set RSA parameter: %s",
+								ERR_reason_error_string(ERR_get_error()));
+		goto fail;
+	}
+
+	/* get output length */
+	if (EVP_PKEY_decrypt(ctx, NULL, &outlen, from, fromlen) <= 0)
+	{
+		libpq_append_conn_error(conn, "RSA decryption setup failed: %s",
+								ERR_reason_error_string(ERR_get_error()));
+		goto fail;
+	}
+
+	out = malloc(outlen);
+	if (!out)
+	{
+		libpq_append_conn_error(conn, "out of memory");
+		goto fail;
+	}
+
+	if (EVP_PKEY_decrypt(ctx, out, &outlen, from, fromlen) <= 0)
+	{
+		libpq_append_conn_error(conn, "RSA decryption failed: %s",
+								ERR_reason_error_string(ERR_get_error()));
+		free(out);
+		out = NULL;
+		goto fail;
+	}
+
+	*tolen = outlen;
+
+fail:
+	EVP_PKEY_CTX_free(ctx);
+	EVP_PKEY_free(key);
+	BIO_free(bio);
+
+	return out;
+}
+
+
+/*
+ * The routines below implement the AEAD algorithms specified in
+ * <https://datatracker.ietf.org/doc/html/draft-mcgrew-aead-aes-cbc-hmac-sha2-05;
+ * for encrypting and decrypting column values.
+ */
+
+#ifdef TEST_ENCRYPT
+
+/*
+ * Test data from
+ * <https://datatracker.ietf.org/doc/html/draft-mcgrew-aead-aes-cbc-hmac-sha2-05#section-5;
+ */
+
+/*
+ * The different test cases just use different prefixes of K, so one constant
+ * is enough here.
+ */
+static const unsigned char K[] = {
+	0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+	0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
+	0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f,
+	0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f,
+};
+
+static const unsigned char P[] = {
+	0x41, 0x20, 0x63, 0x69, 0x70, 0x68, 0x65, 0x72, 0x20, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x20,
+	0x6d, 0x75, 0x73, 0x74, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x62, 0x65, 0x20, 0x72, 0x65, 0x71, 0x75,
+	0x69, 0x72, 0x65, 0x64, 0x20, 0x74, 0x6f, 0x20, 0x62, 0x65, 0x20, 0x73, 0x65, 0x63, 0x72, 0x65,
+	0x74, 0x2c, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x69, 0x74, 0x20, 0x6d, 0x75, 0x73, 0x74, 0x20, 0x62,
+	0x65, 0x20, 0x61, 0x62, 0x6c, 0x65, 0x20, 0x74, 0x6f, 0x20, 0x66, 0x61, 0x6c, 0x6c, 0x20, 0x69,
+	0x6e, 0x74, 0x6f, 0x20, 0x74, 0x68, 0x65, 0x20, 0x68, 0x61, 0x6e, 0x64, 0x73, 0x20, 0x6f, 0x66,
+	0x20, 0x74, 0x68, 0x65, 0x20, 0x65, 0x6e, 0x65, 0x6d, 0x79, 0x20, 0x77, 0x69, 0x74, 0x68, 0x6f,
+	0x75, 0x74, 0x20, 0x69, 0x6e, 0x63, 0x6f, 0x6e, 0x76, 0x65, 0x6e, 0x69, 0x65, 0x6e, 0x63, 0x65,
+};
+
+static const unsigned char test_IV[] = {
+	0x1a, 0xf3, 0x8c, 0x2d, 0xc2, 0xb9, 0x6f, 0xfd, 0xd8, 0x66, 0x94, 0x09, 0x23, 0x41, 0xbc, 0x04,
+};
+
+static const unsigned char test_A[] = {
+	0x54, 0x68, 0x65, 0x20, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x20, 0x70, 0x72, 0x69, 0x6e, 0x63,
+	0x69, 0x70, 0x6c, 0x65, 0x20, 0x6f, 0x66, 0x20, 0x41, 0x75, 0x67, 0x75, 0x73, 0x74, 0x65, 0x20,
+	0x4b, 0x65, 0x72, 0x63, 0x6b, 0x68, 0x6f, 0x66, 0x66, 0x73,
+};
+
+#endif							/* TEST_ENCRYPT */
+
+
+/*
+ * Get OpenSSL cipher that corresponds to the CEK algorithm number.
+ */
+static const EVP_CIPHER *
+pg_cekalg_to_openssl_cipher(int cekalg)
+{
+	const EVP_CIPHER *cipher;
+
+	switch (cekalg)
+	{
+		case PG_CEK_AEAD_AES_128_CBC_HMAC_SHA_256:
+			cipher = EVP_aes_128_cbc();
+			break;
+		case PG_CEK_AEAD_AES_192_CBC_HMAC_SHA_384:
+			cipher = EVP_aes_192_cbc();
+			break;
+		case PG_CEK_AEAD_AES_256_CBC_HMAC_SHA_384:
+		case PG_CEK_AEAD_AES_256_CBC_HMAC_SHA_512:
+			cipher = EVP_aes_256_cbc();
+			break;
+		default:
+			cipher = NULL;
+	}
+
+	return cipher;
+}
+
+/*
+ * Get OpenSSL digest (for MAC) that corresponds to the CEK algorithm number.
+ */
+static const EVP_MD *
+pg_cekalg_to_openssl_md(int cekalg)
+{
+	const EVP_MD *md;
+
+	switch (cekalg)
+	{
+		case PG_CEK_AEAD_AES_128_CBC_HMAC_SHA_256:
+			md = EVP_sha256();
+			break;
+		case PG_CEK_AEAD_AES_192_CBC_HMAC_SHA_384:
+		case PG_CEK_AEAD_AES_256_CBC_HMAC_SHA_384:
+			md = EVP_sha384();
+			break;
+		case PG_CEK_AEAD_AES_256_CBC_HMAC_SHA_512:
+			md = EVP_sha512();
+			break;
+		default:
+			md = NULL;
+	}
+
+	return md;
+}
+
+/*
+ * Get the MAC key length (in octets) that corresponds to the CEK algorithm number.
+ *
+ * This is MAC_KEY_LEN in the mcgrew paper.
+ */
+static int
+md_key_length(const EVP_MD *md)
+{
+	if (md == EVP_sha256())
+		return 16;
+	else if (md == EVP_sha384())
+		return 24;
+	else if (md == EVP_sha512())
+		return 32;
+	else
+		return -1;
+}
+
+/*
+ * Get the HMAC output length (in octets) that corresponds to the CEK
+ * algorithm number.
+ */
+static int
+md_hash_length(const EVP_MD *md)
+{
+	if (md == EVP_sha256())
+		return 32;
+	else if (md == EVP_sha384())
+		return 48;
+	else if (md == EVP_sha512())
+		return 64;
+	else
+		return -1;
+}
+
+/*
+ * Length of associated data (A in mcgrew paper)
+ */
+#ifndef TEST_ENCRYPT
+#define PG_AD_LEN 4
+#else
+#define PG_AD_LEN sizeof(test_A)
+#endif
+
+/*
+ * Compute message authentication tag (T in the mcgrew paper), from MAC key
+ * and ciphertext.
+ *
+ * Returns false on error, with error message in errmsgp.
+ */
+static bool
+get_message_auth_tag(const EVP_MD *md,
+					 const unsigned char *mac_key, int mac_key_len,
+					 const unsigned char *encr, int encrlen,
+					 unsigned char *md_value, size_t *md_len_p,
+					 const char **errmsgp)
+{
+	static char msgbuf[1024];
+	EVP_MD_CTX *evp_md_ctx = NULL;
+	EVP_PKEY   *pkey = NULL;
+	size_t		bufsize;
+	unsigned char *buf = NULL;
+	int64		al;
+	bool		result = false;
+
+	if (encrlen < 0)
+	{
+		snprintf(msgbuf, sizeof(msgbuf),
+				 libpq_gettext("encrypted value has invalid length"));
+		*errmsgp = msgbuf;
+		goto fail;
+	}
+
+	evp_md_ctx = EVP_MD_CTX_new();
+
+	pkey = EVP_PKEY_new_raw_private_key(EVP_PKEY_HMAC, NULL, mac_key, mac_key_len);
+	if (!pkey)
+	{
+		snprintf(msgbuf, sizeof(msgbuf),
+				 libpq_gettext("could not allocate key for HMAC: %s"),
+				 ERR_reason_error_string(ERR_get_error()));
+		*errmsgp = msgbuf;
+		goto fail;
+	}
+
+	/*
+	 * Build input to MAC call (A || S || AL in mcgrew paper)
+	 */
+	bufsize = PG_AD_LEN + encrlen + sizeof(int64);
+	buf = malloc(bufsize);
+	if (!buf)
+	{
+		*errmsgp = libpq_gettext("out of memory");
+		goto fail;
+	}
+	/* A (associated data) */
+#ifndef TEST_ENCRYPT
+	buf[0] = 'P';
+	buf[1] = 'G';
+	*(int16 *) (buf + 2) = pg_hton16(1);
+#else
+	memcpy(buf, test_A, sizeof(test_A));
+#endif
+	/* S (ciphertext) */
+	memcpy(buf + PG_AD_LEN, encr, encrlen);
+	/* AL (number of *bits* in A) */
+	al = pg_hton64(PG_AD_LEN * 8);
+	memcpy(buf + PG_AD_LEN + encrlen, &al, sizeof(al));
+
+	/*
+	 * Call MAC
+	 */
+	if (!EVP_DigestSignInit(evp_md_ctx, NULL, md, NULL, pkey))
+	{
+		snprintf(msgbuf, sizeof(msgbuf),
+				 libpq_gettext("digest initialization failed: %s"),
+				 ERR_reason_error_string(ERR_get_error()));
+		*errmsgp = msgbuf;
+		goto fail;
+	}
+
+	if (!EVP_DigestSignUpdate(evp_md_ctx, buf, bufsize))
+	{
+		snprintf(msgbuf, sizeof(msgbuf),
+				 libpq_gettext("digest signing failed: %s"),
+				 ERR_reason_error_string(ERR_get_error()));
+		*errmsgp = msgbuf;
+		goto fail;
+	}
+
+	if (!EVP_DigestSignFinal(evp_md_ctx, md_value, md_len_p))
+	{
+		snprintf(msgbuf, sizeof(msgbuf),
+				 libpq_gettext("digest signing failed: %s"),
+				 ERR_reason_error_string(ERR_get_error()));
+		*errmsgp = msgbuf;
+		goto fail;
+	}
+	Assert(*md_len_p == md_hash_length(md));
+
+	/* truncate output to half the length, per spec */
+	*md_len_p /= 2;
+
+	result = true;
+fail:
+	free(buf);
+	EVP_PKEY_free(pkey);
+	EVP_MD_CTX_free(evp_md_ctx);
+	return result;
+}
+
+/*
+ * Decrypt a column value
+ */
+unsigned char *
+decrypt_value(PGresult *res, const PGCEK *cek, int cekalg, const unsigned char *input, int inputlen, const char **errmsgp)
+{
+	static char msgbuf[1024];
+
+	const unsigned char *iv = NULL;
+	size_t		ivlen;
+
+	const EVP_CIPHER *cipher;
+	const EVP_MD *md;
+	EVP_CIPHER_CTX *evp_cipher_ctx = NULL;
+	int			enc_key_len;
+	int			mac_key_len;
+	int			iv_key_len;
+	int			key_len;
+	const unsigned char *enc_key;
+	const unsigned char *mac_key;
+	unsigned char md_value[EVP_MAX_MD_SIZE];
+	size_t		md_len = sizeof(md_value);
+	size_t		bufsize;
+	unsigned char *buf = NULL;
+	unsigned char *decr;
+	int			decrlen,
+				decrlen2;
+
+	unsigned char *result = NULL;
+
+	cipher = pg_cekalg_to_openssl_cipher(cekalg);
+	if (!cipher)
+	{
+		snprintf(msgbuf, sizeof(msgbuf),
+				 libpq_gettext("unrecognized encryption algorithm identifier: %d"), cekalg);
+		*errmsgp = msgbuf;
+		goto fail;
+	}
+
+	md = pg_cekalg_to_openssl_md(cekalg);
+	if (!md)
+	{
+		snprintf(msgbuf, sizeof(msgbuf),
+				 libpq_gettext("unrecognized digest algorithm identifier: %d"), cekalg);
+		*errmsgp = msgbuf;
+		goto fail;
+	}
+
+	evp_cipher_ctx = EVP_CIPHER_CTX_new();
+
+	if (!EVP_DecryptInit_ex(evp_cipher_ctx, cipher, NULL, NULL, NULL))
+	{
+		snprintf(msgbuf, sizeof(msgbuf),
+				 libpq_gettext("decryption initialization failed: %s"),
+				 ERR_reason_error_string(ERR_get_error()));
+		*errmsgp = msgbuf;
+		goto fail;
+	}
+
+	enc_key_len = EVP_CIPHER_CTX_key_length(evp_cipher_ctx);
+	mac_key_len = iv_key_len = md_key_length(md);
+	key_len = mac_key_len + enc_key_len + iv_key_len;
+
+	if (cek->cekdatalen != key_len)
+	{
+		snprintf(msgbuf, sizeof(msgbuf),
+				 libpq_gettext("column encryption key has wrong length for algorithm (has: %zu, required: %d)"),
+				 cek->cekdatalen, key_len);
+		*errmsgp = msgbuf;
+		goto fail;
+	}
+
+	mac_key = cek->cekdata;
+	enc_key = cek->cekdata + mac_key_len;
+
+	if (!get_message_auth_tag(md, mac_key, mac_key_len,
+							  input, inputlen - (md_hash_length(md) / 2),
+							  md_value, &md_len,
+							  errmsgp))
+	{
+		goto fail;
+	}
+
+	/* use constant-time comparison, per mcgrew paper */
+	if (CRYPTO_memcmp(input + (inputlen - md_len), md_value, md_len) != 0)
+	{
+		*errmsgp = libpq_gettext("MAC mismatch");
+		goto fail;
+	}
+
+	ivlen = EVP_CIPHER_CTX_iv_length(evp_cipher_ctx);
+	iv = input;
+	input += ivlen;
+	inputlen -= ivlen;
+	if (!EVP_DecryptInit_ex(evp_cipher_ctx, NULL, NULL, enc_key, iv))
+	{
+		snprintf(msgbuf, sizeof(msgbuf),
+				 libpq_gettext("decryption initialization failed: %s"),
+				 ERR_reason_error_string(ERR_get_error()));
+		*errmsgp = msgbuf;
+		goto fail;
+	}
+
+	bufsize = inputlen + EVP_CIPHER_CTX_block_size(evp_cipher_ctx) + 1;
+#ifndef TEST_ENCRYPT
+	buf = pqResultAlloc(res, bufsize, false);
+#else
+	buf = malloc(bufsize);
+#endif
+	if (!buf)
+	{
+		*errmsgp = libpq_gettext("out of memory");
+		goto fail;
+	}
+	decr = buf;
+	if (!EVP_DecryptUpdate(evp_cipher_ctx, decr, &decrlen, input, inputlen - md_len))
+	{
+		snprintf(msgbuf, sizeof(msgbuf),
+				 libpq_gettext("decryption failed: %s"),
+				 ERR_reason_error_string(ERR_get_error()));
+		*errmsgp = msgbuf;
+		goto fail;
+	}
+	if (!EVP_DecryptFinal_ex(evp_cipher_ctx, decr + decrlen, &decrlen2))
+	{
+		snprintf(msgbuf, sizeof(msgbuf),
+				 libpq_gettext("decryption failed: %s"),
+				 ERR_reason_error_string(ERR_get_error()));
+		*errmsgp = msgbuf;
+		goto fail;
+	}
+	decrlen += decrlen2;
+	Assert(decrlen < bufsize);
+	decr[decrlen] = '\0';
+	result = decr;
+
+fail:
+	EVP_CIPHER_CTX_free(evp_cipher_ctx);
+
+	return result;
+}
+
+/*
+ * Compute a synthetic initialization vector (SIV), for deterministic
+ * encryption.
+ *
+ * Per protocol specification, the SIV is computed as:
+ *
+ * SUBSTRING(HMAC(K, P) FOR IVLEN)
+ */
+#ifndef TEST_ENCRYPT
+static bool
+make_siv(PGconn *conn,
+		 unsigned char *iv, size_t ivlen,
+		 const EVP_MD *md,
+		 const unsigned char *iv_key, int iv_key_len,
+		 const unsigned char *plaintext, int plaintext_len)
+{
+	EVP_MD_CTX *evp_md_ctx = NULL;
+	EVP_PKEY   *pkey = NULL;
+	unsigned char md_value[EVP_MAX_MD_SIZE];
+	size_t		md_len = sizeof(md_value);
+	bool		result = false;
+
+	evp_md_ctx = EVP_MD_CTX_new();
+
+	pkey = EVP_PKEY_new_raw_private_key(EVP_PKEY_HMAC, NULL, iv_key, iv_key_len);
+	if (!pkey)
+	{
+		libpq_append_conn_error(conn, "could not allocate key for HMAC: %s",
+								ERR_reason_error_string(ERR_get_error()));
+		goto fail;
+	}
+
+	if (!EVP_DigestSignInit(evp_md_ctx, NULL, md, NULL, pkey))
+	{
+		libpq_append_conn_error(conn, "digest initialization failed: %s",
+								ERR_reason_error_string(ERR_get_error()));
+		goto fail;
+	}
+
+	if (!EVP_DigestSignUpdate(evp_md_ctx, plaintext, plaintext_len))
+	{
+		libpq_append_conn_error(conn, "digest signing failed: %s",
+								ERR_reason_error_string(ERR_get_error()));
+		goto fail;
+	}
+
+	if (!EVP_DigestSignFinal(evp_md_ctx, md_value, &md_len))
+	{
+		libpq_append_conn_error(conn, "digest signing failed: %s",
+								ERR_reason_error_string(ERR_get_error()));
+		goto fail;
+	}
+	Assert(md_len == md_hash_length(md));
+	memcpy(iv, md_value, ivlen);
+
+	result = true;
+fail:
+	EVP_PKEY_free(pkey);
+	EVP_MD_CTX_free(evp_md_ctx);
+	return result;
+}
+#endif							/* TEST_ENCRYPT */
+
+/*
+ * Encrypt a column value
+ */
+unsigned char *
+encrypt_value(PGconn *conn, const PGCEK *cek, int cekalg, const unsigned char *value, int *nbytesp, bool enc_det)
+{
+	int			nbytes = *nbytesp;
+	unsigned char iv[EVP_MAX_IV_LENGTH];
+	size_t		ivlen;
+	const EVP_CIPHER *cipher;
+	const EVP_MD *md;
+	EVP_CIPHER_CTX *evp_cipher_ctx = NULL;
+	int			enc_key_len;
+	int			mac_key_len;
+	int			iv_key_len;
+	int			key_len;
+	const unsigned char *enc_key;
+	const unsigned char *mac_key;
+	const unsigned char *iv_key;
+	size_t		bufsize;
+	unsigned char *buf = NULL;
+	unsigned char *encr;
+	int			encrlen,
+				encrlen2;
+
+	const char *errmsg;
+	unsigned char md_value[EVP_MAX_MD_SIZE];
+	size_t		md_len = sizeof(md_value);
+	size_t		buf2size;
+	unsigned char *buf2 = NULL;
+
+	unsigned char *result = NULL;
+
+	cipher = pg_cekalg_to_openssl_cipher(cekalg);
+	if (!cipher)
+	{
+		libpq_append_conn_error(conn, "unrecognized encryption algorithm identifier: %d", cekalg);
+		goto fail;
+	}
+
+	md = pg_cekalg_to_openssl_md(cekalg);
+	if (!md)
+	{
+		libpq_append_conn_error(conn, "unrecognized digest algorithm identifier: %d", cekalg);
+		goto fail;
+	}
+
+	evp_cipher_ctx = EVP_CIPHER_CTX_new();
+
+	if (!EVP_EncryptInit_ex(evp_cipher_ctx, cipher, NULL, NULL, NULL))
+	{
+		libpq_append_conn_error(conn, "encryption initialization failed: %s",
+								ERR_reason_error_string(ERR_get_error()));
+		goto fail;
+	}
+
+	enc_key_len = EVP_CIPHER_CTX_key_length(evp_cipher_ctx);
+	mac_key_len = iv_key_len = md_key_length(md);
+	key_len = mac_key_len + enc_key_len + iv_key_len;
+
+	if (cek->cekdatalen != key_len)
+	{
+		libpq_append_conn_error(conn, "column encryption key has wrong length for algorithm (has: %zu, required: %d)",
+								cek->cekdatalen, key_len);
+		goto fail;
+	}
+
+	mac_key = cek->cekdata;
+	enc_key = cek->cekdata + mac_key_len;
+	iv_key = cek->cekdata + mac_key_len + enc_key_len;
+
+	ivlen = EVP_CIPHER_CTX_iv_length(evp_cipher_ctx);
+	Assert(ivlen <= sizeof(iv));
+	if (enc_det)
+	{
+#ifndef TEST_ENCRYPT
+		make_siv(conn, iv, ivlen, md, iv_key, iv_key_len, value, nbytes);
+#else
+		(void) iv_key;	/* unused */
+		memcpy(iv, test_IV, ivlen);
+#endif
+	}
+	else
+		pg_strong_random(iv, ivlen);
+	if (!EVP_EncryptInit_ex(evp_cipher_ctx, NULL, NULL, enc_key, iv))
+	{
+		libpq_append_conn_error(conn, "encryption initialization failed: %s",
+								ERR_reason_error_string(ERR_get_error()));
+		goto fail;
+	}
+
+	bufsize = ivlen + (nbytes + 2 * EVP_CIPHER_CTX_block_size(evp_cipher_ctx) - 1);
+	buf = malloc(bufsize);
+	if (!buf)
+	{
+		libpq_append_conn_error(conn, "out of memory");
+		goto fail;
+	}
+	memcpy(buf, iv, ivlen);
+	encr = buf + ivlen;
+	if (!EVP_EncryptUpdate(evp_cipher_ctx, encr, &encrlen, value, nbytes))
+	{
+		libpq_append_conn_error(conn, "encryption failed: %s",
+								ERR_reason_error_string(ERR_get_error()));
+		goto fail;
+	}
+	if (!EVP_EncryptFinal_ex(evp_cipher_ctx, encr + encrlen, &encrlen2))
+	{
+		libpq_append_conn_error(conn, "encryption failed: %s",
+								ERR_reason_error_string(ERR_get_error()));
+		goto fail;
+	}
+	encrlen += encrlen2;
+
+	encr -= ivlen;
+	encrlen += ivlen;
+
+	Assert(encrlen <= bufsize);
+
+	if (!get_message_auth_tag(md, mac_key, mac_key_len,
+							  encr, encrlen,
+							  md_value, &md_len,
+							  &errmsg))
+	{
+		appendPQExpBuffer(&conn->errorMessage, "%s\n", errmsg);
+		goto fail;
+	}
+
+	buf2size = encrlen + md_len;
+	buf2 = malloc(buf2size);
+	if (!buf2)
+	{
+		libpq_append_conn_error(conn, "out of memory");
+		goto fail;
+	}
+	memcpy(buf2, encr, encrlen);
+	memcpy(buf2 + encrlen, md_value, md_len);
+
+	result = buf2;
+	nbytes = buf2size;
+
+fail:
+	free(buf);
+	EVP_CIPHER_CTX_free(evp_cipher_ctx);
+
+	*nbytesp = nbytes;
+	return result;
+}
+
+
+/*
+ * Run test cases
+ */
+#ifdef TEST_ENCRYPT
+
+static void
+debug_print_hex(const char *name, const unsigned char *val, int len)
+{
+	printf("%s =", name);
+	for (int i = 0; i < len; i++)
+	{
+		if (i % 16 == 0)
+			printf("\n");
+		else
+			printf(" ");
+		printf("%02x", val[i]);
+	}
+	printf("\n");
+}
+
+/*
+ * K and P are from the mcgrew paper, K_len and P_len are their respective
+ * lengths.  encrypt_value() requires the key length to contain the IV key, so
+ * we pass it here, too, but it will not be used.
+ */
+static void
+test_case(int alg, const unsigned char *K, size_t K_len, size_t IV_key_len, const unsigned char *P, size_t P_len)
+{
+	unsigned char *C;
+	int			nbytes;
+	PGCEK		cek;
+
+	nbytes = P_len;
+	cek.cekdatalen = K_len + IV_key_len;
+	cek.cekdata = malloc(cek.cekdatalen);
+	memcpy(cek.cekdata, K, K_len);
+
+	C = encrypt_value(NULL, &cek, alg, P, &nbytes, true);
+	debug_print_hex("C", C, nbytes);
+}
+
+int
+main(int argc, char **argv)
+{
+	printf("5.1\n");
+	test_case(PG_CEK_AEAD_AES_128_CBC_HMAC_SHA_256, K, 32, 16, P, sizeof(P));
+	printf("5.2\n");
+	test_case(PG_CEK_AEAD_AES_192_CBC_HMAC_SHA_384, K, 48, 24, P, sizeof(P));
+	printf("5.3\n");
+	test_case(PG_CEK_AEAD_AES_256_CBC_HMAC_SHA_384, K, 56, 24, P, sizeof(P));
+	printf("5.4\n");
+	test_case(PG_CEK_AEAD_AES_256_CBC_HMAC_SHA_512, K, 64, 32, P, sizeof(P));
+
+	return 0;
+}
+
+#endif							/* TEST_ENCRYPT */
diff --git a/src/interfaces/libpq/fe-encrypt.h b/src/interfaces/libpq/fe-encrypt.h
new file mode 100644
index 0000000000..0b65f913da
--- /dev/null
+++ b/src/interfaces/libpq/fe-encrypt.h
@@ -0,0 +1,33 @@
+/*-------------------------------------------------------------------------
+ *
+ * fe-encrypt.h
+ *
+ * client-side column encryption support
+ *
+ * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ *	  src/interfaces/libpq/fe-encrypt.h
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef FE_ENCRYPT_H
+#define FE_ENCRYPT_H
+
+#include "libpq-fe.h"
+#include "libpq-int.h"
+
+extern unsigned char *decrypt_cek_from_file(PGconn *conn, const char *cmkfilename, int cmkalg,
+											int fromlen, const unsigned char *from,
+											int *tolen);
+
+extern unsigned char *decrypt_value(PGresult *res, const PGCEK *cek, int cekalg,
+									const unsigned char *input, int inputlen,
+									const char **errmsgp);
+
+extern unsigned char *encrypt_value(PGconn *conn, const PGCEK *cek, int cekalg,
+									const unsigned char *value, int *nbytesp, bool enc_det);
+
+#endif							/* FE_ENCRYPT_H */
diff --git a/src/interfaces/libpq/fe-exec.c b/src/interfaces/libpq/fe-exec.c
index ec62550e38..7c7e2bac12 100644
--- a/src/interfaces/libpq/fe-exec.c
+++ b/src/interfaces/libpq/fe-exec.c
@@ -24,6 +24,8 @@
 #include <unistd.h>
 #endif
 
+#include "common/colenc.h"
+#include "fe-encrypt.h"
 #include "libpq-fe.h"
 #include "libpq-int.h"
 #include "mb/pg_wchar.h"
@@ -72,7 +74,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);
@@ -1183,6 +1186,414 @@ pqSaveParameterStatus(PGconn *conn, const char *name, const char *value)
 	}
 }
 
+/*
+ * pqSaveColumnMasterKey - save column master key sent by backend
+ */
+int
+pqSaveColumnMasterKey(PGconn *conn, int keyid, const char *keyname,
+					  const char *keyrealm)
+{
+	char	   *keyname_copy;
+	char	   *keyrealm_copy;
+	bool		found;
+
+	keyname_copy = strdup(keyname);
+	if (!keyname_copy)
+		return EOF;
+	keyrealm_copy = strdup(keyrealm);
+	if (!keyrealm_copy)
+	{
+		free(keyname_copy);
+		return EOF;
+	}
+
+	found = false;
+	for (int i = 0; i < conn->ncmks; i++)
+	{
+		struct pg_cmk *checkcmk = &conn->cmks[i];
+
+		/* replace existing? */
+		if (checkcmk->cmkid == keyid)
+		{
+			free(checkcmk->cmkname);
+			free(checkcmk->cmkrealm);
+			checkcmk->cmkname = keyname_copy;
+			checkcmk->cmkrealm = keyrealm_copy;
+			found = true;
+			break;
+		}
+	}
+
+	/* append new? */
+	if (!found)
+	{
+		int			newncmks;
+		struct pg_cmk *newcmks;
+		struct pg_cmk *newcmk;
+
+		newncmks = conn->ncmks + 1;
+		if (newncmks <= 0)
+			return EOF;
+		newcmks = realloc(conn->cmks, newncmks * sizeof(struct pg_cmk));
+		if (!newcmks)
+		{
+			free(keyname_copy);
+			free(keyrealm_copy);
+			return EOF;
+		}
+
+		newcmk = &newcmks[newncmks - 1];
+		newcmk->cmkid = keyid;
+		newcmk->cmkname = keyname_copy;
+		newcmk->cmkrealm = keyrealm_copy;
+
+		conn->ncmks = newncmks;
+		conn->cmks = newcmks;
+	}
+
+	return 0;
+}
+
+/*
+ * Replace placeholders in input string.  Return value malloc'ed.
+ */
+static char *
+replace_cmk_placeholders(const char *in, const char *cmkname, const char *cmkrealm, int cmkalg, const char *tmpfile)
+{
+	PQExpBufferData buf;
+
+	initPQExpBuffer(&buf);
+
+	for (const char *p = in; *p; p++)
+	{
+		if (p[0] == '%')
+		{
+			switch (p[1])
+			{
+				case 'a':
+					{
+						const char *s = get_cmkalg_name(cmkalg);
+
+						appendPQExpBufferStr(&buf, s ? s : "INVALID");
+					}
+					p++;
+					break;
+				case 'j':
+					{
+						const char *s = get_cmkalg_jwa_name(cmkalg);
+
+						appendPQExpBufferStr(&buf, s ? s : "INVALID");
+					}
+					p++;
+					break;
+				case 'k':
+					appendPQExpBufferStr(&buf, cmkname);
+					p++;
+					break;
+				case 'p':
+					appendPQExpBufferStr(&buf, tmpfile);
+					p++;
+					break;
+				case 'r':
+					appendPQExpBufferStr(&buf, cmkrealm);
+					p++;
+					break;
+				default:
+					appendPQExpBufferChar(&buf, p[0]);
+			}
+		}
+		else
+			appendPQExpBufferChar(&buf, p[0]);
+	}
+
+	return buf.data;
+}
+
+#ifndef USE_SSL
+/*
+ * Dummy implementation for non-SSL builds
+ */
+unsigned char *
+decrypt_cek_from_file(PGconn *conn, const char *cmkfilename, int cmkalg,
+					  int fromlen, const unsigned char *from,
+					  int *tolen)
+{
+	libpq_append_conn_error(conn, "column encryption not supported by this build");
+	return NULL;
+}
+#endif
+
+/*
+ * Decrypt a CEK using the given CMK.  The ciphertext is passed in
+ * "from" and "fromlen".  Return the decrypted value in a malloc'ed area, its
+ * length via "tolen".  Return NULL on error; add error messages directly to
+ * "conn".
+ */
+static unsigned char *
+decrypt_cek(PGconn *conn, const PGCMK *cmk, int cmkalg,
+			int fromlen, const unsigned char *from,
+			int *tolen)
+{
+	char	   *cmklookup;
+	bool		found = false;
+	unsigned char *result = NULL;
+
+	cmklookup = strdup(conn->cmklookup ? conn->cmklookup : "");
+
+	if (!cmklookup)
+	{
+		libpq_append_conn_error(conn, "out of memory");
+		return NULL;
+	}
+
+	/*
+	 * Analyze semicolon-separated list
+	 */
+	for (char *s = strtok(cmklookup, ";"); s; s = strtok(NULL, ";"))
+	{
+		char	   *sep;
+
+		/* split found token at '=' */
+		sep = strchr(s, '=');
+		if (!sep)
+		{
+			libpq_append_conn_error(conn, "syntax error in CMK lookup specification, missing \"%c\": %s", '=', s);
+			break;
+		}
+
+		/* matching realm? */
+		if (strncmp(s, "*", sep - s) == 0 || strncmp(s, cmk->cmkrealm, sep - s) == 0)
+		{
+			char	   *sep2;
+
+			found = true;
+
+			sep2 = strchr(sep, ':');
+			if (!sep2)
+			{
+				libpq_append_conn_error(conn, "syntax error in CMK lookup specification, missing \"%c\": %s", ':', s);
+				goto fail;
+			}
+
+			if (strncmp(sep + 1, "file", sep2 - (sep + 1)) == 0)
+			{
+				char	   *cmkfilename;
+
+				cmkfilename = replace_cmk_placeholders(sep2 + 1, cmk->cmkname, cmk->cmkrealm, cmkalg, "INVALID");
+				result = decrypt_cek_from_file(conn, cmkfilename, cmkalg, fromlen, from, tolen);
+				free(cmkfilename);
+			}
+			else if (strncmp(sep + 1, "run", sep2 - (sep + 1)) == 0)
+			{
+				char		tmpfile[MAXPGPATH] = {0};
+				int			fd;
+				char	   *command;
+				FILE	   *fp;
+				/* only needs enough room for CEK key material */
+				char		buf[1024];
+				size_t		nread;
+				int			rc;
+
+#ifndef WIN32
+				{
+					const char *tmpdir;
+
+					tmpdir = getenv("TMPDIR");
+					if (!tmpdir)
+						tmpdir = "/tmp";
+					strlcpy(tmpfile, tmpdir, sizeof(tmpfile));
+					strlcat(tmpfile, "/libpq-XXXXXX", sizeof(tmpfile));
+					fd = mkstemp(tmpfile);
+					if (fd < 0)
+					{
+						libpq_append_conn_error(conn, "could not run create temporary file: %m");
+						goto fail;
+					}
+				}
+#else
+				{
+					char		tmpdir[MAXPGPATH];
+					int			ret;
+
+					ret = GetTempPath(MAXPGPATH, tmpdir);
+					if (ret == 0 || ret > MAXPGPATH)
+					{
+						libpq_append_conn_error(conn, "could not locate temporary directory: %s",
+												!ret ? strerror(errno) : "");
+						return false;
+					}
+
+					if (GetTempFileName(tmpdir, "libpq", 0, tmpfile) == 0)
+					{
+						libpq_append_conn_error(conn, "could not run create temporary file: error code %lu",
+												GetLastError());
+						goto fail;
+					}
+
+					fd = open(tmpfile, O_WRONLY | O_TRUNC | PG_BINARY, 0);
+					if (fd < 0)
+					{
+						libpq_append_conn_error(conn, "could not run open temporary file: %m");
+						goto fail;
+					}
+				}
+#endif
+				if (write(fd, from, fromlen) < fromlen)
+				{
+					libpq_append_conn_error(conn, "could not write to temporary file: %m");
+					close(fd);
+					unlink(tmpfile);
+					goto fail;
+				}
+				if (close(fd) < 0)
+				{
+					libpq_append_conn_error(conn, "could not close temporary file: %m");
+					unlink(tmpfile);
+					goto fail;
+				}
+
+				command = replace_cmk_placeholders(sep2 + 1, cmk->cmkname, cmk->cmkrealm, cmkalg, tmpfile);
+				fp = popen(command, "r");
+				if (!fp)
+				{
+					libpq_append_conn_error(conn, "could not run command \"%s\": %m", command);
+					free(command);
+					unlink(tmpfile);
+					goto fail;
+				}
+				nread = fread(buf, 1, sizeof(buf), fp);
+				if (ferror(fp))
+				{
+					libpq_append_conn_error(conn, "could not read from command: %m");
+					pclose(fp);
+					free(command);
+					unlink(tmpfile);
+					goto fail;
+				}
+				else if (!feof(fp))
+				{
+					libpq_append_conn_error(conn, "output from command too long");
+					pclose(fp);
+					free(command);
+					unlink(tmpfile);
+					goto fail;
+				}
+				rc = pclose(fp);
+				if (rc != 0)
+				{
+					/*
+					 * XXX would like to use wait_result_to_str(rc) but that
+					 * cannot be called from libpq because it calls exit()
+					 */
+					libpq_append_conn_error(conn, "could not run command \"%s\"", command);
+					free(command);
+					unlink(tmpfile);
+					goto fail;
+				}
+				free(command);
+				unlink(tmpfile);
+
+				result = malloc(nread);
+				if (!result)
+				{
+					libpq_append_conn_error(conn, "out of memory");
+					goto fail;
+				}
+				memcpy(result, buf, nread);
+				*tolen = nread;
+			}
+			else
+			{
+				libpq_append_conn_error(conn, "CMK lookup scheme \"%s\" not recognized", sep + 1);
+				goto fail;
+			}
+		}
+	}
+
+	if (!found)
+	{
+		libpq_append_conn_error(conn, "no CMK lookup found for realm \"%s\"", cmk->cmkrealm);
+	}
+
+fail:
+	free(cmklookup);
+	return result;
+}
+
+/*
+ * pqSaveColumnEncryptionKey - save column encryption key sent by backend
+ */
+int
+pqSaveColumnEncryptionKey(PGconn *conn, int keyid, int cmkid, int cmkalg, const unsigned char *value, int len)
+{
+	PGCMK	   *cmk = NULL;
+	unsigned char *plainval = NULL;
+	int			plainvallen = 0;
+	bool		found;
+
+	for (int i = 0; i < conn->ncmks; i++)
+	{
+		if (conn->cmks[i].cmkid == cmkid)
+		{
+			cmk = &conn->cmks[i];
+			break;
+		}
+	}
+
+	if (!cmk)
+		return EOF;
+
+	plainval = decrypt_cek(conn, cmk, cmkalg, len, value, &plainvallen);
+	if (!plainval)
+		return EOF;
+
+	found = false;
+	for (int i = 0; i < conn->nceks; i++)
+	{
+		struct pg_cek *checkcek = &conn->ceks[i];
+
+		/* replace existing? */
+		if (checkcek->cekid == keyid)
+		{
+			free(checkcek->cekdata);
+			checkcek->cekdata = plainval;
+			checkcek->cekdatalen = plainvallen;
+			found = true;
+			break;
+		}
+	}
+
+	/* append new? */
+	if (!found)
+	{
+		int			newnceks;
+		struct pg_cek *newceks;
+		struct pg_cek *newcek;
+
+		newnceks = conn->nceks + 1;
+		if (newnceks <= 0)
+		{
+			free(plainval);
+			return EOF;
+		}
+		newceks = realloc(conn->ceks, newnceks * sizeof(struct pg_cek));
+		if (!newceks)
+		{
+			free(plainval);
+			return EOF;
+		}
+
+		newcek = &newceks[newnceks - 1];
+		newcek->cekid = keyid;
+		newcek->cekdata = plainval;
+		newcek->cekdatalen = plainvallen;
+
+		conn->nceks = newnceks;
+		conn->ceks = newceks;
+	}
+
+	return 0;
+}
 
 /*
  * pqRowProcessor
@@ -1251,13 +1662,51 @@ pqRowProcessor(PGconn *conn, const char **errmsgp)
 			bool		isbinary = (res->attDescs[i].format != 0);
 			char	   *val;
 
-			val = (char *) pqResultAlloc(res, clen + 1, isbinary);
-			if (val == NULL)
+			if (res->attDescs[i].cekid)
+			{
+				/* encrypted column */
+#ifdef USE_SSL
+				PGCEK	   *cek = NULL;
+
+				if (!isbinary)
+				{
+					*errmsgp = libpq_gettext("encrypted column was not sent in binary format");
+					goto fail;
+				}
+
+				for (int j = 0; j < conn->nceks; j++)
+				{
+					if (conn->ceks[j].cekid == res->attDescs[i].cekid)
+					{
+						cek = &conn->ceks[j];
+						break;
+					}
+				}
+				if (!cek)
+				{
+					*errmsgp = libpq_gettext("column encryption key not found");
+					goto fail;
+				}
+
+				val = (char *) decrypt_value(res, cek, res->attDescs[i].cekalg,
+											 (const unsigned char *) columns[i].value, clen, errmsgp);
+				if (val == NULL)
+					goto fail;
+#else
+				*errmsgp = libpq_gettext("column encryption not supported by this build");
 				goto fail;
+#endif
+			}
+			else
+			{
+				val = (char *) pqResultAlloc(res, clen + 1, isbinary);
+				if (val == NULL)
+					goto fail;
 
-			/* copy and zero-terminate the data (even if it's binary) */
-			memcpy(val, columns[i].value, clen);
-			val[clen] = '\0';
+				/* 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;
@@ -1500,6 +1949,8 @@ PQsendQueryParams(PGconn *conn,
 				  const int *paramFormats,
 				  int resultFormat)
 {
+	PGresult   *paramDesc = NULL;
+
 	if (!PQsendQueryStart(conn, true))
 		return 0;
 
@@ -1516,6 +1967,37 @@ PQsendQueryParams(PGconn *conn,
 		return 0;
 	}
 
+	if (conn->column_encryption_enabled)
+	{
+		PGresult   *res;
+		bool		error;
+
+		if (conn->pipelineStatus != PQ_PIPELINE_OFF)
+		{
+			libpq_append_conn_error(conn, "synchronous command execution functions are not allowed in pipeline mode");
+			return 0;
+		}
+
+		if (!PQsendPrepare(conn, "", command, nParams, paramTypes))
+			return 0;
+		error = false;
+		while ((res = PQgetResult(conn)) != NULL)
+		{
+			if (PQresultStatus(res) != PGRES_COMMAND_OK)
+				error = true;
+			PQclear(res);
+		}
+		if (error)
+			return 0;
+
+		paramDesc = PQdescribePrepared(conn, "");
+		if (PQresultStatus(paramDesc) != PGRES_COMMAND_OK)
+			return 0;
+
+		command = NULL;
+		paramTypes = NULL;
+	}
+
 	return PQsendQueryGuts(conn,
 						   command,
 						   "",	/* use unnamed statement */
@@ -1524,7 +2006,8 @@ PQsendQueryParams(PGconn *conn,
 						   paramValues,
 						   paramLengths,
 						   paramFormats,
-						   resultFormat);
+						   resultFormat,
+						   paramDesc);
 }
 
 /*
@@ -1639,6 +2122,24 @@ PQsendQueryPrepared(PGconn *conn,
 					const int *paramLengths,
 					const int *paramFormats,
 					int resultFormat)
+{
+	return PQsendQueryPreparedDescribed(conn, stmtName, nParams, paramValues, paramLengths, paramFormats, resultFormat, NULL);
+}
+
+/*
+ * PQsendQueryPreparedDescribed
+ *		Like PQsendQueryPrepared, but with additional argument to pass
+ *		parameter descriptions, for column encryption.
+ */
+int
+PQsendQueryPreparedDescribed(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;
@@ -1664,7 +2165,8 @@ PQsendQueryPrepared(PGconn *conn,
 						   paramValues,
 						   paramLengths,
 						   paramFormats,
-						   resultFormat);
+						   resultFormat,
+						   paramDesc);
 }
 
 /*
@@ -1762,7 +2264,8 @@ PQsendQueryGuts(PGconn *conn,
 				const char *const *paramValues,
 				const int *paramLengths,
 				const int *paramFormats,
-				int resultFormat)
+				int resultFormat,
+				PGresult *paramDesc)
 {
 	int			i;
 	PGcmdQueueEntry *entry;
@@ -1810,13 +2313,47 @@ 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;
+
+			/* Check force column encryption */
+			if (format & 0x10)
+			{
+				if (!(paramDesc &&
+					  paramDesc->paramDescs &&
+					  paramDesc->paramDescs[i].cekid))
+				{
+					libpq_append_conn_error(conn, "parameter with forced encryption is not to be encrypted");
+					goto sendFailed;
+				}
+			}
+			format &= ~0x10;
+
+			if (paramDesc && paramDesc->paramDescs)
+			{
+				PGresParamDesc *pd = &paramDesc->paramDescs[i];
+
+				if (pd->cekid)
+				{
+					if (format != 0)
+					{
+						libpq_append_conn_error(conn, "format must be text for encrypted parameter");
+						goto sendFailed;
+					}
+					/* Send encrypted value in binary */
+					format = 1;
+					/* And mark it as encrypted */
+					format |= 0x10;
+				}
+			}
+
+			if (pqPutInt(format, 2, conn) < 0)
 				goto sendFailed;
 		}
 	}
@@ -1835,8 +2372,9 @@ PQsendQueryGuts(PGconn *conn,
 		if (paramValues && paramValues[i])
 		{
 			int			nbytes;
+			const char *paramValue;
 
-			if (paramFormats && paramFormats[i] != 0)
+			if (paramFormats && (paramFormats[i] & 0x01) != 0)
 			{
 				/* binary parameter */
 				if (paramLengths)
@@ -1852,9 +2390,53 @@ PQsendQueryGuts(PGconn *conn,
 				/* text parameter, do not use paramLengths */
 				nbytes = strlen(paramValues[i]);
 			}
+
+			paramValue = paramValues[i];
+
+			if (paramDesc && paramDesc->paramDescs && paramDesc->paramDescs[i].cekid)
+			{
+				/* encrypted column */
+#ifdef USE_SSL
+				bool		enc_det = (paramDesc->paramDescs[i].flags & 0x01) != 0;
+				PGCEK	   *cek = NULL;
+				char	   *enc_paramValue;
+				int			enc_nbytes = nbytes;
+
+				for (int j = 0; j < conn->nceks; j++)
+				{
+					if (conn->ceks[j].cekid == paramDesc->paramDescs[i].cekid)
+					{
+						cek = &conn->ceks[j];
+						break;
+					}
+				}
+				if (!cek)
+				{
+					libpq_append_conn_error(conn, "column encryption key not found");
+					goto sendFailed;
+				}
+
+				enc_paramValue = (char *) encrypt_value(conn, cek, paramDesc->paramDescs[i].cekalg,
+														(const unsigned char *) paramValue, &enc_nbytes, enc_det);
+				if (!enc_paramValue)
+					goto sendFailed;
+
+				if (pqPutInt(enc_nbytes, 4, conn) < 0 ||
+					pqPutnchar(enc_paramValue, enc_nbytes, conn) < 0)
+					goto sendFailed;
+
+				free(enc_paramValue);
+#else
+				libpq_append_conn_error(conn, "column encryption not supported by this build");
+				goto sendFailed;
+#endif
+			}
+			else
+			{
 			if (pqPutInt(nbytes, 4, conn) < 0 ||
-				pqPutnchar(paramValues[i], nbytes, conn) < 0)
+				pqPutnchar(paramValue, nbytes, conn) < 0)
 				goto sendFailed;
+			}
 		}
 		else
 		{
@@ -2290,12 +2872,31 @@ PQexecPrepared(PGconn *conn,
 			   const int *paramLengths,
 			   const int *paramFormats,
 			   int resultFormat)
+{
+	return PQexecPreparedDescribed(conn, stmtName, nParams, paramValues, paramLengths, paramFormats, resultFormat, NULL);
+}
+
+/*
+ * PQexecPreparedDescribed
+ *		Like PQexecPrepared, but with additional argument to pass parameter
+ *		descriptions, for column encryption.
+ */
+PGresult *
+PQexecPreparedDescribed(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,
-							 nParams, paramValues, paramLengths,
-							 paramFormats, resultFormat))
+	if (!PQsendQueryPreparedDescribed(conn, stmtName,
+									  nParams, paramValues, paramLengths,
+									  paramFormats,
+									  resultFormat, paramDesc))
 		return NULL;
 	return PQexecFinish(conn);
 }
@@ -3539,7 +4140,17 @@ PQfformat(const PGresult *res, int field_num)
 	if (!check_field_number(res, field_num))
 		return 0;
 	if (res->attDescs)
+	{
+		/*
+		 * An encrypted column is always presented to the application in text
+		 * format.  The .format field applies to the ciphertext, which might
+		 * be in either format, but the plaintext inside is always in text
+		 * format.
+		 */
+		if (res->attDescs[field_num].cekid != 0)
+			return 0;
 		return res->attDescs[field_num].format;
+	}
 	else
 		return 0;
 }
@@ -3577,6 +4188,17 @@ PQfmod(const PGresult *res, int field_num)
 		return 0;
 }
 
+int
+PQfisencrypted(const PGresult *res, int field_num)
+{
+	if (!check_field_number(res, field_num))
+		return false;
+	if (res->attDescs)
+		return (res->attDescs[field_num].cekid != 0);
+	else
+		return false;
+}
+
 char *
 PQcmdStatus(PGresult *res)
 {
@@ -3762,6 +4384,17 @@ PQparamtype(const PGresult *res, int param_num)
 		return InvalidOid;
 }
 
+int
+PQparamisencrypted(const PGresult *res, int param_num)
+{
+	if (!check_param_number(res, param_num))
+		return false;
+	if (res->paramDescs)
+		return (res->paramDescs[param_num].cekid != 0);
+	else
+		return false;
+}
+
 
 /* PQsetnonblocking:
  *	sets the PGconn's database connection non-blocking if the arg is true
diff --git a/src/interfaces/libpq/fe-protocol3.c b/src/interfaces/libpq/fe-protocol3.c
index 8ab6a88416..3e3d8674be 100644
--- a/src/interfaces/libpq/fe-protocol3.c
+++ b/src/interfaces/libpq/fe-protocol3.c
@@ -43,6 +43,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);
@@ -297,6 +299,12 @@ pqParseInput3(PGconn *conn)
 					if (pqGetInt(&(conn->be_key), 4, conn))
 						return;
 					break;
+				case 'y':		/* Column Master Key */
+					getColumnMasterKey(conn);
+					break;
+				case 'Y':		/* Column Encryption Key */
+					getColumnEncryptionKey(conn);
+					break;
 				case 'T':		/* Row Description */
 					if (conn->error_result ||
 						(conn->result != NULL &&
@@ -547,6 +555,8 @@ getRowDescriptions(PGconn *conn, int msgLength)
 		int			typlen;
 		int			atttypmod;
 		int			format;
+		int			cekid;
+		int			cekalg;
 
 		if (pqGets(&conn->workBuffer, conn) ||
 			pqGetInt(&tableid, 4, conn) ||
@@ -561,6 +571,21 @@ getRowDescriptions(PGconn *conn, int msgLength)
 			goto advance_and_error;
 		}
 
+		if (conn->column_encryption_enabled)
+		{
+			if (pqGetInt(&cekid, 4, conn) ||
+				pqGetInt(&cekalg, 4, conn))
+			{
+				errmsg = libpq_gettext("insufficient data in \"T\" message");
+				goto advance_and_error;
+			}
+		}
+		else
+		{
+			cekid = 0;
+			cekalg = 0;
+		}
+
 		/*
 		 * Since pqGetInt treats 2-byte integers as unsigned, we need to
 		 * coerce these results to signed form.
@@ -582,8 +607,10 @@ getRowDescriptions(PGconn *conn, int msgLength)
 		result->attDescs[i].typid = typid;
 		result->attDescs[i].typlen = typlen;
 		result->attDescs[i].atttypmod = atttypmod;
+		result->attDescs[i].cekid = cekid;
+		result->attDescs[i].cekalg = cekalg;
 
-		if (format != 1)
+		if ((format & 0x0F) != 1)
 			result->binary = 0;
 	}
 
@@ -685,10 +712,31 @@ getParamDescriptions(PGconn *conn, int msgLength)
 	for (i = 0; i < nparams; i++)
 	{
 		int			typid;
+		int			cekid;
+		int			cekalg;
+		int			flags;
 
 		if (pqGetInt(&typid, 4, conn))
 			goto not_enough_data;
+		if (conn->column_encryption_enabled)
+		{
+			if (pqGetInt(&cekid, 4, conn))
+				goto not_enough_data;
+			if (pqGetInt(&cekalg, 4, conn))
+				goto not_enough_data;
+			if (pqGetInt(&flags, 2, conn))
+				goto not_enough_data;
+		}
+		else
+		{
+			cekid = 0;
+			cekalg = 0;
+			flags = 0;
+		}
 		result->paramDescs[i].typid = typid;
+		result->paramDescs[i].cekid = cekid;
+		result->paramDescs[i].cekalg = cekalg;
+		result->paramDescs[i].flags = flags;
 	}
 
 	/* Success! */
@@ -1468,6 +1516,92 @@ getParameterStatus(PGconn *conn)
 	return 0;
 }
 
+/*
+ * Attempt to read a ColumnMasterKey message.
+ * Entry: 'y' message type and length have already been consumed.
+ * Exit: returns 0 if successfully consumed message.
+ *		 returns EOF if not enough data.
+ */
+static int
+getColumnMasterKey(PGconn *conn)
+{
+	int			keyid;
+	char	   *keyname;
+	char	   *keyrealm;
+	int			ret;
+
+	/* Get the key ID */
+	if (pqGetInt(&keyid, 4, conn) != 0)
+		return EOF;
+	/* Get the key name */
+	if (pqGets(&conn->workBuffer, conn) != 0)
+		return EOF;
+	keyname = strdup(conn->workBuffer.data);
+	if (!keyname)
+		return EOF;
+	/* Get the key realm */
+	if (pqGets(&conn->workBuffer, conn) != 0)
+		return EOF;
+	keyrealm = strdup(conn->workBuffer.data);
+	if (!keyrealm)
+		return EOF;
+	/* And save it */
+	ret = pqSaveColumnMasterKey(conn, keyid, keyname, keyrealm);
+	if (ret != 0)
+		pqSaveErrorResult(conn);
+
+	free(keyname);
+	free(keyrealm);
+
+	return ret;
+}
+
+/*
+ * Attempt to read a ColumnEncryptionKey message.
+ * Entry: 'Y' message type and length have already been consumed.
+ * Exit: returns 0 if successfully consumed message.
+ *		 returns EOF if not enough data.
+ */
+static int
+getColumnEncryptionKey(PGconn *conn)
+{
+	int			keyid;
+	int			cmkid;
+	int			cmkalg;
+	char	   *buf;
+	int			vallen;
+	int			ret;
+
+	/* Get the key ID */
+	if (pqGetInt(&keyid, 4, conn) != 0)
+		return EOF;
+	/* Get the CMK ID */
+	if (pqGetInt(&cmkid, 4, conn) != 0)
+		return EOF;
+	/* Get the CMK algorithm */
+	if (pqGetInt(&cmkalg, 4, 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 (!buf)
+		return EOF;
+	if (pqGetnchar(buf, vallen, conn) != 0)
+	{
+		free(buf);
+		return EOF;
+	}
+	/* And save it */
+	ret = pqSaveColumnEncryptionKey(conn, keyid, cmkid, cmkalg, (unsigned char *) buf, vallen);
+	if (ret != 0)
+		pqSaveErrorResult(conn);
+
+	free(buf);
+
+	return ret;
+}
 
 /*
  * Attempt to read a Notify response message.
@@ -2286,6 +2420,9 @@ build_startup_packet(const PGconn *conn, char *packet,
 	if (conn->client_encoding_initial && conn->client_encoding_initial[0])
 		ADD_STARTUP_OPTION("client_encoding", conn->client_encoding_initial);
 
+	if (conn->column_encryption_enabled)
+		ADD_STARTUP_OPTION("_pq_.column_encryption", "1");
+
 	/* Add any environment-driven GUC settings needed */
 	for (next_eo = options; next_eo->envName; next_eo++)
 	{
diff --git a/src/interfaces/libpq/fe-trace.c b/src/interfaces/libpq/fe-trace.c
index abaab6a073..77791e8349 100644
--- a/src/interfaces/libpq/fe-trace.c
+++ b/src/interfaces/libpq/fe-trace.c
@@ -450,7 +450,8 @@ pqTraceOutputS(FILE *f, const char *message, int *cursor)
 
 /* ParameterDescription */
 static void
-pqTraceOutputt(FILE *f, const char *message, int *cursor, bool regress)
+pqTraceOutputt(FILE *f, const char *message, int *cursor, bool regress,
+			   bool column_encryption_enabled)
 {
 	int			nfields;
 
@@ -458,12 +459,21 @@ pqTraceOutputt(FILE *f, const char *message, int *cursor, bool regress)
 	nfields = pqTraceOutputInt16(f, message, cursor);
 
 	for (int i = 0; i < nfields; i++)
+	{
 		pqTraceOutputInt32(f, message, cursor, regress);
+		if (column_encryption_enabled)
+		{
+			pqTraceOutputInt32(f, message, cursor, regress);
+			pqTraceOutputInt32(f, message, cursor, false);
+			pqTraceOutputInt16(f, message, cursor);
+		}
+	}
 }
 
 /* RowDescription */
 static void
-pqTraceOutputT(FILE *f, const char *message, int *cursor, bool regress)
+pqTraceOutputT(FILE *f, const char *message, int *cursor, bool regress,
+			   bool column_encryption_enabled)
 {
 	int			nfields;
 
@@ -479,6 +489,11 @@ pqTraceOutputT(FILE *f, const char *message, int *cursor, bool regress)
 		pqTraceOutputInt16(f, message, cursor);
 		pqTraceOutputInt32(f, message, cursor, false);
 		pqTraceOutputInt16(f, message, cursor);
+		if (column_encryption_enabled)
+		{
+			pqTraceOutputInt32(f, message, cursor, regress);
+			pqTraceOutputInt32(f, message, cursor, false);
+		}
 	}
 }
 
@@ -514,6 +529,30 @@ pqTraceOutputW(FILE *f, const char *message, int *cursor, int length)
 		pqTraceOutputInt16(f, message, cursor);
 }
 
+/* ColumnMasterKey */
+static void
+pqTraceOutputy(FILE *f, const char *message, int *cursor, bool regress)
+{
+	fprintf(f, "ColumnMasterKey\t");
+	pqTraceOutputInt32(f, message, cursor, regress);
+	pqTraceOutputString(f, message, cursor, false);
+	pqTraceOutputString(f, message, cursor, false);
+}
+
+/* ColumnEncryptionKey */
+static void
+pqTraceOutputY(FILE *f, const char *message, int *cursor, bool regress)
+{
+	int			len;
+
+	fprintf(f, "ColumnEncryptionKey\t");
+	pqTraceOutputInt32(f, message, cursor, regress);
+	pqTraceOutputInt32(f, message, cursor, regress);
+	pqTraceOutputInt32(f, message, cursor, false);
+	len = pqTraceOutputInt32(f, message, cursor, false);
+	pqTraceOutputNchar(f, len, message, cursor);
+}
+
 /* ReadyForQuery */
 static void
 pqTraceOutputZ(FILE *f, const char *message, int *cursor)
@@ -647,10 +686,12 @@ pqTraceOutputMessage(PGconn *conn, const char *message, bool toServer)
 				fprintf(conn->Pfdebug, "Sync"); /* no message content */
 			break;
 		case 't':				/* Parameter Description */
-			pqTraceOutputt(conn->Pfdebug, message, &logCursor, regress);
+			pqTraceOutputt(conn->Pfdebug, message, &logCursor, regress,
+						   conn->column_encryption_enabled);
 			break;
 		case 'T':				/* Row Description */
-			pqTraceOutputT(conn->Pfdebug, message, &logCursor, regress);
+			pqTraceOutputT(conn->Pfdebug, message, &logCursor, regress,
+						   conn->column_encryption_enabled);
 			break;
 		case 'v':				/* Negotiate Protocol Version */
 			pqTraceOutputv(conn->Pfdebug, message, &logCursor);
@@ -665,6 +706,12 @@ pqTraceOutputMessage(PGconn *conn, const char *message, bool toServer)
 			fprintf(conn->Pfdebug, "Terminate");
 			/* No message content */
 			break;
+		case 'y':
+			pqTraceOutputy(conn->Pfdebug, message, &logCursor, regress);
+			break;
+		case 'Y':
+			pqTraceOutputY(conn->Pfdebug, message, &logCursor, regress);
+			break;
 		case 'Z':				/* Ready For Query */
 			pqTraceOutputZ(conn->Pfdebug, message, &logCursor);
 			break;
diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h
index f3d9220496..cf339a3e53 100644
--- a/src/interfaces/libpq/libpq-fe.h
+++ b/src/interfaces/libpq/libpq-fe.h
@@ -267,6 +267,8 @@ typedef struct pgresAttDesc
 	Oid			typid;			/* type id */
 	int			typlen;			/* type size */
 	int			atttypmod;		/* type-specific modifier info */
+	Oid			cekid;
+	int			cekalg;
 } PGresAttDesc;
 
 /* ----------------
@@ -438,6 +440,14 @@ extern PGresult *PQexecPrepared(PGconn *conn,
 								const int *paramLengths,
 								const int *paramFormats,
 								int resultFormat);
+extern PGresult *PQexecPreparedDescribed(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
@@ -461,6 +471,14 @@ extern int	PQsendQueryPrepared(PGconn *conn,
 								const int *paramLengths,
 								const int *paramFormats,
 								int resultFormat);
+extern int	PQsendQueryPreparedDescribed(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);
 
@@ -531,6 +549,7 @@ extern int	PQfformat(const PGresult *res, int field_num);
 extern Oid	PQftype(const PGresult *res, int field_num);
 extern int	PQfsize(const PGresult *res, int field_num);
 extern int	PQfmod(const PGresult *res, int field_num);
+extern int	PQfisencrypted(const PGresult *res, int field_num);
 extern char *PQcmdStatus(PGresult *res);
 extern char *PQoidStatus(const PGresult *res);	/* old and ugly */
 extern Oid	PQoidValue(const PGresult *res);	/* new and improved */
@@ -540,6 +559,7 @@ extern int	PQgetlength(const PGresult *res, int tup_num, int field_num);
 extern int	PQgetisnull(const PGresult *res, int tup_num, int field_num);
 extern int	PQnparams(const PGresult *res);
 extern Oid	PQparamtype(const PGresult *res, int param_num);
+extern int	PQparamisencrypted(const PGresult *res, int param_num);
 
 /* Describe prepared statements and portals */
 extern PGresult *PQdescribePrepared(PGconn *conn, const char *stmt);
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index d94b648ea5..3dfedc0f9b 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -112,6 +112,9 @@ union pgresult_data
 typedef struct pgresParamDesc
 {
 	Oid			typid;			/* type id */
+	Oid			cekid;
+	int			cekalg;
+	int			flags;
 } PGresParamDesc;
 
 /*
@@ -343,6 +346,26 @@ typedef struct pg_conn_host
 								 * found in password file. */
 } pg_conn_host;
 
+/*
+ * Column encryption support data
+ */
+
+/* column master key */
+typedef struct pg_cmk
+{
+	Oid			cmkid;
+	char	   *cmkname;
+	char	   *cmkrealm;
+} PGCMK;
+
+/* column encryption key */
+typedef struct pg_cek
+{
+	Oid			cekid;
+	unsigned char *cekdata;		/* (decrypted) */
+	size_t		cekdatalen;
+} PGCEK;
+
 /*
  * PGconn stores all the state data associated with a single connection
  * to a backend.
@@ -396,6 +419,8 @@ struct pg_conn
 	char	   *ssl_min_protocol_version;	/* minimum TLS protocol version */
 	char	   *ssl_max_protocol_version;	/* maximum TLS protocol version */
 	char	   *target_session_attrs;	/* desired session properties */
+	char	   *cmklookup;		/* CMK lookup specification */
+	char	   *column_encryption_setting;	/* column_encryption connection parameter (0 or 1) */
 
 	/* Optional file to write trace info to */
 	FILE	   *Pfdebug;
@@ -477,6 +502,13 @@ struct pg_conn
 	PGVerbosity verbosity;		/* error/notice message verbosity */
 	PGContextVisibility show_context;	/* whether to show CONTEXT field */
 	PGlobjfuncs *lobjfuncs;		/* private state for large-object access fns */
+	bool		column_encryption_enabled;	/* parsed version of column_encryption_setting */
+
+	/* Column encryption support data */
+	int			ncmks;
+	PGCMK	   *cmks;
+	int			nceks;
+	PGCEK	   *ceks;
 
 	/* Buffer for data received from backend and not yet processed */
 	char	   *inBuffer;		/* currently allocated buffer */
@@ -673,6 +705,10 @@ extern void pqSaveMessageField(PGresult *res, char code,
 							   const char *value);
 extern void pqSaveParameterStatus(PGconn *conn, const char *name,
 								  const char *value);
+extern int	pqSaveColumnMasterKey(PGconn *conn, int keyid, const char *keyname,
+								  const char *keyrealm);
+extern int	pqSaveColumnEncryptionKey(PGconn *conn, int keyid, int cmkid, int cmkalg,
+									  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/interfaces/libpq/meson.build b/src/interfaces/libpq/meson.build
index 573fd9b6ea..a6e6b2ada9 100644
--- a/src/interfaces/libpq/meson.build
+++ b/src/interfaces/libpq/meson.build
@@ -29,6 +29,7 @@ endif
 
 if ssl.found()
   libpq_sources += files('fe-secure-common.c')
+  libpq_sources += files('fe-encrypt-openssl.c')
   libpq_sources += files('fe-secure-openssl.c')
 endif
 
@@ -116,6 +117,7 @@ tests += {
     'tests': [
       't/001_uri.pl',
       't/002_api.pl',
+      't/003_encrypt.pl',
     ],
     'env': {'with_ssl': get_option('ssl')},
   },
diff --git a/src/interfaces/libpq/nls.mk b/src/interfaces/libpq/nls.mk
index 4df544ecef..0c36aa5f32 100644
--- a/src/interfaces/libpq/nls.mk
+++ b/src/interfaces/libpq/nls.mk
@@ -1,6 +1,6 @@
 # src/interfaces/libpq/nls.mk
 CATALOG_NAME     = libpq
-GETTEXT_FILES    = fe-auth.c fe-auth-scram.c fe-connect.c fe-exec.c fe-gssapi-common.c fe-lobj.c fe-misc.c fe-protocol3.c fe-secure.c fe-secure-common.c fe-secure-gssapi.c fe-secure-openssl.c win32.c ../../port/thread.c
+GETTEXT_FILES    = fe-auth.c fe-auth-scram.c fe-connect.c fe-encrypt-openssl.c fe-exec.c fe-gssapi-common.c fe-lobj.c fe-misc.c fe-protocol3.c fe-secure.c fe-secure-common.c fe-secure-gssapi.c fe-secure-openssl.c win32.c ../../port/thread.c
 GETTEXT_TRIGGERS = libpq_append_conn_error:2 \
                    libpq_append_error:2 \
                    libpq_gettext pqInternalNotice:2
diff --git a/src/interfaces/libpq/t/003_encrypt.pl b/src/interfaces/libpq/t/003_encrypt.pl
new file mode 100644
index 0000000000..94a7441037
--- /dev/null
+++ b/src/interfaces/libpq/t/003_encrypt.pl
@@ -0,0 +1,70 @@
+# Copyright (c) 2023, PostgreSQL Global Development Group
+use strict;
+use warnings;
+
+use PostgreSQL::Test::Utils;
+use Test::More;
+
+plan skip_all => 'OpenSSL not supported by this build' if $ENV{with_ssl} ne 'openssl';
+
+# test data from https://datatracker.ietf.org/doc/html/draft-mcgrew-aead-aes-cbc-hmac-sha2-05#section-5
+command_like([ 'libpq_test_encrypt' ],
+	qr{5.1
+C =
+1a f3 8c 2d c2 b9 6f fd d8 66 94 09 23 41 bc 04
+c8 0e df a3 2d df 39 d5 ef 00 c0 b4 68 83 42 79
+a2 e4 6a 1b 80 49 f7 92 f7 6b fe 54 b9 03 a9 c9
+a9 4a c9 b4 7a d2 65 5c 5f 10 f9 ae f7 14 27 e2
+fc 6f 9b 3f 39 9a 22 14 89 f1 63 62 c7 03 23 36
+09 d4 5a c6 98 64 e3 32 1c f8 29 35 ac 40 96 c8
+6e 13 33 14 c5 40 19 e8 ca 79 80 df a4 b9 cf 1b
+38 4c 48 6f 3a 54 c5 10 78 15 8e e5 d7 9d e5 9f
+bd 34 d8 48 b3 d6 95 50 a6 76 46 34 44 27 ad e5
+4b 88 51 ff b5 98 f7 f8 00 74 b9 47 3c 82 e2 db
+65 2c 3f a3 6b 0a 7c 5b 32 19 fa b3 a3 0b c1 c4
+5.2
+C =
+1a f3 8c 2d c2 b9 6f fd d8 66 94 09 23 41 bc 04
+ea 65 da 6b 59 e6 1e db 41 9b e6 2d 19 71 2a e5
+d3 03 ee b5 00 52 d0 df d6 69 7f 77 22 4c 8e db
+00 0d 27 9b dc 14 c1 07 26 54 bd 30 94 42 30 c6
+57 be d4 ca 0c 9f 4a 84 66 f2 2b 22 6d 17 46 21
+4b f8 cf c2 40 0a dd 9f 51 26 e4 79 66 3f c9 0b
+3b ed 78 7a 2f 0f fc bf 39 04 be 2a 64 1d 5c 21
+05 bf e5 91 ba e2 3b 1d 74 49 e5 32 ee f6 0a 9a
+c8 bb 6c 6b 01 d3 5d 49 78 7b cd 57 ef 48 49 27
+f2 80 ad c9 1a c0 c4 e7 9c 7b 11 ef c6 00 54 e3
+84 90 ac 0e 58 94 9b fe 51 87 5d 73 3f 93 ac 20
+75 16 80 39 cc c7 33 d7
+5.3
+C =
+1a f3 8c 2d c2 b9 6f fd d8 66 94 09 23 41 bc 04
+89 31 29 b0 f4 ee 9e b1 8d 75 ed a6 f2 aa a9 f3
+60 7c 98 c4 ba 04 44 d3 41 62 17 0d 89 61 88 4e
+58 f2 7d 4a 35 a5 e3 e3 23 4a a9 94 04 f3 27 f5
+c2 d7 8e 98 6e 57 49 85 8b 88 bc dd c2 ba 05 21
+8f 19 51 12 d6 ad 48 fa 3b 1e 89 aa 7f 20 d5 96
+68 2f 10 b3 64 8d 3b b0 c9 83 c3 18 5f 59 e3 6d
+28 f6 47 c1 c1 39 88 de 8e a0 d8 21 19 8c 15 09
+77 e2 8c a7 68 08 0b c7 8c 35 fa ed 69 d8 c0 b7
+d9 f5 06 23 21 98 a4 89 a1 a6 ae 03 a3 19 fb 30
+dd 13 1d 05 ab 34 67 dd 05 6f 8e 88 2b ad 70 63
+7f 1e 9a 54 1d 9c 23 e7
+5.4
+C =
+1a f3 8c 2d c2 b9 6f fd d8 66 94 09 23 41 bc 04
+4a ff aa ad b7 8c 31 c5 da 4b 1b 59 0d 10 ff bd
+3d d8 d5 d3 02 42 35 26 91 2d a0 37 ec bc c7 bd
+82 2c 30 1d d6 7c 37 3b cc b5 84 ad 3e 92 79 c2
+e6 d1 2a 13 74 b7 7f 07 75 53 df 82 94 10 44 6b
+36 eb d9 70 66 29 6a e6 42 7e a7 5c 2e 08 46 a1
+1a 09 cc f5 37 0d c8 0b fe cb ad 28 c7 3f 09 b3
+a3 b7 5e 66 2a 25 94 41 0a e4 96 b2 e2 e6 60 9e
+31 e6 e0 2c c8 37 f0 53 d2 1f 37 ff 4f 51 95 0b
+be 26 38 d0 9d d7 a4 93 09 30 80 6d 07 03 b1 f6
+4d d3 b4 c0 88 a7 f4 5c 21 68 39 64 5b 20 12 bf
+2e 62 69 a8 c5 6a 81 6d bc 1b 26 77 61 95 5b c5
+},
+	'AEAD_AES_*_CBC_HMAC_SHA_* test cases');
+
+done_testing();
diff --git a/src/interfaces/libpq/test/.gitignore b/src/interfaces/libpq/test/.gitignore
index 6ba78adb67..1846594ec5 100644
--- a/src/interfaces/libpq/test/.gitignore
+++ b/src/interfaces/libpq/test/.gitignore
@@ -1,2 +1,3 @@
+/libpq_test_encrypt
 /libpq_testclient
 /libpq_uri_regress
diff --git a/src/interfaces/libpq/test/Makefile b/src/interfaces/libpq/test/Makefile
index 75ac08f943..b1ebab90d4 100644
--- a/src/interfaces/libpq/test/Makefile
+++ b/src/interfaces/libpq/test/Makefile
@@ -15,10 +15,17 @@ override CPPFLAGS := -I$(libpq_srcdir) $(CPPFLAGS)
 LDFLAGS_INTERNAL += $(libpq_pgport)
 
 PROGS = libpq_testclient libpq_uri_regress
+ifeq ($(with_ssl),openssl)
+PROGS += libpq_test_encrypt
+endif
+
 
 all: $(PROGS)
 
 $(PROGS): $(WIN32RES)
 
+libpq_test_encrypt: ../fe-encrypt-openssl.c
+	$(CC) $(CPPFLAGS) -DTEST_ENCRYPT $(CFLAGS) $^ $(LDFLAGS) $(LDFLAGS_EX) $(LIBS) -o $@$(X)
+
 clean distclean maintainer-clean:
 	rm -f $(PROGS) *.o
diff --git a/src/interfaces/libpq/test/meson.build b/src/interfaces/libpq/test/meson.build
index b2a4b06fd2..87d2808b52 100644
--- a/src/interfaces/libpq/test/meson.build
+++ b/src/interfaces/libpq/test/meson.build
@@ -36,3 +36,26 @@ executable('libpq_testclient',
     'install': false,
   }
 )
+
+
+libpq_test_encrypt_sources = files(
+  '../fe-encrypt-openssl.c',
+)
+
+if host_system == 'windows'
+  libpq_test_encrypt_sources += rc_bin_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'libpq_test_encrypt',
+    '--FILEDESC', 'libpq test program',])
+endif
+
+if ssl.found()
+  executable('libpq_test_encrypt',
+    libpq_test_encrypt_sources,
+    include_directories: include_directories('../../../port'),
+    dependencies: [frontend_code, libpq, ssl],
+    c_args: ['-DTEST_ENCRYPT'],
+    kwargs: default_bin_args + {
+      'install': false,
+    }
+  )
+endif
diff --git a/src/test/Makefile b/src/test/Makefile
index dbd3192874..c8ba170503 100644
--- a/src/test/Makefile
+++ b/src/test/Makefile
@@ -24,7 +24,7 @@ ifeq ($(with_ldap),yes)
 SUBDIRS += ldap
 endif
 ifeq ($(with_ssl),openssl)
-SUBDIRS += ssl
+SUBDIRS += column_encryption ssl
 endif
 
 # Test suites that are not safe by default but can be run if selected
@@ -36,7 +36,7 @@ export PG_TEST_EXTRA
 # 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 icu ldap ssl)
+ALWAYS_SUBDIRS = $(filter-out $(SUBDIRS),examples kerberos icu 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..d5ead874e5
--- /dev/null
+++ b/src/test/column_encryption/Makefile
@@ -0,0 +1,31 @@
+#-------------------------------------------------------------------------
+#
+# Makefile for src/test/column_encryption
+#
+# Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
+# Portions Copyright (c) 1994, Regents of the University of California
+#
+# src/test/column_encryption/Makefile
+#
+#-------------------------------------------------------------------------
+
+subdir = src/test/column_encryption
+top_builddir = ../../..
+include $(top_builddir)/src/Makefile.global
+
+export OPENSSL PERL
+
+override CPPFLAGS := -I$(libpq_srcdir) $(CPPFLAGS)
+LDFLAGS_INTERNAL += -L$(top_builddir)/src/fe_utils -lpgfeutils $(libpq_pgport)
+
+all: test_client
+
+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/meson.build b/src/test/column_encryption/meson.build
new file mode 100644
index 0000000000..47f88c41ce
--- /dev/null
+++ b/src/test/column_encryption/meson.build
@@ -0,0 +1,24 @@
+column_encryption_test_client = executable('test_client',
+  files('test_client.c'),
+  dependencies: [frontend_code, libpq],
+  kwargs: default_bin_args + {
+    'install': false,
+  },
+)
+testprep_targets += column_encryption_test_client
+
+tests += {
+  'name': 'column_encryption',
+  'sd': meson.current_source_dir(),
+  'bd': meson.current_build_dir(),
+  'tap': {
+    'tests': [
+      't/001_column_encryption.pl',
+      't/002_cmk_rotation.pl',
+    ],
+    'env': {
+      'OPENSSL': openssl.path(),
+      'PERL': perl.path(),
+    },
+  },
+}
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..c56af737ac
--- /dev/null
+++ b/src/test/column_encryption/t/001_column_encryption.pl
@@ -0,0 +1,255 @@
+# Copyright (c) 2021-2023, PostgreSQL Global Development Group
+
+use strict;
+use warnings;
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More;
+
+my $openssl = $ENV{OPENSSL};
+my $perl = $ENV{PERL};
+
+# Can be changed manually for testing other algorithms.  Note that
+# RSAES_OAEP_SHA_256 requires OpenSSL 1.1.0.
+my $cmkalg = 'RSAES_OAEP_SHA_1';
+
+my $node = PostgreSQL::Test::Cluster->new('node');
+$node->init;
+$node->start;
+
+
+sub create_cmk
+{
+	my ($cmkname) = @_;
+	my $cmkfilename = "${PostgreSQL::Test::Utils::tmp_check}/${cmkname}.pem";
+	system_or_bail $openssl, 'genpkey', '-algorithm', 'rsa', '-out', $cmkfilename;
+	$node->safe_psql('postgres', qq{CREATE COLUMN MASTER KEY ${cmkname}});
+	return $cmkfilename;
+}
+
+sub create_cek
+{
+	my ($cekname, $bytes, $cmkname, $cmkfilename) = @_;
+
+	my $digest = $cmkalg;
+	$digest =~ s/.*(?=SHA)//;
+	$digest =~ s/_//g;
+
+	# generate random bytes
+	system_or_bail $openssl, 'rand', '-out', "${PostgreSQL::Test::Utils::tmp_check}/${cekname}.bin", $bytes;
+
+	# encrypt CEK using CMK
+	my @cmd = (
+		$openssl, 'pkeyutl', '-encrypt',
+		'-inkey', $cmkfilename,
+		'-pkeyopt', 'rsa_padding_mode:oaep',
+		'-in', "${PostgreSQL::Test::Utils::tmp_check}/${cekname}.bin",
+		'-out', "${PostgreSQL::Test::Utils::tmp_check}/${cekname}.bin.enc"
+	);
+	if ($digest ne 'SHA1')
+	{
+		# These options require OpenSSL >=1.1.0, so if the digest is
+		# SHA1, which is the default, omit the options.
+		push @cmd,
+		  '-pkeyopt', "rsa_mgf1_md:$digest",
+		  '-pkeyopt', "rsa_oaep_md:$digest";
+	}
+	system_or_bail @cmd;
+
+	my $cekenchex = unpack('H*', slurp_file "${PostgreSQL::Test::Utils::tmp_check}/${cekname}.bin.enc");
+
+	# create CEK in database
+	$node->safe_psql('postgres', qq{CREATE COLUMN ENCRYPTION KEY ${cekname} WITH VALUES (column_master_key = ${cmkname}, algorithm = '${cmkalg}', encrypted_value = '\\x${cekenchex}');});
+
+	return;
+}
+
+
+my $cmk1filename = create_cmk('cmk1');
+my $cmk2filename = create_cmk('cmk2');
+create_cek('cek1', 48, 'cmk1', $cmk1filename);
+create_cek('cek2', 72, 'cmk2', $cmk2filename);
+
+$ENV{PGCOLUMNENCRYPTION} = 'on';
+$ENV{PGCMKLOOKUP} = '*=file:' . ${PostgreSQL::Test::Utils::tmp_check} . '/%k.pem';
+
+
+$node->safe_psql('postgres', qq{
+CREATE TABLE tbl1 (
+    a int,
+    b text ENCRYPTED WITH (column_encryption_key = cek1),
+    c smallint ENCRYPTED WITH (column_encryption_key = cek1)
+);
+});
+
+$node->safe_psql('postgres', q{
+INSERT INTO tbl1 (a, b, c) VALUES (1, $1, $2) \bind 'val1' 11 \g
+INSERT INTO tbl1 (a, b, c) VALUES (2, $1, $2) \bind 'val2' 22 \g
+});
+
+# Expected ciphertext length is 2 blocks of AES output (2 * 16) plus
+# half SHA-256 output (16) in hex encoding: (2 * 16 + 16) * 2 = 96
+like($node->safe_psql('postgres', q{COPY (SELECT * FROM tbl1) TO STDOUT}),
+	qr/1\tencrypted\$[0-9a-f]{96}\tencrypted\$[0-9a-f]{96}\n2\tencrypted\$[0-9a-f]{96}\tencrypted\$[0-9a-f]{96}/,
+	'inserted data is encrypted');
+
+my $result;
+
+$result = $node->safe_psql('postgres', q{SELECT a, b, c FROM tbl1 \gdesc});
+is($result,
+	q(a|integer
+b|text
+c|smallint),
+	'query result description has original type');
+
+$result = $node->safe_psql('postgres', q{SELECT a, b, c FROM tbl1});
+is($result,
+	q(1|val1|11
+2|val2|22),
+	'decrypted query result');
+
+{
+	local $ENV{PGCMKLOOKUP} = '*=run:broken %k %p';
+	$result = $node->psql('postgres', q{SELECT a, b, c FROM tbl1});
+	isnt($result, 0, 'query fails with broken cmklookup run setting');
+}
+
+{
+	local $ENV{TESTWORKDIR} = ${PostgreSQL::Test::Utils::tmp_check};
+	local $ENV{PGCMKLOOKUP} = "*=run:$perl ./test_run_decrypt.pl %k %a %p";
+
+	my $stdout;
+	$result = $node->psql('postgres', q{SELECT a, b, c FROM tbl1}, stdout => \$stdout);
+	is($stdout,
+		q(1|val1|11
+2|val2|22),
+		'decrypted query result with cmklookup run');
+}
+
+
+$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, c FROM tbl1});
+is($result,
+	q(1|val1|11
+2|val2|22),
+	'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, c FROM tbl1});
+is($result,
+	q(1|val1|11
+2|val2|22
+3|val3|33),
+	'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\tencrypted\$[0-9a-f]{96}/,
+	'inserted data is encrypted');
+
+
+# Tests with binary format
+
+# Supplying a parameter in binary format when the parameter is to be
+# encrypted results in an error from libpq.
+$node->command_fails_like(['test_client', 'test3'],
+	qr/format must be text for encrypted parameter/,
+	'test client fails because to-be-encrypted parameter is in binary format');
+
+# Requesting a binary result set still causes any encrypted columns to
+# be returned as text from the libpq API.
+$node->command_like(['test_client', 'test4'],
+	qr/<0,0>=1:\n<0,1>=0:val1\n<0,2>=0:11/,
+	'binary result set with encrypted columns: encrypted columns returned as text');
+
+
+# Test UPDATE
+
+$node->safe_psql('postgres', q{
+UPDATE tbl1 SET b = $2 WHERE a = $1 \bind '3' 'val3upd' \g
+});
+
+$result = $node->safe_psql('postgres', q{SELECT a, b, c FROM tbl1});
+is($result,
+	q(1|val1|11
+2|val2|22
+3|val3upd|33),
+	'decrypted query result after update');
+
+
+# Test views
+
+$node->safe_psql('postgres', q{CREATE VIEW v1 AS SELECT a, b, c FROM tbl1});
+
+$node->safe_psql('postgres', q{UPDATE v1 SET b = $2 WHERE a = $1 \bind '3' 'val3upd2' \g});
+
+$result = $node->safe_psql('postgres', q{SELECT a, b, c FROM v1 WHERE a IN (1, 3)});
+is($result,
+	q(1|val1|11
+3|val3upd2|33),
+	'decrypted query result from view');
+
+
+# Test deterministic encryption
+
+$node->safe_psql('postgres', qq{
+CREATE TABLE tbl2 (
+    a int,
+    b text ENCRYPTED WITH (encryption_type = deterministic, column_encryption_key = cek1)
+);
+});
+
+$node->safe_psql('postgres', q{
+INSERT INTO tbl2 (a, b) VALUES ($1, $2), ($3, $4), ($5, $6) \bind '1' 'valA' '2' 'valB' '3' 'valA' \g
+});
+
+$result = $node->safe_psql('postgres', q{SELECT a, b FROM tbl2});
+is($result,
+	q(1|valA
+2|valB
+3|valA),
+	'decrypted query result in table for deterministic encryption');
+
+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');
+
+is($node->safe_psql('postgres', q{SELECT a FROM tbl2 WHERE b = $1 \bind 'valB' \g}),
+	q(2),
+	'select by deterministically encrypted column');
+
+
+# Test multiple keys in one table
+
+$node->safe_psql('postgres', qq{
+CREATE TABLE tbl3 (
+    a int,
+    b text ENCRYPTED WITH (column_encryption_key = cek1),
+    c text ENCRYPTED WITH (column_encryption_key = cek2, algorithm = 'AEAD_AES_192_CBC_HMAC_SHA_384')
+);
+});
+
+$node->safe_psql('postgres', q{
+INSERT INTO tbl3 (a, b, c) VALUES (1, $1, $2) \bind 'valB1' 'valC1' \g
+});
+
+$result = $node->safe_psql('postgres', q{SELECT a, b, c FROM tbl3});
+is($result,
+	q(1|valB1|valC1),
+	'decrypted query result multiple keys');
+
+$node->safe_psql('postgres', q{
+INSERT INTO tbl3 (a, b, c) VALUES ($1, $2, $3), ($4, $5, $6) \bind '2' 'valB2' 'valC2' '3' 'valB3' 'valC3' \g
+});
+
+$result = $node->safe_psql('postgres', q{SELECT a, b, c FROM tbl3});
+is($result,
+	q(1|valB1|valC1
+2|valB2|valC2
+3|valB3|valC3),
+	'decrypted query result multiple keys after second insert');
+
+
+done_testing();
diff --git a/src/test/column_encryption/t/002_cmk_rotation.pl b/src/test/column_encryption/t/002_cmk_rotation.pl
new file mode 100644
index 0000000000..14eafb8ec9
--- /dev/null
+++ b/src/test/column_encryption/t/002_cmk_rotation.pl
@@ -0,0 +1,112 @@
+# Copyright (c) 2021-2023, PostgreSQL Global Development Group
+
+# Test column master key rotation.  First, we generate CMK1 and a CEK
+# encrypted with it.  Then we add a CMK2 and encrypt the CEK with it
+# as well.  (Recall that a CEK can be associated with multiple CMKs,
+# for this reason.  That's why pg_colenckeydata is split out from
+# pg_colenckey.)  Then we remove CMK1.  We test that we can get
+# decrypted query results at each step.
+
+use strict;
+use warnings;
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More;
+
+my $openssl = $ENV{OPENSSL};
+
+my $cmkalg = 'RSAES_OAEP_SHA_1';
+
+my $node = PostgreSQL::Test::Cluster->new('node');
+$node->init;
+$node->start;
+
+
+sub create_cmk
+{
+	my ($cmkname) = @_;
+	my $cmkfilename = "${PostgreSQL::Test::Utils::tmp_check}/${cmkname}.pem";
+	system_or_bail $openssl, 'genpkey', '-algorithm', 'rsa', '-out', $cmkfilename;
+	$node->safe_psql('postgres', qq{CREATE COLUMN MASTER KEY ${cmkname}});
+	return $cmkfilename;
+}
+
+
+my $cmk1filename = create_cmk('cmk1');
+
+# create CEK
+my ($cekname, $bytes) = ('cek1', 48);
+
+# generate random bytes
+system_or_bail $openssl, 'rand', '-out', "${PostgreSQL::Test::Utils::tmp_check}/${cekname}.bin", $bytes;
+
+# encrypt CEK using CMK
+system_or_bail $openssl, 'pkeyutl', '-encrypt',
+  '-inkey', $cmk1filename,
+  '-pkeyopt', 'rsa_padding_mode:oaep',
+  '-in', "${PostgreSQL::Test::Utils::tmp_check}/${cekname}.bin",
+  '-out', "${PostgreSQL::Test::Utils::tmp_check}/${cekname}.bin.enc";
+
+my $cekenchex = unpack('H*', slurp_file "${PostgreSQL::Test::Utils::tmp_check}/${cekname}.bin.enc");
+
+# create CEK in database
+$node->safe_psql('postgres', qq{CREATE COLUMN ENCRYPTION KEY ${cekname} WITH VALUES (column_master_key = cmk1, algorithm = '$cmkalg', encrypted_value = '\\x${cekenchex}');});
+
+$ENV{PGCOLUMNENCRYPTION} = 'on';
+$ENV{PGCMKLOOKUP} = '*=file:' . ${PostgreSQL::Test::Utils::tmp_check} . '/%k.pem';
+
+$node->safe_psql('postgres', qq{
+CREATE TABLE tbl1 (
+    a int,
+    b text ENCRYPTED WITH (column_encryption_key = cek1)
+);
+});
+
+$node->safe_psql('postgres', q{
+INSERT INTO tbl1 (a, b) VALUES (1, $1) \bind 'val1' \g
+INSERT INTO tbl1 (a, b) VALUES (2, $1) \bind 'val2' \g
+});
+
+is($node->safe_psql('postgres', q{SELECT a, b FROM tbl1}),
+	q(1|val1
+2|val2),
+	'decrypted query result with one CMK');
+
+
+# create new CMK
+my $cmk2filename = create_cmk('cmk2');
+
+# encrypt CEK using new CMK
+#
+# (Here, we still have the plaintext of the CEK available from
+# earlier.  In reality, one would decrypt the CEK with the first CMK
+# and then re-encrypt it with the second CMK.)
+system_or_bail $openssl, 'pkeyutl', '-encrypt',
+  '-inkey', $cmk2filename,
+  '-pkeyopt', 'rsa_padding_mode:oaep',
+  '-in', "${PostgreSQL::Test::Utils::tmp_check}/${cekname}.bin",
+  '-out', "${PostgreSQL::Test::Utils::tmp_check}/${cekname}.bin.enc";
+
+$cekenchex = unpack('H*', slurp_file "${PostgreSQL::Test::Utils::tmp_check}/${cekname}.bin.enc");
+
+# add new data record for CEK in database
+$node->safe_psql('postgres', qq{ALTER COLUMN ENCRYPTION KEY ${cekname} ADD VALUE (column_master_key = cmk2, algorithm = '$cmkalg', encrypted_value = '\\x${cekenchex}');});
+
+
+is($node->safe_psql('postgres', q{SELECT a, b FROM tbl1}),
+	q(1|val1
+2|val2),
+	'decrypted query result with two CMKs');
+
+
+# delete CEK record for first CMK
+$node->safe_psql('postgres', qq{ALTER COLUMN ENCRYPTION KEY ${cekname} DROP VALUE (column_master_key = cmk1);});
+
+
+is($node->safe_psql('postgres', q{SELECT a, b FROM tbl1}),
+	q(1|val1
+2|val2),
+	'decrypted query result with only new CMK');
+
+
+done_testing();
diff --git a/src/test/column_encryption/test_client.c b/src/test/column_encryption/test_client.c
new file mode 100644
index 0000000000..9c257a3ddb
--- /dev/null
+++ b/src/test/column_encryption/test_client.c
@@ -0,0 +1,161 @@
+/*
+ * Copyright (c) 2021-2023, PostgreSQL Global Development Group
+ */
+
+#include "postgres_fe.h"
+
+#include "libpq-fe.h"
+
+
+/*
+ * Test calls that don't support encryption
+ */
+static int
+test1(PGconn *conn)
+{
+	PGresult   *res;
+	const char *values[] = {"3", "val3", "33"};
+
+	res = PQprepare(conn, "", "INSERT INTO tbl1 (a, b, c) VALUES ($1, $2, $3)",
+					3, NULL);
+	if (PQresultStatus(res) != PGRES_COMMAND_OK)
+	{
+		fprintf(stderr, "PQprepare() failed: %s\n",
+				PQerrorMessage(conn));
+		return 1;
+	}
+
+	res = PQexecPrepared(conn, "", 3, values, NULL, NULL, 0);
+	if (PQresultStatus(res) != PGRES_COMMAND_OK)
+	{
+		fprintf(stderr, "PQexecPrepared() failed: %s\n",
+				PQerrorMessage(conn));
+		return 1;
+	}
+
+	return 0;
+}
+
+/*
+ * Test forced encryption
+ */
+static int
+test2(PGconn *conn)
+{
+	PGresult   *res,
+			   *res2;
+	const char *values[] = {"3", "val3", "33"};
+	int			formats[] = {0x00, 0x10, 0x00};
+
+	res = PQprepare(conn, "", "INSERT INTO tbl1 (a, b, c) VALUES ($1, $2, $3)",
+					3, 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;
+	}
+
+	if (!(!PQparamisencrypted(res2, 0) &&
+		  PQparamisencrypted(res2, 1)))
+	{
+		fprintf(stderr, "wrong results from PQparamisencrypted()\n");
+		return 1;
+	}
+
+	res = PQexecPreparedDescribed(conn, "", 3, values, NULL, formats, 0, res2);
+	if (PQresultStatus(res) != PGRES_COMMAND_OK)
+	{
+		fprintf(stderr, "PQexecPrepared() failed: %s\n",
+				PQerrorMessage(conn));
+		return 1;
+	}
+
+	return 0;
+}
+
+/*
+ * Test what happens when you supply a binary parameter that is required to be
+ * encrypted.
+ */
+static int
+test3(PGconn *conn)
+{
+	PGresult   *res;
+	const char *values[] = {""};
+	int			lengths[] = {1};
+	int			formats[] = {1};
+
+	res = PQexecParams(conn, "INSERT INTO tbl1 (a, b, c) VALUES (100, NULL, $1)",
+					   3, NULL, values, lengths, formats, 0);
+	if (PQresultStatus(res) != PGRES_COMMAND_OK)
+	{
+		fprintf(stderr, "PQexecParams() failed: %s\n",
+				PQerrorMessage(conn));
+		return 1;
+	}
+
+	return 0;
+}
+
+/*
+ * Test what happens when you request results in binary and the result rows
+ * contain an encrypted column.
+ */
+static int
+test4(PGconn *conn)
+{
+	PGresult   *res;
+
+	res = PQexecParams(conn, "SELECT a, b, c FROM tbl1 WHERE a = 1", 0, NULL, NULL, NULL, NULL, 1);
+	if (PQresultStatus(res) != PGRES_TUPLES_OK)
+	{
+		fprintf(stderr, "PQexecParams() failed: %s\n",
+				PQerrorMessage(conn));
+		return 1;
+	}
+
+	for (int row = 0; row < PQntuples(res); row++)
+		for (int col = 0; col < PQnfields(res); col++)
+			printf("<%d,%d>=%d:%s\n", row, col, PQfformat(res, col), PQgetvalue(res, row, col));
+	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/column_encryption/test_run_decrypt.pl b/src/test/column_encryption/test_run_decrypt.pl
new file mode 100755
index 0000000000..66871cb438
--- /dev/null
+++ b/src/test/column_encryption/test_run_decrypt.pl
@@ -0,0 +1,58 @@
+#!/usr/bin/perl
+
+# Test/sample command for libpq cmklookup run scheme
+#
+# This just places the data into temporary files and runs the openssl
+# command on it.  (In practice, this could more simply be written as a
+# shell script, but this way it's more portable.)
+
+# Copyright (c) 2021-2023, PostgreSQL Global Development Group
+
+use strict;
+use warnings;
+
+my ($cmkname, $alg, $filename) = @ARGV;
+
+die unless $alg =~ 'RSAES_OAEP_SHA';
+
+my $digest = $alg;
+$digest =~ s/.*(?=SHA)//;
+$digest =~ s/_//g;
+
+my $tmpdir = $ENV{TESTWORKDIR};
+
+my $openssl = $ENV{OPENSSL};
+
+my @cmd = (
+	$openssl, 'pkeyutl', '-decrypt',
+	'-inkey', "${tmpdir}/${cmkname}.pem", '-pkeyopt', 'rsa_padding_mode:oaep',
+	'-in', $filename, '-out', "${tmpdir}/output.tmp"
+);
+
+if ($digest ne 'SHA1')
+{
+	# These options require OpenSSL >=1.1.0, so if the digest is
+	# SHA1, which is the default, omit the options.
+	push @cmd,
+	  '-pkeyopt', "rsa_mgf1_md:$digest",
+	  '-pkeyopt', "rsa_oaep_md:$digest";
+}
+
+system(@cmd) == 0 or die "system failed: $?";
+
+open my $fh, '<:raw', "${tmpdir}/output.tmp" or die $!;
+my $data = '';
+
+while (1) {
+	my $success = read $fh, $data, 100, length($data);
+	die $! if not defined $success;
+	last if not $success;
+}
+
+close $fh;
+
+unlink "${tmpdir}/output.tmp";
+
+binmode STDOUT;
+
+print $data;
diff --git a/src/test/meson.build b/src/test/meson.build
index 5f3c9c2ba2..d55ddb1ab7 100644
--- a/src/test/meson.build
+++ b/src/test/meson.build
@@ -9,6 +9,7 @@ subdir('subscription')
 subdir('modules')
 
 if ssl.found()
+  subdir('column_encryption')
   subdir('ssl')
 endif
 
diff --git a/src/test/regress/expected/column_encryption.out b/src/test/regress/expected/column_encryption.out
new file mode 100644
index 0000000000..354f0bb1e3
--- /dev/null
+++ b/src/test/regress/expected/column_encryption.out
@@ -0,0 +1,322 @@
+\set HIDE_COLUMN_ENCRYPTION false
+CREATE ROLE regress_enc_user1;
+CREATE COLUMN MASTER KEY cmk1 WITH (
+    realm = 'test'
+);
+COMMENT ON COLUMN MASTER KEY cmk1 IS 'column master key';
+CREATE COLUMN MASTER KEY cmk1a WITH (
+    realm = 'test'
+);
+CREATE COLUMN MASTER KEY cmk2;
+CREATE COLUMN MASTER KEY cmk2a WITH (
+    realm = 'testx'
+);
+ALTER COLUMN MASTER KEY cmk2a (realm = 'test2');
+CREATE COLUMN ENCRYPTION KEY fail WITH VALUES (
+    column_master_key = cmk1,
+    algorithm = 'foo',  -- invalid
+    encrypted_value = '\xDEADBEEF'
+);
+ERROR:  unrecognized encryption algorithm: foo
+CREATE COLUMN ENCRYPTION KEY cek1 WITH VALUES (
+    column_master_key = cmk1,
+    algorithm = 'RSAES_OAEP_SHA_1',
+    encrypted_value = '\xDEADBEEF'
+);
+COMMENT ON COLUMN ENCRYPTION KEY cek1 IS 'column encryption key';
+ALTER COLUMN ENCRYPTION KEY cek1 ADD VALUE (
+    column_master_key = cmk1a,
+    algorithm = 'RSAES_OAEP_SHA_1',
+    encrypted_value = '\xDEADBEEF'
+);
+-- duplicate
+ALTER COLUMN ENCRYPTION KEY cek1 ADD VALUE (
+    column_master_key = cmk1a,
+    algorithm = 'RSAES_OAEP_SHA_1',
+    encrypted_value = '\xDEADBEEF'
+);
+ERROR:  column encryption key "cek1" already has data for master key "cmk1a"
+ALTER COLUMN ENCRYPTION KEY fail ADD VALUE (
+    column_master_key = cmk1a,
+    algorithm = 'RSAES_OAEP_SHA_1',
+    encrypted_value = '\xDEADBEEF'
+);
+ERROR:  column encryption key "fail" does not exist
+CREATE COLUMN ENCRYPTION KEY cek2 WITH VALUES (
+    column_master_key = cmk2,
+    algorithm = 'RSAES_OAEP_SHA_1',
+    encrypted_value = '\xDEADBEEF'
+),
+(
+    column_master_key = cmk2a,
+    algorithm = 'RSAES_OAEP_SHA_1',
+    encrypted_value = '\xDEADBEEF'
+);
+CREATE COLUMN ENCRYPTION KEY cek4 WITH VALUES (
+    column_master_key = cmk1,
+    algorithm = 'RSAES_OAEP_SHA_1',
+    encrypted_value = '\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_fail (
+    a int,
+    b text,
+    c text ENCRYPTED WITH (column_encryption_key = cek1, algorithm = 'foo')
+);
+ERROR:  unrecognized encryption algorithm: foo
+CREATE TABLE tbl_fail (
+    a int,
+    b text,
+    c text ENCRYPTED WITH (column_encryption_key = cek1, encryption_type = wrong)
+);
+ERROR:  unrecognized encryption type: wrong
+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      | text    |           |          | 
+
+\d+ tbl_29f3
+                                        Table "public.tbl_29f3"
+ Column |  Type   | Collation | Nullable | Default | Storage  | Encryption | Stats target | Description 
+--------+---------+-----------+----------+---------+----------+------------+--------------+-------------
+ a      | integer |           |          |         | plain    |            |              | 
+ b      | text    |           |          |         | extended |            |              | 
+ c      | text    |           |          |         | external | cek1       |              | 
+
+CREATE TABLE tbl_447f (
+    a int,
+    b text
+);
+ALTER TABLE tbl_447f ADD COLUMN c text ENCRYPTED WITH (column_encryption_key = cek1);
+\d tbl_447f
+              Table "public.tbl_447f"
+ Column |  Type   | Collation | Nullable | Default 
+--------+---------+-----------+----------+---------
+ a      | integer |           |          | 
+ b      | text    |           |          | 
+ c      | text    |           |          | 
+
+\d+ tbl_447f
+                                        Table "public.tbl_447f"
+ Column |  Type   | Collation | Nullable | Default | Storage  | Encryption | Stats target | Description 
+--------+---------+-----------+----------+---------+----------+------------+--------------+-------------
+ a      | integer |           |          |         | plain    |            |              | 
+ b      | text    |           |          |         | extended |            |              | 
+ c      | text    |           |          |         | external | cek1       |              | 
+
+CREATE TABLE tbl_4897 (LIKE tbl_447f);
+CREATE TABLE tbl_6978 (LIKE tbl_447f INCLUDING ENCRYPTED);
+\d+ tbl_4897
+                                        Table "public.tbl_4897"
+ Column |  Type   | Collation | Nullable | Default | Storage  | Encryption | Stats target | Description 
+--------+---------+-----------+----------+---------+----------+------------+--------------+-------------
+ a      | integer |           |          |         | plain    |            |              | 
+ b      | text    |           |          |         | extended |            |              | 
+ c      | text    |           |          |         | extended |            |              | 
+
+\d+ tbl_6978
+                                        Table "public.tbl_6978"
+ Column |  Type   | Collation | Nullable | Default | Storage  | Encryption | Stats target | Description 
+--------+---------+-----------+----------+---------+----------+------------+--------------+-------------
+ a      | integer |           |          |         | plain    |            |              | 
+ b      | text    |           |          |         | extended |            |              | 
+ c      | text    |           |          |         | external | cek1       |              | 
+
+CREATE VIEW view_3bc9 AS SELECT * FROM tbl_29f3;
+\d+ view_3bc9
+                                 View "public.view_3bc9"
+ Column |  Type   | Collation | Nullable | Default | Storage  | Encryption | Description 
+--------+---------+-----------+----------+---------+----------+------------+-------------
+ a      | integer |           |          |         | plain    |            | 
+ b      | text    |           |          |         | extended |            | 
+ c      | text    |           |          |         | external | cek1       | 
+View definition:
+ SELECT a,
+    b,
+    c
+   FROM tbl_29f3;
+
+CREATE TABLE tbl_2386 AS SELECT * FROM tbl_29f3 WITH NO DATA;
+\d+ tbl_2386
+                                        Table "public.tbl_2386"
+ Column |  Type   | Collation | Nullable | Default | Storage  | Encryption | Stats target | Description 
+--------+---------+-----------+----------+---------+----------+------------+--------------+-------------
+ a      | integer |           |          |         | plain    |            |              | 
+ b      | text    |           |          |         | extended |            |              | 
+ c      | text    |           |          |         | external | cek1       |              | 
+
+CREATE TABLE tbl_2941 AS SELECT * FROM tbl_29f3 WITH DATA;
+ERROR:  encrypted columns not yet implemented for this command
+\d+ tbl_2941
+-- test partition declarations
+CREATE TABLE tbl_13fa (
+    a int,
+    b text ENCRYPTED WITH (column_encryption_key = cek1)
+) PARTITION BY RANGE (a);
+CREATE TABLE tbl_13fa_1 PARTITION OF tbl_13fa FOR VALUES FROM (1) TO (100);
+\d+ tbl_13fa
+                                  Partitioned table "public.tbl_13fa"
+ Column |  Type   | Collation | Nullable | Default | Storage  | Encryption | Stats target | Description 
+--------+---------+-----------+----------+---------+----------+------------+--------------+-------------
+ a      | integer |           |          |         | plain    |            |              | 
+ b      | text    |           |          |         | external | cek1       |              | 
+Partition key: RANGE (a)
+Partitions: tbl_13fa_1 FOR VALUES FROM (1) TO (100)
+
+\d+ tbl_13fa_1
+                                       Table "public.tbl_13fa_1"
+ Column |  Type   | Collation | Nullable | Default | Storage  | Encryption | Stats target | Description 
+--------+---------+-----------+----------+---------+----------+------------+--------------+-------------
+ a      | integer |           |          |         | plain    |            |              | 
+ b      | text    |           |          |         | external | cek1       |              | 
+Partition of: tbl_13fa FOR VALUES FROM (1) TO (100)
+Partition constraint: ((a IS NOT NULL) AND (a >= 1) AND (a < 100))
+
+-- test inheritance
+CREATE TABLE tbl_36f3_a (
+    a int,
+    b text ENCRYPTED WITH (column_encryption_key = cek1)
+);
+CREATE TABLE tbl_36f3_b (
+    a int,
+    b text ENCRYPTED WITH (column_encryption_key = cek1)
+);
+CREATE TABLE tbl_36f3_c (
+    a int,
+    b text ENCRYPTED WITH (column_encryption_key = cek2)
+);
+CREATE TABLE tbl_36f3_d (
+    a int,
+    b text ENCRYPTED WITH (column_encryption_key = cek1, encryption_type = deterministic)
+);
+CREATE TABLE tbl_36f3_e (
+    a int,
+    b text
+);
+-- not implemented (but could be ok)
+CREATE TABLE tbl_36f3_ab (c int) INHERITS (tbl_36f3_a, tbl_36f3_b);
+NOTICE:  merging multiple inherited definitions of column "a"
+NOTICE:  merging multiple inherited definitions of column "b"
+ERROR:  multiple inheritance of encrypted columns is not implemented
+\d+ tbl_36f3_ab
+-- not implemented (but should fail)
+CREATE TABLE tbl_36f3_ac (c int) INHERITS (tbl_36f3_a, tbl_36f3_c);
+NOTICE:  merging multiple inherited definitions of column "a"
+NOTICE:  merging multiple inherited definitions of column "b"
+ERROR:  multiple inheritance of encrypted columns is not implemented
+CREATE TABLE tbl_36f3_ad (c int) INHERITS (tbl_36f3_a, tbl_36f3_d);
+NOTICE:  merging multiple inherited definitions of column "a"
+NOTICE:  merging multiple inherited definitions of column "b"
+ERROR:  multiple inheritance of encrypted columns is not implemented
+-- fail
+CREATE TABLE tbl_36f3_ae (c int) INHERITS (tbl_36f3_a, tbl_36f3_e);
+NOTICE:  merging multiple inherited definitions of column "a"
+NOTICE:  merging multiple inherited definitions of column "b"
+ERROR:  column "b" has an encryption specification conflict
+-- ok
+CREATE TABLE tbl_36f3_a_1 (b text ENCRYPTED WITH (column_encryption_key = cek1), c int) INHERITS (tbl_36f3_a);
+NOTICE:  moving and merging column "b" with inherited definition
+DETAIL:  User-specified column moved to the position of the inherited column.
+\d+ tbl_36f3_a_1
+                                      Table "public.tbl_36f3_a_1"
+ Column |  Type   | Collation | Nullable | Default | Storage  | Encryption | Stats target | Description 
+--------+---------+-----------+----------+---------+----------+------------+--------------+-------------
+ a      | integer |           |          |         | plain    |            |              | 
+ b      | text    |           |          |         | external | cek1       |              | 
+ c      | integer |           |          |         | plain    |            |              | 
+Inherits: tbl_36f3_a
+
+-- fail
+CREATE TABLE tbl_36f3_a_2 (b text ENCRYPTED WITH (column_encryption_key = cek2), c int) INHERITS (tbl_36f3_a);
+NOTICE:  moving and merging column "b" with inherited definition
+DETAIL:  User-specified column moved to the position of the inherited column.
+ERROR:  column "b" has an encryption specification conflict
+CREATE TABLE tbl_36f3_a_2 (b text ENCRYPTED WITH (column_encryption_key = cek1, encryption_type = deterministic), c int) INHERITS (tbl_36f3_a);
+NOTICE:  moving and merging column "b" with inherited definition
+DETAIL:  User-specified column moved to the position of the inherited column.
+ERROR:  column "b" has an encryption specification conflict
+CREATE TABLE tbl_36f3_a_2 (b text, c int) INHERITS (tbl_36f3_a);
+NOTICE:  moving and merging column "b" with inherited definition
+DETAIL:  User-specified column moved to the position of the inherited column.
+ERROR:  column "b" has an encryption specification conflict
+DROP TABLE tbl_36f3_b, tbl_36f3_c, tbl_36f3_d, tbl_36f3_e;
+-- SET SCHEMA
+CREATE SCHEMA test_schema_ce;
+ALTER COLUMN ENCRYPTION KEY cek1 SET SCHEMA test_schema_ce;
+ALTER COLUMN MASTER KEY cmk1 SET SCHEMA test_schema_ce;
+ALTER COLUMN ENCRYPTION KEY test_schema_ce.cek1 SET SCHEMA public;
+ALTER COLUMN MASTER KEY test_schema_ce.cmk1 SET SCHEMA public;
+DROP SCHEMA test_schema_ce;
+DROP COLUMN MASTER KEY cmk1 RESTRICT;  -- fail
+ERROR:  cannot drop column master key cmk1 because other objects depend on it
+DETAIL:  column encryption key data of column encryption key cek1 for column master key cmk1 depends on column master key cmk1
+column encryption key data of column encryption key cek4 for column master key cmk1 depends on column master key cmk1
+HINT:  Use DROP ... CASCADE to drop the dependent objects too.
+ALTER COLUMN MASTER KEY cmk2 RENAME TO cmk3;
+ALTER COLUMN MASTER KEY cmk1 RENAME TO cmk3;  -- fail
+ERROR:  column master key "cmk3" already exists in schema "public"
+ALTER COLUMN MASTER KEY cmkx RENAME TO cmky;  -- fail
+ERROR:  column master key "cmkx" does not exist
+ALTER COLUMN ENCRYPTION KEY cek2 RENAME TO cek3;
+ALTER COLUMN ENCRYPTION KEY cek1 RENAME TO cek3;  -- fail
+ERROR:  column encryption key "cek3" already exists in schema "public"
+ALTER COLUMN ENCRYPTION KEY cekx RENAME TO ceky;  -- fail
+ERROR:  column encryption key "cekx" does not exist
+SET SESSION AUTHORIZATION 'regress_enc_user1';
+DROP COLUMN ENCRYPTION KEY cek3;  -- fail
+ERROR:  must be owner of column encryption key cek3
+DROP COLUMN MASTER KEY cmk3;  -- fail
+ERROR:  must be owner of column master key cmk3
+RESET SESSION AUTHORIZATION;
+ALTER COLUMN MASTER KEY cmk3 OWNER TO regress_enc_user1;
+ALTER COLUMN ENCRYPTION KEY cek3 OWNER TO regress_enc_user1;
+\dcek cek3
+         List of column encryption keys
+ Schema | Name |       Owner       | Master key 
+--------+------+-------------------+------------
+ public | cek3 | regress_enc_user1 | cmk2a
+ public | cek3 | regress_enc_user1 | cmk3
+(2 rows)
+
+\dcmk cmk3
+        List of column master keys
+ Schema | Name |       Owner       | Realm 
+--------+------+-------------------+-------
+ public | cmk3 | regress_enc_user1 | 
+(1 row)
+
+SET SESSION AUTHORIZATION 'regress_enc_user1';
+DROP COLUMN ENCRYPTION KEY cek3;  -- ok now
+DROP COLUMN MASTER KEY cmk3;  -- ok now
+RESET SESSION AUTHORIZATION;
+ALTER COLUMN ENCRYPTION KEY cek1 DROP VALUE (column_master_key = cmk1a);
+ALTER COLUMN ENCRYPTION KEY cek1 DROP VALUE (column_master_key = cmk1a);  -- fail
+ERROR:  column encryption key "cek1" has no data for master key "cmk1a"
+ALTER COLUMN ENCRYPTION KEY cek1 DROP VALUE (column_master_key = fail);  -- fail
+ERROR:  column master key "fail" does not exist
+ALTER COLUMN ENCRYPTION KEY cek1 DROP VALUE (column_master_key = cmk1a, algorithm = 'foo');  -- fail
+ERROR:  attribute "algorithm" must not be specified
+DROP COLUMN ENCRYPTION KEY cek4;
+DROP COLUMN ENCRYPTION KEY fail;
+ERROR:  column encryption key "fail" does not exist
+DROP COLUMN ENCRYPTION KEY IF EXISTS nonexistent;
+NOTICE:  column encryption key "nonexistent" does not exist, skipping
+DROP COLUMN MASTER KEY cmk1a;
+DROP COLUMN MASTER KEY fail;
+ERROR:  column master key "fail" does not exist
+DROP COLUMN MASTER KEY IF EXISTS nonexistent;
+NOTICE:  column master key "nonexistent" does not exist, skipping
+DROP ROLE regress_enc_user1;
diff --git a/src/test/regress/expected/object_address.out b/src/test/regress/expected/object_address.out
index 25c174f275..08b76b6cc8 100644
--- a/src/test/regress/expected/object_address.out
+++ b/src/test/regress/expected/object_address.out
@@ -38,6 +38,8 @@ CREATE SERVER "integer" FOREIGN DATA WRAPPER addr_fdw;
 CREATE USER MAPPING FOR regress_addr_user SERVER "integer";
 ALTER DEFAULT PRIVILEGES FOR ROLE regress_addr_user IN SCHEMA public GRANT ALL ON TABLES TO regress_addr_user;
 ALTER DEFAULT PRIVILEGES FOR ROLE regress_addr_user REVOKE DELETE ON TABLES FROM regress_addr_user;
+CREATE COLUMN MASTER KEY addr_cmk;
+CREATE COLUMN ENCRYPTION KEY addr_cek WITH VALUES (column_master_key = addr_cmk, algorithm = 'RSAES_OAEP_SHA_1', encrypted_value = '');
 -- this transform would be quite unsafe to leave lying around,
 -- except that the SQL language pays no attention to transforms:
 CREATE TRANSFORM FOR int LANGUAGE SQL (
@@ -107,7 +109,8 @@ BEGIN
         ('text search template'), ('text search configuration'),
         ('policy'), ('user mapping'), ('default acl'), ('transform'),
         ('operator of access method'), ('function of access method'),
-        ('publication namespace'), ('publication relation')
+        ('publication namespace'), ('publication relation'),
+        ('column encryption key'), ('column encryption key data'), ('column master key')
     LOOP
         FOR names IN VALUES ('{eins}'), ('{addr_nsp, zwei}'), ('{eins, zwei, drei}')
         LOOP
@@ -327,6 +330,24 @@ WARNING:  error for publication relation,{addr_nsp,zwei},{}: argument list lengt
 WARNING:  error for publication relation,{addr_nsp,zwei},{integer}: relation "addr_nsp.zwei" does not exist
 WARNING:  error for publication relation,{eins,zwei,drei},{}: argument list length must be exactly 1
 WARNING:  error for publication relation,{eins,zwei,drei},{integer}: cross-database references are not implemented: "eins.zwei.drei"
+WARNING:  error for column encryption key,{eins},{}: column encryption key "eins" does not exist
+WARNING:  error for column encryption key,{eins},{integer}: column encryption key "eins" does not exist
+WARNING:  error for column encryption key,{addr_nsp,zwei},{}: column encryption key "addr_nsp.zwei" does not exist
+WARNING:  error for column encryption key,{addr_nsp,zwei},{integer}: column encryption key "addr_nsp.zwei" does not exist
+WARNING:  error for column encryption key,{eins,zwei,drei},{}: cross-database references are not implemented: eins.zwei.drei
+WARNING:  error for column encryption key,{eins,zwei,drei},{integer}: cross-database references are not implemented: eins.zwei.drei
+WARNING:  error for column encryption key data,{eins},{}: column encryption key "eins" does not exist
+WARNING:  error for column encryption key data,{eins},{integer}: column encryption key "eins" does not exist
+WARNING:  error for column encryption key data,{addr_nsp,zwei},{}: column encryption key "addr_nsp.zwei" does not exist
+WARNING:  error for column encryption key data,{addr_nsp,zwei},{integer}: column encryption key "addr_nsp.zwei" does not exist
+WARNING:  error for column encryption key data,{eins,zwei,drei},{}: cross-database references are not implemented: eins.zwei.drei
+WARNING:  error for column encryption key data,{eins,zwei,drei},{integer}: cross-database references are not implemented: eins.zwei.drei
+WARNING:  error for column master key,{eins},{}: column master key "eins" does not exist
+WARNING:  error for column master key,{eins},{integer}: column master key "eins" does not exist
+WARNING:  error for column master key,{addr_nsp,zwei},{}: column master key "addr_nsp.zwei" does not exist
+WARNING:  error for column master key,{addr_nsp,zwei},{integer}: column master key "addr_nsp.zwei" does not exist
+WARNING:  error for column master key,{eins,zwei,drei},{}: cross-database references are not implemented: eins.zwei.drei
+WARNING:  error for column master key,{eins,zwei,drei},{integer}: cross-database references are not implemented: eins.zwei.drei
 -- these object types cannot be qualified names
 SELECT pg_get_object_address('language', '{one}', '{}');
 ERROR:  language "one" does not exist
@@ -409,6 +430,9 @@ WITH objects (type, name, args) AS (VALUES
     ('type', '{addr_nsp.genenum}', '{}'),
     ('cast', '{int8}', '{int4}'),
     ('collation', '{default}', '{}'),
+    ('column encryption key', '{addr_cek}', '{}'),
+    ('column encryption key data', '{addr_cek}', '{addr_cmk}'),
+    ('column master key', '{addr_cmk}', '{}'),
     ('table constraint', '{addr_nsp, gentable, a_chk}', '{}'),
     ('domain constraint', '{addr_nsp.gendomain}', '{domconstr}'),
     ('conversion', '{pg_catalog, koi8_r_to_mic}', '{}'),
@@ -505,6 +529,9 @@ subscription|NULL|regress_addr_sub|regress_addr_sub|t
 publication|NULL|addr_pub|addr_pub|t
 publication relation|NULL|NULL|addr_nsp.gentable in publication addr_pub|t
 publication namespace|NULL|NULL|addr_nsp in publication addr_pub_schema|t
+column master key|addr_nsp|addr_cmk|addr_nsp.addr_cmk|t
+column encryption key|addr_nsp|addr_cek|addr_nsp.addr_cek|t
+column encryption key data|NULL|NULL|of addr_nsp.addr_cek for addr_nsp.addr_cmk|t
 ---
 --- Cleanup resources
 ---
@@ -517,6 +544,8 @@ drop cascades to user mapping for regress_addr_user on server integer
 DROP PUBLICATION addr_pub;
 DROP PUBLICATION addr_pub_schema;
 DROP SUBSCRIPTION regress_addr_sub;
+DROP COLUMN ENCRYPTION KEY addr_cek;
+DROP COLUMN MASTER KEY addr_cmk;
 DROP SCHEMA addr_nsp CASCADE;
 NOTICE:  drop cascades to 14 other objects
 DETAIL:  drop cascades to text search dictionary addr_ts_dict
@@ -547,6 +576,9 @@ WITH objects (classid, objid, objsubid) AS (VALUES
     ('pg_type'::regclass, 0, 0), -- no type
     ('pg_cast'::regclass, 0, 0), -- no cast
     ('pg_collation'::regclass, 0, 0), -- no collation
+    ('pg_colenckey'::regclass, 0, 0), -- no column encryption key
+    ('pg_colenckeydata'::regclass, 0, 0), -- no column encryption key data
+    ('pg_colmasterkey'::regclass, 0, 0), -- no column master key
     ('pg_constraint'::regclass, 0, 0), -- no constraint
     ('pg_conversion'::regclass, 0, 0), -- no conversion
     ('pg_attrdef'::regclass, 0, 0), -- no default attribute
@@ -634,5 +666,8 @@ ORDER BY objects.classid, objects.objid, objects.objsubid;
 ("(""publication relation"",,,)")|("(""publication relation"",,)")|NULL
 ("(""publication namespace"",,,)")|("(""publication namespace"",,)")|NULL
 ("(""parameter ACL"",,,)")|("(""parameter ACL"",,)")|NULL
+("(""column master key"",,,)")|("(""column master key"",,)")|NULL
+("(""column encryption key"",,,)")|("(""column encryption key"",,)")|NULL
+("(""column encryption key data"",,,)")|("(""column encryption key data"",,)")|NULL
 -- restore normal output mode
 \a\t
diff --git a/src/test/regress/expected/oidjoins.out b/src/test/regress/expected/oidjoins.out
index 215eb899be..c34fdb1b6c 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,9 @@ 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_colmasterkey {cmknamespace} => pg_namespace {oid}
+NOTICE:  checking pg_colmasterkey {cmkowner} => pg_authid {oid}
+NOTICE:  checking pg_colenckey {ceknamespace} => pg_namespace {oid}
+NOTICE:  checking pg_colenckey {cekowner} => pg_authid {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 02f5348ab1..d493ac5f7c 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -159,7 +159,8 @@ ORDER BY 1, 2;
  text                        | character varying
  timestamp without time zone | timestamp with time zone
  txid_snapshot               | pg_snapshot
-(4 rows)
+ pg_encrypted_det            | pg_encrypted_rnd
+(5 rows)
 
 SELECT DISTINCT p1.proargtypes[0]::regtype, p2.proargtypes[0]::regtype
 FROM pg_proc AS p1, pg_proc AS p2
@@ -175,13 +176,15 @@ WHERE p1.oid != p2.oid AND
 ORDER BY 1, 2;
          proargtypes         |       proargtypes        
 -----------------------------+--------------------------
+ bytea                       | pg_encrypted_det
  bigint                      | xid8
  text                        | character
  text                        | character varying
  timestamp without time zone | timestamp with time zone
  bit                         | bit varying
  txid_snapshot               | pg_snapshot
-(6 rows)
+ pg_encrypted_det            | pg_encrypted_rnd
+(8 rows)
 
 SELECT DISTINCT p1.proargtypes[1]::regtype, p2.proargtypes[1]::regtype
 FROM pg_proc AS p1, pg_proc AS p2
@@ -197,12 +200,13 @@ WHERE p1.oid != p2.oid AND
 ORDER BY 1, 2;
          proargtypes         |       proargtypes        
 -----------------------------+--------------------------
+ bytea                       | pg_encrypted_det
  integer                     | xid
  timestamp without time zone | timestamp with time zone
  bit                         | bit varying
  txid_snapshot               | pg_snapshot
  anyrange                    | anymultirange
-(5 rows)
+(6 rows)
 
 SELECT DISTINCT p1.proargtypes[2]::regtype, p2.proargtypes[2]::regtype
 FROM pg_proc AS p1, pg_proc AS p2
@@ -872,6 +876,8 @@ xid8ge(xid8,xid8)
 xid8eq(xid8,xid8)
 xid8ne(xid8,xid8)
 xid8cmp(xid8,xid8)
+pg_encrypted_det_eq(pg_encrypted_det,pg_encrypted_det)
+pg_encrypted_det_ne(pg_encrypted_det,pg_encrypted_det)
 -- restore normal output mode
 \a\t
 -- List of functions used by libpq's fe-lobj.c
diff --git a/src/test/regress/expected/type_sanity.out b/src/test/regress/expected/type_sanity.out
index a640cfc476..742272225d 100644
--- a/src/test/regress/expected/type_sanity.out
+++ b/src/test/regress/expected/type_sanity.out
@@ -75,7 +75,9 @@ ORDER BY t1.oid;
  4600 | pg_brin_bloom_summary
  4601 | pg_brin_minmax_multi_summary
  5017 | pg_mcv_list
-(6 rows)
+ 8243 | pg_encrypted_det
+ 8244 | pg_encrypted_rnd
+(8 rows)
 
 -- Make sure typarray points to a "true" array type of our own base
 SELECT t1.oid, t1.typname as basetype, t2.typname as arraytype,
@@ -716,6 +718,8 @@ SELECT oid, typname, typtype, typelem, typarray
   WHERE oid < 16384 AND
     -- Exclude pseudotypes and composite types.
     typtype NOT IN ('p', 'c') AND
+    -- Exclude encryption internal types.
+    oid != ALL(ARRAY['pg_encrypted_det', 'pg_encrypted_rnd']::regtype[]) AND
     -- These reg* types cannot be pg_upgraded, so discard them.
     oid != ALL(ARRAY['regproc', 'regprocedure', 'regoper',
                      'regoperator', 'regconfig', 'regdictionary',
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index a930dfe48c..8154dc49ca 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -103,7 +103,7 @@ test: publication subscription
 # Another group of parallel tests
 # select_views depends on create_view
 # ----------
-test: select_views portals_p2 foreign_key cluster dependency guc bitmapops combocid tsearch tsdicts foreign_data window xmlmap functional_deps advisory_lock indirect_toast equivclass
+test: select_views portals_p2 foreign_key cluster dependency guc bitmapops combocid tsearch tsdicts foreign_data window xmlmap functional_deps advisory_lock indirect_toast equivclass column_encryption
 
 # ----------
 # Another group of parallel tests (JSON related)
diff --git a/src/test/regress/pg_regress_main.c b/src/test/regress/pg_regress_main.c
index 427429975e..57c7d2bec3 100644
--- a/src/test/regress/pg_regress_main.c
+++ b/src/test/regress/pg_regress_main.c
@@ -82,7 +82,7 @@ psql_start_test(const char *testname,
 					   bindir ? bindir : "",
 					   bindir ? "/" : "",
 					   dblist->str,
-					   "-v HIDE_TABLEAM=on -v HIDE_TOAST_COMPRESSION=on",
+					   "-v HIDE_TABLEAM=on -v HIDE_TOAST_COMPRESSION=on -v HIDE_COLUMN_ENCRYPTION=on",
 					   infile,
 					   outfile);
 	if (offset >= sizeof(psql_cmd))
diff --git a/src/test/regress/sql/column_encryption.sql b/src/test/regress/sql/column_encryption.sql
new file mode 100644
index 0000000000..3f5fcaba92
--- /dev/null
+++ b/src/test/regress/sql/column_encryption.sql
@@ -0,0 +1,232 @@
+\set HIDE_COLUMN_ENCRYPTION false
+
+CREATE ROLE regress_enc_user1;
+
+CREATE COLUMN MASTER KEY cmk1 WITH (
+    realm = 'test'
+);
+
+COMMENT ON COLUMN MASTER KEY cmk1 IS 'column master key';
+
+CREATE COLUMN MASTER KEY cmk1a WITH (
+    realm = 'test'
+);
+
+CREATE COLUMN MASTER KEY cmk2;
+
+CREATE COLUMN MASTER KEY cmk2a WITH (
+    realm = 'testx'
+);
+
+ALTER COLUMN MASTER KEY cmk2a (realm = 'test2');
+
+CREATE COLUMN ENCRYPTION KEY fail WITH VALUES (
+    column_master_key = cmk1,
+    algorithm = 'foo',  -- invalid
+    encrypted_value = '\xDEADBEEF'
+);
+
+CREATE COLUMN ENCRYPTION KEY cek1 WITH VALUES (
+    column_master_key = cmk1,
+    algorithm = 'RSAES_OAEP_SHA_1',
+    encrypted_value = '\xDEADBEEF'
+);
+
+COMMENT ON COLUMN ENCRYPTION KEY cek1 IS 'column encryption key';
+
+ALTER COLUMN ENCRYPTION KEY cek1 ADD VALUE (
+    column_master_key = cmk1a,
+    algorithm = 'RSAES_OAEP_SHA_1',
+    encrypted_value = '\xDEADBEEF'
+);
+
+-- duplicate
+ALTER COLUMN ENCRYPTION KEY cek1 ADD VALUE (
+    column_master_key = cmk1a,
+    algorithm = 'RSAES_OAEP_SHA_1',
+    encrypted_value = '\xDEADBEEF'
+);
+
+ALTER COLUMN ENCRYPTION KEY fail ADD VALUE (
+    column_master_key = cmk1a,
+    algorithm = 'RSAES_OAEP_SHA_1',
+    encrypted_value = '\xDEADBEEF'
+);
+
+CREATE COLUMN ENCRYPTION KEY cek2 WITH VALUES (
+    column_master_key = cmk2,
+    algorithm = 'RSAES_OAEP_SHA_1',
+    encrypted_value = '\xDEADBEEF'
+),
+(
+    column_master_key = cmk2a,
+    algorithm = 'RSAES_OAEP_SHA_1',
+    encrypted_value = '\xDEADBEEF'
+);
+
+CREATE COLUMN ENCRYPTION KEY cek4 WITH VALUES (
+    column_master_key = cmk1,
+    algorithm = 'RSAES_OAEP_SHA_1',
+    encrypted_value = '\xDEADBEEF'
+);
+
+CREATE TABLE tbl_fail (
+    a int,
+    b text,
+    c text ENCRYPTED WITH (column_encryption_key = notexist)
+);
+
+CREATE TABLE tbl_fail (
+    a int,
+    b text,
+    c text ENCRYPTED WITH (column_encryption_key = cek1, algorithm = 'foo')
+);
+
+CREATE TABLE tbl_fail (
+    a int,
+    b text,
+    c text ENCRYPTED WITH (column_encryption_key = cek1, encryption_type = wrong)
+);
+
+CREATE TABLE tbl_29f3 (
+    a int,
+    b text,
+    c text ENCRYPTED WITH (column_encryption_key = cek1)
+);
+
+\d tbl_29f3
+\d+ tbl_29f3
+
+CREATE TABLE tbl_447f (
+    a int,
+    b text
+);
+
+ALTER TABLE tbl_447f ADD COLUMN c text ENCRYPTED WITH (column_encryption_key = cek1);
+
+\d tbl_447f
+\d+ tbl_447f
+
+CREATE TABLE tbl_4897 (LIKE tbl_447f);
+CREATE TABLE tbl_6978 (LIKE tbl_447f INCLUDING ENCRYPTED);
+
+\d+ tbl_4897
+\d+ tbl_6978
+
+CREATE VIEW view_3bc9 AS SELECT * FROM tbl_29f3;
+
+\d+ view_3bc9
+
+CREATE TABLE tbl_2386 AS SELECT * FROM tbl_29f3 WITH NO DATA;
+
+\d+ tbl_2386
+
+CREATE TABLE tbl_2941 AS SELECT * FROM tbl_29f3 WITH DATA;
+
+\d+ tbl_2941
+
+-- test partition declarations
+
+CREATE TABLE tbl_13fa (
+    a int,
+    b text ENCRYPTED WITH (column_encryption_key = cek1)
+) PARTITION BY RANGE (a);
+CREATE TABLE tbl_13fa_1 PARTITION OF tbl_13fa FOR VALUES FROM (1) TO (100);
+
+\d+ tbl_13fa
+\d+ tbl_13fa_1
+
+
+-- test inheritance
+
+CREATE TABLE tbl_36f3_a (
+    a int,
+    b text ENCRYPTED WITH (column_encryption_key = cek1)
+);
+
+CREATE TABLE tbl_36f3_b (
+    a int,
+    b text ENCRYPTED WITH (column_encryption_key = cek1)
+);
+
+CREATE TABLE tbl_36f3_c (
+    a int,
+    b text ENCRYPTED WITH (column_encryption_key = cek2)
+);
+
+CREATE TABLE tbl_36f3_d (
+    a int,
+    b text ENCRYPTED WITH (column_encryption_key = cek1, encryption_type = deterministic)
+);
+
+CREATE TABLE tbl_36f3_e (
+    a int,
+    b text
+);
+
+-- not implemented (but could be ok)
+CREATE TABLE tbl_36f3_ab (c int) INHERITS (tbl_36f3_a, tbl_36f3_b);
+\d+ tbl_36f3_ab
+-- not implemented (but should fail)
+CREATE TABLE tbl_36f3_ac (c int) INHERITS (tbl_36f3_a, tbl_36f3_c);
+CREATE TABLE tbl_36f3_ad (c int) INHERITS (tbl_36f3_a, tbl_36f3_d);
+-- fail
+CREATE TABLE tbl_36f3_ae (c int) INHERITS (tbl_36f3_a, tbl_36f3_e);
+
+-- ok
+CREATE TABLE tbl_36f3_a_1 (b text ENCRYPTED WITH (column_encryption_key = cek1), c int) INHERITS (tbl_36f3_a);
+\d+ tbl_36f3_a_1
+-- fail
+CREATE TABLE tbl_36f3_a_2 (b text ENCRYPTED WITH (column_encryption_key = cek2), c int) INHERITS (tbl_36f3_a);
+CREATE TABLE tbl_36f3_a_2 (b text ENCRYPTED WITH (column_encryption_key = cek1, encryption_type = deterministic), c int) INHERITS (tbl_36f3_a);
+CREATE TABLE tbl_36f3_a_2 (b text, c int) INHERITS (tbl_36f3_a);
+
+DROP TABLE tbl_36f3_b, tbl_36f3_c, tbl_36f3_d, tbl_36f3_e;
+
+
+-- SET SCHEMA
+CREATE SCHEMA test_schema_ce;
+ALTER COLUMN ENCRYPTION KEY cek1 SET SCHEMA test_schema_ce;
+ALTER COLUMN MASTER KEY cmk1 SET SCHEMA test_schema_ce;
+ALTER COLUMN ENCRYPTION KEY test_schema_ce.cek1 SET SCHEMA public;
+ALTER COLUMN MASTER KEY test_schema_ce.cmk1 SET SCHEMA public;
+DROP SCHEMA test_schema_ce;
+
+
+DROP COLUMN MASTER KEY cmk1 RESTRICT;  -- fail
+
+ALTER COLUMN MASTER KEY cmk2 RENAME TO cmk3;
+ALTER COLUMN MASTER KEY cmk1 RENAME TO cmk3;  -- fail
+ALTER COLUMN MASTER KEY cmkx RENAME TO cmky;  -- fail
+
+ALTER COLUMN ENCRYPTION KEY cek2 RENAME TO cek3;
+ALTER COLUMN ENCRYPTION KEY cek1 RENAME TO cek3;  -- fail
+ALTER COLUMN ENCRYPTION KEY cekx RENAME TO ceky;  -- fail
+
+SET SESSION AUTHORIZATION 'regress_enc_user1';
+DROP COLUMN ENCRYPTION KEY cek3;  -- fail
+DROP COLUMN MASTER KEY cmk3;  -- fail
+RESET SESSION AUTHORIZATION;
+ALTER COLUMN MASTER KEY cmk3 OWNER TO regress_enc_user1;
+ALTER COLUMN ENCRYPTION KEY cek3 OWNER TO regress_enc_user1;
+\dcek cek3
+\dcmk cmk3
+SET SESSION AUTHORIZATION 'regress_enc_user1';
+DROP COLUMN ENCRYPTION KEY cek3;  -- ok now
+DROP COLUMN MASTER KEY cmk3;  -- ok now
+RESET SESSION AUTHORIZATION;
+
+ALTER COLUMN ENCRYPTION KEY cek1 DROP VALUE (column_master_key = cmk1a);
+ALTER COLUMN ENCRYPTION KEY cek1 DROP VALUE (column_master_key = cmk1a);  -- fail
+ALTER COLUMN ENCRYPTION KEY cek1 DROP VALUE (column_master_key = fail);  -- fail
+ALTER COLUMN ENCRYPTION KEY cek1 DROP VALUE (column_master_key = cmk1a, algorithm = 'foo');  -- fail
+
+DROP COLUMN ENCRYPTION KEY cek4;
+DROP COLUMN ENCRYPTION KEY fail;
+DROP COLUMN ENCRYPTION KEY IF EXISTS nonexistent;
+
+DROP COLUMN MASTER KEY cmk1a;
+DROP COLUMN MASTER KEY fail;
+DROP COLUMN MASTER KEY IF EXISTS nonexistent;
+
+DROP ROLE regress_enc_user1;
diff --git a/src/test/regress/sql/object_address.sql b/src/test/regress/sql/object_address.sql
index 1a6c61f49d..35af43032f 100644
--- a/src/test/regress/sql/object_address.sql
+++ b/src/test/regress/sql/object_address.sql
@@ -41,6 +41,8 @@ CREATE SERVER "integer" FOREIGN DATA WRAPPER addr_fdw;
 CREATE USER MAPPING FOR regress_addr_user SERVER "integer";
 ALTER DEFAULT PRIVILEGES FOR ROLE regress_addr_user IN SCHEMA public GRANT ALL ON TABLES TO regress_addr_user;
 ALTER DEFAULT PRIVILEGES FOR ROLE regress_addr_user REVOKE DELETE ON TABLES FROM regress_addr_user;
+CREATE COLUMN MASTER KEY addr_cmk;
+CREATE COLUMN ENCRYPTION KEY addr_cek WITH VALUES (column_master_key = addr_cmk, algorithm = 'RSAES_OAEP_SHA_1', encrypted_value = '');
 -- this transform would be quite unsafe to leave lying around,
 -- except that the SQL language pays no attention to transforms:
 CREATE TRANSFORM FOR int LANGUAGE SQL (
@@ -99,7 +101,8 @@ CREATE STATISTICS addr_nsp.gentable_stat ON a, b FROM addr_nsp.gentable;
         ('text search template'), ('text search configuration'),
         ('policy'), ('user mapping'), ('default acl'), ('transform'),
         ('operator of access method'), ('function of access method'),
-        ('publication namespace'), ('publication relation')
+        ('publication namespace'), ('publication relation'),
+        ('column encryption key'), ('column encryption key data'), ('column master key')
     LOOP
         FOR names IN VALUES ('{eins}'), ('{addr_nsp, zwei}'), ('{eins, zwei, drei}')
         LOOP
@@ -174,6 +177,9 @@ CREATE STATISTICS addr_nsp.gentable_stat ON a, b FROM addr_nsp.gentable;
     ('type', '{addr_nsp.genenum}', '{}'),
     ('cast', '{int8}', '{int4}'),
     ('collation', '{default}', '{}'),
+    ('column encryption key', '{addr_cek}', '{}'),
+    ('column encryption key data', '{addr_cek}', '{addr_cmk}'),
+    ('column master key', '{addr_cmk}', '{}'),
     ('table constraint', '{addr_nsp, gentable, a_chk}', '{}'),
     ('domain constraint', '{addr_nsp.gendomain}', '{domconstr}'),
     ('conversion', '{pg_catalog, koi8_r_to_mic}', '{}'),
@@ -228,6 +234,8 @@ CREATE STATISTICS addr_nsp.gentable_stat ON a, b FROM addr_nsp.gentable;
 DROP PUBLICATION addr_pub;
 DROP PUBLICATION addr_pub_schema;
 DROP SUBSCRIPTION regress_addr_sub;
+DROP COLUMN ENCRYPTION KEY addr_cek;
+DROP COLUMN MASTER KEY addr_cmk;
 
 DROP SCHEMA addr_nsp CASCADE;
 
@@ -247,6 +255,9 @@ CREATE STATISTICS addr_nsp.gentable_stat ON a, b FROM addr_nsp.gentable;
     ('pg_type'::regclass, 0, 0), -- no type
     ('pg_cast'::regclass, 0, 0), -- no cast
     ('pg_collation'::regclass, 0, 0), -- no collation
+    ('pg_colenckey'::regclass, 0, 0), -- no column encryption key
+    ('pg_colenckeydata'::regclass, 0, 0), -- no column encryption key data
+    ('pg_colmasterkey'::regclass, 0, 0), -- no column master key
     ('pg_constraint'::regclass, 0, 0), -- no constraint
     ('pg_conversion'::regclass, 0, 0), -- no conversion
     ('pg_attrdef'::regclass, 0, 0), -- no default attribute
diff --git a/src/test/regress/sql/type_sanity.sql b/src/test/regress/sql/type_sanity.sql
index 79ec410a6c..2ba4c9a545 100644
--- a/src/test/regress/sql/type_sanity.sql
+++ b/src/test/regress/sql/type_sanity.sql
@@ -544,6 +544,8 @@ CREATE TABLE tab_core_types AS SELECT
   WHERE oid < 16384 AND
     -- Exclude pseudotypes and composite types.
     typtype NOT IN ('p', 'c') AND
+    -- Exclude encryption internal types.
+    oid != ALL(ARRAY['pg_encrypted_det', 'pg_encrypted_rnd']::regtype[]) AND
     -- These reg* types cannot be pg_upgraded, so discard them.
     oid != ALL(ARRAY['regproc', 'regprocedure', 'regoper',
                      'regoperator', 'regconfig', 'regdictionary',

base-commit: 642e8821d713d75f142ef4eda14e490ba0fb810b
-- 
2.39.1



Attachments:

  [text/plain] v15-0001-Transparent-column-encryption.patch (413.7K, ../../[email protected]/2-v15-0001-Transparent-column-encryption.patch)
  download | inline diff:
From 0a40fa00a2426d74519415e9089ce7af7df4e012 Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <[email protected]>
Date: Wed, 25 Jan 2023 19:40:05 +0100
Subject: [PATCH v15] Transparent column encryption

This feature enables the automatic, transparent encryption and
decryption of particular columns in the client.  The data for those
columns then only ever appears in ciphertext on the server, so it is
protected from DBAs, sysadmins, cloud operators, etc. as well as
accidental leakage to server logs, file-system backups, etc.  The
canonical use case for this feature is storing credit card numbers
encrypted, in accordance with PCI DSS, as well as similar situations
involving social security numbers etc.  One can't do any computations
with encrypted values on the server, but for these use cases, that is
not necessary.  This feature does support deterministic encryption as
an alternative to the default randomized encryption, so in that mode
one can do equality lookups, at the cost of some security.

This functionality also exists in other database products, and the
overall concepts were mostly adopted from there.

(Note: This feature has nothing to do with any on-disk encryption
feature.  Both can exist independently.)

You declare a column as encrypted in a CREATE TABLE statement.  The
column value is encrypted by a symmetric key called the column
encryption key (CEK).  The CEK is a catalog object.  The CEK key
material is in turn encrypted by an asymmetric key called the column
master key (CMK).  The CMK is not stored in the database but somewhere
where the client can get to it, for example in a file or in a key
management system.  When a server sends rows containing encrypted
column values to the client, it first sends the required CMK and CEK
information (new protocol messages), which the client needs to record.
Then, the client can use this information to automatically decrypt the
incoming row data and forward it in plaintext to the application.

For the CMKs, libpq has a new connection parameter "cmklookup" that
specifies via a mini-language where to get the keys.  Right now, you
can use "file" to read it from a file, or "run" to run some program,
which could get it from a KMS.

The general idea would be for an application to have one CMK per area
of secret stuff, for example, for credit card data.  The CMK can be
rotated: each CEK can be represented multiple times in the database,
encrypted by a different CMK.  (The CEK can't be rotated easily, since
that would require reading out all the data from a table/column and
reencrypting it.  We could/should add some custom tooling for that,
but it wouldn't be a routine operation.)

Several encryption algorithms are provided.  The CMK process uses
RSAES_OAEP_SHA_1 or _256.  The CEK process uses
AEAD_AES_*_CBC_HMAC_SHA_* with several strengths.

In the server, the encrypted datums are stored in types called
pg_encrypted_rnd and pg_encrypted_det (for randomized and
deterministic encryption).  These are essentially cousins of bytea.
For the rest of the database system below the protocol handling, there
is nothing special about those.  For example, pg_encrypted_rnd has no
operators at all, pg_encrypted_det has only an equality operator.
pg_attribute has a new column attrealtypid that stores the original
type of the data in the column.  This is only used for providing it to
clients, so that higher-level clients can convert the decrypted value
to their appropriate data types in their environments.

The protocol extensions are guarded by a new protocol extension option
"_pq_.column_encryption".  If this is not set, nothing changes, the
protocol stays the same, and no encryption or decryption happens.

To get transparently encrypted data into the database (as opposed to
reading it out), it is required to use protocol-level prepared
statements (i.e., extended query).  The client must first prepare a
statement, then describe the statement to get parameter metadata,
which indicates which parameters are to be encrypted and how.  libpq's
PQexecParams() does this internally.  For the asynchronous interfaces,
additional libpq functions are added to be able to pass the describe
result back into the statement execution function.  (Other client APIs
that have a "statement handle" concept could do this more elegantly
and probably without any API changes.)  psql also supports this
transparently if the \bind command is used.

Another challenge is that the parse analysis must check which
underlying column a parameter corresponds to.  This is similar to
resorigtbl and resorigcol in the opposite direction.

Discussion: https://www.postgresql.org/message-id/flat/89157929-c2b6-817b-6025-8e4b2d89d88f%40enterprisedb.com
---
 doc/src/sgml/acronyms.sgml                    |  18 +
 doc/src/sgml/catalogs.sgml                    | 295 ++++++
 doc/src/sgml/charset.sgml                     |   9 +
 doc/src/sgml/datatype.sgml                    |  55 ++
 doc/src/sgml/ddl.sgml                         | 416 +++++++++
 doc/src/sgml/func.sgml                        |  26 +
 doc/src/sgml/glossary.sgml                    |  23 +
 doc/src/sgml/libpq.sgml                       | 315 +++++++
 doc/src/sgml/protocol.sgml                    | 442 +++++++++
 doc/src/sgml/ref/allfiles.sgml                |   6 +
 .../sgml/ref/alter_column_encryption_key.sgml | 197 ++++
 doc/src/sgml/ref/alter_column_master_key.sgml | 134 +++
 doc/src/sgml/ref/comment.sgml                 |   2 +
 doc/src/sgml/ref/copy.sgml                    |   9 +
 .../ref/create_column_encryption_key.sgml     | 169 ++++
 .../sgml/ref/create_column_master_key.sgml    | 107 +++
 doc/src/sgml/ref/create_table.sgml            |  54 +-
 .../sgml/ref/drop_column_encryption_key.sgml  | 112 +++
 doc/src/sgml/ref/drop_column_master_key.sgml  | 112 +++
 doc/src/sgml/ref/pg_dump.sgml                 |  31 +
 doc/src/sgml/ref/pg_dumpall.sgml              |  27 +
 doc/src/sgml/ref/psql-ref.sgml                |  39 +
 doc/src/sgml/reference.sgml                   |   6 +
 src/backend/access/common/printsimple.c       |   7 +
 src/backend/access/common/printtup.c          | 221 ++++-
 src/backend/access/common/tupdesc.c           |  12 +
 src/backend/access/hash/hashvalidate.c        |   2 +-
 src/backend/catalog/Makefile                  |   3 +-
 src/backend/catalog/aclchk.c                  |  12 +
 src/backend/catalog/dependency.c              |  18 +
 src/backend/catalog/heap.c                    |  42 +-
 src/backend/catalog/namespace.c               | 272 ++++++
 src/backend/catalog/objectaddress.c           | 288 ++++++
 src/backend/commands/Makefile                 |   1 +
 src/backend/commands/alter.c                  |  17 +
 src/backend/commands/colenccmds.c             | 427 +++++++++
 src/backend/commands/createas.c               |  32 +
 src/backend/commands/dropcmds.c               |  15 +
 src/backend/commands/event_trigger.c          |  12 +
 src/backend/commands/meson.build              |   1 +
 src/backend/commands/seclabel.c               |   3 +
 src/backend/commands/tablecmds.c              | 190 +++-
 src/backend/commands/variable.c               |   7 +-
 src/backend/commands/view.c                   |  20 +
 src/backend/nodes/nodeFuncs.c                 |   2 +
 src/backend/parser/gram.y                     | 169 +++-
 src/backend/parser/parse_param.c              | 144 +++
 src/backend/parser/parse_utilcmd.c            |  12 +-
 src/backend/postmaster/postmaster.c           |  19 +-
 src/backend/tcop/postgres.c                   |  64 ++
 src/backend/tcop/utility.c                    |  53 ++
 src/backend/utils/adt/varlena.c               | 106 +++
 src/backend/utils/cache/lsyscache.c           |  83 ++
 src/backend/utils/cache/plancache.c           |   4 +-
 src/backend/utils/cache/syscache.c            |  42 +
 src/backend/utils/mb/mbutils.c                |  18 +-
 src/bin/pg_dump/common.c                      |  44 +
 src/bin/pg_dump/pg_backup.h                   |   1 +
 src/bin/pg_dump/pg_backup_archiver.c          |   2 +
 src/bin/pg_dump/pg_backup_db.c                |   9 +-
 src/bin/pg_dump/pg_dump.c                     | 343 ++++++-
 src/bin/pg_dump/pg_dump.h                     |  33 +
 src/bin/pg_dump/pg_dump_sort.c                |  14 +
 src/bin/pg_dump/pg_dumpall.c                  |   5 +
 src/bin/pg_dump/t/002_pg_dump.pl              |  52 ++
 src/bin/psql/command.c                        |   6 +-
 src/bin/psql/describe.c                       | 179 +++-
 src/bin/psql/describe.h                       |   6 +
 src/bin/psql/help.c                           |   4 +
 src/bin/psql/settings.h                       |   1 +
 src/bin/psql/startup.c                        |  10 +
 src/bin/psql/tab-complete.c                   |  55 +-
 src/common/Makefile                           |   1 +
 src/common/colenc.c                           | 104 +++
 src/common/meson.build                        |   1 +
 src/include/access/printtup.h                 |   2 +
 src/include/catalog/dependency.h              |   3 +
 src/include/catalog/heap.h                    |   1 +
 src/include/catalog/meson.build               |   3 +
 src/include/catalog/namespace.h               |   6 +
 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_colenckey.h            |  41 +
 src/include/catalog/pg_colenckeydata.h        |  46 +
 src/include/catalog/pg_colmasterkey.h         |  46 +
 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               |  41 +
 src/include/catalog/pg_type.dat               |  12 +
 src/include/catalog/pg_type.h                 |   1 +
 src/include/commands/colenccmds.h             |  26 +
 src/include/commands/tablecmds.h              |   2 +
 src/include/common/colenc.h                   |  51 ++
 src/include/libpq/libpq-be.h                  |   1 +
 src/include/nodes/parsenodes.h                |  40 +-
 src/include/parser/kwlist.h                   |   2 +
 src/include/parser/parse_param.h              |   1 +
 src/include/tcop/cmdtaglist.h                 |   6 +
 src/include/utils/lsyscache.h                 |   4 +
 src/include/utils/plancache.h                 |   3 +
 src/include/utils/syscache.h                  |   6 +
 src/interfaces/libpq/Makefile                 |   1 +
 src/interfaces/libpq/exports.txt              |   4 +
 src/interfaces/libpq/fe-connect.c             |  46 +
 src/interfaces/libpq/fe-encrypt-openssl.c     | 842 ++++++++++++++++++
 src/interfaces/libpq/fe-encrypt.h             |  33 +
 src/interfaces/libpq/fe-exec.c                | 665 +++++++++++++-
 src/interfaces/libpq/fe-protocol3.c           | 139 ++-
 src/interfaces/libpq/fe-trace.c               |  55 +-
 src/interfaces/libpq/libpq-fe.h               |  20 +
 src/interfaces/libpq/libpq-int.h              |  36 +
 src/interfaces/libpq/meson.build              |   2 +
 src/interfaces/libpq/nls.mk                   |   2 +-
 src/interfaces/libpq/t/003_encrypt.pl         |  70 ++
 src/interfaces/libpq/test/.gitignore          |   1 +
 src/interfaces/libpq/test/Makefile            |   7 +
 src/interfaces/libpq/test/meson.build         |  23 +
 src/test/Makefile                             |   4 +-
 src/test/column_encryption/.gitignore         |   3 +
 src/test/column_encryption/Makefile           |  31 +
 src/test/column_encryption/meson.build        |  24 +
 .../t/001_column_encryption.pl                | 255 ++++++
 .../column_encryption/t/002_cmk_rotation.pl   | 112 +++
 src/test/column_encryption/test_client.c      | 161 ++++
 .../column_encryption/test_run_decrypt.pl     |  58 ++
 src/test/meson.build                          |   1 +
 .../regress/expected/column_encryption.out    | 322 +++++++
 src/test/regress/expected/object_address.out  |  37 +-
 src/test/regress/expected/oidjoins.out        |   8 +
 src/test/regress/expected/opr_sanity.out      |  12 +-
 src/test/regress/expected/type_sanity.out     |   6 +-
 src/test/regress/parallel_schedule            |   2 +-
 src/test/regress/pg_regress_main.c            |   2 +-
 src/test/regress/sql/column_encryption.sql    | 232 +++++
 src/test/regress/sql/object_address.sql       |  13 +-
 src/test/regress/sql/type_sanity.sql          |   2 +
 138 files changed, 9319 insertions(+), 81 deletions(-)
 create mode 100644 doc/src/sgml/ref/alter_column_encryption_key.sgml
 create mode 100644 doc/src/sgml/ref/alter_column_master_key.sgml
 create mode 100644 doc/src/sgml/ref/create_column_encryption_key.sgml
 create mode 100644 doc/src/sgml/ref/create_column_master_key.sgml
 create mode 100644 doc/src/sgml/ref/drop_column_encryption_key.sgml
 create mode 100644 doc/src/sgml/ref/drop_column_master_key.sgml
 create mode 100644 src/backend/commands/colenccmds.c
 create mode 100644 src/common/colenc.c
 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/include/commands/colenccmds.h
 create mode 100644 src/include/common/colenc.h
 create mode 100644 src/interfaces/libpq/fe-encrypt-openssl.c
 create mode 100644 src/interfaces/libpq/fe-encrypt.h
 create mode 100644 src/interfaces/libpq/t/003_encrypt.pl
 create mode 100644 src/test/column_encryption/.gitignore
 create mode 100644 src/test/column_encryption/Makefile
 create mode 100644 src/test/column_encryption/meson.build
 create mode 100644 src/test/column_encryption/t/001_column_encryption.pl
 create mode 100644 src/test/column_encryption/t/002_cmk_rotation.pl
 create mode 100644 src/test/column_encryption/test_client.c
 create mode 100755 src/test/column_encryption/test_run_decrypt.pl
 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/acronyms.sgml b/doc/src/sgml/acronyms.sgml
index 2df6559acc..bd1a0185ed 100644
--- a/doc/src/sgml/acronyms.sgml
+++ b/doc/src/sgml/acronyms.sgml
@@ -56,6 +56,15 @@ <title>Acronyms</title>
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><acronym>CEK</acronym></term>
+    <listitem>
+     <para>
+      Column Encryption Key
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term><acronym>CIDR</acronym></term>
     <listitem>
@@ -67,6 +76,15 @@ <title>Acronyms</title>
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><acronym>CMK</acronym></term>
+    <listitem>
+     <para>
+      Column Master Key
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term><acronym>CPAN</acronym></term>
     <listitem>
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index c1e4048054..1b8e1326c7 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -105,6 +105,21 @@ <title>System Catalogs</title>
       <entry>collations (locale information)</entry>
      </row>
 
+     <row>
+      <entry><link linkend="catalog-pg-colenckey"><structname>pg_colenckey</structname></link></entry>
+      <entry>column encryption keys</entry>
+     </row>
+
+     <row>
+      <entry><link linkend="catalog-pg-colenckeydata"><structname>pg_colenckeydata</structname></link></entry>
+      <entry>column encryption key data</entry>
+     </row>
+
+     <row>
+      <entry><link linkend="catalog-pg-colmasterkey"><structname>pg_colmasterkey</structname></link></entry>
+      <entry>column master keys</entry>
+     </row>
+
      <row>
       <entry><link linkend="catalog-pg-constraint"><structname>pg_constraint</structname></link></entry>
       <entry>check constraints, unique constraints, primary key constraints, foreign key constraints</entry>
@@ -1360,6 +1375,40 @@ <title><structname>pg_attribute</structname> Columns</title>
       </para></entry>
      </row>
 
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>attcek</structfield> <type>oid</type>
+       (references <link linkend="catalog-pg-colenckey"><structname>pg_colenckey</structname></link>.<structfield>oid</structfield>)
+      </para>
+      <para>
+       If the column is encrypted, a reference to the column encryption key, else 0.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>attrealtypid</structfield> <type>oid</type>
+       (references <link linkend="catalog-pg-type"><structname>pg_type</structname></link>.<structfield>oid</structfield>)
+      </para>
+      <para>
+       If the column is encrypted, then this column indicates the type of the
+       encrypted data that is reported to the client.  For encrypted columns,
+       the field <structfield>atttypid</structfield> is either
+       <type>pg_encrypted_det</type> or <type>pg_encrypted_rnd</type>.  If the
+       column is not encrypted, then 0.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>attencalg</structfield> <type>int4</type>
+      </para>
+      <para>
+       If the column is encrypted, the identifier of the encryption algorithm;
+       see <xref linkend="protocol-cek"/> for possible values.
+       </para></entry>
+     </row>
+
      <row>
       <entry role="catalog_table_entry"><para role="column_definition">
        <structfield>attinhcount</structfield> <type>int4</type>
@@ -2467,6 +2516,252 @@ <title><structname>pg_collation</structname> Columns</title>
   </para>
  </sect1>
 
+ <sect1 id="catalog-pg-colenckey">
+  <title><structname>pg_colenckey</structname></title>
+
+  <indexterm zone="catalog-pg-colenckey">
+   <primary>pg_colenckey</primary>
+  </indexterm>
+
+  <para>
+   The catalog <structname>pg_colenckey</structname> contains information
+   about the column encryption keys in the database.  The actual key material
+   of the column encryption keys is in the catalog <link
+   linkend="catalog-pg-colenckeydata"><structname>pg_colenckeydata</structname></link>.
+  </para>
+
+  <table>
+   <title><structname>pg_colenckey</structname> Columns</title>
+   <tgroup cols="1">
+    <thead>
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       Column Type
+      </para>
+      <para>
+       Description
+      </para></entry>
+     </row>
+    </thead>
+
+    <tbody>
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>oid</structfield> <type>oid</type>
+      </para>
+      <para>
+       Row identifier
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>cekname</structfield> <type>name</type>
+      </para>
+      <para>
+       Column encryption key name
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>ceknamespace</structfield> <type>oid</type>
+       (references <link linkend="catalog-pg-namespace"><structname>pg_namespace</structname></link>.<structfield>oid</structfield>)
+      </para>
+      <para>
+       The OID of the namespace that contains this column encryption key
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>cekowner</structfield> <type>oid</type>
+       (references <link linkend="catalog-pg-authid"><structname>pg_authid</structname></link>.<structfield>oid</structfield>)
+      </para>
+      <para>
+       Owner of the column encryption key
+      </para></entry>
+     </row>
+    </tbody>
+   </tgroup>
+  </table>
+ </sect1>
+
+ <sect1 id="catalog-pg-colenckeydata">
+  <title><structname>pg_colenckeydata</structname></title>
+
+  <indexterm zone="catalog-pg-colenckeydata">
+   <primary>pg_colenckeydata</primary>
+  </indexterm>
+
+  <para>
+   The catalog <structname>pg_colenckeydata</structname> 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, <literal>(ckdcekid, ckdcmkid)</literal> is a
+   unique key of this table.
+  </para>
+
+  <para>
+   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.
+  </para>
+
+  <table>
+   <title><structname>pg_colenckeydata</structname> Columns</title>
+   <tgroup cols="1">
+    <thead>
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       Column Type
+      </para>
+      <para>
+       Description
+      </para></entry>
+     </row>
+    </thead>
+
+    <tbody>
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>oid</structfield> <type>oid</type>
+      </para>
+      <para>
+       Row identifier
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>ckdcekid</structfield> <type>oid</type>
+       (references <link linkend="catalog-pg-colenckey"><structname>pg_colenckey</structname></link>.<structfield>oid</structfield>)
+      </para>
+      <para>
+       The column encryption key this entry belongs to
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>ckdcmkid</structfield> <type>oid</type>
+       (references <link linkend="catalog-pg-colmasterkey"><structname>pg_colmasterkey</structname></link>.<structfield>oid</structfield>)
+      </para>
+      <para>
+       The column master key that the key material is encrypted with
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>ckdcmkalg</structfield> <type>int4</type>
+      </para>
+      <para>
+       The encryption algorithm used for encrypting the key material; see
+       <xref linkend="protocol-cmk"/> for possible values.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>ckdencval</structfield> <type>bytea</type>
+      </para>
+      <para>
+       The key material of this column encryption key, encrypted using the
+       referenced column master key
+      </para></entry>
+     </row>
+    </tbody>
+   </tgroup>
+  </table>
+ </sect1>
+
+ <sect1 id="catalog-pg-colmasterkey">
+  <title><structname>pg_colmasterkey</structname></title>
+
+  <indexterm zone="catalog-pg-colmasterkey">
+   <primary>pg_colmasterkey</primary>
+  </indexterm>
+
+  <para>
+   The catalog <structname>pg_colmasterkey</structname> 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.
+  </para>
+
+  <table>
+   <title><structname>pg_colmasterkey</structname> Columns</title>
+   <tgroup cols="1">
+    <thead>
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       Column Type
+      </para>
+      <para>
+       Description
+      </para></entry>
+     </row>
+    </thead>
+
+    <tbody>
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>oid</structfield> <type>oid</type>
+      </para>
+      <para>
+       Row identifier
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>cmkname</structfield> <type>name</type>
+      </para>
+      <para>
+       Column master key name
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>cmknamespace</structfield> <type>oid</type>
+       (references <link linkend="catalog-pg-namespace"><structname>pg_namespace</structname></link>.<structfield>oid</structfield>)
+      </para>
+      <para>
+       The OID of the namespace that contains this column master key
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>cmkowner</structfield> <type>oid</type>
+       (references <link linkend="catalog-pg-authid"><structname>pg_authid</structname></link>.<structfield>oid</structfield>)
+      </para>
+      <para>
+       Owner of the column master key
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>cmkrealm</structfield> <type>text</type>
+      </para>
+      <para>
+       A <quote>realm</quote> associated with this column master key.  This is
+       a freely chosen string that is used by clients to determine how to look
+       up the key.  A typical configuration would put all CMKs that are looked
+       up in the same way into the same realm.
+      </para></entry>
+     </row>
+    </tbody>
+   </tgroup>
+  </table>
+ </sect1>
+
  <sect1 id="catalog-pg-constraint">
   <title><structname>pg_constraint</structname></title>
 
diff --git a/doc/src/sgml/charset.sgml b/doc/src/sgml/charset.sgml
index 3032392b80..e3d21e66f8 100644
--- a/doc/src/sgml/charset.sgml
+++ b/doc/src/sgml/charset.sgml
@@ -1721,6 +1721,15 @@ <title>Automatic Character Set Conversion Between Server and Client</title>
      Just as for the server, use of <literal>SQL_ASCII</literal> is unwise
      unless you are working with all-ASCII data.
     </para>
+
+    <para>
+     When transparent column encryption is used, then no encoding conversion
+     is possible.  (The encoding conversion happens on the server, and the
+     server cannot look inside any encrypted column values.)  If column
+     encryption is enabled for a session, then the server enforces that the
+     client encoding matches the server encoding, and any attempts to change
+     the client encoding will be rejected by the server.
+    </para>
    </sect2>
 
    <sect2 id="multibyte-conversions-supported">
diff --git a/doc/src/sgml/datatype.sgml b/doc/src/sgml/datatype.sgml
index 467b49b199..7494ec85d5 100644
--- a/doc/src/sgml/datatype.sgml
+++ b/doc/src/sgml/datatype.sgml
@@ -5360,4 +5360,59 @@ <title>Pseudo-Types</title>
 
   </sect1>
 
+  <sect1 id="datatype-encrypted">
+   <title>Types Related to Encryption</title>
+
+   <para>
+    An encrypted column value (see <xref linkend="ddl-column-encryption"/>) is
+    internally stored using the types
+    <type>pg_encrypted_rnd</type> (for randomized encryption) or
+    <type>pg_encrypted_det</type> (for deterministic encryption); see <xref
+    linkend="datatype-encrypted-table"/>.  Most of the database system treats
+    these as normal types.  For example, the type <type>pg_encrypted_det</type> has
+    an equals operator that allows lookup of encrypted values.  It is,
+    however, not allowed to create a table using one of these types directly
+    as a column type.
+   </para>
+
+   <para>
+    The external representation of these types is the string
+    <literal>encrypted$</literal> followed by hexadecimal byte values, for
+    example
+    <literal>encrypted$3aacd063d2d3a1a04119df76874e0b9785ea466177f18fe9c0a1a313eaf09c98</literal>.
+    Clients that don't support transparent column encryption or have disabled
+    it will see the encrypted values in this format.  Clients that support
+    transparent data encryption will not see these types in result sets, as
+    the protocol layer will translate them back to the declared underlying type in
+    the table definition.
+   </para>
+
+    <table id="datatype-encrypted-table">
+     <title>Types Related to Encryption</title>
+     <tgroup cols="3">
+      <colspec colname="col1" colwidth="1*"/>
+      <colspec colname="col2" colwidth="3*"/>
+      <colspec colname="col3" colwidth="2*"/>
+      <thead>
+       <row>
+        <entry>Name</entry>
+        <entry>Storage Size</entry>
+        <entry>Description</entry>
+       </row>
+      </thead>
+      <tbody>
+       <row>
+        <entry><type>pg_encrypted_det</type></entry>
+        <entry>1 or 4 bytes plus the actual binary string</entry>
+        <entry>encrypted column value, deterministic encryption</entry>
+       </row>
+       <row>
+        <entry><type>pg_encrypted_rnd</type></entry>
+        <entry>1 or 4 bytes plus the actual binary string</entry>
+        <entry>encrypted column value, randomized encryption</entry>
+       </row>
+      </tbody>
+     </tgroup>
+    </table>
+  </sect1>
  </chapter>
diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml
index 8dc8d7a0ce..fb3c19b01d 100644
--- a/doc/src/sgml/ddl.sgml
+++ b/doc/src/sgml/ddl.sgml
@@ -1238,6 +1238,422 @@ <title>Exclusion Constraints</title>
   </sect2>
  </sect1>
 
+ <sect1 id="ddl-column-encryption">
+  <title>Transparent Column Encryption</title>
+
+  <para>
+   With <firstterm>transparent column encryption</firstterm>, 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.
+  </para>
+
+  <sect2>
+   <title>Using Transparent Column Encryption</title>
+
+  <para>
+   Transparent column encryption uses two levels of cryptographic keys.  The
+   actual column value is encrypted using a symmetric algorithm, such as AES,
+   using a <firstterm>column encryption key</firstterm>
+   (<acronym>CEK</acronym>).  The column encryption key is in turn encrypted
+   using an asymmetric algorithm, such as RSA, using a <firstterm>column
+   master key</firstterm> (<acronym>CMK</acronym>).  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.
+  </para>
+
+  <para>
+   Here is an example declaring a column as encrypted:
+<programlisting>
+CREATE TABLE customers (
+    id int PRIMARY KEY,
+    name text NOT NULL,
+    ...
+    creditcard_num text <emphasis>ENCRYPTED WITH (column_encryption_key = cek1)</emphasis>
+);
+</programlisting>
+  </para>
+
+  <para>
+   Column encryption supports <firstterm>randomized</firstterm>
+   (also known as <firstterm>probabilistic</firstterm>) and
+   <firstterm>deterministic</firstterm> 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 from making
+   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:
+<programlisting>
+CREATE TABLE employees (
+    id int PRIMARY KEY,
+    name text NOT NULL,
+    ...
+    ssn text ENCRYPTED WITH (
+        column_encryption_key = cek1, <emphasis>encryption_type = deterministic</emphasis>)
+);
+</programlisting>
+  </para>
+
+  <para>
+   Null values are not encrypted by transparent column encryption; null values
+   sent by the client are visible as null values in the database.  If the fact
+   that a value is null needs to be hidden from the server, this information
+   needs to be encoded into a nonnull value in the client somehow.
+  </para>
+  </sect2>
+
+  <sect2>
+   <title>Reading and Writing Encrypted Columns</title>
+
+   <para>
+    Reading and writing encrypted columns is meant to be handled automatically
+    by the client library/driver and should be mostly transparent to the
+    application code, if certain prerequisites are fulfilled:
+
+    <itemizedlist>
+     <listitem>
+      <para>
+       The client library needs to support transparent column encryption.  Not
+       all client libraries do.  Furthermore, the client library might require
+       that transparent column encryption is explicitly enabled at connection
+       time.  See the documentation of the client library for details.
+      </para>
+     </listitem>
+
+     <listitem>
+      <para>
+       Column master keys and column encryption keys have been set up, and the
+       client library has been configured to be able to look up column master
+       keys from the key store or key management system.
+      </para>
+     </listitem>
+    </itemizedlist>
+   </para>
+
+   <para>
+    Reading from encrypted columns will then work automatically.  For example,
+    using the above example,
+<programlisting>
+SELECT ssn FROM employees WHERE id = 5;
+</programlisting>
+    will return the unencrypted value for the <literal>ssn</literal> column in
+    any rows found.
+   </para>
+
+   <para>
+    Writing to encrypted columns requires that the extended query protocol
+    (protocol-level prepared statements) be used, so that the values to be
+    encrypted are supplied separately from the SQL command.  For example,
+    using, say, psql or libpq, the following would not work:
+<programlisting>
+-- WRONG!
+INSERT INTO ssn (id, name, ssn) VALUES (1, 'Someone', '12345');
+</programlisting>
+    This will leak the unencrypted value <literal>12345</literal> to the
+    server, thus defeating the point of column encryption.
+    Note that using server-side prepared statements using the SQL commands
+    <command>PREPARE</command> and <command>EXECUTE</command> is equally
+    incorrect, since that would also leak the parameters provided to
+    <command>EXECUTE</command> to the server.
+   </para>
+
+   <para>
+    This shows a correct invocation in libpq (without error checking):
+<programlisting>
+PGresult   *res;
+const char *values[] = {"1", "Someone", "12345"};
+
+res = PQexecParams(conn, "INSERT INTO employees (id, name, ssn) VALUES ($1, $2, $3)",
+                   3, NULL, values, NULL, NULL, 0);
+</programlisting>
+    Higher-level client libraries might use the protocol-level prepared
+    statements automatically and thus won't require any code changes.
+   </para>
+
+   <para>
+    <application>psql</application> provides the command
+    <literal>\bind</literal> to run statements with parameters like this:
+<programlisting>
+INSERT INTO employees (id, name, ssn) VALUES ($1, $2, $3) \bind '1' 'Someone', '12345' \g
+</programlisting>
+   </para>
+
+   <para>
+    Similarly, if deterministic encryption is used, parameters need to be used
+    in search conditions using encrypted columns:
+<programlisting>
+SELECT * FROM employees WHERE ssn = $1 \bind '12345' \g
+</programlisting>
+   </para>
+  </sect2>
+
+  <sect2>
+   <title>Setting up Transparent Column Encryption</title>
+
+  <para>
+   The steps to set up transparent column encryption for a database are:
+
+   <orderedlist>
+    <listitem>
+     <para>
+      Create the key material for the CMK, for example, using a cryptographic
+      library or toolkit, or a key management system.  Secure access to the
+      key as appropriate, using access control, passwords, etc.
+     </para>
+    </listitem>
+
+    <listitem>
+     <para>
+      Register the CMK in the database using the SQL command <xref
+      linkend="sql-create-column-master-key"/>.
+     </para>
+    </listitem>
+
+    <listitem>
+     <para>
+      Create the (unencrypted) key material for the CEK in a temporary
+      location.  (It will be encrypted in the next step.  Depending on the
+      available tools, it might be possible and sensible to combine these two
+      steps.)
+     </para>
+    </listitem>
+
+    <listitem>
+     <para>
+      Encrypt the created CEK key material using the CMK (created earlier).
+      (The unencrypted version of the CEK key material can now be disposed
+      of.)
+     </para>
+    </listitem>
+
+    <listitem>
+     <para>
+      Register the CEK in the database using the SQL command <xref
+      linkend="sql-create-column-encryption-key"/>.  This command
+      <quote>uploads</quote> the encrypted CEK key material created in the
+      previous step to the database server.  The local copy of the CEK key
+      material can then be removed.
+     </para>
+    </listitem>
+
+    <listitem>
+     <para>
+      Create encrypted columns using the created CEK.
+     </para>
+    </listitem>
+
+    <listitem>
+     <para>
+      Configure the client library/driver to be able to look up the CMK
+      created earlier.
+     </para>
+    </listitem>
+   </orderedlist>
+
+   Once this is done, values can be written to and read from the encrypted
+   columns in a transparent way.
+  </para>
+
+  <para>
+   Note that these steps should not be run on the database server, but on some
+   client machine.  Neither the CMK nor the unencrypted CEK should ever appear
+   on the database server host.
+  </para>
+
+  <para>
+   The specific details of this setup depend on the desired CMK storage
+   mechanism/key management system as well as the client libraries to be used.
+   The following example uses the <command>openssl</command> command-line tool
+   to set up the keys.
+
+   <orderedlist>
+    <listitem>
+     <para>
+      Create the key material for the CMK and write it to a file:
+<programlisting>
+openssl genpkey -algorithm rsa -out cmk1.pem
+</programlisting>
+     </para>
+    </listitem>
+
+    <listitem>
+     <para>
+      Register the CMK in the database:
+<programlisting>
+psql ... -c "CREATE COLUMN MASTER KEY cmk1"
+</programlisting>
+     </para>
+    </listitem>
+
+    <listitem>
+     <para>
+      Create the unencrypted CEK key material in a file:
+<programlisting>
+openssl rand -out cek1.bin 48
+</programlisting>
+      (See <xref linkend="protocol-cek-table"/> for required key lengths.)
+     </para>
+    </listitem>
+
+    <listitem>
+     <para>
+      Encrypt the created CEK key material:
+<programlisting>
+openssl pkeyutl -encrypt -inkey cmk1.pem -pkeyopt rsa_padding_mode:oaep -in cek1.bin -out cek1.bin.enc
+rm cek1.bin
+</programlisting>
+     </para>
+    </listitem>
+
+    <listitem>
+     <para>
+      Register the CEK in the database:
+<programlisting>
+# convert file contents to hex encoding; this is just one possible way
+cekenchex=$(perl -0ne 'print unpack "H*"' cek1.bin.enc)
+psql ... -c "CREATE COLUMN ENCRYPTION KEY cek1 WITH VALUES (column_master_key = cmk1, encrypted_value = '\\x${cekenchex}')"
+rm cek1.bin.enc
+</programlisting>
+     </para>
+    </listitem>
+
+    <listitem>
+     <para>
+      Create encrypted columns as shown in the examples above.
+     </para>
+    </listitem>
+
+    <listitem>
+     <para>
+      Configure the libpq for CMK lookup (see also <xref linkend="libpq-connect-cmklookup"/>):
+<programlisting>
+PGCMKLOOKUP="*=file:$PWD/%k.pem"
+export PGCMKLOOKUP
+</programlisting>
+     </para>
+
+     <para>
+      Additionally, libpq requires that the connection parameter <xref
+      linkend="libpq-connect-column-encryption"/> be set in order to activate
+      the transparent column encryption functionality.  This should be done in
+      the connection parameters of the application, but an environment
+      variable (<envar>PGCOLUMNENCRYPTION</envar>) is also available.
+     </para>
+    </listitem>
+   </orderedlist>
+  </para>
+  </sect2>
+
+  <sect2>
+   <title>Guidance on Using Transparent Column Encryption</title>
+
+   <para>
+    This section contains some information on when it is or is not appropriate
+    to use transparent column encryption, and what precautions need to be
+    taken to maintain its security.
+   </para>
+
+   <para>
+    In general, column encryption is never a replacement for additional
+    security and encryption techniques such as transport encryption
+    (SSL/TLS), storage encryption, strong access control, and password
+    security.  Column encryption only targets specific use cases and should be
+    used in conjunction with additional security measures.
+   </para>
+
+   <para>
+    A typical use case for column encryption is to encrypt specific values
+    with additional security requirements, for example credit card numbers.
+    This would allow you to store that security-sensitive data together with
+    the rest of your data (thus getting various benefits, such as referential
+    integrity, consistent backups), while giving access to that data only to
+    specific clients and preventing accidental leakage on the server side
+    (server logs, file system backups, etc.).
+   </para>
+
+   <para>
+    When using parameters to provide values to insert or search by, care must
+    be taken that values meant to be encrypted are not accidentally leaked to
+    the server.  The server will tell the client which parameters to encrypt,
+    based on the schema definition on the server.  But if the query or client
+    application is faulty, values meant to be encrypted might accidentally be
+    associated with parameters that the server does not think need to be
+    encrypted.  Additional robustness can be achieved by forcing encryption of
+    certain parameters in the client library (see its documentation).
+   </para>
+
+   <para>
+    Column encryption cannot hide the existence or absence of data, it can
+    only disguise the particular data that is known to exist.  For example,
+    storing a cleartext person name and an encrypted credit card number
+    indicates that the person has a credit card.  That might not reveal too
+    much if the database is for an online store and there is other data nearby
+    that shows that the person has recently made purchases.  But in another
+    example, storing a cleartext person name and an encrypted diagnosis in a
+    medical database probably indicates that the person has a medical issue.
+    Depending on the circumstances, that might not by itself be sufficient
+    security.
+   </para>
+
+   <para>
+    Encryption cannot completely hide the length of values.  The encryption
+    methods will pad values to multiples of the underlying cipher's block size
+    (usually 16 bytes), so some length differences will be unified this way.
+    There is no concern if all values are of the same length, but if there are
+    signficant length differences between valid values and that length
+    information is security-sensitive, then application-specific workarounds
+    such as padding would need to be applied.  How to do that securely is
+    beyond the scope of this manual.  Note that column encryption is applied
+    to the text representation of the stored value, so length differences can
+    be leaked even for fixed-length column types (e.g.  <type>bigint</type>,
+    whose largest decimal representation is longer than 16 bytes).
+   </para>
+
+   <para>
+    Column encryption provides only partial protection against a malicious
+    user with write access to the table.  Once encrypted, any modifications to
+    a stored value on the server side will cause a decryption failure on the
+    client.  However, a user with write access can still freely swap encrypted
+    values between rows or columns (or even separate database clusters) as
+    long as they were encrypted with the same key.  Attackers can also remove
+    values by replacing them with nulls, and users with ownership over the
+    table schema can replace encryption keys or strip encryption from the
+    columns entirely.  All of this is to say: Proper access control is still
+    of vital importance when using this feature.
+   </para>
+
+   <para>
+    When using asymmetric CMK algorithms to encrypt CEKs, the
+    <quote>public</quote> half of the CMK can be used to replace existing
+    column encryption keys with keys of an attacker's choosing, compromising
+    confidentiality and authenticity for values encrypted under that CMK.  For
+    this reason, it's important to keep both the private
+    <emphasis>and</emphasis> public halves of the CMK key pair confidential.
+   </para>
+
+   <note>
+    <para>
+     Storing data such credit card data, medical data, and so on is usually
+     subject to government or industry regulations.  This section is not meant
+     to provide complete instructions on how to do this correctly.  Please
+     seek additional advice when engaging in such projects.
+    </para>
+   </note>
+  </sect2>
+ </sect1>
+
  <sect1 id="ddl-system-columns">
   <title>System Columns</title>
 
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index e09e289a43..85b5cb1450 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -23335,6 +23335,32 @@ <title>Schema Visibility Inquiry Functions</title>
      </thead>
 
      <tbody>
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_cek_is_visible</primary>
+        </indexterm>
+        <function>pg_cek_is_visible</function> ( <parameter>cek</parameter> <type>oid</type> )
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is column encryption key visible in search path?
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_cmk_is_visible</primary>
+        </indexterm>
+        <function>pg_cmk_is_visible</function> ( <parameter>cmk</parameter> <type>oid</type> )
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is column master key visible in search path?
+       </para></entry>
+      </row>
+
       <row>
        <entry role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/doc/src/sgml/glossary.sgml b/doc/src/sgml/glossary.sgml
index 7c01a541fe..a9eb49b15f 100644
--- a/doc/src/sgml/glossary.sgml
+++ b/doc/src/sgml/glossary.sgml
@@ -389,6 +389,29 @@ <title>Glossary</title>
    </glossdef>
   </glossentry>
 
+  <glossentry id="glossary-column-encryption-key">
+   <glossterm>Column encryption key</glossterm>
+   <glossdef>
+    <para>
+     A cryptographic key used to encrypt column values when using transparent
+     column encryption.  Column encryption keys are stored in the database
+     encrypted by another key, the column master key.
+    </para>
+   </glossdef>
+  </glossentry>
+
+  <glossentry id="glossary-column-master-key">
+   <glossterm>Column master key</glossterm>
+   <glossdef>
+    <para>
+     A cryptographic key used to encrypt column encryption keys.  (So the
+     column master key is a <firstterm>key encryption key</firstterm>.)
+     Column master keys are stored outside the database system, for example in
+     a key management system.
+    </para>
+   </glossdef>
+  </glossentry>
+
   <glossentry id="glossary-commit">
    <glossterm>Commit</glossterm>
    <glossdef>
diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index 0e7ae70c70..a34d694c2a 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -1964,6 +1964,141 @@ <title>Parameter Key Words</title>
        </para>
       </listitem>
      </varlistentry>
+
+     <varlistentry id="libpq-connect-column-encryption" xreflabel="column_encryption">
+      <term><literal>column_encryption</literal></term>
+      <listitem>
+       <para>
+        If set to <literal>on</literal>, <literal>true</literal>, or
+        <literal>1</literal>, this enables transparent column encryption for
+        the connection.  If encrypted columns are queried and this is not
+        enabled, the encrypted value is returned.  See <xref
+        linkend="ddl-column-encryption"/> for more information about this
+        feature.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry id="libpq-connect-cmklookup" xreflabel="cmklookup">
+      <term><literal>cmklookup</literal></term>
+      <listitem>
+       <para>
+        This specifies how libpq should look up column master keys (CMKs) in
+        order to decrypt the column encryption keys (CEKs).
+        The value is a list of <literal>key=value</literal> entries separated
+        by semicolons.  Each key is the name of a key realm, or
+        <literal>*</literal> to match all realms.  The value is a
+        <literal>scheme:data</literal> specification.  The scheme specifies
+        the method to look up the key, the remaining data is specific to the
+        scheme.  Placeholders are replaced in the remaining data as follows:
+
+        <variablelist>
+         <varlistentry>
+          <term><literal>%a</literal></term>
+          <listitem>
+           <para>
+            The CMK algorithm name (see <xref linkend="protocol-cmk-table"/>)
+           </para>
+          </listitem>
+         </varlistentry>
+
+         <varlistentry>
+          <term><literal>%j</literal></term>
+          <listitem>
+           <para>
+            The CMK algorithm name in JSON Web Algorithms format (see <xref
+            linkend="protocol-cmk-table"/>).  This is useful for interfacing
+            with some key management systems that use these names.
+           </para>
+          </listitem>
+         </varlistentry>
+
+         <varlistentry>
+          <term><literal>%k</literal></term>
+          <listitem>
+           <para>
+            The CMK key name
+           </para>
+          </listitem>
+         </varlistentry>
+
+         <varlistentry>
+          <term><literal>%p</literal></term>
+          <listitem>
+           <para>
+            The name of a temporary file with the encrypted CEK data (only for
+            the <literal>run</literal> scheme)
+           </para>
+          </listitem>
+         </varlistentry>
+
+         <varlistentry>
+          <term><literal>%r</literal></term>
+          <listitem>
+           <para>
+            The realm name
+           </para>
+          </listitem>
+         </varlistentry>
+        </variablelist>
+       </para>
+
+       <para>
+        Available schemes are:
+        <variablelist>
+         <varlistentry>
+          <term><literal>file</literal></term>
+          <listitem>
+           <para>
+            Load the key material from a file.  The remaining data is the file
+            name.  Use this if the CMKs are kept in a file on the file system.
+           </para>
+
+           <para>
+            The file scheme does not support the CMK algorithm
+            <literal>unspecified</literal>.
+           </para>
+          </listitem>
+         </varlistentry>
+
+         <varlistentry>
+          <term><literal>run</literal></term>
+          <listitem>
+           <para>
+            Run the specified command to decrypt the CEK.  The remaining data
+            is a shell command.  Use this with key management systems that
+            perform the decryption themselves.  The command must print the
+            decrypted plaintext on the standard output.
+           </para>
+          </listitem>
+         </varlistentry>
+        </variablelist>
+       </para>
+
+       <para>
+        The default value is empty.
+       </para>
+
+       <para>
+        Example:
+<programlisting>
+cmklookup="r1=file:/some/where/secrets/%k.pem;*=file:/else/where/%r/%k.pem"
+</programlisting>
+        This specification says, for keys in realm <quote>r1</quote>, load
+        them from the specified file, replacing <literal>%k</literal> by the
+        key name.  For keys in other realms, load them from the file,
+        replacing realm and key names as specified.
+       </para>
+
+       <para>
+        An example for interacting with a (hypothetical) key management
+        system:
+<programlisting>
+cmklookup="*=run:acmekms decrypt --key %k --alg %a --infile '%p'"
+</programlisting>
+       </para>
+      </listitem>
+     </varlistentry>
     </variablelist>
    </para>
   </sect2>
@@ -2864,6 +2999,25 @@ <title>Main Functions</title>
             <filename>src/backend/utils/adt/numeric.c::numeric_send()</filename> and
             <filename>src/backend/utils/adt/numeric.c::numeric_recv()</filename>.
            </para>
+
+           <para>
+            When column encryption is enabled, the second-least-significant
+            byte of this parameter specifies whether encryption should be
+            forced for a parameter.  Set this byte to one to force encryption.
+            For example, use the C code literal <literal>0x10</literal> to
+            specify text format with forced encryption.  If the array pointer
+            is null then encryption is not forced for any parameter.
+           </para>
+
+           <para>
+            If encryption is forced for a parameter but the parameter does not
+            correspond to an encrypted column on the server, then the call
+            will fail and the parameter will not be sent.  This can be used
+            for additional security against a compromised server.  (The
+            drawback is that application code then needs to be kept up to date
+            with knowledge about which columns are encrypted rather than
+            letting the server specify this.)
+           </para>
           </listitem>
          </varlistentry>
 
@@ -2876,6 +3030,13 @@ <title>Main Functions</title>
             to obtain different result columns in different formats,
             although that is possible in the underlying protocol.)
            </para>
+
+           <para>
+            If column encryption is used, then encrypted columns will be
+            returned in text format independent of this setting.  Applications
+            can check the format of each result column with <xref
+            linkend="libpq-PQfformat"/> before accessing it.
+           </para>
           </listitem>
          </varlistentry>
         </variablelist>
@@ -3028,6 +3189,44 @@ <title>Main Functions</title>
       </listitem>
      </varlistentry>
 
+     <varlistentry id="libpq-PQexecPreparedDescribed">
+      <term><function>PQexecPreparedDescribed</function><indexterm><primary>PQexecPreparedDescribed</primary></indexterm></term>
+
+      <listitem>
+       <para>
+        Sends a request to execute a prepared statement with given
+        parameters, and waits for the result, with support for encrypted columns.
+<synopsis>
+PGresult *PQexecPreparedDescribed(PGconn *conn,
+                                  const char *stmtName,
+                                  int nParams,
+                                  const char * const *paramValues,
+                                  const int *paramLengths,
+                                  const int *paramFormats,
+                                  int resultFormat,
+                                  PGresult *paramDesc);
+</synopsis>
+       </para>
+
+       <para>
+        <xref linkend="libpq-PQexecPreparedDescribed"/> is like <xref
+        linkend="libpq-PQexecPrepared"/> with additional support for encrypted
+        columns.  The parameter <parameter>paramDesc</parameter> must be a
+        result set obtained from <xref linkend="libpq-PQdescribePrepared"/> on
+        the same prepared statement.
+       </para>
+
+       <para>
+        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.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry id="libpq-PQdescribePrepared">
       <term><function>PQdescribePrepared</function><indexterm><primary>PQdescribePrepared</primary></indexterm></term>
 
@@ -3878,6 +4077,28 @@ <title>Retrieving Query Result Information</title>
      </listitem>
     </varlistentry>
 
+    <varlistentry id="libpq-PQfisencrypted">
+     <term><function>PQfisencrypted</function><indexterm><primary>PQfisencrypted</primary></indexterm></term>
+
+     <listitem>
+      <para>
+       Returns whether the value for the given column came from an encrypted
+       column.  Column numbers start at 0.
+<synopsis>
+int PQfisencrypted(const PGresult *res,
+                   int column_number);
+</synopsis>
+      </para>
+
+      <para>
+       Encrypted column values are automatically decrypted, so this function
+       is not necessary to access the column value.  It can be used for extra
+       security to check whether the value was stored encrypted when one
+       thought it should be.
+      </para>
+     </listitem>
+    </varlistentry>
+
     <varlistentry id="libpq-PQfsize">
      <term><function>PQfsize</function><indexterm><primary>PQfsize</primary></indexterm></term>
 
@@ -4059,6 +4280,31 @@ <title>Retrieving Query Result Information</title>
      </listitem>
     </varlistentry>
 
+    <varlistentry id="libpq-PQparamisencrypted">
+     <term><function>PQparamisencrypted</function><indexterm><primary>PQparamisencrypted</primary></indexterm></term>
+
+     <listitem>
+      <para>
+       Returns whether the value for the given parameter is destined for an
+       encrypted column.  Parameter numbers start at 0.
+<synopsis>
+int PQparamisencrypted(const PGresult *res, int param_number);
+</synopsis>
+      </para>
+
+      <para>
+       Values for parameters destined for encrypted columns are automatically
+       encrypted, so this function is not necessary to prepare the parameter
+       value.  It can be used for extra security to check whether the value
+       will be stored encrypted when one thought it should be.  (But see also
+       at <xref linkend="libpq-PQexecPreparedDescribed"/> for another way to do that.)
+       This function is only useful when inspecting the result of <xref
+       linkend="libpq-PQdescribePrepared"/>.  For other types of results it
+       will return false.
+      </para>
+     </listitem>
+    </varlistentry>
+
     <varlistentry id="libpq-PQprint">
      <term><function>PQprint</function><indexterm><primary>PQprint</primary></indexterm></term>
 
@@ -4584,6 +4830,7 @@ <title>Asynchronous Command Processing</title>
    <xref linkend="libpq-PQsendQueryParams"/>,
    <xref linkend="libpq-PQsendPrepare"/>,
    <xref linkend="libpq-PQsendQueryPrepared"/>,
+   <xref linkend="libpq-PQsendQueryPreparedDescribed"/>,
    <xref linkend="libpq-PQsendDescribePrepared"/>, and
    <xref linkend="libpq-PQsendDescribePortal"/>,
    which can be used with <xref linkend="libpq-PQgetResult"/> to duplicate
@@ -4591,6 +4838,7 @@ <title>Asynchronous Command Processing</title>
    <xref linkend="libpq-PQexecParams"/>,
    <xref linkend="libpq-PQprepare"/>,
    <xref linkend="libpq-PQexecPrepared"/>,
+   <xref linkend="libpq-PQexecPreparedDescribed"/>,
    <xref linkend="libpq-PQdescribePrepared"/>, and
    <xref linkend="libpq-PQdescribePortal"/>
    respectively.
@@ -4647,6 +4895,13 @@ <title>Asynchronous Command Processing</title>
        <xref linkend="libpq-PQexecParams"/>, it allows only one command in the
        query string.
       </para>
+
+      <para>
+       If column encryption is enabled, then this function is not
+       asynchronous.  To get asynchronous behavior, <xref
+       linkend="libpq-PQsendPrepare"/> followed by <xref
+       linkend="libpq-PQsendQueryPreparedDescribed"/> should be called individually.
+      </para>
      </listitem>
     </varlistentry>
 
@@ -4701,6 +4956,45 @@ <title>Asynchronous Command Processing</title>
      </listitem>
     </varlistentry>
 
+    <varlistentry id="libpq-PQsendQueryPreparedDescribed">
+     <term><function>PQsendQueryPreparedDescribed</function><indexterm><primary>PQsendQueryPreparedDescribed</primary></indexterm></term>
+
+     <listitem>
+      <para>
+       Sends a request to execute a prepared statement with given
+       parameters, without waiting for the result(s), with support for encrypted columns.
+<synopsis>
+int PQsendQueryPreparedDescribed(PGconn *conn,
+                                 const char *stmtName,
+                                 int nParams,
+                                 const char * const *paramValues,
+                                 const int *paramLengths,
+                                 const int *paramFormats,
+                                 int resultFormat,
+                                 PGresult *paramDesc);
+</synopsis>
+      </para>
+
+      <para>
+       <xref linkend="libpq-PQsendQueryPreparedDescribed"/> is like <xref
+       linkend="libpq-PQsendQueryPrepared"/> with additional support for encrypted
+       columns.  The parameter <parameter>paramDesc</parameter> must be a
+       result set obtained from <xref linkend="libpq-PQsendDescribePrepared"/> on
+       the same prepared statement.
+      </para>
+
+      <para>
+       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.
+       See also under <xref linkend="libpq-PQexecPreparedDescribed"/>.
+      </para>
+     </listitem>
+    </varlistentry>
+
     <varlistentry id="libpq-PQsendDescribePrepared">
      <term><function>PQsendDescribePrepared</function><indexterm><primary>PQsendDescribePrepared</primary></indexterm></term>
 
@@ -4751,6 +5045,7 @@ <title>Asynchronous Command Processing</title>
        <xref linkend="libpq-PQsendQueryParams"/>,
        <xref linkend="libpq-PQsendPrepare"/>,
        <xref linkend="libpq-PQsendQueryPrepared"/>,
+       <xref linkend="libpq-PQsendQueryPreparedDescribed"/>,
        <xref linkend="libpq-PQsendDescribePrepared"/>,
        <xref linkend="libpq-PQsendDescribePortal"/>, or
        <xref linkend="libpq-PQpipelineSync"/>
@@ -7784,6 +8079,26 @@ <title>Environment Variables</title>
      </para>
     </listitem>
 
+    <listitem>
+     <para>
+      <indexterm>
+       <primary><envar>PGCMKLOOKUP</envar></primary>
+      </indexterm>
+      <envar>PGCMKLOOKUP</envar> behaves the same as the <xref
+      linkend="libpq-connect-cmklookup"/> connection parameter.
+     </para>
+    </listitem>
+
+    <listitem>
+     <para>
+      <indexterm>
+       <primary><envar>PGCOLUMNENCRYPTION</envar></primary>
+      </indexterm>
+      <envar>PGCOLUMNENCRYPTION</envar> behaves the same as the <xref
+      linkend="libpq-connect-column-encryption"/> connection parameter.
+     </para>
+    </listitem>
+
     <listitem>
      <para>
       <indexterm>
diff --git a/doc/src/sgml/protocol.sgml b/doc/src/sgml/protocol.sgml
index 93fc7167d4..c888811ad0 100644
--- a/doc/src/sgml/protocol.sgml
+++ b/doc/src/sgml/protocol.sgml
@@ -1109,6 +1109,75 @@ <title>Pipelining</title>
    </para>
   </sect2>
 
+  <sect2 id="protocol-flow-transparent-column-encryption">
+   <title>Transparent Column Encryption</title>
+
+   <para>
+    Transparent column encryption is enabled by sending the parameter
+    <literal>_pq_.column_encryption</literal> with a value of
+    <literal>1</literal> in the StartupMessage.  This is a protocol extension
+    that enables a few additional protocol messages and adds additional fields
+    to existing protocol messages.  Client drivers should only activate this
+    protocol extension when requested by the user, for example through a
+    connection parameter.
+   </para>
+
+   <para>
+    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 an ID
+    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.)
+   </para>
+
+   <para>
+    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.
+   </para>
+
+   <para>
+    When column encryption is used, format specifications (text/binary) in the
+    various protocol messages apply to the ciphertext.  The plaintext inside
+    the ciphertext is always in text format, but this is invisible to the
+    protocol.  Even though the ciphertext could in theory be sent in either
+    text or binary format, the server will always send it in binary if the
+    column encryption protocol option is enabled.  That way, a client library
+    only needs to support decrypting data sent in binary and does not have to
+    support decoding the text format of the encryption-related types (see
+    <xref linkend="datatype-encrypted"/>).
+   </para>
+
+   <para>
+    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 <literal>100</literal> and
+    an integer represented by the string <literal>+100</literal> would result
+    in two different ciphertexts, thus defeating the main point of
+    deterministic encryption.  This protocol specification requires the
+    plaintext to be in <quote>canonical</quote> form, which is the form that
+    is produced by the server when it outputs a particular value in text
+    format.
+   </para>
+
+   <para>
+    When transparent column encryption is enabled, the client encoding must
+    match the server encoding.  This ensures that all values encrypted or
+    decrypted by the client match the server encoding.
+   </para>
+
+   <para>
+    The cryptographic operations used for transparent column encryption are
+    described in <xref linkend="protocol-transparent-column-encryption-crypto"/>.
+   </para>
+  </sect2>
+
   <sect2 id="protocol-flow-function-call">
    <title>Function Call</title>
 
@@ -4061,6 +4130,140 @@ <title>Message Formats</title>
     </listitem>
    </varlistentry>
 
+   <varlistentry id="protocol-message-formats-ColumnEncryptionKey">
+    <term>ColumnEncryptionKey (B)</term>
+    <listitem>
+     <para>
+      This message can only appear if the protocol extension
+      <literal>_pq_.column_encryption</literal> is enabled.  (See <xref
+      linkend="protocol-flow-transparent-column-encryption"/>.)
+     </para>
+
+     <variablelist>
+      <varlistentry>
+       <term>Byte1('Y')</term>
+       <listitem>
+        <para>
+         Identifies the message as a column encryption key message.
+        </para>
+       </listitem>
+      </varlistentry>
+
+      <varlistentry>
+       <term>Int32</term>
+       <listitem>
+        <para>
+         Length of message contents in bytes, including self.
+        </para>
+       </listitem>
+      </varlistentry>
+
+      <varlistentry>
+       <term>Int32</term>
+       <listitem>
+        <para>
+         The session-specific identifier of the key.
+        </para>
+       </listitem>
+      </varlistentry>
+
+      <varlistentry>
+       <term>Int32</term>
+       <listitem>
+        <para>
+         The identifier of the master key used to encrypt this key.
+        </para>
+       </listitem>
+      </varlistentry>
+
+      <varlistentry>
+       <term>Int32</term>
+       <listitem>
+        <para>
+         The identifier of the algorithm used to encrypt this key.
+        </para>
+       </listitem>
+      </varlistentry>
+
+      <varlistentry>
+       <term>Int32</term>
+       <listitem>
+        <para>
+         The length of the following key material.
+        </para>
+       </listitem>
+      </varlistentry>
+
+      <varlistentry>
+       <term>Byte<replaceable>n</replaceable></term>
+       <listitem>
+        <para>
+         The key material, encrypted with the master key referenced above.
+        </para>
+       </listitem>
+      </varlistentry>
+     </variablelist>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry id="protocol-message-formats-ColumnMasterKey">
+    <term>ColumnMasterKey (B)</term>
+    <listitem>
+     <para>
+      This message can only appear if the protocol extension
+      <literal>_pq_.column_encryption</literal> is enabled.  (See <xref
+      linkend="protocol-flow-transparent-column-encryption"/>.)
+     </para>
+
+     <variablelist>
+      <varlistentry>
+       <term>Byte1('y')</term>
+       <listitem>
+        <para>
+         Identifies the message as a column master key message.
+        </para>
+       </listitem>
+      </varlistentry>
+
+      <varlistentry>
+       <term>Int32</term>
+       <listitem>
+        <para>
+         Length of message contents in bytes, including self.
+        </para>
+       </listitem>
+      </varlistentry>
+
+      <varlistentry>
+       <term>Int32</term>
+       <listitem>
+        <para>
+         The session-specific identifier of the key.
+        </para>
+       </listitem>
+      </varlistentry>
+
+      <varlistentry>
+       <term>String</term>
+       <listitem>
+        <para>
+         The name of the key.
+        </para>
+       </listitem>
+      </varlistentry>
+
+      <varlistentry>
+       <term>String</term>
+       <listitem>
+        <para>
+         The key's realm.
+        </para>
+       </listitem>
+      </varlistentry>
+     </variablelist>
+    </listitem>
+   </varlistentry>
+
    <varlistentry id="protocol-message-formats-CommandComplete">
     <term>CommandComplete (B)</term>
     <listitem>
@@ -5157,6 +5360,46 @@ <title>Message Formats</title>
        </listitem>
       </varlistentry>
      </variablelist>
+
+     <para>
+      If the protocol extension <literal>_pq_.column_encryption</literal> is
+      enabled (see <xref
+      linkend="protocol-flow-transparent-column-encryption"/>), then there is
+      also the following for each parameter:
+     </para>
+
+     <variablelist>
+      <varlistentry>
+       <term>Int32</term>
+       <listitem>
+        <para>
+         If this parameter is to be encrypted, this specifies the
+         identifier of the column encryption key to use, else zero.
+        </para>
+       </listitem>
+      </varlistentry>
+
+      <varlistentry>
+       <term>Int32</term>
+       <listitem>
+        <para>
+         If this parameter is to be encrypted, this specifies the
+         identifier of the encryption algorithm, else zero.
+        </para>
+       </listitem>
+      </varlistentry>
+
+      <varlistentry>
+       <term>Int16</term>
+       <listitem>
+        <para>
+         This is used as a bit field of flags.  If the column is
+         encrypted and bit 0x01 is set, the column uses deterministic
+         encryption, otherwise randomized encryption.
+        </para>
+       </listitem>
+      </varlistentry>
+     </variablelist>
     </listitem>
    </varlistentry>
 
@@ -5545,6 +5788,35 @@ <title>Message Formats</title>
        </listitem>
       </varlistentry>
      </variablelist>
+
+     <para>
+      If the protocol extension <literal>_pq_.column_encryption</literal> is
+      enabled (see <xref
+      linkend="protocol-flow-transparent-column-encryption"/>), then there is
+      also the following for each field:
+     </para>
+
+     <variablelist>
+      <varlistentry>
+       <term>Int32</term>
+       <listitem>
+        <para>
+         If the field is encrypted, this specifies the identifier of the
+         column encryption key to use, else zero.
+        </para>
+       </listitem>
+      </varlistentry>
+
+      <varlistentry>
+       <term>Int32</term>
+       <listitem>
+        <para>
+         If the field is encrypted, this specifies the identifier of the
+         encryption algorithm, else zero.
+        </para>
+       </listitem>
+      </varlistentry>
+     </variablelist>
     </listitem>
    </varlistentry>
 
@@ -7370,6 +7642,176 @@ <title>Logical Replication Message Formats</title>
   </variablelist>
  </sect1>
 
+ <sect1 id="protocol-transparent-column-encryption-crypto">
+  <title>Transparent Column Encryption Cryptography</title>
+
+  <para>
+   This section describes the cryptographic operations used by transparent
+   column encryption.  A client that supports transparent column encryption
+   needs to implement these operations as specified here in order to be able
+   to interoperate with other clients.
+  </para>
+
+  <para>
+   Column encryption key algorithms and column master key algorithms are
+   identified by integers in the protocol messages and the system catalogs.
+   Additional algorithms may be added to this protocol specification without a
+   change in the protocol version number.  Clients should implement support
+   for all the algorithms specified here.  If a client encounters an algorithm
+   identifier it does not recognize or does not support, it must raise an
+   error.  A suitable error message should be provided to the application or
+   user.
+  </para>
+
+  <sect2 id="protocol-cmk">
+   <title>Column Master Keys</title>
+
+   <para>
+    The currently defined algorithms for column master keys are listed in
+    <xref linkend="protocol-cmk-table"/>.
+   </para>
+
+   <!-- see also src/include/common/colenc.h -->
+
+   <table id="protocol-cmk-table">
+    <title>Column Master Key Algorithms</title>
+    <tgroup cols="4">
+     <thead>
+      <row>
+       <entry>PostgreSQL ID</entry>
+       <entry>Name</entry>
+       <entry>JWA (<ulink url="https://tools.ietf.org/html/rfc7518">RFC 7518</ulink>) name</entry>
+       <entry>Description</entry>
+      </row>
+     </thead>
+     <tbody>
+      <row>
+       <entry>1</entry>
+       <entry><literal>unspecified</literal></entry>
+       <entry>(none)</entry>
+       <entry>interpreted by client</entry>
+      </row>
+      <row>
+       <entry>2</entry>
+       <entry><literal>RSAES_OAEP_SHA_1</literal></entry>
+       <entry><literal>RSA-OAEP</literal></entry>
+       <entry>RSAES OAEP using default parameters (<ulink
+       url="https://tools.ietf.org/html/rfc8017">RFC 8017</ulink>/PKCS #1)</entry>
+      </row>
+      <row>
+       <entry>3</entry>
+       <entry><literal>RSAES_OAEP_SHA_256</literal></entry>
+       <entry><literal>RSA-OAEP-256</literal></entry>
+       <entry>RSAES OAEP using SHA-256 and MGF1 with SHA-256 (<ulink
+       url="https://tools.ietf.org/html/rfc8017">RFC
+       8017</ulink>/PKCS #1)</entry>
+      </row>
+     </tbody>
+    </tgroup>
+   </table>
+  </sect2>
+
+  <sect2 id="protocol-cek">
+   <title>Column Encryption Keys</title>
+
+   <para>
+    The currently defined algorithms for column encryption keys are listed in
+    <xref linkend="protocol-cek-table"/>.
+   </para>
+
+   <!-- see also src/include/common/colenc.h -->
+
+   <para>
+    The key material of a column encryption key consists of three components,
+    concatenated in this order: the MAC key, the encryption key, and the IV
+    key.  <xref linkend="protocol-cek-table"/> shows the total length that a
+    key generated for each algorithm is required to have.  The MAC key and the
+    encryption key are used by the referenced encryption algorithms; see there
+    for details.  The IV key is used for computing the static initialization
+    vector for deterministic encryption; it is unused for randomized
+    encryption.
+   </para>
+
+   <!-- see also https://datatracker.ietf.org/doc/html/draft-mcgrew-aead-aes-cbc-hmac-sha2-05#section-2.8 -->
+   <table id="protocol-cek-table">
+    <title>Column Encryption Key Algorithms</title>
+    <tgroup cols="7">
+     <thead>
+      <row>
+       <entry>PostgreSQL ID</entry>
+       <entry>Name</entry>
+       <entry>Description</entry>
+       <entry>MAC key length (octets)</entry>
+       <entry>Encryption key length (octets)</entry>
+       <entry>IV key length (octets)</entry>
+       <entry>Total key length (octets)</entry>
+      </row>
+     </thead>
+     <tbody>
+      <row>
+       <entry>32768</entry>
+       <entry><literal>AEAD_AES_128_CBC_HMAC_SHA_256</literal></entry>
+       <entry><ulink url="https://datatracker.ietf.org/doc/html/draft-mcgrew-aead-aes-cbc-hmac-sha2-05"/></entry>
+       <entry>16</entry>
+       <entry>16</entry>
+       <entry>16</entry>
+       <entry>48</entry>
+      </row>
+      <row>
+       <entry>32769</entry>
+       <entry><literal>AEAD_AES_192_CBC_HMAC_SHA_384</literal></entry>
+       <entry><ulink url="https://datatracker.ietf.org/doc/html/draft-mcgrew-aead-aes-cbc-hmac-sha2-05"/></entry>
+       <entry>24</entry>
+       <entry>24</entry>
+       <entry>24</entry>
+       <entry>72</entry>
+      </row>
+      <row>
+       <entry>32770</entry>
+       <entry><literal>AEAD_AES_256_CBC_HMAC_SHA_384</literal></entry>
+       <entry><ulink url="https://datatracker.ietf.org/doc/html/draft-mcgrew-aead-aes-cbc-hmac-sha2-05"/></entry>
+       <entry>24</entry>
+       <entry>32</entry>
+       <entry>24</entry>
+       <entry>90</entry>
+      </row>
+      <row>
+       <entry>32771</entry>
+       <entry><literal>AEAD_AES_256_CBC_HMAC_SHA_512</literal></entry>
+       <entry><ulink url="https://datatracker.ietf.org/doc/html/draft-mcgrew-aead-aes-cbc-hmac-sha2-05"/></entry>
+       <entry>32</entry>
+       <entry>32</entry>
+       <entry>32</entry>
+       <entry>96</entry>
+      </row>
+     </tbody>
+    </tgroup>
+   </table>
+
+   <para>
+    The <quote>associated data</quote> in these algorithms consists of 4
+    bytes: The ASCII letters <literal>P</literal> and <literal>G</literal>
+    (byte values 80 and 71), followed by the version number as a 16-bit
+    unsigned integer in network byte order.  The version number is currently
+    always 1.  (This is intended to allow for possible incompatible changes or
+    extensions in the future.)
+   </para>
+
+   <para>
+    The length of the initialization vector is 16 octets for all CEK algorithm
+    variants.  For randomized encryption, the initialization vector should be
+    (cryptographically strong) random bytes.  For deterministic encryption,
+    the initialization vector is constructed as
+<programlisting>
+SUBSTRING(<replaceable>HMAC</replaceable>(<replaceable>K</replaceable>, <replaceable>P</replaceable>) FOR <replaceable>IVLEN</replaceable>)
+</programlisting>
+    where <replaceable>HMAC</replaceable> is the HMAC function associated with
+    the algorithm, <replaceable>K</replaceable> is the IV key, and
+    <replaceable>P</replaceable> is the plaintext to be encrypted.
+   </para>
+  </sect2>
+ </sect1>
+
  <sect1 id="protocol-changes">
   <title>Summary of Changes since Protocol 2.0</title>
 
diff --git a/doc/src/sgml/ref/allfiles.sgml b/doc/src/sgml/ref/allfiles.sgml
index e90a0e1f83..331b1f010b 100644
--- a/doc/src/sgml/ref/allfiles.sgml
+++ b/doc/src/sgml/ref/allfiles.sgml
@@ -8,6 +8,8 @@
 <!ENTITY abort              SYSTEM "abort.sgml">
 <!ENTITY alterAggregate     SYSTEM "alter_aggregate.sgml">
 <!ENTITY alterCollation     SYSTEM "alter_collation.sgml">
+<!ENTITY alterColumnEncryptionKey SYSTEM "alter_column_encryption_key.sgml">
+<!ENTITY alterColumnMasterKey SYSTEM "alter_column_master_key.sgml">
 <!ENTITY alterConversion    SYSTEM "alter_conversion.sgml">
 <!ENTITY alterDatabase      SYSTEM "alter_database.sgml">
 <!ENTITY alterDefaultPrivileges SYSTEM "alter_default_privileges.sgml">
@@ -62,6 +64,8 @@
 <!ENTITY createAggregate    SYSTEM "create_aggregate.sgml">
 <!ENTITY createCast         SYSTEM "create_cast.sgml">
 <!ENTITY createCollation    SYSTEM "create_collation.sgml">
+<!ENTITY createColumnEncryptionKey SYSTEM "create_column_encryption_key.sgml">
+<!ENTITY createColumnMasterKey SYSTEM "create_column_master_key.sgml">
 <!ENTITY createConversion   SYSTEM "create_conversion.sgml">
 <!ENTITY createDatabase     SYSTEM "create_database.sgml">
 <!ENTITY createDomain       SYSTEM "create_domain.sgml">
@@ -109,6 +113,8 @@
 <!ENTITY dropAggregate      SYSTEM "drop_aggregate.sgml">
 <!ENTITY dropCast           SYSTEM "drop_cast.sgml">
 <!ENTITY dropCollation      SYSTEM "drop_collation.sgml">
+<!ENTITY dropColumnEncryptionKey SYSTEM "drop_column_encryption_key.sgml">
+<!ENTITY dropColumnMasterKey SYSTEM "drop_column_master_key.sgml">
 <!ENTITY dropConversion     SYSTEM "drop_conversion.sgml">
 <!ENTITY dropDatabase       SYSTEM "drop_database.sgml">
 <!ENTITY dropDomain         SYSTEM "drop_domain.sgml">
diff --git a/doc/src/sgml/ref/alter_column_encryption_key.sgml b/doc/src/sgml/ref/alter_column_encryption_key.sgml
new file mode 100644
index 0000000000..655e1e00d8
--- /dev/null
+++ b/doc/src/sgml/ref/alter_column_encryption_key.sgml
@@ -0,0 +1,197 @@
+<!--
+doc/src/sgml/ref/alter_column_encryption_key.sgml
+PostgreSQL documentation
+-->
+
+<refentry id="sql-alter-column-encryption-key">
+ <indexterm zone="sql-alter-column-encryption-key">
+  <primary>ALTER COLUMN ENCRYPTION KEY</primary>
+ </indexterm>
+
+ <refmeta>
+  <refentrytitle>ALTER COLUMN ENCRYPTION KEY</refentrytitle>
+  <manvolnum>7</manvolnum>
+  <refmiscinfo>SQL - Language Statements</refmiscinfo>
+ </refmeta>
+
+ <refnamediv>
+  <refname>ALTER COLUMN ENCRYPTION KEY</refname>
+  <refpurpose>change the definition of a column encryption key</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+<synopsis>
+ALTER COLUMN ENCRYPTION KEY <replaceable>name</replaceable> ADD VALUE (
+    COLUMN_MASTER_KEY = <replaceable>cmk</replaceable>,
+    [ ALGORITHM = <replaceable>algorithm</replaceable>, ]
+    ENCRYPTED_VALUE = <replaceable>encval</replaceable>
+)
+
+ALTER COLUMN ENCRYPTION KEY <replaceable>name</replaceable> DROP VALUE (
+    COLUMN_MASTER_KEY = <replaceable>cmk</replaceable>
+)
+
+ALTER COLUMN ENCRYPTION KEY <replaceable>name</replaceable> RENAME TO <replaceable>new_name</replaceable>
+ALTER COLUMN ENCRYPTION KEY <replaceable>name</replaceable> OWNER TO { <replaceable>new_owner</replaceable> | CURRENT_ROLE | CURRENT_USER | SESSION_USER }
+ALTER COLUMN ENCRYPTION KEY <replaceable>name</replaceable> SET SCHEMA <replaceable>new_schema</replaceable>
+</synopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+  <title>Description</title>
+
+  <para>
+   <command>ALTER COLUMN ENCRYPTION KEY</command> changes the definition of a
+   column encryption key.
+  </para>
+
+  <para>
+   The first form adds new encrypted key data to a column encryption key,
+   which must be encrypted with a different column master key than the
+   existing key data.  The second form removes a key data entry for a given
+   column master key.  Together, these forms can be used for column master key
+   rotation.
+  </para>
+
+  <para>
+   You must own the column encryption key to use <command>ALTER COLUMN
+   ENCRYPTION KEY</command>.  To alter the owner, you must also be a direct or
+   indirect member of the new owning role, and that role must have
+   <literal>CREATE</literal> privilege on the column encryption key's
+   schema.  (These restrictions enforce that altering the owner doesn't do
+   anything you couldn't do by dropping and recreating the column encryption
+   key.  However, a superuser can alter ownership of any column encryption key
+   anyway.)
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Parameters</title>
+
+  <variablelist>
+   <varlistentry>
+    <term><replaceable class="parameter">name</replaceable></term>
+    <listitem>
+     <para>
+      The name (optionally schema-qualified) of an existing column encryption
+      key.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable>cmk</replaceable></term>
+
+    <listitem>
+     <para>
+      The name of the column master key that was used to encrypt this column
+      encryption key.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable>algorithm</replaceable></term>
+
+    <listitem>
+     <para>
+      The encryption algorithm that was used to encrypt the key material of
+      this column encryption key.  See <xref
+      linkend="sql-create-column-encryption-key"/> for details
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable>encval</replaceable></term>
+
+    <listitem>
+     <para>
+      The key material of this column encryption key, encrypted with the
+      specified column master key using the specified algorithm.  The value
+      must be a <type>bytea</type>-compatible literal.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable class="parameter">new_name</replaceable></term>
+    <listitem>
+     <para>
+      The new name of the column encryption key.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable class="parameter">new_owner</replaceable></term>
+    <listitem>
+     <para>
+      The new owner of the column encryption key.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable class="parameter">new_schema</replaceable></term>
+    <listitem>
+     <para>
+      The new schema for the column encryption key.
+     </para>
+    </listitem>
+   </varlistentry>
+  </variablelist>
+ </refsect1>
+
+ <refsect1>
+  <title>Examples</title>
+
+  <para>
+   To rotate the master keys used to encrypt a given column encryption key,
+   use a command sequence like this:
+<programlisting>
+ALTER COLUMN ENCRYPTION KEY cek1 ADD VALUE (
+    COLUMN_MASTER_KEY = cmk2,
+    ENCRYPTED_VALUE = '\x01020204...'
+);
+
+ALTER COLUMN ENCRYPTION KEY cek1 DROP VALUE (
+    COLUMN_MASTER_KEY = cmk1
+);
+</programlisting>
+  </para>
+
+  <para>
+   To rename the column encryption key <literal>cek1</literal> to
+   <literal>cek2</literal>:
+<programlisting>
+ALTER COLUMN ENCRYPTION KEY cek1 RENAME TO cek2;
+</programlisting>
+  </para>
+
+  <para>
+   To change the owner of the column encryption key <literal>cek1</literal> to
+   <literal>joe</literal>:
+<programlisting>
+ALTER COLUMN ENCRYPTION KEY cek1 OWNER TO joe;
+</programlisting></para>
+ </refsect1>
+
+ <refsect1>
+  <title>Compatibility</title>
+
+  <para>
+   There is no <command>ALTER COLUMN ENCRYPTION KEY</command> statement in the
+   SQL standard.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>See Also</title>
+
+  <simplelist type="inline">
+   <member><xref linkend="sql-create-column-encryption-key"/></member>
+   <member><xref linkend="sql-drop-column-encryption-key"/></member>
+  </simplelist>
+ </refsect1>
+</refentry>
diff --git a/doc/src/sgml/ref/alter_column_master_key.sgml b/doc/src/sgml/ref/alter_column_master_key.sgml
new file mode 100644
index 0000000000..7f0e656ef0
--- /dev/null
+++ b/doc/src/sgml/ref/alter_column_master_key.sgml
@@ -0,0 +1,134 @@
+<!--
+doc/src/sgml/ref/alter_column_master_key.sgml
+PostgreSQL documentation
+-->
+
+<refentry id="sql-alter-column-master-key">
+ <indexterm zone="sql-alter-column-master-key">
+  <primary>ALTER COLUMN MASTER KEY</primary>
+ </indexterm>
+
+ <refmeta>
+  <refentrytitle>ALTER COLUMN MASTER KEY</refentrytitle>
+  <manvolnum>7</manvolnum>
+  <refmiscinfo>SQL - Language Statements</refmiscinfo>
+ </refmeta>
+
+ <refnamediv>
+  <refname>ALTER COLUMN MASTER KEY</refname>
+  <refpurpose>change the definition of a column master key</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+<synopsis>
+ALTER COLUMN MASTER KEY <replaceable>name</replaceable> ( REALM = <replaceable>realm</replaceable> )
+
+ALTER COLUMN MASTER KEY <replaceable>name</replaceable> RENAME TO <replaceable>new_name</replaceable>
+ALTER COLUMN MASTER KEY <replaceable>name</replaceable> OWNER TO { <replaceable>new_owner</replaceable> | CURRENT_ROLE | CURRENT_USER | SESSION_USER }
+ALTER COLUMN MASTER KEY <replaceable>name</replaceable> SET SCHEMA <replaceable>new_schema</replaceable>
+</synopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+  <title>Description</title>
+
+  <para>
+   <command>ALTER COLUMN MASTER KEY</command> changes the definition of a
+   column master key.
+  </para>
+
+  <para>
+   The first form changes the parameters of a column master key.  See <xref
+   linkend="sql-create-column-master-key"/> for details.
+  </para>
+
+  <para>
+   You must own the column master key to use <command>ALTER COLUMN MASTER
+   KEY</command>.  To alter the owner, you must also be a direct or indirect
+   member of the new owning role, and that role must have
+   <literal>CREATE</literal> privilege on the column master key's schema.
+   (These restrictions enforce that altering the owner doesn't do anything you
+   couldn't do by dropping and recreating the column master key.  However, a
+   superuser can alter ownership of any column master key anyway.)
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Parameters</title>
+
+  <variablelist>
+   <varlistentry>
+    <term><replaceable class="parameter">name</replaceable></term>
+    <listitem>
+     <para>
+      The name (optionally schema-qualified) of an existing column master key.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable class="parameter">new_name</replaceable></term>
+    <listitem>
+     <para>
+      The new name of the column master key.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable class="parameter">new_owner</replaceable></term>
+    <listitem>
+     <para>
+      The new owner of the column master key.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable class="parameter">new_schema</replaceable></term>
+    <listitem>
+     <para>
+      The new schema for the column master key.
+     </para>
+    </listitem>
+   </varlistentry>
+  </variablelist>
+ </refsect1>
+
+ <refsect1>
+  <title>Examples</title>
+
+  <para>
+   To rename the column master key <literal>cmk1</literal> to
+   <literal>cmk2</literal>:
+<programlisting>
+ALTER COLUMN MASTER KEY cmk1 RENAME TO cmk2;
+</programlisting>
+  </para>
+
+  <para>
+   To change the owner of the column master key <literal>cmk1</literal> to
+   <literal>joe</literal>:
+<programlisting>
+ALTER COLUMN MASTER KEY cmk1 OWNER TO joe;
+</programlisting></para>
+ </refsect1>
+
+ <refsect1>
+  <title>Compatibility</title>
+
+  <para>
+   There is no <command>ALTER COLUMN MASTER KEY</command> statement in the
+   SQL standard.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>See Also</title>
+
+  <simplelist type="inline">
+   <member><xref linkend="sql-create-column-master-key"/></member>
+   <member><xref linkend="sql-drop-column-master-key"/></member>
+  </simplelist>
+ </refsect1>
+</refentry>
diff --git a/doc/src/sgml/ref/comment.sgml b/doc/src/sgml/ref/comment.sgml
index 7499da1d62..995486baf2 100644
--- a/doc/src/sgml/ref/comment.sgml
+++ b/doc/src/sgml/ref/comment.sgml
@@ -28,6 +28,8 @@
   CAST (<replaceable>source_type</replaceable> AS <replaceable>target_type</replaceable>) |
   COLLATION <replaceable class="parameter">object_name</replaceable> |
   COLUMN <replaceable class="parameter">relation_name</replaceable>.<replaceable class="parameter">column_name</replaceable> |
+  COLUMN ENCRYPTION KEY <replaceable class="parameter">object_name</replaceable> |
+  COLUMN MASTER KEY <replaceable class="parameter">object_name</replaceable> |
   CONSTRAINT <replaceable class="parameter">constraint_name</replaceable> ON <replaceable class="parameter">table_name</replaceable> |
   CONSTRAINT <replaceable class="parameter">constraint_name</replaceable> ON DOMAIN <replaceable class="parameter">domain_name</replaceable> |
   CONVERSION <replaceable class="parameter">object_name</replaceable> |
diff --git a/doc/src/sgml/ref/copy.sgml b/doc/src/sgml/ref/copy.sgml
index c25b52d0cb..8c461b8f15 100644
--- a/doc/src/sgml/ref/copy.sgml
+++ b/doc/src/sgml/ref/copy.sgml
@@ -555,6 +555,15 @@ <title>Notes</title>
     null strings to null values and unquoted null strings to empty strings.
    </para>
 
+   <para>
+    <command>COPY</command> does not support transparent column encryption or
+    decryption; its input or output data will always be the ciphertext.  This
+    is usually suitable for backups (see also <xref linkend="app-pgdump"/>).
+    If transparent encryption or decryption is wanted,
+    <command>INSERT</command> and <command>SELECT</command> need to be used
+    instead to write and read the data.
+   </para>
+
  </refsect1>
 
  <refsect1>
diff --git a/doc/src/sgml/ref/create_column_encryption_key.sgml b/doc/src/sgml/ref/create_column_encryption_key.sgml
new file mode 100644
index 0000000000..e10b61267e
--- /dev/null
+++ b/doc/src/sgml/ref/create_column_encryption_key.sgml
@@ -0,0 +1,169 @@
+<!--
+doc/src/sgml/ref/create_column_encryption_key.sgml
+PostgreSQL documentation
+-->
+
+<refentry id="sql-create-column-encryption-key">
+ <indexterm zone="sql-create-column-encryption-key">
+  <primary>CREATE COLUMN ENCRYPTION KEY</primary>
+ </indexterm>
+
+ <refmeta>
+  <refentrytitle>CREATE COLUMN ENCRYPTION KEY</refentrytitle>
+  <manvolnum>7</manvolnum>
+  <refmiscinfo>SQL - Language Statements</refmiscinfo>
+ </refmeta>
+
+ <refnamediv>
+  <refname>CREATE COLUMN ENCRYPTION KEY</refname>
+  <refpurpose>define a new column encryption key</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+<synopsis>
+CREATE COLUMN ENCRYPTION KEY <replaceable>name</replaceable> WITH VALUES (
+    COLUMN_MASTER_KEY = <replaceable>cmk</replaceable>,
+    [ ALGORITHM = <replaceable>algorithm</replaceable>, ]
+    ENCRYPTED_VALUE = <replaceable>encval</replaceable>
+)
+[ , ... ]
+</synopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+  <title>Description</title>
+
+  <para>
+   <command>CREATE COLUMN ENCRYPTION KEY</command> defines a new column
+   encryption key.  A column encryption key is used for client-side encryption
+   of table columns that have been defined as encrypted.  The key material of
+   a column encryption key is stored in the database's system catalogs,
+   encrypted (wrapped) by a column master key (which in turn is only
+   accessible to the client, not the database server).
+  </para>
+
+  <para>
+   A column encryption key can be associated with more than one column master
+   key.  To specify that, specify more than one parenthesized definition (see
+   also the examples).
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Parameters</title>
+
+  <variablelist>
+   <varlistentry>
+    <term><replaceable>name</replaceable></term>
+
+    <listitem>
+     <para>
+      The name of the new column encryption key.  The name can be
+      schema-qualified.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable>cmk</replaceable></term>
+
+    <listitem>
+     <para>
+      The name of the column master key that was used to encrypt this column
+      encryption key.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable>algorithm</replaceable></term>
+
+    <listitem>
+     <para>
+      The encryption algorithm that was used to encrypt the key material of
+      this column encryption key.  Supported algorithms are:
+      <itemizedlist>
+       <listitem>
+        <para><literal>unspecified</literal></para>
+       </listitem>
+       <listitem>
+        <para><literal>RSAES_OAEP_SHA_1</literal></para>
+       </listitem>
+       <listitem>
+        <para><literal>RSAES_OAEP_SHA_256</literal></para>
+       </listitem>
+      </itemizedlist>
+     </para>
+
+     <para>
+      This is informational only.  The specified value is provided to the
+      client, which may use it for decrypting the column encryption key on the
+      client side.  But a client is also free to ignore this information and
+      figure out how to arrange the decryption in some other way.  In that
+      case, specifying the algorithm as <literal>unspecified</literal> would be
+      appropriate.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable>encval</replaceable></term>
+
+    <listitem>
+     <para>
+      The key material of this column encryption key, encrypted with the
+      specified column master key using the specified algorithm.  The value
+      must be a <type>bytea</type>-compatible literal.
+     </para>
+    </listitem>
+   </varlistentry>
+  </variablelist>
+ </refsect1>
+
+ <refsect1>
+  <title>Examples</title>
+
+  <para>
+<programlisting>
+CREATE COLUMN ENCRYPTION KEY cek1 WITH VALUES (
+    COLUMN_MASTER_KEY = cmk1,
+    ENCRYPTED_VALUE = '\x01020204...'
+);
+</programlisting>
+  </para>
+
+  <para>
+   To specify more than one associated column master key:
+<programlisting>
+CREATE COLUMN ENCRYPTION KEY cek1 WITH VALUES (
+    COLUMN_MASTER_KEY = cmk1,
+    ENCRYPTED_VALUE = '\x01020204...'
+),
+(
+    COLUMN_MASTER_KEY = cmk2,
+    ENCRYPTED_VALUE = '\xF1F2F2F4...'
+);
+</programlisting>
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Compatibility</title>
+
+  <para>
+   There is no <command>CREATE COLUMN ENCRYPTION KEY</command> statement in
+   the SQL standard.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>See Also</title>
+
+  <simplelist type="inline">
+   <member><xref linkend="sql-alter-column-encryption-key"/></member>
+   <member><xref linkend="sql-drop-column-encryption-key"/></member>
+   <member><xref linkend="sql-create-column-master-key"/></member>
+  </simplelist>
+ </refsect1>
+
+</refentry>
diff --git a/doc/src/sgml/ref/create_column_master_key.sgml b/doc/src/sgml/ref/create_column_master_key.sgml
new file mode 100644
index 0000000000..6aaa1088d1
--- /dev/null
+++ b/doc/src/sgml/ref/create_column_master_key.sgml
@@ -0,0 +1,107 @@
+<!--
+doc/src/sgml/ref/create_column_master_key.sgml
+PostgreSQL documentation
+-->
+
+<refentry id="sql-create-column-master-key">
+ <indexterm zone="sql-create-column-master-key">
+  <primary>CREATE COLUMN MASTER KEY</primary>
+ </indexterm>
+
+ <refmeta>
+  <refentrytitle>CREATE COLUMN MASTER KEY</refentrytitle>
+  <manvolnum>7</manvolnum>
+  <refmiscinfo>SQL - Language Statements</refmiscinfo>
+ </refmeta>
+
+ <refnamediv>
+  <refname>CREATE COLUMN MASTER KEY</refname>
+  <refpurpose>define a new column master key</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+<synopsis>
+CREATE COLUMN MASTER KEY <replaceable>name</replaceable> [ WITH (
+    [ REALM = <replaceable>realm</replaceable> ]
+) ]
+</synopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+  <title>Description</title>
+
+  <para>
+   <command>CREATE COLUMN MASTER KEY</command> defines a new column master
+   key.  A column master key is used to encrypt column encryption keys, which
+   are the keys that actually encrypt the column data.  The key material of
+   the column master key is not stored in the database.  The definition of a
+   column master key records information that will allow a client to locate
+   the key material, for example in a file or in a key management system.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Parameters</title>
+
+  <variablelist>
+   <varlistentry>
+    <term><replaceable>name</replaceable></term>
+
+    <listitem>
+     <para>
+      The name of the new column master key.  The name can be
+      schema-qualified.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable>realm</replaceable></term>
+
+    <listitem>
+     <para>
+      This is an optional string that can be used to organize column master
+      keys into groups for lookup by clients.  The intent is that all column
+      master keys that are stored in the same system (file system location,
+      key management system, etc.) should be in the same realm.  A client
+      would then be configured to look up all keys in a given realm in a
+      certain way.  See the documentation of the respective client library for
+      further usage instructions.
+     </para>
+
+     <para>
+      The default is the empty string.
+     </para>
+    </listitem>
+   </varlistentry>
+  </variablelist>
+ </refsect1>
+
+ <refsect1>
+  <title>Examples</title>
+
+<programlisting>
+CREATE COLUMN MASTER KEY cmk1 (realm = 'myrealm');
+</programlisting>
+ </refsect1>
+
+ <refsect1>
+  <title>Compatibility</title>
+
+  <para>
+   There is no <command>CREATE COLUMN MASTER KEY</command> statement in
+   the SQL standard.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>See Also</title>
+
+  <simplelist type="inline">
+   <member><xref linkend="sql-alter-column-master-key"/></member>
+   <member><xref linkend="sql-drop-column-master-key"/></member>
+   <member><xref linkend="sql-create-column-encryption-key"/></member>
+  </simplelist>
+ </refsect1>
+
+</refentry>
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index a03dee4afe..6a9e28c2d7 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -22,7 +22,7 @@
  <refsynopsisdiv>
 <synopsis>
 CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXISTS ] <replaceable class="parameter">table_name</replaceable> ( [
-  { <replaceable class="parameter">column_name</replaceable> <replaceable class="parameter">data_type</replaceable> [ STORAGE { PLAIN | EXTERNAL | EXTENDED | MAIN | DEFAULT } ] [ COMPRESSION <replaceable>compression_method</replaceable> ] [ COLLATE <replaceable>collation</replaceable> ] [ <replaceable class="parameter">column_constraint</replaceable> [ ... ] ]
+  { <replaceable class="parameter">column_name</replaceable> <replaceable class="parameter">data_type</replaceable> [ ENCRYPTED WITH ( <replaceable>encryption_options</replaceable> ) ] [ STORAGE { PLAIN | EXTERNAL | EXTENDED | MAIN | DEFAULT } ] [ COMPRESSION <replaceable>compression_method</replaceable> ] [ COLLATE <replaceable>collation</replaceable> ] [ <replaceable class="parameter">column_constraint</replaceable> [ ... ] ]
     | <replaceable>table_constraint</replaceable>
     | LIKE <replaceable>source_table</replaceable> [ <replaceable>like_option</replaceable> ... ] }
     [, ... ]
@@ -87,7 +87,7 @@
 
 <phrase>and <replaceable class="parameter">like_option</replaceable> is:</phrase>
 
-{ INCLUDING | EXCLUDING } { COMMENTS | COMPRESSION | CONSTRAINTS | DEFAULTS | GENERATED | IDENTITY | INDEXES | STATISTICS | STORAGE | ALL }
+{ INCLUDING | EXCLUDING } { COMMENTS | COMPRESSION | CONSTRAINTS | DEFAULTS | ENCRYPTED | GENERATED | IDENTITY | INDEXES | STATISTICS | STORAGE | ALL }
 
 <phrase>and <replaceable class="parameter">partition_bound_spec</replaceable> is:</phrase>
 
@@ -351,6 +351,46 @@ <title>Parameters</title>
     </listitem>
    </varlistentry>
 
+   <varlistentry id="sql-createtable-parms-encrypted">
+    <term><literal>ENCRYPTED WITH ( <replaceable>encryption_options</replaceable> )</literal></term>
+    <listitem>
+     <para>
+      Enables transparent column encryption for the column.
+      <replaceable>encryption_options</replaceable> are comma-separated
+      <literal>key=value</literal> specifications.  The following options are
+      available:
+      <variablelist>
+       <varlistentry>
+        <term><literal>column_encryption_key</literal></term>
+        <listitem>
+         <para>
+          Specifies the name of the column encryption key to use.  Specifying
+          this is mandatory.
+         </para>
+        </listitem>
+       </varlistentry>
+       <varlistentry>
+        <term><literal>encryption_type</literal></term>
+        <listitem>
+         <para>
+          <literal>randomized</literal> (the default) or <literal>deterministic</literal>
+         </para>
+        </listitem>
+       </varlistentry>
+       <varlistentry>
+        <term><literal>algorithm</literal></term>
+        <listitem>
+         <para>
+          The encryption algorithm to use.  The default is
+          <literal>AEAD_AES_128_CBC_HMAC_SHA_256</literal>.
+         </para>
+        </listitem>
+       </varlistentry>
+      </variablelist>
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry id="sql-createtable-parms-inherits">
     <term><literal>INHERITS ( <replaceable>parent_table</replaceable> [, ... ] )</literal></term>
     <listitem>
@@ -704,6 +744,16 @@ <title>Parameters</title>
         </listitem>
        </varlistentry>
 
+       <varlistentry id="sql-createtable-parms-like-opt-encrypted">
+        <term><literal>INCLUDING ENCRYPTED</literal></term>
+        <listitem>
+         <para>
+          Column encryption specifications for the copied column definitions
+          will be copied.  By default, new columns will be unencrypted.
+         </para>
+        </listitem>
+       </varlistentry>
+
        <varlistentry id="sql-createtable-parms-like-opt-generated">
         <term><literal>INCLUDING GENERATED</literal></term>
         <listitem>
diff --git a/doc/src/sgml/ref/drop_column_encryption_key.sgml b/doc/src/sgml/ref/drop_column_encryption_key.sgml
new file mode 100644
index 0000000000..f2ac1beb08
--- /dev/null
+++ b/doc/src/sgml/ref/drop_column_encryption_key.sgml
@@ -0,0 +1,112 @@
+<!--
+doc/src/sgml/ref/drop_column_encryption_key.sgml
+PostgreSQL documentation
+-->
+
+<refentry id="sql-drop-column-encryption-key">
+ <indexterm zone="sql-drop-column-encryption-key">
+  <primary>DROP COLUMN ENCRYPTION KEY</primary>
+ </indexterm>
+
+ <refmeta>
+  <refentrytitle>DROP COLUMN ENCRYPTION KEY</refentrytitle>
+  <manvolnum>7</manvolnum>
+  <refmiscinfo>SQL - Language Statements</refmiscinfo>
+ </refmeta>
+
+ <refnamediv>
+  <refname>DROP COLUMN ENCRYPTION KEY</refname>
+  <refpurpose>remove a column encryption key</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+<synopsis>
+DROP COLUMN ENCRYPTION KEY [ IF EXISTS ] <replaceable>name</replaceable> [ CASCADE | RESTRICT ]
+</synopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+  <title>Description</title>
+
+  <para>
+   <command>DROP COLUMN ENCRYPTION KEY</command> removes a previously defined
+   column encryption key.  To be able to drop a column encryption key, you
+   must be its owner.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Parameters</title>
+
+   <variablelist>
+    <varlistentry>
+     <term><literal>IF EXISTS</literal></term>
+     <listitem>
+      <para>
+       Do not throw an error if the column encryption key does not exist.
+       A notice is issued in this case.
+      </para>
+     </listitem>
+    </varlistentry>
+
+    <varlistentry>
+     <term><replaceable>name</replaceable></term>
+
+     <listitem>
+      <para>
+       The name (optionally schema-qualified) of the column encryption key.
+      </para>
+     </listitem>
+    </varlistentry>
+
+    <varlistentry>
+     <term><literal>CASCADE</literal></term>
+     <listitem>
+      <para>
+       Automatically drop objects that depend on the column encryption key,
+       and in turn all objects that depend on those objects
+       (see <xref linkend="ddl-depend"/>).
+      </para>
+     </listitem>
+    </varlistentry>
+
+    <varlistentry>
+     <term><literal>RESTRICT</literal></term>
+     <listitem>
+      <para>
+       Refuse to drop the column encryption key if any objects depend on it.  This
+       is the default.
+      </para>
+     </listitem>
+    </varlistentry>
+   </variablelist>
+ </refsect1>
+
+ <refsect1>
+  <title>Examples</title>
+
+  <para>
+<programlisting>
+DROP COLUMN ENCRYPTION KEY cek1;
+</programlisting></para>
+ </refsect1>
+
+ <refsect1>
+  <title>Compatibility</title>
+
+  <para>
+   There is no <command>DROP COLUMN ENCRYPTION KEY</command> statement in
+   the SQL standard.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>See Also</title>
+
+    <simplelist type="inline">
+   <member><xref linkend="sql-alter-column-encryption-key"/></member>
+   <member><xref linkend="sql-create-column-encryption-key"/></member>
+  </simplelist>
+ </refsect1>
+
+</refentry>
diff --git a/doc/src/sgml/ref/drop_column_master_key.sgml b/doc/src/sgml/ref/drop_column_master_key.sgml
new file mode 100644
index 0000000000..fae95e09d1
--- /dev/null
+++ b/doc/src/sgml/ref/drop_column_master_key.sgml
@@ -0,0 +1,112 @@
+<!--
+doc/src/sgml/ref/drop_column_master_key.sgml
+PostgreSQL documentation
+-->
+
+<refentry id="sql-drop-column-master-key">
+ <indexterm zone="sql-drop-column-master-key">
+  <primary>DROP COLUMN MASTER KEY</primary>
+ </indexterm>
+
+ <refmeta>
+  <refentrytitle>DROP COLUMN MASTER KEY</refentrytitle>
+  <manvolnum>7</manvolnum>
+  <refmiscinfo>SQL - Language Statements</refmiscinfo>
+ </refmeta>
+
+ <refnamediv>
+  <refname>DROP COLUMN MASTER KEY</refname>
+  <refpurpose>remove a column master key</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+<synopsis>
+DROP COLUMN MASTER KEY [ IF EXISTS ] <replaceable>name</replaceable> [ CASCADE | RESTRICT ]
+</synopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+  <title>Description</title>
+
+  <para>
+   <command>DROP COLUMN MASTER KEY</command> removes a previously defined
+   column master key.  To be able to drop a column master key, you
+   must be its owner.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Parameters</title>
+
+   <variablelist>
+    <varlistentry>
+     <term><literal>IF EXISTS</literal></term>
+     <listitem>
+      <para>
+       Do not throw an error if the column master key does not exist.
+       A notice is issued in this case.
+      </para>
+     </listitem>
+    </varlistentry>
+
+    <varlistentry>
+     <term><replaceable>name</replaceable></term>
+
+     <listitem>
+      <para>
+       The name (optionally schema-qualified) of the column master key.
+      </para>
+     </listitem>
+    </varlistentry>
+
+    <varlistentry>
+     <term><literal>CASCADE</literal></term>
+     <listitem>
+      <para>
+       Automatically drop objects that depend on the column master key,
+       and in turn all objects that depend on those objects
+       (see <xref linkend="ddl-depend"/>).
+      </para>
+     </listitem>
+    </varlistentry>
+
+    <varlistentry>
+     <term><literal>RESTRICT</literal></term>
+     <listitem>
+      <para>
+       Refuse to drop the column master key if any objects depend on it.  This
+       is the default.
+      </para>
+     </listitem>
+    </varlistentry>
+   </variablelist>
+ </refsect1>
+
+ <refsect1>
+  <title>Examples</title>
+
+  <para>
+<programlisting>
+DROP COLUMN MASTER KEY cek1;
+</programlisting></para>
+ </refsect1>
+
+ <refsect1>
+  <title>Compatibility</title>
+
+  <para>
+   There is no <command>DROP COLUMN MASTER KEY</command> statement in
+   the SQL standard.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>See Also</title>
+
+    <simplelist type="inline">
+   <member><xref linkend="sql-alter-column-master-key"/></member>
+   <member><xref linkend="sql-create-column-master-key"/></member>
+  </simplelist>
+ </refsect1>
+
+</refentry>
diff --git a/doc/src/sgml/ref/pg_dump.sgml b/doc/src/sgml/ref/pg_dump.sgml
index 2c938cd7e1..5b3cbf919c 100644
--- a/doc/src/sgml/ref/pg_dump.sgml
+++ b/doc/src/sgml/ref/pg_dump.sgml
@@ -715,6 +715,37 @@ <title>Options</title>
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>--decrypt-encrypted-columns</option></term>
+      <listitem>
+       <para>
+        This option causes the values of all encrypted columns to be decrypted
+        and written to the output in plaintext.  By default, the values of
+        encrypted columns are written to the dump in ciphertext (that is, they
+        are not decrypted).
+       </para>
+
+       <para>
+        This option requires that <option>--inserts</option>,
+        <option>--column-inserts</option> or
+        <option>--rows-per-insert</option> is also specified.
+        (<command>COPY</command> does not support column decryption.)
+       </para>
+
+       <para>
+        For routine backups, the default behavior is appropriate and most
+        efficient.  This option is suitable if the data is meant to be
+        inspected or exported for other purposes.  Note that a dump created
+        with this option cannot be restored into a database with column
+        encryption.
+       </para>
+       <!--
+           XXX: The latter would require another pg_dump option to put out
+           INSERT ... \bind ... \g commands.
+       -->
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>--disable-dollar-quoting</option></term>
       <listitem>
diff --git a/doc/src/sgml/ref/pg_dumpall.sgml b/doc/src/sgml/ref/pg_dumpall.sgml
index e62d05e5ab..4bf60c729f 100644
--- a/doc/src/sgml/ref/pg_dumpall.sgml
+++ b/doc/src/sgml/ref/pg_dumpall.sgml
@@ -259,6 +259,33 @@ <title>Options</title>
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>--decrypt-encrypted-columns</option></term>
+      <listitem>
+       <para>
+        This option causes the values of all encrypted columns to be decrypted
+        and written to the output in plaintext.  By default, the values of
+        encrypted columns are written to the dump in ciphertext (that is, they
+        are not decrypted).
+       </para>
+
+       <para>
+        This option requires that <option>--inserts</option>,
+        <option>--column-inserts</option> or
+        <option>--rows-per-insert</option> is also specified.
+        (<command>COPY</command> does not support column decryption.)
+       </para>
+
+       <para>
+        For routine backups, the default behavior is appropriate and most
+        efficient.  This option is suitable if the data is meant to be
+        inspected or exported for other purposes.  Note that a dump created
+        with this option cannot be restored into a database with column
+        encryption.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>--disable-dollar-quoting</option></term>
       <listitem>
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index dc6528dc11..e68b8440be 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -1420,6 +1420,34 @@ <title>Meta-Commands</title>
       </varlistentry>
 
 
+      <varlistentry id="app-psql-meta-command-dcek">
+        <term><literal>\dcek[+] [ <link linkend="app-psql-patterns"><replaceable class="parameter">pattern</replaceable></link> ]</literal></term>
+        <listitem>
+        <para>
+        Lists column encryption keys.  If <replaceable
+        class="parameter">pattern</replaceable> is specified, only column
+        encryption keys whose names match the pattern are listed.  If
+        <literal>+</literal> is appended to the command name, each object is
+        listed with its associated description.
+        </para>
+        </listitem>
+      </varlistentry>
+
+
+      <varlistentry id="app-psql-meta-command-dcmk">
+        <term><literal>\dcmk[+] [ <link linkend="app-psql-patterns"><replaceable class="parameter">pattern</replaceable></link> ]</literal></term>
+        <listitem>
+        <para>
+        Lists column master keys.  If <replaceable
+        class="parameter">pattern</replaceable> is specified, only column
+        master keys whose names match the pattern are listed.  If
+        <literal>+</literal> is appended to the command name, each object is
+        listed with its associated description.
+        </para>
+        </listitem>
+      </varlistentry>
+
+
       <varlistentry id="app-psql-meta-command-dconfig">
         <term><literal>\dconfig[+] [ <link linkend="app-psql-patterns"><replaceable class="parameter">pattern</replaceable></link> ]</literal></term>
         <listitem>
@@ -4026,6 +4054,17 @@ <title>Variables</title>
         </listitem>
       </varlistentry>
 
+      <varlistentry id="app-psql-variables-hide-column-encryption">
+        <term><varname>HIDE_COLUMN_ENCRYPTION</varname></term>
+        <listitem>
+        <para>
+         If this variable is set to <literal>true</literal>, column encryption
+         details are not displayed. This is mainly useful for regression
+         tests.
+        </para>
+        </listitem>
+      </varlistentry>
+
       <varlistentry id="app-psql-variables-hide-toast-compression">
         <term><varname>HIDE_TOAST_COMPRESSION</varname></term>
         <listitem>
diff --git a/doc/src/sgml/reference.sgml b/doc/src/sgml/reference.sgml
index a3b743e8c1..e1425c222f 100644
--- a/doc/src/sgml/reference.sgml
+++ b/doc/src/sgml/reference.sgml
@@ -36,6 +36,8 @@ <title>SQL Commands</title>
    &abort;
    &alterAggregate;
    &alterCollation;
+   &alterColumnEncryptionKey;
+   &alterColumnMasterKey;
    &alterConversion;
    &alterDatabase;
    &alterDefaultPrivileges;
@@ -90,6 +92,8 @@ <title>SQL Commands</title>
    &createAggregate;
    &createCast;
    &createCollation;
+   &createColumnEncryptionKey;
+   &createColumnMasterKey;
    &createConversion;
    &createDatabase;
    &createDomain;
@@ -137,6 +141,8 @@ <title>SQL Commands</title>
    &dropAggregate;
    &dropCast;
    &dropCollation;
+   &dropColumnEncryptionKey;
+   &dropColumnMasterKey;
    &dropConversion;
    &dropDatabase;
    &dropDomain;
diff --git a/src/backend/access/common/printsimple.c b/src/backend/access/common/printsimple.c
index ef818228ac..a00545080d 100644
--- a/src/backend/access/common/printsimple.c
+++ b/src/backend/access/common/printsimple.c
@@ -20,7 +20,9 @@
 
 #include "access/printsimple.h"
 #include "catalog/pg_type.h"
+#include "libpq/libpq-be.h"
 #include "libpq/pqformat.h"
+#include "miscadmin.h"
 #include "utils/builtins.h"
 
 /*
@@ -46,6 +48,11 @@ printsimple_startup(DestReceiver *self, int operation, TupleDesc tupdesc)
 		pq_sendint16(&buf, attr->attlen);
 		pq_sendint32(&buf, attr->atttypmod);
 		pq_sendint16(&buf, 0);	/* format code */
+		if (MyProcPort->column_encryption_enabled)
+		{
+			pq_sendint32(&buf, 0);	/* CEK */
+			pq_sendint32(&buf, 0);	/* CEK alg */
+		}
 	}
 
 	pq_endmessage(&buf);
diff --git a/src/backend/access/common/printtup.c b/src/backend/access/common/printtup.c
index 72faeb5dfa..eb93c4bb3f 100644
--- a/src/backend/access/common/printtup.c
+++ b/src/backend/access/common/printtup.c
@@ -15,13 +15,28 @@
  */
 #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/libpq-be.h"
 #include "libpq/pqformat.h"
+#include "miscadmin.h"
 #include "tcop/pquery.h"
+#include "utils/array.h"
+#include "utils/arrayaccess.h"
+#include "utils/builtins.h"
+#include "utils/fmgroids.h"
+#include "utils/inval.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 +166,156 @@ printtup_startup(DestReceiver *self, int operation, TupleDesc typeinfo)
 	 */
 }
 
+/*
+ * Send ColumnMasterKey message, unless it's already been sent in this session
+ * for this key.
+ */
+List	   *cmk_sent = NIL;
+
+static void
+cmk_change_cb(Datum arg, int cacheid, uint32 hashvalue)
+{
+	list_free(cmk_sent);
+	cmk_sent = NIL;
+}
+
+static void
+MaybeSendColumnMasterKeyMessage(Oid cmkid)
+{
+	HeapTuple	tuple;
+	Form_pg_colmasterkey cmkform;
+	Datum		datum;
+	bool		isnull;
+	StringInfoData buf;
+	static bool registered_inval = false;
+	MemoryContext oldcontext;
+
+	Assert(MyProcPort->column_encryption_enabled);
+
+	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_sendint32(&buf, cmkform->oid);
+	pq_sendstring(&buf, NameStr(cmkform->cmkname));
+	datum = SysCacheGetAttr(CMKOID, tuple, Anum_pg_colmasterkey_cmkrealm, &isnull);
+	Assert(!isnull);
+	pq_sendstring(&buf, TextDatumGetCString(datum));
+	pq_endmessage(&buf);
+
+	ReleaseSysCache(tuple);
+
+	if (!registered_inval)
+	{
+		CacheRegisterSyscacheCallback(CMKOID, cmk_change_cb, (Datum) 0);
+		registered_inval = true;
+	}
+
+	oldcontext = MemoryContextSwitchTo(TopMemoryContext);
+	cmk_sent = lappend_oid(cmk_sent, cmkid);
+	MemoryContextSwitchTo(oldcontext);
+}
+
+/*
+ * Send ColumnEncryptionKey message, unless it's already been sent in this
+ * session for this key.
+ */
+List	   *cek_sent = NIL;
+
+static void
+cek_change_cb(Datum arg, int cacheid, uint32 hashvalue)
+{
+	list_free(cek_sent);
+	cek_sent = NIL;
+}
+
+void
+MaybeSendColumnEncryptionKeyMessage(Oid attcek)
+{
+	HeapTuple	tuple;
+	ScanKeyData skey[1];
+	SysScanDesc sd;
+	Relation	rel;
+	bool		found = false;
+	static bool registered_inval = false;
+	MemoryContext oldcontext;
+
+	Assert(MyProcPort->column_encryption_enabled);
+
+	if (list_member_oid(cek_sent, attcek))
+		return;
+
+	/*
+	 * We really only need data from pg_colenckeydata, but before we scan
+	 * that, let's check that an entry exists in pg_colenckey, so that if
+	 * there are catalog inconsistencies, we can locate them better.
+	 */
+	if (!SearchSysCacheExists1(CEKOID, ObjectIdGetDatum(attcek)))
+		elog(ERROR, "cache lookup failed for column encryption key %u", attcek);
+
+	/*
+	 * Now scan pg_colenckeydata.
+	 */
+	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 ((tuple = systable_getnext(sd)))
+	{
+		Form_pg_colenckeydata ckdform = (Form_pg_colenckeydata) GETSTRUCT(tuple);
+		Datum		datum;
+		bool		isnull;
+		bytea	   *ba;
+		StringInfoData buf;
+
+		MaybeSendColumnMasterKeyMessage(ckdform->ckdcmkid);
+
+		datum = heap_getattr(tuple, Anum_pg_colenckeydata_ckdencval, RelationGetDescr(rel), &isnull);
+		Assert(!isnull);
+		ba = pg_detoast_datum_packed((bytea *) DatumGetPointer(datum));
+
+		pq_beginmessage(&buf, 'Y'); /* ColumnEncryptionKey */
+		pq_sendint32(&buf, ckdform->ckdcekid);
+		pq_sendint32(&buf, ckdform->ckdcmkid);
+		pq_sendint32(&buf, ckdform->ckdcmkalg);
+		pq_sendint32(&buf, VARSIZE_ANY_EXHDR(ba));
+		pq_sendbytes(&buf, VARDATA_ANY(ba), VARSIZE_ANY_EXHDR(ba));
+		pq_endmessage(&buf);
+
+		found = true;
+	}
+
+	/*
+	 * This is a user-facing message, because with ALTER it is possible to
+	 * delete all data entries for a CEK.
+	 */
+	if (!found)
+		ereport(ERROR,
+				errcode(ERRCODE_UNDEFINED_OBJECT),
+				errmsg("no data for column encryption key \"%s\"", get_cek_name(attcek, false)));
+
+	systable_endscan(sd);
+	table_close(rel, NoLock);
+
+	if (!registered_inval)
+	{
+		CacheRegisterSyscacheCallback(CEKDATAOID, cek_change_cb, (Datum) 0);
+		registered_inval = true;
+	}
+
+	oldcontext = MemoryContextSwitchTo(TopMemoryContext);
+	cek_sent = lappend_oid(cek_sent, attcek);
+	MemoryContextSwitchTo(oldcontext);
+}
+
 /*
  * SendRowDescriptionMessage --- send a RowDescription message to the frontend
  *
@@ -167,6 +332,7 @@ SendRowDescriptionMessage(StringInfo buf, TupleDesc typeinfo,
 						  List *targetlist, int16 *formats)
 {
 	int			natts = typeinfo->natts;
+	size_t		sz;
 	int			i;
 	ListCell   *tlist_item = list_head(targetlist);
 
@@ -183,14 +349,17 @@ SendRowDescriptionMessage(StringInfo buf, TupleDesc typeinfo,
 	 * Have to overestimate the size of the column-names, to account for
 	 * character set overhead.
 	 */
-	enlargeStringInfo(buf, (NAMEDATALEN * MAX_CONVERSION_GROWTH /* attname */
-							+ sizeof(Oid)	/* resorigtbl */
-							+ sizeof(AttrNumber)	/* resorigcol */
-							+ sizeof(Oid)	/* atttypid */
-							+ sizeof(int16) /* attlen */
-							+ sizeof(int32) /* attypmod */
-							+ sizeof(int16) /* format */
-							) * natts);
+	sz = (NAMEDATALEN * MAX_CONVERSION_GROWTH /* attname */
+		  + sizeof(Oid)	/* resorigtbl */
+		  + sizeof(AttrNumber)	/* resorigcol */
+		  + sizeof(Oid)	/* atttypid */
+		  + sizeof(int16) /* attlen */
+		  + sizeof(int32) /* attypmod */
+		  + sizeof(int16)); /* format */
+	if (MyProcPort->column_encryption_enabled)
+		sz += (sizeof(int32)		/* attcekid */
+			   + sizeof(int32));	/* attencalg */
+	enlargeStringInfo(buf, sz * natts);
 
 	for (i = 0; i < natts; ++i)
 	{
@@ -200,6 +369,8 @@ SendRowDescriptionMessage(StringInfo buf, TupleDesc typeinfo,
 		Oid			resorigtbl;
 		AttrNumber	resorigcol;
 		int16		format;
+		Oid			attcekid = InvalidOid;
+		int32		attencalg = 0;
 
 		/*
 		 * If column is a domain, send the base type and typmod instead.
@@ -231,6 +402,28 @@ SendRowDescriptionMessage(StringInfo buf, TupleDesc typeinfo,
 		else
 			format = 0;
 
+		if (MyProcPort->column_encryption_enabled && type_is_encrypted(atttypid))
+		{
+			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;
+			attcekid = orig_att->attcek;
+			attencalg = orig_att->attencalg;
+			ReleaseSysCache(tp);
+
+			/*
+			 * Encrypted types are always sent in binary when column
+			 * encryption is enabled.
+			 */
+			format = 1;
+		}
+
 		pq_writestring(buf, NameStr(att->attname));
 		pq_writeint32(buf, resorigtbl);
 		pq_writeint16(buf, resorigcol);
@@ -238,6 +431,11 @@ SendRowDescriptionMessage(StringInfo buf, TupleDesc typeinfo,
 		pq_writeint16(buf, att->attlen);
 		pq_writeint32(buf, atttypmod);
 		pq_writeint16(buf, format);
+		if (MyProcPort->column_encryption_enabled)
+		{
+			pq_writeint32(buf, attcekid);
+			pq_writeint32(buf, attencalg);
+		}
 	}
 
 	pq_endmessage_reuse(buf);
@@ -271,6 +469,13 @@ printtup_prepare_info(DR_printtup *myState, TupleDesc typeinfo, int numAttrs)
 		int16		format = (formats ? formats[i] : 0);
 		Form_pg_attribute attr = TupleDescAttr(typeinfo, i);
 
+		/*
+		 * Encrypted types are always sent in binary when column encryption is
+		 * enabled.
+		 */
+		if (MyProcPort->column_encryption_enabled && type_is_encrypted(attr->atttypid))
+			format = 1;
+
 		thisState->format = format;
 		if (format == 0)
 		{
diff --git a/src/backend/access/common/tupdesc.c b/src/backend/access/common/tupdesc.c
index 72a2c3d3db..b869739b43 100644
--- a/src/backend/access/common/tupdesc.c
+++ b/src/backend/access/common/tupdesc.c
@@ -459,6 +459,12 @@ 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->attencalg != attr2->attencalg)
+			return false;
 		if (attr1->attinhcount != attr2->attinhcount)
 			return false;
 		if (attr1->attcollation != attr2->attcollation)
@@ -629,6 +635,9 @@ TupleDescInitEntry(TupleDesc desc,
 	att->attgenerated = '\0';
 	att->attisdropped = false;
 	att->attislocal = true;
+	att->attcek = 0;
+	att->attrealtypid = 0;
+	att->attencalg = 0;
 	att->attinhcount = 0;
 	/* variable-length fields are not present in tupledescs */
 
@@ -690,6 +699,9 @@ TupleDescInitBuiltinEntry(TupleDesc desc,
 	att->attgenerated = '\0';
 	att->attisdropped = false;
 	att->attislocal = true;
+	att->attcek = 0;
+	att->attrealtypid = 0;
+	att->attencalg = 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 24bab58499..cc48069932 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 == PG_ENCRYPTED_DETOID))
 			 /* okay, allowed use of hashvarlena() */ ;
 		else
 			result = false;
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index a60107bf94..7b9575635b 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -72,7 +72,8 @@ CATALOG_HEADERS := \
 	pg_collation.h pg_parameter_acl.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/aclchk.c b/src/backend/catalog/aclchk.c
index c4232344aa..d835e84134 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -33,7 +33,9 @@
 #include "catalog/pg_authid.h"
 #include "catalog/pg_cast.h"
 #include "catalog/pg_class.h"
+#include "catalog/pg_colenckey.h"
 #include "catalog/pg_collation.h"
+#include "catalog/pg_colmasterkey.h"
 #include "catalog/pg_conversion.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_default_acl.h"
@@ -2798,6 +2800,9 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
 					case OBJECT_AMPROC:
 					case OBJECT_ATTRIBUTE:
 					case OBJECT_CAST:
+					case OBJECT_CEK:
+					case OBJECT_CEKDATA:
+					case OBJECT_CMK:
 					case OBJECT_DEFAULT:
 					case OBJECT_DEFACL:
 					case OBJECT_DOMCONSTRAINT:
@@ -2828,6 +2833,12 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
 					case OBJECT_AGGREGATE:
 						msg = gettext_noop("must be owner of aggregate %s");
 						break;
+					case OBJECT_CEK:
+						msg = gettext_noop("must be owner of column encryption key %s");
+						break;
+					case OBJECT_CMK:
+						msg = gettext_noop("must be owner of column master key %s");
+						break;
 					case OBJECT_COLLATION:
 						msg = gettext_noop("must be owner of collation %s");
 						break;
@@ -2938,6 +2949,7 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
 					case OBJECT_AMPROC:
 					case OBJECT_ATTRIBUTE:
 					case OBJECT_CAST:
+					case OBJECT_CEKDATA:
 					case OBJECT_DEFAULT:
 					case OBJECT_DEFACL:
 					case OBJECT_DOMCONSTRAINT:
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 7acf654bf8..b963e660e9 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -30,7 +30,10 @@
 #include "catalog/pg_authid.h"
 #include "catalog/pg_auth_members.h"
 #include "catalog/pg_cast.h"
+#include "catalog/pg_colenckey.h"
+#include "catalog/pg_colenckeydata.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"
@@ -153,6 +156,9 @@ static const Oid object_classes[] = {
 	TypeRelationId,				/* OCLASS_TYPE */
 	CastRelationId,				/* OCLASS_CAST */
 	CollationRelationId,		/* OCLASS_COLLATION */
+	ColumnEncKeyRelationId,		/* OCLASS_CEK */
+	ColumnEncKeyDataRelationId,	/* OCLASS_CEKDATA */
+	ColumnMasterKeyRelationId,	/* OCLASS_CMK */
 	ConstraintRelationId,		/* OCLASS_CONSTRAINT */
 	ConversionRelationId,		/* OCLASS_CONVERSION */
 	AttrDefaultRelationId,		/* OCLASS_DEFAULT */
@@ -1493,6 +1499,9 @@ doDeletion(const ObjectAddress *object, int flags)
 
 		case OCLASS_CAST:
 		case OCLASS_COLLATION:
+		case OCLASS_CEK:
+		case OCLASS_CEKDATA:
+		case OCLASS_CMK:
 		case OCLASS_CONVERSION:
 		case OCLASS_LANGUAGE:
 		case OCLASS_OPCLASS:
@@ -2859,6 +2868,15 @@ getObjectClass(const ObjectAddress *object)
 		case CollationRelationId:
 			return OCLASS_COLLATION;
 
+		case ColumnEncKeyRelationId:
+			return OCLASS_CEK;
+
+		case ColumnEncKeyDataRelationId:
+			return OCLASS_CEKDATA;
+
+		case ColumnMasterKeyRelationId:
+			return OCLASS_CMK;
+
 		case ConstraintRelationId:
 			return OCLASS_CONSTRAINT;
 
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 4f006820b8..63b1e180c0 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -42,6 +42,7 @@
 #include "catalog/partition.h"
 #include "catalog/pg_am.h"
 #include "catalog/pg_attrdef.h"
+#include "catalog/pg_colenckey.h"
 #include "catalog/pg_collation.h"
 #include "catalog/pg_constraint.h"
 #include "catalog/pg_foreign_table.h"
@@ -511,7 +512,14 @@ CheckAttributeNamesTypes(TupleDesc tupdesc, char relkind,
 						   TupleDescAttr(tupdesc, i)->atttypid,
 						   TupleDescAttr(tupdesc, i)->attcollation,
 						   NIL, /* assume we're creating a new rowtype */
-						   flags);
+						   flags |
+						   /*
+							* Allow encrypted types if CEK has been provided,
+							* which means this type has been internally
+							* generated.  We don't want to allow explicitly
+							* using these types.
+							*/
+						   (TupleDescAttr(tupdesc, i)->attcek ? CHKATYPE_ENCRYPTED : 0));
 	}
 }
 
@@ -653,6 +661,21 @@ CheckAttributeType(const char *attname,
 						   flags);
 	}
 
+	/*
+	 * Encrypted types are not allowed explictly as column types.  Most
+	 * callers run this check before transforming the column definition to use
+	 * the encrypted types.  Some callers call it again after; those should
+	 * set the CHKATYPE_ENCRYPTED to let this pass.
+	 */
+	if (type_is_encrypted(atttypid) && !(flags & CHKATYPE_ENCRYPTED))
+	{
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+				 errbacktrace(),
+				 errmsg("column \"%s\" has internal type %s",
+						attname, format_type_be(atttypid))));
+	}
+
 	/*
 	 * This might not be strictly invalid per SQL standard, but it is pretty
 	 * useless, and it cannot be dumped, so we must disallow it.
@@ -749,6 +772,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)
@@ -840,6 +866,20 @@ AddNewAttributeTuples(Oid new_rel_oid,
 							 tupdesc->attrs[i].attcollation);
 			recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
 		}
+
+		if (OidIsValid(tupdesc->attrs[i].attcek))
+		{
+			ObjectAddressSet(referenced, ColumnEncKeyRelationId,
+							 tupdesc->attrs[i].attcek);
+			recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+		}
+
+		if (OidIsValid(tupdesc->attrs[i].attrealtypid))
+		{
+			ObjectAddressSet(referenced, TypeRelationId,
+							 tupdesc->attrs[i].attrealtypid);
+			recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+		}
 	}
 
 	/*
diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index 14e57adee2..00f914bc5f 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -26,6 +26,8 @@
 #include "catalog/dependency.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_authid.h"
+#include "catalog/pg_colenckey.h"
+#include "catalog/pg_colmasterkey.h"
 #include "catalog/pg_collation.h"
 #include "catalog/pg_conversion.h"
 #include "catalog/pg_database.h"
@@ -1997,6 +1999,254 @@ OpfamilyIsVisible(Oid opfid)
 	return visible;
 }
 
+/*
+ * get_cek_oid - find a CEK by possibly qualified name
+ */
+Oid
+get_cek_oid(List *names, bool missing_ok)
+{
+	char	   *schemaname;
+	char	   *cekname;
+	Oid			namespaceId;
+	Oid			cekoid = InvalidOid;
+	ListCell   *l;
+
+	/* deconstruct the name list */
+	DeconstructQualifiedName(names, &schemaname, &cekname);
+
+	if (schemaname)
+	{
+		/* use exact schema given */
+		namespaceId = LookupExplicitNamespace(schemaname, missing_ok);
+		if (missing_ok && !OidIsValid(namespaceId))
+			cekoid = InvalidOid;
+		else
+			cekoid = GetSysCacheOid2(CEKNAMENSP, Anum_pg_colenckey_oid,
+									 PointerGetDatum(cekname),
+									 ObjectIdGetDatum(namespaceId));
+	}
+	else
+	{
+		/* search for it in search path */
+		recomputeNamespacePath();
+
+		foreach(l, activeSearchPath)
+		{
+			namespaceId = lfirst_oid(l);
+
+			if (namespaceId == myTempNamespace)
+				continue;		/* do not look in temp namespace */
+
+			cekoid = GetSysCacheOid2(CEKNAMENSP, Anum_pg_colenckey_oid,
+									 PointerGetDatum(cekname),
+									 ObjectIdGetDatum(namespaceId));
+			if (OidIsValid(cekoid))
+				return cekoid;
+		}
+	}
+
+	/* Not found in path */
+	if (!OidIsValid(cekoid) && !missing_ok)
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_OBJECT),
+				 errmsg("column encryption key \"%s\" does not exist",
+						NameListToString(names))));
+	return cekoid;
+}
+
+/*
+ * CEKIsVisible
+ *		Determine whether a CEK (identified by OID) is visible in the
+ *		current search path.  Visible means "would be found by searching
+ *		for the unqualified parser name".
+ */
+bool
+CEKIsVisible(Oid cekid)
+{
+	HeapTuple	tup;
+	Form_pg_colenckey form;
+	Oid			namespace;
+	bool		visible;
+
+	tup = SearchSysCache1(CEKOID, ObjectIdGetDatum(cekid));
+	if (!HeapTupleIsValid(tup))
+		elog(ERROR, "cache lookup failed for column encryption key %u", cekid);
+	form = (Form_pg_colenckey) GETSTRUCT(tup);
+
+	recomputeNamespacePath();
+
+	/*
+	 * Quick check: if it ain't in the path at all, it ain't visible. Items in
+	 * the system namespace are surely in the path and so we needn't even do
+	 * list_member_oid() for them.
+	 */
+	namespace = form->ceknamespace;
+	if (namespace != PG_CATALOG_NAMESPACE &&
+		!list_member_oid(activeSearchPath, namespace))
+		visible = false;
+	else
+	{
+		/*
+		 * If it is in the path, it might still not be visible; it could be
+		 * hidden by another parser of the same name earlier in the path. So
+		 * we must do a slow check for conflicting CEKs.
+		 */
+		char	   *name = NameStr(form->cekname);
+		ListCell   *l;
+
+		visible = false;
+		foreach(l, activeSearchPath)
+		{
+			Oid			namespaceId = lfirst_oid(l);
+
+			if (namespaceId == myTempNamespace)
+				continue;		/* do not look in temp namespace */
+
+			if (namespaceId == namespace)
+			{
+				/* Found it first in path */
+				visible = true;
+				break;
+			}
+			if (SearchSysCacheExists2(CEKNAMENSP,
+									  PointerGetDatum(name),
+									  ObjectIdGetDatum(namespaceId)))
+			{
+				/* Found something else first in path */
+				break;
+			}
+		}
+	}
+
+	ReleaseSysCache(tup);
+
+	return visible;
+}
+
+/*
+ * get_cmk_oid - find a CMK by possibly qualified name
+ */
+Oid
+get_cmk_oid(List *names, bool missing_ok)
+{
+	char	   *schemaname;
+	char	   *cmkname;
+	Oid			namespaceId;
+	Oid			cmkoid = InvalidOid;
+	ListCell   *l;
+
+	/* deconstruct the name list */
+	DeconstructQualifiedName(names, &schemaname, &cmkname);
+
+	if (schemaname)
+	{
+		/* use exact schema given */
+		namespaceId = LookupExplicitNamespace(schemaname, missing_ok);
+		if (missing_ok && !OidIsValid(namespaceId))
+			cmkoid = InvalidOid;
+		else
+			cmkoid = GetSysCacheOid2(CMKNAMENSP, Anum_pg_colmasterkey_oid,
+									 PointerGetDatum(cmkname),
+									 ObjectIdGetDatum(namespaceId));
+	}
+	else
+	{
+		/* search for it in search path */
+		recomputeNamespacePath();
+
+		foreach(l, activeSearchPath)
+		{
+			namespaceId = lfirst_oid(l);
+
+			if (namespaceId == myTempNamespace)
+				continue;		/* do not look in temp namespace */
+
+			cmkoid = GetSysCacheOid2(CMKNAMENSP, Anum_pg_colmasterkey_oid,
+									 PointerGetDatum(cmkname),
+									 ObjectIdGetDatum(namespaceId));
+			if (OidIsValid(cmkoid))
+				return cmkoid;
+		}
+	}
+
+	/* Not found in path */
+	if (!OidIsValid(cmkoid) && !missing_ok)
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_OBJECT),
+				 errmsg("column master key \"%s\" does not exist",
+						NameListToString(names))));
+	return cmkoid;
+}
+
+/*
+ * CMKIsVisible
+ *		Determine whether a CMK (identified by OID) is visible in the
+ *		current search path.  Visible means "would be found by searching
+ *		for the unqualified parser name".
+ */
+bool
+CMKIsVisible(Oid cmkid)
+{
+	HeapTuple	tup;
+	Form_pg_colmasterkey form;
+	Oid			namespace;
+	bool		visible;
+
+	tup = SearchSysCache1(CMKOID, ObjectIdGetDatum(cmkid));
+	if (!HeapTupleIsValid(tup))
+		elog(ERROR, "cache lookup failed for column master key %u", cmkid);
+	form = (Form_pg_colmasterkey) GETSTRUCT(tup);
+
+	recomputeNamespacePath();
+
+	/*
+	 * Quick check: if it ain't in the path at all, it ain't visible. Items in
+	 * the system namespace are surely in the path and so we needn't even do
+	 * list_member_oid() for them.
+	 */
+	namespace = form->cmknamespace;
+	if (namespace != PG_CATALOG_NAMESPACE &&
+		!list_member_oid(activeSearchPath, namespace))
+		visible = false;
+	else
+	{
+		/*
+		 * If it is in the path, it might still not be visible; it could be
+		 * hidden by another parser of the same name earlier in the path. So
+		 * we must do a slow check for conflicting CMKs.
+		 */
+		char	   *name = NameStr(form->cmkname);
+		ListCell   *l;
+
+		visible = false;
+		foreach(l, activeSearchPath)
+		{
+			Oid			namespaceId = lfirst_oid(l);
+
+			if (namespaceId == myTempNamespace)
+				continue;		/* do not look in temp namespace */
+
+			if (namespaceId == namespace)
+			{
+				/* Found it first in path */
+				visible = true;
+				break;
+			}
+			if (SearchSysCacheExists2(CMKNAMENSP,
+									  PointerGetDatum(name),
+									  ObjectIdGetDatum(namespaceId)))
+			{
+				/* Found something else first in path */
+				break;
+			}
+		}
+	}
+
+	ReleaseSysCache(tup);
+
+	return visible;
+}
+
 /*
  * lookup_collation
  *		If there's a collation of the given name/namespace, and it works
@@ -4567,6 +4817,28 @@ pg_opfamily_is_visible(PG_FUNCTION_ARGS)
 	PG_RETURN_BOOL(OpfamilyIsVisible(oid));
 }
 
+Datum
+pg_cek_is_visible(PG_FUNCTION_ARGS)
+{
+	Oid			oid = PG_GETARG_OID(0);
+
+	if (!SearchSysCacheExists1(CEKOID, ObjectIdGetDatum(oid)))
+		PG_RETURN_NULL();
+
+	PG_RETURN_BOOL(CEKIsVisible(oid));
+}
+
+Datum
+pg_cmk_is_visible(PG_FUNCTION_ARGS)
+{
+	Oid			oid = PG_GETARG_OID(0);
+
+	if (!SearchSysCacheExists1(CMKOID, ObjectIdGetDatum(oid)))
+		PG_RETURN_NULL();
+
+	PG_RETURN_BOOL(CMKIsVisible(oid));
+}
+
 Datum
 pg_collation_is_visible(PG_FUNCTION_ARGS)
 {
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 25c50d66fd..35dfc6fb3c 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -29,7 +29,10 @@
 #include "catalog/pg_authid.h"
 #include "catalog/pg_auth_members.h"
 #include "catalog/pg_cast.h"
+#include "catalog/pg_colenckey.h"
+#include "catalog/pg_colenckeydata.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"
@@ -191,6 +194,48 @@ static const ObjectPropertyType ObjectProperty[] =
 		OBJECT_COLLATION,
 		true
 	},
+	{
+		"column encryption key",
+		ColumnEncKeyRelationId,
+		ColumnEncKeyOidIndexId,
+		CEKOID,
+		CEKNAMENSP,
+		Anum_pg_colenckey_oid,
+		Anum_pg_colenckey_cekname,
+		Anum_pg_colenckey_ceknamespace,
+		Anum_pg_colenckey_cekowner,
+		InvalidAttrNumber,
+		OBJECT_CEK,
+		true
+	},
+	{
+		"column encryption key data",
+		ColumnEncKeyDataRelationId,
+		ColumnEncKeyDataOidIndexId,
+		CEKDATAOID,
+		-1,
+		Anum_pg_colenckeydata_oid,
+		InvalidAttrNumber,
+		InvalidAttrNumber,
+		InvalidAttrNumber,
+		InvalidAttrNumber,
+		-1,
+		false
+	},
+	{
+		"column master key",
+		ColumnMasterKeyRelationId,
+		ColumnMasterKeyOidIndexId,
+		CMKOID,
+		CMKNAMENSP,
+		Anum_pg_colmasterkey_oid,
+		Anum_pg_colmasterkey_cmkname,
+		Anum_pg_colmasterkey_cmknamespace,
+		Anum_pg_colmasterkey_cmkowner,
+		InvalidAttrNumber,
+		OBJECT_CMK,
+		true
+	},
 	{
 		"constraint",
 		ConstraintRelationId,
@@ -723,6 +768,18 @@ static const struct object_type_map
 	{
 		"collation", OBJECT_COLLATION
 	},
+	/* OCLASS_CEK */
+	{
+		"column encryption key", OBJECT_CEK
+	},
+	/* OCLASS_CEKDATA */
+	{
+		"column encryption key data", OBJECT_CEKDATA
+	},
+	/* OCLASS_CMK */
+	{
+		"column master key", OBJECT_CMK
+	},
 	/* OCLASS_CONSTRAINT */
 	{
 		"table constraint", OBJECT_TABCONSTRAINT
@@ -1029,6 +1086,16 @@ get_object_address(ObjectType objtype, Node *object,
 					address.objectSubId = 0;
 				}
 				break;
+			case OBJECT_CEK:
+				address.classId = ColumnEncKeyRelationId;
+				address.objectId = get_cek_oid(castNode(List, object), missing_ok);
+				address.objectSubId = 0;
+				break;
+			case OBJECT_CMK:
+				address.classId = ColumnMasterKeyRelationId;
+				address.objectId = get_cmk_oid(castNode(List, object), missing_ok);
+				address.objectSubId = 0;
+				break;
 			case OBJECT_DATABASE:
 			case OBJECT_EXTENSION:
 			case OBJECT_TABLESPACE:
@@ -1108,6 +1175,21 @@ get_object_address(ObjectType objtype, Node *object,
 					address.objectSubId = 0;
 				}
 				break;
+			case OBJECT_CEKDATA:
+				{
+					List	   *cekname = linitial_node(List, castNode(List, object));
+					List	   *cmkname = lsecond_node(List, castNode(List, object));
+					Oid			cekid;
+					Oid			cmkid;
+
+					cekid = get_cek_oid(cekname, missing_ok);
+					cmkid = get_cmk_oid(cmkname, missing_ok);
+
+					address.classId = ColumnEncKeyDataRelationId;
+					address.objectId = get_cekdata_oid(cekid, cmkid, missing_ok);
+					address.objectSubId = 0;
+				}
+				break;
 			case OBJECT_TRANSFORM:
 				{
 					TypeName   *typename = linitial_node(TypeName, castNode(List, object));
@@ -2311,6 +2393,8 @@ pg_get_object_address(PG_FUNCTION_ARGS)
 		case OBJECT_FOREIGN_TABLE:
 		case OBJECT_COLUMN:
 		case OBJECT_ATTRIBUTE:
+		case OBJECT_CEK:
+		case OBJECT_CMK:
 		case OBJECT_COLLATION:
 		case OBJECT_CONVERSION:
 		case OBJECT_STATISTIC_EXT:
@@ -2367,6 +2451,7 @@ pg_get_object_address(PG_FUNCTION_ARGS)
 			break;
 		case OBJECT_AMOP:
 		case OBJECT_AMPROC:
+		case OBJECT_CEKDATA:
 			objnode = (Node *) list_make2(name, args);
 			break;
 		case OBJECT_FUNCTION:
@@ -2489,6 +2574,8 @@ check_object_ownership(Oid roleid, ObjectType objtype, ObjectAddress address,
 				aclcheck_error(ACLCHECK_NOT_OWNER, objtype,
 							   strVal(object));
 			break;
+		case OBJECT_CEK:
+		case OBJECT_CMK:
 		case OBJECT_COLLATION:
 		case OBJECT_CONVERSION:
 		case OBJECT_OPCLASS:
@@ -2575,6 +2662,7 @@ check_object_ownership(Oid roleid, ObjectType objtype, ObjectAddress address,
 			break;
 		case OBJECT_AMOP:
 		case OBJECT_AMPROC:
+		case OBJECT_CEKDATA:
 		case OBJECT_DEFAULT:
 		case OBJECT_DEFACL:
 		case OBJECT_PUBLICATION_NAMESPACE:
@@ -3040,6 +3128,92 @@ getObjectDescription(const ObjectAddress *object, bool missing_ok)
 				break;
 			}
 
+		case OCLASS_CEK:
+			{
+				HeapTuple	tup;
+				Form_pg_colenckey form;
+				char	   *nspname;
+
+				tup = SearchSysCache1(CEKOID,
+									  ObjectIdGetDatum(object->objectId));
+				if (!HeapTupleIsValid(tup))
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for column encryption key %u",
+							 object->objectId);
+					break;
+				}
+
+				form = (Form_pg_colenckey) GETSTRUCT(tup);
+
+				/* Qualify the name if not visible in search path */
+				if (CEKIsVisible(object->objectId))
+					nspname = NULL;
+				else
+					nspname = get_namespace_name(form->ceknamespace);
+
+				appendStringInfo(&buffer, _("column encryption key %s"),
+								 quote_qualified_identifier(nspname,
+															NameStr(form->cekname)));
+				ReleaseSysCache(tup);
+				break;
+			}
+
+		case OCLASS_CEKDATA:
+			{
+				HeapTuple	tup;
+				Form_pg_colenckeydata cekdata;
+
+				tup = SearchSysCache1(CEKDATAOID, ObjectIdGetDatum(object->objectId));
+				if (!HeapTupleIsValid(tup))
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for column encryption key data %u",
+							 object->objectId);
+					break;
+				}
+
+				cekdata = (Form_pg_colenckeydata) GETSTRUCT(tup);
+
+				appendStringInfo(&buffer, _("column encryption key data of %s for %s"),
+								 getObjectDescription(&(ObjectAddress){ColumnEncKeyRelationId, cekdata->ckdcekid}, false),
+								 getObjectDescription(&(ObjectAddress){ColumnMasterKeyRelationId, cekdata->ckdcmkid}, false));
+
+				ReleaseSysCache(tup);
+				break;
+			}
+
+		case OCLASS_CMK:
+			{
+				HeapTuple	tup;
+				Form_pg_colmasterkey form;
+				char	   *nspname;
+
+				tup = SearchSysCache1(CMKOID,
+									  ObjectIdGetDatum(object->objectId));
+				if (!HeapTupleIsValid(tup))
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for column encryption key %u",
+							 object->objectId);
+					break;
+				}
+
+				form = (Form_pg_colmasterkey) GETSTRUCT(tup);
+
+				/* Qualify the name if not visible in search path */
+				if (CMKIsVisible(object->objectId))
+					nspname = NULL;
+				else
+					nspname = get_namespace_name(form->cmknamespace);
+
+				appendStringInfo(&buffer, _("column master key %s"),
+								 quote_qualified_identifier(nspname,
+															NameStr(form->cmkname)));
+				ReleaseSysCache(tup);
+				break;
+			}
+
 		case OCLASS_CONSTRAINT:
 			{
 				HeapTuple	conTup;
@@ -4440,6 +4614,18 @@ getObjectTypeDescription(const ObjectAddress *object, bool missing_ok)
 			appendStringInfoString(&buffer, "collation");
 			break;
 
+		case OCLASS_CEK:
+			appendStringInfoString(&buffer, "column encryption key");
+			break;
+
+		case OCLASS_CEKDATA:
+			appendStringInfoString(&buffer, "column encryption key data");
+			break;
+
+		case OCLASS_CMK:
+			appendStringInfoString(&buffer, "column master key");
+			break;
+
 		case OCLASS_CONSTRAINT:
 			getConstraintTypeDescription(&buffer, object->objectId,
 										 missing_ok);
@@ -4905,6 +5091,108 @@ getObjectIdentityParts(const ObjectAddress *object,
 				break;
 			}
 
+		case OCLASS_CEK:
+			{
+				HeapTuple	tup;
+				Form_pg_colenckey form;
+				char	   *schema;
+
+				tup = SearchSysCache1(CEKOID, ObjectIdGetDatum(object->objectId));
+				if (!HeapTupleIsValid(tup))
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for column encryption key %u",
+							 object->objectId);
+					break;
+				}
+				form = (Form_pg_colenckey) GETSTRUCT(tup);
+				schema = get_namespace_name_or_temp(form->ceknamespace);
+				appendStringInfoString(&buffer,
+									   quote_qualified_identifier(schema,
+																  NameStr(form->cekname)));
+				if (objname)
+					*objname = list_make2(schema, pstrdup(NameStr(form->cekname)));
+				ReleaseSysCache(tup);
+				break;
+			}
+
+		case OCLASS_CEKDATA:
+			{
+				HeapTuple	tup;
+				Form_pg_colenckeydata form;
+
+				tup = SearchSysCache1(CEKDATAOID, ObjectIdGetDatum(object->objectId));
+				if (!HeapTupleIsValid(tup))
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for column encryption key data %u",
+							 object->objectId);
+					break;
+				}
+				form = (Form_pg_colenckeydata) GETSTRUCT(tup);
+				appendStringInfo(&buffer,
+								 "of %s for %s",
+								 getObjectIdentityParts(&(ObjectAddress){ColumnEncKeyRelationId, form->ckdcekid}, NULL, NULL, false),
+								 getObjectIdentityParts(&(ObjectAddress){ColumnMasterKeyRelationId, form->ckdcmkid}, NULL, NULL, false));
+
+				if (objname)
+				{
+					HeapTuple	tup2;
+					Form_pg_colenckey form2;
+					char	   *schema;
+
+					tup2 = SearchSysCache1(CEKOID, ObjectIdGetDatum(form->ckdcekid));
+					if (!HeapTupleIsValid(tup2))
+						elog(ERROR, "cache lookup failed for column encryption key %u", form->ckdcekid);
+					form2 = (Form_pg_colenckey) GETSTRUCT(tup2);
+					schema = get_namespace_name_or_temp(form2->ceknamespace);
+					*objname = list_make2(schema, pstrdup(NameStr(form2->cekname)));
+					ReleaseSysCache(tup2);
+				}
+				if (objargs)
+				{
+					HeapTuple	tup2;
+					Form_pg_colmasterkey form2;
+					char	   *schema;
+
+					tup2 = SearchSysCache1(CMKOID, ObjectIdGetDatum(form->ckdcmkid));
+					if (!HeapTupleIsValid(tup2))
+						elog(ERROR, "cache lookup failed for column master key %u", form->ckdcmkid);
+					form2 = (Form_pg_colmasterkey) GETSTRUCT(tup2);
+					schema = get_namespace_name_or_temp(form2->cmknamespace);
+					if (objargs)
+						*objargs = list_make2(schema, pstrdup(NameStr(form2->cmkname)));
+					ReleaseSysCache(tup2);
+				}
+				ReleaseSysCache(tup);
+				break;
+			}
+
+		case OCLASS_CMK:
+			{
+				HeapTuple	tup;
+				Form_pg_colmasterkey form;
+				char	   *schema;
+
+				tup = SearchSysCache1(CMKOID, ObjectIdGetDatum(object->objectId));
+				if (!HeapTupleIsValid(tup))
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for column master key %u",
+							 object->objectId);
+					break;
+				}
+				form = (Form_pg_colmasterkey) GETSTRUCT(tup);
+				schema = get_namespace_name_or_temp(form->cmknamespace);
+				appendStringInfoString(&buffer,
+									   quote_qualified_identifier(schema,
+																  NameStr(form->cmkname)));
+				if (objname)
+					*objname = list_make2(schema, pstrdup(NameStr(form->cmkname)));
+				ReleaseSysCache(tup);
+				break;
+			}
+
 		case OCLASS_CONSTRAINT:
 			{
 				HeapTuple	conTup;
diff --git a/src/backend/commands/Makefile b/src/backend/commands/Makefile
index 48f7348f91..69f6175c60 100644
--- a/src/backend/commands/Makefile
+++ b/src/backend/commands/Makefile
@@ -19,6 +19,7 @@ OBJS = \
 	analyze.o \
 	async.o \
 	cluster.o \
+	colenccmds.o \
 	collationcmds.o \
 	comment.o \
 	constraint.o \
diff --git a/src/backend/commands/alter.c b/src/backend/commands/alter.c
index bea51b3af1..59c23e9ef8 100644
--- a/src/backend/commands/alter.c
+++ b/src/backend/commands/alter.c
@@ -22,7 +22,9 @@
 #include "catalog/indexing.h"
 #include "catalog/namespace.h"
 #include "catalog/objectaccess.h"
+#include "catalog/pg_colenckey.h"
 #include "catalog/pg_collation.h"
+#include "catalog/pg_colmasterkey.h"
 #include "catalog/pg_conversion.h"
 #include "catalog/pg_event_trigger.h"
 #include "catalog/pg_foreign_data_wrapper.h"
@@ -118,6 +120,12 @@ report_namespace_conflict(Oid classId, const char *name, Oid nspOid)
 
 	switch (classId)
 	{
+		case ColumnEncKeyRelationId:
+			msgfmt = gettext_noop("column encryption key \"%s\" already exists in schema \"%s\"");
+			break;
+		case ColumnMasterKeyRelationId:
+			msgfmt = gettext_noop("column master key \"%s\" already exists in schema \"%s\"");
+			break;
 		case ConversionRelationId:
 			Assert(OidIsValid(nspOid));
 			msgfmt = gettext_noop("conversion \"%s\" already exists in schema \"%s\"");
@@ -379,6 +387,8 @@ ExecRenameStmt(RenameStmt *stmt)
 			return RenameType(stmt);
 
 		case OBJECT_AGGREGATE:
+		case OBJECT_CEK:
+		case OBJECT_CMK:
 		case OBJECT_COLLATION:
 		case OBJECT_CONVERSION:
 		case OBJECT_EVENT_TRIGGER:
@@ -527,6 +537,8 @@ ExecAlterObjectSchemaStmt(AlterObjectSchemaStmt *stmt,
 
 			/* generic code path */
 		case OBJECT_AGGREGATE:
+		case OBJECT_CEK:
+		case OBJECT_CMK:
 		case OBJECT_COLLATION:
 		case OBJECT_CONVERSION:
 		case OBJECT_FUNCTION:
@@ -643,6 +655,9 @@ AlterObjectNamespace_oid(Oid classId, Oid objid, Oid nspOid,
 			break;
 
 		case OCLASS_CAST:
+		case OCLASS_CEK:
+		case OCLASS_CEKDATA:
+		case OCLASS_CMK:
 		case OCLASS_CONSTRAINT:
 		case OCLASS_DEFAULT:
 		case OCLASS_LANGUAGE:
@@ -876,6 +891,8 @@ ExecAlterOwnerStmt(AlterOwnerStmt *stmt)
 
 			/* Generic cases */
 		case OBJECT_AGGREGATE:
+		case OBJECT_CEK:
+		case OBJECT_CMK:
 		case OBJECT_COLLATION:
 		case OBJECT_CONVERSION:
 		case OBJECT_FUNCTION:
diff --git a/src/backend/commands/colenccmds.c b/src/backend/commands/colenccmds.c
new file mode 100644
index 0000000000..0bb4948672
--- /dev/null
+++ b/src/backend/commands/colenccmds.c
@@ -0,0 +1,427 @@
+/*-------------------------------------------------------------------------
+ *
+ * colenccmds.c
+ *	  column-encryption-related commands support code
+ *
+ * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *	  src/backend/commands/colenccmds.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/htup_details.h"
+#include "access/table.h"
+#include "catalog/catalog.h"
+#include "catalog/dependency.h"
+#include "catalog/indexing.h"
+#include "catalog/namespace.h"
+#include "catalog/objectaccess.h"
+#include "catalog/pg_colenckey.h"
+#include "catalog/pg_colenckeydata.h"
+#include "catalog/pg_colmasterkey.h"
+#include "catalog/pg_namespace.h"
+#include "commands/colenccmds.h"
+#include "commands/dbcommands.h"
+#include "commands/defrem.h"
+#include "common/colenc.h"
+#include "miscadmin.h"
+#include "utils/acl.h"
+#include "utils/builtins.h"
+#include "utils/lsyscache.h"
+#include "utils/rel.h"
+#include "utils/syscache.h"
+
+static void
+parse_cek_attributes(ParseState *pstate, List *definition, Oid *cmkoid_p, int *alg_p, char **encval_p)
+{
+	ListCell   *lc;
+	DefElem    *cmkEl = NULL;
+	DefElem    *algEl = NULL;
+	DefElem    *encvalEl = NULL;
+	Oid			cmkoid = InvalidOid;
+	int			alg = 0;
+	char	   *encval = NULL;
+
+	Assert(cmkoid_p);
+
+	foreach(lc, definition)
+	{
+		DefElem    *defel = lfirst_node(DefElem, lc);
+		DefElem   **defelp;
+
+		if (strcmp(defel->defname, "column_master_key") == 0)
+			defelp = &cmkEl;
+		else if (strcmp(defel->defname, "algorithm") == 0)
+			defelp = &algEl;
+		else if (strcmp(defel->defname, "encrypted_value") == 0)
+			defelp = &encvalEl;
+		else
+		{
+			ereport(ERROR,
+					(errcode(ERRCODE_SYNTAX_ERROR),
+					 errmsg("column encryption key attribute \"%s\" not recognized",
+							defel->defname),
+					 parser_errposition(pstate, defel->location)));
+		}
+		if (*defelp != NULL)
+			errorConflictingDefElem(defel, pstate);
+		*defelp = defel;
+	}
+
+	if (cmkEl)
+	{
+		List	   *val = defGetQualifiedName(cmkEl);
+
+		cmkoid = get_cmk_oid(val, false);
+	}
+	else
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("attribute \"%s\" must be specified",
+						"column_master_key")));
+
+	if (algEl)
+	{
+		char	   *val = defGetString(algEl);
+
+		if (!alg_p)
+			ereport(ERROR,
+					(errcode(ERRCODE_SYNTAX_ERROR),
+					 errmsg("attribute \"%s\" must not be specified",
+							"algorithm")));
+
+		alg = get_cmkalg_num(val);
+		if (!alg)
+			ereport(ERROR,
+					errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+					errmsg("unrecognized encryption algorithm: %s", val));
+	}
+	else
+	{
+		if (alg_p)
+			ereport(ERROR,
+					(errcode(ERRCODE_SYNTAX_ERROR),
+					 errmsg("attribute \"%s\" must be specified",
+							"algorithm")));
+	}
+
+	if (encvalEl)
+	{
+		if (!encval_p)
+			ereport(ERROR,
+					(errcode(ERRCODE_SYNTAX_ERROR),
+					 errmsg("attribute \"%s\" must not be specified",
+							"encrypted_value")));
+
+		encval = defGetString(encvalEl);
+	}
+	else
+	{
+		if (encval_p)
+			ereport(ERROR,
+					(errcode(ERRCODE_SYNTAX_ERROR),
+					 errmsg("attribute \"%s\" must be specified",
+							"encrypted_value")));
+	}
+
+	*cmkoid_p = cmkoid;
+	if (alg_p)
+		*alg_p = alg;
+	if (encval_p)
+		*encval_p = encval;
+}
+
+static void
+insert_cekdata_record(Oid cekoid, Oid cmkoid, int alg, char *encval)
+{
+	Oid			cekdataoid;
+	Relation	rel;
+	Datum		values[Natts_pg_colenckeydata] = {0};
+	bool		nulls[Natts_pg_colenckeydata] = {0};
+	HeapTuple	tup;
+	ObjectAddress myself;
+	ObjectAddress other;
+
+	rel = table_open(ColumnEncKeyDataRelationId, RowExclusiveLock);
+
+	cekdataoid = GetNewOidWithIndex(rel, ColumnEncKeyDataOidIndexId, Anum_pg_colenckeydata_oid);
+	values[Anum_pg_colenckeydata_oid - 1] = ObjectIdGetDatum(cekdataoid);
+	values[Anum_pg_colenckeydata_ckdcekid - 1] = ObjectIdGetDatum(cekoid);
+	values[Anum_pg_colenckeydata_ckdcmkid - 1] = ObjectIdGetDatum(cmkoid);
+	values[Anum_pg_colenckeydata_ckdcmkalg - 1] = Int32GetDatum(alg);
+	values[Anum_pg_colenckeydata_ckdencval - 1] = DirectFunctionCall1(byteain, CStringGetDatum(encval));
+
+	tup = heap_form_tuple(RelationGetDescr(rel), values, nulls);
+	CatalogTupleInsert(rel, tup);
+	heap_freetuple(tup);
+
+	ObjectAddressSet(myself, ColumnEncKeyDataRelationId, cekdataoid);
+
+	/* dependency cekdata -> cek */
+	ObjectAddressSet(other, ColumnEncKeyRelationId, cekoid);
+	recordDependencyOn(&myself, &other, DEPENDENCY_AUTO);
+
+	/* dependency cekdata -> cmk */
+	ObjectAddressSet(other, ColumnMasterKeyRelationId, cmkoid);
+	recordDependencyOn(&myself, &other, DEPENDENCY_NORMAL);
+
+	table_close(rel, NoLock);
+}
+
+ObjectAddress
+CreateCEK(ParseState *pstate, DefineStmt *stmt)
+{
+	Oid			namespaceId;
+	char	   *ceknamestr;
+	AclResult	aclresult;
+	Relation	rel;
+	ObjectAddress myself;
+	Oid			cekoid;
+	ListCell   *lc;
+	NameData	cekname;
+	Datum		values[Natts_pg_colenckey] = {0};
+	bool		nulls[Natts_pg_colenckey] = {0};
+	HeapTuple	tup;
+
+	namespaceId = QualifiedNameGetCreationNamespace(stmt->defnames, &ceknamestr);
+
+	aclresult = object_aclcheck(NamespaceRelationId, namespaceId, GetUserId(), ACL_CREATE);
+	if (aclresult != ACLCHECK_OK)
+		aclcheck_error(aclresult, OBJECT_SCHEMA, get_namespace_name(namespaceId));
+
+	rel = table_open(ColumnEncKeyRelationId, RowExclusiveLock);
+
+	if (SearchSysCacheExists2(CEKNAMENSP, PointerGetDatum(ceknamestr), ObjectIdGetDatum(namespaceId)))
+		ereport(ERROR,
+				errcode(ERRCODE_DUPLICATE_OBJECT),
+				errmsg("column encryption key \"%s\" already exists", ceknamestr));
+
+	cekoid = GetNewOidWithIndex(rel, ColumnEncKeyOidIndexId, Anum_pg_colenckey_oid);
+
+	foreach (lc, stmt->definition)
+	{
+		List	   *definition = lfirst_node(List, lc);
+		Oid			cmkoid = 0;
+		int			alg;
+		char	   *encval;
+
+		parse_cek_attributes(pstate, definition, &cmkoid, &alg, &encval);
+
+		/* pg_colenckeydata */
+		insert_cekdata_record(cekoid, cmkoid, alg, encval);
+	}
+
+	/* pg_colenckey */
+	namestrcpy(&cekname, ceknamestr);
+	values[Anum_pg_colenckey_oid - 1] = ObjectIdGetDatum(cekoid);
+	values[Anum_pg_colenckey_cekname - 1] = NameGetDatum(&cekname);
+	values[Anum_pg_colenckey_ceknamespace - 1] = ObjectIdGetDatum(namespaceId);
+	values[Anum_pg_colenckey_cekowner - 1] = ObjectIdGetDatum(GetUserId());
+
+	tup = heap_form_tuple(RelationGetDescr(rel), values, nulls);
+	CatalogTupleInsert(rel, tup);
+	heap_freetuple(tup);
+
+	ObjectAddressSet(myself, ColumnEncKeyRelationId, cekoid);
+	recordDependencyOnOwner(ColumnEncKeyRelationId, cekoid, GetUserId());
+
+	table_close(rel, RowExclusiveLock);
+
+	InvokeObjectPostCreateHook(ColumnEncKeyRelationId, cekoid, 0);
+
+	return myself;
+}
+
+ObjectAddress
+AlterColumnEncryptionKey(ParseState *pstate, AlterColumnEncryptionKeyStmt *stmt)
+{
+	Oid			cekoid;
+	ObjectAddress address;
+
+	cekoid = get_cek_oid(stmt->cekname, false);
+
+	if (!object_ownercheck(ColumnEncKeyRelationId, cekoid, GetUserId()))
+		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_CEK, NameListToString(stmt->cekname));
+
+	if (stmt->isDrop)
+	{
+		Oid			cmkoid = 0;
+		Oid			cekdataoid;
+		ObjectAddress obj;
+
+		parse_cek_attributes(pstate, stmt->definition, &cmkoid, NULL, NULL);
+		cekdataoid = get_cekdata_oid(cekoid, cmkoid, false);
+		ObjectAddressSet(obj, ColumnEncKeyDataRelationId, cekdataoid);
+		performDeletion(&obj, DROP_CASCADE, 0);
+	}
+	else
+	{
+		Oid			cmkoid = 0;
+		int			alg;
+		char	   *encval;
+
+		parse_cek_attributes(pstate, stmt->definition, &cmkoid, &alg, &encval);
+		if (get_cekdata_oid(cekoid, cmkoid, true))
+			ereport(ERROR,
+					errcode(ERRCODE_DUPLICATE_OBJECT),
+					errmsg("column encryption key \"%s\" already has data for master key \"%s\"",
+						   NameListToString(stmt->cekname), get_cmk_name(cmkoid, false)));
+		insert_cekdata_record(cekoid, cmkoid, alg, encval);
+	}
+
+	InvokeObjectPostAlterHook(ColumnEncKeyRelationId, cekoid, 0);
+	ObjectAddressSet(address, ColumnEncKeyRelationId, cekoid);
+
+	return address;
+}
+
+ObjectAddress
+CreateCMK(ParseState *pstate, DefineStmt *stmt)
+{
+	Oid			namespaceId;
+	char	   *cmknamestr;
+	AclResult	aclresult;
+	Relation	rel;
+	ObjectAddress myself;
+	Oid			cmkoid;
+	ListCell   *lc;
+	DefElem    *realmEl = NULL;
+	char	   *realm;
+	NameData	cmkname;
+	Datum		values[Natts_pg_colmasterkey] = {0};
+	bool		nulls[Natts_pg_colmasterkey] = {0};
+	HeapTuple	tup;
+
+	namespaceId = QualifiedNameGetCreationNamespace(stmt->defnames, &cmknamestr);
+
+	aclresult = object_aclcheck(NamespaceRelationId, namespaceId, GetUserId(), ACL_CREATE);
+	if (aclresult != ACLCHECK_OK)
+		aclcheck_error(aclresult, OBJECT_SCHEMA, get_namespace_name(namespaceId));
+
+	rel = table_open(ColumnMasterKeyRelationId, RowExclusiveLock);
+
+	if (SearchSysCacheExists2(CMKNAMENSP, PointerGetDatum(cmknamestr), ObjectIdGetDatum(namespaceId)))
+		ereport(ERROR,
+				errcode(ERRCODE_DUPLICATE_OBJECT),
+				errmsg("column master key \"%s\" already exists", cmknamestr));
+
+	foreach(lc, stmt->definition)
+	{
+		DefElem    *defel = lfirst_node(DefElem, lc);
+		DefElem   **defelp;
+
+		if (strcmp(defel->defname, "realm") == 0)
+			defelp = &realmEl;
+		else
+		{
+			ereport(ERROR,
+					(errcode(ERRCODE_SYNTAX_ERROR),
+					 errmsg("column master key attribute \"%s\" not recognized",
+							defel->defname),
+					 parser_errposition(pstate, defel->location)));
+		}
+		if (*defelp != NULL)
+			errorConflictingDefElem(defel, pstate);
+		*defelp = defel;
+	}
+
+	if (realmEl)
+		realm = defGetString(realmEl);
+	else
+		realm = "";
+
+	cmkoid = GetNewOidWithIndex(rel, ColumnMasterKeyOidIndexId, Anum_pg_colmasterkey_oid);
+	namestrcpy(&cmkname, cmknamestr);
+	values[Anum_pg_colmasterkey_oid - 1] = ObjectIdGetDatum(cmkoid);
+	values[Anum_pg_colmasterkey_cmkname - 1] = NameGetDatum(&cmkname);
+	values[Anum_pg_colmasterkey_cmknamespace - 1] = ObjectIdGetDatum(namespaceId);
+	values[Anum_pg_colmasterkey_cmkowner - 1] = ObjectIdGetDatum(GetUserId());
+	values[Anum_pg_colmasterkey_cmkrealm - 1] = CStringGetTextDatum(realm);
+
+	tup = heap_form_tuple(RelationGetDescr(rel), values, nulls);
+	CatalogTupleInsert(rel, tup);
+	heap_freetuple(tup);
+
+	recordDependencyOnOwner(ColumnMasterKeyRelationId, cmkoid, GetUserId());
+
+	ObjectAddressSet(myself, ColumnMasterKeyRelationId, cmkoid);
+
+	table_close(rel, RowExclusiveLock);
+
+	InvokeObjectPostCreateHook(ColumnMasterKeyRelationId, cmkoid, 0);
+
+	return myself;
+}
+
+ObjectAddress
+AlterColumnMasterKey(ParseState *pstate, AlterColumnMasterKeyStmt *stmt)
+{
+	Oid			cmkoid;
+	Relation	rel;
+	HeapTuple	tup;
+	HeapTuple	newtup;
+	ObjectAddress address;
+	ListCell   *lc;
+	DefElem    *realmEl = NULL;
+	Datum		values[Natts_pg_colmasterkey] = {0};
+	bool		nulls[Natts_pg_colmasterkey] = {0};
+	bool		replaces[Natts_pg_colmasterkey] = {0};
+
+	cmkoid = get_cmk_oid(stmt->cmkname, false);
+
+	rel = table_open(ColumnMasterKeyRelationId, RowExclusiveLock);
+
+	tup = SearchSysCache1(CMKOID, ObjectIdGetDatum(cmkoid));
+
+	if (!HeapTupleIsValid(tup))
+		elog(ERROR, "cache lookup failed for column master key %u", cmkoid);
+
+	if (!object_ownercheck(ColumnMasterKeyRelationId, cmkoid, GetUserId()))
+		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_CMK, NameListToString(stmt->cmkname));
+
+	foreach(lc, stmt->definition)
+	{
+		DefElem    *defel = lfirst_node(DefElem, lc);
+		DefElem   **defelp;
+
+		if (strcmp(defel->defname, "realm") == 0)
+			defelp = &realmEl;
+		else
+		{
+			ereport(ERROR,
+					(errcode(ERRCODE_SYNTAX_ERROR),
+					 errmsg("column master key attribute \"%s\" not recognized",
+							defel->defname),
+					 parser_errposition(pstate, defel->location)));
+		}
+		if (*defelp != NULL)
+			errorConflictingDefElem(defel, pstate);
+		*defelp = defel;
+	}
+
+	if (realmEl)
+	{
+		values[Anum_pg_colmasterkey_cmkrealm - 1] = CStringGetTextDatum(defGetString(realmEl));
+		replaces[Anum_pg_colmasterkey_cmkrealm - 1] = true;
+	}
+
+	newtup = heap_modify_tuple(tup, RelationGetDescr(rel), values, nulls, replaces);
+
+	CatalogTupleUpdate(rel, &tup->t_self, newtup);
+
+	InvokeObjectPostAlterHook(ColumnMasterKeyRelationId, cmkoid, 0);
+
+	ObjectAddressSet(address, ColumnMasterKeyRelationId, cmkoid);
+
+	heap_freetuple(newtup);
+	ReleaseSysCache(tup);
+
+	table_close(rel, RowExclusiveLock);
+
+	return address;
+}
diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c
index d6c6d514f3..d53c283ad8 100644
--- a/src/backend/commands/createas.c
+++ b/src/backend/commands/createas.c
@@ -50,6 +50,7 @@
 #include "utils/rel.h"
 #include "utils/rls.h"
 #include "utils/snapmgr.h"
+#include "utils/syscache.h"
 
 typedef struct
 {
@@ -205,6 +206,26 @@ create_ctas_nodata(List *tlist, IntoClause *into)
 								format_type_be(col->typeName->typeOid)),
 						 errhint("Use the COLLATE clause to set the collation explicitly.")));
 
+			if (type_is_encrypted(exprType((Node *) tle->expr)))
+			{
+				HeapTuple	tp;
+				Form_pg_attribute orig_att;
+
+				if (!tle->resorigtbl || !tle->resorigcol)
+					ereport(ERROR,
+							errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+							errmsg("underlying table and column could not be determined for encrypted table column"));
+
+				tp = SearchSysCache2(ATTNUM, ObjectIdGetDatum(tle->resorigtbl), Int16GetDatum(tle->resorigcol));
+				if (!HeapTupleIsValid(tp))
+					elog(ERROR, "cache lookup failed for attribute %d of relation %u", tle->resorigcol, tle->resorigtbl);
+				orig_att = (Form_pg_attribute) GETSTRUCT(tp);
+				col->typeName = makeTypeNameFromOid(orig_att->attrealtypid,
+													orig_att->atttypmod);
+				col->encryption = makeColumnEncryption(orig_att);
+				ReleaseSysCache(tp);
+			}
+
 			attrList = lappend(attrList, col);
 		}
 	}
@@ -514,6 +535,17 @@ intorel_startup(DestReceiver *self, int operation, TupleDesc typeinfo)
 							format_type_be(col->typeName->typeOid)),
 					 errhint("Use the COLLATE clause to set the collation explicitly.")));
 
+		if (type_is_encrypted(attribute->atttypid))
+		{
+			/*
+			 * We don't have the required information available here, so
+			 * prevent it for now.
+			 */
+			ereport(ERROR,
+					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+					 errmsg("encrypted columns not yet implemented for this command")));
+		}
+
 		attrList = lappend(attrList, col);
 	}
 
diff --git a/src/backend/commands/dropcmds.c b/src/backend/commands/dropcmds.c
index 82bda15889..b4c681005a 100644
--- a/src/backend/commands/dropcmds.c
+++ b/src/backend/commands/dropcmds.c
@@ -276,6 +276,20 @@ does_not_exist_skipping(ObjectType objtype, Node *object)
 				name = NameListToString(castNode(List, object));
 			}
 			break;
+		case OBJECT_CEK:
+			if (!schema_does_not_exist_skipping(castNode(List, object), &msg, &name))
+			{
+				msg = gettext_noop("column encryption key \"%s\" does not exist, skipping");
+				name = NameListToString(castNode(List, object));
+			}
+			break;
+		case OBJECT_CMK:
+			if (!schema_does_not_exist_skipping(castNode(List, object), &msg, &name))
+			{
+				msg = gettext_noop("column master key \"%s\" does not exist, skipping");
+				name = NameListToString(castNode(List, object));
+			}
+			break;
 		case OBJECT_CONVERSION:
 			if (!schema_does_not_exist_skipping(castNode(List, object), &msg, &name))
 			{
@@ -503,6 +517,7 @@ does_not_exist_skipping(ObjectType objtype, Node *object)
 		case OBJECT_AMOP:
 		case OBJECT_AMPROC:
 		case OBJECT_ATTRIBUTE:
+		case OBJECT_CEKDATA:
 		case OBJECT_DEFAULT:
 		case OBJECT_DEFACL:
 		case OBJECT_DOMCONSTRAINT:
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index d4b00d1a82..4c1628cf7b 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -951,6 +951,9 @@ EventTriggerSupportsObjectType(ObjectType obtype)
 		case OBJECT_AMPROC:
 		case OBJECT_ATTRIBUTE:
 		case OBJECT_CAST:
+		case OBJECT_CEK:
+		case OBJECT_CEKDATA:
+		case OBJECT_CMK:
 		case OBJECT_COLUMN:
 		case OBJECT_COLLATION:
 		case OBJECT_CONVERSION:
@@ -1027,6 +1030,9 @@ EventTriggerSupportsObjectClass(ObjectClass objclass)
 		case OCLASS_TYPE:
 		case OCLASS_CAST:
 		case OCLASS_COLLATION:
+		case OCLASS_CEK:
+		case OCLASS_CEKDATA:
+		case OCLASS_CMK:
 		case OCLASS_CONSTRAINT:
 		case OCLASS_CONVERSION:
 		case OCLASS_DEFAULT:
@@ -2056,6 +2062,9 @@ stringify_grant_objtype(ObjectType objtype)
 		case OBJECT_AMPROC:
 		case OBJECT_ATTRIBUTE:
 		case OBJECT_CAST:
+		case OBJECT_CEK:
+		case OBJECT_CEKDATA:
+		case OBJECT_CMK:
 		case OBJECT_COLLATION:
 		case OBJECT_CONVERSION:
 		case OBJECT_DEFAULT:
@@ -2139,6 +2148,9 @@ stringify_adefprivs_objtype(ObjectType objtype)
 		case OBJECT_AMPROC:
 		case OBJECT_ATTRIBUTE:
 		case OBJECT_CAST:
+		case OBJECT_CEK:
+		case OBJECT_CEKDATA:
+		case OBJECT_CMK:
 		case OBJECT_COLLATION:
 		case OBJECT_CONVERSION:
 		case OBJECT_DEFAULT:
diff --git a/src/backend/commands/meson.build b/src/backend/commands/meson.build
index 42cced9ebe..4b5ac30441 100644
--- a/src/backend/commands/meson.build
+++ b/src/backend/commands/meson.build
@@ -7,6 +7,7 @@ backend_sources += files(
   'analyze.c',
   'async.c',
   'cluster.c',
+  'colenccmds.c',
   'collationcmds.c',
   'comment.c',
   'constraint.c',
diff --git a/src/backend/commands/seclabel.c b/src/backend/commands/seclabel.c
index 7ff16e3276..93d61c96ad 100644
--- a/src/backend/commands/seclabel.c
+++ b/src/backend/commands/seclabel.c
@@ -66,6 +66,9 @@ SecLabelSupportsObjectType(ObjectType objtype)
 		case OBJECT_AMPROC:
 		case OBJECT_ATTRIBUTE:
 		case OBJECT_CAST:
+		case OBJECT_CEK:
+		case OBJECT_CEKDATA:
+		case OBJECT_CMK:
 		case OBJECT_COLLATION:
 		case OBJECT_CONVERSION:
 		case OBJECT_DEFAULT:
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 1293545947..62454bc265 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -35,6 +35,7 @@
 #include "catalog/partition.h"
 #include "catalog/pg_am.h"
 #include "catalog/pg_attrdef.h"
+#include "catalog/pg_colenckey.h"
 #include "catalog/pg_collation.h"
 #include "catalog/pg_constraint.h"
 #include "catalog/pg_depend.h"
@@ -61,6 +62,7 @@
 #include "commands/trigger.h"
 #include "commands/typecmds.h"
 #include "commands/user.h"
+#include "common/colenc.h"
 #include "executor/executor.h"
 #include "foreign/fdwapi.h"
 #include "foreign/foreign.h"
@@ -637,6 +639,7 @@ static List *GetParentedForeignKeyRefs(Relation partition);
 static void ATDetachCheckNoForeignKeyRefs(Relation partition);
 static char GetAttributeCompression(Oid atttypid, char *compression);
 static char GetAttributeStorage(Oid atttypid, const char *storagemode);
+static void GetColumnEncryption(const List *coldefencryption, Form_pg_attribute attr);
 
 
 /* ----------------------------------------------------------------
@@ -936,6 +939,9 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 			attr->attcompression = GetAttributeCompression(attr->atttypid,
 														   colDef->compression);
 
+		if (colDef->encryption)
+			GetColumnEncryption(colDef->encryption, attr);
+
 		if (colDef->storage_name)
 			attr->attstorage = GetAttributeStorage(attr->atttypid, colDef->storage_name);
 	}
@@ -2562,13 +2568,43 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 				Oid			defCollId;
 
 				/*
-				 * Yes, try to merge the two column definitions. They must
-				 * have the same type, typmod, and collation.
+				 * Yes, try to merge the two column definitions.
 				 */
 				ereport(NOTICE,
 						(errmsg("merging multiple inherited definitions of column \"%s\"",
 								attributeName)));
 				def = (ColumnDef *) list_nth(inhSchema, exist_attno - 1);
+
+				/*
+				 * Check encryption parameter.  All parents must have the same
+				 * encryption settings for a column.
+				 */
+				if ((def->encryption && !attribute->attcek) ||
+					(!def->encryption && attribute->attcek))
+				{
+					ereport(ERROR,
+							(errcode(ERRCODE_DATATYPE_MISMATCH),
+							 errmsg("column \"%s\" has an encryption specification conflict",
+									attributeName)));
+				}
+				else if (def->encryption && attribute->attcek)
+				{
+					/*
+					 * Merging the encryption properties of two encrypted
+					 * parent columns is not yet implemented.  Right now, this
+					 * would confuse the checks of the type etc. below (we
+					 * must check the physical and the real types against each
+					 * other, respectively), which might require a larger
+					 * restructuring.  For now, just give up here.
+					 */
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("multiple inheritance of encrypted columns is not implemented")));
+				}
+
+				/*
+				 * Must have the same type, typmod, and collation.
+				 */
 				typenameTypeIdAndMod(NULL, def->typeName, &defTypeId, &deftypmod);
 				if (defTypeId != attribute->atttypid ||
 					deftypmod != attribute->atttypmod)
@@ -2641,6 +2677,12 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 				def->colname = pstrdup(attributeName);
 				def->typeName = makeTypeNameFromOid(attribute->atttypid,
 													attribute->atttypmod);
+				if (type_is_encrypted(attribute->atttypid))
+				{
+					def->typeName = makeTypeNameFromOid(attribute->attrealtypid,
+														attribute->atttypmod);
+					def->encryption = makeColumnEncryption(attribute);
+				}
 				def->inhcount = 1;
 				def->is_local = false;
 				def->is_not_null = attribute->attnotnull;
@@ -2919,6 +2961,34 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 								 errdetail("%s versus %s", def->compression, newdef->compression)));
 				}
 
+				/*
+				 * Check encryption parameter.  All parents and children must
+				 * have the same encryption settings for a column.
+				 */
+				if ((def->encryption && !newdef->encryption) ||
+					(!def->encryption && newdef->encryption))
+				{
+					ereport(ERROR,
+							(errcode(ERRCODE_DATATYPE_MISMATCH),
+							 errmsg("column \"%s\" has an encryption specification conflict",
+									attributeName)));
+				}
+				else if (def->encryption && newdef->encryption)
+				{
+					FormData_pg_attribute a, newa;
+
+					GetColumnEncryption(def->encryption, &a);
+					GetColumnEncryption(newdef->encryption, &newa);
+
+					if (a.atttypid != newa.atttypid ||
+						a.attcek != newa.attcek ||
+						a.attencalg != newa.attencalg)
+						ereport(ERROR,
+								(errcode(ERRCODE_DATATYPE_MISMATCH),
+								 errmsg("column \"%s\" has an encryption specification conflict",
+										attributeName)));
+				}
+
 				/* Mark the column as locally defined */
 				def->is_local = true;
 				/* Merge of NOT NULL constraints = OR 'em together */
@@ -6844,6 +6914,14 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	attribute.attislocal = colDef->is_local;
 	attribute.attinhcount = colDef->inhcount;
 	attribute.attcollation = collOid;
+	if (colDef->encryption)
+		GetColumnEncryption(colDef->encryption, &attribute);
+	else
+	{
+		attribute.attcek = 0;
+		attribute.attrealtypid = 0;
+		attribute.attencalg = 0;
+	}
 
 	ReleaseSysCache(typeTuple);
 
@@ -12675,6 +12753,9 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 			case OCLASS_TYPE:
 			case OCLASS_CAST:
 			case OCLASS_COLLATION:
+			case OCLASS_CEK:
+			case OCLASS_CEKDATA:
+			case OCLASS_CMK:
 			case OCLASS_CONVERSION:
 			case OCLASS_LANGUAGE:
 			case OCLASS_LARGEOBJECT:
@@ -19274,3 +19355,108 @@ GetAttributeStorage(Oid atttypid, const char *storagemode)
 
 	return cstorage;
 }
+
+/*
+ * resolve column encryption specification
+ */
+static void
+GetColumnEncryption(const List *coldefencryption, Form_pg_attribute attr)
+{
+	ListCell   *lc;
+	List	   *cek = NULL;
+	Oid			cekoid;
+	bool		encdet = false;
+	int			alg = PG_CEK_AEAD_AES_128_CBC_HMAC_SHA_256;
+
+	foreach(lc, coldefencryption)
+	{
+		DefElem    *el = lfirst_node(DefElem, lc);
+
+		if (strcmp(el->defname, "column_encryption_key") == 0)
+			cek = defGetQualifiedName(el);
+		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
+				ereport(ERROR,
+						errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						errmsg("unrecognized encryption type: %s", val));
+		}
+		else if (strcmp(el->defname, "algorithm") == 0)
+		{
+			char	   *val = strVal(el->arg);
+
+			alg = get_cekalg_num(val);
+
+			if (!alg)
+				ereport(ERROR,
+						errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						errmsg("unrecognized encryption algorithm: %s", val));
+		}
+		else
+			ereport(ERROR,
+					errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+					errmsg("unrecognized column encryption parameter: %s", el->defname));
+	}
+
+	if (!cek)
+		ereport(ERROR,
+				errcode(ERRCODE_INVALID_COLUMN_DEFINITION),
+				errmsg("column encryption key must be specified"));
+
+	cekoid = get_cek_oid(cek, false);
+
+	attr->attcek = cekoid;
+	attr->attrealtypid = attr->atttypid;
+	attr->attencalg = alg;
+
+	/* override physical type */
+	if (encdet)
+		attr->atttypid = PG_ENCRYPTED_DETOID;
+	else
+		attr->atttypid = PG_ENCRYPTED_RNDOID;
+	get_typlenbyvalalign(attr->atttypid,
+						 &attr->attlen, &attr->attbyval, &attr->attalign);
+	attr->attstorage = get_typstorage(attr->atttypid);
+	attr->attcollation = InvalidOid;
+}
+
+/*
+ * Construct input to GetColumnEncryption(), for synthesizing a column
+ * definition.
+ */
+List *
+makeColumnEncryption(const FormData_pg_attribute *attr)
+{
+	List	   *result;
+	HeapTuple	tup;
+	Form_pg_colenckey form;
+	char	   *nspname;
+
+	tup = SearchSysCache1(CEKOID, ObjectIdGetDatum(attr->attcek));
+	if (!HeapTupleIsValid(tup))
+		elog(ERROR, "cache lookup failed for column encryption key %u", attr->attcek);
+
+	form = (Form_pg_colenckey) GETSTRUCT(tup);
+	nspname = get_namespace_name(form->ceknamespace);
+
+	result = list_make3(makeDefElem("column_encryption_key",
+									(Node *) list_make2(makeString(nspname), makeString(pstrdup(NameStr(form->cekname)))),
+									-1),
+						makeDefElem("encryption_type",
+									(Node *) makeTypeName(attr->atttypid == PG_ENCRYPTED_DETOID ?
+														  "deterministic" : "randomized"),
+									-1),
+						makeDefElem("algorithm",
+									(Node *) makeString(pstrdup(get_cekalg_name(attr->attencalg))),
+									-1));
+
+	ReleaseSysCache(tup);
+
+	return result;
+}
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index bb0f5de4c2..cec2daa9c3 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -25,6 +25,7 @@
 #include "access/xlogprefetcher.h"
 #include "catalog/pg_authid.h"
 #include "common/string.h"
+#include "libpq/libpq-be.h"
 #include "mb/pg_wchar.h"
 #include "miscadmin.h"
 #include "postmaster/postmaster.h"
@@ -706,7 +707,11 @@ check_client_encoding(char **newval, void **extra, GucSource source)
 	 */
 	if (PrepareClientEncoding(encoding) < 0)
 	{
-		if (IsTransactionState())
+		if (MyProcPort->column_encryption_enabled)
+			GUC_check_errdetail("Conversion between %s and %s is not possible when column encryption is enabled.",
+								canonical_name,
+								GetDatabaseEncodingName());
+		else if (IsTransactionState())
 		{
 			/* Must be a genuine no-such-conversion problem */
 			GUC_check_errcode(ERRCODE_FEATURE_NOT_SUPPORTED);
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index ff98c773f5..c93e2faa94 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -88,6 +88,26 @@ DefineVirtualRelation(RangeVar *relation, List *tlist, bool replace,
 			else
 				Assert(!OidIsValid(def->collOid));
 
+			if (type_is_encrypted(exprType((Node *) tle->expr)))
+			{
+				HeapTuple	tp;
+				Form_pg_attribute orig_att;
+
+				if (!tle->resorigtbl || !tle->resorigcol)
+					ereport(ERROR,
+							errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+							errmsg("underlying table and column could not be determined for encrypted view column"));
+
+				tp = SearchSysCache2(ATTNUM, ObjectIdGetDatum(tle->resorigtbl), Int16GetDatum(tle->resorigcol));
+				if (!HeapTupleIsValid(tp))
+					elog(ERROR, "cache lookup failed for attribute %d of relation %u", tle->resorigcol, tle->resorigtbl);
+				orig_att = (Form_pg_attribute) GETSTRUCT(tp);
+				def->typeName = makeTypeNameFromOid(orig_att->attrealtypid,
+													orig_att->atttypmod);
+				def->encryption = makeColumnEncryption(orig_att);
+				ReleaseSysCache(tp);
+			}
+
 			attrList = lappend(attrList, def);
 		}
 	}
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index 8fc24c882b..2cd32b3637 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -3982,6 +3982,8 @@ raw_expression_tree_walker_impl(Node *node,
 					return true;
 				if (WALK(coldef->compression))
 					return true;
+				if (WALK(coldef->encryption))
+					return true;
 				if (WALK(coldef->raw_default))
 					return true;
 				if (WALK(coldef->collClause))
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index a0138382a1..8d0727cac6 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -280,6 +280,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 
 %type <node>	stmt toplevel_stmt schema_stmt routine_body_stmt
 		AlterEventTrigStmt AlterCollationStmt
+		AlterColumnEncryptionKeyStmt AlterColumnMasterKeyStmt
 		AlterDatabaseStmt AlterDatabaseSetStmt AlterDomainStmt AlterEnumStmt
 		AlterFdwStmt AlterForeignServerStmt AlterGroupStmt
 		AlterObjectDependsStmt AlterObjectSchemaStmt AlterOwnerStmt
@@ -419,6 +420,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 
 %type <list>	parse_toplevel stmtmulti routine_body_stmt_list
 				OptTableElementList TableElementList OptInherit definition
+				list_of_definitions
 				OptTypedTableElementList TypedTableElementList
 				reloptions opt_reloptions
 				OptWith opt_definition func_args func_args_list
@@ -592,6 +594,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <node>	TableConstraint TableLikeClause
 %type <ival>	TableLikeOptionList TableLikeOption
 %type <str>		column_compression opt_column_compression column_storage opt_column_storage
+%type <list>	opt_column_encryption
 %type <list>	ColQualList
 %type <node>	ColConstraint ColConstraintElem ConstraintAttr
 %type <ival>	key_match
@@ -690,8 +693,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	DETACH DICTIONARY DISABLE_P DISCARD DISTINCT DO DOCUMENT_P DOMAIN_P
 	DOUBLE_P DROP
 
-	EACH ELSE ENABLE_P ENCODING ENCRYPTED END_P ENUM_P ESCAPE EVENT EXCEPT
-	EXCLUDE EXCLUDING EXCLUSIVE EXECUTE EXISTS EXPLAIN EXPRESSION
+	EACH ELSE ENABLE_P ENCODING ENCRYPTION ENCRYPTED END_P ENUM_P ESCAPE
+	EVENT EXCEPT EXCLUDE EXCLUDING EXCLUSIVE EXECUTE EXISTS EXPLAIN EXPRESSION
 	EXTENSION EXTERNAL EXTRACT
 
 	FALSE_P FAMILY FETCH FILTER FINALIZE FIRST_P FLOAT_P FOLLOWING FOR
@@ -714,7 +717,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	LEADING LEAKPROOF LEAST LEFT LEVEL LIKE LIMIT LISTEN LOAD LOCAL
 	LOCALTIME LOCALTIMESTAMP LOCATION LOCK_P LOCKED LOGGED
 
-	MAPPING MATCH MATCHED MATERIALIZED MAXVALUE MERGE METHOD
+	MAPPING MASTER MATCH MATCHED MATERIALIZED MAXVALUE MERGE METHOD
 	MINUTE_P MINVALUE MODE MONTH_P MOVE
 
 	NAME_P NAMES NATIONAL NATURAL NCHAR NEW NEXT NFC NFD NFKC NFKD NO NONE
@@ -942,6 +945,8 @@ toplevel_stmt:
 stmt:
 			AlterEventTrigStmt
 			| AlterCollationStmt
+			| AlterColumnEncryptionKeyStmt
+			| AlterColumnMasterKeyStmt
 			| AlterDatabaseStmt
 			| AlterDatabaseSetStmt
 			| AlterDefaultPrivilegesStmt
@@ -3700,14 +3705,15 @@ TypedTableElement:
 			| TableConstraint					{ $$ = $1; }
 		;
 
-columnDef:	ColId Typename opt_column_storage opt_column_compression create_generic_options ColQualList
+columnDef:	ColId Typename opt_column_encryption opt_column_storage opt_column_compression create_generic_options ColQualList
 				{
 					ColumnDef *n = makeNode(ColumnDef);
 
 					n->colname = $1;
 					n->typeName = $2;
-					n->storage_name = $3;
-					n->compression = $4;
+					n->encryption = $3;
+					n->storage_name = $4;
+					n->compression = $5;
 					n->inhcount = 0;
 					n->is_local = true;
 					n->is_not_null = false;
@@ -3716,8 +3722,8 @@ columnDef:	ColId Typename opt_column_storage opt_column_compression create_gener
 					n->raw_default = NULL;
 					n->cooked_default = NULL;
 					n->collOid = InvalidOid;
-					n->fdwoptions = $5;
-					SplitColQualList($6, &n->constraints, &n->collClause,
+					n->fdwoptions = $6;
+					SplitColQualList($7, &n->constraints, &n->collClause,
 									 yyscanner);
 					n->location = @1;
 					$$ = (Node *) n;
@@ -3774,6 +3780,11 @@ opt_column_compression:
 			| /*EMPTY*/								{ $$ = NULL; }
 		;
 
+opt_column_encryption:
+			ENCRYPTED WITH '(' def_list ')'			{ $$ = $4; }
+			| /*EMPTY*/								{ $$ = NULL; }
+		;
+
 column_storage:
 			STORAGE ColId							{ $$ = $2; }
 			| STORAGE DEFAULT						{ $$ = pstrdup("default"); }
@@ -4034,6 +4045,7 @@ TableLikeOption:
 				| COMPRESSION		{ $$ = CREATE_TABLE_LIKE_COMPRESSION; }
 				| CONSTRAINTS		{ $$ = CREATE_TABLE_LIKE_CONSTRAINTS; }
 				| DEFAULTS			{ $$ = CREATE_TABLE_LIKE_DEFAULTS; }
+				| ENCRYPTED			{ $$ = CREATE_TABLE_LIKE_ENCRYPTED; }
 				| IDENTITY_P		{ $$ = CREATE_TABLE_LIKE_IDENTITY; }
 				| GENERATED			{ $$ = CREATE_TABLE_LIKE_GENERATED; }
 				| INDEXES			{ $$ = CREATE_TABLE_LIKE_INDEXES; }
@@ -6270,6 +6282,33 @@ DefineStmt:
 					n->if_not_exists = true;
 					$$ = (Node *) n;
 				}
+			| CREATE COLUMN ENCRYPTION KEY any_name WITH VALUES list_of_definitions
+				{
+					DefineStmt *n = makeNode(DefineStmt);
+
+					n->kind = OBJECT_CEK;
+					n->defnames = $5;
+					n->definition = $8;
+					$$ = (Node *) n;
+				}
+			| CREATE COLUMN MASTER KEY any_name
+				{
+					DefineStmt *n = makeNode(DefineStmt);
+
+					n->kind = OBJECT_CMK;
+					n->defnames = $5;
+					n->definition = NIL;
+					$$ = (Node *) n;
+				}
+			| CREATE COLUMN MASTER KEY any_name WITH definition
+				{
+					DefineStmt *n = makeNode(DefineStmt);
+
+					n->kind = OBJECT_CMK;
+					n->defnames = $5;
+					n->definition = $7;
+					$$ = (Node *) n;
+				}
 		;
 
 definition: '(' def_list ')'						{ $$ = $2; }
@@ -6289,6 +6328,10 @@ def_elem:	ColLabel '=' def_arg
 				}
 		;
 
+list_of_definitions: definition						{ $$ = list_make1($1); }
+			| list_of_definitions ',' definition	{ $$ = lappend($1, $3); }
+		;
+
 /* Note: any simple identifier will be returned as a type name! */
 def_arg:	func_type						{ $$ = (Node *) $1; }
 			| reserved_keyword				{ $$ = (Node *) makeString(pstrdup($1)); }
@@ -6800,6 +6843,8 @@ object_type_any_name:
 			| INDEX									{ $$ = OBJECT_INDEX; }
 			| FOREIGN TABLE							{ $$ = OBJECT_FOREIGN_TABLE; }
 			| COLLATION								{ $$ = OBJECT_COLLATION; }
+			| COLUMN ENCRYPTION KEY					{ $$ = OBJECT_CEK; }
+			| COLUMN MASTER KEY						{ $$ = OBJECT_CMK; }
 			| CONVERSION_P							{ $$ = OBJECT_CONVERSION; }
 			| STATISTICS							{ $$ = OBJECT_STATISTIC_EXT; }
 			| TEXT_P SEARCH PARSER					{ $$ = OBJECT_TSPARSER; }
@@ -9140,6 +9185,26 @@ RenameStmt: ALTER AGGREGATE aggregate_with_argtypes RENAME TO name
 					n->missing_ok = false;
 					$$ = (Node *) n;
 				}
+			| ALTER COLUMN ENCRYPTION KEY any_name RENAME TO name
+				{
+					RenameStmt *n = makeNode(RenameStmt);
+
+					n->renameType = OBJECT_CEK;
+					n->object = (Node *) $5;
+					n->newname = $8;
+					n->missing_ok = false;
+					$$ = (Node *) n;
+				}
+			| ALTER COLUMN MASTER KEY any_name RENAME TO name
+				{
+					RenameStmt *n = makeNode(RenameStmt);
+
+					n->renameType = OBJECT_CMK;
+					n->object = (Node *) $5;
+					n->newname = $8;
+					n->missing_ok = false;
+					$$ = (Node *) n;
+				}
 			| ALTER CONVERSION_P any_name RENAME TO name
 				{
 					RenameStmt *n = makeNode(RenameStmt);
@@ -9817,6 +9882,26 @@ AlterObjectSchemaStmt:
 					n->missing_ok = false;
 					$$ = (Node *) n;
 				}
+			| ALTER COLUMN ENCRYPTION KEY any_name SET SCHEMA name
+				{
+					AlterObjectSchemaStmt *n = makeNode(AlterObjectSchemaStmt);
+
+					n->objectType = OBJECT_CEK;
+					n->object = (Node *) $5;
+					n->newschema = $8;
+					n->missing_ok = false;
+					$$ = (Node *) n;
+				}
+			| ALTER COLUMN MASTER KEY any_name SET SCHEMA name
+				{
+					AlterObjectSchemaStmt *n = makeNode(AlterObjectSchemaStmt);
+
+					n->objectType = OBJECT_CMK;
+					n->object = (Node *) $5;
+					n->newschema = $8;
+					n->missing_ok = false;
+					$$ = (Node *) n;
+				}
 			| ALTER CONVERSION_P any_name SET SCHEMA name
 				{
 					AlterObjectSchemaStmt *n = makeNode(AlterObjectSchemaStmt);
@@ -10148,6 +10233,24 @@ AlterOwnerStmt: ALTER AGGREGATE aggregate_with_argtypes OWNER TO RoleSpec
 					n->newowner = $6;
 					$$ = (Node *) n;
 				}
+			| ALTER COLUMN ENCRYPTION KEY any_name OWNER TO RoleSpec
+				{
+					AlterOwnerStmt *n = makeNode(AlterOwnerStmt);
+
+					n->objectType = OBJECT_CEK;
+					n->object = (Node *) $5;
+					n->newowner = $8;
+					$$ = (Node *) n;
+				}
+			| ALTER COLUMN MASTER KEY any_name OWNER TO RoleSpec
+				{
+					AlterOwnerStmt *n = makeNode(AlterOwnerStmt);
+
+					n->objectType = OBJECT_CMK;
+					n->object = (Node *) $5;
+					n->newowner = $8;
+					$$ = (Node *) n;
+				}
 			| ALTER CONVERSION_P any_name OWNER TO RoleSpec
 				{
 					AlterOwnerStmt *n = makeNode(AlterOwnerStmt);
@@ -11304,6 +11407,52 @@ AlterCollationStmt: ALTER COLLATION any_name REFRESH VERSION_P
 		;
 
 
+/*****************************************************************************
+ *
+ *		ALTER COLUMN ENCRYPTION KEY
+ *
+ *****************************************************************************/
+
+AlterColumnEncryptionKeyStmt:
+			ALTER COLUMN ENCRYPTION KEY any_name ADD_P VALUE_P definition
+				{
+					AlterColumnEncryptionKeyStmt *n = makeNode(AlterColumnEncryptionKeyStmt);
+
+					n->cekname = $5;
+					n->isDrop = false;
+					n->definition = $8;
+					$$ = (Node *) n;
+				}
+			| ALTER COLUMN ENCRYPTION KEY any_name DROP VALUE_P definition
+				{
+					AlterColumnEncryptionKeyStmt *n = makeNode(AlterColumnEncryptionKeyStmt);
+
+					n->cekname = $5;
+					n->isDrop = true;
+					n->definition = $8;
+					$$ = (Node *) n;
+				}
+		;
+
+
+/*****************************************************************************
+ *
+ *		ALTER COLUMN MASTER KEY
+ *
+ *****************************************************************************/
+
+AlterColumnMasterKeyStmt:
+			ALTER COLUMN MASTER KEY any_name definition
+				{
+					AlterColumnMasterKeyStmt *n = makeNode(AlterColumnMasterKeyStmt);
+
+					n->cmkname = $5;
+					n->definition = $6;
+					$$ = (Node *) n;
+				}
+		;
+
+
 /*****************************************************************************
  *
  *		ALTER SYSTEM
@@ -16791,6 +16940,7 @@ unreserved_keyword:
 			| ENABLE_P
 			| ENCODING
 			| ENCRYPTED
+			| ENCRYPTION
 			| ENUM_P
 			| ESCAPE
 			| EVENT
@@ -16854,6 +17004,7 @@ unreserved_keyword:
 			| LOCKED
 			| LOGGED
 			| MAPPING
+			| MASTER
 			| MATCH
 			| MATCHED
 			| MATERIALIZED
@@ -17337,6 +17488,7 @@ bare_label_keyword:
 			| ENABLE_P
 			| ENCODING
 			| ENCRYPTED
+			| ENCRYPTION
 			| END_P
 			| ENUM_P
 			| ESCAPE
@@ -17425,6 +17577,7 @@ bare_label_keyword:
 			| LOCKED
 			| LOGGED
 			| MAPPING
+			| MASTER
 			| MATCH
 			| MATCHED
 			| MATERIALIZED
diff --git a/src/backend/parser/parse_param.c b/src/backend/parser/parse_param.c
index 2240284f21..bcfb2d087f 100644
--- a/src/backend/parser/parse_param.c
+++ b/src/backend/parser/parse_param.c
@@ -29,6 +29,7 @@
 #include "catalog/pg_type.h"
 #include "nodes/nodeFuncs.h"
 #include "parser/parse_param.h"
+#include "parser/parsetree.h"
 #include "utils/builtins.h"
 #include "utils/lsyscache.h"
 
@@ -357,3 +358,146 @@ query_contains_extern_params_walker(Node *node, void *context)
 	return expression_tree_walker(node, query_contains_extern_params_walker,
 								  context);
 }
+
+/*
+ * Walk a query tree and find out what tables and columns a parameter is
+ * associated with.
+ *
+ * We need to find 1) parameters written directly into a table column, and 2)
+ * binary predicates relating a parameter to a table column.
+ *
+ * We just need to find Var and Param nodes in appropriate places.  We don't
+ * need to do harder things like looking through casts, since this is used for
+ * column encryption, and encrypted columns can't be usefully cast to
+ * anything.
+ */
+
+struct find_param_origs_context
+{
+	const Query *query;
+	Oid		   *param_orig_tbls;
+	AttrNumber *param_orig_cols;
+};
+
+static bool
+find_param_origs_walker(Node *node, struct find_param_origs_context *context)
+{
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, OpExpr) || IsA(node, DistinctExpr) || IsA(node, NullIfExpr))
+	{
+		OpExpr	   *opexpr = (OpExpr *) node;
+
+		if (list_length(opexpr->args) == 2)
+		{
+			Node	   *lexpr = linitial(opexpr->args);
+			Node	   *rexpr = lsecond(opexpr->args);
+			Var		   *v = NULL;
+			Param	   *p = NULL;
+
+			if (IsA(lexpr, Var) && IsA(rexpr, Param))
+			{
+				v = castNode(Var, lexpr);
+				p = castNode(Param, rexpr);
+			}
+			else if (IsA(rexpr, Var) && IsA(lexpr, Param))
+			{
+				v = castNode(Var, rexpr);
+				p = castNode(Param, lexpr);
+			}
+
+			if (v && p)
+			{
+				RangeTblEntry *rte;
+
+				rte = rt_fetch(v->varno, context->query->rtable);
+				if (rte->rtekind == RTE_RELATION)
+				{
+					context->param_orig_tbls[p->paramid - 1] = rte->relid;
+					context->param_orig_cols[p->paramid - 1] = v->varattno;
+				}
+			}
+		}
+		return false;
+	}
+
+	/*
+	 * TargetEntry in a query with a result relation
+	 */
+	if (IsA(node, TargetEntry) && context->query->resultRelation > 0)
+	{
+		TargetEntry *te = (TargetEntry *) node;
+		RangeTblEntry *resrte;
+
+		resrte = rt_fetch(context->query->resultRelation, context->query->rtable);
+		if (resrte->rtekind == RTE_RELATION)
+		{
+			/*
+			 * Param directly in a target list
+			 */
+			if (IsA(te->expr, Param))
+			{
+				Param	   *p = (Param *) te->expr;
+
+				context->param_orig_tbls[p->paramid - 1] = resrte->relid;
+				context->param_orig_cols[p->paramid - 1] = te->resno;
+			}
+			/*
+			 * If it's a Var, check whether it corresponds to a VALUES list
+			 * with top-level parameters.  This covers multi-row INSERTS.
+			 */
+			else if (IsA(te->expr, Var))
+			{
+				Var	   *v = (Var *) te->expr;
+				RangeTblEntry *srcrte;
+
+				srcrte = rt_fetch(v->varno, context->query->rtable);
+				if (srcrte->rtekind == RTE_VALUES)
+				{
+					ListCell *lc;
+
+					foreach (lc, srcrte->values_lists)
+					{
+						List *values_list = lfirst_node(List, lc);
+						Node *value = list_nth(values_list, v->varattno - 1);
+
+						if (IsA(value, Param))
+						{
+							Param	   *p = (Param *) value;
+
+							context->param_orig_tbls[p->paramid - 1] = resrte->relid;
+							context->param_orig_cols[p->paramid - 1] = te->resno;
+						}
+					}
+				}
+			}
+		}
+		return false;
+	}
+
+	if (IsA(node, Query))
+	{
+		return query_tree_walker((Query *) node, find_param_origs_walker, context, 0);
+	}
+
+	return expression_tree_walker(node, find_param_origs_walker, context);
+}
+
+void
+find_param_origs(List *query_list, Oid **param_orig_tbls, AttrNumber **param_orig_cols)
+{
+	struct find_param_origs_context context;
+	ListCell   *lc;
+
+	context.param_orig_tbls = *param_orig_tbls;
+	context.param_orig_cols = *param_orig_cols;
+
+	foreach (lc, query_list)
+	{
+		Query	   *query = lfirst_node(Query, lc);
+
+		context.query = query;
+		query_tree_walker(query, find_param_origs_walker, &context, 0);
+	}
+}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index f9218f48aa..00492e1eaf 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -1035,8 +1035,16 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla
 		 */
 		def = makeNode(ColumnDef);
 		def->colname = pstrdup(attributeName);
-		def->typeName = makeTypeNameFromOid(attribute->atttypid,
-											attribute->atttypmod);
+		if (type_is_encrypted(attribute->atttypid))
+		{
+			def->typeName = makeTypeNameFromOid(attribute->attrealtypid,
+												attribute->atttypmod);
+			if (table_like_clause->options & CREATE_TABLE_LIKE_ENCRYPTED)
+				def->encryption = makeColumnEncryption(attribute);
+		}
+		else
+			def->typeName = makeTypeNameFromOid(attribute->atttypid,
+												attribute->atttypmod);
 		def->inhcount = 0;
 		def->is_local = true;
 		def->is_not_null = attribute->attnotnull;
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index 5b775cf7d0..958fd65ac4 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -2209,12 +2209,27 @@ ProcessStartupPacket(Port *port, bool ssl_done, bool gss_done)
 									valptr),
 							 errhint("Valid values are: \"false\", 0, \"true\", 1, \"database\".")));
 			}
+			else if (strcmp(nameptr, "_pq_.column_encryption") == 0)
+			{
+				/*
+				 * Right now, the only accepted value is "1".  This gives room
+				 * to expand this into a version number, for example.
+				 */
+				if (strcmp(valptr, "1") == 0)
+					port->column_encryption_enabled = true;
+				else
+					ereport(FATAL,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for parameter \"%s\": \"%s\"",
+									"column_encryption",
+									valptr),
+							 errhint("Valid values are: 1.")));
+			}
 			else if (strncmp(nameptr, "_pq_.", 5) == 0)
 			{
 				/*
 				 * Any option beginning with _pq_. is reserved for use as a
-				 * protocol-level option, but at present no such options are
-				 * defined.
+				 * protocol-level option.
 				 */
 				unrecognized_protocol_options =
 					lappend(unrecognized_protocol_options, pstrdup(nameptr));
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index 470b734e9e..e20ab8966c 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -44,6 +44,7 @@
 #include "nodes/print.h"
 #include "optimizer/optimizer.h"
 #include "parser/analyze.h"
+#include "parser/parse_param.h"
 #include "parser/parser.h"
 #include "pg_getopt.h"
 #include "pg_trace.h"
@@ -71,6 +72,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"
 
@@ -1812,6 +1814,16 @@ exec_bind_message(StringInfo input_message)
 			else
 				pformat = 0;	/* default = text */
 
+			if (type_is_encrypted(ptype))
+			{
+				if (pformat & 0xF0)
+					pformat &= ~0xF0;
+				else
+					ereport(ERROR,
+							(errcode(ERRCODE_PROTOCOL_VIOLATION),
+							 errmsg("parameter %d corresponds to an encrypted column, but the parameter value was not encrypted", paramno + 1)));
+			}
+
 			if (pformat == 0)	/* text mode */
 			{
 				Oid			typinput;
@@ -2553,6 +2565,8 @@ static void
 exec_describe_statement_message(const char *stmt_name)
 {
 	CachedPlanSource *psrc;
+	Oid		   *param_orig_tbls;
+	AttrNumber *param_orig_cols;
 
 	/*
 	 * Start up a transaction command. (Note that this will normally change
@@ -2611,11 +2625,61 @@ exec_describe_statement_message(const char *stmt_name)
 														 * message type */
 	pq_sendint16(&row_description_buf, psrc->num_params);
 
+	/*
+	 * If column encryption is enabled, find the associated tables and columns
+	 * for any parameters, so that we can determine encryption information for
+	 * them.
+	 */
+	if (MyProcPort->column_encryption_enabled && psrc->num_params)
+	{
+		param_orig_tbls = palloc0_array(Oid, psrc->num_params);
+		param_orig_cols = palloc0_array(AttrNumber, psrc->num_params);
+
+		RevalidateCachedQuery(psrc, NULL);
+		find_param_origs(psrc->query_list, &param_orig_tbls, &param_orig_cols);
+	}
+
 	for (int i = 0; i < psrc->num_params; i++)
 	{
 		Oid			ptype = psrc->param_types[i];
+		Oid			pcekid = InvalidOid;
+		int			pcekalg = 0;
+		int16		pflags = 0;
+
+		if (MyProcPort->column_encryption_enabled && type_is_encrypted(ptype))
+		{
+			Oid			porigtbl = param_orig_tbls[i];
+			AttrNumber	porigcol = param_orig_cols[i];
+			HeapTuple	tp;
+			Form_pg_attribute orig_att;
+
+			if (porigtbl == InvalidOid || porigcol == InvalidAttrNumber)
+				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 + 1)));
+
+			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);
+			ptype = orig_att->attrealtypid;
+			pcekid = orig_att->attcek;
+			pcekalg = orig_att->attencalg;
+			ReleaseSysCache(tp);
+
+			if (psrc->param_types[i] == PG_ENCRYPTED_DETOID)
+				pflags |= 0x01;
+
+			MaybeSendColumnEncryptionKeyMessage(pcekid);
+		}
 
 		pq_sendint32(&row_description_buf, (int) ptype);
+		if (MyProcPort->column_encryption_enabled)
+		{
+			pq_sendint32(&row_description_buf, (int) pcekid);
+			pq_sendint32(&row_description_buf, pcekalg);
+			pq_sendint16(&row_description_buf, pflags);
+		}
 	}
 	pq_endmessage_reuse(&row_description_buf);
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index c7d9d96b45..0a17eb4486 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -30,6 +30,7 @@
 #include "commands/alter.h"
 #include "commands/async.h"
 #include "commands/cluster.h"
+#include "commands/colenccmds.h"
 #include "commands/collationcmds.h"
 #include "commands/comment.h"
 #include "commands/conversioncmds.h"
@@ -137,6 +138,8 @@ ClassifyUtilityCommandAsReadOnly(Node *parsetree)
 	switch (nodeTag(parsetree))
 	{
 		case T_AlterCollationStmt:
+		case T_AlterColumnEncryptionKeyStmt:
+		case T_AlterColumnMasterKeyStmt:
 		case T_AlterDatabaseRefreshCollStmt:
 		case T_AlterDatabaseSetStmt:
 		case T_AlterDatabaseStmt:
@@ -1441,6 +1444,14 @@ ProcessUtilitySlow(ParseState *pstate,
 															stmt->definition,
 															&secondaryObject);
 							break;
+						case OBJECT_CEK:
+							Assert(stmt->args == NIL);
+							address = CreateCEK(pstate, stmt);
+							break;
+						case OBJECT_CMK:
+							Assert(stmt->args == NIL);
+							address = CreateCMK(pstate, stmt);
+							break;
 						case OBJECT_COLLATION:
 							Assert(stmt->args == NIL);
 							address = DefineCollation(pstate,
@@ -1903,6 +1914,14 @@ ProcessUtilitySlow(ParseState *pstate,
 				address = AlterCollation((AlterCollationStmt *) parsetree);
 				break;
 
+			case T_AlterColumnEncryptionKeyStmt:
+				address = AlterColumnEncryptionKey(pstate, (AlterColumnEncryptionKeyStmt *) parsetree);
+				break;
+
+			case T_AlterColumnMasterKeyStmt:
+				address = AlterColumnMasterKey(pstate, (AlterColumnMasterKeyStmt *) parsetree);
+				break;
+
 			default:
 				elog(ERROR, "unrecognized node type: %d",
 					 (int) nodeTag(parsetree));
@@ -2225,6 +2244,12 @@ AlterObjectTypeCommandTag(ObjectType objtype)
 		case OBJECT_COLUMN:
 			tag = CMDTAG_ALTER_TABLE;
 			break;
+		case OBJECT_CEK:
+			tag = CMDTAG_ALTER_COLUMN_ENCRYPTION_KEY;
+			break;
+		case OBJECT_CMK:
+			tag = CMDTAG_ALTER_COLUMN_MASTER_KEY;
+			break;
 		case OBJECT_CONVERSION:
 			tag = CMDTAG_ALTER_CONVERSION;
 			break;
@@ -2640,6 +2665,12 @@ CreateCommandTag(Node *parsetree)
 				case OBJECT_STATISTIC_EXT:
 					tag = CMDTAG_DROP_STATISTICS;
 					break;
+				case OBJECT_CEK:
+					tag = CMDTAG_DROP_COLUMN_ENCRYPTION_KEY;
+					break;
+				case OBJECT_CMK:
+					tag = CMDTAG_DROP_COLUMN_MASTER_KEY;
+					break;
 				default:
 					tag = CMDTAG_UNKNOWN;
 			}
@@ -2760,6 +2791,12 @@ CreateCommandTag(Node *parsetree)
 				case OBJECT_COLLATION:
 					tag = CMDTAG_CREATE_COLLATION;
 					break;
+				case OBJECT_CEK:
+					tag = CMDTAG_CREATE_COLUMN_ENCRYPTION_KEY;
+					break;
+				case OBJECT_CMK:
+					tag = CMDTAG_CREATE_COLUMN_MASTER_KEY;
+					break;
 				case OBJECT_ACCESS_METHOD:
 					tag = CMDTAG_CREATE_ACCESS_METHOD;
 					break;
@@ -3063,6 +3100,14 @@ CreateCommandTag(Node *parsetree)
 			tag = CMDTAG_ALTER_COLLATION;
 			break;
 
+		case T_AlterColumnEncryptionKeyStmt:
+			tag = CMDTAG_ALTER_COLUMN_ENCRYPTION_KEY;
+			break;
+
+		case T_AlterColumnMasterKeyStmt:
+			tag = CMDTAG_ALTER_COLUMN_MASTER_KEY;
+			break;
+
 		case T_PrepareStmt:
 			tag = CMDTAG_PREPARE;
 			break;
@@ -3688,6 +3733,14 @@ GetCommandLogLevel(Node *parsetree)
 			lev = LOGSTMT_DDL;
 			break;
 
+		case T_AlterColumnEncryptionKeyStmt:
+			lev = LOGSTMT_DDL;
+			break;
+
+		case T_AlterColumnMasterKeyStmt:
+			lev = LOGSTMT_DDL;
+			break;
+
 			/* already-planned queries */
 		case T_PlannedStmt:
 			{
diff --git a/src/backend/utils/adt/varlena.c b/src/backend/utils/adt/varlena.c
index fd81c47474..af0bca7d3e 100644
--- a/src/backend/utils/adt/varlena.c
+++ b/src/backend/utils/adt/varlena.c
@@ -686,6 +686,112 @@ unknownsend(PG_FUNCTION_ARGS)
 	PG_RETURN_BYTEA_P(pq_endtypsend(&buf));
 }
 
+/*
+ * pg_encrypted_in -
+ *
+ * Input function for pg_encrypted_* types.
+ *
+ * The format and additional checks ensure that one cannot easily insert a
+ * value directly into an encrypted column by accident.  (That's why we don't
+ * just use the bytea format, for example.)  But we still have to support
+ * direct inserts into encrypted columns, for example for restoring backups
+ * made by pg_dump.
+ */
+Datum
+pg_encrypted_in(PG_FUNCTION_ARGS)
+{
+	char	   *inputText = PG_GETARG_CSTRING(0);
+	char	   *ip;
+	size_t		hexlen;
+	int			bc;
+	bytea	   *result;
+
+	if (strncmp(inputText, "encrypted$", 10) != 0)
+		ereport(ERROR,
+				errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+				errmsg("invalid input value for encrypted column value: \"%s\"",
+					   inputText));
+
+	ip = inputText + 10;
+	hexlen = strlen(ip);
+
+	/* sanity check to catch obvious mistakes */
+	if (hexlen / 2 < 32 || (hexlen / 2) % 16 != 0)
+		ereport(ERROR,
+				errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+				errmsg("invalid input value for encrypted column value: \"%s\"",
+					   inputText));
+
+	bc = hexlen / 2 + VARHDRSZ;	/* maximum possible length */
+	result = palloc(bc);
+	bc = hex_decode(ip, hexlen, VARDATA(result));
+	SET_VARSIZE(result, bc + VARHDRSZ); /* actual length */
+
+	PG_RETURN_BYTEA_P(result);
+}
+
+/*
+ * pg_encrypted_out -
+ *
+ * Output function for pg_encrypted_* types.
+ *
+ * This output is seen when reading an encrypted column without column
+ * encryption mode enabled.  Therefore, the output format is chosen so that it
+ * is easily recognizable.
+ */
+Datum
+pg_encrypted_out(PG_FUNCTION_ARGS)
+{
+	bytea	   *vlena = PG_GETARG_BYTEA_PP(0);
+	char	   *result;
+	char	   *rp;
+
+	rp = result = palloc(VARSIZE_ANY_EXHDR(vlena) * 2 + 10 + 1);
+	memcpy(rp, "encrypted$", 10);
+	rp += 10;
+	rp += hex_encode(VARDATA_ANY(vlena), VARSIZE_ANY_EXHDR(vlena), rp);
+	*rp = '\0';
+	PG_RETURN_CSTRING(result);
+}
+
+/*
+ * pg_encrypted_recv -
+ *
+ * Receive function for pg_encrypted_* types.
+ */
+Datum
+pg_encrypted_recv(PG_FUNCTION_ARGS)
+{
+	StringInfo	buf = (StringInfo) PG_GETARG_POINTER(0);
+	bytea	   *result;
+	int			nbytes;
+
+	nbytes = buf->len - buf->cursor;
+	/* sanity check to catch obvious mistakes */
+	if (nbytes < 32)
+		ereport(ERROR,
+				errcode(ERRCODE_INVALID_BINARY_REPRESENTATION),
+				errmsg("invalid binary input value for encrypted column value"));
+	result = (bytea *) palloc(nbytes + VARHDRSZ);
+	SET_VARSIZE(result, nbytes + VARHDRSZ);
+	pq_copymsgbytes(buf, VARDATA(result), nbytes);
+	PG_RETURN_BYTEA_P(result);
+}
+
+/*
+ * pg_encrypted_send -
+ *
+ * Send function for pg_encrypted_* types.
+ */
+Datum
+pg_encrypted_send(PG_FUNCTION_ARGS)
+{
+	bytea	   *vlena = PG_GETARG_BYTEA_P_COPY(0);
+
+	/* just return input */
+	PG_RETURN_BYTEA_P(vlena);
+}
+
 
 /* ========== PUBLIC ROUTINES ========== */
 
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index c07382051d..7ad159110f 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -24,7 +24,10 @@
 #include "catalog/pg_amop.h"
 #include "catalog/pg_amproc.h"
 #include "catalog/pg_cast.h"
+#include "catalog/pg_colenckey.h"
+#include "catalog/pg_colenckeydata.h"
 #include "catalog/pg_collation.h"
+#include "catalog/pg_colmasterkey.h"
 #include "catalog/pg_constraint.h"
 #include "catalog/pg_language.h"
 #include "catalog/pg_namespace.h"
@@ -2658,6 +2661,25 @@ type_is_multirange(Oid typid)
 	return (get_typtype(typid) == TYPTYPE_MULTIRANGE);
 }
 
+bool
+type_is_encrypted(Oid typid)
+{
+	HeapTuple	tp;
+
+	tp = SearchSysCache1(TYPEOID, ObjectIdGetDatum(typid));
+	if (HeapTupleIsValid(tp))
+	{
+		Form_pg_type typtup = (Form_pg_type) GETSTRUCT(tp);
+		bool		result;
+
+		result = (typtup->typcategory == TYPCATEGORY_ENCRYPTED);
+		ReleaseSysCache(tp);
+		return result;
+	}
+	else
+		return false;
+}
+
 /*
  * get_type_category_preferred
  *
@@ -3683,3 +3705,64 @@ get_subscription_name(Oid subid, bool missing_ok)
 
 	return subname;
 }
+
+char *
+get_cek_name(Oid cekid, bool missing_ok)
+{
+	HeapTuple	tup;
+	char	   *cekname;
+
+	tup = SearchSysCache1(CEKOID, ObjectIdGetDatum(cekid));
+
+	if (!HeapTupleIsValid(tup))
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for column encryption key %u", cekid);
+		return NULL;
+	}
+
+	cekname = pstrdup(NameStr(((Form_pg_colenckey) GETSTRUCT(tup))->cekname));
+
+	ReleaseSysCache(tup);
+
+	return cekname;
+}
+
+Oid
+get_cekdata_oid(Oid cekid, Oid cmkid, bool missing_ok)
+{
+	Oid			cekdataid;
+
+	cekdataid = GetSysCacheOid2(CEKDATACEKCMK, Anum_pg_colenckeydata_oid,
+								ObjectIdGetDatum(cekid),
+								ObjectIdGetDatum(cmkid));
+	if (!OidIsValid(cekdataid) && !missing_ok)
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_OBJECT),
+				 errmsg("column encryption key \"%s\" has no data for master key \"%s\"",
+						get_cek_name(cekid, false), get_cmk_name(cmkid, false))));
+
+	return cekdataid;
+}
+
+char *
+get_cmk_name(Oid cmkid, bool missing_ok)
+{
+	HeapTuple	tup;
+	char	   *cmkname;
+
+	tup = SearchSysCache1(CMKOID, ObjectIdGetDatum(cmkid));
+
+	if (!HeapTupleIsValid(tup))
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for column master key %u", cmkid);
+		return NULL;
+	}
+
+	cmkname = pstrdup(NameStr(((Form_pg_colmasterkey) GETSTRUCT(tup))->cmkname));
+
+	ReleaseSysCache(tup);
+
+	return cmkname;
+}
diff --git a/src/backend/utils/cache/plancache.c b/src/backend/utils/cache/plancache.c
index 77c2ba3f8f..d40af13efe 100644
--- a/src/backend/utils/cache/plancache.c
+++ b/src/backend/utils/cache/plancache.c
@@ -97,8 +97,6 @@ static dlist_head saved_plan_list = DLIST_STATIC_INIT(saved_plan_list);
 static dlist_head cached_expression_list = DLIST_STATIC_INIT(cached_expression_list);
 
 static void ReleaseGenericPlan(CachedPlanSource *plansource);
-static List *RevalidateCachedQuery(CachedPlanSource *plansource,
-								   QueryEnvironment *queryEnv);
 static bool CheckCachedPlan(CachedPlanSource *plansource);
 static CachedPlan *BuildCachedPlan(CachedPlanSource *plansource, List *qlist,
 								   ParamListInfo boundParams, QueryEnvironment *queryEnv);
@@ -551,7 +549,7 @@ ReleaseGenericPlan(CachedPlanSource *plansource)
  * had to do re-analysis, and NIL otherwise.  (This is returned just to save
  * a tree copying step in a subsequent BuildCachedPlan call.)
  */
-static List *
+List *
 RevalidateCachedQuery(CachedPlanSource *plansource,
 					  QueryEnvironment *queryEnv)
 {
diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index 94abede512..eb432260da 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -29,7 +29,10 @@
 #include "catalog/pg_auth_members.h"
 #include "catalog/pg_authid.h"
 #include "catalog/pg_cast.h"
+#include "catalog/pg_colenckey.h"
+#include "catalog/pg_colenckeydata.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"
@@ -219,6 +222,32 @@ static const struct cachedesc cacheinfo[] = {
 			Anum_pg_cast_casttarget),
 		256
 	},
+	[CEKDATACEKCMK] = {
+		ColumnEncKeyDataRelationId,
+		ColumnEncKeyCekidCmkidIndexId,
+		KEY(Anum_pg_colenckeydata_ckdcekid,
+			Anum_pg_colenckeydata_ckdcmkid),
+		8
+	},
+	[CEKDATAOID] = {
+		ColumnEncKeyDataRelationId,
+		ColumnEncKeyDataOidIndexId,
+		KEY(Anum_pg_colenckeydata_oid),
+		8
+	},
+	[CEKNAMENSP] = {
+		ColumnEncKeyRelationId,
+		ColumnEncKeyNameNspIndexId,
+		KEY(Anum_pg_colenckey_cekname,
+			Anum_pg_colenckey_ceknamespace),
+		8
+	},
+	[CEKOID] = {
+		ColumnEncKeyRelationId,
+		ColumnEncKeyOidIndexId,
+		KEY(Anum_pg_colenckey_oid),
+		8
+	},
 	[CLAAMNAMENSP] = {
 		OperatorClassRelationId,
 		OpclassAmNameNspIndexId,
@@ -233,6 +262,19 @@ static const struct cachedesc cacheinfo[] = {
 		KEY(Anum_pg_opclass_oid),
 		8
 	},
+	[CMKNAMENSP] = {
+		ColumnMasterKeyRelationId,
+		ColumnMasterKeyNameNspIndexId,
+		KEY(Anum_pg_colmasterkey_cmkname,
+			Anum_pg_colmasterkey_cmknamespace),
+		8
+	},
+	[CMKOID] = {
+		ColumnMasterKeyRelationId,
+		ColumnMasterKeyOidIndexId,
+		KEY(Anum_pg_colmasterkey_oid),
+		8
+	},
 	[COLLNAMEENCNSP] = {
 		CollationRelationId,
 		CollationNameEncNspIndexId,
diff --git a/src/backend/utils/mb/mbutils.c b/src/backend/utils/mb/mbutils.c
index 033647011b..4b6be68f27 100644
--- a/src/backend/utils/mb/mbutils.c
+++ b/src/backend/utils/mb/mbutils.c
@@ -36,7 +36,9 @@
 
 #include "access/xact.h"
 #include "catalog/namespace.h"
+#include "libpq/libpq-be.h"
 #include "mb/pg_wchar.h"
+#include "miscadmin.h"
 #include "utils/builtins.h"
 #include "utils/memutils.h"
 #include "utils/syscache.h"
@@ -130,6 +132,12 @@ PrepareClientEncoding(int encoding)
 		encoding == PG_SQL_ASCII)
 		return 0;
 
+	/*
+	 * Cannot do conversion when column encryption is enabled.
+	 */
+	if (MyProcPort->column_encryption_enabled)
+		return -1;
+
 	if (IsTransactionState())
 	{
 		/*
@@ -237,6 +245,12 @@ SetClientEncoding(int encoding)
 		return 0;
 	}
 
+	/*
+	 * Cannot do conversion when column encryption is enabled.
+	 */
+	if (MyProcPort->column_encryption_enabled)
+		return -1;
+
 	/*
 	 * Search the cache for the entry previously prepared by
 	 * PrepareClientEncoding; if there isn't one, we lose.  While at it,
@@ -297,7 +311,9 @@ InitializeClientEncoding(void)
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				 errmsg("conversion between %s and %s is not supported",
 						pg_enc2name_tbl[pending_client_encoding].name,
-						GetDatabaseEncodingName())));
+						GetDatabaseEncodingName()),
+				 (MyProcPort->column_encryption_enabled) ?
+				 errdetail("Encoding conversion is not possible when column encryption is enabled.") : 0));
 	}
 
 	/*
diff --git a/src/bin/pg_dump/common.c b/src/bin/pg_dump/common.c
index a43f2e5553..e67462c81a 100644
--- a/src/bin/pg_dump/common.c
+++ b/src/bin/pg_dump/common.c
@@ -18,7 +18,9 @@
 #include <ctype.h>
 
 #include "catalog/pg_class_d.h"
+#include "catalog/pg_colenckey_d.h"
 #include "catalog/pg_collation_d.h"
+#include "catalog/pg_colmasterkey_d.h"
 #include "catalog/pg_extension_d.h"
 #include "catalog/pg_namespace_d.h"
 #include "catalog/pg_operator_d.h"
@@ -201,6 +203,12 @@ getSchemaData(Archive *fout, int *numTablesPtr)
 	pg_log_info("reading user-defined collations");
 	(void) getCollations(fout, &numCollations);
 
+	pg_log_info("reading column master keys");
+	getColumnMasterKeys(fout);
+
+	pg_log_info("reading column encryption keys");
+	getColumnEncryptionKeys(fout);
+
 	pg_log_info("reading user-defined conversions");
 	getConversions(fout, &numConversions);
 
@@ -859,6 +867,42 @@ findOprByOid(Oid oid)
 	return (OprInfo *) dobj;
 }
 
+/*
+ * findCekByOid
+ *	  finds the DumpableObject for the CEK with the given oid
+ *	  returns NULL if not found
+ */
+CekInfo *
+findCekByOid(Oid oid)
+{
+	CatalogId	catId;
+	DumpableObject *dobj;
+
+	catId.tableoid = ColumnEncKeyRelationId;
+	catId.oid = oid;
+	dobj = findObjectByCatalogId(catId);
+	Assert(dobj == NULL || dobj->objType == DO_CEK);
+	return (CekInfo *) dobj;
+}
+
+/*
+ * findCmkByOid
+ *	  finds the DumpableObject for the CMK with the given oid
+ *	  returns NULL if not found
+ */
+CmkInfo *
+findCmkByOid(Oid oid)
+{
+	CatalogId	catId;
+	DumpableObject *dobj;
+
+	catId.tableoid = ColumnMasterKeyRelationId;
+	catId.oid = oid;
+	dobj = findObjectByCatalogId(catId);
+	Assert(dobj == NULL || dobj->objType == DO_CMK);
+	return (CmkInfo *) dobj;
+}
+
 /*
  * findCollationByOid
  *	  finds the DumpableObject for the collation with the given oid
diff --git a/src/bin/pg_dump/pg_backup.h b/src/bin/pg_dump/pg_backup.h
index aba780ef4b..afba79b2ea 100644
--- a/src/bin/pg_dump/pg_backup.h
+++ b/src/bin/pg_dump/pg_backup.h
@@ -85,6 +85,7 @@ typedef struct _connParams
 	char	   *pghost;
 	char	   *username;
 	trivalue	promptPassword;
+	int			column_encryption;
 	/* If not NULL, this overrides the dbname obtained from command line */
 	/* (but *only* the DB name, not anything else in the connstring) */
 	char	   *override_dbname;
diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c
index ba5e6acbbb..7f55eb2716 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -3430,6 +3430,8 @@ _getObjectDescription(PQExpBuffer buf, const TocEntry *te)
 
 	/* objects that don't require special decoration */
 	if (strcmp(type, "COLLATION") == 0 ||
+		strcmp(type, "COLUMN ENCRYPTION KEY") == 0 ||
+		strcmp(type, "COLUMN MASTER KEY") == 0 ||
 		strcmp(type, "CONVERSION") == 0 ||
 		strcmp(type, "DOMAIN") == 0 ||
 		strcmp(type, "FOREIGN TABLE") == 0 ||
diff --git a/src/bin/pg_dump/pg_backup_db.c b/src/bin/pg_dump/pg_backup_db.c
index f766b65059..c90c2803fc 100644
--- a/src/bin/pg_dump/pg_backup_db.c
+++ b/src/bin/pg_dump/pg_backup_db.c
@@ -133,8 +133,8 @@ ConnectDatabase(Archive *AHX,
 	 */
 	do
 	{
-		const char *keywords[8];
-		const char *values[8];
+		const char *keywords[9];
+		const char *values[9];
 		int			i = 0;
 
 		/*
@@ -159,6 +159,11 @@ ConnectDatabase(Archive *AHX,
 		}
 		keywords[i] = "fallback_application_name";
 		values[i++] = progname;
+		if (cparams->column_encryption)
+		{
+			keywords[i] = "column_encryption";
+			values[i++] = "1";
+		}
 		keywords[i] = NULL;
 		values[i++] = NULL;
 		Assert(i <= lengthof(keywords));
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 527c7651ab..e09eef540d 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -54,6 +54,7 @@
 #include "catalog/pg_subscription.h"
 #include "catalog/pg_trigger_d.h"
 #include "catalog/pg_type_d.h"
+#include "common/colenc.h"
 #include "common/connect.h"
 #include "common/relpath.h"
 #include "dumputils.h"
@@ -228,6 +229,8 @@ static void dumpAccessMethod(Archive *fout, const AccessMethodInfo *aminfo);
 static void dumpOpclass(Archive *fout, const OpclassInfo *opcinfo);
 static void dumpOpfamily(Archive *fout, const OpfamilyInfo *opfinfo);
 static void dumpCollation(Archive *fout, const CollInfo *collinfo);
+static void dumpColumnEncryptionKey(Archive *fout, const CekInfo *cekinfo);
+static void dumpColumnMasterKey(Archive *fout, const CmkInfo *cekinfo);
 static void dumpConversion(Archive *fout, const ConvInfo *convinfo);
 static void dumpRule(Archive *fout, const RuleInfo *rinfo);
 static void dumpAgg(Archive *fout, const AggInfo *agginfo);
@@ -393,6 +396,7 @@ main(int argc, char **argv)
 		{"attribute-inserts", no_argument, &dopt.column_inserts, 1},
 		{"binary-upgrade", no_argument, &dopt.binary_upgrade, 1},
 		{"column-inserts", no_argument, &dopt.column_inserts, 1},
+		{"decrypt-encrypted-columns", no_argument, &dopt.cparams.column_encryption, 1},
 		{"disable-dollar-quoting", no_argument, &dopt.disable_dollar_quoting, 1},
 		{"disable-triggers", no_argument, &dopt.disable_triggers, 1},
 		{"enable-row-security", no_argument, &dopt.enable_row_security, 1},
@@ -685,6 +689,9 @@ main(int argc, char **argv)
 	 * --inserts are already implied above if --column-inserts or
 	 * --rows-per-insert were specified.
 	 */
+	if (dopt.cparams.column_encryption && dopt.dump_inserts == 0)
+		pg_fatal("option --decrypt-encrypted-columns requires option --inserts, --rows-per-insert, or --column-inserts");
+
 	if (dopt.do_nothing && dopt.dump_inserts == 0)
 		pg_fatal("option --on-conflict-do-nothing requires option --inserts, --rows-per-insert, or --column-inserts");
 
@@ -1057,6 +1064,7 @@ help(const char *progname)
 	printf(_("  -x, --no-privileges          do not dump privileges (grant/revoke)\n"));
 	printf(_("  --binary-upgrade             for use by upgrade utilities only\n"));
 	printf(_("  --column-inserts             dump data as INSERT commands with column names\n"));
+	printf(_("  --decrypt-encrypted-columns  decrypt encrypted columns in the output\n"));
 	printf(_("  --disable-dollar-quoting     disable dollar quoting, use SQL standard quoting\n"));
 	printf(_("  --disable-triggers           disable triggers during data-only restore\n"));
 	printf(_("  --enable-row-security        enable row security (dump only content user has\n"
@@ -5572,6 +5580,144 @@ getCollations(Archive *fout, int *numCollations)
 	return collinfo;
 }
 
+/*
+ * getColumnEncryptionKeys
+ *	  get information about column encryption keys
+ */
+void
+getColumnEncryptionKeys(Archive *fout)
+{
+	PQExpBuffer query;
+	PGresult   *res;
+	int			ntups;
+	CekInfo	   *cekinfo;
+	int			i_tableoid;
+	int			i_oid;
+	int			i_cekname;
+	int			i_ceknamespace;
+	int			i_cekowner;
+
+	if (fout->remoteVersion < 160000)
+		return;
+
+	query = createPQExpBuffer();
+
+	appendPQExpBuffer(query,
+					  "SELECT cek.tableoid, cek.oid, cek.cekname, cek.ceknamespace, cek.cekowner\n"
+					  "FROM pg_colenckey cek");
+
+	res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+	ntups = PQntuples(res);
+
+	i_tableoid = PQfnumber(res, "tableoid");
+	i_oid = PQfnumber(res, "oid");
+	i_cekname = PQfnumber(res, "cekname");
+	i_ceknamespace = PQfnumber(res, "ceknamespace");
+	i_cekowner = PQfnumber(res, "cekowner");
+
+	cekinfo = pg_malloc(ntups * sizeof(CekInfo));
+
+	for (int i = 0; i < ntups; i++)
+	{
+		PGresult	   *res2;
+		int				ntups2;
+
+		cekinfo[i].dobj.objType = DO_CEK;
+		cekinfo[i].dobj.catId.tableoid = atooid(PQgetvalue(res, i, i_tableoid));
+		cekinfo[i].dobj.catId.oid = atooid(PQgetvalue(res, i, i_oid));
+		AssignDumpId(&cekinfo[i].dobj);
+		cekinfo[i].dobj.name = pg_strdup(PQgetvalue(res, i, i_cekname));
+		cekinfo[i].dobj.namespace = findNamespace(atooid(PQgetvalue(res, i, i_ceknamespace)));
+		cekinfo[i].rolname = getRoleName(PQgetvalue(res, i, i_cekowner));
+
+		resetPQExpBuffer(query);
+		appendPQExpBuffer(query,
+						  "SELECT ckdcmkid, ckdcmkalg, ckdencval\n"
+						  "FROM pg_catalog.pg_colenckeydata\n"
+						  "WHERE ckdcekid = %u", cekinfo[i].dobj.catId.oid);
+		res2 = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+		ntups2 = PQntuples(res2);
+		cekinfo[i].numdata = ntups2;
+		cekinfo[i].cekcmks = pg_malloc(sizeof(CmkInfo *) * ntups2);
+		cekinfo[i].cekcmkalgs = pg_malloc(sizeof(int) * ntups2);
+		cekinfo[i].cekencvals = pg_malloc(sizeof(char *) * ntups2);
+		for (int j = 0; j < ntups2; j++)
+		{
+			Oid			ckdcmkid;
+
+			ckdcmkid = atooid(PQgetvalue(res2, j, PQfnumber(res2, "ckdcmkid")));
+			cekinfo[i].cekcmks[j] = findCmkByOid(ckdcmkid);
+			cekinfo[i].cekcmkalgs[j] = atoi(PQgetvalue(res2, j, PQfnumber(res2, "ckdcmkalg")));
+			cekinfo[i].cekencvals[j] = pg_strdup(PQgetvalue(res2, j, PQfnumber(res2, "ckdencval")));
+		}
+		PQclear(res2);
+
+		selectDumpableObject(&(cekinfo[i].dobj), fout);
+	}
+	PQclear(res);
+
+	destroyPQExpBuffer(query);
+}
+
+/*
+ * getColumnMasterKeys
+ *	  get information about column master keys
+ */
+void
+getColumnMasterKeys(Archive *fout)
+{
+	PQExpBuffer query;
+	PGresult   *res;
+	int			ntups;
+	CmkInfo	   *cmkinfo;
+	int			i_tableoid;
+	int			i_oid;
+	int			i_cmkname;
+	int			i_cmknamespace;
+	int			i_cmkowner;
+	int			i_cmkrealm;
+
+	if (fout->remoteVersion < 160000)
+		return;
+
+	query = createPQExpBuffer();
+
+	appendPQExpBuffer(query,
+					  "SELECT cmk.tableoid, cmk.oid, cmk.cmkname, cmk.cmknamespace, cmk.cmkowner, cmk.cmkrealm\n"
+					  "FROM pg_colmasterkey cmk");
+
+	res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+	ntups = PQntuples(res);
+
+	i_tableoid = PQfnumber(res, "tableoid");
+	i_oid = PQfnumber(res, "oid");
+	i_cmkname = PQfnumber(res, "cmkname");
+	i_cmknamespace = PQfnumber(res, "cmknamespace");
+	i_cmkowner = PQfnumber(res, "cmkowner");
+	i_cmkrealm = PQfnumber(res, "cmkrealm");
+
+	cmkinfo = pg_malloc(ntups * sizeof(CmkInfo));
+
+	for (int i = 0; i < ntups; i++)
+	{
+		cmkinfo[i].dobj.objType = DO_CMK;
+		cmkinfo[i].dobj.catId.tableoid = atooid(PQgetvalue(res, i, i_tableoid));
+		cmkinfo[i].dobj.catId.oid = atooid(PQgetvalue(res, i, i_oid));
+		AssignDumpId(&cmkinfo[i].dobj);
+		cmkinfo[i].dobj.name = pg_strdup(PQgetvalue(res, i, i_cmkname));
+		cmkinfo[i].dobj.namespace = findNamespace(atooid(PQgetvalue(res, i, i_cmknamespace)));
+		cmkinfo[i].rolname = getRoleName(PQgetvalue(res, i, i_cmkowner));
+		cmkinfo[i].cmkrealm = pg_strdup(PQgetvalue(res, i, i_cmkrealm));
+
+		selectDumpableObject(&(cmkinfo[i].dobj), fout);
+	}
+	PQclear(res);
+
+	destroyPQExpBuffer(query);
+}
+
 /*
  * getConversions:
  *	  read all conversions in the system catalogs and return them in the
@@ -8188,6 +8334,9 @@ getTableAttrs(Archive *fout, TableInfo *tblinfo, int numTables)
 	int			i_typstorage;
 	int			i_attidentity;
 	int			i_attgenerated;
+	int			i_attcek;
+	int			i_attencalg;
+	int			i_attencdet;
 	int			i_attisdropped;
 	int			i_attlen;
 	int			i_attalign;
@@ -8297,20 +8446,35 @@ getTableAttrs(Archive *fout, TableInfo *tblinfo, int numTables)
 
 	if (fout->remoteVersion >= 120000)
 		appendPQExpBufferStr(q,
-							 "a.attgenerated\n");
+							 "a.attgenerated,\n");
 	else
 		appendPQExpBufferStr(q,
-							 "'' AS attgenerated\n");
+							 "'' AS attgenerated,\n");
+
+	if (fout->remoteVersion >= 160000)
+		appendPQExpBuffer(q,
+						  "a.attcek,\n"
+						  "CASE atttypid WHEN %u THEN true WHEN %u THEN false END AS attencdet,\n"
+						  "attencalg\n",
+						  PG_ENCRYPTED_DETOID, PG_ENCRYPTED_RNDOID);
+	else
+		appendPQExpBufferStr(q,
+							 "NULL AS attcek,\n"
+							 "NULL AS attencdet,\n"
+							 "NULL AS attencalg\n");
 
 	/* need left join to pg_type to not fail on dropped columns ... */
 	appendPQExpBuffer(q,
 					  "FROM unnest('%s'::pg_catalog.oid[]) AS src(tbloid)\n"
 					  "JOIN pg_catalog.pg_attribute a ON (src.tbloid = a.attrelid) "
 					  "LEFT JOIN pg_catalog.pg_type t "
-					  "ON (a.atttypid = t.oid)\n"
+					  "ON (%s = t.oid)\n"
 					  "WHERE a.attnum > 0::pg_catalog.int2\n"
 					  "ORDER BY a.attrelid, a.attnum",
-					  tbloids->data);
+					  tbloids->data,
+					  fout->remoteVersion >= 160000 ?
+					  "CASE WHEN a.attrealtypid <> 0 THEN a.attrealtypid ELSE a.atttypid END" :
+					  "a.atttypid");
 
 	res = ExecuteSqlQuery(fout, q->data, PGRES_TUPLES_OK);
 
@@ -8326,6 +8490,9 @@ getTableAttrs(Archive *fout, TableInfo *tblinfo, int numTables)
 	i_typstorage = PQfnumber(res, "typstorage");
 	i_attidentity = PQfnumber(res, "attidentity");
 	i_attgenerated = PQfnumber(res, "attgenerated");
+	i_attcek = PQfnumber(res, "attcek");
+	i_attencdet = PQfnumber(res, "attencdet");
+	i_attencalg = PQfnumber(res, "attencalg");
 	i_attisdropped = PQfnumber(res, "attisdropped");
 	i_attlen = PQfnumber(res, "attlen");
 	i_attalign = PQfnumber(res, "attalign");
@@ -8387,6 +8554,9 @@ getTableAttrs(Archive *fout, TableInfo *tblinfo, int numTables)
 		tbinfo->typstorage = (char *) pg_malloc(numatts * sizeof(char));
 		tbinfo->attidentity = (char *) pg_malloc(numatts * sizeof(char));
 		tbinfo->attgenerated = (char *) pg_malloc(numatts * sizeof(char));
+		tbinfo->attcek = (CekInfo **) pg_malloc(numatts * sizeof(CekInfo *));
+		tbinfo->attencdet = (bool *) pg_malloc(numatts * sizeof(bool));
+		tbinfo->attencalg = (int *) pg_malloc(numatts * sizeof(int));
 		tbinfo->attisdropped = (bool *) pg_malloc(numatts * sizeof(bool));
 		tbinfo->attlen = (int *) pg_malloc(numatts * sizeof(int));
 		tbinfo->attalign = (char *) pg_malloc(numatts * sizeof(char));
@@ -8415,6 +8585,22 @@ getTableAttrs(Archive *fout, TableInfo *tblinfo, int numTables)
 			tbinfo->attidentity[j] = *(PQgetvalue(res, r, i_attidentity));
 			tbinfo->attgenerated[j] = *(PQgetvalue(res, r, i_attgenerated));
 			tbinfo->needs_override = tbinfo->needs_override || (tbinfo->attidentity[j] == ATTRIBUTE_IDENTITY_ALWAYS);
+			if (!PQgetisnull(res, r, i_attcek))
+			{
+				Oid		attcekid = atooid(PQgetvalue(res, r, i_attcek));
+
+				tbinfo->attcek[j] = findCekByOid(attcekid);
+			}
+			else
+				tbinfo->attcek[j] = NULL;
+			if (!PQgetisnull(res, r, i_attencdet))
+				tbinfo->attencdet[j] = (PQgetvalue(res, r, i_attencdet)[0] == 't');
+			else
+				tbinfo->attencdet[j] = 0;
+			if (!PQgetisnull(res, r, i_attencalg))
+				tbinfo->attencalg[j] = atoi(PQgetvalue(res, r, i_attencalg));
+			else
+				tbinfo->attencalg[j] = 0;
 			tbinfo->attisdropped[j] = (PQgetvalue(res, r, i_attisdropped)[0] == 't');
 			tbinfo->attlen[j] = atoi(PQgetvalue(res, r, i_attlen));
 			tbinfo->attalign[j] = *(PQgetvalue(res, r, i_attalign));
@@ -9933,6 +10119,12 @@ dumpDumpableObject(Archive *fout, DumpableObject *dobj)
 		case DO_OPFAMILY:
 			dumpOpfamily(fout, (const OpfamilyInfo *) dobj);
 			break;
+		case DO_CEK:
+			dumpColumnEncryptionKey(fout, (const CekInfo *) dobj);
+			break;
+		case DO_CMK:
+			dumpColumnMasterKey(fout, (const CmkInfo *) dobj);
+			break;
 		case DO_COLLATION:
 			dumpCollation(fout, (const CollInfo *) dobj);
 			break;
@@ -13326,6 +13518,131 @@ dumpCollation(Archive *fout, const CollInfo *collinfo)
 	free(qcollname);
 }
 
+/*
+ * dumpColumnEncryptionKey
+ *	  dump the definition of the given column encryption key
+ */
+static void
+dumpColumnEncryptionKey(Archive *fout, const CekInfo *cekinfo)
+{
+	DumpOptions *dopt = fout->dopt;
+	PQExpBuffer delq;
+	PQExpBuffer query;
+	char	   *qcekname;
+
+	/* Do nothing in data-only dump */
+	if (dopt->dataOnly)
+		return;
+
+	delq = createPQExpBuffer();
+	query = createPQExpBuffer();
+
+	qcekname = pg_strdup(fmtId(cekinfo->dobj.name));
+
+	appendPQExpBuffer(delq, "DROP COLUMN ENCRYPTION KEY %s;\n",
+					  fmtQualifiedDumpable(cekinfo));
+
+	appendPQExpBuffer(query, "CREATE COLUMN ENCRYPTION KEY %s WITH VALUES ",
+					  fmtQualifiedDumpable(cekinfo));
+
+	for (int i = 0; i < cekinfo->numdata; i++)
+	{
+		appendPQExpBuffer(query, "(");
+
+		appendPQExpBuffer(query, "column_master_key = %s, ", fmtQualifiedDumpable(cekinfo->cekcmks[i]));
+		appendPQExpBuffer(query, "algorithm = '%s', ", get_cmkalg_name(cekinfo->cekcmkalgs[i]));
+		appendPQExpBuffer(query, "encrypted_value = ");
+		appendStringLiteralAH(query, cekinfo->cekencvals[i], fout);
+
+		appendPQExpBuffer(query, ")");
+		if (i < cekinfo->numdata - 1)
+			appendPQExpBuffer(query, ", ");
+	}
+
+	appendPQExpBufferStr(query, ";\n");
+
+	if (cekinfo->dobj.dump & DUMP_COMPONENT_DEFINITION)
+		ArchiveEntry(fout, cekinfo->dobj.catId, cekinfo->dobj.dumpId,
+					 ARCHIVE_OPTS(.tag = cekinfo->dobj.name,
+								  .namespace = cekinfo->dobj.namespace->dobj.name,
+								  .owner = cekinfo->rolname,
+								  .description = "COLUMN ENCRYPTION KEY",
+								  .section = SECTION_PRE_DATA,
+								  .createStmt = query->data,
+								  .dropStmt = delq->data));
+
+	if (cekinfo->dobj.dump & DUMP_COMPONENT_COMMENT)
+		dumpComment(fout, "COLUMN ENCRYPTION KEY", qcekname,
+					cekinfo->dobj.namespace->dobj.name, cekinfo->rolname,
+					cekinfo->dobj.catId, 0, cekinfo->dobj.dumpId);
+
+	if (cekinfo->dobj.dump & DUMP_COMPONENT_SECLABEL)
+		dumpSecLabel(fout, "COLUMN ENCRYPTION KEY", qcekname,
+					 cekinfo->dobj.namespace->dobj.name, cekinfo->rolname,
+					 cekinfo->dobj.catId, 0, cekinfo->dobj.dumpId);
+
+	destroyPQExpBuffer(delq);
+	destroyPQExpBuffer(query);
+	free(qcekname);
+}
+
+/*
+ * dumpColumnMasterKey
+ *	  dump the definition of the given column master key
+ */
+static void
+dumpColumnMasterKey(Archive *fout, const CmkInfo *cmkinfo)
+{
+	DumpOptions *dopt = fout->dopt;
+	PQExpBuffer delq;
+	PQExpBuffer query;
+	char	   *qcmkname;
+
+	/* Do nothing in data-only dump */
+	if (dopt->dataOnly)
+		return;
+
+	delq = createPQExpBuffer();
+	query = createPQExpBuffer();
+
+	qcmkname = pg_strdup(fmtId(cmkinfo->dobj.name));
+
+	appendPQExpBuffer(delq, "DROP COLUMN MASTER KEY %s;\n",
+					  fmtQualifiedDumpable(cmkinfo));
+
+	appendPQExpBuffer(query, "CREATE COLUMN MASTER KEY %s WITH (",
+					  fmtQualifiedDumpable(cmkinfo));
+
+	appendPQExpBuffer(query, "realm = ");
+	appendStringLiteralAH(query, cmkinfo->cmkrealm, fout);
+
+	appendPQExpBufferStr(query, ");\n");
+
+	if (cmkinfo->dobj.dump & DUMP_COMPONENT_DEFINITION)
+		ArchiveEntry(fout, cmkinfo->dobj.catId, cmkinfo->dobj.dumpId,
+					 ARCHIVE_OPTS(.tag = cmkinfo->dobj.name,
+								  .namespace = cmkinfo->dobj.namespace->dobj.name,
+								  .owner = cmkinfo->rolname,
+								  .description = "COLUMN MASTER KEY",
+								  .section = SECTION_PRE_DATA,
+								  .createStmt = query->data,
+								  .dropStmt = delq->data));
+
+	if (cmkinfo->dobj.dump & DUMP_COMPONENT_COMMENT)
+		dumpComment(fout, "COLUMN MASTER KEY", qcmkname,
+					cmkinfo->dobj.namespace->dobj.name, cmkinfo->rolname,
+					cmkinfo->dobj.catId, 0, cmkinfo->dobj.dumpId);
+
+	if (cmkinfo->dobj.dump & DUMP_COMPONENT_SECLABEL)
+		dumpSecLabel(fout, "COLUMN MASTER KEY", qcmkname,
+					 cmkinfo->dobj.namespace->dobj.name, cmkinfo->rolname,
+					 cmkinfo->dobj.catId, 0, cmkinfo->dobj.dumpId);
+
+	destroyPQExpBuffer(delq);
+	destroyPQExpBuffer(query);
+	free(qcmkname);
+}
+
 /*
  * dumpConversion
  *	  write out a single conversion definition
@@ -15409,6 +15726,22 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo)
 										  tbinfo->atttypnames[j]);
 					}
 
+					if (tbinfo->attcek[j])
+					{
+						appendPQExpBuffer(q, " ENCRYPTED WITH (column_encryption_key = %s, ",
+										  fmtQualifiedDumpable(tbinfo->attcek[j]));
+						/*
+						 * To reduce output size, we don't print the default
+						 * of encryption_type, but we do print the default of
+						 * algorithm, since we might want to change to a new
+						 * default algorithm sometime in the future.
+						 */
+						if (tbinfo->attencdet[j])
+							appendPQExpBuffer(q, "encryption_type = deterministic, ");
+						appendPQExpBuffer(q, "algorithm = '%s')",
+										  get_cekalg_name(tbinfo->attencalg[j]));
+					}
+
 					if (print_default)
 					{
 						if (tbinfo->attgenerated[j] == ATTRIBUTE_GENERATED_STORED)
@@ -17972,6 +18305,8 @@ addBoundaryDependencies(DumpableObject **dobjs, int numObjs,
 			case DO_ACCESS_METHOD:
 			case DO_OPCLASS:
 			case DO_OPFAMILY:
+			case DO_CEK:
+			case DO_CMK:
 			case DO_COLLATION:
 			case DO_CONVERSION:
 			case DO_TABLE:
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index e7cbd8d7ed..99b6b20ced 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -47,6 +47,8 @@ typedef enum
 	DO_ACCESS_METHOD,
 	DO_OPCLASS,
 	DO_OPFAMILY,
+	DO_CEK,
+	DO_CMK,
 	DO_COLLATION,
 	DO_CONVERSION,
 	DO_TABLE,
@@ -333,6 +335,9 @@ typedef struct _tableInfo
 	bool	   *attisdropped;	/* true if attr is dropped; don't dump it */
 	char	   *attidentity;
 	char	   *attgenerated;
+	struct _CekInfo **attcek;
+	int		   *attencalg;
+	bool	   *attencdet;
 	int		   *attlen;			/* attribute length, used by binary_upgrade */
 	char	   *attalign;		/* attribute align, used by binary_upgrade */
 	bool	   *attislocal;		/* true if attr has local definition */
@@ -664,6 +669,30 @@ typedef struct _SubscriptionInfo
 	char	   *subpublications;
 } SubscriptionInfo;
 
+/*
+ * The CekInfo struct is used to represent column encryption key.
+ */
+typedef struct _CekInfo
+{
+	DumpableObject dobj;
+	const char *rolname;
+	int			numdata;
+	/* The following are arrays of numdata entries each: */
+	struct _CmkInfo **cekcmks;
+	int		   *cekcmkalgs;
+	char	  **cekencvals;
+} CekInfo;
+
+/*
+ * The CmkInfo struct is used to represent column master key.
+ */
+typedef struct _CmkInfo
+{
+	DumpableObject dobj;
+	const char *rolname;
+	char	   *cmkrealm;
+} CmkInfo;
+
 /*
  *	common utility functions
  */
@@ -684,6 +713,8 @@ extern TableInfo *findTableByOid(Oid oid);
 extern TypeInfo *findTypeByOid(Oid oid);
 extern FuncInfo *findFuncByOid(Oid oid);
 extern OprInfo *findOprByOid(Oid oid);
+extern CekInfo *findCekByOid(Oid oid);
+extern CmkInfo *findCmkByOid(Oid oid);
 extern CollInfo *findCollationByOid(Oid oid);
 extern NamespaceInfo *findNamespaceByOid(Oid oid);
 extern ExtensionInfo *findExtensionByOid(Oid oid);
@@ -711,6 +742,8 @@ extern AccessMethodInfo *getAccessMethods(Archive *fout, int *numAccessMethods);
 extern OpclassInfo *getOpclasses(Archive *fout, int *numOpclasses);
 extern OpfamilyInfo *getOpfamilies(Archive *fout, int *numOpfamilies);
 extern CollInfo *getCollations(Archive *fout, int *numCollations);
+extern void getColumnEncryptionKeys(Archive *fout);
+extern void getColumnMasterKeys(Archive *fout);
 extern ConvInfo *getConversions(Archive *fout, int *numConversions);
 extern TableInfo *getTables(Archive *fout, int *numTables);
 extern void getOwnedSeqs(Archive *fout, TableInfo tblinfo[], int numTables);
diff --git a/src/bin/pg_dump/pg_dump_sort.c b/src/bin/pg_dump/pg_dump_sort.c
index f963b9a449..d56372cd1c 100644
--- a/src/bin/pg_dump/pg_dump_sort.c
+++ b/src/bin/pg_dump/pg_dump_sort.c
@@ -69,6 +69,8 @@ enum dbObjectTypePriorities
 	PRIO_TSTEMPLATE,
 	PRIO_TSDICT,
 	PRIO_TSCONFIG,
+	PRIO_CMK,
+	PRIO_CEK,
 	PRIO_FDW,
 	PRIO_FOREIGN_SERVER,
 	PRIO_TABLE,
@@ -111,6 +113,8 @@ static const int dbObjectTypePriority[] =
 	PRIO_ACCESS_METHOD,			/* DO_ACCESS_METHOD */
 	PRIO_OPFAMILY,				/* DO_OPCLASS */
 	PRIO_OPFAMILY,				/* DO_OPFAMILY */
+	PRIO_CEK,					/* DO_CEK */
+	PRIO_CMK,					/* DO_CMK */
 	PRIO_COLLATION,				/* DO_COLLATION */
 	PRIO_CONVERSION,			/* DO_CONVERSION */
 	PRIO_TABLE,					/* DO_TABLE */
@@ -1322,6 +1326,16 @@ describeDumpableObject(DumpableObject *obj, char *buf, int bufsize)
 					 "OPERATOR FAMILY %s  (ID %d OID %u)",
 					 obj->name, obj->dumpId, obj->catId.oid);
 			return;
+		case DO_CEK:
+			snprintf(buf, bufsize,
+					 "COLUMN ENCRYPTION KEY (ID %d OID %u)",
+					 obj->dumpId, obj->catId.oid);
+			return;
+		case DO_CMK:
+			snprintf(buf, bufsize,
+					 "COLUMN MASTER KEY (ID %d OID %u)",
+					 obj->dumpId, obj->catId.oid);
+			return;
 		case DO_COLLATION:
 			snprintf(buf, bufsize,
 					 "COLLATION %s  (ID %d OID %u)",
diff --git a/src/bin/pg_dump/pg_dumpall.c b/src/bin/pg_dump/pg_dumpall.c
index fbd1c6fc85..f35f3b26ae 100644
--- a/src/bin/pg_dump/pg_dumpall.c
+++ b/src/bin/pg_dump/pg_dumpall.c
@@ -93,6 +93,7 @@ static bool dosync = true;
 
 static int	binary_upgrade = 0;
 static int	column_inserts = 0;
+static int	decrypt_encrypted_columns = 0;
 static int	disable_dollar_quoting = 0;
 static int	disable_triggers = 0;
 static int	if_exists = 0;
@@ -154,6 +155,7 @@ main(int argc, char *argv[])
 		{"attribute-inserts", no_argument, &column_inserts, 1},
 		{"binary-upgrade", no_argument, &binary_upgrade, 1},
 		{"column-inserts", no_argument, &column_inserts, 1},
+		{"decrypt-encrypted-columns", no_argument, &decrypt_encrypted_columns, 1},
 		{"disable-dollar-quoting", no_argument, &disable_dollar_quoting, 1},
 		{"disable-triggers", no_argument, &disable_triggers, 1},
 		{"exclude-database", required_argument, NULL, 6},
@@ -424,6 +426,8 @@ main(int argc, char *argv[])
 		appendPQExpBufferStr(pgdumpopts, " --binary-upgrade");
 	if (column_inserts)
 		appendPQExpBufferStr(pgdumpopts, " --column-inserts");
+	if (decrypt_encrypted_columns)
+		appendPQExpBufferStr(pgdumpopts, " --decrypt-encrypted-columns");
 	if (disable_dollar_quoting)
 		appendPQExpBufferStr(pgdumpopts, " --disable-dollar-quoting");
 	if (disable_triggers)
@@ -649,6 +653,7 @@ help(void)
 	printf(_("  -x, --no-privileges          do not dump privileges (grant/revoke)\n"));
 	printf(_("  --binary-upgrade             for use by upgrade utilities only\n"));
 	printf(_("  --column-inserts             dump data as INSERT commands with column names\n"));
+	printf(_("  --decrypt-encrypted-columns  decrypt encrypted columns in the output\n"));
 	printf(_("  --disable-dollar-quoting     disable dollar quoting, use SQL standard quoting\n"));
 	printf(_("  --disable-triggers           disable triggers during data-only restore\n"));
 	printf(_("  --exclude-database=PATTERN   exclude databases whose name matches PATTERN\n"));
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index d92247c915..dec2a0d305 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -645,6 +645,18 @@
 		unlike    => { %dump_test_schema_runs, no_owner => 1, },
 	},
 
+	'ALTER COLUMN ENCRYPTION KEY cek1 OWNER TO' => {
+		regexp => qr/^ALTER COLUMN ENCRYPTION KEY dump_test.cek1 OWNER TO .+;/m,
+		like   => { %full_runs, %dump_test_schema_runs, section_pre_data => 1, },
+		unlike => { exclude_dump_test_schema => 1, no_owner => 1, },
+	},
+
+	'ALTER COLUMN MASTER KEY cmk1 OWNER TO' => {
+		regexp => qr/^ALTER COLUMN MASTER KEY dump_test.cmk1 OWNER TO .+;/m,
+		like   => { %full_runs, %dump_test_schema_runs, section_pre_data => 1, },
+		unlike => { exclude_dump_test_schema => 1, no_owner => 1, },
+	},
+
 	'ALTER FOREIGN DATA WRAPPER dummy OWNER TO' => {
 		regexp => qr/^ALTER FOREIGN DATA WRAPPER dummy OWNER TO .+;/m,
 		like   => { %full_runs, section_pre_data => 1, },
@@ -1245,6 +1257,26 @@
 		like      => { %full_runs, section_pre_data => 1, },
 	},
 
+	'COMMENT ON COLUMN ENCRYPTION KEY cek1' => {
+		create_order => 55,
+		create_sql   => 'COMMENT ON COLUMN ENCRYPTION KEY dump_test.cek1
+					   IS \'comment on column encryption key\';',
+		regexp =>
+		  qr/^COMMENT ON COLUMN ENCRYPTION KEY dump_test.cek1 IS 'comment on column encryption key';/m,
+		like => { %full_runs, %dump_test_schema_runs, section_pre_data => 1, },
+		unlike => { exclude_dump_test_schema => 1, },
+	},
+
+	'COMMENT ON COLUMN MASTER KEY cmk1' => {
+		create_order => 55,
+		create_sql   => 'COMMENT ON COLUMN MASTER KEY dump_test.cmk1
+					   IS \'comment on column master key\';',
+		regexp =>
+		  qr/^COMMENT ON COLUMN MASTER KEY dump_test.cmk1 IS 'comment on column master key';/m,
+		like => { %full_runs, %dump_test_schema_runs, section_pre_data => 1, },
+		unlike => { exclude_dump_test_schema => 1, },
+	},
+
 	'COMMENT ON LARGE OBJECT ...' => {
 		create_order => 65,
 		create_sql   => 'DO $$
@@ -1663,6 +1695,26 @@
 		like => { %full_runs, section_pre_data => 1, },
 	},
 
+	'CREATE COLUMN ENCRYPTION KEY cek1' => {
+		create_order => 51,
+		create_sql   => "CREATE COLUMN ENCRYPTION KEY dump_test.cek1 WITH VALUES (column_master_key = dump_test.cmk1, algorithm = 'RSAES_OAEP_SHA_1', encrypted_value = '\\xDEADBEEF');",
+		regexp       => qr/^
+			\QCREATE COLUMN ENCRYPTION KEY dump_test.cek1 WITH VALUES (column_master_key = dump_test.cmk1, algorithm = 'RSAES_OAEP_SHA_1', encrypted_value = \E
+			/xm,
+		like => { %full_runs, %dump_test_schema_runs, section_pre_data => 1, },
+		unlike => { exclude_dump_test_schema => 1, },
+	},
+
+	'CREATE COLUMN MASTER KEY cmk1' => {
+		create_order => 50,
+		create_sql   => "CREATE COLUMN MASTER KEY dump_test.cmk1 WITH (realm = 'myrealm');",
+		regexp       => qr/^
+			\QCREATE COLUMN MASTER KEY dump_test.cmk1 WITH (realm = 'myrealm');\E
+			/xm,
+		like => { %full_runs, %dump_test_schema_runs, section_pre_data => 1, },
+		unlike => { exclude_dump_test_schema => 1, },
+	},
+
 	'CREATE DATABASE postgres' => {
 		regexp => qr/^
 			\QCREATE DATABASE postgres WITH TEMPLATE = template0 \E
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index b5201edf55..9e940dca6e 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -815,7 +815,11 @@ exec_command_d(PsqlScanState scan_state, bool active_branch, const char *cmd)
 				success = describeTablespaces(pattern, show_verbose);
 				break;
 			case 'c':
-				if (strncmp(cmd, "dconfig", 7) == 0)
+				if (strncmp(cmd, "dcek", 4) == 0)
+					success = listCEKs(pattern, show_verbose);
+				else if (strncmp(cmd, "dcmk", 4) == 0)
+					success = listCMKs(pattern, show_verbose);
+				else if (strncmp(cmd, "dconfig", 7) == 0)
 					success = describeConfigurationParameters(pattern,
 															  show_verbose,
 															  show_system);
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index c8a0bb7b3a..e004e30d86 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -1530,7 +1530,7 @@ describeOneTableDetails(const char *schemaname,
 	bool		printTableInitialized = false;
 	int			i;
 	char	   *view_def = NULL;
-	char	   *headers[12];
+	char	   *headers[13];
 	PQExpBufferData title;
 	PQExpBufferData tmpbuf;
 	int			cols;
@@ -1546,6 +1546,7 @@ describeOneTableDetails(const char *schemaname,
 				fdwopts_col = -1,
 				attstorage_col = -1,
 				attcompression_col = -1,
+				attcekname_col = -1,
 				attstattarget_col = -1,
 				attdescr_col = -1;
 	int			numrows;
@@ -1568,6 +1569,7 @@ describeOneTableDetails(const char *schemaname,
 		char	   *relam;
 	}			tableinfo;
 	bool		show_column_details = false;
+	const char *attrealtypid;
 
 	myopt.default_footer = false;
 	/* This output looks confusing in expanded mode. */
@@ -1844,7 +1846,11 @@ describeOneTableDetails(const char *schemaname,
 	cols = 0;
 	printfPQExpBuffer(&buf, "SELECT a.attname");
 	attname_col = cols++;
-	appendPQExpBufferStr(&buf, ",\n  pg_catalog.format_type(a.atttypid, a.atttypmod)");
+	if (pset.sversion >= 160000)
+		attrealtypid = "CASE WHEN a.attrealtypid <> 0 THEN a.attrealtypid ELSE a.atttypid END";
+	else
+		attrealtypid = "a.atttypid";
+	appendPQExpBuffer(&buf, ",\n  pg_catalog.format_type(%s, a.atttypmod)", attrealtypid);
 	atttype_col = cols++;
 
 	if (show_column_details)
@@ -1857,8 +1863,10 @@ describeOneTableDetails(const char *schemaname,
 							 ",\n  a.attnotnull");
 		attrdef_col = cols++;
 		attnotnull_col = cols++;
-		appendPQExpBufferStr(&buf, ",\n  (SELECT c.collname FROM pg_catalog.pg_collation c, pg_catalog.pg_type t\n"
-							 "   WHERE c.oid = a.attcollation AND t.oid = a.atttypid AND a.attcollation <> t.typcollation) AS attcollation");
+		appendPQExpBuffer(&buf, ",\n"
+						  "  (SELECT c.collname FROM pg_catalog.pg_collation c, pg_catalog.pg_type t\n"
+						  "   WHERE c.oid = a.attcollation AND t.oid = %s AND a.attcollation <> t.typcollation) AS attcollation",
+						  attrealtypid);
 		attcoll_col = cols++;
 		if (pset.sversion >= 100000)
 			appendPQExpBufferStr(&buf, ",\n  a.attidentity");
@@ -1909,6 +1917,18 @@ describeOneTableDetails(const char *schemaname,
 			attcompression_col = cols++;
 		}
 
+		/* encryption info */
+		if (pset.sversion >= 160000 &&
+			!pset.hide_column_encryption &&
+			(tableinfo.relkind == RELKIND_RELATION ||
+			 tableinfo.relkind == RELKIND_VIEW ||
+			 tableinfo.relkind == RELKIND_MATVIEW ||
+			 tableinfo.relkind == RELKIND_PARTITIONED_TABLE))
+		{
+			appendPQExpBufferStr(&buf, ",\n  (SELECT cekname FROM pg_colenckey cek WHERE cek.oid = a.attcek) AS attcekname");
+			attcekname_col = cols++;
+		}
+
 		/* stats target, if relevant to relkind */
 		if (tableinfo.relkind == RELKIND_RELATION ||
 			tableinfo.relkind == RELKIND_INDEX ||
@@ -2032,6 +2052,8 @@ describeOneTableDetails(const char *schemaname,
 		headers[cols++] = gettext_noop("Storage");
 	if (attcompression_col >= 0)
 		headers[cols++] = gettext_noop("Compression");
+	if (attcekname_col >= 0)
+		headers[cols++] = gettext_noop("Encryption");
 	if (attstattarget_col >= 0)
 		headers[cols++] = gettext_noop("Stats target");
 	if (attdescr_col >= 0)
@@ -2124,6 +2146,17 @@ describeOneTableDetails(const char *schemaname,
 							  false, false);
 		}
 
+		/* Column encryption */
+		if (attcekname_col >= 0)
+		{
+			if (!PQgetisnull(res, i, attcekname_col))
+				printTableAddCell(&cont, PQgetvalue(res, i, attcekname_col),
+								  false, false);
+			else
+				printTableAddCell(&cont, "",
+								  false, false);
+		}
+
 		/* Statistics target, if the relkind supports this feature */
 		if (attstattarget_col >= 0)
 			printTableAddCell(&cont, PQgetvalue(res, i, attstattarget_col),
@@ -4477,6 +4510,144 @@ listConversions(const char *pattern, bool verbose, bool showSystem)
 	return true;
 }
 
+/*
+ * \dcek
+ *
+ * Lists column encryption keys.
+ */
+bool
+listCEKs(const char *pattern, bool verbose)
+{
+	PQExpBufferData buf;
+	PGresult   *res;
+	printQueryOpt myopt = pset.popt;
+
+	if (pset.sversion < 160000)
+	{
+		char		sverbuf[32];
+
+		pg_log_error("The server (version %s) does not support column encryption.",
+					 formatPGVersionNumber(pset.sversion, false,
+										   sverbuf, sizeof(sverbuf)));
+		return true;
+	}
+
+	initPQExpBuffer(&buf);
+
+	printfPQExpBuffer(&buf,
+					  "SELECT "
+					  "n.nspname AS \"%s\", "
+					  "cekname AS \"%s\", "
+					  "pg_catalog.pg_get_userbyid(cekowner) AS \"%s\", "
+					  "cmkname AS \"%s\"",
+					  gettext_noop("Schema"),
+					  gettext_noop("Name"),
+					  gettext_noop("Owner"),
+					  gettext_noop("Master key"));
+	if (verbose)
+		appendPQExpBuffer(&buf,
+						  ", pg_catalog.obj_description(cek.oid, 'pg_colenckey') AS \"%s\"",
+						  gettext_noop("Description"));
+	appendPQExpBufferStr(&buf,
+						 "\nFROM pg_catalog.pg_colenckey cek "
+						 "LEFT JOIN pg_catalog.pg_namespace n ON n.oid = cek.ceknamespace "
+						 "JOIN pg_catalog.pg_colenckeydata ckd ON (cek.oid = ckd.ckdcekid) "
+						 "JOIN pg_catalog.pg_colmasterkey cmk ON (ckd.ckdcmkid = cmk.oid) ");
+
+	if (!validateSQLNamePattern(&buf, pattern, false, false,
+								"n.nspname", "cekname", NULL,
+								"pg_catalog.pg_cek_is_visible(cek.oid)",
+								NULL, 3))
+	{
+		termPQExpBuffer(&buf);
+		return false;
+	}
+
+	appendPQExpBufferStr(&buf, "ORDER BY 1, 2, 4");
+
+	res = PSQLexec(buf.data);
+	termPQExpBuffer(&buf);
+	if (!res)
+		return false;
+
+	myopt.nullPrint = NULL;
+	myopt.title = _("List of column encryption keys");
+	myopt.translate_header = true;
+
+	printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
+
+	PQclear(res);
+	return true;
+}
+
+/*
+ * \dcmk
+ *
+ * Lists column master keys.
+ */
+bool
+listCMKs(const char *pattern, bool verbose)
+{
+	PQExpBufferData buf;
+	PGresult   *res;
+	printQueryOpt myopt = pset.popt;
+
+	if (pset.sversion < 160000)
+	{
+		char		sverbuf[32];
+
+		pg_log_error("The server (version %s) does not support column encryption.",
+					 formatPGVersionNumber(pset.sversion, false,
+										   sverbuf, sizeof(sverbuf)));
+		return true;
+	}
+
+	initPQExpBuffer(&buf);
+
+	printfPQExpBuffer(&buf,
+					  "SELECT "
+					  "n.nspname AS \"%s\", "
+					  "cmkname AS \"%s\", "
+					  "pg_catalog.pg_get_userbyid(cmkowner) AS \"%s\", "
+					  "cmkrealm AS \"%s\"",
+					  gettext_noop("Schema"),
+					  gettext_noop("Name"),
+					  gettext_noop("Owner"),
+					  gettext_noop("Realm"));
+	if (verbose)
+		appendPQExpBuffer(&buf,
+						  ", pg_catalog.obj_description(oid, 'pg_colmasterkey') AS \"%s\"",
+						  gettext_noop("Description"));
+	appendPQExpBufferStr(&buf,
+						 "\nFROM pg_catalog.pg_colmasterkey cmk "
+						 "LEFT JOIN pg_catalog.pg_namespace n ON n.oid = cmk.cmknamespace ");
+
+	if (!validateSQLNamePattern(&buf, pattern, false, false,
+								"n.nspname", "cmkname", NULL,
+								"pg_catalog.pg_cmk_is_visible(cmk.oid)",
+								NULL, 3))
+	{
+		termPQExpBuffer(&buf);
+		return false;
+	}
+
+	appendPQExpBufferStr(&buf, "ORDER BY 1, 2");
+
+	res = PSQLexec(buf.data);
+	termPQExpBuffer(&buf);
+	if (!res)
+		return false;
+
+	myopt.nullPrint = NULL;
+	myopt.title = _("List of column master keys");
+	myopt.translate_header = true;
+
+	printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
+
+	PQclear(res);
+	return true;
+}
+
 /*
  * \dconfig
  *
diff --git a/src/bin/psql/describe.h b/src/bin/psql/describe.h
index 554fe86725..1cf8f72176 100644
--- a/src/bin/psql/describe.h
+++ b/src/bin/psql/describe.h
@@ -76,6 +76,12 @@ extern bool listDomains(const char *pattern, bool verbose, bool showSystem);
 /* \dc */
 extern bool listConversions(const char *pattern, bool verbose, bool showSystem);
 
+/* \dcek */
+extern bool listCEKs(const char *pattern, bool verbose);
+
+/* \dcmk */
+extern bool listCMKs(const char *pattern, bool verbose);
+
 /* \dconfig */
 extern bool describeConfigurationParameters(const char *pattern, bool verbose,
 											bool showSystem);
diff --git a/src/bin/psql/help.c b/src/bin/psql/help.c
index e45c4aaca5..1729966959 100644
--- a/src/bin/psql/help.c
+++ b/src/bin/psql/help.c
@@ -252,6 +252,8 @@ slashUsage(unsigned short int pager)
 	HELP0("  \\dAp[+] [AMPTRN [OPFPTRN]]   list support functions of operator families\n");
 	HELP0("  \\db[+]  [PATTERN]      list tablespaces\n");
 	HELP0("  \\dc[S+] [PATTERN]      list conversions\n");
+	HELP0("  \\dcek[+] [PATTERN]     list column encryption keys\n");
+	HELP0("  \\dcmk[+] [PATTERN]     list column master keys\n");
 	HELP0("  \\dconfig[+] [PATTERN]  list configuration parameters\n");
 	HELP0("  \\dC[+]  [PATTERN]      list casts\n");
 	HELP0("  \\dd[S]  [PATTERN]      show object descriptions not displayed elsewhere\n");
@@ -413,6 +415,8 @@ helpVariables(unsigned short int pager)
 		  "    true if last query failed, else false\n");
 	HELP0("  FETCH_COUNT\n"
 		  "    the number of result rows to fetch and display at a time (0 = unlimited)\n");
+	HELP0("  HIDE_COLUMN_ENCRYPTION\n"
+		  "    if set, column encryption details are not displayed\n");
 	HELP0("  HIDE_TABLEAM\n"
 		  "    if set, table access methods are not displayed\n");
 	HELP0("  HIDE_TOAST_COMPRESSION\n"
diff --git a/src/bin/psql/settings.h b/src/bin/psql/settings.h
index 73d4b393bc..010bc5a6d5 100644
--- a/src/bin/psql/settings.h
+++ b/src/bin/psql/settings.h
@@ -137,6 +137,7 @@ typedef struct _psqlSettings
 	bool		quiet;
 	bool		singleline;
 	bool		singlestep;
+	bool		hide_column_encryption;
 	bool		hide_compression;
 	bool		hide_tableam;
 	int			fetch_count;
diff --git a/src/bin/psql/startup.c b/src/bin/psql/startup.c
index 5a28b6f713..6736505c3a 100644
--- a/src/bin/psql/startup.c
+++ b/src/bin/psql/startup.c
@@ -1188,6 +1188,13 @@ hide_compression_hook(const char *newval)
 							 &pset.hide_compression);
 }
 
+static bool
+hide_column_encryption_hook(const char *newval)
+{
+	return ParseVariableBool(newval, "HIDE_COLUMN_ENCRYPTION",
+							 &pset.hide_column_encryption);
+}
+
 static bool
 hide_tableam_hook(const char *newval)
 {
@@ -1259,6 +1266,9 @@ EstablishVariableSpace(void)
 	SetVariableHooks(pset.vars, "SHOW_CONTEXT",
 					 show_context_substitute_hook,
 					 show_context_hook);
+	SetVariableHooks(pset.vars, "HIDE_COLUMN_ENCRYPTION",
+					 bool_substitute_hook,
+					 hide_column_encryption_hook);
 	SetVariableHooks(pset.vars, "HIDE_TOAST_COMPRESSION",
 					 bool_substitute_hook,
 					 hide_compression_hook);
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 5e1882eaea..fc8ed212b6 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -933,6 +933,20 @@ static const SchemaQuery Query_for_list_of_collations = {
 	.result = "c.collname",
 };
 
+static const SchemaQuery Query_for_list_of_ceks = {
+	.catname = "pg_catalog.pg_colenckey c",
+	.viscondition = "pg_catalog.pg_cek_is_visible(c.oid)",
+	.namespace = "c.ceknamespace",
+	.result = "c.cekname",
+};
+
+static const SchemaQuery Query_for_list_of_cmks = {
+	.catname = "pg_catalog.pg_colmasterkey c",
+	.viscondition = "pg_catalog.pg_cmk_is_visible(c.oid)",
+	.namespace = "c.cmknamespace",
+	.result = "c.cmkname",
+};
+
 static const SchemaQuery Query_for_partition_of_table = {
 	.catname = "pg_catalog.pg_class c1, pg_catalog.pg_class c2, pg_catalog.pg_inherits i",
 	.selcondition = "c1.oid=i.inhparent and i.inhrelid=c2.oid and c2.relispartition",
@@ -1223,6 +1237,8 @@ static const pgsql_thing_t words_after_create[] = {
 	{"CAST", NULL, NULL, NULL}, /* Casts have complex structures for names, so
 								 * skip it */
 	{"COLLATION", NULL, NULL, &Query_for_list_of_collations},
+	{"COLUMN ENCRYPTION KEY", NULL, NULL, NULL},
+	{"COLUMN MASTER KEY KEY", NULL, NULL, NULL},
 
 	/*
 	 * CREATE CONSTRAINT TRIGGER is not supported here because it is designed
@@ -1705,7 +1721,7 @@ psql_completion(const char *text, int start, int end)
 		"\\connect", "\\conninfo", "\\C", "\\cd", "\\copy",
 		"\\copyright", "\\crosstabview",
 		"\\d", "\\da", "\\dA", "\\dAc", "\\dAf", "\\dAo", "\\dAp",
-		"\\db", "\\dc", "\\dconfig", "\\dC", "\\dd", "\\ddp", "\\dD",
+		"\\db", "\\dc", "\\dcek", "\\dcmk", "\\dconfig", "\\dC", "\\dd", "\\ddp", "\\dD",
 		"\\des", "\\det", "\\deu", "\\dew", "\\dE", "\\df",
 		"\\dF", "\\dFd", "\\dFp", "\\dFt", "\\dg", "\\di", "\\dl", "\\dL",
 		"\\dm", "\\dn", "\\do", "\\dO", "\\dp", "\\dP", "\\dPi", "\\dPt",
@@ -1952,6 +1968,22 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches("ALTER", "COLLATION", MatchAny))
 		COMPLETE_WITH("OWNER TO", "REFRESH VERSION", "RENAME TO", "SET SCHEMA");
 
+	/* ALTER/DROP COLUMN ENCRYPTION KEY */
+	else if (Matches("ALTER|DROP", "COLUMN", "ENCRYPTION", "KEY"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_ceks);
+
+	/* ALTER COLUMN ENCRYPTION KEY */
+	else if (Matches("ALTER", "COLUMN", "ENCRYPTION", "KEY", MatchAny))
+		COMPLETE_WITH("ADD VALUE (", "DROP VALUE (", "OWNER TO", "RENAME TO", "SET SCHEMA");
+
+	/* ALTER/DROP COLUMN MASTER KEY */
+	else if (Matches("ALTER|DROP", "COLUMN", "MASTER", "KEY"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_cmks);
+
+	/* ALTER COLUMN MASTER KEY */
+	else if (Matches("ALTER", "COLUMN", "MASTER", "KEY", MatchAny))
+		COMPLETE_WITH("(", "OWNER TO", "RENAME TO", "SET SCHEMA");
+
 	/* ALTER CONVERSION <name> */
 	else if (Matches("ALTER", "CONVERSION", MatchAny))
 		COMPLETE_WITH("OWNER TO", "RENAME TO", "SET SCHEMA");
@@ -2894,6 +2926,26 @@ psql_completion(const char *text, int start, int end)
 			COMPLETE_WITH("true", "false");
 	}
 
+	/* CREATE/ALTER/DROP COLUMN ... KEY */
+	else if (Matches("CREATE|ALTER|DROP", "COLUMN"))
+		COMPLETE_WITH("ENCRYPTION", "MASTER");
+	else if (Matches("CREATE|ALTER|DROP", "COLUMN", "ENCRYPTION|MASTER"))
+		COMPLETE_WITH("KEY");
+
+	/* CREATE COLUMN ENCRYPTION KEY */
+	else if (Matches("CREATE", "COLUMN", "ENCRYPTION", "KEY", MatchAny))
+		COMPLETE_WITH("WITH");
+	else if (Matches("CREATE", "COLUMN", "ENCRYPTION", "KEY", MatchAny, "WITH"))
+		COMPLETE_WITH("VALUES");
+	else if (Matches("CREATE", "COLUMN", "ENCRYPTION", "KEY", MatchAny, "WITH", "VALUES"))
+		COMPLETE_WITH("(");
+
+	/* CREATE COLUMN MASTER KEY */
+	else if (Matches("CREATE", "COLUMN", "MASTER", "KEY", MatchAny))
+		COMPLETE_WITH("WITH");
+	else if (Matches("CREATE", "COLUMN", "MASTER", "KEY", MatchAny, "WITH"))
+		COMPLETE_WITH("(");
+
 	/* CREATE DATABASE */
 	else if (Matches("CREATE", "DATABASE", MatchAny))
 		COMPLETE_WITH("OWNER", "TEMPLATE", "ENCODING", "TABLESPACE",
@@ -3619,6 +3671,7 @@ psql_completion(const char *text, int start, int end)
 			 Matches("DROP", "ACCESS", "METHOD", MatchAny) ||
 			 (Matches("DROP", "AGGREGATE|FUNCTION|PROCEDURE|ROUTINE", MatchAny, MatchAny) &&
 			  ends_with(prev_wd, ')')) ||
+			 Matches("DROP", "COLUMN", "ENCRYPTION|MASTER", "KEY", MatchAny) ||
 			 Matches("DROP", "EVENT", "TRIGGER", MatchAny) ||
 			 Matches("DROP", "FOREIGN", "DATA", "WRAPPER", MatchAny) ||
 			 Matches("DROP", "FOREIGN", "TABLE", MatchAny) ||
diff --git a/src/common/Makefile b/src/common/Makefile
index 2f424a5735..c3ac6cbc35 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -48,6 +48,7 @@ LIBS += $(PTHREAD_LIBS)
 OBJS_COMMON = \
 	base64.o \
 	checksum_helper.o \
+	colenc.o \
 	compression.o \
 	config_info.o \
 	controldata_utils.o \
diff --git a/src/common/colenc.c b/src/common/colenc.c
new file mode 100644
index 0000000000..86c735878e
--- /dev/null
+++ b/src/common/colenc.c
@@ -0,0 +1,104 @@
+/*-------------------------------------------------------------------------
+ *
+ * colenc.c
+ *
+ * Shared code for column encryption algorithms.
+ *
+ * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *		  src/common/colenc.c
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef FRONTEND
+#include "postgres.h"
+#else
+#include "postgres_fe.h"
+#endif
+
+#include "common/colenc.h"
+
+int
+get_cmkalg_num(const char *name)
+{
+	if (strcmp(name, "unspecified") == 0)
+		return PG_CMK_UNSPECIFIED;
+	else if (strcmp(name, "RSAES_OAEP_SHA_1") == 0)
+		return PG_CMK_RSAES_OAEP_SHA_1;
+	else if (strcmp(name, "RSAES_OAEP_SHA_256") == 0)
+		return PG_CMK_RSAES_OAEP_SHA_256;
+	else
+		return 0;
+}
+
+const char *
+get_cmkalg_name(int num)
+{
+	switch (num)
+	{
+		case PG_CMK_UNSPECIFIED:
+			return "unspecified";
+		case PG_CMK_RSAES_OAEP_SHA_1:
+			return "RSAES_OAEP_SHA_1";
+		case PG_CMK_RSAES_OAEP_SHA_256:
+			return "RSAES_OAEP_SHA_256";
+	}
+
+	return NULL;
+}
+
+/*
+ * JSON Web Algorithms (JWA) names (RFC 7518)
+ *
+ * This is useful for some key management systems that use these names
+ * natively.
+ */
+const char *
+get_cmkalg_jwa_name(int num)
+{
+	switch (num)
+	{
+		case PG_CMK_UNSPECIFIED:
+			return NULL;
+		case PG_CMK_RSAES_OAEP_SHA_1:
+			return "RSA-OAEP";
+		case PG_CMK_RSAES_OAEP_SHA_256:
+			return "RSA-OAEP-256";
+	}
+
+	return NULL;
+}
+
+int
+get_cekalg_num(const char *name)
+{
+	if (strcmp(name, "AEAD_AES_128_CBC_HMAC_SHA_256") == 0)
+		return PG_CEK_AEAD_AES_128_CBC_HMAC_SHA_256;
+	else if (strcmp(name, "AEAD_AES_192_CBC_HMAC_SHA_384") == 0)
+		return PG_CEK_AEAD_AES_192_CBC_HMAC_SHA_384;
+	else if (strcmp(name, "AEAD_AES_256_CBC_HMAC_SHA_384") == 0)
+		return PG_CEK_AEAD_AES_256_CBC_HMAC_SHA_384;
+	else if (strcmp(name, "AEAD_AES_256_CBC_HMAC_SHA_512") == 0)
+		return PG_CEK_AEAD_AES_256_CBC_HMAC_SHA_512;
+	else
+		return 0;
+}
+
+const char *
+get_cekalg_name(int num)
+{
+	switch (num)
+	{
+		case PG_CEK_AEAD_AES_128_CBC_HMAC_SHA_256:
+			return "AEAD_AES_128_CBC_HMAC_SHA_256";
+		case PG_CEK_AEAD_AES_192_CBC_HMAC_SHA_384:
+			return "AEAD_AES_192_CBC_HMAC_SHA_384";
+		case PG_CEK_AEAD_AES_256_CBC_HMAC_SHA_384:
+			return "AEAD_AES_256_CBC_HMAC_SHA_384";
+		case PG_CEK_AEAD_AES_256_CBC_HMAC_SHA_512:
+			return "AEAD_AES_256_CBC_HMAC_SHA_512";
+	}
+
+	return NULL;
+}
diff --git a/src/common/meson.build b/src/common/meson.build
index 1caa1fed04..4ef62bddf2 100644
--- a/src/common/meson.build
+++ b/src/common/meson.build
@@ -3,6 +3,7 @@
 common_sources = files(
   'base64.c',
   'checksum_helper.c',
+  'colenc.c',
   'compression.c',
   'controldata_utils.c',
   'encnames.c',
diff --git a/src/include/access/printtup.h b/src/include/access/printtup.h
index 747ecb800d..d199033651 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/dependency.h b/src/include/catalog/dependency.h
index ffd5e9dc82..eb59e73c0a 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -92,6 +92,9 @@ typedef enum ObjectClass
 	OCLASS_TYPE,				/* pg_type */
 	OCLASS_CAST,				/* pg_cast */
 	OCLASS_COLLATION,			/* pg_collation */
+	OCLASS_CEK,					/* pg_colenckey */
+	OCLASS_CEKDATA,				/* pg_colenckeydata */
+	OCLASS_CMK,					/* pg_colmasterkey */
 	OCLASS_CONSTRAINT,			/* pg_constraint */
 	OCLASS_CONVERSION,			/* pg_conversion */
 	OCLASS_DEFAULT,				/* pg_attrdef */
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index d01ab504b6..758696b539 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -23,6 +23,7 @@
 #define CHKATYPE_ANYARRAY		0x01	/* allow ANYARRAY */
 #define CHKATYPE_ANYRECORD		0x02	/* allow RECORD and RECORD[] */
 #define CHKATYPE_IS_PARTKEY		0x04	/* attname is part key # not column */
+#define CHKATYPE_ENCRYPTED		0x08	/* allow internal encrypted types */
 
 typedef struct RawColumnDefault
 {
diff --git a/src/include/catalog/meson.build b/src/include/catalog/meson.build
index 3179be09d3..9e2c5256f7 100644
--- a/src/include/catalog/meson.build
+++ b/src/include/catalog/meson.build
@@ -65,6 +65,9 @@ catalog_headers = [
   'pg_publication_rel.h',
   'pg_subscription.h',
   'pg_subscription_rel.h',
+  'pg_colmasterkey.h',
+  'pg_colenckey.h',
+  'pg_colenckeydata.h',
 ]
 
 bki_data = [
diff --git a/src/include/catalog/namespace.h b/src/include/catalog/namespace.h
index f64a0ec26b..d0b9e8458d 100644
--- a/src/include/catalog/namespace.h
+++ b/src/include/catalog/namespace.h
@@ -115,6 +115,12 @@ extern bool OpclassIsVisible(Oid opcid);
 extern Oid	OpfamilynameGetOpfid(Oid amid, const char *opfname);
 extern bool OpfamilyIsVisible(Oid opfid);
 
+extern Oid	get_cek_oid(List *names, bool missing_ok);
+extern bool CEKIsVisible(Oid cekid);
+
+extern Oid	get_cmk_oid(List *names, bool missing_ok);
+extern bool CMKIsVisible(Oid cmkid);
+
 extern Oid	CollationGetCollid(const char *collname);
 extern bool CollationIsVisible(Oid collid);
 
diff --git a/src/include/catalog/pg_amop.dat b/src/include/catalog/pg_amop.dat
index c4d6adcd3e..c58b79e3a7 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' },
 
+# pg_encrypted_det_ops
+{ amopfamily => 'hash/pg_encrypted_det_ops', amoplefttype => 'pg_encrypted_det',
+  amoprighttype => 'pg_encrypted_det', amopstrategy => '1', amopopr => '=(pg_encrypted_det,pg_encrypted_det)',
+  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 5b950129de..0e9e85ebf3 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/pg_encrypted_det_ops', amproclefttype => 'pg_encrypted_det',
+  amprocrighttype => 'pg_encrypted_det', amprocnum => '1', amproc => 'hashvarlena' },
+{ amprocfamily => 'hash/pg_encrypted_det_ops', amproclefttype => 'pg_encrypted_det',
+  amprocrighttype => 'pg_encrypted_det', 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 b561e17781..4b5b5ad32f 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 (PG_CEK_* values) */
+	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_colenckey.h b/src/include/catalog/pg_colenckey.h
new file mode 100644
index 0000000000..be44c0d70d
--- /dev/null
+++ b/src/include/catalog/pg_colenckey.h
@@ -0,0 +1,41 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_colenckey.h
+ *	  definition of the "column encryption key" system catalog
+ *
+ * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/catalog/pg_colenkey.h
+ *
+ * NOTES
+ *	  The Catalog.pm module reads this file and derives schema
+ *	  information.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_COLENCKEY_H
+#define PG_COLENCKEY_H
+
+#include "catalog/genbki.h"
+#include "catalog/pg_colenckey_d.h"
+
+/* ----------------
+ *		pg_colenckey definition. cpp turns this into
+ *		typedef struct FormData_pg_colenckey
+ * ----------------
+ */
+CATALOG(pg_colenckey,8234,ColumnEncKeyRelationId)
+{
+	Oid			oid;
+	NameData	cekname;
+	Oid			ceknamespace BKI_DEFAULT(pg_catalog) BKI_LOOKUP(pg_namespace);
+	Oid			cekowner BKI_LOOKUP(pg_authid);
+} 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_nsp_index, 8242, ColumnEncKeyNameNspIndexId, on pg_colenckey using btree(cekname name_ops, ceknamespace oid_ops));
+
+#endif
diff --git a/src/include/catalog/pg_colenckeydata.h b/src/include/catalog/pg_colenckeydata.h
new file mode 100644
index 0000000000..c88e7e65ad
--- /dev/null
+++ b/src/include/catalog/pg_colenckeydata.h
@@ -0,0 +1,46 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_colenckeydata.h
+ *	  definition of the "column encryption key data" system catalog
+ *
+ * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/catalog/pg_colenkeydata.h
+ *
+ * NOTES
+ *	  The Catalog.pm module reads this file and derives schema
+ *	  information.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_COLENCKEYDATA_H
+#define PG_COLENCKEYDATA_H
+
+#include "catalog/genbki.h"
+#include "catalog/pg_colenckeydata_d.h"
+
+/* ----------------
+ *		pg_colenckeydata definition. cpp turns this into
+ *		typedef struct FormData_pg_colenckeydata
+ * ----------------
+ */
+CATALOG(pg_colenckeydata,8250,ColumnEncKeyDataRelationId)
+{
+	Oid			oid;
+	Oid			ckdcekid BKI_LOOKUP(pg_colenckey);
+	Oid			ckdcmkid BKI_LOOKUP(pg_colmasterkey);
+	int32		ckdcmkalg;		/* PG_CMK_* values */
+#ifdef CATALOG_VARLEN			/* variable-length fields start here */
+	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..a4a103bbed
--- /dev/null
+++ b/src/include/catalog/pg_colmasterkey.h
@@ -0,0 +1,46 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_colmasterkey.h
+ *	  definition of the "column master key" system catalog
+ *
+ * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/catalog/pg_colmasterkey.h
+ *
+ * NOTES
+ *	  The Catalog.pm module reads this file and derives schema
+ *	  information.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_COLMASTERKEY_H
+#define PG_COLMASTERKEY_H
+
+#include "catalog/genbki.h"
+#include "catalog/pg_colmasterkey_d.h"
+
+/* ----------------
+ *		pg_colmasterkey definition. cpp turns this into
+ *		typedef struct FormData_pg_colmasterkey
+ * ----------------
+ */
+CATALOG(pg_colmasterkey,8233,ColumnMasterKeyRelationId)
+{
+	Oid			oid;
+	NameData	cmkname;
+	Oid			cmknamespace BKI_DEFAULT(pg_catalog) BKI_LOOKUP(pg_namespace);
+	Oid			cmkowner BKI_LOOKUP(pg_authid);
+#ifdef CATALOG_VARLEN			/* variable-length fields start here */
+	text		cmkrealm 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_nsp_index, 8241, ColumnMasterKeyNameNspIndexId, on pg_colmasterkey using btree(cmkname name_ops, cmknamespace oid_ops));
+
+#endif
diff --git a/src/include/catalog/pg_opclass.dat b/src/include/catalog/pg_opclass.dat
index c867d99563..ff06d52fd0 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 => 'pg_encrypted_det_ops', opcfamily => 'hash/pg_encrypted_det_ops',
+  opcintype => 'pg_encrypted_det' },
 { 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 b2cdea66c4..114279fa64 100644
--- a/src/include/catalog/pg_operator.dat
+++ b/src/include/catalog/pg_operator.dat
@@ -3458,4 +3458,14 @@
   oprcode => 'multirange_after_multirange', oprrest => 'multirangesel',
   oprjoin => 'scalargtjoinsel' },
 
+{ oid => '8247', descr => 'equal',
+  oprname => '=', oprcanmerge => 'f', oprcanhash => 't', oprleft => 'pg_encrypted_det',
+  oprright => 'pg_encrypted_det', oprresult => 'bool', oprcom => '=(pg_encrypted_det,pg_encrypted_det)',
+  oprnegate => '<>(pg_encrypted_det,pg_encrypted_det)', oprcode => 'pg_encrypted_det_eq', oprrest => 'eqsel',
+  oprjoin => 'eqjoinsel' },
+{ oid => '8248', descr => 'not equal',
+  oprname => '<>', oprleft => 'pg_encrypted_det', oprright => 'pg_encrypted_det', oprresult => 'bool',
+  oprcom => '<>(pg_encrypted_det,pg_encrypted_det)', oprnegate => '=(pg_encrypted_det,pg_encrypted_det)',
+  oprcode => 'pg_encrypted_det_ne', oprrest => 'neqsel', oprjoin => 'neqjoinsel' },
+
 ]
diff --git a/src/include/catalog/pg_opfamily.dat b/src/include/catalog/pg_opfamily.dat
index 91587b99d0..c21052a3f7 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 => 'pg_encrypted_det_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 c0f2a8a77c..11bf7e05f1 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -6346,6 +6346,14 @@
   proname => 'pg_collation_is_visible', procost => '10', provolatile => 's',
   prorettype => 'bool', proargtypes => 'oid',
   prosrc => 'pg_collation_is_visible' },
+{ oid => '8261', descr => 'is column encryption key visible in search path?',
+  proname => 'pg_cek_is_visible', procost => '10', provolatile => 's',
+  prorettype => 'bool', proargtypes => 'oid',
+  prosrc => 'pg_cek_is_visible' },
+{ oid => '8262', descr => 'is column master key visible in search path?',
+  proname => 'pg_cmk_is_visible', procost => '10', provolatile => 's',
+  prorettype => 'bool', proargtypes => 'oid',
+  prosrc => 'pg_cmk_is_visible' },
 
 { oid => '2854', descr => 'get OID of current session\'s temp schema, if any',
   proname => 'pg_my_temp_schema', provolatile => 's', proparallel => 'r',
@@ -11918,4 +11926,37 @@
   prorettype => 'bytea', proargtypes => 'pg_brin_minmax_multi_summary',
   prosrc => 'brin_minmax_multi_summary_send' },
 
+{ oid => '8253', descr => 'I/O',
+  proname => 'pg_encrypted_det_in', prorettype => 'pg_encrypted_det', proargtypes => 'cstring',
+  prosrc => 'pg_encrypted_in' },
+{ oid => '8254', descr => 'I/O',
+  proname => 'pg_encrypted_det_out', prorettype => 'cstring', proargtypes => 'pg_encrypted_det',
+  prosrc => 'pg_encrypted_out' },
+{ oid => '8255', descr => 'I/O',
+  proname => 'pg_encrypted_det_recv', prorettype => 'pg_encrypted_det', proargtypes => 'internal',
+  prosrc => 'pg_encrypted_recv' },
+{ oid => '8256', descr => 'I/O',
+  proname => 'pg_encrypted_det_send', prorettype => 'bytea', proargtypes => 'pg_encrypted_det',
+  prosrc => 'pg_encrypted_send' },
+
+{ oid => '8257', descr => 'I/O',
+  proname => 'pg_encrypted_rnd_in', prorettype => 'pg_encrypted_rnd', proargtypes => 'cstring',
+  prosrc => 'pg_encrypted_in' },
+{ oid => '8258', descr => 'I/O',
+  proname => 'pg_encrypted_rnd_out', prorettype => 'cstring', proargtypes => 'pg_encrypted_rnd',
+  prosrc => 'pg_encrypted_out' },
+{ oid => '8259', descr => 'I/O',
+  proname => 'pg_encrypted_rnd_recv', prorettype => 'pg_encrypted_rnd', proargtypes => 'internal',
+  prosrc => 'pg_encrypted_recv' },
+{ oid => '8260', descr => 'I/O',
+  proname => 'pg_encrypted_rnd_send', prorettype => 'bytea', proargtypes => 'pg_encrypted_rnd',
+  prosrc => 'pg_encrypted_send' },
+
+{ oid => '8245',
+  proname => 'pg_encrypted_det_eq', proleakproof => 't', prorettype => 'bool',
+  proargtypes => 'pg_encrypted_det pg_encrypted_det', prosrc => 'byteaeq' },
+{ oid => '8246',
+  proname => 'pg_encrypted_det_ne', proleakproof => 't', prorettype => 'bool',
+  proargtypes => 'pg_encrypted_det pg_encrypted_det', prosrc => 'byteane' },
+
 ]
diff --git a/src/include/catalog/pg_type.dat b/src/include/catalog/pg_type.dat
index 92bcaf2c73..85e4b5554e 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 => 'pg_encrypted_det', typlen => '-1', typbyval => 'f', typtype => 'b',
+  typcategory => 'Y', typinput => 'pg_encrypted_det_in', typoutput => 'pg_encrypted_det_out',
+  typreceive => 'pg_encrypted_det_recv', typsend => 'pg_encrypted_det_send', typalign => 'i',
+  typstorage => 'e' },
+{ oid => '8244', descr => 'encrypted column (randomized)',
+  typname => 'pg_encrypted_rnd', typlen => '-1', typbyval => 'f', typtype => 'b',
+  typcategory => 'Y', typinput => 'pg_encrypted_rnd_in', typoutput => 'pg_encrypted_rnd_out',
+  typreceive => 'pg_encrypted_rnd_recv', typsend => 'pg_encrypted_rnd_send', typalign => 'i',
+  typstorage => 'e' },
+
 ]
diff --git a/src/include/catalog/pg_type.h b/src/include/catalog/pg_type.h
index 519e570c8c..3c7ab2a8fe 100644
--- a/src/include/catalog/pg_type.h
+++ b/src/include/catalog/pg_type.h
@@ -294,6 +294,7 @@ DECLARE_UNIQUE_INDEX(pg_type_typname_nsp_index, 2704, TypeNameNspIndexId, on pg_
 #define  TYPCATEGORY_USER		'U'
 #define  TYPCATEGORY_BITSTRING	'V' /* er ... "varbit"? */
 #define  TYPCATEGORY_UNKNOWN	'X'
+#define  TYPCATEGORY_ENCRYPTED	'Y'
 #define  TYPCATEGORY_INTERNAL	'Z'
 
 #define  TYPALIGN_CHAR			'c' /* char alignment (i.e. unaligned) */
diff --git a/src/include/commands/colenccmds.h b/src/include/commands/colenccmds.h
new file mode 100644
index 0000000000..7127e0ca5e
--- /dev/null
+++ b/src/include/commands/colenccmds.h
@@ -0,0 +1,26 @@
+/*-------------------------------------------------------------------------
+ *
+ * colenccmds.h
+ *	  prototypes for colenccmds.c.
+ *
+ *
+ * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/commands/colenccmds.h
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef COLENCCMDS_H
+#define COLENCCMDS_H
+
+#include "catalog/objectaddress.h"
+#include "parser/parse_node.h"
+
+extern ObjectAddress CreateCEK(ParseState *pstate, DefineStmt *stmt);
+extern ObjectAddress AlterColumnEncryptionKey(ParseState *pstate, AlterColumnEncryptionKeyStmt *stmt);
+extern ObjectAddress CreateCMK(ParseState *pstate, DefineStmt *stmt);
+extern ObjectAddress AlterColumnMasterKey(ParseState *pstate, AlterColumnMasterKeyStmt *stmt);
+
+#endif							/* COLENCCMDS_H */
diff --git a/src/include/commands/tablecmds.h b/src/include/commands/tablecmds.h
index e7c2b91a58..11f88c59ce 100644
--- a/src/include/commands/tablecmds.h
+++ b/src/include/commands/tablecmds.h
@@ -105,4 +105,6 @@ extern void RangeVarCallbackOwnsRelation(const RangeVar *relation,
 extern bool PartConstraintImpliedByRelConstraint(Relation scanrel,
 												 List *partConstraint);
 
+extern List *makeColumnEncryption(const FormData_pg_attribute *attr);
+
 #endif							/* TABLECMDS_H */
diff --git a/src/include/common/colenc.h b/src/include/common/colenc.h
new file mode 100644
index 0000000000..212587d222
--- /dev/null
+++ b/src/include/common/colenc.h
@@ -0,0 +1,51 @@
+/*-------------------------------------------------------------------------
+ *
+ * colenc.h
+ *
+ * Shared definitions for column encryption algorithms.
+ *
+ * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *		  src/include/common/colenc.h
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef COMMON_COLENC_H
+#define COMMON_COLENC_H
+
+/*
+ * Constants for CMK and CEK algorithms.  Note that these are part of the
+ * protocol.  In either case, don't assign zero, so that that can be used as
+ * an invalid value.
+ *
+ * Names should use IANA-style capitalization and punctuation ("LIKE_THIS").
+ *
+ * When making changes, also update protocol.sgml.
+ */
+
+#define PG_CMK_UNSPECIFIED				1
+#define PG_CMK_RSAES_OAEP_SHA_1			2
+#define PG_CMK_RSAES_OAEP_SHA_256		3
+
+/*
+ * These algorithms are part of the RFC 5116 realm of AEAD algorithms (even
+ * though they never became an official IETF standard).  So for propriety, we
+ * use "private use" numbers from
+ * <https://www.iana.org/assignments/aead-parameters/aead-parameters.xhtml>.
+ */
+#define PG_CEK_AEAD_AES_128_CBC_HMAC_SHA_256	32768
+#define PG_CEK_AEAD_AES_192_CBC_HMAC_SHA_384	32769
+#define PG_CEK_AEAD_AES_256_CBC_HMAC_SHA_384	32770
+#define PG_CEK_AEAD_AES_256_CBC_HMAC_SHA_512	32771
+
+/*
+ * Functions to convert between names and numbers
+ */
+extern int get_cmkalg_num(const char *name);
+extern const char *get_cmkalg_name(int num);
+extern const char *get_cmkalg_jwa_name(int num);
+extern int get_cekalg_num(const char *name);
+extern const char *get_cekalg_name(int num);
+
+#endif
diff --git a/src/include/libpq/libpq-be.h b/src/include/libpq/libpq-be.h
index 8c70b2fd5b..6cf4b65500 100644
--- a/src/include/libpq/libpq-be.h
+++ b/src/include/libpq/libpq-be.h
@@ -164,6 +164,7 @@ typedef struct Port
 	 */
 	char	   *database_name;
 	char	   *user_name;
+	bool		column_encryption_enabled;
 	char	   *cmdline_options;
 	List	   *guc_options;
 
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 89335d95e7..abf59d0187 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -715,6 +715,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? */
@@ -751,11 +752,12 @@ typedef enum TableLikeOption
 	CREATE_TABLE_LIKE_COMPRESSION = 1 << 1,
 	CREATE_TABLE_LIKE_CONSTRAINTS = 1 << 2,
 	CREATE_TABLE_LIKE_DEFAULTS = 1 << 3,
-	CREATE_TABLE_LIKE_GENERATED = 1 << 4,
-	CREATE_TABLE_LIKE_IDENTITY = 1 << 5,
-	CREATE_TABLE_LIKE_INDEXES = 1 << 6,
-	CREATE_TABLE_LIKE_STATISTICS = 1 << 7,
-	CREATE_TABLE_LIKE_STORAGE = 1 << 8,
+	CREATE_TABLE_LIKE_ENCRYPTED = 1 << 4,
+	CREATE_TABLE_LIKE_GENERATED = 1 << 5,
+	CREATE_TABLE_LIKE_IDENTITY = 1 << 6,
+	CREATE_TABLE_LIKE_INDEXES = 1 << 7,
+	CREATE_TABLE_LIKE_STATISTICS = 1 << 8,
+	CREATE_TABLE_LIKE_STORAGE = 1 << 9,
 	CREATE_TABLE_LIKE_ALL = PG_INT32_MAX
 } TableLikeOption;
 
@@ -1949,6 +1951,9 @@ typedef enum ObjectType
 	OBJECT_CAST,
 	OBJECT_COLUMN,
 	OBJECT_COLLATION,
+	OBJECT_CEK,
+	OBJECT_CEKDATA,
+	OBJECT_CMK,
 	OBJECT_CONVERSION,
 	OBJECT_DATABASE,
 	OBJECT_DEFAULT,
@@ -2136,6 +2141,31 @@ typedef struct AlterCollationStmt
 } AlterCollationStmt;
 
 
+/* ----------------------
+ * Alter Column Encryption Key
+ * ----------------------
+ */
+typedef struct AlterColumnEncryptionKeyStmt
+{
+	NodeTag		type;
+	List	   *cekname;
+	bool		isDrop;			/* ADD or DROP the items? */
+	List	   *definition;
+} AlterColumnEncryptionKeyStmt;
+
+
+/* ----------------------
+ * Alter Column Master Key
+ * ----------------------
+ */
+typedef struct AlterColumnMasterKeyStmt
+{
+	NodeTag		type;
+	List	   *cmkname;
+	List	   *definition;
+} AlterColumnMasterKeyStmt;
+
+
 /* ----------------------
  *	Alter Domain
  *
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index bb36213e6f..c8cf9c7776 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -149,6 +149,7 @@ PG_KEYWORD("else", ELSE, RESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("enable", ENABLE_P, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("encoding", ENCODING, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("encrypted", ENCRYPTED, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("encryption", ENCRYPTION, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("end", END_P, RESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("enum", ENUM_P, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("escape", ESCAPE, UNRESERVED_KEYWORD, BARE_LABEL)
@@ -250,6 +251,7 @@ PG_KEYWORD("lock", LOCK_P, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("locked", LOCKED, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("logged", LOGGED, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("mapping", MAPPING, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("master", MASTER, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("match", MATCH, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("matched", MATCHED, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("materialized", MATERIALIZED, UNRESERVED_KEYWORD, BARE_LABEL)
diff --git a/src/include/parser/parse_param.h b/src/include/parser/parse_param.h
index d4865e50f6..26e1cd617a 100644
--- a/src/include/parser/parse_param.h
+++ b/src/include/parser/parse_param.h
@@ -21,5 +21,6 @@ extern void setup_parse_variable_parameters(ParseState *pstate,
 											Oid **paramTypes, int *numParams);
 extern void check_variable_parameters(ParseState *pstate, Query *query);
 extern bool query_contains_extern_params(Query *query);
+extern void find_param_origs(List *query_list, Oid **param_orig_tbls, AttrNumber **param_orig_cols);
 
 #endif							/* PARSE_PARAM_H */
diff --git a/src/include/tcop/cmdtaglist.h b/src/include/tcop/cmdtaglist.h
index e738ac1c09..c1f6fe1410 100644
--- a/src/include/tcop/cmdtaglist.h
+++ b/src/include/tcop/cmdtaglist.h
@@ -29,6 +29,8 @@ PG_CMDTAG(CMDTAG_ALTER_ACCESS_METHOD, "ALTER ACCESS METHOD", true, false, false)
 PG_CMDTAG(CMDTAG_ALTER_AGGREGATE, "ALTER AGGREGATE", true, false, false)
 PG_CMDTAG(CMDTAG_ALTER_CAST, "ALTER CAST", true, false, false)
 PG_CMDTAG(CMDTAG_ALTER_COLLATION, "ALTER COLLATION", true, false, false)
+PG_CMDTAG(CMDTAG_ALTER_COLUMN_ENCRYPTION_KEY, "ALTER COLUMN ENCRYPTION KEY", true, false, false)
+PG_CMDTAG(CMDTAG_ALTER_COLUMN_MASTER_KEY, "ALTER COLUMN MASTER KEY", true, false, false)
 PG_CMDTAG(CMDTAG_ALTER_CONSTRAINT, "ALTER CONSTRAINT", true, false, false)
 PG_CMDTAG(CMDTAG_ALTER_CONVERSION, "ALTER CONVERSION", true, false, false)
 PG_CMDTAG(CMDTAG_ALTER_DATABASE, "ALTER DATABASE", false, false, false)
@@ -86,6 +88,8 @@ PG_CMDTAG(CMDTAG_CREATE_ACCESS_METHOD, "CREATE ACCESS METHOD", true, false, fals
 PG_CMDTAG(CMDTAG_CREATE_AGGREGATE, "CREATE AGGREGATE", true, false, false)
 PG_CMDTAG(CMDTAG_CREATE_CAST, "CREATE CAST", true, false, false)
 PG_CMDTAG(CMDTAG_CREATE_COLLATION, "CREATE COLLATION", true, false, false)
+PG_CMDTAG(CMDTAG_CREATE_COLUMN_ENCRYPTION_KEY, "CREATE COLUMN ENCRYPTION KEY", true, false, false)
+PG_CMDTAG(CMDTAG_CREATE_COLUMN_MASTER_KEY, "CREATE COLUMN MASTER KEY", true, false, false)
 PG_CMDTAG(CMDTAG_CREATE_CONSTRAINT, "CREATE CONSTRAINT", true, false, false)
 PG_CMDTAG(CMDTAG_CREATE_CONVERSION, "CREATE CONVERSION", true, false, false)
 PG_CMDTAG(CMDTAG_CREATE_DATABASE, "CREATE DATABASE", false, false, false)
@@ -138,6 +142,8 @@ PG_CMDTAG(CMDTAG_DROP_ACCESS_METHOD, "DROP ACCESS METHOD", true, false, false)
 PG_CMDTAG(CMDTAG_DROP_AGGREGATE, "DROP AGGREGATE", true, false, false)
 PG_CMDTAG(CMDTAG_DROP_CAST, "DROP CAST", true, false, false)
 PG_CMDTAG(CMDTAG_DROP_COLLATION, "DROP COLLATION", true, false, false)
+PG_CMDTAG(CMDTAG_DROP_COLUMN_ENCRYPTION_KEY, "DROP COLUMN ENCRYPTION KEY", true, false, false)
+PG_CMDTAG(CMDTAG_DROP_COLUMN_MASTER_KEY, "DROP COLUMN MASTER KEY", true, false, false)
 PG_CMDTAG(CMDTAG_DROP_CONSTRAINT, "DROP CONSTRAINT", true, false, false)
 PG_CMDTAG(CMDTAG_DROP_CONVERSION, "DROP CONVERSION", true, false, false)
 PG_CMDTAG(CMDTAG_DROP_DATABASE, "DROP DATABASE", false, false, false)
diff --git a/src/include/utils/lsyscache.h b/src/include/utils/lsyscache.h
index 4f5418b972..1dac24e4b4 100644
--- a/src/include/utils/lsyscache.h
+++ b/src/include/utils/lsyscache.h
@@ -163,6 +163,7 @@ extern bool type_is_rowtype(Oid typid);
 extern bool type_is_enum(Oid typid);
 extern bool type_is_range(Oid typid);
 extern bool type_is_multirange(Oid typid);
+extern bool type_is_encrypted(Oid typid);
 extern void get_type_category_preferred(Oid typid,
 										char *typcategory,
 										bool *typispreferred);
@@ -202,6 +203,9 @@ extern Oid	get_publication_oid(const char *pubname, bool missing_ok);
 extern char *get_publication_name(Oid pubid, bool missing_ok);
 extern Oid	get_subscription_oid(const char *subname, bool missing_ok);
 extern char *get_subscription_name(Oid subid, bool missing_ok);
+extern char *get_cek_name(Oid cekid, bool missing_ok);
+extern Oid	get_cekdata_oid(Oid cekid, Oid cmkid, bool missing_ok);
+extern char *get_cmk_name(Oid cmkid, bool missing_ok);
 
 #define type_is_array(typid)  (get_element_type(typid) != InvalidOid)
 /* type_is_array_domain accepts both plain arrays and domains over arrays */
diff --git a/src/include/utils/plancache.h b/src/include/utils/plancache.h
index a443181d41..8ecacb29df 100644
--- a/src/include/utils/plancache.h
+++ b/src/include/utils/plancache.h
@@ -207,6 +207,9 @@ extern void CompleteCachedPlan(CachedPlanSource *plansource,
 extern void SaveCachedPlan(CachedPlanSource *plansource);
 extern void DropCachedPlan(CachedPlanSource *plansource);
 
+extern List *RevalidateCachedQuery(CachedPlanSource *plansource,
+								   QueryEnvironment *queryEnv);
+
 extern void CachedPlanSetParentContext(CachedPlanSource *plansource,
 									   MemoryContext newcontext);
 
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index d5d50ceab4..bf1d527c1a 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -44,8 +44,14 @@ enum SysCacheIdentifier
 	AUTHNAME,
 	AUTHOID,
 	CASTSOURCETARGET,
+	CEKDATACEKCMK,
+	CEKDATAOID,
+	CEKNAMENSP,
+	CEKOID,
 	CLAAMNAMENSP,
 	CLAOID,
+	CMKNAMENSP,
+	CMKOID,
 	COLLNAMEENCNSP,
 	COLLOID,
 	CONDEFAULT,
diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile
index c18e914228..10dfda8016 100644
--- a/src/interfaces/libpq/Makefile
+++ b/src/interfaces/libpq/Makefile
@@ -54,6 +54,7 @@ endif
 
 ifeq ($(with_ssl),openssl)
 OBJS += \
+	fe-encrypt-openssl.o \
 	fe-secure-openssl.o
 endif
 
diff --git a/src/interfaces/libpq/exports.txt b/src/interfaces/libpq/exports.txt
index e8bcc88370..8897aa243c 100644
--- a/src/interfaces/libpq/exports.txt
+++ b/src/interfaces/libpq/exports.txt
@@ -186,3 +186,7 @@ PQpipelineStatus          183
 PQsetTraceFlags           184
 PQmblenBounded            185
 PQsendFlushRequest        186
+PQexecPreparedDescribed   187
+PQsendQueryPreparedDescribed 188
+PQfisencrypted            189
+PQparamisencrypted        190
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index 50b5df3490..bf6149e859 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -341,6 +341,14 @@ static const internalPQconninfoOption PQconninfoOptions[] = {
 		"Target-Session-Attrs", "", 15, /* sizeof("prefer-standby") = 15 */
 	offsetof(struct pg_conn, target_session_attrs)},
 
+	{"cmklookup", "PGCMKLOOKUP", "", NULL,
+		"CMK-Lookup", "", 64,
+	offsetof(struct pg_conn, cmklookup)},
+
+	{"column_encryption", "PGCOLUMNENCRYPTION", "0", NULL,
+		"Column-Encryption", "", 1,
+	offsetof(struct pg_conn, column_encryption_setting)},
+
 	/* Terminating entry --- MUST BE LAST */
 	{NULL, NULL, NULL, NULL,
 	NULL, NULL, 0}
@@ -1412,6 +1420,28 @@ connectOptions2(PGconn *conn)
 			goto oom_error;
 	}
 
+	/*
+	 * validate column_encryption option
+	 */
+	if (conn->column_encryption_setting)
+	{
+		if (strcmp(conn->column_encryption_setting, "on") == 0 ||
+			strcmp(conn->column_encryption_setting, "true") == 0 ||
+			strcmp(conn->column_encryption_setting, "1") == 0)
+			conn->column_encryption_enabled = true;
+		else if (strcmp(conn->column_encryption_setting, "off") == 0 ||
+				 strcmp(conn->column_encryption_setting, "false") == 0 ||
+				 strcmp(conn->column_encryption_setting, "0") == 0)
+			conn->column_encryption_enabled = false;
+		else
+		{
+			conn->status = CONNECTION_BAD;
+			libpq_append_conn_error(conn, "invalid %s value: \"%s\"",
+									"column_encryption", conn->column_encryption_setting);
+			return false;
+		}
+	}
+
 	/*
 	 * Only if we get this far is it appropriate to try to connect. (We need a
 	 * state flag, rather than just the boolean result of this function, in
@@ -4029,6 +4059,22 @@ freePGconn(PGconn *conn)
 	free(conn->krbsrvname);
 	free(conn->gsslib);
 	free(conn->connip);
+	free(conn->cmklookup);
+	for (int i = 0; i < conn->ncmks; i++)
+	{
+		free(conn->cmks[i].cmkname);
+		free(conn->cmks[i].cmkrealm);
+	}
+	free(conn->cmks);
+	for (int i = 0; i < conn->nceks; i++)
+	{
+		if (conn->ceks[i].cekdata)
+		{
+			explicit_bzero(conn->ceks[i].cekdata, conn->ceks[i].cekdatalen);
+			free(conn->ceks[i].cekdata);
+		}
+	}
+	free(conn->ceks);
 	/* Note that conn->Pfdebug is not ours to close or free */
 	free(conn->write_err_msg);
 	free(conn->inBuffer);
diff --git a/src/interfaces/libpq/fe-encrypt-openssl.c b/src/interfaces/libpq/fe-encrypt-openssl.c
new file mode 100644
index 0000000000..ab6c70967f
--- /dev/null
+++ b/src/interfaces/libpq/fe-encrypt-openssl.c
@@ -0,0 +1,842 @@
+/*-------------------------------------------------------------------------
+ *
+ * fe-encrypt-openssl.c
+ *
+ * client-side column encryption support using OpenSSL
+ *
+ * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *	  src/interfaces/libpq/fe-encrypt-openssl.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres_fe.h"
+
+#include "fe-encrypt.h"
+#include "libpq-int.h"
+
+#include "common/colenc.h"
+#include "port/pg_bswap.h"
+
+#include <openssl/evp.h>
+
+
+/*
+ * When TEST_ENCRYPT is defined, this file builds a standalone program that
+ * checks encryption test cases against the specification document.
+ *
+ * We have to replace some functions that are not available in that
+ * environment.
+ */
+#ifdef TEST_ENCRYPT
+
+#define libpq_gettext(x) (x)
+#define libpq_append_conn_error(conn, ...) do { fprintf(stderr, __VA_ARGS__); fprintf(stderr, "\n"); exit(1); } while(0)
+
+#endif							/* TEST_ENCRYPT */
+
+
+/*
+ * Decrypt the CEK given by "from" and "fromlen" (data typically sent from the
+ * server) using the CMK in cmkfilename.
+ */
+unsigned char *
+decrypt_cek_from_file(PGconn *conn, const char *cmkfilename, int cmkalg,
+					  int fromlen, const unsigned char *from,
+					  int *tolen)
+{
+	const EVP_MD *md = NULL;
+	EVP_PKEY   *key = NULL;
+	RSA		   *rsa = NULL;
+	BIO		   *bio = NULL;
+	EVP_PKEY_CTX *ctx = NULL;
+	unsigned char *out = NULL;
+	size_t		outlen;
+
+	switch (cmkalg)
+	{
+		case PG_CMK_RSAES_OAEP_SHA_1:
+			md = EVP_sha1();
+			break;
+		case PG_CMK_RSAES_OAEP_SHA_256:
+			md = EVP_sha256();
+			break;
+		case PG_CMK_UNSPECIFIED:
+			libpq_append_conn_error(conn, "unspecified CMK algorithm not supported with file lookup scheme");
+			goto fail;
+		default:
+			libpq_append_conn_error(conn, "unsupported CMK algorithm ID: %d", cmkalg);
+			goto fail;
+	}
+
+	bio = BIO_new_file(cmkfilename, "r");
+	if (!bio)
+	{
+		libpq_append_conn_error(conn, "could not open file \"%s\": %m", cmkfilename);
+		goto fail;
+	}
+
+	rsa = RSA_new();
+	if (!rsa)
+	{
+		libpq_append_conn_error(conn, "could not allocate RSA structure: %s",
+								ERR_reason_error_string(ERR_get_error()));
+		goto fail;
+	}
+	/*
+	 * Note: We must go through BIO and not say use PEM_read_RSAPrivateKey()
+	 * directly on a FILE.  Otherwise, we get into "no OPENSSL_Applink" hell
+	 * on Windows (which happens whenever you pass a stdio handle from the
+	 * application into OpenSSL).
+	 */
+	rsa = PEM_read_bio_RSAPrivateKey(bio, &rsa, NULL, NULL);
+	if (!rsa)
+	{
+		libpq_append_conn_error(conn, "could not read RSA private key: %s",
+								ERR_reason_error_string(ERR_get_error()));
+		goto fail;
+	}
+
+	key = EVP_PKEY_new();
+	if (!key)
+	{
+		libpq_append_conn_error(conn, "could not allocate private key structure: %s",
+								ERR_reason_error_string(ERR_get_error()));
+		goto fail;
+	}
+
+	if (!EVP_PKEY_assign_RSA(key, rsa))
+	{
+		libpq_append_conn_error(conn, "could not assign private key: %s",
+								ERR_reason_error_string(ERR_get_error()));
+		goto fail;
+	}
+
+	ctx = EVP_PKEY_CTX_new(key, NULL);
+	if (!ctx)
+	{
+		libpq_append_conn_error(conn, "could not allocate public key algorithm context: %s",
+								ERR_reason_error_string(ERR_get_error()));
+		goto fail;
+	}
+
+	if (EVP_PKEY_decrypt_init(ctx) <= 0)
+	{
+		libpq_append_conn_error(conn, "decryption initialization failed: %s",
+								ERR_reason_error_string(ERR_get_error()));
+		goto fail;
+	}
+
+	if (EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_OAEP_PADDING) <= 0 ||
+		EVP_PKEY_CTX_set_rsa_mgf1_md(ctx, md) <= 0 ||
+		EVP_PKEY_CTX_set_rsa_oaep_md(ctx, md) <= 0)
+	{
+		libpq_append_conn_error(conn, "could not set RSA parameter: %s",
+								ERR_reason_error_string(ERR_get_error()));
+		goto fail;
+	}
+
+	/* get output length */
+	if (EVP_PKEY_decrypt(ctx, NULL, &outlen, from, fromlen) <= 0)
+	{
+		libpq_append_conn_error(conn, "RSA decryption setup failed: %s",
+								ERR_reason_error_string(ERR_get_error()));
+		goto fail;
+	}
+
+	out = malloc(outlen);
+	if (!out)
+	{
+		libpq_append_conn_error(conn, "out of memory");
+		goto fail;
+	}
+
+	if (EVP_PKEY_decrypt(ctx, out, &outlen, from, fromlen) <= 0)
+	{
+		libpq_append_conn_error(conn, "RSA decryption failed: %s",
+								ERR_reason_error_string(ERR_get_error()));
+		free(out);
+		out = NULL;
+		goto fail;
+	}
+
+	*tolen = outlen;
+
+fail:
+	EVP_PKEY_CTX_free(ctx);
+	EVP_PKEY_free(key);
+	BIO_free(bio);
+
+	return out;
+}
+
+
+/*
+ * The routines below implement the AEAD algorithms specified in
+ * <https://datatracker.ietf.org/doc/html/draft-mcgrew-aead-aes-cbc-hmac-sha2-05>
+ * for encrypting and decrypting column values.
+ */
+
+#ifdef TEST_ENCRYPT
+
+/*
+ * Test data from
+ * <https://datatracker.ietf.org/doc/html/draft-mcgrew-aead-aes-cbc-hmac-sha2-05#section-5>
+ */
+
+/*
+ * The different test cases just use different prefixes of K, so one constant
+ * is enough here.
+ */
+static const unsigned char K[] = {
+	0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+	0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
+	0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f,
+	0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f,
+};
+
+static const unsigned char P[] = {
+	0x41, 0x20, 0x63, 0x69, 0x70, 0x68, 0x65, 0x72, 0x20, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x20,
+	0x6d, 0x75, 0x73, 0x74, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x62, 0x65, 0x20, 0x72, 0x65, 0x71, 0x75,
+	0x69, 0x72, 0x65, 0x64, 0x20, 0x74, 0x6f, 0x20, 0x62, 0x65, 0x20, 0x73, 0x65, 0x63, 0x72, 0x65,
+	0x74, 0x2c, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x69, 0x74, 0x20, 0x6d, 0x75, 0x73, 0x74, 0x20, 0x62,
+	0x65, 0x20, 0x61, 0x62, 0x6c, 0x65, 0x20, 0x74, 0x6f, 0x20, 0x66, 0x61, 0x6c, 0x6c, 0x20, 0x69,
+	0x6e, 0x74, 0x6f, 0x20, 0x74, 0x68, 0x65, 0x20, 0x68, 0x61, 0x6e, 0x64, 0x73, 0x20, 0x6f, 0x66,
+	0x20, 0x74, 0x68, 0x65, 0x20, 0x65, 0x6e, 0x65, 0x6d, 0x79, 0x20, 0x77, 0x69, 0x74, 0x68, 0x6f,
+	0x75, 0x74, 0x20, 0x69, 0x6e, 0x63, 0x6f, 0x6e, 0x76, 0x65, 0x6e, 0x69, 0x65, 0x6e, 0x63, 0x65,
+};
+
+static const unsigned char test_IV[] = {
+	0x1a, 0xf3, 0x8c, 0x2d, 0xc2, 0xb9, 0x6f, 0xfd, 0xd8, 0x66, 0x94, 0x09, 0x23, 0x41, 0xbc, 0x04,
+};
+
+static const unsigned char test_A[] = {
+	0x54, 0x68, 0x65, 0x20, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x20, 0x70, 0x72, 0x69, 0x6e, 0x63,
+	0x69, 0x70, 0x6c, 0x65, 0x20, 0x6f, 0x66, 0x20, 0x41, 0x75, 0x67, 0x75, 0x73, 0x74, 0x65, 0x20,
+	0x4b, 0x65, 0x72, 0x63, 0x6b, 0x68, 0x6f, 0x66, 0x66, 0x73,
+};
+
+#endif							/* TEST_ENCRYPT */
+
+
+/*
+ * Get OpenSSL cipher that corresponds to the CEK algorithm number.
+ */
+static const EVP_CIPHER *
+pg_cekalg_to_openssl_cipher(int cekalg)
+{
+	const EVP_CIPHER *cipher;
+
+	switch (cekalg)
+	{
+		case PG_CEK_AEAD_AES_128_CBC_HMAC_SHA_256:
+			cipher = EVP_aes_128_cbc();
+			break;
+		case PG_CEK_AEAD_AES_192_CBC_HMAC_SHA_384:
+			cipher = EVP_aes_192_cbc();
+			break;
+		case PG_CEK_AEAD_AES_256_CBC_HMAC_SHA_384:
+		case PG_CEK_AEAD_AES_256_CBC_HMAC_SHA_512:
+			cipher = EVP_aes_256_cbc();
+			break;
+		default:
+			cipher = NULL;
+	}
+
+	return cipher;
+}
+
+/*
+ * Get OpenSSL digest (for MAC) that corresponds to the CEK algorithm number.
+ */
+static const EVP_MD *
+pg_cekalg_to_openssl_md(int cekalg)
+{
+	const EVP_MD *md;
+
+	switch (cekalg)
+	{
+		case PG_CEK_AEAD_AES_128_CBC_HMAC_SHA_256:
+			md = EVP_sha256();
+			break;
+		case PG_CEK_AEAD_AES_192_CBC_HMAC_SHA_384:
+		case PG_CEK_AEAD_AES_256_CBC_HMAC_SHA_384:
+			md = EVP_sha384();
+			break;
+		case PG_CEK_AEAD_AES_256_CBC_HMAC_SHA_512:
+			md = EVP_sha512();
+			break;
+		default:
+			md = NULL;
+	}
+
+	return md;
+}
+
+/*
+ * Get the MAC key length (in octets) that corresponds to the CEK algorithm number.
+ *
+ * This is MAC_KEY_LEN in the mcgrew paper.
+ */
+static int
+md_key_length(const EVP_MD *md)
+{
+	if (md == EVP_sha256())
+		return 16;
+	else if (md == EVP_sha384())
+		return 24;
+	else if (md == EVP_sha512())
+		return 32;
+	else
+		return -1;
+}
+
+/*
+ * Get the HMAC output length (in octets) that corresponds to the CEK
+ * algorithm number.
+ */
+static int
+md_hash_length(const EVP_MD *md)
+{
+	if (md == EVP_sha256())
+		return 32;
+	else if (md == EVP_sha384())
+		return 48;
+	else if (md == EVP_sha512())
+		return 64;
+	else
+		return -1;
+}
+
+/*
+ * Length of associated data (A in mcgrew paper)
+ */
+#ifndef TEST_ENCRYPT
+#define PG_AD_LEN 4
+#else
+#define PG_AD_LEN sizeof(test_A)
+#endif
+
+/*
+ * Compute message authentication tag (T in the mcgrew paper), from MAC key
+ * and ciphertext.
+ *
+ * Returns false on error, with error message in errmsgp.
+ */
+static bool
+get_message_auth_tag(const EVP_MD *md,
+					 const unsigned char *mac_key, int mac_key_len,
+					 const unsigned char *encr, int encrlen,
+					 unsigned char *md_value, size_t *md_len_p,
+					 const char **errmsgp)
+{
+	static char msgbuf[1024];
+	EVP_MD_CTX *evp_md_ctx = NULL;
+	EVP_PKEY   *pkey = NULL;
+	size_t		bufsize;
+	unsigned char *buf = NULL;
+	int64		al;
+	bool		result = false;
+
+	if (encrlen < 0)
+	{
+		snprintf(msgbuf, sizeof(msgbuf),
+				 libpq_gettext("encrypted value has invalid length"));
+		*errmsgp = msgbuf;
+		goto fail;
+	}
+
+	evp_md_ctx = EVP_MD_CTX_new();
+
+	pkey = EVP_PKEY_new_raw_private_key(EVP_PKEY_HMAC, NULL, mac_key, mac_key_len);
+	if (!pkey)
+	{
+		snprintf(msgbuf, sizeof(msgbuf),
+				 libpq_gettext("could not allocate key for HMAC: %s"),
+				 ERR_reason_error_string(ERR_get_error()));
+		*errmsgp = msgbuf;
+		goto fail;
+	}
+
+	/*
+	 * Build input to MAC call (A || S || AL in mcgrew paper)
+	 */
+	bufsize = PG_AD_LEN + encrlen + sizeof(int64);
+	buf = malloc(bufsize);
+	if (!buf)
+	{
+		*errmsgp = libpq_gettext("out of memory");
+		goto fail;
+	}
+	/* A (associated data) */
+#ifndef TEST_ENCRYPT
+	buf[0] = 'P';
+	buf[1] = 'G';
+	*(int16 *) (buf + 2) = pg_hton16(1);
+#else
+	memcpy(buf, test_A, sizeof(test_A));
+#endif
+	/* S (ciphertext) */
+	memcpy(buf + PG_AD_LEN, encr, encrlen);
+	/* AL (number of *bits* in A) */
+	al = pg_hton64(PG_AD_LEN * 8);
+	memcpy(buf + PG_AD_LEN + encrlen, &al, sizeof(al));
+
+	/*
+	 * Call MAC
+	 */
+	if (!EVP_DigestSignInit(evp_md_ctx, NULL, md, NULL, pkey))
+	{
+		snprintf(msgbuf, sizeof(msgbuf),
+				 libpq_gettext("digest initialization failed: %s"),
+				 ERR_reason_error_string(ERR_get_error()));
+		*errmsgp = msgbuf;
+		goto fail;
+	}
+
+	if (!EVP_DigestSignUpdate(evp_md_ctx, buf, bufsize))
+	{
+		snprintf(msgbuf, sizeof(msgbuf),
+				 libpq_gettext("digest signing failed: %s"),
+				 ERR_reason_error_string(ERR_get_error()));
+		*errmsgp = msgbuf;
+		goto fail;
+	}
+
+	if (!EVP_DigestSignFinal(evp_md_ctx, md_value, md_len_p))
+	{
+		snprintf(msgbuf, sizeof(msgbuf),
+				 libpq_gettext("digest signing failed: %s"),
+				 ERR_reason_error_string(ERR_get_error()));
+		*errmsgp = msgbuf;
+		goto fail;
+	}
+	Assert(*md_len_p == md_hash_length(md));
+
+	/* truncate output to half the length, per spec */
+	*md_len_p /= 2;
+
+	result = true;
+fail:
+	free(buf);
+	EVP_PKEY_free(pkey);
+	EVP_MD_CTX_free(evp_md_ctx);
+	return result;
+}
+
+/*
+ * Decrypt a column value
+ */
+unsigned char *
+decrypt_value(PGresult *res, const PGCEK *cek, int cekalg, const unsigned char *input, int inputlen, const char **errmsgp)
+{
+	static char msgbuf[1024];
+
+	const unsigned char *iv = NULL;
+	size_t		ivlen;
+
+	const EVP_CIPHER *cipher;
+	const EVP_MD *md;
+	EVP_CIPHER_CTX *evp_cipher_ctx = NULL;
+	int			enc_key_len;
+	int			mac_key_len;
+	int			iv_key_len;
+	int			key_len;
+	const unsigned char *enc_key;
+	const unsigned char *mac_key;
+	unsigned char md_value[EVP_MAX_MD_SIZE];
+	size_t		md_len = sizeof(md_value);
+	size_t		bufsize;
+	unsigned char *buf = NULL;
+	unsigned char *decr;
+	int			decrlen,
+				decrlen2;
+
+	unsigned char *result = NULL;
+
+	cipher = pg_cekalg_to_openssl_cipher(cekalg);
+	if (!cipher)
+	{
+		snprintf(msgbuf, sizeof(msgbuf),
+				 libpq_gettext("unrecognized encryption algorithm identifier: %d"), cekalg);
+		*errmsgp = msgbuf;
+		goto fail;
+	}
+
+	md = pg_cekalg_to_openssl_md(cekalg);
+	if (!md)
+	{
+		snprintf(msgbuf, sizeof(msgbuf),
+				 libpq_gettext("unrecognized digest algorithm identifier: %d"), cekalg);
+		*errmsgp = msgbuf;
+		goto fail;
+	}
+
+	evp_cipher_ctx = EVP_CIPHER_CTX_new();
+
+	if (!EVP_DecryptInit_ex(evp_cipher_ctx, cipher, NULL, NULL, NULL))
+	{
+		snprintf(msgbuf, sizeof(msgbuf),
+				 libpq_gettext("decryption initialization failed: %s"),
+				 ERR_reason_error_string(ERR_get_error()));
+		*errmsgp = msgbuf;
+		goto fail;
+	}
+
+	enc_key_len = EVP_CIPHER_CTX_key_length(evp_cipher_ctx);
+	mac_key_len = iv_key_len = md_key_length(md);
+	key_len = mac_key_len + enc_key_len + iv_key_len;
+
+	if (cek->cekdatalen != key_len)
+	{
+		snprintf(msgbuf, sizeof(msgbuf),
+				 libpq_gettext("column encryption key has wrong length for algorithm (has: %zu, required: %d)"),
+				 cek->cekdatalen, key_len);
+		*errmsgp = msgbuf;
+		goto fail;
+	}
+
+	mac_key = cek->cekdata;
+	enc_key = cek->cekdata + mac_key_len;
+
+	if (!get_message_auth_tag(md, mac_key, mac_key_len,
+							  input, inputlen - (md_hash_length(md) / 2),
+							  md_value, &md_len,
+							  errmsgp))
+	{
+		goto fail;
+	}
+
+	/* use constant-time comparison, per mcgrew paper */
+	if (CRYPTO_memcmp(input + (inputlen - md_len), md_value, md_len) != 0)
+	{
+		*errmsgp = libpq_gettext("MAC mismatch");
+		goto fail;
+	}
+
+	ivlen = EVP_CIPHER_CTX_iv_length(evp_cipher_ctx);
+	iv = input;
+	input += ivlen;
+	inputlen -= ivlen;
+	if (!EVP_DecryptInit_ex(evp_cipher_ctx, NULL, NULL, enc_key, iv))
+	{
+		snprintf(msgbuf, sizeof(msgbuf),
+				 libpq_gettext("decryption initialization failed: %s"),
+				 ERR_reason_error_string(ERR_get_error()));
+		*errmsgp = msgbuf;
+		goto fail;
+	}
+
+	bufsize = inputlen + EVP_CIPHER_CTX_block_size(evp_cipher_ctx) + 1;
+#ifndef TEST_ENCRYPT
+	buf = pqResultAlloc(res, bufsize, false);
+#else
+	buf = malloc(bufsize);
+#endif
+	if (!buf)
+	{
+		*errmsgp = libpq_gettext("out of memory");
+		goto fail;
+	}
+	decr = buf;
+	if (!EVP_DecryptUpdate(evp_cipher_ctx, decr, &decrlen, input, inputlen - md_len))
+	{
+		snprintf(msgbuf, sizeof(msgbuf),
+				 libpq_gettext("decryption failed: %s"),
+				 ERR_reason_error_string(ERR_get_error()));
+		*errmsgp = msgbuf;
+		goto fail;
+	}
+	if (!EVP_DecryptFinal_ex(evp_cipher_ctx, decr + decrlen, &decrlen2))
+	{
+		snprintf(msgbuf, sizeof(msgbuf),
+				 libpq_gettext("decryption failed: %s"),
+				 ERR_reason_error_string(ERR_get_error()));
+		*errmsgp = msgbuf;
+		goto fail;
+	}
+	decrlen += decrlen2;
+	Assert(decrlen < bufsize);
+	decr[decrlen] = '\0';
+	result = decr;
+
+fail:
+	EVP_CIPHER_CTX_free(evp_cipher_ctx);
+
+	return result;
+}
+
+/*
+ * Compute a synthetic initialization vector (SIV), for deterministic
+ * encryption.
+ *
+ * Per protocol specification, the SIV is computed as:
+ *
+ * SUBSTRING(HMAC(K, P) FOR IVLEN)
+ */
+#ifndef TEST_ENCRYPT
+static bool
+make_siv(PGconn *conn,
+		 unsigned char *iv, size_t ivlen,
+		 const EVP_MD *md,
+		 const unsigned char *iv_key, int iv_key_len,
+		 const unsigned char *plaintext, int plaintext_len)
+{
+	EVP_MD_CTX *evp_md_ctx = NULL;
+	EVP_PKEY   *pkey = NULL;
+	unsigned char md_value[EVP_MAX_MD_SIZE];
+	size_t		md_len = sizeof(md_value);
+	bool		result = false;
+
+	evp_md_ctx = EVP_MD_CTX_new();
+
+	pkey = EVP_PKEY_new_raw_private_key(EVP_PKEY_HMAC, NULL, iv_key, iv_key_len);
+	if (!pkey)
+	{
+		libpq_append_conn_error(conn, "could not allocate key for HMAC: %s",
+								ERR_reason_error_string(ERR_get_error()));
+		goto fail;
+	}
+
+	if (!EVP_DigestSignInit(evp_md_ctx, NULL, md, NULL, pkey))
+	{
+		libpq_append_conn_error(conn, "digest initialization failed: %s",
+								ERR_reason_error_string(ERR_get_error()));
+		goto fail;
+	}
+
+	if (!EVP_DigestSignUpdate(evp_md_ctx, plaintext, plaintext_len))
+	{
+		libpq_append_conn_error(conn, "digest signing failed: %s",
+								ERR_reason_error_string(ERR_get_error()));
+		goto fail;
+	}
+
+	if (!EVP_DigestSignFinal(evp_md_ctx, md_value, &md_len))
+	{
+		libpq_append_conn_error(conn, "digest signing failed: %s",
+								ERR_reason_error_string(ERR_get_error()));
+		goto fail;
+	}
+	Assert(md_len == md_hash_length(md));
+	memcpy(iv, md_value, ivlen);
+
+	result = true;
+fail:
+	EVP_PKEY_free(pkey);
+	EVP_MD_CTX_free(evp_md_ctx);
+	return result;
+}
+#endif							/* TEST_ENCRYPT */
+
+/*
+ * Encrypt a column value
+ */
+unsigned char *
+encrypt_value(PGconn *conn, const PGCEK *cek, int cekalg, const unsigned char *value, int *nbytesp, bool enc_det)
+{
+	int			nbytes = *nbytesp;
+	unsigned char iv[EVP_MAX_IV_LENGTH];
+	size_t		ivlen;
+	const EVP_CIPHER *cipher;
+	const EVP_MD *md;
+	EVP_CIPHER_CTX *evp_cipher_ctx = NULL;
+	int			enc_key_len;
+	int			mac_key_len;
+	int			iv_key_len;
+	int			key_len;
+	const unsigned char *enc_key;
+	const unsigned char *mac_key;
+	const unsigned char *iv_key;
+	size_t		bufsize;
+	unsigned char *buf = NULL;
+	unsigned char *encr;
+	int			encrlen,
+				encrlen2;
+
+	const char *errmsg;
+	unsigned char md_value[EVP_MAX_MD_SIZE];
+	size_t		md_len = sizeof(md_value);
+	size_t		buf2size;
+	unsigned char *buf2 = NULL;
+
+	unsigned char *result = NULL;
+
+	cipher = pg_cekalg_to_openssl_cipher(cekalg);
+	if (!cipher)
+	{
+		libpq_append_conn_error(conn, "unrecognized encryption algorithm identifier: %d", cekalg);
+		goto fail;
+	}
+
+	md = pg_cekalg_to_openssl_md(cekalg);
+	if (!md)
+	{
+		libpq_append_conn_error(conn, "unrecognized digest algorithm identifier: %d", cekalg);
+		goto fail;
+	}
+
+	evp_cipher_ctx = EVP_CIPHER_CTX_new();
+
+	if (!EVP_EncryptInit_ex(evp_cipher_ctx, cipher, NULL, NULL, NULL))
+	{
+		libpq_append_conn_error(conn, "encryption initialization failed: %s",
+								ERR_reason_error_string(ERR_get_error()));
+		goto fail;
+	}
+
+	enc_key_len = EVP_CIPHER_CTX_key_length(evp_cipher_ctx);
+	mac_key_len = iv_key_len = md_key_length(md);
+	key_len = mac_key_len + enc_key_len + iv_key_len;
+
+	if (cek->cekdatalen != key_len)
+	{
+		libpq_append_conn_error(conn, "column encryption key has wrong length for algorithm (has: %zu, required: %d)",
+								cek->cekdatalen, key_len);
+		goto fail;
+	}
+
+	mac_key = cek->cekdata;
+	enc_key = cek->cekdata + mac_key_len;
+	iv_key = cek->cekdata + mac_key_len + enc_key_len;
+
+	ivlen = EVP_CIPHER_CTX_iv_length(evp_cipher_ctx);
+	Assert(ivlen <= sizeof(iv));
+	if (enc_det)
+	{
+#ifndef TEST_ENCRYPT
+		make_siv(conn, iv, ivlen, md, iv_key, iv_key_len, value, nbytes);
+#else
+		(void) iv_key;	/* unused */
+		memcpy(iv, test_IV, ivlen);
+#endif
+	}
+	else
+		pg_strong_random(iv, ivlen);
+	if (!EVP_EncryptInit_ex(evp_cipher_ctx, NULL, NULL, enc_key, iv))
+	{
+		libpq_append_conn_error(conn, "encryption initialization failed: %s",
+								ERR_reason_error_string(ERR_get_error()));
+		goto fail;
+	}
+
+	bufsize = ivlen + (nbytes + 2 * EVP_CIPHER_CTX_block_size(evp_cipher_ctx) - 1);
+	buf = malloc(bufsize);
+	if (!buf)
+	{
+		libpq_append_conn_error(conn, "out of memory");
+		goto fail;
+	}
+	memcpy(buf, iv, ivlen);
+	encr = buf + ivlen;
+	if (!EVP_EncryptUpdate(evp_cipher_ctx, encr, &encrlen, value, nbytes))
+	{
+		libpq_append_conn_error(conn, "encryption failed: %s",
+								ERR_reason_error_string(ERR_get_error()));
+		goto fail;
+	}
+	if (!EVP_EncryptFinal_ex(evp_cipher_ctx, encr + encrlen, &encrlen2))
+	{
+		libpq_append_conn_error(conn, "encryption failed: %s",
+								ERR_reason_error_string(ERR_get_error()));
+		goto fail;
+	}
+	encrlen += encrlen2;
+
+	encr -= ivlen;
+	encrlen += ivlen;
+
+	Assert(encrlen <= bufsize);
+
+	if (!get_message_auth_tag(md, mac_key, mac_key_len,
+							  encr, encrlen,
+							  md_value, &md_len,
+							  &errmsg))
+	{
+		appendPQExpBuffer(&conn->errorMessage, "%s\n", errmsg);
+		goto fail;
+	}
+
+	buf2size = encrlen + md_len;
+	buf2 = malloc(buf2size);
+	if (!buf2)
+	{
+		libpq_append_conn_error(conn, "out of memory");
+		goto fail;
+	}
+	memcpy(buf2, encr, encrlen);
+	memcpy(buf2 + encrlen, md_value, md_len);
+
+	result = buf2;
+	nbytes = buf2size;
+
+fail:
+	free(buf);
+	EVP_CIPHER_CTX_free(evp_cipher_ctx);
+
+	*nbytesp = nbytes;
+	return result;
+}
+
+
+/*
+ * Run test cases
+ */
+#ifdef TEST_ENCRYPT
+
+static void
+debug_print_hex(const char *name, const unsigned char *val, int len)
+{
+	printf("%s =", name);
+	for (int i = 0; i < len; i++)
+	{
+		if (i % 16 == 0)
+			printf("\n");
+		else
+			printf(" ");
+		printf("%02x", val[i]);
+	}
+	printf("\n");
+}
+
+/*
+ * K and P are from the mcgrew paper, K_len and P_len are their respective
+ * lengths.  encrypt_value() requires the key length to contain the IV key, so
+ * we pass it here, too, but it will not be used.
+ */
+static void
+test_case(int alg, const unsigned char *K, size_t K_len, size_t IV_key_len, const unsigned char *P, size_t P_len)
+{
+	unsigned char *C;
+	int			nbytes;
+	PGCEK		cek;
+
+	nbytes = P_len;
+	cek.cekdatalen = K_len + IV_key_len;
+	cek.cekdata = malloc(cek.cekdatalen);
+	memcpy(cek.cekdata, K, K_len);
+
+	C = encrypt_value(NULL, &cek, alg, P, &nbytes, true);
+	debug_print_hex("C", C, nbytes);
+}
+
+int
+main(int argc, char **argv)
+{
+	printf("5.1\n");
+	test_case(PG_CEK_AEAD_AES_128_CBC_HMAC_SHA_256, K, 32, 16, P, sizeof(P));
+	printf("5.2\n");
+	test_case(PG_CEK_AEAD_AES_192_CBC_HMAC_SHA_384, K, 48, 24, P, sizeof(P));
+	printf("5.3\n");
+	test_case(PG_CEK_AEAD_AES_256_CBC_HMAC_SHA_384, K, 56, 24, P, sizeof(P));
+	printf("5.4\n");
+	test_case(PG_CEK_AEAD_AES_256_CBC_HMAC_SHA_512, K, 64, 32, P, sizeof(P));
+
+	return 0;
+}
+
+#endif							/* TEST_ENCRYPT */
diff --git a/src/interfaces/libpq/fe-encrypt.h b/src/interfaces/libpq/fe-encrypt.h
new file mode 100644
index 0000000000..0b65f913da
--- /dev/null
+++ b/src/interfaces/libpq/fe-encrypt.h
@@ -0,0 +1,33 @@
+/*-------------------------------------------------------------------------
+ *
+ * fe-encrypt.h
+ *
+ * client-side column encryption support
+ *
+ * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ *	  src/interfaces/libpq/fe-encrypt.h
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef FE_ENCRYPT_H
+#define FE_ENCRYPT_H
+
+#include "libpq-fe.h"
+#include "libpq-int.h"
+
+extern unsigned char *decrypt_cek_from_file(PGconn *conn, const char *cmkfilename, int cmkalg,
+											int fromlen, const unsigned char *from,
+											int *tolen);
+
+extern unsigned char *decrypt_value(PGresult *res, const PGCEK *cek, int cekalg,
+									const unsigned char *input, int inputlen,
+									const char **errmsgp);
+
+extern unsigned char *encrypt_value(PGconn *conn, const PGCEK *cek, int cekalg,
+									const unsigned char *value, int *nbytesp, bool enc_det);
+
+#endif							/* FE_ENCRYPT_H */
diff --git a/src/interfaces/libpq/fe-exec.c b/src/interfaces/libpq/fe-exec.c
index ec62550e38..7c7e2bac12 100644
--- a/src/interfaces/libpq/fe-exec.c
+++ b/src/interfaces/libpq/fe-exec.c
@@ -24,6 +24,8 @@
 #include <unistd.h>
 #endif
 
+#include "common/colenc.h"
+#include "fe-encrypt.h"
 #include "libpq-fe.h"
 #include "libpq-int.h"
 #include "mb/pg_wchar.h"
@@ -72,7 +74,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);
@@ -1183,6 +1186,414 @@ pqSaveParameterStatus(PGconn *conn, const char *name, const char *value)
 	}
 }
 
+/*
+ * pqSaveColumnMasterKey - save column master key sent by backend
+ */
+int
+pqSaveColumnMasterKey(PGconn *conn, int keyid, const char *keyname,
+					  const char *keyrealm)
+{
+	char	   *keyname_copy;
+	char	   *keyrealm_copy;
+	bool		found;
+
+	keyname_copy = strdup(keyname);
+	if (!keyname_copy)
+		return EOF;
+	keyrealm_copy = strdup(keyrealm);
+	if (!keyrealm_copy)
+	{
+		free(keyname_copy);
+		return EOF;
+	}
+
+	found = false;
+	for (int i = 0; i < conn->ncmks; i++)
+	{
+		struct pg_cmk *checkcmk = &conn->cmks[i];
+
+		/* replace existing? */
+		if (checkcmk->cmkid == keyid)
+		{
+			free(checkcmk->cmkname);
+			free(checkcmk->cmkrealm);
+			checkcmk->cmkname = keyname_copy;
+			checkcmk->cmkrealm = keyrealm_copy;
+			found = true;
+			break;
+		}
+	}
+
+	/* append new? */
+	if (!found)
+	{
+		int			newncmks;
+		struct pg_cmk *newcmks;
+		struct pg_cmk *newcmk;
+
+		newncmks = conn->ncmks + 1;
+		if (newncmks <= 0)
+			return EOF;
+		newcmks = realloc(conn->cmks, newncmks * sizeof(struct pg_cmk));
+		if (!newcmks)
+		{
+			free(keyname_copy);
+			free(keyrealm_copy);
+			return EOF;
+		}
+
+		newcmk = &newcmks[newncmks - 1];
+		newcmk->cmkid = keyid;
+		newcmk->cmkname = keyname_copy;
+		newcmk->cmkrealm = keyrealm_copy;
+
+		conn->ncmks = newncmks;
+		conn->cmks = newcmks;
+	}
+
+	return 0;
+}
+
+/*
+ * Replace placeholders in input string.  Return value malloc'ed.
+ */
+static char *
+replace_cmk_placeholders(const char *in, const char *cmkname, const char *cmkrealm, int cmkalg, const char *tmpfile)
+{
+	PQExpBufferData buf;
+
+	initPQExpBuffer(&buf);
+
+	for (const char *p = in; *p; p++)
+	{
+		if (p[0] == '%')
+		{
+			switch (p[1])
+			{
+				case 'a':
+					{
+						const char *s = get_cmkalg_name(cmkalg);
+
+						appendPQExpBufferStr(&buf, s ? s : "INVALID");
+					}
+					p++;
+					break;
+				case 'j':
+					{
+						const char *s = get_cmkalg_jwa_name(cmkalg);
+
+						appendPQExpBufferStr(&buf, s ? s : "INVALID");
+					}
+					p++;
+					break;
+				case 'k':
+					appendPQExpBufferStr(&buf, cmkname);
+					p++;
+					break;
+				case 'p':
+					appendPQExpBufferStr(&buf, tmpfile);
+					p++;
+					break;
+				case 'r':
+					appendPQExpBufferStr(&buf, cmkrealm);
+					p++;
+					break;
+				default:
+					appendPQExpBufferChar(&buf, p[0]);
+			}
+		}
+		else
+			appendPQExpBufferChar(&buf, p[0]);
+	}
+
+	return buf.data;
+}
+
+#ifndef USE_SSL
+/*
+ * Dummy implementation for non-SSL builds
+ */
+unsigned char *
+decrypt_cek_from_file(PGconn *conn, const char *cmkfilename, int cmkalg,
+					  int fromlen, const unsigned char *from,
+					  int *tolen)
+{
+	libpq_append_conn_error(conn, "column encryption not supported by this build");
+	return NULL;
+}
+#endif
+
+/*
+ * Decrypt a CEK using the given CMK.  The ciphertext is passed in
+ * "from" and "fromlen".  Return the decrypted value in a malloc'ed area, its
+ * length via "tolen".  Return NULL on error; add error messages directly to
+ * "conn".
+ */
+static unsigned char *
+decrypt_cek(PGconn *conn, const PGCMK *cmk, int cmkalg,
+			int fromlen, const unsigned char *from,
+			int *tolen)
+{
+	char	   *cmklookup;
+	bool		found = false;
+	unsigned char *result = NULL;
+
+	cmklookup = strdup(conn->cmklookup ? conn->cmklookup : "");
+
+	if (!cmklookup)
+	{
+		libpq_append_conn_error(conn, "out of memory");
+		return NULL;
+	}
+
+	/*
+	 * Analyze semicolon-separated list
+	 */
+	for (char *s = strtok(cmklookup, ";"); s; s = strtok(NULL, ";"))
+	{
+		char	   *sep;
+
+		/* split found token at '=' */
+		sep = strchr(s, '=');
+		if (!sep)
+		{
+			libpq_append_conn_error(conn, "syntax error in CMK lookup specification, missing \"%c\": %s", '=', s);
+			break;
+		}
+
+		/* matching realm? */
+		if (strncmp(s, "*", sep - s) == 0 || strncmp(s, cmk->cmkrealm, sep - s) == 0)
+		{
+			char	   *sep2;
+
+			found = true;
+
+			sep2 = strchr(sep, ':');
+			if (!sep2)
+			{
+				libpq_append_conn_error(conn, "syntax error in CMK lookup specification, missing \"%c\": %s", ':', s);
+				goto fail;
+			}
+
+			if (strncmp(sep + 1, "file", sep2 - (sep + 1)) == 0)
+			{
+				char	   *cmkfilename;
+
+				cmkfilename = replace_cmk_placeholders(sep2 + 1, cmk->cmkname, cmk->cmkrealm, cmkalg, "INVALID");
+				result = decrypt_cek_from_file(conn, cmkfilename, cmkalg, fromlen, from, tolen);
+				free(cmkfilename);
+			}
+			else if (strncmp(sep + 1, "run", sep2 - (sep + 1)) == 0)
+			{
+				char		tmpfile[MAXPGPATH] = {0};
+				int			fd;
+				char	   *command;
+				FILE	   *fp;
+				/* only needs enough room for CEK key material */
+				char		buf[1024];
+				size_t		nread;
+				int			rc;
+
+#ifndef WIN32
+				{
+					const char *tmpdir;
+
+					tmpdir = getenv("TMPDIR");
+					if (!tmpdir)
+						tmpdir = "/tmp";
+					strlcpy(tmpfile, tmpdir, sizeof(tmpfile));
+					strlcat(tmpfile, "/libpq-XXXXXX", sizeof(tmpfile));
+					fd = mkstemp(tmpfile);
+					if (fd < 0)
+					{
+						libpq_append_conn_error(conn, "could not run create temporary file: %m");
+						goto fail;
+					}
+				}
+#else
+				{
+					char		tmpdir[MAXPGPATH];
+					int			ret;
+
+					ret = GetTempPath(MAXPGPATH, tmpdir);
+					if (ret == 0 || ret > MAXPGPATH)
+					{
+						libpq_append_conn_error(conn, "could not locate temporary directory: %s",
+												!ret ? strerror(errno) : "");
+						return false;
+					}
+
+					if (GetTempFileName(tmpdir, "libpq", 0, tmpfile) == 0)
+					{
+						libpq_append_conn_error(conn, "could not run create temporary file: error code %lu",
+												GetLastError());
+						goto fail;
+					}
+
+					fd = open(tmpfile, O_WRONLY | O_TRUNC | PG_BINARY, 0);
+					if (fd < 0)
+					{
+						libpq_append_conn_error(conn, "could not run open temporary file: %m");
+						goto fail;
+					}
+				}
+#endif
+				if (write(fd, from, fromlen) < fromlen)
+				{
+					libpq_append_conn_error(conn, "could not write to temporary file: %m");
+					close(fd);
+					unlink(tmpfile);
+					goto fail;
+				}
+				if (close(fd) < 0)
+				{
+					libpq_append_conn_error(conn, "could not close temporary file: %m");
+					unlink(tmpfile);
+					goto fail;
+				}
+
+				command = replace_cmk_placeholders(sep2 + 1, cmk->cmkname, cmk->cmkrealm, cmkalg, tmpfile);
+				fp = popen(command, "r");
+				if (!fp)
+				{
+					libpq_append_conn_error(conn, "could not run command \"%s\": %m", command);
+					free(command);
+					unlink(tmpfile);
+					goto fail;
+				}
+				nread = fread(buf, 1, sizeof(buf), fp);
+				if (ferror(fp))
+				{
+					libpq_append_conn_error(conn, "could not read from command: %m");
+					pclose(fp);
+					free(command);
+					unlink(tmpfile);
+					goto fail;
+				}
+				else if (!feof(fp))
+				{
+					libpq_append_conn_error(conn, "output from command too long");
+					pclose(fp);
+					free(command);
+					unlink(tmpfile);
+					goto fail;
+				}
+				rc = pclose(fp);
+				if (rc != 0)
+				{
+					/*
+					 * XXX would like to use wait_result_to_str(rc) but that
+					 * cannot be called from libpq because it calls exit()
+					 */
+					libpq_append_conn_error(conn, "could not run command \"%s\"", command);
+					free(command);
+					unlink(tmpfile);
+					goto fail;
+				}
+				free(command);
+				unlink(tmpfile);
+
+				result = malloc(nread);
+				if (!result)
+				{
+					libpq_append_conn_error(conn, "out of memory");
+					goto fail;
+				}
+				memcpy(result, buf, nread);
+				*tolen = nread;
+			}
+			else
+			{
+				libpq_append_conn_error(conn, "CMK lookup scheme \"%s\" not recognized", sep + 1);
+				goto fail;
+			}
+		}
+	}
+
+	if (!found)
+	{
+		libpq_append_conn_error(conn, "no CMK lookup found for realm \"%s\"", cmk->cmkrealm);
+	}
+
+fail:
+	free(cmklookup);
+	return result;
+}
+
+/*
+ * pqSaveColumnEncryptionKey - save column encryption key sent by backend
+ */
+int
+pqSaveColumnEncryptionKey(PGconn *conn, int keyid, int cmkid, int cmkalg, const unsigned char *value, int len)
+{
+	PGCMK	   *cmk = NULL;
+	unsigned char *plainval = NULL;
+	int			plainvallen = 0;
+	bool		found;
+
+	for (int i = 0; i < conn->ncmks; i++)
+	{
+		if (conn->cmks[i].cmkid == cmkid)
+		{
+			cmk = &conn->cmks[i];
+			break;
+		}
+	}
+
+	if (!cmk)
+		return EOF;
+
+	plainval = decrypt_cek(conn, cmk, cmkalg, len, value, &plainvallen);
+	if (!plainval)
+		return EOF;
+
+	found = false;
+	for (int i = 0; i < conn->nceks; i++)
+	{
+		struct pg_cek *checkcek = &conn->ceks[i];
+
+		/* replace existing? */
+		if (checkcek->cekid == keyid)
+		{
+			free(checkcek->cekdata);
+			checkcek->cekdata = plainval;
+			checkcek->cekdatalen = plainvallen;
+			found = true;
+			break;
+		}
+	}
+
+	/* append new? */
+	if (!found)
+	{
+		int			newnceks;
+		struct pg_cek *newceks;
+		struct pg_cek *newcek;
+
+		newnceks = conn->nceks + 1;
+		if (newnceks <= 0)
+		{
+			free(plainval);
+			return EOF;
+		}
+		newceks = realloc(conn->ceks, newnceks * sizeof(struct pg_cek));
+		if (!newceks)
+		{
+			free(plainval);
+			return EOF;
+		}
+
+		newcek = &newceks[newnceks - 1];
+		newcek->cekid = keyid;
+		newcek->cekdata = plainval;
+		newcek->cekdatalen = plainvallen;
+
+		conn->nceks = newnceks;
+		conn->ceks = newceks;
+	}
+
+	return 0;
+}
 
 /*
  * pqRowProcessor
@@ -1251,13 +1662,51 @@ pqRowProcessor(PGconn *conn, const char **errmsgp)
 			bool		isbinary = (res->attDescs[i].format != 0);
 			char	   *val;
 
-			val = (char *) pqResultAlloc(res, clen + 1, isbinary);
-			if (val == NULL)
+			if (res->attDescs[i].cekid)
+			{
+				/* encrypted column */
+#ifdef USE_SSL
+				PGCEK	   *cek = NULL;
+
+				if (!isbinary)
+				{
+					*errmsgp = libpq_gettext("encrypted column was not sent in binary format");
+					goto fail;
+				}
+
+				for (int j = 0; j < conn->nceks; j++)
+				{
+					if (conn->ceks[j].cekid == res->attDescs[i].cekid)
+					{
+						cek = &conn->ceks[j];
+						break;
+					}
+				}
+				if (!cek)
+				{
+					*errmsgp = libpq_gettext("column encryption key not found");
+					goto fail;
+				}
+
+				val = (char *) decrypt_value(res, cek, res->attDescs[i].cekalg,
+											 (const unsigned char *) columns[i].value, clen, errmsgp);
+				if (val == NULL)
+					goto fail;
+#else
+				*errmsgp = libpq_gettext("column encryption not supported by this build");
 				goto fail;
+#endif
+			}
+			else
+			{
+				val = (char *) pqResultAlloc(res, clen + 1, isbinary);
+				if (val == NULL)
+					goto fail;
 
-			/* copy and zero-terminate the data (even if it's binary) */
-			memcpy(val, columns[i].value, clen);
-			val[clen] = '\0';
+				/* 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;
@@ -1500,6 +1949,8 @@ PQsendQueryParams(PGconn *conn,
 				  const int *paramFormats,
 				  int resultFormat)
 {
+	PGresult   *paramDesc = NULL;
+
 	if (!PQsendQueryStart(conn, true))
 		return 0;
 
@@ -1516,6 +1967,37 @@ PQsendQueryParams(PGconn *conn,
 		return 0;
 	}
 
+	if (conn->column_encryption_enabled)
+	{
+		PGresult   *res;
+		bool		error;
+
+		if (conn->pipelineStatus != PQ_PIPELINE_OFF)
+		{
+			libpq_append_conn_error(conn, "synchronous command execution functions are not allowed in pipeline mode");
+			return 0;
+		}
+
+		if (!PQsendPrepare(conn, "", command, nParams, paramTypes))
+			return 0;
+		error = false;
+		while ((res = PQgetResult(conn)) != NULL)
+		{
+			if (PQresultStatus(res) != PGRES_COMMAND_OK)
+				error = true;
+			PQclear(res);
+		}
+		if (error)
+			return 0;
+
+		paramDesc = PQdescribePrepared(conn, "");
+		if (PQresultStatus(paramDesc) != PGRES_COMMAND_OK)
+			return 0;
+
+		command = NULL;
+		paramTypes = NULL;
+	}
+
 	return PQsendQueryGuts(conn,
 						   command,
 						   "",	/* use unnamed statement */
@@ -1524,7 +2006,8 @@ PQsendQueryParams(PGconn *conn,
 						   paramValues,
 						   paramLengths,
 						   paramFormats,
-						   resultFormat);
+						   resultFormat,
+						   paramDesc);
 }
 
 /*
@@ -1639,6 +2122,24 @@ PQsendQueryPrepared(PGconn *conn,
 					const int *paramLengths,
 					const int *paramFormats,
 					int resultFormat)
+{
+	return PQsendQueryPreparedDescribed(conn, stmtName, nParams, paramValues, paramLengths, paramFormats, resultFormat, NULL);
+}
+
+/*
+ * PQsendQueryPreparedDescribed
+ *		Like PQsendQueryPrepared, but with additional argument to pass
+ *		parameter descriptions, for column encryption.
+ */
+int
+PQsendQueryPreparedDescribed(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;
@@ -1664,7 +2165,8 @@ PQsendQueryPrepared(PGconn *conn,
 						   paramValues,
 						   paramLengths,
 						   paramFormats,
-						   resultFormat);
+						   resultFormat,
+						   paramDesc);
 }
 
 /*
@@ -1762,7 +2264,8 @@ PQsendQueryGuts(PGconn *conn,
 				const char *const *paramValues,
 				const int *paramLengths,
 				const int *paramFormats,
-				int resultFormat)
+				int resultFormat,
+				PGresult *paramDesc)
 {
 	int			i;
 	PGcmdQueueEntry *entry;
@@ -1810,13 +2313,47 @@ 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;
+
+			/* Check force column encryption */
+			if (format & 0x10)
+			{
+				if (!(paramDesc &&
+					  paramDesc->paramDescs &&
+					  paramDesc->paramDescs[i].cekid))
+				{
+					libpq_append_conn_error(conn, "parameter with forced encryption is not to be encrypted");
+					goto sendFailed;
+				}
+			}
+			format &= ~0x10;
+
+			if (paramDesc && paramDesc->paramDescs)
+			{
+				PGresParamDesc *pd = &paramDesc->paramDescs[i];
+
+				if (pd->cekid)
+				{
+					if (format != 0)
+					{
+						libpq_append_conn_error(conn, "format must be text for encrypted parameter");
+						goto sendFailed;
+					}
+					/* Send encrypted value in binary */
+					format = 1;
+					/* And mark it as encrypted */
+					format |= 0x10;
+				}
+			}
+
+			if (pqPutInt(format, 2, conn) < 0)
 				goto sendFailed;
 		}
 	}
@@ -1835,8 +2372,9 @@ PQsendQueryGuts(PGconn *conn,
 		if (paramValues && paramValues[i])
 		{
 			int			nbytes;
+			const char *paramValue;
 
-			if (paramFormats && paramFormats[i] != 0)
+			if (paramFormats && (paramFormats[i] & 0x01) != 0)
 			{
 				/* binary parameter */
 				if (paramLengths)
@@ -1852,9 +2390,53 @@ PQsendQueryGuts(PGconn *conn,
 				/* text parameter, do not use paramLengths */
 				nbytes = strlen(paramValues[i]);
 			}
+
+			paramValue = paramValues[i];
+
+			if (paramDesc && paramDesc->paramDescs && paramDesc->paramDescs[i].cekid)
+			{
+				/* encrypted column */
+#ifdef USE_SSL
+				bool		enc_det = (paramDesc->paramDescs[i].flags & 0x01) != 0;
+				PGCEK	   *cek = NULL;
+				char	   *enc_paramValue;
+				int			enc_nbytes = nbytes;
+
+				for (int j = 0; j < conn->nceks; j++)
+				{
+					if (conn->ceks[j].cekid == paramDesc->paramDescs[i].cekid)
+					{
+						cek = &conn->ceks[j];
+						break;
+					}
+				}
+				if (!cek)
+				{
+					libpq_append_conn_error(conn, "column encryption key not found");
+					goto sendFailed;
+				}
+
+				enc_paramValue = (char *) encrypt_value(conn, cek, paramDesc->paramDescs[i].cekalg,
+														(const unsigned char *) paramValue, &enc_nbytes, enc_det);
+				if (!enc_paramValue)
+					goto sendFailed;
+
+				if (pqPutInt(enc_nbytes, 4, conn) < 0 ||
+					pqPutnchar(enc_paramValue, enc_nbytes, conn) < 0)
+					goto sendFailed;
+
+				free(enc_paramValue);
+#else
+				libpq_append_conn_error(conn, "column encryption not supported by this build");
+				goto sendFailed;
+#endif
+			}
+			else
+			{
 			if (pqPutInt(nbytes, 4, conn) < 0 ||
-				pqPutnchar(paramValues[i], nbytes, conn) < 0)
+				pqPutnchar(paramValue, nbytes, conn) < 0)
 				goto sendFailed;
+			}
 		}
 		else
 		{
@@ -2290,12 +2872,31 @@ PQexecPrepared(PGconn *conn,
 			   const int *paramLengths,
 			   const int *paramFormats,
 			   int resultFormat)
+{
+	return PQexecPreparedDescribed(conn, stmtName, nParams, paramValues, paramLengths, paramFormats, resultFormat, NULL);
+}
+
+/*
+ * PQexecPreparedDescribed
+ *		Like PQexecPrepared, but with additional argument to pass parameter
+ *		descriptions, for column encryption.
+ */
+PGresult *
+PQexecPreparedDescribed(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,
-							 nParams, paramValues, paramLengths,
-							 paramFormats, resultFormat))
+	if (!PQsendQueryPreparedDescribed(conn, stmtName,
+									  nParams, paramValues, paramLengths,
+									  paramFormats,
+									  resultFormat, paramDesc))
 		return NULL;
 	return PQexecFinish(conn);
 }
@@ -3539,7 +4140,17 @@ PQfformat(const PGresult *res, int field_num)
 	if (!check_field_number(res, field_num))
 		return 0;
 	if (res->attDescs)
+	{
+		/*
+		 * An encrypted column is always presented to the application in text
+		 * format.  The .format field applies to the ciphertext, which might
+		 * be in either format, but the plaintext inside is always in text
+		 * format.
+		 */
+		if (res->attDescs[field_num].cekid != 0)
+			return 0;
 		return res->attDescs[field_num].format;
+	}
 	else
 		return 0;
 }
@@ -3577,6 +4188,17 @@ PQfmod(const PGresult *res, int field_num)
 		return 0;
 }
 
+int
+PQfisencrypted(const PGresult *res, int field_num)
+{
+	if (!check_field_number(res, field_num))
+		return false;
+	if (res->attDescs)
+		return (res->attDescs[field_num].cekid != 0);
+	else
+		return false;
+}
+
 char *
 PQcmdStatus(PGresult *res)
 {
@@ -3762,6 +4384,17 @@ PQparamtype(const PGresult *res, int param_num)
 		return InvalidOid;
 }
 
+int
+PQparamisencrypted(const PGresult *res, int param_num)
+{
+	if (!check_param_number(res, param_num))
+		return false;
+	if (res->paramDescs)
+		return (res->paramDescs[param_num].cekid != 0);
+	else
+		return false;
+}
+
 
 /* PQsetnonblocking:
  *	sets the PGconn's database connection non-blocking if the arg is true
diff --git a/src/interfaces/libpq/fe-protocol3.c b/src/interfaces/libpq/fe-protocol3.c
index 8ab6a88416..3e3d8674be 100644
--- a/src/interfaces/libpq/fe-protocol3.c
+++ b/src/interfaces/libpq/fe-protocol3.c
@@ -43,6 +43,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);
@@ -297,6 +299,12 @@ pqParseInput3(PGconn *conn)
 					if (pqGetInt(&(conn->be_key), 4, conn))
 						return;
 					break;
+				case 'y':		/* Column Master Key */
+					getColumnMasterKey(conn);
+					break;
+				case 'Y':		/* Column Encryption Key */
+					getColumnEncryptionKey(conn);
+					break;
 				case 'T':		/* Row Description */
 					if (conn->error_result ||
 						(conn->result != NULL &&
@@ -547,6 +555,8 @@ getRowDescriptions(PGconn *conn, int msgLength)
 		int			typlen;
 		int			atttypmod;
 		int			format;
+		int			cekid;
+		int			cekalg;
 
 		if (pqGets(&conn->workBuffer, conn) ||
 			pqGetInt(&tableid, 4, conn) ||
@@ -561,6 +571,21 @@ getRowDescriptions(PGconn *conn, int msgLength)
 			goto advance_and_error;
 		}
 
+		if (conn->column_encryption_enabled)
+		{
+			if (pqGetInt(&cekid, 4, conn) ||
+				pqGetInt(&cekalg, 4, conn))
+			{
+				errmsg = libpq_gettext("insufficient data in \"T\" message");
+				goto advance_and_error;
+			}
+		}
+		else
+		{
+			cekid = 0;
+			cekalg = 0;
+		}
+
 		/*
 		 * Since pqGetInt treats 2-byte integers as unsigned, we need to
 		 * coerce these results to signed form.
@@ -582,8 +607,10 @@ getRowDescriptions(PGconn *conn, int msgLength)
 		result->attDescs[i].typid = typid;
 		result->attDescs[i].typlen = typlen;
 		result->attDescs[i].atttypmod = atttypmod;
+		result->attDescs[i].cekid = cekid;
+		result->attDescs[i].cekalg = cekalg;
 
-		if (format != 1)
+		if ((format & 0x0F) != 1)
 			result->binary = 0;
 	}
 
@@ -685,10 +712,31 @@ getParamDescriptions(PGconn *conn, int msgLength)
 	for (i = 0; i < nparams; i++)
 	{
 		int			typid;
+		int			cekid;
+		int			cekalg;
+		int			flags;
 
 		if (pqGetInt(&typid, 4, conn))
 			goto not_enough_data;
+		if (conn->column_encryption_enabled)
+		{
+			if (pqGetInt(&cekid, 4, conn))
+				goto not_enough_data;
+			if (pqGetInt(&cekalg, 4, conn))
+				goto not_enough_data;
+			if (pqGetInt(&flags, 2, conn))
+				goto not_enough_data;
+		}
+		else
+		{
+			cekid = 0;
+			cekalg = 0;
+			flags = 0;
+		}
 		result->paramDescs[i].typid = typid;
+		result->paramDescs[i].cekid = cekid;
+		result->paramDescs[i].cekalg = cekalg;
+		result->paramDescs[i].flags = flags;
 	}
 
 	/* Success! */
@@ -1468,6 +1516,92 @@ getParameterStatus(PGconn *conn)
 	return 0;
 }
 
+/*
+ * Attempt to read a ColumnMasterKey message.
+ * Entry: 'y' message type and length have already been consumed.
+ * Exit: returns 0 if successfully consumed message.
+ *		 returns EOF if not enough data.
+ */
+static int
+getColumnMasterKey(PGconn *conn)
+{
+	int			keyid;
+	char	   *keyname;
+	char	   *keyrealm;
+	int			ret;
+
+	/* Get the key ID */
+	if (pqGetInt(&keyid, 4, conn) != 0)
+		return EOF;
+	/* Get the key name */
+	if (pqGets(&conn->workBuffer, conn) != 0)
+		return EOF;
+	keyname = strdup(conn->workBuffer.data);
+	if (!keyname)
+		return EOF;
+	/* Get the key realm */
+	if (pqGets(&conn->workBuffer, conn) != 0)
+		return EOF;
+	keyrealm = strdup(conn->workBuffer.data);
+	if (!keyrealm)
+		return EOF;
+	/* And save it */
+	ret = pqSaveColumnMasterKey(conn, keyid, keyname, keyrealm);
+	if (ret != 0)
+		pqSaveErrorResult(conn);
+
+	free(keyname);
+	free(keyrealm);
+
+	return ret;
+}
+
+/*
+ * Attempt to read a ColumnEncryptionKey message.
+ * Entry: 'Y' message type and length have already been consumed.
+ * Exit: returns 0 if successfully consumed message.
+ *		 returns EOF if not enough data.
+ */
+static int
+getColumnEncryptionKey(PGconn *conn)
+{
+	int			keyid;
+	int			cmkid;
+	int			cmkalg;
+	char	   *buf;
+	int			vallen;
+	int			ret;
+
+	/* Get the key ID */
+	if (pqGetInt(&keyid, 4, conn) != 0)
+		return EOF;
+	/* Get the CMK ID */
+	if (pqGetInt(&cmkid, 4, conn) != 0)
+		return EOF;
+	/* Get the CMK algorithm */
+	if (pqGetInt(&cmkalg, 4, 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 (!buf)
+		return EOF;
+	if (pqGetnchar(buf, vallen, conn) != 0)
+	{
+		free(buf);
+		return EOF;
+	}
+	/* And save it */
+	ret = pqSaveColumnEncryptionKey(conn, keyid, cmkid, cmkalg, (unsigned char *) buf, vallen);
+	if (ret != 0)
+		pqSaveErrorResult(conn);
+
+	free(buf);
+
+	return ret;
+}
 
 /*
  * Attempt to read a Notify response message.
@@ -2286,6 +2420,9 @@ build_startup_packet(const PGconn *conn, char *packet,
 	if (conn->client_encoding_initial && conn->client_encoding_initial[0])
 		ADD_STARTUP_OPTION("client_encoding", conn->client_encoding_initial);
 
+	if (conn->column_encryption_enabled)
+		ADD_STARTUP_OPTION("_pq_.column_encryption", "1");
+
 	/* Add any environment-driven GUC settings needed */
 	for (next_eo = options; next_eo->envName; next_eo++)
 	{
diff --git a/src/interfaces/libpq/fe-trace.c b/src/interfaces/libpq/fe-trace.c
index abaab6a073..77791e8349 100644
--- a/src/interfaces/libpq/fe-trace.c
+++ b/src/interfaces/libpq/fe-trace.c
@@ -450,7 +450,8 @@ pqTraceOutputS(FILE *f, const char *message, int *cursor)
 
 /* ParameterDescription */
 static void
-pqTraceOutputt(FILE *f, const char *message, int *cursor, bool regress)
+pqTraceOutputt(FILE *f, const char *message, int *cursor, bool regress,
+			   bool column_encryption_enabled)
 {
 	int			nfields;
 
@@ -458,12 +459,21 @@ pqTraceOutputt(FILE *f, const char *message, int *cursor, bool regress)
 	nfields = pqTraceOutputInt16(f, message, cursor);
 
 	for (int i = 0; i < nfields; i++)
+	{
 		pqTraceOutputInt32(f, message, cursor, regress);
+		if (column_encryption_enabled)
+		{
+			pqTraceOutputInt32(f, message, cursor, regress);
+			pqTraceOutputInt32(f, message, cursor, false);
+			pqTraceOutputInt16(f, message, cursor);
+		}
+	}
 }
 
 /* RowDescription */
 static void
-pqTraceOutputT(FILE *f, const char *message, int *cursor, bool regress)
+pqTraceOutputT(FILE *f, const char *message, int *cursor, bool regress,
+			   bool column_encryption_enabled)
 {
 	int			nfields;
 
@@ -479,6 +489,11 @@ pqTraceOutputT(FILE *f, const char *message, int *cursor, bool regress)
 		pqTraceOutputInt16(f, message, cursor);
 		pqTraceOutputInt32(f, message, cursor, false);
 		pqTraceOutputInt16(f, message, cursor);
+		if (column_encryption_enabled)
+		{
+			pqTraceOutputInt32(f, message, cursor, regress);
+			pqTraceOutputInt32(f, message, cursor, false);
+		}
 	}
 }
 
@@ -514,6 +529,30 @@ pqTraceOutputW(FILE *f, const char *message, int *cursor, int length)
 		pqTraceOutputInt16(f, message, cursor);
 }
 
+/* ColumnMasterKey */
+static void
+pqTraceOutputy(FILE *f, const char *message, int *cursor, bool regress)
+{
+	fprintf(f, "ColumnMasterKey\t");
+	pqTraceOutputInt32(f, message, cursor, regress);
+	pqTraceOutputString(f, message, cursor, false);
+	pqTraceOutputString(f, message, cursor, false);
+}
+
+/* ColumnEncryptionKey */
+static void
+pqTraceOutputY(FILE *f, const char *message, int *cursor, bool regress)
+{
+	int			len;
+
+	fprintf(f, "ColumnEncryptionKey\t");
+	pqTraceOutputInt32(f, message, cursor, regress);
+	pqTraceOutputInt32(f, message, cursor, regress);
+	pqTraceOutputInt32(f, message, cursor, false);
+	len = pqTraceOutputInt32(f, message, cursor, false);
+	pqTraceOutputNchar(f, len, message, cursor);
+}
+
 /* ReadyForQuery */
 static void
 pqTraceOutputZ(FILE *f, const char *message, int *cursor)
@@ -647,10 +686,12 @@ pqTraceOutputMessage(PGconn *conn, const char *message, bool toServer)
 				fprintf(conn->Pfdebug, "Sync"); /* no message content */
 			break;
 		case 't':				/* Parameter Description */
-			pqTraceOutputt(conn->Pfdebug, message, &logCursor, regress);
+			pqTraceOutputt(conn->Pfdebug, message, &logCursor, regress,
+						   conn->column_encryption_enabled);
 			break;
 		case 'T':				/* Row Description */
-			pqTraceOutputT(conn->Pfdebug, message, &logCursor, regress);
+			pqTraceOutputT(conn->Pfdebug, message, &logCursor, regress,
+						   conn->column_encryption_enabled);
 			break;
 		case 'v':				/* Negotiate Protocol Version */
 			pqTraceOutputv(conn->Pfdebug, message, &logCursor);
@@ -665,6 +706,12 @@ pqTraceOutputMessage(PGconn *conn, const char *message, bool toServer)
 			fprintf(conn->Pfdebug, "Terminate");
 			/* No message content */
 			break;
+		case 'y':
+			pqTraceOutputy(conn->Pfdebug, message, &logCursor, regress);
+			break;
+		case 'Y':
+			pqTraceOutputY(conn->Pfdebug, message, &logCursor, regress);
+			break;
 		case 'Z':				/* Ready For Query */
 			pqTraceOutputZ(conn->Pfdebug, message, &logCursor);
 			break;
diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h
index f3d9220496..cf339a3e53 100644
--- a/src/interfaces/libpq/libpq-fe.h
+++ b/src/interfaces/libpq/libpq-fe.h
@@ -267,6 +267,8 @@ typedef struct pgresAttDesc
 	Oid			typid;			/* type id */
 	int			typlen;			/* type size */
 	int			atttypmod;		/* type-specific modifier info */
+	Oid			cekid;
+	int			cekalg;
 } PGresAttDesc;
 
 /* ----------------
@@ -438,6 +440,14 @@ extern PGresult *PQexecPrepared(PGconn *conn,
 								const int *paramLengths,
 								const int *paramFormats,
 								int resultFormat);
+extern PGresult *PQexecPreparedDescribed(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
@@ -461,6 +471,14 @@ extern int	PQsendQueryPrepared(PGconn *conn,
 								const int *paramLengths,
 								const int *paramFormats,
 								int resultFormat);
+extern int	PQsendQueryPreparedDescribed(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);
 
@@ -531,6 +549,7 @@ extern int	PQfformat(const PGresult *res, int field_num);
 extern Oid	PQftype(const PGresult *res, int field_num);
 extern int	PQfsize(const PGresult *res, int field_num);
 extern int	PQfmod(const PGresult *res, int field_num);
+extern int	PQfisencrypted(const PGresult *res, int field_num);
 extern char *PQcmdStatus(PGresult *res);
 extern char *PQoidStatus(const PGresult *res);	/* old and ugly */
 extern Oid	PQoidValue(const PGresult *res);	/* new and improved */
@@ -540,6 +559,7 @@ extern int	PQgetlength(const PGresult *res, int tup_num, int field_num);
 extern int	PQgetisnull(const PGresult *res, int tup_num, int field_num);
 extern int	PQnparams(const PGresult *res);
 extern Oid	PQparamtype(const PGresult *res, int param_num);
+extern int	PQparamisencrypted(const PGresult *res, int param_num);
 
 /* Describe prepared statements and portals */
 extern PGresult *PQdescribePrepared(PGconn *conn, const char *stmt);
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index d94b648ea5..3dfedc0f9b 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -112,6 +112,9 @@ union pgresult_data
 typedef struct pgresParamDesc
 {
 	Oid			typid;			/* type id */
+	Oid			cekid;
+	int			cekalg;
+	int			flags;
 } PGresParamDesc;
 
 /*
@@ -343,6 +346,26 @@ typedef struct pg_conn_host
 								 * found in password file. */
 } pg_conn_host;
 
+/*
+ * Column encryption support data
+ */
+
+/* column master key */
+typedef struct pg_cmk
+{
+	Oid			cmkid;
+	char	   *cmkname;
+	char	   *cmkrealm;
+} PGCMK;
+
+/* column encryption key */
+typedef struct pg_cek
+{
+	Oid			cekid;
+	unsigned char *cekdata;		/* (decrypted) */
+	size_t		cekdatalen;
+} PGCEK;
+
 /*
  * PGconn stores all the state data associated with a single connection
  * to a backend.
@@ -396,6 +419,8 @@ struct pg_conn
 	char	   *ssl_min_protocol_version;	/* minimum TLS protocol version */
 	char	   *ssl_max_protocol_version;	/* maximum TLS protocol version */
 	char	   *target_session_attrs;	/* desired session properties */
+	char	   *cmklookup;		/* CMK lookup specification */
+	char	   *column_encryption_setting;	/* column_encryption connection parameter (0 or 1) */
 
 	/* Optional file to write trace info to */
 	FILE	   *Pfdebug;
@@ -477,6 +502,13 @@ struct pg_conn
 	PGVerbosity verbosity;		/* error/notice message verbosity */
 	PGContextVisibility show_context;	/* whether to show CONTEXT field */
 	PGlobjfuncs *lobjfuncs;		/* private state for large-object access fns */
+	bool		column_encryption_enabled;	/* parsed version of column_encryption_setting */
+
+	/* Column encryption support data */
+	int			ncmks;
+	PGCMK	   *cmks;
+	int			nceks;
+	PGCEK	   *ceks;
 
 	/* Buffer for data received from backend and not yet processed */
 	char	   *inBuffer;		/* currently allocated buffer */
@@ -673,6 +705,10 @@ extern void pqSaveMessageField(PGresult *res, char code,
 							   const char *value);
 extern void pqSaveParameterStatus(PGconn *conn, const char *name,
 								  const char *value);
+extern int	pqSaveColumnMasterKey(PGconn *conn, int keyid, const char *keyname,
+								  const char *keyrealm);
+extern int	pqSaveColumnEncryptionKey(PGconn *conn, int keyid, int cmkid, int cmkalg,
+									  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/interfaces/libpq/meson.build b/src/interfaces/libpq/meson.build
index 573fd9b6ea..a6e6b2ada9 100644
--- a/src/interfaces/libpq/meson.build
+++ b/src/interfaces/libpq/meson.build
@@ -29,6 +29,7 @@ endif
 
 if ssl.found()
   libpq_sources += files('fe-secure-common.c')
+  libpq_sources += files('fe-encrypt-openssl.c')
   libpq_sources += files('fe-secure-openssl.c')
 endif
 
@@ -116,6 +117,7 @@ tests += {
     'tests': [
       't/001_uri.pl',
       't/002_api.pl',
+      't/003_encrypt.pl',
     ],
     'env': {'with_ssl': get_option('ssl')},
   },
diff --git a/src/interfaces/libpq/nls.mk b/src/interfaces/libpq/nls.mk
index 4df544ecef..0c36aa5f32 100644
--- a/src/interfaces/libpq/nls.mk
+++ b/src/interfaces/libpq/nls.mk
@@ -1,6 +1,6 @@
 # src/interfaces/libpq/nls.mk
 CATALOG_NAME     = libpq
-GETTEXT_FILES    = fe-auth.c fe-auth-scram.c fe-connect.c fe-exec.c fe-gssapi-common.c fe-lobj.c fe-misc.c fe-protocol3.c fe-secure.c fe-secure-common.c fe-secure-gssapi.c fe-secure-openssl.c win32.c ../../port/thread.c
+GETTEXT_FILES    = fe-auth.c fe-auth-scram.c fe-connect.c fe-encrypt-openssl.c fe-exec.c fe-gssapi-common.c fe-lobj.c fe-misc.c fe-protocol3.c fe-secure.c fe-secure-common.c fe-secure-gssapi.c fe-secure-openssl.c win32.c ../../port/thread.c
 GETTEXT_TRIGGERS = libpq_append_conn_error:2 \
                    libpq_append_error:2 \
                    libpq_gettext pqInternalNotice:2
diff --git a/src/interfaces/libpq/t/003_encrypt.pl b/src/interfaces/libpq/t/003_encrypt.pl
new file mode 100644
index 0000000000..94a7441037
--- /dev/null
+++ b/src/interfaces/libpq/t/003_encrypt.pl
@@ -0,0 +1,70 @@
+# Copyright (c) 2023, PostgreSQL Global Development Group
+use strict;
+use warnings;
+
+use PostgreSQL::Test::Utils;
+use Test::More;
+
+plan skip_all => 'OpenSSL not supported by this build' if $ENV{with_ssl} ne 'openssl';
+
+# test data from https://datatracker.ietf.org/doc/html/draft-mcgrew-aead-aes-cbc-hmac-sha2-05#section-5
+command_like([ 'libpq_test_encrypt' ],
+	qr{5.1
+C =
+1a f3 8c 2d c2 b9 6f fd d8 66 94 09 23 41 bc 04
+c8 0e df a3 2d df 39 d5 ef 00 c0 b4 68 83 42 79
+a2 e4 6a 1b 80 49 f7 92 f7 6b fe 54 b9 03 a9 c9
+a9 4a c9 b4 7a d2 65 5c 5f 10 f9 ae f7 14 27 e2
+fc 6f 9b 3f 39 9a 22 14 89 f1 63 62 c7 03 23 36
+09 d4 5a c6 98 64 e3 32 1c f8 29 35 ac 40 96 c8
+6e 13 33 14 c5 40 19 e8 ca 79 80 df a4 b9 cf 1b
+38 4c 48 6f 3a 54 c5 10 78 15 8e e5 d7 9d e5 9f
+bd 34 d8 48 b3 d6 95 50 a6 76 46 34 44 27 ad e5
+4b 88 51 ff b5 98 f7 f8 00 74 b9 47 3c 82 e2 db
+65 2c 3f a3 6b 0a 7c 5b 32 19 fa b3 a3 0b c1 c4
+5.2
+C =
+1a f3 8c 2d c2 b9 6f fd d8 66 94 09 23 41 bc 04
+ea 65 da 6b 59 e6 1e db 41 9b e6 2d 19 71 2a e5
+d3 03 ee b5 00 52 d0 df d6 69 7f 77 22 4c 8e db
+00 0d 27 9b dc 14 c1 07 26 54 bd 30 94 42 30 c6
+57 be d4 ca 0c 9f 4a 84 66 f2 2b 22 6d 17 46 21
+4b f8 cf c2 40 0a dd 9f 51 26 e4 79 66 3f c9 0b
+3b ed 78 7a 2f 0f fc bf 39 04 be 2a 64 1d 5c 21
+05 bf e5 91 ba e2 3b 1d 74 49 e5 32 ee f6 0a 9a
+c8 bb 6c 6b 01 d3 5d 49 78 7b cd 57 ef 48 49 27
+f2 80 ad c9 1a c0 c4 e7 9c 7b 11 ef c6 00 54 e3
+84 90 ac 0e 58 94 9b fe 51 87 5d 73 3f 93 ac 20
+75 16 80 39 cc c7 33 d7
+5.3
+C =
+1a f3 8c 2d c2 b9 6f fd d8 66 94 09 23 41 bc 04
+89 31 29 b0 f4 ee 9e b1 8d 75 ed a6 f2 aa a9 f3
+60 7c 98 c4 ba 04 44 d3 41 62 17 0d 89 61 88 4e
+58 f2 7d 4a 35 a5 e3 e3 23 4a a9 94 04 f3 27 f5
+c2 d7 8e 98 6e 57 49 85 8b 88 bc dd c2 ba 05 21
+8f 19 51 12 d6 ad 48 fa 3b 1e 89 aa 7f 20 d5 96
+68 2f 10 b3 64 8d 3b b0 c9 83 c3 18 5f 59 e3 6d
+28 f6 47 c1 c1 39 88 de 8e a0 d8 21 19 8c 15 09
+77 e2 8c a7 68 08 0b c7 8c 35 fa ed 69 d8 c0 b7
+d9 f5 06 23 21 98 a4 89 a1 a6 ae 03 a3 19 fb 30
+dd 13 1d 05 ab 34 67 dd 05 6f 8e 88 2b ad 70 63
+7f 1e 9a 54 1d 9c 23 e7
+5.4
+C =
+1a f3 8c 2d c2 b9 6f fd d8 66 94 09 23 41 bc 04
+4a ff aa ad b7 8c 31 c5 da 4b 1b 59 0d 10 ff bd
+3d d8 d5 d3 02 42 35 26 91 2d a0 37 ec bc c7 bd
+82 2c 30 1d d6 7c 37 3b cc b5 84 ad 3e 92 79 c2
+e6 d1 2a 13 74 b7 7f 07 75 53 df 82 94 10 44 6b
+36 eb d9 70 66 29 6a e6 42 7e a7 5c 2e 08 46 a1
+1a 09 cc f5 37 0d c8 0b fe cb ad 28 c7 3f 09 b3
+a3 b7 5e 66 2a 25 94 41 0a e4 96 b2 e2 e6 60 9e
+31 e6 e0 2c c8 37 f0 53 d2 1f 37 ff 4f 51 95 0b
+be 26 38 d0 9d d7 a4 93 09 30 80 6d 07 03 b1 f6
+4d d3 b4 c0 88 a7 f4 5c 21 68 39 64 5b 20 12 bf
+2e 62 69 a8 c5 6a 81 6d bc 1b 26 77 61 95 5b c5
+},
+	'AEAD_AES_*_CBC_HMAC_SHA_* test cases');
+
+done_testing();
diff --git a/src/interfaces/libpq/test/.gitignore b/src/interfaces/libpq/test/.gitignore
index 6ba78adb67..1846594ec5 100644
--- a/src/interfaces/libpq/test/.gitignore
+++ b/src/interfaces/libpq/test/.gitignore
@@ -1,2 +1,3 @@
+/libpq_test_encrypt
 /libpq_testclient
 /libpq_uri_regress
diff --git a/src/interfaces/libpq/test/Makefile b/src/interfaces/libpq/test/Makefile
index 75ac08f943..b1ebab90d4 100644
--- a/src/interfaces/libpq/test/Makefile
+++ b/src/interfaces/libpq/test/Makefile
@@ -15,10 +15,17 @@ override CPPFLAGS := -I$(libpq_srcdir) $(CPPFLAGS)
 LDFLAGS_INTERNAL += $(libpq_pgport)
 
 PROGS = libpq_testclient libpq_uri_regress
+ifeq ($(with_ssl),openssl)
+PROGS += libpq_test_encrypt
+endif
+
 
 all: $(PROGS)
 
 $(PROGS): $(WIN32RES)
 
+libpq_test_encrypt: ../fe-encrypt-openssl.c
+	$(CC) $(CPPFLAGS) -DTEST_ENCRYPT $(CFLAGS) $^ $(LDFLAGS) $(LDFLAGS_EX) $(LIBS) -o $@$(X)
+
 clean distclean maintainer-clean:
 	rm -f $(PROGS) *.o
diff --git a/src/interfaces/libpq/test/meson.build b/src/interfaces/libpq/test/meson.build
index b2a4b06fd2..87d2808b52 100644
--- a/src/interfaces/libpq/test/meson.build
+++ b/src/interfaces/libpq/test/meson.build
@@ -36,3 +36,26 @@ executable('libpq_testclient',
     'install': false,
   }
 )
+
+
+libpq_test_encrypt_sources = files(
+  '../fe-encrypt-openssl.c',
+)
+
+if host_system == 'windows'
+  libpq_test_encrypt_sources += rc_bin_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'libpq_test_encrypt',
+    '--FILEDESC', 'libpq test program',])
+endif
+
+if ssl.found()
+  executable('libpq_test_encrypt',
+    libpq_test_encrypt_sources,
+    include_directories: include_directories('../../../port'),
+    dependencies: [frontend_code, libpq, ssl],
+    c_args: ['-DTEST_ENCRYPT'],
+    kwargs: default_bin_args + {
+      'install': false,
+    }
+  )
+endif
diff --git a/src/test/Makefile b/src/test/Makefile
index dbd3192874..c8ba170503 100644
--- a/src/test/Makefile
+++ b/src/test/Makefile
@@ -24,7 +24,7 @@ ifeq ($(with_ldap),yes)
 SUBDIRS += ldap
 endif
 ifeq ($(with_ssl),openssl)
-SUBDIRS += ssl
+SUBDIRS += column_encryption ssl
 endif
 
 # Test suites that are not safe by default but can be run if selected
@@ -36,7 +36,7 @@ export PG_TEST_EXTRA
 # 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 icu ldap ssl)
+ALWAYS_SUBDIRS = $(filter-out $(SUBDIRS),examples kerberos icu 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..d5ead874e5
--- /dev/null
+++ b/src/test/column_encryption/Makefile
@@ -0,0 +1,31 @@
+#-------------------------------------------------------------------------
+#
+# Makefile for src/test/column_encryption
+#
+# Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
+# Portions Copyright (c) 1994, Regents of the University of California
+#
+# src/test/column_encryption/Makefile
+#
+#-------------------------------------------------------------------------
+
+subdir = src/test/column_encryption
+top_builddir = ../../..
+include $(top_builddir)/src/Makefile.global
+
+export OPENSSL PERL
+
+override CPPFLAGS := -I$(libpq_srcdir) $(CPPFLAGS)
+LDFLAGS_INTERNAL += -L$(top_builddir)/src/fe_utils -lpgfeutils $(libpq_pgport)
+
+all: test_client
+
+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/meson.build b/src/test/column_encryption/meson.build
new file mode 100644
index 0000000000..47f88c41ce
--- /dev/null
+++ b/src/test/column_encryption/meson.build
@@ -0,0 +1,24 @@
+column_encryption_test_client = executable('test_client',
+  files('test_client.c'),
+  dependencies: [frontend_code, libpq],
+  kwargs: default_bin_args + {
+    'install': false,
+  },
+)
+testprep_targets += column_encryption_test_client
+
+tests += {
+  'name': 'column_encryption',
+  'sd': meson.current_source_dir(),
+  'bd': meson.current_build_dir(),
+  'tap': {
+    'tests': [
+      't/001_column_encryption.pl',
+      't/002_cmk_rotation.pl',
+    ],
+    'env': {
+      'OPENSSL': openssl.path(),
+      'PERL': perl.path(),
+    },
+  },
+}
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..c56af737ac
--- /dev/null
+++ b/src/test/column_encryption/t/001_column_encryption.pl
@@ -0,0 +1,255 @@
+# Copyright (c) 2021-2023, PostgreSQL Global Development Group
+
+use strict;
+use warnings;
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More;
+
+my $openssl = $ENV{OPENSSL};
+my $perl = $ENV{PERL};
+
+# Can be changed manually for testing other algorithms.  Note that
+# RSAES_OAEP_SHA_256 requires OpenSSL 1.1.0.
+my $cmkalg = 'RSAES_OAEP_SHA_1';
+
+my $node = PostgreSQL::Test::Cluster->new('node');
+$node->init;
+$node->start;
+
+
+sub create_cmk
+{
+	my ($cmkname) = @_;
+	my $cmkfilename = "${PostgreSQL::Test::Utils::tmp_check}/${cmkname}.pem";
+	system_or_bail $openssl, 'genpkey', '-algorithm', 'rsa', '-out', $cmkfilename;
+	$node->safe_psql('postgres', qq{CREATE COLUMN MASTER KEY ${cmkname}});
+	return $cmkfilename;
+}
+
+sub create_cek
+{
+	my ($cekname, $bytes, $cmkname, $cmkfilename) = @_;
+
+	my $digest = $cmkalg;
+	$digest =~ s/.*(?=SHA)//;
+	$digest =~ s/_//g;
+
+	# generate random bytes
+	system_or_bail $openssl, 'rand', '-out', "${PostgreSQL::Test::Utils::tmp_check}/${cekname}.bin", $bytes;
+
+	# encrypt CEK using CMK
+	my @cmd = (
+		$openssl, 'pkeyutl', '-encrypt',
+		'-inkey', $cmkfilename,
+		'-pkeyopt', 'rsa_padding_mode:oaep',
+		'-in', "${PostgreSQL::Test::Utils::tmp_check}/${cekname}.bin",
+		'-out', "${PostgreSQL::Test::Utils::tmp_check}/${cekname}.bin.enc"
+	);
+	if ($digest ne 'SHA1')
+	{
+		# These options require OpenSSL >=1.1.0, so if the digest is
+		# SHA1, which is the default, omit the options.
+		push @cmd,
+		  '-pkeyopt', "rsa_mgf1_md:$digest",
+		  '-pkeyopt', "rsa_oaep_md:$digest";
+	}
+	system_or_bail @cmd;
+
+	my $cekenchex = unpack('H*', slurp_file "${PostgreSQL::Test::Utils::tmp_check}/${cekname}.bin.enc");
+
+	# create CEK in database
+	$node->safe_psql('postgres', qq{CREATE COLUMN ENCRYPTION KEY ${cekname} WITH VALUES (column_master_key = ${cmkname}, algorithm = '${cmkalg}', encrypted_value = '\\x${cekenchex}');});
+
+	return;
+}
+
+
+my $cmk1filename = create_cmk('cmk1');
+my $cmk2filename = create_cmk('cmk2');
+create_cek('cek1', 48, 'cmk1', $cmk1filename);
+create_cek('cek2', 72, 'cmk2', $cmk2filename);
+
+$ENV{PGCOLUMNENCRYPTION} = 'on';
+$ENV{PGCMKLOOKUP} = '*=file:' . ${PostgreSQL::Test::Utils::tmp_check} . '/%k.pem';
+
+
+$node->safe_psql('postgres', qq{
+CREATE TABLE tbl1 (
+    a int,
+    b text ENCRYPTED WITH (column_encryption_key = cek1),
+    c smallint ENCRYPTED WITH (column_encryption_key = cek1)
+);
+});
+
+$node->safe_psql('postgres', q{
+INSERT INTO tbl1 (a, b, c) VALUES (1, $1, $2) \bind 'val1' 11 \g
+INSERT INTO tbl1 (a, b, c) VALUES (2, $1, $2) \bind 'val2' 22 \g
+});
+
+# Expected ciphertext length is 2 blocks of AES output (2 * 16) plus
+# half SHA-256 output (16) in hex encoding: (2 * 16 + 16) * 2 = 96
+like($node->safe_psql('postgres', q{COPY (SELECT * FROM tbl1) TO STDOUT}),
+	qr/1\tencrypted\$[0-9a-f]{96}\tencrypted\$[0-9a-f]{96}\n2\tencrypted\$[0-9a-f]{96}\tencrypted\$[0-9a-f]{96}/,
+	'inserted data is encrypted');
+
+my $result;
+
+$result = $node->safe_psql('postgres', q{SELECT a, b, c FROM tbl1 \gdesc});
+is($result,
+	q(a|integer
+b|text
+c|smallint),
+	'query result description has original type');
+
+$result = $node->safe_psql('postgres', q{SELECT a, b, c FROM tbl1});
+is($result,
+	q(1|val1|11
+2|val2|22),
+	'decrypted query result');
+
+{
+	local $ENV{PGCMKLOOKUP} = '*=run:broken %k %p';
+	$result = $node->psql('postgres', q{SELECT a, b, c FROM tbl1});
+	isnt($result, 0, 'query fails with broken cmklookup run setting');
+}
+
+{
+	local $ENV{TESTWORKDIR} = ${PostgreSQL::Test::Utils::tmp_check};
+	local $ENV{PGCMKLOOKUP} = "*=run:$perl ./test_run_decrypt.pl %k %a %p";
+
+	my $stdout;
+	$result = $node->psql('postgres', q{SELECT a, b, c FROM tbl1}, stdout => \$stdout);
+	is($stdout,
+		q(1|val1|11
+2|val2|22),
+		'decrypted query result with cmklookup run');
+}
+
+
+$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, c FROM tbl1});
+is($result,
+	q(1|val1|11
+2|val2|22),
+	'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, c FROM tbl1});
+is($result,
+	q(1|val1|11
+2|val2|22
+3|val3|33),
+	'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\tencrypted\$[0-9a-f]{96}/,
+	'inserted data is encrypted');
+
+
+# Tests with binary format
+
+# Supplying a parameter in binary format when the parameter is to be
+# encrypted results in an error from libpq.
+$node->command_fails_like(['test_client', 'test3'],
+	qr/format must be text for encrypted parameter/,
+	'test client fails because to-be-encrypted parameter is in binary format');
+
+# Requesting a binary result set still causes any encrypted columns to
+# be returned as text from the libpq API.
+$node->command_like(['test_client', 'test4'],
+	qr/<0,0>=1:\n<0,1>=0:val1\n<0,2>=0:11/,
+	'binary result set with encrypted columns: encrypted columns returned as text');
+
+
+# Test UPDATE
+
+$node->safe_psql('postgres', q{
+UPDATE tbl1 SET b = $2 WHERE a = $1 \bind '3' 'val3upd' \g
+});
+
+$result = $node->safe_psql('postgres', q{SELECT a, b, c FROM tbl1});
+is($result,
+	q(1|val1|11
+2|val2|22
+3|val3upd|33),
+	'decrypted query result after update');
+
+
+# Test views
+
+$node->safe_psql('postgres', q{CREATE VIEW v1 AS SELECT a, b, c FROM tbl1});
+
+$node->safe_psql('postgres', q{UPDATE v1 SET b = $2 WHERE a = $1 \bind '3' 'val3upd2' \g});
+
+$result = $node->safe_psql('postgres', q{SELECT a, b, c FROM v1 WHERE a IN (1, 3)});
+is($result,
+	q(1|val1|11
+3|val3upd2|33),
+	'decrypted query result from view');
+
+
+# Test deterministic encryption
+
+$node->safe_psql('postgres', qq{
+CREATE TABLE tbl2 (
+    a int,
+    b text ENCRYPTED WITH (encryption_type = deterministic, column_encryption_key = cek1)
+);
+});
+
+$node->safe_psql('postgres', q{
+INSERT INTO tbl2 (a, b) VALUES ($1, $2), ($3, $4), ($5, $6) \bind '1' 'valA' '2' 'valB' '3' 'valA' \g
+});
+
+$result = $node->safe_psql('postgres', q{SELECT a, b FROM tbl2});
+is($result,
+	q(1|valA
+2|valB
+3|valA),
+	'decrypted query result in table for deterministic encryption');
+
+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');
+
+is($node->safe_psql('postgres', q{SELECT a FROM tbl2 WHERE b = $1 \bind 'valB' \g}),
+	q(2),
+	'select by deterministically encrypted column');
+
+
+# Test multiple keys in one table
+
+$node->safe_psql('postgres', qq{
+CREATE TABLE tbl3 (
+    a int,
+    b text ENCRYPTED WITH (column_encryption_key = cek1),
+    c text ENCRYPTED WITH (column_encryption_key = cek2, algorithm = 'AEAD_AES_192_CBC_HMAC_SHA_384')
+);
+});
+
+$node->safe_psql('postgres', q{
+INSERT INTO tbl3 (a, b, c) VALUES (1, $1, $2) \bind 'valB1' 'valC1' \g
+});
+
+$result = $node->safe_psql('postgres', q{SELECT a, b, c FROM tbl3});
+is($result,
+	q(1|valB1|valC1),
+	'decrypted query result multiple keys');
+
+$node->safe_psql('postgres', q{
+INSERT INTO tbl3 (a, b, c) VALUES ($1, $2, $3), ($4, $5, $6) \bind '2' 'valB2' 'valC2' '3' 'valB3' 'valC3' \g
+});
+
+$result = $node->safe_psql('postgres', q{SELECT a, b, c FROM tbl3});
+is($result,
+	q(1|valB1|valC1
+2|valB2|valC2
+3|valB3|valC3),
+	'decrypted query result multiple keys after second insert');
+
+
+done_testing();
diff --git a/src/test/column_encryption/t/002_cmk_rotation.pl b/src/test/column_encryption/t/002_cmk_rotation.pl
new file mode 100644
index 0000000000..14eafb8ec9
--- /dev/null
+++ b/src/test/column_encryption/t/002_cmk_rotation.pl
@@ -0,0 +1,112 @@
+# Copyright (c) 2021-2023, PostgreSQL Global Development Group
+
+# Test column master key rotation.  First, we generate CMK1 and a CEK
+# encrypted with it.  Then we add a CMK2 and encrypt the CEK with it
+# as well.  (Recall that a CEK can be associated with multiple CMKs,
+# for this reason.  That's why pg_colenckeydata is split out from
+# pg_colenckey.)  Then we remove CMK1.  We test that we can get
+# decrypted query results at each step.
+
+use strict;
+use warnings;
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More;
+
+my $openssl = $ENV{OPENSSL};
+
+my $cmkalg = 'RSAES_OAEP_SHA_1';
+
+my $node = PostgreSQL::Test::Cluster->new('node');
+$node->init;
+$node->start;
+
+
+sub create_cmk
+{
+	my ($cmkname) = @_;
+	my $cmkfilename = "${PostgreSQL::Test::Utils::tmp_check}/${cmkname}.pem";
+	system_or_bail $openssl, 'genpkey', '-algorithm', 'rsa', '-out', $cmkfilename;
+	$node->safe_psql('postgres', qq{CREATE COLUMN MASTER KEY ${cmkname}});
+	return $cmkfilename;
+}
+
+
+my $cmk1filename = create_cmk('cmk1');
+
+# create CEK
+my ($cekname, $bytes) = ('cek1', 48);
+
+# generate random bytes
+system_or_bail $openssl, 'rand', '-out', "${PostgreSQL::Test::Utils::tmp_check}/${cekname}.bin", $bytes;
+
+# encrypt CEK using CMK
+system_or_bail $openssl, 'pkeyutl', '-encrypt',
+  '-inkey', $cmk1filename,
+  '-pkeyopt', 'rsa_padding_mode:oaep',
+  '-in', "${PostgreSQL::Test::Utils::tmp_check}/${cekname}.bin",
+  '-out', "${PostgreSQL::Test::Utils::tmp_check}/${cekname}.bin.enc";
+
+my $cekenchex = unpack('H*', slurp_file "${PostgreSQL::Test::Utils::tmp_check}/${cekname}.bin.enc");
+
+# create CEK in database
+$node->safe_psql('postgres', qq{CREATE COLUMN ENCRYPTION KEY ${cekname} WITH VALUES (column_master_key = cmk1, algorithm = '$cmkalg', encrypted_value = '\\x${cekenchex}');});
+
+$ENV{PGCOLUMNENCRYPTION} = 'on';
+$ENV{PGCMKLOOKUP} = '*=file:' . ${PostgreSQL::Test::Utils::tmp_check} . '/%k.pem';
+
+$node->safe_psql('postgres', qq{
+CREATE TABLE tbl1 (
+    a int,
+    b text ENCRYPTED WITH (column_encryption_key = cek1)
+);
+});
+
+$node->safe_psql('postgres', q{
+INSERT INTO tbl1 (a, b) VALUES (1, $1) \bind 'val1' \g
+INSERT INTO tbl1 (a, b) VALUES (2, $1) \bind 'val2' \g
+});
+
+is($node->safe_psql('postgres', q{SELECT a, b FROM tbl1}),
+	q(1|val1
+2|val2),
+	'decrypted query result with one CMK');
+
+
+# create new CMK
+my $cmk2filename = create_cmk('cmk2');
+
+# encrypt CEK using new CMK
+#
+# (Here, we still have the plaintext of the CEK available from
+# earlier.  In reality, one would decrypt the CEK with the first CMK
+# and then re-encrypt it with the second CMK.)
+system_or_bail $openssl, 'pkeyutl', '-encrypt',
+  '-inkey', $cmk2filename,
+  '-pkeyopt', 'rsa_padding_mode:oaep',
+  '-in', "${PostgreSQL::Test::Utils::tmp_check}/${cekname}.bin",
+  '-out', "${PostgreSQL::Test::Utils::tmp_check}/${cekname}.bin.enc";
+
+$cekenchex = unpack('H*', slurp_file "${PostgreSQL::Test::Utils::tmp_check}/${cekname}.bin.enc");
+
+# add new data record for CEK in database
+$node->safe_psql('postgres', qq{ALTER COLUMN ENCRYPTION KEY ${cekname} ADD VALUE (column_master_key = cmk2, algorithm = '$cmkalg', encrypted_value = '\\x${cekenchex}');});
+
+
+is($node->safe_psql('postgres', q{SELECT a, b FROM tbl1}),
+	q(1|val1
+2|val2),
+	'decrypted query result with two CMKs');
+
+
+# delete CEK record for first CMK
+$node->safe_psql('postgres', qq{ALTER COLUMN ENCRYPTION KEY ${cekname} DROP VALUE (column_master_key = cmk1);});
+
+
+is($node->safe_psql('postgres', q{SELECT a, b FROM tbl1}),
+	q(1|val1
+2|val2),
+	'decrypted query result with only new CMK');
+
+
+done_testing();
diff --git a/src/test/column_encryption/test_client.c b/src/test/column_encryption/test_client.c
new file mode 100644
index 0000000000..9c257a3ddb
--- /dev/null
+++ b/src/test/column_encryption/test_client.c
@@ -0,0 +1,161 @@
+/*
+ * Copyright (c) 2021-2023, PostgreSQL Global Development Group
+ */
+
+#include "postgres_fe.h"
+
+#include "libpq-fe.h"
+
+
+/*
+ * Test calls that don't support encryption
+ */
+static int
+test1(PGconn *conn)
+{
+	PGresult   *res;
+	const char *values[] = {"3", "val3", "33"};
+
+	res = PQprepare(conn, "", "INSERT INTO tbl1 (a, b, c) VALUES ($1, $2, $3)",
+					3, NULL);
+	if (PQresultStatus(res) != PGRES_COMMAND_OK)
+	{
+		fprintf(stderr, "PQprepare() failed: %s\n",
+				PQerrorMessage(conn));
+		return 1;
+	}
+
+	res = PQexecPrepared(conn, "", 3, values, NULL, NULL, 0);
+	if (PQresultStatus(res) != PGRES_COMMAND_OK)
+	{
+		fprintf(stderr, "PQexecPrepared() failed: %s\n",
+				PQerrorMessage(conn));
+		return 1;
+	}
+
+	return 0;
+}
+
+/*
+ * Test forced encryption
+ */
+static int
+test2(PGconn *conn)
+{
+	PGresult   *res,
+			   *res2;
+	const char *values[] = {"3", "val3", "33"};
+	int			formats[] = {0x00, 0x10, 0x00};
+
+	res = PQprepare(conn, "", "INSERT INTO tbl1 (a, b, c) VALUES ($1, $2, $3)",
+					3, 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;
+	}
+
+	if (!(!PQparamisencrypted(res2, 0) &&
+		  PQparamisencrypted(res2, 1)))
+	{
+		fprintf(stderr, "wrong results from PQparamisencrypted()\n");
+		return 1;
+	}
+
+	res = PQexecPreparedDescribed(conn, "", 3, values, NULL, formats, 0, res2);
+	if (PQresultStatus(res) != PGRES_COMMAND_OK)
+	{
+		fprintf(stderr, "PQexecPrepared() failed: %s\n",
+				PQerrorMessage(conn));
+		return 1;
+	}
+
+	return 0;
+}
+
+/*
+ * Test what happens when you supply a binary parameter that is required to be
+ * encrypted.
+ */
+static int
+test3(PGconn *conn)
+{
+	PGresult   *res;
+	const char *values[] = {""};
+	int			lengths[] = {1};
+	int			formats[] = {1};
+
+	res = PQexecParams(conn, "INSERT INTO tbl1 (a, b, c) VALUES (100, NULL, $1)",
+					   3, NULL, values, lengths, formats, 0);
+	if (PQresultStatus(res) != PGRES_COMMAND_OK)
+	{
+		fprintf(stderr, "PQexecParams() failed: %s\n",
+				PQerrorMessage(conn));
+		return 1;
+	}
+
+	return 0;
+}
+
+/*
+ * Test what happens when you request results in binary and the result rows
+ * contain an encrypted column.
+ */
+static int
+test4(PGconn *conn)
+{
+	PGresult   *res;
+
+	res = PQexecParams(conn, "SELECT a, b, c FROM tbl1 WHERE a = 1", 0, NULL, NULL, NULL, NULL, 1);
+	if (PQresultStatus(res) != PGRES_TUPLES_OK)
+	{
+		fprintf(stderr, "PQexecParams() failed: %s\n",
+				PQerrorMessage(conn));
+		return 1;
+	}
+
+	for (int row = 0; row < PQntuples(res); row++)
+		for (int col = 0; col < PQnfields(res); col++)
+			printf("<%d,%d>=%d:%s\n", row, col, PQfformat(res, col), PQgetvalue(res, row, col));
+	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/column_encryption/test_run_decrypt.pl b/src/test/column_encryption/test_run_decrypt.pl
new file mode 100755
index 0000000000..66871cb438
--- /dev/null
+++ b/src/test/column_encryption/test_run_decrypt.pl
@@ -0,0 +1,58 @@
+#!/usr/bin/perl
+
+# Test/sample command for libpq cmklookup run scheme
+#
+# This just places the data into temporary files and runs the openssl
+# command on it.  (In practice, this could more simply be written as a
+# shell script, but this way it's more portable.)
+
+# Copyright (c) 2021-2023, PostgreSQL Global Development Group
+
+use strict;
+use warnings;
+
+my ($cmkname, $alg, $filename) = @ARGV;
+
+die unless $alg =~ 'RSAES_OAEP_SHA';
+
+my $digest = $alg;
+$digest =~ s/.*(?=SHA)//;
+$digest =~ s/_//g;
+
+my $tmpdir = $ENV{TESTWORKDIR};
+
+my $openssl = $ENV{OPENSSL};
+
+my @cmd = (
+	$openssl, 'pkeyutl', '-decrypt',
+	'-inkey', "${tmpdir}/${cmkname}.pem", '-pkeyopt', 'rsa_padding_mode:oaep',
+	'-in', $filename, '-out', "${tmpdir}/output.tmp"
+);
+
+if ($digest ne 'SHA1')
+{
+	# These options require OpenSSL >=1.1.0, so if the digest is
+	# SHA1, which is the default, omit the options.
+	push @cmd,
+	  '-pkeyopt', "rsa_mgf1_md:$digest",
+	  '-pkeyopt', "rsa_oaep_md:$digest";
+}
+
+system(@cmd) == 0 or die "system failed: $?";
+
+open my $fh, '<:raw', "${tmpdir}/output.tmp" or die $!;
+my $data = '';
+
+while (1) {
+	my $success = read $fh, $data, 100, length($data);
+	die $! if not defined $success;
+	last if not $success;
+}
+
+close $fh;
+
+unlink "${tmpdir}/output.tmp";
+
+binmode STDOUT;
+
+print $data;
diff --git a/src/test/meson.build b/src/test/meson.build
index 5f3c9c2ba2..d55ddb1ab7 100644
--- a/src/test/meson.build
+++ b/src/test/meson.build
@@ -9,6 +9,7 @@ subdir('subscription')
 subdir('modules')
 
 if ssl.found()
+  subdir('column_encryption')
   subdir('ssl')
 endif
 
diff --git a/src/test/regress/expected/column_encryption.out b/src/test/regress/expected/column_encryption.out
new file mode 100644
index 0000000000..354f0bb1e3
--- /dev/null
+++ b/src/test/regress/expected/column_encryption.out
@@ -0,0 +1,322 @@
+\set HIDE_COLUMN_ENCRYPTION false
+CREATE ROLE regress_enc_user1;
+CREATE COLUMN MASTER KEY cmk1 WITH (
+    realm = 'test'
+);
+COMMENT ON COLUMN MASTER KEY cmk1 IS 'column master key';
+CREATE COLUMN MASTER KEY cmk1a WITH (
+    realm = 'test'
+);
+CREATE COLUMN MASTER KEY cmk2;
+CREATE COLUMN MASTER KEY cmk2a WITH (
+    realm = 'testx'
+);
+ALTER COLUMN MASTER KEY cmk2a (realm = 'test2');
+CREATE COLUMN ENCRYPTION KEY fail WITH VALUES (
+    column_master_key = cmk1,
+    algorithm = 'foo',  -- invalid
+    encrypted_value = '\xDEADBEEF'
+);
+ERROR:  unrecognized encryption algorithm: foo
+CREATE COLUMN ENCRYPTION KEY cek1 WITH VALUES (
+    column_master_key = cmk1,
+    algorithm = 'RSAES_OAEP_SHA_1',
+    encrypted_value = '\xDEADBEEF'
+);
+COMMENT ON COLUMN ENCRYPTION KEY cek1 IS 'column encryption key';
+ALTER COLUMN ENCRYPTION KEY cek1 ADD VALUE (
+    column_master_key = cmk1a,
+    algorithm = 'RSAES_OAEP_SHA_1',
+    encrypted_value = '\xDEADBEEF'
+);
+-- duplicate
+ALTER COLUMN ENCRYPTION KEY cek1 ADD VALUE (
+    column_master_key = cmk1a,
+    algorithm = 'RSAES_OAEP_SHA_1',
+    encrypted_value = '\xDEADBEEF'
+);
+ERROR:  column encryption key "cek1" already has data for master key "cmk1a"
+ALTER COLUMN ENCRYPTION KEY fail ADD VALUE (
+    column_master_key = cmk1a,
+    algorithm = 'RSAES_OAEP_SHA_1',
+    encrypted_value = '\xDEADBEEF'
+);
+ERROR:  column encryption key "fail" does not exist
+CREATE COLUMN ENCRYPTION KEY cek2 WITH VALUES (
+    column_master_key = cmk2,
+    algorithm = 'RSAES_OAEP_SHA_1',
+    encrypted_value = '\xDEADBEEF'
+),
+(
+    column_master_key = cmk2a,
+    algorithm = 'RSAES_OAEP_SHA_1',
+    encrypted_value = '\xDEADBEEF'
+);
+CREATE COLUMN ENCRYPTION KEY cek4 WITH VALUES (
+    column_master_key = cmk1,
+    algorithm = 'RSAES_OAEP_SHA_1',
+    encrypted_value = '\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_fail (
+    a int,
+    b text,
+    c text ENCRYPTED WITH (column_encryption_key = cek1, algorithm = 'foo')
+);
+ERROR:  unrecognized encryption algorithm: foo
+CREATE TABLE tbl_fail (
+    a int,
+    b text,
+    c text ENCRYPTED WITH (column_encryption_key = cek1, encryption_type = wrong)
+);
+ERROR:  unrecognized encryption type: wrong
+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      | text    |           |          | 
+
+\d+ tbl_29f3
+                                        Table "public.tbl_29f3"
+ Column |  Type   | Collation | Nullable | Default | Storage  | Encryption | Stats target | Description 
+--------+---------+-----------+----------+---------+----------+------------+--------------+-------------
+ a      | integer |           |          |         | plain    |            |              | 
+ b      | text    |           |          |         | extended |            |              | 
+ c      | text    |           |          |         | external | cek1       |              | 
+
+CREATE TABLE tbl_447f (
+    a int,
+    b text
+);
+ALTER TABLE tbl_447f ADD COLUMN c text ENCRYPTED WITH (column_encryption_key = cek1);
+\d tbl_447f
+              Table "public.tbl_447f"
+ Column |  Type   | Collation | Nullable | Default 
+--------+---------+-----------+----------+---------
+ a      | integer |           |          | 
+ b      | text    |           |          | 
+ c      | text    |           |          | 
+
+\d+ tbl_447f
+                                        Table "public.tbl_447f"
+ Column |  Type   | Collation | Nullable | Default | Storage  | Encryption | Stats target | Description 
+--------+---------+-----------+----------+---------+----------+------------+--------------+-------------
+ a      | integer |           |          |         | plain    |            |              | 
+ b      | text    |           |          |         | extended |            |              | 
+ c      | text    |           |          |         | external | cek1       |              | 
+
+CREATE TABLE tbl_4897 (LIKE tbl_447f);
+CREATE TABLE tbl_6978 (LIKE tbl_447f INCLUDING ENCRYPTED);
+\d+ tbl_4897
+                                        Table "public.tbl_4897"
+ Column |  Type   | Collation | Nullable | Default | Storage  | Encryption | Stats target | Description 
+--------+---------+-----------+----------+---------+----------+------------+--------------+-------------
+ a      | integer |           |          |         | plain    |            |              | 
+ b      | text    |           |          |         | extended |            |              | 
+ c      | text    |           |          |         | extended |            |              | 
+
+\d+ tbl_6978
+                                        Table "public.tbl_6978"
+ Column |  Type   | Collation | Nullable | Default | Storage  | Encryption | Stats target | Description 
+--------+---------+-----------+----------+---------+----------+------------+--------------+-------------
+ a      | integer |           |          |         | plain    |            |              | 
+ b      | text    |           |          |         | extended |            |              | 
+ c      | text    |           |          |         | external | cek1       |              | 
+
+CREATE VIEW view_3bc9 AS SELECT * FROM tbl_29f3;
+\d+ view_3bc9
+                                 View "public.view_3bc9"
+ Column |  Type   | Collation | Nullable | Default | Storage  | Encryption | Description 
+--------+---------+-----------+----------+---------+----------+------------+-------------
+ a      | integer |           |          |         | plain    |            | 
+ b      | text    |           |          |         | extended |            | 
+ c      | text    |           |          |         | external | cek1       | 
+View definition:
+ SELECT a,
+    b,
+    c
+   FROM tbl_29f3;
+
+CREATE TABLE tbl_2386 AS SELECT * FROM tbl_29f3 WITH NO DATA;
+\d+ tbl_2386
+                                        Table "public.tbl_2386"
+ Column |  Type   | Collation | Nullable | Default | Storage  | Encryption | Stats target | Description 
+--------+---------+-----------+----------+---------+----------+------------+--------------+-------------
+ a      | integer |           |          |         | plain    |            |              | 
+ b      | text    |           |          |         | extended |            |              | 
+ c      | text    |           |          |         | external | cek1       |              | 
+
+CREATE TABLE tbl_2941 AS SELECT * FROM tbl_29f3 WITH DATA;
+ERROR:  encrypted columns not yet implemented for this command
+\d+ tbl_2941
+-- test partition declarations
+CREATE TABLE tbl_13fa (
+    a int,
+    b text ENCRYPTED WITH (column_encryption_key = cek1)
+) PARTITION BY RANGE (a);
+CREATE TABLE tbl_13fa_1 PARTITION OF tbl_13fa FOR VALUES FROM (1) TO (100);
+\d+ tbl_13fa
+                                  Partitioned table "public.tbl_13fa"
+ Column |  Type   | Collation | Nullable | Default | Storage  | Encryption | Stats target | Description 
+--------+---------+-----------+----------+---------+----------+------------+--------------+-------------
+ a      | integer |           |          |         | plain    |            |              | 
+ b      | text    |           |          |         | external | cek1       |              | 
+Partition key: RANGE (a)
+Partitions: tbl_13fa_1 FOR VALUES FROM (1) TO (100)
+
+\d+ tbl_13fa_1
+                                       Table "public.tbl_13fa_1"
+ Column |  Type   | Collation | Nullable | Default | Storage  | Encryption | Stats target | Description 
+--------+---------+-----------+----------+---------+----------+------------+--------------+-------------
+ a      | integer |           |          |         | plain    |            |              | 
+ b      | text    |           |          |         | external | cek1       |              | 
+Partition of: tbl_13fa FOR VALUES FROM (1) TO (100)
+Partition constraint: ((a IS NOT NULL) AND (a >= 1) AND (a < 100))
+
+-- test inheritance
+CREATE TABLE tbl_36f3_a (
+    a int,
+    b text ENCRYPTED WITH (column_encryption_key = cek1)
+);
+CREATE TABLE tbl_36f3_b (
+    a int,
+    b text ENCRYPTED WITH (column_encryption_key = cek1)
+);
+CREATE TABLE tbl_36f3_c (
+    a int,
+    b text ENCRYPTED WITH (column_encryption_key = cek2)
+);
+CREATE TABLE tbl_36f3_d (
+    a int,
+    b text ENCRYPTED WITH (column_encryption_key = cek1, encryption_type = deterministic)
+);
+CREATE TABLE tbl_36f3_e (
+    a int,
+    b text
+);
+-- not implemented (but could be ok)
+CREATE TABLE tbl_36f3_ab (c int) INHERITS (tbl_36f3_a, tbl_36f3_b);
+NOTICE:  merging multiple inherited definitions of column "a"
+NOTICE:  merging multiple inherited definitions of column "b"
+ERROR:  multiple inheritance of encrypted columns is not implemented
+\d+ tbl_36f3_ab
+-- not implemented (but should fail)
+CREATE TABLE tbl_36f3_ac (c int) INHERITS (tbl_36f3_a, tbl_36f3_c);
+NOTICE:  merging multiple inherited definitions of column "a"
+NOTICE:  merging multiple inherited definitions of column "b"
+ERROR:  multiple inheritance of encrypted columns is not implemented
+CREATE TABLE tbl_36f3_ad (c int) INHERITS (tbl_36f3_a, tbl_36f3_d);
+NOTICE:  merging multiple inherited definitions of column "a"
+NOTICE:  merging multiple inherited definitions of column "b"
+ERROR:  multiple inheritance of encrypted columns is not implemented
+-- fail
+CREATE TABLE tbl_36f3_ae (c int) INHERITS (tbl_36f3_a, tbl_36f3_e);
+NOTICE:  merging multiple inherited definitions of column "a"
+NOTICE:  merging multiple inherited definitions of column "b"
+ERROR:  column "b" has an encryption specification conflict
+-- ok
+CREATE TABLE tbl_36f3_a_1 (b text ENCRYPTED WITH (column_encryption_key = cek1), c int) INHERITS (tbl_36f3_a);
+NOTICE:  moving and merging column "b" with inherited definition
+DETAIL:  User-specified column moved to the position of the inherited column.
+\d+ tbl_36f3_a_1
+                                      Table "public.tbl_36f3_a_1"
+ Column |  Type   | Collation | Nullable | Default | Storage  | Encryption | Stats target | Description 
+--------+---------+-----------+----------+---------+----------+------------+--------------+-------------
+ a      | integer |           |          |         | plain    |            |              | 
+ b      | text    |           |          |         | external | cek1       |              | 
+ c      | integer |           |          |         | plain    |            |              | 
+Inherits: tbl_36f3_a
+
+-- fail
+CREATE TABLE tbl_36f3_a_2 (b text ENCRYPTED WITH (column_encryption_key = cek2), c int) INHERITS (tbl_36f3_a);
+NOTICE:  moving and merging column "b" with inherited definition
+DETAIL:  User-specified column moved to the position of the inherited column.
+ERROR:  column "b" has an encryption specification conflict
+CREATE TABLE tbl_36f3_a_2 (b text ENCRYPTED WITH (column_encryption_key = cek1, encryption_type = deterministic), c int) INHERITS (tbl_36f3_a);
+NOTICE:  moving and merging column "b" with inherited definition
+DETAIL:  User-specified column moved to the position of the inherited column.
+ERROR:  column "b" has an encryption specification conflict
+CREATE TABLE tbl_36f3_a_2 (b text, c int) INHERITS (tbl_36f3_a);
+NOTICE:  moving and merging column "b" with inherited definition
+DETAIL:  User-specified column moved to the position of the inherited column.
+ERROR:  column "b" has an encryption specification conflict
+DROP TABLE tbl_36f3_b, tbl_36f3_c, tbl_36f3_d, tbl_36f3_e;
+-- SET SCHEMA
+CREATE SCHEMA test_schema_ce;
+ALTER COLUMN ENCRYPTION KEY cek1 SET SCHEMA test_schema_ce;
+ALTER COLUMN MASTER KEY cmk1 SET SCHEMA test_schema_ce;
+ALTER COLUMN ENCRYPTION KEY test_schema_ce.cek1 SET SCHEMA public;
+ALTER COLUMN MASTER KEY test_schema_ce.cmk1 SET SCHEMA public;
+DROP SCHEMA test_schema_ce;
+DROP COLUMN MASTER KEY cmk1 RESTRICT;  -- fail
+ERROR:  cannot drop column master key cmk1 because other objects depend on it
+DETAIL:  column encryption key data of column encryption key cek1 for column master key cmk1 depends on column master key cmk1
+column encryption key data of column encryption key cek4 for column master key cmk1 depends on column master key cmk1
+HINT:  Use DROP ... CASCADE to drop the dependent objects too.
+ALTER COLUMN MASTER KEY cmk2 RENAME TO cmk3;
+ALTER COLUMN MASTER KEY cmk1 RENAME TO cmk3;  -- fail
+ERROR:  column master key "cmk3" already exists in schema "public"
+ALTER COLUMN MASTER KEY cmkx RENAME TO cmky;  -- fail
+ERROR:  column master key "cmkx" does not exist
+ALTER COLUMN ENCRYPTION KEY cek2 RENAME TO cek3;
+ALTER COLUMN ENCRYPTION KEY cek1 RENAME TO cek3;  -- fail
+ERROR:  column encryption key "cek3" already exists in schema "public"
+ALTER COLUMN ENCRYPTION KEY cekx RENAME TO ceky;  -- fail
+ERROR:  column encryption key "cekx" does not exist
+SET SESSION AUTHORIZATION 'regress_enc_user1';
+DROP COLUMN ENCRYPTION KEY cek3;  -- fail
+ERROR:  must be owner of column encryption key cek3
+DROP COLUMN MASTER KEY cmk3;  -- fail
+ERROR:  must be owner of column master key cmk3
+RESET SESSION AUTHORIZATION;
+ALTER COLUMN MASTER KEY cmk3 OWNER TO regress_enc_user1;
+ALTER COLUMN ENCRYPTION KEY cek3 OWNER TO regress_enc_user1;
+\dcek cek3
+         List of column encryption keys
+ Schema | Name |       Owner       | Master key 
+--------+------+-------------------+------------
+ public | cek3 | regress_enc_user1 | cmk2a
+ public | cek3 | regress_enc_user1 | cmk3
+(2 rows)
+
+\dcmk cmk3
+        List of column master keys
+ Schema | Name |       Owner       | Realm 
+--------+------+-------------------+-------
+ public | cmk3 | regress_enc_user1 | 
+(1 row)
+
+SET SESSION AUTHORIZATION 'regress_enc_user1';
+DROP COLUMN ENCRYPTION KEY cek3;  -- ok now
+DROP COLUMN MASTER KEY cmk3;  -- ok now
+RESET SESSION AUTHORIZATION;
+ALTER COLUMN ENCRYPTION KEY cek1 DROP VALUE (column_master_key = cmk1a);
+ALTER COLUMN ENCRYPTION KEY cek1 DROP VALUE (column_master_key = cmk1a);  -- fail
+ERROR:  column encryption key "cek1" has no data for master key "cmk1a"
+ALTER COLUMN ENCRYPTION KEY cek1 DROP VALUE (column_master_key = fail);  -- fail
+ERROR:  column master key "fail" does not exist
+ALTER COLUMN ENCRYPTION KEY cek1 DROP VALUE (column_master_key = cmk1a, algorithm = 'foo');  -- fail
+ERROR:  attribute "algorithm" must not be specified
+DROP COLUMN ENCRYPTION KEY cek4;
+DROP COLUMN ENCRYPTION KEY fail;
+ERROR:  column encryption key "fail" does not exist
+DROP COLUMN ENCRYPTION KEY IF EXISTS nonexistent;
+NOTICE:  column encryption key "nonexistent" does not exist, skipping
+DROP COLUMN MASTER KEY cmk1a;
+DROP COLUMN MASTER KEY fail;
+ERROR:  column master key "fail" does not exist
+DROP COLUMN MASTER KEY IF EXISTS nonexistent;
+NOTICE:  column master key "nonexistent" does not exist, skipping
+DROP ROLE regress_enc_user1;
diff --git a/src/test/regress/expected/object_address.out b/src/test/regress/expected/object_address.out
index 25c174f275..08b76b6cc8 100644
--- a/src/test/regress/expected/object_address.out
+++ b/src/test/regress/expected/object_address.out
@@ -38,6 +38,8 @@ CREATE SERVER "integer" FOREIGN DATA WRAPPER addr_fdw;
 CREATE USER MAPPING FOR regress_addr_user SERVER "integer";
 ALTER DEFAULT PRIVILEGES FOR ROLE regress_addr_user IN SCHEMA public GRANT ALL ON TABLES TO regress_addr_user;
 ALTER DEFAULT PRIVILEGES FOR ROLE regress_addr_user REVOKE DELETE ON TABLES FROM regress_addr_user;
+CREATE COLUMN MASTER KEY addr_cmk;
+CREATE COLUMN ENCRYPTION KEY addr_cek WITH VALUES (column_master_key = addr_cmk, algorithm = 'RSAES_OAEP_SHA_1', encrypted_value = '');
 -- this transform would be quite unsafe to leave lying around,
 -- except that the SQL language pays no attention to transforms:
 CREATE TRANSFORM FOR int LANGUAGE SQL (
@@ -107,7 +109,8 @@ BEGIN
         ('text search template'), ('text search configuration'),
         ('policy'), ('user mapping'), ('default acl'), ('transform'),
         ('operator of access method'), ('function of access method'),
-        ('publication namespace'), ('publication relation')
+        ('publication namespace'), ('publication relation'),
+        ('column encryption key'), ('column encryption key data'), ('column master key')
     LOOP
         FOR names IN VALUES ('{eins}'), ('{addr_nsp, zwei}'), ('{eins, zwei, drei}')
         LOOP
@@ -327,6 +330,24 @@ WARNING:  error for publication relation,{addr_nsp,zwei},{}: argument list lengt
 WARNING:  error for publication relation,{addr_nsp,zwei},{integer}: relation "addr_nsp.zwei" does not exist
 WARNING:  error for publication relation,{eins,zwei,drei},{}: argument list length must be exactly 1
 WARNING:  error for publication relation,{eins,zwei,drei},{integer}: cross-database references are not implemented: "eins.zwei.drei"
+WARNING:  error for column encryption key,{eins},{}: column encryption key "eins" does not exist
+WARNING:  error for column encryption key,{eins},{integer}: column encryption key "eins" does not exist
+WARNING:  error for column encryption key,{addr_nsp,zwei},{}: column encryption key "addr_nsp.zwei" does not exist
+WARNING:  error for column encryption key,{addr_nsp,zwei},{integer}: column encryption key "addr_nsp.zwei" does not exist
+WARNING:  error for column encryption key,{eins,zwei,drei},{}: cross-database references are not implemented: eins.zwei.drei
+WARNING:  error for column encryption key,{eins,zwei,drei},{integer}: cross-database references are not implemented: eins.zwei.drei
+WARNING:  error for column encryption key data,{eins},{}: column encryption key "eins" does not exist
+WARNING:  error for column encryption key data,{eins},{integer}: column encryption key "eins" does not exist
+WARNING:  error for column encryption key data,{addr_nsp,zwei},{}: column encryption key "addr_nsp.zwei" does not exist
+WARNING:  error for column encryption key data,{addr_nsp,zwei},{integer}: column encryption key "addr_nsp.zwei" does not exist
+WARNING:  error for column encryption key data,{eins,zwei,drei},{}: cross-database references are not implemented: eins.zwei.drei
+WARNING:  error for column encryption key data,{eins,zwei,drei},{integer}: cross-database references are not implemented: eins.zwei.drei
+WARNING:  error for column master key,{eins},{}: column master key "eins" does not exist
+WARNING:  error for column master key,{eins},{integer}: column master key "eins" does not exist
+WARNING:  error for column master key,{addr_nsp,zwei},{}: column master key "addr_nsp.zwei" does not exist
+WARNING:  error for column master key,{addr_nsp,zwei},{integer}: column master key "addr_nsp.zwei" does not exist
+WARNING:  error for column master key,{eins,zwei,drei},{}: cross-database references are not implemented: eins.zwei.drei
+WARNING:  error for column master key,{eins,zwei,drei},{integer}: cross-database references are not implemented: eins.zwei.drei
 -- these object types cannot be qualified names
 SELECT pg_get_object_address('language', '{one}', '{}');
 ERROR:  language "one" does not exist
@@ -409,6 +430,9 @@ WITH objects (type, name, args) AS (VALUES
     ('type', '{addr_nsp.genenum}', '{}'),
     ('cast', '{int8}', '{int4}'),
     ('collation', '{default}', '{}'),
+    ('column encryption key', '{addr_cek}', '{}'),
+    ('column encryption key data', '{addr_cek}', '{addr_cmk}'),
+    ('column master key', '{addr_cmk}', '{}'),
     ('table constraint', '{addr_nsp, gentable, a_chk}', '{}'),
     ('domain constraint', '{addr_nsp.gendomain}', '{domconstr}'),
     ('conversion', '{pg_catalog, koi8_r_to_mic}', '{}'),
@@ -505,6 +529,9 @@ subscription|NULL|regress_addr_sub|regress_addr_sub|t
 publication|NULL|addr_pub|addr_pub|t
 publication relation|NULL|NULL|addr_nsp.gentable in publication addr_pub|t
 publication namespace|NULL|NULL|addr_nsp in publication addr_pub_schema|t
+column master key|addr_nsp|addr_cmk|addr_nsp.addr_cmk|t
+column encryption key|addr_nsp|addr_cek|addr_nsp.addr_cek|t
+column encryption key data|NULL|NULL|of addr_nsp.addr_cek for addr_nsp.addr_cmk|t
 ---
 --- Cleanup resources
 ---
@@ -517,6 +544,8 @@ drop cascades to user mapping for regress_addr_user on server integer
 DROP PUBLICATION addr_pub;
 DROP PUBLICATION addr_pub_schema;
 DROP SUBSCRIPTION regress_addr_sub;
+DROP COLUMN ENCRYPTION KEY addr_cek;
+DROP COLUMN MASTER KEY addr_cmk;
 DROP SCHEMA addr_nsp CASCADE;
 NOTICE:  drop cascades to 14 other objects
 DETAIL:  drop cascades to text search dictionary addr_ts_dict
@@ -547,6 +576,9 @@ WITH objects (classid, objid, objsubid) AS (VALUES
     ('pg_type'::regclass, 0, 0), -- no type
     ('pg_cast'::regclass, 0, 0), -- no cast
     ('pg_collation'::regclass, 0, 0), -- no collation
+    ('pg_colenckey'::regclass, 0, 0), -- no column encryption key
+    ('pg_colenckeydata'::regclass, 0, 0), -- no column encryption key data
+    ('pg_colmasterkey'::regclass, 0, 0), -- no column master key
     ('pg_constraint'::regclass, 0, 0), -- no constraint
     ('pg_conversion'::regclass, 0, 0), -- no conversion
     ('pg_attrdef'::regclass, 0, 0), -- no default attribute
@@ -634,5 +666,8 @@ ORDER BY objects.classid, objects.objid, objects.objsubid;
 ("(""publication relation"",,,)")|("(""publication relation"",,)")|NULL
 ("(""publication namespace"",,,)")|("(""publication namespace"",,)")|NULL
 ("(""parameter ACL"",,,)")|("(""parameter ACL"",,)")|NULL
+("(""column master key"",,,)")|("(""column master key"",,)")|NULL
+("(""column encryption key"",,,)")|("(""column encryption key"",,)")|NULL
+("(""column encryption key data"",,,)")|("(""column encryption key data"",,)")|NULL
 -- restore normal output mode
 \a\t
diff --git a/src/test/regress/expected/oidjoins.out b/src/test/regress/expected/oidjoins.out
index 215eb899be..c34fdb1b6c 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,9 @@ 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_colmasterkey {cmknamespace} => pg_namespace {oid}
+NOTICE:  checking pg_colmasterkey {cmkowner} => pg_authid {oid}
+NOTICE:  checking pg_colenckey {ceknamespace} => pg_namespace {oid}
+NOTICE:  checking pg_colenckey {cekowner} => pg_authid {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 02f5348ab1..d493ac5f7c 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -159,7 +159,8 @@ ORDER BY 1, 2;
  text                        | character varying
  timestamp without time zone | timestamp with time zone
  txid_snapshot               | pg_snapshot
-(4 rows)
+ pg_encrypted_det            | pg_encrypted_rnd
+(5 rows)
 
 SELECT DISTINCT p1.proargtypes[0]::regtype, p2.proargtypes[0]::regtype
 FROM pg_proc AS p1, pg_proc AS p2
@@ -175,13 +176,15 @@ WHERE p1.oid != p2.oid AND
 ORDER BY 1, 2;
          proargtypes         |       proargtypes        
 -----------------------------+--------------------------
+ bytea                       | pg_encrypted_det
  bigint                      | xid8
  text                        | character
  text                        | character varying
  timestamp without time zone | timestamp with time zone
  bit                         | bit varying
  txid_snapshot               | pg_snapshot
-(6 rows)
+ pg_encrypted_det            | pg_encrypted_rnd
+(8 rows)
 
 SELECT DISTINCT p1.proargtypes[1]::regtype, p2.proargtypes[1]::regtype
 FROM pg_proc AS p1, pg_proc AS p2
@@ -197,12 +200,13 @@ WHERE p1.oid != p2.oid AND
 ORDER BY 1, 2;
          proargtypes         |       proargtypes        
 -----------------------------+--------------------------
+ bytea                       | pg_encrypted_det
  integer                     | xid
  timestamp without time zone | timestamp with time zone
  bit                         | bit varying
  txid_snapshot               | pg_snapshot
  anyrange                    | anymultirange
-(5 rows)
+(6 rows)
 
 SELECT DISTINCT p1.proargtypes[2]::regtype, p2.proargtypes[2]::regtype
 FROM pg_proc AS p1, pg_proc AS p2
@@ -872,6 +876,8 @@ xid8ge(xid8,xid8)
 xid8eq(xid8,xid8)
 xid8ne(xid8,xid8)
 xid8cmp(xid8,xid8)
+pg_encrypted_det_eq(pg_encrypted_det,pg_encrypted_det)
+pg_encrypted_det_ne(pg_encrypted_det,pg_encrypted_det)
 -- restore normal output mode
 \a\t
 -- List of functions used by libpq's fe-lobj.c
diff --git a/src/test/regress/expected/type_sanity.out b/src/test/regress/expected/type_sanity.out
index a640cfc476..742272225d 100644
--- a/src/test/regress/expected/type_sanity.out
+++ b/src/test/regress/expected/type_sanity.out
@@ -75,7 +75,9 @@ ORDER BY t1.oid;
  4600 | pg_brin_bloom_summary
  4601 | pg_brin_minmax_multi_summary
  5017 | pg_mcv_list
-(6 rows)
+ 8243 | pg_encrypted_det
+ 8244 | pg_encrypted_rnd
+(8 rows)
 
 -- Make sure typarray points to a "true" array type of our own base
 SELECT t1.oid, t1.typname as basetype, t2.typname as arraytype,
@@ -716,6 +718,8 @@ SELECT oid, typname, typtype, typelem, typarray
   WHERE oid < 16384 AND
     -- Exclude pseudotypes and composite types.
     typtype NOT IN ('p', 'c') AND
+    -- Exclude encryption internal types.
+    oid != ALL(ARRAY['pg_encrypted_det', 'pg_encrypted_rnd']::regtype[]) AND
     -- These reg* types cannot be pg_upgraded, so discard them.
     oid != ALL(ARRAY['regproc', 'regprocedure', 'regoper',
                      'regoperator', 'regconfig', 'regdictionary',
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index a930dfe48c..8154dc49ca 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -103,7 +103,7 @@ test: publication subscription
 # Another group of parallel tests
 # select_views depends on create_view
 # ----------
-test: select_views portals_p2 foreign_key cluster dependency guc bitmapops combocid tsearch tsdicts foreign_data window xmlmap functional_deps advisory_lock indirect_toast equivclass
+test: select_views portals_p2 foreign_key cluster dependency guc bitmapops combocid tsearch tsdicts foreign_data window xmlmap functional_deps advisory_lock indirect_toast equivclass column_encryption
 
 # ----------
 # Another group of parallel tests (JSON related)
diff --git a/src/test/regress/pg_regress_main.c b/src/test/regress/pg_regress_main.c
index 427429975e..57c7d2bec3 100644
--- a/src/test/regress/pg_regress_main.c
+++ b/src/test/regress/pg_regress_main.c
@@ -82,7 +82,7 @@ psql_start_test(const char *testname,
 					   bindir ? bindir : "",
 					   bindir ? "/" : "",
 					   dblist->str,
-					   "-v HIDE_TABLEAM=on -v HIDE_TOAST_COMPRESSION=on",
+					   "-v HIDE_TABLEAM=on -v HIDE_TOAST_COMPRESSION=on -v HIDE_COLUMN_ENCRYPTION=on",
 					   infile,
 					   outfile);
 	if (offset >= sizeof(psql_cmd))
diff --git a/src/test/regress/sql/column_encryption.sql b/src/test/regress/sql/column_encryption.sql
new file mode 100644
index 0000000000..3f5fcaba92
--- /dev/null
+++ b/src/test/regress/sql/column_encryption.sql
@@ -0,0 +1,232 @@
+\set HIDE_COLUMN_ENCRYPTION false
+
+CREATE ROLE regress_enc_user1;
+
+CREATE COLUMN MASTER KEY cmk1 WITH (
+    realm = 'test'
+);
+
+COMMENT ON COLUMN MASTER KEY cmk1 IS 'column master key';
+
+CREATE COLUMN MASTER KEY cmk1a WITH (
+    realm = 'test'
+);
+
+CREATE COLUMN MASTER KEY cmk2;
+
+CREATE COLUMN MASTER KEY cmk2a WITH (
+    realm = 'testx'
+);
+
+ALTER COLUMN MASTER KEY cmk2a (realm = 'test2');
+
+CREATE COLUMN ENCRYPTION KEY fail WITH VALUES (
+    column_master_key = cmk1,
+    algorithm = 'foo',  -- invalid
+    encrypted_value = '\xDEADBEEF'
+);
+
+CREATE COLUMN ENCRYPTION KEY cek1 WITH VALUES (
+    column_master_key = cmk1,
+    algorithm = 'RSAES_OAEP_SHA_1',
+    encrypted_value = '\xDEADBEEF'
+);
+
+COMMENT ON COLUMN ENCRYPTION KEY cek1 IS 'column encryption key';
+
+ALTER COLUMN ENCRYPTION KEY cek1 ADD VALUE (
+    column_master_key = cmk1a,
+    algorithm = 'RSAES_OAEP_SHA_1',
+    encrypted_value = '\xDEADBEEF'
+);
+
+-- duplicate
+ALTER COLUMN ENCRYPTION KEY cek1 ADD VALUE (
+    column_master_key = cmk1a,
+    algorithm = 'RSAES_OAEP_SHA_1',
+    encrypted_value = '\xDEADBEEF'
+);
+
+ALTER COLUMN ENCRYPTION KEY fail ADD VALUE (
+    column_master_key = cmk1a,
+    algorithm = 'RSAES_OAEP_SHA_1',
+    encrypted_value = '\xDEADBEEF'
+);
+
+CREATE COLUMN ENCRYPTION KEY cek2 WITH VALUES (
+    column_master_key = cmk2,
+    algorithm = 'RSAES_OAEP_SHA_1',
+    encrypted_value = '\xDEADBEEF'
+),
+(
+    column_master_key = cmk2a,
+    algorithm = 'RSAES_OAEP_SHA_1',
+    encrypted_value = '\xDEADBEEF'
+);
+
+CREATE COLUMN ENCRYPTION KEY cek4 WITH VALUES (
+    column_master_key = cmk1,
+    algorithm = 'RSAES_OAEP_SHA_1',
+    encrypted_value = '\xDEADBEEF'
+);
+
+CREATE TABLE tbl_fail (
+    a int,
+    b text,
+    c text ENCRYPTED WITH (column_encryption_key = notexist)
+);
+
+CREATE TABLE tbl_fail (
+    a int,
+    b text,
+    c text ENCRYPTED WITH (column_encryption_key = cek1, algorithm = 'foo')
+);
+
+CREATE TABLE tbl_fail (
+    a int,
+    b text,
+    c text ENCRYPTED WITH (column_encryption_key = cek1, encryption_type = wrong)
+);
+
+CREATE TABLE tbl_29f3 (
+    a int,
+    b text,
+    c text ENCRYPTED WITH (column_encryption_key = cek1)
+);
+
+\d tbl_29f3
+\d+ tbl_29f3
+
+CREATE TABLE tbl_447f (
+    a int,
+    b text
+);
+
+ALTER TABLE tbl_447f ADD COLUMN c text ENCRYPTED WITH (column_encryption_key = cek1);
+
+\d tbl_447f
+\d+ tbl_447f
+
+CREATE TABLE tbl_4897 (LIKE tbl_447f);
+CREATE TABLE tbl_6978 (LIKE tbl_447f INCLUDING ENCRYPTED);
+
+\d+ tbl_4897
+\d+ tbl_6978
+
+CREATE VIEW view_3bc9 AS SELECT * FROM tbl_29f3;
+
+\d+ view_3bc9
+
+CREATE TABLE tbl_2386 AS SELECT * FROM tbl_29f3 WITH NO DATA;
+
+\d+ tbl_2386
+
+CREATE TABLE tbl_2941 AS SELECT * FROM tbl_29f3 WITH DATA;
+
+\d+ tbl_2941
+
+-- test partition declarations
+
+CREATE TABLE tbl_13fa (
+    a int,
+    b text ENCRYPTED WITH (column_encryption_key = cek1)
+) PARTITION BY RANGE (a);
+CREATE TABLE tbl_13fa_1 PARTITION OF tbl_13fa FOR VALUES FROM (1) TO (100);
+
+\d+ tbl_13fa
+\d+ tbl_13fa_1
+
+
+-- test inheritance
+
+CREATE TABLE tbl_36f3_a (
+    a int,
+    b text ENCRYPTED WITH (column_encryption_key = cek1)
+);
+
+CREATE TABLE tbl_36f3_b (
+    a int,
+    b text ENCRYPTED WITH (column_encryption_key = cek1)
+);
+
+CREATE TABLE tbl_36f3_c (
+    a int,
+    b text ENCRYPTED WITH (column_encryption_key = cek2)
+);
+
+CREATE TABLE tbl_36f3_d (
+    a int,
+    b text ENCRYPTED WITH (column_encryption_key = cek1, encryption_type = deterministic)
+);
+
+CREATE TABLE tbl_36f3_e (
+    a int,
+    b text
+);
+
+-- not implemented (but could be ok)
+CREATE TABLE tbl_36f3_ab (c int) INHERITS (tbl_36f3_a, tbl_36f3_b);
+\d+ tbl_36f3_ab
+-- not implemented (but should fail)
+CREATE TABLE tbl_36f3_ac (c int) INHERITS (tbl_36f3_a, tbl_36f3_c);
+CREATE TABLE tbl_36f3_ad (c int) INHERITS (tbl_36f3_a, tbl_36f3_d);
+-- fail
+CREATE TABLE tbl_36f3_ae (c int) INHERITS (tbl_36f3_a, tbl_36f3_e);
+
+-- ok
+CREATE TABLE tbl_36f3_a_1 (b text ENCRYPTED WITH (column_encryption_key = cek1), c int) INHERITS (tbl_36f3_a);
+\d+ tbl_36f3_a_1
+-- fail
+CREATE TABLE tbl_36f3_a_2 (b text ENCRYPTED WITH (column_encryption_key = cek2), c int) INHERITS (tbl_36f3_a);
+CREATE TABLE tbl_36f3_a_2 (b text ENCRYPTED WITH (column_encryption_key = cek1, encryption_type = deterministic), c int) INHERITS (tbl_36f3_a);
+CREATE TABLE tbl_36f3_a_2 (b text, c int) INHERITS (tbl_36f3_a);
+
+DROP TABLE tbl_36f3_b, tbl_36f3_c, tbl_36f3_d, tbl_36f3_e;
+
+
+-- SET SCHEMA
+CREATE SCHEMA test_schema_ce;
+ALTER COLUMN ENCRYPTION KEY cek1 SET SCHEMA test_schema_ce;
+ALTER COLUMN MASTER KEY cmk1 SET SCHEMA test_schema_ce;
+ALTER COLUMN ENCRYPTION KEY test_schema_ce.cek1 SET SCHEMA public;
+ALTER COLUMN MASTER KEY test_schema_ce.cmk1 SET SCHEMA public;
+DROP SCHEMA test_schema_ce;
+
+
+DROP COLUMN MASTER KEY cmk1 RESTRICT;  -- fail
+
+ALTER COLUMN MASTER KEY cmk2 RENAME TO cmk3;
+ALTER COLUMN MASTER KEY cmk1 RENAME TO cmk3;  -- fail
+ALTER COLUMN MASTER KEY cmkx RENAME TO cmky;  -- fail
+
+ALTER COLUMN ENCRYPTION KEY cek2 RENAME TO cek3;
+ALTER COLUMN ENCRYPTION KEY cek1 RENAME TO cek3;  -- fail
+ALTER COLUMN ENCRYPTION KEY cekx RENAME TO ceky;  -- fail
+
+SET SESSION AUTHORIZATION 'regress_enc_user1';
+DROP COLUMN ENCRYPTION KEY cek3;  -- fail
+DROP COLUMN MASTER KEY cmk3;  -- fail
+RESET SESSION AUTHORIZATION;
+ALTER COLUMN MASTER KEY cmk3 OWNER TO regress_enc_user1;
+ALTER COLUMN ENCRYPTION KEY cek3 OWNER TO regress_enc_user1;
+\dcek cek3
+\dcmk cmk3
+SET SESSION AUTHORIZATION 'regress_enc_user1';
+DROP COLUMN ENCRYPTION KEY cek3;  -- ok now
+DROP COLUMN MASTER KEY cmk3;  -- ok now
+RESET SESSION AUTHORIZATION;
+
+ALTER COLUMN ENCRYPTION KEY cek1 DROP VALUE (column_master_key = cmk1a);
+ALTER COLUMN ENCRYPTION KEY cek1 DROP VALUE (column_master_key = cmk1a);  -- fail
+ALTER COLUMN ENCRYPTION KEY cek1 DROP VALUE (column_master_key = fail);  -- fail
+ALTER COLUMN ENCRYPTION KEY cek1 DROP VALUE (column_master_key = cmk1a, algorithm = 'foo');  -- fail
+
+DROP COLUMN ENCRYPTION KEY cek4;
+DROP COLUMN ENCRYPTION KEY fail;
+DROP COLUMN ENCRYPTION KEY IF EXISTS nonexistent;
+
+DROP COLUMN MASTER KEY cmk1a;
+DROP COLUMN MASTER KEY fail;
+DROP COLUMN MASTER KEY IF EXISTS nonexistent;
+
+DROP ROLE regress_enc_user1;
diff --git a/src/test/regress/sql/object_address.sql b/src/test/regress/sql/object_address.sql
index 1a6c61f49d..35af43032f 100644
--- a/src/test/regress/sql/object_address.sql
+++ b/src/test/regress/sql/object_address.sql
@@ -41,6 +41,8 @@ CREATE SERVER "integer" FOREIGN DATA WRAPPER addr_fdw;
 CREATE USER MAPPING FOR regress_addr_user SERVER "integer";
 ALTER DEFAULT PRIVILEGES FOR ROLE regress_addr_user IN SCHEMA public GRANT ALL ON TABLES TO regress_addr_user;
 ALTER DEFAULT PRIVILEGES FOR ROLE regress_addr_user REVOKE DELETE ON TABLES FROM regress_addr_user;
+CREATE COLUMN MASTER KEY addr_cmk;
+CREATE COLUMN ENCRYPTION KEY addr_cek WITH VALUES (column_master_key = addr_cmk, algorithm = 'RSAES_OAEP_SHA_1', encrypted_value = '');
 -- this transform would be quite unsafe to leave lying around,
 -- except that the SQL language pays no attention to transforms:
 CREATE TRANSFORM FOR int LANGUAGE SQL (
@@ -99,7 +101,8 @@ CREATE STATISTICS addr_nsp.gentable_stat ON a, b FROM addr_nsp.gentable;
         ('text search template'), ('text search configuration'),
         ('policy'), ('user mapping'), ('default acl'), ('transform'),
         ('operator of access method'), ('function of access method'),
-        ('publication namespace'), ('publication relation')
+        ('publication namespace'), ('publication relation'),
+        ('column encryption key'), ('column encryption key data'), ('column master key')
     LOOP
         FOR names IN VALUES ('{eins}'), ('{addr_nsp, zwei}'), ('{eins, zwei, drei}')
         LOOP
@@ -174,6 +177,9 @@ CREATE STATISTICS addr_nsp.gentable_stat ON a, b FROM addr_nsp.gentable;
     ('type', '{addr_nsp.genenum}', '{}'),
     ('cast', '{int8}', '{int4}'),
     ('collation', '{default}', '{}'),
+    ('column encryption key', '{addr_cek}', '{}'),
+    ('column encryption key data', '{addr_cek}', '{addr_cmk}'),
+    ('column master key', '{addr_cmk}', '{}'),
     ('table constraint', '{addr_nsp, gentable, a_chk}', '{}'),
     ('domain constraint', '{addr_nsp.gendomain}', '{domconstr}'),
     ('conversion', '{pg_catalog, koi8_r_to_mic}', '{}'),
@@ -228,6 +234,8 @@ CREATE STATISTICS addr_nsp.gentable_stat ON a, b FROM addr_nsp.gentable;
 DROP PUBLICATION addr_pub;
 DROP PUBLICATION addr_pub_schema;
 DROP SUBSCRIPTION regress_addr_sub;
+DROP COLUMN ENCRYPTION KEY addr_cek;
+DROP COLUMN MASTER KEY addr_cmk;
 
 DROP SCHEMA addr_nsp CASCADE;
 
@@ -247,6 +255,9 @@ CREATE STATISTICS addr_nsp.gentable_stat ON a, b FROM addr_nsp.gentable;
     ('pg_type'::regclass, 0, 0), -- no type
     ('pg_cast'::regclass, 0, 0), -- no cast
     ('pg_collation'::regclass, 0, 0), -- no collation
+    ('pg_colenckey'::regclass, 0, 0), -- no column encryption key
+    ('pg_colenckeydata'::regclass, 0, 0), -- no column encryption key data
+    ('pg_colmasterkey'::regclass, 0, 0), -- no column master key
     ('pg_constraint'::regclass, 0, 0), -- no constraint
     ('pg_conversion'::regclass, 0, 0), -- no conversion
     ('pg_attrdef'::regclass, 0, 0), -- no default attribute
diff --git a/src/test/regress/sql/type_sanity.sql b/src/test/regress/sql/type_sanity.sql
index 79ec410a6c..2ba4c9a545 100644
--- a/src/test/regress/sql/type_sanity.sql
+++ b/src/test/regress/sql/type_sanity.sql
@@ -544,6 +544,8 @@ CREATE TABLE tab_core_types AS SELECT
   WHERE oid < 16384 AND
     -- Exclude pseudotypes and composite types.
     typtype NOT IN ('p', 'c') AND
+    -- Exclude encryption internal types.
+    oid != ALL(ARRAY['pg_encrypted_det', 'pg_encrypted_rnd']::regtype[]) AND
     -- These reg* types cannot be pg_upgraded, so discard them.
     oid != ALL(ARRAY['regproc', 'regprocedure', 'regoper',
                      'regoperator', 'regconfig', 'regdictionary',

base-commit: 642e8821d713d75f142ef4eda14e490ba0fb810b
-- 
2.39.1



^ permalink  raw  reply  [nested|flat] 5+ messages in thread

* Re: Transparent column encryption
  2023-01-11 16:46 Re: Transparent column encryption vignesh C <[email protected]>
  2023-01-25 18:44 ` Re: Transparent column encryption Peter Eisentraut <[email protected]>
@ 2023-02-11 21:54   ` Mark Dilger <[email protected]>
  2023-02-22 10:29     ` Re: Transparent column encryption Peter Eisentraut <[email protected]>
  0 siblings, 1 reply; 5+ messages in thread

From: Mark Dilger @ 2023-02-11 21:54 UTC (permalink / raw)
  To: Peter Eisentraut <[email protected]>; +Cc: pgsql-hackers; vignesh C <[email protected]>



> On Jan 25, 2023, at 10:44 AM, Peter Eisentraut <[email protected]> wrote:
> 
> Here is a new patch.  Changes since v14:
> 
> - Fixed some typos (review by Justin Pryzby)
> - Fixed backward compat. psql and pg_dump (review by Justin Pryzby)
> - Doc additions (review by Jacob Champion)
> - Validate column_encryption option in libpq (review by Jacob Champion)
> - Handle column encryption in inheritance
> - Change CEKs and CMKs to live inside schemas<v15-0001-Transparent-column-encryption.patch>

Thanks Peter.  Here are some observations about the documentation in patch version 15.

In acronyms.sgml, the CEK and CMK entries should link to documentation, perhaps linkend="glossary-column-encryption-key" and linkend="glossary-column-master-key".  These glossary entries should in turn link to linkend="ddl-column-encryption".

In ddl.sgml, the sentence "forcing encryption of certain parameters in the client library (see its documentation)" should link to linkend="libpq-connect-column-encryption".

Did you intend the use of "transparent data encryption" (rather than "transparent column encryption") in datatype.sgml?  If so, what's the difference?

Is this feature intended to be available from ecpg?  If so, can we maybe include an example in 36.3.4. Prepared Statements showing how to pass the encrypted values securely.  If not, can we include verbiage about that limitation, so folks don't waste time trying to figure out how to do it?

The documentation for pg_dump (and pg_dumpall) now includes a --decrypt-encrypted-columns option, which I suppose requires cmklookup to first be configured, and for PGCMKLOOKUP to be exported.  There isn't anything in the pg_dump docs about this, though, so maybe a link to section 5.5.3 with a warning about not running pg_dump this way on the database server itself?

How does a psql user mark a parameter as having forced encryption?  A libpq user can specify this in the paramFormats array, but I don't see any syntax for doing this from psql.  None of the perl tap tests you've included appear to do this (except indirectly when calling test_client); grep'ing for the libpq error message "parameter with forced encryption is not to be encrypted" in the tests has no matches.  Is it just not possible?  I thought you'd mentioned some syntax for this when we spoke in person, but I don't see it now.

—
Mark Dilger
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company









^ permalink  raw  reply  [nested|flat] 5+ messages in thread

* Re: Transparent column encryption
  2023-01-11 16:46 Re: Transparent column encryption vignesh C <[email protected]>
  2023-01-25 18:44 ` Re: Transparent column encryption Peter Eisentraut <[email protected]>
  2023-02-11 21:54   ` Re: Transparent column encryption Mark Dilger <[email protected]>
@ 2023-02-22 10:29     ` Peter Eisentraut <[email protected]>
  0 siblings, 0 replies; 5+ messages in thread

From: Peter Eisentraut @ 2023-02-22 10:29 UTC (permalink / raw)
  To: Mark Dilger <[email protected]>; +Cc: pgsql-hackers; vignesh C <[email protected]>

On 11.02.23 22:54, Mark Dilger wrote:
> Thanks Peter.  Here are some observations about the documentation in patch version 15.
> 
> In acronyms.sgml, the CEK and CMK entries should link to documentation, perhaps linkend="glossary-column-encryption-key" and linkend="glossary-column-master-key".  These glossary entries should in turn link to linkend="ddl-column-encryption".
> 
> In ddl.sgml, the sentence "forcing encryption of certain parameters in the client library (see its documentation)" should link to linkend="libpq-connect-column-encryption".
> 
> Did you intend the use of "transparent data encryption" (rather than "transparent column encryption") in datatype.sgml?  If so, what's the difference?

There are all addressed in the v16 patch I just posted.

> Is this feature intended to be available from ecpg?  If so, can we maybe include an example in 36.3.4. Prepared Statements showing how to pass the encrypted values securely.  If not, can we include verbiage about that limitation, so folks don't waste time trying to figure out how to do it?

It should "just work".  I will give this a try sometime, but I don't see 
why it wouldn't work.

> The documentation for pg_dump (and pg_dumpall) now includes a --decrypt-encrypted-columns option, which I suppose requires cmklookup to first be configured, and for PGCMKLOOKUP to be exported.  There isn't anything in the pg_dump docs about this, though, so maybe a link to section 5.5.3 with a warning about not running pg_dump this way on the database server itself?

Also addressed in v16.

> How does a psql user mark a parameter as having forced encryption?  A libpq user can specify this in the paramFormats array, but I don't see any syntax for doing this from psql.  None of the perl tap tests you've included appear to do this (except indirectly when calling test_client); grep'ing for the libpq error message "parameter with forced encryption is not to be encrypted" in the tests has no matches.  Is it just not possible?  I thought you'd mentioned some syntax for this when we spoke in person, but I don't see it now.

This has been asked about before.  We just need to come up with a syntax 
for it.  The issue is contained inside psql.








^ permalink  raw  reply  [nested|flat] 5+ messages in thread


end of thread, other threads:[~2023-02-22 10:29 UTC | newest]

Thread overview: 5+ messages (download: mbox mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
2020-08-03 16:32 [PATCH] WIP Support libnss for as TLS backend v7 Andrew Dunstan <[email protected]>
2023-01-11 16:46 Re: Transparent column encryption vignesh C <[email protected]>
2023-01-25 18:44 ` Re: Transparent column encryption Peter Eisentraut <[email protected]>
2023-02-11 21:54   ` Re: Transparent column encryption Mark Dilger <[email protected]>
2023-02-22 10:29     ` Re: Transparent column encryption Peter Eisentraut <[email protected]>

This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox