blob: d14c94f7dc2b62bea05f65b0764d95e783f853da [file] [log] [blame]
Tao Bao1cd59f22019-03-15 15:13:01 -07001#!/usr/bin/env python
2#
3# Copyright (C) 2019 The Android Open Source Project
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16
17import logging
18import os.path
19import re
20import shlex
21import sys
22
23import common
24
25logger = logging.getLogger(__name__)
26
27
28class ApexInfoError(Exception):
29 """An Exception raised during Apex Information command."""
30
31 def __init__(self, message):
32 Exception.__init__(self, message)
33
34
35class ApexSigningError(Exception):
36 """An Exception raised during Apex Payload signing."""
37
38 def __init__(self, message):
39 Exception.__init__(self, message)
40
41
42def SignApexPayload(payload_file, payload_key_path, payload_key_name, algorithm,
43 salt, signing_args=None):
44 """Signs a given payload_file with the payload key."""
45 # Add the new footer. Old footer, if any, will be replaced by avbtool.
46 cmd = ['avbtool', 'add_hashtree_footer',
47 '--do_not_generate_fec',
48 '--algorithm', algorithm,
49 '--key', payload_key_path,
50 '--prop', 'apex.key:{}'.format(payload_key_name),
51 '--image', payload_file,
52 '--salt', salt]
53 if signing_args:
54 cmd.extend(shlex.split(signing_args))
55
56 try:
57 common.RunAndCheckOutput(cmd)
58 except common.ExternalError as e:
59 raise ApexSigningError, \
60 'Failed to sign APEX payload {} with {}:\n{}'.format(
61 payload_file, payload_key_path, e), sys.exc_info()[2]
62
63 # Verify the signed payload image with specified public key.
64 logger.info('Verifying %s', payload_file)
65 VerifyApexPayload(payload_file, payload_key_path)
66
67
68def VerifyApexPayload(payload_file, payload_key):
69 """Verifies the APEX payload signature with the given key."""
70 cmd = ['avbtool', 'verify_image', '--image', payload_file,
71 '--key', payload_key]
72 try:
73 common.RunAndCheckOutput(cmd)
74 except common.ExternalError as e:
75 raise ApexSigningError, \
76 'Failed to validate payload signing for {} with {}:\n{}'.format(
77 payload_file, payload_key, e), sys.exc_info()[2]
78
79
80def ParseApexPayloadInfo(payload_path):
81 """Parses the APEX payload info.
82
83 Args:
84 payload_path: The path to the payload image.
85
86 Raises:
87 ApexInfoError on parsing errors.
88
89 Returns:
90 A dict that contains payload property-value pairs. The dict should at least
91 contain Algorithm, Salt and apex.key.
92 """
93 if not os.path.exists(payload_path):
94 raise ApexInfoError('Failed to find image: {}'.format(payload_path))
95
96 cmd = ['avbtool', 'info_image', '--image', payload_path]
97 try:
98 output = common.RunAndCheckOutput(cmd)
99 except common.ExternalError as e:
100 raise ApexInfoError, \
101 'Failed to get APEX payload info for {}:\n{}'.format(
102 payload_path, e), sys.exc_info()[2]
103
104 # Extract the Algorithm / Salt / Prop info from payload (i.e. an image signed
105 # with avbtool). For example,
106 # Algorithm: SHA256_RSA4096
107 PAYLOAD_INFO_PATTERN = (
108 r'^\s*(?P<key>Algorithm|Salt|Prop)\:\s*(?P<value>.*?)$')
109 payload_info_matcher = re.compile(PAYLOAD_INFO_PATTERN)
110
111 payload_info = {}
112 for line in output.split('\n'):
113 line_info = payload_info_matcher.match(line)
114 if not line_info:
115 continue
116
117 key, value = line_info.group('key'), line_info.group('value')
118
119 if key == 'Prop':
120 # Further extract the property key-value pair, from a 'Prop:' line. For
121 # example,
122 # Prop: apex.key -> 'com.android.runtime'
123 # Note that avbtool writes single or double quotes around values.
124 PROPERTY_DESCRIPTOR_PATTERN = r'^\s*(?P<key>.*?)\s->\s*(?P<value>.*?)$'
125
126 prop_matcher = re.compile(PROPERTY_DESCRIPTOR_PATTERN)
127 prop = prop_matcher.match(value)
128 if not prop:
129 raise ApexInfoError(
130 'Failed to parse prop string {}'.format(value))
131
132 prop_key, prop_value = prop.group('key'), prop.group('value')
133 if prop_key == 'apex.key':
134 # avbtool dumps the prop value with repr(), which contains single /
135 # double quotes that we don't want.
136 payload_info[prop_key] = prop_value.strip('\"\'')
137
138 else:
139 payload_info[key] = value
140
141 # Sanity check.
142 for key in ('Algorithm', 'Salt', 'apex.key'):
143 if key not in payload_info:
144 raise ApexInfoError(
145 'Failed to find {} prop in {}'.format(key, payload_path))
146
147 return payload_info