Skip to content

Commit df4d019

Browse files
committed
WL#15916 Support for WebauthN authentication in Shell
This patch introduces the following options to fully support the webauthn authentication plugin: --register-factor --plugin-authentication-webauthn-client-preserve-privacy Additionally deprecates the existing --fido-register-factor option in favor of the new --register-factor option. Change-Id: Ia0f447db87f605bdb8ee18ca98a265ff5e0d7b34
1 parent 808f178 commit df4d019

20 files changed

+352
-48
lines changed

mysqlshdk/include/shellcore/shell_options.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,8 @@ class Shell_options final : public shcore::Options {
108108
std::string run_module;
109109

110110
// Individual connection parameters
111-
std::string fido_register_factor;
111+
std::string register_factor;
112+
std::optional<bool> webauthn_client_preserve_privacy;
112113
std::string oci_profile;
113114
std::string oci_config_file;
114115

mysqlshdk/libs/db/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ set(db_SOURCE
4848
mysql/auth_plugins/fido.cc
4949
mysql/auth_plugins/oci.cc
5050
mysql/auth_plugins/kerberos.cc
51+
mysql/auth_plugins/webauthn.cc
5152
mysqlx/xsession.cc
5253
mysqlx/xresult.cc
5354
mysqlx/xrow.cc

mysqlshdk/libs/db/mysql/auth_plugins/fido.cc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ int plugin_messages_callback_get_uint(unsigned int *val) {
9090
}
9191

9292
/**
93-
* Method to parse the parameter for --fido-register-factor, taken verbatim from
93+
* Method to parse the parameter for --register-factor, taken verbatim from
9494
* the CLI code (unavailable in the client lib).
9595
*/
9696
bool parse_register_factor(const char *what_factor,
@@ -159,7 +159,7 @@ void register_device(MYSQL *conn, const char *factor) {
159159
std::vector<unsigned int> list;
160160
if (!parse_register_factor(factor, &list)) {
161161
throw std::invalid_argument(
162-
"Incorrect value specified for --fido-register-factor option. "
162+
"Incorrect value specified for --register-factor option. "
163163
"Correct values can be '2', '3', '2,3' or '3,2'.");
164164
}
165165

mysqlshdk/libs/db/mysql/auth_plugins/mysql_event_handler_plugin.cc

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
#include "mysqlshdk/libs/db/mysql/auth_plugins/fido.h"
3939
#include "mysqlshdk/libs/db/mysql/auth_plugins/kerberos.h"
4040
#include "mysqlshdk/libs/db/mysql/auth_plugins/oci.h"
41+
#include "mysqlshdk/libs/db/mysql/auth_plugins/webauthn.h"
4142
#include "mysqlshdk/libs/utils/logger.h"
4243

4344
namespace mysqlshdk {
@@ -124,10 +125,14 @@ int trace_event(struct st_mysql_client_plugin_TRACE * /*plugin_data*/,
124125
log_protocol_event("PLUGIN-EVENT: %s.%s.%s", args.plugin_name,
125126
protocol_stage_str(stage), trace_event_str(ev));
126127

127-
if (0 == strcmp(args.plugin_name, "authentication_fido_client") ||
128-
0 == strcmp(args.plugin_name, "authentication_webauthn_client")) {
128+
if (0 == strcmp(args.plugin_name, "authentication_fido_client")) {
129129
// Instantiating the fido handler will set the print callback
130130
fido::register_callbacks(conn, args.plugin_name);
131+
} else if (0 ==
132+
strcmp(args.plugin_name, "authentication_webauthn_client")) {
133+
// Instantiating the fido handler will set the print callback
134+
fido::register_callbacks(conn, args.plugin_name);
135+
webauthn::set_preserve_privacy(conn);
131136
} else if (0 == strcmp(args.plugin_name, "authentication_oci_client")) {
132137
// make sure that the configured OCI config file is used for
133138
// authentication
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/*
2+
* Copyright (c) 2023, Oracle and/or its affiliates.
3+
*
4+
* This program is free software; you can redistribute it and/or modify
5+
* it under the terms of the GNU General Public License, version 2.0,
6+
* as published by the Free Software Foundation.
7+
*
8+
* This program is also distributed with certain software (including
9+
* but not limited to OpenSSL) that is licensed under separate terms, as
10+
* designated in a particular file or component or in included license
11+
* documentation. The authors of MySQL hereby grant you an additional
12+
* permission to link the program and your derivative works with the
13+
* separately licensed software that they have included with MySQL.
14+
* This program is distributed in the hope that it will be useful, but
15+
* WITHOUT ANY WARRANTY; without even the implied warranty of
16+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
17+
* the GNU General Public License, version 2.0, for more details.
18+
*
19+
* You should have received a copy of the GNU General Public License
20+
* along with this program; if not, write to the Free Software Foundation, Inc.,
21+
* 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
22+
*/
23+
24+
#include "mysqlshdk/libs/db/mysql/auth_plugins/webauthn.h"
25+
26+
#include <cassert>
27+
28+
#include "mysqlshdk/libs/db/connection_options.h"
29+
#include "mysqlshdk/libs/db/mysql/auth_plugins/common.h"
30+
#include "mysqlshdk/libs/db/utils_connection.h"
31+
#include "mysqlshdk/libs/utils/logger.h"
32+
33+
namespace mysqlshdk {
34+
namespace db {
35+
namespace mysql {
36+
namespace webauthn {
37+
38+
void set_preserve_privacy(MYSQL *conn) {
39+
auto conn_data = auth::get_connection_options_for_mysql(conn);
40+
assert(conn_data != nullptr);
41+
42+
if (conn_data->has(mysqlshdk::db::kWebauthnClientPreservePrivacy)) {
43+
auto &str_value =
44+
conn_data->get(mysqlshdk::db::kWebauthnClientPreservePrivacy);
45+
bool value = str_value.at(0) == '1';
46+
47+
const auto plugin =
48+
auth::get_authentication_plugin(conn, "authentication_webauthn_client");
49+
50+
if (mysql_plugin_options(plugin,
51+
"authentication_webauthn_client_preserve_privacy",
52+
&value)) {
53+
throw std::runtime_error(
54+
"Failed to set the Preserve Privacy option on "
55+
"authentication_webauthn_client plugin.");
56+
} else {
57+
log_debug3("Using authentication_webauthn_client_preserve_privacy = %s",
58+
str_value.c_str());
59+
}
60+
}
61+
}
62+
63+
} // namespace webauthn
64+
} // namespace mysql
65+
} // namespace db
66+
} // namespace mysqlshdk
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/*
2+
* Copyright (c) 2023, Oracle and/or its affiliates.
3+
*
4+
* This program is free software; you can redistribute it and/or modify
5+
* it under the terms of the GNU General Public License, version 2.0,
6+
* as published by the Free Software Foundation.
7+
*
8+
* This program is also distributed with certain software (including
9+
* but not limited to OpenSSL) that is licensed under separate terms, as
10+
* designated in a particular file or component or in included license
11+
* documentation. The authors of MySQL hereby grant you an additional
12+
* permission to link the program and your derivative works with the
13+
* separately licensed software that they have included with MySQL.
14+
* This program is distributed in the hope that it will be useful, but
15+
* WITHOUT ANY WARRANTY; without even the implied warranty of
16+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
17+
* the GNU General Public License, version 2.0, for more details.
18+
*
19+
* You should have received a copy of the GNU General Public License
20+
* along with this program; if not, write to the Free Software Foundation, Inc.,
21+
* 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
22+
*/
23+
24+
#ifndef MYSQLSHDK_LIBS_DB_MYSQL_AUTH_PLUGINS_WEBAUTHN_H_
25+
#define MYSQLSHDK_LIBS_DB_MYSQL_AUTH_PLUGINS_WEBAUTHN_H_
26+
27+
#include <mysql.h>
28+
29+
namespace mysqlshdk {
30+
namespace db {
31+
namespace mysql {
32+
namespace webauthn {
33+
34+
void set_preserve_privacy(MYSQL *conn);
35+
36+
} // namespace webauthn
37+
} // namespace mysql
38+
} // namespace db
39+
} // namespace mysqlshdk
40+
41+
#endif // MYSQLSHDK_LIBS_DB_MYSQL_AUTH_PLUGINS_WEBAUTHN_H_

mysqlshdk/libs/db/mysql/session.cc

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -424,11 +424,6 @@ void Session_impl::connect(
424424
throw_on_connection_fail();
425425
}
426426

427-
if (connection_options.has(mysqlshdk::db::kFidoRegisterFactor)) {
428-
auto factor = connection_options.get(mysqlshdk::db::kFidoRegisterFactor);
429-
fido::register_device(_mysql, factor.c_str());
430-
}
431-
432427
DBUG_LOG("sql", get_thread_id()
433428
<< ": CONNECTED: " << _connection_options.uri_endpoint());
434429
{

mysqlshdk/libs/db/mysql/session.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,8 @@ class Session_impl : public std::enable_shared_from_this<Session_impl> {
216216

217217
void setup_default_character_set();
218218

219+
MYSQL *get_handle() { return _mysql; }
220+
219221
std::string _uri;
220222
MYSQL *_mysql = nullptr;
221223
std::shared_ptr<MYSQL_RES> _prev_result;
@@ -338,6 +340,8 @@ class SHCORE_PUBLIC Session : public ISession,
338340
return _impl->_mysql->net.fd;
339341
}
340342

343+
MYSQL *get_handle() { return _impl->get_handle(); }
344+
341345
~Session() override { close(); }
342346

343347
protected:

mysqlshdk/libs/db/utils_connection.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,8 @@ inline constexpr const char kLocalInfile[] = "local-infile";
100100
inline constexpr const char kNetBufferLength[] = "net-buffer-length";
101101
inline constexpr const char kMaxAllowedPacket[] = "max-allowed-packet";
102102
inline constexpr const char kMysqlPluginDir[] = "mysql-plugin-dir";
103-
inline constexpr const char kFidoRegisterFactor[] = "fido-register-factor";
103+
inline constexpr const char kWebauthnClientPreservePrivacy[] =
104+
"plugin-authentication-webauthn-client-preserve-privacy";
104105
inline constexpr const char kConnectionAttributes[] = "connection-attributes";
105106
inline constexpr const char kUri[] = "uri";
106107
inline constexpr const char kSsh[] = "ssh";

mysqlshdk/shellcore/shell_options.cc

Lines changed: 56 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -106,9 +106,10 @@ mysqlshdk::db::Connection_options Shell_options::Storage::connection_options()
106106
target_server.set_scheme("mysql");
107107
}
108108

109-
if (!fido_register_factor.empty()) {
110-
target_server.set_unchecked(mysqlshdk::db::kFidoRegisterFactor,
111-
fido_register_factor.c_str());
109+
if (webauthn_client_preserve_privacy.has_value()) {
110+
target_server.set_unchecked(
111+
mysqlshdk::db::kWebauthnClientPreservePrivacy,
112+
(*webauthn_client_preserve_privacy) ? "1" : "0");
112113
}
113114

114115
return target_server;
@@ -472,10 +473,25 @@ Shell_options::Shell_options(
472473
[this](const std::string&, const char* value) {
473474
storage.connection_data.set_schema(value);
474475
})
475-
(&storage.fido_register_factor, "",
476-
cmdline("--fido-register-factor=<name>"),
476+
(cmdline("--fido-register-factor=<name>"),deprecated(m_on_warning, "--register-factor",
477+
[this](const std::string& , const char* value){
478+
storage.register_factor.assign(value);
479+
}))
480+
(&storage.register_factor, "",
481+
cmdline("--register-factor=<name>"),
477482
"Specifies authentication factor, for which registration needs to be "
478483
"done.")
484+
(cmdline("--plugin-authentication-webauthn-client-preserve-privacy[=<value>]"),
485+
"Allows selection of discoverable credential to be used for signing "
486+
"challenge. If not enabled, challenge is signed by all credentials for "
487+
"given relying party.", [this](const std::string &, const char *value){
488+
if (value == nullptr) {
489+
storage.webauthn_client_preserve_privacy=true;
490+
} else {
491+
storage.webauthn_client_preserve_privacy = shcore::opts::convert<bool>(value,
492+
shcore::opts::Source::Command_line);
493+
}
494+
})
479495
(cmdline("--recreate-schema"), "Drop and recreate the specified schema. "
480496
"Schema will be deleted if it exists!", deprecated(m_on_warning, nullptr, [this](const std::string&, const char* ) {
481497
storage.recreate_database = true;
@@ -1445,16 +1461,43 @@ void Shell_options::check_import_options() {
14451461
}
14461462

14471463
void Shell_options::check_connection_options() {
1448-
if (!storage.fido_register_factor.empty()) {
1449-
if (!storage.connection_data.has_data()) {
1464+
std::vector<std::string> connection_dependent_options;
1465+
if (!storage.register_factor.empty()) {
1466+
connection_dependent_options.push_back("--register-factor");
1467+
}
1468+
if (storage.webauthn_client_preserve_privacy.has_value()) {
1469+
connection_dependent_options.push_back(
1470+
"--plugin-authentication-webauthn-client-preserve-privacy");
1471+
}
1472+
1473+
if (!connection_dependent_options.empty() &&
1474+
!storage.connection_data.has_data()) {
1475+
if (connection_dependent_options.size() == 1) {
14501476
m_on_warning(
1451-
"WARNING: --fido-register-factor was specified without "
1452-
"connection data, the option will be ignored.");
1453-
} else if (storage.connection_data.has_scheme() &&
1454-
storage.connection_data.get_scheme() == "mysqlx") {
1477+
shcore::str_format("WARNING: %s was specified without "
1478+
"connection data, the option will be ignored.",
1479+
connection_dependent_options.at(0).c_str()));
1480+
} else {
1481+
shcore::str_format(
1482+
"WARNING: The following options were specified without "
1483+
"connection data, they will be ignored: %s.",
1484+
shcore::str_join(connection_dependent_options, ", ").c_str());
1485+
}
1486+
}
1487+
1488+
if (!connection_dependent_options.empty() &&
1489+
storage.connection_data.has_scheme() &&
1490+
storage.connection_data.get_scheme() == "mysqlx") {
1491+
if (connection_dependent_options.size() == 1) {
14551492
throw std::runtime_error(
1456-
"Option --fido-register-factor is only available for MySQL protocol "
1457-
"connections.");
1493+
shcore::str_format("Option %s is only available for MySQL protocol "
1494+
"connections.",
1495+
connection_dependent_options.at(0).c_str()));
1496+
} else {
1497+
throw std::runtime_error(shcore::str_format(
1498+
"The following options are only available for MySQL protocol "
1499+
"connections: %s",
1500+
shcore::str_join(connection_dependent_options, ", ").c_str()));
14581501
}
14591502
}
14601503

src/mysqlsh/main.cc

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
#include "mysqlshdk/include/shellcore/base_session.h"
2929
#include "mysqlshdk/include/shellcore/interrupt_helper.h"
3030
#include "mysqlshdk/include/shellcore/shell_init.h"
31+
#include "mysqlshdk/libs/db/mysql/auth_plugins/fido.h"
3132
#include "mysqlshdk/libs/textui/textui.h"
3233
#include "mysqlshdk/libs/utils/debug.h"
3334
#include "mysqlshdk/libs/utils/document_parser.h"
@@ -848,8 +849,27 @@ int main(int argc, char **argv) {
848849
"insecure.");
849850
}
850851

852+
std::function<void(std::shared_ptr<mysqlshdk::db::ISession>)>
853+
extra_init;
854+
855+
if (!options.register_factor.empty()) {
856+
extra_init =
857+
[&options](std::shared_ptr<mysqlshdk::db::ISession> session) {
858+
auto mysql_session =
859+
std::dynamic_pointer_cast<mysqlshdk::db::mysql::Session>(
860+
session);
861+
862+
if (mysql_session) {
863+
mysqlshdk::db::mysql::fido::register_device(
864+
mysql_session->get_handle(),
865+
options.register_factor.c_str());
866+
}
867+
};
868+
}
869+
851870
// Connect to the requested instance
852-
shell->connect(target, options.recreate_database);
871+
shell->connect(target, options.recreate_database, true,
872+
std::move(extra_init));
853873

854874
// If redirect is requested, then reconnect to the right instance
855875
handle_redirect(shell, options.redirect_session);

src/mysqlsh/mysql_shell.cc

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -992,7 +992,8 @@ void Mysql_shell::print_connection_message(
992992

993993
std::shared_ptr<mysqlsh::ShellBaseSession> Mysql_shell::connect(
994994
const mysqlshdk::db::Connection_options &connection_options_,
995-
bool recreate_schema, bool shell_global_session) {
995+
bool recreate_schema, bool shell_global_session,
996+
std::function<void(std::shared_ptr<mysqlshdk::db::ISession>)> extra_init) {
996997
FI_SUPPRESS(mysql);
997998
FI_SUPPRESS(mysqlx);
998999

@@ -1033,6 +1034,10 @@ std::shared_ptr<mysqlsh::ShellBaseSession> Mysql_shell::connect(
10331034

10341035
toggle_print();
10351036
isession = establish_session(connection_options, options().wizards);
1037+
1038+
if (extra_init) {
1039+
extra_init(isession);
1040+
}
10361041
}
10371042

10381043
auto new_session = ShellBaseSession::wrap_session(isession);

src/mysqlsh/mysql_shell.h

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2017, 2022, Oracle and/or its affiliates.
2+
* Copyright (c) 2017, 2023, Oracle and/or its affiliates.
33
*
44
* This program is free software; you can redistribute it and/or modify
55
* it under the terms of the GNU General Public License, version 2.0,
@@ -51,7 +51,9 @@ class Mysql_shell : public mysqlsh::Base_shell {
5151

5252
virtual std::shared_ptr<mysqlsh::ShellBaseSession> connect(
5353
const mysqlshdk::db::Connection_options &args,
54-
bool recreate_schema = false, bool shell_global_session = true);
54+
bool recreate_schema = false, bool shell_global_session = true,
55+
std::function<void(std::shared_ptr<mysqlshdk::db::ISession>)> extra_init =
56+
nullptr);
5557

5658
bool redirect_session_if_needed(
5759
bool secondary, const Connection_options &opts = Connection_options());

0 commit comments

Comments
 (0)