tpm2_key_protector_test.in 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390
  1. #! @BUILD_SHEBANG@ -e
  2. # Test GRUBs ability to unseal a LUKS key with TPM 2.0
  3. # Copyright (C) 2024 Free Software Foundation, Inc.
  4. #
  5. # GRUB is free software: you can redistribute it and/or modify
  6. # it under the terms of the GNU General Public License as published by
  7. # the Free Software Foundation, either version 3 of the License, or
  8. # (at your option) any later version.
  9. #
  10. # GRUB is distributed in the hope that it will be useful,
  11. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. # GNU General Public License for more details.
  14. #
  15. # You should have received a copy of the GNU General Public License
  16. # along with GRUB. If not, see <http://www.gnu.org/licenses/>.
  17. grubshell=@builddir@/grub-shell
  18. . "@builddir@/grub-core/modinfo.sh"
  19. if [ x${grub_modinfo_platform} != xemu ]; then
  20. exit 77
  21. fi
  22. builddir="@builddir@"
  23. # Force build directory components
  24. PATH="${builddir}:${PATH}"
  25. export PATH
  26. if [ "x${EUID}" = "x" ] ; then
  27. EUID=`id -u`
  28. fi
  29. if [ "${EUID}" != 0 ] ; then
  30. echo "not root; cannot test tpm2."
  31. exit 99
  32. fi
  33. if ! command -v cryptsetup >/dev/null 2>&1; then
  34. echo "cryptsetup not installed; cannot test tpm2."
  35. exit 99
  36. fi
  37. if ! grep -q tpm_vtpm_proxy /proc/modules && ! modprobe tpm_vtpm_proxy; then
  38. echo "no tpm_vtpm_proxy support; cannot test tpm2."
  39. exit 99
  40. fi
  41. if ! command -v swtpm >/dev/null 2>&1; then
  42. echo "swtpm not installed; cannot test tpm2."
  43. exit 99
  44. fi
  45. if ! command -v tpm2_startup >/dev/null 2>&1; then
  46. echo "tpm2-tools not installed; cannot test tpm2."
  47. exit 99
  48. fi
  49. tpm2testdir="`mktemp -d "${TMPDIR:-/tmp}/$(basename "$0").XXXXXXXXXX"`" || exit 99
  50. disksize=20M
  51. luksfile=${tpm2testdir}/luks.disk
  52. lukskeyfile=${tpm2testdir}/password.txt
  53. # Choose a low iteration number to reduce the time to decrypt the disk
  54. csopt="--type luks2 --pbkdf pbkdf2 --iter-time 1000"
  55. tpm2statedir=${tpm2testdir}/tpm
  56. tpm2ctrl=${tpm2statedir}/ctrl
  57. tpm2log=${tpm2statedir}/logfile
  58. sealedkey=${tpm2testdir}/sealed.tpm
  59. timeout=20
  60. testoutput=${tpm2testdir}/testoutput
  61. vtext="TEST VERIFIED"
  62. ret=0
  63. # Create the password file
  64. echo -n "top secret" > "${lukskeyfile}"
  65. # Setup LUKS2 image
  66. truncate -s ${disksize} "${luksfile}" || exit 99
  67. cryptsetup luksFormat -q ${csopt} "${luksfile}" "${lukskeyfile}" || exit 99
  68. # Write vtext into the first block of the LUKS2 image
  69. luksdev=/dev/mapper/`basename "${tpm2testdir}"`
  70. cryptsetup open --key-file "${lukskeyfile}" "${luksfile}" `basename "${luksdev}"` || exit 99
  71. echo "${vtext}" > "${luksdev}"
  72. cryptsetup close "${luksdev}"
  73. # Shutdown the swtpm instance on exit
  74. cleanup() {
  75. RET=$?
  76. if [ -e "${tpm2ctrl}" ]; then
  77. swtpm_ioctl -s --unix "${tpm2ctrl}"
  78. fi
  79. if [ "${RET}" -eq 0 ]; then
  80. rm -rf "$tpm2testdir" || :
  81. fi
  82. }
  83. trap cleanup EXIT INT TERM KILL QUIT
  84. mkdir -p "${tpm2statedir}"
  85. # Create the swtpm chardev instance
  86. swtpm chardev --vtpm-proxy --tpmstate dir="${tpm2statedir}" \
  87. --tpm2 --ctrl type=unixio,path="${tpm2ctrl}" \
  88. --flags startup-clear --daemon > "${tpm2log}" || ret=$?
  89. if [ "${ret}" -ne 0 ]; then
  90. echo "Failed to start swtpm chardev: ${ret}" >&2
  91. exit 99
  92. fi
  93. # Wait for tpm2 chardev
  94. tpm2timeout=${GRUB_TEST_SWTPM_DEFAULT_TIMEOUT:-3}
  95. for count in `seq 1 ${tpm2timeout}`; do
  96. sleep 1
  97. tpm2dev=$(grep "New TPM device" "${tpm2log}" | cut -d' ' -f 4)
  98. if [ -c "${tpm2dev}" ]; then
  99. break
  100. elif [ "${count}" -eq "${tpm2timeout}" ]; then
  101. echo "TPM device did not appear." >&2
  102. exit 99
  103. fi
  104. done
  105. # Export the TCTI variable for tpm2-tools
  106. export TPM2TOOLS_TCTI="device:${tpm2dev}"
  107. # Extend PCR 0
  108. tpm2_pcrextend 0:sha256=$(echo "test0" | sha256sum | cut -d ' ' -f 1) || exit 99
  109. # Extend PCR 1
  110. tpm2_pcrextend 1:sha256=$(echo "test1" | sha256sum | cut -d ' ' -f 1) || exit 99
  111. tpm2_seal_unseal() {
  112. srk_alg="$1"
  113. handle_type="$2"
  114. srk_test="$3"
  115. grub_srk_alg=${srk_alg}
  116. extra_opt=""
  117. extra_grub_opt=""
  118. persistent_handle="0x81000000"
  119. grub_cfg=${tpm2testdir}/testcase.cfg
  120. if [ "${handle_type}" = "persistent" ]; then
  121. extra_opt="--tpm2-srk=${persistent_handle}"
  122. fi
  123. if [ "${srk_alg}" != "default" ]; then
  124. extra_opt="${extra_opt} --tpm2-asymmetric=${srk_alg}"
  125. fi
  126. # Seal the password with grub-protect
  127. grub-protect ${extra_opt} \
  128. --tpm2-device="${tpm2dev}" \
  129. --action=add \
  130. --protector=tpm2 \
  131. --tpm2key \
  132. --tpm2-bank=sha256 \
  133. --tpm2-pcrs=0,1 \
  134. --tpm2-keyfile="${lukskeyfile}" \
  135. --tpm2-outfile="${sealedkey}" || ret=$?
  136. if [ "${ret}" -ne 0 ]; then
  137. echo "Failed to seal the secret key: ${ret}" >&2
  138. return 99
  139. fi
  140. # Flip the asymmetric algorithm in grub.cfg to trigger fallback SRKs
  141. if [ "${srk_test}" = "fallback_srk" ]; then
  142. if [ -z "${srk_alg##RSA*}" ]; then
  143. grub_srk_alg="ECC"
  144. elif [ -z "${srk_alg##ECC*}" ]; then
  145. grub_srk_alg="RSA"
  146. fi
  147. fi
  148. if [ "${grub_srk_alg}" != "default" ] && [ "${handle_type}" != "persistent" ]; then
  149. extra_grub_opt="-a ${grub_srk_alg}"
  150. fi
  151. # Write the TPM unsealing script
  152. cat > "${grub_cfg}" <<EOF
  153. loopback luks (host)${luksfile}
  154. tpm2_key_protector_init -T (host)${sealedkey} ${extra_grub_opt}
  155. if cryptomount -a --protector tpm2; then
  156. cat (crypto0)+1
  157. fi
  158. EOF
  159. # Test TPM unsealing with the same PCR
  160. ${grubshell} --timeout=${timeout} --emu-opts="-t ${tpm2dev}" < "${grub_cfg}" > "${testoutput}" || ret=$?
  161. # Remove the persistent handle
  162. if [ "${handle_type}" = "persistent" ]; then
  163. grub-protect \
  164. --tpm2-device="${tpm2dev}" \
  165. --protector=tpm2 \
  166. --action=remove \
  167. --tpm2-srk=${persistent_handle} \
  168. --tpm2-evict || :
  169. fi
  170. if [ "${ret}" -eq 0 ]; then
  171. if ! grep -q "^${vtext}$" "${testoutput}"; then
  172. echo "error: test not verified [`cat ${testoutput}`]" >&2
  173. return 1
  174. fi
  175. else
  176. echo "grub-emu exited with error: ${ret}" >&2
  177. return 99
  178. fi
  179. }
  180. tpm2_seal_nv () {
  181. keyfile="$1"
  182. nv_index="$2"
  183. pcr_list="$3"
  184. primary_file=${tpm2testdir}/primary.ctx
  185. session_file=${tpm2testdir}/session.dat
  186. policy_file=${tpm2testdir}/policy.dat
  187. keypub_file=${tpm2testdir}/key.pub
  188. keypriv_file=${tpm2testdir}/key.priv
  189. name_file=${tpm2testdir}/sealing.name
  190. sealing_ctx_file=${tpm2testdir}/sealing.ctx
  191. # Since we don't run a resource manager on our swtpm instance, it has
  192. # to flush the transient handles after tpm2_createprimary, tpm2_create
  193. # and tpm2_load to avoid the potential out-of-memory (0x902) errors.
  194. # Ref: https://github.com/tpm2-software/tpm2-tools/issues/1338#issuecomment-469689398
  195. # Create the primary object
  196. tpm2_createprimary -Q -C o -g sha256 -G ecc -c "${primary_file}" || ret=$?
  197. if [ "${ret}" -ne 0 ]; then
  198. echo "Failed to create the primary object: ${ret}" >&2
  199. return 1
  200. fi
  201. tpm2_flushcontext -t || ret=$?
  202. if [ "${ret}" -ne 0 ]; then
  203. echo "Failed to flush the transient handles: ${ret}" >&2
  204. return 1
  205. fi
  206. # Create the policy object
  207. tpm2_startauthsession -S "${session_file}" || ret=$?
  208. if [ "${ret}" -ne 0 ]; then
  209. echo "Failed to start auth session: ${ret}" >&2
  210. return 1
  211. fi
  212. tpm2_policypcr -Q -S "${session_file}" -l "${pcr_list}" -L "${policy_file}" || ret=$?
  213. if [ "${ret}" -ne 0 ]; then
  214. echo "Failed to create the policy object: ${ret}" >&2
  215. return 1
  216. fi
  217. tpm2_flushcontext "${session_file}" || ret=$?
  218. if [ "${ret}" -ne 0 ]; then
  219. echo "Failed to flush the transient handles: ${ret}" >&2
  220. return 1
  221. fi
  222. # Seal the key into TPM
  223. tpm2_create -Q \
  224. -C "${primary_file}" \
  225. -u "${keypub_file}" \
  226. -r "${keypriv_file}" \
  227. -L "${policy_file}" \
  228. -i "${keyfile}" || ret=$?
  229. if [ "${ret}" -ne 0 ]; then
  230. echo "Failed to seal \"${keyfile}\": ${ret}" >&2
  231. return 1
  232. fi
  233. tpm2_flushcontext -t || ret=$?
  234. if [ "${ret}" -ne 0 ]; then
  235. echo "Failed to flush the transient handles: ${ret}" >&2
  236. return 1
  237. fi
  238. tpm2_load -Q \
  239. -C "${primary_file}" \
  240. -u "${keypub_file}" \
  241. -r "${keypriv_file}" \
  242. -n "${name_file}" \
  243. -c "${sealing_ctx_file}" || ret=$?
  244. if [ "${ret}" -ne 0 ]; then
  245. echo "Failed to load the sealed key into TPM: ${ret}" >&2
  246. return 1
  247. fi
  248. tpm2_flushcontext -t || ret=$?
  249. if [ "${ret}" -ne 0 ]; then
  250. echo "Failed to flush the transient handles: ${ret}" >&2
  251. return 1
  252. fi
  253. tpm2_evictcontrol -Q -C o -c "${sealing_ctx_file}" ${nv_index} || ret=$?
  254. if [ "${ret}" -ne 0 ]; then
  255. echo "Failed to store the sealed key into ${nv_index}: ${ret}" >&2
  256. return 1
  257. fi
  258. return 0
  259. }
  260. tpm2_seal_unseal_nv() {
  261. nv_index="0x81000000"
  262. pcr_list="sha256:0,1"
  263. grub_cfg=${tpm2testdir}/testcase.cfg
  264. # Seal the key into a NV index guarded by PCR 0 and 1
  265. tpm2_seal_nv "${lukskeyfile}" ${nv_index} ${pcr_list} || ret=$?
  266. if [ "${ret}" -ne 0 ]; then
  267. echo "Failed to seal the secret key into ${nv_index}" >&2
  268. return 99
  269. fi
  270. # Write the TPM unsealing script
  271. cat > ${grub_cfg} <<EOF
  272. loopback luks (host)${luksfile}
  273. tpm2_key_protector_init --mode=nv --nvindex=${nv_index} --pcrs=0,1
  274. if cryptomount -a --protector tpm2; then
  275. cat (crypto0)+1
  276. fi
  277. EOF
  278. # Test TPM unsealing with the same PCR
  279. ${grubshell} --timeout=${timeout} --emu-opts="-t ${tpm2dev}" < "${grub_cfg}" > "${testoutput}" || ret=$?
  280. # Remove the object from the NV index
  281. tpm2_evictcontrol -Q -C o -c "${nv_index}" || :
  282. if [ "${ret}" -eq 0 ]; then
  283. if ! grep -q "^${vtext}$" "${testoutput}"; then
  284. echo "error: test not verified [`cat ${testoutput}`]" >&2
  285. return 1
  286. fi
  287. else
  288. echo "grub-emu exited with error: ${ret}" >&2
  289. return 99
  290. fi
  291. }
  292. # Testcases for SRK mode
  293. declare -a srktests=()
  294. srktests+=("default transient no_fallback_srk")
  295. srktests+=("RSA transient no_fallback_srk")
  296. srktests+=("ECC transient no_fallback_srk")
  297. srktests+=("RSA persistent no_fallback_srk")
  298. srktests+=("ECC persistent no_fallback_srk")
  299. srktests+=("RSA transient fallback_srk")
  300. srktests+=("ECC transient fallback_srk")
  301. for i in "${!srktests[@]}"; do
  302. tpm2_seal_unseal ${srktests[$i]} || ret=$?
  303. if [ "${ret}" -eq 0 ]; then
  304. echo "TPM2 [${srktests[$i]}]: PASS"
  305. elif [ "${ret}" -eq 1 ]; then
  306. echo "TPM2 [${srktests[$i]}]: FAIL"
  307. else
  308. echo "Unexpected failure [${srktests[$i]}]" >&2
  309. exit ${ret}
  310. fi
  311. done
  312. # Testcase for NV index mode
  313. tpm2_seal_unseal_nv || ret=$?
  314. if [ "${ret}" -eq 0 ]; then
  315. echo "TPM2 [NV Index]: PASS"
  316. elif [ "${ret}" -eq 1 ]; then
  317. echo "TPM2 [NV Index]: FAIL"
  318. else
  319. echo "Unexpected failure [NV index]" >&2
  320. exit ${ret}
  321. fi
  322. exit 0