/* Copyright (c) 2023, 2024, Oracle and/or its affiliates. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2.0, as published by the Free Software Foundation. This program is designed to work with certain software (including but not limited to OpenSSL) that is licensed under separate terms, as designated in a particular file or component or in included license documentation. The authors of MySQL hereby grant you an additional permission to link the program and your derivative works with the separately licensed software that they have either included with the program or referenced in the documentation. Without limiting anything contained in the foregoing, this file, which is part of C Driver for MySQL (Connector/C), is also subject to the Universal FOSS Exception, version 1.0, a copy of which can be found at http://oss.oracle.com/licenses/universal-foss-exception. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License, version 2.0, for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include #include "webauthn_assertion.h" #include "webauthn_registration.h" #ifndef NDEBUG static bool is_fido_testing = false; #endif static unsigned char registration_challenge[128] = {0}; static unsigned char *registration_challenge_response = nullptr; /* ToDo: Change this to false */ static bool preserve_privacy = false; static bool do_registration(); /* Handler to callback function which will pass informative messages generated by this plugin caller. This callback function is registered via mysql_plugin_option("webauthn_messages_callback") If callback is not registered, all messaged are redirected to stderr/stdout. */ plugin_messages_callback mc = nullptr; plugin_messages_callback_get_uint mc_get_uint = nullptr; plugin_messages_callback_get_password mc_get_password = nullptr; /** The libfido "device" to use. */ unsigned int libfido_device_id = 0; /** authentication_webauthn_client plugin API to initialize */ static int webauthn_auth_client_plugin_init(char *, size_t, int, va_list) { fido_init(0); return 0; } /** Deinitialize authentication_webauthn_client plugin */ static int webauthn_auth_client_plugin_deinit() { return 0; } /** authentication_webauthn_client plugin API to allow client to pass optional data for plugin to process */ static int webauthn_auth_client_plugin_option(const char *option, const void *val) { #ifndef NDEBUG if (strcmp(option, "is_fido_testing") == 0) { is_fido_testing = *static_cast(val); return 0; } #endif if (strcmp(option, "plugin_authentication_webauthn_client_messages_callback") == 0) { mc = (plugin_messages_callback)(const_cast(val)); return 0; } if (strcmp(option, "plugin_authentication_webauthn_client_callback_get_uint") == 0) { mc_get_uint = (plugin_messages_callback_get_uint)(const_cast(val)); return 0; } if (strcmp(option, "plugin_authentication_webauthn_client_callback_get_" "password") == 0) { mc_get_password = (plugin_messages_callback_get_password)(const_cast(val)); return 0; } if (strcmp(option, "registration_challenge") == 0) { unsigned char *p = reinterpret_cast(const_cast(val)); memcpy(registration_challenge, p, strlen(reinterpret_cast(p))); /* finish registration */ if (do_registration()) return 1; return 0; } if (strcmp(option, "authentication_webauthn_client_preserve_privacy") == 0) { preserve_privacy = *static_cast(val); return 0; } if (0 == strcmp(option, "device")) { /* An artifical limit on the number of devices supported to avoid excessive memory consumption */ static const int MAX_FIDO_DEVICE_ID = 15; libfido_device_id = *static_cast(val); if (libfido_device_id > MAX_FIDO_DEVICE_ID) return 1; return 0; } return 1; } /** authentication_webauthn_client plugin API to allow client to get optional data from plugin */ static int webauthn_auth_client_get_plugin_option(const char *option, void *val) { if (strcmp(option, "registration_response") == 0) { *(static_cast(val)) = registration_challenge_response; } return 0; } /** WebAuthN client side authentication method. This method does following: 1. Receive challenge from server side FIDO plugin. This challenge comprises of 1 byte capability, salt and relying party name. 2. Construct client data hash in the form of JSON object comprising of salt, relying party name aka Origin. Set client data hash. 3. If token device does not have CTAP2.1 protocol support, then request credential ID from server. 4. Token will sign clientdatahash. Client will send authenticator data, signature and clientDataJSON to server to be verified. @param [in] vio Virtual I/O interface @return authentication status @retval CR_OK Successful authentication @retval true Authentication failure */ static int webauthn_auth_client(MYSQL_PLUGIN_VIO *vio, MYSQL *) { unsigned char *server_challenge = nullptr; int server_challenge_len = 0; /** Get the challenge from the MySQL server. */ server_challenge_len = vio->read_packet(vio, &server_challenge); if (server_challenge_len == 0) { /* an empty packet means registration step is pending, thus for now allow connection with limited operations for user so that user can perform registration step. */ return CR_OK_AUTH_IN_SANDBOX_MODE; } unsigned char *buff = nullptr; webauthn_assertion *wa = nullptr; size_t length = 0; auto cleanup = create_scope_guard([&] { if (buff) delete[] buff; if (wa) delete wa; }); #ifndef NDEBUG if (is_fido_testing) { length = 34; buff = new (std::nothrow) unsigned char[length]; unsigned char *pos = buff; *pos = '\2'; pos++; memcpy(pos, "\nsakila \nsakila \nsakila ", length - 1); vio->write_packet(vio, buff, length); return CR_OK; } else #endif { wa = new webauthn_assertion(preserve_privacy); if (wa->parse_challenge(server_challenge)) return true; bool is_fido2 = false; if (wa->check_fido2_device(is_fido2)) return true; if (is_fido2) { if (wa->select_credential_id()) return true; } else { /* request credential ID */ const unsigned char cred_req = '\1'; vio->write_packet(vio, &cred_req, 1); unsigned char *cred_id = nullptr; int cred_id_len = 0; /** Get the credential ID from MySQL server. */ if (vio->read_packet(vio, &cred_id) < 0) return true; if (!cred_id) return true; cred_id_len = net_field_length_ll(&cred_id); /* extract cred ID */ wa->set_cred_id(cred_id, cred_id_len); } if (wa->sign_challenge()) return true; /* copy signed challenge into buff */ wa->get_signed_challenge(&buff, length); /* send signed challenge to webauthn server plugin */ vio->write_packet(vio, buff, length); } return CR_OK; } /** WebAuthN client side registration method. This method does following: 1. Receive challenge from server side WebAuthN plugin. This challenge comprises of capability flag, username, salt and relying party name. 2. Send this challenge to FIDO device and get the signature, authenticator data and x509 certificate generated by device. This along with client data JSON is sent to server as challenge response. @return registration status @retval false Successful registration @retval true Registration failure */ static bool do_registration() { #ifndef NDEBUG if (is_fido_testing) { const char *dummy = "\nSIGNATURE \nAUTHDATA \nCERT "; size_t sz = strlen(dummy); memcpy(registration_challenge, dummy, sz); /* dummy challenge response for testing */ registration_challenge_response = new unsigned char[sz + 1]; memcpy(registration_challenge_response, dummy, sz); registration_challenge_response[sz] = 0; return false; } else #endif { webauthn_registration *fr = new webauthn_registration(); if (fr->make_credentials(const_cast( reinterpret_cast(registration_challenge)))) { delete fr; return true; } if (fr->make_challenge_response(registration_challenge_response)) { delete fr; return true; } delete fr; } return false; } mysql_declare_client_plugin(AUTHENTICATION) "authentication_webauthn_client", MYSQL_CLIENT_PLUGIN_AUTHOR_ORACLE, "Webauthn Client Authentication Plugin", {0, 1, 0}, "GPL", nullptr, webauthn_auth_client_plugin_init, webauthn_auth_client_plugin_deinit, webauthn_auth_client_plugin_option, webauthn_auth_client_get_plugin_option, webauthn_auth_client, nullptr, mysql_end_client_plugin;