-
Notifications
You must be signed in to change notification settings - Fork 1.4k
/
avbtool
4276 lines (4276 loc) · 196 KB
/
avbtool
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
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#!/usr/bin/env python3
# Copyright 2016, The Android Open Source Project
#
# Permission is hereby granted, free of charge, to any person
# obtaining a copy of this software and associated documentation
# files (the "Software"), to deal in the Software without
# restriction, including without limitation the rights to use, copy,
# modify, merge, publish, distribute, sublicense, and/or sell copies
# of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
#
"""Command-line tool for working with Android Verified Boot images."""
import argparse
import binascii
import bisect
import hashlib
import json
import math
import os
import struct
import subprocess
import sys
import tempfile
import time
# Keep in sync with libavb/avb_version.h.
AVB_VERSION_MAJOR = 1
AVB_VERSION_MINOR = 2
AVB_VERSION_SUB = 0
# Keep in sync with libavb/avb_footer.h.
AVB_FOOTER_VERSION_MAJOR = 1
AVB_FOOTER_VERSION_MINOR = 0
AVB_VBMETA_IMAGE_FLAGS_HASHTREE_DISABLED = 1
# Configuration for enabling logging of calls to avbtool.
AVB_INVOCATION_LOGFILE = os.environ.get('AVB_INVOCATION_LOGFILE')
class AvbError(Exception):
"""Application-specific errors.
These errors represent issues for which a stack-trace should not be
presented.
Attributes:
message: Error message.
"""
def __init__(self, message):
Exception.__init__(self, message)
class Algorithm(object):
"""Contains details about an algorithm.
See the avb_vbmeta_image.h file for more details about algorithms.
The constant |ALGORITHMS| is a dictionary from human-readable
names (e.g 'SHA256_RSA2048') to instances of this class.
Attributes:
algorithm_type: Integer code corresponding to |AvbAlgorithmType|.
hash_name: Empty or a name from |hashlib.algorithms|.
hash_num_bytes: Number of bytes used to store the hash.
signature_num_bytes: Number of bytes used to store the signature.
public_key_num_bytes: Number of bytes used to store the public key.
padding: Padding used for signature as bytes, if any.
"""
def __init__(self, algorithm_type, hash_name, hash_num_bytes,
signature_num_bytes, public_key_num_bytes, padding):
self.algorithm_type = algorithm_type
self.hash_name = hash_name
self.hash_num_bytes = hash_num_bytes
self.signature_num_bytes = signature_num_bytes
self.public_key_num_bytes = public_key_num_bytes
self.padding = padding
# This must be kept in sync with the avb_crypto.h file.
#
# The PKC1-v1.5 padding is a blob of binary DER of ASN.1 and is
# obtained from section 5.2.2 of RFC 4880.
ALGORITHMS = {
'NONE': Algorithm(
algorithm_type=0, # AVB_ALGORITHM_TYPE_NONE
hash_name='',
hash_num_bytes=0,
signature_num_bytes=0,
public_key_num_bytes=0,
padding=b''),
'SHA256_RSA2048': Algorithm(
algorithm_type=1, # AVB_ALGORITHM_TYPE_SHA256_RSA2048
hash_name='sha256',
hash_num_bytes=32,
signature_num_bytes=256,
public_key_num_bytes=8 + 2*2048//8,
padding=bytes(bytearray([
# PKCS1-v1_5 padding
0x00, 0x01] + [0xff]*202 + [0x00] + [
# ASN.1 header
0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86,
0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05,
0x00, 0x04, 0x20,
]))),
'SHA256_RSA4096': Algorithm(
algorithm_type=2, # AVB_ALGORITHM_TYPE_SHA256_RSA4096
hash_name='sha256',
hash_num_bytes=32,
signature_num_bytes=512,
public_key_num_bytes=8 + 2*4096//8,
padding=bytes(bytearray([
# PKCS1-v1_5 padding
0x00, 0x01] + [0xff]*458 + [0x00] + [
# ASN.1 header
0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86,
0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05,
0x00, 0x04, 0x20,
]))),
'SHA256_RSA8192': Algorithm(
algorithm_type=3, # AVB_ALGORITHM_TYPE_SHA256_RSA8192
hash_name='sha256',
hash_num_bytes=32,
signature_num_bytes=1024,
public_key_num_bytes=8 + 2*8192//8,
padding=bytes(bytearray([
# PKCS1-v1_5 padding
0x00, 0x01] + [0xff]*970 + [0x00] + [
# ASN.1 header
0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86,
0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05,
0x00, 0x04, 0x20,
]))),
'SHA512_RSA2048': Algorithm(
algorithm_type=4, # AVB_ALGORITHM_TYPE_SHA512_RSA2048
hash_name='sha512',
hash_num_bytes=64,
signature_num_bytes=256,
public_key_num_bytes=8 + 2*2048//8,
padding=bytes(bytearray([
# PKCS1-v1_5 padding
0x00, 0x01] + [0xff]*170 + [0x00] + [
# ASN.1 header
0x30, 0x51, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86,
0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03, 0x05,
0x00, 0x04, 0x40
]))),
'SHA512_RSA4096': Algorithm(
algorithm_type=5, # AVB_ALGORITHM_TYPE_SHA512_RSA4096
hash_name='sha512',
hash_num_bytes=64,
signature_num_bytes=512,
public_key_num_bytes=8 + 2*4096//8,
padding=bytes(bytearray([
# PKCS1-v1_5 padding
0x00, 0x01] + [0xff]*426 + [0x00] + [
# ASN.1 header
0x30, 0x51, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86,
0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03, 0x05,
0x00, 0x04, 0x40
]))),
'SHA512_RSA8192': Algorithm(
algorithm_type=6, # AVB_ALGORITHM_TYPE_SHA512_RSA8192
hash_name='sha512',
hash_num_bytes=64,
signature_num_bytes=1024,
public_key_num_bytes=8 + 2*8192//8,
padding=bytes(bytearray([
# PKCS1-v1_5 padding
0x00, 0x01] + [0xff]*938 + [0x00] + [
# ASN.1 header
0x30, 0x51, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86,
0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03, 0x05,
0x00, 0x04, 0x40
]))),
}
def get_release_string():
"""Calculates the release string to use in the VBMeta struct."""
# Keep in sync with libavb/avb_version.c:avb_version_string().
return 'avbtool {}.{}.{}'.format(AVB_VERSION_MAJOR,
AVB_VERSION_MINOR,
AVB_VERSION_SUB)
def round_to_multiple(number, size):
"""Rounds a number up to nearest multiple of another number.
Arguments:
number: The number to round up.
size: The multiple to round up to.
Returns:
If |number| is a multiple of |size|, returns |number|, otherwise
returns |number| + |size|.
"""
remainder = number % size
if remainder == 0:
return number
return number + size - remainder
def round_to_pow2(number):
"""Rounds a number up to the next power of 2.
Arguments:
number: The number to round up.
Returns:
If |number| is already a power of 2 then |number| is
returned. Otherwise the smallest power of 2 greater than |number|
is returned.
"""
return 2**((number - 1).bit_length())
def encode_long(num_bits, value):
"""Encodes a long to a bytearray() using a given amount of bits.
This number is written big-endian, e.g. with the most significant
bit first.
This is the reverse of decode_long().
Arguments:
num_bits: The number of bits to write, e.g. 2048.
value: The value to write.
Returns:
A bytearray() with the encoded long.
"""
ret = bytearray()
for bit_pos in range(num_bits, 0, -8):
octet = (value >> (bit_pos - 8)) & 0xff
ret.extend(struct.pack('!B', octet))
return ret
def decode_long(blob):
"""Decodes a long from a bytearray() using a given amount of bits.
This number is expected to be in big-endian, e.g. with the most
significant bit first.
This is the reverse of encode_long().
Arguments:
blob: A bytearray() with the encoded long.
Returns:
The decoded value.
"""
ret = 0
for b in bytearray(blob):
ret *= 256
ret += b
return ret
def egcd(a, b):
"""Calculate greatest common divisor of two numbers.
This implementation uses a recursive version of the extended
Euclidian algorithm.
Arguments:
a: First number.
b: Second number.
Returns:
A tuple (gcd, x, y) that where |gcd| is the greatest common
divisor of |a| and |b| and |a|*|x| + |b|*|y| = |gcd|.
"""
if a == 0:
return (b, 0, 1)
g, y, x = egcd(b % a, a)
return (g, x - (b // a) * y, y)
def modinv(a, m):
"""Calculate modular multiplicative inverse of |a| modulo |m|.
This calculates the number |x| such that |a| * |x| == 1 (modulo
|m|). This number only exists if |a| and |m| are co-prime - |None|
is returned if this isn't true.
Arguments:
a: The number to calculate a modular inverse of.
m: The modulo to use.
Returns:
The modular multiplicative inverse of |a| and |m| or |None| if
these numbers are not co-prime.
"""
gcd, x, _ = egcd(a, m)
if gcd != 1:
return None # modular inverse does not exist
return x % m
def parse_number(string):
"""Parse a string as a number.
This is just a short-hand for int(string, 0) suitable for use in the
|type| parameter of |ArgumentParser|'s add_argument() function. An
improvement to just using type=int is that this function supports
numbers in other bases, e.g. "0x1234".
Arguments:
string: The string to parse.
Returns:
The parsed integer.
Raises:
ValueError: If the number could not be parsed.
"""
return int(string, 0)
class RSAPublicKey(object):
"""Data structure used for a RSA public key.
Attributes:
exponent: The key exponent.
modulus: The key modulus.
num_bits: The key size.
key_path: The path to a key file.
"""
MODULUS_PREFIX = b'modulus='
def __init__(self, key_path):
"""Loads and parses an RSA key from either a private or public key file.
Arguments:
key_path: The path to a key file.
Raises:
AvbError: If RSA key parameters could not be read from file.
"""
# We used to have something as simple as this:
#
# key = Crypto.PublicKey.RSA.importKey(open(key_path).read())
# self.exponent = key.e
# self.modulus = key.n
# self.num_bits = key.size() + 1
#
# but unfortunately PyCrypto is not available in the builder. So
# instead just parse openssl(1) output to get this
# information. It's ugly but...
args = ['openssl', 'rsa', '-in', key_path, '-modulus', '-noout']
p = subprocess.Popen(args,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
(pout, perr) = p.communicate()
if p.wait() != 0:
# Could be just a public key is passed, try that.
args.append('-pubin')
p = subprocess.Popen(args,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
(pout, perr) = p.communicate()
if p.wait() != 0:
raise AvbError('Error getting public key: {}'.format(perr))
if not pout.lower().startswith(self.MODULUS_PREFIX):
raise AvbError('Unexpected modulus output')
modulus_hexstr = pout[len(self.MODULUS_PREFIX):]
# The exponent is assumed to always be 65537 and the number of
# bits can be derived from the modulus by rounding up to the
# nearest power of 2.
self.key_path = key_path
self.modulus = int(modulus_hexstr, 16)
self.num_bits = round_to_pow2(int(math.ceil(math.log(self.modulus, 2))))
self.exponent = 65537
def encode(self):
"""Encodes the public RSA key in |AvbRSAPublicKeyHeader| format.
This creates a |AvbRSAPublicKeyHeader| as well as the two large
numbers (|key_num_bits| bits long) following it.
Returns:
The |AvbRSAPublicKeyHeader| followed by two large numbers as bytes.
Raises:
AvbError: If given RSA key exponent is not 65537.
"""
if self.exponent != 65537:
raise AvbError('Only RSA keys with exponent 65537 are supported.')
ret = bytearray()
# Calculate n0inv = -1/n[0] (mod 2^32)
b = 2 ** 32
n0inv = b - modinv(self.modulus, b)
# Calculate rr = r^2 (mod N), where r = 2^(# of key bits)
r = 2 ** self.modulus.bit_length()
rrmodn = r * r % self.modulus
ret.extend(struct.pack('!II', self.num_bits, n0inv))
ret.extend(encode_long(self.num_bits, self.modulus))
ret.extend(encode_long(self.num_bits, rrmodn))
return bytes(ret)
def sign(self, algorithm_name, data_to_sign, signing_helper=None,
signing_helper_with_files=None):
"""Sign given data using |signing_helper| or openssl.
openssl is used if neither the parameters signing_helper nor
signing_helper_with_files are given.
Arguments:
algorithm_name: The algorithm name as per the ALGORITHMS dict.
data_to_sign: Data to sign as bytes or bytearray.
signing_helper: Program which signs a hash and returns the signature.
signing_helper_with_files: Same as signing_helper but uses files instead.
Returns:
The signature as bytes.
Raises:
AvbError: If an error occurred during signing.
"""
# Checks requested algorithm for validity.
algorithm = ALGORITHMS.get(algorithm_name)
if not algorithm:
raise AvbError('Algorithm with name {} is not supported.'
.format(algorithm_name))
if self.num_bits != (algorithm.signature_num_bytes * 8):
raise AvbError('Key size of key ({} bits) does not match key size '
'({} bits) of given algorithm {}.'
.format(self.num_bits, algorithm.signature_num_bytes * 8,
algorithm_name))
# Hashes the data.
hasher = hashlib.new(algorithm.hash_name)
hasher.update(data_to_sign)
digest = hasher.digest()
# Calculates the signature.
padding_and_hash = algorithm.padding + digest
p = None
if signing_helper_with_files is not None:
with tempfile.NamedTemporaryFile() as signing_file:
signing_file.write(padding_and_hash)
signing_file.flush()
p = subprocess.Popen([signing_helper_with_files, algorithm_name,
self.key_path, signing_file.name])
retcode = p.wait()
if retcode != 0:
raise AvbError('Error signing')
signing_file.seek(0)
signature = signing_file.read()
else:
if signing_helper is not None:
p = subprocess.Popen(
[signing_helper, algorithm_name, self.key_path],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
else:
p = subprocess.Popen(
['openssl', 'rsautl', '-sign', '-inkey', self.key_path, '-raw'],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
(pout, perr) = p.communicate(padding_and_hash)
retcode = p.wait()
if retcode != 0:
raise AvbError('Error signing: {}'.format(perr))
signature = pout
if len(signature) != algorithm.signature_num_bytes:
raise AvbError('Error signing: Invalid length of signature')
return signature
def lookup_algorithm_by_type(alg_type):
"""Looks up algorithm by type.
Arguments:
alg_type: The integer representing the type.
Returns:
A tuple with the algorithm name and an |Algorithm| instance.
Raises:
Exception: If the algorithm cannot be found
"""
for alg_name in ALGORITHMS:
alg_data = ALGORITHMS[alg_name]
if alg_data.algorithm_type == alg_type:
return (alg_name, alg_data)
raise AvbError('Unknown algorithm type {}'.format(alg_type))
def lookup_hash_size_by_type(alg_type):
"""Looks up hash size by type.
Arguments:
alg_type: The integer representing the type.
Returns:
The corresponding hash size.
Raises:
AvbError: If the algorithm cannot be found.
"""
for alg_name in ALGORITHMS:
alg_data = ALGORITHMS[alg_name]
if alg_data.algorithm_type == alg_type:
return alg_data.hash_num_bytes
raise AvbError('Unsupported algorithm type {}'.format(alg_type))
def verify_vbmeta_signature(vbmeta_header, vbmeta_blob):
"""Checks that signature in a vbmeta blob was made by the embedded public key.
Arguments:
vbmeta_header: A AvbVBMetaHeader.
vbmeta_blob: The whole vbmeta blob, including the header as bytes or
bytearray.
Returns:
True if the signature is valid and corresponds to the embedded
public key. Also returns True if the vbmeta blob is not signed.
Raises:
AvbError: If there errors calling out to openssl command during
signature verification.
"""
(_, alg) = lookup_algorithm_by_type(vbmeta_header.algorithm_type)
if not alg.hash_name:
return True
header_blob = vbmeta_blob[0:256]
auth_offset = 256
aux_offset = auth_offset + vbmeta_header.authentication_data_block_size
aux_size = vbmeta_header.auxiliary_data_block_size
aux_blob = vbmeta_blob[aux_offset:aux_offset + aux_size]
pubkey_offset = aux_offset + vbmeta_header.public_key_offset
pubkey_size = vbmeta_header.public_key_size
pubkey_blob = vbmeta_blob[pubkey_offset:pubkey_offset + pubkey_size]
digest_offset = auth_offset + vbmeta_header.hash_offset
digest_size = vbmeta_header.hash_size
digest_blob = vbmeta_blob[digest_offset:digest_offset + digest_size]
sig_offset = auth_offset + vbmeta_header.signature_offset
sig_size = vbmeta_header.signature_size
sig_blob = vbmeta_blob[sig_offset:sig_offset + sig_size]
# Now that we've got the stored digest, public key, and signature
# all we need to do is to verify. This is the exactly the same
# steps as performed in the avb_vbmeta_image_verify() function in
# libavb/avb_vbmeta_image.c.
ha = hashlib.new(alg.hash_name)
ha.update(header_blob)
ha.update(aux_blob)
computed_digest = ha.digest()
if computed_digest != digest_blob:
return False
padding_and_digest = alg.padding + computed_digest
(num_bits,) = struct.unpack('!I', pubkey_blob[0:4])
modulus_blob = pubkey_blob[8:8 + num_bits//8]
modulus = decode_long(modulus_blob)
exponent = 65537
# We used to have this:
#
# import Crypto.PublicKey.RSA
# key = Crypto.PublicKey.RSA.construct((modulus, long(exponent)))
# if not key.verify(decode_long(padding_and_digest),
# (decode_long(sig_blob), None)):
# return False
# return True
#
# but since 'avbtool verify_image' is used on the builders we don't want
# to rely on Crypto.PublicKey.RSA. Instead just use openssl(1) to verify.
asn1_str = ('asn1=SEQUENCE:pubkeyinfo\n'
'\n'
'[pubkeyinfo]\n'
'algorithm=SEQUENCE:rsa_alg\n'
'pubkey=BITWRAP,SEQUENCE:rsapubkey\n'
'\n'
'[rsa_alg]\n'
'algorithm=OID:rsaEncryption\n'
'parameter=NULL\n'
'\n'
'[rsapubkey]\n'
'n=INTEGER:{}\n'
'e=INTEGER:{}\n').format(hex(modulus).rstrip('L'),
hex(exponent).rstrip('L'))
with tempfile.NamedTemporaryFile() as asn1_tmpfile:
asn1_tmpfile.write(asn1_str.encode('ascii'))
asn1_tmpfile.flush()
with tempfile.NamedTemporaryFile() as der_tmpfile:
p = subprocess.Popen(
['openssl', 'asn1parse', '-genconf', asn1_tmpfile.name, '-out',
der_tmpfile.name, '-noout'])
retcode = p.wait()
if retcode != 0:
raise AvbError('Error generating DER file')
p = subprocess.Popen(
['openssl', 'rsautl', '-verify', '-pubin', '-inkey', der_tmpfile.name,
'-keyform', 'DER', '-raw'],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
(pout, perr) = p.communicate(sig_blob)
retcode = p.wait()
if retcode != 0:
raise AvbError('Error verifying data: {}'.format(perr))
if pout != padding_and_digest:
sys.stderr.write('Signature not correct\n')
return False
return True
def create_avb_hashtree_hasher(algorithm, salt):
"""Create the hasher for AVB hashtree based on the input algorithm."""
if algorithm.lower() == 'blake2b-256':
return hashlib.new('blake2b', salt, digest_size=32)
return hashlib.new(algorithm, salt)
class ImageChunk(object):
"""Data structure used for representing chunks in Android sparse files.
Attributes:
chunk_type: One of TYPE_RAW, TYPE_FILL, or TYPE_DONT_CARE.
chunk_offset: Offset in the sparse file where this chunk begins.
output_offset: Offset in de-sparsified file where output begins.
output_size: Number of bytes in output.
input_offset: Offset in sparse file for data if TYPE_RAW otherwise None.
fill_data: Blob with data to fill if TYPE_FILL otherwise None.
"""
FORMAT = '<2H2I'
TYPE_RAW = 0xcac1
TYPE_FILL = 0xcac2
TYPE_DONT_CARE = 0xcac3
TYPE_CRC32 = 0xcac4
def __init__(self, chunk_type, chunk_offset, output_offset, output_size,
input_offset, fill_data):
"""Initializes an ImageChunk object.
Arguments:
chunk_type: One of TYPE_RAW, TYPE_FILL, or TYPE_DONT_CARE.
chunk_offset: Offset in the sparse file where this chunk begins.
output_offset: Offset in de-sparsified file.
output_size: Number of bytes in output.
input_offset: Offset in sparse file if TYPE_RAW otherwise None.
fill_data: Blob as bytes with data to fill if TYPE_FILL otherwise None.
Raises:
ValueError: If given chunk parameters are invalid.
"""
self.chunk_type = chunk_type
self.chunk_offset = chunk_offset
self.output_offset = output_offset
self.output_size = output_size
self.input_offset = input_offset
self.fill_data = fill_data
# Check invariants.
if self.chunk_type == self.TYPE_RAW:
if self.fill_data is not None:
raise ValueError('RAW chunk cannot have fill_data set.')
if not self.input_offset:
raise ValueError('RAW chunk must have input_offset set.')
elif self.chunk_type == self.TYPE_FILL:
if self.fill_data is None:
raise ValueError('FILL chunk must have fill_data set.')
if self.input_offset:
raise ValueError('FILL chunk cannot have input_offset set.')
elif self.chunk_type == self.TYPE_DONT_CARE:
if self.fill_data is not None:
raise ValueError('DONT_CARE chunk cannot have fill_data set.')
if self.input_offset:
raise ValueError('DONT_CARE chunk cannot have input_offset set.')
else:
raise ValueError('Invalid chunk type')
class ImageHandler(object):
"""Abstraction for image I/O with support for Android sparse images.
This class provides an interface for working with image files that
may be using the Android Sparse Image format. When an instance is
constructed, we test whether it's an Android sparse file. If so,
operations will be on the sparse file by interpreting the sparse
format, otherwise they will be directly on the file. Either way the
operations do the same.
For reading, this interface mimics a file object - it has seek(),
tell(), and read() methods. For writing, only truncation
(truncate()) and appending is supported (append_raw() and
append_dont_care()). Additionally, data can only be written in units
of the block size.
Attributes:
filename: Name of file.
is_sparse: Whether the file being operated on is sparse.
block_size: The block size, typically 4096.
image_size: The size of the unsparsified file.
"""
# See system/core/libsparse/sparse_format.h for details.
MAGIC = 0xed26ff3a
HEADER_FORMAT = '<I4H4I'
# These are formats and offset of just the |total_chunks| and
# |total_blocks| fields.
NUM_CHUNKS_AND_BLOCKS_FORMAT = '<II'
NUM_CHUNKS_AND_BLOCKS_OFFSET = 16
def __init__(self, image_filename, read_only=False):
"""Initializes an image handler.
Arguments:
image_filename: The name of the file to operate on.
read_only: True if file is only opened for read-only operations.
Raises:
ValueError: If data in the file is invalid.
"""
self.filename = image_filename
self._num_total_blocks = 0
self._num_total_chunks = 0
self._file_pos = 0
self._read_only = read_only
self._read_header()
def _read_header(self):
"""Initializes internal data structures used for reading file.
This may be called multiple times and is typically called after
modifying the file (e.g. appending, truncation).
Raises:
ValueError: If data in the file is invalid.
"""
self.is_sparse = False
self.block_size = 4096
self._file_pos = 0
if self._read_only:
self._image = open(self.filename, 'rb')
else:
self._image = open(self.filename, 'r+b')
self._image.seek(0, os.SEEK_END)
self.image_size = self._image.tell()
self._image.seek(0, os.SEEK_SET)
header_bin = self._image.read(struct.calcsize(self.HEADER_FORMAT))
(magic, major_version, minor_version, file_hdr_sz, chunk_hdr_sz,
block_size, self._num_total_blocks, self._num_total_chunks,
_) = struct.unpack(self.HEADER_FORMAT, header_bin)
if magic != self.MAGIC:
# Not a sparse image, our job here is done.
return
if not (major_version == 1 and minor_version == 0):
raise ValueError('Encountered sparse image format version {}.{} but '
'only 1.0 is supported'.format(major_version,
minor_version))
if file_hdr_sz != struct.calcsize(self.HEADER_FORMAT):
raise ValueError('Unexpected file_hdr_sz value {}.'.
format(file_hdr_sz))
if chunk_hdr_sz != struct.calcsize(ImageChunk.FORMAT):
raise ValueError('Unexpected chunk_hdr_sz value {}.'.
format(chunk_hdr_sz))
self.block_size = block_size
# Build an list of chunks by parsing the file.
self._chunks = []
# Find the smallest offset where only "Don't care" chunks
# follow. This will be the size of the content in the sparse
# image.
offset = 0
output_offset = 0
for _ in range(1, self._num_total_chunks + 1):
chunk_offset = self._image.tell()
header_bin = self._image.read(struct.calcsize(ImageChunk.FORMAT))
(chunk_type, _, chunk_sz, total_sz) = struct.unpack(ImageChunk.FORMAT,
header_bin)
data_sz = total_sz - struct.calcsize(ImageChunk.FORMAT)
if chunk_type == ImageChunk.TYPE_RAW:
if data_sz != (chunk_sz * self.block_size):
raise ValueError('Raw chunk input size ({}) does not match output '
'size ({})'.
format(data_sz, chunk_sz*self.block_size))
self._chunks.append(ImageChunk(ImageChunk.TYPE_RAW,
chunk_offset,
output_offset,
chunk_sz*self.block_size,
self._image.tell(),
None))
self._image.seek(data_sz, os.SEEK_CUR)
elif chunk_type == ImageChunk.TYPE_FILL:
if data_sz != 4:
raise ValueError('Fill chunk should have 4 bytes of fill, but this '
'has {}'.format(data_sz))
fill_data = self._image.read(4)
self._chunks.append(ImageChunk(ImageChunk.TYPE_FILL,
chunk_offset,
output_offset,
chunk_sz*self.block_size,
None,
fill_data))
elif chunk_type == ImageChunk.TYPE_DONT_CARE:
if data_sz != 0:
raise ValueError('Don\'t care chunk input size is non-zero ({})'.
format(data_sz))
self._chunks.append(ImageChunk(ImageChunk.TYPE_DONT_CARE,
chunk_offset,
output_offset,
chunk_sz*self.block_size,
None,
None))
elif chunk_type == ImageChunk.TYPE_CRC32:
if data_sz != 4:
raise ValueError('CRC32 chunk should have 4 bytes of CRC, but '
'this has {}'.format(data_sz))
self._image.read(4)
else:
raise ValueError('Unknown chunk type {}'.format(chunk_type))
offset += chunk_sz
output_offset += chunk_sz*self.block_size
# Record where sparse data end.
self._sparse_end = self._image.tell()
# Now that we've traversed all chunks, sanity check.
if self._num_total_blocks != offset:
raise ValueError('The header said we should have {} output blocks, '
'but we saw {}'.format(self._num_total_blocks, offset))
junk_len = len(self._image.read())
if junk_len > 0:
raise ValueError('There were {} bytes of extra data at the end of the '
'file.'.format(junk_len))
# Assign |image_size|.
self.image_size = output_offset
# This is used when bisecting in read() to find the initial slice.
self._chunk_output_offsets = [i.output_offset for i in self._chunks]
self.is_sparse = True
def _update_chunks_and_blocks(self):
"""Helper function to update the image header.
The the |total_chunks| and |total_blocks| fields in the header
will be set to value of the |_num_total_blocks| and
|_num_total_chunks| attributes.
"""
self._image.seek(self.NUM_CHUNKS_AND_BLOCKS_OFFSET, os.SEEK_SET)
self._image.write(struct.pack(self.NUM_CHUNKS_AND_BLOCKS_FORMAT,
self._num_total_blocks,
self._num_total_chunks))
def append_dont_care(self, num_bytes):
"""Appends a DONT_CARE chunk to the sparse file.
The given number of bytes must be a multiple of the block size.
Arguments:
num_bytes: Size in number of bytes of the DONT_CARE chunk.
Raises:
OSError: If ImageHandler was initialized in read-only mode.
"""
assert num_bytes % self.block_size == 0
if self._read_only:
raise OSError('ImageHandler is in read-only mode.')
if not self.is_sparse:
self._image.seek(0, os.SEEK_END)
# This is more efficient that writing NUL bytes since it'll add
# a hole on file systems that support sparse files (native
# sparse, not Android sparse).
self._image.truncate(self._image.tell() + num_bytes)
self._read_header()
return
self._num_total_chunks += 1
self._num_total_blocks += num_bytes // self.block_size
self._update_chunks_and_blocks()
self._image.seek(self._sparse_end, os.SEEK_SET)
self._image.write(struct.pack(ImageChunk.FORMAT,
ImageChunk.TYPE_DONT_CARE,
0, # Reserved
num_bytes // self.block_size,
struct.calcsize(ImageChunk.FORMAT)))
self._read_header()
def append_raw(self, data):
"""Appends a RAW chunk to the sparse file.
The length of the given data must be a multiple of the block size.
Arguments:
data: Data to append as bytes.
Raises:
OSError: If ImageHandler was initialized in read-only mode.
"""
assert len(data) % self.block_size == 0
if self._read_only:
raise OSError('ImageHandler is in read-only mode.')
if not self.is_sparse:
self._image.seek(0, os.SEEK_END)
self._image.write(data)
self._read_header()
return
self._num_total_chunks += 1
self._num_total_blocks += len(data) // self.block_size
self._update_chunks_and_blocks()
self._image.seek(self._sparse_end, os.SEEK_SET)
self._image.write(struct.pack(ImageChunk.FORMAT,
ImageChunk.TYPE_RAW,
0, # Reserved
len(data) // self.block_size,
len(data) +
struct.calcsize(ImageChunk.FORMAT)))
self._image.write(data)
self._read_header()
def append_fill(self, fill_data, size):
"""Appends a fill chunk to the sparse file.
The total length of the fill data must be a multiple of the block size.
Arguments:
fill_data: Fill data to append - must be four bytes.
size: Number of chunk - must be a multiple of four and the block size.
Raises:
OSError: If ImageHandler was initialized in read-only mode.
"""
assert len(fill_data) == 4
assert size % 4 == 0
assert size % self.block_size == 0
if self._read_only:
raise OSError('ImageHandler is in read-only mode.')
if not self.is_sparse:
self._image.seek(0, os.SEEK_END)
self._image.write(fill_data * (size//4))
self._read_header()
return
self._num_total_chunks += 1
self._num_total_blocks += size // self.block_size
self._update_chunks_and_blocks()
self._image.seek(self._sparse_end, os.SEEK_SET)
self._image.write(struct.pack(ImageChunk.FORMAT,
ImageChunk.TYPE_FILL,
0, # Reserved
size // self.block_size,
4 + struct.calcsize(ImageChunk.FORMAT)))
self._image.write(fill_data)
self._read_header()
def seek(self, offset):
"""Sets the cursor position for reading from unsparsified file.
Arguments:
offset: Offset to seek to from the beginning of the file.
Raises:
RuntimeError: If the given offset is negative.
"""
if offset < 0:
raise RuntimeError('Seeking with negative offset: {}'.format(offset))
self._file_pos = offset
def read(self, size):
"""Reads data from the unsparsified file.
This method may return fewer than |size| bytes of data if the end
of the file was encountered.
The file cursor for reading is advanced by the number of bytes
read.
Arguments:
size: Number of bytes to read.
Returns:
The data as bytes.
"""
if not self.is_sparse:
self._image.seek(self._file_pos)
data = self._image.read(size)
self._file_pos += len(data)
return data
# Iterate over all chunks.
chunk_idx = bisect.bisect_right(self._chunk_output_offsets,
self._file_pos) - 1
data = bytearray()
to_go = size
while to_go > 0:
chunk = self._chunks[chunk_idx]
chunk_pos_offset = self._file_pos - chunk.output_offset
chunk_pos_to_go = min(chunk.output_size - chunk_pos_offset, to_go)
if chunk.chunk_type == ImageChunk.TYPE_RAW:
self._image.seek(chunk.input_offset + chunk_pos_offset)
data.extend(self._image.read(chunk_pos_to_go))
elif chunk.chunk_type == ImageChunk.TYPE_FILL:
all_data = chunk.fill_data*(chunk_pos_to_go // len(chunk.fill_data) + 2)
offset_mod = chunk_pos_offset % len(chunk.fill_data)
data.extend(all_data[offset_mod:(offset_mod + chunk_pos_to_go)])
else:
assert chunk.chunk_type == ImageChunk.TYPE_DONT_CARE
data.extend(b'\0' * chunk_pos_to_go)
to_go -= chunk_pos_to_go
self._file_pos += chunk_pos_to_go
chunk_idx += 1
# Generate partial read in case of EOF.
if chunk_idx >= len(self._chunks):
break
return bytes(data)
def tell(self):
"""Returns the file cursor position for reading from unsparsified file.
Returns:
The file cursor position for reading.
"""
return self._file_pos
def truncate(self, size):
"""Truncates the unsparsified file.
Arguments:
size: Desired size of unsparsified file.
Raises:
ValueError: If desired size isn't a multiple of the block size.
OSError: If ImageHandler was initialized in read-only mode.
"""
if self._read_only:
raise OSError('ImageHandler is in read-only mode.')
if not self.is_sparse:
self._image.truncate(size)
self._read_header()
return
if size % self.block_size != 0:
raise ValueError('Cannot truncate to a size which is not a multiple '
'of the block size')
if size == self.image_size:
# Trivial where there's nothing to do.
return
if size < self.image_size:
chunk_idx = bisect.bisect_right(self._chunk_output_offsets, size) - 1
chunk = self._chunks[chunk_idx]
if chunk.output_offset != size:
# Truncation in the middle of a trunk - need to keep the chunk
# and modify it.
chunk_idx_for_update = chunk_idx + 1
num_to_keep = size - chunk.output_offset
assert num_to_keep % self.block_size == 0
if chunk.chunk_type == ImageChunk.TYPE_RAW:
truncate_at = (chunk.chunk_offset +
struct.calcsize(ImageChunk.FORMAT) + num_to_keep)
data_sz = num_to_keep
elif chunk.chunk_type == ImageChunk.TYPE_FILL:
truncate_at = (chunk.chunk_offset +
struct.calcsize(ImageChunk.FORMAT) + 4)
data_sz = 4
else:
assert chunk.chunk_type == ImageChunk.TYPE_DONT_CARE
truncate_at = chunk.chunk_offset + struct.calcsize(ImageChunk.FORMAT)
data_sz = 0
chunk_sz = num_to_keep // self.block_size
total_sz = data_sz + struct.calcsize(ImageChunk.FORMAT)
self._image.seek(chunk.chunk_offset)
self._image.write(struct.pack(ImageChunk.FORMAT,
chunk.chunk_type,
0, # Reserved
chunk_sz,
total_sz))
chunk.output_size = num_to_keep
else:
# Truncation at trunk boundary.
truncate_at = chunk.chunk_offset
chunk_idx_for_update = chunk_idx
self._num_total_chunks = chunk_idx_for_update
self._num_total_blocks = 0
for i in range(0, chunk_idx_for_update):
self._num_total_blocks += self._chunks[i].output_size // self.block_size
self._update_chunks_and_blocks()
self._image.truncate(truncate_at)
# We've modified the file so re-read all data.
self._read_header()
else:
# Truncating to grow - just add a DONT_CARE section.
self.append_dont_care(size - self.image_size)
class AvbDescriptor(object):
"""Class for AVB descriptor.
See the |AvbDescriptor| C struct for more information.
Attributes:
tag: The tag identifying what kind of descriptor this is.
data: The data in the descriptor.
"""
SIZE = 16
FORMAT_STRING = ('!QQ') # tag, num_bytes_following (descriptor header)
def __init__(self, data):
"""Initializes a new property descriptor.
Arguments:
data: If not None, must be a bytearray().
Raises:
LookupError: If the given descriptor is malformed.
"""
assert struct.calcsize(self.FORMAT_STRING) == self.SIZE
if data:
(self.tag, num_bytes_following) = (
struct.unpack(self.FORMAT_STRING, data[0:self.SIZE]))
self.data = data[self.SIZE:self.SIZE + num_bytes_following]
else:
self.tag = None
self.data = None
def print_desc(self, o):
"""Print the descriptor.
Arguments:
o: The object to write the output to.
"""
o.write(' Unknown descriptor:\n')
o.write(' Tag: {}\n'.format(self.tag))
if len(self.data) < 256:
o.write(' Data: {} ({} bytes)\n'.format(
repr(str(self.data)), len(self.data)))
else:
o.write(' Data: {} bytes\n'.format(len(self.data)))
def encode(self):
"""Serializes the descriptor.
Returns:
A bytearray() with the descriptor data.