mirror of https://github.com/postgres/postgres
Revert "Add key management system" (978f869b99) & later commits
The patch needs test cases, reorganization, and cfbot testing. Technically reverts commits 5c31afc49d..e35b2bad1a (exclusive/inclusive) and 08db7c63f3..ccbe34139b. Reported-by: Tom Lane, Michael Paquier Discussion: https://postgr.es/m/E1ktAAG-0002V2-VB@gemulon.postgresql.orgpull/59/head
parent
facad31474
commit
3187ef7c46
@ -1,97 +0,0 @@ |
||||
<!-- 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 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 |
||||
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 encryption 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 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> |
||||
@ -1,197 +0,0 @@ |
||||
<!-- |
||||
doc/src/sgml/ref/pg_alterckey.sgml |
||||
PostgreSQL documentation |
||||
--> |
||||
|
||||
<refentry id="app-pg_alterckey"> |
||||
<indexterm zone="app-pg_alterckey"> |
||||
<primary>pg_alterckey</primary> |
||||
</indexterm> |
||||
|
||||
<refmeta> |
||||
<refentrytitle><application>pg_alterckey</application></refentrytitle> |
||||
<manvolnum>1</manvolnum> |
||||
<refmiscinfo>Application</refmiscinfo> |
||||
</refmeta> |
||||
|
||||
<refnamediv> |
||||
<refname>pg_alterckey</refname> |
||||
<refpurpose>alter the <productname>PostgreSQL</productname> cluster key</refpurpose> |
||||
</refnamediv> |
||||
|
||||
<refsynopsisdiv> |
||||
<cmdsynopsis> |
||||
<command>pg_alterckey</command> |
||||
|
||||
<group choice="plain"> |
||||
<arg choice="plain"><option>-R</option></arg> |
||||
<arg choice="plain"><option>--authprompt</option></arg> |
||||
</group> |
||||
|
||||
<arg choice="plain"> |
||||
<replaceable class="parameter">old_cluster_key_command</replaceable> |
||||
<replaceable class="parameter">new_cluster_key_command</replaceable> |
||||
</arg> |
||||
|
||||
<arg choice="opt"> |
||||
<group choice="plain"> |
||||
<arg choice="plain"><option>-D</option></arg> |
||||
<arg choice="plain"><option>--pgdata</option></arg> |
||||
</group> |
||||
<replaceable class="parameter">datadir</replaceable> |
||||
</arg> |
||||
|
||||
</cmdsynopsis> |
||||
|
||||
<cmdsynopsis> |
||||
<command>pg_alterckey</command> |
||||
|
||||
<group choice="opt"> |
||||
<arg choice="plain"><option>-R</option></arg> |
||||
<arg choice="plain"><option>--authprompt</option></arg> |
||||
</group> |
||||
|
||||
<group choice="plain"> |
||||
<arg choice="plain"><option>-r</option></arg> |
||||
<arg choice="plain"><option>--repair</option></arg> |
||||
</group> |
||||
|
||||
<arg choice="opt"> |
||||
<group choice="opt"> |
||||
<arg choice="plain"><option>-D</option></arg> |
||||
<arg choice="plain"><option>--pgdata</option></arg> |
||||
</group> |
||||
<replaceable class="parameter">datadir</replaceable> |
||||
</arg> |
||||
|
||||
</cmdsynopsis> |
||||
</refsynopsisdiv> |
||||
|
||||
<refsect1 id="r1-app-pg_alterckey-1"> |
||||
<title>Description</title> |
||||
<para> |
||||
<command>pg_alterckey</command> alters the cluster key used |
||||
for cluster file encryption. The cluster key is initially set |
||||
during <xref linkend="app-initdb"/>. The command can be run while the |
||||
server is running or stopped. The new password must be used the next |
||||
time the server is started. |
||||
</para> |
||||
|
||||
<para> |
||||
Technically, <command>pg_alterckey</command> changes the key |
||||
encryption key (<acronym>KEK</acronym>) which encrypts the data |
||||
encryption keys; it does not change the data encryption keys. It does |
||||
this by decrypting each data encryption key using the <replaceable |
||||
class="parameter">old_cluster_key_command</replaceable>, |
||||
re-encrypting it using the <replaceable |
||||
class="parameter">new_cluster_key_command</replaceable>, and |
||||
then writes the result back to the cluster directory. |
||||
</para> |
||||
|
||||
<para> |
||||
See the <xref linkend="app-initdb"/> documentation for how to define |
||||
the old and new passphrase commands. You can use different executables |
||||
for these commands, or you can use the same executable with different |
||||
arguments to specify retrieval of the old or new key. |
||||
</para> |
||||
|
||||
<para> |
||||
When started, <command>pg_alterckey</command> repairs any files that |
||||
remain from previous <command>pg_alterckey</command> failures before |
||||
altering the cluster key. To perform only the repair task, |
||||
use the <option>--repair</option> option. The server will not start |
||||
if repair is needed, though a running server is unaffected by an |
||||
unrepaired cluster key configuration. |
||||
</para> |
||||
|
||||
<para> |
||||
You can specify the data directory on the command line, or use |
||||
the environment variable <envar>PGDATA</envar>. |
||||
</para> |
||||
</refsect1> |
||||
|
||||
<refsect1> |
||||
<title>Options</title> |
||||
|
||||
<para> |
||||
<variablelist> |
||||
<varlistentry> |
||||
<term><option>-R</option></term> |
||||
<term><option>--authprompt</option></term> |
||||
<listitem> |
||||
<para> |
||||
Allows the <option>old_cluster_key_command</option> and |
||||
<option>new_cluster_key_command</option> commands |
||||
to prompt for a passphrase or PIN. |
||||
</para> |
||||
</listitem> |
||||
</varlistentry> |
||||
</variablelist> |
||||
</para> |
||||
|
||||
<para> |
||||
Other options: |
||||
|
||||
<variablelist> |
||||
<varlistentry> |
||||
<term><option>-V</option></term> |
||||
<term><option>--version</option></term> |
||||
<listitem> |
||||
<para> |
||||
Print the <application>pg_alterckey</application> version and exit. |
||||
</para> |
||||
</listitem> |
||||
</varlistentry> |
||||
|
||||
<varlistentry> |
||||
<term><option>-?</option></term> |
||||
<term><option>--help</option></term> |
||||
<listitem> |
||||
<para> |
||||
Show help about <application>pg_alterckey</application> command line |
||||
arguments, and exit. |
||||
</para> |
||||
</listitem> |
||||
</varlistentry> |
||||
|
||||
</variablelist> |
||||
</para> |
||||
|
||||
</refsect1> |
||||
|
||||
<refsect1> |
||||
<title>Environment</title> |
||||
|
||||
<variablelist> |
||||
<varlistentry> |
||||
<term><envar>PGDATA</envar></term> |
||||
|
||||
<listitem> |
||||
<para> |
||||
Default data directory location |
||||
</para> |
||||
</listitem> |
||||
</varlistentry> |
||||
|
||||
<varlistentry> |
||||
<term><envar>PG_COLOR</envar></term> |
||||
<listitem> |
||||
<para> |
||||
Specifies whether to use color in diagnostic messages. Possible values |
||||
are <literal>always</literal>, <literal>auto</literal> and |
||||
<literal>never</literal>. |
||||
</para> |
||||
</listitem> |
||||
</varlistentry> |
||||
</variablelist> |
||||
</refsect1> |
||||
|
||||
<refsect1> |
||||
<title>See Also</title> |
||||
|
||||
<simplelist type="inline"> |
||||
<member><xref linkend="app-initdb"/></member> |
||||
</simplelist> |
||||
</refsect1> |
||||
|
||||
</refentry> |
||||
@ -1,18 +0,0 @@ |
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
# 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 |
||||
@ -1,50 +0,0 @@ |
||||
#!/bin/sh |
||||
|
||||
# This uses the AWS Secrets Manager using the AWS CLI and OpenSSL. |
||||
|
||||
[ "$#" -ne 1 ] && echo "cluster_key_command usage: $0 \"%d\"" 1>&2 && exit 1 |
||||
# No need for %R or -R since we are not prompting |
||||
|
||||
DIR="$1" |
||||
[ ! -e "$DIR" ] && echo "$DIR does not exist" 1>&2 && exit 1 |
||||
[ ! -d "$DIR" ] && echo "$DIR is not a directory" 1>&2 && exit 1 |
||||
|
||||
# File containing the id of the AWS secret |
||||
AWS_ID_FILE="$DIR/aws-secret.id" |
||||
|
||||
|
||||
# ---------------------------------------------------------------------- |
||||
|
||||
|
||||
# Create an AWS Secrets Manager secret? |
||||
if [ ! -e "$AWS_ID_FILE" ] |
||||
then # The 'postgres' operating system user must have permission to |
||||
# access the AWS CLI |
||||
|
||||
# The epoch-time/directory/hostname combination is unique |
||||
HASH=$(echo -n "$(date '+%s')$DIR$(hostname)" | sha1sum | cut -d' ' -f1) |
||||
AWS_SECRET_ID="Postgres-cluster-key-$HASH" |
||||
|
||||
# Use stdin to avoid passing the secret on the command line |
||||
openssl rand -hex 32 | |
||||
aws secretsmanager create-secret \ |
||||
--name "$AWS_SECRET_ID" \ |
||||
--description 'Used for Postgres cluster file encryption' \ |
||||
--secret-string 'file:///dev/stdin' \ |
||||
--output text > /dev/null |
||||
if [ "$?" -ne 0 ] |
||||
then echo 'cluster key generation failed' 1>&2 |
||||
exit 1 |
||||
fi |
||||
|
||||
echo "$AWS_SECRET_ID" > "$AWS_ID_FILE" |
||||
fi |
||||
|
||||
if ! aws secretsmanager get-secret-value \ |
||||
--secret-id "$(cat "$AWS_ID_FILE")" \ |
||||
--output text |
||||
then echo 'cluster key retrieval failed' 1>&2 |
||||
exit 1 |
||||
fi | awk -F'\t' 'NR == 1 {print $4}' |
||||
|
||||
exit 0 |
||||
@ -1,37 +0,0 @@ |
||||
#!/bin/sh |
||||
|
||||
# This uses a key supplied by the user |
||||
# If OpenSSL is installed, you can generate a pseudo-random key by running: |
||||
# openssl rand -hex 32 |
||||
# To get a true random key, run: |
||||
# wget -q -O - 'https://www.random.org/cgi-bin/randbyte?nbytes=32&format=h' | tr -d ' \n'; echo |
||||
|
||||
[ "$#" -lt 1 ] && echo "cluster_key_command usage: $0 %R [%p]" 1>&2 && exit 1 |
||||
# Supports environment variable PROMPT |
||||
|
||||
FD="$1" |
||||
[ ! -t "$FD" ] && echo "file descriptor $FD does not refer to a terminal" 1>&2 && exit 1 |
||||
|
||||
[ "$2" ] && PROMPT="$2" |
||||
|
||||
|
||||
# ---------------------------------------------------------------------- |
||||
|
||||
[ ! "$PROMPT" ] && PROMPT='Enter cluster key as 64 hexadecimal characters: ' |
||||
|
||||
stty -echo <&"$FD" |
||||
|
||||
echo 1>&"$FD" |
||||
echo -n "$PROMPT" 1>&"$FD" |
||||
read KEY <&"$FD" |
||||
|
||||
stty echo <&"$FD" |
||||
|
||||
if [ "$(expr "$KEY" : '[0-9a-fA-F]*$')" -ne 64 ] |
||||
then echo 'invalid; must be 64 hexadecimal characters' 1>&2 |
||||
exit 1 |
||||
fi |
||||
|
||||
echo "$KEY" |
||||
|
||||
exit 0 |
||||
@ -1,33 +0,0 @@ |
||||
#!/bin/sh |
||||
|
||||
# This uses a passphrase supplied by the user. |
||||
|
||||
[ "$#" -lt 1 ] && echo "cluster_key_command usage: $0 %R [\"%p\"]" 1>&2 && exit 1 |
||||
|
||||
FD="$1" |
||||
[ ! -t "$FD" ] && echo "file descriptor $FD does not refer to a terminal" 1>&2 && exit 1 |
||||
# Supports environment variable PROMPT |
||||
|
||||
[ "$2" ] && PROMPT="$2" |
||||
|
||||
|
||||
# ---------------------------------------------------------------------- |
||||
|
||||
[ ! "$PROMPT" ] && PROMPT='Enter cluster passphrase: ' |
||||
|
||||
stty -echo <&"$FD" |
||||
|
||||
echo 1>&"$FD" |
||||
echo -n "$PROMPT" 1>&"$FD" |
||||
read PASS <&"$FD" |
||||
|
||||
stty echo <&"$FD" |
||||
|
||||
if [ ! "$PASS" ] |
||||
then echo 'invalid: empty passphrase' 1>&2 |
||||
exit 1 |
||||
fi |
||||
|
||||
echo "$PASS" | sha256sum | cut -d' ' -f1 |
||||
|
||||
exit 0 |
||||
@ -1,63 +0,0 @@ |
||||
#!/bin/sh |
||||
|
||||
# This uses the public/private keys on a PIV device, like a CAC or Yubikey. |
||||
# It uses a PIN stored in a file. |
||||
# It uses OpenSSL with PKCS11 enabled via OpenSC. |
||||
|
||||
[ "$#" -ne 1 ] && echo "cluster_key_command usage: $0 \"%d\"" 1>&2 && exit 1 |
||||
# Supports environment variable PIV_PIN_FILE |
||||
# No need for %R or -R since we are not prompting for a PIN |
||||
|
||||
DIR="$1" |
||||
[ ! -e "$DIR" ] && echo "$DIR does not exist" 1>&2 && exit 1 |
||||
[ ! -d "$DIR" ] && echo "$DIR is not a directory" 1>&2 && exit 1 |
||||
|
||||
# Set these here or pass in as environment variables. |
||||
# File that stores the PIN to unlock the PIV |
||||
#PIV_PIN_FILE='' |
||||
# PIV slot 3 is the "Key Management" slot, so we use '0:3' |
||||
PIV_SLOT='0:3' |
||||
|
||||
# File containing the cluster key encrypted with the PIV_SLOT's public key |
||||
KEY_FILE="$DIR/pivpass.key" |
||||
|
||||
|
||||
# ---------------------------------------------------------------------- |
||||
|
||||
[ ! "$PIV_PIN_FILE" ] && echo 'PIV_PIN_FILE undefined' 1>&2 && exit 1 |
||||
[ ! -e "$PIV_PIN_FILE" ] && echo "$PIV_PIN_FILE does not exist" 1>&2 && exit 1 |
||||
[ -d "$PIV_PIN_FILE" ] && echo "$PIV_PIN_FILE is a directory" 1>&2 && exit 1 |
||||
|
||||
[ ! "$KEY_FILE" ] && echo 'KEY_FILE undefined' 1>&2 && exit 1 |
||||
[ -d "$KEY_FILE" ] && echo "$KEY_FILE is a directory" 1>&2 && exit 1 |
||||
|
||||
# Create a cluster key encrypted with the PIV_SLOT's public key? |
||||
if [ ! -e "$KEY_FILE" ] |
||||
then # The 'postgres' operating system user must have permission to |
||||
# access the PIV device. |
||||
|
||||
openssl rand -hex 32 | |
||||
if ! openssl rsautl -engine pkcs11 -keyform engine -encrypt \ |
||||
-inkey "$PIV_SLOT" -passin file:"$PIV_PIN_FILE" -out "$KEY_FILE" |
||||
then echo 'cluster key generation failed' 1>&2 |
||||
exit 1 |
||||
fi |
||||
|
||||
# Warn the user to save the cluster key in a safe place |
||||
cat 1>&2 <<END |
||||
|
||||
WARNING: The PIV device can be locked and require a reset if too many PIN |
||||
attempts fail. It is recommended to run this command manually and save |
||||
the cluster key in a secure location for possible recovery. |
||||
END |
||||
|
||||
fi |
||||
|
||||
# Decrypt the cluster key encrypted with the PIV_SLOT's public key |
||||
if ! openssl rsautl -engine pkcs11 -keyform engine -decrypt \ |
||||
-inkey "$PIV_SLOT" -passin file:"$PIV_PIN_FILE" -in "$KEY_FILE" |
||||
then echo 'cluster key decryption failed' 1>&2 |
||||
exit 1 |
||||
fi |
||||
|
||||
exit 0 |
||||
@ -1,76 +0,0 @@ |
||||
#!/bin/sh |
||||
|
||||
# This uses the public/private keys on a PIV device, like a CAC or Yubikey. |
||||
# It requires a user-entered PIN. |
||||
# It uses OpenSSL with PKCS11 enabled via OpenSC. |
||||
|
||||
[ "$#" -lt 2 ] && echo "cluster_key_command usage: $0 \"%d\" %R [\"%p\"]" 1>&2 && exit 1 |
||||
# Supports environment variable PROMPT |
||||
|
||||
DIR="$1" |
||||
[ ! -e "$DIR" ] && echo "$DIR does not exist" 1>&2 && exit 1 |
||||
[ ! -d "$DIR" ] && echo "$DIR is not a directory" 1>&2 && exit 1 |
||||
|
||||
FD="$2" |
||||
[ ! -t "$FD" ] && echo "file descriptor $FD does not refer to a terminal" 1>&2 && exit 1 |
||||
|
||||
[ "$3" ] && PROMPT="$3" |
||||
|
||||
# PIV slot 3 is the "Key Management" slot, so we use '0:3' |
||||
PIV_SLOT='0:3' |
||||
|
||||
# File containing the cluster key encrypted with the PIV_SLOT's public key |
||||
KEY_FILE="$DIR/pivpass.key" |
||||
|
||||
|
||||
# ---------------------------------------------------------------------- |
||||
|
||||
[ ! "$PROMPT" ] && PROMPT='Enter PIV PIN: ' |
||||
|
||||
stty -echo <&"$FD" |
||||
|
||||
# Create a cluster key encrypted with the PIV_SLOT's public key? |
||||
if [ ! -e "$KEY_FILE" ] |
||||
then echo 1>&"$FD" |
||||
echo -n "$PROMPT" 1>&"$FD" |
||||
|
||||
# The 'postgres' operating system user must have permission to |
||||
# access the PIV device. |
||||
|
||||
openssl rand -hex 32 | |
||||
# 'engine "pkcs11" set.' message confuses prompting |
||||
if ! openssl rsautl -engine pkcs11 -keyform engine -encrypt \ |
||||
-inkey "$PIV_SLOT" -passin fd:"$FD" -out "$KEY_FILE" 2>&1 |
||||
then stty echo <&"$FD" |
||||
echo 'cluster key generation failed' 1>&2 |
||||
exit 1 |
||||
fi | grep -v 'engine "pkcs11" set\.' |
||||
|
||||
echo 1>&"$FD" |
||||
|
||||
# Warn the user to save the cluster key in a safe place |
||||
cat 1>&"$FD" <<END |
||||
|
||||
WARNING: The PIV can be locked and require a reset if too many PIN |
||||
attempts fail. It is recommended to run this command manually and save |
||||
the cluster key in a secure location for possible recovery. |
||||
END |
||||
|
||||
fi |
||||
|
||||
echo 1>&"$FD" |
||||
echo -n "$PROMPT" 1>&"$FD" |
||||
|
||||
# Decrypt the cluster key encrypted with the PIV_SLOT's public key |
||||
if ! openssl rsautl -engine pkcs11 -keyform engine -decrypt \ |
||||
-inkey "$PIV_SLOT" -passin fd:"$FD" -in "$KEY_FILE" 2>&1 |
||||
then stty echo <&"$FD" |
||||
echo 'cluster key retrieval failed' 1>&2 |
||||
exit 1 |
||||
fi | grep -v 'engine "pkcs11" set\.' |
||||
|
||||
echo 1>&"$FD" |
||||
|
||||
stty echo <&"$FD" |
||||
|
||||
exit 0 |
||||
@ -1,372 +0,0 @@ |
||||
/*-------------------------------------------------------------------------
|
||||
* |
||||
* 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))); |
||||
} |
||||
} |
||||
@ -1,33 +0,0 @@ |
||||
#!/bin/sh |
||||
|
||||
# This uses a passphrase supplied by the user. |
||||
|
||||
[ "$#" -lt 1 ] && echo "ssl_passphrase_command usage: $0 %R [\"%p\"]" 1>&2 && exit 1 |
||||
|
||||
FD="$1" |
||||
[ ! -t "$FD" ] && echo "file descriptor $FD does not refer to a terminal" 1>&2 && exit 1 |
||||
# Supports environment variable PROMPT |
||||
|
||||
[ "$2" ] && PROMPT="$2" |
||||
|
||||
|
||||
# ---------------------------------------------------------------------- |
||||
|
||||
[ ! "$PROMPT" ] && PROMPT='Enter cluster passphrase: ' |
||||
|
||||
stty -echo <&"$FD" |
||||
|
||||
echo 1>&"$FD" |
||||
echo -n "$PROMPT" 1>&"$FD" |
||||
read PASS <&"$FD" |
||||
|
||||
stty echo <&"$FD" |
||||
|
||||
if [ ! "$PASS" ] |
||||
then echo 'invalid: empty passphrase' 1>&2 |
||||
exit 1 |
||||
fi |
||||
|
||||
echo "$PASS" |
||||
|
||||
exit 0 |
||||
@ -1 +0,0 @@ |
||||
/pg_alterckey |
||||
@ -1,38 +0,0 @@ |
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
# Makefile for src/bin/pg_alterckey
|
||||
#
|
||||
# Copyright (c) 1998-2020, PostgreSQL Global Development Group
|
||||
#
|
||||
# src/bin/pg_alterckey/Makefile
|
||||
#
|
||||
#-------------------------------------------------------------------------
|
||||
|
||||
PGFILEDESC = "pg_alterckey - alter the cluster key"
|
||||
PGAPPICON=win32
|
||||
|
||||
subdir = src/bin/pg_alterckey
|
||||
top_builddir = ../../..
|
||||
include $(top_builddir)/src/Makefile.global |
||||
|
||||
OBJS = \
|
||||
$(WIN32RES) \
|
||||
pg_alterckey.o
|
||||
|
||||
all: pg_alterckey |
||||
|
||||
pg_alterckey: $(OBJS) | submake-libpgport |
||||
$(CC) $(CFLAGS) $^ $(LDFLAGS) $(LDFLAGS_EX) $(LIBS) -o $@$(X)
|
||||
|
||||
install: all installdirs |
||||
$(INSTALL_PROGRAM) pg_alterckey$(X) '$(DESTDIR)$(bindir)/pg_alterckey$(X)'
|
||||
|
||||
installdirs: |
||||
$(MKDIR_P) '$(DESTDIR)$(bindir)'
|
||||
|
||||
uninstall: |
||||
rm -f '$(DESTDIR)$(bindir)/pg_alterckey$(X)'
|
||||
|
||||
clean distclean maintainer-clean: |
||||
rm -f pg_alterckey$(X) $(OBJS)
|
||||
rm -rf tmp_check
|
||||
@ -1,694 +0,0 @@ |
||||
/*-------------------------------------------------------------------------
|
||||
* |
||||
* pg_alterckey.c |
||||
* A utility to change the cluster key (key encryption key, KEK) |
||||
* used for cluster file encryption. |
||||
* |
||||
* The theory of operation is fairly simple: |
||||
* 1. Create lock file |
||||
* 2. Retrieve current and new cluster key using the supplied |
||||
* commands. |
||||
* 3. Revert any failed alter operation. |
||||
* 4. Create a temporary directory in PGDATA |
||||
* 5. For each data encryption key in the pg_cryptokeys directory, |
||||
* decrypt it with the old cluster key and re-encrypt it |
||||
* with the new cluster key. |
||||
* 6. Make the temporary directory the new pg_cryptokeys directory. |
||||
* 7. Remove lock file |
||||
* |
||||
* |
||||
* Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group |
||||
* Portions Copyright (c) 1994, Regents of the University of California |
||||
* |
||||
* src/bin/pg_alterckey/pg_alterckey.c |
||||
* |
||||
*------------------------------------------------------------------------- |
||||
*/ |
||||
|
||||
|
||||
#define FRONTEND 1 |
||||
|
||||
#include "postgres_fe.h" |
||||
|
||||
#include <signal.h> |
||||
#include <unistd.h> |
||||
#include <sys/stat.h> |
||||
|
||||
#include "common/file_perm.h" |
||||
#include "common/file_utils.h" |
||||
#include "common/hex_decode.h" |
||||
#include "common/restricted_token.h" |
||||
#include "crypto/kmgr.h" |
||||
#include "common/logging.h" |
||||
#include "getopt_long.h" |
||||
#include "pg_getopt.h" |
||||
|
||||
typedef enum { |
||||
SUCCESS_EXIT = 0, |
||||
ERROR_EXIT, |
||||
RMDIR_EXIT, |
||||
REPAIR_EXIT |
||||
} exit_action; |
||||
|
||||
static int lock_fd = -1; |
||||
static bool pass_terminal_fd = false; |
||||
int terminal_fd = -1; |
||||
static bool repair_mode = false; |
||||
static char *old_cluster_key_cmd = NULL, |
||||
*new_cluster_key_cmd = NULL; |
||||
static char old_cluster_key[KMGR_CLUSTER_KEY_LEN], |
||||
new_cluster_key[KMGR_CLUSTER_KEY_LEN]; |
||||
static CryptoKey in_key, data_key, out_key; |
||||
static char top_path[MAXPGPATH], pid_path[MAXPGPATH], live_path[MAXPGPATH], |
||||
new_path[MAXPGPATH], old_path[MAXPGPATH]; |
||||
|
||||
static char *DataDir = NULL; |
||||
static const char *progname; |
||||
|
||||
static void create_lockfile(void); |
||||
static void recover_failure(void); |
||||
static void retrieve_cluster_keys(void); |
||||
static void bzero_keys_and_exit(exit_action action); |
||||
static void reencrypt_data_keys(void); |
||||
static void install_new_keys(void); |
||||
|
||||
static void |
||||
usage(const char *progname) |
||||
{ |
||||
printf(_("%s changes the cluster key of a PostgreSQL database cluster.\n\n"), progname); |
||||
printf(_("Usage:\n")); |
||||
printf(_(" %s [OPTION] old_cluster_key_command new_cluster_key_command [DATADIR]\n"), progname); |
||||
printf(_(" %s [repair_option] [DATADIR]\n"), progname); |
||||
printf(_("\nOptions:\n")); |
||||
printf(_(" -R, --authprompt prompt for a passphrase or PIN\n")); |
||||
printf(_(" [-D, --pgdata=]DATADIR data directory\n")); |
||||
printf(_(" -V, --version output version information, then exit\n")); |
||||
printf(_(" -?, --help show this help, then exit\n")); |
||||
printf(_("\nRepair options:\n")); |
||||
printf(_(" -r, --repair repair previous failure\n")); |
||||
printf(_("\nIf no data directory (DATADIR) is specified, " |
||||
"the environment variable PGDATA\nis used.\n\n")); |
||||
printf(_("Report bugs to <%s>.\n"), PACKAGE_BUGREPORT); |
||||
printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL); |
||||
} |
||||
|
||||
|
||||
int |
||||
main(int argc, char *argv[]) |
||||
{ |
||||
static struct option long_options1[] = { |
||||
{"authprompt", required_argument, NULL, 'R'}, |
||||
{"repair", required_argument, NULL, 'r'}, |
||||
{NULL, 0, NULL, 0} |
||||
}; |
||||
|
||||
static struct option long_options2[] = { |
||||
{"pgdata", required_argument, NULL, 'D'}, |
||||
{NULL, 0, NULL, 0} |
||||
}; |
||||
|
||||
int c; |
||||
|
||||
pg_logging_init(argv[0]); |
||||
set_pglocale_pgservice(argv[0], PG_TEXTDOMAIN("pg_alterckey")); |
||||
progname = get_progname(argv[0]); |
||||
|
||||
if (argc > 1) |
||||
{ |
||||
if (strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-?") == 0) |
||||
{ |
||||
usage(progname); |
||||
exit(0); |
||||
} |
||||
if (strcmp(argv[1], "--version") == 0 || strcmp(argv[1], "-V") == 0) |
||||
{ |
||||
puts("pg_alterckey (PostgreSQL) " PG_VERSION); |
||||
exit(0); |
||||
} |
||||
} |
||||
|
||||
/* check for -r/-R */ |
||||
while ((c = getopt_long(argc, argv, "rR", long_options1, NULL)) != -1) |
||||
{ |
||||
switch (c) |
||||
{ |
||||
case 'r': |
||||
repair_mode = true; |
||||
break; |
||||
|
||||
case 'R': |
||||
pass_terminal_fd = true; |
||||
break; |
||||
|
||||
default: |
||||
fprintf(stderr, _("Try \"%s --help\" for more information.\n"), progname); |
||||
exit(1); |
||||
} |
||||
} |
||||
|
||||
if (!repair_mode) |
||||
{ |
||||
/* get cluster key commands */ |
||||
if (optind < argc) |
||||
old_cluster_key_cmd = argv[optind++]; |
||||
else |
||||
{ |
||||
pg_log_error("missing old_cluster_key_command"); |
||||
fprintf(stderr, _("Try \"%s --help\" for more information.\n"), |
||||
progname); |
||||
exit(1); |
||||
} |
||||
|
||||
if (optind < argc) |
||||
new_cluster_key_cmd = argv[optind++]; |
||||
else |
||||
{ |
||||
pg_log_error("missing new_cluster_key_command"); |
||||
fprintf(stderr, _("Try \"%s --help\" for more information.\n"), |
||||
progname); |
||||
exit(1); |
||||
} |
||||
} |
||||
|
||||
/* check for datadir */ |
||||
argc -= optind; |
||||
argv += optind; |
||||
|
||||
while ((c = getopt_long(argc, argv, "D:", long_options2, NULL)) != -1) |
||||
{ |
||||
switch (c) |
||||
{ |
||||
case 'D': |
||||
DataDir = optarg; |
||||
break; |
||||
|
||||
default: |
||||
fprintf(stderr, _("Try \"%s --help\" for more information.\n"), progname); |
||||
exit(1); |
||||
} |
||||
} |
||||
|
||||
if (DataDir == NULL) |
||||
{ |
||||
if (optind < argc) |
||||
DataDir = argv[optind++]; |
||||
else |
||||
DataDir = getenv("PGDATA"); |
||||
} |
||||
|
||||
/*
|
||||
* Disallow running as root because we create directories in PGDATA |
||||
*/ |
||||
#ifndef WIN32 |
||||
if (geteuid() == 0) |
||||
{ |
||||
pg_log_error("%s: cannot be run as root\n" |
||||
"Please log in (using, e.g., \"su\") as the " |
||||
"(unprivileged) user that will\n" |
||||
"own the server process.\n", |
||||
progname); |
||||
exit(1); |
||||
} |
||||
#endif |
||||
|
||||
get_restricted_token(); |
||||
|
||||
/* Set mask based on PGDATA permissions */ |
||||
if (!GetDataDirectoryCreatePerm(DataDir)) |
||||
{ |
||||
pg_log_error("could not read permissions of directory \"%s\": %m", |
||||
DataDir); |
||||
exit(1); |
||||
} |
||||
|
||||
umask(pg_mode_mask); |
||||
|
||||
snprintf(top_path, sizeof(top_path), "%s/%s", DataDir, KMGR_DIR); |
||||
snprintf(pid_path, sizeof(pid_path), "%s/%s", DataDir, KMGR_DIR_PID); |
||||
snprintf(live_path, sizeof(live_path), "%s/%s", DataDir, LIVE_KMGR_DIR); |
||||
snprintf(new_path, sizeof(new_path), "%s/%s", DataDir, NEW_KMGR_DIR); |
||||
snprintf(old_path, sizeof(old_path), "%s/%s", DataDir, OLD_KMGR_DIR); |
||||
|
||||
/* Complain if any arguments remain */ |
||||
if (optind < argc) |
||||
{ |
||||
pg_log_error("too many command-line arguments (first is \"%s\")", |
||||
argv[optind]); |
||||
fprintf(stderr, _("Try \"%s --help\" for more information.\n"), |
||||
progname); |
||||
exit(1); |
||||
} |
||||
|
||||
if (DataDir == NULL) |
||||
{ |
||||
pg_log_error("no data directory specified"); |
||||
fprintf(stderr, _("Try \"%s --help\" for more information.\n"), progname); |
||||
exit(1); |
||||
} |
||||
|
||||
create_lockfile(); |
||||
|
||||
recover_failure(); |
||||
|
||||
if (!repair_mode) |
||||
{ |
||||
retrieve_cluster_keys(); |
||||
reencrypt_data_keys(); |
||||
install_new_keys(); |
||||
} |
||||
|
||||
#ifndef WIN32 |
||||
/* remove file system reference to file */ |
||||
if (unlink(pid_path) < 0) |
||||
{ |
||||
pg_log_error("could not delete lock file \"%s\": %m", KMGR_DIR_PID); |
||||
exit(1); |
||||
} |
||||
#endif |
||||
|
||||
close (lock_fd); |
||||
|
||||
bzero_keys_and_exit(SUCCESS_EXIT); |
||||
} |
||||
|
||||
/* This prevents almost all cases of concurrent access */ |
||||
void |
||||
create_lockfile(void) |
||||
{ |
||||
struct stat buffer; |
||||
char lock_pid_str[20]; |
||||
|
||||
if (stat(top_path, &buffer) != 0 || !S_ISDIR(buffer.st_mode)) |
||||
{ |
||||
pg_log_error("cluster file encryption directory \"%s\" is missing; is it enabled?", KMGR_DIR_PID); |
||||
fprintf(stderr, _("Exiting with no changes made.\n")); |
||||
exit(1); |
||||
} |
||||
|
||||
/* Does a lockfile exist? */ |
||||
if ((lock_fd = open(pid_path, O_RDONLY, 0)) != -1) |
||||
{ |
||||
int lock_pid; |
||||
int len; |
||||
|
||||
/* read the PID */ |
||||
if ((len = read(lock_fd, lock_pid_str, sizeof(lock_pid_str) - 1)) == 0) |
||||
{ |
||||
pg_log_error("cannot read pid from lock file \"%s\": %m", KMGR_DIR_PID); |
||||
fprintf(stderr, _("Exiting with no changes made.\n")); |
||||
exit(1); |
||||
} |
||||
lock_pid_str[len] = '\0'; |
||||
|
||||
if ((lock_pid = atoi(lock_pid_str)) == 0) |
||||
{ |
||||
pg_log_error("invalid pid in lock file \"%s\": %m", KMGR_DIR_PID); |
||||
fprintf(stderr, _("Exiting with no changes made.\n")); |
||||
exit(1); |
||||
} |
||||
|
||||
/* Is the PID running? */ |
||||
if (kill(lock_pid, 0) == 0) |
||||
{ |
||||
pg_log_error("active process %d currently holds a lock on this operation, recorded in \"%s\"", |
||||
lock_pid, KMGR_DIR_PID); |
||||
fprintf(stderr, _("Exiting with no changes made.\n")); |
||||
exit(1); |
||||
} |
||||
|
||||
close(lock_fd); |
||||
|
||||
if (repair_mode) |
||||
printf("old lock file removed\n"); |
||||
|
||||
/*
|
||||
* pid is no longer running, so remove the lock file. |
||||
* This is not 100% safe from concurrent access, e.g.: |
||||
* |
||||
* process 1 exits and leaves stale lock file |
||||
* process 2 checks stale lock file of process 1 |
||||
* process 3 checks stale lock file of process 1 |
||||
* process 2 remove the lock file of process 1 |
||||
* process 4 creates a lock file |
||||
* process 3 remove the lock file of process 4 |
||||
* process 5 creates a lock file |
||||
* |
||||
* The sleep(2) helps with this since it reduces the likelihood |
||||
* a process that did an unlock will interfere with another unlock |
||||
* process. We could ask users to remove the lock, but that seems |
||||
* even more error-prone, especially since this might happen |
||||
* on server start. Many PG tools seem to have problems with |
||||
* concurrent access. |
||||
*/ |
||||
unlink(pid_path); |
||||
|
||||
/* Sleep to reduce the likelihood of concurrent unlink */ |
||||
pg_usleep(2000000L); /* 2 seconds */ |
||||
} |
||||
|
||||
/* Create our own lockfile? */ |
||||
#ifndef WIN32 |
||||
lock_fd = open(pid_path, O_RDWR | O_CREAT | O_EXCL, pg_file_create_mode); |
||||
#else |
||||
/* delete on close */ |
||||
lock_fd = open(pid_path, O_RDWR | O_CREAT | O_EXCL | O_TEMPORARY, |
||||
pg_file_create_mode); |
||||
#endif |
||||
|
||||
if (lock_fd == -1) |
||||
{ |
||||
if (errno == EEXIST) |
||||
pg_log_error("an active process currently holds a lock on this operation, recorded in \"%s\"", |
||||
KMGR_DIR_PID); |
||||
else |
||||
pg_log_error("unable to create lock file \"%s\": %m", KMGR_DIR_PID); |
||||
fprintf(stderr, _("Exiting with no changes made.\n")); |
||||
exit(1); |
||||
} |
||||
|
||||
snprintf(lock_pid_str, sizeof(lock_pid_str), "%d\n", getpid()); |
||||
if (write(lock_fd, lock_pid_str, strlen(lock_pid_str)) != strlen(lock_pid_str)) |
||||
{ |
||||
pg_log_error("could not write pid to lock file \"%s\": %m", KMGR_DIR_PID); |
||||
fprintf(stderr, _("Exiting with no changes made.\n")); |
||||
exit(1); |
||||
} |
||||
} |
||||
|
||||
/*
|
||||
* recover_failure |
||||
* |
||||
* A previous pg_alterckey might have failed, so it might need recovery. |
||||
* The normal operation is: |
||||
* 1. reencrypt LIVE_KMGR_DIR -> NEW_KMGR_DIR |
||||
* 2. rename KMGR_DIR -> OLD_KMGR_DIR |
||||
* 3. rename NEW_KMGR_DIR -> LIVE_KMGR_DIR |
||||
* remove OLD_KMGR_DIR |
||||
* |
||||
* There are eight possible directory configurations: |
||||
* |
||||
* LIVE_KMGR_DIR NEW_KMGR_DIR OLD_KMGR_DIR |
||||
* |
||||
* Normal: |
||||
* 0. normal X
|
||||
* 1. remove new X X
|
||||
* 2. install new X X |
||||
* 3. remove old X X |
||||
* |
||||
* Abnormal: |
||||
* fatal
|
||||
* restore old X |
||||
* install new X
|
||||
* remove old and new X X X |
||||
* |
||||
* We don't handle the abnormal cases, just report an error. |
||||
*/ |
||||
static void |
||||
recover_failure(void) |
||||
{ |
||||
struct stat buffer; |
||||
bool is_live, is_new, is_old; |
||||
|
||||
is_live = !stat(live_path, &buffer); |
||||
is_new = !stat(new_path, &buffer); |
||||
is_old = !stat(old_path, &buffer); |
||||
|
||||
/* normal #0 */ |
||||
if (is_live && !is_new && !is_old) |
||||
{ |
||||
if (repair_mode) |
||||
printf("repair unnecessary\n"); |
||||
return; |
||||
} |
||||
/* remove new #1 */ |
||||
else if (is_live && is_new && !is_old) |
||||
{ |
||||
if (!rmtree(new_path, true)) |
||||
{ |
||||
pg_log_error("unable to remove new directory \"%s\": %m", NEW_KMGR_DIR); |
||||
fprintf(stderr, _("Exiting with no changes made.\n")); |
||||
exit(1); |
||||
} |
||||
printf(_("removed files created during previously aborted alter operation\n")); |
||||
return; |
||||
} |
||||
/* install new #2 */ |
||||
else if (!is_live && is_new && is_old) |
||||
{ |
||||
if (rename(new_path, live_path) != 0) |
||||
{ |
||||
pg_log_error("unable to rename directory \"%s\" to \"%s\": %m", |
||||
NEW_KMGR_DIR, LIVE_KMGR_DIR); |
||||
fprintf(stderr, _("Exiting with no changes made.\n")); |
||||
exit(1); |
||||
} |
||||
printf(_("Installed new cluster password supplied in previous alter operation\n")); |
||||
return; |
||||
} |
||||
/* remove old #3 */ |
||||
else if (is_live && !is_new && is_old) |
||||
{ |
||||
if (!rmtree(old_path, true)) |
||||
{ |
||||
pg_log_error("unable to remove old directory \"%s\": %m", OLD_KMGR_DIR); |
||||
fprintf(stderr, _("Exiting with no changes made.\n")); |
||||
exit(1); |
||||
} |
||||
printf(_("Removed old files invalidated during previous alter operation\n")); |
||||
return; |
||||
} |
||||
else |
||||
{ |
||||
pg_log_error("cluster file encryption directory \"%s\" is in an abnormal state and cannot be processed", |
||||
KMGR_DIR); |
||||
fprintf(stderr, _("Exiting with no changes made.\n")); |
||||
exit(1); |
||||
} |
||||
} |
||||
|
||||
/* Retrieve old and new cluster keys */
|
||||
void |
||||
retrieve_cluster_keys() |
||||
{ |
||||
int cluster_key_len; |
||||
char cluster_key_hex[ALLOC_KMGR_CLUSTER_KEY_LEN]; |
||||
|
||||
/*
|
||||
* If we have been asked to pass an open file descriptor to the user |
||||
* terminal to the commands, set one up. |
||||
*/ |
||||
if (pass_terminal_fd) |
||||
{ |
||||
#ifndef WIN32 |
||||
terminal_fd = open("/dev/tty", O_RDWR, 0); |
||||
#else |
||||
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); |
||||
} |
||||
} |
||||
|
||||
/* Get old key encryption key from the cluster key command */ |
||||
cluster_key_len = kmgr_run_cluster_key_command(old_cluster_key_cmd, |
||||
(char *) cluster_key_hex, |
||||
ALLOC_KMGR_CLUSTER_KEY_LEN, |
||||
live_path); |
||||
if (hex_decode(cluster_key_hex, cluster_key_len, (char *) old_cluster_key) != |
||||
KMGR_CLUSTER_KEY_LEN) |
||||
{ |
||||
pg_log_error("cluster key must be at %d hex bytes", KMGR_CLUSTER_KEY_LEN); |
||||
bzero_keys_and_exit(ERROR_EXIT); |
||||
} |
||||
|
||||
/*
|
||||
* Create new key directory here in case the new cluster key command needs it |
||||
* to exist. |
||||
*/ |
||||
if (mkdir(new_path, pg_dir_create_mode) != 0) |
||||
{ |
||||
pg_log_error("unable to create new cluster key directory \"%s\": %m", NEW_KMGR_DIR); |
||||
bzero_keys_and_exit(ERROR_EXIT); |
||||
} |
||||
|
||||
/* Get new key */ |
||||
cluster_key_len = kmgr_run_cluster_key_command(new_cluster_key_cmd, |
||||
(char *) cluster_key_hex, |
||||
ALLOC_KMGR_CLUSTER_KEY_LEN, |
||||
live_path); |
||||
if (hex_decode(cluster_key_hex, cluster_key_len, (char *) new_cluster_key) != |
||||
KMGR_CLUSTER_KEY_LEN) |
||||
{ |
||||
pg_log_error("cluster key must be at %d hex bytes", KMGR_CLUSTER_KEY_LEN); |
||||
bzero_keys_and_exit(ERROR_EXIT); |
||||
} |
||||
|
||||
if (pass_terminal_fd) |
||||
close(terminal_fd); |
||||
|
||||
/* output newline */ |
||||
puts(""); |
||||
|
||||
if (strcmp(old_cluster_key, new_cluster_key) == 0) |
||||
{ |
||||
pg_log_error("cluster keys are identical, exiting\n"); |
||||
bzero_keys_and_exit(RMDIR_EXIT); |
||||
} |
||||
|
||||
} |
||||
|
||||
/* Decrypt old keys encrypted with old pass phrase and reencrypt with new one */ |
||||
void |
||||
reencrypt_data_keys(void) |
||||
{ |
||||
DIR *dir; |
||||
struct dirent *de; |
||||
PgCipherCtx *old_ctx, *new_ctx; |
||||
|
||||
if ((dir = opendir(live_path)) == NULL) |
||||
{ |
||||
pg_log_error("unable to open live cluster key directory \"%s\": %m", LIVE_KMGR_DIR); |
||||
bzero_keys_and_exit(RMDIR_EXIT); |
||||
} |
||||
|
||||
old_ctx = pg_cipher_ctx_create(PG_CIPHER_AES_GCM, |
||||
(unsigned char *)old_cluster_key, |
||||
KMGR_CLUSTER_KEY_LEN, true); |
||||
if (!old_ctx) |
||||
pg_log_error("could not initialize encryption context"); |
||||
|
||||
new_ctx = pg_cipher_ctx_create(PG_CIPHER_AES_GCM, |
||||
(unsigned char *)new_cluster_key, |
||||
KMGR_CLUSTER_KEY_LEN, true); |
||||
if (!new_ctx) |
||||
pg_log_error("could not initialize encryption context"); |
||||
|
||||
while ((de = readdir(dir)) != NULL) |
||||
{ |
||||
/*
|
||||
* We copy only the numeric files/keys, since there might be encrypted |
||||
* cluster key files in the old directory that only match the old key. |
||||
*/ |
||||
if (strspn(de->d_name, "0123456789") == strlen(de->d_name)) |
||||
{ |
||||
char src_path[MAXPGPATH], dst_path[MAXPGPATH]; |
||||
int src_fd, dst_fd; |
||||
int len; |
||||
uint32 id = strtoul(de->d_name, NULL, 10); |
||||
|
||||
CryptoKeyFilePath(src_path, live_path, id); |
||||
CryptoKeyFilePath(dst_path, new_path, id); |
||||
|
||||
if ((src_fd = open(src_path, O_RDONLY | PG_BINARY, 0)) < 0) |
||||
{ |
||||
pg_log_error("could not open file \"%s\": %m", src_path); |
||||
bzero_keys_and_exit(RMDIR_EXIT); |
||||
} |
||||
|
||||
if ((dst_fd = open(dst_path, O_RDWR | O_CREAT | O_TRUNC | PG_BINARY, |
||||
pg_file_create_mode)) < 0) |
||||
{ |
||||
pg_log_error("could not open file \"%s\": %m", dst_path); |
||||
bzero_keys_and_exit(RMDIR_EXIT); |
||||
} |
||||
|
||||
/* Read the source key */ |
||||
len = read(src_fd, &in_key, sizeof(CryptoKey)); |
||||
if (len != sizeof(CryptoKey)) |
||||
{ |
||||
if (len < 0) |
||||
pg_log_error("could read file \"%s\": %m", src_path); |
||||
else |
||||
pg_log_error("could read file \"%s\": read %d of %zu", |
||||
src_path, len, sizeof(CryptoKey)); |
||||
bzero_keys_and_exit(RMDIR_EXIT); |
||||
} |
||||
|
||||
/* decrypt with old key */ |
||||
if (!kmgr_unwrap_key(old_ctx, &in_key, &data_key)) |
||||
{ |
||||
pg_log_error("incorrect old key specified"); |
||||
bzero_keys_and_exit(RMDIR_EXIT); |
||||
} |
||||
|
||||
/* encrypt with new key */ |
||||
if (!kmgr_wrap_key(new_ctx, &data_key, &out_key)) |
||||
{ |
||||
pg_log_error("could not encrypt new key"); |
||||
bzero_keys_and_exit(RMDIR_EXIT); |
||||
}
|
||||
|
||||
/* Write to the dest key */ |
||||
len = write(dst_fd, &out_key, sizeof(CryptoKey)); |
||||
if (len != sizeof(CryptoKey)) |
||||
{ |
||||
pg_log_error("could not write fie \"%s\"", dst_path); |
||||
bzero_keys_and_exit(RMDIR_EXIT); |
||||
} |
||||
|
||||
close(src_fd); |
||||
close(dst_fd); |
||||
} |
||||
} |
||||
|
||||
/* The cluster key is correct, free the cipher context */ |
||||
pg_cipher_ctx_free(old_ctx); |
||||
pg_cipher_ctx_free(new_ctx); |
||||
|
||||
closedir(dir); |
||||
} |
||||
|
||||
void |
||||
install_new_keys(void) |
||||
{ |
||||
/* add fsyncs? XXX */ |
||||
if (rename(live_path, old_path) != 0) |
||||
{ |
||||
pg_log_error("unable to rename directory \"%s\" to \"%s\": %m", |
||||
LIVE_KMGR_DIR, OLD_KMGR_DIR); |
||||
bzero_keys_and_exit(RMDIR_EXIT); |
||||
} |
||||
|
||||
if (rename(new_path, live_path) != 0) |
||||
{ |
||||
pg_log_error("unable to rename directory \"%s\" to \"%s\": %m", |
||||
NEW_KMGR_DIR, LIVE_KMGR_DIR); |
||||
bzero_keys_and_exit(REPAIR_EXIT); |
||||
} |
||||
|
||||
if (!rmtree(old_path, true)) |
||||
{ |
||||
pg_log_error("unable to remove old directory \"%s\": %m", OLD_KMGR_DIR); |
||||
bzero_keys_and_exit(REPAIR_EXIT); |
||||
} |
||||
} |
||||
|
||||
void |
||||
bzero_keys_and_exit(exit_action action) |
||||
{ |
||||
explicit_bzero(old_cluster_key, sizeof(old_cluster_key)); |
||||
explicit_bzero(new_cluster_key, sizeof(new_cluster_key)); |
||||
|
||||
explicit_bzero(&in_key, sizeof(in_key)); |
||||
explicit_bzero(&data_key, sizeof(data_key)); |
||||
explicit_bzero(&out_key, sizeof(out_key)); |
||||
|
||||
if (action == RMDIR_EXIT) |
||||
{ |
||||
if (!rmtree(new_path, true)) |
||||
pg_log_error("unable to remove new directory \"%s\": %m", NEW_KMGR_DIR); |
||||
printf("Re-running pg_alterckey to repair might be needed before the next server start\n"); |
||||
exit(1); |
||||
} |
||||
else if (action == REPAIR_EXIT) |
||||
{ |
||||
unlink(pid_path); |
||||
printf("Re-running pg_alterckey to repair might be needed before the next server start\n"); |
||||
} |
||||
|
||||
/* return 0 or 1 */ |
||||
exit(action != SUCCESS_EXIT); |
||||
} |
||||
@ -1,70 +0,0 @@ |
||||
/*-------------------------------------------------------------------------
|
||||
* |
||||
* 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 void cipher_failure(void) pg_attribute_noreturn(); |
||||
|
||||
|
||||
PgCipherCtx * |
||||
pg_cipher_ctx_create(int cipher, uint8 *key, int klen, bool enc) |
||||
{ |
||||
cipher_failure(); |
||||
return NULL; /* keep compiler quiet */ |
||||
} |
||||
|
||||
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(); |
||||
return false; /* keep compiler quiet */ |
||||
} |
||||
|
||||
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) |
||||
{ |
||||
cipher_failure(); |
||||
return false; /* keep compiler quiet */ |
||||
} |
||||
|
||||
static void |
||||
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 |
||||
} |
||||
@ -1,268 +0,0 @@ |
||||
/*-------------------------------------------------------------------------
|
||||
* 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; |
||||
} |
||||
|
||||
@ -1,507 +0,0 @@ |
||||
/*-------------------------------------------------------------------------
|
||||
* |
||||
* 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 */ |
||||
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; |
||||
memcpy(out->tag, in->tag, sizeof(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 */ |
||||
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] = '"'; |
||||
memcpy(&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 |
||||
} |
||||
@ -1,62 +0,0 @@ |
||||
/*-------------------------------------------------------------------------
|
||||
* |
||||
* 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 */ |
||||
@ -1,98 +0,0 @@ |
||||
/*-------------------------------------------------------------------------
|
||||
* |
||||
* 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 */ |
||||
unsigned char tag[16]; /* 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 */ |
||||
@ -1,29 +0,0 @@ |
||||
/*-------------------------------------------------------------------------
|
||||
* |
||||
* 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 */ |
||||
Loading…
Reference in new issue