summaryrefslogtreecommitdiff
path: root/src/monkeysphere-server
blob: b6bf78b6e64d074cc74e71d14e62ac7de9439598 (plain)
  1. #!/usr/bin/env bash
  2. # monkeysphere-server: MonkeySphere server admin tool
  3. #
  4. # The monkeysphere scripts are written by:
  5. # Jameson Rollins <jrollins@fifthhorseman.net>
  6. # Jamie McClelland <jm@mayfirst.org>
  7. # Daniel Kahn Gillmor <dkg@fifthhorseman.net>
  8. #
  9. # They are Copyright 2008, and are all released under the GPL, version 3
  10. # or later.
  11. ########################################################################
  12. PGRM=$(basename $0)
  13. SYSSHAREDIR=${MONKEYSPHERE_SYSSHAREDIR:-"/usr/share/monkeysphere"}
  14. export SYSSHAREDIR
  15. . "${SYSSHAREDIR}/common" || exit 1
  16. SYSDATADIR=${MONKEYSPHERE_SYSDATADIR:-"/var/lib/monkeysphere"}
  17. export SYSDATADIR
  18. # UTC date in ISO 8601 format if needed
  19. DATE=$(date -u '+%FT%T')
  20. # unset some environment variables that could screw things up
  21. unset GREP_OPTIONS
  22. # default return code
  23. RETURN=0
  24. ########################################################################
  25. # FUNCTIONS
  26. ########################################################################
  27. usage() {
  28. cat <<EOF >&2
  29. usage: $PGRM <subcommand> [options] [args]
  30. Monkeysphere server admin tool.
  31. subcommands:
  32. update-users (u) [USER]... update user authorized_keys files
  33. gen-key (g) [NAME[:PORT]] generate gpg key for the server
  34. --length (-l) BITS key length in bits (2048)
  35. --expire (-e) EXPIRE date to expire
  36. --revoker (-r) FINGERPRINT add a revoker
  37. extend-key (e) EXPIRE extend expiration to EXPIRE
  38. add-hostname (n+) NAME[:PORT] add hostname user ID to server key
  39. revoke-hostname (n-) NAME[:PORT] revoke hostname user ID
  40. show-key (s) output all server host key information
  41. publish-key (p) publish server host key to keyserver
  42. diagnostics (d) report on server monkeysphere status
  43. add-id-certifier (c+) KEYID import and tsign a certification key
  44. --domain (-n) DOMAIN limit ID certifications to DOMAIN
  45. --trust (-t) TRUST trust level of certifier (full)
  46. --depth (-d) DEPTH trust depth for certifier (1)
  47. remove-id-certifier (c-) KEYID remove a certification key
  48. list-id-certifiers (c) list certification keys
  49. gpg-authentication-cmd CMD gnupg-authentication command
  50. help (h,?) this help
  51. EOF
  52. }
  53. # function to run command as monkeysphere user
  54. su_monkeysphere_user() {
  55. # if the current user is the monkeysphere user, then just eval
  56. # command
  57. if [ $(id -un) = "$MONKEYSPHERE_USER" ] ; then
  58. eval "$@"
  59. # otherwise su command as monkeysphere user
  60. else
  61. su "$MONKEYSPHERE_USER" -c "$@"
  62. fi
  63. }
  64. # function to interact with the host gnupg keyring
  65. gpg_host() {
  66. local returnCode
  67. GNUPGHOME="$GNUPGHOME_HOST"
  68. export GNUPGHOME
  69. # NOTE: we supress this warning because we need the monkeysphere
  70. # user to be able to read the host pubring. we realize this might
  71. # be problematic, but it's the simplest solution, without too much
  72. # loss of security.
  73. gpg --no-permission-warning "$@"
  74. returnCode="$?"
  75. # always reset the permissions on the host pubring so that the
  76. # monkeysphere user can read the trust signatures
  77. chgrp "$MONKEYSPHERE_USER" "${GNUPGHOME_HOST}/pubring.gpg"
  78. chmod g+r "${GNUPGHOME_HOST}/pubring.gpg"
  79. return "$returnCode"
  80. }
  81. # function to interact with the authentication gnupg keyring
  82. # FIXME: this function requires basically accepts only a single
  83. # argument because of problems with quote expansion. this needs to be
  84. # fixed/improved.
  85. gpg_authentication() {
  86. GNUPGHOME="$GNUPGHOME_AUTHENTICATION"
  87. export GNUPGHOME
  88. su_monkeysphere_user "gpg $@"
  89. }
  90. # output just key fingerprint
  91. fingerprint_server_key() {
  92. gpg_host --list-secret-keys --fingerprint \
  93. --with-colons --fixed-list-mode 2> /dev/null | \
  94. grep '^fpr:' | head -1 | cut -d: -f10
  95. }
  96. # output key information
  97. show_server_key() {
  98. local fingerprint
  99. local tmpkey
  100. fingerprint=$(fingerprint_server_key)
  101. gpg_authentication "--fingerprint --list-key --list-options show-unusable-uids $fingerprint"
  102. # dumping to a file named ' ' so that the ssh-keygen output
  103. # doesn't claim any potentially bogus hostname(s):
  104. tmpkey=$(mktemp -d ${TMPDIR:-/tmp}/tmp.XXXXXXXXXX)
  105. gpg_authentication "--export $fingerprint" | openpgp2ssh "$fingerprint" 2>/dev/null > "$tmpkey/ "
  106. echo -n "ssh fingerprint: "
  107. (cd "$tmpkey" && ssh-keygen -l -f ' ' | awk '{ print $2 }')
  108. rm -rf "$tmpkey"
  109. echo -n "OpenPGP fingerprint: "
  110. echo "$fingerprint"
  111. }
  112. # update authorized_keys for users
  113. update_users() {
  114. if [ "$1" ] ; then
  115. # get users from command line
  116. unames="$@"
  117. else
  118. # or just look at all users if none specified
  119. unames=$(getent passwd | cut -d: -f1)
  120. fi
  121. # set mode
  122. MODE="authorized_keys"
  123. # set gnupg home
  124. GNUPGHOME="$GNUPGHOME_AUTHENTICATION"
  125. # check to see if the gpg trust database has been initialized
  126. if [ ! -s "${GNUPGHOME}/trustdb.gpg" ] ; then
  127. failure "GNUPG trust database uninitialized. Please see MONKEYSPHERE-SERVER(8)."
  128. fi
  129. # make sure the authorized_keys directory exists
  130. mkdir -p "${SYSDATADIR}/authorized_keys"
  131. # loop over users
  132. for uname in $unames ; do
  133. # check all specified users exist
  134. if ! getent passwd "$uname" >/dev/null ; then
  135. log error "----- unknown user '$uname' -----"
  136. continue
  137. fi
  138. log verbose "----- user: $uname -----"
  139. # make temporary directory
  140. TMPLOC=$(mktemp -d ${TMPDIR:-/tmp}/tmp.XXXXXXXXXX)
  141. # trap to delete temporary directory on exit
  142. trap "rm -rf $TMPLOC" EXIT
  143. # create temporary authorized_user_ids file
  144. TMP_AUTHORIZED_USER_IDS="${TMPLOC}/authorized_user_ids"
  145. touch "$TMP_AUTHORIZED_USER_IDS"
  146. # create temporary authorized_keys file
  147. AUTHORIZED_KEYS="${TMPLOC}/authorized_keys"
  148. touch "$AUTHORIZED_KEYS"
  149. # set restrictive permissions on the temporary files
  150. # FIXME: is there a better way to do this?
  151. chmod 0700 "$TMPLOC"
  152. chmod 0600 "$AUTHORIZED_KEYS"
  153. chmod 0600 "$TMP_AUTHORIZED_USER_IDS"
  154. chown -R "$MONKEYSPHERE_USER" "$TMPLOC"
  155. # process authorized_user_ids file
  156. log debug "checking for authorized_user_ids..."
  157. # translating ssh-style path variables
  158. authorizedUserIDs=$(translate_ssh_variables "$uname" "$AUTHORIZED_USER_IDS")
  159. if [ -s "$authorizedUserIDs" ] ; then
  160. # check permissions on the authorized_user_ids file path
  161. if check_key_file_permissions "$uname" "$authorizedUserIDs" ; then
  162. # copy user authorized_user_ids file to temporary
  163. # location
  164. cat "$authorizedUserIDs" > "$TMP_AUTHORIZED_USER_IDS"
  165. # export needed variables
  166. export AUTHORIZED_KEYS
  167. export TMP_AUTHORIZED_USER_IDS
  168. # process authorized_user_ids file, as monkeysphere
  169. # user
  170. su_monkeysphere_user \
  171. ". ${SYSSHAREDIR}/common; process_authorized_user_ids $TMP_AUTHORIZED_USER_IDS"
  172. RETURN="$?"
  173. else
  174. log debug "not processing authorized_user_ids."
  175. fi
  176. else
  177. log debug "empty or absent authorized_user_ids file."
  178. fi
  179. # add user-controlled authorized_keys file if specified
  180. # translate ssh-style path variables
  181. rawAuthorizedKeys=$(translate_ssh_variables "$uname" "$RAW_AUTHORIZED_KEYS")
  182. if [ "$rawAuthorizedKeys" ] ; then
  183. log debug "checking for raw authorized_keys..."
  184. if [ -s "$rawAuthorizedKeys" ] ; then
  185. # check permissions on the authorized_keys file path
  186. if check_key_file_permissions "$uname" "$rawAuthorizedKeys" ; then
  187. log verbose "adding raw authorized_keys file... "
  188. cat "$rawAuthorizedKeys" >> "$AUTHORIZED_KEYS"
  189. else
  190. log debug "not adding raw authorized_keys file."
  191. fi
  192. else
  193. log debug "empty or absent authorized_keys file."
  194. fi
  195. fi
  196. # move the new authorized_keys file into place
  197. if [ -s "$AUTHORIZED_KEYS" ] ; then
  198. # openssh appears to check the contents of the
  199. # authorized_keys file as the user in question, so the
  200. # file must be readable by that user at least.
  201. # FIXME: is there a better way to do this?
  202. chown root "$AUTHORIZED_KEYS"
  203. chgrp $(getent passwd "$uname" | cut -f4 -d:) "$AUTHORIZED_KEYS"
  204. chmod g+r "$AUTHORIZED_KEYS"
  205. mv -f "$AUTHORIZED_KEYS" "${SYSDATADIR}/authorized_keys/${uname}"
  206. else
  207. rm -f "${SYSDATADIR}/authorized_keys/${uname}"
  208. fi
  209. # unset the trap
  210. trap - EXIT
  211. # destroy temporary directory
  212. rm -rf "$TMPLOC"
  213. done
  214. }
  215. # generate server gpg key
  216. gen_key() {
  217. local keyType
  218. local keyLength
  219. local keyUsage
  220. local keyExpire
  221. local revoker
  222. local hostName
  223. local userID
  224. local keyParameters
  225. local fingerprint
  226. # set default key parameter values
  227. keyType="RSA"
  228. keyLength="2048"
  229. keyUsage="auth"
  230. keyExpire=
  231. revoker=
  232. # get options
  233. TEMP=$(PATH="/usr/local/bin:$PATH" getopt -o e:l:r -l expire:,length:,revoker: -n "$PGRM" -- "$@") || failure "getopt failed! Does your getopt support GNU-style long options?"
  234. if [ $? != 0 ] ; then
  235. exit 1
  236. fi
  237. # Note the quotes around `$TEMP': they are essential!
  238. eval set -- "$TEMP"
  239. while true ; do
  240. case "$1" in
  241. -l|--length)
  242. keyLength="$2"
  243. shift 2
  244. ;;
  245. -e|--expire)
  246. keyExpire="$2"
  247. shift 2
  248. ;;
  249. -r|--revoker)
  250. revoker="$2"
  251. shift 2
  252. ;;
  253. --)
  254. shift
  255. ;;
  256. *)
  257. break
  258. ;;
  259. esac
  260. done
  261. hostName=${1:-$(hostname -f)}
  262. userID="ssh://${hostName}"
  263. # check for presense of key with user ID
  264. if gpg_host --list-key ="$userID" > /dev/null 2>&1 ; then
  265. failure "Key for '$userID' already exists"
  266. fi
  267. # prompt about key expiration if not specified
  268. keyExpire=$(get_gpg_expiration "$keyExpire")
  269. # set key parameters
  270. keyParameters=$(cat <<EOF
  271. Key-Type: $keyType
  272. Key-Length: $keyLength
  273. Key-Usage: $keyUsage
  274. Name-Real: $userID
  275. Expire-Date: $keyExpire
  276. EOF
  277. )
  278. # add the revoker field if specified
  279. # FIXME: the "1:" below assumes that $REVOKER's key is an RSA key.
  280. # FIXME: key is marked "sensitive"? is this appropriate?
  281. if [ "$revoker" ] ; then
  282. keyParameters="${keyParameters}"$(cat <<EOF
  283. Revoker: 1:$revoker sensitive
  284. EOF
  285. )
  286. fi
  287. echo "The following key parameters will be used for the host private key:"
  288. echo "$keyParameters"
  289. read -p "Generate key? (Y/n) " OK; OK=${OK:=Y}
  290. if [ ${OK/y/Y} != 'Y' ] ; then
  291. failure "aborting."
  292. fi
  293. # add commit command
  294. keyParameters="${keyParameters}"$(cat <<EOF
  295. %commit
  296. %echo done
  297. EOF
  298. )
  299. log verbose "generating server key..."
  300. echo "$keyParameters" | gpg_host --batch --gen-key
  301. # output the server fingerprint
  302. fingerprint_server_key "=${userID}"
  303. # find the key fingerprint of the newly generated key
  304. fingerprint=$(fingerprint_server_key)
  305. # export host ownertrust to authentication keyring
  306. log verbose "setting ultimate owner trust for server key..."
  307. echo "${fingerprint}:6:" | gpg_authentication "--import-ownertrust"
  308. # translate the private key to ssh format, and export to a file
  309. # for sshs usage.
  310. # NOTE: assumes that the primary key is the proper key to use
  311. (umask 077 && \
  312. gpg_host --export-secret-key "$fingerprint" | \
  313. openpgp2ssh "$fingerprint" > "${SYSDATADIR}/ssh_host_rsa_key")
  314. log info "private SSH host key output to file: ${SYSDATADIR}/ssh_host_rsa_key"
  315. }
  316. # extend the lifetime of a host key:
  317. extend_key() {
  318. local fpr=$(fingerprint_server_key)
  319. local extendTo="$1"
  320. if [ -z "$fpr" ] ; then
  321. failure "You don't appear to have a MonkeySphere host key on this server. Try 'monkeysphere-server gen-key' first."
  322. fi
  323. # get the new expiration date
  324. extendTo=$(get_gpg_expiration "$extendTo")
  325. gpg_host --quiet --command-fd 0 --edit-key "$fpr" <<EOF
  326. expire
  327. $extendTo
  328. save
  329. EOF
  330. echo
  331. echo "NOTE: Host key expiration date adjusted, but not yet published."
  332. echo "Run '$PGRM publish-key' to publish the new expiration date."
  333. }
  334. # add hostname user ID to server key
  335. add_hostname() {
  336. local userID
  337. local fingerprint
  338. local tmpuidMatch
  339. local line
  340. local adduidCommand
  341. if [ -z "$1" ] ; then
  342. failure "You must specify a hostname to add."
  343. fi
  344. userID="ssh://${1}"
  345. fingerprint=$(fingerprint_server_key)
  346. # match to only ultimately trusted user IDs
  347. tmpuidMatch="u:$(echo $userID | gpg_escape)"
  348. # find the index of the requsted user ID
  349. # NOTE: this is based on circumstantial evidence that the order of
  350. # this output is the appropriate index
  351. if line=$(gpg_host --list-keys --with-colons --fixed-list-mode "0x${fingerprint}!" \
  352. | egrep '^(uid|uat):' | cut -f2,10 -d: | grep -n -x -F "$tmpuidMatch") ; then
  353. failure "Host userID '$userID' already exists."
  354. fi
  355. echo "The following user ID will be added to the host key:"
  356. echo " $userID"
  357. read -p "Are you sure you would like to add this user ID? (y/N) " OK; OK=${OK:=N}
  358. if [ ${OK/y/Y} != 'Y' ] ; then
  359. failure "User ID not added."
  360. fi
  361. # edit-key script command to add user ID
  362. adduidCommand=$(cat <<EOF
  363. adduid
  364. $userID
  365. save
  366. EOF
  367. )
  368. # execute edit-key script
  369. if echo "$adduidCommand" | \
  370. gpg_host --quiet --command-fd 0 --edit-key "0x${fingerprint}!" ; then
  371. # update the trustdb for the authentication keyring
  372. gpg_authentication "--check-trustdb"
  373. show_server_key
  374. echo
  375. echo "NOTE: User ID added to key, but key not published."
  376. echo "Run '$PGRM publish-key' to publish the new user ID."
  377. else
  378. failure "Problem adding user ID."
  379. fi
  380. }
  381. # revoke hostname user ID to server key
  382. revoke_hostname() {
  383. local userID
  384. local fingerprint
  385. local tmpuidMatch
  386. local line
  387. local uidIndex
  388. local message
  389. local revuidCommand
  390. if [ -z "$1" ] ; then
  391. failure "You must specify a hostname to revoke."
  392. fi
  393. echo "WARNING: There is a known bug in this function."
  394. echo "This function has been known to occasionally revoke the wrong user ID."
  395. echo "Please see the following bug report for more information:"
  396. echo "http://web.monkeysphere.info/bugs/revoke-hostname-revoking-wrong-userid/"
  397. read -p "Are you sure you would like to proceed? (y/N) " OK; OK=${OK:=N}
  398. if [ ${OK/y/Y} != 'Y' ] ; then
  399. failure "aborting."
  400. fi
  401. userID="ssh://${1}"
  402. fingerprint=$(fingerprint_server_key)
  403. # match to only ultimately trusted user IDs
  404. tmpuidMatch="u:$(echo $userID | gpg_escape)"
  405. # find the index of the requsted user ID
  406. # NOTE: this is based on circumstantial evidence that the order of
  407. # this output is the appropriate index
  408. if line=$(gpg_host --list-keys --with-colons --fixed-list-mode "0x${fingerprint}!" \
  409. | egrep '^(uid|uat):' | cut -f2,10 -d: | grep -n -x -F "$tmpuidMatch") ; then
  410. uidIndex=${line%%:*}
  411. else
  412. failure "No non-revoked user ID '$userID' is found."
  413. fi
  414. echo "The following host key user ID will be revoked:"
  415. echo " $userID"
  416. read -p "Are you sure you would like to revoke this user ID? (y/N) " OK; OK=${OK:=N}
  417. if [ ${OK/y/Y} != 'Y' ] ; then
  418. failure "User ID not revoked."
  419. fi
  420. message="Hostname removed by monkeysphere-server $DATE"
  421. # edit-key script command to revoke user ID
  422. revuidCommand=$(cat <<EOF
  423. $uidIndex
  424. revuid
  425. y
  426. 4
  427. $message
  428. y
  429. save
  430. EOF
  431. )
  432. # execute edit-key script
  433. if echo "$revuidCommand" | \
  434. gpg_host --quiet --command-fd 0 --edit-key "0x${fingerprint}!" ; then
  435. # update the trustdb for the authentication keyring
  436. gpg_authentication "--check-trustdb"
  437. show_server_key
  438. echo
  439. echo "NOTE: User ID revoked, but revocation not published."
  440. echo "Run '$PGRM publish-key' to publish the revocation."
  441. else
  442. failure "Problem revoking user ID."
  443. fi
  444. }
  445. # publish server key to keyserver
  446. publish_server_key() {
  447. read -p "Really publish host key to $KEYSERVER? (y/N) " OK; OK=${OK:=N}
  448. if [ ${OK/y/Y} != 'Y' ] ; then
  449. failure "key not published."
  450. fi
  451. # find the key fingerprint
  452. fingerprint=$(fingerprint_server_key)
  453. # publish host key
  454. gpg_authentication "--keyserver $KEYSERVER --send-keys '0x${fingerprint}!'"
  455. }
  456. diagnostics() {
  457. # * check on the status and validity of the key and public certificates
  458. local seckey
  459. local keysfound
  460. local curdate
  461. local warnwindow
  462. local warndate
  463. local create
  464. local expire
  465. local uid
  466. local fingerprint
  467. local badhostkeys
  468. local sshd_config
  469. local problemsfound=0
  470. # FIXME: what's the correct, cross-platform answer?
  471. sshd_config=/etc/ssh/sshd_config
  472. seckey=$(gpg_host --list-secret-keys --fingerprint --with-colons --fixed-list-mode)
  473. keysfound=$(echo "$seckey" | grep -c ^sec:)
  474. curdate=$(date +%s)
  475. # warn when anything is 2 months away from expiration
  476. warnwindow='2 months'
  477. warndate=$(advance_date $warnwindow +%s)
  478. if ! id monkeysphere >/dev/null ; then
  479. echo "! No monkeysphere user found! Please create a monkeysphere system user with bash as its shell."
  480. problemsfound=$(($problemsfound+1))
  481. fi
  482. if ! [ -d "$SYSDATADIR" ] ; then
  483. echo "! no $SYSDATADIR directory found. Please create it."
  484. problemsfound=$(($problemsfound+1))
  485. fi
  486. echo "Checking host GPG key..."
  487. if (( "$keysfound" < 1 )); then
  488. echo "! No host key found."
  489. echo " - Recommendation: run 'monkeysphere-server gen-key'"
  490. problemsfound=$(($problemsfound+1))
  491. elif (( "$keysfound" > 1 )); then
  492. echo "! More than one host key found?"
  493. # FIXME: recommend a way to resolve this
  494. problemsfound=$(($problemsfound+1))
  495. else
  496. create=$(echo "$seckey" | grep ^sec: | cut -f6 -d:)
  497. expire=$(echo "$seckey" | grep ^sec: | cut -f7 -d:)
  498. fingerprint=$(echo "$seckey" | grep ^fpr: | head -n1 | cut -f10 -d:)
  499. # check for key expiration:
  500. if [ "$expire" ]; then
  501. if (( "$expire" < "$curdate" )); then
  502. echo "! Host key is expired."
  503. echo " - Recommendation: extend lifetime of key with 'monkeysphere-server extend-key'"
  504. problemsfound=$(($problemsfound+1))
  505. elif (( "$expire" < "$warndate" )); then
  506. echo "! Host key expires in less than $warnwindow:" $(advance_date $(( $expire - $curdate )) seconds +%F)
  507. echo " - Recommendation: extend lifetime of key with 'monkeysphere-server extend-key'"
  508. problemsfound=$(($problemsfound+1))
  509. fi
  510. fi
  511. # and weirdnesses:
  512. if [ "$create" ] && (( "$create" > "$curdate" )); then
  513. echo "! Host key was created in the future(?!). Is your clock correct?"
  514. echo " - Recommendation: Check clock ($(date +%F_%T)); use NTP?"
  515. problemsfound=$(($problemsfound+1))
  516. fi
  517. # check for UserID expiration:
  518. echo "$seckey" | grep ^uid: | cut -d: -f6,7,10 | \
  519. while IFS=: read create expire uid ; do
  520. # FIXME: should we be doing any checking on the form
  521. # of the User ID? Should we be unmangling it somehow?
  522. if [ "$create" ] && (( "$create" > "$curdate" )); then
  523. echo "! User ID '$uid' was created in the future(?!). Is your clock correct?"
  524. echo " - Recommendation: Check clock ($(date +%F_%T)); use NTP?"
  525. problemsfound=$(($problemsfound+1))
  526. fi
  527. if [ "$expire" ] ; then
  528. if (( "$expire" < "$curdate" )); then
  529. echo "! User ID '$uid' is expired."
  530. # FIXME: recommend a way to resolve this
  531. problemsfound=$(($problemsfound+1))
  532. elif (( "$expire" < "$warndate" )); then
  533. echo "! User ID '$uid' expires in less than $warnwindow:" $(advance_date $(( $expire - $curdate )) seconds +%F)
  534. # FIXME: recommend a way to resolve this
  535. problemsfound=$(($problemsfound+1))
  536. fi
  537. fi
  538. done
  539. # FIXME: verify that the host key is properly published to the
  540. # keyservers (do this with the non-privileged user)
  541. # FIXME: check that there are valid, non-expired certifying signatures
  542. # attached to the host key after fetching from the public keyserver
  543. # (do this with the non-privileged user as well)
  544. # FIXME: propose adding a revoker to the host key if none exist (do we
  545. # have a way to do that after key generation?)
  546. # Ensure that the ssh_host_rsa_key file is present and non-empty:
  547. echo
  548. echo "Checking host SSH key..."
  549. if [ ! -s "${SYSDATADIR}/ssh_host_rsa_key" ] ; then
  550. echo "! The host key as prepared for SSH (${SYSDATADIR}/ssh_host_rsa_key) is missing or empty."
  551. problemsfound=$(($problemsfound+1))
  552. else
  553. if [ $(ls -l "${SYSDATADIR}/ssh_host_rsa_key" | cut -f1 -d\ ) != '-rw-------' ] ; then
  554. echo "! Permissions seem wrong for ${SYSDATADIR}/ssh_host_rsa_key -- should be 0600."
  555. problemsfound=$(($problemsfound+1))
  556. fi
  557. # propose changes needed for sshd_config (if any)
  558. if ! grep -q "^HostKey[[:space:]]\+${SYSDATADIR}/ssh_host_rsa_key$" "$sshd_config"; then
  559. echo "! $sshd_config does not point to the monkeysphere host key (${SYSDATADIR}/ssh_host_rsa_key)."
  560. echo " - Recommendation: add a line to $sshd_config: 'HostKey ${SYSDATADIR}/ssh_host_rsa_key'"
  561. problemsfound=$(($problemsfound+1))
  562. fi
  563. if badhostkeys=$(grep -i '^HostKey' "$sshd_config" | grep -v "^HostKey[[:space:]]\+${SYSDATADIR}/ssh_host_rsa_key$") ; then
  564. echo "! $sshd_config refers to some non-monkeysphere host keys:"
  565. echo "$badhostkeys"
  566. echo " - Recommendation: remove the above HostKey lines from $sshd_config"
  567. problemsfound=$(($problemsfound+1))
  568. fi
  569. fi
  570. fi
  571. # FIXME: look at the ownership/privileges of the various keyrings,
  572. # directories housing them, etc (what should those values be? can
  573. # we make them as minimal as possible?)
  574. # FIXME: look to see that the ownertrust rules are set properly on the
  575. # authentication keyring
  576. # FIXME: make sure that at least one identity certifier exists
  577. # FIXME: look at the timestamps on the monkeysphere-generated
  578. # authorized_keys files -- warn if they seem out-of-date.
  579. # FIXME: check for a cronjob that updates monkeysphere-generated
  580. # authorized_keys?
  581. echo
  582. echo "Checking for MonkeySphere-enabled public-key authentication for users ..."
  583. # Ensure that User ID authentication is enabled:
  584. if ! grep -q "^AuthorizedKeysFile[[:space:]]\+${SYSDATADIR}/authorized_keys/%u$" "$sshd_config"; then
  585. echo "! $sshd_config does not point to monkeysphere authorized keys."
  586. echo " - Recommendation: add a line to $sshd_config: 'AuthorizedKeysFile ${SYSDATADIR}/authorized_keys/%u'"
  587. problemsfound=$(($problemsfound+1))
  588. fi
  589. if badauthorizedkeys=$(grep -i '^AuthorizedKeysFile' "$sshd_config" | grep -v "^AuthorizedKeysFile[[:space:]]\+${SYSDATADIR}/authorized_keys/%u$") ; then
  590. echo "! $sshd_config refers to non-monkeysphere authorized_keys files:"
  591. echo "$badauthorizedkeys"
  592. echo " - Recommendation: remove the above AuthorizedKeysFile lines from $sshd_config"
  593. problemsfound=$(($problemsfound+1))
  594. fi
  595. if [ "$problemsfound" -gt 0 ]; then
  596. echo "When the above $problemsfound issue"$(if [ "$problemsfound" -eq 1 ] ; then echo " is" ; else echo "s are" ; fi)" resolved, please re-run:"
  597. echo " monkeysphere-server diagnostics"
  598. else
  599. echo "Everything seems to be in order!"
  600. fi
  601. }
  602. # retrieve key from web of trust, import it into the host keyring, and
  603. # ltsign the key in the host keyring so that it may certify other keys
  604. add_certifier() {
  605. local domain
  606. local trust
  607. local depth
  608. local keyID
  609. local fingerprint
  610. local ltsignCommand
  611. local trustval
  612. # set default values for trust depth and domain
  613. domain=
  614. trust=full
  615. depth=1
  616. # get options
  617. TEMP=$(PATH="/usr/local/bin:$PATH" getopt -o n:t:d: -l domain:,trust:,depth: -n "$PGRM" -- "$@") || failure "getopt failed! Does your getopt support GNU-style long options?"
  618. if [ $? != 0 ] ; then
  619. exit 1
  620. fi
  621. # Note the quotes around `$TEMP': they are essential!
  622. eval set -- "$TEMP"
  623. while true ; do
  624. case "$1" in
  625. -n|--domain)
  626. domain="$2"
  627. shift 2
  628. ;;
  629. -t|--trust)
  630. trust="$2"
  631. shift 2
  632. ;;
  633. -d|--depth)
  634. depth="$2"
  635. shift 2
  636. ;;
  637. --)
  638. shift
  639. ;;
  640. *)
  641. break
  642. ;;
  643. esac
  644. done
  645. keyID="$1"
  646. if [ -z "$keyID" ] ; then
  647. failure "You must specify the key ID of a key to add, or specify a file to read the key from."
  648. fi
  649. if [ -f "$keyID" ] ; then
  650. echo "Reading key from file '$keyID':"
  651. importinfo=$(gpg_authentication "--import" < "$keyID" 2>&1) || failure "could not read key from '$keyID'"
  652. # FIXME: if this is tried when the key database is not
  653. # up-to-date, i got these errors (using set -x):
  654. # ++ su -m monkeysphere -c '\''gpg --import'\''
  655. # Warning: using insecure memory!
  656. # gpg: key D21739E9: public key "Daniel Kahn Gillmor <dkg@fifthhorseman.net>" imported
  657. # gpg: Total number processed: 1
  658. # gpg: imported: 1 (RSA: 1)
  659. # gpg: can'\''t create `/var/monkeysphere/gnupg-host/pubring.gpg.tmp'\'': Permission denied
  660. # gpg: failed to rebuild keyring cache: Permission denied
  661. # gpg: 3 marginal(s) needed, 1 complete(s) needed, PGP trust model
  662. # gpg: depth: 0 valid: 1 signed: 0 trust: 0-, 0q, 0n, 0m, 0f, 1u
  663. # gpg: next trustdb check due at 2009-01-10'
  664. # + failure 'could not read key from '\''/root/dkg.gpg'\'''
  665. # + echo 'could not read key from '\''/root/dkg.gpg'\'''
  666. keyID=$(echo "$importinfo" | grep '^gpg: key ' | cut -f2 -d: | cut -f3 -d\ )
  667. if [ -z "$keyID" ] || [ $(echo "$keyID" | wc -l) -ne 1 ] ; then
  668. failure "Expected there to be a single gpg key in the file."
  669. fi
  670. else
  671. # get the key from the key server
  672. gpg_authentication "--keyserver $KEYSERVER --recv-key '0x${keyID}!'" || failure "Could not receive a key with this ID from the '$KEYSERVER' keyserver."
  673. fi
  674. export keyID
  675. # get the full fingerprint of a key ID
  676. fingerprint=$(gpg_authentication "--list-key --with-colons --with-fingerprint 0x${keyID}!" | \
  677. grep '^fpr:' | grep "$keyID" | cut -d: -f10)
  678. if [ -z "$fingerprint" ] ; then
  679. failure "Key '$keyID' not found."
  680. fi
  681. echo
  682. echo "key found:"
  683. gpg_authentication "--fingerprint 0x${fingerprint}!"
  684. echo "Are you sure you want to add the above key as a"
  685. read -p "certifier of users on this system? (y/N) " OK; OK=${OK:-N}
  686. if [ "${OK/y/Y}" != 'Y' ] ; then
  687. failure "Identity certifier not added."
  688. fi
  689. # export the key to the host keyring
  690. gpg_authentication "--export 0x${fingerprint}!" | gpg_host --import
  691. if [ "$trust" == marginal ]; then
  692. trustval=1
  693. elif [ "$trust" == full ]; then
  694. trustval=2
  695. else
  696. failure "Trust value requested ('$trust') was unclear (only 'marginal' or 'full' are supported)."
  697. fi
  698. # ltsign command
  699. # NOTE: *all* user IDs will be ltsigned
  700. ltsignCommand=$(cat <<EOF
  701. ltsign
  702. y
  703. $trustval
  704. $depth
  705. $domain
  706. y
  707. save
  708. EOF
  709. )
  710. # ltsign the key
  711. if echo "$ltsignCommand" | \
  712. gpg_host --quiet --command-fd 0 --edit-key "0x${fingerprint}!" ; then
  713. # update the trustdb for the authentication keyring
  714. gpg_authentication "--check-trustdb"
  715. echo
  716. echo "Identity certifier added."
  717. else
  718. failure "Problem adding identify certifier."
  719. fi
  720. }
  721. # delete a certifiers key from the host keyring
  722. remove_certifier() {
  723. local keyID
  724. local fingerprint
  725. keyID="$1"
  726. if [ -z "$keyID" ] ; then
  727. failure "You must specify the key ID of a key to remove."
  728. fi
  729. if gpg_authentication "--no-options --list-options show-uid-validity --keyring ${GNUPGHOME_AUTHENTICATION}/pubring.gpg --list-key 0x${keyID}!" ; then
  730. read -p "Really remove above listed identity certifier? (y/N) " OK; OK=${OK:-N}
  731. if [ "${OK/y/Y}" != 'Y' ] ; then
  732. failure "Identity certifier not removed."
  733. fi
  734. else
  735. failure
  736. fi
  737. # delete the requested key
  738. if gpg_authentication "--delete-key --batch --yes 0x${keyID}!" ; then
  739. # delete key from host keyring as well
  740. gpg_host --delete-key --batch --yes "0x${keyID}!"
  741. # update the trustdb for the authentication keyring
  742. gpg_authentication "--check-trustdb"
  743. echo
  744. echo "Identity certifier removed."
  745. else
  746. failure "Problem removing identity certifier."
  747. fi
  748. }
  749. # list the host certifiers
  750. list_certifiers() {
  751. local keys
  752. local key
  753. # find trusted keys in authentication keychain
  754. keys=$(gpg_authentication "--no-options --list-options show-uid-validity --keyring ${GNUPGHOME_AUTHENTICATION}/pubring.gpg --list-keys --with-colons --fingerprint" | \
  755. grep ^pub: | cut -d: -f2,5 | egrep '^(u|f):' | cut -d: -f2)
  756. # output keys
  757. for key in $keys ; do
  758. gpg_authentication "--no-options --list-options show-uid-validity --keyring ${GNUPGHOME_AUTHENTICATION}/pubring.gpg --list-key --fingerprint $key"
  759. done
  760. }
  761. # issue command to gpg-authentication keyring
  762. gpg_authentication_cmd() {
  763. gpg_authentication "$@"
  764. }
  765. ########################################################################
  766. # MAIN
  767. ########################################################################
  768. # unset variables that should be defined only in config file
  769. unset KEYSERVER
  770. unset AUTHORIZED_USER_IDS
  771. unset RAW_AUTHORIZED_KEYS
  772. unset MONKEYSPHERE_USER
  773. # load configuration file
  774. [ -e ${MONKEYSPHERE_SERVER_CONFIG:="${SYSCONFIGDIR}/monkeysphere-server.conf"} ] && . "$MONKEYSPHERE_SERVER_CONFIG"
  775. # set empty config variable with ones from the environment, or with
  776. # defaults
  777. LOG_LEVEL=${MONKEYSPHERE_LOG_LEVEL:=${LOG_LEVEL:="INFO"}}
  778. KEYSERVER=${MONKEYSPHERE_KEYSERVER:=${KEYSERVER:="pool.sks-keyservers.net"}}
  779. AUTHORIZED_USER_IDS=${MONKEYSPHERE_AUTHORIZED_USER_IDS:=${AUTHORIZED_USER_IDS:="%h/.monkeysphere/authorized_user_ids"}}
  780. RAW_AUTHORIZED_KEYS=${MONKEYSPHERE_RAW_AUTHORIZED_KEYS:=${RAW_AUTHORIZED_KEYS:="%h/.ssh/authorized_keys"}}
  781. MONKEYSPHERE_USER=${MONKEYSPHERE_MONKEYSPHERE_USER:=${MONKEYSPHERE_USER:="monkeysphere"}}
  782. # other variables
  783. CHECK_KEYSERVER=${MONKEYSPHERE_CHECK_KEYSERVER:="true"}
  784. REQUIRED_USER_KEY_CAPABILITY=${MONKEYSPHERE_REQUIRED_USER_KEY_CAPABILITY:="a"}
  785. GNUPGHOME_HOST=${MONKEYSPHERE_GNUPGHOME_HOST:="${SYSDATADIR}/gnupg-host"}
  786. GNUPGHOME_AUTHENTICATION=${MONKEYSPHERE_GNUPGHOME_AUTHENTICATION:="${SYSDATADIR}/gnupg-authentication"}
  787. # export variables needed in su invocation
  788. export DATE
  789. export MODE
  790. export MONKEYSPHERE_USER
  791. export LOG_LEVEL
  792. export KEYSERVER
  793. export CHECK_KEYSERVER
  794. export REQUIRED_USER_KEY_CAPABILITY
  795. export GNUPGHOME_HOST
  796. export GNUPGHOME_AUTHENTICATION
  797. export GNUPGHOME
  798. # get subcommand
  799. COMMAND="$1"
  800. [ "$COMMAND" ] || failure "Type '$PGRM help' for usage."
  801. shift
  802. case $COMMAND in
  803. 'update-users'|'update-user'|'u')
  804. update_users "$@"
  805. ;;
  806. 'gen-key'|'g')
  807. gen_key "$@"
  808. ;;
  809. 'extend-key'|'e')
  810. extend_key "$@"
  811. ;;
  812. 'add-hostname'|'add-name'|'n+')
  813. add_hostname "$@"
  814. ;;
  815. 'revoke-hostname'|'revoke-name'|'n-')
  816. revoke_hostname "$@"
  817. ;;
  818. 'show-key'|'show'|'s')
  819. show_server_key
  820. ;;
  821. 'publish-key'|'publish'|'p')
  822. publish_server_key
  823. ;;
  824. 'diagnostics'|'d')
  825. diagnostics
  826. ;;
  827. 'add-identity-certifier'|'add-id-certifier'|'add-certifier'|'c+')
  828. add_certifier "$@"
  829. ;;
  830. 'remove-identity-certifier'|'remove-id-certifier'|'remove-certifier'|'c-')
  831. remove_certifier "$@"
  832. ;;
  833. 'list-identity-certifiers'|'list-id-certifiers'|'list-certifiers'|'list-certifier'|'c')
  834. list_certifiers "$@"
  835. ;;
  836. 'gpg-authentication-cmd')
  837. gpg_authentication_cmd "$@"
  838. ;;
  839. '--help'|'help'|'-h'|'h'|'?')
  840. usage
  841. ;;
  842. *)
  843. failure "Unknown command: '$COMMAND'
  844. Type '$PGRM help' for usage."
  845. ;;
  846. esac
  847. exit "$RETURN"