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