-
Notifications
You must be signed in to change notification settings - Fork 1
/
luksblk
executable file
·326 lines (288 loc) · 9.31 KB
/
luksblk
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
#!/bin/sh
# High-level tools for managing a LUKS block device.
#
# see `help` for summary of subcommands and environment variables
BLK="${BLK:-block}"
BLK_FS="${BLK_FS:-ext4}"
LUKS_CIPHER="${LUKS_CIPHER:-aes-xts-plain64}"
set -e
S_UNKNOWN=UNKNOWN
S_NODEV_FILE=NODEV_FILE
S_CRYPT_PREINIT=CRYPT_PREINIT
S_CRYPT_AVAIL=CRYPT_AVAIL
S_PLAIN_MOUNTED=PLAIN_MOUNTED
S_CURRENT=
_rereadvars() {
BLK_KEY="${BLK}.luks.0"
BLK_HEADER="${BLK}.luks"
BLK_MNT="${BLK}.mnt"
BLK_DEV="${BLK}"
BLK_CRYPT="$(basename "$BLK")_crypt" # crypt device name
DEV="$(readlink -f "$BLK_DEV" || true)"
DST="$(readlink -f "$BLK_MNT" || true)"
}
_state() {
if [ -b "$BLK_DEV" ]; then
if mountpoint -q "$DST"; then
S_CURRENT=$S_PLAIN_MOUNTED
return
fi
if [ ! -r "$DEV" ]; then
echo >&2 "warning: no read permission on device $DEV"
S_CURRENT=$S_UNKNOWN
return
fi
if [ ! -f "$BLK_HEADER" ]; then
S_CURRENT=$S_CRYPT_PREINIT
return
fi
case $(head -c4 "$BLK_HEADER" | hexdump -e '/1 "%02X"') in
"4C554B53") S_CURRENT=$S_CRYPT_AVAIL;;
*) S_CURRENT=$S_CRYPT_PREINIT;;
esac
else
if [ -f "$BLK.img" ]; then
S_CURRENT=$S_NODEV_FILE
else
S_CURRENT=$S_UNKNOWN
fi
fi
}
_state_in() {
_state
for i in "$@"; do test "$i" = "$S_CURRENT" && return 0; done
return 1
}
SILENT_PRECOND="${SILENT_PRECOND:-false}"
_precond() { _state_in "$@" || { $SILENT_PRECOND || echo >&2 "precondition not fufilled: current state not one of $@"; return 1; }; }
# sleep 0.2 since udev can take a while to apply settings
_postcond() { _state_in "$@" || { echo >&2 "postcondition not fufilled (bug?): current state not one of $@"; return 127; }; }
log() { echo >&2 "$@"; }
# lots of shell fuckery to approximate a try-finally block
_TRY_STACK=0
_TRY_EXIT_CODE=0 # it should be ok for child blocks to clobber this since non-0 means we want to propagate this value to the parent anyway
try() {
_TRY_EXIT_CODE=0
_TRY_STACK=$((_TRY_STACK+1));
set +e;
}
not_catch() {
_TRY_EXIT_CODE=$?
return $_TRY_EXIT_CODE
}
finally() {
local x=$?
test "$_TRY_EXIT_CODE" = 0 && _TRY_EXIT_CODE=$x
_TRY_STACK=$((_TRY_STACK-1));
if [ "$_TRY_STACK" = 0 ]; then set -e; fi
}
endf() { # use as eval $(endf)
test "$_TRY_EXIT_CODE" = 0 || echo 'return $_TRY_EXIT_CODE'
}
################################################################################
## file helper functions
################################################################################
file_init() {
_precond $S_UNKNOWN
local MB="$1"
expr "$MB" + 0 >/dev/null
test ! -f "$BLK.img"
log "create $BLK.img empty image file"
dd if=/dev/zero of="$BLK.img" seek="$MB" bs=1048576 count=0 status=none
_postcond $S_NODEV_FILE
}
file_expose() {
if [ "$(sudo losetup -j "$BLK.img")" != "$(readlink "$BLK_DEV")" ]; then
rm -f "$BLK_DEV"
_state
fi
_postcond $S_CRYPT_PREINIT $S_CRYPT_AVAIL 2>/dev/null && return
_precond $S_NODEV_FILE
local DEV="$(sudo losetup --show -f "$BLK.img" || exit 1)"
ln -sf "$DEV" "$BLK_DEV"
_rereadvars
_postcond $S_CRYPT_PREINIT $S_CRYPT_AVAIL
}
file_unexpose() {
_postcond $S_NODEV_FILE 2>/dev/null && return
_precond $S_CRYPT_PREINIT $S_CRYPT_AVAIL
if sudo losetup -j "$BLK.img" | grep -q "$(readlink -f "$BLK_DEV")"; then
sudo losetup -d "$DEV"
fi
rm -f "$BLK_DEV"
_postcond $S_NODEV_FILE
}
file_expand2fs() {
_precond $S_NODEV_FILE
local MB="$1"
expr "$MB" + 0 >/dev/null
local osz="$(stat -L -c %s "$BLK.img")"
test "$(expr "$MB" \* 1048576)" -ge "$osz" || { log "won't shrink due to higher risk of data loss; do this yourself manually"; exit 1; }
local omb=$(((osz-1)/1048576+1))
# needs to be run on actual image file
log "expanding $BLK.img"
dd if=/dev/zero of="$BLK.img" seek="$MB" bs=1048576 count=0 status=none
file_expose
_state
log "filling $BLK with random data"
blk_random "$omb"
try
sudo cryptsetup luksOpen --header "$BLK_HEADER" -d "$BLK_KEY" "$DEV" "$BLK_CRYPT"
not_catch && __file_expand2fs_next() {
try
/sbin/e2fsck -f "/dev/mapper/$BLK_CRYPT"
not_catch && /sbin/resize2fs "/dev/mapper/$BLK_CRYPT" "${MB}M"
finally
sudo cryptsetup luksClose "$BLK_CRYPT"
eval $(endf)
} && __file_expand2fs_next
finally
file_unexpose
eval $(endf)
_postcond $S_NODEV_FILE
}
################################################################################
## block device functions
################################################################################
BADBLOCKS=${BADBLOCKS:-true}
RNGFILL=${RNGFILL:-true}
blk_init() {
_precond $S_CRYPT_PREINIT
test -f "$BLK_KEY" || {
log "create $BLK_KEY containing random key"
head -c32 /dev/random | base64 > "$BLK_KEY"
}
rm -f "$BLK_HEADER"
log "create $BLK_HEADER with random data"
dd if=/dev/urandom of="$BLK_HEADER" bs=1048576 count=2 status=none
if $BADBLOCKS; then
log "running badblocks (set BADBLOCKS=false to skip, e.g. for non-physical device)"
badblocks -svw -b 1048576 "$DEV"
fi
if $RNGFILL; then
log "filling $BLK with random data (set RNGFILL=false to skip, e.g. if already done)"
blk_random 0
fi
log "create $BLK with key $BLK_KEY and filesystem $BLK_FS"
expr "$LUKS_CIPHER" : '.*xts.*' >/dev/null && key_size=512 || key_size=256
sudo cryptsetup luksFormat --header "$BLK_HEADER" -q -c "$LUKS_CIPHER" -s "$key_size" -h sha256 "$DEV" "$BLK_KEY"
sudo cryptsetup luksOpen --header "$BLK_HEADER" -d "$BLK_KEY" "$DEV" "$BLK_CRYPT"
try
"/sbin/mkfs.$BLK_FS" "/dev/mapper/$BLK_CRYPT"
finally
sudo cryptsetup luksClose "$BLK_CRYPT"
eval $(endf)
_postcond $S_CRYPT_AVAIL
}
blk_random() {
dd if=/dev/urandom of="$BLK" bs=1048576 seek="$1" & pid_dd=$!
# TODO: support resume: grep for 00000000 before/after? "$1" and start there
pgrep -a dd >/dev/null
trap "kill $pid_dd; exit 127" INT TERM
while kill -USR1 $pid_dd 2>/dev/null; do
for i in $(seq 1 16); do
sleep 1
kill -0 $pid_dd 2>/dev/null || break 2
done
done
}
blk_mount() {
_postcond $S_PLAIN_MOUNTED 2>/dev/null && return
_precond $S_CRYPT_AVAIL
test -d "$BLK_MNT" || {
log "creating mount point: $BLK_MNT"
log "if you want to use a different mount point, please unmount your cryptdisk, then symlink $BLK_MNT to the desired location."
mkdir -p "$BLK_MNT"
}
sudo cryptsetup luksOpen --header "$BLK_HEADER" -d "$BLK_KEY" "$DEV" "$BLK_CRYPT"
sudo mount $MOUNTOPTS "/dev/mapper/$BLK_CRYPT" "$DST"
_postcond $S_PLAIN_MOUNTED
}
blk_umount() {
_postcond $S_CRYPT_AVAIL 2>/dev/null && return
_precond $S_PLAIN_MOUNTED
sudo umount "/dev/mapper/$BLK_CRYPT" || true
sudo cryptsetup luksClose "$BLK_CRYPT"
_postcond $S_CRYPT_AVAIL
}
blk_compound() {
local subcmd="$1"
shift
case "$subcmd" in
*_setup)
local type="${subcmd%_setup}"
_state
"${type}_init" "$@"
"${type}_expose"
try
BADBLOCKS=false blk_init
finally
"${type}_unexpose"
eval $(endf)
;;
*_mount)
local type="${subcmd%_mount}"
# `(subshell) || true` would unrecoverably cancel errexit inside the subshell
SILENT_PRECOND=true sh "$0" "${type}_expose" || true
blk_mount
;;
*_umount)
local type="${subcmd%_umount}"
# `(subshell) || true` would unrecoverably cancel errexit inside the subshell
SILENT_PRECOND=true sh "$0" blk_umount || true
"${type}_unexpose"
;;
esac
}
################################################################################
## main
################################################################################
if [ -n "$DEBUG" ]; then set -x; fi
_rereadvars
_state
subcmd="$1"; test -n "$subcmd" && shift
case "$subcmd" in
blk_*) "$subcmd" "$@";;
*_setup|*_mount|*_umount) blk_compound "$subcmd" "$@";;
file_*) "$subcmd" "$@";;
status)
case $S_CURRENT in
$S_UNKNOWN) echo "unknown status";;
$S_NODEV_FILE) echo "unavailable device, from image file";;
$S_CRYPT_PREINIT) echo "uninitialised device";;
$S_CRYPT_AVAIL) echo "crypt device, unmounted";;
$S_PLAIN_MOUNTED) echo "crypt device, mounted";;
esac
;;
*) cat <<'EOF'
Usage: [<VAR>=<val>] $0 <SUBCMD> [<ARGS>]
High-level tools for managing a LUKS block device, with detached headers.
Subcommands:
file_setup <sz> Create an image file to hold LUKS encrypted data.
file_mount Mount an image file holding LUKS encrypted data.
file_umount Unmount an image file holding LUKS encrypted data.
Subcommands (block devices):
blk_init LUKS-format the device, and create a new filesystem inside it
blk_mount Open the crypt device and mount the filesystem
blk_umount Unmount the filesystem and close the crypt device
Subcommands ("file" helpers):
file_init <sz> Create a file as a block device image, <sz> in MiB
file_expose Expose the file as a loopback block device
file_unexpose Unexpose the loopback device
file_expand2fs <sz> Expand the file image and the encrypted ext2+ fs within it
Subcommands ("LVM" helpers):
lvm_init <sz> [TODO] Create an LV as a block device, <sz> in MiB
lvm_expand2fs [TODO] Expand an LV and the encrypted ext2+ fs within it
Environment variables:
BLK Path to symlink to your block device, default 'block'
BLK_FS (setup only) set the filesystem type, default ext4
LUKS_CIPHER (setup only) set the cipher spec, default aes-xts-plain64
Files:
${block} symlink to block device ($BLK)
${block}.luks.0 LUKS key for slot 0
${block}.luks LUKS header
${block}.mnt mount destination (or symlink to it)
${block}.img device image file (or symlink to it)
EOF
;;
esac