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