1 /*
2 * Copyright (C) 2019 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include "apexd_verity.h"
18
19 #include <android-base/file.h>
20 #include <android-base/result.h>
21 #include <android-base/unique_fd.h>
22 #include <verity/hash_tree_builder.h>
23
24 #include <filesystem>
25 #include <iomanip>
26 #include <sstream>
27 #include <string>
28 #include <vector>
29
30 #include "apex_constants.h"
31 #include "apex_file.h"
32 #include "apexd_utils.h"
33
34 using android::base::Dirname;
35 using android::base::ErrnoError;
36 using android::base::Error;
37 using android::base::ReadFully;
38 using android::base::Result;
39 using android::base::unique_fd;
40
41 namespace android {
42 namespace apex {
43
44 namespace {
45
HexToBin(char h)46 uint8_t HexToBin(char h) {
47 if (h >= 'A' && h <= 'H') return h - 'A' + 10;
48 if (h >= 'a' && h <= 'h') return h - 'a' + 10;
49 return h - '0';
50 }
51
HexToBin(const std::string & hex)52 std::vector<uint8_t> HexToBin(const std::string& hex) {
53 std::vector<uint8_t> bin;
54 bin.reserve(hex.size() / 2);
55 for (size_t i = 0; i + 1 < hex.size(); i += 2) {
56 uint8_t c = (HexToBin(hex[i]) << 4) + HexToBin(hex[i + 1]);
57 bin.push_back(c);
58 }
59 return bin;
60 }
61
GenerateHashTree(const ApexFile & apex,const ApexVerityData & verity_data,const std::string & hashtree_file)62 Result<void> GenerateHashTree(const ApexFile& apex,
63 const ApexVerityData& verity_data,
64 const std::string& hashtree_file) {
65 unique_fd fd(
66 TEMP_FAILURE_RETRY(open(apex.GetPath().c_str(), O_RDONLY | O_CLOEXEC)));
67 if (fd.get() == -1) {
68 return ErrnoError() << "Failed to open " << apex.GetPath();
69 }
70
71 auto block_size = verity_data.desc->hash_block_size;
72 auto image_size = verity_data.desc->image_size;
73
74 auto hash_fn = HashTreeBuilder::HashFunction(verity_data.hash_algorithm);
75 if (hash_fn == nullptr) {
76 return Error() << "Unsupported hash algorithm "
77 << verity_data.hash_algorithm;
78 }
79
80 auto builder = std::make_unique<HashTreeBuilder>(block_size, hash_fn);
81 if (!builder->Initialize(image_size, HexToBin(verity_data.salt))) {
82 return Error() << "Invalid image size " << image_size;
83 }
84
85 if (!apex.GetImageOffset()) {
86 return Error() << "Cannot generate HashTree without image offset";
87 }
88 if (lseek(fd, apex.GetImageOffset().value(), SEEK_SET) == -1) {
89 return ErrnoError() << "Failed to seek";
90 }
91
92 auto block_count = image_size / block_size;
93 auto buf = std::vector<uint8_t>(block_size);
94 while (block_count-- > 0) {
95 if (!ReadFully(fd, buf.data(), block_size)) {
96 return Error() << "Failed to read";
97 }
98 if (!builder->Update(buf.data(), block_size)) {
99 return Error() << "Failed to build hashtree: Update";
100 }
101 }
102 if (!builder->BuildHashTree()) {
103 return Error() << "Failed to build hashtree: incomplete data";
104 }
105
106 auto golden_digest = HexToBin(verity_data.root_digest);
107 auto digest = builder->root_hash();
108 // This returns zero-padded digest.
109 // resize() it to compare with golden digest,
110 digest.resize(golden_digest.size());
111 if (digest != golden_digest) {
112 return Error() << "Failed to build hashtree: root digest mismatch";
113 }
114
115 unique_fd out_fd(TEMP_FAILURE_RETRY(open(
116 hashtree_file.c_str(), O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0600)));
117 if (!builder->WriteHashTreeToFd(out_fd, 0)) {
118 return Error() << "Failed to write hashtree to " << hashtree_file;
119 }
120 return {};
121 }
122
CalculateRootDigest(const std::string & hashtree_file,const ApexVerityData & verity_data)123 Result<std::string> CalculateRootDigest(const std::string& hashtree_file,
124 const ApexVerityData& verity_data) {
125 unique_fd fd(
126 TEMP_FAILURE_RETRY(open(hashtree_file.c_str(), O_RDONLY | O_CLOEXEC)));
127 if (fd.get() == -1) {
128 return ErrnoError() << "Failed to open " << hashtree_file;
129 }
130 auto block_size = verity_data.desc->hash_block_size;
131 auto image_size = verity_data.desc->image_size;
132 std::vector<uint8_t> root_verity(block_size);
133 if (!ReadFully(fd.get(), root_verity.data(), block_size)) {
134 return ErrnoError() << "Failed to read " << block_size << " bytes from "
135 << hashtree_file;
136 }
137 auto hash_fn = HashTreeBuilder::HashFunction(verity_data.hash_algorithm);
138 if (hash_fn == nullptr) {
139 return Error() << "Unsupported hash algorithm "
140 << verity_data.hash_algorithm;
141 }
142 auto builder = std::make_unique<HashTreeBuilder>(block_size, hash_fn);
143 if (!builder->Initialize(image_size, HexToBin(verity_data.salt))) {
144 return Error() << "Invalid image size " << image_size;
145 }
146 std::vector<unsigned char> root_digest;
147 if (!builder->CalculateRootDigest(root_verity, &root_digest)) {
148 return Error() << "Failed to calculate digest of " << hashtree_file;
149 }
150 auto result = HashTreeBuilder::BytesArrayToString(root_digest);
151 result.resize(verity_data.root_digest.size());
152 return result;
153 }
154
155 } // namespace
156
PrepareHashTree(const ApexFile & apex,const ApexVerityData & verity_data,const std::string & hashtree_file)157 Result<PrepareHashTreeResult> PrepareHashTree(
158 const ApexFile& apex, const ApexVerityData& verity_data,
159 const std::string& hashtree_file) {
160 if (apex.IsCompressed()) {
161 return Error() << "Cannot prepare HashTree of compressed APEX";
162 }
163
164 if (auto st = CreateDirIfNeeded(Dirname(hashtree_file), 0700); !st.ok()) {
165 return st.error();
166 }
167 bool should_regenerate_hashtree = false;
168 auto exists = PathExists(hashtree_file);
169 if (!exists.ok()) {
170 return exists.error();
171 }
172 if (*exists) {
173 auto digest = CalculateRootDigest(hashtree_file, verity_data);
174 if (!digest.ok()) {
175 return digest.error();
176 }
177 if (*digest != verity_data.root_digest) {
178 LOG(ERROR) << "Regenerating hashtree! Digest of " << hashtree_file
179 << " does not match digest of " << apex.GetPath() << " : "
180 << *digest << "\nvs\n"
181 << verity_data.root_digest;
182 should_regenerate_hashtree = true;
183 }
184 } else {
185 should_regenerate_hashtree = true;
186 }
187
188 if (should_regenerate_hashtree) {
189 if (auto st = GenerateHashTree(apex, verity_data, hashtree_file);
190 !st.ok()) {
191 return st.error();
192 }
193 LOG(INFO) << "hashtree: generated to " << hashtree_file;
194 return KRegenerate;
195 }
196 LOG(INFO) << "hashtree: reuse " << hashtree_file;
197 return kReuse;
198 }
199
RemoveObsoleteHashTrees()200 void RemoveObsoleteHashTrees() {
201 // TODO(b/120058143): on boot complete, remove unused hashtree files
202 }
203
BytesToHex(const uint8_t * bytes,size_t bytes_len)204 std::string BytesToHex(const uint8_t* bytes, size_t bytes_len) {
205 std::ostringstream s;
206
207 s << std::hex << std::setfill('0');
208 for (size_t i = 0; i < bytes_len; i++) {
209 s << std::setw(2) << static_cast<int>(bytes[i]);
210 }
211 return s.str();
212 }
213
214 } // namespace apex
215 } // namespace android
216