From 4654d99fda30800024c56b085ec9dcf2ac26c417 Mon Sep 17 00:00:00 2001 From: Richard Marian Thomaiyar Date: Thu, 19 Apr 2018 05:38:37 +0530 Subject: Add first user layer support. Exposes user layer library which will be used by both apphandler and netipmid for RAKP. Password is decrypted from the special file which is used by pam-ipmi. Change-Id: Ia6b99d4359a03843f19cd0e18f7269ad2e3cdb44 Signed-off-by: Richard Marian Thomaiyar --- Makefile.am | 16 ++- configure.ac | 1 + user_channel/passwd_mgr.cpp | 261 ++++++++++++++++++++++++++++++++++++++++++++ user_channel/passwd_mgr.hpp | 86 +++++++++++++++ user_channel/shadowlock.hpp | 50 +++++++++ user_channel/user_layer.cpp | 32 ++++++ user_channel/user_layer.hpp | 33 ++++++ 7 files changed, 478 insertions(+), 1 deletion(-) create mode 100644 user_channel/passwd_mgr.cpp create mode 100644 user_channel/passwd_mgr.hpp create mode 100644 user_channel/shadowlock.hpp create mode 100644 user_channel/user_layer.cpp create mode 100644 user_channel/user_layer.hpp 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 +#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; + +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("Error in getting last updated time stamp"); + return; + } + std::time_t updatedTime = fileStat.st_mtime; + if (fileLastUpdatedTime != updatedTime) + { + log("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("Error Invalid Inputs"); + return -1; + } + + 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 -1; + } + if (!((calMacLen == macLen) && + (std::memcmp(calMac.data(), mac, calMacLen) == 0))) + { + log("Authenticated message doesn't match"); + return -1; + } + + std::unique_ptr 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("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("EVP_CipherFinal fails", + entry("RET_VAL=%d", retval)); + return -1; + } + } + else + { + log("EVP_CipherUpdate fails", + entry("RET_VAL=%d", retval)); + return -1; + } + return 0; +} + +void PasswdMgr::initPasswordMap(void) +{ + phosphor::user::shadow::Lock lock(); + + 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; + } + keyFile.read((char*)keyBuff.data(), keyBuff.size()); + if (keyFile.fail()) + { + log("Error in reading encryption key file"); + return; + } + + std::ifstream passwdFile(passwdFileName, std::ios::in | std::ios::binary); + if (!passwdFile.is_open()) + { + log("Error in opening ipmi password file"); + return; + } + + // calculate file size and read the data + std::vector 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("Error in reading encryption key file"); + return; + } + + // 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; + } + + // compute the key needed to decrypt + std::array key; + auto keyLen = key.size(); + HMAC(EVP_sha256(), keyBuff.data(), keyBuff.size(), + input.data() + sizeof(*metaData), metaData->hashSize, key.data(), + reinterpret_cast(&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 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("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(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("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 + +#include +#include + +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 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 + +#include +#include +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("Locking Shadow failed"); + elog(); + } + } + ~Lock() + { + if (!ulckpwdf()) + { + log("Un-Locking Shadow failed"); + elog(); + } + } +}; + +} // 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 + +#include + +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 -- cgit v1.2.1