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