clevis.in 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224
  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. # Wait for askpass, and then try and decrypt immediately. Just in case
  91. # there are multiple devices that need decrypting, this will loop
  92. # infinitely (The local-bottom script will kill this after decryption)
  93. clevisloop() {
  94. # Set the path how we want it (Probably not all needed)
  95. PATH="/sbin:/usr/sbin:/bin:/usr/bin:/usr/local/bin"
  96. if [ -x /bin/plymouth ] && plymouth --ping; then
  97. cryptkeyscript='plymouth ask-for-password'
  98. else
  99. # This has to be escaped for awk
  100. cryptkeyscript='\/lib\/cryptsetup\/askpass'
  101. fi
  102. OLD_CRYPTTAB_SOURCE=""
  103. while true; do
  104. until [ "$pid" ] && [ -p "$PASSFIFO" ]; do
  105. sleep .1
  106. pid_fifo=$(get_askpass_pid)
  107. pid=$(echo "${pid_fifo}" | cut -d' ' -f1)
  108. PASSFIFO=$(echo "${pid_fifo}" | cut -d' ' -f2-)
  109. done
  110. # Import CRYPTTAB_SOURCE from the askpass process.
  111. local CRYPTTAB_SOURCE="$(cat /proc/${pid}/environ 2> /dev/null | \
  112. tr '\0' '\n' | grep '^CRYPTTAB_SOURCE=' | cut -d= -f2)"
  113. [ -n "$CRYPTTAB_SOURCE" ] || continue
  114. # Make sure that CRYPTTAB_SOURCE is actually a block device
  115. [ ! -b "$CRYPTTAB_SOURCE" ] && continue
  116. sleep .1
  117. # Make the source has changed if needed
  118. [ "$CRYPTTAB_SOURCE" = "$OLD_CRYPTTAB_SOURCE" ] && continue
  119. OLD_CRYPTTAB_SOURCE="$CRYPTTAB_SOURCE"
  120. if cryptsetup isLuks --type luks1 "$CRYPTTAB_SOURCE"; then
  121. # If the device is not initialized, sliently skip it.
  122. luksmeta test -d "$CRYPTTAB_SOURCE" || continue
  123. if luks1_decrypt "${CRYPTTAB_SOURCE}" "${PASSFIFO}"; then
  124. echo "Unlocked ${CRYPTTAB_SOURCE} with clevis"
  125. else
  126. OLD_CRYPTTAB_SOURCE=""
  127. sleep 5
  128. fi
  129. elif cryptsetup isLuks --type luks2 "$CRYPTTAB_SOURCE"; then
  130. if luks2_decrypt "${CRYPTTAB_SOURCE}" "${PASSFIFO}"; then
  131. echo "Unlocked ${CRYPTTAB_SOURCE} with clevis"
  132. else
  133. OLD_CRYPTTAB_SOURCE=""
  134. sleep 5
  135. fi
  136. fi
  137. # Now that the current device has its password, let's sleep a
  138. # bit. This gives cryptsetup time to actually decrypt the
  139. # device and prompt for the next password if needed.
  140. sleep .5
  141. done
  142. }
  143. . /scripts/functions
  144. # This is a copy of 'all_netbootable_devices/all_non_enslaved_devices' for
  145. # platforms that might not provide it.
  146. clevis_all_netbootable_devices() {
  147. for device in /sys/class/net/*; do
  148. if [ ! -e "$device/flags" ]; then
  149. continue
  150. fi
  151. loop=$(($(cat "$device/flags") & 0x8 && 1 || 0))
  152. bc=$(($(cat "$device/flags") & 0x2 && 1 || 0))
  153. ptp=$(($(cat "$device/flags") & 0x10 && 1 || 0))
  154. # Skip any device that is a loopback
  155. if [ $loop = 1 ]; then
  156. continue
  157. fi
  158. # Skip any device that isn't a broadcast
  159. # or point-to-point.
  160. if [ $bc = 0 ] && [ $ptp = 0 ]; then
  161. continue
  162. fi
  163. # Skip any enslaved device (has "master" link
  164. # attribute on it)
  165. device=$(basename "$device")
  166. ip -o link show "$device" | grep -q -w master && continue
  167. DEVICE="$DEVICE $device"
  168. done
  169. echo "$DEVICE"
  170. }
  171. # Check if network is up before trying to configure it.
  172. eth_check() {
  173. for device in $(clevis_all_netbootable_devices); do
  174. ip link set dev "$device" up
  175. sleep 1
  176. ETH_HAS_CARRIER=$(cat /sys/class/net/"$device"/carrier)
  177. if [ "$ETH_HAS_CARRIER" = '1' ]; then
  178. return 0
  179. fi
  180. done
  181. return 1
  182. }
  183. if eth_check; then
  184. # Make sure networking is set up: if booting via nfs, it already is
  185. # Doesn't seem to work when added to clevisloop for some reason
  186. [ "$boot" = nfs ] || configure_networking
  187. fi
  188. clevisloop &
  189. echo $! >/run/clevis.pid