1 #include "config.h"
2 
3 #include "image_verify.hpp"
4 
5 #include "version.hpp"
6 
7 #include <fcntl.h>
8 #include <openssl/err.h>
9 #include <sys/stat.h>
10 
11 #include <phosphor-logging/elog-errors.hpp>
12 #include <phosphor-logging/elog.hpp>
13 #include <phosphor-logging/log.hpp>
14 #include <xyz/openbmc_project/Common/error.hpp>
15 
16 #include <fstream>
17 #include <set>
18 
19 namespace openpower
20 {
21 namespace software
22 {
23 namespace image
24 {
25 
26 using namespace phosphor::logging;
27 using namespace openpower::software::updater;
28 using InternalFailure =
29     sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure;
30 
31 constexpr auto keyTypeTag = "KeyType";
32 constexpr auto hashFunctionTag = "HashType";
33 
34 Signature::Signature(const std::filesystem::path& imageDirPath,
35                      const std::string& pnorFileName,
36                      const std::filesystem::path& signedConfPath) :
37     imageDirPath(imageDirPath),
38     pnorFileName(pnorFileName), signedConfPath(signedConfPath)
39 {
40     std::filesystem::path file(imageDirPath / MANIFEST_FILE);
41 
42     auto keyValues =
43         Version::getValue(file, {{keyTypeTag, " "}, {hashFunctionTag, " "}});
44     keyType = keyValues.at(keyTypeTag);
45     hashType = keyValues.at(hashFunctionTag);
46 }
47 
48 AvailableKeyTypes Signature::getAvailableKeyTypesFromSystem() const
49 {
50     AvailableKeyTypes keyTypes{};
51 
52     // Find the path of all the files
53     if (!std::filesystem::is_directory(signedConfPath))
54     {
55         log<level::ERR>("Signed configuration path not found in the system");
56         elog<InternalFailure>();
57     }
58 
59     // Look for all the hash and public key file names get the key value
60     // For example:
61     // /etc/activationdata/OpenPOWER/publickey
62     // /etc/activationdata/OpenPOWER/hashfunc
63     // /etc/activationdata/GA/publickey
64     // /etc/activationdata/GA/hashfunc
65     // Set will have OpenPOWER, GA
66 
67     for (const auto& p :
68          std::filesystem::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/GA/  -> get GA 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     std::filesystem::path hashpath(signedConfPath / key / HASH_FILE_NAME);
86     std::filesystem::path keyPath(signedConfPath / key / PUBLICKEY_FILE_NAME);
87 
88     return std::make_pair(std::move(hashpath), std::move(keyPath));
89 }
90 
91 bool Signature::verify()
92 {
93     try
94     {
95         // Verify the MANIFEST and publickey file using available
96         // public keys and hash on the system.
97         if (false == systemLevelVerify())
98         {
99             log<level::ERR>("System level Signature Validation failed");
100             return false;
101         }
102 
103         // image specific publickey file name.
104         std::filesystem::path publicKeyFile(imageDirPath / PUBLICKEY_FILE_NAME);
105 
106         // Validate the PNOR image file.
107         // Build Image File name
108         std::filesystem::path file(imageDirPath);
109         file /= pnorFileName;
110 
111         // Build Signature File name
112         std::string fileName = file.filename();
113         std::filesystem::path sigFile(imageDirPath);
114         sigFile /= fileName + SIGNATURE_FILE_EXT;
115 
116         // Verify the signature.
117         auto valid = verifyFile(file, sigFile, publicKeyFile, hashType);
118         if (valid == false)
119         {
120             log<level::ERR>("Image file Signature Validation failed",
121                             entry("IMAGE=%s", pnorFileName.c_str()));
122             return false;
123         }
124 
125         log<level::DEBUG>("Successfully completed Signature vaildation.");
126 
127         return true;
128     }
129     catch (const InternalFailure& e)
130     {
131         return false;
132     }
133     catch (const std::exception& e)
134     {
135         log<level::ERR>(e.what());
136         return false;
137     }
138 }
139 
140 bool Signature::systemLevelVerify()
141 {
142     // Get available key types from the system.
143     auto keyTypes = getAvailableKeyTypesFromSystem();
144     if (keyTypes.empty())
145     {
146         log<level::ERR>("Missing Signature configuration data in system");
147         elog<InternalFailure>();
148     }
149 
150     // Build publickey and its signature file name.
151     std::filesystem::path pkeyFile(imageDirPath / PUBLICKEY_FILE_NAME);
152     std::filesystem::path pkeyFileSig(pkeyFile);
153     pkeyFileSig.replace_extension(SIGNATURE_FILE_EXT);
154 
155     // Build manifest and its signature file name.
156     std::filesystem::path manifestFile(imageDirPath / MANIFEST_FILE);
157     std::filesystem::path manifestFileSig(manifestFile);
158     manifestFileSig.replace_extension(SIGNATURE_FILE_EXT);
159 
160     auto valid = false;
161 
162     // Verify the file signature with available key types
163     // public keys and hash function.
164     // For any internal failure during the key/hash pair specific
165     // validation, should continue the validation with next
166     // available Key/hash pair.
167     for (const auto& keyType : keyTypes)
168     {
169         auto keyHashPair = getKeyHashFileNames(keyType);
170         auto keyValues =
171             Version::getValue(keyHashPair.first, {{hashFunctionTag, " "}});
172         auto hashFunc = keyValues.at(hashFunctionTag);
173 
174         try
175         {
176             // Verify manifest file signature
177             valid = verifyFile(manifestFile, manifestFileSig,
178                                keyHashPair.second, hashFunc);
179             if (valid)
180             {
181                 // Verify publickey file signature.
182                 valid = verifyFile(pkeyFile, pkeyFileSig, keyHashPair.second,
183                                    hashFunc);
184                 if (valid)
185                 {
186                     break;
187                 }
188             }
189         }
190         catch (const InternalFailure& e)
191         {
192             valid = false;
193         }
194     }
195     return valid;
196 }
197 
198 bool Signature::verifyFile(const std::filesystem::path& file,
199                            const std::filesystem::path& sigFile,
200                            const std::filesystem::path& publicKey,
201                            const std::string& hashFunc)
202 {
203 
204     // Check existence of the files in the system.
205     if (!(std::filesystem::exists(file) && std::filesystem::exists(sigFile)))
206     {
207         log<level::ERR>("Failed to find the Data or signature file.",
208                         entry("FILE=%s", file.c_str()));
209         elog<InternalFailure>();
210     }
211 
212     // Create RSA.
213     auto publicRSA = createPublicRSA(publicKey);
214     if (publicRSA == nullptr)
215     {
216         log<level::ERR>("Failed to create RSA",
217                         entry("FILE=%s", publicKey.c_str()));
218         elog<InternalFailure>();
219     }
220 
221     // Assign key to RSA.
222     EVP_PKEY_Ptr pKeyPtr(EVP_PKEY_new(), ::EVP_PKEY_free);
223     EVP_PKEY_assign_RSA(pKeyPtr.get(), publicRSA);
224 
225     // Initializes a digest context.
226     EVP_MD_CTX_Ptr rsaVerifyCtx(EVP_MD_CTX_new(), ::EVP_MD_CTX_free);
227 
228     // Adds all digest algorithms to the internal table
229     OpenSSL_add_all_digests();
230 
231     // Create Hash structure.
232     auto hashStruct = EVP_get_digestbyname(hashFunc.c_str());
233     if (!hashStruct)
234     {
235         log<level::ERR>("EVP_get_digestbynam: Unknown message digest",
236                         entry("HASH=%s", hashFunc.c_str()));
237         elog<InternalFailure>();
238     }
239 
240     auto result = EVP_DigestVerifyInit(rsaVerifyCtx.get(), nullptr, hashStruct,
241                                        nullptr, pKeyPtr.get());
242 
243     if (result <= 0)
244     {
245         log<level::ERR>("Error occurred during EVP_DigestVerifyInit",
246                         entry("ERRCODE=%lu", ERR_get_error()));
247         elog<InternalFailure>();
248     }
249 
250     // Hash the data file and update the verification context
251     auto size = std::filesystem::file_size(file);
252     auto dataPtr = mapFile(file, size);
253 
254     result = EVP_DigestVerifyUpdate(rsaVerifyCtx.get(), dataPtr(), size);
255     if (result <= 0)
256     {
257         log<level::ERR>("Error occurred during EVP_DigestVerifyUpdate",
258                         entry("ERRCODE=%lu", ERR_get_error()));
259         elog<InternalFailure>();
260     }
261 
262     // Verify the data with signature.
263     size = std::filesystem::file_size(sigFile);
264     auto signature = mapFile(sigFile, size);
265 
266     result = EVP_DigestVerifyFinal(
267         rsaVerifyCtx.get(), reinterpret_cast<unsigned char*>(signature()),
268         size);
269 
270     // Check the verification result.
271     if (result < 0)
272     {
273         log<level::ERR>("Error occurred during EVP_DigestVerifyFinal",
274                         entry("ERRCODE=%lu", ERR_get_error()));
275         elog<InternalFailure>();
276     }
277 
278     if (result == 0)
279     {
280         log<level::ERR>("EVP_DigestVerifyFinal:Signature validation failed",
281                         entry("PATH=%s", sigFile.c_str()));
282         return false;
283     }
284     return true;
285 }
286 
287 inline RSA* Signature::createPublicRSA(const std::filesystem::path& publicKey)
288 {
289     RSA* rsa = nullptr;
290     auto size = std::filesystem::file_size(publicKey);
291 
292     // Read public key file
293     auto data = mapFile(publicKey, size);
294 
295     BIO_MEM_Ptr keyBio(BIO_new_mem_buf(data(), -1), &::BIO_free);
296     if (keyBio.get() == nullptr)
297     {
298         log<level::ERR>("Failed to create new BIO Memory buffer");
299         elog<InternalFailure>();
300     }
301 
302     rsa = PEM_read_bio_RSA_PUBKEY(keyBio.get(), &rsa, nullptr, nullptr);
303 
304     return rsa;
305 }
306 
307 CustomMap Signature::mapFile(const std::filesystem::path& path, size_t size)
308 {
309 
310     CustomFd fd(open(path.c_str(), O_RDONLY));
311 
312     return CustomMap(mmap(nullptr, size, PROT_READ, MAP_PRIVATE, fd(), 0),
313                      size);
314 }
315 
316 } // namespace image
317 } // namespace software
318 } // namespace openpower
319