Commit message: Use more restrictive permissions on /etc/ipmi-pass

This forces the permissions on /etc/ipmi-pass to be 0600 or RW only by
owner. This is to prevent non-owners from reading the file, even though
it is obfuscated to make it harder for ipmi passwords to leak.

Tested: change ipmi passwords and see that the /etc/ipmi-pass file has
        0600 permissions.

Change-Id: I4be0b8a65f98ced031493f7767879eb054e1ee84
Signed-off-by: Vernon Mauery <vernon.mauery@linux.intel.com>
CVE: CVE-2020-14156
CWE: CWE-276
Vulnerability description: user_channel/passwd_mgr.cpp in OpenBMC phosphor-host-ipmid before 2020-04-03 does not ensure that /etc/ipmi-pass has strong file permissions.

Project: phosphor-host-ipmid

int PasswdMgr::updatePasswdSpecialFile(const std::string& userName,
                                       const std::string& newUserName)
{
    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();
    std::vector<uint8_t> dataBuf;

    // Read the encrypted file and get the file data
    // Check user existance and return if not exist.
    if (readPasswdFileData(dataBuf) != 0)
    {
        log<level::DEBUG>("Error in reading the encrypted pass file");
        return -EIO;
    }

    if (dataBuf.size() != 0)
    {
        inBytesLen =
            dataBuf.size() + newUserName.size() + EVP_CIPHER_block_size(cipher);
    }

    std::vector<uint8_t> inBytes(inBytesLen);
    if (inBytesLen != 0)
    {
        char* outPtr = reinterpret_cast<char*>(dataBuf.data());
        char* nToken = NULL;
        char* linePtr = strtok_r(outPtr, "\n", &nToken);
        while (linePtr != NULL)
        {
            size_t userEPos = 0;

            std::string 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(
                            reinterpret_cast<char*>(&inBytes[0]) + bytesWritten,
                            (inBytesLen - bytesWritten), "%s%s\n",
                            newUserName.c_str(),
                            lineStr.substr(userEPos, lineStr.size()).data());
                    }
                }
                else
                {
                    bytesWritten += std::snprintf(
                        reinterpret_cast<char*>(&inBytes[0]) + bytesWritten,
                        (inBytesLen - bytesWritten), "%s\n", lineStr.data());
                }
            }
            linePtr = strtok_r(NULL, "\n", &nToken);
        }
        inBytesLen = bytesWritten;
    }
    if (!isUsrFound)
    {
        log<level::DEBUG>("User doesn't exist");
        return 0;
    }

    // Read the key buff from key file
    std::array<uint8_t, maxKeySize> keyBuff;
    std::ifstream keyFile(encryptKeyFileName, std::ios::in | std::ios::binary);
    if (!keyFile.good())
    {
        log<level::DEBUG>("Error in opening encryption key file");
        return -EIO;
    }
    keyFile.read(reinterpret_cast<char*>(keyBuff.data()), keyBuff.size());
    if (keyFile.fail())
    {
        log<level::DEBUG>("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<level::DEBUG>("Error in getting password file fstat()");
        return -EIO;
    }

    // Create temporary file for write
    std::string pwdFile(passwdFileName);
    std::vector<char> tempFileName(pwdFile.begin(), pwdFile.end());
    std::vector<char> 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<level::DEBUG>("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<level::DEBUG>("Error creating temp file");
        return -EIO;
    }

    // Set the file mode as of actual ipmi-pass file.
    if (fchmod(fileno((temp)()), st.st_mode) < 0)
    {
        log<level::DEBUG>("Error setting fchmod for temp file");
        return -EIO;
    }

    const EVP_MD* digest = EVP_sha256();
    size_t hashLen = EVP_MD_block_size(digest);
    std::vector<uint8_t> hash(hashLen);
    size_t ivLen = EVP_CIPHER_iv_length(cipher);
    std::vector<uint8_t> iv(ivLen);
    std::array<uint8_t, EVP_MAX_KEY_LENGTH> key;
    size_t keyLen = key.size();
    std::array<uint8_t, EVP_MAX_MD_SIZE> 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<level::DEBUG>("Hash genertion failed, bailing out");
        return -EIO;
    }
    if (NULL == HMAC(digest, keyBuff.data(), keyBuff.size(), hash.data(),
                     hashLen, key.data(),
                     reinterpret_cast<unsigned int*>(&keyLen)))
    {
        log<level::DEBUG>("Failed to create MAC for authentication");
        return -EIO;
    }

    // Generate IV values
    if (RAND_bytes(iv.data(), ivLen) != 1)
    {
        log<level::DEBUG>("UV genertion failed, bailing out");
        return -EIO;
    }

    // Encrypt the input data
    std::vector<uint8_t> 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, inBytes.data(), inBytesLen,
                               mac.data(), &macLen, outBytes.data(),
                               &outBytesLen) != 0)
        {
            log<level::DEBUG>("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<level::DEBUG>("Error in writing meta data");
        return -EIO;
    }

    if (fwrite(&hash[0], 1, hashLen, (temp)()) != hashLen)
    {
        log<level::DEBUG>("Error in writing hash data");
        return -EIO;
    }

    if (fwrite(&iv[0], 1, ivLen, (temp)()) != ivLen)
    {
        log<level::DEBUG>("Error in writing IV data");
        return -EIO;
    }

    if (fwrite(&outBytes[0], 1, outBytesLen, (temp)()) != outBytesLen)
    {
        log<level::DEBUG>("Error in writing encrypted data");
        return -EIO;
    }

    if (fwrite(&mac[0], 1, macLen, (temp)()) != macLen)
    {
        log<level::DEBUG>("Error in writing MAC data");
        return -EIO;
    }

    if (fflush((temp)()))
    {
        log<level::DEBUG>(
            "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<level::DEBUG>("Failed to rename tmp file to ipmi-pass");
        return -EIO;
    }

    return 0;
}