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