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