chiark / gitweb /
Multiple key types, key profiles, and user key storage.
authorMark Wooding <mdw@distorted.org.uk>
Sat, 24 Dec 2011 02:29:11 +0000 (02:29 +0000)
committerMark Wooding <mdw@distorted.org.uk>
Sat, 24 Dec 2011 02:33:15 +0000 (02:33 +0000)
  * Introduce multiple key types (currently GnuPG and Seccure, but maybe
    more later, e.g., OpenSSL).

  * Parameters are provided via time-varying profiles.

  * Profiles can be chosen for keeper and recovery keys.

  * Allow users to generate and use keys.

23 files changed:
Makefile.am
cryptop.decrypt [new file with mode: 0644]
cryptop.delkey [new file with mode: 0644]
cryptop.encrypt [new file with mode: 0644]
cryptop.genkey [new file with mode: 0644]
cryptop.in [new file with mode: 0755]
cryptop.info [new file with mode: 0644]
cryptop.public [new file with mode: 0644]
cryptop.recover [new file with mode: 0644]
cryptop.sign [new file with mode: 0644]
cryptop.verify [new file with mode: 0644]
extract-profile.in [new file with mode: 0644]
keyfunc.sh.in
keys.conceal [new file with mode: 0755]
keys.in
keys.keeper-cards [moved from keeper-cards with 97% similarity]
keys.new-keeper [moved from new-keeper with 77% similarity]
keys.new-recov [moved from new-recov with 79% similarity]
keys.recover [moved from recover with 77% similarity, mode: 0755]
keys.reveal [moved from reveal with 61% similarity]
keys.stash [moved from stash with 88% similarity, mode: 0755]
ktype.gnupg [new file with mode: 0644]
ktype.seccure [new file with mode: 0644]

index 12168f62b8f986bfc2c1ff9dcf9a272c7cc701cb..0cd0854cf089bbc0ab7269e942b37dbd09f7c3a6 100644 (file)
@@ -24,7 +24,9 @@
 ### Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 
 bin_SCRIPTS             =
 ### Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 
 bin_SCRIPTS             =
+sbin_SCRIPTS            =
 dist_pkglib_SCRIPTS     =
 dist_pkglib_SCRIPTS     =
+dist_pkglib_DATA        =
 pkglib_DATA             =
 noinst_SCRIPTS          =
 
 pkglib_DATA             =
 noinst_SCRIPTS          =
 
@@ -43,6 +45,7 @@ SUBSTVARS = \
        PYTHON="$(PYTHON)" \
        bindir="$(bindir)" \
        pkgconfdir="$(sysconfdir)/$(PACKAGE)" \
        PYTHON="$(PYTHON)" \
        bindir="$(bindir)" \
        pkgconfdir="$(sysconfdir)/$(PACKAGE)" \
+       pkgstatedir="$(localstatedir)/$(PACKAGE)" \
        pkglibdir="$(pkglibdir)"
 
 SUBST = $(AM_V_GEN)$(confsubst)
        pkglibdir="$(pkglibdir)"
 
 SUBST = $(AM_V_GEN)$(confsubst)
@@ -58,6 +61,42 @@ shamir: shamir.in Makefile
        $(SUBST) $(srcdir)/shamir.in $(SUBSTVARS) >shamir.new && \
                chmod +x shamir.new && mv shamir.new shamir
 
        $(SUBST) $(srcdir)/shamir.in $(SUBSTVARS) >shamir.new && \
                chmod +x shamir.new && mv shamir.new shamir
 
+## Property expansion.
+bin_SCRIPTS            += extract-profile
+EXTRA_DIST             += extract-profile.in
+CLEANFILES             += extract-profile
+extract-profile: extract-profile.in Makefile
+       $(SUBST) $(srcdir)/extract-profile.in $(SUBSTVARS) \
+                       >extract-profile.new && \
+               chmod +x extract-profile.new && \
+               mv extract-profile.new extract-profile
+
+###--------------------------------------------------------------------------
+### Crypto operations.
+
+## Main driver program.
+sbin_SCRIPTS           += cryptop
+EXTRA_DIST             += cryptop.in
+CLEANFILES             += cryptop
+cryptop: cryptop.in Makefile
+       $(SUBST) $(srcdir)/cryptop.in $(SUBSTVARS) >cryptop.new && \
+               chmod +x cryptop.new && mv cryptop.new cryptop
+
+## Key type libraries.
+dist_pkglib_DATA       += ktype.gnupg
+dist_pkglib_DATA       += ktype.seccure
+
+## Commands.
+dist_pkglib_SCRIPTS    += cryptop.genkey
+dist_pkglib_SCRIPTS    += cryptop.delkey
+dist_pkglib_SCRIPTS    += cryptop.recover
+dist_pkglib_SCRIPTS    += cryptop.info
+dist_pkglib_SCRIPTS    += cryptop.public
+dist_pkglib_SCRIPTS    += cryptop.encrypt
+dist_pkglib_SCRIPTS    += cryptop.decrypt
+dist_pkglib_SCRIPTS    += cryptop.sign
+dist_pkglib_SCRIPTS    += cryptop.verify
+
 ###--------------------------------------------------------------------------
 ### Main driver program and commands.
 
 ###--------------------------------------------------------------------------
 ### Main driver program and commands.
 
@@ -78,11 +117,12 @@ keyfunc.sh: keyfunc.sh.in Makefile
                mv keyfunc.sh.new keyfunc.sh
 
 ## Commands.
                mv keyfunc.sh.new keyfunc.sh
 
 ## Commands.
-dist_pkglib_SCRIPTS    += keeper-cards
-dist_pkglib_SCRIPTS    += new-keeper
-dist_pkglib_SCRIPTS    += new-recov
-dist_pkglib_SCRIPTS    += recover
-dist_pkglib_SCRIPTS    += reveal
-dist_pkglib_SCRIPTS    += stash
+dist_pkglib_SCRIPTS    += keys.conceal
+dist_pkglib_SCRIPTS    += keys.keeper-cards
+dist_pkglib_SCRIPTS    += keys.new-keeper
+dist_pkglib_SCRIPTS    += keys.new-recov
+dist_pkglib_SCRIPTS    += keys.recover
+dist_pkglib_SCRIPTS    += keys.reveal
+dist_pkglib_SCRIPTS    += keys.stash
 
 ###----- That's all, folks --------------------------------------------------
 
 ###----- That's all, folks --------------------------------------------------
diff --git a/cryptop.decrypt b/cryptop.decrypt
new file mode 100644 (file)
index 0000000..ccc42a8
--- /dev/null
@@ -0,0 +1,43 @@
+#! /bin/sh
+###
+### Decrypt data using a user key
+###
+### (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
+KEY
+Decrypt the ciphertext from standard input using the named KEY.  The
+plaintext is written to standard output.
+HELP
+
+case $# in 1) ;; *) usage_err ;; esac
+key=$1
+
+mktmp
+prepare "$key" decrypt
+c_decrypt $kdir $knub
+
+###----- That's all, folks --------------------------------------------------
diff --git a/cryptop.delkey b/cryptop.delkey
new file mode 100644 (file)
index 0000000..15dcc7d
--- /dev/null
@@ -0,0 +1,43 @@
+#! /bin/sh
+###
+### Delete a user key
+###
+### (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
+KEY
+Delete the named user KEY.
+HELP
+
+case $# in 1) ;; *) usage_err ;; esac
+key=$1
+
+parse_keylabel "$key"
+rm -rf $kdir
+rm -f $knub
+rm -f $KEYS/recov/*/current/$kowner/$klabel.recov
+
+###----- That's all, folks --------------------------------------------------
diff --git a/cryptop.encrypt b/cryptop.encrypt
new file mode 100644 (file)
index 0000000..16da12a
--- /dev/null
@@ -0,0 +1,43 @@
+#! /bin/sh
+###
+### Encrypt data using a user key
+###
+### (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
+KEY
+Encrypt the data from standard input using the named KEY.  The ciphertext is
+written to standard output.
+HELP
+
+case $# in 1) ;; *) usage_err ;; esac
+key=$1
+
+mktmp
+prepare "$key" encrypt
+c_encrypt $kdir $knub
+
+###----- That's all, folks --------------------------------------------------
diff --git a/cryptop.genkey b/cryptop.genkey
new file mode 100644 (file)
index 0000000..b33458d
--- /dev/null
@@ -0,0 +1,100 @@
+#! /bin/sh
+###
+### Generate a user key
+###
+### (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
+[-f] KEY PROFILE [ARGUMENTS ...]
+Generate a named KEY, according to the PROFILE.
+
+The ARGUMENTS are passed to the key-type handler selected by the profile.
+
+Options:
+  -f           Force overwriting an existing key.
+HELP
+
+genhook_recov () {
+  base=$1 nub=$2
+  for recov in $kopt_recovery; do
+    (stash $recov $kowner/$klabel <"$nub")
+  done
+}
+
+## Parse command-line arguments.
+forcep=nil
+while getopts "f" opt; do
+  case "$opt" in
+    f) forcep=t ;;
+    *) usage_err ;;
+  esac
+done
+shift $(( $OPTIND - 1 ))
+case $# in 0 | 1) usage_err ;; esac
+key=$1 profile=$2; shift 2
+
+## Check the key name carefully.  This is where we actually create files,
+## so it's reallly important to make sure that we don't make any stupid
+## mistakes.
+mktmp
+parse_keylabel "$key"
+case "$kowner" in
+  "$USERV_USER") ;;
+  *) echo >&2 "$quis: invalid owner \`$kowner' for key"; exit 1 ;;
+esac
+case $forcep in
+  nil)
+    if [ -d $kdir ]; then
+      echo >&2 "$quis: key \`$key' already exists"
+      exit 1
+    fi
+    ;;
+esac
+
+## Check the profile name.  We shouldn't allow users to refer to each
+## other's profiles.
+case "$profile" in
+  :*)
+    label=${profile#:}
+    ;;
+  *:*)
+    powner=${profile%%:*} label=${profile#*:}
+    case "$powner" in
+      "$USERV_USER") ;;
+      *) echo >&2 "$quis: invalid owner \`$powner' for profile"; exit 1 ;;
+    esac
+    ;;
+  *)
+    label=$profile
+    ;;
+esac
+checkword "profile label" "$label"
+read_profile $profile
+
+## Generate the key.
+c_genkey $profile $kdir $knub genhook_recov "$@"
+
+###----- That's all, folks --------------------------------------------------
diff --git a/cryptop.in b/cryptop.in
new file mode 100755 (executable)
index 0000000..3efb554
--- /dev/null
@@ -0,0 +1,61 @@
+#! /bin/sh
+###
+### User cryptographic operations
+###
+### (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
+: ${ETC=@pkgconfdir@}
+: ${KEYS=@pkgstatedir@}
+: ${KEYSLIB=@pkglibdir@}
+export ETC KEYS KEYSLIB
+
+. "$KEYSLIB"/keyfunc.sh
+
+usage="usage: $quis COMMAND [ARGUMENTS ...]"
+prefix=cryptop
+
+## Fake up caller credentials if not called via userv.
+case "${USERV_USER+t}" in
+  t) ;;
+  *) USERV_USER=${LOGNAME-${USER-$(id -un)}} USERV_UID=$(id -u) ;;
+esac
+case "${USERV_GROUP+t}" in
+  t) ;;
+  *) USERV_GROUP=$(id -Gn) USERV_GID=$(id -gn) ;;
+esac
+export USERV_USER USERV_UID USERV_GROUP USERV_GID
+
+## Parse options.
+while getopts "hv" opt; do
+  case "$opt" in
+    h) cmd_help; exit ;;
+    v) version; exit ;;
+    *) usage_err ;;
+  esac
+done
+shift $(( $OPTIND - 1 ))
+
+## Dispatch.
+dispatch "$@"
+
+###----- That's all, folks --------------------------------------------------
diff --git a/cryptop.info b/cryptop.info
new file mode 100644 (file)
index 0000000..1d0ff21
--- /dev/null
@@ -0,0 +1,43 @@
+#! /bin/sh
+###
+### Show information about a key
+###
+### (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
+KEY
+Show information about the named user KEY.
+HELP
+
+case $# in 1) ;; *) usage_err ;; esac
+key=$1
+
+mktmp
+prepare "$key" info
+set | sed -n '/^kprop_/{s///;y/_/-/;p}'
+k_info $kdir
+
+###----- That's all, folks --------------------------------------------------
diff --git a/cryptop.public b/cryptop.public
new file mode 100644 (file)
index 0000000..a0054e9
--- /dev/null
@@ -0,0 +1,47 @@
+#! /bin/sh
+###
+### Show a public key
+###
+### (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
+KEY
+If the user KEY is asymmetric, write the public key to stdout.
+HELP
+
+case $# in 1) ;; *) usage_err ;; esac
+key=$1
+
+mktmp
+prepare "$key" info
+if [ -f $kdir/pub ]; then
+  cat $kdir/pub
+else
+  echo >&2 "$quis: \`$key' has no public part"
+  exit 1
+fi
+
+###----- That's all, folks --------------------------------------------------
diff --git a/cryptop.recover b/cryptop.recover
new file mode 100644 (file)
index 0000000..f661569
--- /dev/null
@@ -0,0 +1,68 @@
+#! /bin/sh
+###
+### Recover a user key nub
+###
+### (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
+KEY RECOV
+Recover the named user KEY using a blob protected using the recovery key
+RECOV; it is an error if RECOV is not currently revealed.
+HELP
+
+case $# in 2) ;; *) usage_err ;; esac
+key=$1 recov=$2
+parse_keylabel "$key"
+if [ ! -d $kdir ]; then echo >&2 "$quis: unknown key \`$key'"; exit 1; fi
+checkword "recovery key label" "$recov"
+
+mktmp
+nubid=$(cat $kdir/nubid)
+readmeta $kdir
+read_profile "$profile"
+if [ -f $knub ]; then
+  nubbin=$(nubid <$knub)
+  case "$nubbin" in
+    "$nubid")
+      echo >&2 "$quis: key \`$key' doesn't need recovery"
+      exit 1
+      ;;
+  esac
+fi
+
+umask 077
+recover $recov $kowner/$klabel >$knub.new
+nubbin=$(nubid <$knub.new)
+case "$nubbin" in
+  "$nubid") ;;
+  *)
+    echo >&2 "$quis: recovery produced incorrect nub"
+    exit 1
+    ;;
+esac
+mv $knub.new $knub
+
+###----- That's all, folks --------------------------------------------------
diff --git a/cryptop.sign b/cryptop.sign
new file mode 100644 (file)
index 0000000..38d4e78
--- /dev/null
@@ -0,0 +1,43 @@
+#! /bin/sh
+###
+### Sign data using a user key
+###
+### (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
+KEY
+Sign the data from standard input using the named KEY.  The signature is
+written to standard output.
+HELP
+
+case $# in 1) ;; *) usage_err ;; esac
+key=$1
+
+mktmp
+prepare "$key" sign
+c_sign $kdir $knub
+
+###----- That's all, folks --------------------------------------------------
diff --git a/cryptop.verify b/cryptop.verify
new file mode 100644 (file)
index 0000000..9e6b63c
--- /dev/null
@@ -0,0 +1,43 @@
+#! /bin/sh
+###
+### Verify signed data using a user key
+###
+### (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
+KEY SIGNATURE
+Verify the message on standard input against a literla SIGNATURE using the
+named KEY.
+HELP
+
+case $# in 2) ;; *) usage_err ;; esac
+key=$1 sig=$2
+
+mktmp
+prepare "$key" sign
+c_sign $kdir $knub "$sig"
+
+###----- That's all, folks --------------------------------------------------
diff --git a/extract-profile.in b/extract-profile.in
new file mode 100644 (file)
index 0000000..1d19fc2
--- /dev/null
@@ -0,0 +1,465 @@
+#! @PYTHON@
+###
+### Parse a profile definition file and emit a particular section
+###
+### (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.
+
+from __future__ import with_statement
+
+import sys as SYS
+import os as OS
+import UserDict as UD
+import optparse as O
+from cStringIO import StringIO
+
+PACKAGE = "@PACKAGE@"
+VERSION = "@VERSION@"
+
+###--------------------------------------------------------------------------
+### Utilities.
+
+class struct (object):
+  def __init__(me, **kw):
+    me.__dict__.update(kw)
+
+class UserError (Exception): pass
+
+###--------------------------------------------------------------------------
+### Configuration section management.
+
+class prop (struct): pass
+
+class Section (object, UD.DictMixin):
+  """
+  A section of a profile configuration file.
+  """
+
+  ## States for depth-first traversal.
+  V_WHITE = 0                           # Not yet visited.
+  V_GREY = 1                            # Currently being visited.
+  V_BLACK = 2                           # Visited previously and processed.
+
+  def __init__(me, name, dict = None):
+    """
+    Initialize a Section object.
+
+    The DICT provides the initial (direct) contents of the Section.  The NAME
+    is used for presentation purposes, e.g., when reporting error conditions.
+    """
+    super(Section, me).__init__()
+    if dict is None: me._dict = {}
+    else: me._dict = dict
+    me._visited = me.V_WHITE
+    me.name = name
+    me.includes = set()
+    me.inferiors = set()
+    me.inherited = {}
+
+  ## Dictionary methods for UD.DictMixin.  The built-in `dict' class provides
+  ## equality, and is therefore not hashable.  By doing things this way, we
+  ## can retain reference equality and stay hashable, which will be useful
+  ## later.
+  def __getitem__(me, key):
+    return me._dict[key]
+  def __setitem__(me, key, value):
+    me._dict[key] = value
+  def __delitem__(me, key):
+    del me._dict[key]
+  def keys(me):
+    return me._dict.keys()
+  def __contains__(me, key):
+    return key in me._dict
+  def __iter__(me):
+    return me._dict.__iter__()
+  def iteritems(me):
+    return me._dict.iteritems()
+  def __repr__(me):
+    return 'Section(%r, %r)' % (me.name, me.inherited)
+
+  def transit(me, seen = None, path = None):
+    """
+    Visit the Section for the purposes of computing transitive inclusion.
+
+    If this completes successfully, the Section's inferiors slot is set up to
+    contain all of its (non-strict) inferiors.  A section's inferiors consist
+    of itself, together with the union of the inferiors of all of its
+    included Sections.
+
+    If the Section's visited state is black, nothing happens; if it's white
+    then it will be coloured grey temporarily, and its included Sections
+    processed recursively; if it's grey to begin with then we have
+    encountered a cycle.
+
+    The SEEN dictionary and PATH list are used for detecting and reporting
+    cycles.  The PATH contains a list of the currently grey Sections, in the
+    order in which they were encountered; SEEN maps Section names to their
+    indices in the PATH list.
+
+    It is possible to make this work in the presence of cycles, but it's more
+    effort than it's worth.
+    """
+
+    ## Extend the path to include us.  This will be useful when reporting
+    ## cycles.
+    if seen is None: seen = {}
+    if path is None: path = []
+    path.append(me)
+
+    ## Already been here: nothing to do.
+    if me._visited == me.V_BLACK:
+      pass
+
+    ## We've found a cycle: report it to the user.
+    elif me._visited == me.V_GREY:
+      raise UserError, 'detected inclusion cycle:\n\t%s' % \
+            ' -> '.join(["`%s'" % s.name for s in path[seen[me]:]])
+
+    ## Not done this one yet: process my included Sections, and compute the
+    ## union of their inferiors.
+    else:
+      seen[me] = len(path) - 1
+      me._visited = me.V_GREY
+      me.inferiors = set([me])
+      for s in me.includes:
+        s.transit(seen, path)
+        me.inferiors.update(s.inferiors)
+      me._visited = me.V_BLACK
+
+    ## Remove myself from the path.
+    path.pop()
+
+  def inherit(me):
+    """
+    Compute the inherited properties for this Section.
+
+    A Section has an inherited property named P if any inferior has a direct
+    property named P.  The value of the property is determined as follows.
+    Firstly, determine the set A of all inferiors which have a direct
+    property P.  Secondly, determine a /reduced/ set containing only the
+    maximal elements of A: if R contains a pair of distinct inferiors I and J
+    such that I is an inferior of J, then R does not contain I; R contains
+    all elements A not so excluded.  If all inferiors in R define the same
+    value for the property, then that is the value of the inherited property;
+    if two inferiors disagree, then the situation is erroneous.
+
+    Note that if a Section defines a direct property then it has an inherited
+    property with the same value: in this case, the reduced set is a
+    singleton.
+    """
+
+    ## First pass: for each property name, determine the reduced set of
+    ## inferiors defining that property, and the values they have for it.
+    ## Here, D maps property names to lists of `prop' records.
+    d = {}
+    for s in me.inferiors:
+
+      ## Work through the direct properties of inferior S.
+      for k, v in s.iteritems():
+
+        ## Ignore `special' properties.
+        if k.startswith('@'):
+          continue
+
+        ## Work through the current reduced set.  Discard entries from
+        ## sections inferior to S.  If an entry exists for a section T to
+        ## which S is inferior, then don't add S itself.
+        addp = True
+        pp = []
+        try:
+          for q in d[k]:
+            if s in q.source.inferiors:
+              addp = False
+            if q.source not in s.inferiors:
+              pp.append(q)
+        except KeyError:
+          pass
+        if addp:
+          pp.append(prop(value = v, source = s))
+        d[k] = pp
+
+    ## Second pass: check that the reduced set defines a unique value for
+    ## each inherited property.
+    for k, vv in d.iteritems():
+      c = {}
+
+      ## Build in C a dictionary mapping candidate values to lists of
+      ## inferiors asserting those values.
+      for p in vv:
+        c.setdefault(p.value, []).append(p.source)
+
+      ## Now C should have only one key.  If not, C records enough
+      ## information that we can give a useful error report.
+      if len(c) != 1:
+        raise UserError, \
+              "inconsistent values for property `%s' in section `%s': %s" % \
+              (k, me.name,
+               ''.join(["\n\t`%s' via %s" %
+                        (v, ', '.join(["`%s'" % s.name for s in ss]))
+                        for v, ss in c.iteritems()]))
+
+      ## Insert the computed property value.
+      me.inherited[k] = c.keys()[0]
+
+  def expand(me, string, seen = None, path = None):
+    """
+    Expand placeholders in STRING and return the result.
+
+    A placeholder has the form $PROP or ${PROP} (the latter syntax identifies
+    the property name unambiguously), and is replaced by the value of the
+    (inherited) property named PROP.  A token $$ is replaced with a single $.
+
+    The SEEN and PATH parameters work the same way as in the `transit'
+    method.
+    """
+
+    if seen is None: seen = {}
+    if path is None: path = []
+
+    ## Prepare stuff for the loop.
+    out = StringIO()
+    left = 0
+    n = len(string)
+
+    ## Pick out placeholders and expand them.
+    while True:
+
+      ## Find a placeholder.
+      dol = string.find('$', left)
+
+      ## None: commit the rest of the string and we're done.
+      if dol < 0:
+        out.write(string[left:])
+        break
+
+      ## Commit the portion before the placeholder.
+      out.write(string[left:dol])
+
+      ## Check for a trailing `$'.  After this, we can be sure of at least
+      ## one more character.
+      if dol + 1 >= n:
+        prop = ''
+
+      ## If there's a left brace, find a right brace: the property name is
+      ## between them.
+      elif string[dol + 1] == '{':
+        ace = string.find('}', dol + 2)
+        if ace < 0:
+          raise UserError, \
+                "invalid placeholder (missing `}') in `%s'" % string
+        prop = string[dol + 2:ace]
+        left = ace + 1
+
+      ## If there's a dollar, just commit it and go round again.
+      elif string[dol + 1] == '$':
+        left = dol + 2
+        out.write('$')
+        continue
+
+      ## Otherwise take as many constituent characters as we can.
+      else:
+        left = dol + 1
+        while left < n and (string[left].isalnum() or string[left] in '-_'):
+          left += 1
+        prop = string[dol + 1:left].replace('-', '_')
+
+      ## If we came up empty, report an error.
+      if prop == '':
+        raise UserError, \
+              "invalid placeholder (empty name) in `%s'" % string
+
+      ## Extend the path: we're going to do a recursive expansion.
+      path.append(prop)
+
+      ## Report a cycle if we found one.
+      if prop in seen:
+        raise UserError, 'substitution cycle:\n\t%s' % \
+              (' -> '.join(["`%s'" % p for p in path[seen[prop]:]]))
+
+      ## Look up the raw value.
+      try:
+        value = me.inherited[prop]
+      except KeyError:
+        raise UserError, "unknown property `%s'" % prop
+
+      ## Recursively expand, and unwind the PATH and SEEN stuff.
+      seen[prop] = len(path) - 1
+      out.write(me.expand(value, seen, path))
+      path.pop()
+      del seen[prop]
+
+    ## Done: return the accumulated result.
+    return out.getvalue()
+
+def link(d):
+  """
+  Link together the Sections in D according to their inclusions.
+
+  If a Section S has an `@include' special property, then set S's `includes'
+  slot to be the set of sections named in that property's value.  Then
+  compute the inferiors and inherited properties for all of the Sections.
+  """
+
+  ## Capture the global section.
+  g = d['@GLOBAL']
+
+  ## Walk through all of the sections.
+  for sect in d.itervalues():
+
+    ## If this isn't the global section, then add the global section as an
+    ## implicit inclusion.
+    if sect is not g:
+      sect.includes.add(g)
+
+    ## If there are explicit inclusions, then add them to the included set.
+    try:
+      inc = sect['@include']
+    except KeyError:
+      pass
+    else:
+      for s in inc.split():
+        try:
+          sect.includes.add(d[s])
+        except KeyError:
+          raise UserError, \
+                "unknown section `%s' included in `%s'" % (s, sect.name)
+
+  ## Compute the inferiors and inherited properties.
+  for sect in d.itervalues():
+    sect.transit()
+  for sect in d.itervalues():
+    sect.inherit()
+
+###--------------------------------------------------------------------------
+### Parsing input files.
+
+## Names of special properties.  All of these begin with an `@' sign.
+SPECIALS = set(['@include'])
+
+def parse(filename, d):
+  """
+  Parse a profile file FILENAME, updating dictionary D.
+
+  Each entry in the dictionary maps a section name to the section's contents;
+  the contents are in turn represented as a dictionary mapping properties to
+  values.  Inter-section references, defaults, and so on are not processed
+  here.
+  """
+
+  sect = '@GLOBAL'
+
+  with open(filename) as f:
+    n = 0
+    for line in f:
+      n += 1
+      line = line.strip()
+      if not line or line[0] in ';#':
+        continue
+      if line[0] == '[' and line[-1] == ']':
+        sect = line[1:-1]
+        continue
+
+      ## Parse an assignment.
+      eq = line.find('=')
+      colon = line.find(':')
+      if eq < 0 or 0 <= colon < eq: eq = colon
+      if eq < 0: raise UserError, '%s:%d: no assignment' % (filename, n)
+      name, value = line[:eq].strip(), line[eq + 1:].strip()
+
+      ## Check that the name is well-formed.
+      name = name.replace('-', '_')
+      if not (name and
+              (name in SPECIALS or
+               all(map(lambda ch: ch == '_' or ch.isalnum(), name)))):
+        raise UserError, "%s:%d: bad name `%s'" % (filename, n, name)
+
+      ## Store the assignment.
+      try:
+        d[sect][name] = value
+      except KeyError:
+        s = Section(sect)
+        d[sect] = s
+        s[name] = value
+
+###--------------------------------------------------------------------------
+### Main program.
+
+OP = O.OptionParser(
+  usage = '%prog FILE|DIRECTORY ... SECTION',
+  version = '%%prog, version %s' % VERSION,
+  description = '''\
+Parse the configurations FILE and DIRECTORY contents, and output the named
+SECTION as a sequence of simple assignments.
+''')
+
+def main(args):
+  try:
+
+    ## Check the arguments.
+    opts, args = OP.parse_args(args[1:])
+    if len(args) < 2:
+      OP.error('not enough positional parameters')
+    files = args[:-1]
+    sect = args[-1]
+
+    ## Read in the inputs.
+    d = { '@GLOBAL': Section('@GLOBAL') }
+    for f in files:
+
+      ## It's a directory: pick out the files contained.
+      if OS.path.isdir(f):
+        for sf in sorted(OS.listdir(f)):
+          if not all(map(lambda ch: ch in '_-' or ch.isalnum(), sf)):
+            continue
+          ff = OS.path.join(f, sf)
+          if not OS.path.isfile(ff):
+            continue
+          parse(ff, d)
+
+      ## Not a directory: just try to parse it.
+      else:
+        parse(f, d)
+
+    ## Print the contents.
+    link(d)
+    try:
+      s = d[sect]
+    except KeyError:
+      raise UserError, "unknown section `%s'" % sect
+    for k, v in s.inherited.iteritems():
+      print '%s=%s' % (k, s.expand(v))
+
+  ## Report errors for expected problems.
+  except UserError, e:
+    SYS.stderr.write('%s: %s\n' % (OP.get_prog_name(), e.args[0]))
+    SYS.exit(1)
+  except OSError, e:
+    SYS.stderr.write('%s: %s\n' % (OP.get_prog_name(), e.args[1]))
+    SYS.exit(1)
+  except IOError, e:
+    SYS.stderr.write('%s: %s: %s\n' %
+                     (OP.get_prog_name(), e.filename, e.strerror))
+    SYS.exit(1)
+
+if __name__ == '__main__':
+  main(SYS.argv)
+
+###----- That's all, folks --------------------------------------------------
index 70da24a130e9c8684f413ea956d9da5bb662db4e..ca14782840dfd64d8a238b955e156db95620fc3a 100644 (file)
@@ -29,13 +29,11 @@ quis=${0##*/}
 ### Configuration variables.
 
 PACKAGE="@PACKAGE@" VERSION="@VERSION@"
 ### Configuration variables.
 
 PACKAGE="@PACKAGE@" VERSION="@VERSION@"
-pkgconfdir="@pkgconfdir@" pkglibdir="@pkglibdir@"
 bindir="@bindir@"
 
 case ":$PATH:" in *:"$bindir":*) ;; *) PATH=$bindir:$PATH ;; esac
 
 bindir="@bindir@"
 
 case ":$PATH:" in *:"$bindir":*) ;; *) PATH=$bindir:$PATH ;; esac
 
-if [ -f $KEYS/keys.conf ]; then . $KEYS/keys.conf; fi
-: ${random=/dev/random}
+if [ -f $ETC/keys.conf ]; then . $ETC/keys.conf; fi
 
 case "${KEYS_DEBUG+t}" in t) set -x ;; esac
 
 
 case "${KEYS_DEBUG+t}" in t) set -x ;; esac
 
@@ -43,142 +41,534 @@ case "${KEYS_DEBUG+t}" in t) set -x ;; esac
 ### Cleanup handling.
 
 cleanups=""
 ### Cleanup handling.
 
 cleanups=""
-cleanup () { cleanups="$cleanups $1"; }
-trap 'rc=$?; for i in $cleanups; do $i; done; exit $rc' EXIT
-trap 'exit 127' INT TERM
+cleanup () { cleanups=${cleanups+$cleanups }$1; }
+runcleanups () { for i in $cleanups; do $i; done; }
+trap 'rc=$?; runcleanups; exit $rc' EXIT
+trap 'trap "" EXIT; runcleanups; exit 127' INT TERM
 
 ###--------------------------------------------------------------------------
 ### Utility functions.
 
 
 ###--------------------------------------------------------------------------
 ### Utility functions.
 
+reqsafe () {
+  ## Fail unless a safe directory is set.
+
+  err="$quis: (CONFIGURATION ERROR)"
+  case ${SAFE+t} in
+    t) ;;
+    *) echo >&2 "$err: no SAFE directory"; exit 1 ;;
+  esac
+  if [ ! -d "$SAFE" ]; then
+    echo >&2 "$err: SAFE path \`$SAFE' isn't a directory"
+    exit 1
+  fi
+  case "$SAFE" in
+    [!/]* | *[][[:space:]*?]*)
+      echo >&2 "$err: SAFE path \`$SAFE' contains bad characters"
+      exit 1
+      ;;
+  esac
+  ls -ld "$SAFE" | {
+    me=$(id -un)
+    read perm _ user stuff
+    case "$perm:$user" in
+      d???------:"$me") ;;
+      *)
+       echo >&2 "$err: SAFE path \`$SAFE' has bad owner or permissions"
+       exit 1
+       ;;
+    esac
+  }
+}
+
 ## Temporary directory.
 unset tmp
 ## Temporary directory.
 unset tmp
-rmtmp () { cd /; rm -rf $tmp; }
+rmtmp () { case ${tmp+t} in t) cd /; rm -rf $tmp ;; esac; }
+cleanup rmtmp
 mktmp () {
 mktmp () {
-  ## Make and return the name of a temporary directory.
+  ## Make a temporary directory and store its name in `tmp'.
 
 
-  case "${tmp+t}" in t) echo "$tmp"; return ;; esac
-  mem=$(userv root claim-mem-dir </dev/null)
-  tmp="$mem/keys.tmp.$$"
+  case "${tmp+t}" in t) return ;; esac
+  reqsafe
+  tmp="$SAFE/keys.tmp.$$"
   rm -rf "$tmp"
   mkdir -m700 "$tmp"
   rm -rf "$tmp"
   mkdir -m700 "$tmp"
-  echo "$tmp"
 }
 
 }
 
-###--------------------------------------------------------------------------
-### Input validation functions.
+reqtmp () {
+  ## Fail unless a temporary directory is set.
 
 
-checknumber () {
-  what=$1 thing=$2
-  case "$thing" in
-    "" | [!1-9]* | *[!0-9]*)
-      echo >&2 "$quis: bad $what \`$thing'"
-      exit 1
-      ;;
+  case ${tmp+t} in
+    t) ;;
+    *) echo >&2 "$quis (INTERNAL): no tmp directory set"; exit 127 ;;
   esac
 }
 
   esac
 }
 
-checkword () {
-  what=$1 thing=$2
-  case "$thing" in
-    "" | *[!-0-9a-zA-Z_!%@+=]*)
-      echo >&2 "$quis: bad $what: \`$thing'"
-      exit 1
-      ;;
+parse_keylabel () {
+  key=$1
+  ## Parse the key label string KEY.  Set `kdir' to the base path to use for
+  ## the key's storage, and `kowner' to the key owner's name.
+
+  case "$key" in
+    *:*) kowner=${key%%:*} klabel=${key#*:} ;;
+    *) kowner=$USERV_USER klabel=$key ;;
   esac
   esac
+  checkword "key owner name" "$kowner"
+  checklabel "key" "$klabel"
+  kdir=$KEYS/store/$kowner/$klabel
+  knub=$KEYS/nub/$kowner/$klabel
 }
 
 }
 
-checklabel () {
-  what=$1 thing=$2
+###--------------------------------------------------------------------------
+### Input validation functions.
+
+nl="
+"
+check () {
+  ckwhat=$1 ckpat=$2 thing=$3
+  ## Verify that THING matches the (anchored, basic) regular expression
+  ## CKPAT.  Since matching newlines is hard to do portably, also check that
+  ## THING doesn't contain any.  If the checks fail, report an error and
+  ## exit.
+
+  validp=t
   case "$thing" in
   case "$thing" in
-    *[!-0-9a-zA-Z_!%@+=/#]* | *//* | /* | */)
-      echo >&2 "$quis: bad $what label \`$thing'"
-      exit 1
-      ;;
+    *"$nl"*) validp=nil ;;
+    *) if ! expr >/dev/null "$thing" : "$ckpat\$"; then validp=nil; fi ;;
+  esac
+  case $validp in
+    nil) echo >&2 "$quis: bad $ckwhat \`$thing'"; exit 1 ;;
   esac
 }
 
   esac
 }
 
+## Regular expressions for validating input.
+R_IDENTCHARS="A-Za-z0-9_"
+R_WORDCHARS="-$R_IDENTCHARS!%@+="
+R_IDENT="[$R_IDENTCHARS][$R_IDENTCHARS]*"
+R_WORD="[$R_WORDCHARS][$R_WORDCHARS]*"
+R_WORDSEQ="[$R_WORDCHARS[:space:]][$R_WORDCHARS[:space:]]*"
+R_NUMERIC='\(\([1-9][0-9]*\)\{0,1\}0\{0,1\}\)'
+R_LABEL="\($R_WORD\(/$R_WORD\)*\)"
+R_LINE=".*"
+
+## Various validation functions.
+checknumber () { check "$1" "$R_NUMERIC" "$2"; }
+checkident () { check "$1" "$R_IDENT" "$2"; }
+checkword () { check "$1" "$R_WORD" "$2"; }
+checklabel () { check "$1 label" "$R_LABEL" "$2"; }
+
 ###--------------------------------------------------------------------------
 ###--------------------------------------------------------------------------
-### Crypto operations.
-###
-### We use Seccure for this, but it's interface is Very Annoying.
+### Key storage and properties.
+
+getsysprofile () {
+  profile=$1
+  ## Write the named system PROFILE to standard output.
+
+  $bindir/extract-profile $ETC/profile.d/ "$profile"
+}
+
+setprops () {
+  what=$1 prefix=$2; shift 2
+  ## Set variables based on the NAME=VALUE assignments in the arguments.  The
+  ## value for property NAME is stored in the shell variable PREFIX_NAME.
+
+  for assg in "$@"; do
+    goodp=t
+    case "$assg" in
+      *\=*) name=${assg%%=*} value=${assg#*=} ;;
+      *) goodp=nil ;;
+    esac
+    case "$goodp,$name" in t,*[!0-9A-Za-z_]*=*) goodp=nil ;; esac
+    case "$goodp" in
+      nil) echo >&2 "$quis: bad $what assignment \`$assg'"; exit 1 ;;
+    esac
+    eval "$prefix$name=\$value"
+  done
+}
 
 
-run_seccure () {
-  op=$1; shift
-  ## run_seccure OP ARG ...
+checkprops () {
+  whatprop=$1 prefix=$2; shift 2
+  ## Check that property variables are set in accordance with the remaining
+  ## TABLE arguments.  Each row of TABLE has the form
   ##
   ##
-  ## Run a Seccure program, ensuring that its stderr is reported if it had
-  ## anything very interesting to say, but suppressed if it was boring.
+  ##   NAME OMIT PAT
+  ##
+  ## A table row is satisfied if there is a variable PREFIXNAME whose value
+  ## matces the (basic) regular expression PAT, or if the variable is unset
+  ## and OMIT is `t'.
+
+  for table in "$@"; do
+    case "$table" in ?*) ;; *) continue ;; esac
+    while read -r name omit pat; do
+      eval foundp=\${$prefix$name+t}
+      case "$foundp,$omit" in
+       ,t) continue ;;
+       ,nil)
+         echo >&2 "$quis: missing $whatprop \`$name' required"
+         exit 1
+         ;;
+      esac
+      eval value=\$$prefix$name
+      check "value for $whatprop \`$name'" "$pat" "$value"
+    done <<EOF
+$table
+EOF
+  done
+}
 
 
-  ## We need a temporary place for the error output.
-  case ${tmp+t} in
-    t) ;;
+defprops () {
+  name=$1
+  ## Define a properties table NAME.
+
+  table=$(cat)
+  eval $name=\$table
+}
+
+defprops g_props <<EOF
+type                   nil     $R_IDENT
+recovery               t       $R_WORDSEQ
+random                 t       $R_WORD
+nubhash                        t       $R_WORD
+nubidhash              t       $R_WORD
+nubsz                  t       $R_NUMERIC
+EOF
+
+readprops () {
+  file=$1
+  ## Read a profile from a file.  This doesn't check the form of the
+  ## filename, so it's not suitable for unchecked input.  Properties are set
+  ## using `setprops' with prefix `kprop_'.
+
+  ## Parse the settings from the file.
+  exec 3<"$file"
+  while read line; do
+    case "$line" in "" | \#*) continue ;; esac
+    setprops "property" kprop_ "$line"
+  done <&3
+  exec 3>&-
+  checkprops "property" kprop_ "$g_props"
+
+  ## Fetch the key-type handling library.
+  if [ ! -f $KEYSLIB/ktype.$kprop_type ]; then
+    echo >&2 "$quis: unknown key type \`$kprop_type'"
+    exit 1
+  fi
+  . $KEYSLIB/ktype.$kprop_type
+  checkprops "property" kprop_ "$k_props"
+}
+
+readmeta () {
+  kdir=$1
+  ## Read key metadata from KDIR.
+
+  { read profile; } <"$kdir"/meta
+}
+
+makenub () {
+  ## Generate a key nub in the default way, and write it to standard output.
+  ## The properties `random', `nubsz' and `nubhash' are referred to.
+
+  dd 2>/dev/null \
+    if=/dev/${kprop_random-random} bs=1 count=${kprop_nubsz-512} |
+  openssl dgst -${kprop_nubhash-sha384} -binary |
+  openssl base64
+}
+
+nubid () {
+  ## Compute a hash of the key nub in stdin, and write it to stdout in hex.
+  ## The property `nubidhash' is used.
+
+  { echo "distorted-keys nubid"; cat -; } |
+  openssl dgst -${kprop_nubidhash-sha256}
+}
+
+subst () {
+  what=$1 templ=$2 prefix=$3 pat=$4
+  ## Substitute option values into the template TEMPL.  Each occurrence of
+  ## %{VAR} is replaced by the value of the variable PREFIXVAR.  Finally, an
+  ## error is reported unless the final value matches the regular expression
+  ## PAT.
+
+  out=""
+  rest=$templ
+  while :; do
+
+    ## If there are no more markers to substitute, then finish.
+    case "$rest" in *"%{"*"}"*) ;; *) out=$out$rest; break ;; esac
+
+    ## Split the template into three parts.
+    left=${rest%%\%\{*} right=${rest#*\%\{}
+    var=${right%%\}*} rest=${right#*\}}
+    case "$var" in
+      *-*) default=${var#*-} var=${var%%-*} defaultp=t ;;
+      *) defaultp=nil ;;
+    esac
+
+    ## Find the variable value.
+    checkident "template variable name" "$var"
+    eval foundp=\${$prefix$var+t}
+    case $foundp,$defaultp in
+      t,*) eval value=\$$prefix$var ;;
+      ,t) value=$default ;;
+      *)
+       echo >&2 "$quis: option \`$var' unset, used in template \`$templ'"
+       exit 1
+       ;;
+    esac
+
+    ## Do the substitution.
+    out=$out$left$value
+  done
+
+  ## Check the final result.
+  check "$what" "$pat" "$out"
+
+  ## Done.
+  echo "$out"
+}
+
+read_profile () {
+  profile=$1
+  ## Read property settings from a profile.  The PROFILE name has the form
+  ## [USER:]LABEL.  Properties are set using `setprops' with prefix `kprop_'.
+
+  reqtmp
+  case "$profile" in
+    :*)
+      label=${profile#:} uservp=nil
+      ;;
     *)
     *)
-      echo >&2 "$quis (INTERNAL): run_seccure called without tmpdir"
-      exit 127
+      user=$USERV_USER label=$profile uservp=t
+      ;;
+    *:*)
+      user=${profile%%:*} label=${profile#*:} uservp=t
+      ;;
+  esac
+  checkword "profile label" "$label"
+
+  ## Fetch the profile settings from the user.
+  reqtmp
+  case $uservp in
+    t)
+      checkword "profile user" "$user"
+      userv "$user" cryptop-profile "$label" >$tmp/profile
+      ;;
+    nil)
+      $bindir/extract-profile $ETC/profile.d/ "$label" >$tmp/profile
       ;;
   esac
 
       ;;
   esac
 
-  ## Run the program.
-  set +e; seccure-$op "$@" 2>$tmp/seccure.out; rc=$?; set -e
-  grep -v '^WARNING: Cannot obtain memory lock' $tmp/seccure.out >&2 || :
-  return $rc
+  ## Read the file.
+  readprops $tmp/profile
 }
 
 }
 
-ec_public () {
-  private=$1
-  ## Write the public key corresponding to PRIVATE to stdout.
+###--------------------------------------------------------------------------
+### General crypto operations.
+
+c_genkey () {
+  profile=$1 kdir=$2 knub=$3 hook=$4; shift 4
+  ## Generate a key, and associate it with the named PROFILE (which is
+  ## assumed already to have been read!); store the main data in KDIR, and
+  ## the nub separately in the file KNUB; run HOOK after generation, passing
+  ## it the working key directory and nub file.  Remaining arguments are
+  ## options to the key type.
+
+  ## Set options and check them.
+  setprops "option" kopt_ "$@"
+  checkprops "option" kopt_ "$k_genopts"
+
+  ## Create directory structure and start writing metadata.
+  rm -rf "$kdir.new"
+  mkdir -m755 -p "$kdir.new"
+  case "$knub" in */*) mkdir -m700 -p "${knub%/*}" ;; esac
+  cat >"$kdir.new/meta" <<EOF
+$profile
+EOF
 
 
-  run_seccure key -q -cp256 -F"$private"
+  ## Generate the key.
+  umask=$(umask); umask 077; >"$knub.new"; umask $umask
+  k_generate "$kdir.new" "$knub.new"
+  $hook "$kdir.new" "$knub.new"
+
+  ## Hash the nub.
+  nubid <"$knub.new" >"$kdir.new/nubid"
+
+  ## Juggle everything into place.  Doing this atomically is very difficult,
+  ## and requires more machinery than I can really justify here.  If
+  ## something goes wrong halfway, it should always be possible to fix it,
+  ## either by backing out (if $kdir.new still exists) or pressing on
+  ## forwards (if not).
+  rm -rf "$kdir.old"
+  if [ -e "$kdir" ]; then mv "$kdir" "$kdir.old"; fi
+  mv "$kdir.new" "$kdir"
+  mv "$knub.new" "$knub"
+  rm -rf "$kdir.old"
 }
 
 }
 
-ec_keygen () {
-  private=$1 public=$2
-  ## Make a new key, write private key to PRIVATE and public key to PUBLIC.
-
-  dd if=$random bs=1 count=512 2>/dev/null |
-    openssl dgst -sha384 -binary |
-    (umask 077 && openssl base64 >"$private")
-  ec_public "$private" >"$public"
+c_encrypt () { k_encrypt "$@"; }
+c_decrypt () {
+  if k_decrypt "$@" >$tmp/plain; then cat $tmp/plain
+  else return $?
+  fi
+}
+c_sign () { k_sign "$@"; }
+c_verify () { k_verify "$@"; }
+
+## Stub implementations.
+notsupp () { op=$1; echo >&2 "$quis: operation \`$op' not supported"; }
+k_info () { :; }
+k_encrypt () { notsupp encrypt; }
+k_decrypt () { notsupp decrypt; }
+k_sign () { notsupp sign; }
+k_verify () { notsupp verify; }
+
+prepare () {
+  key=$1 op=$2
+  ## Prepare for a crypto operation OP, using the KEY.  This validates the
+  ## key label, reads the profile, and checks the access-control list.
+
+  ## Find the key properties.
+  parse_keylabel "$key"
+  if [ ! -d $kdir ]; then echo >&2 "$quis: unknown key \`$key'"; exit 1; fi
+  readmeta $kdir
+  read_profile "$profile"
+
+  ## Check whether we're allowed to do this thing.  This is annoyingly
+  ## fiddly.
+  eval acl=\${kprop_acl_$op-!owner}
+  verdict=forbid
+  while :; do
+
+    ## Remove leading whitespace.
+    while :; do
+      case "$acl" in
+       [[:space:]]*) acl=${acl#?} ;;
+       *) break ;;
+      esac
+    done
+
+    ## If there's nothing left, leave.
+    case "$acl" in ?*) ;; *) break ;; esac
+
+    ## Split off the leading word.
+    case "$acl" in
+      *[[:space:]]*) word=${acl%%[[:space:]]*} acl=${acl#*[[:space:]]} ;;
+      *) word=$acl acl="" ;;
+    esac
+
+    ## See what sense it has if it matches.
+    case "$word" in
+      -*) sense=forbid rest=${word#-} ;;
+      *) sense=allow rest=$word ;;
+    esac
+
+    ## See whether the calling user matches.
+    case "$rest" in
+      !owner) pat=$kowner list=$USERV_USER ;;
+      !*) echo >&2 "$quis: unknown ACL token \`$word'" ;;
+      %*) pat=${rest#%} list="$USERV_GROUP $USERV_GID" ;;
+      *) pat=$rest list="$USERV_USER $USERV_UID" ;;
+    esac
+    matchp=nil
+    for i in $list; do case "$i" in $pat) matchp=t; break ;; esac; done
+    case $matchp in t) verdict=$sense; break ;; esac
+  done
+
+  case $verdict in
+    forbid) echo >&2 "$quis: $op access to key \`$key' forbidden"; exit ;;
+  esac
 }
 
 }
 
-ec_encrypt () {
-  public=$1; shift
-  ## Encrypt stuff using the PUBLIC key.  Use -i/-o or redirection.
+###--------------------------------------------------------------------------
+### Crypto operations for infrastructure purposes.
+
+c_sysprofile () {
+  profile=$1
+  ## Select the profile in FILE for future crypto operations.
 
 
-  run_seccure encrypt -q -cp256 -m128 "$@" -- $(cat "$public")
+  unset $(set | sed -n '/^kprop_/s/=.*$//p')
+  reqtmp
+  getsysprofile "$profile" >$tmp/profile
+  readprops $tmp/profile
 }
 
 }
 
-ec_decrypt () {
-  private=$1; shift
-  ## Decrypt stuff using the PRIVATE key.  Use -i/-o or redirection.
+c_gensyskey () {
+  profile=$1 kdir=$2 knub=$3; shift 3
+  ## Generate a system key using PROFILE; store the data in KDIR and the nub
+  ## in KNUB.  Remaining arguments are options.
 
 
-  run_seccure decrypt -q -cp256 -m128 -F"$private" "$@"
+  c_sysprofile "$profile"
+  c_genkey "$profile" "$kdir" "$knub" : "$@"
 }
 
 }
 
-ec_sign () {
-  private=$1; shift
-  ## Sign stuff using the PRIVATE key.  Use -i/-o or redirection.
+c_sysprepare () {
+  kdir=$1
+  readmeta "$kdir"
+  c_sysprofile "$profile"
+}
 
 
-  run_seccure sign -q -cp256 -F"$private" "$@"
+c_sysop () {
+  op=$1 kdir=$2; shift 1
+  c_sysprepare "$kdir"
+  c_$op "$@"
 }
 
 }
 
-ec_verify () {
-  public=$1 signature=$2; shift
-  ## Verify a SIGNATURE using the PUBLIC key; use -i or redirection for the
-  ## input.
+c_sysencrypt () { c_sysop encrypt "$1" /dev/null; }
+c_sysdecrypt () { c_sysop decrypt "$1" "$2"; }
+c_syssign () { c_sysop sign "$1" "$2"; }
+c_sysverify () { c_sysop verify "$1" /dev/null; }
+
+###--------------------------------------------------------------------------
+### Recovery operations.
+
+stash () {
+  recov=$1 label=$2
+  ## Stash a copy of stdin encrypted under the recovery key RECOV, with a
+  ## given LABEL.
+  checkword "recovery key label" "$recov"
+  checklabel "secret" "$label"
+
+  rdir=$KEYS/recov/$recov/current
+  if [ ! -d $rdir/store ]; then
+    echo >&2 "$quis: unknown recovery key \`$recov'"
+    exit 1
+  fi
+  case $label in */*) mkdir -m755 -p $rdir/${label%/*} ;; esac
+  (c_sysencrypt $rdir/store >$rdir/$label.new)
+  mv $rdir/$label.new $rdir/$label.recov
+}
 
 
-  run_seccure verify -q -cp256 "$@" -- $(cat "$public") "$signature"
+recover () {
+  recov=$1 label=$2
+  ## Recover a stashed secret, protected by RECOV and stored as LABEL, and
+  ## write it to stdout.
+  checkword "recovery key label" "$recov"
+  checklabel "secret" "$label"
+
+  rdir=$KEYS/recov/$recov/current
+  if [ ! -f $rdir/$label.recov ]; then
+    echo >&2 "$quis: no blob for \`$label' under recovery key \`$recov'"
+    exit 1
+  fi
+  reqsafe
+  nub=$SAFE/keys.reveal/$recov.current/nub
+  if [ ! -f $nub ]; then
+    echo >&2 "$quis: current recovery key \`$recov' not revealed"
+    exit 1;
+  fi
+  mktmp
+  c_sysdecrypt $rdir/store $nub <$rdir/$label.recov
 }
 
 ###--------------------------------------------------------------------------
 ### Help text.
 
 }
 
 ###--------------------------------------------------------------------------
 ### Help text.
 
-dohelp () {
-  case "$KEYS_HELP" in t) ;; *) return ;; esac
-  help; exit
+defhelp () {
+  read umsg
+  usage="usage: $quis${umsg+ }$umsg"
+  help=$(cat)
+  case "$KEYS_HELP" in t) help; exit ;; esac
 }
 
 }
 
-defhelp () { read umsg; usage="usage: $quis${umsg+ }$umsg"; help=$(cat); }
 help () { showhelp; }
 showhelp () {
   cat <<EOF
 help () { showhelp; }
 showhelp () {
   cat <<EOF
@@ -188,4 +578,63 @@ $help
 EOF
 }
 
 EOF
 }
 
+usage_err () { echo >&2 "$usage"; exit 1; }
+
+###--------------------------------------------------------------------------
+### Subcommand handling.
+
+version () {
+  echo "$PACKAGE version $VERSION"
+}
+
+cmd_help () {
+  rc=0
+  version
+  case $# in
+    0)
+      cat <<EOF
+
+$usage
+
+Options:
+  -h           Show this help text.
+  -v           Show the program version number.
+
+Commands installed:
+EOF
+      cd "$KEYSLIB"
+      for i in $prefix.*; do
+       if [ ! -x "$i" ]; then continue; fi
+       sed -n "/<<HELP/{n;s/^/ ${i#$prefix.} /;p;q;}" "$i"
+      done
+      ;;
+    *)
+      for i in "$@"; do
+       echo
+       if [ ! -x "$KEYSLIB/$prefix.$i" ]; then
+         echo >&2 "$quis: unrecognized command \`$i'"
+         rc=1
+         continue
+       elif ! KEYS_HELP=t "$KEYSLIB/$prefix.$i"; then
+         rc=1
+       fi
+      done
+      ;;
+  esac
+  return $rc
+}
+
+dispatch () {
+  case $# in 0) echo >&2 "$usage"; exit 1 ;; esac
+  cmd=$1; shift
+  case "$cmd" in help) cmd_help "$@"; exit ;; esac
+  if [ ! -x "$KEYSLIB/$prefix.$cmd" ]; then
+    echo >&2 "$quis: unrecognized command \`$cmd'"
+    exit 1
+  fi
+
+  unset KEYS_HELP
+  exec "$KEYSLIB/$prefix.$cmd" "$@"
+}
+
 ###----- That's all, folks --------------------------------------------------
 ###----- That's all, folks --------------------------------------------------
diff --git a/keys.conceal b/keys.conceal
new file mode 100755 (executable)
index 0000000..db34fec
--- /dev/null
@@ -0,0 +1,51 @@
+#! /bin/sh
+###
+### Unreveal a revealed recovery key
+###
+### (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
+RECOV
+Remove the revealed nub of the recovery key RECOV.
+HELP
+
+case $# in 1) ;; *) echo >&2 "$usage"; exit 1 ;; esac
+recov=$1
+checklabel "recovery key" "$recov"
+case "$recov" in
+  */*) ;;
+  *) recov=$recov/current ;;
+esac
+
+reqsafe
+tag=$(echo $recov | tr / .)
+if [ ! -d $SAFE/keys.reveal/$tag ]; then
+  echo >&2 "$quis: recovery key \`$recov' is not revealed"
+  exit 1
+fi
+rm -rf $SAFE/keys.reveal/$tag
+
+###----- That's all, folks --------------------------------------------------
diff --git a/keys.in b/keys.in
index 7915a4fbd88439fe78e3cd0bae2499ab3ab4d21d..2676d5626f78597287a9e292fcafd83cac46fb17 100755 (executable)
--- a/keys.in
+++ b/keys.in
 ### Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 
 set -e
 ### Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 
 set -e
-
-quis=${0##*/}
-PACKAGE=@PACKAGE@
-VERSION=@VERSION@
-
-: ${KEYS=@pkgconfdir@}
+: ${ETC=@pkgconfdir@}
+: ${KEYS=@pkgstatedir@}
 : ${KEYSLIB=@pkglibdir@}
 : ${KEYSLIB=@pkglibdir@}
-export KEYS KEYSLIB
+export ETC KEYS KEYSLIB
 
 
-###--------------------------------------------------------------------------
-### Help.
+. "$KEYSLIB"/keyfunc.sh
 
 usage="usage: $quis COMMAND [ARGUMENTS ...]"
 
 usage="usage: $quis COMMAND [ARGUMENTS ...]"
+prefix=keys
 
 
-version () {
-  echo "$PACKAGE version $VERSION"
-}
-
-help () {
-  rc=0
-  version
-  case $# in
-    0)
-      cat <<EOF
-
-$usage
-
-Options:
-  -h           Show this help text.
-  -v           Show the program version number.
-
-Commands installed:
-EOF
-      cd "$KEYSLIB"
-      for i in *; do
-       case "$i" in *.*) continue ;; esac
-       if [ ! -x "$i" ]; then continue; fi
-       sed -n "/<<HELP/{n;s/^/ $i /;p;q;}" "$i"
-      done
-      ;;
-    *)
-      for i in "$@"; do
-       echo
-       if [ ! -x "$KEYSLIB"/"$i" ]; then
-         echo >&2 "$quis: unrecognized command \`$i'"
-         rc=1
-         continue
-       elif ! KEYS_HELP=t "$KEYSLIB"/"$i"; then
-         rc=1
-       fi
-      done
-      ;;
-  esac
-  return $rc
-}
-
-###--------------------------------------------------------------------------
-### Command dispatch.
-
+## Parse options.
 while getopts "hv" opt; do
   case "$opt" in
 while getopts "hv" opt; do
   case "$opt" in
-    h) help; exit ;;
+    h) cmd_help; exit ;;
     v) version; exit ;;
     v) version; exit ;;
-    *) echo >&2 "$usage"; exit 1 ;;
+    *) usage_err ;;
   esac
 done
   esac
 done
-shift $((OPTIND - 1))
-
-case $# in 0) echo >&2 "$usage"; exit 1 ;; esac
-cmd=$1; shift
-case "$cmd" in help) help "$@"; exit ;; esac
-if [ ! -x "$KEYSLIB"/"$cmd" ]; then
-  echo >&2 "$quis: unrecognized command \`$cmd'"
-  exit 1
-fi
+shift $(( $OPTIND - 1 ))
 
 
-unset KEYS_HELP
-exec "$KEYSLIB"/"$cmd" "$@"
+## Dispatch.
+dispatch "$@"
 
 ###----- That's all, folks --------------------------------------------------
 
 ###----- That's all, folks --------------------------------------------------
similarity index 97%
rename from keeper-cards
rename to keys.keeper-cards
index 359c5a2af9ac417c71a2984168bd25e4bfec24f6..73f2411e616f997869b1fb8a6a58e6773fcb09e7 100755 (executable)
@@ -40,10 +40,9 @@ index.  If no INDICES are given then all secret keys are written.
 The public keys are found in $KEYS/keeper/KEEPER/I.pub;
 private keys are read from KEEPER/I in the current directory.
 HELP
 The public keys are found in $KEYS/keeper/KEEPER/I.pub;
 private keys are read from KEEPER/I in the current directory.
 HELP
-dohelp
 
 ## Parse the command line.
 
 ## Parse the command line.
-case $# in 0) echo >&2 "$usage"; exit 1 ;; esac
+case $# in 0) usage_err ;; esac
 keeper=$1; shift
 checkword "keeper set label" "$keeper"
 read n hunoz <$KEYS/keeper/$keeper/meta
 keeper=$1; shift
 checkword "keeper set label" "$keeper"
 read n hunoz <$KEYS/keeper/$keeper/meta
@@ -65,21 +64,21 @@ for range in "$@"; do
       ;;
   esac
   case "$low" in ?*) ;; *) low=0 ;; esac
       ;;
   esac
   case "$low" in ?*) ;; *) low=0 ;; esac
-  case "$high" in ?*) ;; *) high=$((n - 1)) ;; esac
+  case "$high" in ?*) ;; *) high=$(( $n - 1 )) ;; esac
   if [ 0 -gt $low -o $low -gt $high -o $high -ge $n ]; then
     echo >&2 "$quis: invalid index range \`$range'"
     exit 1
   fi
   if [ 0 -gt $low -o $low -gt $high -o $high -ge $n ]; then
     echo >&2 "$quis: invalid index range \`$range'"
     exit 1
   fi
-  i=$((low + 0))
+  i=$(( $low + 0 ))
   while [ $i -le $high ]; do
     case $want in *:"$i":*) ;; *) want=$want$i: ;; esac
   while [ $i -le $high ]; do
     case $want in *:"$i":*) ;; *) want=$want$i: ;; esac
-    i=$((i + 1))
+    i=$(( $i + 1 ))
   done
 done
 
 ## Start working on the output file.  This will contain deep secrets, so
 ## don't leave stuff easily readable.
   done
 done
 
 ## Start working on the output file.  This will contain deep secrets, so
 ## don't leave stuff easily readable.
-tmp=$(mktmp); cleanup rmtmp
+mktmp
 umask 077
 exec 3>$tmp/$keeper.tex
 cat >&3 <<'EOF'
 umask 077
 exec 3>$tmp/$keeper.tex
 cat >&3 <<'EOF'
@@ -247,7 +246,7 @@ while [ $i -lt $n ]; do
 \card{$i}{$secret}
 EOF
   esac
 \card{$i}{$secret}
 EOF
   esac
-  i=$((i + 1))
+  i=$(( $i + 1 ))
 done
 
 ## Wrap up and build the document.
 done
 
 ## Wrap up and build the document.
similarity index 77%
rename from new-keeper
rename to keys.new-keeper
index 05423f47882b787bbd7920d976ba0131bc2b3b68..45764bdf249560611955b5521055fd36d27ee3d2 100755 (executable)
@@ -28,21 +28,29 @@ case "${KEYSLIB+t}" in t) ;; *) echo >&2 "$0: KEYSLIB unset"; exit 1 ;; esac
 . "$KEYSLIB"/keyfunc.sh
 
 defhelp <<HELP
 . "$KEYSLIB"/keyfunc.sh
 
 defhelp <<HELP
-KEEPER N
+[-p PROFILE] KEEPER N [OPTION=VALUE ...]
 Create a new set of keeper keys.
 
 Create a new set of keeper keys.
 
-The private keys are stored in KEEPER/I for each 0 <= I < N in the current
+The key nubs are stored in KEEPER/I for each 0 <= I < N in the current
 directory; presumably you'll do something sensible with them.  A new
 directory $KEYS/keeper/KEEPER is created (it is an error if it already
 directory; presumably you'll do something sensible with them.  A new
 directory $KEYS/keeper/KEEPER is created (it is an error if it already
-exists), containing the public keys I.pub and some metadata meta.
+exists), containing the key store directories and some metadata meta.
 HELP
 HELP
-dohelp
 
 ## Parse the command line.
 
 ## Parse the command line.
-case $# in 2) ;; *) echo >&2 "$usage"; exit 1 ;; esac
-keeper=$1 n=$2
+profile=${keeper_profile-keeper}
+while getopts "p:" opt; do
+  case "$opt" in
+    p) profile=$OPTARG ;;
+    *) usage_err ;;
+  esac
+done
+shift $(( $OPTIND - 1 ))
+case $# in 0 | 1) usage_err ;; esac
+keeper=$1 n=$2; shift 2
 checkword "keeper set label" "$keeper"
 checknumber "set size" "$n"
 checkword "keeper set label" "$keeper"
 checknumber "set size" "$n"
+checkword "profile label" "$profile"
 
 ## Preflight checking.
 if [ -e $KEYS/keeper/$keeper ]; then
 
 ## Preflight checking.
 if [ -e $KEYS/keeper/$keeper ]; then
@@ -55,15 +63,18 @@ if [ -e $keeper ]; then
 fi
 
 ## Generate the private keys, one per file, and compute the public keys.
 fi
 
 ## Generate the private keys, one per file, and compute the public keys.
-tmp=$(mktmp); cleanup rmtmp
+mktmp
 rm -rf $keeper.new
 mkdir -m700 $keeper.new
 mkdir -p -m755 $KEYS/keeper/$keeper.new
 echo $n >$KEYS/keeper/$keeper.new/meta
 i=0
 while [ $i -lt $n ]; do
 rm -rf $keeper.new
 mkdir -m700 $keeper.new
 mkdir -p -m755 $KEYS/keeper/$keeper.new
 echo $n >$KEYS/keeper/$keeper.new/meta
 i=0
 while [ $i -lt $n ]; do
-  ec_keygen $keeper.new/$i $KEYS/keeper/$keeper.new/$i.pub
-  i=$(( i + 1 ))
+  c_gensyskey $profile \
+    $KEYS/keeper/$keeper.new/$i \
+    $keeper.new/$i \
+    keeper="$keeper" seq="$i" tot="$n" "$@"
+  i=$(( $i + 1 ))
 done
 mv $keeper.new $keeper
 mv $KEYS/keeper/$keeper.new $KEYS/keeper/$keeper
 done
 mv $keeper.new $keeper
 mv $KEYS/keeper/$keeper.new $KEYS/keeper/$keeper
similarity index 79%
rename from new-recov
rename to keys.new-recov
index d221764bd3e21775ab33f6a4949697ac322d122c..c5d7fce7da0f5ac417bc4449a1aac842e5c3a0a1 100755 (executable)
--- a/new-recov
@@ -28,7 +28,7 @@ case "${KEYSLIB+t}" in t) ;; *) echo >&2 "$0: KEYSLIB unset"; exit 1 ;; esac
 . "$KEYSLIB"/keyfunc.sh
 
 defhelp <<HELP
 . "$KEYSLIB"/keyfunc.sh
 
 defhelp <<HELP
-RECOV [KEEPER:K ...]
+[-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
 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
@@ -43,18 +43,28 @@ used.
 If there is not a recovery key called RECOV then at least one keeper set must
 be specified.
 HELP
 If there is not a recovery key called RECOV then at least one keeper set must
 be specified.
 HELP
-dohelp
 
 ## Parse the command line.
 
 ## Parse the command line.
-case $# in 0) echo >&2 "$usage"; exit 1 ;; esac
+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"
 recov=$1; shift
 checkword "recovery key label" "$recov"
+checkword "profile label" "$profile"
+nkeep=0
 for k in "$@"; do
   case "$k" in
 for k in "$@"; do
   case "$k" in
+    --) break ;;
     *:*) ;;
     *) echo >&2 "$quis: bad keeper spec \`$k'"; exit 1 ;;
   esac
     *:*) ;;
     *) echo >&2 "$quis: bad keeper spec \`$k'"; exit 1 ;;
   esac
-  keeper=${k%:*} t=${k#*:}
+  keeper=${k%:*} t=${k#*:} nkeep=$(( $nkeep + 1 ))
   checkword "keeper set label" "$keeper"
   checknumber "keeper quorum" "$t"
 done
   checkword "keeper set label" "$keeper"
   checknumber "keeper quorum" "$t"
 done
@@ -62,12 +72,13 @@ done
 ## Establish the keeper parameters.
 rdir=$KEYS/recov/$recov
 if [ ! -d $rdir ]; then mkdir -m755 -p $rdir; fi
 ## Establish the keeper parameters.
 rdir=$KEYS/recov/$recov
 if [ ! -d $rdir ]; then mkdir -m755 -p $rdir; fi
-case $# in
+case $nkeep in
   0)
     kparam=$rdir/keepers
     ;;
   *)
     for k in "$@"; do
   0)
     kparam=$rdir/keepers
     ;;
   *)
     for k in "$@"; do
+      case "$k" in --) break ;; esac
       keeper=${k%:*} t=${k#*:}
       echo $keeper $t
     done >$rdir/keepers.new
       keeper=${k%:*} t=${k#*:}
       echo $keeper $t
     done >$rdir/keepers.new
@@ -77,11 +88,18 @@ esac
 if [ ! -f $kparam ]; then echo >&2 "$quis: no keepers specified"; exit 1; fi
 
 ## Make the new key and issue the shares.
 if [ ! -f $kparam ]; then echo >&2 "$quis: no keepers specified"; exit 1; fi
 
 ## Make the new key and issue the shares.
-tmp=$(mktmp); cleanup rmtmp
+mktmp
 rm -rf $rdir/new
 mkdir -m755 $rdir/new
 cd $tmp
 rm -rf $rdir/new
 mkdir -m755 $rdir/new
 cd $tmp
-ec_keygen secret $rdir/new/pub
+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 | {
 while read keeper k; do
   read n hunoz <$KEYS/keeper/$keeper/meta
   shamir issue $k/$n secret | {
@@ -89,9 +107,10 @@ while read keeper k; do
     echo "$param" >$rdir/new/$keeper.param
     i=0
     while read share; do
     echo "$param" >$rdir/new/$keeper.param
     i=0
     while read share; do
-      echo $share |
-       ec_encrypt $KEYS/keeper/$keeper/$i.pub -o$rdir/new/$keeper.$i.share
-      i=$(( i + 1 ))
+      c_sysencrypt $KEYS/keeper/$keeper/$i >$rdir/new/$keeper.$i.share <<EOF
+$share
+EOF
+      i=$(( $i + 1 ))
     done
   }
 done <$kparam
     done
   }
 done <$kparam
@@ -102,8 +121,8 @@ if [ ! -d $rdir/current ]; then
   seq=0
 else
   seq=$(readlink $rdir/current)
   seq=0
 else
   seq=$(readlink $rdir/current)
-  mem=$(userv root claim-mem-dir </dev/null)
-  reveal=$mem/keys.reveal/$recov.current/secret
+  reqsafe
+  reveal=$SAFE/keys.reveal/$recov.current/secret
   if [ ! -f $reveal ]; then
     echo >&2 "$quis: current $recov key not revealed"
     exit 1
   if [ ! -f $reveal ]; then
     echo >&2 "$quis: current $recov key not revealed"
     exit 1
@@ -112,10 +131,11 @@ else
   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
   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
-    ec_decrypt $reveal -i$rdir/current/$name |
-      ec_encrypt $rdir/new/pub -o$rdir/new/$name
+    c_sysdecrypt $rdir/current/store $reveal <$rdir/current/$name >tmp
+    c_sysencrypt $rdir/new/store <tmp >$rdir/new/$name
+    rm tmp
   done
   done
-  rm -r $mem/keys.reveal/$recov.current
+  rm -r $SAFE/keys.reveal/$recov.current
 fi
 
 ## Tidy up and commit.  Repointing the symlink is grim because, according to
 fi
 
 ## Tidy up and commit.  Repointing the symlink is grim because, according to
@@ -123,7 +143,7 @@ fi
 ## symlink to a directory -- and there's no way of turning this behaviour
 ## off.  The subterfuge here is due to Colin Watson.
 cd $rdir
 ## 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
+while [ -d $seq ]; do seq=$(( $seq + 1 )); done
 case $kparam in *.new) mv keepers.new keepers ;; esac
 rm -f next
 ln -s $seq next
 case $kparam in *.new) mv keepers.new keepers ;; esac
 rm -f next
 ln -s $seq next
old mode 100644 (file)
new mode 100755 (executable)
similarity index 77%
rename from recover
rename to keys.recover
index b4f64d2..2c48be8
--- a/recover
@@ -33,27 +33,14 @@ Recover the secret LABEL using recovery key RECOV.
 
 The recovery key must be revealed.  The secret is written to stdout.
 HELP
 
 The recovery key must be revealed.  The secret is written to stdout.
 HELP
-dohelp
 
 ## Parse the command line.
 
 ## Parse the command line.
-case $# in 2) ;; *) echo >&2 "$usage"; exit 1 ;; esac
+case $# in 2) ;; *) usage_err ;; esac
 recov=$1 label=$2
 checklabel "recovery key label" "$recov"
 checklabel "secret" "$label"
 
 ## Do the recovery.
 recov=$1 label=$2
 checklabel "recovery key label" "$recov"
 checklabel "secret" "$label"
 
 ## Do the recovery.
-blob=$KEYS/recov/$recov/current/$label.recov
-if [ ! -f $blob ]; then
-  echo >&2 "$quis: no recovery blob for secret \`$label'"
-  exit 1
-fi
-mem=$(userv root claim-mem-dir </dev/null)
-reveal=$mem/keys.reveal/$recov.current/secret
-if [ ! -f $reveal ]; then
-  echo >&2 "$quis: current $recov key not revealed"
-  exit 1
-fi
-tmp=$(mktmp); cleanup rmtmp
-ec_decrypt $reveal -i$blob
+recover $recov $label
 
 ###----- That's all, folks --------------------------------------------------
 
 ###----- That's all, folks --------------------------------------------------
similarity index 61%
rename from reveal
rename to keys.reveal
index 8f8e347634a752fe80dc063b29584b76a36690fd..a93ec9050ff6d43e821c8549bc000aed5cee9bdc 100755 (executable)
--- a/reveal
@@ -28,19 +28,18 @@ case "${KEYSLIB+t}" in t) ;; *) echo >&2 "$0: KEYSLIB unset"; exit 1 ;; esac
 . "$KEYSLIB"/keyfunc.sh
 
 defhelp <<HELP
 . "$KEYSLIB"/keyfunc.sh
 
 defhelp <<HELP
-RECOV KEEPER [KEY]
+RECOV KEEPER [NUB]
 Reveal a share of a recovery key distributed among keepers.
 
 If enough shares have been revealed, reconstruct the recovery private key.
 Reveal a share of a recovery key distributed among keepers.
 
 If enough shares have been revealed, reconstruct the recovery private key.
-The key is read from KEY, or stdin if KEY is omitted or \`-'.
+The keeper nub is read from NUB, or stdin if NUB is omitted or \`-'.
 HELP
 HELP
-dohelp
 
 ## Parse the command line.
 case $# in
   2) if [ -t 0 ]; then echo >&2 "$quis: stdin is a terminal"; exit 1; fi ;;
   3) ;;
 
 ## Parse the command line.
 case $# in
   2) if [ -t 0 ]; then echo >&2 "$quis: stdin is a terminal"; exit 1; fi ;;
   3) ;;
-  *) echo >&2 "$usage"; exit 1 ;;
+  *) usage_err ;;
 esac
 recov=$1 keeper=$2; shift 2
 checklabel "recovery key" "$recov"
 esac
 recov=$1 keeper=$2; shift 2
 checklabel "recovery key" "$recov"
@@ -50,13 +49,23 @@ case "$recov" in
 esac
 checkword "keeper set label" "$keeper"
 
 esac
 checkword "keeper set label" "$keeper"
 
+## Check that this is a sensible thing to do.
+if [ ! -f $KEYS/keeper/$keeper/meta ]; then
+  echo >&2 "$quis: unknown keeper set \`$keeper'"
+  exit 1
+fi
+if [ ! -d $KEYS/recov/$recov ]; then
+  echo >&2 "$quis: unknown recovery key \`$recov'"
+  exit 1
+fi
+if [ ! -f $KEYS/recov/$recov/$keeper.param ]; then
+  echo >&2 "$quis: recovery key \`$recov' not kept by keeper set \`$keeper'"
+  exit 1
+fi
+
 ## Grab the key, because we'll need to read it several times.
 ## Grab the key, because we'll need to read it several times.
-tmp=$(mktmp); cleanup rmtmp
-secret=$(cat -- "$@")
-pub=$(ec_public /dev/stdin <<EOF
-$secret
-EOF
-)
+mktmp
+cat -- "$@" >$tmp/secret
 
 ## Read the threshold from the recovery metadata.
 read param <$KEYS/recov/$recov/$keeper.param
 
 ## Read the threshold from the recovery metadata.
 read param <$KEYS/recov/$recov/$keeper.param
@@ -82,62 +91,62 @@ t=${t%%;*}
 read n hunoz <$KEYS/keeper/$keeper/meta
 i=0
 foundp=nil
 read n hunoz <$KEYS/keeper/$keeper/meta
 i=0
 foundp=nil
-: "$pub"
 while [ $i -lt $n ]; do
 while [ $i -lt $n ]; do
-  read cand <$KEYS/keeper/$keeper/$i.pub
-  : "$cand"
-  case "$pub" in "$cand") foundp=t; break ;; esac
-  i=$(( i + 1 ))
+  c_sysprepare $KEYS/keeper/$keeper/$i
+  nubbin=$(nubid <$tmp/secret)
+  nubid=$(cat $KEYS/keeper/$keeper/$i/nubid)
+  case "$nubbin" in "$nubid") foundp=t; break ;; esac
+  i=$(( $i + 1 ))
 done
 case $foundp in
 done
 case $foundp in
-  nil) echo >&2 "$quis: key doesn't match keeper \`$keeper'"; exit 1 ;;
+  nil) echo >&2 "$quis: nub doesn't match keeper \`$keeper'"; exit 1 ;;
 esac
 
 ## Establish the recovery staging area.  See whether we've done enough
 ## already.
 esac
 
 ## Establish the recovery staging area.  See whether we've done enough
 ## already.
-mem=$(userv root claim-mem-dir </dev/null)
+reqsafe
 tag=$(echo $recov | tr / .)
 tag=$(echo $recov | tr / .)
-mkdir -p -m700 $mem/keys.reveal
-reveal=$mem/keys.reveal/$tag
+mkdir -p -m700 $SAFE/keys.reveal
+reveal=$SAFE/keys.reveal/$tag
 if [ ! -d $reveal ]; then mkdir -m700 $reveal; fi
 cd $reveal
 if [ ! -d $reveal ]; then mkdir -m700 $reveal; fi
 cd $reveal
-if [ -f secret ]; then
-  echo >&2 "$quis: secret $recov already revealed"
-  exit 1
-fi
-if [ -f $keeper.$i ]; then
-  echo >&2 "$quis: share $i already revealed"
+if [ -f nub ]; then
+  echo >&2 "$quis: recovery key \`$recov' already revealed"
   exit 1
 fi
 
 ## Decrypt the share.
 umask 077
   exit 1
 fi
 
 ## Decrypt the share.
 umask 077
-ec_decrypt /dev/stdin \
-  -i$KEYS/recov/$recov/$keeper.$i.share \
-  -o$keeper.$i.new <<EOF
-$secret
-EOF
-mv $keeper.$i.new $keeper.$i
+if [ -f $keeper.$i.share ]; then
+  echo >&2 "$quis: share $i already revealed"
+else
+  c_sysdecrypt $KEYS/keeper/$keeper/$i $tmp/secret \
+    <$KEYS/recov/$recov/$keeper.$i.share \
+    >$keeper.$i.new
+  mv $keeper.$i.new $keeper.$i.share
+fi
 
 ## See if there's enough for a recovery.
 n=0
 
 ## See if there's enough for a recovery.
 n=0
-for j in $keeper.*; do if [ -f "$j" ]; then n=$(( n + 1 )); fi; done
+for j in $keeper.*.share; do if [ -f "$j" ]; then n=$(( $n + 1 )); fi; done
 if [ $n -lt $t ]; then
 if [ $n -lt $t ]; then
-  echo >&2 "$quis: share $i revealed; $(( t - n )) more required"
+  echo >&2 "$quis: share $i revealed; $(( $t - $n )) more required"
 else
 else
-  cat $KEYS/recov/$recov/$keeper.param $keeper.* >$keeper.shares
-  shamir recover <$keeper.shares >secret.new
-  pubx=$(ec_public secret.new)
-  puby=$(cat $KEYS/recov/$recov/pub)
-  case "$pubx" in
-    "$puby") ;;
+  cat $KEYS/recov/$recov/$keeper.param $keeper.*.share >$keeper.shares
+  shamir recover <$keeper.shares >nub.new
+  c_sysprepare $KEYS/recov/$recov/store
+  nubbin=$(nubid <nub.new)
+  nubid=$(cat $KEYS/recov/$recov/store/nubid)
+  case "$nubbin" in
+    "$nubid") ;;
     *)
     *)
-      echo >&2 "quis: recovered secret key doesn't match public key"
+      echo >&2 "$quis: recovered nub doesn't match stored hash"
       exit 1
       ;;
   esac
       exit 1
       ;;
   esac
-  mv secret.new secret
-  echo >&2 "$quis: secret $recov revealed"
+  mv nub.new nub
+  rm -f $keeper.*
+  echo >&2 "$quis: recovery key \`$recov' revealed"
 fi
 
 ###----- That's all, folks --------------------------------------------------
 fi
 
 ###----- That's all, folks --------------------------------------------------
old mode 100644 (file)
new mode 100755 (executable)
similarity index 88%
rename from stash
rename to keys.stash
index 98116b5..59eeabf
--- a/stash
@@ -35,24 +35,20 @@ The LABEL is used to identify the encrypted secret later to the \`recover'
 command.  The secret is read from SECRET, or stdin if SECRET is omitted or
 \`-'.
 HELP
 command.  The secret is read from SECRET, or stdin if SECRET is omitted or
 \`-'.
 HELP
-dohelp
 
 ## Parse the command line.
 case $# in
   2) if [ -t 0 ]; then echo >&2 "$quis: stdin is a terminal"; exit 1; fi ;;
   3) ;;
 
 ## Parse the command line.
 case $# in
   2) if [ -t 0 ]; then echo >&2 "$quis: stdin is a terminal"; exit 1; fi ;;
   3) ;;
-  *) echo >&2 "$usage"; exit 1 ;;
+  *) usage_err ;;
 esac
 recov=$1 label=$2; shift 2
 checkword "recovery key label" "$recov"
 checklabel "secret" "$label"
 
 ## Do the thing.
 esac
 recov=$1 label=$2; shift 2
 checkword "recovery key label" "$recov"
 checklabel "secret" "$label"
 
 ## Do the thing.
-tmp=$(mktmp); cleanup rmtmp
+mktmp
 cat -- "$@" >$tmp/secret
 cat -- "$@" >$tmp/secret
-cd $KEYS/recov/$recov/current
-case $label in */*) mkdir -m755 -p ${label%/*} ;; esac
-ec_encrypt pub -i$tmp/secret -o$label.new
-mv $label.new $label.recov
+stash $recov $label <$tmp/secret
 
 ###----- That's all, folks --------------------------------------------------
 
 ###----- That's all, folks --------------------------------------------------
diff --git a/ktype.gnupg b/ktype.gnupg
new file mode 100644 (file)
index 0000000..8a8e764
--- /dev/null
@@ -0,0 +1,150 @@
+### -*-sh-*-
+###
+### Key type for GNU Privacy Guard
+###
+### (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.
+
+run_gnupg () {
+  base=$1; shift
+  ## Run GnuPG with some standard options.
+
+  gpg --homedir="$base" --no-permission-warning -q --batch \
+    --always-trust \
+    "$@"
+}
+
+defprops k_props <<EOF
+main_type              t       $R_WORD
+main_length            t       $R_NUMERIC
+sub_type               t       $R_WORD
+sub_length             t       $R_NUMERIC
+s2k_cipher             t       $R_WORD
+s2k_digest             t       $R_WORD
+cipher_prefs           t       $R_WORDSEQ
+digest_prefs           t       $R_WORDSEQ
+compress_prefs         t       $R_WORDSEQ
+realname               t       $R_LINE
+comment                        t       $R_LINE
+email                  t       $R_LINE
+EOF
+
+: ${kprop_main_type=RSA} ${kprop_main_length=3072}
+: ${kprop_sub_type=ELG-E} ${kprop_sub_length=3072}
+: ${kprop_cipher_prefs=AES256 AES TWOFISH 3DES BLOWFISH CAST5}
+: ${kprop_digest_prefs=SHA256 SHA1 RIPEMD160}
+: ${kprop_compress_prefs=ZLIB ZIP}
+
+: ${kprop_realname=%{realname\}} ${kprop_email=%{email\}}
+: ${kprop_comment=%{comment-nil\}}
+
+k_generate () {
+  base=$1 nub=$2
+
+  makenub >"$nub"
+  prefs="$kprop_cipher_prefs $kprop_digest_prefs $kprop_compress_prefs"
+
+  case ${kprop_s2k_cipher+t} in
+    t) ;;
+    *) set -- $kprop_cipher_prefs; kprop_s2k_cipher=$1 ;;
+  esac
+  case ${kprop_s2k_digest+t} in
+    t) ;;
+    *) set -- $kprop_digest_prefs; kprop_s2k_digest=$1 ;;
+  esac
+
+  cat >"$base/gpg.conf" <<EOF
+### GnuPG configuration
+
+## Annoying copyright notice and other tedious warnings.
+no-greeting
+expert
+always-trust
+
+## Algorithm selection
+s2k-cipher-algo $kprop_s2k_cipher
+s2k-digest-algo $kprop_s2k_digest
+personal-cipher-preferences $kprop_cipher_prefs
+personal-digest-preferences $kprop_digest_prefs
+personal-compress-preferences $kprop_compress_prefs
+default-preference-list $prefs
+EOF
+
+  { cat <<EOF
+Key-Type: $kprop_main_type
+Key-Length: $kprop_main_length
+Passphrase: $(cat "$nub")
+EOF
+    case ${kprop_sub_type-nil} in
+      nil) ;;
+      *) cat <<EOF
+Subkey-Type: $kprop_sub_type
+Subkey-Length: $kprop_sub_length
+EOF
+    esac
+    real=$(subst "\`realname' value" "$kprop_realname" kopt_ "$R_LINE")
+    email=$(subst "\`email' value" "$kprop_email" kopt_ "$R_LINE")
+    cat <<EOF
+Name-Real: $real
+Name-Email: $email
+EOF
+    comment=$(subst "\`comment' value" "$kprop_comment" kopt_ "$R_LINE")
+    case "$comment" in
+      ?*) cat <<EOF
+Name-Comment: $comment
+EOF
+       ;;
+    esac
+  } | run_gnupg "$base" --gen-key
+
+  ## Commit the new key.
+  run_gnupg "$base" --fingerprint --with-colons | \
+    grep '^fpr:' | cut -d: -f10 >"$base/fpr"
+  run_gnupg "$base" --export --armor --output="$base/pub"
+}
+
+k_encrypt () {
+  base=$1
+  run_gnupg "$base" --encrypt --armor --recipient=$(cat "$base/fpr")
+}
+
+k_decrypt () {
+  base=$1 nub=$2
+  run_gnupg "$base" --passphrase-file "$nub" --decrypt
+}
+
+k_sign () {
+  base=$1 nub=$2
+  run_gnupg "$base" --passphrase-file "$nub" --detach-sign --armor
+}
+
+k_verify () {
+  base=$1 sig=$3
+  echo "$sig" >$tmp/sig
+  if run_gnupg "$base" --verify $tmp/sig - >/dev/null 2>$tmp/err
+  then :; else
+    rc=$?
+    cat >&2 $tmp/err
+    return $rc
+  fi
+}
+
+###----- That's all, folks --------------------------------------------------
diff --git a/ktype.seccure b/ktype.seccure
new file mode 100644 (file)
index 0000000..3b716d2
--- /dev/null
@@ -0,0 +1,99 @@
+### -*-sh-*-
+###
+### Key type for B. Poettering's `Seccure' suite
+###
+### (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.
+
+###--------------------------------------------------------------------------
+### Utility functions.
+
+run_seccure () {
+  op=$1; shift
+  ## run_seccure OP ARG ...
+  ##
+  ## Run a Seccure program, ensuring that its stderr is reported if it had
+  ## anything very interesting to say, but suppressed if it was boring.
+
+  set +e; seccure-$op "$@" 2>$tmp/seccure.out; rc=$?; set -e
+  grep -v '^WARNING: Cannot obtain memory lock' $tmp/seccure.out >&2 || :
+  return $rc
+}
+
+###--------------------------------------------------------------------------
+### Key type definition.
+
+defprops k_props <<EOF
+curve                  t       $R_LABEL
+tagsz                  t       $R_NUMERIC
+EOF
+
+: ${kprop_curve=p256}
+: ${kprop_tagsz=128}
+
+k_public () {
+  nub=$2
+  run_seccure key -q -c$kprop_curve -F"$nub"
+}
+
+k_generate () {
+  base=$1 nub=$2
+  makenub >"$nub"
+  k_public "$base" "$nub" >"$base/pub"
+}
+
+k_check () {
+  base=$1 nub=$2
+  this=$(k_public "$base" "$nub")
+  orig=$(cat "$base/pub")
+  case "$orig" in "$this") return 0 ;; *) return 1 ;; esac
+}
+
+k_encrypt () {
+  base=$1
+  run_seccure encrypt -q -c$kprop_curve -m$kprop_tagsz -- $(cat "$base/pub")
+}
+
+k_decrypt () {
+  nub=$2
+  if ! run_seccure decrypt -q -c$kprop_curve -m$kprop_tagsz -F"$nub"; then
+    echo >&2 "$quis: decryption failed"
+    return 1
+  fi
+}
+
+k_sign () {
+  nub=$2
+  run_seccure sign -q -c$kprop_curve -F"$nub" -s/dev/stdout
+}
+
+k_verify () {
+  base=$1 sig=$3
+  if run_seccure verify -q -c$kprop_curve -- \
+    $(cat "$base/pub") "$sig"
+  then :; else
+    rc=$?
+    echo >&2 "$quis: signature verification failed"
+    return $rc
+  fi
+}
+
+###----- That's all, folks --------------------------------------------------