clevis-luks-common-functions 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992
  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. CLEVIS_UUID="cb6e8904-81ff-40da-a84a-07ab9ab5715e"
  21. # valid_slot() will check whether a given slot is possibly valid, i.e., if it
  22. # is a numeric value within the specified range.
  23. valid_slot() {
  24. local SLT="${1}"
  25. local MAX_SLOTS="${2}"
  26. case "${SLT}" in
  27. ''|*[!0-9]*)
  28. return 1
  29. ;;
  30. *)
  31. # We got an integer, now let's make sure it is within the
  32. # supported range.
  33. if [ "${SLT}" -ge "${MAX_SLOTS}" ]; then
  34. return 1
  35. fi
  36. ;;
  37. esac
  38. }
  39. # clevis_luks_read_slot() will read a particular slot of a given device, which
  40. # should be either LUKS1 or LUKS2. Returns 1 in case of failure; 0 in case of
  41. # success.
  42. clevis_luks_read_slot() {
  43. local DEV="${1}"
  44. local SLT="${2}"
  45. if [ -z "${DEV}" ] || [ -z "${SLT}" ]; then
  46. echo "Need both a device and a slot as arguments." >&2
  47. return 1
  48. fi
  49. local DATA_CODED=''
  50. local MAX_LUKS1_SLOTS=8
  51. local MAX_LUKS2_SLOTS=32
  52. if cryptsetup isLuks --type luks1 "${DEV}"; then
  53. if ! valid_slot "${SLT}" "${MAX_LUKS1_SLOTS}"; then
  54. echo "Please, provide a valid key slot number; 0-7 for LUKS1" >&2
  55. return 1
  56. fi
  57. if ! luksmeta test -d "${DEV}"; then
  58. echo "The ${DEV} device is not valid!" >&2
  59. return 1
  60. fi
  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. | sed -n 1p | 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. [ -z "${DEV}" ] && return 1
  104. local used_slots
  105. if cryptsetup isLuks --type luks1 "${DEV}"; then
  106. if ! used_slots=$(cryptsetup luksDump "${DEV}" 2>/dev/null \
  107. | sed -rn 's|^Key Slot ([0-7]): ENABLED$|\1|p'); then
  108. return 1
  109. fi
  110. elif cryptsetup isLuks --type luks2 "${DEV}"; then
  111. if ! used_slots=$(cryptsetup luksDump "${DEV}" 2>/dev/null \
  112. | sed -rn 's|^\s+([0-9]+): luks2$|\1|p'); then
  113. return 1
  114. fi
  115. else
  116. echo "${DEV} is not a supported LUKS device!" >&2
  117. return 1
  118. fi
  119. echo "${used_slots}"
  120. }
  121. # clevis_luks_decode_jwe() will decode a given JWE.
  122. clevis_luks_decode_jwe() {
  123. local jwe="${1}"
  124. local coded
  125. read -r -d . coded <<< "${jwe}"
  126. jose b64 dec -i- <<< "${coded}"
  127. }
  128. # clevis_luks_print_pin_config() will print the config of a given pin; i.e.
  129. # for tang it will display the associated url address, and for tpm2, the
  130. # properties in place, like the hash, for instance.
  131. clevis_luks_print_pin_config() {
  132. local P="${1}"
  133. local decoded="${2}"
  134. local content
  135. if ! content="$(jose fmt -j- -g clevis -g "${P}" -o- <<< "${decoded}")" \
  136. || [ -z "${content}" ]; then
  137. return 1
  138. fi
  139. local pin=
  140. case "${P}" in
  141. tang)
  142. local url
  143. url="$(jose fmt -j- -g url -u- <<< "${content}")"
  144. pin=$(printf '{"url":"%s"}' "${url}")
  145. printf "tang '%s'" "${pin}"
  146. ;;
  147. tpm2)
  148. # Valid properties for tpm2 pin are the following:
  149. # hash, key, pcr_bank, pcr_ids, pcr_digest.
  150. local key
  151. local value
  152. for key in 'hash' 'key' 'pcr_bank' 'pcr_ids' 'pcr_digest'; do
  153. if value=$(jose fmt -j- -g "${key}" -u- <<< "${content}"); then
  154. pin=$(printf '%s,"%s":"%s"' "${pin}" "${key}" "${value}")
  155. fi
  156. done
  157. # Remove possible leading comma.
  158. pin=${pin/#,/}
  159. printf "tpm2 '{%s}'" "${pin}"
  160. ;;
  161. sss)
  162. local threshold
  163. threshold=$(jose fmt -j- -Og t -o- <<< "${content}")
  164. clevis_luks_process_sss_pin "${content}" "${threshold}"
  165. ;;
  166. *)
  167. printf "unknown pin '%s'" "${P}"
  168. ;;
  169. esac
  170. }
  171. # clevis_luks_decode_pin_config() will receive a JWE and extract a pin config
  172. # from it.
  173. clevis_luks_decode_pin_config() {
  174. local jwe="${1}"
  175. local decoded
  176. if ! decoded=$(clevis_luks_decode_jwe "${jwe}"); then
  177. return 1
  178. fi
  179. local P
  180. if ! P=$(jose fmt -j- -Og clevis -g pin -u- <<< "${decoded}"); then
  181. return 1
  182. fi
  183. clevis_luks_print_pin_config "${P}" "${decoded}"
  184. }
  185. # clevis_luks_join_sss_cfg() will receive a list of configurations for a given
  186. # pin and returns it as list, in the format PIN [cfg1, cfg2, ..., cfgN].
  187. clevis_luks_join_sss_cfg() {
  188. local pin="${1}"
  189. local cfg="${2}"
  190. cfg=$(echo "${cfg}" | tr -d "'" | sed -e 's/^,//')
  191. printf '"%s":[%s]' "${pin}" "${cfg}"
  192. }
  193. # clevis_luks_process_sss_pin() will receive a JWE with information on the sss
  194. # pin config, and also its associated threshold, and will extract the info.
  195. clevis_luks_process_sss_pin() {
  196. local jwe="${1}"
  197. local threshold="${2}"
  198. local sss_tang
  199. local sss_tpm2
  200. local sss
  201. local pin_cfg
  202. local pin
  203. local cfg
  204. local coded
  205. for coded in $(jose fmt -j- -Og jwe -Af- <<< "${jwe}"| tr -d '"'); do
  206. if ! pin_cfg="$(clevis_luks_decode_pin_config "${coded}")"; then
  207. continue
  208. fi
  209. read -r pin cfg <<< "${pin_cfg}"
  210. case "${pin}" in
  211. tang)
  212. sss_tang="${sss_tang},${cfg}"
  213. ;;
  214. tpm2)
  215. sss_tpm2="${sss_tpm2},${cfg}"
  216. ;;
  217. sss)
  218. sss=$(echo "${cfg}" | tr -d "'")
  219. ;;
  220. esac
  221. done
  222. cfg=
  223. if [ -n "${sss_tang}" ]; then
  224. cfg=$(clevis_luks_join_sss_cfg "tang" "${sss_tang}")
  225. fi
  226. if [ -n "${sss_tpm2}" ]; then
  227. cfg="${cfg},"$(clevis_luks_join_sss_cfg "tpm2" "${sss_tpm2}")
  228. fi
  229. if [ -n "${sss}" ]; then
  230. cfg=$(printf '%s,"sss":%s' "${cfg}" "${sss}")
  231. fi
  232. # Remove possible leading comma.
  233. cfg=${cfg/#,/}
  234. pin=$(printf '{"t":%d,"pins":{%s}}' "${threshold}" "${cfg}")
  235. printf "sss '%s'" "${pin}"
  236. }
  237. # clevis_luks_read_pins_from_slot() will receive a given device and slot and
  238. # will then output its associated policy configuration.
  239. clevis_luks_read_pins_from_slot() {
  240. local DEV="${1}"
  241. local SLOT="${2}"
  242. local jwe
  243. if ! jwe=$(clevis_luks_read_slot "${DEV}" "${SLOT}" 2>/dev/null); then
  244. return 1
  245. fi
  246. local cfg
  247. if ! cfg="$(clevis_luks_decode_pin_config "${jwe}")"; then
  248. return 1
  249. fi
  250. printf "%s: %s\n" "${SLOT}" "${cfg}"
  251. }
  252. # clevis_luks_check_valid_key_or_keyfile() receives a devices and either a
  253. # passphrase or keyfile and then checks whether it is able to unlock the
  254. # device wih the received passphrase/keyfile.
  255. clevis_luks_check_valid_key_or_keyfile() {
  256. local DEV="${1}"
  257. local KEY="${2:-}"
  258. local KEYFILE="${3:-}"
  259. local SLT="${4:-}"
  260. [ -z "${DEV}" ] && return 1
  261. [ -z "${KEYFILE}" ] && [ -z "${KEY}" ] && return 1
  262. local extra_args
  263. extra_args="$([ -n "${SLT}" ] && printf -- '--key-slot %s' "${SLT}")"
  264. if [ -n "${KEYFILE}" ]; then
  265. cryptsetup open --test-passphrase "${DEV}" --key-file "${KEYFILE}" \
  266. ${extra_args}
  267. return
  268. fi
  269. printf '%s' "${KEY}" | cryptsetup open --test-passphrase "${DEV}" \
  270. ${extra_args}
  271. }
  272. # clevis_luks_unlock_device_by_slot() does the unlock of the device and slot
  273. # passed as parameters and returns the decoded passphrase.
  274. clevis_luks_unlock_device_by_slot() {
  275. local DEV="${1}"
  276. local SLT="${2}"
  277. [ -z "${DEV}" ] && return 1
  278. [ -z "${SLT}" ] && return 1
  279. local jwe passphrase
  280. if ! jwe="$(clevis_luks_read_slot "${DEV}" "${SLT}" 2>/dev/null)" \
  281. || [ -z "${jwe}" ]; then
  282. return 1
  283. fi
  284. if ! passphrase="$(printf '%s' "${jwe}" | clevis decrypt 2>/dev/null)" \
  285. || [ -z "${passphrase}" ]; then
  286. return 1
  287. fi
  288. clevis_luks_check_valid_key_or_keyfile "${DEV}" "${passphrase}" || return 1
  289. printf '%s' "${passphrase}"
  290. }
  291. # clevis_luks_unlock_device() does the unlock of the device passed as
  292. # parameter and returns the decoded passphrase.
  293. clevis_luks_unlock_device() {
  294. local DEV="${1}"
  295. [ -z "${DEV}" ] && return 1
  296. local used_slots
  297. if ! used_slots=$(clevis_luks_used_slots "${DEV}") \
  298. || [ -z "${used_slots}" ]; then
  299. return 1
  300. fi
  301. local slt pt
  302. for slt in ${used_slots}; do
  303. if ! pt=$(clevis_luks_unlock_device_by_slot "${DEV}" "${slt}") \
  304. || [ -z "${pt}" ]; then
  305. continue
  306. fi
  307. printf '%s' "${pt}"
  308. return 0
  309. done
  310. return 1
  311. }
  312. # clevis_map_device() tries to map the device received as a parameter to a
  313. # block device. As per crypttab(5), we support /path/to/encrypted/blockdev
  314. # or UUID=<uuid>.
  315. clevis_map_device() {
  316. local CDEV="${1}"
  317. if [[ "${CDEV}" == UUID=* ]]; then
  318. CDEV=/dev/disk/by-uuid/${CDEV#UUID=}
  319. fi
  320. if [[ "${CDEV}" == /* ]] && [ -b "${CDEV}" ]; then
  321. echo "${CDEV}"
  322. else
  323. # Invalid crypttab entry.
  324. return 1
  325. fi
  326. }
  327. # clevis_is_luks_device_by_uuid_open() checks whether the LUKS device whose
  328. # UUID was passed as a parameter is already open.
  329. clevis_is_luks_device_by_uuid_open() {
  330. local dev_luks_uuid="${1}"
  331. [ -z "${dev_luks_uuid}" ] && return 1
  332. dev_luks_uuid="$(echo "${dev_luks_uuid}" | sed -e 's/-//g')"
  333. test -b /dev/disk/by-id/dm-uuid-*"${dev_luks_uuid}"*
  334. }
  335. # clevis_devices_to_unlock() returns a list of devices to be unlocked, as per
  336. # the info from crypttab.
  337. clevis_devices_to_unlock() {
  338. local list_open_devices="${1:-}"
  339. [ ! -r /etc/crypttab ] && return 1
  340. local dev clevis_devices crypt_device dev_uuid bindings
  341. clevis_devices=
  342. # Build list of devices to unlock.
  343. while read -r _ crypt_device _; do
  344. if ! dev=$(clevis_map_device "${crypt_device}") \
  345. || [ -z "${dev}" ]; then
  346. # Unable to get the device - maybe it's not available, e.g. a
  347. # device on a volume group that has not been activated yet.
  348. # Add it to the list anyway, since it's a pending device.
  349. clevis_devices="${clevis_devices} ${dev}"
  350. continue
  351. fi
  352. # Check if this device has clevis bindings.
  353. if ! bindings="$(clevis luks list -d "${dev}" 2>/dev/null)" \
  354. || [ -z "${bindings}" ]; then
  355. continue
  356. fi
  357. if [ -z "${list_open_devices}" ]; then
  358. # Check if this device is already open.
  359. dev_uuid="$(cryptsetup luksUUID "${dev}")"
  360. if clevis_is_luks_device_by_uuid_open "${dev_uuid}"; then
  361. continue
  362. fi
  363. fi
  364. clevis_devices="${clevis_devices} ${dev}"
  365. done < /etc/crypttab
  366. echo "${clevis_devices}" | sed -e 's/^ //'
  367. }
  368. # clevis_luks1_save_slot() works with LUKS1 devices and it saves a given JWE
  369. # to a specific device and slot. The last parameter indicates whether we
  370. # should overwrite existing metadata.
  371. clevis_luks1_save_slot() {
  372. local DEV="${1}"
  373. local SLOT="${2}"
  374. local JWE="${3}"
  375. local SHOULD_OVERWRITE="${4:-}"
  376. luksmeta test -d "${DEV}" || return 1
  377. if luksmeta load -d "${DEV}" -s "${SLOT}" -u "${CLEVIS_UUID}" \
  378. >/dev/null 2>/dev/null; then
  379. [ -z "${SHOULD_OVERWRITE}" ] && return 1
  380. if ! luksmeta wipe -f -d "${DEV}" -s "${SLOT}" \
  381. -u "${CLEVIS_UUID}"; then
  382. echo "Error wiping slot ${SLOT} from ${DEV}" >&2
  383. return 1
  384. fi
  385. fi
  386. if ! echo -n "${JWE}" | luksmeta save -d "${DEV}" -s "${SLOT}" \
  387. -u "${CLEVIS_UUID}"; then
  388. echo "Error saving metadata to LUKSMeta slot ${SLOT} from ${DEV}" >&2
  389. return 1
  390. fi
  391. return 0
  392. }
  393. # clevis_luks2_save_slot() works with LUKS2 devices and it saves a given JWE
  394. # to a specific device and slot. The last parameter indicates whether we
  395. # should overwrite existing metadata.
  396. clevis_luks2_save_slot() {
  397. local DEV="${1}"
  398. local SLOT="${2}"
  399. local TKN_ID="${3}"
  400. local JWE="${4}"
  401. local SHOULD_OVERWRITE="${5:-}"
  402. # Sanitize clevis LUKS2 tokens. Remove "orphan" clevis tokens, i.e.,
  403. # tokens that are not linked to any key slots.
  404. local token array_len
  405. for token in $(cryptsetup luksDump "${DEV}" \
  406. | sed -rn 's|^\s+([0-9]+): clevis|\1|p'); do
  407. # Let's check the length of the "keyslots" array. If zero, it means
  408. # no key slots are linked, which is a problem.
  409. if ! array_len=$(cryptsetup token export --token-id \
  410. "${token}" "${DEV}" \
  411. | jose fmt --json=- --get keyslots --array --length \
  412. --output=-) || [ "${array_len}" -eq 0 ]; then
  413. # Remove bad token.
  414. cryptsetup token remove --token-id "${token}" "${DEV}"
  415. fi
  416. done
  417. if ! token="$(cryptsetup luksDump "${DEV}" \
  418. | grep -E -B1 "^\s+Keyslot:\s+${SLOT}$" \
  419. | sed -rn 's|^\s+([0-9]+): clevis|\1|p')"; then
  420. echo "Error trying to read token from LUKS2 device ${DEV}, slot ${SLOT}" >&2
  421. return 1
  422. fi
  423. if [ -n "${token}" ]; then
  424. [ -z "${SHOULD_OVERWRITE}" ] && return 1
  425. if ! cryptsetup token remove --token-id "${token}" "${DEV}"; then
  426. echo "Error while removing token ${token} from LUKS2 device ${DEV}" >&2
  427. return 1
  428. fi
  429. fi
  430. if [ -n "${SHOULD_OVERWRITE}" ] && [ -n "${TKN_ID}" ]; then
  431. cryptsetup token remove --token-id "${TKN_ID}" "${DEV}" 2>/dev/null || :
  432. fi
  433. local metadata
  434. metadata=$(printf '{"type":"clevis","keyslots":["%s"],"jwe":%s}' \
  435. "${SLOT}" "$(jose jwe fmt --input="${JWE}")")
  436. if ! printf '%s' "${metadata}" | cryptsetup token import \
  437. $([ -n "${TKN_ID}" ] && printf -- '--token-id %s' "${TKN_ID}") \
  438. "${DEV}"; then
  439. echo "Error saving metadata to LUKS2 header in device ${DEV}" >&2
  440. return 1
  441. fi
  442. return 0
  443. }
  444. # clevis_luks_save_slot() saves a given JWE to a LUKS device+slot. It can also
  445. # overwrite existing metadata.
  446. clevis_luks_save_slot() {
  447. local DEV="${1}"
  448. local SLOT="${2}"
  449. local TKN_ID="${3}"
  450. local JWE="${4}"
  451. local SHOULD_OVERWRITE="${5:-}"
  452. if cryptsetup isLuks --type luks1 "${DEV}"; then
  453. clevis_luks1_save_slot "${DEV}" "${SLOT}" "${JWE}" \
  454. "${SHOULD_OVERWRITE}" || return 1
  455. elif cryptsetup isLuks --type luks2 "${DEV}"; then
  456. clevis_luks2_save_slot "${DEV}" "${SLOT}" "${TKN_ID}" "${JWE}" \
  457. "${SHOULD_OVERWRITE}" || return 1
  458. else
  459. return 1
  460. fi
  461. return 0
  462. }
  463. # clevis_luks1_backup_dev() backups the LUKSMeta slots from a LUKS device,
  464. # which can be restored with clevis_luks1_restore_dev().
  465. clevis_luks1_backup_dev() {
  466. local DEV="${1}"
  467. local TMP="${2}"
  468. [ -z "${DEV}" ] && return 1
  469. [ -z "${TMP}" ] && return 1
  470. luksmeta test -d "${DEV}" || return 0
  471. touch "${TMP}/initialized"
  472. local used_slots slt uuid jwe fname
  473. if ! used_slots=$(clevis_luks_used_slots "${DEV}") \
  474. || [ -z "${used_slots}" ]; then
  475. return 1
  476. fi
  477. for slt in ${used_slots}; do
  478. if ! uuid=$(luksmeta show -d "${DEV}" -s "${slt}") \
  479. || [ -z "${uuid}" ]; then
  480. continue
  481. fi
  482. if ! jwe=$(luksmeta load -d "${DEV}" -s "${slt}") \
  483. || [ -z "${jwe}" ]; then
  484. continue
  485. fi
  486. fname=$(printf "slot_%s_%s" "${slt}" "${uuid}")
  487. printf "%s" "${jwe}" > "${TMP}/${fname}"
  488. done
  489. return 0
  490. }
  491. # clevis_luks1_restore_dev() takes care of restoring the LUKSMeta slots from
  492. # a LUKS device that was backup'ed by clevis_luks1_backup_dev().
  493. clevis_luks1_restore_dev() {
  494. local DEV="${1}"
  495. local TMP="${2}"
  496. [ -z "${DEV}" ] && return 1
  497. [ -z "${TMP}" ] && return 1
  498. [ -e "${TMP}/initialized" ] || return 0
  499. luksmeta test -d "${DEV}" || luksmeta init -f -d "${DEV}"
  500. local slt uuid jwe fname
  501. for fname in "${TMP}"/slot_*; do
  502. [ -f "${fname}" ] || break
  503. if ! slt=$(echo "${fname}" | cut -d '_' -f 2) \
  504. || [ -z "${slt}" ]; then
  505. continue
  506. fi
  507. if ! uuid=$(echo "${fname}" | cut -d '_' -f 3) \
  508. || [ -z "${uuid}" ]; then
  509. continue
  510. fi
  511. if ! jwe=$(cat "${fname}") || [ -z "${jwe}" ]; then
  512. continue
  513. fi
  514. if ! clevis_luks1_save_slot "${DEV}" "${slt}" \
  515. "${jwe}" "overwrite"; then
  516. echo "Error restoring LUKSmeta slot ${slt} from ${DEV}" >&2
  517. return 1
  518. fi
  519. done
  520. return 0
  521. }
  522. # clevis_luks_backup_dev() backups a particular LUKS device, which can then
  523. # be restored with clevis_luks_restore_dev().
  524. clevis_luks_backup_dev() {
  525. local DEV="${1}"
  526. local TMP="${2}"
  527. [ -z "${DEV}" ] && return 1
  528. [ -z "${TMP}" ] && return 1
  529. printf '%s' "${DEV}" > "${TMP}/device"
  530. printf '%s' "${DEV}" > "${TMP}/device"
  531. local HDR
  532. HDR="${TMP}/$(basename "${DEV}").header"
  533. if ! cryptsetup luksHeaderBackup "${DEV}" --batch-mode \
  534. --header-backup-file "${HDR}"; then
  535. echo "Error backing up LUKS header from ${DEV}" >&2
  536. return 1
  537. fi
  538. # If LUKS1, we need to manually back up (and later restore) the
  539. # LUKSmeta slots. For LUKS2, simply saving the header also saves
  540. # the associated tokens.
  541. if cryptsetup isLuks --type luks1 "${DEV}"; then
  542. if ! clevis_luks1_backup_dev "${DEV}" "${TMP}"; then
  543. return 1
  544. fi
  545. fi
  546. return 0
  547. }
  548. # clevis_luks_restore_dev() restores a given device that was backup'ed by
  549. # clevis_luks_backup_dev().
  550. clevis_luks_restore_dev() {
  551. local TMP="${1}"
  552. [ -z "${TMP}" ] && return 1
  553. [ -r "${TMP}"/device ] || return 1
  554. local DEV
  555. DEV="$(cat "${TMP}"/device)"
  556. local HDR
  557. HDR="${TMP}/$(basename "${DEV}").header"
  558. if [ ! -e "${HDR}" ]; then
  559. echo "LUKS header backup does not exist" >&2
  560. return 1
  561. fi
  562. if ! cryptsetup luksHeaderRestore "${DEV}" --batch-mode \
  563. --header-backup-file "${HDR}"; then
  564. echo "Error restoring LUKS header from ${DEV}" >&2
  565. return 1
  566. fi
  567. # If LUKS1, we need to manually back up (and later restore) the
  568. # LUKSmeta slots. For LUKS2, simply saving the header also saves
  569. # the associated tokens.
  570. if cryptsetup isLuks --type luks1 "${DEV}"; then
  571. if ! clevis_luks1_restore_dev "${DEV}" "${TMP}"; then
  572. return 1
  573. fi
  574. fi
  575. return 0
  576. }
  577. # clevis_luks_get_existing_key() may try to recover a valid password from
  578. # existing bindings and additionally prompt the user for the passphrase.
  579. clevis_luks_get_existing_key() {
  580. local DEV="${1}"
  581. local PROMPT="${2}"
  582. local RECOVER="${3:-}"
  583. [ -z "${DEV}" ] && return 1
  584. local pt
  585. if [ -n "${RECOVER}" ] && pt="$(clevis_luks_unlock_device "${DEV}")" \
  586. && [ -n "${pt}" ]; then
  587. printf '%s' "${pt}"
  588. return 0
  589. fi
  590. # Let's prompt the user for the password.
  591. read -r -s -p "${PROMPT}" pt; echo >&2
  592. # Check if key is valid.
  593. clevis_luks_check_valid_key_or_keyfile "${DEV}" "${pt}" || return 1
  594. printf '%s' "${pt}"
  595. }
  596. # clevis_luks_luksmeta_sync_fix() makes sure LUKSmeta slots are sync'ed with
  597. # cryptsetup, in order to prevent issues when saving clevis metadata.
  598. clevis_luks_luksmeta_sync_fix() {
  599. local DEV="${DEV}"
  600. [ -z "${DEV}" ] && return 1
  601. # This applies only to LUKS1 devices.
  602. cryptsetup isLuks --type luks1 "${DEV}" || return 0
  603. # No issues if the LUKSmeta metadata is not initialized.
  604. luksmeta test -d "${DEV}" || return 0
  605. local first_free_slot
  606. if ! first_free_slot=$(clevis_luks_first_free_slot "${DEV}") \
  607. || [ -z "${first_free_slot}" ]; then
  608. echo "There are possibly no free slots in ${DEV}" >&2
  609. return 1
  610. fi
  611. # In certain circumstances, we may have LUKSMeta slots "not in sync" with
  612. # cryptsetup, which means we will try to save LUKSMeta metadata over an
  613. # already used or partially used slot -- github issue #70.
  614. # If that is the case, let's wipe the LUKSMeta slot here prior to using
  615. # the LUKSMeta slot.
  616. local lmeta_slot lmeta_status lmeta_uuid
  617. lmeta_slot="$(luksmeta show -d "${DEV}" | grep "^${first_free_slot}")"
  618. # 1 active cb6e8904-81ff-40da-a84a-07ab9ab5715e
  619. # 2 inactive cb6e8904-81ff-40da-a84a-07ab9ab5715e
  620. lmeta_status="$(echo "${lmeta_slot}" | awk '{print $2}')"
  621. [ "${lmeta_status}" != 'inactive' ] && return 0
  622. lmeta_uuid="$(echo "${lmeta_slot}" | awk '{print $3}')"
  623. [ "${lmeta_uuid}" != "${CLEVIS_UUID}" ] && return 0
  624. luksmeta wipe -f -d "${DEV}" -s "${first_free_slot}"
  625. }
  626. # clevis_luks_add_key() adds a new key to a key slot.
  627. clevis_luks_add_key() {
  628. local DEV="${1}"
  629. local SLT="${2}"
  630. local NEWKEY="${3}"
  631. local KEY="${4}"
  632. local KEYFILE="${5:-}"
  633. [ -z "${DEV}" ] && return 1
  634. [ -z "${NEWKEY}" ] && return 1
  635. [ -z "${KEY}" ] && [ -z "${KEYFILE}" ] && return 1
  636. local extra_args='' input
  637. input="$(printf '%s\n%s' "${KEY}" "${NEWKEY}")"
  638. if [ -n "${KEYFILE}" ]; then
  639. extra_args="$(printf -- '--key-file %s' "${KEYFILE}")"
  640. input="$(printf '%s' "${NEWKEY}")"
  641. fi
  642. printf '%s' "${input}" | cryptsetup luksAddKey --batch-mode \
  643. --key-slot "${SLT}" \
  644. "${DEV}" \
  645. ${extra_args}
  646. }
  647. # clevis_luks_update_key() will update a key slot with a new key.
  648. clevis_luks_update_key() {
  649. local DEV="${1}"
  650. local SLT="${2}"
  651. local NEWKEY="${3}"
  652. local KEY="${4}"
  653. local KEYFILE="${5:-}"
  654. [ -z "${DEV}" ] && return 1
  655. [ -z "${NEWKEY}" ] && return 1
  656. # Update the key slot with the new key. If we have the key for this slot,
  657. # the change happens in-place. Otherwise, we kill the slot and re-add it.
  658. local in_place
  659. clevis_luks_check_valid_key_or_keyfile "${DEV}" \
  660. "${KEY}" "${KEYFILE}" \
  661. "${SLT}" 2>/dev/null \
  662. && in_place=true
  663. local input extra_args=
  664. input="$(printf '%s\n%s' "${KEY}" "${NEWKEY}")"
  665. if [ -n "${KEYFILE}" ]; then
  666. extra_args="$(printf -- '--key-file %s' "${KEYFILE}")"
  667. input="$(printf '%s' "${NEWKEY}")"
  668. fi
  669. if [ -n "${in_place}" ]; then
  670. printf '%s' "${input}" | cryptsetup luksChangeKey "${DEV}" \
  671. --key-slot "${SLT}" \
  672. --batch-mode ${extra_args}
  673. return
  674. fi
  675. if ! printf '%s' "${input}" | cryptsetup luksKillSlot "${DEV}" \
  676. "${SLT}" \
  677. ${extra_args}; then
  678. echo "Error wiping slot ${SLT} from ${DEV}" >&2
  679. return 1
  680. fi
  681. clevis_luks_add_key "${DEV}" "${SLT}" "${NEWKEY}" "${KEY}" "${KEYFILE}"
  682. }
  683. # clevis_luks_save_key_to_slot() will save a new key to a slot. It can either
  684. # add a new key to a slot or updating an already used slot.
  685. clevis_luks_save_key_to_slot() {
  686. local DEV="${1}"
  687. local SLT="${2}"
  688. local NEWKEY="${3}"
  689. local KEY="${4}"
  690. local KEYFILE="${5:-}"
  691. local OVERWRITE="${6:-}"
  692. [ -z "${DEV}" ] && return 1
  693. [ -z "${SLT}" ] && return 1
  694. [ -z "${NEWKEY}" ] && return 1
  695. # Make sure LUKSmeta slots are in sync with cryptsetup, to avoid the
  696. # problem reported in github issue #70. Applies to LUKS1 only.
  697. clevis_luks_luksmeta_sync_fix "${DEV}"
  698. # Let's check if we are adding a new key or updating an existing one.
  699. local update
  700. update="$(clevis_luks_used_slots "${DEV}" | grep "^${SLT}$")"
  701. if [ -n "${update}" ]; then
  702. # Replace an existing key.
  703. [ -n "${OVERWRITE}" ] || return 1
  704. clevis_luks_update_key "${DEV}" "${SLT}" \
  705. "${NEWKEY}" "${KEY}" "${KEYFILE}"
  706. return
  707. fi
  708. # Add a new key.
  709. clevis_luks_add_key "${DEV}" "${SLT}" \
  710. "${NEWKEY}" "${KEY}" "${KEYFILE}"
  711. }
  712. # clevis_luks_generate_key() generates a new key for use with clevis.
  713. clevis_luks_generate_key() {
  714. local DEV="${1}"
  715. [ -z "${DEV}" ] && return 1
  716. local dump filter bits
  717. dump=$(cryptsetup luksDump "${DEV}")
  718. if cryptsetup isLuks --type luks1 "${DEV}"; then
  719. filter="$(echo "${dump}" | sed -rn 's|MK bits:[ \t]*([0-9]+)|\1|p')"
  720. elif cryptsetup isLuks --type luks2 "${DEV}"; then
  721. filter="$(echo -n "${dump}" | \
  722. sed -rn 's|^\s+Key:\s+([0-9]+) bits\s*$|\1|p')"
  723. else
  724. return 1
  725. fi
  726. bits="$(echo -n "${filter}" | sort -n | tail -n 1)"
  727. pwmake "${bits}"
  728. }
  729. # clevis_luks_token_id_by_slot() returns the token ID linked to a
  730. # particular LUKS2 key slot.
  731. clevis_luks_token_id_by_slot() {
  732. local DEV="${1}"
  733. local SLT="${2}"
  734. [ -z "${DEV}" ] && return 1
  735. [ -z "${SLT}" ] && return 1
  736. cryptsetup isLuks --type luks1 "${DEV}" && echo && return
  737. local tkn_id
  738. tkn_id="$(cryptsetup luksDump "${DEV}" \
  739. | grep -E -B1 "^\s+Keyslot:\s+${SLT}$" \
  740. | sed -rn 's|^\s+([0-9]+): clevis|\1|p')"
  741. printf '%s' "${tkn_id}"
  742. }
  743. # clevis_luks_cleanup() removes the temporary directory used to store the data
  744. # relevant to device backup and restore.
  745. clevis_luks_cleanup() {
  746. [ -z "${CLEVIS_TMP_DIR}" ] && return 0
  747. [ -d "${CLEVIS_TMP_DIR}" ] || return 0
  748. if ! rm -rf "${CLEVIS_TMP_DIR}"; then
  749. echo "Deleting temporary files failed!" >&2
  750. echo "You may need to clean up '${CLEVIS_TMP_DIR}'" >&2
  751. exit 1
  752. fi
  753. unset CLEVIS_TMP_DIR
  754. }
  755. # clevis_luks_first_free_slot() returns the first key slot that is available
  756. # in a LUKS device.
  757. clevis_luks_first_free_slot() {
  758. local DEV="${1}"
  759. [ -z "${DEV}" ] && return 1
  760. local first_free_slot
  761. if cryptsetup isLuks --type luks1 "${DEV}"; then
  762. first_free_slot=$(cryptsetup luksDump "${DEV}" \
  763. | sed -rn 's|^Key Slot ([0-7]): DISABLED$|\1|p' \
  764. | sed -n 1p)
  765. elif cryptsetup isLuks --type luks2 "${DEV}"; then
  766. local used_slots slt
  767. used_slots="$(clevis_luks_used_slots "${DEV}")"
  768. for slt in $(seq 0 31); do
  769. if ! echo "${used_slots}" | grep -q "^${slt}$"; then
  770. first_free_slot="${slt}"
  771. break
  772. fi
  773. done
  774. else
  775. echo "Unsupported device ${DEV}" >&2
  776. return 1
  777. fi
  778. echo "${first_free_slot}"
  779. }
  780. # clevis_luks_do_bind() creates or updates a particular binding.
  781. clevis_luks_do_bind() {
  782. local DEV="${1}"
  783. local SLT="${2}"
  784. local TKN_ID="${3}"
  785. local PIN="${4}"
  786. local CFG="${5}"
  787. local YES="${6:-}"
  788. local OVERWRITE="${7:-}"
  789. local KEY="${8:-}"
  790. local KEYFILE="${9:-}"
  791. [ -z "${DEV}" ] && return 1
  792. [ -z "${PIN}" ] && return 1
  793. [ -z "${CFG}" ] && return 1
  794. if ! clevis_luks_check_valid_key_or_keyfile "${DEV}" \
  795. "${KEY}" \
  796. "${KEYFILE}" \
  797. && ! KEY="$(clevis_luks_get_existing_key "${DEV}" \
  798. "Enter existing LUKS password: " \
  799. "recover")" || [ -z "${KEY}" ]; then
  800. return 1
  801. fi
  802. local newkey jwe
  803. if ! newkey="$(clevis_luks_generate_key "${DEV}")" \
  804. || [ -z "${newkey}" ]; then
  805. echo "Unable to generate a new key" >&2
  806. return 1
  807. fi
  808. # Encrypt the new key.
  809. jwe="$(printf '%s' "${newkey}" | clevis encrypt "${PIN}" "${CFG}" ${YES})"
  810. # We can proceed to binding, after backing up the LUKS header and
  811. # metadata.
  812. local CLEVIS_TMP_DIR
  813. if ! CLEVIS_TMP_DIR="$(mktemp -d)" || [ -z "${CLEVIS_TMP_DIR}" ]; then
  814. echo "Unable to create a a temporary dir for device backup/restore" >&2
  815. return 1
  816. fi
  817. export CLEVIS_TMP_DIR
  818. trap 'clevis_luks_cleanup' EXIT
  819. # Backup LUKS header.
  820. if ! clevis_luks_backup_dev "${DEV}" "${CLEVIS_TMP_DIR}"; then
  821. echo "Unable to back up LUKS header from ${DEV}" >&2
  822. return 1
  823. fi
  824. if [ -z "${SLT}" ] && ! SLT=$(clevis_luks_first_free_slot "${DEV}") \
  825. || [ -z "${SLT}" ]; then
  826. echo "Unable to find a free slot in ${DEV}" >&2
  827. return 1
  828. fi
  829. [ -z "${TKN_ID}" ] && ! TKN_ID="$(clevis_luks_token_id_by_slot "${DEV}" \
  830. "${SLT}")" && return 1
  831. if ! clevis_luks_save_key_to_slot "${DEV}" "${SLT}" \
  832. "${newkey}" "${KEY}" "${KEYFILE}" \
  833. "${OVERWRITE}"; then
  834. echo "Unable to save/update key slot; operation cancelled" >&2
  835. clevis_luks_restore_dev "${CLEVIS_TMP_DIR}" || :
  836. rm -rf "${CLEVIS_TMP_DIR}"
  837. return 1
  838. fi
  839. if ! clevis_luks_save_slot "${DEV}" "${SLT}" "${tkn_id}" \
  840. "${jwe}" "${OVERWRITE}"; then
  841. echo "Unable to update metadata; operation cancelled" >&2
  842. clevis_luks_restore_dev "${CLEVIS_TMP_DIR}" || :
  843. rm -rf "${CLEVIS_TMP_DIR}"
  844. return 1
  845. fi
  846. clevis_luks_cleanup
  847. trap - EXIT
  848. return 0
  849. }