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