blob: a5d09e1d3ab9cb5b1ae089961e1edeb83619f03f [file] [log] [blame]
Kelvin Zhang059bf6e2022-08-12 14:03:41 -07001#!/usr/bin/env python3
2#
3# Copyright (C) 2022 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 common
18import logging
Kelvin Zhang68252b82023-11-14 10:58:02 -080019import shlex
20from common import OPTIONS, OptionHandler
Kelvin Zhang059bf6e2022-08-12 14:03:41 -070021
22logger = logging.getLogger(__name__)
23
Kelvin Zhang68252b82023-11-14 10:58:02 -080024OPTIONS.payload_signer = None
25OPTIONS.payload_signer_args = []
26OPTIONS.payload_signer_maximum_signature_size = None
27OPTIONS.package_key = None
28
29
30class SignerOptions(OptionHandler):
31
32 @staticmethod
33 def ParseOptions(o, a):
34 if o in ("-k", "--package_key"):
35 OPTIONS.package_key = a
36 elif o == "--payload_signer":
37 OPTIONS.payload_signer = a
38 elif o == "--payload_signer_args":
39 OPTIONS.payload_signer_args = shlex.split(a)
40 elif o == "--payload_signer_maximum_signature_size":
41 OPTIONS.payload_signer_maximum_signature_size = a
42 elif o == "--payload_signer_key_size":
43 # TODO(xunchang) remove this option after cleaning up the callers.
44 logger.warning("The option '--payload_signer_key_size' is deprecated."
45 " Use '--payload_signer_maximum_signature_size' instead.")
46 OPTIONS.payload_signer_maximum_signature_size = a
47 else:
48 return False
49 return True
50
51 def __init__(self):
52 super().__init__(
53 ["payload_signer=",
54 "package_key=",
55 "payload_signer_args=",
56 "payload_signer_maximum_signature_size=",
57 "payload_signer_key_size="],
58 SignerOptions.ParseOptions
59 )
60
61
62signer_options = SignerOptions()
63
Kelvin Zhang059bf6e2022-08-12 14:03:41 -070064
65class PayloadSigner(object):
66 """A class that wraps the payload signing works.
67
68 When generating a Payload, hashes of the payload and metadata files will be
69 signed with the device key, either by calling an external payload signer or
70 by calling openssl with the package key. This class provides a unified
71 interface, so that callers can just call PayloadSigner.Sign().
72
73 If an external payload signer has been specified (OPTIONS.payload_signer), it
74 calls the signer with the provided args (OPTIONS.payload_signer_args). Note
75 that the signing key should be provided as part of the payload_signer_args.
76 Otherwise without an external signer, it uses the package key
77 (OPTIONS.package_key) and calls openssl for the signing works.
78 """
79
Satoshi Futenma1f93ce22023-04-18 16:41:35 +090080 def __init__(self, package_key=None, private_key_suffix=None, pw=None, payload_signer=None,
81 payload_signer_args=None, payload_signer_maximum_signature_size=None):
Kelvin Zhang059bf6e2022-08-12 14:03:41 -070082 if package_key is None:
83 package_key = OPTIONS.package_key
84 if private_key_suffix is None:
85 private_key_suffix = OPTIONS.private_key_suffix
Satoshi Futenma1f93ce22023-04-18 16:41:35 +090086 if payload_signer_args is None:
87 payload_signer_args = OPTIONS.payload_signer_args
88 if payload_signer_maximum_signature_size is None:
89 payload_signer_maximum_signature_size = OPTIONS.payload_signer_maximum_signature_size
Kelvin Zhang059bf6e2022-08-12 14:03:41 -070090
91 if payload_signer is None:
92 # Prepare the payload signing key.
93 private_key = package_key + private_key_suffix
94
95 cmd = ["openssl", "pkcs8", "-in", private_key, "-inform", "DER"]
96 cmd.extend(["-passin", "pass:" + pw] if pw else ["-nocrypt"])
97 signing_key = common.MakeTempFile(prefix="key-", suffix=".key")
98 cmd.extend(["-out", signing_key])
99 common.RunAndCheckOutput(cmd, verbose=True)
100
101 self.signer = "openssl"
102 self.signer_args = ["pkeyutl", "-sign", "-inkey", signing_key,
103 "-pkeyopt", "digest:sha256"]
104 self.maximum_signature_size = self._GetMaximumSignatureSizeInBytes(
105 signing_key)
106 else:
107 self.signer = payload_signer
Satoshi Futenma1f93ce22023-04-18 16:41:35 +0900108 self.signer_args = payload_signer_args
109 if payload_signer_maximum_signature_size:
Kelvin Zhang059bf6e2022-08-12 14:03:41 -0700110 self.maximum_signature_size = int(
Satoshi Futenma1f93ce22023-04-18 16:41:35 +0900111 payload_signer_maximum_signature_size)
Kelvin Zhang059bf6e2022-08-12 14:03:41 -0700112 else:
113 # The legacy config uses RSA2048 keys.
114 logger.warning("The maximum signature size for payload signer is not"
115 " set, default to 256 bytes.")
116 self.maximum_signature_size = 256
117
118 @staticmethod
119 def _GetMaximumSignatureSizeInBytes(signing_key):
120 out_signature_size_file = common.MakeTempFile("signature_size")
121 cmd = ["delta_generator", "--out_maximum_signature_size_file={}".format(
122 out_signature_size_file), "--private_key={}".format(signing_key)]
123 common.RunAndCheckOutput(cmd, verbose=True)
124 with open(out_signature_size_file) as f:
125 signature_size = f.read().rstrip()
126 logger.info("%s outputs the maximum signature size: %s", cmd[0],
127 signature_size)
128 return int(signature_size)
129
Kelvin Zhangbf01f8b2022-08-30 18:25:43 +0000130 @staticmethod
131 def _Run(cmd):
132 common.RunAndCheckOutput(cmd, stdout=None, stderr=None)
133
134 def SignPayload(self, unsigned_payload):
135
136 # 1. Generate hashes of the payload and metadata files.
137 payload_sig_file = common.MakeTempFile(prefix="sig-", suffix=".bin")
138 metadata_sig_file = common.MakeTempFile(prefix="sig-", suffix=".bin")
Kelvin Zhangf6fe0a92023-08-25 13:41:42 -0700139 cmd = ["delta_generator",
140 "--in_file=" + unsigned_payload,
141 "--signature_size=" + str(self.maximum_signature_size),
142 "--out_metadata_hash_file=" + metadata_sig_file,
143 "--out_hash_file=" + payload_sig_file]
Kelvin Zhangbf01f8b2022-08-30 18:25:43 +0000144 self._Run(cmd)
145
146 # 2. Sign the hashes.
147 signed_payload_sig_file = self.SignHashFile(payload_sig_file)
148 signed_metadata_sig_file = self.SignHashFile(metadata_sig_file)
149
150 # 3. Insert the signatures back into the payload file.
151 signed_payload_file = common.MakeTempFile(prefix="signed-payload-",
152 suffix=".bin")
Kelvin Zhangf6fe0a92023-08-25 13:41:42 -0700153 cmd = ["delta_generator",
154 "--in_file=" + unsigned_payload,
155 "--out_file=" + signed_payload_file,
156 "--signature_size=" + str(self.maximum_signature_size),
157 "--metadata_signature_file=" + signed_metadata_sig_file,
158 "--payload_signature_file=" + signed_payload_sig_file]
Kelvin Zhangbf01f8b2022-08-30 18:25:43 +0000159 self._Run(cmd)
160 return signed_payload_file
161
Kelvin Zhangbf01f8b2022-08-30 18:25:43 +0000162 def SignHashFile(self, in_file):
Kelvin Zhang059bf6e2022-08-12 14:03:41 -0700163 """Signs the given input file. Returns the output filename."""
164 out_file = common.MakeTempFile(prefix="signed-", suffix=".bin")
165 cmd = [self.signer] + self.signer_args + ['-in', in_file, '-out', out_file]
166 common.RunAndCheckOutput(cmd)
167 return out_file