diff options
-rw-r--r-- | Makefile.am | 16 | ||||
-rw-r--r-- | configure.ac | 1 | ||||
-rw-r--r-- | user_channel/passwd_mgr.cpp | 261 | ||||
-rw-r--r-- | user_channel/passwd_mgr.hpp | 86 | ||||
-rw-r--r-- | user_channel/shadowlock.hpp | 50 | ||||
-rw-r--r-- | user_channel/user_layer.cpp | 32 | ||||
-rw-r--r-- | user_channel/user_layer.hpp | 33 |
7 files changed, 478 insertions, 1 deletions
diff --git a/Makefile.am b/Makefile.am index a5dfdab..7be3bc9 100644 --- a/Makefile.am +++ b/Makefile.am @@ -39,6 +39,7 @@ ipmid_LDFLAGS = \ $(LIBADD_DLOPEN) \ $(PHOSPHOR_LOGGING_LIBS) \ $(PHOSPHOR_DBUS_INTERFACES_LIBS) \ + $(CRYPTO_LIBS) \ -lstdc++fs \ -pthread \ -export-dynamic @@ -61,8 +62,20 @@ fru-read-gen.cpp: channel-gen.cpp: $(AM_V_GEN)@CHANNELGEN@ -o $(top_builddir) generate-cpp +libuserlayerdir = ${libdir} +libuserlayer_LTLIBRARIES = libuserlayer.la +libuserlayer_la_SOURCES = \ + user_channel/user_layer.cpp \ + user_channel/passwd_mgr.cpp + +libuserlayer_la_LDFLAGS = $(SYSTEMD_LIBS) $(libmapper_LIBS) \ + $(PHOSPHOR_LOGGING_LIBS) $(PHOSPHOR_DBUS_INTERFACES_LIBS) -lstdc++fs \ + $(LIBPAM) $(LIBCRYPT) -lpam_misc -lssl -version-info 0:0:0 -shared +libuserlayer_la_CXXFLAGS = $(SYSTEMD_CFLAGS) $(libmapper_CFLAGS) \ + $(PHOSPHOR_LOGGING_CFLAGS) $(PHOSPHOR_DBUS_INTERFACES_CFLAGS) libipmi20dir = ${libdir}/ipmid-providers libipmi20_LTLIBRARIES = libipmi20.la +libipmi20_la_DEPENDENCIES = libuserlayer.la libipmi20_la_SOURCES = \ net.cpp \ app/channel.cpp \ @@ -95,7 +108,7 @@ TESTS = $(check_PROGRAMS) libipmi20_la_LDFLAGS = $(SYSTEMD_LIBS) $(libmapper_LIBS) \ $(PHOSPHOR_LOGGING_LIBS) $(PHOSPHOR_DBUS_INTERFACES_LIBS) -lstdc++fs \ - -version-info 0:0:0 -shared + -luserlayer -version-info 0:0:0 -shared libipmi20_la_CXXFLAGS = $(SYSTEMD_CFLAGS) $(libmapper_CFLAGS) \ $(BOOST_CXX) $(PHOSPHOR_LOGGING_CFLAGS) \ $(PHOSPHOR_DBUS_INTERFACES_CFLAGS) \ @@ -122,6 +135,7 @@ libsysintfcmds_la_CXXFLAGS = $(SYSTEMD_CFLAGS) \ nobase_include_HEADERS = \ host-ipmid/iana.hpp \ + user_channel/user_layer.hpp \ host-ipmid/ipmid-api.h \ host-ipmid/ipmid-host-cmd.hpp \ host-ipmid/ipmid-host-cmd-utils.hpp \ diff --git a/configure.ac b/configure.ac index 5cbdfc6..78af3df 100644 --- a/configure.ac +++ b/configure.ac @@ -25,6 +25,7 @@ AS_IF([test "x$enable_softoff" != "xno"], # Checks for libraries. AC_CHECK_LIB([mapper], [mapper_get_service], ,[AC_MSG_ERROR([Could not find libmapper...openbmc/phosphor-objmgr package required])]) PKG_CHECK_MODULES([SYSTEMD], [libsystemd >= 221], [], [AC_MSG_ERROR(["systemd required and not found"])]) +PKG_CHECK_MODULES([CRYPTO], [libcrypto >= 1.0.2g], ,[AC_MSG_ERROR([can't find openssl libcrypto])]) PKG_CHECK_MODULES([PHOSPHOR_LOGGING], [phosphor-logging],, [AC_MSG_ERROR([Could not find phosphor-logging...openbmc/phosphor-logging package required])]) PKG_CHECK_MODULES([PHOSPHOR_DBUS_INTERFACES], [phosphor-dbus-interfaces],, [AC_MSG_ERROR([Could not find phosphor-dbus-interfaces...openbmc/phosphor-dbus-interfaces package required])]) PKG_CHECK_MODULES([SDBUSPLUS], [sdbusplus],,\ diff --git a/user_channel/passwd_mgr.cpp b/user_channel/passwd_mgr.cpp new file mode 100644 index 0000000..1d8a1ce --- /dev/null +++ b/user_channel/passwd_mgr.cpp @@ -0,0 +1,261 @@ +/* +// 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 "shadowlock.hpp" + +#include <openssl/hmac.h> +#include <openssl/sha.h> +#include <string.h> +#include <sys/stat.h> + +#include <cstring> +#include <fstream> +#include <phosphor-logging/log.hpp> + +namespace ipmi +{ + +static const char* passwdFileName = "/etc/ipmi_pass"; +static const char* encryptKeyFileName = "/etc/key_file"; +static const size_t maxKeySize = 8; + +static const char* 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() +{ + initPasswordMap(); +} + +std::string PasswdMgr::getPasswdByUserName(const std::string& userName) +{ + checkAndReload(); + auto iter = passwdMapList.find(userName); + if (iter == passwdMapList.end()) + { + return std::string(); + } + return iter->second; +} + +void PasswdMgr::checkAndReload(void) +{ + struct stat fileStat = {}; + if (stat(passwdFileName, &fileStat) != 0) + { + log<level::DEBUG>("Error in getting last updated time stamp"); + return; + } + std::time_t updatedTime = fileStat.st_mtime; + if (fileLastUpdatedTime != updatedTime) + { + log<level::DEBUG>("Reloading password map list"); + passwdMapList.clear(); + initPasswordMap(); + } +} + +int PasswdMgr::decrypt(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, + uint8_t* 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<level::DEBUG>("Error Invalid Inputs"); + return -1; + } + + std::array<uint8_t, EVP_MAX_MD_SIZE> 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<unsigned int*>(&calMacLen))) + { + log<level::DEBUG>("Error: Failed to calculate MAC"); + return -1; + } + if (!((calMacLen == macLen) && + (std::memcmp(calMac.data(), mac, calMacLen) == 0))) + { + log<level::DEBUG>("Authenticated message doesn't match"); + return -1; + } + + std::unique_ptr<EVP_CIPHER_CTX, decltype(&::EVP_CIPHER_CTX_free)> ctx( + EVP_CIPHER_CTX_new(), ::EVP_CIPHER_CTX_free); + EVP_CIPHER_CTX_set_padding(ctx.get(), 1); + + // Set key & IV to decrypt + int retval = EVP_CipherInit_ex(ctx.get(), cipher, NULL, key, iv, 0); + if (!retval) + { + log<level::DEBUG>("EVP_CipherInit_ex failed", + entry("RET_VAL=%d", retval)); + return -1; + } + + 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<level::DEBUG>("EVP_CipherFinal fails", + entry("RET_VAL=%d", retval)); + return -1; + } + } + else + { + log<level::DEBUG>("EVP_CipherUpdate fails", + entry("RET_VAL=%d", retval)); + return -1; + } + return 0; +} + +void PasswdMgr::initPasswordMap(void) +{ + phosphor::user::shadow::Lock lock(); + + std::array<uint8_t, maxKeySize> keyBuff; + std::ifstream keyFile(encryptKeyFileName, std::ios::in | std::ios::binary); + if (!keyFile.is_open()) + { + log<level::DEBUG>("Error in opening encryption key file"); + return; + } + keyFile.read((char*)keyBuff.data(), keyBuff.size()); + if (keyFile.fail()) + { + log<level::DEBUG>("Error in reading encryption key file"); + return; + } + + std::ifstream passwdFile(passwdFileName, std::ios::in | std::ios::binary); + if (!passwdFile.is_open()) + { + log<level::DEBUG>("Error in opening ipmi password file"); + return; + } + + // calculate file size and read the data + std::vector<uint8_t> input; + passwdFile.seekg(0, std::ios::end); + ssize_t fileSize = passwdFile.tellg(); + passwdFile.seekg(0, std::ios::beg); + input.resize(fileSize); + passwdFile.read((char*)input.data(), fileSize); + if (passwdFile.fail()) + { + log<level::DEBUG>("Error in reading encryption key file"); + return; + } + + // verify the signature first + metaPassStruct* metaData = reinterpret_cast<metaPassStruct*>(input.data()); + if (std::strncmp(metaData->signature, META_PASSWD_SIG, + sizeof(metaData->signature))) + { + log<level::DEBUG>("Error signature mismatch in password file"); + return; + } + + // compute the key needed to decrypt + std::array<uint8_t, EVP_MAX_KEY_LENGTH> key; + auto keyLen = key.size(); + HMAC(EVP_sha256(), keyBuff.data(), keyBuff.size(), + input.data() + sizeof(*metaData), metaData->hashSize, key.data(), + reinterpret_cast<unsigned int*>(&keyLen)); + + // decrypt the data + uint8_t* iv = input.data() + sizeof(*metaData) + metaData->hashSize; + size_t ivLen = metaData->ivSize; + uint8_t* inBytes = iv + ivLen; + size_t inBytesLen = metaData->dataSize + metaData->padSize; + uint8_t* mac = inBytes + inBytesLen; + size_t macLen = metaData->macSize; + std::vector<uint8_t> outBytes(inBytesLen + EVP_MAX_BLOCK_LENGTH); + size_t outBytesLen = outBytes.size(); + if (decrypt(EVP_aes_128_cbc(), key.data(), keyLen, iv, ivLen, inBytes, + inBytesLen, mac, macLen, outBytes.data(), &outBytesLen) != 0) + { + log<level::DEBUG>("Error in decryption"); + return; + } + outBytes[outBytesLen] = 0; + OPENSSL_cleanse(key.data(), keyLen); + OPENSSL_cleanse(iv, ivLen); + + // populate the user list with password + char* outPtr = reinterpret_cast<char*>(outBytes.data()); + char* nToken = NULL; + char* linePtr = strtok_r(outPtr, "\n", &nToken); + size_t userEPos = 0, lineSize = 0; + while (linePtr != NULL) + { + std::string 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 + struct stat fileStat = {}; + if (stat(passwdFileName, &fileStat) != 0) + { + log<level::DEBUG>("Error in getting last updated time stamp"); + return; + } + fileLastUpdatedTime = fileStat.st_mtime; + return; +} + +} // namespace ipmi diff --git a/user_channel/passwd_mgr.hpp b/user_channel/passwd_mgr.hpp new file mode 100644 index 0000000..3078e21 --- /dev/null +++ b/user_channel/passwd_mgr.hpp @@ -0,0 +1,86 @@ +/* +// 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. +*/ +#pragma once +#include <openssl/evp.h> + +#include <ctime> +#include <unordered_map> + +namespace ipmi +{ + +class PasswdMgr +{ + public: + ~PasswdMgr() = default; + PasswdMgr(const PasswdMgr&) = delete; + PasswdMgr& operator=(const PasswdMgr&) = delete; + PasswdMgr(PasswdMgr&&) = delete; + PasswdMgr& operator=(PasswdMgr&&) = delete; + + /** @brief Constructs user password list + * + */ + PasswdMgr(); + + /** @brief Get password for the user + * + * @param[in] userName - user name + * + * @return password string. will return empty string, if unable to locate + * the user + */ + std::string getPasswdByUserName(const std::string& userName); + + private: + using UserName = std::string; + using Password = std::string; + std::unordered_map<UserName, Password> passwdMapList; + std::time_t fileLastUpdatedTime; + /** @brief check timestamp and reload password map if required + * + */ + void checkAndReload(void); + /** @brief initializes passwdMapList by reading the encrypted file + * + * Initializes the passwordMapList members after decrypting the + * password file. passwordMapList will be used further in IPMI + * authentication. + */ + void initPasswordMap(void); + /** @brief decrypts the data provided + * + * @param[in] cipher - cipher to be used + * @param[in] key - pointer to the key + * @param[in] keyLen - Length of the key to be used + * @param[in] iv - pointer to initialization vector + * @param[in] ivLen - Length of the iv + * @param[in] inBytes - input data to be encrypted / decrypted + * @param[in] inBytesLen - input size to be decrypted + * @param[in] mac - message authentication code - to figure out corruption + * @param[in] macLen - size of MAC + * @param[in] outBytes - ptr to store output bytes + * @param[in] outBytesLen - outbut data length. + * + * @return error response + */ + int decrypt(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, uint8_t* outBytes, + size_t* outBytesLen); +}; + +} // namespace ipmi diff --git a/user_channel/shadowlock.hpp b/user_channel/shadowlock.hpp new file mode 100644 index 0000000..8b09f21 --- /dev/null +++ b/user_channel/shadowlock.hpp @@ -0,0 +1,50 @@ +#pragma once + +#include <shadow.h> + +#include <phosphor-logging/elog-errors.hpp> +#include <xyz/openbmc_project/Common/error.hpp> +namespace phosphor +{ +namespace user +{ +namespace shadow +{ + +using InternalFailure = + sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure; +using namespace phosphor::logging; + +/** @class Lock + * @brief Responsible for locking and unlocking /etc/shadow + */ +class Lock +{ + public: + Lock(const Lock&) = delete; + Lock& operator=(const Lock&) = delete; + Lock(Lock&&) = delete; + Lock& operator=(Lock&&) = delete; + + /** @brief Default constructor that just locks the shadow file */ + Lock() + { + if (!lckpwdf()) + { + log<level::ERR>("Locking Shadow failed"); + elog<InternalFailure>(); + } + } + ~Lock() + { + if (!ulckpwdf()) + { + log<level::ERR>("Un-Locking Shadow failed"); + elog<InternalFailure>(); + } + } +}; + +} // namespace shadow +} // namespace user +} // namespace phosphor diff --git a/user_channel/user_layer.cpp b/user_channel/user_layer.cpp new file mode 100644 index 0000000..dce33d9 --- /dev/null +++ b/user_channel/user_layer.cpp @@ -0,0 +1,32 @@ +/* +// 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 "user_layer.hpp" + +#include "passwd_mgr.hpp" +namespace +{ +ipmi::PasswdMgr passwdMgr; +} + +namespace ipmi +{ +std::string ipmiUserGetPassword(const std::string& userName) +{ + return passwdMgr.getPasswdByUserName(userName); +} + +} // namespace ipmi diff --git a/user_channel/user_layer.hpp b/user_channel/user_layer.hpp new file mode 100644 index 0000000..b797007 --- /dev/null +++ b/user_channel/user_layer.hpp @@ -0,0 +1,33 @@ +/* +// 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. +*/ +#pragma once +#include <host-ipmid/ipmid-api.h> + +#include <string> + +namespace ipmi +{ +/** @brief The ipmi get user password layer call + * + * @param[in] userName + * + * @return password or empty string + */ +std::string ipmiUserGetPassword(const std::string& userName); + +// TODO: Define required user layer API Call's which user layer shared library +// must implement. +} // namespace ipmi |