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 
Signature(const fs::path & imageDirPath,const fs::path & signedConfPath)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 
getAvailableKeyTypesFromSystem() const56 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 
getKeyHashFileNames(const Key_t & key) const92 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 
verifyFullImage()100 bool Signature::verifyFullImage()
101 {
102     bool ret = true;
103 #ifdef WANT_SIGNATURE_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 
verify()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 = verifyFile(file, sigFile, publicKeyFile,
204                                                  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 validation.");
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 
systemLevelVerify()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 
verifyFile(const fs::path & file,const fs::path & sigFile,const fs::path & publicKey,const std::string & hashFunc)300 bool Signature::verifyFile(const fs::path& file, const fs::path& sigFile,
301                            const fs::path& publicKey,
302                            const std::string& hashFunc)
303 {
304     // Check existence of the files in the system.
305     std::error_code ec;
306     if (!(fs::exists(file, ec) && fs::exists(sigFile, ec)))
307     {
308         error("Failed to find the Data or signature file {PATH}", "PATH", file);
309         if (ec)
310         {
311             error("Error message: {ERROR_MSG}", "ERROR_MSG", ec.message());
312         }
313         elog<InternalFailure>();
314     }
315 
316     // Create RSA.
317     auto publicRSA = createPublicRSA(publicKey);
318     if (!publicRSA)
319     {
320         error("Failed to create RSA from {PATH}", "PATH", publicKey);
321         elog<InternalFailure>();
322     }
323 
324     // Initializes a digest context.
325     EVP_MD_CTX_Ptr rsaVerifyCtx(EVP_MD_CTX_new(), ::EVP_MD_CTX_free);
326 
327     // Adds all digest algorithms to the internal table
328     OpenSSL_add_all_digests();
329 
330     // Create Hash structure.
331     auto hashStruct = EVP_get_digestbyname(hashFunc.c_str());
332     if (!hashStruct)
333     {
334         error("EVP_get_digestbynam: Unknown message digest: {HASH}", "HASH",
335               hashFunc);
336         elog<InternalFailure>();
337     }
338 
339     auto result = EVP_DigestVerifyInit(rsaVerifyCtx.get(), nullptr, hashStruct,
340                                        nullptr, publicRSA.get());
341 
342     if (result <= 0)
343     {
344         error("Error ({RC}) occurred during EVP_DigestVerifyInit", "RC",
345               ERR_get_error());
346         elog<InternalFailure>();
347     }
348 
349     // Hash the data file and update the verification context
350     auto size = fs::file_size(file, ec);
351     auto dataPtr = mapFile(file, size);
352 
353     result = EVP_DigestVerifyUpdate(rsaVerifyCtx.get(), dataPtr(), size);
354     if (result <= 0)
355     {
356         error("Error ({RC}) occurred during EVP_DigestVerifyUpdate", "RC",
357               ERR_get_error());
358         elog<InternalFailure>();
359     }
360 
361     // Verify the data with signature.
362     size = fs::file_size(sigFile, ec);
363     auto signature = mapFile(sigFile, size);
364 
365     result = EVP_DigestVerifyFinal(
366         rsaVerifyCtx.get(), reinterpret_cast<unsigned char*>(signature()),
367         size);
368 
369     // Check the verification result.
370     if (result < 0)
371     {
372         error("Error ({RC}) occurred during EVP_DigestVerifyFinal", "RC",
373               ERR_get_error());
374         elog<InternalFailure>();
375     }
376 
377     if (result == 0)
378     {
379         error("EVP_DigestVerifyFinal:Signature validation failed on {PATH}",
380               "PATH", sigFile);
381         return false;
382     }
383     return true;
384 }
385 
createPublicRSA(const fs::path & publicKey)386 inline EVP_PKEY_Ptr Signature::createPublicRSA(const fs::path& publicKey)
387 {
388     std::error_code ec;
389     auto size = fs::file_size(publicKey, ec);
390 
391     // Read public key file
392     auto data = mapFile(publicKey, size);
393 
394     BIO_MEM_Ptr keyBio(BIO_new_mem_buf(data(), -1), &::BIO_free);
395     if (keyBio.get() == nullptr)
396     {
397         error("Failed to create new BIO Memory buffer");
398         elog<InternalFailure>();
399     }
400 
401     return {PEM_read_bio_PUBKEY(keyBio.get(), nullptr, nullptr, nullptr),
402             &::EVP_PKEY_free};
403 }
404 
mapFile(const fs::path & path,size_t size)405 CustomMap Signature::mapFile(const fs::path& path, size_t size)
406 {
407     CustomFd fd(open(path.c_str(), O_RDONLY));
408 
409     return CustomMap(mmap(nullptr, size, PROT_READ, MAP_PRIVATE, fd(), 0),
410                      size);
411 }
412 
checkAndVerifyImage(const std::string & filePath,const std::string & publicKeyPath,const std::vector<std::string> & imageList,bool & fileFound)413 bool Signature::checkAndVerifyImage(const std::string& filePath,
414                                     const std::string& publicKeyPath,
415                                     const std::vector<std::string>& imageList,
416                                     bool& fileFound)
417 {
418     bool valid = true;
419 
420     fileFound = false;
421     for (auto& bmcImage : imageList)
422     {
423         fs::path file(filePath);
424         file /= bmcImage;
425 
426         std::error_code ec;
427         if (!fs::exists(file, ec))
428         {
429             valid = false;
430             break;
431         }
432         fileFound = true;
433 
434         fs::path sigFile(file);
435         sigFile += SIGNATURE_FILE_EXT;
436 
437         // Verify the signature.
438         valid = verifyFile(file, sigFile, publicKeyPath, hashType);
439         if (valid == false)
440         {
441             error("Image file Signature Validation failed on {PATH}", "PATH",
442                   bmcImage);
443             return false;
444         }
445     }
446 
447     return valid;
448 }
449 } // namespace image
450 } // namespace software
451 } // namespace phosphor
452