summaryrefslogtreecommitdiff
path: root/src/common
blob: 0f98923805600d7d916aa3a1dc352193e627c6dd (plain)
  1. # -*-shell-script-*-
  2. # Shared bash functions for the monkeysphere
  3. #
  4. # Written by
  5. # Jameson Rollins <jrollins@fifthhorseman.net>
  6. #
  7. # Copyright 2008, released under the GPL, version 3 or later
  8. # all caps variables are meant to be user supplied (ie. from config
  9. # file) and are considered global
  10. ########################################################################
  11. # managed directories
  12. ETC="/etc/monkeysphere"
  13. export ETC
  14. CACHE="/var/cache/monkeysphere"
  15. export CACHE
  16. ########################################################################
  17. failure() {
  18. echo "$1" >&2
  19. exit ${2:-'1'}
  20. }
  21. # write output to stderr
  22. log() {
  23. echo -n "ms: " 1>&2
  24. echo "$@" 1>&2
  25. }
  26. # cut out all comments(#) and blank lines from standard input
  27. meat() {
  28. grep -v -e "^[[:space:]]*#" -e '^$'
  29. }
  30. # cut a specified line from standard input
  31. cutline() {
  32. head --line="$1" | tail -1
  33. }
  34. # retrieve all keys with given user id from keyserver
  35. # FIXME: need to figure out how to retrieve all matching keys
  36. # (not just first 5)
  37. gpg_fetch_keys() {
  38. local id
  39. id="$1"
  40. echo 1,2,3,4,5 | \
  41. gpg --quiet --batch --command-fd 0 --with-colons \
  42. --keyserver "$KEYSERVER" \
  43. --search ="$id" >/dev/null 2>&1
  44. }
  45. # check that characters are in a string (in an AND fashion).
  46. # used for checking key capability
  47. # check_capability capability a [b...]
  48. check_capability() {
  49. local capability
  50. local capcheck
  51. capability="$1"
  52. shift 1
  53. for capcheck ; do
  54. if echo "$capability" | grep -q -v "$capcheck" ; then
  55. return 1
  56. fi
  57. done
  58. return 0
  59. }
  60. # convert escaped characters from gpg output back into original
  61. # character
  62. # FIXME: undo all escape character translation in with-colons gpg output
  63. unescape() {
  64. echo "$1" | sed 's/\\x3a/:/'
  65. }
  66. # convert key from gpg to ssh known_hosts format
  67. gpg2known_hosts() {
  68. local keyID
  69. local host
  70. keyID="$1"
  71. host=$(echo "$2" | sed -e "s|ssh://||")
  72. # NOTE: it seems that ssh-keygen -R removes all comment fields from
  73. # all lines in the known_hosts file. why?
  74. # NOTE: just in case, the COMMENT can be matched with the
  75. # following regexp:
  76. # '^MonkeySphere[[:digit:]]{4}(-[[:digit:]]{2}){2}T[[:digit:]]{2}(:[[:digit:]]{2}){2}$'
  77. echo -n "$host "
  78. gpg --export "$keyID" | \
  79. openpgp2ssh "$keyID" | tr -d '\n'
  80. echo " MonkeySphere${DATE}"
  81. }
  82. # convert key from gpg to ssh authorized_keys format
  83. gpg2authorized_keys() {
  84. local keyID
  85. local userID
  86. keyID="$1"
  87. userID="$2"
  88. gpg --export "$keyID" | \
  89. openpgp2ssh "$keyID" | tr -d '\n'
  90. echo " MonkeySphere${DATE}:${userID}"
  91. }
  92. # userid and key policy checking
  93. # the following checks policy on the returned keys
  94. # - checks that full key has appropriate valididy (u|f)
  95. # - checks key has specified capability (REQUIRED_KEY_CAPABILITY)
  96. # - checks that particular desired user id has appropriate validity
  97. # see /usr/share/doc/gnupg/DETAILS.gz
  98. # expects global variable: "MODE"
  99. process_user_id() {
  100. local userID
  101. local cacheDir
  102. local requiredPubCapability
  103. local gpgOut
  104. local line
  105. local type
  106. local validity
  107. local keyid
  108. local uidfpr
  109. local capability
  110. local keyOK
  111. local pubKeyID
  112. local uidOK
  113. local keyIDs
  114. local userIDHash
  115. local keyID
  116. userID="$1"
  117. cacheDir="$2"
  118. requiredPubCapability=$(echo "$REQUIRED_KEY_CAPABILITY" | tr "[:lower:]" "[:upper:]")
  119. # fetch keys from keyserver, return 1 if none found
  120. gpg_fetch_keys "$userID" || return 1
  121. # output gpg info for (exact) userid and store
  122. gpgOut=$(gpg --fixed-list-mode --list-key --with-colons \
  123. ="$userID" 2> /dev/null)
  124. # return 1 if there only "tru" lines are output from gpg
  125. if [ -z "$(echo "$gpgOut" | grep -v '^tru:')" ] ; then
  126. log " key not found."
  127. return 1
  128. fi
  129. # loop over all lines in the gpg output and process.
  130. # need to do it this way (as opposed to "while read...") so that
  131. # variables set in loop will be visible outside of loop
  132. for line in $(seq 1 $(echo "$gpgOut" | wc -l)) ; do
  133. # read the contents of the line
  134. type=$(echo "$gpgOut" | cutline "$line" | cut -d: -f1)
  135. validity=$(echo "$gpgOut" | cutline "$line" | cut -d: -f2)
  136. keyid=$(echo "$gpgOut" | cutline "$line" | cut -d: -f5)
  137. uidfpr=$(echo "$gpgOut" | cutline "$line" | cut -d: -f10)
  138. capability=$(echo "$gpgOut" | cutline "$line" | cut -d: -f12)
  139. # process based on record type
  140. case $type in
  141. 'pub') # primary keys
  142. # new key, wipe the slate
  143. keyOK=
  144. pubKeyID=
  145. uidOK=
  146. keyIDs=
  147. pubKeyID="$keyid"
  148. # check primary key validity
  149. if [ "$validity" != 'u' -a "$validity" != 'f' ] ; then
  150. log " unacceptable primary key validity ($validity)."
  151. continue
  152. fi
  153. # check capability is not Disabled...
  154. if check_capability "$capability" 'D' ; then
  155. log " key disabled."
  156. continue
  157. fi
  158. # check overall key capability
  159. # must be Encryption and Authentication
  160. if ! check_capability "$capability" $requiredPubCapability ; then
  161. log " unacceptable primary key capability ($capability)."
  162. continue
  163. fi
  164. # mark if primary key is acceptable
  165. keyOK=true
  166. # add primary key ID to key list if it has required capability
  167. if check_capability "$capability" $REQUIRED_KEY_CAPABILITY ; then
  168. keyIDs[${#keyIDs[*]}]="$keyid"
  169. fi
  170. ;;
  171. 'uid') # user ids
  172. # check key ok and we have key fingerprint
  173. if [ -z "$keyOK" ] ; then
  174. continue
  175. fi
  176. # check key validity
  177. if [ "$validity" != 'u' -a "$validity" != 'f' ] ; then
  178. continue
  179. fi
  180. # check the uid matches
  181. if [ "$(unescape "$uidfpr")" != "$userID" ] ; then
  182. continue
  183. fi
  184. # mark if uid acceptable
  185. uidOK=true
  186. ;;
  187. 'sub') # sub keys
  188. # add sub key ID to key list if it has required capability
  189. if check_capability "$capability" $REQUIRED_KEY_CAPABILITY ; then
  190. keyIDs[${#keyIDs[*]}]="$keyid"
  191. fi
  192. ;;
  193. esac
  194. done
  195. # hash userid for cache file name
  196. userIDHash=$(echo "$userID" | sha1sum | awk '{ print $1 }')
  197. # touch/clear key cache file
  198. # (will be left empty if there are noacceptable keys)
  199. > "$cacheDir"/"$userIDHash"."$pubKeyID"
  200. # for each acceptable key, write an ssh key line to the
  201. # key cache file
  202. if [ "$keyOK" -a "$uidOK" -a "${keyIDs[*]}" ] ; then
  203. for keyID in ${keyIDs[@]} ; do
  204. log " acceptable key/uid found."
  205. if [ "$MODE" = 'known_hosts' ] ; then
  206. # export the key
  207. gpg2known_hosts "$keyID" "$userID" >> \
  208. "$cacheDir"/"$userIDHash"."$pubKeyID"
  209. # hash the cache file if specified
  210. if [ "$HASH_KNOWN_HOSTS" ] ; then
  211. ssh-keygen -H -f "$cacheDir"/"$userIDHash"."$pubKeyID" > /dev/null 2>&1
  212. rm "$cacheDir"/"$userIDHash"."$pubKeyID".old
  213. fi
  214. elif [ "$MODE" = 'authorized_keys' ] ; then
  215. # export the key
  216. # FIXME: needs to apply extra options for authorized_keys
  217. # lines if specified
  218. gpg2authorized_keys "$keyID" "$userID" >> \
  219. "$cacheDir"/"$userIDHash"."$pubKeyID"
  220. fi
  221. done
  222. fi
  223. # echo the path to the key cache file
  224. echo "$cacheDir"/"$userIDHash"."$pubKeyID"
  225. }
  226. # process a host for addition to a known_host file
  227. process_host() {
  228. local host
  229. local cacheDir
  230. local hostKeyCachePath
  231. host="$1"
  232. cacheDir="$2"
  233. log "processing host: '$host'"
  234. hostKeyCachePath=$(process_user_id "ssh://${host}" "$cacheDir")
  235. if [ $? = 0 ] ; then
  236. ssh-keygen -R "$host" -f "$USER_KNOWN_HOSTS"
  237. cat "$hostKeyCachePath" >> "$USER_KNOWN_HOSTS"
  238. fi
  239. }
  240. # process known_hosts file
  241. # go through line-by-line, extract each host, and process with the
  242. # host processing function
  243. process_known_hosts() {
  244. local knownHosts
  245. local cacheDir
  246. local hosts
  247. local host
  248. knownHosts="$1"
  249. cacheDir="$2"
  250. # take all the hosts from the known_hosts file (first field),
  251. # grep out all the hashed hosts (lines starting with '|')...
  252. cut -d ' ' -f 1 "$knownHosts" | \
  253. grep -v '^|.*$' | \
  254. while IFS=, read -r -a hosts ; do
  255. # ...and process each host
  256. for host in ${hosts[*]} ; do
  257. process_host "$host" "$cacheDir"
  258. done
  259. done
  260. }
  261. # update an authorized_keys file after first processing the
  262. # authorized_user_ids file
  263. update_authorized_keys() {
  264. local msAuthorizedKeys
  265. local userAuthorizedKeys
  266. local cacheDir
  267. msAuthorizedKeys="$1"
  268. userAuthorizedKeys="$2"
  269. cacheDir="$3"
  270. process_authorized_ids "$AUTHORIZED_USER_IDS" "$cacheDir"
  271. # write output key file
  272. log "writing monkeysphere authorized_keys file... "
  273. touch "$msAuthorizedKeys"
  274. if [ "$(ls "$cacheDir")" ] ; then
  275. log -n "adding gpg keys... "
  276. cat "$cacheDir"/* > "$msAuthorizedKeys"
  277. echo "done."
  278. else
  279. log "no gpg keys to add."
  280. fi
  281. if [ "$userAuthorizedKeys" -a -s "$userAuthorizedKeys" ] ; then
  282. log -n "adding user authorized_keys file... "
  283. cat "$userAuthorizedKeys" >> "$msAuthorizedKeys"
  284. echo "done."
  285. fi
  286. log "monkeysphere authorized_keys file generated: $msAuthorizedKeys"
  287. }
  288. # process an authorized_*_ids file
  289. # go through line-by-line, extract each userid, and process
  290. process_authorized_ids() {
  291. local authorizedIDs
  292. local cacheDir
  293. local userID
  294. authorizedIDs="$1"
  295. cacheDir="$2"
  296. # clean out keys file and remake keys directory
  297. rm -rf "$cacheDir"
  298. mkdir -p "$cacheDir"
  299. # loop through all user ids in file
  300. # FIXME: needs to handle authorized_keys options
  301. cat "$authorizedIDs" | meat | \
  302. while read -r userID ; do
  303. # process the userid
  304. log "processing userid: '$userID'"
  305. process_user_id "$userID" "$cacheDir" > /dev/null
  306. done
  307. }
  308. # EXPERIMENTAL (unused) process userids found in authorized_keys file
  309. # go through line-by-line, extract monkeysphere userids from comment
  310. # fields, and process each userid
  311. process_userids_from_authorized_keys() {
  312. local authorizedKeys
  313. local cacheDir
  314. local userID
  315. authorizedKeys="$1"
  316. cacheDir="$2"
  317. # take all the monkeysphere userids from the authorized_keys file
  318. # comment field (third field) that starts with "MonkeySphere uid:"
  319. # FIXME: needs to handle authorized_keys options (field 0)
  320. cat "$authorizedKeys" | \
  321. while read -r options keytype key comment ; do
  322. # if the comment field is empty, assume the third field was
  323. # the comment
  324. if [ -z "$comment" ] ; then
  325. comment="$key"
  326. fi
  327. if ! echo "$comment" | grep '^MonkeySphere userID:.*$' ; then
  328. continue
  329. fi
  330. userID=$(echo "$comment" | sed -e "/^MonkeySphere userID://")
  331. if [ -z "$userID" ] ; then
  332. continue
  333. fi
  334. # process the userid
  335. log "processing userid: '$userID'"
  336. process_user_id "$userID" "$cacheDir" > /dev/null
  337. done
  338. }
  339. # update the cache for userid, and prompt to add file to
  340. # authorized_user_ids file if the userid is found in gpg
  341. # and not already in file.
  342. update_userid() {
  343. local userID
  344. local cacheDir
  345. local userIDKeyCache
  346. userID="$1"
  347. cacheDir="$2"
  348. log "processing userid: '$userID'"
  349. userIDKeyCache=$(process_user_id "$userID" "$cacheDir")
  350. if [ -z "$userIDKeyCache" ] ; then
  351. return 1
  352. fi
  353. if ! grep -q "^${userID}\$" "$AUTHORIZED_USER_IDS" ; then
  354. echo "the following userid is not in the authorized_user_ids file:"
  355. echo " $userID"
  356. read -p "would you like to add? [Y|n]: " OK; OK=${OK:=Y}
  357. if [ ${OK/y/Y} = 'Y' ] ; then
  358. log -n " adding userid to authorized_user_ids file... "
  359. echo "$userID" >> "$AUTHORIZED_USER_IDS"
  360. echo "done."
  361. fi
  362. fi
  363. }
  364. # retrieve key from web of trust, and set owner trust to "full"
  365. # if key is found.
  366. trust_key() {
  367. # get the key from the key server
  368. gpg --keyserver "$KEYSERVER" --recv-key "$keyID" || failure "could not retrieve key '$keyID'"
  369. # edit the key to change trust
  370. # FIXME: need to figure out how to automate this,
  371. # in a batch mode or something.
  372. gpg --edit-key "$keyID"
  373. }