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), 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 
getAvailableKeyTypesFromSystem() const55 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 
getKeyHashFileNames(const Key_t & key) const91 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 
verifyFullImage()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 
verify()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 
systemLevelVerify()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 
verifyFile(const fs::path & file,const fs::path & sigFile,const fs::path & publicKey,const std::string & hashFunc)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 
createPublicRSA(const fs::path & publicKey)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 
mapFile(const fs::path & path,size_t size)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 
checkAndVerifyImage(const std::string & filePath,const std::string & publicKeyPath,const std::vector<std::string> & imageList,bool & fileFound)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