clevis-luks-common-functions.in 34 KB

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