xref: /openbmc/phosphor-host-ipmid/user_channel/passwd_mgr.cpp (revision 1318a5ed36cfd41335e687b54db1c17c0dde8f45)
1  /*
2  // Copyright (c) 2018 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  
17  #include "passwd_mgr.hpp"
18  
19  #include "file.hpp"
20  #include "shadowlock.hpp"
21  
22  #include <openssl/hmac.h>
23  #include <openssl/rand.h>
24  #include <openssl/sha.h>
25  #include <string.h>
26  #include <sys/stat.h>
27  #include <unistd.h>
28  
29  #include <phosphor-logging/lg2.hpp>
30  
31  #include <cerrno>
32  #include <cstring>
33  #include <fstream>
34  #include <iomanip>
35  
36  namespace ipmi
37  {
38  
39  static const char* passwdFileName = "/etc/ipmi_pass";
40  static const char* encryptKeyFileName = "/etc/key_file";
41  static const size_t maxKeySize = 8;
42  
43  constexpr mode_t modeMask =
44      (S_ISUID | S_ISGID | S_ISVTX | S_IRWXU | S_IRWXG | S_IRWXO);
45  
46  #define META_PASSWD_SIG "=OPENBMC="
47  
48  /*
49   * Meta data struct for encrypted password file
50   */
51  struct MetaPassStruct
52  {
53      char signature[10];
54      unsigned char reseved[2];
55      size_t hashSize;
56      size_t ivSize;
57      size_t dataSize;
58      size_t padSize;
59      size_t macSize;
60  };
61  
PasswdMgr()62  PasswdMgr::PasswdMgr()
63  {
64      restrictFilesPermission();
65      initPasswordMap();
66  }
67  
restrictFilesPermission(void)68  void PasswdMgr::restrictFilesPermission(void)
69  {
70      struct stat st = {};
71      // Restrict file permission to owner read & write
72      if (stat(passwdFileName, &st) == 0)
73      {
74          if ((st.st_mode & modeMask) != (S_IRUSR | S_IWUSR))
75          {
76              if (chmod(passwdFileName, S_IRUSR | S_IWUSR) == -1)
77              {
78                  lg2::debug("Error setting chmod for ipmi_pass file");
79              }
80          }
81      }
82  
83      if (stat(encryptKeyFileName, &st) == 0)
84      {
85          if ((st.st_mode & modeMask) != (S_IRUSR | S_IWUSR))
86          {
87              if (chmod(encryptKeyFileName, S_IRUSR | S_IWUSR) == -1)
88              {
89                  lg2::debug("Error setting chmod for ipmi_pass file");
90              }
91          }
92      }
93  }
94  
getPasswdByUserName(const std::string & userName)95  SecureString PasswdMgr::getPasswdByUserName(const std::string& userName)
96  {
97      checkAndReload();
98      auto iter = passwdMapList.find(userName);
99      if (iter == passwdMapList.end())
100      {
101          return SecureString();
102      }
103      return iter->second;
104  }
105  
updateUserEntry(const std::string & userName,const std::string & newUserName)106  int PasswdMgr::updateUserEntry(const std::string& userName,
107                                 const std::string& newUserName)
108  {
109      std::time_t updatedTime = getUpdatedFileTime();
110      // Check file time stamp to know passwdMapList is up-to-date.
111      // If not up-to-date, then updatePasswdSpecialFile will read and
112      // check the user entry existance.
113      if (fileLastUpdatedTime == updatedTime && updatedTime != -EIO)
114      {
115          if (passwdMapList.find(userName) == passwdMapList.end())
116          {
117              lg2::debug("User not found");
118              return 0;
119          }
120      }
121  
122      // Write passwdMap to Encryted file
123      if (updatePasswdSpecialFile(userName, newUserName) != 0)
124      {
125          lg2::debug("Passwd file update failed");
126          return -EIO;
127      }
128  
129      lg2::debug("Passwd file updated successfully");
130      return 0;
131  }
132  
checkAndReload(void)133  void PasswdMgr::checkAndReload(void)
134  {
135      std::time_t updatedTime = getUpdatedFileTime();
136      if (fileLastUpdatedTime != updatedTime && updatedTime != -1)
137      {
138          lg2::debug("Reloading password map list");
139          passwdMapList.clear();
140          initPasswordMap();
141      }
142  }
143  
encryptDecryptData(bool doEncrypt,const EVP_CIPHER * cipher,uint8_t * key,size_t keyLen,uint8_t * iv,size_t ivLen,uint8_t * inBytes,size_t inBytesLen,uint8_t * mac,size_t * macLen,unsigned char * outBytes,size_t * outBytesLen)144  int PasswdMgr::encryptDecryptData(
145      bool doEncrypt, const EVP_CIPHER* cipher, uint8_t* key, size_t keyLen,
146      uint8_t* iv, size_t ivLen, uint8_t* inBytes, size_t inBytesLen,
147      uint8_t* mac, size_t* macLen, unsigned char* outBytes, size_t* outBytesLen)
148  {
149      if (cipher == NULL || key == NULL || iv == NULL || inBytes == NULL ||
150          outBytes == NULL || mac == NULL || inBytesLen == 0 ||
151          (size_t)EVP_CIPHER_key_length(cipher) > keyLen ||
152          (size_t)EVP_CIPHER_iv_length(cipher) > ivLen)
153      {
154          lg2::debug("Error Invalid Inputs");
155          return -EINVAL;
156      }
157  
158      if (!doEncrypt)
159      {
160          // verify MAC before decrypting the data.
161          std::array<uint8_t, EVP_MAX_MD_SIZE> calMac;
162          size_t calMacLen = calMac.size();
163          // calculate MAC for the encrypted message.
164          if (NULL ==
165              HMAC(EVP_sha256(), key, keyLen, inBytes, inBytesLen, calMac.data(),
166                   reinterpret_cast<unsigned int*>(&calMacLen)))
167          {
168              lg2::debug("Error: Failed to calculate MAC");
169              return -EIO;
170          }
171          if (!((calMacLen == *macLen) &&
172                (std::memcmp(calMac.data(), mac, calMacLen) == 0)))
173          {
174              lg2::debug("Authenticated message doesn't match");
175              return -EBADMSG;
176          }
177      }
178  
179      std::unique_ptr<EVP_CIPHER_CTX, decltype(&::EVP_CIPHER_CTX_free)> ctx(
180          EVP_CIPHER_CTX_new(), ::EVP_CIPHER_CTX_free);
181  
182      if (!ctx)
183      {
184          lg2::debug("Error: EVP_CIPHER_CTX is NULL");
185          return -ENOMEM;
186      }
187  
188      EVP_CIPHER_CTX_set_padding(ctx.get(), 1);
189  
190      // Set key & IV
191      int retval = EVP_CipherInit_ex(ctx.get(), cipher, NULL, key, iv,
192                                     static_cast<int>(doEncrypt));
193      if (!retval)
194      {
195          lg2::debug("EVP_CipherInit_ex failed: {RET_VAL}", "RET_VAL", retval);
196          return -EIO;
197      }
198  
199      int outLen = 0, outEVPLen = 0;
200      if ((retval = EVP_CipherUpdate(ctx.get(), outBytes + outLen, &outEVPLen,
201                                     inBytes, inBytesLen)))
202      {
203          outLen += outEVPLen;
204          if ((retval =
205                   EVP_CipherFinal(ctx.get(), outBytes + outLen, &outEVPLen)))
206          {
207              outLen += outEVPLen;
208              *outBytesLen = outLen;
209          }
210          else
211          {
212              lg2::debug("EVP_CipherFinal fails: {RET_VAL}", "RET_VAL", retval);
213              return -EIO;
214          }
215      }
216      else
217      {
218          lg2::debug("EVP_CipherUpdate fails: {RET_VAL}", "RET_VAL", retval);
219          return -EIO;
220      }
221  
222      if (doEncrypt)
223      {
224          // Create MAC for the encrypted message
225          if (NULL == HMAC(EVP_sha256(), key, keyLen, outBytes, *outBytesLen, mac,
226                           reinterpret_cast<unsigned int*>(macLen)))
227          {
228              lg2::debug("Failed to create authentication");
229              return -EIO;
230          }
231      }
232      return 0;
233  }
234  
initPasswordMap(void)235  void PasswdMgr::initPasswordMap(void)
236  {
237      // TODO  phosphor-host-ipmid#170 phosphor::user::shadow::Lock lock{};
238      SecureString dataBuf;
239  
240      if (readPasswdFileData(dataBuf) != 0)
241      {
242          lg2::debug("Error in reading the encrypted pass file");
243          return;
244      }
245  
246      if (dataBuf.size() != 0)
247      {
248          // populate the user list with password
249          char* outPtr = dataBuf.data();
250          char* nToken = NULL;
251          char* linePtr = strtok_r(outPtr, "\n", &nToken);
252          size_t lineSize = 0;
253          while (linePtr != NULL)
254          {
255              size_t userEPos = 0;
256              SecureString lineStr(linePtr);
257              if ((userEPos = lineStr.find(":")) != std::string::npos)
258              {
259                  lineSize = lineStr.size();
260                  passwdMapList.emplace(
261                      lineStr.substr(0, userEPos),
262                      lineStr.substr(userEPos + 1, lineSize - (userEPos + 1)));
263              }
264              linePtr = strtok_r(NULL, "\n", &nToken);
265          }
266      }
267  
268      // Update the timestamp
269      fileLastUpdatedTime = getUpdatedFileTime();
270      return;
271  }
272  
readPasswdFileData(SecureString & outBytes)273  int PasswdMgr::readPasswdFileData(SecureString& outBytes)
274  {
275      std::array<uint8_t, maxKeySize> keyBuff;
276      std::ifstream keyFile(encryptKeyFileName, std::ios::in | std::ios::binary);
277      if (!keyFile.is_open())
278      {
279          lg2::debug("Error in opening encryption key file");
280          return -EIO;
281      }
282      keyFile.read(reinterpret_cast<char*>(keyBuff.data()), keyBuff.size());
283      if (keyFile.fail())
284      {
285          lg2::debug("Error in reading encryption key file");
286          return -EIO;
287      }
288  
289      std::ifstream passwdFile(passwdFileName, std::ios::in | std::ios::binary);
290      if (!passwdFile.is_open())
291      {
292          lg2::debug("Error in opening ipmi password file");
293          return -EIO;
294      }
295  
296      // calculate file size and read the data
297      passwdFile.seekg(0, std::ios::end);
298      ssize_t fileSize = passwdFile.tellg();
299      passwdFile.seekg(0, std::ios::beg);
300      std::vector<uint8_t> input(fileSize);
301      passwdFile.read(reinterpret_cast<char*>(input.data()), fileSize);
302      if (passwdFile.fail())
303      {
304          lg2::debug("Error in reading encryption key file");
305          return -EIO;
306      }
307  
308      // verify the signature first
309      MetaPassStruct* metaData = reinterpret_cast<MetaPassStruct*>(input.data());
310      if (std::strncmp(metaData->signature, META_PASSWD_SIG,
311                       sizeof(metaData->signature)))
312      {
313          lg2::debug("Error signature mismatch in password file");
314          return -EBADMSG;
315      }
316  
317      size_t inBytesLen = metaData->dataSize + metaData->padSize;
318      // If data is empty i.e no password map then return success
319      if (inBytesLen == 0)
320      {
321          lg2::debug("Empty password file");
322          return 0;
323      }
324  
325      // compute the key needed to decrypt
326      std::array<uint8_t, EVP_MAX_KEY_LENGTH> key;
327      auto keyLen = key.size();
328      if (NULL == HMAC(EVP_sha256(), keyBuff.data(), keyBuff.size(),
329                       input.data() + sizeof(*metaData), metaData->hashSize,
330                       key.data(), reinterpret_cast<unsigned int*>(&keyLen)))
331      {
332          lg2::debug("Failed to create MAC for authentication");
333          return -EIO;
334      }
335  
336      // decrypt the data
337      uint8_t* iv = input.data() + sizeof(*metaData) + metaData->hashSize;
338      size_t ivLen = metaData->ivSize;
339      uint8_t* inBytes = iv + ivLen;
340      uint8_t* mac = inBytes + inBytesLen;
341      size_t macLen = metaData->macSize;
342  
343      size_t outBytesLen = 0;
344      // Resize to actual data size
345      outBytes.resize(inBytesLen + EVP_MAX_BLOCK_LENGTH, '\0');
346      if (encryptDecryptData(false, EVP_aes_128_cbc(), key.data(), keyLen, iv,
347                             ivLen, inBytes, inBytesLen, mac, &macLen,
348                             reinterpret_cast<unsigned char*>(outBytes.data()),
349                             &outBytesLen) != 0)
350      {
351          lg2::debug("Error in decryption");
352          return -EIO;
353      }
354      // Resize the vector to outBytesLen
355      outBytes.resize(outBytesLen);
356  
357      OPENSSL_cleanse(key.data(), keyLen);
358      OPENSSL_cleanse(iv, ivLen);
359  
360      return 0;
361  }
362  
updatePasswdSpecialFile(const std::string & userName,const std::string & newUserName)363  int PasswdMgr::updatePasswdSpecialFile(const std::string& userName,
364                                         const std::string& newUserName)
365  {
366      // TODO  phosphor-host-ipmid#170 phosphor::user::shadow::Lock lock{};
367  
368      size_t bytesWritten = 0;
369      size_t inBytesLen = 0;
370      size_t isUsrFound = false;
371      const EVP_CIPHER* cipher = EVP_aes_128_cbc();
372      SecureString dataBuf;
373  
374      // Read the encrypted file and get the file data
375      // Check user existance and return if not exist.
376      if (readPasswdFileData(dataBuf) != 0)
377      {
378          lg2::debug("Error in reading the encrypted pass file");
379          return -EIO;
380      }
381  
382      if (dataBuf.size() != 0)
383      {
384          inBytesLen = dataBuf.size() + newUserName.size() +
385                       EVP_CIPHER_block_size(cipher);
386      }
387  
388      SecureString inBytes(inBytesLen, '\0');
389      if (inBytesLen != 0)
390      {
391          char* outPtr = reinterpret_cast<char*>(dataBuf.data());
392          char* nToken = NULL;
393          char* linePtr = strtok_r(outPtr, "\n", &nToken);
394          while (linePtr != NULL)
395          {
396              size_t userEPos = 0;
397  
398              SecureString lineStr(linePtr);
399              if ((userEPos = lineStr.find(":")) != std::string::npos)
400              {
401                  if (userName.compare(lineStr.substr(0, userEPos)) == 0)
402                  {
403                      isUsrFound = true;
404                      if (!newUserName.empty())
405                      {
406                          bytesWritten += std::snprintf(
407                              &inBytes[0] + bytesWritten,
408                              (inBytesLen - bytesWritten), "%s%s\n",
409                              newUserName.c_str(),
410                              lineStr.substr(userEPos, lineStr.size()).data());
411                      }
412                  }
413                  else
414                  {
415                      bytesWritten += std::snprintf(&inBytes[0] + bytesWritten,
416                                                    (inBytesLen - bytesWritten),
417                                                    "%s\n", lineStr.data());
418                  }
419              }
420              linePtr = strtok_r(NULL, "\n", &nToken);
421          }
422          inBytesLen = bytesWritten;
423      }
424      if (!isUsrFound)
425      {
426          lg2::debug("User doesn't exist");
427          return 0;
428      }
429  
430      // Read the key buff from key file
431      std::array<uint8_t, maxKeySize> keyBuff;
432      std::ifstream keyFile(encryptKeyFileName, std::ios::in | std::ios::binary);
433      if (!keyFile.good())
434      {
435          lg2::debug("Error in opening encryption key file");
436          return -EIO;
437      }
438      keyFile.read(reinterpret_cast<char*>(keyBuff.data()), keyBuff.size());
439      if (keyFile.fail())
440      {
441          lg2::debug("Error in reading encryption key file");
442          return -EIO;
443      }
444      keyFile.close();
445  
446      // Read the original passwd file mode
447      struct stat st = {};
448      if (stat(passwdFileName, &st) != 0)
449      {
450          lg2::debug("Error in getting password file fstat()");
451          return -EIO;
452      }
453  
454      // Create temporary file for write
455      std::string pwdFile(passwdFileName);
456      std::vector<char> tempFileName(pwdFile.begin(), pwdFile.end());
457      std::vector<char> fileTemplate = {'_', '_', 'X', 'X', 'X',
458                                        'X', 'X', 'X', '\0'};
459      tempFileName.insert(tempFileName.end(), fileTemplate.begin(),
460                          fileTemplate.end());
461      int fd = mkstemp((char*)tempFileName.data());
462      if (fd == -1)
463      {
464          lg2::debug("Error creating temp file");
465          return -EIO;
466      }
467  
468      std::string strTempFileName(tempFileName.data());
469      // Open the temp file for writing from provided fd
470      // By "true", remove it at exit if still there.
471      // This is needed to cleanup the temp file at exception
472      phosphor::user::File temp(fd, strTempFileName, "w", true);
473      if ((temp)() == NULL)
474      {
475          close(fd);
476          lg2::debug("Error creating temp file");
477          return -EIO;
478      }
479  
480      // Set the file mode as read-write for owner only
481      if (fchmod(fileno((temp)()), S_IRUSR | S_IWUSR) < 0)
482      {
483          lg2::debug("Error setting fchmod for temp file");
484          return -EIO;
485      }
486  
487      const EVP_MD* digest = EVP_sha256();
488      size_t hashLen = EVP_MD_block_size(digest);
489      std::vector<uint8_t> hash(hashLen);
490      size_t ivLen = EVP_CIPHER_iv_length(cipher);
491      std::vector<uint8_t> iv(ivLen);
492      std::array<uint8_t, EVP_MAX_KEY_LENGTH> key;
493      size_t keyLen = key.size();
494      std::array<uint8_t, EVP_MAX_MD_SIZE> mac;
495      size_t macLen = mac.size();
496  
497      // Create random hash and generate hash key which will be used for
498      // encryption.
499      if (RAND_bytes(hash.data(), hashLen) != 1)
500      {
501          lg2::debug("Hash genertion failed, bailing out");
502          return -EIO;
503      }
504      if (NULL ==
505          HMAC(digest, keyBuff.data(), keyBuff.size(), hash.data(), hashLen,
506               key.data(), reinterpret_cast<unsigned int*>(&keyLen)))
507      {
508          lg2::debug("Failed to create MAC for authentication");
509          return -EIO;
510      }
511  
512      // Generate IV values
513      if (RAND_bytes(iv.data(), ivLen) != 1)
514      {
515          lg2::debug("UV genertion failed, bailing out");
516          return -EIO;
517      }
518  
519      // Encrypt the input data
520      std::vector<uint8_t> outBytes(inBytesLen + EVP_MAX_BLOCK_LENGTH);
521      size_t outBytesLen = 0;
522      if (inBytesLen != 0)
523      {
524          if (encryptDecryptData(
525                  true, EVP_aes_128_cbc(), key.data(), keyLen, iv.data(), ivLen,
526                  reinterpret_cast<unsigned char*>(inBytes.data()), inBytesLen,
527                  mac.data(), &macLen, outBytes.data(), &outBytesLen) != 0)
528          {
529              lg2::debug("Error while encrypting the data");
530              return -EIO;
531          }
532          outBytes[outBytesLen] = 0;
533      }
534      OPENSSL_cleanse(key.data(), keyLen);
535  
536      // Update the meta password structure.
537      MetaPassStruct metaData = {META_PASSWD_SIG, {0, 0}, 0, 0, 0, 0, 0};
538      metaData.hashSize = hashLen;
539      metaData.ivSize = ivLen;
540      metaData.dataSize = bytesWritten;
541      metaData.padSize = outBytesLen - bytesWritten;
542      metaData.macSize = macLen;
543  
544      if (fwrite(&metaData, 1, sizeof(metaData), (temp)()) != sizeof(metaData))
545      {
546          lg2::debug("Error in writing meta data");
547          return -EIO;
548      }
549  
550      if (fwrite(&hash[0], 1, hashLen, (temp)()) != hashLen)
551      {
552          lg2::debug("Error in writing hash data");
553          return -EIO;
554      }
555  
556      if (fwrite(&iv[0], 1, ivLen, (temp)()) != ivLen)
557      {
558          lg2::debug("Error in writing IV data");
559          return -EIO;
560      }
561  
562      if (fwrite(&outBytes[0], 1, outBytesLen, (temp)()) != outBytesLen)
563      {
564          lg2::debug("Error in writing encrypted data");
565          return -EIO;
566      }
567  
568      if (fwrite(&mac[0], 1, macLen, (temp)()) != macLen)
569      {
570          lg2::debug("Error in writing MAC data");
571          return -EIO;
572      }
573  
574      if (fflush((temp)()))
575      {
576          lg2::debug("File fflush error while writing entries to special file");
577          return -EIO;
578      }
579  
580      OPENSSL_cleanse(iv.data(), ivLen);
581  
582      // Rename the tmp  file to actual file
583      if (std::rename(strTempFileName.data(), passwdFileName) != 0)
584      {
585          lg2::debug("Failed to rename tmp file to ipmi-pass");
586          return -EIO;
587      }
588  
589      return 0;
590  }
591  
getUpdatedFileTime()592  std::time_t PasswdMgr::getUpdatedFileTime()
593  {
594      struct stat fileStat = {};
595      if (stat(passwdFileName, &fileStat) != 0)
596      {
597          lg2::debug("Error - Getting passwd file time stamp");
598          return -EIO;
599      }
600      return fileStat.st_mtime;
601  }
602  
603  } // namespace ipmi
604