clevis-luks-common-functions.in 34 KB

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