xref: /openbmc/phosphor-bmc-code-mgmt/image_verify.cpp (revision cc74233cbe5b4d99787eb30d85c34d7d8e7aafb7)
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