/
sign_image
executable file
·484 lines (484 loc) · 24.9 KB
/
sign_image
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
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
#! /bin/sh
# SPDX-License-Identifier: GPL-2.0-or-later
####################################################################################
# #
# sign a tar archive with our private RSA key #
# #
# The key has to be generated with the right name (look at image_signing_files.inc #
# for the name components) and has to be reachable at the current working #
# directory, if the "name_prefix" component does not contain a complete path #
# (absolute or relative). #
# #
# The archive to sign may not contain a var/signature member, it would lead to #
# a confusion in "firmwarecfg", if the hash is recomputed later for checking. #
# #
# The signed image is written to STDOUT (it is created "on the fly" and will never #
# be stored by the script itself to save space, if the process runs on the target #
# device) and the caller is responsible to redirect the output to the final #
# destination. #
# #
# To ensure proper archive handling, the input file must use the pure GNU tar #
# format without PAXHEADERS and longer EoA headers. #
# #
####################################################################################
# #
# determine our script path to locate the include file #
# #
####################################################################################
my_path="$0"
[ "${my_path%/*}" == "$my_path" ] && my_path="." || my_path="${my_path%/*}"
my_name="${0##*/}"
box_key_name="/var/flash/websrv_ssl_key"
####################################################################################
# #
# include the common file definitions #
# #
####################################################################################
source "$my_path/image_signing_files.inc"
####################################################################################
# #
# get the path for needed external commands, if called in any toolchain #
# #
# looks a little bit complicated, but has to support definitions for BusyBox #
# applets as commands to be used and simple quoting isn't enough to ensure proper #
# substitution, but spreading 'eval' statements all over the code looks ugly, too #
# #
####################################################################################
if ! [ -z "$YF_SIGNIMAGE_TAR" ]; then
__YF_SIGNIMAGE_TAR="$YF_SIGNIMAGE_TAR"
__yf_signimage_tar()
{
eval $__YF_SIGNIMAGE_TAR $*
}
YF_SIGNIMAGE_TAR="__yf_signimage_tar"
else
YF_SIGNIMAGE_TAR="tar"
fi
if ! [ -z "$YF_SIGNIMAGE_DD" ]; then
__YF_SIGNIMAGE_DD="$YF_SIGNIMAGE_DD"
__yf_signimage_dd()
{
eval $__YF_SIGNIMAGE_DD $*
}
YF_SIGNIMAGE_DD="__yf_signimage_dd"
else
YF_SIGNIMAGE_DD="dd"
fi
if ! [ -z "$YF_SIGNIMAGE_OPENSSL" ]; then
__YF_SIGNIMAGE_OPENSSL="$YF_SIGNIMAGE_OPENSSL"
__yf_signimage_openssl()
{
eval $__YF_SIGNIMAGE_OPENSSL $*
}
YF_SIGNIMAGE_OPENSSL="__yf_signimage_openssl"
else
YF_SIGNIMAGE_OPENSSL="openssl"
fi
####################################################################################
# #
# some subfunctions #
# #
####################################################################################
show_error() { echo -e "\x1B[1;31mFAILED\x1B[0m" 1>&2; }
show_ok() { echo -e "\x1B[1;32mOK\x1B[0m" 1>&2; }
show_version()
{
local v
v=$("$YF_SIGNIMAGE_OPENSSL" version 2>/dev/null)
if [ $? -eq 127 ]; then
echo -e "Missing \x1B[1mopenssl\x1B[0m binary, set YF_SIGNIMAGE_OPENSSL variable to its path name." 1>&2
return 1
else
echo -e "Found \x1B[1;34m$v\x1B[0m" 1>&2
return 0
fi
}
####################################################################################
# #
# usage screen, caller has to redirect output to STDERR if needed #
# #
####################################################################################
usage()
{
echo -e "\x1B[1mSign a tar archive file as firmware image for FRITZ!OS devices.\
\x1B[0m\n"
echo -e "Usage:\n"
echo -e "$0 \x1B[1mimagefile\x1B[0m [\x1B[1mpassword\x1B[0m]\n"
echo -e "\x1B[1mimagefile\x1B[0m is the archive to sign, there must be no \x1B[\
1mvar/signature\x1B[0m file"
echo -e "embedded yet.\n"
echo -e "The \x1B[1mpassword\x1B[0m will be read from the terminal, if it's not\
specified."
echo -e "The signed image is written to STDOUT, you should redirect it to the"
echo -e "proper target file location.\n"
echo -e "The default hash algorithm used is MD5, as it's used by the current\nA\
VM implementation. If you know for sure, that you will only use non-\nAVM scripts/c\
omponents to sign and verify, you may specify a better\nhash algorithm (remember th\
at the openssl binary still has to support\nit) with an environment variable USEHAS\
H specifying the name of the\nalgorithm to use.\n"
echo -e "A special use case is the signing of a TAR file directly in a FRITZ!OS\
\nenvironment, if such a signature is surely checked later only on the\nsame device\
. Then we may use the RSA key from \x1B[1mwebsrv_ssl_key.pem\x1B[0m\ninstead of an \
additional key and we don't need to specify its\npassword, because it may be read w\
ith \x1B[1mprivatekeypassword\x1B[0m. To switch\nthis script into this 'internal-\o\
nly mode', set the environment\nvariable SIGN_ON_BOX to '1' while calling it. Be aw\
are, that a key\nsize other than 1024 bits would lead to the impossibility to verif\
y\nthe signed file with AVM components."
}
####################################################################################
# #
# compute checksum of a TAR header #
# #
####################################################################################
checksum()
{
read_values()
{
while read pos left right; do
sum=$(( sum + 0$left ))
done
printf "%d" $sum
}
sum=0
cmp -l -- "$1" /dev/zero 2>/dev/null | read_values
}
####################################################################################
# #
# try to identify FRITZ!OS as runtime environment (not too sophisticated, but it #
# should be able to make this distinction) #
# #
####################################################################################
is_fritzos_environment()
{
local rc=1 eva_prompt="Eva_AVM"
[ ${#HWRevision} -eq 0 ] && return $rc
[ ${#CONFIG_ENVIRONMENT_PATH} -eq 0 ] && return $rc
[ -d "$CONFIG_ENVIRONMENT_PATH" ] || return $rc
[ -f "$CONFIG_ENVIRONMENT_PATH/environment" ] || return $rc
local HWRev="$(sed -n -e "s|^HWRevision\t\(.*\)\$|\1|p" $CONFIG_ENVIRONMENT_PATH/environment)"
[ ${#HWRev} -eq 0 ] && return $rc
[ "$HWRev" != "$HWRevision" ] && return $rc
local prompt="$(sed -n -e "s|^prompt\t\(.*\)\$|\1|p" $CONFIG_ENVIRONMENT_PATH/environment)"
[ ${#prompt} -eq 0 ] && return $rc
[ "$prompt" != "$eva_prompt" ] && return $rc
return 0
}
####################################################################################
# #
# check parameters #
# #
####################################################################################
if [ -z "$1" ]; then
usage 1>&2
exit 1
else
image_file="$1"
fi
if ! [ -f "$image_file" ]; then
echo -e "The specified image file \x1B[1m$image_file\x1B[0m does not exist." 1>&2
exit 1
fi
if [ -t 1 ]; then
echo -e "The output stream is a terminal device, please redirect output to a file." 1>&2
exit 1
fi
####################################################################################
# #
# check OpenSSL presence and version #
# #
####################################################################################
show_version
[ $? -ne 0 ] && exit 1
echo -en "Check \x1B[1mdgst\x1B[0m command ... " 1>&2
echo "" | "$YF_SIGNIMAGE_OPENSSL" dgst 2>&1 | grep -q '^(stdin)=' 2>/dev/null 1>&2
rc=$?
if [ $rc -eq 0 ]; then
show_ok
else
show_error
exit 1
fi
echo -en "Check \x1B[1mrsa\x1B[0m command ... " 1>&2
echo "" | "$YF_SIGNIMAGE_OPENSSL" rsa 2>&1 | grep -q '^unable to load' 2>/dev/null 1>&2
rc=$?
if [ $rc -eq 0 ]; then
show_ok
else
show_error
exit 1
fi
####################################################################################
# #
# check the specified hash algorithm and verify, it's provided by the openssl #
# binary #
# #
####################################################################################
if [ "${#USEHASH}" -gt 0 ]; then
check_algo="$(echo "$USEHASH" | sed -e "y/ABCDEFGHIJKLMNOPQRSTUVWXYZ/abcdefghijklmnopqrstuvwxyz/")"
hash_algo=""
for algo in md5 sha1 sha224 sha256 sha384 sha512 whirlpool; do
if [ "$algo" == "$check_algo" ]; then
hash_algo=$algo
break
fi
done
if [ "${#hash_algo}" -eq 0 ]; then
echo -e "Unknown or unsupported (by this script) hash algorithm \x1B[1m$USEHASH\x1B[0m specified." 1>&2
exit 1
fi
else
hash_algo="md5"
fi
echo -en "Verify hash algorithm \x1B[1m$hash_algo\x1B[0m is supported ... " 1>&2
echo "" | "$YF_SIGNIMAGE_OPENSSL" dgst -$hash_algo 2>&1 2>/dev/null 1>&2
rc=$?
if [ $rc -eq 0 ]; then
show_ok
else
show_error
exit 1
fi
####################################################################################
# #
# use 'force-local' option with GNU TAR, if the image name contains a colon #
# #
####################################################################################
if [ -n "$(expr "$image_file" : ".*\(:\).*")" ]; then
"$YF_SIGNIMAGE_TAR" --help 2>&1 | grep -q 'force-local' 2>/dev/null && force_local="--force-local"
else
force_local=""
fi
####################################################################################
# #
# verify input file format #
# #
####################################################################################
printf "Checking input file format ... " 1>&2
"$YF_SIGNIMAGE_DD" if="$image_file" bs=1 skip=257 count=5 status=none 2>/dev/null | grep -q "^ustar\$" 2>/dev/null
if [ $? -ne 0 ]; then
show_error
echo -e "Input file doesn't look like a TAR archive." 1>&2
exit 1
fi
"$YF_SIGNIMAGE_DD" if="$image_file" bs=100 count=1 status=none 2>/dev/null | grep -q "PaxHeaders" 2>/dev/null
if [ $? -eq 0 ]; then
show_error
echo -e "Input file contains extended headers (PaxHeaders) and may not be signed this way." 1>&2
exit 1
fi
####################################################################################
# #
# check first archive member (name=./var/, type=directory) #
# #
####################################################################################
if ! [ "$("$YF_SIGNIMAGE_DD" if="$image_file" bs=1 count=7 2>/dev/null | base64)" = "Li92YXIvAA==" ] || \
! [ "$("$YF_SIGNIMAGE_DD" if="$image_file" bs=1 count=1 skip=156 2>/dev/null)" = "5" ]; then
show_error
printf "First member of archive file has to be a directory and has to use the name './var/'.\n" 1>&2
exit 1
fi
####################################################################################
# #
# prepare a temporary directory and cleanup on exit #
# #
####################################################################################
tmp=$(mktemp -d)
[ $? -eq 127 ] && tmp="/tmp/tmp.$(date +%s).$$" && mkdir -p "$tmp"
trap "rm -r \"$tmp\"" EXIT HUP
####################################################################################
# #
# check end of archive headers, GNU tar writes more than needed #
# #
####################################################################################
offset=0
"$YF_SIGNIMAGE_TAR" -t -v -f "$image_file" $force_local |
sed -n -e "s|^[^ ]* *[^ ]* *\([0-9]*\) *[^ ]* *[^ ]* *\(.*\)\$|SIZE=\1 MEMBER=\2|p" |
while read line; do
eval $line
file_offset=$offset
file_start=$(( file_offset + 512 ))
file_end=$(( file_start + SIZE ))
offset=$(( ( ( file_end + 511 ) / 512 ) * 512 ))
echo "HEADER=$file_offset START=$file_start END=$file_end SIZE=$SIZE BLOCKS=$(( ( offset - file_offset ) / 512 )) MEMBER=\"$MEMBER\"" >>"$tmp/image_members"
done
copy_blocks=0
while read line; do
i=$(( i + 1 ))
eval $line
copy_blocks=$(( copy_blocks + BLOCKS ))
if [ "$MEMBER" == "./var/signature" ]; then
show_error
echo -e "The input file already contains a member \x1B[1m./var/signature\x1B[0m and may not be signed (again) by this script." 1>&2
exit 1
fi
done <"$tmp/image_members"
file_size="$(stat -c %s "$image_file")"
file_blocks=$(( file_size / 512 ))
eoa_blocks=$(( file_blocks - copy_blocks ))
show_ok
if [ $eoa_blocks -gt 2 ]; then
echo -e "The end of archive markers at the input file are too large: blocks expected=\x1B[1m2\x1B[0m, blocks present=\x1B[1m$eoa_blocks\x1B[0m." 1>&2
echo -e "The input file will be truncated after the last archive member." 1>&2
fi
####################################################################################
# #
# check the special case of signing on the FRITZ!Box with the internal key #
# #
####################################################################################
on_box=0
if [ ${#SIGN_ON_BOX} -gt 0 ]; then
if [ "$SIGN_ON_BOX" == "1" ]; then
if ! is_fritzos_environment; then
echo -e "The special mode to sign an image with the FRITZ!OS RSA key may only be used in a FRITZ!OS environment."
exit 1
else
pkp="$(which privatekeypassword)"
found=$rc
if [ $found -ne 0 ]; then
echo -e "The \x1B[1mprivatekeypassword\x1B[0m executable is missing." 1>&2
exit 1
else
KEYPASSWORD="$(privatekeypassword 2>/dev/null)"
if [ ${#KEYPASSWORD} -eq 0 ]; then
echo -e "Error determining the password for the FRITZ!OS RSA key." 1>&2
exit 1
else
name_prefix="$box_key_name"
private_extension="pem"
on_box=1
fi
fi
fi
fi
fi
####################################################################################
# #
# get the password for the private key, if needed #
# #
####################################################################################
if [ $on_box -eq 0 ]; then
if [ -z $2 ]; then
if ! [ -t 0 ]; then
usage 1>&2
exit 1
else
read -sp "Enter the password for the signing key: " KEYPASSWORD
echo "" 1>&2
fi
else
KEYPASSWORD="$2"
fi
fi
####################################################################################
# #
# check key password first (and key file presence) #
# #
####################################################################################
if [ $on_box -eq 0 ]; then
echo -en "Check the password for the private key file ... " 1>&2
"$YF_SIGNIMAGE_OPENSSL" rsa -in "${name_prefix}.${private_extension}" -noout -passin "pass:$KEYPASSWORD" 2>/dev/null 1>&2
rc=$?
if [ $rc -eq 0 ]; then
show_ok
else
show_error
exit 1
fi
fi
####################################################################################
# #
# create a subdirectory 'var' to prepare a TAR file of our signature later #
# #
####################################################################################
mkdir -p "$tmp/var"
####################################################################################
# #
# prepare a temporary block as filler, if circumvention of AVM's hash error is #
# needed #
# #
####################################################################################
if [ $(( ( copy_blocks + 2 ) % 20 )) -eq 0 ]; then
echo -ne "Repeating first entry as filler ... " 1>&2
"$YF_SIGNIMAGE_DD" if="$image_file" of="$tmp/filler.bin" bs=512 count=1 status=none 2>/dev/null
show_ok
elif [ $(( copy_blocks % 8 )) -eq 5 ] || [ $(( copy_blocks % 8 )) -eq 6 ]; then
# Our signature would nearly fill the last 4K block and another error would get triggered
# (a tar file without proper EoA blocks is used by AVM, if in last 4K block only 1 or 0
# 512-byte blocks are free at the end - it results usually in a "short read" from tar).
echo -ne "Repeating first entry as filler ... " 1>&2
( "$YF_SIGNIMAGE_DD" if="$image_file" bs=512 count=1 status=none 2>/dev/null; \
"$YF_SIGNIMAGE_DD" if="$image_file" bs=512 count=1 status=none 2>/dev/null; ) \
> "$tmp/filler.bin"
show_ok
else
touch "$tmp/filler.bin"
fi
####################################################################################
# #
# prepare a temporary block for an empty TAR member - 512 byte header and 512 byte #
# content, it will be used as "end of archive" header too (2 continous empty #
# blocks with a size of 512 octets) #
# #
####################################################################################
"$YF_SIGNIMAGE_DD" if=/dev/zero of="$tmp/1K.bin" bs=512 count=2 status=none 2>/dev/null
####################################################################################
# #
# generate the signature file #
# #
# - the input file will be combined with another 1 KB containing zeros #
# - any superfluous blocks at the end of the archive are truncated #
# - the original EoA entry will be replaced with an empty 1K block for the sig #
# - an additional 1K block of zeros serves as new EoA header #
# #
##################################A#################################################
echo -en "Signing the image hash (\x1B[1m$hash_algo\x1B[0m) with RSA key from \x1B[1m${name_prefix}.${private_extension}\x1B[0m ... " 1>&2
"$YF_SIGNIMAGE_DD" if="$image_file" bs=512 count=$copy_blocks status=none 2>&1 | cat - "$tmp/filler.bin" "$tmp/1K.bin" "$tmp/1K.bin" |
"$YF_SIGNIMAGE_OPENSSL" dgst -$hash_algo -sign "${name_prefix}.${private_extension}" -out "$tmp/var/signature" -passin "pass:$KEYPASSWORD"
rc=$?
if [ $rc -eq 0 ]; then
show_ok
sigsize=$(stat -c %s "$tmp/var/signature")
else
show_error
exit 1
fi
####################################################################################
# #
# stream the shortened image file (without EoA) to STDOUT #
# #
####################################################################################
echo -en "Copying resulting image to output ... " 1>&2
"$YF_SIGNIMAGE_DD" if="$image_file" bs=512 count=$copy_blocks status=none
cat "$tmp/filler.bin"
####################################################################################
# #
# build our own signature member using the first member entry from image #
# #
####################################################################################
"$YF_SIGNIMAGE_DD" if="$image_file" of="$tmp/header" bs=512 count=1 status=none 2>/dev/null
printf "signature" | "$YF_SIGNIMAGE_DD" of="$tmp/header" bs=1 seek=6 conv=notrunc status=none 2>/dev/null
printf "%04o\000" $sigsize | "$YF_SIGNIMAGE_DD" of="$tmp/header" bs=1 seek=131 conv=notrunc status=none 2>/dev/null
printf "0" | "$YF_SIGNIMAGE_DD" of="$tmp/header" bs=1 seek=156 conv=notrunc status=none 2>/dev/null
printf " " | "$YF_SIGNIMAGE_DD" of="$tmp/header" bs=1 seek=148 conv=notrunc status=none 2>/dev/null
chksum=$(checksum "$tmp/header")
printf "%06o\000" $chksum | "$YF_SIGNIMAGE_DD" of="$tmp/header" bs=1 seek=148 conv=notrunc status=none 2>/dev/null
####################################################################################
# #
# and append signature content and EoA blocks #
# #
####################################################################################
cat "$tmp/header" "$tmp/var/signature"
"$YF_SIGNIMAGE_DD" if=/dev/zero bs=$(( 512 - sigsize )) count=1 status=none 2>/dev/null
cat "$tmp/1K.bin"
show_ok
####################################################################################
# #
# all done, now STDOUT has seen all the needed data to be used as a signed image #
# #
####################################################################################
exit 0
####################################################################################
# #
# end of file #
# #
####################################################################################