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 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 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 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 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 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 198 void RemoveObsoleteHashTrees() { 199 // TODO(b/120058143): on boot complete, remove unused hashtree files 200 } 201 202 } // namespace apex 203 } // namespace android 204