From 2a976115128b61efd9f85bbc045c48d2679d9f82 Mon Sep 17 00:00:00 2001
From: Masahiko Sawada <sawada.mshk@gmail.com>
Date: Tue, 19 May 2020 16:57:57 +0900
Subject: [PATCH v11 7/7] POC: libpq encryption protocol.

---
 src/bin/psql/command.c              |  46 +++++++++++
 src/interfaces/libpq/exports.txt    |   1 +
 src/interfaces/libpq/fe-exec.c      | 119 ++++++++++++++++++++++++++++
 src/interfaces/libpq/fe-protocol3.c |   6 +-
 src/interfaces/libpq/libpq-fe.h     |   1 +
 src/interfaces/libpq/libpq-int.h    |   2 +-
 6 files changed, 172 insertions(+), 3 deletions(-)

diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index a5160f91de..3039abbc42 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -129,6 +129,8 @@ static backslashResult exec_command_write(PsqlScanState scan_state, bool active_
 										  PQExpBuffer query_buf, PQExpBuffer previous_buf);
 static backslashResult exec_command_watch(PsqlScanState scan_state, bool active_branch,
 										  PQExpBuffer query_buf, PQExpBuffer previous_buf);
+static backslashResult exec_command_cipher(PsqlScanState scan_state, bool active_branch,
+										   bool for_enc);
 static backslashResult exec_command_x(PsqlScanState scan_state, bool active_branch);
 static backslashResult exec_command_z(PsqlScanState scan_state, bool active_branch);
 static backslashResult exec_command_shell_escape(PsqlScanState scan_state, bool active_branch);
@@ -398,6 +400,8 @@ exec_command(const char *cmd,
 	else if (strcmp(cmd, "watch") == 0)
 		status = exec_command_watch(scan_state, active_branch,
 									query_buf, previous_buf);
+	else if (strcmp(cmd, "encrypt") == 0)
+		status = exec_command_cipher(scan_state, active_branch, true);
 	else if (strcmp(cmd, "x") == 0)
 		status = exec_command_x(scan_state, active_branch);
 	else if (strcmp(cmd, "z") == 0)
@@ -2656,6 +2660,48 @@ exec_command_watch(PsqlScanState scan_state, bool active_branch,
 	return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR;
 }
 
+static backslashResult
+exec_command_cipher(PsqlScanState scan_state, bool active_branch, bool for_enc)
+{
+	bool success = true;
+
+	if (active_branch)
+	{
+		char token1[128];
+		char token2[128];
+		char *res;
+
+		simple_prompt("Enter data: ", token1, sizeof(token1), false);
+		simple_prompt("Enter it again: ", token2, sizeof(token2), false);
+
+		if (strcmp(token1, token2) != 0)
+		{
+			pg_log_error("Data didn't match.");
+			success = false;
+		}
+		else
+		{
+			res = PQencrypt(pset.db, token1);
+
+			if (!res)
+			{
+				pg_log_info("%s", PQerrorMessage(pset.db));
+				success = false;
+			}
+			else
+			{
+				fprintf(stdout, "encrypted data: %s\n", res);
+				fflush(stdout);
+				free(res);
+			}
+		}
+	}
+	else
+		ignore_slash_options(scan_state);
+
+	return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR;
+}
+
 /*
  * \x -- set or toggle expanded table representation
  */
diff --git a/src/interfaces/libpq/exports.txt b/src/interfaces/libpq/exports.txt
index bbc1f90481..a3a84951dc 100644
--- a/src/interfaces/libpq/exports.txt
+++ b/src/interfaces/libpq/exports.txt
@@ -179,3 +179,4 @@ PQgetgssctx               176
 PQsetSSLKeyPassHook_OpenSSL         177
 PQgetSSLKeyPassHook_OpenSSL         178
 PQdefaultSSLKeyPassHook_OpenSSL     179
+PQencrypt                 180
diff --git a/src/interfaces/libpq/fe-exec.c b/src/interfaces/libpq/fe-exec.c
index eea0237c3a..67d291696d 100644
--- a/src/interfaces/libpq/fe-exec.c
+++ b/src/interfaces/libpq/fe-exec.c
@@ -27,6 +27,7 @@
 #include "libpq-fe.h"
 #include "libpq-int.h"
 #include "mb/pg_wchar.h"
+#include "common/aead.h"
 
 /* keep this in same order as ExecStatusType in libpq-fe.h */
 char	   *const pgresStatus[] = {
@@ -69,6 +70,13 @@ static bool PQexecStart(PGconn *conn);
 static PGresult *PQexecFinish(PGconn *conn);
 static int	PQsendDescribe(PGconn *conn, char desc_type,
 						   const char *desc_target);
+static PGresult *PQfnText(PGconn *conn,
+						  int fnid,
+						  int *result_buf,
+						  int *result_len,
+						  int result_is_int,
+						  const PQArgBlock *args,
+						  int nargs);
 static int	check_field_number(const PGresult *res, int field_num);
 
 
@@ -2677,6 +2685,7 @@ PQfn(PGconn *conn,
 		return pqFunctionCall3(conn, fnid,
 							   result_buf, result_len,
 							   result_is_int,
+							   1,	/* output format is binary */
 							   args, nargs);
 	else
 		return pqFunctionCall2(conn, fnid,
@@ -2685,6 +2694,45 @@ PQfn(PGconn *conn,
 							   args, nargs);
 }
 
+static PGresult *
+PQfnText(PGconn *conn,
+		 int fnid,
+		 int *result_buf,
+		 int *result_len,
+		 int result_is_int,
+		 const PQArgBlock *args,
+		 int nargs)
+{
+	*result_len = 0;
+
+	if (!conn)
+		return NULL;
+
+	/* This isn't gonna work on a 2.0 server */
+	if (PG_PROTOCOL_MAJOR(conn->pversion) < 3)
+	{
+		printfPQExpBuffer(&conn->errorMessage,
+						  libpq_gettext("function requires at least protocol version 3.0\n"));
+		return NULL;
+	}
+
+	/* clear the error string */
+	resetPQExpBuffer(&conn->errorMessage);
+
+	if (conn->sock == PGINVALID_SOCKET || conn->asyncStatus != PGASYNC_IDLE ||
+		conn->result != NULL)
+	{
+		printfPQExpBuffer(&conn->errorMessage,
+						  libpq_gettext("connection in wrong state\n"));
+		return NULL;
+	}
+
+	return pqFunctionCall3(conn, fnid,
+						   result_buf, result_len,
+						   result_is_int,
+						   0,	/* output format is text */
+						   args, nargs);
+}
 
 /* ====== accessor funcs for PGresult ======== */
 
@@ -3858,3 +3906,74 @@ PQunescapeBytea(const unsigned char *strtext, size_t *retbuflen)
 	*retbuflen = buflen;
 	return tmpbuf;
 }
+
+char *
+PQencrypt(PGconn *conn, const char *token)
+{
+	static Oid	foid = InvalidOid;
+	PQArgBlock	argv[1];
+	PGresult	*res;
+	int			reslen;
+	int			tokenlen;
+	int			*buf;
+
+	/* This isn't gonna work on a 2.0 server */
+	if (PG_PROTOCOL_MAJOR(conn->pversion) < 3)
+	{
+		printfPQExpBuffer(&conn->errorMessage,
+						  libpq_gettext("function requires at least protocol version 3.0\n"));
+		return 0;
+	}
+
+	if (!OidIsValid(foid))
+	{
+		const char *query;
+		char *fname;
+
+		query = "select proname, oid from pg_proc "
+			"where proname = 'pg_encrypt'";
+		res = PQexec(conn, query);
+
+		if (res == NULL)
+		{
+			PQclear(res);
+			printfPQExpBuffer(&conn->errorMessage,
+							  libpq_gettext("query to initialize encryption functions did not return data\n"));
+			return NULL;
+		}
+
+		fname = PQgetvalue(res, 0, 0);
+
+		if (PQntuples(res) > 1 || strcmp(fname, "pg_encrypt") != 0)
+		{
+			PQclear(res);
+			printfPQExpBuffer(&conn->errorMessage,
+							  libpq_gettext("query to initialize encryption functions returned invalid data\n"));
+			return NULL;
+		}
+
+		/* get OID of pg_encrypt function */
+		foid = (Oid) atoi(PQgetvalue(res, 0, 1));
+
+		PQclear(res);
+	}
+
+	tokenlen = strlen(token);
+	buf = (int *) malloc(AEADSizeOfCipherText(tokenlen));
+
+	argv[0].isint = 0;
+	argv[0].len = strlen(token);
+	argv[0].u.ptr = (int *) token;
+	res = PQfnText(conn, foid, buf, &reslen, 0, argv, 1);
+
+	if (PQresultStatus(res) == PGRES_COMMAND_OK)
+	{
+		PQclear(res);
+		return (char *) buf;
+	}
+	else
+	{
+		PQclear(res);
+		return NULL;
+	}
+}
diff --git a/src/interfaces/libpq/fe-protocol3.c b/src/interfaces/libpq/fe-protocol3.c
index 1696525475..2179143a11 100644
--- a/src/interfaces/libpq/fe-protocol3.c
+++ b/src/interfaces/libpq/fe-protocol3.c
@@ -1922,7 +1922,7 @@ pqEndcopy3(PGconn *conn)
 PGresult *
 pqFunctionCall3(PGconn *conn, Oid fnid,
 				int *result_buf, int *actual_result_len,
-				int result_is_int,
+				int result_is_int, int result_format,
 				const PQArgBlock *args, int nargs)
 {
 	bool		needInput = false;
@@ -1930,6 +1930,7 @@ pqFunctionCall3(PGconn *conn, Oid fnid,
 	char		id;
 	int			msgLength;
 	int			avail;
+	int			format;
 	int			i;
 
 	/* PQfn already validated connection state */
@@ -1963,7 +1964,8 @@ pqFunctionCall3(PGconn *conn, Oid fnid,
 		}
 	}
 
-	if (pqPutInt(1, 2, conn) < 0)	/* result format code: BINARY */
+	format = result_is_int ? 1 : result_format;
+	if (pqPutInt(format, 2, conn) < 0)	/* 0: TEXT, 1: BINARY */
 		return NULL;
 
 	if (pqPutMsgEnd(conn) < 0 ||
diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h
index 3b6a9fbce3..2e5ae05ef8 100644
--- a/src/interfaces/libpq/libpq-fe.h
+++ b/src/interfaces/libpq/libpq-fe.h
@@ -457,6 +457,7 @@ extern int	PQisthreadsafe(void);
 extern PGPing PQping(const char *conninfo);
 extern PGPing PQpingParams(const char *const *keywords,
 						   const char *const *values, int expand_dbname);
+extern char *PQencrypt(PGconn *conn, const char *token);
 
 /* Force the write buffer to be written (or at least try) */
 extern int	PQflush(PGconn *conn);
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index 1de91ae295..f479aa8412 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -643,7 +643,7 @@ extern int	pqGetlineAsync3(PGconn *conn, char *buffer, int bufsize);
 extern int	pqEndcopy3(PGconn *conn);
 extern PGresult *pqFunctionCall3(PGconn *conn, Oid fnid,
 								 int *result_buf, int *actual_result_len,
-								 int result_is_int,
+								 int result_is_int, int result_format,
 								 const PQArgBlock *args, int nargs);
 
 /* === in fe-misc.c === */
-- 
2.23.0

