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