1 #include "config.h" 2 3 #include "image_verify.hpp" 4 5 #include "images.hpp" 6 #include "utils.hpp" 7 #include "version.hpp" 8 9 #include <fcntl.h> 10 #include <openssl/err.h> 11 #include <sys/stat.h> 12 13 #include <phosphor-logging/elog-errors.hpp> 14 #include <phosphor-logging/elog.hpp> 15 #include <phosphor-logging/lg2.hpp> 16 #include <xyz/openbmc_project/Common/error.hpp> 17 18 #include <cassert> 19 #include <fstream> 20 #include <set> 21 #include <system_error> 22 23 namespace phosphor 24 { 25 namespace software 26 { 27 namespace image 28 { 29 30 PHOSPHOR_LOG2_USING; 31 using namespace phosphor::logging; 32 using namespace phosphor::software::manager; 33 using InternalFailure = 34 sdbusplus::error::xyz::openbmc_project::common::InternalFailure; 35 36 constexpr auto keyTypeTag = "KeyType"; 37 constexpr auto hashFunctionTag = "HashType"; 38 39 Signature::Signature(const fs::path& imageDirPath, 40 const fs::path& signedConfPath) : 41 imageDirPath(imageDirPath), signedConfPath(signedConfPath) 42 { 43 fs::path file(imageDirPath / MANIFEST_FILE_NAME); 44 45 keyType = Version::getValue(file, keyTypeTag); 46 hashType = Version::getValue(file, hashFunctionTag); 47 48 // Get purpose 49 auto purposeString = Version::getValue(file, "purpose"); 50 auto convertedPurpose = 51 sdbusplus::message::convert_from_string<VersionPurpose>(purposeString); 52 purpose = convertedPurpose.value_or(Version::VersionPurpose::Unknown); 53 } 54 55 AvailableKeyTypes Signature::getAvailableKeyTypesFromSystem() const 56 { 57 AvailableKeyTypes keyTypes{}; 58 59 // Find the path of all the files 60 std::error_code ec; 61 if (!fs::is_directory(signedConfPath, ec)) 62 { 63 error("Signed configuration path not found in the system: {ERROR_MSG}", 64 "ERROR_MSG", ec.message()); 65 elog<InternalFailure>(); 66 } 67 68 // Look for all the hash and public key file names get the key value 69 // For example: 70 // /etc/activationdata/OpenBMC/publickey 71 // /etc/activationdata/OpenBMC/hashfunc 72 // /etc/activationdata/GA/publickey 73 // /etc/activationdata/GA/hashfunc 74 // Set will have OpenBMC, GA 75 76 for (const auto& p : fs::recursive_directory_iterator(signedConfPath)) 77 { 78 if ((p.path().filename() == HASH_FILE_NAME) || 79 (p.path().filename() == PUBLICKEY_FILE_NAME)) 80 { 81 // extract the key types 82 // /etc/activationdata/OpenBMC/ -> get OpenBMC from the path 83 auto key = p.path().parent_path(); 84 keyTypes.insert(key.filename()); 85 } 86 } 87 88 return keyTypes; 89 } 90 91 inline KeyHashPathPair Signature::getKeyHashFileNames(const Key_t& key) const 92 { 93 fs::path hashpath(signedConfPath / key / HASH_FILE_NAME); 94 fs::path keyPath(signedConfPath / key / PUBLICKEY_FILE_NAME); 95 96 return std::make_pair(std::move(hashpath), std::move(keyPath)); 97 } 98 99 bool Signature::verifyFullImage() 100 { 101 bool ret = true; 102 #ifdef WANT_SIGNATURE_VERIFY 103 // Only verify full image for BMC 104 if (purpose != VersionPurpose::BMC) 105 { 106 return ret; 107 } 108 109 std::vector<std::string> fullImages = { 110 fs::path(imageDirPath) / "image-bmc.sig", 111 fs::path(imageDirPath) / "image-hostfw.sig", 112 fs::path(imageDirPath) / "image-kernel.sig", 113 fs::path(imageDirPath) / "image-rofs.sig", 114 fs::path(imageDirPath) / "image-rwfs.sig", 115 fs::path(imageDirPath) / "image-u-boot.sig", 116 fs::path(imageDirPath) / "MANIFEST.sig", 117 fs::path(imageDirPath) / "publickey.sig"}; 118 119 // Merge files 120 std::string tmpFullFile = "/tmp/image-full"; 121 utils::mergeFiles(fullImages, tmpFullFile); 122 123 // Validate the full image files 124 fs::path pkeyFullFile(tmpFullFile); 125 126 std::string imageFullSig = "image-full.sig"; 127 fs::path pkeyFullFileSig(imageDirPath / imageFullSig); 128 pkeyFullFileSig.replace_extension(SIGNATURE_FILE_EXT); 129 130 // image specific publickey file name. 131 fs::path publicKeyFile(imageDirPath / PUBLICKEY_FILE_NAME); 132 133 ret = verifyFile(pkeyFullFile, pkeyFullFileSig, publicKeyFile, hashType); 134 135 std::error_code ec; 136 fs::remove(tmpFullFile, ec); 137 #endif 138 139 return ret; 140 } 141 142 bool Signature::verify() 143 { 144 try 145 { 146 bool valid; 147 // Verify the MANIFEST and publickey file using available 148 // public keys and hash on the system. 149 if (!systemLevelVerify()) 150 { 151 error("System level Signature Validation failed"); 152 return false; 153 } 154 155 bool bmcFilesFound = false; 156 // image specific publickey file name. 157 fs::path publicKeyFile(imageDirPath / PUBLICKEY_FILE_NAME); 158 159 // Record the images which are being updated 160 // First check and Validate for the fullimage, then check and Validate 161 // for images with partitions 162 std::vector<std::string> imageUpdateList = {bmcFullImages}; 163 valid = checkAndVerifyImage(imageDirPath, publicKeyFile, 164 imageUpdateList, bmcFilesFound); 165 if (bmcFilesFound && !valid) 166 { 167 return false; 168 } 169 170 if (!valid) 171 { 172 // Validate bmcImages 173 imageUpdateList.clear(); 174 imageUpdateList.assign(bmcImages.begin(), bmcImages.end()); 175 valid = checkAndVerifyImage(imageDirPath, publicKeyFile, 176 imageUpdateList, bmcFilesFound); 177 if (bmcFilesFound && !valid) 178 { 179 return false; 180 } 181 } 182 183 // Validate the optional image files. 184 auto optionalImages = getOptionalImages(); 185 bool optionalFilesFound = false; 186 bool optionalImagesValid = false; 187 for (const auto& optionalImage : optionalImages) 188 { 189 // Build Image File name 190 fs::path file(imageDirPath); 191 file /= optionalImage; 192 193 std::error_code ec; 194 if (fs::exists(file, ec)) 195 { 196 optionalFilesFound = true; 197 // Build Signature File name 198 fs::path sigFile(file); 199 sigFile += SIGNATURE_FILE_EXT; 200 201 // Verify the signature. 202 optionalImagesValid = 203 verifyFile(file, sigFile, publicKeyFile, hashType); 204 if (!optionalImagesValid) 205 { 206 error("Image file Signature Validation failed on {IMAGE}", 207 "IMAGE", optionalImage); 208 return false; 209 } 210 } 211 } 212 213 if (!verifyFullImage()) 214 { 215 error("Image full file Signature Validation failed"); 216 return false; 217 } 218 219 if (!bmcFilesFound && !optionalFilesFound) 220 { 221 error("Unable to find files to verify"); 222 return false; 223 } 224 225 // Either BMC images or optional images shall be valid 226 assert(valid || optionalImagesValid); 227 228 debug("Successfully completed Signature validation."); 229 return true; 230 } 231 catch (const InternalFailure& e) 232 { 233 return false; 234 } 235 catch (const std::exception& e) 236 { 237 error("Error during processing: {ERROR}", "ERROR", e); 238 return false; 239 } 240 } 241 242 bool Signature::systemLevelVerify() 243 { 244 // Get available key types from the system. 245 auto keyTypes = getAvailableKeyTypesFromSystem(); 246 if (keyTypes.empty()) 247 { 248 error("Missing Signature configuration data in system"); 249 elog<InternalFailure>(); 250 } 251 252 // Build publickey and its signature file name. 253 fs::path pkeyFile(imageDirPath / PUBLICKEY_FILE_NAME); 254 fs::path pkeyFileSig(pkeyFile); 255 pkeyFileSig.replace_extension(SIGNATURE_FILE_EXT); 256 257 // Build manifest and its signature file name. 258 fs::path manifestFile(imageDirPath / MANIFEST_FILE_NAME); 259 fs::path manifestFileSig(manifestFile); 260 manifestFileSig.replace_extension(SIGNATURE_FILE_EXT); 261 262 auto valid = false; 263 264 // Verify the file signature with available key types 265 // public keys and hash function. 266 // For any internal failure during the key/hash pair specific 267 // validation, should continue the validation with next 268 // available Key/hash pair. 269 for (const auto& keyType : keyTypes) 270 { 271 auto keyHashPair = getKeyHashFileNames(keyType); 272 273 auto hashFunc = Version::getValue(keyHashPair.first, hashFunctionTag); 274 275 try 276 { 277 // Verify manifest file signature 278 valid = verifyFile(manifestFile, manifestFileSig, 279 keyHashPair.second, hashFunc); 280 if (valid) 281 { 282 // Verify publickey file signature. 283 valid = verifyFile(pkeyFile, pkeyFileSig, keyHashPair.second, 284 hashFunc); 285 if (valid) 286 { 287 break; 288 } 289 } 290 } 291 catch (const InternalFailure& e) 292 { 293 valid = false; 294 } 295 } 296 return valid; 297 } 298 299 bool Signature::verifyFile(const fs::path& file, const fs::path& sigFile, 300 const fs::path& publicKey, 301 const std::string& hashFunc) 302 { 303 // Check existence of the files in the system. 304 std::error_code ec; 305 if (!(fs::exists(file, ec) && fs::exists(sigFile, ec))) 306 { 307 error("Failed to find the Data or signature file {PATH}", "PATH", file); 308 if (ec) 309 { 310 error("Error message: {ERROR_MSG}", "ERROR_MSG", ec.message()); 311 } 312 elog<InternalFailure>(); 313 } 314 315 // Create RSA. 316 auto publicRSA = createPublicRSA(publicKey); 317 if (!publicRSA) 318 { 319 error("Failed to create RSA from {PATH}", "PATH", publicKey); 320 elog<InternalFailure>(); 321 } 322 323 // Initializes a digest context. 324 EVP_MD_CTX_Ptr rsaVerifyCtx(EVP_MD_CTX_new(), ::EVP_MD_CTX_free); 325 326 // Adds all digest algorithms to the internal table 327 OpenSSL_add_all_digests(); 328 329 // Create Hash structure. 330 auto hashStruct = EVP_get_digestbyname(hashFunc.c_str()); 331 if (!hashStruct) 332 { 333 error("EVP_get_digestbynam: Unknown message digest: {HASH}", "HASH", 334 hashFunc); 335 elog<InternalFailure>(); 336 } 337 338 auto result = EVP_DigestVerifyInit(rsaVerifyCtx.get(), nullptr, hashStruct, 339 nullptr, publicRSA.get()); 340 341 if (result <= 0) 342 { 343 error("Error ({RC}) occurred during EVP_DigestVerifyInit", "RC", 344 ERR_get_error()); 345 elog<InternalFailure>(); 346 } 347 348 // Hash the data file and update the verification context 349 auto size = fs::file_size(file, ec); 350 auto dataPtr = mapFile(file, size); 351 352 result = EVP_DigestVerifyUpdate(rsaVerifyCtx.get(), dataPtr(), size); 353 if (result <= 0) 354 { 355 error("Error ({RC}) occurred during EVP_DigestVerifyUpdate", "RC", 356 ERR_get_error()); 357 elog<InternalFailure>(); 358 } 359 360 // Verify the data with signature. 361 size = fs::file_size(sigFile, ec); 362 auto signature = mapFile(sigFile, size); 363 364 result = EVP_DigestVerifyFinal( 365 rsaVerifyCtx.get(), reinterpret_cast<unsigned char*>(signature()), 366 size); 367 368 // Check the verification result. 369 if (result < 0) 370 { 371 error("Error ({RC}) occurred during EVP_DigestVerifyFinal", "RC", 372 ERR_get_error()); 373 elog<InternalFailure>(); 374 } 375 376 if (result == 0) 377 { 378 error("EVP_DigestVerifyFinal:Signature validation failed on {PATH}", 379 "PATH", sigFile); 380 return false; 381 } 382 return true; 383 } 384 385 inline EVP_PKEY_Ptr Signature::createPublicRSA(const fs::path& publicKey) 386 { 387 std::error_code ec; 388 auto size = fs::file_size(publicKey, ec); 389 390 // Read public key file 391 auto data = mapFile(publicKey, size); 392 393 BIO_MEM_Ptr keyBio(BIO_new_mem_buf(data(), -1), &::BIO_free); 394 if (keyBio.get() == nullptr) 395 { 396 error("Failed to create new BIO Memory buffer"); 397 elog<InternalFailure>(); 398 } 399 400 return {PEM_read_bio_PUBKEY(keyBio.get(), nullptr, nullptr, nullptr), 401 &::EVP_PKEY_free}; 402 } 403 404 CustomMap Signature::mapFile(const fs::path& path, size_t size) 405 { 406 CustomFd fd(open(path.c_str(), O_RDONLY)); 407 408 return CustomMap(mmap(nullptr, size, PROT_READ, MAP_PRIVATE, fd(), 0), 409 size); 410 } 411 412 bool Signature::checkAndVerifyImage( 413 const std::string& filePath, const std::string& publicKeyPath, 414 const std::vector<std::string>& imageList, bool& fileFound) 415 { 416 bool valid = true; 417 418 fileFound = false; 419 for (auto& bmcImage : imageList) 420 { 421 fs::path file(filePath); 422 file /= bmcImage; 423 424 std::error_code ec; 425 if (!fs::exists(file, ec)) 426 { 427 valid = false; 428 break; 429 } 430 fileFound = true; 431 432 fs::path sigFile(file); 433 sigFile += SIGNATURE_FILE_EXT; 434 435 // Verify the signature. 436 valid = verifyFile(file, sigFile, publicKeyPath, hashType); 437 if (!valid) 438 { 439 error("Image file Signature Validation failed on {PATH}", "PATH", 440 bmcImage); 441 return false; 442 } 443 } 444 445 return valid; 446 } 447 } // namespace image 448 } // namespace software 449 } // namespace phosphor 450