/* // Copyright (c) 2018 Intel Corporation // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. */ #include "passwd_mgr.hpp" #include "file.hpp" #include "shadowlock.hpp" #include #include #include #include #include #include #include #include #include #include #include namespace ipmi { static const char* passwdFileName = "/etc/ipmi_pass"; static const char* encryptKeyFileName = "/etc/key_file"; static const size_t maxKeySize = 8; constexpr mode_t modeMask = (S_ISUID | S_ISGID | S_ISVTX | S_IRWXU | S_IRWXG | S_IRWXO); #define META_PASSWD_SIG "=OPENBMC=" /* * Meta data struct for encrypted password file */ struct MetaPassStruct { char signature[10]; unsigned char reseved[2]; size_t hashSize; size_t ivSize; size_t dataSize; size_t padSize; size_t macSize; }; using namespace phosphor::logging; PasswdMgr::PasswdMgr() { restrictFilesPermission(); initPasswordMap(); } void PasswdMgr::restrictFilesPermission(void) { struct stat st = {}; // Restrict file permission to owner read & write if (stat(passwdFileName, &st) == 0) { if ((st.st_mode & modeMask) != (S_IRUSR | S_IWUSR)) { if (chmod(passwdFileName, S_IRUSR | S_IWUSR) == -1) { log("Error setting chmod for ipmi_pass file"); } } } if (stat(encryptKeyFileName, &st) == 0) { if ((st.st_mode & modeMask) != (S_IRUSR | S_IWUSR)) { if (chmod(encryptKeyFileName, S_IRUSR | S_IWUSR) == -1) { log("Error setting chmod for ipmi_pass file"); } } } } SecureString PasswdMgr::getPasswdByUserName(const std::string& userName) { checkAndReload(); auto iter = passwdMapList.find(userName); if (iter == passwdMapList.end()) { return SecureString(); } return iter->second; } int PasswdMgr::updateUserEntry(const std::string& userName, const std::string& newUserName) { std::time_t updatedTime = getUpdatedFileTime(); // Check file time stamp to know passwdMapList is up-to-date. // If not up-to-date, then updatePasswdSpecialFile will read and // check the user entry existance. if (fileLastUpdatedTime == updatedTime && updatedTime != -EIO) { if (passwdMapList.find(userName) == passwdMapList.end()) { log("User not found"); return 0; } } // Write passwdMap to Encryted file if (updatePasswdSpecialFile(userName, newUserName) != 0) { log("Passwd file update failed"); return -EIO; } log("Passwd file updated successfully"); return 0; } void PasswdMgr::checkAndReload(void) { std::time_t updatedTime = getUpdatedFileTime(); if (fileLastUpdatedTime != updatedTime && updatedTime != -1) { log("Reloading password map list"); passwdMapList.clear(); initPasswordMap(); } } int PasswdMgr::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) { if (cipher == NULL || key == NULL || iv == NULL || inBytes == NULL || outBytes == NULL || mac == NULL || inBytesLen == 0 || (size_t)EVP_CIPHER_key_length(cipher) > keyLen || (size_t)EVP_CIPHER_iv_length(cipher) > ivLen) { log("Error Invalid Inputs"); return -EINVAL; } if (!doEncrypt) { // verify MAC before decrypting the data. std::array calMac; size_t calMacLen = calMac.size(); // calculate MAC for the encrypted message. if (NULL == HMAC(EVP_sha256(), key, keyLen, inBytes, inBytesLen, calMac.data(), reinterpret_cast(&calMacLen))) { log("Error: Failed to calculate MAC"); return -EIO; } if (!((calMacLen == *macLen) && (std::memcmp(calMac.data(), mac, calMacLen) == 0))) { log("Authenticated message doesn't match"); return -EBADMSG; } } std::unique_ptr ctx( EVP_CIPHER_CTX_new(), ::EVP_CIPHER_CTX_free); if (!ctx) { log("Error: EVP_CIPHER_CTX is NULL"); return -ENOMEM; } EVP_CIPHER_CTX_set_padding(ctx.get(), 1); // Set key & IV int retval = EVP_CipherInit_ex(ctx.get(), cipher, NULL, key, iv, static_cast(doEncrypt)); if (!retval) { log("EVP_CipherInit_ex failed", entry("RET_VAL=%d", retval)); return -EIO; } int outLen = 0, outEVPLen = 0; if ((retval = EVP_CipherUpdate(ctx.get(), outBytes + outLen, &outEVPLen, inBytes, inBytesLen))) { outLen += outEVPLen; if ((retval = EVP_CipherFinal(ctx.get(), outBytes + outLen, &outEVPLen))) { outLen += outEVPLen; *outBytesLen = outLen; } else { log("EVP_CipherFinal fails", entry("RET_VAL=%d", retval)); return -EIO; } } else { log("EVP_CipherUpdate fails", entry("RET_VAL=%d", retval)); return -EIO; } if (doEncrypt) { // Create MAC for the encrypted message if (NULL == HMAC(EVP_sha256(), key, keyLen, outBytes, *outBytesLen, mac, reinterpret_cast(macLen))) { log("Failed to create authentication"); return -EIO; } } return 0; } void PasswdMgr::initPasswordMap(void) { // TODO phosphor-host-ipmid#170 phosphor::user::shadow::Lock lock{}; SecureString dataBuf; if (readPasswdFileData(dataBuf) != 0) { log("Error in reading the encrypted pass file"); return; } if (dataBuf.size() != 0) { // populate the user list with password char* outPtr = dataBuf.data(); char* nToken = NULL; char* linePtr = strtok_r(outPtr, "\n", &nToken); size_t lineSize = 0; while (linePtr != NULL) { size_t userEPos = 0; SecureString lineStr(linePtr); if ((userEPos = lineStr.find(":")) != std::string::npos) { lineSize = lineStr.size(); passwdMapList.emplace( lineStr.substr(0, userEPos), lineStr.substr(userEPos + 1, lineSize - (userEPos + 1))); } linePtr = strtok_r(NULL, "\n", &nToken); } } // Update the timestamp fileLastUpdatedTime = getUpdatedFileTime(); return; } int PasswdMgr::readPasswdFileData(SecureString& outBytes) { std::array keyBuff; std::ifstream keyFile(encryptKeyFileName, std::ios::in | std::ios::binary); if (!keyFile.is_open()) { log("Error in opening encryption key file"); return -EIO; } keyFile.read(reinterpret_cast(keyBuff.data()), keyBuff.size()); if (keyFile.fail()) { log("Error in reading encryption key file"); return -EIO; } std::ifstream passwdFile(passwdFileName, std::ios::in | std::ios::binary); if (!passwdFile.is_open()) { log("Error in opening ipmi password file"); return -EIO; } // calculate file size and read the data passwdFile.seekg(0, std::ios::end); ssize_t fileSize = passwdFile.tellg(); passwdFile.seekg(0, std::ios::beg); std::vector input(fileSize); passwdFile.read(reinterpret_cast(input.data()), fileSize); if (passwdFile.fail()) { log("Error in reading encryption key file"); return -EIO; } // verify the signature first MetaPassStruct* metaData = reinterpret_cast(input.data()); if (std::strncmp(metaData->signature, META_PASSWD_SIG, sizeof(metaData->signature))) { log("Error signature mismatch in password file"); return -EBADMSG; } size_t inBytesLen = metaData->dataSize + metaData->padSize; // If data is empty i.e no password map then return success if (inBytesLen == 0) { log("Empty password file"); return 0; } // compute the key needed to decrypt std::array key; auto keyLen = key.size(); if (NULL == HMAC(EVP_sha256(), keyBuff.data(), keyBuff.size(), input.data() + sizeof(*metaData), metaData->hashSize, key.data(), reinterpret_cast(&keyLen))) { log("Failed to create MAC for authentication"); return -EIO; } // decrypt the data uint8_t* iv = input.data() + sizeof(*metaData) + metaData->hashSize; size_t ivLen = metaData->ivSize; uint8_t* inBytes = iv + ivLen; uint8_t* mac = inBytes + inBytesLen; size_t macLen = metaData->macSize; size_t outBytesLen = 0; // Resize to actual data size outBytes.resize(inBytesLen + EVP_MAX_BLOCK_LENGTH, '\0'); if (encryptDecryptData(false, EVP_aes_128_cbc(), key.data(), keyLen, iv, ivLen, inBytes, inBytesLen, mac, &macLen, reinterpret_cast(outBytes.data()), &outBytesLen) != 0) { log("Error in decryption"); return -EIO; } // Resize the vector to outBytesLen outBytes.resize(outBytesLen); OPENSSL_cleanse(key.data(), keyLen); OPENSSL_cleanse(iv, ivLen); return 0; } int PasswdMgr::updatePasswdSpecialFile(const std::string& userName, const std::string& newUserName) { // TODO phosphor-host-ipmid#170 phosphor::user::shadow::Lock lock{}; size_t bytesWritten = 0; size_t inBytesLen = 0; size_t isUsrFound = false; const EVP_CIPHER* cipher = EVP_aes_128_cbc(); SecureString dataBuf; // Read the encrypted file and get the file data // Check user existance and return if not exist. if (readPasswdFileData(dataBuf) != 0) { log("Error in reading the encrypted pass file"); return -EIO; } if (dataBuf.size() != 0) { inBytesLen = dataBuf.size() + newUserName.size() + EVP_CIPHER_block_size(cipher); } SecureString inBytes(inBytesLen, '\0'); if (inBytesLen != 0) { char* outPtr = reinterpret_cast(dataBuf.data()); char* nToken = NULL; char* linePtr = strtok_r(outPtr, "\n", &nToken); while (linePtr != NULL) { size_t userEPos = 0; SecureString lineStr(linePtr); if ((userEPos = lineStr.find(":")) != std::string::npos) { if (userName.compare(lineStr.substr(0, userEPos)) == 0) { isUsrFound = true; if (!newUserName.empty()) { bytesWritten += std::snprintf( &inBytes[0] + bytesWritten, (inBytesLen - bytesWritten), "%s%s\n", newUserName.c_str(), lineStr.substr(userEPos, lineStr.size()).data()); } } else { bytesWritten += std::snprintf(&inBytes[0] + bytesWritten, (inBytesLen - bytesWritten), "%s\n", lineStr.data()); } } linePtr = strtok_r(NULL, "\n", &nToken); } inBytesLen = bytesWritten; } if (!isUsrFound) { log("User doesn't exist"); return 0; } // Read the key buff from key file std::array keyBuff; std::ifstream keyFile(encryptKeyFileName, std::ios::in | std::ios::binary); if (!keyFile.good()) { log("Error in opening encryption key file"); return -EIO; } keyFile.read(reinterpret_cast(keyBuff.data()), keyBuff.size()); if (keyFile.fail()) { log("Error in reading encryption key file"); return -EIO; } keyFile.close(); // Read the original passwd file mode struct stat st = {}; if (stat(passwdFileName, &st) != 0) { log("Error in getting password file fstat()"); return -EIO; } // Create temporary file for write std::string pwdFile(passwdFileName); std::vector tempFileName(pwdFile.begin(), pwdFile.end()); std::vector fileTemplate = {'_', '_', 'X', 'X', 'X', 'X', 'X', 'X', '\0'}; tempFileName.insert(tempFileName.end(), fileTemplate.begin(), fileTemplate.end()); int fd = mkstemp((char*)tempFileName.data()); if (fd == -1) { log("Error creating temp file"); return -EIO; } std::string strTempFileName(tempFileName.data()); // Open the temp file for writing from provided fd // By "true", remove it at exit if still there. // This is needed to cleanup the temp file at exception phosphor::user::File temp(fd, strTempFileName, "w", true); if ((temp)() == NULL) { close(fd); log("Error creating temp file"); return -EIO; } // Set the file mode as read-write for owner only if (fchmod(fileno((temp)()), S_IRUSR | S_IWUSR) < 0) { log("Error setting fchmod for temp file"); return -EIO; } const EVP_MD* digest = EVP_sha256(); size_t hashLen = EVP_MD_block_size(digest); std::vector hash(hashLen); size_t ivLen = EVP_CIPHER_iv_length(cipher); std::vector iv(ivLen); std::array key; size_t keyLen = key.size(); std::array mac; size_t macLen = mac.size(); // Create random hash and generate hash key which will be used for // encryption. if (RAND_bytes(hash.data(), hashLen) != 1) { log("Hash genertion failed, bailing out"); return -EIO; } if (NULL == HMAC(digest, keyBuff.data(), keyBuff.size(), hash.data(), hashLen, key.data(), reinterpret_cast(&keyLen))) { log("Failed to create MAC for authentication"); return -EIO; } // Generate IV values if (RAND_bytes(iv.data(), ivLen) != 1) { log("UV genertion failed, bailing out"); return -EIO; } // Encrypt the input data std::vector outBytes(inBytesLen + EVP_MAX_BLOCK_LENGTH); size_t outBytesLen = 0; if (inBytesLen != 0) { if (encryptDecryptData( true, EVP_aes_128_cbc(), key.data(), keyLen, iv.data(), ivLen, reinterpret_cast(inBytes.data()), inBytesLen, mac.data(), &macLen, outBytes.data(), &outBytesLen) != 0) { log("Error while encrypting the data"); return -EIO; } outBytes[outBytesLen] = 0; } OPENSSL_cleanse(key.data(), keyLen); // Update the meta password structure. MetaPassStruct metaData = {META_PASSWD_SIG, {0, 0}, 0, 0, 0, 0, 0}; metaData.hashSize = hashLen; metaData.ivSize = ivLen; metaData.dataSize = bytesWritten; metaData.padSize = outBytesLen - bytesWritten; metaData.macSize = macLen; if (fwrite(&metaData, 1, sizeof(metaData), (temp)()) != sizeof(metaData)) { log("Error in writing meta data"); return -EIO; } if (fwrite(&hash[0], 1, hashLen, (temp)()) != hashLen) { log("Error in writing hash data"); return -EIO; } if (fwrite(&iv[0], 1, ivLen, (temp)()) != ivLen) { log("Error in writing IV data"); return -EIO; } if (fwrite(&outBytes[0], 1, outBytesLen, (temp)()) != outBytesLen) { log("Error in writing encrypted data"); return -EIO; } if (fwrite(&mac[0], 1, macLen, (temp)()) != macLen) { log("Error in writing MAC data"); return -EIO; } if (fflush((temp)())) { log( "File fflush error while writing entries to special file"); return -EIO; } OPENSSL_cleanse(iv.data(), ivLen); // Rename the tmp file to actual file if (std::rename(strTempFileName.data(), passwdFileName) != 0) { log("Failed to rename tmp file to ipmi-pass"); return -EIO; } return 0; } std::time_t PasswdMgr::getUpdatedFileTime() { struct stat fileStat = {}; if (stat(passwdFileName, &fileStat) != 0) { log("Error - Getting passwd file time stamp"); return -EIO; } return fileStat.st_mtime; } } // namespace ipmi