--- /dev/null
+#! /bin/sh
+###
+### Generate a new recovery key and split it among keepers
+###
+### (c) 2011 Mark Wooding
+###
+
+###----- Licensing notice ---------------------------------------------------
+###
+### This file is part of the distorted.org.uk key management suite.
+###
+### distorted-keys is free software; you can redistribute it and/or modify
+### it under the terms of the GNU General Public License as published by
+### the Free Software Foundation; either version 2 of the License, or
+### (at your option) any later version.
+###
+### distorted-keys is distributed in the hope that it will be useful,
+### but WITHOUT ANY WARRANTY; without even the implied warranty of
+### MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+### GNU General Public License for more details.
+###
+### You should have received a copy of the GNU General Public License
+### along with distorted-keys; if not, write to the Free Software Foundation,
+### Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+set -e
+case "${KEYSLIB+t}" in t) ;; *) echo >&2 "$0: KEYSLIB unset"; exit 1 ;; esac
+. "$KEYSLIB"/keyfunc.sh
+
+defhelp <<HELP
+[-p PROFILE] RECOV [KEEPER:K ...] [-- OPTION=VALUE ...]
+Generate a new recovery secret, and split it among the available keepers.
+
+The new secret will be called RECOV. For each KEEPER set, the private key
+wil be split into shares, and encrypted with the KEEPER's public keys; any K
+of these shares can be used to reveal the original key.
+
+If there is already a recovery key called RECOV then it will be rolled over.
+The previous key must already be revealed. If KEEPERs are listed, these
+replace the existing keeper sets; otherwise the same keepers as before are
+used.
+
+If there is not a recovery key called RECOV then at least one keeper set must
+be specified.
+HELP
+
+## Parse the command line.
+profile=${recov_profile-recovery}
+while getopts "p:" opt; do
+ case "$opt" in
+ p) profile=$OPTARG ;;
+ *) usage_err ;;
+ esac
+done
+shift $(( $OPTIND - 1 ))
+case $# in 0) usage_err ;; esac
+recov=$1; shift
+checkword "recovery key label" "$recov"
+checkword "profile label" "$profile"
+nkeep=0
+for k in "$@"; do
+ case "$k" in
+ --) break ;;
+ *:*) ;;
+ *) echo >&2 "$quis: bad keeper spec \`$k'"; exit 1 ;;
+ esac
+ keeper=${k%:*} t=${k#*:} nkeep=$(( $nkeep + 1 ))
+ checkword "keeper set label" "$keeper"
+ checknumber "keeper quorum" "$t"
+done
+
+## Establish the keeper parameters.
+rdir=$KEYS/recov/$recov
+if [ ! -d $rdir ]; then mkdir -m755 -p $rdir; fi
+case $nkeep in
+ 0)
+ kparam=$rdir/keepers
+ ;;
+ *)
+ for k in "$@"; do
+ case "$k" in --) break ;; esac
+ keeper=${k%:*} t=${k#*:}
+ echo $keeper $t
+ done >$rdir/keepers.new
+ kparam=$rdir/keepers.new
+ ;;
+esac
+if [ ! -f $kparam ]; then echo >&2 "$quis: no keepers specified"; exit 1; fi
+
+## Make the new key and issue the shares.
+mktmp
+rm -rf $rdir/new
+mkdir -m755 $rdir/new
+cd $tmp
+while :; do case "$#,$1" in
+ 0,) break ;;
+ *,*,*) ;;
+ *,--) break ;;
+ esac
+ shift
+done
+c_gensyskey $profile $rdir/new/store secret recov="$recov"
+while read keeper k; do
+ read n hunoz <$KEYS/keeper/$keeper/meta
+ shamir issue $k/$n secret | {
+ read param
+ echo "$param" >$rdir/new/$keeper.param
+ i=0
+ while read share; do
+ c_sysencrypt $KEYS/keeper/$keeper/$i >$rdir/new/$keeper.$i.share <<EOF
+$share
+EOF
+ i=$(( $i + 1 ))
+ done
+ }
+done <$kparam
+rm -f secret
+
+## If there's an existing instance of this key, transfer the recovery blobs.
+if [ ! -d $rdir/current ]; then
+ seq=0
+else
+ seq=$(readlink $rdir/current)
+ reqsafe
+ reveal=$SAFE/keys.reveal/$recov.current/secret
+ if [ ! -f $reveal ]; then
+ echo >&2 "$quis: current $recov key not revealed"
+ exit 1
+ fi
+
+ find $rdir/current/ -type f -name '*.recov' -print | while read name; do
+ name=${name#$rdir/current/}
+ case "$name" in */*) mkdir -p -m755 $rdir/new/${name%/*} ;; esac
+ c_sysdecrypt $rdir/current/store $reveal <$rdir/current/$name >tmp
+ c_sysencrypt $rdir/new/store <tmp >$rdir/new/$name
+ rm tmp
+ done
+ rm -r $SAFE/keys.reveal/$recov.current
+fi
+
+## Tidy up and commit. Repointing the symlink is grim because, according to
+## POSIX rules, `mv foo bar' should rename `foo' to `bar/foo' is `bar' is a
+## symlink to a directory -- and there's no way of turning this behaviour
+## off. The subterfuge here is due to Colin Watson.
+cd $rdir
+while [ -d $seq ]; do seq=$(( $seq + 1 )); done
+case $kparam in *.new) mv keepers.new keepers ;; esac
+rm -f next
+ln -s $seq next
+mv new $seq
+mkdir hack; mv next hack/current; mv hack/current .; rmdir hack
+
+###----- That's all, folks --------------------------------------------------