clevis.in 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270
  1. #!/bin/bash
  2. #
  3. # Copyright (c) 2017 Red Hat, Inc.
  4. # Copyright (c) 2017 Shawn Rose
  5. # Copyright (c) 2017 Guilhem Moulin
  6. #
  7. # Author: Harald Hoyer <harald@redhat.com>
  8. # Author: Nathaniel McCallum <npmccallum@redhat.com>
  9. # Author: Shawn Rose <shawnandrewrose@gmail.com>
  10. # Author: Guilhem Moulin <guilhem@guilhem.org>
  11. #
  12. # This program is free software: you can redistribute it and/or modify
  13. # it under the terms of the GNU General Public License as published by
  14. # the Free Software Foundation, either version 3 of the License, or
  15. # (at your option) any later version.
  16. #
  17. # This program is distributed in the hope that it will be useful,
  18. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  19. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  20. # GNU General Public License for more details.
  21. #
  22. # You should have received a copy of the GNU General Public License
  23. # along with this program. If not, see <http://www.gnu.org/licenses/>.
  24. #
  25. case $1 in
  26. prereqs) exit 0 ;;
  27. esac
  28. # Return fifo path or nothing if not found
  29. get_fifo_path() {
  30. local pid="$1"
  31. for fd in /proc/$pid/fd/*; do
  32. if [ -e "$fd" ]; then
  33. if [[ $(readlink -f "${fd}") == *"/cryptsetup/passfifo" ]]; then
  34. readlink -f "${fd}"
  35. fi
  36. fi
  37. done
  38. }
  39. # Print the PID of the askpass process and fifo path with a file descriptor opened to
  40. get_askpass_pid() {
  41. psinfo=$(ps) # Doing this so I don't end up matching myself
  42. echo "$psinfo" | awk "/$cryptkeyscript/ { print \$1 }" | while read -r pid; do
  43. pf=$(get_fifo_path "${pid}")
  44. if [[ $pf != "" ]]; then
  45. echo "${pid} ${pf}"
  46. break
  47. fi
  48. done
  49. }
  50. luks1_decrypt() {
  51. local CRYPTTAB_SOURCE=$1
  52. local PASSFIFO=$2
  53. UUID=cb6e8904-81ff-40da-a84a-07ab9ab5715e
  54. luksmeta show -d "$CRYPTTAB_SOURCE" | while read -r slot state uuid; do
  55. [ "$state" == "active" ] || continue
  56. [ "$uuid" == "$UUID" ] || continue
  57. lml=$(luksmeta load -d "${CRYPTTAB_SOURCE}" -s "${slot}" -u "${UUID}")
  58. [ $? -eq 0 ] || continue
  59. decrypted=$(echo -n "${lml}" | clevis decrypt 2>/dev/null)
  60. [ $? -eq 0 ] || continue
  61. # Fail safe
  62. [ "$decrypted" != "" ] || continue
  63. echo -n "${decrypted}" >"$PASSFIFO"
  64. return 0
  65. done
  66. return 1
  67. }
  68. luks2_decrypt() {
  69. local CRYPTTAB_SOURCE=$1
  70. local PASSFIFO=$2
  71. cryptsetup luksDump "$CRYPTTAB_SOURCE" | sed -rn 's|^\s+([0-9]+): clevis|\1|p' | while read -r id; do
  72. # jose jwe fmt -c outputs extra \n, so clean it up
  73. cte=$(cryptsetup token export --token-id "$id" "$CRYPTTAB_SOURCE")
  74. [ $? -eq 0 ] || continue
  75. josefmt=$(echo "${cte}" | jose fmt -j- -Og jwe -o-)
  76. [ $? -eq 0 ] || continue
  77. josejwe=$(echo "${josefmt}" | jose jwe fmt -i- -c)
  78. [ $? -eq 0 ] || continue
  79. jwe=$(echo "${josejwe}" | tr -d '\n')
  80. [ $? -eq 0 ] || continue
  81. decrypted=$(echo -n "${jwe}" | clevis decrypt 2>/dev/null)
  82. [ $? -eq 0 ] || continue
  83. # Fail safe
  84. [ "$decrypted" != "" ] || continue
  85. echo -n "${decrypted}" >"$PASSFIFO"
  86. return 0
  87. done
  88. return 1
  89. }
  90. has_tang_pin() {
  91. local dev="$1"
  92. clevis luks list -d "${dev}" | grep -q tang
  93. }
  94. # Wait for askpass, and then try and decrypt immediately. Just in case
  95. # there are multiple devices that need decrypting, this will loop
  96. # infinitely (The local-bottom script will kill this after decryption)
  97. clevisloop() {
  98. # Set the path how we want it (Probably not all needed)
  99. PATH="/sbin:/usr/sbin:/bin:/usr/bin:/usr/local/bin"
  100. if [ -x /bin/plymouth ] && plymouth --ping; then
  101. cryptkeyscript='plymouth ask-for-password'
  102. else
  103. # This has to be escaped for awk
  104. cryptkeyscript='\/lib\/cryptsetup\/askpass'
  105. fi
  106. OLD_CRYPTTAB_SOURCE=""
  107. local netcfg_attempted=0
  108. while true; do
  109. # Re-get the askpass PID in case there are multiple encrypted devices
  110. pid=""
  111. until [ "$pid" ] && [ -p "$PASSFIFO" ]; do
  112. sleep .1
  113. pid_fifo=$(get_askpass_pid)
  114. pid=$(echo "${pid_fifo}" | cut -d' ' -f1)
  115. PASSFIFO=$(echo "${pid_fifo}" | cut -d' ' -f2-)
  116. done
  117. # Import CRYPTTAB_SOURCE from the askpass process.
  118. local CRYPTTAB_SOURCE="$(cat /proc/${pid}/environ 2> /dev/null | \
  119. tr '\0' '\n' | grep '^CRYPTTAB_SOURCE=' | cut -d= -f2)"
  120. [ -n "$CRYPTTAB_SOURCE" ] || continue
  121. # Make sure that CRYPTTAB_SOURCE is actually a block device
  122. [ ! -b "$CRYPTTAB_SOURCE" ] && continue
  123. sleep .1
  124. # Make the source has changed if needed
  125. [ "$CRYPTTAB_SOURCE" = "$OLD_CRYPTTAB_SOURCE" ] && continue
  126. OLD_CRYPTTAB_SOURCE="$CRYPTTAB_SOURCE"
  127. if [ $netcfg_attempted -eq 0 ] && has_tang_pin ${CRYPTTAB_SOURCE}; then
  128. netcfg_attempted=1
  129. do_configure_networking
  130. fi
  131. if cryptsetup isLuks --type luks1 "$CRYPTTAB_SOURCE"; then
  132. # If the device is not initialized, sliently skip it.
  133. luksmeta test -d "$CRYPTTAB_SOURCE" || continue
  134. if luks1_decrypt "${CRYPTTAB_SOURCE}" "${PASSFIFO}"; then
  135. echo "Unlocked ${CRYPTTAB_SOURCE} with clevis"
  136. else
  137. OLD_CRYPTTAB_SOURCE=""
  138. sleep 5
  139. fi
  140. elif cryptsetup isLuks --type luks2 "$CRYPTTAB_SOURCE"; then
  141. if luks2_decrypt "${CRYPTTAB_SOURCE}" "${PASSFIFO}"; then
  142. echo "Unlocked ${CRYPTTAB_SOURCE} with clevis"
  143. else
  144. OLD_CRYPTTAB_SOURCE=""
  145. sleep 5
  146. fi
  147. fi
  148. # Now that the current device has its password, let's sleep a
  149. # bit. This gives cryptsetup time to actually decrypt the
  150. # device and prompt for the next password if needed.
  151. sleep .5
  152. done
  153. }
  154. . /scripts/functions
  155. # This is a copy of 'all_netbootable_devices/all_non_enslaved_devices' for
  156. # platforms that might not provide it.
  157. clevis_all_netbootable_devices() {
  158. for device in /sys/class/net/*; do
  159. if [ ! -e "$device/flags" ]; then
  160. continue
  161. fi
  162. loop=$(($(cat "$device/flags") & 0x8 && 1 || 0))
  163. bc=$(($(cat "$device/flags") & 0x2 && 1 || 0))
  164. ptp=$(($(cat "$device/flags") & 0x10 && 1 || 0))
  165. # Skip any device that is a loopback
  166. if [ $loop = 1 ]; then
  167. continue
  168. fi
  169. # Skip any device that isn't a broadcast
  170. # or point-to-point.
  171. if [ $bc = 0 ] && [ $ptp = 0 ]; then
  172. continue
  173. fi
  174. # Skip any enslaved device (has "master" link
  175. # attribute on it)
  176. device=$(basename "$device")
  177. ip -o link show "$device" | grep -q -w master && continue
  178. if [ -z "$DEVICE" ]; then
  179. DEVICE="$device"
  180. else
  181. DEVICE="$DEVICE $device"
  182. fi
  183. done
  184. echo "$DEVICE"
  185. }
  186. get_specified_device() {
  187. local dev="$(echo $IP | cut -d: -f6)"
  188. [ -z "$dev" ] || echo $dev
  189. }
  190. # Workaround configure_networking() not waiting long enough for an interface
  191. # to appear. This code can be removed once that has been fixed in all the
  192. # distro releases we care about.
  193. wait_for_device() {
  194. local device="$(get_specified_device)"
  195. local ret=0
  196. if [ -n "$device" ]; then
  197. log_begin_msg "clevis: Waiting for interface ${device} to become available"
  198. local netdev_wait=0
  199. while [ $netdev_wait -lt 10 ]; do
  200. if [ -e "/sys/class/net/${device}" ]; then
  201. break
  202. fi
  203. netdev_wait=$((netdev_wait + 1))
  204. sleep 1
  205. done
  206. if [ ! -e "/sys/class/net/${device}" ]; then
  207. log_failure_msg "clevis: Interface ${device} did not appear in time"
  208. ret=1
  209. fi
  210. log_end_msg
  211. fi
  212. wait_for_udev 10
  213. return $ret
  214. }
  215. do_configure_networking() {
  216. # Make sure networking is set up: if booting via nfs, it already is
  217. if [ "$boot" != nfs ] && wait_for_device; then
  218. clevis_net_cnt=$(clevis_all_netbootable_devices | tr ' ' '\n' | wc -l)
  219. if [ -z "$IP" ] && [ "$clevis_net_cnt" -gt 1 ]; then
  220. echo ""
  221. echo "clevis: Warning: multiple network interfaces available but no ip= parameter provided."
  222. fi
  223. configure_networking
  224. fi
  225. }
  226. clevisloop &
  227. echo $! >/run/clevis.pid