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