clevis-luks-common-functions 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283
  1. #!/bin/bash -e
  2. # vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80:
  3. #
  4. # Copyright (c) 2019 Red Hat, Inc.
  5. # Author: Sergio Correia <scorreia@redhat.com>
  6. #
  7. # This program is free software: you can redistribute it and/or modify
  8. # it under the terms of the GNU General Public License as published by
  9. # the Free Software Foundation, either version 3 of the License, or
  10. # (at your option) any later version.
  11. #
  12. # This program is distributed in the hope that it will be useful,
  13. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. # GNU General Public License for more details.
  16. #
  17. # You should have received a copy of the GNU General Public License
  18. # along with this program. If not, see <http://www.gnu.org/licenses/>.
  19. #
  20. # valid_slot() will check whether a given slot is possibly valid, i.e., if it
  21. # is a numeric value within the specified range.
  22. valid_slot() {
  23. local SLT="${1}"
  24. local MAX_SLOTS="${2}"
  25. case "${SLT}" in
  26. ''|*[!0-9]*)
  27. return 1
  28. ;;
  29. *)
  30. # We got an integer, now let's make sure it is within the
  31. # supported range.
  32. if [ "${SLT}" -ge "${MAX_SLOTS}" ]; then
  33. return 1
  34. fi
  35. ;;
  36. esac
  37. }
  38. # clevis_luks_read_slot() will read a particular slot of a given device, which
  39. # should be either LUKS1 or LUKS2. Returns 1 in case of failure; 0 in case of
  40. # success.
  41. clevis_luks_read_slot() {
  42. local DEV="${1}"
  43. local SLT="${2}"
  44. if [ -z "${DEV}" ] || [ -z "${SLT}" ]; then
  45. echo "Need both a device and a slot as arguments." >&2
  46. return 1
  47. fi
  48. local DATA_CODED=''
  49. local MAX_LUKS1_SLOTS=8
  50. local MAX_LUKS2_SLOTS=32
  51. if cryptsetup isLuks --type luks1 "${DEV}"; then
  52. if ! valid_slot "${SLT}" "${MAX_LUKS1_SLOTS}"; then
  53. echo "Please, provide a valid key slot number; 0-7 for LUKS1" >&2
  54. return 1
  55. fi
  56. if ! luksmeta test -d "${DEV}"; then
  57. echo "The ${DEV} device is not valid!" >&2
  58. return 1
  59. fi
  60. local CLEVIS_UUID="cb6e8904-81ff-40da-a84a-07ab9ab5715e"
  61. local uuid
  62. # Pattern from luksmeta: active slot uuid.
  63. read -r _ _ uuid <<< "$(luksmeta show -d "${DEV}" | grep "^${SLT} *")"
  64. if [ "${uuid}" != ${CLEVIS_UUID}"" ]; then
  65. echo "Not a clevis slot!" >&2
  66. return 1
  67. fi
  68. if ! DATA_CODED="$(luksmeta load -d "${DEV}" -s "${SLT}")"; then
  69. echo "Cannot load data from ${DEV} slot:${SLT}!" >&2
  70. return 1
  71. fi
  72. elif cryptsetup isLuks --type luks2 "${DEV}"; then
  73. if ! valid_slot "${SLT}" "${MAX_LUKS2_SLOTS}"; then
  74. echo "Please, provide a valid key slot number; 0-31 for LUKS2" >&2
  75. return 1
  76. fi
  77. local token_id
  78. token_id=$(cryptsetup luksDump "${DEV}" \
  79. | grep -E -B1 "^\s+Keyslot:\s+${SLT}$" \
  80. | head -n 1 | sed -rn 's|^\s+([0-9]+): clevis|\1|p')
  81. if [ -z "${token_id}" ]; then
  82. echo "Cannot load data from ${DEV} slot:${SLT}. No token found!" >&2
  83. return 1
  84. fi
  85. local token
  86. token=$(cryptsetup token export --token-id "${token_id}" "${DEV}")
  87. DATA_CODED=$(jose fmt -j- -Og jwe -o- <<< "${token}" \
  88. | jose jwe fmt -i- -c)
  89. if [ -z "${DATA_CODED}" ]; then
  90. echo "Cannot load data from ${DEV} slot:${SLT}!" >&2
  91. return 1
  92. fi
  93. else
  94. echo "${DEV} is not a supported LUKS device!" >&2
  95. return 1
  96. fi
  97. echo "${DATA_CODED}"
  98. }
  99. # clevis_luks_used_slots() will return the list of used slots for a given LUKS
  100. # device.
  101. clevis_luks_used_slots() {
  102. local DEV="${1}"
  103. local slots
  104. if cryptsetup isLuks --type luks1 "${DEV}"; then
  105. readarray -t slots < <(cryptsetup luksDump "${DEV}" \
  106. | sed -rn 's|^Key Slot ([0-7]): ENABLED$|\1|p')
  107. elif cryptsetup isLuks --type luks2 "${DEV}"; then
  108. readarray -t slots < <(cryptsetup luksDump "${DEV}" \
  109. | sed -rn 's|^\s+([0-9]+): luks2$|\1|p')
  110. else
  111. echo "${DEV} is not a supported LUKS device!" >&2
  112. return 1
  113. fi
  114. echo "${slots[@]}"
  115. }
  116. # clevis_luks_decode_jwe() will decode a given JWE.
  117. clevis_luks_decode_jwe() {
  118. local jwe="${1}"
  119. local coded
  120. if ! coded=$(jose jwe fmt -i- <<< "${jwe}"); then
  121. return 1
  122. fi
  123. coded=$(jose fmt -j- -g protected -u- <<< "${coded}" | tr -d '"')
  124. jose b64 dec -i- <<< "${coded}"
  125. }
  126. # clevis_luks_print_pin_config() will print the config of a given pin; i.e.
  127. # for tang it will display the associated url address, and for tpm2, the
  128. # properties in place, like the hash, for instance.
  129. clevis_luks_print_pin_config() {
  130. local P="${1}"
  131. local decoded="${2}"
  132. local content
  133. if ! content="$(jose fmt -j- -g clevis -g "${P}" -o- <<< "${decoded}")" \
  134. || [ -z "${content}" ]; then
  135. return 1
  136. fi
  137. local pin=
  138. case "${P}" in
  139. tang)
  140. local url
  141. url="$(jose fmt -j- -g url -u- <<< "${content}")"
  142. pin=$(printf '{"url":"%s"}' "${url}")
  143. printf "tang '%s'" "${pin}"
  144. ;;
  145. tpm2)
  146. # Valid properties for tpm2 pin are the following:
  147. # hash, key, pcr_bank, pcr_ids, pcr_digest.
  148. local key
  149. local value
  150. for key in 'hash' 'key' 'pcr_bank' 'pcr_ids' 'pcr_digest'; do
  151. if value=$(jose fmt -j- -g "${key}" -u- <<< "${content}"); then
  152. pin=$(printf '%s,"%s":"%s"' "${pin}" "${key}" "${value}")
  153. fi
  154. done
  155. # Remove possible leading comma.
  156. pin=${pin/#,/}
  157. printf "tpm2 '{%s}'" "${pin}"
  158. ;;
  159. sss)
  160. local threshold
  161. threshold=$(jose fmt -j- -Og t -o- <<< "${content}")
  162. clevis_luks_process_sss_pin "${content}" "${threshold}"
  163. ;;
  164. *)
  165. printf "unknown pin '%s'" "${P}"
  166. ;;
  167. esac
  168. }
  169. # clevis_luks_decode_pin_config() will receive a JWE and extract a pin config
  170. # from it.
  171. clevis_luks_decode_pin_config() {
  172. local jwe="${1}"
  173. local decoded
  174. if ! decoded=$(clevis_luks_decode_jwe "${jwe}"); then
  175. return 1
  176. fi
  177. local P
  178. if ! P=$(jose fmt -j- -Og clevis -g pin -u- <<< "${decoded}"); then
  179. return 1
  180. fi
  181. clevis_luks_print_pin_config "${P}" "${decoded}"
  182. }
  183. # clevis_luks_join_sss_cfg() will receive a list of configurations for a given
  184. # pin and returns it as list, in the format PIN [cfg1, cfg2, ..., cfgN].
  185. clevis_luks_join_sss_cfg() {
  186. local pin="${1}"
  187. local cfg="${2}"
  188. cfg=$(echo "${cfg}" | tr -d "'" | sed -e 's/^,//')
  189. printf '"%s":[%s]' "${pin}" "${cfg}"
  190. }
  191. # clevis_luks_process_sss_pin() will receive a JWE with information on the sss
  192. # pin config, and also its associated threshold, and will extract the info.
  193. clevis_luks_process_sss_pin() {
  194. local jwe="${1}"
  195. local threshold="${2}"
  196. local sss_tang
  197. local sss_tpm2
  198. local sss
  199. local pin_cfg
  200. local pin
  201. local cfg
  202. local coded
  203. for coded in $(jose fmt -j- -Og jwe -Af- <<< "${jwe}"| tr -d '"'); do
  204. if ! pin_cfg="$(clevis_luks_decode_pin_config "${coded}")"; then
  205. continue
  206. fi
  207. read -r pin cfg <<< "${pin_cfg}"
  208. case "${pin}" in
  209. tang)
  210. sss_tang="${sss_tang},${cfg}"
  211. ;;
  212. tpm2)
  213. sss_tpm2="${sss_tpm2},${cfg}"
  214. ;;
  215. sss)
  216. sss=$(echo "${cfg}" | tr -d "'")
  217. ;;
  218. esac
  219. done
  220. cfg=
  221. if [ -n "${sss_tang}" ]; then
  222. cfg=$(clevis_luks_join_sss_cfg "tang" "${sss_tang}")
  223. fi
  224. if [ -n "${sss_tpm2}" ]; then
  225. cfg="${cfg},"$(clevis_luks_join_sss_cfg "tpm2" "${sss_tpm2}")
  226. fi
  227. if [ -n "${sss}" ]; then
  228. cfg=$(printf '%s,"sss":%s' "${cfg}" "${sss}")
  229. fi
  230. # Remove possible leading comma.
  231. cfg=${cfg/#,/}
  232. pin=$(printf '{"t":%d,"pins":{%s}}' "${threshold}" "${cfg}")
  233. printf "sss '%s'" "${pin}"
  234. }
  235. # clevis_luks_read_pins_from_slot() will receive a given device and slot and
  236. # will then output its associated policy configuration.
  237. clevis_luks_read_pins_from_slot() {
  238. local DEV="${1}"
  239. local SLOT="${2}"
  240. local jwe
  241. if ! jwe=$(clevis_luks_read_slot "${DEV}" "${SLOT}" 2>/dev/null); then
  242. return 1
  243. fi
  244. local cfg
  245. if ! cfg="$(clevis_luks_decode_pin_config "${jwe}")"; then
  246. return 1
  247. fi
  248. printf "%s: %s\n" "${SLOT}" "${cfg}"
  249. }