diff --git a/utils/scripts/cassh-install-hostkeys.sh b/utils/scripts/cassh-install-hostkeys.sh
new file mode 100644
index 0000000..8ddd46d
--- /dev/null
+++ b/utils/scripts/cassh-install-hostkeys.sh
@@ -0,0 +1,80 @@
+#!/bin/sh
+# env
+# Written in 2022 by Lucas
+# CC0 1.0 Universal/Public domain - No rights reserved
+#
+# To the extent possible under law, the author(s) have dedicated all
+# copyright and related and neighboring rights to this software to the
+# public domain worldwide. This software is distributed without any
+# warranty. You should have received a copy of the CC0 Public Domain
+# Dedication along with this software. If not, see
+# .
+
+usage()
+{
+ printf "Usage: %s [-c] [-l login_name] host ...\n" "${0##*/}" >&2
+ exit 1
+}
+
+err()
+{
+ printf "%s: %s\n" "${0##*/}" "$*" >&2
+ exit 1
+}
+
+login_name=$(id -nu)
+agent_mode=yes
+while getopts cl: flag; do
+ case $flag in
+ c) agent_mode=confirm ;;
+ l) login_name=$OPTARG ;;
+ *) usage
+ esac
+done
+shift $(($OPTIND - 1))
+[ $# -gt 0 ] || usage
+
+if [ -z "$SSH_AUTH_SOCK" ] || [ ! -S "$SSH_AUTH_SOCK" ]; then
+ eval $(ssh-agent -s)
+ trap 'eval $(ssh-agent -ks)' EXIT INT QUIT TERM
+fi
+
+set -e
+
+for host; do
+ [ -d "$host" ] || err "no host keys for $host"
+
+ # Load correct key into ssh-agent.
+ ssh -o AddKeysToAgent=$agent_mode "$login_name@$host" true
+
+ tmpdir=$(ssh "$login_name@$host" mktemp -dt hostkeys.XXXXXX)
+
+ sftp "$login_name@$host" <\"$tmpdir/install-hostkeys.sh\"" <.
+
+usage()
+{
+ cat - <&2
+Usage:
+ ${0##*/} [-h] [-n principals] [-V validity_interval]
+ [-z serial_number] key ...
+EOF
+ exit 1
+}
+
+_cleanup()
+{
+ eval $(ssh-agent -ks)
+ rm -Pfr -- "$T"
+}
+
+hflag=
+nflag=
+Vflag=
+zflag=
+while getopts hn:V:z: flag; do
+ case $flag in
+ h) hflag=-h ;;
+ n) nflag=$OPTARG ;;
+ V) Vflag=$OPTARG ;;
+ z) zflag=$OPTARG ;;
+ *) usage ;;
+ esac
+done
+shift $(($OPTIND - 1))
+[ $# -gt 0 ] || usage
+
+T=$(mktemp -d) || exit 1
+trap _cleanup EXIT INT QUIT TERM
+
+yyyymmdd=$(date +%Y%m%d)
+if [ -n "$hflag" ]; then
+ cafile=~/.ssh/keys/hostca
+ outfile=hostca-signed-keys.tgz
+else
+ cafile=~/.ssh/keys/userca
+ outfile=userca-signed-keys.tgz
+fi
+
+id=$(ssh-keygen -lf "$cafile.pub" | cut -d " " -f 3-)
+id=${id% (*)}
+if [ -z "$id" ]; then
+ id=${cafile##*/}-$yyyymmdd
+fi
+
+eval $(ssh-agent -s)
+ssh-add "$cafile"
+for arg; do
+ mkdir -p "$T/$arg"
+ if [ -n "$hflag" ]; then
+ f=$T/$arg/ssh_host_ed25519_key
+ else
+ f=$T/$arg/id_ed25519
+ fi
+ comment=$arg-infra-$yyyymmdd
+
+ principals=${nflag:-$arg}
+ if [ -n "$hflag" ]; then
+ principals=$principals,localhost
+ fi
+
+ ssh-keygen -q -t ed25519 -C "$comment" -f "$f" ${hflag:+-N ""} &&
+ ssh-keygen -Us "$cafile.pub" -I "$id" $hflag -n "$principals" \
+ ${Vflag:+-V "$Vflag"} ${zflag:+-z "$zflag"} "$f.pub"
+
+ if [ $? -ne 0 ]; then
+ rm -Pfr -- "$T/$arg"
+ exit 1
+ fi
+done
+
+(cd "$T" && pax -w .) | gzip >"$outfile"