summaryrefslogtreecommitdiff
path: root/src/monkeysphere-server
blob: 3caa63d4ac18061537cb2a111fdcd16f7c91b34e (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. # 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. check_host_keyring
  123. fingerprintPGP=$(fingerprint_server_key)
  124. gpg_authentication "--fingerprint --list-key --list-options show-unusable-uids $fingerprintPGP" 2>/dev/null
  125. if [ $? -ne 0 ] ; then
  126. log info "You must be root to see host OpenPGP fingerprint."
  127. ret='1'
  128. else
  129. echo "OpenPGP fingerprint: $fingerprintPGP"
  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. # import an existing ssh key to a gpg key
  258. import_key() {
  259. local hostName=$(hostname -f)
  260. local keyFile="/etc/ssh/ssh_host_rsa_key"
  261. local keyExpire
  262. local userID
  263. # check for presense of secret key
  264. # FIXME: is this the proper test to be doing here?
  265. fingerprint_server_key >/dev/null \
  266. && failure "An OpenPGP host key already exists."
  267. # get options
  268. while true ; do
  269. case "$1" in
  270. -h|--hostname)
  271. hostName="$2"
  272. shift 2
  273. ;;
  274. -f|--keyfile)
  275. keyFile="$2"
  276. shift 2
  277. ;;
  278. -e|--expire)
  279. keyExpire="$2"
  280. shift 2
  281. ;;
  282. *)
  283. if [ "$(echo "$1" | cut -c 1)" = '-' ] ; then
  284. failure "Unknown option '$1'.
  285. Type '$PGRM help' for usage."
  286. fi
  287. break
  288. ;;
  289. esac
  290. done
  291. if [ ! -f "$keyFile" ] ; then
  292. failure "SSH secret key file '$keyFile' not found."
  293. fi
  294. userID="ssh://${hostName}"
  295. # prompt about key expiration if not specified
  296. keyExpire=$(get_gpg_expiration "$keyExpire")
  297. echo "The following key parameters will be used for the host private key:"
  298. echo "Import: $keyFile"
  299. echo "Name-Real: $userID"
  300. echo "Expire-Date: $keyExpire"
  301. read -p "Import key? (Y/n) " OK; OK=${OK:=Y}
  302. if [ ${OK/y/Y} != 'Y' ] ; then
  303. failure "aborting."
  304. fi
  305. log verbose "importing ssh key..."
  306. # translate ssh key to a private key
  307. (umask 077 && \
  308. pem2openpgp "$userID" "$keyExpire" < "$sshKey" | gpg_host --import)
  309. # find the key fingerprint of the newly converted key
  310. fingerprint=$(fingerprint_server_key)
  311. # export host ownertrust to authentication keyring
  312. log verbose "setting ultimate owner trust for host key..."
  313. echo "${fingerprint}:6:" | gpg_host "--import-ownertrust"
  314. echo "${fingerprint}:6:" | gpg_authentication "--import-ownertrust"
  315. # export public key to file
  316. gpg_authentication "--export-options export-minimal --armor --export 0x${fingerprint}\!" > "${SYSDATADIR}/ssh_host_rsa_key.pub.gpg"
  317. log info "SSH host public key in OpenPGP form: ${SYSDATADIR}/ssh_host_rsa_key.pub.gpg"
  318. # show info about new key
  319. show_server_key
  320. }
  321. # generate server gpg key
  322. gen_key() {
  323. local keyType="RSA"
  324. local keyLength="2048"
  325. local keyUsage="auth"
  326. local keyExpire
  327. local revoker
  328. local hostName=$(hostname -f)
  329. local userID
  330. local keyParameters
  331. local fingerprint
  332. # check for presense of secret key
  333. # FIXME: is this the proper test to be doing here?
  334. fingerprint_server_key >/dev/null \
  335. && failure "An OpenPGP host key already exists."
  336. # get options
  337. while true ; do
  338. case "$1" in
  339. -l|--length)
  340. keyLength="$2"
  341. shift 2
  342. ;;
  343. -e|--expire)
  344. keyExpire="$2"
  345. shift 2
  346. ;;
  347. -r|--revoker)
  348. revoker="$2"
  349. shift 2
  350. ;;
  351. *)
  352. if [ "$(echo "$1" | cut -c 1)" = '-' ] ; then
  353. failure "Unknown option '$1'.
  354. Type '$PGRM help' for usage."
  355. fi
  356. hostName="$1"
  357. break
  358. ;;
  359. esac
  360. done
  361. userID="ssh://${hostName}"
  362. # prompt about key expiration if not specified
  363. keyExpire=$(get_gpg_expiration "$keyExpire")
  364. # set key parameters
  365. keyParameters=\
  366. "Key-Type: $keyType
  367. Key-Length: $keyLength
  368. Key-Usage: $keyUsage
  369. Name-Real: $userID
  370. Expire-Date: $keyExpire"
  371. # add the revoker field if specified
  372. # FIXME: the "1:" below assumes that $REVOKER's key is an RSA key.
  373. # FIXME: key is marked "sensitive"? is this appropriate?
  374. if [ "$revoker" ] ; then
  375. keyParameters=\
  376. "${keyParameters}
  377. Revoker: 1:${revoker} sensitive"
  378. fi
  379. echo "The following key parameters will be used for the host private key:"
  380. echo "$keyParameters"
  381. read -p "Generate key? (Y/n) " OK; OK=${OK:=Y}
  382. if [ ${OK/y/Y} != 'Y' ] ; then
  383. failure "aborting."
  384. fi
  385. # add commit command
  386. # must include blank line!
  387. keyParameters=\
  388. "${keyParameters}
  389. %commit
  390. %echo done"
  391. log verbose "generating host key..."
  392. echo "$keyParameters" | gpg_host --batch --gen-key
  393. # find the key fingerprint of the newly generated key
  394. fingerprint=$(fingerprint_server_key)
  395. # export host ownertrust to authentication keyring
  396. log verbose "setting ultimate owner trust for host key..."
  397. echo "${fingerprint}:6:" | gpg_authentication "--import-ownertrust"
  398. # translate the private key to ssh format, and export to a file
  399. # for sshs usage.
  400. # NOTE: assumes that the primary key is the proper key to use
  401. (umask 077 && \
  402. gpg_host --export-secret-key "$fingerprint" | \
  403. openpgp2ssh "$fingerprint" > "${SYSDATADIR}/ssh_host_rsa_key")
  404. log info "SSH host private key output to file: ${SYSDATADIR}/ssh_host_rsa_key"
  405. ssh-keygen -y -f "${SYSDATADIR}/ssh_host_rsa_key" > "${SYSDATADIR}/ssh_host_rsa_key.pub"
  406. log info "SSH host public key output to file: ${SYSDATADIR}/ssh_host_rsa_key.pub"
  407. gpg_authentication "--export-options export-minimal --armor --export 0x${fingerprint}\!" > "${SYSDATADIR}/ssh_host_rsa_key.pub.gpg"
  408. log info "SSH host public key in OpenPGP form: ${SYSDATADIR}/ssh_host_rsa_key.pub.gpg"
  409. # show info about new key
  410. show_server_key
  411. }
  412. # extend the lifetime of a host key:
  413. extend_key() {
  414. local fpr=$(fingerprint_server_key)
  415. local extendTo="$1"
  416. # get the new expiration date
  417. extendTo=$(get_gpg_expiration "$extendTo")
  418. gpg_host --quiet --command-fd 0 --edit-key "$fpr" <<EOF
  419. expire
  420. $extendTo
  421. save
  422. EOF
  423. echo
  424. echo "NOTE: Host key expiration date adjusted, but not yet published."
  425. echo "Run '$PGRM publish-key' to publish the new expiration date."
  426. }
  427. # add hostname user ID to server key
  428. add_hostname() {
  429. local userID
  430. local fingerprint
  431. local tmpuidMatch
  432. local line
  433. local adduidCommand
  434. if [ -z "$1" ] ; then
  435. failure "You must specify a hostname to add."
  436. fi
  437. userID="ssh://${1}"
  438. fingerprint=$(fingerprint_server_key)
  439. # match to only ultimately trusted user IDs
  440. tmpuidMatch="u:$(echo $userID | gpg_escape)"
  441. # find the index of the requsted user ID
  442. # NOTE: this is based on circumstantial evidence that the order of
  443. # this output is the appropriate index
  444. if line=$(gpg_host --list-keys --with-colons --fixed-list-mode "0x${fingerprint}!" \
  445. | egrep '^(uid|uat):' | cut -f2,10 -d: | grep -n -x -F "$tmpuidMatch") ; then
  446. failure "Host userID '$userID' already exists."
  447. fi
  448. echo "The following user ID will be added to the host key:"
  449. echo " $userID"
  450. read -p "Are you sure you would like to add this user ID? (y/N) " OK; OK=${OK:=N}
  451. if [ ${OK/y/Y} != 'Y' ] ; then
  452. failure "User ID not added."
  453. fi
  454. # edit-key script command to add user ID
  455. adduidCommand=$(cat <<EOF
  456. adduid
  457. $userID
  458. save
  459. EOF
  460. )
  461. # execute edit-key script
  462. if echo "$adduidCommand" | \
  463. gpg_host --quiet --command-fd 0 --edit-key "0x${fingerprint}!" ; then
  464. # update the trustdb for the authentication keyring
  465. gpg_authentication "--check-trustdb"
  466. show_server_key
  467. echo
  468. echo "NOTE: User ID added to key, but key not published."
  469. echo "Run '$PGRM publish-key' to publish the new user ID."
  470. else
  471. failure "Problem adding user ID."
  472. fi
  473. }
  474. # revoke hostname user ID to server key
  475. revoke_hostname() {
  476. local userID
  477. local fingerprint
  478. local tmpuidMatch
  479. local line
  480. local uidIndex
  481. local message
  482. local revuidCommand
  483. if [ -z "$1" ] ; then
  484. failure "You must specify a hostname to revoke."
  485. fi
  486. echo "WARNING: There is a known bug in this function."
  487. echo "This function has been known to occasionally revoke the wrong user ID."
  488. echo "Please see the following bug report for more information:"
  489. echo "http://web.monkeysphere.info/bugs/revoke-hostname-revoking-wrong-userid/"
  490. read -p "Are you sure you would like to proceed? (y/N) " OK; OK=${OK:=N}
  491. if [ ${OK/y/Y} != 'Y' ] ; then
  492. failure "aborting."
  493. fi
  494. userID="ssh://${1}"
  495. fingerprint=$(fingerprint_server_key)
  496. # match to only ultimately trusted user IDs
  497. tmpuidMatch="u:$(echo $userID | gpg_escape)"
  498. # find the index of the requsted user ID
  499. # NOTE: this is based on circumstantial evidence that the order of
  500. # this output is the appropriate index
  501. if line=$(gpg_host --list-keys --with-colons --fixed-list-mode "0x${fingerprint}!" \
  502. | egrep '^(uid|uat):' | cut -f2,10 -d: | grep -n -x -F "$tmpuidMatch") ; then
  503. uidIndex=${line%%:*}
  504. else
  505. failure "No non-revoked user ID '$userID' is found."
  506. fi
  507. echo "The following host key user ID will be revoked:"
  508. echo " $userID"
  509. read -p "Are you sure you would like to revoke this user ID? (y/N) " OK; OK=${OK:=N}
  510. if [ ${OK/y/Y} != 'Y' ] ; then
  511. failure "User ID not revoked."
  512. fi
  513. message="Hostname removed by monkeysphere-server $DATE"
  514. # edit-key script command to revoke user ID
  515. revuidCommand=$(cat <<EOF
  516. $uidIndex
  517. revuid
  518. y
  519. 4
  520. $message
  521. y
  522. save
  523. EOF
  524. )
  525. # execute edit-key script
  526. if echo "$revuidCommand" | \
  527. gpg_host --quiet --command-fd 0 --edit-key "0x${fingerprint}!" ; then
  528. # update the trustdb for the authentication keyring
  529. gpg_authentication "--check-trustdb"
  530. show_server_key
  531. echo
  532. echo "NOTE: User ID revoked, but revocation not published."
  533. echo "Run '$PGRM publish-key' to publish the revocation."
  534. else
  535. failure "Problem revoking user ID."
  536. fi
  537. }
  538. # add a revoker to the host key
  539. add_revoker() {
  540. # FIXME: implement!
  541. failure "not implemented yet!"
  542. }
  543. # revoke the host key
  544. revoke_key() {
  545. # FIXME: implement!
  546. failure "not implemented yet!"
  547. }
  548. # publish server key to keyserver
  549. publish_server_key() {
  550. read -p "Really publish host key to $KEYSERVER? (y/N) " OK; OK=${OK:=N}
  551. if [ ${OK/y/Y} != 'Y' ] ; then
  552. failure "key not published."
  553. fi
  554. # find the key fingerprint
  555. fingerprint=$(fingerprint_server_key)
  556. # publish host key
  557. gpg_authentication "--keyserver $KEYSERVER --send-keys '0x${fingerprint}!'"
  558. }
  559. diagnostics() {
  560. # * check on the status and validity of the key and public certificates
  561. local seckey
  562. local keysfound
  563. local curdate
  564. local warnwindow
  565. local warndate
  566. local create
  567. local expire
  568. local uid
  569. local fingerprint
  570. local badhostkeys
  571. local sshd_config
  572. local problemsfound=0
  573. # FIXME: what's the correct, cross-platform answer?
  574. sshd_config=/etc/ssh/sshd_config
  575. seckey=$(gpg_host --list-secret-keys --fingerprint --with-colons --fixed-list-mode)
  576. keysfound=$(echo "$seckey" | grep -c ^sec:)
  577. curdate=$(date +%s)
  578. # warn when anything is 2 months away from expiration
  579. warnwindow='2 months'
  580. warndate=$(advance_date $warnwindow +%s)
  581. if ! id monkeysphere >/dev/null ; then
  582. echo "! No monkeysphere user found! Please create a monkeysphere system user with bash as its shell."
  583. problemsfound=$(($problemsfound+1))
  584. fi
  585. if ! [ -d "$SYSDATADIR" ] ; then
  586. echo "! no $SYSDATADIR directory found. Please create it."
  587. problemsfound=$(($problemsfound+1))
  588. fi
  589. echo "Checking host GPG key..."
  590. if (( "$keysfound" < 1 )); then
  591. echo "! No host key found."
  592. echo " - Recommendation: run 'monkeysphere-server gen-key'"
  593. problemsfound=$(($problemsfound+1))
  594. elif (( "$keysfound" > 1 )); then
  595. echo "! More than one host key found?"
  596. # FIXME: recommend a way to resolve this
  597. problemsfound=$(($problemsfound+1))
  598. else
  599. create=$(echo "$seckey" | grep ^sec: | cut -f6 -d:)
  600. expire=$(echo "$seckey" | grep ^sec: | cut -f7 -d:)
  601. fingerprint=$(echo "$seckey" | grep ^fpr: | head -n1 | cut -f10 -d:)
  602. # check for key expiration:
  603. if [ "$expire" ]; then
  604. if (( "$expire" < "$curdate" )); then
  605. echo "! Host key is expired."
  606. echo " - Recommendation: extend lifetime of key with 'monkeysphere-server extend-key'"
  607. problemsfound=$(($problemsfound+1))
  608. elif (( "$expire" < "$warndate" )); then
  609. echo "! Host key expires in less than $warnwindow:" $(advance_date $(( $expire - $curdate )) seconds +%F)
  610. echo " - Recommendation: extend lifetime of key with 'monkeysphere-server extend-key'"
  611. problemsfound=$(($problemsfound+1))
  612. fi
  613. fi
  614. # and weirdnesses:
  615. if [ "$create" ] && (( "$create" > "$curdate" )); then
  616. echo "! Host key was created in the future(?!). Is your clock correct?"
  617. echo " - Recommendation: Check clock ($(date +%F_%T)); use NTP?"
  618. problemsfound=$(($problemsfound+1))
  619. fi
  620. # check for UserID expiration:
  621. echo "$seckey" | grep ^uid: | cut -d: -f6,7,10 | \
  622. while IFS=: read create expire uid ; do
  623. # FIXME: should we be doing any checking on the form
  624. # of the User ID? Should we be unmangling it somehow?
  625. if [ "$create" ] && (( "$create" > "$curdate" )); then
  626. echo "! User ID '$uid' was created in the future(?!). Is your clock correct?"
  627. echo " - Recommendation: Check clock ($(date +%F_%T)); use NTP?"
  628. problemsfound=$(($problemsfound+1))
  629. fi
  630. if [ "$expire" ] ; then
  631. if (( "$expire" < "$curdate" )); then
  632. echo "! User ID '$uid' is expired."
  633. # FIXME: recommend a way to resolve this
  634. problemsfound=$(($problemsfound+1))
  635. elif (( "$expire" < "$warndate" )); then
  636. echo "! User ID '$uid' expires in less than $warnwindow:" $(advance_date $(( $expire - $curdate )) seconds +%F)
  637. # FIXME: recommend a way to resolve this
  638. problemsfound=$(($problemsfound+1))
  639. fi
  640. fi
  641. done
  642. # FIXME: verify that the host key is properly published to the
  643. # keyservers (do this with the non-privileged user)
  644. # FIXME: check that there are valid, non-expired certifying signatures
  645. # attached to the host key after fetching from the public keyserver
  646. # (do this with the non-privileged user as well)
  647. # FIXME: propose adding a revoker to the host key if none exist (do we
  648. # have a way to do that after key generation?)
  649. # Ensure that the ssh_host_rsa_key file is present and non-empty:
  650. echo
  651. echo "Checking host SSH key..."
  652. if [ ! -s "${SYSDATADIR}/ssh_host_rsa_key" ] ; then
  653. echo "! The host key as prepared for SSH (${SYSDATADIR}/ssh_host_rsa_key) is missing or empty."
  654. problemsfound=$(($problemsfound+1))
  655. else
  656. if [ $(ls -l "${SYSDATADIR}/ssh_host_rsa_key" | cut -f1 -d\ ) != '-rw-------' ] ; then
  657. echo "! Permissions seem wrong for ${SYSDATADIR}/ssh_host_rsa_key -- should be 0600."
  658. problemsfound=$(($problemsfound+1))
  659. fi
  660. # propose changes needed for sshd_config (if any)
  661. if ! grep -q "^HostKey[[:space:]]\+${SYSDATADIR}/ssh_host_rsa_key$" "$sshd_config"; then
  662. echo "! $sshd_config does not point to the monkeysphere host key (${SYSDATADIR}/ssh_host_rsa_key)."
  663. echo " - Recommendation: add a line to $sshd_config: 'HostKey ${SYSDATADIR}/ssh_host_rsa_key'"
  664. problemsfound=$(($problemsfound+1))
  665. fi
  666. if badhostkeys=$(grep -i '^HostKey' "$sshd_config" | grep -v "^HostKey[[:space:]]\+${SYSDATADIR}/ssh_host_rsa_key$") ; then
  667. echo "! $sshd_config refers to some non-monkeysphere host keys:"
  668. echo "$badhostkeys"
  669. echo " - Recommendation: remove the above HostKey lines from $sshd_config"
  670. problemsfound=$(($problemsfound+1))
  671. fi
  672. # FIXME: test (with ssh-keyscan?) that the running ssh
  673. # daemon is actually offering the monkeysphere host key.
  674. fi
  675. fi
  676. # FIXME: look at the ownership/privileges of the various keyrings,
  677. # directories housing them, etc (what should those values be? can
  678. # we make them as minimal as possible?)
  679. # FIXME: look to see that the ownertrust rules are set properly on the
  680. # authentication keyring
  681. # FIXME: make sure that at least one identity certifier exists
  682. # FIXME: look at the timestamps on the monkeysphere-generated
  683. # authorized_keys files -- warn if they seem out-of-date.
  684. # FIXME: check for a cronjob that updates monkeysphere-generated
  685. # authorized_keys?
  686. echo
  687. echo "Checking for MonkeySphere-enabled public-key authentication for users ..."
  688. # Ensure that User ID authentication is enabled:
  689. if ! grep -q "^AuthorizedKeysFile[[:space:]]\+${SYSDATADIR}/authorized_keys/%u$" "$sshd_config"; then
  690. echo "! $sshd_config does not point to monkeysphere authorized keys."
  691. echo " - Recommendation: add a line to $sshd_config: 'AuthorizedKeysFile ${SYSDATADIR}/authorized_keys/%u'"
  692. problemsfound=$(($problemsfound+1))
  693. fi
  694. if badauthorizedkeys=$(grep -i '^AuthorizedKeysFile' "$sshd_config" | grep -v "^AuthorizedKeysFile[[:space:]]\+${SYSDATADIR}/authorized_keys/%u$") ; then
  695. echo "! $sshd_config refers to non-monkeysphere authorized_keys files:"
  696. echo "$badauthorizedkeys"
  697. echo " - Recommendation: remove the above AuthorizedKeysFile lines from $sshd_config"
  698. problemsfound=$(($problemsfound+1))
  699. fi
  700. if [ "$problemsfound" -gt 0 ]; then
  701. echo "When the above $problemsfound issue"$(if [ "$problemsfound" -eq 1 ] ; then echo " is" ; else echo "s are" ; fi)" resolved, please re-run:"
  702. echo " monkeysphere-server diagnostics"
  703. else
  704. echo "Everything seems to be in order!"
  705. fi
  706. }
  707. # retrieve key from web of trust, import it into the host keyring, and
  708. # ltsign the key in the host keyring so that it may certify other keys
  709. add_certifier() {
  710. local domain
  711. local trust
  712. local depth
  713. local keyID
  714. local fingerprint
  715. local ltsignCommand
  716. local trustval
  717. # set default values for trust depth and domain
  718. domain=
  719. trust=full
  720. depth=1
  721. # get options
  722. while true ; do
  723. case "$1" in
  724. -n|--domain)
  725. domain="$2"
  726. shift 2
  727. ;;
  728. -t|--trust)
  729. trust="$2"
  730. shift 2
  731. ;;
  732. -d|--depth)
  733. depth="$2"
  734. shift 2
  735. ;;
  736. *)
  737. if [ "$(echo "$1" | cut -c 1)" = '-' ] ; then
  738. failure "Unknown option '$1'.
  739. Type '$PGRM help' for usage."
  740. fi
  741. break
  742. ;;
  743. esac
  744. done
  745. keyID="$1"
  746. if [ -z "$keyID" ] ; then
  747. failure "You must specify the key ID of a key to add, or specify a file to read the key from."
  748. fi
  749. if [ -f "$keyID" ] ; then
  750. echo "Reading key from file '$keyID':"
  751. importinfo=$(gpg_authentication "--import" < "$keyID" 2>&1) || failure "could not read key from '$keyID'"
  752. # FIXME: if this is tried when the key database is not
  753. # up-to-date, i got these errors (using set -x):
  754. # ++ su -m monkeysphere -c '\''gpg --import'\''
  755. # Warning: using insecure memory!
  756. # gpg: key D21739E9: public key "Daniel Kahn Gillmor <dkg@fifthhorseman.net>" imported
  757. # gpg: Total number processed: 1
  758. # gpg: imported: 1 (RSA: 1)
  759. # gpg: can'\''t create `/var/monkeysphere/gnupg-host/pubring.gpg.tmp'\'': Permission denied
  760. # gpg: failed to rebuild keyring cache: Permission denied
  761. # gpg: 3 marginal(s) needed, 1 complete(s) needed, PGP trust model
  762. # gpg: depth: 0 valid: 1 signed: 0 trust: 0-, 0q, 0n, 0m, 0f, 1u
  763. # gpg: next trustdb check due at 2009-01-10'
  764. # + failure 'could not read key from '\''/root/dkg.gpg'\'''
  765. # + echo 'could not read key from '\''/root/dkg.gpg'\'''
  766. keyID=$(echo "$importinfo" | grep '^gpg: key ' | cut -f2 -d: | cut -f3 -d\ )
  767. if [ -z "$keyID" ] || [ $(echo "$keyID" | wc -l) -ne 1 ] ; then
  768. failure "Expected there to be a single gpg key in the file."
  769. fi
  770. else
  771. # get the key from the key server
  772. gpg_authentication "--keyserver $KEYSERVER --recv-key '0x${keyID}!'" || failure "Could not receive a key with this ID from the '$KEYSERVER' keyserver."
  773. fi
  774. export keyID
  775. # get the full fingerprint of a key ID
  776. fingerprint=$(gpg_authentication "--list-key --with-colons --with-fingerprint 0x${keyID}!" | \
  777. grep '^fpr:' | grep "$keyID" | cut -d: -f10)
  778. if [ -z "$fingerprint" ] ; then
  779. failure "Key '$keyID' not found."
  780. fi
  781. echo
  782. echo "key found:"
  783. gpg_authentication "--fingerprint 0x${fingerprint}!"
  784. echo "Are you sure you want to add the above key as a"
  785. read -p "certifier of users on this system? (y/N) " OK; OK=${OK:-N}
  786. if [ "${OK/y/Y}" != 'Y' ] ; then
  787. failure "Identity certifier not added."
  788. fi
  789. # export the key to the host keyring
  790. gpg_authentication "--export 0x${fingerprint}!" | gpg_host --import
  791. if [ "$trust" = marginal ]; then
  792. trustval=1
  793. elif [ "$trust" = full ]; then
  794. trustval=2
  795. else
  796. failure "Trust value requested ('$trust') was unclear (only 'marginal' or 'full' are supported)."
  797. fi
  798. # ltsign command
  799. # NOTE: *all* user IDs will be ltsigned
  800. ltsignCommand=$(cat <<EOF
  801. ltsign
  802. y
  803. $trustval
  804. $depth
  805. $domain
  806. y
  807. save
  808. EOF
  809. )
  810. # ltsign the key
  811. if echo "$ltsignCommand" | \
  812. gpg_host --quiet --command-fd 0 --edit-key "0x${fingerprint}!" ; then
  813. # update the trustdb for the authentication keyring
  814. gpg_authentication "--check-trustdb"
  815. echo
  816. echo "Identity certifier added."
  817. else
  818. failure "Problem adding identify certifier."
  819. fi
  820. }
  821. # delete a certifiers key from the host keyring
  822. remove_certifier() {
  823. local keyID
  824. local fingerprint
  825. keyID="$1"
  826. if [ -z "$keyID" ] ; then
  827. failure "You must specify the key ID of a key to remove."
  828. fi
  829. if gpg_authentication "--no-options --list-options show-uid-validity --keyring ${GNUPGHOME_AUTHENTICATION}/pubring.gpg --list-key 0x${keyID}!" ; then
  830. read -p "Really remove above listed identity certifier? (y/N) " OK; OK=${OK:-N}
  831. if [ "${OK/y/Y}" != 'Y' ] ; then
  832. failure "Identity certifier not removed."
  833. fi
  834. else
  835. failure
  836. fi
  837. # delete the requested key
  838. if gpg_authentication "--delete-key --batch --yes 0x${keyID}!" ; then
  839. # delete key from host keyring as well
  840. gpg_host --delete-key --batch --yes "0x${keyID}!"
  841. # update the trustdb for the authentication keyring
  842. gpg_authentication "--check-trustdb"
  843. echo
  844. echo "Identity certifier removed."
  845. else
  846. failure "Problem removing identity certifier."
  847. fi
  848. }
  849. # list the host certifiers
  850. list_certifiers() {
  851. local keys
  852. local key
  853. # find trusted keys in authentication keychain
  854. keys=$(gpg_authentication "--no-options --list-options show-uid-validity --keyring ${GNUPGHOME_AUTHENTICATION}/pubring.gpg --list-keys --with-colons --fingerprint" | \
  855. grep ^pub: | cut -d: -f2,5 | egrep '^(u|f):' | cut -d: -f2)
  856. # output keys
  857. for key in $keys ; do
  858. gpg_authentication "--no-options --list-options show-uid-validity --keyring ${GNUPGHOME_AUTHENTICATION}/pubring.gpg --list-key --fingerprint $key"
  859. done
  860. }
  861. # issue command to gpg-authentication keyring
  862. gpg_authentication_cmd() {
  863. gpg_authentication "$@"
  864. }
  865. ########################################################################
  866. # MAIN
  867. ########################################################################
  868. # unset variables that should be defined only in config file
  869. unset KEYSERVER
  870. unset AUTHORIZED_USER_IDS
  871. unset RAW_AUTHORIZED_KEYS
  872. unset MONKEYSPHERE_USER
  873. # load configuration file
  874. [ -e ${MONKEYSPHERE_SERVER_CONFIG:="${SYSCONFIGDIR}/monkeysphere-server.conf"} ] && . "$MONKEYSPHERE_SERVER_CONFIG"
  875. # set empty config variable with ones from the environment, or with
  876. # defaults
  877. LOG_LEVEL=${MONKEYSPHERE_LOG_LEVEL:=${LOG_LEVEL:="INFO"}}
  878. KEYSERVER=${MONKEYSPHERE_KEYSERVER:=${KEYSERVER:="pool.sks-keyservers.net"}}
  879. AUTHORIZED_USER_IDS=${MONKEYSPHERE_AUTHORIZED_USER_IDS:=${AUTHORIZED_USER_IDS:="%h/.monkeysphere/authorized_user_ids"}}
  880. RAW_AUTHORIZED_KEYS=${MONKEYSPHERE_RAW_AUTHORIZED_KEYS:=${RAW_AUTHORIZED_KEYS:="%h/.ssh/authorized_keys"}}
  881. MONKEYSPHERE_USER=${MONKEYSPHERE_MONKEYSPHERE_USER:=${MONKEYSPHERE_USER:="monkeysphere"}}
  882. # other variables
  883. CHECK_KEYSERVER=${MONKEYSPHERE_CHECK_KEYSERVER:="true"}
  884. REQUIRED_USER_KEY_CAPABILITY=${MONKEYSPHERE_REQUIRED_USER_KEY_CAPABILITY:="a"}
  885. GNUPGHOME_HOST=${MONKEYSPHERE_GNUPGHOME_HOST:="${SYSDATADIR}/gnupg-host"}
  886. GNUPGHOME_AUTHENTICATION=${MONKEYSPHERE_GNUPGHOME_AUTHENTICATION:="${SYSDATADIR}/gnupg-authentication"}
  887. # export variables needed in su invocation
  888. export DATE
  889. export MODE
  890. export MONKEYSPHERE_USER
  891. export LOG_LEVEL
  892. export KEYSERVER
  893. export CHECK_KEYSERVER
  894. export REQUIRED_USER_KEY_CAPABILITY
  895. export GNUPGHOME_HOST
  896. export GNUPGHOME_AUTHENTICATION
  897. export GNUPGHOME
  898. # get subcommand
  899. COMMAND="$1"
  900. [ "$COMMAND" ] || failure "Type '$PGRM help' for usage."
  901. shift
  902. case $COMMAND in
  903. 'update-users'|'update-user'|'u')
  904. check_host_keyring
  905. update_users "$@"
  906. ;;
  907. 'import-key'|'i')
  908. import_key "$@"
  909. ;;
  910. 'gen-key'|'g')
  911. gen_key "$@"
  912. ;;
  913. 'extend-key'|'e')
  914. check_host_keyring
  915. extend_key "$@"
  916. ;;
  917. 'add-hostname'|'add-name'|'n+')
  918. check_host_keyring
  919. add_hostname "$@"
  920. ;;
  921. 'revoke-hostname'|'revoke-name'|'n-')
  922. check_host_keyring
  923. revoke_hostname "$@"
  924. ;;
  925. 'add-revoker'|'o')
  926. check_host_keyring
  927. add_revoker "$@"
  928. ;;
  929. 'revoke-key'|'r')
  930. check_host_keyring
  931. revoke_key "$@"
  932. ;;
  933. 'show-key'|'show'|'s')
  934. show_server_key
  935. ;;
  936. 'publish-key'|'publish'|'p')
  937. check_host_keyring
  938. publish_server_key
  939. ;;
  940. 'diagnostics'|'d')
  941. diagnostics
  942. ;;
  943. 'add-identity-certifier'|'add-id-certifier'|'add-certifier'|'c+')
  944. check_host_keyring
  945. add_certifier "$@"
  946. ;;
  947. 'remove-identity-certifier'|'remove-id-certifier'|'remove-certifier'|'c-')
  948. check_host_keyring
  949. remove_certifier "$@"
  950. ;;
  951. 'list-identity-certifiers'|'list-id-certifiers'|'list-certifiers'|'list-certifier'|'c')
  952. check_host_keyring
  953. list_certifiers "$@"
  954. ;;
  955. 'gpg-authentication-cmd')
  956. gpg_authentication_cmd "$@"
  957. ;;
  958. 'version'|'v')
  959. echo "$VERSION"
  960. ;;
  961. '--help'|'help'|'-h'|'h'|'?')
  962. usage
  963. ;;
  964. *)
  965. failure "Unknown command: '$COMMAND'
  966. Type '$PGRM help' for usage."
  967. ;;
  968. esac
  969. exit "$RETURN"