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 
compareDigest(const EVP_MD * digestFunc,size_t digestLen,const std::array<uint8_t,maxHashSize> & expected,const std::array<uint8_t,maxSeedSize> & seed,const std::string & rawData)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 
isMatch(const std::array<uint8_t,maxHashSize> & expected,const std::array<uint8_t,maxSeedSize> & seed,const std::string & rawData,const std::string & algo)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 
getParam(std::array<uint8_t,maxHashSize> & orgUsrPwdHash,std::array<uint8_t,maxHashSize> & orgAdminPwdHash,std::array<uint8_t,maxSeedSize> & seed,std::string & hashAlgo)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 
verifyIntegrityCheck(std::string & newPassword,std::array<uint8_t,maxSeedSize> & seed,unsigned int mdLen,const EVP_MD * digestFunc)123 bool Password::verifyIntegrityCheck(
124     std::string& newPassword, std::array<uint8_t, maxSeedSize>& seed,
125     unsigned int mdLen, const EVP_MD* digestFunc)
126 {
127     mNewPwdHash.fill(0);
128 
129     if (!PKCS5_PBKDF2_HMAC(reinterpret_cast<const char*>(newPassword.c_str()),
130                            newPassword.length() + 1,
131                            reinterpret_cast<const unsigned char*>(seed.data()),
132                            seed.size(), iterValue, digestFunc, mdLen,
133                            mNewPwdHash.data()))
134     {
135         lg2::error("Verify PKCS5_PBKDF2_HMAC Integrity Check failed");
136         return false;
137     }
138 
139     return true;
140 }
141 
verifyPassword(std::string userName,std::string currentPassword,std::string newPassword)142 void Password::verifyPassword(std::string userName, std::string currentPassword,
143                               std::string newPassword)
144 {
145     if (fs::exists(seedFile.c_str()))
146     {
147         std::array<uint8_t, maxHashSize> orgUsrPwdHash;
148         std::array<uint8_t, maxHashSize> orgAdminPwdHash;
149         std::array<uint8_t, maxSeedSize> seed;
150         std::string hashAlgo = "";
151 
152         if (getParam(orgUsrPwdHash, orgAdminPwdHash, seed, hashAlgo))
153         {
154             if (orgUsrPwdHash.empty() || orgAdminPwdHash.empty() ||
155                 seed.empty() || hashAlgo.empty())
156             {
157                 return;
158             }
159         }
160         else
161         {
162             throw InternalFailure();
163         }
164 
165         if (userName == "AdminPassword")
166         {
167             if (!isMatch(orgAdminPwdHash, seed, currentPassword, hashAlgo))
168             {
169                 throw InvalidCurrentPassword();
170             }
171         }
172         else
173         {
174             if (!isMatch(orgUsrPwdHash, seed, currentPassword, hashAlgo))
175             {
176                 throw InvalidCurrentPassword();
177             }
178         }
179         if (hashAlgo == "SHA256")
180         {
181             if (!verifyIntegrityCheck(newPassword, seed, 32, EVP_sha256()))
182             {
183                 throw InternalFailure();
184             }
185         }
186         if (hashAlgo == "SHA384")
187         {
188             if (!verifyIntegrityCheck(newPassword, seed, 48, EVP_sha384()))
189             {
190                 throw InternalFailure();
191             }
192         }
193         return;
194     }
195     throw InternalFailure();
196 }
changePassword(std::string userName,std::string currentPassword,std::string newPassword)197 void Password::changePassword(std::string userName, std::string currentPassword,
198                               std::string newPassword)
199 {
200     lg2::debug("BIOS config changePassword");
201     verifyPassword(userName, currentPassword, newPassword);
202 
203     std::ifstream fs(seedFile.c_str());
204     nlohmann::json json = nullptr;
205 
206     if (fs.is_open())
207     {
208         try
209         {
210             json = nlohmann::json::parse(fs, nullptr, false);
211         }
212         catch (const nlohmann::json::parse_error& e)
213         {
214             lg2::error("Failed to parse JSON file: {ERROR}", "ERROR", e);
215             throw InternalFailure();
216         }
217 
218         if (json.is_discarded())
219         {
220             throw InternalFailure();
221         }
222         json["AdminPwdHash"] = mNewPwdHash;
223         json["IsAdminPwdChanged"] = true;
224 
225         std::ofstream ofs(seedFile.c_str(), std::ios::out);
226         const auto& writeData = json.dump();
227         ofs << writeData;
228         ofs.close();
229     }
230     else
231     {
232         lg2::debug("Cannot open file stream");
233         throw InternalFailure();
234     }
235 }
Password(sdbusplus::asio::object_server & objectServer,std::shared_ptr<sdbusplus::asio::connection> & systemBus)236 Password::Password(sdbusplus::asio::object_server& objectServer,
237                    std::shared_ptr<sdbusplus::asio::connection>& systemBus) :
238     sdbusplus::xyz::openbmc_project::BIOSConfig::server::Password(
239         *systemBus, objectPathPwd),
240     objServer(objectServer), systemBus(systemBus)
241 {
242     lg2::debug("BIOS config password is running");
243     try
244     {
245         fs::path biosDir(BIOS_PERSIST_PATH);
246         fs::create_directories(biosDir);
247         seedFile = biosDir / biosSeedFile;
248     }
249     catch (const fs::filesystem_error& e)
250     {
251         lg2::error("Failed to parse JSON file: {ERROR}", "ERROR", e);
252         throw InternalFailure();
253     }
254 }
255 
256 } // namespace bios_config_pwd
257