Skip to content

Commit a1a754c

Browse files
committed
MFA feature: Enable google authenticator
Enabling multi-factor authentication for BMC. This feature enables google authenticator using TOTP method. This commit implements interface published [here][1] and [here][2] The implementation supports features such as create secret key,verify TOTP token, enable system level MFA, and enable bypass options. Currently the support is only for GoogleAuthenticator. [1]: https://github.com/openbmc/phosphor-dbus-interfaces/blob/master/yaml/xyz/openbmc_project/User/MultiFactorAuthConfiguration.interface.yaml [2]: https://github.com/openbmc/phosphor-dbus-interfaces/blob/master/yaml/xyz/openbmc_project/User/TOTPAuthenticator.interface.yaml Tested By: Unit test https://gerrit.openbmc.org/c/openbmc/phosphor-user-manager/+/78583/1 Change-Id: I053095763c65963ff865b487ab08f05039d2fc3a Signed-off-by: Abhilash Raju <[email protected]>
1 parent d4d22b0 commit a1a754c

File tree

7 files changed

+417
-6
lines changed

7 files changed

+417
-6
lines changed

meson.build

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,11 +103,13 @@ if not has_cereal
103103
assert(cereal_proj.found(), 'cereal is required')
104104
cereal_dep = cereal_proj.dependency('cereal')
105105
endif
106-
106+
pam_dep = dependency('pam')
107107
user_manager_src = ['mainapp.cpp', 'user_mgr.cpp', 'users.cpp']
108108

109+
109110
user_manager_deps = [
110111
boost_dep,
112+
pam_dep,
111113
sdbusplus_dep,
112114
phosphor_logging_dep,
113115
phosphor_dbus_interfaces_dep,
@@ -150,6 +152,8 @@ install_data(
150152
install_dir: get_option('datadir') / 'phosphor-certificate-manager',
151153
)
152154

155+
install_data('mfa_pam', install_dir: '/etc/pam.d/')
156+
153157
# Figure out how to use install_symlink to install symlink to a file of another
154158
# recipe
155159
#install_symlink(

mfa_pam

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# /etc/pam.d/mfa
2+
3+
# Google Authenticator for two-factor authentication.
4+
auth required pam_google_authenticator.so secret=/home/${USER}/.config/phosphor-user-manager/.google_authenticator.tmp

totp.hpp

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
#pragma once
2+
3+
#include "config.h"
4+
5+
#include <security/pam_appl.h>
6+
#include <sys/types.h>
7+
#include <sys/wait.h>
8+
#include <unistd.h>
9+
10+
#include <phosphor-logging/elog-errors.hpp>
11+
#include <phosphor-logging/elog.hpp>
12+
#include <phosphor-logging/lg2.hpp>
13+
14+
#include <cstring>
15+
#include <memory>
16+
#include <span>
17+
#include <string_view>
18+
19+
struct PasswordData
20+
{
21+
struct Response
22+
{
23+
std::string_view prompt;
24+
std::string value;
25+
};
26+
27+
std::vector<Response> responseData;
28+
29+
int addPrompt(std::string_view prompt, std::string_view value)
30+
{
31+
if (value.size() + 1 > PAM_MAX_MSG_SIZE)
32+
{
33+
lg2::error("value length error{PROMPT}", "PROMPT", prompt);
34+
return PAM_CONV_ERR;
35+
}
36+
responseData.emplace_back(prompt, std::string(value));
37+
return PAM_SUCCESS;
38+
}
39+
40+
int makeResponse(const pam_message& msg, pam_response& response)
41+
{
42+
switch (msg.msg_style)
43+
{
44+
case PAM_PROMPT_ECHO_ON:
45+
break;
46+
case PAM_PROMPT_ECHO_OFF:
47+
{
48+
std::string prompt(msg.msg);
49+
auto iter = std::ranges::find_if(
50+
responseData, [&prompt](const Response& data) {
51+
return prompt.starts_with(data.prompt);
52+
});
53+
if (iter == responseData.end())
54+
{
55+
return PAM_CONV_ERR;
56+
}
57+
response.resp = strdup(iter->value.c_str());
58+
return PAM_SUCCESS;
59+
}
60+
break;
61+
case PAM_ERROR_MSG:
62+
{
63+
lg2::error("Pam error {MSG}", "MSG", msg.msg);
64+
}
65+
break;
66+
case PAM_TEXT_INFO:
67+
{
68+
lg2::error("Pam info {MSG}", "MSG", msg.msg);
69+
}
70+
break;
71+
default:
72+
{
73+
return PAM_CONV_ERR;
74+
}
75+
}
76+
return PAM_SUCCESS;
77+
}
78+
};
79+
80+
// function used to get user input
81+
inline int pamFunctionConversation(int numMsg, const struct pam_message** msgs,
82+
struct pam_response** resp, void* appdataPtr)
83+
{
84+
if ((appdataPtr == nullptr) || (msgs == nullptr) || (resp == nullptr))
85+
{
86+
return PAM_CONV_ERR;
87+
}
88+
89+
if (numMsg <= 0 || numMsg >= PAM_MAX_NUM_MSG)
90+
{
91+
return PAM_CONV_ERR;
92+
}
93+
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
94+
PasswordData* appPass = reinterpret_cast<PasswordData*>(appdataPtr);
95+
auto msgCount = static_cast<size_t>(numMsg);
96+
// NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays)
97+
// auto responseArrPtr = std::make_unique<pam_response[]>(msgCount);
98+
auto pamResponseDeleter = [](pam_response* ptr) { free(ptr); };
99+
100+
std::unique_ptr<pam_response[], decltype(pamResponseDeleter)> responses(
101+
static_cast<pam_response*>(calloc(msgCount, sizeof(pam_response))),
102+
pamResponseDeleter);
103+
104+
auto messagePtrs = std::span(msgs, msgCount);
105+
for (size_t i = 0; i < msgCount; ++i)
106+
{
107+
const pam_message& msg = *(messagePtrs[i]);
108+
109+
pam_response& response = responses[i];
110+
response.resp_retcode = 0;
111+
response.resp = nullptr;
112+
113+
int r = appPass->makeResponse(msg, response);
114+
if (r != PAM_SUCCESS)
115+
{
116+
return r;
117+
}
118+
}
119+
120+
*resp = responses.release();
121+
return PAM_SUCCESS;
122+
}
123+
124+
struct Totp
125+
{
126+
/**
127+
* @brief Attempt username/password authentication via PAM.
128+
* @param username The provided username aka account name.
129+
* @param token The provided MFA token.
130+
* @returns PAM error code or PAM_SUCCESS for success. */
131+
static inline int verify(std::string_view username, std::string token)
132+
{
133+
std::string userStr(username);
134+
PasswordData data;
135+
136+
if (int ret = data.addPrompt("Verification code: ", token);
137+
ret != PAM_SUCCESS)
138+
{
139+
return ret;
140+
}
141+
142+
const struct pam_conv localConversation = {pamFunctionConversation,
143+
&data};
144+
pam_handle_t* localAuthHandle = nullptr; // this gets set by pam_start
145+
146+
int retval = pam_start("mfa_pam", userStr.c_str(), &localConversation,
147+
&localAuthHandle);
148+
if (retval != PAM_SUCCESS)
149+
{
150+
return retval;
151+
}
152+
153+
retval = pam_authenticate(localAuthHandle,
154+
PAM_SILENT | PAM_DISALLOW_NULL_AUTHTOK);
155+
if (retval != PAM_SUCCESS)
156+
{
157+
pam_end(localAuthHandle, PAM_SUCCESS); // ignore retval
158+
return retval;
159+
}
160+
return pam_end(localAuthHandle, PAM_SUCCESS);
161+
}
162+
};

user_mgr.cpp

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1207,6 +1207,8 @@ UserInfoMap UserMgr::getUserInfo(std::string userName)
12071207
user.get()->userLockedForFailedAttempt());
12081208
userInfo.emplace("UserPasswordExpired",
12091209
user.get()->userPasswordExpired());
1210+
userInfo.emplace("TOTPSecretkeyRequired",
1211+
user.get()->secretKeyGenerationRequired());
12101212
userInfo.emplace("RemoteUser", false);
12111213
}
12121214
else
@@ -1539,5 +1541,39 @@ std::vector<std::string> UserMgr::getFailedAttempt(const char* userName)
15391541
return executeCmd("/usr/sbin/faillock", "--user", userName);
15401542
}
15411543

1544+
MultiFactorAuthType UserMgr::enabled(MultiFactorAuthType value, bool skipSignal)
1545+
{
1546+
if (value == enabled())
1547+
{
1548+
return value;
1549+
}
1550+
switch (value)
1551+
{
1552+
case MultiFactorAuthType::None:
1553+
for (auto type : {MultiFactorAuthType::GoogleAuthenticator})
1554+
{
1555+
for (auto& u : usersList)
1556+
{
1557+
u.second->enableMultiFactorAuth(type, false);
1558+
}
1559+
}
1560+
break;
1561+
default:
1562+
for (auto& u : usersList)
1563+
{
1564+
u.second->enableMultiFactorAuth(value, true);
1565+
}
1566+
break;
1567+
}
1568+
return MultiFactorAuthConfigurationIface::enabled(value, skipSignal);
1569+
}
1570+
bool UserMgr::secretKeyRequired(std::string userName)
1571+
{
1572+
if (usersList.contains(userName))
1573+
{
1574+
return usersList[userName]->secretKeyGenerationRequired();
1575+
}
1576+
return false;
1577+
}
15421578
} // namespace user
15431579
} // namespace phosphor

user_mgr.hpp

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@
2626
#include <xyz/openbmc_project/Common/error.hpp>
2727
#include <xyz/openbmc_project/User/AccountPolicy/server.hpp>
2828
#include <xyz/openbmc_project/User/Manager/server.hpp>
29+
#include <xyz/openbmc_project/User/MultiFactorAuthConfiguration/server.hpp>
30+
#include <xyz/openbmc_project/User/TOTPState/server.hpp>
2931

3032
#include <span>
3133
#include <string>
@@ -50,7 +52,14 @@ using UserSSHLists =
5052
using AccountPolicyIface =
5153
sdbusplus::xyz::openbmc_project::User::server::AccountPolicy;
5254

53-
using Ifaces = sdbusplus::server::object_t<UserMgrIface, AccountPolicyIface>;
55+
using MultiFactorAuthConfigurationIface =
56+
sdbusplus::xyz::openbmc_project::User::server::MultiFactorAuthConfiguration;
57+
58+
using TOTPStateIface = sdbusplus::xyz::openbmc_project::User::server::TOTPState;
59+
60+
using Ifaces = sdbusplus::server::object_t<UserMgrIface, AccountPolicyIface,
61+
MultiFactorAuthConfigurationIface,
62+
TOTPStateIface>;
5463

5564
using Privilege = std::string;
5665
using GroupList = std::vector<std::string>;
@@ -73,6 +82,8 @@ using DbusUserObjValue = std::map<Interface, DbusUserObjProperties>;
7382

7483
using DbusUserObj = std::map<DbusUserObjPath, DbusUserObjValue>;
7584

85+
using MultiFactorAuthType = sdbusplus::common::xyz::openbmc_project::user::
86+
MultiFactorAuthConfiguration::Type;
7687
std::string getCSVFromVector(std::span<const std::string> vec);
7788

7889
bool removeStringFromCSV(std::string& csvStr, const std::string& delStr);
@@ -259,7 +270,13 @@ class UserMgr : public Ifaces
259270
void createGroup(std::string groupName) override;
260271

261272
void deleteGroup(std::string groupName) override;
262-
273+
MultiFactorAuthType enabled() const override
274+
{
275+
return MultiFactorAuthConfigurationIface::enabled();
276+
}
277+
MultiFactorAuthType enabled(MultiFactorAuthType value,
278+
bool skipSignal) override;
279+
bool secretKeyRequired(std::string userName) override;
263280
static std::vector<std::string> readAllGroupsOnSystem();
264281

265282
protected:

0 commit comments

Comments
 (0)