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