Skip to content

Commit

Permalink
Docker image that generates PBKDF2 keys for the JSON file auth (#355)
Browse files Browse the repository at this point in the history
  • Loading branch information
robklg authored May 28, 2021
1 parent a60f6eb commit 2ff21ba
Show file tree
Hide file tree
Showing 3 changed files with 224 additions and 1 deletion.
7 changes: 6 additions & 1 deletion crates/unftp-auth-jsonfile/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 .

212 changes: 212 additions & 0 deletions crates/unftp-auth-jsonfile/files/key-generator.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
#!/usr/bin/env bash

function error {
RED='\033[0;31m'
NO_COLOR='\033[0m'
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 {
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 <image>'"

function read_password {
local -n opts=$1
local valid_password
while true; do
valid_password=true
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
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
warning "Password must be at least ${opts[length]} characters long."
fi
if [[ ${opts[case]} == "yes" ]] && ! ( [[ $PASSWORD =~ [[:upper:]] ]] && [[ $PASSWORD =~ [[:lower:]] ]] ); then
valid_password=false
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
warning "Password complexity rules require a symbolic character in the password."
fi
if [[ ${opts[digits]} == "yes" && ! $PASSWORD =~ [[:digit:]] ]]; then
valid_password=false
warning "Password complexity rules require a digit character in the password."
fi
fi
if $valid_password && ${options[print]}; then
return
fi
while true; do
if ! $valid_password; then
echo
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
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
}

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 -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]}"
else
printf "pbkdf2_salt: %s\npbkdf2_key: %s\n" $b64_salt $pbkdf2
fi
}

function validate_yes_no {
if [[ $1 =~ ^(yes|no)$ ]]; then
return 0
else
return 1
fi
}

function usage {
cat <<USAGE
Usage: $(basename $0) [-l length] [-m length] [-s yes|no] [-c yes|no] [-d yes|no] [-i iter] [-n] [-p] [-u] [-h]
Flags
-h Show this summary
-n Disable password complexity check (complexity options will be ignored)
-u Generate copy-pastable JSON credentials output for one or more users directly usable in unFTP
-p Don't hide password input
Options
-l length Minimum length requirement (default: 12)
-m length Maximum length requirement (default: none)
-s yes|no Require at least one symbol (default: yes)
-d yes|no Require at least one digit (default: yes)
-c yes|no Require mixed case (default: yes)
-i iterations The number of iterations for PBKDF2 (default: 500000)
USAGE
}

declare -A options
options[length]=12
options[symbols]=yes
options[digits]=yes
options[case]=yes
options[print]=false
options[iter]=500000
while getopts ":l:m:s:c:d:i:nuph" arg; do
case $arg in
h)
usage
exit 0
;;
l)
options[length]=$OPTARG
;;
m)
options[maxlength]=$OPTARG
;;
s)
validate_yes_no $OPTARG || exit_fail "Invalid param for -${arg}: valid values are 'yes' or 'no'"
options[symbols]=$OPTARG
;;
c)
validate_yes_no $OPTARG || exit_fail "Invalid param for -${arg}: valid values are 'yes' or 'no'"
options[case]=$OPTARG
;;
d)
validate_yes_no $OPTARG || exit_fail "Invalid param for -${arg}: valid values are 'yes' or 'no'"
options[digits]=$OPTARG
;;
i)
options[iter]=$OPTARG
;;
n)
options[length]=0
;;
u)
GENERATE_JSON=true
;;
p)
options[print]=true
;;
:)
error "$0: Must supply an argument to -$OPTARG"
usage
exit 2
;;
?)
error "Invalid option: -${OPTARG}"
usage
exit 2
;;
esac
done

if [[ -z $GENERATE_JSON ]]; then
read_password options
generate_pbkdf2 options
exit 0
else
read -p "Enter username or press ENTER to finish: " USERNAME
if [[ -z $USERNAME ]]; then
exit 0
fi
read_password options
generate_pbkdf2 options $USERNAME
json="[ { $ENTRY }"
while [[ -n $USERNAME ]]; do
jq <<<"$json ]"
read -p "Enter username or press ENTER to finish: " USERNAME
if [[ -z $USERNAME ]]; then
break
fi
read_password options
generate_pbkdf2 options $USERNAME
json+=",{ $ENTRY }"
done
jq <<<"${json} ]"
fi

6 changes: 6 additions & 0 deletions crates/unftp-auth-jsonfile/key-generator.Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
FROM alpine:latest

RUN apk add bash openssl nettle-utils jq pwgen
COPY files/key-generator.sh /

ENTRYPOINT ["/key-generator.sh"]

0 comments on commit 2ff21ba

Please sign in to comment.