summaryrefslogtreecommitdiff
path: root/src/common
blob: 64d28cb221871c351d798c31d74f908bcb3b8265 (plain)
  1. # -*-shell-script-*-
  2. # Shared sh 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 userID
  39. userID="$1"
  40. log "checking keyserver $KEYSERVER..."
  41. echo 1,2,3,4,5 | \
  42. gpg --quiet --batch --command-fd 0 --with-colons \
  43. --keyserver "$KEYSERVER" \
  44. --search ="$userID" >/dev/null 2>&1
  45. if [ "$?" = 0 ] ; then
  46. log " user ID found on keyserver."
  47. return 0
  48. else
  49. log " user ID not found on keyserver."
  50. return 1
  51. fi
  52. }
  53. # check that characters are in a string (in an AND fashion).
  54. # used for checking key capability
  55. # check_capability capability a [b...]
  56. check_capability() {
  57. local capability
  58. local capcheck
  59. capability="$1"
  60. shift 1
  61. for capcheck ; do
  62. if echo "$capability" | grep -q -v "$capcheck" ; then
  63. return 1
  64. fi
  65. done
  66. return 0
  67. }
  68. # get the full fingerprint of a key ID
  69. get_key_fingerprint() {
  70. local keyID
  71. keyID="$1"
  72. gpg --list-key --with-colons --fixed-list-mode \
  73. --with-fingerprint "$keyID" | grep "$keyID" | \
  74. grep '^fpr:' | cut -d: -f10
  75. }
  76. # convert escaped characters from gpg output back into original
  77. # character
  78. # FIXME: undo all escape character translation in with-colons gpg output
  79. unescape() {
  80. echo "$1" | sed 's/\\x3a/:/'
  81. }
  82. # convert key from gpg to ssh known_hosts format
  83. gpg2known_hosts() {
  84. local keyID
  85. local host
  86. keyID="$1"
  87. host=$(echo "$2" | sed -e "s|ssh://||")
  88. # NOTE: it seems that ssh-keygen -R removes all comment fields from
  89. # all lines in the known_hosts file. why?
  90. # NOTE: just in case, the COMMENT can be matched with the
  91. # following regexp:
  92. # '^MonkeySphere[[:digit:]]{4}(-[[:digit:]]{2}){2}T[[:digit:]]{2}(:[[:digit:]]{2}){2}$'
  93. echo -n "$host "
  94. gpg --export "$keyID" | \
  95. openpgp2ssh "$keyID" | tr -d '\n'
  96. echo " MonkeySphere${DATE}"
  97. }
  98. # convert key from gpg to ssh authorized_keys format
  99. gpg2authorized_keys() {
  100. local keyID
  101. local userID
  102. keyID="$1"
  103. userID="$2"
  104. gpg --export "$keyID" | \
  105. openpgp2ssh "$keyID" | tr -d '\n'
  106. echo " MonkeySphere${DATE}: ${userID}"
  107. }
  108. # userid and key policy checking
  109. # the following checks policy on the returned keys
  110. # - checks that full key has appropriate valididy (u|f)
  111. # - checks key has specified capability (REQUIRED_*_KEY_CAPABILITY)
  112. # - checks that particular desired user id has appropriate validity
  113. # see /usr/share/doc/gnupg/DETAILS.gz
  114. # expects global variable: "MODE"
  115. process_user_id() {
  116. local userID
  117. local cacheDir
  118. local requiredCapability
  119. local requiredPubCapability
  120. local gpgOut
  121. local userIDHash
  122. local keyCacheDir
  123. local line
  124. local type
  125. local validity
  126. local keyid
  127. local uidfpr
  128. local usage
  129. local keyOK
  130. local pubKeyID
  131. local uidOK
  132. local keyIDs
  133. local keyID
  134. userID="$1"
  135. cacheDir="$2"
  136. # set the required key capability based on the mode
  137. if [ "$MODE" = 'known_hosts' ] ; then
  138. requiredCapability="$REQUIRED_HOST_KEY_CAPABILITY"
  139. elif [ "$MODE" = 'authorized_keys' ] ; then
  140. requiredCapability="$REQUIRED_USER_KEY_CAPABILITY"
  141. fi
  142. requiredPubCapability=$(echo "$requiredCapability" | tr "[:lower:]" "[:upper:]")
  143. # if CHECK_KEYSERVER variable set, check the keyserver
  144. # for the user ID
  145. if [ "$CHECK_KEYSERVER" = "true" ] ; then
  146. gpg_fetch_userid "$userID"
  147. fi
  148. # output gpg info for (exact) userid and store
  149. gpgOut=$(gpg --list-key --fixed-list-mode --with-colon \
  150. --with-fingerprint --with-fingerprint \
  151. ="$userID" 2>/dev/null)
  152. # if the gpg query return code is not 0, return 1
  153. if [ "$?" -ne 0 ] ; then
  154. log " key not found."
  155. return 1
  156. fi
  157. echo "$gpgOut"
  158. # loop over all lines in the gpg output and process.
  159. # need to do it this way (as opposed to "while read...") so that
  160. # variables set in loop will be visible outside of loop
  161. echo "$gpgOut" | cut -d: -f1,2,5,10,12 | \
  162. while IFS=: read -r type validity keyid uidfpr usage ; do
  163. # process based on record type
  164. case $type in
  165. 'pub') # primary keys
  166. # new key, wipe the slate
  167. keyOK=
  168. uidOK=
  169. pubKeyOK=
  170. fingerprint=
  171. # if overall key is not valid, skip
  172. if [ "$validity" != 'u' -a "$validity" != 'f' ] ; then
  173. log " unacceptable primary key validity ($validity)."
  174. continue
  175. fi
  176. # if overall key is disabled, skip
  177. if check_capability "$usage" 'D' ; then
  178. log " key disabled."
  179. continue
  180. fi
  181. # if overall key capability is not ok, skip
  182. if ! check_capability "$usage" $requiredPubCapability ; then
  183. log " unacceptable primary key capability ($usage)."
  184. continue
  185. fi
  186. # mark overall key as ok
  187. keyOK=true
  188. # mark primary key as ok if capability is ok
  189. if check_capability "$usage" $requiredCapability ; then
  190. pubKeyOK=true
  191. fi
  192. ;;
  193. 'uid') # user ids
  194. # if the overall key is not ok, skip
  195. if [ -z "$keyOK" ] ; then
  196. continue
  197. fi
  198. # if an acceptable user ID was already found, skip
  199. if [ "$uidOK" ] ; then
  200. continue
  201. fi
  202. # if the user ID does not match, skip
  203. if [ "$(unescape "$uidfpr")" != "$userID" ] ; then
  204. continue
  205. fi
  206. # if the user ID validity is not ok, skip
  207. if [ "$validity" != 'u' -a "$validity" != 'f' ] ; then
  208. continue
  209. fi
  210. # mark user ID acceptable
  211. uidOK=true
  212. # output a line for the primary key
  213. # 0 = ok, 1 = bad
  214. if [ "$keyOK" -a "$uidOK" -a "$pubKeyOK" ] ; then
  215. log " acceptable key found"
  216. echo 0 "$fingerprint"
  217. else
  218. echo 1 "$fingerprint"
  219. fi
  220. ;;
  221. 'sub') # sub keys
  222. # unset acceptability of last key
  223. subKeyOK=
  224. fingerprint=
  225. # if the overall key is not ok, skip
  226. if [ -z "$keyOK" ] ; then
  227. continue
  228. fi
  229. # if sub key validity is not ok, skip
  230. if [ "$validity" != 'u' -a "$validity" != 'f' ] ; then
  231. continue
  232. fi
  233. # if sub key capability is not ok, skip
  234. if ! check_capability "$usage" $requiredCapability ; then
  235. continue
  236. fi
  237. # mark sub key as ok
  238. subKeyOK=true
  239. ;;
  240. 'fpr') # key fingerprint
  241. fingerprint="$uidfpr"
  242. # output a line for the last subkey
  243. # 0 = ok, 1 = bad
  244. if [ "$keyOK" -a "$uidOK" -a "$subKeyOK" ] ; then
  245. log " acceptable key found"
  246. echo 0 "$fingerprint"
  247. else
  248. echo 1 "$fingerprint"
  249. fi
  250. ;;
  251. esac
  252. done
  253. }
  254. # update the cache for userid, and prompt to add file to
  255. # authorized_user_ids file if the userid is found in gpg
  256. # and not already in file.
  257. update_userid() {
  258. local userID
  259. local cacheDir
  260. local keyCache
  261. userID="$1"
  262. cacheDir="$2"
  263. log "processing userid: '$userID'"
  264. # return 1 if there is no output of the user ID processing
  265. # ie. no key was found
  266. keyCachePath=$(process_user_id "$userID" "$cacheDir")
  267. if [ -z "$keyCachePath" ] ; then
  268. return 1
  269. fi
  270. # check if user ID is in the authorized_user_ids file
  271. if ! grep -q "^${userID}\$" "$AUTHORIZED_USER_IDS" ; then
  272. read -p "user ID not currently authorized. authorize? [Y|n]: " OK; OK=${OK:=Y}
  273. if [ ${OK/y/Y} = 'Y' ] ; then
  274. # add if specified
  275. log -n "adding user ID to authorized_user_ids file... "
  276. echo "$userID" >> "$AUTHORIZED_USER_IDS"
  277. echo "done."
  278. else
  279. # else do nothing
  280. log "authorized_user_ids file untouched."
  281. fi
  282. fi
  283. }
  284. # remove a userid from the authorized_user_ids file
  285. remove_userid() {
  286. local userID
  287. userID="$1"
  288. log "processing userid: '$userID'"
  289. # check if user ID is in the authorized_user_ids file
  290. if ! grep -q "^${userID}\$" "$AUTHORIZED_USER_IDS" ; then
  291. log "user ID not currently authorized."
  292. return 1
  293. fi
  294. # remove user ID from file
  295. log -n "removing user ID '$userID'... "
  296. grep -v "$userID" "$AUTHORIZED_USER_IDS" | sponge "$AUTHORIZED_USER_IDS"
  297. echo "done."
  298. }
  299. # remove all keys from specified key cache from known_hosts file
  300. remove_known_hosts_host_keys() {
  301. local keyCachePath
  302. local hosts
  303. local type
  304. local key
  305. local comment
  306. keyCachePath="$1"
  307. meat "${keyCachePath}/keys" | \
  308. while read -r hosts type key comment ; do
  309. grep -v "$key" "$USER_KNOWN_HOSTS" | sponge "$USER_KNOWN_HOSTS"
  310. done
  311. }
  312. # process a host for addition to a known_host file
  313. process_host() {
  314. local host
  315. local cacheDir
  316. local keyCachePath
  317. host="$1"
  318. cacheDir="$2"
  319. log "processing host: $host"
  320. userID="ssh://${host}"
  321. process_user_id "ssh://${host}"
  322. exit
  323. process_user_id "ssh://${host}" | \
  324. while read -r ok key ; do
  325. # remove the old host key line
  326. remove_known_hosts_host_keys "$key"
  327. # if key OK, add new host line
  328. if [ "$ok" -eq '0' ] ; then
  329. known_hosts_line "$host" "$key" >> "$USER_KNOWN_HOSTS"
  330. fi
  331. done
  332. }
  333. # process known_hosts file
  334. # go through line-by-line, extract each host, and process with the
  335. # host processing function
  336. process_known_hosts() {
  337. local cacheDir
  338. local hosts
  339. local host
  340. cacheDir="$1"
  341. # take all the hosts from the known_hosts file (first field),
  342. # grep out all the hashed hosts (lines starting with '|')...
  343. meat "$USER_KNOWN_HOSTS" | cut -d ' ' -f 1 | grep -v '^|.*$' | \
  344. while IFS=, read -r -a hosts ; do
  345. # ...and process each host
  346. for host in ${hosts[*]} ; do
  347. process_host "$host" "$cacheDir"
  348. done
  349. done
  350. }
  351. # update an authorized_keys file after first processing the
  352. # authorized_user_ids file
  353. update_authorized_keys() {
  354. local msAuthorizedKeys
  355. local userAuthorizedKeys
  356. local cacheDir
  357. msAuthorizedKeys="$1"
  358. userAuthorizedKeys="$2"
  359. cacheDir="$3"
  360. process_authorized_ids "$AUTHORIZED_USER_IDS" "$cacheDir"
  361. # write output key file
  362. log "writing monkeysphere authorized_keys file... "
  363. touch "$msAuthorizedKeys"
  364. if [ "$(ls "$cacheDir")" ] ; then
  365. log -n "adding gpg keys... "
  366. cat "$cacheDir"/* > "$msAuthorizedKeys"
  367. echo "done."
  368. else
  369. log "no gpg keys to add."
  370. fi
  371. if [ "$userAuthorizedKeys" != "-" -a -s "$userAuthorizedKeys" ] ; then
  372. log -n "adding user authorized_keys file... "
  373. cat "$userAuthorizedKeys" >> "$msAuthorizedKeys"
  374. echo "done."
  375. fi
  376. log "monkeysphere authorized_keys file generated:"
  377. log "$msAuthorizedKeys"
  378. }
  379. # process an authorized_*_ids file
  380. # go through line-by-line, extract each userid, and process
  381. process_authorized_ids() {
  382. local authorizedIDs
  383. local cacheDir
  384. local userID
  385. authorizedIDs="$1"
  386. cacheDir="$2"
  387. process_user_id "$userID" | \
  388. while read -r ok key ; do
  389. # remove the old host key line
  390. remove_authorized_keys_user_keys "$key"
  391. # if key OK, add new host line
  392. if [ "$ok" -eq '0' ] ; then
  393. authorized_keys_line "$userID" "$key" >> "$USER_AUTHORIZED_KEYS"
  394. fi
  395. done
  396. }
  397. # EXPERIMENTAL (unused) process userids found in authorized_keys file
  398. # go through line-by-line, extract monkeysphere userids from comment
  399. # fields, and process each userid
  400. process_authorized_keys() {
  401. local authorizedKeys
  402. local cacheDir
  403. local userID
  404. authorizedKeys="$1"
  405. cacheDir="$2"
  406. # take all the monkeysphere userids from the authorized_keys file
  407. # comment field (third field) that starts with "MonkeySphere uid:"
  408. # FIXME: needs to handle authorized_keys options (field 0)
  409. cat "$authorizedKeys" | \
  410. while read -r options keytype key comment ; do
  411. # if the comment field is empty, assume the third field was
  412. # the comment
  413. if [ -z "$comment" ] ; then
  414. comment="$key"
  415. fi
  416. if ! echo "$comment" | grep '^MonkeySphere userID:.*$' ; then
  417. continue
  418. fi
  419. userID=$(echo "$comment" | sed -e "/^MonkeySphere userID://")
  420. if [ -z "$userID" ] ; then
  421. continue
  422. fi
  423. # process the userid
  424. log "processing userid: '$userID'"
  425. process_user_id "$userID" "$cacheDir" > /dev/null
  426. done
  427. }
  428. # retrieve key from web of trust, and set owner trust to "full"
  429. # if key is found.
  430. trust_key() {
  431. # get the key from the key server
  432. if ! gpg --keyserver "$KEYSERVER" --recv-key "$keyID" ; then
  433. log "could not retrieve key '$keyID'"
  434. return 1
  435. fi
  436. # get key fingerprint
  437. fingerprint=$(get_key_fingerprint "$keyID")
  438. # attach a "non-exportable" signature to the key
  439. # this is required for the key to have any validity at all
  440. # the 'y's on stdin indicates "yes, i really want to sign"
  441. echo -e 'y\ny' | gpg --lsign-key --command-fd 0 "$fingerprint"
  442. # import "full" trust for fingerprint into gpg
  443. echo ${fingerprint}:5: | gpg --import-ownertrust
  444. if [ $? = 0 ] ; then
  445. log "owner trust updated."
  446. else
  447. failure "there was a problem changing owner trust."
  448. fi
  449. }
  450. # publish server key to keyserver
  451. publish_server_key() {
  452. read -p "really publish key to $KEYSERVER? [y|N]: " OK; OK=${OK:=N}
  453. if [ ${OK/y/Y} != 'Y' ] ; then
  454. failure "aborting."
  455. fi
  456. # publish host key
  457. # FIXME: need to figure out better way to identify host key
  458. # dummy command so as not to publish fakes keys during testing
  459. # eventually:
  460. #gpg --send-keys --keyserver "$KEYSERVER" $(hostname -f)
  461. echo "NOT PUBLISHED: gpg --send-keys --keyserver $KEYSERVER $(hostname -f)"
  462. }