Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
DNSCrypt - Better key management with dnscrypt-wrapper. Use this script to automatically rotate keys and restart Unbound/DNSCrypt-wrapper
# Sample config file, place it at /usr/local/etc/dnscrypt-autokey.conf
#
## Key Config
# Location to store the provider key pair. Default is /usr/local/etc/dnscrypt-wrapper/keys
KEY_DIR=$(dirname "$0")/keys
# Location to generate the short-lived keys and certificates. Default is $KEY_DIR
# Note: Do not manually add, move, modify or touch files in this folder!
SHORT_TERM_KEYS_DIR=$KEY_DIR
# Certificate expiration time in days
VALIDITY_PERIOD=1
# Whether to generate xchacha20 certificate. 0=false 1=true
CHACHA20=1
## Server Config
# dnscrypt provider name
PROVIDER_NAME=2.dnscrypt-cert.example.com
# Auto restart mode: [unbound, wrapper]. Default is none
MODE=
# CUSTOM_FLAGS for dnscrypt-wrapper [--dnssec, --nofilter, --nolog]
CUSTOM_FLAGS=
# Listen address and ports for dnscrypt-wrapper
LISTEN="0.0.0.0"
PORTS="443, 8080"
# Resolver Address
RESOLVER="127.0.0.1:53" # RESOLVER="9.9.9.9:53"
# unbound-control path when MODE is unbound
UNBOUND_CONTROL=unbound-control
#!/bin/sh
. /etc/rc.subr
name=dnscrypt_autokey
rcvar=dnscrypt_autokey_enable
load_rc_config ${name}
: ${dnscrypt_autokey_enable:=NO}
command=/usr/local/sbin/dnscrypt-autokey
command_args="-ws"
start_precmd="${command} -a"
run_rc_command "$1"
[Unit]
Description=Auto rotate DNSCrypt keys
After=network.target
Before=nss-lookup.target
[Install]
WantedBy=multi-user.target
[Service]
Type=simple
Restart=always
ExecStart=/usr/local/sbin/dnscrypt-autokey -a
#!/bin/sh
# Thanks to
# https://github.com/jedisct1
# https://github.com/cofyc/dnscrypt-wrapper
# https://github.com/DNSCrypt/dnscrypt-server-docker
set -e
set -u
# Add colour to terminals that support it
colour_support=true
tput sgr0 >/dev/null 2>&1 || colour_support=false
tput setaf 2 >/dev/null 2>&1 || colour_support=false
if $colour_support; then
RED=$(tput setaf 1)
GREEN=$(tput setaf 2)
YELLOW=$(tput setaf 3)
BLUE=$(tput setaf 4)
CLEAR=$(tput sgr0)
tput sgr0
else
RED=""
GREEN=""
YELLOW=""
BLUE=""
CLEAR=""
fi
# Check if command is installed
check_requirment() {
if ! command -v "$1" >/dev/null 2>&1; then
echo "'$1' not found!"
exit 1
fi
}
check_requirment dnscrypt-wrapper
check_requirment sed
check_requirment awk
# test that the server is working:
# drill -DQ NS . @127.0.0.1
get_realpath() {
if command -v realpath > /dev/null 2>&1; then
realpath "$1"
else
readlink -f "$1"
fi
}
# Print help
usage() {
printf 'DNSCrypt Easy Key Management\n'
printf '\n'
printf 'Usage: ./dnscrypt-key.sh -c [config file] [options]\n'
printf ' -h --help help This help message\n'
printf ' -c --conf file conf file Load config file (default: /usr/local/etc/dnscrypt-autokey.conf)\n'
printf ' -t --init init Create provider key pair\n'
printf ' -a --auto auto Automatically run appropriate action/command\n'
printf ' -spk --show-public-key show-public-key Show the provider public key\n'
printf ' -s --status status Show keys/cert expiration and other status info\n'
printf ' -n --new [file name] new [file name] Create short lived key and certificate (default: current timestamp)\n'
printf ' -r --renew renew Renew key and certificate\n'
printf ' -d --delete-expired delete-expired Delete expired certs and keys\n'
printf ' -p --proxy proxy Start local dnscrypt proxy\n'
printf ' -w --wrapper [port] wrapper [port] Start dnscrypt wrapper on a specific port (default: 443)\n'
printf ' -ws --wrappers wrappers Start dnscrypt wrappers on all ports set in the config file\n'
printf ' -u --update update Update this script\n'
printf '\n'
}
# Search and Load config file
load_conf() {
# Make sure config is only loaded once
if [ -z ${conf_loaded+x} ] || [ "$conf_loaded" -ne 1 ]; then
conf_loaded=1
# check for CONFIG vaiable. Set a sane default if not set
if [ ! -z ${CONFIG+x} ]; then
read_conf "$CONFIG"
else
search_paths="$(dirname "$0") /usr/local/etc /etc"
for path in $search_paths; do
# Check that the config file exists
if [ -f "$path/dnscrypt-autokey.conf" ]; then
found=1
read_conf "$path/dnscrypt-autokey.conf"
return
fi
done
if [ -z ${found+x} ] || [ $found -ne 1 ]; then
echo "No config file found!" >&2
exit 1
fi
fi
fi
}
# Read and set config variables
read_conf() {
# Check that the config file exists
if [ -f "$1" ]; then
# Load the config file as a shell script
# shellcheck disable=SC1090
. "$1" # Only load config files you trust!
# Load variables from the config file with sane default values
CONFIG="$(get_realpath "$1")"
KEY_DIR=${KEY_DIR:-"/usr/local/etc/dnscrypt-wrapper/keys"}
SHORT_TERM_KEYS_DIR=${SHORT_TERM_KEYS_DIR:-"${KEY_DIR}"}
VALIDITY_PERIOD=${VALIDITY_PERIOD:-1} # in days
TIME=$((60 * 24 * VALIDITY_PERIOD))
CHACHA20=${CHACHA20:-0}
CURRENT_KEY_ID=${CURRENT_KEY_ID:-0}
PROVIDER_NAME=${PROVIDER_NAME:-""} # 2.dnscrypt-cert.example.com
MODE=${MODE:-""} # unbound, wrapper
CUSTOM_FLAGS=${CUSTOM_FLAGS:-""} # --dnssec, --nofilter, --nolog
LISTEN=${LISTEN:-"0.0.0.0"}
PORTS=${PORTS:-"443, 5353"}
RESOLVER=${RESOLVER:-"127.0.0.1:53"}
UNBOUND_CONTROL=${UNBOUND_CONTROL:-"unbound-control"}
# Read the dnscrypt provider name from unbound's config.
if [ ! -z ${MODE+x} ] && [ "$MODE" = "unbound" ]; then
PROVIDER_NAME=${PROVIDER_NAME:-"$($UNBOUND_CONTROL get_option dnscrypt-provider)"}
fi
script_file_path="$(get_realpath "$0")"
else
echo "Config file '$1' doesn't exist" >&2
exit 1
fi
}
# returns 1 (true) if no key or cert (in the short term key directory) has been modified in the last 12h.
is_about_to_expire() {
if [ "$(find "$SHORT_TERM_KEYS_DIR" -type f -cmin -720 \
\( -iname "*.key" -or -iname "*.cert" \) -print \
| wc -l | sed 's/[^0-9]//g')" -le 0 ]; then
echo 1
else
echo 0
fi
}
# Print a list of valid keys
valid_keys() {
res=""
for file in $(find "$SHORT_TERM_KEYS_DIR" -type f -cmin -"$TIME" \( -iname "*.key" -and \
! -iname "public.key" -and ! -iname "secret.key" \) | sort -r); do
res="${res}${file},"
done
echo "$res"
}
# Print a list of valid certificates
valid_certs() {
res=""
for file in $(find "$SHORT_TERM_KEYS_DIR" -type f -cmin -"$TIME" \( -iname "*.cert" -and \
! -iname "public.key" -and ! -iname "secret.key" \) | sort -r); do
res="${res}${file},"
done
echo "$res"
}
# Show public key fingerprint, certificate expire periods and currently loaded certificates in unbound
status() {
time_in_hours=$((TIME / 60 ))
echo "Script file path: $script_file_path"
echo "Loaded config file: $CONFIG"
printf "%s" "$BLUE"
show_public_key
printf "%sVALID (modified less than ${time_in_hours}h ago):\\n" "$GREEN"
valid_keys
valid_certs
printf "%sABOUT TO EXPIRE (modified more than $((time_in_hours-12))h and less than $((time_in_hours))h ago):\\n" "$YELLOW"
find "$SHORT_TERM_KEYS_DIR" -type f -cmin +"$((TIME-12*60))" ! -cmin +"$TIME" \
\( \( -iname "*.key" -or -iname "*.cert" \) -and \
! -iname "public.key" -and ! -iname "secret.key" \)
printf "%sEXPIRED (modified more than ${time_in_hours}h ago):\\n" "$RED"
find "$SHORT_TERM_KEYS_DIR" -type f -cmin +"$TIME" \
\( \( -iname "*.key" -or -iname "*.cert" \) -and \
! -iname "public.key" -and ! -iname "secret.key" \)
printf "%s" "$CLEAR"
printf "Do we need to create new certs?: "
is_about_to_expire
if [ ! -z ${MODE+x} ] && [ "$MODE" = "unbound" ]; then
printf "\\nUnbound loaded Keys and Certs:\\n"
# Show current loaded certificate and key files in unbound
$UNBOUND_CONTROL get_option dnscrypt-secret-key
$UNBOUND_CONTROL get_option dnscrypt-provider-cert
fi
echo "Running dnscrypt-wrapper/dnscrypt-proxy instances:"
pgrep -l "(dnscrypt-wrapper|dnscrypt-proxy)" || true
}
# Removes expired certificates / keys from the disk
delete_expired() {
find "$SHORT_TERM_KEYS_DIR" -type f -cmin +"$TIME" \
\( \( -iname "*.key" -or -iname "*.cert" \) -and \
! -iname "public.key" -and ! -iname "secret.key" \) \
-exec rm -vf {} \;
}
# Set-up folders and Generate the provider key pair (long term)
init() {
# create KEYS_DIR if it dosn't exist
if [ ! -d "$KEY_DIR" ]; then
mkdir -p "$KEY_DIR"
fi
# create SHORT_TERM_KEYS_DIR if it dosn't exist
if [ ! -d "$SHORT_TERM_KEYS_DIR" ]; then
mkdir -p "$SHORT_TERM_KEYS_DIR"
fi
# Generate the provider key pair (long term)
dnscrypt-wrapper --gen-provider-keypair \
--provider-publickey-file="$KEY_DIR"/public.key \
--provider-secretkey-file="$KEY_DIR"/secret.key
}
# Show provider public key fingerprint
show_public_key() {
dnscrypt-wrapper --show-provider-publickey \
--provider-publickey-file "$KEY_DIR"/public.key
}
# Generate a time-limited secret key (short term)
# accepts a file name as a parameter
new() {
filename="$1"
dnscrypt-wrapper --gen-crypt-keypair \
--crypt-secretkey-file="$SHORT_TERM_KEYS_DIR"/"$filename".key
if [ "$CHACHA20" -eq 1 ]; then
dnscrypt-wrapper --gen-cert-file \
--xchacha20 \
--crypt-secretkey-file="$SHORT_TERM_KEYS_DIR"/"$filename".key \
--provider-cert-file="$SHORT_TERM_KEYS_DIR"/"$filename".cert \
--provider-publickey-file="$KEY_DIR"/public.key \
--provider-secretkey-file="$KEY_DIR"/secret.key \
--cert-file-expire-days="$VALIDITY_PERIOD"
else
dnscrypt-wrapper --gen-cert-file \
--crypt-secretkey-file="$SHORT_TERM_KEYS_DIR"/"$filename".key \
--provider-cert-file="$SHORT_TERM_KEYS_DIR/$filename".cert \
--provider-publickey-file="$KEY_DIR"/public.key \
--provider-secretkey-file="$KEY_DIR"/secret.key \
--cert-file-expire-days="$VALIDITY_PERIOD"
fi
# set permissions for unbound
if [ ! -z ${MODE+x} ] && [ "$MODE" = "unbound" ]; then
chown unbound:unbound "$SHORT_TERM_KEYS_DIR/$filename".*
fi
}
# Renew certificate. Use current timestamp as the CURRENT_KEY_ID
renew() {
CURRENT_KEY_ID="$(date '+%s')"
# new "$CURRENT_KEY_ID-tmp"
new "$CURRENT_KEY_ID"
# Be almost atomic
# mv -fv "$SHORT_TERM_KEYS_DIR/$CURRENT_KEY_ID-tmp.key" \
# "$SHORT_TERM_KEYS_DIR/$CURRENT_KEY_ID.key"
# mv -fv "$SHORT_TERM_KEYS_DIR/$CURRENT_KEY_ID-tmp.cert" \
# "$SHORT_TERM_KEYS_DIR/$CURRENT_KEY_ID.cert"
# if [ "$CHACHA20" -eq 1 ]; then
# mv -fv "$SHORT_TERM_KEYS_DIR/$CURRENT_KEY_ID-tmp-xchacha20.cert" \
# "$SHORT_TERM_KEYS_DIR/$CURRENT_KEY_ID-xchacha20.cert"
# fi
if [ ! -z ${MODE+x} ] && [ "$MODE" = "unbound" ]; then
tell_unbound # about the new key and cert
fi
if [ ! -z ${MODE+x} ] && [ "$MODE" = "wrapper" ]; then
restart_wrappers
fi
}
# Run the init, new, renew, remove-expired commands when needed
auto() {
# Generate the provider key pair when not already present and create first short-term keypair
if [ ! -f "$KEY_DIR/public.key" ] && [ ! -f "$KEY_DIR/secret.key" ]; then
init
new 0
# new 1
fi
# Remove expired keys and certificates
delete_expired
# Renew when the latest cert is about to expire (12h left)
if [ "$(is_about_to_expire)" -eq 1 ]; then
renew
fi
}
# Tell unbound about the new key and cert
# a reload will empty the cache (maybe use dump_cache>file and load_cache<file ?)
tell_unbound() {
printf "Reloading unbound... "
# $UNBOUND_CONTROL dump_cache > /usr/local/etc/unbound/unbound_cache.dmp
$UNBOUND_CONTROL reload
# $UNBOUND_CONTROL load_cache < /usr/local/etc/unbound/unbound_cache.dmp
}
# Start dnscrypt-proxy for local testing on localhost
start_wrapper() {
# --user=_dnscrypt-wrapper \
/usr/local/sbin/dnscrypt-wrapper \
--daemonize \
--listen-address="$LISTEN":"$1" \
--resolver-address="$RESOLVER" \
--provider-name="$PROVIDER_NAME" \
--provider-cert-file="$(valid_certs)" \
--crypt-secretkey-file="$(valid_keys)" \
"$CUSTOM_FLAGS"
}
restart_wrappers() {
/bin/pkill dnscrypt-wrapper || true
# wait for ports to clear
# sleep 180 #(TIME_WAIT) tcp timout time
#sleep 4
original_IFS=$IFS
IFS=', ' # split on commas and spaces
for port in $PORTS; do
IFS=$original_IFS
echo "Listening on: $port"
start_wrapper "$port"
done
IFS=$original_IFS
}
# Start dnscrypt-proxy for local testing on localhost
start_proxy() {
# dnscrypt-proxy v1 is required
dnscrypt-proxy --local-address=127.0.0.1:5300 \
--resolver-address=127.0.0.1:443 \
--provider-name="$PROVIDER_NAME" \
--provider-key="$(show_public_key | awk '{print $4}')"
}
update() {
script_file_path="$(get_realpath "$0")"
wget https://gist.githubusercontent.com/publicarray/a246106b5a6821b69b86e8d05ee41896/raw/dnscrypt-autokey.sh -O "$script_file_path"
chmod +x "$script_file_path"
}
if [ "$#" -eq 0 ]; then
usage
exit 1
fi
# Read arguments from the commandline
while [ $# -gt 0 ] && [ "$1" != "" ]; do
case $1 in
-h | --help | help | -\? | \?)
usage
exit
;;
-c | --conf | conf | config)
shift
CONFIG="$1"
load_conf
;;
-i | --init | init)
load_conf
init
;;
-spk | --show-public-key | show-public-key)
load_conf
show_public_key
;;
-n | --new | new)
load_conf
shift
arg="${1:-$(date '+%s')}" # use the current UNIX time for a cert_id
new "$arg"
exit
;;
-r | --renew | renew)
load_conf
renew
;;
-s | --status | status | --check | check)
load_conf
status
;;
-p | --proxy | proxy)
load_conf
start_proxy
exit
;;
-w | --wrapper | wrapper)
load_conf
shift
port=${1:-443}
start_wrapper "$port"
exit
;;
-ws | --wrappers | wrappers)
load_conf
restart_wrappers
exit
;;
-d | --delete-expired | delete-expired)
load_conf
delete_expired
;;
-a | --auto | auto)
load_conf
auto
exit
;;
-u | --update | update)
update
exit
;;
*)
echo "ERROR: unknown parameter \"$1\""
usage
exit 1
;;
esac
shift # get next parameter
done
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.