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 #define LOG_TAG "apexd"
17
18 #include "apexd_verity.h"
19
20 #include <filesystem>
21 #include <vector>
22
23 #include <android-base/file.h>
24 #include <android-base/result.h>
25 #include <android-base/unique_fd.h>
26 #include <verity/hash_tree_builder.h>
27
28 #include "apex_constants.h"
29 #include "apex_file.h"
30 #include "apexd_utils.h"
31
32 using android::base::Dirname;
33 using android::base::ErrnoError;
34 using android::base::Error;
35 using android::base::ReadFully;
36 using android::base::Result;
37 using android::base::unique_fd;
38
39 namespace android {
40 namespace apex {
41
42 namespace {
43
HexToBin(char h)44 uint8_t HexToBin(char h) {
45 if (h >= 'A' && h <= 'H') return h - 'A' + 10;
46 if (h >= 'a' && h <= 'h') return h - 'a' + 10;
47 return h - '0';
48 }
49
HexToBin(const std::string & hex)50 std::vector<uint8_t> HexToBin(const std::string& hex) {
51 std::vector<uint8_t> bin;
52 bin.reserve(hex.size() / 2);
53 for (size_t i = 0; i + 1 < hex.size(); i += 2) {
54 uint8_t c = (HexToBin(hex[i]) << 4) + HexToBin(hex[i + 1]);
55 bin.push_back(c);
56 }
57 return bin;
58 }
59
GenerateHashTree(const ApexFile & apex,const ApexVerityData & verity_data,const std::string & hashtree_file)60 Result<void> GenerateHashTree(const ApexFile& apex,
61 const ApexVerityData& verity_data,
62 const std::string& hashtree_file) {
63 unique_fd fd(
64 TEMP_FAILURE_RETRY(open(apex.GetPath().c_str(), O_RDONLY | O_CLOEXEC)));
65 if (fd.get() == -1) {
66 return ErrnoError() << "Failed to open " << apex.GetPath();
67 }
68
69 auto block_size = verity_data.desc->hash_block_size;
70 auto image_size = verity_data.desc->image_size;
71
72 auto hash_fn = HashTreeBuilder::HashFunction(verity_data.hash_algorithm);
73 if (hash_fn == nullptr) {
74 return Error() << "Unsupported hash algorithm "
75 << verity_data.hash_algorithm;
76 }
77
78 auto builder = std::make_unique<HashTreeBuilder>(block_size, hash_fn);
79 if (!builder->Initialize(image_size, HexToBin(verity_data.salt))) {
80 return Error() << "Invalid image size " << image_size;
81 }
82
83 if (!apex.GetImageOffset()) {
84 return Error() << "Cannot generate HashTree without image offset";
85 }
86 if (lseek(fd, apex.GetImageOffset().value(), SEEK_SET) == -1) {
87 return ErrnoError() << "Failed to seek";
88 }
89
90 auto block_count = image_size / block_size;
91 auto buf = std::vector<uint8_t>(block_size);
92 while (block_count-- > 0) {
93 if (!ReadFully(fd, buf.data(), block_size)) {
94 return Error() << "Failed to read";
95 }
96 if (!builder->Update(buf.data(), block_size)) {
97 return Error() << "Failed to build hashtree: Update";
98 }
99 }
100 if (!builder->BuildHashTree()) {
101 return Error() << "Failed to build hashtree: incomplete data";
102 }
103
104 auto golden_digest = HexToBin(verity_data.root_digest);
105 auto digest = builder->root_hash();
106 // This returns zero-padded digest.
107 // resize() it to compare with golden digest,
108 digest.resize(golden_digest.size());
109 if (digest != golden_digest) {
110 return Error() << "Failed to build hashtree: root digest mismatch";
111 }
112
113 unique_fd out_fd(TEMP_FAILURE_RETRY(open(
114 hashtree_file.c_str(), O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0600)));
115 if (!builder->WriteHashTreeToFd(out_fd, 0)) {
116 return Error() << "Failed to write hashtree to " << hashtree_file;
117 }
118 return {};
119 }
120
CalculateRootDigest(const std::string & hashtree_file,const ApexVerityData & verity_data)121 Result<std::string> CalculateRootDigest(const std::string& hashtree_file,
122 const ApexVerityData& verity_data) {
123 unique_fd fd(
124 TEMP_FAILURE_RETRY(open(hashtree_file.c_str(), O_RDONLY | O_CLOEXEC)));
125 if (fd.get() == -1) {
126 return ErrnoError() << "Failed to open " << hashtree_file;
127 }
128 auto block_size = verity_data.desc->hash_block_size;
129 auto image_size = verity_data.desc->image_size;
130 std::vector<uint8_t> root_verity(block_size);
131 if (!ReadFully(fd.get(), root_verity.data(), block_size)) {
132 return ErrnoError() << "Failed to read " << block_size << " bytes from "
133 << hashtree_file;
134 }
135 auto hash_fn = HashTreeBuilder::HashFunction(verity_data.hash_algorithm);
136 if (hash_fn == nullptr) {
137 return Error() << "Unsupported hash algorithm "
138 << verity_data.hash_algorithm;
139 }
140 auto builder = std::make_unique<HashTreeBuilder>(block_size, hash_fn);
141 if (!builder->Initialize(image_size, HexToBin(verity_data.salt))) {
142 return Error() << "Invalid image size " << image_size;
143 }
144 std::vector<unsigned char> root_digest;
145 if (!builder->CalculateRootDigest(root_verity, &root_digest)) {
146 return Error() << "Failed to calculate digest of " << hashtree_file;
147 }
148 auto result = HashTreeBuilder::BytesArrayToString(root_digest);
149 result.resize(verity_data.root_digest.size());
150 return result;
151 }
152
153 } // namespace
154
PrepareHashTree(const ApexFile & apex,const ApexVerityData & verity_data,const std::string & hashtree_file)155 Result<PrepareHashTreeResult> PrepareHashTree(
156 const ApexFile& apex, const ApexVerityData& verity_data,
157 const std::string& hashtree_file) {
158 if (apex.IsCompressed()) {
159 return Error() << "Cannot prepare HashTree of compressed APEX";
160 }
161
162 if (auto st = CreateDirIfNeeded(Dirname(hashtree_file), 0700); !st.ok()) {
163 return st.error();
164 }
165 bool should_regenerate_hashtree = false;
166 auto exists = PathExists(hashtree_file);
167 if (!exists.ok()) {
168 return exists.error();
169 }
170 if (*exists) {
171 auto digest = CalculateRootDigest(hashtree_file, verity_data);
172 if (!digest.ok()) {
173 return digest.error();
174 }
175 if (*digest != verity_data.root_digest) {
176 LOG(ERROR) << "Regenerating hashtree! Digest of " << hashtree_file
177 << " does not match digest of " << apex.GetPath() << " : "
178 << *digest << "\nvs\n"
179 << verity_data.root_digest;
180 should_regenerate_hashtree = true;
181 }
182 } else {
183 should_regenerate_hashtree = true;
184 }
185
186 if (should_regenerate_hashtree) {
187 if (auto st = GenerateHashTree(apex, verity_data, hashtree_file);
188 !st.ok()) {
189 return st.error();
190 }
191 LOG(INFO) << "hashtree: generated to " << hashtree_file;
192 return KRegenerate;
193 }
194 LOG(INFO) << "hashtree: reuse " << hashtree_file;
195 return kReuse;
196 }
197
RemoveObsoleteHashTrees()198 void RemoveObsoleteHashTrees() {
199 // TODO(b/120058143): on boot complete, remove unused hashtree files
200 }
201
202 } // namespace apex
203 } // namespace android
204