diff --git a/CMakeLists.txt b/CMakeLists.txt index 92e3fe1a..605edeaa 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,18 +5,21 @@ project(mysql-asio) set(BOOST_ROOT /opt/boost_1_70_0 CACHE STRING "Path to boost installation") find_package(Boost REQUIRED COMPONENTS system) find_package(Threads REQUIRED) +find_package(OpenSSL REQUIRED) # Library add_library( mysql_asio SHARED src/deserialization.cpp + src/auth.cpp ) target_link_libraries( mysql_asio PUBLIC Boost::system Threads::Threads + OpenSSL::Crypto ) target_include_directories( mysql_asio diff --git a/include/auth.hpp b/include/auth.hpp new file mode 100644 index 00000000..7f8899fa --- /dev/null +++ b/include/auth.hpp @@ -0,0 +1,29 @@ +#ifndef INCLUDE_AUTH_HPP_ +#define INCLUDE_AUTH_HPP_ + +#include +#include + +namespace mysql +{ + +namespace mysql_native_password +{ + +constexpr std::size_t challenge_length = 20; +constexpr std::size_t response_length = 20; +using response_buffer = std::uint8_t [response_length]; + +// challenge must point to challenge_length bytes of data +void compute_auth_string(std::string_view password, const void* challenge, response_buffer& output); + +} + + + + +} + + + +#endif /* INCLUDE_AUTH_HPP_ */ diff --git a/include/deserialization.hpp b/include/deserialization.hpp index 8ac72e94..a968e47f 100644 --- a/include/deserialization.hpp +++ b/include/deserialization.hpp @@ -78,16 +78,13 @@ inline ReadIterator deserialize(ReadIterator from, ReadIterator last, string_len return from; } -ReadIterator deserialize(ReadIterator from, ReadIterator last, OkPacket& output); -ReadIterator deserialize(ReadIterator from, ReadIterator last, ErrPacket& output); -ReadIterator deserialize(ReadIterator from, ReadIterator last, Handshake& output); - // SERIALIZATION class DynamicBuffer { std::vector buffer_; public: + DynamicBuffer(int1 sequence_number): buffer_ { 0, 0, 0, sequence_number } {}; void add(const void* data, std::size_t size) { auto current_size = buffer_.size(); @@ -95,6 +92,14 @@ public: memcpy(buffer_.data() + current_size, data, size); } void add(std::uint8_t value) { buffer_.push_back(value); } + void set_size() + { + int3 packet_length { static_cast(buffer_.size() - 4) }; + boost::endian::native_to_little_inplace(packet_length.value); + memcpy(buffer_.data(), &packet_length.value, 3); + } + const void* data() const { return buffer_.data(); } + std::size_t size() const { return buffer_.size(); } }; template void native_to_little(T& value) { boost::endian::native_to_little_inplace(value); } @@ -107,7 +112,7 @@ template std::enable_if_t> serialize(DynamicBuffer& buffer, T value) { - boost::endian::native_to_little_inplace(value); + native_to_little(value); buffer.add(&value, get_size_v); } @@ -145,6 +150,11 @@ inline void serialize(DynamicBuffer& buffer, const string_lenenc& value) } +// Packet serialization and deserialization +ReadIterator deserialize(ReadIterator from, ReadIterator last, OkPacket& output); +ReadIterator deserialize(ReadIterator from, ReadIterator last, ErrPacket& output); +ReadIterator deserialize(ReadIterator from, ReadIterator last, Handshake& output); +void serialize(DynamicBuffer& buffer, const HandshakeResponse& value); } diff --git a/include/messages.hpp b/include/messages.hpp index eedcead0..ee8b80af 100644 --- a/include/messages.hpp +++ b/include/messages.hpp @@ -7,6 +7,36 @@ namespace mysql { +constexpr int4 CLIENT_LONG_PASSWORD = 1; // Use the improved version of Old Password Authentication +constexpr int4 CLIENT_FOUND_ROWS = 2; // Send found rows instead of affected rows in EOF_Packet +constexpr int4 CLIENT_LONG_FLAG = 4; // Get all column flags +constexpr int4 CLIENT_CONNECT_WITH_DB = 8; // Database (schema) name can be specified on connect in Handshake Response Packet +constexpr int4 CLIENT_NO_SCHEMA = 16; // Don't allow database.table.column +constexpr int4 CLIENT_COMPRESS = 32; // Compression protocol supported +constexpr int4 CLIENT_ODBC = 64; // Special handling of ODBC behavior +constexpr int4 CLIENT_LOCAL_FILES = 128; // Can use LOAD DATA LOCAL +constexpr int4 CLIENT_IGNORE_SPACE = 256; // Ignore spaces before '(' +constexpr int4 CLIENT_PROTOCOL_41 = 512; // New 4.1 protocol +constexpr int4 CLIENT_INTERACTIVE = 1024; // This is an interactive client +constexpr int4 CLIENT_SSL = 2048; // Use SSL encryption for the session +constexpr int4 CLIENT_IGNORE_SIGPIPE = 4096; // Client only flag +constexpr int4 CLIENT_TRANSACTIONS = 8192; // Client knows about transactions +constexpr int4 CLIENT_RESERVED = 16384; // DEPRECATED: Old flag for 4.1 protocol +constexpr int4 CLIENT_RESERVED2 = 32768; // DEPRECATED: Old flag for 4.1 authentication \ CLIENT_SECURE_CONNECTION +constexpr int4 CLIENT_MULTI_STATEMENTS = (1UL << 16); // Enable/disable multi-stmt support +constexpr int4 CLIENT_MULTI_RESULTS = (1UL << 17); // Enable/disable multi-results +constexpr int4 CLIENT_PS_MULTI_RESULTS = (1UL << 18); // Multi-results and OUT parameters in PS-protocol +constexpr int4 CLIENT_PLUGIN_AUTH = (1UL << 19); // Client supports plugin authentication +constexpr int4 CLIENT_CONNECT_ATTRS = (1UL << 20); // Client supports connection attributes +constexpr int4 CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA = (1UL << 21); // Enable authentication response packet to be larger than 255 bytes +constexpr int4 CLIENT_CAN_HANDLE_EXPIRED_PASSWORDS = (1UL << 22); // Don't close the connection for a user account with expired password +constexpr int4 CLIENT_SESSION_TRACK = (1UL << 23); // Capable of handling server state change information +constexpr int4 CLIENT_DEPRECATE_EOF = (1UL << 24); // Client no longer needs EOF_Packet and will use OK_Packet instead +constexpr int4 CLIENT_SSL_VERIFY_SERVER_CERT = (1UL << 30); // Verify server certificate +constexpr int4 CLIENT_OPTIONAL_RESULTSET_METADATA = (1UL << 25); // The client can handle optional metadata information in the resultset +constexpr int4 CLIENT_REMEMBER_OPTIONS = (1UL << 31); // Don't reset the options after an unsuccessful connect + + enum server_status_flags { SERVER_STATUS_IN_TRANS = 1 << 0, diff --git a/main.cpp b/main.cpp index 1bec4742..312f332e 100644 --- a/main.cpp +++ b/main.cpp @@ -7,11 +7,15 @@ #include #include #include +#include +#include #include "basic_types.hpp" #include "deserialization.hpp" +#include "auth.hpp" using namespace std; using namespace boost::asio; +using namespace mysql; constexpr auto HOSTNAME = "localhost"sv; constexpr auto PORT = "3306"sv; @@ -70,7 +74,28 @@ int main() "character_set=" << handshake.character_set << ",\n" << "status_flags=" << std::bitset<16>{handshake.status_flags} << ",\n" << "auth_plugin_name=" << handshake.auth_plugin_name.value << endl; + mysql::HandshakeResponse handshake_response; + assert(handshake.auth_plugin_data.size() == mysql_native_password::challenge_length); + mysql_native_password::response_buffer auth_response; + mysql_native_password::compute_auth_string("root", handshake.auth_plugin_data.data(), auth_response); + handshake_response.client_flag = + CLIENT_CONNECT_WITH_DB | + CLIENT_PROTOCOL_41 | + CLIENT_PLUGIN_AUTH | + CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA | + CLIENT_DEPRECATE_EOF; + handshake_response.max_packet_size = 0xffff; + handshake_response.character_set = 33; // utf8 + handshake_response.username.value = "root"; + handshake_response.auth_response.value = string_view {(const char*)auth_response, sizeof(auth_response)}; + handshake_response.database.value = "mysql"; + handshake_response.client_plugin_name.value = "mysql_native_password"; + // Serialize and send + DynamicBuffer response_buffer { ++sequence_number }; + serialize(response_buffer, handshake_response); + response_buffer.set_size(); + boost::asio::write(sock, boost::asio::buffer(response_buffer.data(), response_buffer.size())); } else { diff --git a/src/auth.cpp b/src/auth.cpp new file mode 100644 index 00000000..dcce224c --- /dev/null +++ b/src/auth.cpp @@ -0,0 +1,36 @@ +/* + * auth.cpp + * + * Created on: Jun 30, 2019 + * Author: ruben + */ + +#include "auth.hpp" +#include +#include + +// SHA1( password ) XOR SHA1( "20-bytes random data from server" SHA1( SHA1( password ) ) ) +void mysql::mysql_native_password::compute_auth_string( + std::string_view password, + const void* challenge, + response_buffer& output +) +{ + // SHA1 (password) + using sha1_buffer = unsigned char [SHA_DIGEST_LENGTH]; + sha1_buffer password_sha1; + SHA1(reinterpret_cast(password.data()), password.size(), password_sha1); + + // Add server challenge (salt) + unsigned char salted_buffer [challenge_length + SHA_DIGEST_LENGTH]; + memcpy(salted_buffer, challenge, challenge_length); + SHA1(password_sha1, sizeof(password_sha1), salted_buffer + 20); + sha1_buffer salted_sha1; + SHA1(salted_buffer, sizeof(salted_buffer), salted_sha1); + + // XOR + static_assert(sizeof(output) == SHA_DIGEST_LENGTH); + for (std::size_t i = 0; i < SHA_DIGEST_LENGTH; ++i) + output[i] = password_sha1[i] ^ salted_sha1[i]; +} + diff --git a/src/deserialization.cpp b/src/deserialization.cpp index 45143e0f..2ed55ce6 100644 --- a/src/deserialization.cpp +++ b/src/deserialization.cpp @@ -53,6 +53,30 @@ mysql::ReadIterator mysql::deserialize(ReadIterator from, ReadIterator last, str return string_end + 1; // skip the null terminator } +void mysql::serialize(DynamicBuffer& buffer, int_lenenc value) +{ + if (value.value < 251) + { + serialize(buffer, static_cast(value.value)); + } + else if (value.value < 0x10000) + { + serialize(buffer, int1(0xfc)); + serialize(buffer, static_cast(value.value)); + } + else if (value.value < 0x1000000) + { + serialize(buffer, int1(0xfd)); + serialize(buffer, int3 {static_cast(value.value)}); + } + else + { + serialize(buffer, int1(0xfe)); + serialize(buffer, static_cast(value.value)); + } +} + +// Packet serialization and deserialization mysql::ReadIterator mysql::deserialize(ReadIterator from, ReadIterator last, OkPacket& output) { // TODO: is packet header to be deserialized as part of this? @@ -97,29 +121,21 @@ mysql::ReadIterator mysql::deserialize(ReadIterator from, ReadIterator last, Han from = deserialize(from, last, output.auth_plugin_name); output.auth_plugin_data = auth_plugin_data_part_1; output.auth_plugin_data += auth_plugin_data_part_2; + output.auth_plugin_data.pop_back(); // includes a null byte at the end boost::endian::little_to_native_inplace(output.capability_falgs); return from; } -void mysql::serialize(DynamicBuffer& buffer, int_lenenc value) +void mysql::serialize(DynamicBuffer& buffer, const HandshakeResponse& value) { - if (value.value < 251) - { - serialize(buffer, static_cast(value.value)); - } - else if (value.value < 0x10000) - { - serialize(buffer, int1(0xfc)); - serialize(buffer, static_cast(value.value)); - } - else if (value.value < 0x1000000) - { - serialize(buffer, int1(0xfd)); - serialize(buffer, int3 {static_cast(value.value)}); - } - else - { - serialize(buffer, int1(0xfe)); - serialize(buffer, static_cast(value.value)); - } + serialize(buffer, value.client_flag); + serialize(buffer, value.max_packet_size); + serialize(buffer, value.character_set); + serialize(buffer, string_fixed<23>{}); // filler + serialize(buffer, value.username); + serialize(buffer, value.auth_response); + serialize(buffer, value.database); + serialize(buffer, value.client_plugin_name); } + +