8f308f5d4f44b0e8987dee64bf025efe5746d5f0
[platform/upstream/cryptsetup.git] / tests / luks2-reencryption-mangle-test
1 #!/bin/bash
2
3 PS4='$LINENO:'
4 [ -z "$CRYPTSETUP_PATH" ] && CRYPTSETUP_PATH=".."
5 CRYPTSETUP=$CRYPTSETUP_PATH/cryptsetup
6 CRYPTSETUP_RAW=$CRYPTSETUP
7
8 CRYPTSETUP_VALGRIND=../.libs/cryptsetup
9 CRYPTSETUP_LIB_VALGRIND=../.libs
10 IMG=reenc-mangle-data
11 IMG_HDR=$IMG.hdr
12 IMG_JSON=$IMG.json
13 KEY1=key1
14 DEV_NAME=reenc3492834
15
16 FAST_PBKDF2="--pbkdf pbkdf2 --pbkdf-force-iterations 1000"
17 CS_PWPARAMS="--disable-keyring --key-file $KEY1"
18 CS_PARAMS="-q --disable-locks $CS_PWPARAMS"
19 JSON_MSIZE=16384
20
21 function remove_mapping()
22 {
23         [ -b /dev/mapper/$DEV_NAME ] && dmsetup remove --retry $DEV_NAME
24         rm -f $IMG $IMG_HDR $IMG_JSON $KEY1 >/dev/null 2>&1
25 }
26
27 function fail()
28 {
29         local frame=0
30         [ -n "$1" ] && echo "$1"
31         echo "FAILED backtrace:"
32         while caller $frame; do ((frame++)); done
33         remove_mapping
34         exit 2
35 }
36
37 function skip()
38 {
39         [ -n "$1" ] && echo "$1"
40         remove_mapping
41         exit 77
42 }
43
44 function bin_check()
45 {
46         which $1 >/dev/null 2>&1 || skip "WARNING: test require $1 binary, test skipped."
47 }
48
49 function img_json_save()
50 {
51         # FIXME: why --json-file cannot be used?
52         #$CRYPTSETUP luksDump --dump-json-metadata $IMG | jq -c -M | tr -d '\n' >$IMG_JSON
53         local LUKS2_JSON_SIZE=$(($JSON_MSIZE - 4096))
54         _dd if=$IMG count=$LUKS2_JSON_SIZE skip=4096 | jq -c -M . | tr -d '\n' >$IMG_JSON
55 }
56
57 function img_json_dump()
58 {
59         img_json_save
60         jq . $IMG_JSON
61 }
62
63 function img_hash_save()
64 {
65         IMG_HASH=$(sha256sum $IMG | cut -d' ' -f 1)
66 }
67
68 function img_hash_unchanged()
69 {
70         local IMG_HASH2=$(sha256sum $IMG | cut -d' ' -f 1)
71         [ "$IMG_HASH" != "$IMG_HASH2" ] && fail "Image changed!"
72 }
73
74 function img_prepare_raw() # $1 options
75 {
76         remove_mapping
77
78         if [ ! -e $KEY1 ]; then
79                 dd if=/dev/urandom of=$KEY1 count=1 bs=32 >/dev/null 2>&1
80         fi
81
82         truncate -s 32M $IMG || fail
83         $CRYPTSETUP luksFormat $FAST_PBKDF2 $CS_PARAMS --luks2-metadata-size $JSON_MSIZE $IMG $1 || fail
84 }
85
86 function img_prepare() # $1 options
87 {
88         img_prepare_raw
89         # FIXME: resilience is not saved here (always none)?
90         $CRYPTSETUP reencrypt $IMG $CS_PARAMS -q --init-only --resilience none $1 >/dev/null 2>&1
91         [ $? -ne 0 ] && skip "Reencryption unsupported, test skipped."
92         img_json_save
93         img_hash_save
94 }
95
96 function _dd()
97 {
98         dd $@ status=none conv=notrunc bs=1
99 }
100
101 # header mangle functions
102 function img_update_json()
103 {
104         local LUKS2_BIN1_OFFSET=448
105         local LUKS2_BIN2_OFFSET=$((LUKS2_BIN1_OFFSET + $JSON_MSIZE))
106         local LUKS2_JSON_SIZE=$(($JSON_MSIZE - 4096))
107
108         # if present jq script, mangle JSON
109         if [ -n "$1" ]; then
110                 local JSON=$(cat $IMG_JSON)
111                 echo $JSON | jq -M -c "$1" >$IMG_JSON || fail
112                 local JSON=$(cat $IMG_JSON)
113                 echo $JSON | tr -d '\n' >$IMG_JSON || fail
114         fi
115
116         # wipe JSON areas
117         _dd if=/dev/zero of=$IMG count=$LUKS2_JSON_SIZE seek=4096
118         _dd if=/dev/zero of=$IMG count=$LUKS2_JSON_SIZE seek=$(($JSON_MSIZE + 4096))
119
120         # write JSON data
121         _dd if=$IMG_JSON of=$IMG count=$LUKS2_JSON_SIZE seek=4096
122         _dd if=$IMG_JSON of=$IMG count=$LUKS2_JSON_SIZE seek=$(($JSON_MSIZE + 4096))
123
124         # erase sha256 checksums
125         _dd if=/dev/zero of=$IMG count=64 seek=$LUKS2_BIN1_OFFSET
126         _dd if=/dev/zero of=$IMG count=64 seek=$LUKS2_BIN2_OFFSET
127
128         # calculate sha256 and write chexksums
129         local SUM1_HEX=$(_dd if=$IMG count=$JSON_MSIZE | sha256sum | cut -d ' ' -f 1)
130         echo $SUM1_HEX | xxd -r -p | _dd of=$IMG seek=$LUKS2_BIN1_OFFSET count=64 || fail
131
132         local SUM2_HEX=$(_dd if=$IMG skip=$JSON_MSIZE count=$JSON_MSIZE | sha256sum | cut -d ' ' -f 1)
133         echo $SUM2_HEX | xxd -r -p | _dd of=$IMG seek=$LUKS2_BIN2_OFFSET count=64 || fail
134
135         img_hash_save
136 }
137
138 function img_check_ok()
139 {
140         if [ $(id -u) == 0 ]; then
141                 $CRYPTSETUP open $CS_PWPARAMS $IMG $DEV_NAME || fail
142                 $CRYPTSETUP close $DEV_NAME || fail
143         fi
144
145         $CRYPTSETUP repair $IMG $CS_PARAMS || fail
146 }
147
148 function img_check_fail()
149 {
150         if [ $(id -u) == 0 ]; then
151                 $CRYPTSETUP open $CS_PWPARAMS $IMG $DEV_NAME 2>/dev/null && fail
152         fi
153
154         $CRYPTSETUP repair $IMG $CS_PARAMS 2>/dev/null && fail
155         img_hash_unchanged
156 }
157
158 function img_run_reenc_ok()
159 {
160 local EXPECT_TIMEOUT=5
161 [ -n "$VALG" ] && EXPECT_TIMEOUT=60
162 # For now, we cannot run reencryption in batch mode for non-block device. Just fake the terminal here.
163 expect_run - >/dev/null <<EOF
164 proc abort {} { send_error "Timeout. "; exit 2 }
165 set timeout $EXPECT_TIMEOUT
166 eval spawn $CRYPTSETUP_RAW reencrypt $IMG $CS_PWPARAMS --disable-locks --resilience none
167 expect timeout abort "Are you sure? (Type 'yes' in capital letters):"
168 send "YES\n"
169 expect timeout abort eof
170 exit
171 EOF
172 [ $? -eq 0 ] || fail "Expect script failed."
173 }
174
175 function img_run_reenc_fail()
176 {
177 local EXPECT_TIMEOUT=5
178 [ -n "$VALG" ] && EXPECT_TIMEOUT=60
179 # For now, we cannot run reencryption in batch mode for non-block device. Just fake the terminal here.
180 expect_run - >/dev/null <<EOF
181 proc abort {} { send_error "Timeout. "; exit 42 }
182 set timeout $EXPECT_TIMEOUT
183 eval spawn $CRYPTSETUP_RAW reencrypt $IMG $CS_PWPARAMS --disable-locks
184 expect timeout abort "Are you sure? (Type 'yes' in capital letters):"
185 send "YES\n"
186 expect timeout abort eof
187 catch wait result
188 exit [lindex \$result 3]
189 EOF
190 local ret=$?
191 [ $ret -eq 0 ] &&  fail "Reencryption passed (should have failed)."
192 [ $ret -eq 42 ] && fail "Expect script failed."
193 img_hash_unchanged
194 }
195
196 function img_check_fail_repair_ok()
197 {
198         if [ $(id -u) == 0 ]; then
199                 $CRYPTSETUP open $CS_PWPARAMS $IMG $DEV_NAME 2>/dev/null && fail
200         fi
201
202         img_run_reenc_fail
203
204         # repair metadata
205         $CRYPTSETUP repair $IMG $CS_PARAMS || fail
206
207         img_check_ok
208         img_run_reenc_ok
209 }
210
211 function valgrind_setup()
212 {
213         bin_check valgrind
214         [ ! -f $CRYPTSETUP_VALGRIND ] && fail "Unable to get location of cryptsetup executable."
215         export LD_LIBRARY_PATH="$CRYPTSETUP_LIB_VALGRIND:$LD_LIBRARY_PATH"
216         CRYPTSETUP=valgrind_run
217         CRYPTSETUP_RAW="./valg.sh ${CRYPTSETUP_VALGRIND}"
218 }
219
220 function valgrind_run()
221 {
222         INFOSTRING="$(basename ${BASH_SOURCE[1]})-line-${BASH_LINENO[0]}" ./valg.sh ${CRYPTSETUP_VALGRIND} "$@"
223 }
224
225 function expect_run()
226 {
227         export INFOSTRING="$(basename ${BASH_SOURCE[1]})-line-${BASH_LINENO[0]}"
228         expect "$@"
229 }
230
231 bin_check jq
232 bin_check sha256sum
233 bin_check xxd
234 bin_check expect
235
236 export LANG=C
237
238 [ -n "$VALG" ] && valgrind_setup && CRYPTSETUP=valgrind_run
239
240 #while false; do
241
242 echo "[1] Reencryption with old flag is rejected"
243 img_prepare
244 img_update_json '.config.requirements.mandatory = ["online-reencryptx"]'
245 img_check_fail
246 img_update_json '.config.requirements.mandatory = ["online-reencrypt-v2"]'
247 img_check_ok
248 img_run_reenc_ok
249 img_check_ok
250
251 # Simulate old reencryption with no digest (repairable)
252 img_prepare
253 img_update_json 'del(.digests."2") | .config.requirements.mandatory = ["online-reencrypt"]'
254 img_check_fail_repair_ok
255
256 # This must fail for new releases
257 echo "[2] Old reencryption in-progress (journal)"
258 img_prepare
259 img_update_json '
260         del(.digests."2") |
261         .keyslots."2".area.type = "journal" |
262         .segments = {
263                 "0" : (.segments."0" +
264                         {"size" : .keyslots."2".area.size} +
265                         {"flags" : ["in-reencryption"]}),
266                 "1" : (.segments."0" +
267                         {"offset" : ((.segments."0".offset|tonumber) +
268                         (.keyslots."2".area.size|tonumber))|tostring}),
269                 "2" : .segments."1",
270                 "3" : .segments."2"
271         } |
272         .digests."0".segments = ["1","2"] |
273         .digests."1".segments = ["0","3"] |
274         .config.requirements.mandatory = ["online-reencrypt"]'
275 img_check_fail_repair_ok
276
277 echo "[3] Old reencryption in-progress (checksum)"
278 img_prepare
279 img_update_json '
280         del(.digests."2") |
281         .keyslots."2".area.type = "checksum" |
282         .keyslots."2".area.hash = "sha256" |
283         .keyslots."2".area.sector_size = 4096 |
284         .segments = {
285                 "0" : (.segments."0" +
286                         {"size" : .keyslots."2".area.size} +
287                         {"flags" : ["in-reencryption"]}),
288                 "1" : (.segments."0" +
289                         {"offset": ((.segments."0".offset|tonumber) +
290                         (.keyslots."2".area.size|tonumber))|tostring}),
291                 "2" : .segments."1",
292                 "3" : .segments."2"
293         } |
294         .digests."0".segments = ["1","2"] |
295         .digests."1".segments = ["0","3"] |
296         .config.requirements.mandatory = ["online-reencrypt"]'
297 img_check_fail_repair_ok
298
299 # Note: older tools cannot create this from commandline
300 echo "[4] Old decryption in-progress (journal)"
301 img_prepare
302 img_update_json '
303         del(.digests."1") |
304         del(.digests."2") |
305         del(.keyslots."1") |
306         .keyslots."2".mode = "decrypt" |
307         .keyslots."2".area.type = "journal" |
308         .segments = {
309                 "0" : {
310                         "type" : "linear",
311                         "offset" : .segments."0".offset,
312                         "size" : .keyslots."2".area.size,
313                         "flags" : ["in-reencryption"]
314                 },
315                 "1" : (.segments."0" +
316                         {"offset" : ((.segments."0".offset|tonumber) +
317                         (.keyslots."2".area.size|tonumber))|tostring}),
318                 "2" : .segments."1",
319                 "3" : {
320                         "type" : "linear",
321                         "offset" : .segments."0".offset,
322                         "size" : "dynamic",
323                         "flags" : ["backup-final"]
324                 }
325         } |
326         .digests."0".segments = ["1","2"] |
327         .config.requirements.mandatory = ["online-reencrypt"]'
328 img_check_fail_repair_ok
329
330 echo "[5] Old decryption in-progress (checksum)"
331 img_prepare
332 img_update_json '
333         del(.digests."1") |
334         del(.digests."2") |
335         del(.keyslots."1") |
336         .keyslots."2".mode = "decrypt" |
337         .keyslots."2".area.type = "checksum" |
338         .keyslots."2".area.hash = "sha256" |
339         .keyslots."2".area.sector_size = 4096 |
340         .segments = {
341                 "0" : {
342                         "type" : "linear",
343                         "offset" : .segments."0".offset,
344                         "size" : .keyslots."2".area.size,
345                         "flags" : ["in-reencryption"]
346                 },
347                 "1" : (.segments."0" +
348                         {"offset" : ((.segments."0".offset|tonumber) +
349                         (.keyslots."2".area.size|tonumber))|tostring}),
350                 "2" : .segments."1",
351                 "3" : {
352                         "type" : "linear",
353                         "offset" : .segments."0".offset,
354                         "size" : "dynamic",
355                         "flags" : ["backup-final"]
356                 }
357         } |
358         .digests."0".segments = ["1","2"] |
359         .config.requirements.mandatory = ["online-reencrypt"]'
360 img_check_fail_repair_ok
361
362 # Note - offset is set to work with the old version (with a datashift bug)
363 echo "[6] Old reencryption in-progress (datashift)"
364 img_prepare
365 img_update_json '
366         del(.digests."2") |
367         .keyslots."2".direction = "backward" |
368         .keyslots."2".area.type = "datashift" |
369         .keyslots."2".area.size = "4096" |
370         .keyslots."2".area.shift_size = ((1 * 1024 * 1024)|tostring) |
371         .segments = {
372                 "0" : (.segments."0" +
373                         {"size" : ((13 * 1024 * 1024)|tostring)}),
374                 "1" : (.segments."0" +
375                         {"offset" : ((30 * 1024 * 1024)|tostring)}),
376                 "2" : .segments."1",
377                 "3" : (.segments."2" +
378                         {"offset" : ((17 * 1024 * 1024)|tostring)}),
379         } |
380         .digests."0".segments = ["0","2"] |
381         .digests."1".segments = ["1","3"] |
382         .config.requirements.mandatory = ["online-reencrypt"]'
383 img_check_fail_repair_ok
384
385 #
386 # NEW metadata (with reenc digest)
387 #
388 echo "[7] Reencryption with various mangled metadata"
389
390 # Normal situation
391 img_prepare
392 img_run_reenc_ok
393 img_check_ok
394
395 # The same in various steps.
396 # Repair must validate not only metadata, but also reencryption digest.
397 img_prepare
398 img_update_json 'del(.digests."2")'
399 img_check_fail_repair_ok
400
401 img_prepare '--reduce-device-size 2M'
402 img_update_json '.keyslots."2".area.shift_size = ((.keyslots."2".area.shift_size|tonumber / 2)|tostring)'
403 img_check_fail
404
405 #FIXME: cannot check with correct digest for now (--init-only does not store area type)
406 img_prepare
407 img_update_json '
408         .keyslots."2".area.type = "checksum" |
409         .keyslots."2".area.hash = "sha256" |
410         .keyslots."2".area.sector_size = 4096'
411 img_check_fail
412
413 img_prepare
414 img_update_json '.keyslots."2".area.type = "journal"'
415 img_check_fail
416
417 img_prepare
418 img_update_json '.keyslots."2".mode = "decrypt"'
419 img_check_fail
420
421 img_prepare
422 img_update_json '.keyslots."2".direction = "backward"'
423 img_check_fail
424
425 # key_size must be 1
426 img_prepare
427 img_update_json '.keyslots."2".key_size = 16'
428 img_check_fail
429
430 # Mangling segments
431 img_prepare
432 img_update_json 'del(.segments."1")'
433 img_check_fail
434
435 img_prepare
436 img_update_json '.segments."0".encryption = "aes-cbc-null"'
437 img_check_fail
438
439 img_prepare
440 img_update_json '.segments."1".encryption = "aes-cbc-null"'
441 img_check_fail
442
443 img_prepare
444 img_update_json '.segments."2".encryption = "aes-cbc-null"'
445 img_check_fail
446
447 # Mangling digests
448 img_prepare
449 img_update_json '
450         .digests."2" = .digests."0" |
451         .digests."2".keyslots = ["2"] |
452         .digests."2".segments = []'
453 img_check_fail
454
455 img_prepare
456 img_update_json '.digests."2".iterations = 1111'
457 img_check_fail
458
459 # Simulate correct progress
460 img_prepare
461 img_update_json '
462         .segments = {
463                 "0" : (.segments."0" +
464                         {"size" : ((1 * 1024 * 1024)|tostring)}),
465                 "1" : (.segments."0" +
466                         {"offset" : ((17 * 1024 * 1024)|tostring)}),
467                 "2" : .segments."1",
468                 "3" : .segments."2"
469         } |
470         .digests."0".segments = ["1","2"] |
471         .digests."1".segments = ["0","3"]'
472 img_check_ok
473
474 # Mangling keyslots
475
476 # Set reencrypt slot to non-ignore priority
477 # This should be benign, just avoid noisy messages
478 img_prepare
479 img_update_json 'del(.keyslots."2".priority)'
480 img_check_ok
481
482 # Flags
483
484 # Remove mandatory reenc flag, but keep reenc metadata
485 img_prepare
486 img_update_json '.config.requirements.mandatory = []'
487 img_check_fail
488
489 # Unknown segment flag, should be ignored
490 img_prepare
491 img_update_json '.segments."0".flags = ["dead-parrot"]'
492 img_check_ok
493
494 echo "[8] Reencryption with AEAD is not supported"
495 img_prepare_raw
496 img_json_save
497 img_update_json '
498         .segments."0".integrity = {
499                 "type" : "hmac(sha256)",
500                 "journal_encryption": "none",
501                 "journal_integrity": "none"
502         }'
503 $CRYPTSETUP reencrypt $IMG $CS_PARAMS >/dev/null 2>&1 && fail
504
505 remove_mapping
506 exit 0