Add key management system

This adds a key management system that stores (currently) two data
encryption keys of length 128, 192, or 256 bits.  The data keys are
AES256 encrypted using a key encryption key, and validated via GCM
cipher mode.  A command to obtain the key encryption key must be
specified at initdb time, and will be run at every database server
start.  New parameters allow a file descriptor open to the terminal to
be passed.  pg_upgrade support has also been added.

Discussion: https://postgr.es/m/CA+fd4k7q5o6Nc_AaX6BcYM9yqTbC6_pnH-6nSD=54Zp6NBQTCQ@mail.gmail.com
Discussion: https://postgr.es/m/20201202213814.GG20285@momjian.us

Author: Masahiko Sawada, me, Stephen Frost
This commit is contained in:
Bruce Momjian 2020-12-25 10:19:44 -05:00
parent 5c31afc49d
commit 978f869b99
49 changed files with 2091 additions and 35 deletions

View File

@ -7816,6 +7816,52 @@ COPY postgres_log FROM '/full/path/to/logfile.csv' WITH csv;
</variablelist>
</sect1>
<sect1 id="runtime-config-encryption">
<title>Cluster File Encryption</title>
<variablelist>
<varlistentry id="guc-cluster-key-command" xreflabel="cluster_key_command">
<term><varname>cluster_key_command</varname> (<type>string</type>)
<indexterm>
<primary><varname>cluster_key_command</varname> configuration parameter</primary>
</indexterm>
</term>
<listitem>
<para>
This option specifies an external command to obtain the cluster-level
key for cluster file encryption during server initialization and
server start.
</para>
<para>
The command must print the cluster key to the standard output as
64 hexadecimal characters, and exit with code 0. The command
can prompt for the passphrase or PIN from the terminal if
<option>--authprompt</option> is used. In the parameter value,
<literal>%R</literal> represents the file descriptor number opened
to the terminal that started the server. A file descriptor is only
available if enabled at server start. If <literal>%R</literal>
is used and no file descriptor is available, the server will not
start. Value <literal>%p</literal> is replaced by a pre-defined
prompt string. Value <literal>%d</literal> is replaced by the
directory containing the keys; this is useful if the command
must create files with the keys, e.g., to store a cluster-level
key encryped by a key stored in a hardware security module.
(Write <literal>%%</literal> for a literal <literal>%</literal>.)
Note that the prompt string will probably contain whitespace,
so be sure to quote its use adequately. Newlines are stripped
from the end of the output if present.
</para>
<para>
This parameter can only be set by
<application>initdb</application>, in the
<filename>postgresql.conf</filename> file, or on the server
command line.
</para>
</listitem>
</varlistentry>
</variablelist>
</sect1>
<sect1 id="runtime-config-client">
<title>Client Connection Defaults</title>
@ -9637,6 +9683,22 @@ dynamic_library_path = 'C:\tools\postgresql;H:\my_project\lib;$libdir'
</listitem>
</varlistentry>
<varlistentry id="guc-file-encryption-keylen" xreflabel="file_encryption_keylen">
<term><varname>file_encryption_keylen</varname> (<type>boolean</type>)
<indexterm>
<primary>Cluster file encryption key length</primary>
</indexterm>
</term>
<listitem>
<para>
Reports the bit length of the cluster file
encryption key, or zero if disabled. See <xref
linkend="app-initdb-cluster-key-command"/> for more
information.
</para>
</listitem>
</varlistentry>
<varlistentry id="guc-data-directory-mode" xreflabel="data_directory_mode">
<term><varname>data_directory_mode</varname> (<type>integer</type>)
<indexterm>

View File

@ -0,0 +1,97 @@
<!-- doc/src/sgml/database-encryption.sgml -->
<chapter id="database-file-encryption">
<title>Cluster File Encryption</title>
<indexterm zone="database-file-encryption">
<primary>Cluster File Encryption</primary>
</indexterm>
<para>
The purpose of cluster file encryption is to prevent users with read
access to the directories used to store database files and write-ahead
log from being able to access the data stored in those files.
For example, when using cluster file encryption, users who have read
access to the cluster directories for backup purposes will not be able
to decrypt the data stored in the these files.
</para>
<para>
Cluster file encryption uses two levels of encryption. The first level
is data encryption keys, specifically keys zero and one. Key zero is
the key used to encrypt database heap and index files which are stored in
the file system, plus temporary files created during database operation.
Key one is used to encrypt write-ahead log (WAL) files. Two different
keys are used so that primary and standby servers can use different zero
(heap/index/temp) keys, but the same one (WAL) key, so that these keys
can eventually be rotated by switching the primary to the standby as
and then changing the WAL key.
</para>
<para>
The second level of encryption is a key used to encrypt first-level
keys. This type of key is often referred to as a Key Encryption Key
(<acronym>KEK</acronym>). This key is <emphasis>not</emphasis> stored
in the file system, but provided at <command>initdb</command> time and
each time the server is started. This key prevents anyone with access
to the database directories from decrypting the data because they do
not know the second-level key which encrypted the first-level keys
which encrypted the database cluster files.
</para>
<sect1 id="encryption-file-encryption">
<title>Initialization</title>
<para>
Cluster file encryption is enabled when
<productname>PostgreSQL</productname> is built
with <literal>--with-openssl</literal> and <xref
linkend="app-initdb-cluster-key-command"/> is specified
during <command>initdb</command>. The cluster key
provided by the <option>--cluster-key-command</option>
option during <command>initdb</command> and the one generated
by <xref linkend="guc-cluster-key-command"/> in the
<filename>postgresql.conf</filename> must match for the database
cluster to start. Note that the cluster key command
passed to <command>initdb</command> must return a key of
64 hexadecimal characters. For example.
<programlisting>
initdb -D dbname --cluster-key-command='ckey_passphrase.sh'
</programlisting>
</para>
</sect1>
<sect1 id="key-encryption-key">
<title>Internals</title>
<para>
During the <command>initdb</command> process, if
<option>--cluster-key-command</option> is specified, two data-level
encryption keys are created. These two keys are then encrypted with
the key enryption key (KEK) supplied by the cluster key command before
being stored in the database directory. The key or passphrase that
derives the key must be supplied from the terminal or stored in a
trusted key store, such as key vault software, hardware security module.
</para>
<para>
If the <productname>PostgreSQL</productname> server has
been initialized to require a cluster key, each time the
server starts the <filename>postgresql.conf</filename>
<varname>cluster_key_command</varname> command will be executed
and the cluster key retrieved. The data encryption keys in the
<filename>pg_cryptokeys</filename> directory will then be decrypted
using the supplied key and integrity-checked to ensure it
matches the initdb-supplied key. If this check fails, the
server will refuse to start.
</para>
<para>
The data encryption keys are randomly generated and are of 128, 192,
or 256-bits in length. They are encrypted by the key encryption key
(KEK) using Advanced Encryption Standard (<acronym>AES256</acronym>)
encryption in Galois/Counter Mode (<acronym>GCM</acronym>), which also
provides KEK authentication.
</para>
</sect1>
</chapter>

View File

@ -49,6 +49,7 @@
<!ENTITY wal SYSTEM "wal.sgml">
<!ENTITY logical-replication SYSTEM "logical-replication.sgml">
<!ENTITY jit SYSTEM "jit.sgml">
<!ENTITY database-encryption SYSTEM "database-encryption.sgml">
<!-- programmer's guide -->
<!ENTITY bgworker SYSTEM "bgworker.sgml">

View File

@ -976,8 +976,9 @@ build-postgresql:
<listitem>
<para>
Build with support for <acronym>SSL</acronym> (encrypted)
connections. This requires the <productname>OpenSSL</productname>
package to be installed. <filename>configure</filename> will check
connections and cluster file encryption. This requires the
<productname>OpenSSL</productname> package to be installed.
<filename>configure</filename> will check
for the required header files and libraries to make sure that
your <productname>OpenSSL</productname> installation is sufficient
before proceeding.

View File

@ -171,6 +171,7 @@ break is not needed in a wider output rendering.
&wal;
&logical-replication;
&jit;
&database-encryption;
&regress;
</part>

View File

@ -163,6 +163,17 @@ PostgreSQL documentation
</listitem>
</varlistentry>
<varlistentry id="app-initdb-cluster-key-command" xreflabel="cluster key command">
<term><option>--cluster-key-command=<replaceable class="parameter">command</replaceable></option></term>
<listitem>
<para>
This option specifies an external command to obtain the cluster-level
key for cluster file encryption during server initialization and
server start; see <xref linkend="guc-cluster-key-command"/> for details.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><option>-D <replaceable class="parameter">directory</replaceable></option></term>
<term><option>--pgdata=<replaceable class="parameter">directory</replaceable></option></term>
@ -223,6 +234,18 @@ PostgreSQL documentation
</listitem>
</varlistentry>
<varlistentry id="app-initdb-file-encryption-keylen"
xreflabel="file encryption">
<term><option>-K</option></term>
<term><option>--file-encryption-keylen</option></term>
<listitem>
<para>
Specifies the number of bits for the file encryption keys. The
default is 128 bits.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><option>--locale=<replaceable>locale</replaceable></option></term>
<listitem>
@ -285,6 +308,17 @@ PostgreSQL documentation
</listitem>
</varlistentry>
<varlistentry>
<term><option>-R</option></term>
<term><option>--authprompt</option></term>
<listitem>
<para>
Allows the <option>--cluster-key-command</option> command
to prompt for a passphrase or PIN.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><option>-S</option></term>
<term><option>--sync-only</option></term>
@ -307,6 +341,18 @@ PostgreSQL documentation
</listitem>
</varlistentry>
<varlistentry>
<term><option>-u <replaceable>datadir</replaceable></option></term>
<term><option>--copy-encryption-keys=<replaceable>datadir</replaceable></option></term>
<listitem>
<para>
Copies cluster file encryption keys from another cluster; required
when using <application>pg_upgrade</application> on a cluster
with cluster file encryption enabled.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><option>-U <replaceable class="parameter">username</replaceable></option></term>
<term><option>--username=<replaceable class="parameter">username</replaceable></option></term>

View File

@ -38,6 +38,7 @@ PostgreSQL documentation
<arg choice="opt"><option>-s</option></arg>
<arg choice="opt"><option>-o</option> <replaceable>options</replaceable></arg>
<arg choice="opt"><option>-p</option> <replaceable>path</replaceable></arg>
<arg choice="opt"><option>-R</option></arg>
<arg choice="opt"><option>-c</option></arg>
</cmdsynopsis>
@ -72,6 +73,7 @@ PostgreSQL documentation
<arg choice="opt"><option>-t</option> <replaceable>seconds</replaceable></arg>
<arg choice="opt"><option>-s</option></arg>
<arg choice="opt"><option>-o</option> <replaceable>options</replaceable></arg>
<arg choice="opt"><option>-R</option></arg>
<arg choice="opt"><option>-c</option></arg>
</cmdsynopsis>
@ -373,6 +375,17 @@ PostgreSQL documentation
</listitem>
</varlistentry>
<varlistentry>
<term><option>-R</option></term>
<term><option>--authprompt</option></term>
<listitem>
<para>
Allows the <option>--cluster-key-command</option> command
to prompt for a passphrase or PIN.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><option>-s</option></term>
<term><option>--silent</option></term>

View File

@ -167,6 +167,13 @@ PostgreSQL documentation
</para></listitem>
</varlistentry>
<varlistentry>
<term><option>-R</option></term>
<term><option>--authprompt</option></term>
<listitem><para>allows prompting for a passphrase or PIN
</para></listitem>
</varlistentry>
<varlistentry>
<term><option>-s</option> <replaceable>dir</replaceable></term>
<term><option>--socketdir=</option><replaceable>dir</replaceable></term>
@ -309,7 +316,9 @@ make prefix=/usr/local/pgsql.new install
Again, use compatible <command>initdb</command>
flags that match the old cluster. Many
prebuilt installers do this step automatically. There is no need to
start the new cluster.
start the new cluster. If upgrading a cluster that uses
cluster file encryption, the <command>initdb</command> option
<option>--copy-encryption-keys</option> must be specified.
</para>
</step>
@ -838,6 +847,13 @@ psql --username=postgres --file=script.sql postgres
is down.
</para>
<para>
If the old cluster uses file encryption, the new cluster must use
the same keys, so <command>pg_upgrade</command> copies them to the
new cluster. It is necessary to initialize the new cluster with
the same <varname>cluster_key_command</varname> and the same
file encryption key length.
</para>
</refsect1>
<refsect1>

View File

@ -297,6 +297,19 @@ PostgreSQL documentation
</listitem>
</varlistentry>
<varlistentry>
<term><option>-R <replaceable class="parameter">file-descriptor</replaceable></option></term>
<listitem>
<para>
Makes <command>postgres</command> prompt for a passphrase or PIN
from the specified open numeric file descriptor. The descriptor
is closed after the key is read. The file descriptor number
<literal>-1</literal> duplicates standard error for the terminal;
this is useful for single-user mode.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><option>-s</option></term>
<listitem>

View File

@ -77,6 +77,11 @@ Item
<entry>Subdirectory containing transaction commit timestamp data</entry>
</row>
<row>
<entry><filename>pg_cryptokeys</filename></entry>
<entry>Subdirectory containing file encryption keys</entry>
</row>
<row>
<entry><filename>pg_dynshmem</filename></entry>
<entry>Subdirectory containing files used by the dynamic shared memory

View File

@ -21,7 +21,7 @@ SUBDIRS = access bootstrap catalog parser commands executor foreign lib libpq \
main nodes optimizer partitioning port postmaster \
regex replication rewrite \
statistics storage tcop tsearch utils $(top_builddir)/src/timezone \
jit
jit crypto
include $(srcdir)/common.mk

View File

@ -44,11 +44,13 @@
#include "commands/tablespace.h"
#include "common/controldata_utils.h"
#include "executor/instrument.h"
#include "crypto/kmgr.h"
#include "miscadmin.h"
#include "pg_trace.h"
#include "pgstat.h"
#include "port/atomics.h"
#include "postmaster/bgwriter.h"
#include "postmaster/postmaster.h"
#include "postmaster/startup.h"
#include "postmaster/walwriter.h"
#include "replication/basebackup.h"
@ -81,6 +83,7 @@
#include "utils/timestamp.h"
extern uint32 bootstrap_data_checksum_version;
extern int bootstrap_file_encryption_keylen;
/* Unsupported old recovery command file names (relative to $PGDATA) */
#define RECOVERY_COMMAND_FILE "recovery.conf"
@ -4618,6 +4621,7 @@ InitControlFile(uint64 sysidentifier)
ControlFile->wal_log_hints = wal_log_hints;
ControlFile->track_commit_timestamp = track_commit_timestamp;
ControlFile->data_checksum_version = bootstrap_data_checksum_version;
ControlFile->file_encryption_keylen = bootstrap_file_encryption_keylen;
}
static void
@ -4717,6 +4721,7 @@ ReadControlFile(void)
pg_crc32c crc;
int fd;
static char wal_segsz_str[20];
static char file_encryption_keylen_str[20];
int r;
/*
@ -4905,6 +4910,12 @@ ReadControlFile(void)
/* Make the initdb settings visible as GUC variables, too */
SetConfigOption("data_checksums", DataChecksumsEnabled() ? "yes" : "no",
PGC_INTERNAL, PGC_S_OVERRIDE);
Assert(ControlFile != NULL);
snprintf(file_encryption_keylen_str, sizeof(file_encryption_keylen_str), "%d",
ControlFile->file_encryption_keylen);
SetConfigOption("file_encryption_keylen", file_encryption_keylen_str, PGC_INTERNAL,
PGC_S_OVERRIDE);
}
/*
@ -5354,6 +5365,16 @@ BootStrapXLOG(void)
/* some additional ControlFile fields are set in WriteControlFile() */
WriteControlFile();
/* Enable file encryption if required */
if (ControlFile->file_encryption_keylen > 0)
BootStrapKmgr();
if (terminal_fd != -1)
{
close(terminal_fd);
terminal_fd = -1;
}
/* Bootstrap the commit log, too */
BootStrapCLOG();
BootStrapCommitTs();

View File

@ -28,12 +28,14 @@
#include "catalog/pg_collation.h"
#include "catalog/pg_type.h"
#include "common/link-canary.h"
#include "crypto/kmgr.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "pg_getopt.h"
#include "pgstat.h"
#include "postmaster/bgwriter.h"
#include "postmaster/postmaster.h"
#include "postmaster/startup.h"
#include "postmaster/walwriter.h"
#include "replication/walreceiver.h"
@ -51,6 +53,8 @@
#include "utils/relmapper.h"
uint32 bootstrap_data_checksum_version = 0; /* No checksum */
int bootstrap_file_encryption_keylen = 0; /* disabled */
char *bootstrap_old_key_datadir = NULL; /* disabled */
static void CheckerModeMain(void);
@ -224,7 +228,7 @@ AuxiliaryProcessMain(int argc, char *argv[])
/* If no -x argument, we are a CheckerProcess */
MyAuxProcType = CheckerProcess;
while ((flag = getopt(argc, argv, "B:c:d:D:Fkr:x:X:-:")) != -1)
while ((flag = getopt(argc, argv, "B:c:d:D:FkK:r:R:u:x:X:-:")) != -1)
{
switch (flag)
{
@ -253,9 +257,18 @@ AuxiliaryProcessMain(int argc, char *argv[])
case 'k':
bootstrap_data_checksum_version = PG_DATA_CHECKSUM_VERSION;
break;
case 'K':
bootstrap_file_encryption_keylen = atoi(optarg);
break;
case 'u':
bootstrap_old_key_datadir = pstrdup(optarg);
break;
case 'r':
strlcpy(OutputFileName, optarg, MAXPGPATH);
break;
case 'R':
terminal_fd = atoi(optarg);
break;
case 'x':
MyAuxProcType = atoi(optarg);
break;
@ -312,6 +325,12 @@ AuxiliaryProcessMain(int argc, char *argv[])
proc_exit(1);
}
if (bootstrap_file_encryption_keylen != 0 &&
bootstrap_file_encryption_keylen != 128 &&
bootstrap_file_encryption_keylen != 192 &&
bootstrap_file_encryption_keylen != 256)
elog(PANIC, "unrecognized file encryption length: %d", bootstrap_file_encryption_keylen);
switch (MyAuxProcType)
{
case StartupProcess:

View File

@ -0,0 +1,18 @@
#-------------------------------------------------------------------------
#
# Makefile
# Makefile for src/backend/crypto
#
# IDENTIFICATION
# src/backend/crypto/Makefile
#
#-------------------------------------------------------------------------
subdir = src/backend/crypto
top_builddir = ../../..
include $(top_builddir)/src/Makefile.global
OBJS = \
kmgr.o
include $(top_srcdir)/src/backend/common.mk

372
src/backend/crypto/kmgr.c Normal file
View File

@ -0,0 +1,372 @@
/*-------------------------------------------------------------------------
*
* kmgr.c
* Cluster file encryption routines
*
* Cluster file encryption is enabled if user requests it during initdb.
* During bootstrap, we generate data encryption keys, wrap them with the
* cluster-level key, and store them into each file located at KMGR_DIR.
* Once generated, these are not changed. During startup, we decrypt all
* internal keys and load them to the shared memory space. Internal keys
* on the shared memory are read-only. All wrapping and unwrapping key
* routines require the OpenSSL library.
*
* Copyright (c) 2020, PostgreSQL Global Development Group
*
* IDENTIFICATION
* src/backend/crypto/kmgr.c
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include <sys/stat.h>
#include <unistd.h>
#include "funcapi.h"
#include "miscadmin.h"
#include "pgstat.h"
#include "common/file_perm.h"
#include "common/hex_decode.h"
#include "common/kmgr_utils.h"
#include "common/sha2.h"
#include "access/xlog.h"
#include "crypto/kmgr.h"
#include "storage/copydir.h"
#include "storage/fd.h"
#include "storage/ipc.h"
#include "storage/shmem.h"
#include "utils/builtins.h"
#include "utils/memutils.h"
/* Struct stores file encryption keys in plaintext format */
typedef struct KmgrShmemData
{
CryptoKey intlKeys[KMGR_MAX_INTERNAL_KEYS];
} KmgrShmemData;
static KmgrShmemData *KmgrShmem;
/* GUC variables */
char *cluster_key_command = NULL;
int file_encryption_keylen = 0;
CryptoKey bootstrap_keys[KMGR_MAX_INTERNAL_KEYS];
extern char *bootstrap_old_key_datadir;
extern int bootstrap_file_encryption_keylen;
static void bzeroKmgrKeys(int status, Datum arg);
static void KmgrSaveCryptoKeys(const char *dir, CryptoKey *keys);
static CryptoKey *generate_crypto_key(int len);
/*
* This function must be called ONCE during initdb.
*/
void
BootStrapKmgr(void)
{
char live_path[MAXPGPATH];
CryptoKey *keys_wrap;
int nkeys;
char cluster_key_hex[ALLOC_KMGR_CLUSTER_KEY_LEN];
int cluster_key_hex_len;
unsigned char cluster_key[KMGR_CLUSTER_KEY_LEN];
#ifndef USE_OPENSSL
ereport(ERROR,
(errcode(ERRCODE_CONFIG_FILE_ERROR),
(errmsg("cluster file encryption is not supported because OpenSSL is not supported by this build"),
errhint("Compile with --with-openssl to use this feature."))));
#endif
snprintf(live_path, sizeof(live_path), "%s/%s", DataDir, LIVE_KMGR_DIR);
/* copy cluster file encryption keys from an old cluster? */
if (bootstrap_old_key_datadir != NULL)
{
char old_key_dir[MAXPGPATH];
snprintf(old_key_dir, sizeof(old_key_dir), "%s/%s",
bootstrap_old_key_datadir, LIVE_KMGR_DIR);
copydir(old_key_dir, LIVE_KMGR_DIR, true);
}
/* create empty directory */
else
{
if (mkdir(LIVE_KMGR_DIR, pg_dir_create_mode) < 0)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not create cluster file encryption directory \"%s\": %m",
LIVE_KMGR_DIR)));
}
/*
* Get key encryption key from the cluster_key command. The cluster_key
* command might want to check for the existance of files in the
* live directory, so run this _after_ copying the directory in place.
*/
cluster_key_hex_len = kmgr_run_cluster_key_command(cluster_key_command,
cluster_key_hex,
ALLOC_KMGR_CLUSTER_KEY_LEN,
live_path);
if (hex_decode(cluster_key_hex, cluster_key_hex_len, (char*) cluster_key) !=
KMGR_CLUSTER_KEY_LEN)
ereport(ERROR,
(errmsg("cluster key must be %d hexadecimal characters",
KMGR_CLUSTER_KEY_LEN * 2)));
/* generate new cluster file encryption keys */
if (bootstrap_old_key_datadir == NULL)
{
CryptoKey bootstrap_keys_wrap[KMGR_MAX_INTERNAL_KEYS];
PgCipherCtx *cluster_key_ctx;
/* Create KEK encryption context */
cluster_key_ctx = pg_cipher_ctx_create(PG_CIPHER_AES_GCM, cluster_key,
KMGR_CLUSTER_KEY_LEN, true);
if (!cluster_key_ctx)
elog(ERROR, "could not initialize encryption context");
/* Wrap all data encryption keys by key encryption key */
for (int id = 0; id < KMGR_MAX_INTERNAL_KEYS; id++)
{
CryptoKey *key;
/* generate a data encryption key */
key = generate_crypto_key(bootstrap_file_encryption_keylen);
/* Set this key's ID */
key->pgkey_id = id;
if (!kmgr_wrap_key(cluster_key_ctx, key, &(bootstrap_keys_wrap[id])))
{
pg_cipher_ctx_free(cluster_key_ctx);
elog(ERROR, "failed to wrap data encryption key");
}
explicit_bzero(key, sizeof(CryptoKey));
}
/* Save data encryption keys to the disk */
KmgrSaveCryptoKeys(LIVE_KMGR_DIR, bootstrap_keys_wrap);
explicit_bzero(bootstrap_keys_wrap, sizeof(bootstrap_keys_wrap));
pg_cipher_ctx_free(cluster_key_ctx);
}
/*
* We are either decrypting keys we copied from an old cluster, or
* decrypting keys we just wrote above --- either way, we decrypt
* them here and store them in a file-scoped variable for use in
* later encrypting during bootstrap mode.
*/
/* Get the crypto keys from the file */
keys_wrap = kmgr_get_cryptokeys(LIVE_KMGR_DIR, &nkeys);
Assert(nkeys == KMGR_MAX_INTERNAL_KEYS);
if (!kmgr_verify_cluster_key(cluster_key, keys_wrap, bootstrap_keys,
KMGR_MAX_INTERNAL_KEYS))
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("supplied cluster key does not match expected cluster_key")));
/* bzero keys on exit */
on_proc_exit(bzeroKmgrKeys, 0);
explicit_bzero(cluster_key_hex, cluster_key_hex_len);
explicit_bzero(cluster_key, KMGR_CLUSTER_KEY_LEN);
}
/* Report shared-memory space needed by KmgrShmem */
Size
KmgrShmemSize(void)
{
if (!file_encryption_keylen)
return 0;
return MAXALIGN(sizeof(KmgrShmemData));
}
/* Allocate and initialize key manager memory */
void
KmgrShmemInit(void)
{
bool found;
if (!file_encryption_keylen)
return;
KmgrShmem = (KmgrShmemData *) ShmemInitStruct("File encryption key manager",
KmgrShmemSize(), &found);
on_shmem_exit(bzeroKmgrKeys, 0);
}
/*
* Get cluster key and verify it, then get the data encryption keys.
* This function is called by postmaster at startup time.
*/
void
InitializeKmgr(void)
{
CryptoKey *keys_wrap;
int nkeys;
char cluster_key_hex[ALLOC_KMGR_CLUSTER_KEY_LEN];
int cluster_key_hex_len;
struct stat buffer;
char live_path[MAXPGPATH];
unsigned char cluster_key[KMGR_CLUSTER_KEY_LEN];
if (!file_encryption_keylen)
return;
elog(DEBUG1, "starting up cluster file encryption manager");
if (stat(KMGR_DIR, &buffer) != 0 || !S_ISDIR(buffer.st_mode))
ereport(ERROR,
(errcode(ERRCODE_INTERNAL_ERROR),
(errmsg("cluster file encryption directory %s is missing", KMGR_DIR))));
if (stat(KMGR_DIR_PID, &buffer) == 0)
ereport(ERROR,
(errcode(ERRCODE_INTERNAL_ERROR),
(errmsg("cluster had a pg_alterckey failure that needs repair or pg_alterckey is running"),
errhint("Run pg_alterckey --repair or wait for it to complete."))));
/*
* We want OLD deleted since it allows access to the data encryption
* keys using the old cluster key. If NEW exists, it means either
* NEW is partly written, or NEW wasn't renamed to LIVE --- in either
* case, it needs to be repaired.
*/
if (stat(OLD_KMGR_DIR, &buffer) == 0 || stat(NEW_KMGR_DIR, &buffer) == 0)
ereport(ERROR,
(errcode(ERRCODE_INTERNAL_ERROR),
(errmsg("cluster had a pg_alterckey failure that needs repair"),
errhint("Run pg_alterckey --repair."))));
/* If OLD, NEW, and LIVE do not exist, there is a serious problem. */
if (stat(LIVE_KMGR_DIR, &buffer) != 0 || !S_ISDIR(buffer.st_mode))
ereport(ERROR,
(errcode(ERRCODE_INTERNAL_ERROR),
(errmsg("cluster has no data encryption keys"))));
/* Get cluster key */
snprintf(live_path, sizeof(live_path), "%s/%s", DataDir, LIVE_KMGR_DIR);
cluster_key_hex_len = kmgr_run_cluster_key_command(cluster_key_command,
cluster_key_hex,
ALLOC_KMGR_CLUSTER_KEY_LEN,
live_path);
if (hex_decode(cluster_key_hex, cluster_key_hex_len, (char*) cluster_key) !=
KMGR_CLUSTER_KEY_LEN)
ereport(ERROR,
(errmsg("cluster key must be %d hexadecimal characters",
KMGR_CLUSTER_KEY_LEN * 2)));
/* Get the crypto keys from the file */
keys_wrap = kmgr_get_cryptokeys(LIVE_KMGR_DIR, &nkeys);
Assert(nkeys == KMGR_MAX_INTERNAL_KEYS);
/*
* Verify cluster key and prepare a data encryption key in plaintext in shared memory.
*/
if (!kmgr_verify_cluster_key(cluster_key, keys_wrap, KmgrShmem->intlKeys,
KMGR_MAX_INTERNAL_KEYS))
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("supplied cluster key does not match expected cluster key")));
explicit_bzero(cluster_key_hex, cluster_key_hex_len);
explicit_bzero(cluster_key, KMGR_CLUSTER_KEY_LEN);
}
static void
bzeroKmgrKeys(int status, Datum arg)
{
if (IsBootstrapProcessingMode())
explicit_bzero(bootstrap_keys, sizeof(bootstrap_keys));
else
explicit_bzero(KmgrShmem->intlKeys, sizeof(KmgrShmem->intlKeys));
}
const CryptoKey *
KmgrGetKey(int id)
{
Assert(id < KMGR_MAX_INTERNAL_KEYS);
return (const CryptoKey *) (IsBootstrapProcessingMode() ?
&(bootstrap_keys[id]) : &(KmgrShmem->intlKeys[id]));
}
/* Generate an empty CryptoKey */
static CryptoKey *
generate_crypto_key(int len)
{
CryptoKey *newkey;
Assert(len <= KMGR_MAX_KEY_LEN);
newkey = (CryptoKey *) palloc0(sizeof(CryptoKey));
/* We store the key as length + key into 'encrypted_key' */
memcpy(newkey->encrypted_key, &len, sizeof(len));
if (!pg_strong_random(newkey->encrypted_key + sizeof(len), len))
elog(ERROR, "failed to generate new file encryption key");
return newkey;
}
/*
* Save the given file encryption keys to the disk.
*/
static void
KmgrSaveCryptoKeys(const char *dir, CryptoKey *keys)
{
elog(DEBUG2, "saving all cryptographic keys");
for (int i = 0; i < KMGR_MAX_INTERNAL_KEYS; i++)
{
int fd;
char path[MAXPGPATH];
CryptoKeyFilePath(path, dir, i);
if ((fd = BasicOpenFile(path, O_RDWR | O_CREAT | O_EXCL | PG_BINARY)) < 0)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not open file \"%s\": %m",
path)));
errno = 0;
pgstat_report_wait_start(WAIT_EVENT_KEY_FILE_WRITE);
if (write(fd, &(keys[i]), sizeof(CryptoKey)) != sizeof(CryptoKey))
{
/* if write didn't set errno, assume problem is no disk space */
if (errno == 0)
errno = ENOSPC;
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not write file \"%s\": %m",
path)));
}
pgstat_report_wait_end();
pgstat_report_wait_start(WAIT_EVENT_KEY_FILE_SYNC);
if (pg_fsync(fd) != 0)
ereport(PANIC,
(errcode_for_file_access(),
errmsg("could not fsync file \"%s\": %m",
path)));
pgstat_report_wait_end();
if (close(fd) != 0)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not close file \"%s\": %m",
path)));
}
}

View File

@ -324,6 +324,7 @@ help(const char *progname)
#endif
printf(_(" -N MAX-CONNECT maximum number of allowed connections\n"));
printf(_(" -p PORT port number to listen on\n"));
printf(_(" -R fd prompt for the cluster key\n"));
printf(_(" -s show statistics after each query\n"));
printf(_(" -S WORK-MEM set amount of memory for sorts (in kB)\n"));
printf(_(" -V, --version output version information, then exit\n"));
@ -351,7 +352,9 @@ help(const char *progname)
printf(_("\nOptions for bootstrapping mode:\n"));
printf(_(" --boot selects bootstrapping mode (must be first argument)\n"));
printf(_(" DBNAME database name (mandatory argument in bootstrapping mode)\n"));
printf(_(" -K LEN enable cluster file encryption with specified key length\n"));
printf(_(" -r FILENAME send stdout and stderr to given file\n"));
printf(_(" -u DATADIR copy encryption keys from datadir\n"));
printf(_(" -x NUM internal use\n"));
printf(_("\nPlease read the documentation for the complete list of run-time\n"

View File

@ -4152,6 +4152,15 @@ pgstat_get_wait_io(WaitEventIO w)
case WAIT_EVENT_DSM_FILL_ZERO_WRITE:
event_name = "DSMFillZeroWrite";
break;
case WAIT_EVENT_KEY_FILE_READ:
event_name = "KeyFileRead";
break;
case WAIT_EVENT_KEY_FILE_WRITE:
event_name = "KeyFileWrite";
break;
case WAIT_EVENT_KEY_FILE_SYNC:
event_name = "KeyFileSync";
break;
case WAIT_EVENT_LOCK_FILE_ADDTODATADIR_READ:
event_name = "LockFileAddToDataDirRead";
break;

View File

@ -100,6 +100,7 @@
#include "common/file_perm.h"
#include "common/ip.h"
#include "common/string.h"
#include "crypto/kmgr.h"
#include "lib/ilist.h"
#include "libpq/auth.h"
#include "libpq/libpq.h"
@ -231,6 +232,7 @@ static int SendStop = false;
/* still more option variables */
bool EnableSSL = false;
int terminal_fd = -1;
int PreAuthDelay = 0;
int AuthenticationTimeout = 60;
@ -687,7 +689,7 @@ PostmasterMain(int argc, char *argv[])
* tcop/postgres.c (the option sets should not conflict) and with the
* common help() function in main/main.c.
*/
while ((opt = getopt(argc, argv, "B:bc:C:D:d:EeFf:h:ijk:lN:nOPp:r:S:sTt:W:-:")) != -1)
while ((opt = getopt(argc, argv, "B:bc:C:D:d:EeFf:h:ijk:lN:nOPp:r:R:S:sTt:W:-:")) != -1)
{
switch (opt)
{
@ -778,6 +780,10 @@ PostmasterMain(int argc, char *argv[])
/* only used by single-user backend */
break;
case 'R':
terminal_fd = atoi(optarg);
break;
case 'S':
SetConfigOption("work_mem", optarg, PGC_POSTMASTER, PGC_S_ARGV);
break;
@ -1326,6 +1332,11 @@ PostmasterMain(int argc, char *argv[])
*/
RemovePgTempFiles();
InitializeKmgr();
if (terminal_fd != -1)
close(terminal_fd);
/*
* Initialize stats collection subsystem (this does NOT start the
* collector process!)

View File

@ -18,6 +18,7 @@
#include "access/xlog_internal.h" /* for pg_start/stop_backup */
#include "catalog/pg_type.h"
#include "common/kmgr_utils.h"
#include "common/file_perm.h"
#include "commands/progress.h"
#include "lib/stringinfo.h"
@ -152,6 +153,10 @@ struct exclude_list_item
*/
static const char *const excludeDirContents[] =
{
/* Skip temporary crypto key directories */
NEW_KMGR_DIR,
OLD_KMGR_DIR,
/*
* Skip temporary statistics files. PG_STAT_TMP_DIR must be skipped even
* when stats_temp_directory is set because PGSS_TEXT_FILE is always

View File

@ -23,6 +23,7 @@
#include "access/syncscan.h"
#include "access/twophase.h"
#include "commands/async.h"
#include "crypto/kmgr.h"
#include "miscadmin.h"
#include "pgstat.h"
#include "postmaster/autovacuum.h"
@ -149,6 +150,7 @@ CreateSharedMemoryAndSemaphores(void)
size = add_size(size, BTreeShmemSize());
size = add_size(size, SyncScanShmemSize());
size = add_size(size, AsyncShmemSize());
size = add_size(size, KmgrShmemSize());
#ifdef EXEC_BACKEND
size = add_size(size, ShmemBackendArraySize());
#endif
@ -267,6 +269,7 @@ CreateSharedMemoryAndSemaphores(void)
BTreeShmemInit();
SyncScanShmemInit();
AsyncShmemInit();
KmgrShmemInit();
#ifdef EXEC_BACKEND

View File

@ -53,3 +53,4 @@ XactTruncationLock 44
# 45 was XactTruncationLock until removal of BackendRandomLock
WrapLimitsVacuumLock 46
NotifyQueueTailLock 47
KmgrFileLock 48

View File

@ -42,6 +42,7 @@
#include "catalog/pg_type.h"
#include "commands/async.h"
#include "commands/prepare.h"
#include "crypto/kmgr.h"
#include "executor/spi.h"
#include "jit/jit.h"
#include "libpq/libpq.h"
@ -3578,7 +3579,7 @@ process_postgres_switches(int argc, char *argv[], GucContext ctx,
* postmaster/postmaster.c (the option sets should not conflict) and with
* the common help() function in main/main.c.
*/
while ((flag = getopt(argc, argv, "B:bc:C:D:d:EeFf:h:ijk:lN:nOPp:r:S:sTt:v:W:-:")) != -1)
while ((flag = getopt(argc, argv, "B:bc:C:D:d:EeFf:h:ijk:lN:nOPp:r:R:S:sTt:v:W:-:")) != -1)
{
switch (flag)
{
@ -3670,6 +3671,16 @@ process_postgres_switches(int argc, char *argv[], GucContext ctx,
strlcpy(OutputFileName, optarg, MAXPGPATH);
break;
case 'R':
terminal_fd = atoi(optarg);
if (terminal_fd == -1)
/*
* Allow file descriptor closing to be bypassed via -1.
* We just dup sterr. This is useful for single-user mode.
*/
terminal_fd = dup(2);
break;
case 'S':
SetConfigOption("work_mem", optarg, ctx, gucsource);
break;
@ -3921,6 +3932,18 @@ PostgresMain(int argc, char *argv[],
/* Early initialization */
BaseInit();
/*
* Initialize kmgr for cluster encryption. Since kmgr needs to attach to
* shared memory the initialization must be called after BaseInit().
*/
if (!IsUnderPostmaster)
{
InitializeKmgr();
if (terminal_fd != -1)
close(terminal_fd);
}
/*
* Create a per-backend PGPROC struct in shared memory, except in the
* EXEC_BACKEND case where this was done in SubPostmasterMain. We must do

View File

@ -47,6 +47,7 @@
#include "commands/vacuum.h"
#include "commands/variable.h"
#include "common/string.h"
#include "crypto/kmgr.h"
#include "funcapi.h"
#include "jit/jit.h"
#include "libpq/auth.h"
@ -745,6 +746,8 @@ const char *const config_group_names[] =
gettext_noop("Statistics / Monitoring"),
/* STATS_COLLECTOR */
gettext_noop("Statistics / Query and Index Statistics Collector"),
/* ENCRYPTION */
gettext_noop("Encryption"),
/* AUTOVACUUM */
gettext_noop("Autovacuum"),
/* CLIENT_CONN */
@ -3389,6 +3392,17 @@ static struct config_int ConfigureNamesInt[] =
check_huge_page_size, NULL, NULL
},
{
{"file_encryption_keylen", PGC_INTERNAL, PRESET_OPTIONS,
gettext_noop("Shows the bit length of the file encryption key."),
NULL,
GUC_NOT_IN_SAMPLE | GUC_DISALLOW_IN_FILE
},
&file_encryption_keylen,
0, 0, 256,
NULL, NULL, NULL
},
/* End-of-list marker */
{
{NULL, 0, 0, NULL, NULL}, NULL, 0, 0, 0, NULL, NULL, NULL
@ -4383,6 +4397,16 @@ static struct config_string ConfigureNamesString[] =
NULL, NULL, NULL
},
{
{"cluster_key_command", PGC_SIGHUP, ENCRYPTION,
gettext_noop("Command to obtain cluster key for cluster file encryption."),
NULL
},
&cluster_key_command,
"",
NULL, NULL, NULL
},
{
{"application_name", PGC_USERSET, LOGGING_WHAT,
gettext_noop("Sets the application name to be reported in statistics and logs."),

View File

@ -263,8 +263,8 @@ pg_control_recovery(PG_FUNCTION_ARGS)
Datum
pg_control_init(PG_FUNCTION_ARGS)
{
Datum values[11];
bool nulls[11];
Datum values[12];
bool nulls[12];
TupleDesc tupdesc;
HeapTuple htup;
ControlFileData *ControlFile;
@ -274,7 +274,7 @@ pg_control_init(PG_FUNCTION_ARGS)
* Construct a tuple descriptor for the result row. This must match this
* function's pg_proc entry!
*/
tupdesc = CreateTemplateTupleDesc(11);
tupdesc = CreateTemplateTupleDesc(12);
TupleDescInitEntry(tupdesc, (AttrNumber) 1, "max_data_alignment",
INT4OID, -1, 0);
TupleDescInitEntry(tupdesc, (AttrNumber) 2, "database_block_size",
@ -297,6 +297,8 @@ pg_control_init(PG_FUNCTION_ARGS)
BOOLOID, -1, 0);
TupleDescInitEntry(tupdesc, (AttrNumber) 11, "data_page_checksum_version",
INT4OID, -1, 0);
TupleDescInitEntry(tupdesc, (AttrNumber) 12, "file_encryption_keylen",
INT4OID, -1, 0);
tupdesc = BlessTupleDesc(tupdesc);
/* read the control file */
@ -338,6 +340,9 @@ pg_control_init(PG_FUNCTION_ARGS)
values[10] = Int32GetDatum(ControlFile->data_checksum_version);
nulls[10] = false;
values[11] = Int32GetDatum(ControlFile->file_encryption_keylen);
nulls[11] = false;
htup = heap_form_tuple(tupdesc, values, nulls);
PG_RETURN_DATUM(HeapTupleGetDatum(htup));

View File

@ -632,6 +632,11 @@
# autovacuum, -1 means use
# vacuum_cost_limit
#------------------------------------------------------------------------------
# ENCRYPTION
#------------------------------------------------------------------------------
#cluster_key_command = ''
#------------------------------------------------------------------------------
# CLIENT CONNECTION DEFAULTS

View File

@ -141,11 +141,16 @@ static bool debug = false;
static bool noclean = false;
static bool do_sync = true;
static bool sync_only = false;
static bool pass_terminal_fd = false;
static char *term_fd_opt = NULL;
static int file_encryption_keylen = 0;
static bool show_setting = false;
static bool data_checksums = false;
static char *xlog_dir = NULL;
static char *str_wal_segment_size_mb = NULL;
static int wal_segment_size_mb;
static char *cluster_key_cmd = NULL;
static char *old_key_datadir = NULL;
/* internal vars */
@ -203,6 +208,7 @@ static const char *const subdirs[] = {
"global",
"pg_wal/archive_status",
"pg_commit_ts",
"pg_cryptokeys",
"pg_dynshmem",
"pg_notify",
"pg_serial",
@ -954,12 +960,13 @@ test_config_settings(void)
test_buffs = MIN_BUFS_FOR_CONNS(test_conns);
snprintf(cmd, sizeof(cmd),
"\"%s\" --boot -x0 %s "
"\"%s\" --boot -x0 %s %s "
"-c max_connections=%d "
"-c shared_buffers=%d "
"-c dynamic_shared_memory_type=%s "
"< \"%s\" > \"%s\" 2>&1",
backend_exec, boot_options,
term_fd_opt ? term_fd_opt : "",
test_conns, test_buffs,
dynamic_shared_memory_type,
DEVNULL, DEVNULL);
@ -990,12 +997,13 @@ test_config_settings(void)
}
snprintf(cmd, sizeof(cmd),
"\"%s\" --boot -x0 %s "
"\"%s\" --boot -x0 %s %s "
"-c max_connections=%d "
"-c shared_buffers=%d "
"-c dynamic_shared_memory_type=%s "
"< \"%s\" > \"%s\" 2>&1",
backend_exec, boot_options,
term_fd_opt ? term_fd_opt : "",
n_connections, test_buffs,
dynamic_shared_memory_type,
DEVNULL, DEVNULL);
@ -1185,6 +1193,13 @@ setup_config(void)
"password_encryption = md5");
}
if (cluster_key_cmd)
{
snprintf(repltok, sizeof(repltok), "cluster_key_command = '%s'",
escape_quotes(cluster_key_cmd));
conflines = replace_token(conflines, "#cluster_key_command = ''", repltok);
}
/*
* If group access has been enabled for the cluster then it makes sense to
* ensure that the log files also allow group access. Otherwise a backup
@ -1394,13 +1409,22 @@ bootstrap_template1(void)
/* Also ensure backend isn't confused by this environment var: */
unsetenv("PGCLIENTENCODING");
if (file_encryption_keylen != 0)
sprintf(buf, "%d", file_encryption_keylen);
else
buf[0] = '\0';
snprintf(cmd, sizeof(cmd),
"\"%s\" --boot -x1 -X %u %s %s %s",
"\"%s\" --boot -x1 -X %u %s %s %s %s %s %s %s %s",
backend_exec,
wal_segment_size_mb * (1024 * 1024),
data_checksums ? "-k" : "",
cluster_key_cmd ? "-K" : "", buf,
old_key_datadir ? "-u" : "",
old_key_datadir ? old_key_datadir : "",
boot_options,
debug ? "-d 5" : "");
debug ? "-d 5" : "",
term_fd_opt ? term_fd_opt : "");
PG_CMD_OPEN;
@ -2281,19 +2305,25 @@ usage(const char *progname)
" set default locale in the respective category for\n"
" new databases (default taken from environment)\n"));
printf(_(" --no-locale equivalent to --locale=C\n"));
printf(_(" --pwfile=FILE read password for the new superuser from file\n"));
printf(_(" --pwfile=FILE read the new superuser password from file\n"));
printf(_(" -T, --text-search-config=CFG\n"
" default text search configuration\n"));
printf(_(" -U, --username=NAME database superuser name\n"));
printf(_(" -W, --pwprompt prompt for a password for the new superuser\n"));
printf(_(" -W, --pwprompt prompt for the new superuser password\n"));
printf(_(" -X, --waldir=WALDIR location for the write-ahead log directory\n"));
printf(_(" --wal-segsize=SIZE size of WAL segments, in megabytes\n"));
printf(_("\nLess commonly used options:\n"));
printf(_(" -c --cluster-key-command=COMMAND\n"
" enable cluster file encryption and set command\n"
" to obtain the cluster key\n"));
printf(_(" -d, --debug generate lots of debugging output\n"));
printf(_(" -k, --data-checksums use data page checksums\n"));
printf(_(" -K, --file-encryption-keylen\n"
" bit length of the file encryption key\n"));
printf(_(" -L DIRECTORY where to find the input files\n"));
printf(_(" -n, --no-clean do not clean up after errors\n"));
printf(_(" -N, --no-sync do not wait for changes to be written safely to disk\n"));
printf(_(" -R, --authprompt prompt for a passphrase or PIN\n"));
printf(_(" -s, --show show internal settings\n"));
printf(_(" -S, --sync-only only sync data directory\n"));
printf(_("\nOther options:\n"));
@ -2860,6 +2890,23 @@ initialize_data_directory(void)
/* Top level PG_VERSION is checked by bootstrapper, so make it first */
write_version_file(NULL);
if (pass_terminal_fd)
{
#ifndef WIN32
int terminal_fd = open("/dev/tty", O_RDWR, 0);
#else
int terminal_fd = open("CONOUT$", O_RDWR, 0);
#endif
if (terminal_fd < 0)
{
pg_log_error(_("%s: could not open terminal: %s\n"),
progname, strerror(errno));
exit(1);
}
term_fd_opt = psprintf("-R %d", terminal_fd);
}
/* Select suitable configuration settings */
set_null_conf();
test_config_settings();
@ -2883,8 +2930,9 @@ initialize_data_directory(void)
fflush(stdout);
snprintf(cmd, sizeof(cmd),
"\"%s\" %s template1 >%s",
"\"%s\" %s %s template1 >%s",
backend_exec, backend_options,
term_fd_opt ? term_fd_opt : "",
DEVNULL);
PG_CMD_OPEN;
@ -2957,7 +3005,11 @@ main(int argc, char *argv[])
{"waldir", required_argument, NULL, 'X'},
{"wal-segsize", required_argument, NULL, 12},
{"data-checksums", no_argument, NULL, 'k'},
{"authprompt", no_argument, NULL, 'R'},
{"file-encryption-keylen", no_argument, NULL, 'K'},
{"allow-group-access", no_argument, NULL, 'g'},
{"cluster-key-command", required_argument, NULL, 'c'},
{"copy-encryption-keys", required_argument, NULL, 'u'},
{NULL, 0, NULL, 0}
};
@ -2999,7 +3051,7 @@ main(int argc, char *argv[])
/* process command-line options */
while ((c = getopt_long(argc, argv, "A:dD:E:gkL:nNsST:U:WX:", long_options, &option_index)) != -1)
while ((c = getopt_long(argc, argv, "A:c:dD:E:gkK:L:nNRsST:u:U:WX:", long_options, &option_index)) != -1)
{
switch (c)
{
@ -3045,6 +3097,12 @@ main(int argc, char *argv[])
case 'N':
do_sync = false;
break;
case 'R':
pass_terminal_fd = true;
break;
case 'K':
file_encryption_keylen = atoi(optarg);
break;
case 'S':
sync_only = true;
break;
@ -3081,6 +3139,12 @@ main(int argc, char *argv[])
case 9:
pwfilename = pg_strdup(optarg);
break;
case 'c':
cluster_key_cmd = pg_strdup(optarg);
break;
case 'u':
old_key_datadir = pg_strdup(optarg);
break;
case 's':
show_setting = true;
break;
@ -3151,6 +3215,37 @@ main(int argc, char *argv[])
exit(1);
}
#ifndef USE_OPENSSL
if (cluster_key_cmd)
{
pg_log_error("cluster file encryption is not supported because OpenSSL is not supported by this build");
exit(1);
}
#endif
if (old_key_datadir != NULL && cluster_key_cmd == NULL)
{
pg_log_error("copying encryption keys requires the cluster key command to be specified");
exit(1);
}
if (file_encryption_keylen != 0 && cluster_key_cmd == NULL)
{
pg_log_error("a non-zero file encryption key length requires the cluster key command to be specified");
exit(1);
}
if (file_encryption_keylen != 0 && file_encryption_keylen != 128 &&
file_encryption_keylen != 192 && file_encryption_keylen != 256)
{
pg_log_error("invalid file encrypt key length; supported values are 0 (disabled), 128, 192, and 256");
exit(1);
}
/* set the default */
if (file_encryption_keylen == 0 && cluster_key_cmd != NULL)
file_encryption_keylen = 128;
check_authmethod_unspecified(&authmethodlocal);
check_authmethod_unspecified(&authmethodhost);
@ -3218,6 +3313,11 @@ main(int argc, char *argv[])
else
printf(_("Data page checksums are disabled.\n"));
if (cluster_key_cmd)
printf(_("Cluster file encryption is enabled.\n"));
else
printf(_("Cluster file encryption is disabled.\n"));
if (pwprompt || pwfilename)
get_su_pwd();

View File

@ -25,6 +25,7 @@
#include "access/xlog_internal.h"
#include "catalog/pg_control.h"
#include "common/controldata_utils.h"
#include "common/kmgr_utils.h"
#include "common/logging.h"
#include "getopt_long.h"
#include "pg_getopt.h"
@ -334,5 +335,7 @@ main(int argc, char *argv[])
ControlFile->data_checksum_version);
printf(_("Mock authentication nonce: %s\n"),
mock_auth_nonce_str);
printf(_("File encryption key length: %d\n"),
ControlFile->file_encryption_keylen);
return 0;
}

View File

@ -79,6 +79,7 @@ typedef enum
static bool do_wait = true;
static int wait_seconds = DEFAULT_WAIT;
static bool wait_seconds_arg = false;
static bool pass_terminal_fd = false;
static bool silent_mode = false;
static ShutdownMode shutdown_mode = FAST_MODE;
static int sig = SIGINT; /* default */
@ -442,7 +443,7 @@ free_readfile(char **optlines)
static pgpid_t
start_postmaster(void)
{
char cmd[MAXPGPATH];
char cmd[MAXPGPATH], *term_fd_opt = NULL;
#ifndef WIN32
pgpid_t pm_pid;
@ -467,6 +468,19 @@ start_postmaster(void)
/* fork succeeded, in child */
if (pass_terminal_fd)
{
int terminal_fd = open("/dev/tty", O_RDWR, 0);
if (terminal_fd < 0)
{
write_stderr(_("%s: could not open terminal: %s\n"),
progname, strerror(errno));
exit(1);
}
term_fd_opt = psprintf(" -R %d", terminal_fd);
}
/*
* If possible, detach the postmaster process from the launching process
* group and make it a group leader, so that it doesn't get signaled along
@ -487,12 +501,14 @@ start_postmaster(void)
* has the same PID as the current child process.
*/
if (log_file != NULL)
snprintf(cmd, MAXPGPATH, "exec \"%s\" %s%s < \"%s\" >> \"%s\" 2>&1",
snprintf(cmd, MAXPGPATH, "exec \"%s\" %s%s%s < \"%s\" >> \"%s\" 2>&1",
exec_path, pgdata_opt, post_opts,
term_fd_opt ? term_fd_opt : "",
DEVNULL, log_file);
else
snprintf(cmd, MAXPGPATH, "exec \"%s\" %s%s < \"%s\" 2>&1",
exec_path, pgdata_opt, post_opts, DEVNULL);
snprintf(cmd, MAXPGPATH, "exec \"%s\" %s%s%s < \"%s\" 2>&1",
exec_path, pgdata_opt, post_opts,
term_fd_opt ? term_fd_opt : "", DEVNULL);
(void) execl("/bin/sh", "/bin/sh", "-c", cmd, (char *) NULL);
@ -513,6 +529,21 @@ start_postmaster(void)
PROCESS_INFORMATION pi;
const char *comspec;
if (pass_terminal_fd)
{
/* Hopefully we can read and write CONOUT, see simple_prompt() XXX */
/* Do CreateRestrictedProcess() children even inherit open file descriptors? XXX */
int terminal_fd = open("CONOUT$", O_RDWR, 0);
if (terminal_fd < 0)
{
write_stderr(_("%s: could not open terminal: %s\n"),
progname, strerror(errno));
exit(1);
}
term_fd_opt = psprintf(" -R %d", terminal_fd);
}
/* Find CMD.EXE location using COMSPEC, if it's set */
comspec = getenv("COMSPEC");
if (comspec == NULL)
@ -553,12 +584,14 @@ start_postmaster(void)
else
close(fd);
snprintf(cmd, MAXPGPATH, "\"%s\" /C \"\"%s\" %s%s < \"%s\" >> \"%s\" 2>&1\"",
comspec, exec_path, pgdata_opt, post_opts, DEVNULL, log_file);
snprintf(cmd, MAXPGPATH, "\"%s\" /C \"\"%s\" %s%s%s < \"%s\" >> \"%s\" 2>&1\"",
comspec, exec_path, pgdata_opt, post_opts,
term_fd_opt ? term_fd_opt : "", DEVNULL, log_file);
}
else
snprintf(cmd, MAXPGPATH, "\"%s\" /C \"\"%s\" %s%s < \"%s\" 2>&1\"",
comspec, exec_path, pgdata_opt, post_opts, DEVNULL);
snprintf(cmd, MAXPGPATH, "\"%s\" /C \"\"%s\" %s%s%s < \"%s\" 2>&1\"",
comspec, exec_path, pgdata_opt, post_opts,
term_fd_opt ? term_fd_opt : "", DEVNULL);
if (!CreateRestrictedProcess(cmd, &pi, false))
{
@ -689,7 +722,8 @@ wait_for_postmaster(pgpid_t pm_pid, bool do_checkpoint)
}
else
#endif
print_msg(".");
if (!pass_terminal_fd)
print_msg(".");
}
pg_usleep(USEC_PER_SEC / WAITS_PER_SEC);
@ -2066,6 +2100,7 @@ do_help(void)
printf(_(" -o, --options=OPTIONS command line options to pass to postgres\n"
" (PostgreSQL server executable) or initdb\n"));
printf(_(" -p PATH-TO-POSTGRES normally not necessary\n"));
printf(_(" -R, --authprompt prompt for a paasphrase or PIN\n"));
printf(_("\nOptions for stop or restart:\n"));
printf(_(" -m, --mode=MODE MODE can be \"smart\", \"fast\", or \"immediate\"\n"));
@ -2260,6 +2295,7 @@ main(int argc, char **argv)
{"mode", required_argument, NULL, 'm'},
{"pgdata", required_argument, NULL, 'D'},
{"options", required_argument, NULL, 'o'},
{"authprompt", no_argument, NULL, 'R'},
{"silent", no_argument, NULL, 's'},
{"timeout", required_argument, NULL, 't'},
{"core-files", no_argument, NULL, 'c'},
@ -2332,7 +2368,7 @@ main(int argc, char **argv)
/* process command-line options */
while (optind < argc)
{
while ((c = getopt_long(argc, argv, "cD:e:l:m:N:o:p:P:sS:t:U:wW",
while ((c = getopt_long(argc, argv, "cD:e:l:m:N:o:p:P:RsS:t:U:wW",
long_options, &option_index)) != -1)
{
switch (c)
@ -2385,6 +2421,9 @@ main(int argc, char **argv)
case 'P':
register_password = pg_strdup(optarg);
break;
case 'R':
pass_terminal_fd = true;
break;
case 's':
silent_mode = true;
break;

View File

@ -804,6 +804,8 @@ PrintControlValues(bool guessed)
(ControlFile.float8ByVal ? _("by value") : _("by reference")));
printf(_("Data page checksum version: %u\n"),
ControlFile.data_checksum_version);
printf(_("File encryption key length: %d\n"),
ControlFile.file_encryption_keylen);
}

View File

@ -28,6 +28,7 @@
#include "catalog/pg_tablespace_d.h"
#include "common/hashfn.h"
#include "common/kmgr_utils.h"
#include "common/string.h"
#include "datapagemap.h"
#include "filemap.h"
@ -107,6 +108,13 @@ static const char *excludeDirContents[] =
/* Contents removed on startup, see AsyncShmemInit(). */
"pg_notify",
/*
* Skip cryptographic keys. It's generally not a good idea to copy the
* cryptographic keys from source database because these might use
* different cluster key.
*/
KMGR_DIR,
/*
* Old contents are loaded for possible debugging but are not required for
* normal operation, see SerialInit().

View File

@ -10,6 +10,7 @@
#include "postgres_fe.h"
#include "catalog/pg_authid_d.h"
#include "common/kmgr_utils.h"
#include "fe_utils/string_utils.h"
#include "mb/pg_wchar.h"
#include "pg_upgrade.h"
@ -27,6 +28,7 @@ static void check_for_tables_with_oids(ClusterInfo *cluster);
static void check_for_reg_data_type_usage(ClusterInfo *cluster);
static void check_for_jsonb_9_4_usage(ClusterInfo *cluster);
static void check_for_pg_role_prefix(ClusterInfo *cluster);
static void check_for_cluster_key_failure(ClusterInfo *cluster);
static void check_for_new_tablespace_dir(ClusterInfo *new_cluster);
static char *get_canonical_locale_name(int category, const char *locale);
@ -139,6 +141,9 @@ check_and_dump_old_cluster(bool live_check)
if (GET_MAJOR_VERSION(old_cluster.major_version) <= 905)
check_for_pg_role_prefix(&old_cluster);
if (GET_MAJOR_VERSION(old_cluster.major_version) >= 1400)
check_for_cluster_key_failure(&old_cluster);
if (GET_MAJOR_VERSION(old_cluster.major_version) == 904 &&
old_cluster.controldata.cat_ver < JSONB_FORMAT_CHANGE_CAT_VER)
check_for_jsonb_9_4_usage(&old_cluster);
@ -173,6 +178,9 @@ check_new_cluster(void)
check_loadable_libraries();
if (GET_MAJOR_VERSION(old_cluster.major_version) >= 1400)
check_for_cluster_key_failure(&new_cluster);
switch (user_opts.transfer_mode)
{
case TRANSFER_MODE_CLONE:
@ -1269,6 +1277,32 @@ check_for_pg_role_prefix(ClusterInfo *cluster)
}
/*
* check_for_cluster_key_failure()
*
* Make sure there was no unrepaired pg_alterckey failure
*/
static void
check_for_cluster_key_failure(ClusterInfo *cluster)
{
struct stat buffer;
if (stat (KMGR_DIR_PID, &buffer) == 0)
{
if (cluster == &old_cluster)
pg_fatal("The source cluster had a pg_alterckey failure that needs repair or\n"
"pg_alterckey is running. Run pg_alterckey --repair or wait for it\n"
"to complete.\n");
else
pg_fatal("The target cluster had a pg_alterckey failure that needs repair or\n"
"pg_alterckey is running. Run pg_alterckey --repair or wait for it\n"
"to complete.\n");
}
check_ok();
}
/*
* get_canonical_locale_name
*

View File

@ -9,10 +9,16 @@
#include "postgres_fe.h"
#include <dirent.h>
#include <ctype.h>
#include "pg_upgrade.h"
#include "access/xlog_internal.h"
#include "common/controldata_utils.h"
#include "common/file_utils.h"
#include "common/kmgr_utils.h"
/*
* get_control_data()
*
@ -59,6 +65,7 @@ get_control_data(ClusterInfo *cluster, bool live_check)
bool got_date_is_int = false;
bool got_data_checksum_version = false;
bool got_cluster_state = false;
int got_file_encryption_keylen = 0;
char *lc_collate = NULL;
char *lc_ctype = NULL;
char *lc_monetary = NULL;
@ -202,6 +209,13 @@ get_control_data(ClusterInfo *cluster, bool live_check)
got_data_checksum_version = true;
}
/* Only in <= 14 */
if (GET_MAJOR_VERSION(cluster->major_version) <= 1400)
{
cluster->controldata.file_encryption_keylen = 0;
got_file_encryption_keylen = true;
}
/* we have the result of cmd in "output". so parse it line by line now */
while (fgets(bufin, sizeof(bufin), output))
{
@ -485,6 +499,18 @@ get_control_data(ClusterInfo *cluster, bool live_check)
cluster->controldata.data_checksum_version = str2uint(p);
got_data_checksum_version = true;
}
else if ((p = strstr(bufin, "File encryption key length:")) != NULL)
{
p = strchr(p, ':');
if (p == NULL || strlen(p) <= 1)
pg_fatal("%d: controldata retrieval problem\n", __LINE__);
p++; /* remove ':' char */
/* used later for contrib check */
cluster->controldata.file_encryption_keylen = atoi(p);
got_file_encryption_keylen = true;
}
}
pclose(output);
@ -539,7 +565,8 @@ get_control_data(ClusterInfo *cluster, bool live_check)
!got_index || !got_toast ||
(!got_large_object &&
cluster->controldata.ctrl_ver >= LARGE_OBJECT_SIZE_PG_CONTROL_VER) ||
!got_date_is_int || !got_data_checksum_version)
!got_date_is_int || !got_data_checksum_version ||
!got_file_encryption_keylen)
{
if (cluster == &old_cluster)
pg_log(PG_REPORT,
@ -605,6 +632,10 @@ get_control_data(ClusterInfo *cluster, bool live_check)
if (!got_data_checksum_version)
pg_log(PG_REPORT, " data checksum version\n");
/* value added in Postgres 14 */
if (!got_file_encryption_keylen)
pg_log(PG_REPORT, " file encryption key length\n");
pg_fatal("Cannot continue without required control information, terminating\n");
}
}
@ -669,6 +700,15 @@ check_control_data(ControlData *oldctrl,
pg_fatal("old cluster uses data checksums but the new one does not\n");
else if (oldctrl->data_checksum_version != newctrl->data_checksum_version)
pg_fatal("old and new cluster pg_controldata checksum versions do not match\n");
/*
* We cannot upgrade if the old cluster file encryption key length
* doesn't match the new one.
*/
if (oldctrl->file_encryption_keylen != newctrl->file_encryption_keylen)
pg_fatal("old and new clusters use different file encryption key lengths or\n"
"one cluster uses encryption and the other does not");
}

View File

@ -11,6 +11,7 @@
#include <sys/stat.h>
#include <fcntl.h>
#include <dirent.h>
#ifdef HAVE_COPYFILE_H
#include <copyfile.h>
#endif
@ -21,6 +22,7 @@
#include "access/visibilitymap.h"
#include "common/file_perm.h"
#include "common/file_utils.h"
#include "pg_upgrade.h"
#include "storage/bufpage.h"
#include "storage/checksum.h"

View File

@ -52,6 +52,7 @@ parseCommandLine(int argc, char *argv[])
{"check", no_argument, NULL, 'c'},
{"link", no_argument, NULL, 'k'},
{"retain", no_argument, NULL, 'r'},
{"authprompt", no_argument, NULL, 'R'},
{"jobs", required_argument, NULL, 'j'},
{"socketdir", required_argument, NULL, 's'},
{"verbose", no_argument, NULL, 'v'},
@ -102,7 +103,7 @@ parseCommandLine(int argc, char *argv[])
if (os_user_effective_id == 0)
pg_fatal("%s: cannot be run as root\n", os_info.progname);
while ((option = getopt_long(argc, argv, "d:D:b:B:cj:ko:O:p:P:rs:U:v",
while ((option = getopt_long(argc, argv, "d:D:b:B:cj:ko:O:p:P:rRs:U:v",
long_options, &optindex)) != -1)
{
switch (option)
@ -180,6 +181,10 @@ parseCommandLine(int argc, char *argv[])
log_opts.retain = true;
break;
case 'R':
user_opts.pass_terminal_fd = true;
break;
case 's':
user_opts.socketdir = pg_strdup(optarg);
break;

View File

@ -11,6 +11,7 @@
#include <sys/time.h>
#include "libpq-fe.h"
#include "common/kmgr_utils.h"
/* Use port in the private/dynamic port number range */
#define DEF_PGUPORT 50432
@ -219,6 +220,7 @@ typedef struct
bool date_is_int;
bool float8_pass_by_value;
bool data_checksum_version;
int file_encryption_keylen;
} ControlData;
/*
@ -293,6 +295,7 @@ typedef struct
int jobs; /* number of processes/threads to use */
char *socketdir; /* directory to use for Unix sockets */
bool ind_coll_unknown; /* mark unknown index collation versions */
bool pass_terminal_fd; /* pass -R to pg_ctl? */
} UserOpts;
typedef struct

View File

@ -244,8 +244,9 @@ start_postmaster(ClusterInfo *cluster, bool report_and_exit_on_error)
* vacuumdb --freeze actually freezes the tuples.
*/
snprintf(cmd, sizeof(cmd),
"\"%s/pg_ctl\" -w -l \"%s\" -D \"%s\" -o \"-p %d%s%s %s%s\" start",
cluster->bindir, SERVER_LOG_FILE, cluster->pgconfig, cluster->port,
"\"%s/pg_ctl\" -w%s -l \"%s\" -D \"%s\" -o \"-p %d%s%s %s%s\" start",
cluster->bindir, user_opts.pass_terminal_fd ? " -R" : "",
SERVER_LOG_FILE, cluster->pgconfig, cluster->port,
(cluster->controldata.cat_ver >=
BINARY_UPGRADE_SERVER_FLAG_CAT_VER) ? " -b" :
" -c autovacuum=off -c autovacuum_freeze_max_age=2000000000",

View File

@ -62,6 +62,7 @@ OBJS_COMMON = \
ip.o \
jsonapi.o \
keywords.o \
kmgr_utils.o \
kwlookup.o \
link-canary.o \
md5_common.o \
@ -82,10 +83,12 @@ OBJS_COMMON = \
ifeq ($(with_openssl),yes)
OBJS_COMMON += \
cipher_openssl.o \
protocol_openssl.o \
cryptohash_openssl.o
else
OBJS_COMMON += \
cipher.o \
cryptohash.o \
md5.o \
sha2.o

67
src/common/cipher.c Normal file
View File

@ -0,0 +1,67 @@
/*-------------------------------------------------------------------------
*
* cipher.c
* Shared frontend/backend for cryptographic functions
*
* Copyright (c) 2020, PostgreSQL Global Development Group
*
* IDENTIFICATION
* src/common/cipher.c
*
*-------------------------------------------------------------------------
*/
#ifndef FRONTEND
#include "postgres.h"
#else
#include "postgres_fe.h"
#endif
#include "common/cipher.h"
static cipher_failure(void);
PgCipherCtx *
pg_cipher_ctx_create(int cipher, uint8 *key, int klen, bool enc)
{
cipher_failure();
}
void
pg_cipher_ctx_free(PgCipherCtx *ctx)
{
cipher_failure();
}
bool
pg_cipher_encrypt(PgCipherCtx *ctx, const unsigned char *plaintext,
const int inlen, unsigned char *ciphertext, int *outlen,
const unsigned char *iv, const int ivlen,
unsigned char *outtag, const int taglen)
{
cipher_failure();
}
bool
pg_cipher_decrypt(PgCipherCtx *ctx, const unsigned char *ciphertext,
const int inlen, unsigned char *plaintext, int *outlen,
const unsigned char *iv, const int ivlen,
const unsigned char *intag, const int taglen)
{
cipher_failure();
}
static
cipher_failure(void)
{
#ifndef FRONTEND
ereport(ERROR,
(errcode(ERRCODE_CONFIG_FILE_ERROR),
(errmsg("cluster file encryption is not supported because OpenSSL is not supported by this build"),
errhint("Compile with --with-openssl to use this feature."))));
#else
fprintf(stderr, _("cluster file encryption is not supported because OpenSSL is not supported by this build"));
exit(1);
#endif
}

268
src/common/cipher_openssl.c Normal file
View File

@ -0,0 +1,268 @@
/*-------------------------------------------------------------------------
* cipher_openssl.c
* Cryptographic function using OpenSSL
*
* This contains the common low-level functions needed in both frontend and
* backend, for implement the database encryption.
*
* Portions Copyright (c) 2020, PostgreSQL Global Development Group
*
* IDENTIFICATION
* src/common/cipher_openssl.c
*
*-------------------------------------------------------------------------
*/
#ifndef FRONTEND
#include "postgres.h"
#else
#include "postgres_fe.h"
#endif
#include "common/cipher.h"
#include <openssl/conf.h>
#include <openssl/err.h>
#include <openssl/evp.h>
#include <openssl/ssl.h>
/*
* prototype for the EVP functions that return an algorithm, e.g.
* EVP_aes_128_gcm().
*/
typedef const EVP_CIPHER *(*ossl_EVP_cipher_func) (void);
static ossl_EVP_cipher_func get_evp_aes_gcm(int klen);
static EVP_CIPHER_CTX *ossl_cipher_ctx_create(int cipher, uint8 *key, int klen,
bool enc);
/*
* Return a newly created cipher context. 'cipher' specifies cipher algorithm
* by identifer like PG_CIPHER_XXX.
*/
PgCipherCtx *
pg_cipher_ctx_create(int cipher, uint8 *key, int klen, bool enc)
{
PgCipherCtx *ctx = NULL;
if (cipher >= PG_MAX_CIPHER_ID)
return NULL;
ctx = ossl_cipher_ctx_create(cipher, key, klen, enc);
return ctx;
}
void
pg_cipher_ctx_free(PgCipherCtx *ctx)
{
EVP_CIPHER_CTX_free(ctx);
}
/*
* Encryption routine to encrypt data provided.
*
* ctx is the encryption context which must have been created previously.
*
* plaintext is the data we are going to encrypt
* inlen is the length of the data to encrypt
*
* ciphertext is the encrypted result
* outlen is the encrypted length
*
* iv is the IV to use.
* ivlen is the IV length to use.
*
* outtag is the resulting tag.
* taglen is the length of the tag.
*/
bool
pg_cipher_encrypt(PgCipherCtx *ctx,
const unsigned char *plaintext, const int inlen,
unsigned char *ciphertext, int *outlen,
const unsigned char *iv, const int ivlen,
unsigned char *outtag, const int taglen)
{
int len;
int enclen;
Assert(ctx != NULL);
/*
* Here we are setting the IV for the context which was passed
* in. Note that we signal to OpenSSL that we are configuring
* a new value for the context by passing in 'NULL' for the
* 2nd ('type') parameter.
*/
/* Set the IV length first */
if (!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, ivlen, NULL))
return false;
/* Set the IV for this encryption. */
if (!EVP_EncryptInit_ex(ctx, NULL, NULL, NULL, iv))
return false;
/*
* This is the function which is actually performing the
* encryption for us.
*/
if (!EVP_EncryptUpdate(ctx, ciphertext, &len, plaintext, inlen))
return false;
enclen = len;
/* Finalize the encryption, which could add more to output. */
if (!EVP_EncryptFinal_ex(ctx, ciphertext + enclen, &len))
return false;
*outlen = enclen + len;
/*
* Once all of the encryption has been completed we grab
* the tag.
*/
if (!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, taglen, outtag))
return false;
return true;
}
/*
* Decryption routine
*
* ctx is the encryption context which must have been created previously.
*
* ciphertext is the data we are going to decrypt
* inlen is the length of the data to decrypt
*
* plaintext is the decrypted result
* outlen is the decrypted length
*
* iv is the IV to use.
* ivlen is the length of the IV.
*
* intag is the tag to use to verify.
* taglen is the length of the tag.
*/
bool
pg_cipher_decrypt(PgCipherCtx *ctx,
const unsigned char *ciphertext, const int inlen,
unsigned char *plaintext, int *outlen,
const unsigned char *iv, const int ivlen,
unsigned char *intag, const int taglen)
{
int declen;
int len;
/*
* Here we are setting the IV for the context which was passed
* in. Note that we signal to OpenSSL that we are configuring
* a new value for the context by passing in 'NULL' for the
* 2nd ('type') parameter.
*/
/* Set the IV length first */
if (!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, ivlen, NULL))
return false;
/* Set the IV for this decryption. */
if (!EVP_DecryptInit_ex(ctx, NULL, NULL, NULL, iv))
return false;
/*
* This is the function which is actually performing the
* decryption for us.
*/
if (!EVP_DecryptUpdate(ctx, plaintext, &len, ciphertext, inlen))
return false;
declen = len;
/* Set the expected tag value. */
if (!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, taglen, intag))
return false;
/*
* Finalize the decryption, which could add more to output,
* this is also the step which checks the tag and we MUST
* fail if this indicates an invalid tag!
*/
if (!EVP_DecryptFinal_ex(ctx, plaintext + declen, &len))
return false;
*outlen = declen + len;
return true;
}
/*
* Returns the correct cipher functions for OpenSSL based
* on the key length requested.
*/
static ossl_EVP_cipher_func
get_evp_aes_gcm(int klen)
{
switch (klen)
{
case PG_AES128_KEY_LEN:
return EVP_aes_128_gcm;
case PG_AES192_KEY_LEN:
return EVP_aes_192_gcm;
case PG_AES256_KEY_LEN:
return EVP_aes_256_gcm;
default:
return NULL;
}
}
/*
* Initialize and return an EVP_CIPHER_CTX. Returns NULL if the given
* cipher algorithm is not supported or on failure.
*/
static EVP_CIPHER_CTX *
ossl_cipher_ctx_create(int cipher, uint8 *key, int klen, bool enc)
{
EVP_CIPHER_CTX *ctx;
ossl_EVP_cipher_func func;
int ret;
ctx = EVP_CIPHER_CTX_new();
/*
* We currently only support AES GCM but others could be
* added in the future.
*/
switch (cipher)
{
case PG_CIPHER_AES_GCM:
func = get_evp_aes_gcm(klen);
if (!func)
goto failed;
break;
default:
goto failed;
}
/*
* We create the context here based on the cipher requested and the provided
* key. Note that the IV will be provided in the actual encryption call
* through another EVP_EncryptInit_ex call- this is fine as long as 'type'
* is passed in as NULL!
*/
if (enc)
ret = EVP_EncryptInit_ex(ctx, (const EVP_CIPHER *) func(), NULL, key, NULL);
else
ret = EVP_DecryptInit_ex(ctx, (const EVP_CIPHER *) func(), NULL, key, NULL);
if (!ret)
goto failed;
/* Set the key length based on the key length requested. */
if (!EVP_CIPHER_CTX_set_key_length(ctx, klen))
goto failed;
return ctx;
failed:
EVP_CIPHER_CTX_free(ctx);
return NULL;
}

507
src/common/kmgr_utils.c Normal file
View File

@ -0,0 +1,507 @@
/*-------------------------------------------------------------------------
*
* kmgr_utils.c
* Shared frontend/backend for cluster file encryption
*
* Copyright (c) 2020, PostgreSQL Global Development Group
*
* IDENTIFICATION
* src/common/kmgr_utils.c
*
*-------------------------------------------------------------------------
*/
#ifndef FRONTEND
#include "postgres.h"
#else
#include "postgres_fe.h"
#endif
#include <unistd.h>
#include <sys/stat.h>
#ifdef FRONTEND
#include "common/logging.h"
#endif
#include "common/cryptohash.h"
#include "common/file_perm.h"
#include "common/kmgr_utils.h"
#include "common/hex_decode.h"
#include "common/string.h"
#include "crypto/kmgr.h"
#include "lib/stringinfo.h"
#include "postmaster/postmaster.h"
#include "storage/fd.h"
#ifndef FRONTEND
#include "pgstat.h"
#include "storage/fd.h"
#endif
#define KMGR_PROMPT_MSG "Enter authentication needed to generate the cluster key: "
#ifdef FRONTEND
static FILE *open_pipe_stream(const char *command);
static int close_pipe_stream(FILE *file);
#endif
static void read_one_keyfile(const char *dataDir, uint32 id, CryptoKey *key_p);
/*
* Encrypt the given data. Return true and set encrypted data to 'out' if
* success. Otherwise return false. The caller must allocate sufficient space
* for cipher data calculated by using KmgrSizeOfCipherText(). Please note that
* this function modifies 'out' data even on failure case.
*/
bool
kmgr_wrap_key(PgCipherCtx *ctx, CryptoKey *in, CryptoKey *out)
{
int len, enclen;
unsigned char iv[sizeof(in->pgkey_id) + sizeof(in->counter)];
Assert(ctx && in && out);
/* Get the actual length of the key we are wrapping */
memcpy(&len, in->encrypted_key, sizeof(len));
/* Key ID remains the same */
out->pgkey_id = in->pgkey_id;
/* Increment the counter */
out->counter = in->counter + 1;
/* Construct the IV we are going to use, see kmgr_utils.h */
memcpy(iv, &out->pgkey_id, sizeof(out->pgkey_id));
memcpy(iv + sizeof(out->pgkey_id), &out->counter, sizeof(out->counter));
if (!pg_cipher_encrypt(ctx,
in->encrypted_key, /* Plaintext source, key length + key */
sizeof(in->encrypted_key), /* Full data length */
out->encrypted_key, /* Ciphertext result */
&enclen, /* Resulting length, must match input for us */
iv, /* Generated IV from above */
sizeof(iv), /* Length of the IV */
(unsigned char *) &out->tag, /* Resulting tag */
sizeof(out->tag))) /* Length of our tag */
return false;
Assert(enclen == sizeof(in->encrypted_key));
return true;
}
/*
* Decrypt the given Data. Return true and set plain text data to `out` if
* success. Otherwise return false. The caller must allocate sufficient space
* for cipher data calculated by using KmgrSizeOfPlainText(). Please note that
* this function modifies 'out' data even on failure case.
*/
bool
kmgr_unwrap_key(PgCipherCtx *ctx, CryptoKey *in, CryptoKey *out)
{
int declen;
unsigned char iv[sizeof(in->pgkey_id) + sizeof(in->counter)];
Assert(ctx && in && out);
out->pgkey_id = in->pgkey_id;
out->counter = in->counter;
out->tag = in->tag;
/* Construct the IV we are going to use, see kmgr_utils.h */
memcpy(iv, &out->pgkey_id, sizeof(out->pgkey_id));
memcpy(iv + sizeof(out->pgkey_id), &out->counter, sizeof(out->counter));
/* Decrypt encrypted data */
if (!pg_cipher_decrypt(ctx,
in->encrypted_key, /* Encrypted source */
sizeof(in->encrypted_key), /* Length of encrypted data */
out->encrypted_key, /* Plaintext result */
&declen, /* Length of plaintext */
iv, /* IV we constructed above */
sizeof(iv), /* Size of our IV */
(unsigned char *) &in->tag, /* Tag which will be verified */
sizeof(in->tag))) /* Size of our tag */
return false;
Assert(declen == sizeof(in->encrypted_key));
return true;
}
/*
* Verify the correctness of the given cluster key by unwrapping the given keys.
* If the given cluster key is correct we set unwrapped keys to out_keys and return
* true. Otherwise return false. Please note that this function changes the
* contents of out_keys even on failure. Both in_keys and out_keys must be the
* same length, nkey.
*/
bool
kmgr_verify_cluster_key(unsigned char *cluster_key,
CryptoKey *in_keys, CryptoKey *out_keys, int nkeys)
{
PgCipherCtx *ctx;
/*
* Create decryption context with cluster KEK.
*/
ctx = pg_cipher_ctx_create(PG_CIPHER_AES_GCM, cluster_key,
KMGR_CLUSTER_KEY_LEN, false);
for (int i = 0; i < nkeys; i++)
{
if (!kmgr_unwrap_key(ctx, &(in_keys[i]), &(out_keys[i])))
{
/* The cluster key is not correct */
pg_cipher_ctx_free(ctx);
return false;
}
explicit_bzero(&(in_keys[i]), sizeof(in_keys[i]));
}
/* The cluster key is correct, free the cipher context */
pg_cipher_ctx_free(ctx);
return true;
}
/*
* Run cluster key command.
*
* prompt will be substituted for %p, file descriptor for %R
*
* The result will be put in buffer buf, which is of size size.
* The return value is the length of the actual result.
*/
int
kmgr_run_cluster_key_command(char *cluster_key_command, char *buf,
int size, char *dir)
{
StringInfoData command;
const char *sp;
FILE *fh;
int pclose_rc;
size_t len = 0;
buf[0] = '\0';
Assert(size > 0);
/*
* Build the command to be executed.
*/
initStringInfo(&command);
for (sp = cluster_key_command; *sp; sp++)
{
if (*sp == '%')
{
switch (sp[1])
{
case 'd':
{
char *nativePath;
sp++;
/*
* This needs to use a placeholder to not modify the
* input with the conversion done via
* make_native_path().
*/
nativePath = pstrdup(dir);
make_native_path(nativePath);
appendStringInfoString(&command, nativePath);
pfree(nativePath);
break;
}
case 'p':
sp++;
appendStringInfoString(&command, KMGR_PROMPT_MSG);
break;
case 'R':
{
char fd_str[20];
if (terminal_fd == -1)
{
#ifdef FRONTEND
pg_log_fatal("cluster key command referenced %%R, but --authprompt not specified");
exit(EXIT_FAILURE);
#else
ereport(ERROR,
(errcode(ERRCODE_INTERNAL_ERROR),
errmsg("cluster key command referenced %%R, but --authprompt not specified")));
#endif
}
sp++;
snprintf(fd_str, sizeof(fd_str), "%d", terminal_fd);
appendStringInfoString(&command, fd_str);
break;
}
case '%':
/* convert %% to a single % */
sp++;
appendStringInfoChar(&command, *sp);
break;
default:
/* otherwise treat the % as not special */
appendStringInfoChar(&command, *sp);
break;
}
}
else
{
appendStringInfoChar(&command, *sp);
}
}
#ifdef FRONTEND
fh = open_pipe_stream(command.data);
if (fh == NULL)
{
pg_log_fatal("could not execute command \"%s\": %m",
command.data);
exit(EXIT_FAILURE);
}
#else
fh = OpenPipeStream(command.data, "r");
if (fh == NULL)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not execute command \"%s\": %m",
command.data)));
#endif
if (!fgets(buf, size, fh))
{
if (ferror(fh))
{
#ifdef FRONTEND
pg_log_fatal("could not read from command \"%s\": %m",
command.data);
exit(EXIT_FAILURE);
#else
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not read from command \"%s\": %m",
command.data)));
#endif
}
}
#ifdef FRONTEND
pclose_rc = close_pipe_stream(fh);
#else
pclose_rc = ClosePipeStream(fh);
#endif
if (pclose_rc == -1)
{
#ifdef FRONTEND
pg_log_fatal("could not close pipe to external command: %m");
exit(EXIT_FAILURE);
#else
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not close pipe to external command: %m")));
#endif
}
else if (pclose_rc != 0)
{
#ifdef FRONTEND
pg_log_fatal("command \"%s\" failed", command.data);
exit(EXIT_FAILURE);
#else
ereport(ERROR,
(errcode_for_file_access(),
errmsg("command \"%s\" failed",
command.data),
errdetail_internal("%s", wait_result_to_str(pclose_rc))));
#endif
}
/* strip trailing newline and carriage return */
len = pg_strip_crlf(buf);
pfree(command.data);
return len;
}
#ifdef FRONTEND
static FILE *
open_pipe_stream(const char *command)
{
FILE *res;
#ifdef WIN32
size_t cmdlen = strlen(command);
char *buf;
int save_errno;
buf = malloc(cmdlen + 2 + 1);
if (buf == NULL)
{
errno = ENOMEM;
return NULL;
}
buf[0] = '"';
mempcy(&buf[1], command, cmdlen);
buf[cmdlen + 1] = '"';
buf[cmdlen + 2] = '\0';
res = _popen(buf, "r");
save_errno = errno;
free(buf);
errno = save_errno;
#else
res = popen(command, "r");
#endif /* WIN32 */
return res;
}
static int
close_pipe_stream(FILE *file)
{
#ifdef WIN32
return _pclose(file);
#else
return pclose(file);
#endif /* WIN32 */
}
#endif /* FRONTEND */
CryptoKey *
kmgr_get_cryptokeys(const char *path, int *nkeys)
{
struct dirent *de;
DIR *dir;
CryptoKey *keys;
#ifndef FRONTEND
if ((dir = AllocateDir(path)) == NULL)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not open directory \"%s\": %m",
path)));
#else
if ((dir = opendir(path)) == NULL)
pg_log_fatal("could not open directory \"%s\": %m", path);
#endif
keys = (CryptoKey *) palloc0(sizeof(CryptoKey) * KMGR_MAX_INTERNAL_KEYS);
*nkeys = 0;
#ifndef FRONTEND
while ((de = ReadDir(dir, LIVE_KMGR_DIR)) != NULL)
#else
while ((de = readdir(dir)) != NULL)
#endif
{
if (strspn(de->d_name, "0123456789") == strlen(de->d_name))
{
uint32 id = strtoul(de->d_name, NULL, 10);
if (id < 0 || id >= KMGR_MAX_INTERNAL_KEYS)
{
#ifndef FRONTEND
elog(ERROR, "invalid cryptographic key identifier %u", id);
#else
pg_log_fatal("invalid cryptographic key identifier %u", id);
#endif
}
if (*nkeys >= KMGR_MAX_INTERNAL_KEYS)
{
#ifndef FRONTEND
elog(ERROR, "too many cryptographic keys");
#else
pg_log_fatal("too many cryptographic keys");
#endif
}
read_one_keyfile(path, id, &(keys[id]));
(*nkeys)++;
}
}
#ifndef FRONTEND
FreeDir(dir);
#else
closedir(dir);
#endif
return keys;
}
static void
read_one_keyfile(const char *cryptoKeyDir, uint32 id, CryptoKey *key_p)
{
char path[MAXPGPATH];
int fd;
int r;
CryptoKeyFilePath(path, cryptoKeyDir, id);
#ifndef FRONTEND
if ((fd = OpenTransientFile(path, O_RDONLY | PG_BINARY)) == -1)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not open file \"%s\" for reading: %m",
path)));
#else
if ((fd = open(path, O_RDONLY | PG_BINARY, 0)) == -1)
pg_log_fatal("could not open file \"%s\" for reading: %m",
path);
#endif
#ifndef FRONTEND
pgstat_report_wait_start(WAIT_EVENT_KEY_FILE_READ);
#endif
/* Get key bytes */
r = read(fd, key_p, sizeof(CryptoKey));
if (r != sizeof(CryptoKey))
{
if (r < 0)
{
#ifndef FRONTEND
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not read file \"%s\": %m", path)));
#else
pg_log_fatal("could not read file \"%s\": %m", path);
#endif
}
else
{
#ifndef FRONTEND
ereport(ERROR,
(errcode(ERRCODE_DATA_CORRUPTED),
errmsg("could not read file \"%s\": read %d of %zu",
path, r, sizeof(CryptoKey))));
#else
pg_log_fatal("could not read file \"%s\": read %d of %zu",
path, r, sizeof(CryptoKey));
#endif
}
}
#ifndef FRONTEND
pgstat_report_wait_end();
#endif
#ifndef FRONTEND
if (CloseTransientFile(fd) != 0)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not close file \"%s\": %m",
path)));
#else
if (close(fd) != 0)
pg_log_fatal("could not close file \"%s\": %m", path);
#endif
}

View File

@ -22,7 +22,7 @@
/* Version identifier for this pg_control format */
#define PG_CONTROL_VERSION 1300
#define PG_CONTROL_VERSION 1400
/* Nonce key length, see below */
#define MOCK_AUTH_NONCE_LEN 32
@ -226,6 +226,9 @@ typedef struct ControlFileData
*/
char mock_authentication_nonce[MOCK_AUTH_NONCE_LEN];
/* File encryption key length. Zero if disabled. */
int file_encryption_keylen;
/* CRC of all above ... MUST BE LAST! */
pg_crc32c crc;
} ControlFileData;

View File

@ -0,0 +1,62 @@
/*-------------------------------------------------------------------------
*
* cipher.h
* Declarations for cryptographic functions
*
* Portions Copyright (c) 2020, PostgreSQL Global Development Group
*
* src/include/common/cipher.h
*
*-------------------------------------------------------------------------
*/
#ifndef PG_CIPHER_H
#define PG_CIPHER_H
#ifdef USE_OPENSSL
#include <openssl/evp.h>
#include <openssl/conf.h>
#include <openssl/err.h>
#endif
/*
* Supported symmetric encryption algorithm. These identifiers are passed
* to pg_cipher_ctx_create() function, and then actual encryption
* implementations need to initialize their context of the given encryption
* algorithm.
*/
#define PG_CIPHER_AES_GCM 0
#define PG_MAX_CIPHER_ID 1
/* AES128/192/256 various length definitions */
#define PG_AES128_KEY_LEN (128 / 8)
#define PG_AES192_KEY_LEN (192 / 8)
#define PG_AES256_KEY_LEN (256 / 8)
/*
* The encrypted data is a series of blocks of size. Initialization
* vector(IV) is the same size of cipher block.
*/
#define PG_AES_BLOCK_SIZE 16
#define PG_AES_IV_SIZE (PG_AES_BLOCK_SIZE)
#ifdef USE_OPENSSL
typedef EVP_CIPHER_CTX PgCipherCtx;
#else
typedef void PgCipherCtx;
#endif
extern PgCipherCtx *pg_cipher_ctx_create(int cipher, uint8 *key, int klen,
bool enc);
extern void pg_cipher_ctx_free(PgCipherCtx *ctx);
extern bool pg_cipher_encrypt(PgCipherCtx *ctx,
const unsigned char *plaintext, const int inlen,
unsigned char *ciphertext, int *outlen,
const unsigned char *iv, const int ivlen,
unsigned char *tag, const int taglen);
extern bool pg_cipher_decrypt(PgCipherCtx *ctx,
const unsigned char *ciphertext, const int inlen,
unsigned char *plaintext, int *outlen,
const unsigned char *iv, const int ivlen,
unsigned char *intag, const int taglen);
#endif /* PG_CIPHER_H */

View File

@ -0,0 +1,98 @@
/*-------------------------------------------------------------------------
*
* kmgr_utils.h
* Declarations for utility function for file encryption key
*
* Portions Copyright (c) 2020, PostgreSQL Global Development Group
*
* src/include/common/kmgr_utils.h
*
*-------------------------------------------------------------------------
*/
#ifndef KMGR_UTILS_H
#define KMGR_UTILS_H
#include "common/cipher.h"
/* Current version number */
#define KMGR_VERSION 1
/*
* Directories where cluster file encryption keys reside within PGDATA.
*/
#define KMGR_DIR "pg_cryptokeys"
#define KMGR_DIR_PID KMGR_DIR"/pg_alterckey.pid"
#define LIVE_KMGR_DIR KMGR_DIR"/live"
/* used during cluster key rotation */
#define NEW_KMGR_DIR KMGR_DIR"/new"
#define OLD_KMGR_DIR KMGR_DIR"/old"
/* CryptoKey file name is keys id */
#define CryptoKeyFilePath(path, dir, id) \
snprintf((path), MAXPGPATH, "%s/%d", (dir), (id))
/*
* Identifiers of internal keys.
*/
#define KMGR_KEY_ID_REL 0
#define KMGR_KEY_ID_WAL 1
#define KMGR_MAX_INTERNAL_KEYS 2
/* We always, today, use a 256-bit AES key. */
#define KMGR_CLUSTER_KEY_LEN PG_AES256_KEY_LEN
/* double for hex format, plus some for spaces, \r,\n, and null byte */
#define ALLOC_KMGR_CLUSTER_KEY_LEN (KMGR_CLUSTER_KEY_LEN * 2 + 10 + 2 + 1)
/* Maximum length of key the key manager can store */
#define KMGR_MAX_KEY_LEN 256
#define KMGR_MAX_KEY_LEN_BYTES KMGR_MAX_KEY_LEN / 8
#define KMGR_MAX_WRAPPED_KEY_LEN KmgrSizeOfCipherText(KMGR_MAX_KEY_LEN)
/*
* Cryptographic key data structure.
*
* This is the structure we use to write out the encrypted keys.
*
* pgkey_id is the identifier for this key (should be same as the
* file name and be one of KMGR_KEY_ID_* from above). This is what
* we consider our 'context' or 'fixed' portion of the deterministic
* IV we create.
*
* counter is updated each time we use the cluster KEK to encrypt a
* new key. This is our the 'invocation' field of the deterministic
* IV we create.
*
* Absolutely essential when using GCM (or CTR) is that the IV is unique,
* for a given key, but a deterministic IV such as this is perfectly
* acceptable and encouraged. If (and only if!) the KEK is changed to a
* new key, then we can re-initialize the counter.
*
* Detailed discussion of deterministic IV creation can be found here:
*
* https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38d.pdf
*
* tag is the GCM tag which is produced and must be validated in order
* to be able to trust the results of our decryption.
*
* encrypted_key is the encrypted key length (as an int) + encrypted key.
*/
typedef struct CryptoKey
{
uint64 pgkey_id; /* Upper half of IV */
uint64 counter; /* Lower half of IV */
uint128 tag; /* GCM tag */
unsigned char encrypted_key[sizeof(int) + KMGR_MAX_KEY_LEN_BYTES];
} CryptoKey;
extern bool kmgr_wrap_key(PgCipherCtx *ctx, CryptoKey *in, CryptoKey *out);
extern bool kmgr_unwrap_key(PgCipherCtx *ctx, CryptoKey *in, CryptoKey *out);
extern bool kmgr_verify_cluster_key(unsigned char *cluster_key,
CryptoKey *in_keys, CryptoKey *out_keys,
int nkey);
extern int kmgr_run_cluster_key_command(char *cluster_key_command,
char *buf, int size, char *dir);
extern CryptoKey *kmgr_get_cryptokeys(const char *path, int *nkeys);
#endif /* KMGR_UTILS_H */

29
src/include/crypto/kmgr.h Normal file
View File

@ -0,0 +1,29 @@
/*-------------------------------------------------------------------------
*
* kmgr.h
*
* Portions Copyright (c) 2020, PostgreSQL Global Development Group
*
* src/include/crypto/kmgr.h
*
*-------------------------------------------------------------------------
*/
#ifndef KMGR_H
#define KMGR_H
#include "common/cipher.h"
#include "common/kmgr_utils.h"
#include "storage/relfilenode.h"
#include "storage/bufpage.h"
/* GUC parameters */
extern int file_encryption_keylen;
extern char *cluster_key_command;
extern Size KmgrShmemSize(void);
extern void KmgrShmemInit(void);
extern void BootStrapKmgr(void);
extern void InitializeKmgr(void);
extern const CryptoKey *KmgrGetKey(int id);
#endif /* KMGR_H */

View File

@ -1010,6 +1010,9 @@ typedef enum
WAIT_EVENT_DATA_FILE_TRUNCATE,
WAIT_EVENT_DATA_FILE_WRITE,
WAIT_EVENT_DSM_FILL_ZERO_WRITE,
WAIT_EVENT_KEY_FILE_READ,
WAIT_EVENT_KEY_FILE_WRITE,
WAIT_EVENT_KEY_FILE_SYNC,
WAIT_EVENT_LOCK_FILE_ADDTODATADIR_READ,
WAIT_EVENT_LOCK_FILE_ADDTODATADIR_SYNC,
WAIT_EVENT_LOCK_FILE_ADDTODATADIR_WRITE,

View File

@ -30,6 +30,8 @@ extern bool enable_bonjour;
extern char *bonjour_name;
extern bool restart_after_crash;
extern int terminal_fd;
#ifdef WIN32
extern HANDLE PostmasterHandle;
#else

View File

@ -89,6 +89,7 @@ enum config_group
STATS,
STATS_MONITORING,
STATS_COLLECTOR,
ENCRYPTION,
AUTOVACUUM,
CLIENT_CONN,
CLIENT_CONN_STATEMENT,

View File

@ -30,7 +30,7 @@ endif
endif
ifeq ($(with_openssl),yes)
ifneq (,$(filter ssl,$(PG_TEST_EXTRA)))
SUBDIRS += ssl
SUBDIRS += ssl crypto
endif
endif

View File

@ -122,18 +122,20 @@ sub mkvcbuild
archive.c base64.c checksum_helper.c
config_info.c controldata_utils.c d2s.c encnames.c exec.c
f2s.c file_perm.c file_utils.c hashfn.c hex_decode.c ip.c jsonapi.c
keywords.c kwlookup.c link-canary.c md5_common.c
keywords.c kmgr_utils.c kwlookup.c link-canary.c md5_common.c
pg_get_line.c pg_lzcompress.c pgfnames.c psprintf.c relpath.c rmtree.c
saslprep.c scram-common.c string.c stringinfo.c unicode_norm.c username.c
wait_error.c wchar.c);
if ($solution->{options}->{openssl})
{
push(@pgcommonallfiles, 'cipher_openssl.c');
push(@pgcommonallfiles, 'cryptohash_openssl.c');
push(@pgcommonallfiles, 'protocol_openssl.c');
}
else
{
push(@pgcommonallfiles, 'cipher.c');
push(@pgcommonallfiles, 'cryptohash.c');
push(@pgcommonallfiles, 'md5.c');
push(@pgcommonallfiles, 'sha2.c');