blob: bbd2896508fb3b5dd9bd7b05b0dfc4e022e705d3 [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
19from common import OPTIONS
20
21logger = logging.getLogger(__name__)
22
23
24class PayloadSigner(object):
25 """A class that wraps the payload signing works.
26
27 When generating a Payload, hashes of the payload and metadata files will be
28 signed with the device key, either by calling an external payload signer or
29 by calling openssl with the package key. This class provides a unified
30 interface, so that callers can just call PayloadSigner.Sign().
31
32 If an external payload signer has been specified (OPTIONS.payload_signer), it
33 calls the signer with the provided args (OPTIONS.payload_signer_args). Note
34 that the signing key should be provided as part of the payload_signer_args.
35 Otherwise without an external signer, it uses the package key
36 (OPTIONS.package_key) and calls openssl for the signing works.
37 """
38
Satoshi Futenma1f93ce22023-04-18 16:41:35 +090039 def __init__(self, package_key=None, private_key_suffix=None, pw=None, payload_signer=None,
40 payload_signer_args=None, payload_signer_maximum_signature_size=None):
Kelvin Zhang059bf6e2022-08-12 14:03:41 -070041 if package_key is None:
42 package_key = OPTIONS.package_key
43 if private_key_suffix is None:
44 private_key_suffix = OPTIONS.private_key_suffix
Satoshi Futenma1f93ce22023-04-18 16:41:35 +090045 if payload_signer_args is None:
46 payload_signer_args = OPTIONS.payload_signer_args
47 if payload_signer_maximum_signature_size is None:
48 payload_signer_maximum_signature_size = OPTIONS.payload_signer_maximum_signature_size
Kelvin Zhang059bf6e2022-08-12 14:03:41 -070049
50 if payload_signer is None:
51 # Prepare the payload signing key.
52 private_key = package_key + private_key_suffix
53
54 cmd = ["openssl", "pkcs8", "-in", private_key, "-inform", "DER"]
55 cmd.extend(["-passin", "pass:" + pw] if pw else ["-nocrypt"])
56 signing_key = common.MakeTempFile(prefix="key-", suffix=".key")
57 cmd.extend(["-out", signing_key])
58 common.RunAndCheckOutput(cmd, verbose=True)
59
60 self.signer = "openssl"
61 self.signer_args = ["pkeyutl", "-sign", "-inkey", signing_key,
62 "-pkeyopt", "digest:sha256"]
63 self.maximum_signature_size = self._GetMaximumSignatureSizeInBytes(
64 signing_key)
65 else:
66 self.signer = payload_signer
Satoshi Futenma1f93ce22023-04-18 16:41:35 +090067 self.signer_args = payload_signer_args
68 if payload_signer_maximum_signature_size:
Kelvin Zhang059bf6e2022-08-12 14:03:41 -070069 self.maximum_signature_size = int(
Satoshi Futenma1f93ce22023-04-18 16:41:35 +090070 payload_signer_maximum_signature_size)
Kelvin Zhang059bf6e2022-08-12 14:03:41 -070071 else:
72 # The legacy config uses RSA2048 keys.
73 logger.warning("The maximum signature size for payload signer is not"
74 " set, default to 256 bytes.")
75 self.maximum_signature_size = 256
76
77 @staticmethod
78 def _GetMaximumSignatureSizeInBytes(signing_key):
79 out_signature_size_file = common.MakeTempFile("signature_size")
80 cmd = ["delta_generator", "--out_maximum_signature_size_file={}".format(
81 out_signature_size_file), "--private_key={}".format(signing_key)]
82 common.RunAndCheckOutput(cmd, verbose=True)
83 with open(out_signature_size_file) as f:
84 signature_size = f.read().rstrip()
85 logger.info("%s outputs the maximum signature size: %s", cmd[0],
86 signature_size)
87 return int(signature_size)
88
Kelvin Zhangbf01f8b2022-08-30 18:25:43 +000089 @staticmethod
90 def _Run(cmd):
91 common.RunAndCheckOutput(cmd, stdout=None, stderr=None)
92
93 def SignPayload(self, unsigned_payload):
94
95 # 1. Generate hashes of the payload and metadata files.
96 payload_sig_file = common.MakeTempFile(prefix="sig-", suffix=".bin")
97 metadata_sig_file = common.MakeTempFile(prefix="sig-", suffix=".bin")
Kelvin Zhangf6fe0a92023-08-25 13:41:42 -070098 cmd = ["delta_generator",
99 "--in_file=" + unsigned_payload,
100 "--signature_size=" + str(self.maximum_signature_size),
101 "--out_metadata_hash_file=" + metadata_sig_file,
102 "--out_hash_file=" + payload_sig_file]
Kelvin Zhangbf01f8b2022-08-30 18:25:43 +0000103 self._Run(cmd)
104
105 # 2. Sign the hashes.
106 signed_payload_sig_file = self.SignHashFile(payload_sig_file)
107 signed_metadata_sig_file = self.SignHashFile(metadata_sig_file)
108
109 # 3. Insert the signatures back into the payload file.
110 signed_payload_file = common.MakeTempFile(prefix="signed-payload-",
111 suffix=".bin")
Kelvin Zhangf6fe0a92023-08-25 13:41:42 -0700112 cmd = ["delta_generator",
113 "--in_file=" + unsigned_payload,
114 "--out_file=" + signed_payload_file,
115 "--signature_size=" + str(self.maximum_signature_size),
116 "--metadata_signature_file=" + signed_metadata_sig_file,
117 "--payload_signature_file=" + signed_payload_sig_file]
Kelvin Zhangbf01f8b2022-08-30 18:25:43 +0000118 self._Run(cmd)
119 return signed_payload_file
120
Kelvin Zhangbf01f8b2022-08-30 18:25:43 +0000121 def SignHashFile(self, in_file):
Kelvin Zhang059bf6e2022-08-12 14:03:41 -0700122 """Signs the given input file. Returns the output filename."""
123 out_file = common.MakeTempFile(prefix="signed-", suffix=".bin")
124 cmd = [self.signer] + self.signer_args + ['-in', in_file, '-out', out_file]
125 common.RunAndCheckOutput(cmd)
126 return out_file