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,std::string persistPath)236 Password::Password(sdbusplus::asio::object_server& objectServer,
237                    std::shared_ptr<sdbusplus::asio::connection>& systemBus,
238                    std::string persistPath) :
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(persistPath);
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