On Mon, Dec 21, 2020 at 04:35:14PM -0500, Bruce Momjian wrote:
> On Sun, Dec 20, 2020 at 05:59:09PM -0500, Stephen Frost wrote:
> OK, here it the updated patch.  The major change, as Stephen pointed
> out, is that the cluser_key_command (was cluster_passphrase_command) now
> returns a hex-string representing the 32-byte KEK, rather than having
> the server hash whatever the command used to return.  This avoids
> double-hashing in cases where you are _not_ using a passphrase, but are
> computing a random 32-byte value in the script.  This does mean the
> script has to run sha256 for passphrases, but it seems like a win. 
> Updated script are attached too.

Attached is the script patch.  It is also at:

        
https://github.com/postgres/postgres/compare/master...bmomjian:cfe-sh.diff

I think it still needs docs but those will have to be done after the
code doc patch is added.

-- 
  Bruce Momjian  <br...@momjian.us>        https://momjian.us
  EnterpriseDB                             https://enterprisedb.com

  The usefulness of a cup is in its emptiness, Bruce Lee

diff --git a/src/backend/utils/auth_key_cmds/Makefile b/src/backend/utils/auth_key_cmds/Makefile
new file mode 100644
index ...e0bee97
*** a/src/backend/utils/auth_key_cmds/Makefile
--- b/src/backend/utils/auth_key_cmds/Makefile
***************
*** 0 ****
--- 1,41 ----
+ #-------------------------------------------------------------------------
+ #
+ # Makefile for src/backend/utils/auth_key_cmds
+ #
+ # src/backend/utils/auth_key_cmds/Makefile
+ #
+ #-------------------------------------------------------------------------
+ 
+ PGFILEDESC = "auth_key_cmds - authentication key commands"
+ PGAPPICON = win32
+ 
+ subdir = src/backend/utils/auth_key_cmds
+ top_builddir = ../../../..
+ include $(top_builddir)/src/Makefile.global
+ 
+ SCRIPTS = ckey_aws.sh.sample \
+ 	ckey_direct.sh.sample \
+ 	ckey_passphrase.sh.sample \
+ 	ckey_piv_nopin.sh.sample  \
+ 	ckey_piv_pin.sh.sample \
+ 	ssl_paasphrase.sh.sample
+ 
+ SCRIPTDIR=auth_key_cmds
+ 
+ install: all installdirs
+ 	$(INSTALL_DATA) $(SCRIPTS) '$(DESTDIR)$(datadir)/$(SCRIPTDIR)'
+ 
+ installdirs:
+ 	$(MKDIR_P) '$(DESTDIR)$(datadir)' '$(DESTDIR)$(datadir)/$(SCRIPTDIR)'
+ 
+ uninstall:
+ 	@set -e; \
+ 	set $(SCRIPT) ; \
+ 	while [ "$$#" -gt 0 ] ; \
+ 	do \
+ 		script=$$1; shift;  \
+ 		rm -f '$(DESTDIR)$(datadir)/$(SCRIPTDIR)/'$${lang}.stop ; \
+ 	done
+ 
+ clean distclean maintainer-clean: clean-lib
+ 	rm -f $(OBJS) $(SQLSCRIPT)
diff --git a/src/backend/utils/auth_key_cmds/ckey_aws.sh.sample b/src/backend/utils/auth_key_cmds/ckey_aws.sh.sample
new file mode 100755
index ...0341621
*** a/src/backend/utils/auth_key_cmds/ckey_aws.sh.sample
--- b/src/backend/utils/auth_key_cmds/ckey_aws.sh.sample
***************
*** 0 ****
--- 1,50 ----
+ #!/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
diff --git a/src/backend/utils/auth_key_cmds/ckey_direct.sh.sample b/src/backend/utils/auth_key_cmds/ckey_direct.sh.sample
new file mode 100755
index ...4a56cc3
*** a/src/backend/utils/auth_key_cmds/ckey_direct.sh.sample
--- b/src/backend/utils/auth_key_cmds/ckey_direct.sh.sample
***************
*** 0 ****
--- 1,37 ----
+ #!/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
diff --git a/src/backend/utils/auth_key_cmds/ckey_passphrase.sh.sample b/src/backend/utils/auth_key_cmds/ckey_passphrase.sh.sample
new file mode 100755
index ...8640c48
*** a/src/backend/utils/auth_key_cmds/ckey_passphrase.sh.sample
--- b/src/backend/utils/auth_key_cmds/ckey_passphrase.sh.sample
***************
*** 0 ****
--- 1,33 ----
+ #!/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
diff --git a/src/backend/utils/auth_key_cmds/ckey_piv_nopin.sh.sample b/src/backend/utils/auth_key_cmds/ckey_piv_nopin.sh.sample
new file mode 100755
index ...ac7dc94
*** a/src/backend/utils/auth_key_cmds/ckey_piv_nopin.sh.sample
--- b/src/backend/utils/auth_key_cmds/ckey_piv_nopin.sh.sample
***************
*** 0 ****
--- 1,63 ----
+ #!/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
diff --git a/src/backend/utils/auth_key_cmds/ckey_piv_pin.sh.sample b/src/backend/utils/auth_key_cmds/ckey_piv_pin.sh.sample
new file mode 100755
index ...d283ad8
*** a/src/backend/utils/auth_key_cmds/ckey_piv_pin.sh.sample
--- b/src/backend/utils/auth_key_cmds/ckey_piv_pin.sh.sample
***************
*** 0 ****
--- 1,76 ----
+ #!/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
diff --git a/src/backend/utils/auth_key_cmds/ssl_paasphrase.sh.sample b/src/backend/utils/auth_key_cmds/ssl_paasphrase.sh.sample
new file mode 100755
index ...7d8f422
*** a/src/backend/utils/auth_key_cmds/ssl_paasphrase.sh.sample
--- b/src/backend/utils/auth_key_cmds/ssl_paasphrase.sh.sample
***************
*** 0 ****
--- 1,33 ----
+ #!/bin/sh
+ 
+ # This uses a passphrase supplied by the user.
+ 
+ [ "$#" -ne 2 ] && 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

Reply via email to