tests-common-functions.in 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367
  1. #!/bin/bash -ex
  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. error() {
  21. echo "${1}" >&2
  22. exit 1
  23. }
  24. skip_test() {
  25. echo "${1}" >&2
  26. exit 77
  27. }
  28. # We require cryptsetup >= 2.0.4 to fully support LUKSv2.
  29. # Support is determined at build time.
  30. luks2_supported() {
  31. return @OLD_CRYPTSETUP@
  32. }
  33. # Creates a tang adv to be used in the test.
  34. create_tang_adv() {
  35. local adv="${1}"
  36. local SIG="${TMP}/sig.jwk"
  37. jose jwk gen -i '{"alg":"ES512"}' > "${SIG}"
  38. local EXC="${TMP}/exc.jwk"
  39. jose jwk gen -i '{"alg":"ECMR"}' > "${EXC}"
  40. local TEMPLATE='{"protected":{"cty":"jwk-set+json"}}'
  41. jose jwk pub -s -i "${SIG}" -i "${EXC}" \
  42. | jose jws sig -I- -s "${TEMPLATE}" -k "${SIG}" -o "${adv}"
  43. }
  44. # Creates a new LUKS1 or LUKS2 device to be used.
  45. new_device() {
  46. local LUKS="${1}"
  47. local DEV="${2}"
  48. local PASS="${3}"
  49. # Some builders fail if the cryptsetup steps are not ran as root, so let's
  50. # skip the test now if not running as root.
  51. if [ "$(id -u)" != 0 ]; then
  52. skip_test "WARNING: You must be root to run this test; test skipped."
  53. fi
  54. # Using a default password, if none has been provided.
  55. if [ -z "${PASS}" ]; then
  56. PASS="${DEFAULT_PASS}"
  57. fi
  58. local DEV_CACHED="${TMP}/${LUKS}.cached"
  59. # Let's reuse an existing device, if there is one.
  60. if [ -f "${DEV_CACHED}" ]; then
  61. echo "Reusing cached ${LUKS} device..."
  62. cp -f "${DEV_CACHED}" "${DEV}"
  63. return 0
  64. fi
  65. fallocate -l64M "${DEV}"
  66. cryptsetup luksFormat --type "${LUKS}" --pbkdf pbkdf2 \
  67. --pbkdf-force-iterations 1000 --batch-mode \
  68. --force-password "${DEV}" <<< "${PASS}"
  69. # Caching the just-formatted device for possible reuse.
  70. cp -f "${DEV}" "${DEV_CACHED}"
  71. }
  72. # Creates a new LUKS1 or LUKS2 device to be used, using a keyfile.
  73. new_device_keyfile() {
  74. local LUKS="${1}"
  75. local DEV="${2}"
  76. local KEYFILE="${3}"
  77. # Some builders fail if the cryptsetup steps are not ran as root, so let's
  78. # skip the test now if not running as root.
  79. if [ "$(id -u)" != 0 ]; then
  80. skip_test "WARNING: You must be root to run this test; test skipped."
  81. fi
  82. if [[ -z "${KEYFILE}" ]] || [[ ! -f "${KEYFILE}" ]]; then
  83. error "Invalid keyfile (${KEYFILE})."
  84. fi
  85. fallocate -l64M "${DEV}"
  86. cryptsetup luksFormat --type "${LUKS}" --pbkdf pbkdf2 \
  87. --pbkdf-force-iterations 1000 --batch-mode \
  88. "${DEV}" "${KEYFILE}"
  89. }
  90. pin_cfg_equal() {
  91. # Let's remove the single quotes from the pin configuration.
  92. local cfg1="${1//\'/}"
  93. local cfg2="${2//\'/}"
  94. # Now we sort and present them in compact form.
  95. local sorted_cfg1 sorted_cfg2
  96. sorted_cfg1="$(jq --compact-output --sort-keys . < <(echo -n "${cfg1}"))"
  97. sorted_cfg2="$(jq --compact-output --sort-keys . < <(echo -n "${cfg2}"))"
  98. # And we finally compare.
  99. if [ "${sorted_cfg1}" = "${sorted_cfg2}" ]; then
  100. return 0
  101. fi
  102. return 1
  103. }
  104. compare_luks_header() {
  105. DEV1="${1}"
  106. DEV2="${2}"
  107. TMP="${3}"
  108. cryptsetup luksHeaderBackup "${DEV1}" \
  109. --header-backup-file "${TMP}"/check-header1
  110. cryptsetup luksHeaderBackup "${DEV2}" \
  111. --header-backup-file "${TMP}"/check-header2
  112. local cs1 cs2
  113. cs1=$(cksum "${TMP}"/check-header1 | cut -d' ' -f 1)
  114. cs2=$(cksum "${TMP}"/check-header2 | cut -d' ' -f 1)
  115. rm -f "${TMP}"/check-header{1,2}
  116. if [ "${cs1}" == "${cs2}" ]; then
  117. return 0
  118. fi
  119. return 1
  120. }
  121. used_luks1_metadata_slots() {
  122. DEV="${1}"
  123. if ! luksmeta test -d "${DEV}"; then
  124. echo ""
  125. return 0
  126. fi
  127. local clevis_uuid="cb6e8904-81ff-40da-a84a-07ab9ab5715e"
  128. luksmeta show -d "${DEV}" \
  129. | sed -rn "s|^([0-9]+)\s+active\s+${clevis_uuid}$|\1|p" \
  130. | tr '\n' ' ' | sed 's/ $//'
  131. }
  132. used_luks2_metadata_slots() {
  133. DEV="${1}"
  134. cryptsetup luksDump "${DEV}" \
  135. | grep -E -A1 "^\s+[0-9]+:\s+clevis$" \
  136. | sed -rn 's|^\s+Keyslot:\s+([0-9]+)$|\1|p' | sort -n \
  137. | tr '\n' ' ' | sed 's/ $//'
  138. }
  139. used_luks2_metadata_tokens() {
  140. DEV="${1}"
  141. cryptsetup luksDump "${DEV}" \
  142. | grep -E -B1 "^\s+Keyslot:\s+[0-9]+$" \
  143. | sed -rn 's|^\s+([0-9]+): clevis|\1|p' \
  144. | tr '\n' ' ' | sed 's/ $//'
  145. }
  146. compare_luks1_metadata() {
  147. DEV1="${1}"
  148. DEV2="${2}"
  149. # If both are non-initialized, metadata is the same.
  150. ! luksmeta test -d "${DEV1}" && ! luksmeta test -d "${DEV2}" && return 0
  151. # Otherwise, metadata differ.
  152. ! luksmeta test -d "${DEV1}" && return 1
  153. ! luksmeta test -d "${DEV2}" && return 1
  154. local slt1 slt2
  155. slt1=$(used_luks1_metadata_slots "${DEV1}")
  156. slt2=$(used_luks1_metadata_slots "${DEV2}")
  157. if [ "${slt1}" != "${slt2}" ]; then
  158. echo "used slots did not match ($slt1) ($slt2)" >&2
  159. return 1
  160. fi
  161. local slt md1 md2
  162. for slt in ${slt1}; do
  163. md1="$(luksmeta load -d "${DEV}" -s "${slt}")"
  164. md2="$(luksmeta load -d "${DEV2}" -s "${slt}")"
  165. if [ "${md1}" != "${md2}" ]; then
  166. echo "metadata in slot ${slt} did not match" >&2
  167. return 1
  168. fi
  169. done
  170. return 0
  171. }
  172. compare_luks2_metadata() {
  173. DEV1="${1}"
  174. DEV2="${2}"
  175. local slt1 slt2
  176. slt1=$(used_luks2_metadata_slots "${DEV1}")
  177. slt2=$(used_luks2_metadata_slots "${DEV2}")
  178. if [ "${slt1}" != "${slt2}" ]; then
  179. echo "used slots did not match ($slt1) ($slt2)" >&2
  180. return 1
  181. fi
  182. local tkn1 tkn2
  183. tkn1=$(used_luks2_metadata_tokens "${DEV1}")
  184. tkn2=$(used_luks2_metadata_tokens "${DEV2}")
  185. if [ "${tkn1}" != "${tkn2}" ]; then
  186. echo "used tokens did not match ($tkn1) ($tkn2)" >&2
  187. return 1
  188. fi
  189. local tkn md1 md2
  190. for tkn in ${tkn1}; do
  191. md1="$(cryptsetup token export --token-id "${tkn}" "${DEV1}")"
  192. md2="$(cryptsetup token export --token-id "${tkn}" "${DEV2}")"
  193. if [ "${md1}" != "${md2}" ]; then
  194. echo "metadata in token ${tkn} did not match" >&2
  195. return 1
  196. fi
  197. done
  198. return 0
  199. }
  200. # Get a random port to be used with a test tang server.
  201. get_random_port() {
  202. shuf -i 1024-65535 -n 1
  203. }
  204. # Removes tang rotated keys from the test server.
  205. tang_remove_rotated_keys() {
  206. local basedir="${1}"
  207. if [ -z "${basedir}" ]; then
  208. echo "Please pass a valid base directory for tang"
  209. return 1
  210. fi
  211. [ -z "${TANGD_UPDATE}" ] && skip_test "WARNING: TANGD_UPDATE is not defined."
  212. local db="${basedir}/db"
  213. local cache="${basedir}/cache"
  214. mkdir -p "${db}"
  215. mkdir -p "${cache}"
  216. pushd "${db}"
  217. find . -name ".*.jwk" -exec rm -f {} \;
  218. popd
  219. "${TANGD_UPDATE}" "${db}" "${cache}"
  220. return 0
  221. }
  222. # Creates new keys for the test tang server.
  223. tang_new_keys() {
  224. local basedir="${1}"
  225. local rotate="${2}"
  226. if [ -z "${basedir}" ]; then
  227. echo "Please pass a valid base directory for tang"
  228. return 1
  229. fi
  230. [ -z "${TANGD_KEYGEN}" ] && skip_test "WARNING: TANGD_KEYGEN is not defined."
  231. [ -z "${TANGD_UPDATE}" ] && skip_test "WARNING: TANGD_UPDATE is not defined."
  232. local db="${basedir}/db"
  233. local cache="${basedir}/cache"
  234. mkdir -p "${db}"
  235. if [ -n "${rotate}" ]; then
  236. pushd "${db}"
  237. local k
  238. k=$(find . -name "*.jwk" | wc -l)
  239. if [ "${k}" -gt 0 ]; then
  240. for k in *.jwk; do
  241. mv -f -- "${k}" ".${k}"
  242. done
  243. fi
  244. popd
  245. fi
  246. "${TANGD_KEYGEN}" "${db}"
  247. "${TANGD_UPDATE}" "${db}" "${cache}"
  248. return 0
  249. }
  250. # Start a test tang server.
  251. tang_run() {
  252. local basedir="${1}"
  253. local port="${2}"
  254. if [ -z "${basedir}" ]; then
  255. echo "Please pass a valid base directory for tang" >&2
  256. return 1
  257. fi
  258. if [ -z "${port}" ]; then
  259. echo "Please pass a valid port for tang" >&2
  260. return 1
  261. fi
  262. if ! tang_new_keys "${basedir}"; then
  263. echo "Error creating new keys for tang server" >&2
  264. return 1
  265. fi
  266. local KEYS="${basedir}/cache"
  267. local inetd='--inetd'
  268. [ "${SD_ACTIVATE##*/}" = "systemd-activate" ] && inetd=
  269. local pid pidfile
  270. pidfile="${basedir}/tang.pid"
  271. "${SD_ACTIVATE}" ${inetd} -l "${TANG_HOST}":"${port}" \
  272. -a "${TANGD}" "${KEYS}" &
  273. pid=$!
  274. echo "${pid}" > "${pidfile}"
  275. }
  276. # Stop tang server.
  277. tang_stop() {
  278. local basedir="${1}"
  279. local pidfile="${basedir}/tang.pid"
  280. [ -f "${pidfile}" ] || return 0
  281. local pid
  282. pid=$(<"${pidfile}")
  283. kill "${pid}"
  284. }
  285. # Wait for the tang server to be operational.
  286. tang_wait_until_ready() {
  287. local port="${1}"
  288. while ! curl --output /dev/null --silent --fail \
  289. http://"${TANG_HOST}":"${port}"/adv; do
  290. sleep 0.1
  291. echo -n . >&2
  292. done
  293. }
  294. # Get tang advertisement.
  295. tang_get_adv() {
  296. local port="${1}"
  297. local adv="${2}"
  298. curl -o "${adv}" http://"${TANG_HOST}":"${port}"/adv
  299. }
  300. export TANG_HOST=127.0.0.1
  301. export DEFAULT_PASS='just-some-test-password-here'