summaryrefslogtreecommitdiff
path: root/rhesus/rhesus
blob: 7a43fca0ac19f456f95c6daa4c746feb05878c7b (plain)
  1. #!/bin/sh -e
  2. # rhesus: monkeysphere authorized_keys/known_hosts generating script
  3. #
  4. # Written by
  5. # Jameson Rollins <jrollins@fifthhorseman.net>
  6. #
  7. # Copyright 2008, released under the GPL, version 3 or later
  8. PGRM=$(basename $0)
  9. ########################################################################
  10. # FUNCTIONS
  11. ########################################################################
  12. usage() {
  13. cat <<EOF
  14. usage: $PGRM k|known_hosts [userid...]
  15. $PGRM a|authorized_keys [userid...]
  16. Monkeysphere update of known_hosts or authorized_keys file.
  17. If userids are specified, only specified userids will be processed
  18. (userids must be included in the appropriate auth_*_ids file).
  19. EOF
  20. }
  21. failure() {
  22. echo "$1" >&2
  23. exit ${2:-'1'}
  24. }
  25. log() {
  26. echo -n "ms: "
  27. echo "$@"
  28. }
  29. # cut out all comments(#) and blank lines from standard input
  30. meat() {
  31. grep -v -e "^[[:space:]]*#" -e '^$'
  32. }
  33. # cut a specified line from standard input
  34. cutline() {
  35. head --line="$1" | tail -1
  36. }
  37. # retrieve all keys with given user id from keyserver
  38. # FIXME: need to figure out how to retrieve all matching keys
  39. # (not just first 5)
  40. gpg_fetch_keys() {
  41. local id
  42. id="$1"
  43. echo 1,2,3,4,5 | \
  44. gpg --quiet --batch --command-fd 0 --with-colons \
  45. --keyserver "$KEYSERVER" \
  46. --search ="$id" >/dev/null 2>&1
  47. }
  48. # convert escaped characters from gpg output back into original
  49. # character
  50. # FIXME: undo all escape character translation in with-colons gpg output
  51. unescape() {
  52. echo "$1" | sed 's/\\x3a/:/'
  53. }
  54. # stand in until we get dkg's gpg2ssh program
  55. gpg2ssh_tmp() {
  56. local mode
  57. local keyID
  58. mode="$1"
  59. keyID="$2"
  60. userID="$3"
  61. if [ "$mode" = 'authorized_keys' -o "$mode" = 'a' ] ; then
  62. gpgkey2ssh "$keyID" | sed -e "s/COMMENT/$userID/"
  63. elif [ "$mode" = 'known_hosts' -o "$mode" = 'k' ] ; then
  64. echo -n "$userID "; gpgkey2ssh "$keyID" | sed -e 's/ COMMENT//'
  65. fi
  66. }
  67. # userid and key policy checking
  68. # the following checks policy on the returned keys
  69. # - checks that full key has appropriate valididy (u|f)
  70. # - checks key has appropriate capability (E|A)
  71. # - checks that particular desired user id has appropriate validity
  72. # see /usr/share/doc/gnupg/DETAILS.gz
  73. # FIXME: add some more status output
  74. # expects global variable: "mode"
  75. process_user_id() {
  76. local userID
  77. local cacheDir
  78. local keyOK
  79. local keyCapability
  80. local keyFingerprint
  81. local userIDHash
  82. userID="$1"
  83. cacheDir="$2"
  84. # fetch all keys from keyserver
  85. # if none found, break
  86. if ! gpg_fetch_keys "$userID" ; then
  87. echo " no keys found."
  88. return
  89. fi
  90. # some crazy piping here that takes the output of gpg and
  91. # pipes it into a "while read" loop that reads each line
  92. # of standard input one-by-one.
  93. gpg --fixed-list-mode --list-key --with-colons \
  94. --with-fingerprint ="$userID" 2> /dev/null | \
  95. cut -d : -f 1,2,5,10,12 | \
  96. while IFS=: read -r type validity keyid uidfpr capability ; do
  97. # process based on record type
  98. case $type in
  99. 'pub')
  100. # new key, wipe the slate
  101. keyOK=
  102. keyCapability=
  103. keyFingerprint=
  104. # check primary key validity
  105. if [ "$validity" != 'u' -a "$validity" != 'f' ] ; then
  106. continue
  107. fi
  108. # check capability is not Disabled...
  109. if echo "$capability" | grep -q 'D' ; then
  110. continue
  111. fi
  112. # check capability is Encryption and Authentication
  113. # FIXME: make more flexible capability specification
  114. # (ie. in conf file)
  115. if echo "$capability" | grep -q -v 'E' ; then
  116. if echo "$capability" | grep -q -v 'A' ; then
  117. continue
  118. fi
  119. fi
  120. keyCapability="$capability"
  121. keyOK=true
  122. keyID="$keyid"
  123. ;;
  124. 'fpr')
  125. # if key ok, get fingerprint
  126. if [ "$keyOK" ] ; then
  127. keyFingerprint="$uidfpr"
  128. fi
  129. ;;
  130. 'uid')
  131. # check key ok and we have key fingerprint
  132. if [ -z "$keyOK" -o -z "$keyFingerprint" ] ; then
  133. continue
  134. fi
  135. # check key validity
  136. if [ "$validity" != 'u' -a "$validity" != 'f' ] ; then
  137. continue
  138. fi
  139. # check the uid matches
  140. if [ "$(unescape "$uidfpr")" != "$userID" ] ; then
  141. continue
  142. fi
  143. # convert the key
  144. # FIXME: needs to apply extra options if specified
  145. echo -n " valid key found; generating ssh key(s)... "
  146. userIDHash=$(echo "$userID" | sha1sum | awk '{ print $1 }')
  147. # export the key with gpg2ssh
  148. #gpg --export "$keyFingerprint" | gpg2ssh "$mode" > "$cacheDir"/"$userIDHash"."$keyFingerprint"
  149. # stand in until we get dkg's gpg2ssh program
  150. gpg2ssh_tmp "$mode" "$keyID" "$userID" > "$cacheDir"/"$userIDHash"."$keyFingerprint"
  151. if [ "$?" = 0 ] ; then
  152. echo "done."
  153. else
  154. echo "error."
  155. fi
  156. ;;
  157. esac
  158. done
  159. }
  160. # process the auth_*_ids file
  161. # go through line-by-line, extracting and processing each user id
  162. # expects global variable: "mode"
  163. process_auth_file() {
  164. local authIDsFile
  165. local cacheDir
  166. local nLines
  167. local line
  168. local userID
  169. authIDsFile="$1"
  170. cacheDir="$2"
  171. # find number of user ids in auth_user_ids file
  172. nLines=$(meat <"$authIDsFile" | wc -l)
  173. # clean out keys file and remake keys directory
  174. rm -rf "$cacheDir"
  175. mkdir -p "$cacheDir"
  176. # loop through all user ids
  177. for line in $(seq 1 $nLines) ; do
  178. # get user id
  179. # FIXME: needs to handle extra options if necessary
  180. userID=$(meat <"$authIDsFile" | cutline "$line" )
  181. # process the user id and extract keys
  182. log "processing user id: '$userID'"
  183. process_user_id "$userID" "$cacheDir"
  184. done
  185. }
  186. ########################################################################
  187. # MAIN
  188. ########################################################################
  189. if [ -z "$1" ] ; then
  190. usage
  191. exit 1
  192. fi
  193. # check mode
  194. mode="$1"
  195. shift 1
  196. # check user
  197. if ! id -u "$USER" > /dev/null 2>&1 ; then
  198. failure "invalid user '$USER'."
  199. fi
  200. # set user home directory
  201. HOME=$(getent passwd "$USER" | cut -d: -f6)
  202. # set ms home directory
  203. MS_HOME=${MS_HOME:-"$HOME"/.config/monkeysphere}
  204. # load configuration file
  205. MS_CONF=${MS_CONF:-"$MS_HOME"/monkeysphere.conf}
  206. [ -e "$MS_CONF" ] && . "$MS_CONF"
  207. # set config variable defaults
  208. STAGING_AREA=${STAGING_AREA:-"$MS_HOME"}
  209. AUTH_HOST_FILE=${AUTH_HOST_FILE:-"$MS_HOME"/auth_host_ids}
  210. AUTH_USER_FILE=${AUTH_USER_FILE:-"$MS_HOME"/auth_user_ids}
  211. GNUPGHOME=${GNUPGHOME:-"$HOME"/.gnupg}
  212. KEYSERVER=${KEYSERVER:-subkeys.pgp.net}
  213. USER_KNOW_HOSTS="$HOME"/.ssh/known_hosts
  214. USER_AUTHORIZED_KEYS="$HOME"/.ssh/authorized_keys
  215. # export USER and GNUPGHOME variables, since they are used by gpg
  216. export USER
  217. export GNUPGHOME
  218. # stagging locations
  219. hostKeysCacheDir="$STAGING_AREA"/host_keys
  220. userKeysCacheDir="$STAGING_AREA"/user_keys
  221. msKnownHosts="$STAGING_AREA"/known_hosts
  222. msAuthorizedKeys="$STAGING_AREA"/authorized_keys
  223. # set mode variables
  224. if [ "$mode" = 'known_hosts' -o "$mode" = 'k' ] ; then
  225. fileType=known_hosts
  226. authFileType=auth_host_ids
  227. authIDsFile="$AUTH_HOST_FILE"
  228. outFile="$msKnownHosts"
  229. cacheDir="$hostKeysCacheDir"
  230. userFile="$USER_KNOWN_HOSTS"
  231. elif [ "$mode" = 'authorized_keys' -o "$mode" = 'a' ] ; then
  232. fileType=authorized_keys
  233. authFileType=auth_user_ids
  234. authIDsFile="$AUTH_USER_FILE"
  235. outFile="$msAuthorizedKeys"
  236. cacheDir="$userKeysCacheDir"
  237. userFile="$USER_AUTHORIZED_KEYS"
  238. else
  239. failure "unknown command '$mode'."
  240. fi
  241. # check auth ids file
  242. if [ ! -s "$authIDsFile" ] ; then
  243. echo "'$authFileType' file is empty or does not exist."
  244. exit
  245. fi
  246. log "user '$USER': monkeysphere $fileType generation"
  247. # make sure gpg home exists with proper permissions
  248. mkdir -p -m 0700 "$GNUPGHOME"
  249. # if users are specified on the command line, process just
  250. # those users
  251. if [ "$1" ] ; then
  252. # process userids given on the command line
  253. for userID ; do
  254. if ! grep -q "$userID" "$authIDsFile" ; then
  255. log "userid '$userID' not in $authFileType file."
  256. continue
  257. fi
  258. log "processing user id: '$userID'"
  259. process_user_id "$userID" "$cacheDir"
  260. done
  261. # otherwise if no users are specified, process the entire
  262. # auth_*_ids file
  263. else
  264. # process the auth file
  265. process_auth_file "$authIDsFile" "$cacheDir"
  266. fi
  267. # write output key file
  268. log "writing ms $fileType file... "
  269. > "$outFile"
  270. if [ "$(ls "$cacheDir")" ] ; then
  271. log -n "adding gpg keys... "
  272. cat "$cacheDir"/* > "$outFile"
  273. echo "done."
  274. else
  275. log "no gpg keys to add."
  276. fi
  277. if [ -s "$userFile" ] ; then
  278. log -n "adding user $fileType file... "
  279. cat "$userFile" >> "$outFile"
  280. echo "done."
  281. fi
  282. log "ms $fileType file generated:"
  283. log "$outFile"