1#!/usr/bin/env bash
2
3# monkeysphere: Monkeysphere client tool
4#
5# The monkeysphere scripts are written by:
6# Jameson Rollins <jrollins@finestructure.net>
7# Jamie McClelland <jm@mayfirst.org>
8# Daniel Kahn Gillmor <dkg@fifthhorseman.net>
9# Micah Anderson <micah@riseup.net>
10#
11# They are Copyright 2008-2009, and are all released under the GPL, version 3
12# or later.
13
14########################################################################
15set -e
16
17PGRM=$(basename $0)
18
19SYSSHAREDIR=${MONKEYSPHERE_SYSSHAREDIR:-"__SYSSHAREDIR_PREFIX__/share/monkeysphere"}
20export SYSSHAREDIR
21. "${SYSSHAREDIR}/defaultenv"
22. "${SYSSHAREDIR}/common"
23
24# sharedir for host functions
25MSHAREDIR="${SYSSHAREDIR}/m"
26
27# UTC date in ISO 8601 format if needed
28DATE=$(date -u '+%FT%T')
29
30# unset some environment variables that could screw things up
31unset GREP_OPTIONS
32
33# set the file creation mask to be only owner rw
34umask 077
35
36########################################################################
37# FUNCTIONS
38########################################################################
39
40usage() {
41    cat <<EOF >&2
42usage: $PGRM <subcommand> [options] [args]
43Monkeysphere client tool.
44
45subcommands:
46 update-known_hosts (k) [HOST]...    update known_hosts file
47 update-authorized_keys (a)          update authorized_keys file
48 ssh-proxycommand HOST [PORT]        monkeysphere ssh ProxyCommand
49   --no-connect                        do not make TCP connection to host
50 subkey-to-ssh-agent (s)             store authentication subkey in ssh-agent
51
52 keys-for-userid (u) USERID          output valid ssh keys for given user id
53 sshfprs-for-userid USERID           output ssh fingerprints for given user id
54 gen-subkey (g) [KEYID]              generate an authentication subkey
55   --length (-l) BITS                  key length in bits (2048)
56
57 version (v)                         show version number
58 help (h,?)                          this help
59
60EOF
61}
62
63# user gpg command to define common options
64gpg_user() {
65    LC_ALL=C "${GPG:-gpg2}" --fixed-list-mode --no-greeting --quiet --no-tty "$@"
66}
67
68# output the ssh fingerprint of a gpg key
69gpg_ssh_fingerprint() {
70    keyid="$1"
71    gpg_user --export "$keyid" --no-armor | "$SYSSHAREDIR/keytrans" openpgp2sshfpr "$keyid"
72}
73
74# take a secret key ID and check that only zero or one ID is provided,
75# and that it corresponds to only a single secret key ID
76check_gpg_sec_key_id() {
77    local gpgSecOut
78
79    case "$#" in
80	0)
81	    gpgSecOut=$(gpg_user --list-secret-keys --with-colons 2>/dev/null | egrep '^sec:')
82	    ;;
83	1)
84	    gpgSecOut=$(gpg_user --list-secret-keys --with-colons "$1" | egrep '^sec:') || failure
85	    ;;
86	*)
87	    failure "You must specify only a single primary key ID."
88	    ;;
89    esac
90
91    # check that only a single secret key was found
92    case $(echo "$gpgSecOut" | grep -c '^sec:') in
93	0)
94	    failure "No secret keys found.  Create an OpenPGP key with the following command:
95 gpg2 --gen-key"
96	    ;;
97	1)
98	    echo "$gpgSecOut" | cut -d: -f5
99	    ;;
100	*)
101	    local seckeys=$(echo "$gpgSecOut" | cut -d: -f5)
102	    failure "Multiple primary secret keys found:
103$seckeys
104Please specify which primary key to use."
105	    ;;
106    esac
107}
108
109# check that a valid authentication subkey does not already exist
110check_gpg_authentication_subkey() {
111    local keyID
112    local IFS
113    local line
114    local type
115    local validity
116    local usage
117
118    keyID="$1"
119
120    # check that a valid authentication key does not already exist
121    IFS=$'\n'
122    for line in $(gpg_user --list-keys --with-colons "$keyID") ; do
123	type=$(echo "$line" | cut -d: -f1)
124	validity=$(echo "$line" | cut -d: -f2)
125	usage=$(echo "$line" | cut -d: -f12)
126
127	# look at keys only
128	if [ "$type" != 'pub' -a "$type" != 'sub' ] ; then
129	    continue
130	fi
131	# check for authentication capability
132	if ! check_capability "$usage" 'a' ; then
133	    continue
134	fi
135	# if authentication key is valid, prompt to continue
136	if [ "$validity" = 'u' ] ; then
137	    echo "A valid authentication key already exists for primary key '$keyID'." 1>&2
138	    if [ "$PROMPT" != "false" ] ; then
139		printf "Are you sure you would like to generate another one? (y/N) " >&2
140		read OK; OK=${OK:N}
141		if [ "${OK/y/Y}" != 'Y' ] ; then
142		    failure "aborting."
143		fi
144		break
145	    else
146		failure "aborting."
147	    fi
148	fi
149    done
150}
151
152########################################################################
153# MAIN
154########################################################################
155
156# set unset default variables
157GNUPGHOME=${GNUPGHOME:="${HOME}/.gnupg"}
158KNOWN_HOSTS="${HOME}/.ssh/known_hosts"
159HASH_KNOWN_HOSTS="false"
160AUTHORIZED_KEYS="${HOME}/.ssh/authorized_keys"
161
162# unset the check keyserver variable, since that needs to have
163# different defaults for the different functions
164unset CHECK_KEYSERVER
165
166# load global config
167[ -r "${SYSCONFIGDIR}/monkeysphere.conf" ] \
168    && . "${SYSCONFIGDIR}/monkeysphere.conf"
169
170# set monkeysphere home directory
171MONKEYSPHERE_HOME=${MONKEYSPHERE_HOME:="${HOME}/.monkeysphere"}
172mkdir -p -m 0700 "$MONKEYSPHERE_HOME"
173
174# load local config
175[ -e ${MONKEYSPHERE_CONFIG:="${MONKEYSPHERE_HOME}/monkeysphere.conf"} ] \
176    && . "$MONKEYSPHERE_CONFIG"
177
178# set empty config variables with ones from the environment
179GNUPGHOME=${MONKEYSPHERE_GNUPGHOME:=$GNUPGHOME}
180LOG_LEVEL=${MONKEYSPHERE_LOG_LEVEL:=$LOG_LEVEL}
181KEYSERVER=${MONKEYSPHERE_KEYSERVER:=$KEYSERVER}
182# if keyserver not specified in env or conf, then look in gpg.conf
183if [ -z "$KEYSERVER" ] ; then
184    if [ -f "${GNUPGHOME}/gpg.conf" ] ; then
185	KEYSERVER=$(grep -e "^[[:space:]]*keyserver " "${GNUPGHOME}/gpg.conf" | tail -1 | awk '{ print $2 }')
186    fi
187fi
188PROMPT=${MONKEYSPHERE_PROMPT:=$PROMPT}
189KNOWN_HOSTS=${MONKEYSPHERE_KNOWN_HOSTS:=$KNOWN_HOSTS}
190HASH_KNOWN_HOSTS=${MONKEYSPHERE_HASH_KNOWN_HOSTS:=$HASH_KNOWN_HOSTS}
191AUTHORIZED_KEYS=${MONKEYSPHERE_AUTHORIZED_KEYS:=$AUTHORIZED_KEYS}
192STRICT_MODES=${MONKEYSPHERE_STRICT_MODES:=$STRICT_MODES}
193
194# other variables not in config file
195AUTHORIZED_USER_IDS=${MONKEYSPHERE_AUTHORIZED_USER_IDS:="${MONKEYSPHERE_HOME}/authorized_user_ids"}
196REQUIRED_HOST_KEY_CAPABILITY=${MONKEYSPHERE_REQUIRED_HOST_KEY_CAPABILITY:="a"}
197REQUIRED_USER_KEY_CAPABILITY=${MONKEYSPHERE_REQUIRED_USER_KEY_CAPABILITY:="a"}
198# note that only using '=' instead of ':=' tests only if the variable
199# in unset, not if it's "null"
200LOG_PREFIX=${MONKEYSPHERE_LOG_PREFIX='ms: '}
201
202# export GNUPGHOME and make sure gpg home exists with proper
203# permissions
204export GNUPGHOME
205mkdir -p -m 0700 "$GNUPGHOME"
206export LOG_LEVEL
207export LOG_PREFIX
208
209if [ "$#" -eq 0 ] ; then
210    usage
211    failure "Please supply a subcommand."
212fi
213
214# get subcommand
215COMMAND="$1"
216shift
217
218case $COMMAND in
219    'update-known_hosts'|'update-known-hosts'|'k')
220	# whether or not to check keyservers
221	CHECK_KEYSERVER=${MONKEYSPHERE_CHECK_KEYSERVER:=${CHECK_KEYSERVER:="true"}}
222
223	source "${MSHAREDIR}/update_known_hosts"
224
225	# if hosts are specified on the command line, process just
226	# those hosts
227	if [ "$1" ] ; then
228	    update_known_hosts "$@"
229
230	# otherwise, if no hosts are specified, process every host
231	# in the user's known_hosts file
232	else
233	    process_known_hosts
234	fi
235	;;
236
237    'update-authorized_keys'|'update-authorized-keys'|'a')
238	# whether or not to check keyservers
239	CHECK_KEYSERVER=${MONKEYSPHERE_CHECK_KEYSERVER:=${CHECK_KEYSERVER:="true"}}
240	source "${MSHAREDIR}/update_authorized_keys"
241	update_authorized_keys
242	;;
243
244    'import-subkey'|'import'|'i')
245	source "${MSHAREDIR}/import_subkey"
246	import_subkey "$@"
247	;;
248
249    'gen-subkey'|'g')
250	source "${MSHAREDIR}/gen_subkey"
251	gen_subkey "$@"
252	;;
253
254    'ssh-proxycommand'|'p')
255	source "${MSHAREDIR}/ssh_proxycommand"
256	ssh_proxycommand "$@"
257	;;
258
259    'subkey-to-ssh-agent'|'s')
260	source "${MSHAREDIR}/subkey_to_ssh_agent"
261	subkey_to_ssh_agent "$@"
262	;;
263
264    'sshfpr')
265	echo "Warning: 'sshfpr' is deprecated.  Please use 'sshfprs-for-userid' instead." >&2
266	gpg_ssh_fingerprint "$@"
267	;;
268
269    'keys-for-userid'|'u')
270	CHECK_KEYSERVER=${MONKEYSPHERE_CHECK_KEYSERVER:=${CHECK_KEYSERVER:="true"}}
271	source "${MSHAREDIR}/keys_for_userid"
272	keys_for_userid "$@"
273	;;
274
275    'sshfprs-for-userid')
276	CHECK_KEYSERVER=${MONKEYSPHERE_CHECK_KEYSERVER:=${CHECK_KEYSERVER:="true"}}
277	source "${MSHAREDIR}/keys_for_userid"
278	keys_for_userid "$@" | "$SYSSHAREDIR/keytrans" sshfpr
279	;;
280
281    'keys-from-userid')
282	echo "Warning: 'keys-from-userid' is deprecated.  Please use 'keys-for-userid' instead." >&2
283	CHECK_KEYSERVER=${MONKEYSPHERE_CHECK_KEYSERVER:=${CHECK_KEYSERVER:="true"}}
284	source "${MSHAREDIR}/keys_for_userid"
285	keys_for_userid "$@"
286	;;
287
288    'version'|'--version'|'v')
289	version
290	;;
291
292    'help'|'--help'|'-h'|'h'|'?')
293        usage
294        ;;
295
296    *)
297        failure "Unknown command: '$COMMAND'
298Try '$PGRM help' for usage."
299        ;;
300esac
301