1 /*
2 // Copyright (c) 2020 Intel Corporation
3 //
4 // Licensed under the Apache License, Version 2.0 (the "License");
5 // you may not use this file except in compliance with the License.
6 // You may obtain a copy of the License at
7 //
8 //      http://www.apache.org/licenses/LICENSE-2.0
9 //
10 // Unless required by applicable law or agreed to in writing, software
11 // distributed under the License is distributed on an "AS IS" BASIS,
12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 // See the License for the specific language governing permissions and
14 // limitations under the License.
15 */
16 #include "password.hpp"
17 
18 #include "xyz/openbmc_project/BIOSConfig/Common/error.hpp"
19 #include "xyz/openbmc_project/Common/error.hpp"
20 
21 #include <boost/algorithm/hex.hpp>
22 #include <boost/asio.hpp>
23 #include <phosphor-logging/elog-errors.hpp>
24 #include <phosphor-logging/lg2.hpp>
25 #include <sdbusplus/asio/connection.hpp>
26 #include <sdbusplus/asio/object_server.hpp>
27 
28 #include <fstream>
29 #include <iostream>
30 
31 namespace bios_config_pwd
32 {
33 using namespace sdbusplus::xyz::openbmc_project::Common::Error;
34 using namespace sdbusplus::xyz::openbmc_project::BIOSConfig::Common::Error;
35 
36 bool Password::compareDigest(const EVP_MD* digestFunc, size_t digestLen,
37                              const std::array<uint8_t, maxHashSize>& expected,
38                              const std::array<uint8_t, maxSeedSize>& seed,
39                              const std::string& rawData)
40 {
41     std::vector<uint8_t> output(digestLen);
42     unsigned int hashLen = digestLen;
43 
44     if (!PKCS5_PBKDF2_HMAC(reinterpret_cast<const char*>(rawData.c_str()),
45                            rawData.length() + 1,
46                            reinterpret_cast<const unsigned char*>(seed.data()),
47                            seed.size(), iterValue, digestFunc, hashLen,
48                            output.data()))
49     {
50         lg2::error("Generate PKCS5_PBKDF2_HMAC Integrity Check Value failed");
51         throw InternalFailure();
52     }
53 
54     if (std::memcmp(output.data(), expected.data(),
55                     output.size() * sizeof(uint8_t)) == 0)
56     {
57         return true;
58     }
59 
60     return false;
61 }
62 
63 bool Password::isMatch(const std::array<uint8_t, maxHashSize>& expected,
64                        const std::array<uint8_t, maxSeedSize>& seed,
65                        const std::string& rawData, const std::string& algo)
66 {
67     lg2::error("isMatch");
68 
69     if (algo == "SHA256")
70     {
71         return compareDigest(EVP_sha256(), SHA256_DIGEST_LENGTH, expected, seed,
72                              rawData);
73     }
74 
75     if (algo == "SHA384")
76     {
77         return compareDigest(EVP_sha384(), SHA384_DIGEST_LENGTH, expected, seed,
78                              rawData);
79     }
80 
81     return false;
82 }
83 
84 bool Password::getParam(std::array<uint8_t, maxHashSize>& orgUsrPwdHash,
85                         std::array<uint8_t, maxHashSize>& orgAdminPwdHash,
86                         std::array<uint8_t, maxSeedSize>& seed,
87                         std::string& hashAlgo)
88 {
89     try
90     {
91         nlohmann::json json = nullptr;
92         std::ifstream ifs(seedFile.c_str());
93         if (ifs.is_open())
94         {
95             try
96             {
97                 json = nlohmann::json::parse(ifs, nullptr, false);
98             }
99             catch (const nlohmann::json::parse_error& e)
100             {
101                 lg2::error("Failed to parse JSON file: {ERROR}", "ERROR", e);
102                 return false;
103             }
104 
105             if (!json.is_discarded())
106             {
107                 orgUsrPwdHash = json["UserPwdHash"];
108                 orgAdminPwdHash = json["AdminPwdHash"];
109                 seed = json["Seed"];
110                 hashAlgo = json["HashAlgo"];
111             }
112         }
113     }
114     catch (nlohmann::detail::exception& e)
115     {
116         lg2::error("Failed to parse JSON file: {ERROR}", "ERROR", e);
117         return false;
118     }
119 
120     return true;
121 }
122 
123 bool Password::verifyIntegrityCheck(std::string& newPassword,
124                                     std::array<uint8_t, maxSeedSize>& seed,
125                                     unsigned int mdLen,
126                                     const EVP_MD* digestFunc)
127 {
128     mNewPwdHash.fill(0);
129 
130     if (!PKCS5_PBKDF2_HMAC(reinterpret_cast<const char*>(newPassword.c_str()),
131                            newPassword.length() + 1,
132                            reinterpret_cast<const unsigned char*>(seed.data()),
133                            seed.size(), iterValue, digestFunc, mdLen,
134                            mNewPwdHash.data()))
135     {
136         lg2::error("Verify PKCS5_PBKDF2_HMAC Integrity Check failed");
137         return false;
138     }
139 
140     return true;
141 }
142 
143 void Password::verifyPassword(std::string userName, std::string currentPassword,
144                               std::string newPassword)
145 {
146     if (fs::exists(seedFile.c_str()))
147     {
148         std::array<uint8_t, maxHashSize> orgUsrPwdHash;
149         std::array<uint8_t, maxHashSize> orgAdminPwdHash;
150         std::array<uint8_t, maxSeedSize> seed;
151         std::string hashAlgo = "";
152 
153         if (getParam(orgUsrPwdHash, orgAdminPwdHash, seed, hashAlgo))
154         {
155             if (orgUsrPwdHash.empty() || orgAdminPwdHash.empty() ||
156                 seed.empty() || hashAlgo.empty())
157             {
158                 return;
159             }
160         }
161         else
162         {
163             throw InternalFailure();
164         }
165 
166         if (userName == "AdminPassword")
167         {
168             if (!isMatch(orgAdminPwdHash, seed, currentPassword, hashAlgo))
169             {
170                 throw InvalidCurrentPassword();
171             }
172         }
173         else
174         {
175             if (!isMatch(orgUsrPwdHash, seed, currentPassword, hashAlgo))
176             {
177                 throw InvalidCurrentPassword();
178             }
179         }
180         if (hashAlgo == "SHA256")
181         {
182             if (!verifyIntegrityCheck(newPassword, seed, 32, EVP_sha256()))
183             {
184                 throw InternalFailure();
185             }
186         }
187         if (hashAlgo == "SHA384")
188         {
189             if (!verifyIntegrityCheck(newPassword, seed, 48, EVP_sha384()))
190             {
191                 throw InternalFailure();
192             }
193         }
194         return;
195     }
196     throw InternalFailure();
197 }
198 void Password::changePassword(std::string userName, std::string currentPassword,
199                               std::string newPassword)
200 {
201     lg2::debug("BIOS config changePassword");
202     verifyPassword(userName, currentPassword, newPassword);
203 
204     std::ifstream fs(seedFile.c_str());
205     nlohmann::json json = nullptr;
206 
207     if (fs.is_open())
208     {
209         try
210         {
211             json = nlohmann::json::parse(fs, nullptr, false);
212         }
213         catch (const nlohmann::json::parse_error& e)
214         {
215             lg2::error("Failed to parse JSON file: {ERROR}", "ERROR", e);
216             throw InternalFailure();
217         }
218 
219         if (json.is_discarded())
220         {
221             throw InternalFailure();
222         }
223         json["AdminPwdHash"] = mNewPwdHash;
224         json["IsAdminPwdChanged"] = true;
225 
226         std::ofstream ofs(seedFile.c_str(), std::ios::out);
227         const auto& writeData = json.dump();
228         ofs << writeData;
229         ofs.close();
230     }
231     else
232     {
233         lg2::debug("Cannot open file stream");
234         throw InternalFailure();
235     }
236 }
237 Password::Password(sdbusplus::asio::object_server& objectServer,
238                    std::shared_ptr<sdbusplus::asio::connection>& systemBus) :
239     sdbusplus::xyz::openbmc_project::BIOSConfig::server::Password(
240         *systemBus, objectPathPwd),
241     objServer(objectServer), systemBus(systemBus)
242 {
243     lg2::debug("BIOS config password is running");
244     try
245     {
246         fs::path biosDir(BIOS_PERSIST_PATH);
247         fs::create_directories(biosDir);
248         seedFile = biosDir / biosSeedFile;
249     }
250     catch (const fs::filesystem_error& e)
251     {
252         lg2::error("Failed to parse JSON file: {ERROR}", "ERROR", e);
253         throw InternalFailure();
254     }
255 }
256 
257 } // namespace bios_config_pwd
258