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