From b4ee1a81509aa4ebbd7725c8bcb446729f3aefa9 Mon Sep 17 00:00:00 2001 From: Robby klein Gunnewiek Date: Fri, 28 May 2021 13:41:51 +0200 Subject: [PATCH 1/5] Docker image that generates PBKDF2 keys for the JSON file auth --- crates/unftp-auth-jsonfile/Makefile | 7 +- crates/unftp-auth-jsonfile/files/run.sh | 182 ++++++++++++++++++ .../key-generator.Dockerfile | 7 + 3 files changed, 195 insertions(+), 1 deletion(-) create mode 100755 crates/unftp-auth-jsonfile/files/run.sh create mode 100644 crates/unftp-auth-jsonfile/key-generator.Dockerfile diff --git a/crates/unftp-auth-jsonfile/Makefile b/crates/unftp-auth-jsonfile/Makefile index e46a9f3b..332cc3c6 100644 --- a/crates/unftp-auth-jsonfile/Makefile +++ b/crates/unftp-auth-jsonfile/Makefile @@ -2,7 +2,7 @@ help: # Shows available `make` commands @echo 'Available `make` commands:' >/dev/stderr @echo >/dev/stderr - @awk -F'#' '/^[a-z][A-Za-z0-9]+/ {if (NF > 1) { sub(/:[^#]*/, ""); print $$1 "\t\t" $$2}}' Makefile + @awk -F'#' '/^[a-z][A-Za-z0-9]+/ {if (NF > 1) { sub(/:[^#]*/, ""); printf("%-25s %s\n", $$1, $$2)}}' Makefile .PHONY: docs docs: # Creates the API docs and opens it in the browser @@ -21,3 +21,8 @@ pr-prep: # Runs checks to ensure you're ready for a pull request .PHONY: publish publish: # Publishes the lib to crates.io cargo publish --verbose + +.PHONY: key-generator-image +key-generator-image: # Generate a Docker image for the unftp key generator script (files/run.sh) + docker build -f key-generator.Dockerfile -t bolcom/unftp-key-generator:latest -f key-generator.Dockerfile . + diff --git a/crates/unftp-auth-jsonfile/files/run.sh b/crates/unftp-auth-jsonfile/files/run.sh new file mode 100755 index 00000000..d3974c23 --- /dev/null +++ b/crates/unftp-auth-jsonfile/files/run.sh @@ -0,0 +1,182 @@ +#!/usr/bin/env bash + +function error { + RED='\033[0;31m' + NO_COLOR='\033[0m' + echo -e ${RED}$*${NO_COLOR} >&2 +} + +function exit_fail { + error $* + exit 1 +} + +[[ $BASH_VERSION =~ ^5 ]] || exit_fail "This script needs to run with Bash version 5" + +tty -s &>/dev/null || exit_fail "This script needs to run interactively. Use 'docker run -ti '" + +function read_password { + local -n opts=$1 + local valid_password + while true; do + valid_password=true + read -s -p "Enter password or press ENTER to generate one: " PASSWORD + echo + if [[ ${#PASSWORD} -eq 0 ]]; then + local output_length=16 + if [[ ${opts[length]} > 16 ]]; then + output_length=${opts[length]} + fi + PASSWORD=$(pwgen -c -n -y -s -B -v -1 $output_length) + if [[ $? -ne 0 ]]; then + exit 5 + fi + echo Generated password: $PASSWORD + break + fi + if [[ ${opts[length]} -eq 0 ]]; then + : # No complexity requirements (-d or -l 0 argument was used) + else + if [[ ${#PASSWORD} -lt ${opts[length]} ]]; then + valid_password=false + error "Password must be at least ${opts[length]} characters long." + fi + if [[ ${opts[case]} == "yes" ]] && ! ( [[ $PASSWORD =~ [[:upper:]] ]] && [[ $PASSWORD =~ [[:lower:]] ]] ); then + valid_password=false + error "Password complexity rules require a mixed case password. So make sure to include both lower and uppercase characters in your password." + fi + if [[ ${opts[symbols]} == "yes" && ! $PASSWORD =~ [[:punct:]] ]]; then + valid_password=false + error "Password complexity rules require a symbolic character in the password." + fi + if [[ ${opts[digits]} == "yes" && ! $PASSWORD =~ [[:digit:]] ]]; then + valid_password=false + error "Password complexity rules require a digit character in the password." + fi + fi + if $valid_password; then + while true; do + read -s -p "Repeat password (leave blank to re-enter initial password): " _PASSWORD + echo + if [[ -z $_PASSWORD ]]; then + break + elif [[ $_PASSWORD = $PASSWORD ]]; then + return + else + error "Repeated password does not match" + error "Try again." + fi + done + else + echo + echo "Try again with above requirements satisfied." + fi + done +} + +function generate_pbkdf2 { + local -n opts=$1 + local username=$2 + local salt=$(dd if=/dev/urandom bs=1 count=8 2>/dev/null | hexdump -v -e '"\\" "x" 1/1 "%02x"') + local b64_salt=$(echo -ne $salt | openssl base64 -A) + local pbkdf2=$(echo -n $PASSWORD | nettle-pbkdf2 -i 500000 -l 32 --hex-salt $(echo -n $salt | xxd -p -c 80) --raw |openssl base64 -A) + + if [[ -n $username ]]; then + ENTRY="\"username\": \"$username\", \"pbkdf2_salt\": \"$b64_salt\", \"pbkdf2_key\": \"${pbkdf2}\", \"pbkdf2_iter\": ${options[iter]}" + else + printf "pbkdf2_salt: %s\npbkdf2_key: %s\n" $b64_salt $pbkdf2 + fi +} + +function usage { + cat < Date: Fri, 28 May 2021 13:52:56 +0200 Subject: [PATCH 2/5] document the -i param --- crates/unftp-auth-jsonfile/Makefile | 2 +- crates/unftp-auth-jsonfile/files/run.sh | 3 ++- crates/unftp-auth-jsonfile/key-generator.Dockerfile | 1 - 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/unftp-auth-jsonfile/Makefile b/crates/unftp-auth-jsonfile/Makefile index 332cc3c6..9fed9ad3 100644 --- a/crates/unftp-auth-jsonfile/Makefile +++ b/crates/unftp-auth-jsonfile/Makefile @@ -24,5 +24,5 @@ publish: # Publishes the lib to crates.io .PHONY: key-generator-image key-generator-image: # Generate a Docker image for the unftp key generator script (files/run.sh) - docker build -f key-generator.Dockerfile -t bolcom/unftp-key-generator:latest -f key-generator.Dockerfile . + docker build -f key-generator.Dockerfile -t bolcom/unftp-key-generator:latest . diff --git a/crates/unftp-auth-jsonfile/files/run.sh b/crates/unftp-auth-jsonfile/files/run.sh index d3974c23..82f52205 100755 --- a/crates/unftp-auth-jsonfile/files/run.sh +++ b/crates/unftp-auth-jsonfile/files/run.sh @@ -90,7 +90,7 @@ function generate_pbkdf2 { function usage { cat < Date: Fri, 28 May 2021 14:58:52 +0200 Subject: [PATCH 3/5] some usability improvements --- crates/unftp-auth-jsonfile/files/run.sh | 79 +++++++++++++++++-------- 1 file changed, 54 insertions(+), 25 deletions(-) diff --git a/crates/unftp-auth-jsonfile/files/run.sh b/crates/unftp-auth-jsonfile/files/run.sh index 82f52205..d6d407e4 100755 --- a/crates/unftp-auth-jsonfile/files/run.sh +++ b/crates/unftp-auth-jsonfile/files/run.sh @@ -3,7 +3,13 @@ function error { RED='\033[0;31m' NO_COLOR='\033[0m' - echo -e ${RED}$*${NO_COLOR} >&2 + echo -e ${RED}ERROR: $*${NO_COLOR} >&2 +} + +function warning { + YELLOW='\033[0;33m' + NO_COLOR='\033[0m' + echo -e ${YELLOW}WARNING: $*${NO_COLOR} >&2 } function exit_fail { @@ -20,8 +26,12 @@ function read_password { local valid_password while true; do valid_password=true - read -s -p "Enter password or press ENTER to generate one: " PASSWORD - echo + if ${opts[print]}; then + read -p "Enter password or press ENTER to generate one: " PASSWORD + else + read -s -p "Enter password or press ENTER to generate one: " PASSWORD + echo + fi if [[ ${#PASSWORD} -eq 0 ]]; then local output_length=16 if [[ ${opts[length]} > 16 ]]; then @@ -39,38 +49,41 @@ function read_password { else if [[ ${#PASSWORD} -lt ${opts[length]} ]]; then valid_password=false - error "Password must be at least ${opts[length]} characters long." + warning "Password must be at least ${opts[length]} characters long." fi if [[ ${opts[case]} == "yes" ]] && ! ( [[ $PASSWORD =~ [[:upper:]] ]] && [[ $PASSWORD =~ [[:lower:]] ]] ); then valid_password=false - error "Password complexity rules require a mixed case password. So make sure to include both lower and uppercase characters in your password." + warning "Password complexity rules require a mixed case password. So make sure to include both lower and uppercase characters in your password." fi if [[ ${opts[symbols]} == "yes" && ! $PASSWORD =~ [[:punct:]] ]]; then valid_password=false - error "Password complexity rules require a symbolic character in the password." + warning "Password complexity rules require a symbolic character in the password." fi if [[ ${opts[digits]} == "yes" && ! $PASSWORD =~ [[:digit:]] ]]; then valid_password=false - error "Password complexity rules require a digit character in the password." + warning "Password complexity rules require a digit character in the password." fi fi - if $valid_password; then - while true; do - read -s -p "Repeat password (leave blank to re-enter initial password): " _PASSWORD + if $valid_password && ${options[print]}; then + return + fi + while true; do + if ! $valid_password; then echo - if [[ -z $_PASSWORD ]]; then - break - elif [[ $_PASSWORD = $PASSWORD ]]; then - return - else - error "Repeated password does not match" - error "Try again." - fi - done - else + warning "Password does not meet the above mentioned password complexity rules!\n To ignore this: Repeat the weak password at the next prompt.\n To be safe: press ENTER to try again." + fi + read -s -p "Repeat password (leave blank to re-enter initial password): " _PASSWORD echo - echo "Try again with above requirements satisfied." - fi + if [[ -z $_PASSWORD ]]; then + break + elif [[ $_PASSWORD = $PASSWORD ]]; then + warning "Accepted a possibly insecure password." + return + else + error "Repeated password does not match" + error "Try again." + fi + done done } @@ -88,6 +101,14 @@ function generate_pbkdf2 { fi } +function validate_yes_no { + if [[ $1 =~ ^(yes|no)$ ]]; then + return 0 + else + return 1 + fi +} + function usage { cat < Date: Fri, 28 May 2021 15:12:46 +0200 Subject: [PATCH 4/5] fix salt --- crates/unftp-auth-jsonfile/files/run.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/unftp-auth-jsonfile/files/run.sh b/crates/unftp-auth-jsonfile/files/run.sh index d6d407e4..b299c7fa 100755 --- a/crates/unftp-auth-jsonfile/files/run.sh +++ b/crates/unftp-auth-jsonfile/files/run.sh @@ -92,7 +92,7 @@ function generate_pbkdf2 { local username=$2 local salt=$(dd if=/dev/urandom bs=1 count=8 2>/dev/null | hexdump -v -e '"\\" "x" 1/1 "%02x"') local b64_salt=$(echo -ne $salt | openssl base64 -A) - local pbkdf2=$(echo -n $PASSWORD | nettle-pbkdf2 -i 500000 -l 32 --hex-salt $(echo -n $salt | xxd -p -c 80) --raw |openssl base64 -A) + local pbkdf2=$(echo -n $PASSWORD | nettle-pbkdf2 -i 500000 -l 32 --hex-salt $(echo -ne $salt | xxd -p -c 80) --raw |openssl base64 -A) if [[ -n $username ]]; then ENTRY="\"username\": \"$username\", \"pbkdf2_salt\": \"$b64_salt\", \"pbkdf2_key\": \"${pbkdf2}\", \"pbkdf2_iter\": ${options[iter]}" From 4599ab82c55707b91252e2b3a04f760b9715d180 Mon Sep 17 00:00:00 2001 From: Robby klein Gunnewiek Date: Fri, 28 May 2021 15:16:41 +0200 Subject: [PATCH 5/5] Rename script --- crates/unftp-auth-jsonfile/files/{run.sh => key-generator.sh} | 0 crates/unftp-auth-jsonfile/key-generator.Dockerfile | 4 ++-- 2 files changed, 2 insertions(+), 2 deletions(-) rename crates/unftp-auth-jsonfile/files/{run.sh => key-generator.sh} (100%) diff --git a/crates/unftp-auth-jsonfile/files/run.sh b/crates/unftp-auth-jsonfile/files/key-generator.sh similarity index 100% rename from crates/unftp-auth-jsonfile/files/run.sh rename to crates/unftp-auth-jsonfile/files/key-generator.sh diff --git a/crates/unftp-auth-jsonfile/key-generator.Dockerfile b/crates/unftp-auth-jsonfile/key-generator.Dockerfile index ef0639cf..85c9f7cf 100644 --- a/crates/unftp-auth-jsonfile/key-generator.Dockerfile +++ b/crates/unftp-auth-jsonfile/key-generator.Dockerfile @@ -1,6 +1,6 @@ FROM alpine:latest RUN apk add bash openssl nettle-utils jq pwgen -COPY files/run.sh / +COPY files/key-generator.sh / -ENTRYPOINT ["/run.sh"] +ENTRYPOINT ["/key-generator.sh"]