From 91aea88ae266a30696f3d2b4050520f11d836c55 Mon Sep 17 00:00:00 2001 From: Kaitmazian Maksim <m.kaitmazian@picodata.io> Date: Tue, 23 May 2023 17:48:26 +0300 Subject: [PATCH] feat: implement md5 authentication It prevents password sniffing and avoids storing passwords on the server in plain text but provides no protection if an attacker manages to steal the password hash from the server. Usage example: ```lua -- Enable the md5 authentication method for all new users. box.cfg({auth_type = 'md5'}) -- Reset existing user passwords to use the md5 authentication method. box.schema.user.passwd('alice', 'topsecret') -- Authenticate using the md5 authentication method via net.box. conn = require('net.box').connect(uri, { user = 'alice', password = 'topsecret', -- Specifying the authentication method isn't strictly necessary: -- by default the client will use the method set in the remote -- server config (box.cfg.auth_type) auth_type = 'md5', }) ``` part of picodata/picodata/sbroad!377 @TarantoolBot document Title: md5 authentication method See the commit message. --- .../unreleased/support-md5-authentication | 5 + src/box/CMakeLists.txt | 1 + src/box/auth_md5.c | 245 ++++++++++++++ src/box/auth_md5.h | 23 ++ src/box/authentication.c | 9 +- src/lib/core/CMakeLists.txt | 3 + src/lib/core/crypt.c | 98 ++++++ src/lib/core/crypt.h | 44 +++ src/lib/core/cryptohash.c | 106 +++++++ src/lib/core/cryptohash.h | 57 ++++ src/lib/core/md5.c | 298 ++++++++++++++++++ src/lib/core/md5.h | 84 +++++ test/box-luatest/md5_auth_test.lua | 107 +++++++ 13 files changed, 1077 insertions(+), 3 deletions(-) create mode 100644 changelogs/unreleased/support-md5-authentication create mode 100644 src/box/auth_md5.c create mode 100644 src/box/auth_md5.h create mode 100644 src/lib/core/crypt.c create mode 100644 src/lib/core/crypt.h create mode 100644 src/lib/core/cryptohash.c create mode 100644 src/lib/core/cryptohash.h create mode 100644 src/lib/core/md5.c create mode 100644 src/lib/core/md5.h create mode 100644 test/box-luatest/md5_auth_test.lua diff --git a/changelogs/unreleased/support-md5-authentication b/changelogs/unreleased/support-md5-authentication new file mode 100644 index 0000000000..471456d423 --- /dev/null +++ b/changelogs/unreleased/support-md5-authentication @@ -0,0 +1,5 @@ +## feat/box +* Support md5 authentication method. + It prevents password sniffing and avoids storing passwords on the + server in plain text but provides no protection if an attacker + manages to steal the password hash from the server. diff --git a/src/box/CMakeLists.txt b/src/box/CMakeLists.txt index 0532fb1005..90164a0ea3 100644 --- a/src/box/CMakeLists.txt +++ b/src/box/CMakeLists.txt @@ -203,6 +203,7 @@ set(box_sources user.cc authentication.c auth_chap_sha1.c + auth_md5.c replication.cc recovery.cc xstream.cc diff --git a/src/box/auth_md5.c b/src/box/auth_md5.c new file mode 100644 index 0000000000..17ce39087c --- /dev/null +++ b/src/box/auth_md5.c @@ -0,0 +1,245 @@ +/* + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright 2010-2022, Tarantool AUTHORS, please see AUTHORS file. + */ +#include "auth_md5.h" + +#include <assert.h> +#include <stddef.h> +#include <stdint.h> +#include <stdlib.h> +#include <string.h> + +#include "authentication.h" +#include "crypt.h" +#include "diag.h" +#include "errcode.h" +#include "error.h" +#include "fiber.h" +#include "msgpuck.h" +#include "small/region.h" +#include "trivia/util.h" + +/** + * These are the core bits of the md5 authentication. + * The algorithm is the same as postgres md5 authentication: + * + * SERVER: shadow_pass = md5(password, user) + * salt = create_random_string() + * send(salt) + * + * CLIENT: recv(salt) + * shadow_pass = md5(password, user) + * client_pass = md5(shadow_pass, salt) + * send(client_pass) + * + * SERVER: recv(client_pass) + * check(client_pass == md5(shadow_pass, salt)) + */ + +#define AUTH_MD5_NAME "md5" + +/** md5 authenticator implementation. */ +struct auth_md5_authenticator { + /** Base class. */ + struct authenticator base; + /** md5(password, user). */ + char shadow_pass[MD5_PASSWD_LEN]; +}; + +/** + * Prepare a client password to send over the wire to the server + * for authentication. password may be not null-terminated. + * client_pass and salt sizes are regulated by MD5_PASSWD_LEN and MD5_SALT_LEN. + * Never fails. + */ +static void +client_password_prepare(char *client_pass, + const void *password, size_t password_len, + const char *user, + const char *salt) +{ + char shadow_pass[MD5_PASSWD_LEN]; + md5_encrypt(password, password_len, user, strlen(user), shadow_pass); + md5_encrypt(shadow_pass + strlen("md5"), MD5_PASSWD_LEN - strlen("md5"), + salt, MD5_SALT_LEN, client_pass); +} + +/** + * Verify a password. + * salt size must be at least MD5_SALT_LEN. + * + * @retval true passwords match + * @retval false passwords do not match or error occurred + */ +static bool +client_password_check(const char *client_pass, + const char *shadow_pass, + const char *salt) +{ + char candidate[MD5_PASSWD_LEN]; + /* Compute the correct answer for the MD5 challenge. */ + md5_encrypt(shadow_pass + strlen("md5"), MD5_PASSWD_LEN - strlen("md5"), + salt, MD5_SALT_LEN, candidate); + return memcmp(client_pass, candidate, MD5_PASSWD_LEN) == 0; +} + +/** auth_method::auth_method_delete */ +static void +auth_md5_delete(struct auth_method *method) +{ + TRASH(method); + free(method); +} + +/** auth_method::auth_data_prepare */ +static void +auth_md5_data_prepare(const struct auth_method *method, + const char *password, int password_len, + const char *user, + const char **auth_data, + const char **auth_data_end) +{ + (void)method; + struct region *region = &fiber()->gc; + size_t size = mp_sizeof_str(MD5_PASSWD_LEN); + char *p = xregion_alloc(region, size); + *auth_data = p; + *auth_data_end = p + size; + char *shadow_pass = mp_encode_strl(p, MD5_PASSWD_LEN); + + md5_encrypt(password, password_len, user, strlen(user), shadow_pass); +} + +/** auth_method::auth_request_prepare */ +static void +auth_md5_request_prepare(const struct auth_method *method, + const char *password, int password_len, + const char *user, + const char *salt, + const char **auth_request, + const char **auth_request_end) +{ + (void)method; + struct region *region = &fiber()->gc; + size_t size = mp_sizeof_str(MD5_PASSWD_LEN); + char *p = xregion_alloc(region, size); + *auth_request = p; + *auth_request_end = p + size; + char *client_password = mp_encode_strl(p, MD5_PASSWD_LEN); + client_password_prepare(client_password, + password, password_len, user, salt); +} + +/** auth_method::auth_request_check */ +static int +auth_md5_request_check(const struct auth_method *method, + const char *auth_request, + const char *auth_request_end) +{ + (void)method; + uint32_t client_pass_len; + if (mp_typeof(*auth_request) == MP_STR) { + client_pass_len = mp_decode_strl(&auth_request); + } else if (mp_typeof(*auth_request) == MP_BIN) { + /* + * Password is not a character stream, so some codecs + * automatically pack it as MP_BIN. + */ + client_pass_len = mp_decode_binl(&auth_request); + } else { + diag_set(ClientError, ER_INVALID_AUTH_REQUEST, + AUTH_MD5_NAME, "client password must be a string"); + return -1; + } + assert(auth_request + client_pass_len == auth_request_end); + (void)auth_request_end; + if (client_pass_len != MD5_PASSWD_LEN) { + diag_set(ClientError, ER_INVALID_AUTH_REQUEST, + AUTH_MD5_NAME, "invalid client password size"); + return -1; + } + return 0; +} + +/** auth_method::authenticator_new */ +static struct authenticator * +auth_md5_authenticator_new(const struct auth_method *method, + const char *auth_data, + const char *auth_data_end) +{ + if (mp_typeof(*auth_data) != MP_STR) { + diag_set(ClientError, ER_INVALID_AUTH_DATA, + AUTH_MD5_NAME, "password must be a string"); + return NULL; + } + + uint32_t shadow_pass_len; + const char *shadow_pass = mp_decode_str(&auth_data, &shadow_pass_len); + assert(auth_data == auth_data_end); + (void)auth_data_end; + + if (shadow_pass_len != MD5_PASSWD_LEN) { + diag_set(ClientError, ER_INVALID_AUTH_DATA, + AUTH_MD5_NAME, "invalid shadow password size"); + return NULL; + } + + struct auth_md5_authenticator *auth = xcalloc(1, sizeof(*auth)); + auth->base.method = method; + memcpy(auth->shadow_pass, shadow_pass, MD5_PASSWD_LEN); + return (struct authenticator *)auth; +} + +/** auth_method::authenticator_delete */ +static void +auth_md5_authenticator_delete(struct authenticator *auth_) +{ + struct auth_md5_authenticator *auth = + (struct auth_md5_authenticator *)auth_; + TRASH(auth); + free(auth); +} + +/** auth_method::authenticator_check_request */ +static bool +auth_md5_authenticate_request(const struct authenticator *auth_, + const char *salt, + const char *auth_request, + const char *auth_request_end) +{ + const struct auth_md5_authenticator *auth = + (const struct auth_md5_authenticator *)auth_; + uint32_t client_pass_len; + const char *client_pass; + if (mp_typeof(*auth_request) == MP_STR) { + client_pass = mp_decode_str(&auth_request, &client_pass_len); + } else if (mp_typeof(*auth_request) == MP_BIN) { + client_pass = mp_decode_bin(&auth_request, &client_pass_len); + } else { + unreachable(); + } + + assert(auth_request == auth_request_end); + (void)auth_request_end; + assert(client_pass_len == MD5_PASSWD_LEN); + return client_password_check(client_pass, auth->shadow_pass, salt); +} + +struct auth_method * +auth_md5_new(void) +{ + struct auth_method *method = xmalloc(sizeof(*method)); + method->name = AUTH_MD5_NAME; + method->flags = 0; + method->auth_method_delete = auth_md5_delete; + method->auth_method_delete = auth_md5_delete; + method->auth_data_prepare = auth_md5_data_prepare; + method->auth_request_prepare = auth_md5_request_prepare; + method->auth_request_check = auth_md5_request_check; + method->authenticator_new = auth_md5_authenticator_new; + method->authenticator_delete = auth_md5_authenticator_delete; + method->authenticate_request = auth_md5_authenticate_request; + return method; +} diff --git a/src/box/auth_md5.h b/src/box/auth_md5.h new file mode 100644 index 0000000000..1c32009cf4 --- /dev/null +++ b/src/box/auth_md5.h @@ -0,0 +1,23 @@ +/* + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright 2010-2022, Tarantool AUTHORS, please see AUTHORS file. + */ +#pragma once + +#if defined(__cplusplus) +extern "C" { +#endif /* defined(__cplusplus) */ + +struct auth_method; + +/** + * Allocates and initializes 'md5' authentication method. + * This function never fails. + */ +struct auth_method * +auth_md5_new(void); + +#if defined(__cplusplus) +} /* extern "C" */ +#endif /* defined(__cplusplus) */ diff --git a/src/box/authentication.c b/src/box/authentication.c index fadd4defdc..602e59d293 100644 --- a/src/box/authentication.c +++ b/src/box/authentication.c @@ -13,6 +13,7 @@ #include "assoc.h" #include "auth_chap_sha1.h" +#include "auth_md5.h" #include "base64.h" #include "diag.h" #include "errcode.h" @@ -169,9 +170,11 @@ void auth_init(void) { auth_methods = mh_strnptr_new(); - struct auth_method *method = auth_chap_sha1_new(); - AUTH_METHOD_DEFAULT = method; - auth_method_register(method); + struct auth_method *chap_sha1_method = auth_chap_sha1_new(); + AUTH_METHOD_DEFAULT = chap_sha1_method; + auth_method_register(chap_sha1_method); + struct auth_method *md5_method = auth_md5_new(); + auth_method_register(md5_method); } void diff --git a/src/lib/core/CMakeLists.txt b/src/lib/core/CMakeLists.txt index f9cfdabae2..9a32e1969e 100644 --- a/src/lib/core/CMakeLists.txt +++ b/src/lib/core/CMakeLists.txt @@ -45,6 +45,9 @@ set(core_sources mp_util.c cord_on_demand.cc tweaks.c + md5.c + cryptohash.c + crypt.c ) if (ENABLE_BACKTRACE) diff --git a/src/lib/core/crypt.c b/src/lib/core/crypt.c new file mode 100644 index 0000000000..412e70a8cb --- /dev/null +++ b/src/lib/core/crypt.c @@ -0,0 +1,98 @@ +/** + * PostgreSQL Database Management System + * (formerly known as Postgres, then as Postgres95) + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * + * Portions Copyright (c) 1994, The Regents of the University of California + * + * Permission to use, copy, modify, and distribute this software and its + * documentation for any purpose, without fee, and without a written agreement + * is hereby granted, provided that the above copyright notice and this + * paragraph and the following two paragraphs appear in all copies. + * + * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR + * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING + * LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS + * DOCUMENTATION, EVEN IF THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS + * ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATIONS TO + * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. + */ +#include "crypt.h" +#include "md5.h" +#include "cryptohash.h" +#include "trivia/util.h" +#include "diag.h" + +#include <string.h> + +/** Convert bytes to hex values. */ +static void +bytes_to_hex(uint8_t b[16], char *s) +{ + static const char *hex = "0123456789abcdef"; + int q, w; + + for (q = 0, w = 0; q < 16; q++) { + s[w++] = hex[(b[q] >> 4) & 0x0F]; + s[w++] = hex[b[q] & 0x0F]; + } + s[w] = '\0'; +} + +/** + * Calculates the MD5 sum of the bytes in a buffer. + * + * @param buff the buffer containing the bytes that you want the MD5 sum of. + * @param len number of bytes in the buffer. + * + * @param hexsum the MD5 sum as a '\0'-terminated string of + * hexadecimal digits. an MD5 sum is 16 bytes long. + * each byte is represented by two hexadecimal + * characters. you thus need to provide an array + * of 33 characters, including the trailing '\0'. + * errstr filled with a constant-string error message + * on failure return; NULL on success. + * + * @note MD5 is described in RFC 1321. + * + * @author Sverre H. Huseby <sverrehu@online.no> + * + */ +static void +md5_hash(const void *buff, size_t len, char *hexsum) +{ + uint8_t sum[MD5_DIGEST_LENGTH]; + + struct cryptohash_ctx *ctx = cryptohash_create(CRYPTOHASH_MD5); + cryptohash_init(ctx); + cryptohash_update(ctx, buff, len); + cryptohash_final(ctx, sum, sizeof(sum)); + bytes_to_hex(sum, hexsum); + cryptohash_free(ctx); +} + +void +md5_encrypt(const char *password, size_t password_len, + const char *salt, size_t salt_len, char *buf) +{ + assert(password_len + salt_len > 0); + + char *crypt_buf = xmalloc(password_len + salt_len); + /* + * Place salt at the end because it may be known by users trying to + * crack the MD5 output. + */ + memcpy(crypt_buf, password, password_len); + memcpy(crypt_buf + password_len, salt, salt_len); + + memcpy(buf, "md5", strlen("md5")); + md5_hash(crypt_buf, password_len + salt_len, buf + strlen("md5")); + + free(crypt_buf); +} diff --git a/src/lib/core/crypt.h b/src/lib/core/crypt.h new file mode 100644 index 0000000000..86122ee00a --- /dev/null +++ b/src/lib/core/crypt.h @@ -0,0 +1,44 @@ +/** + * PostgreSQL Database Management System + * (formerly known as Postgres, then as Postgres95) + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * + * Portions Copyright (c) 1994, The Regents of the University of California + * + * Permission to use, copy, modify, and distribute this software and its + * documentation for any purpose, without fee, and without a written agreement + * is hereby granted, provided that the above copyright notice and this + * paragraph and the following two paragraphs appear in all copies. + * + * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR + * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING + * LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS + * DOCUMENTATION, EVEN IF THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS + * ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATIONS TO + * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. + */ +#pragma once + +#include <stddef.h> +#include "md5.h" + +/** + * Computes MD5 checksum of a password followed by salt. + * + * @param password password to be encrypted (need not be null-terminated) + * @param password_len length of the password + * @param salt salt used for encryption (need not be null-terminated) + * @param salt_len len of salt string. May differ from MD5_SALT_LEN if + * user name is used as a salt when computing a shadow password. + * @param buf buffer for encrypted password with length of MD5_PASSWD_LEN. + * Output format is "md5" followed by a 32-hex-digit MD5 checksum. + */ +void +md5_encrypt(const char *password, size_t password_len, + const char *salt, size_t salt_len, char *buf); diff --git a/src/lib/core/cryptohash.c b/src/lib/core/cryptohash.c new file mode 100644 index 0000000000..d017ece27c --- /dev/null +++ b/src/lib/core/cryptohash.c @@ -0,0 +1,106 @@ +/** + * PostgreSQL Database Management System + * (formerly known as Postgres, then as Postgres95) + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * + * Portions Copyright (c) 1994, The Regents of the University of California + * + * Permission to use, copy, modify, and distribute this software and its + * documentation for any purpose, without fee, and without a written agreement + * is hereby granted, provided that the above copyright notice and this + * paragraph and the following two paragraphs appear in all copies. + * + * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR + * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING + * LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS + * DOCUMENTATION, EVEN IF THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS + * ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATIONS TO + * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. + */ +#include "cryptohash.h" +#include "md5.h" + +#include "trivia/util.h" + +/** + * Internal cryptohash_ctx structure. + * It may seem overcomplicated since only one method is supported at the moment, + * but more methods are planned to be added, for instance, SHA-256. + */ +struct cryptohash_ctx { + /* which algorithm to use */ + enum cryptohash_type type; + + /* algorithm context, depending on the type field */ + union { + /* md5 context */ + struct md5_ctx md5; + } data; +}; + +struct cryptohash_ctx * +cryptohash_create(enum cryptohash_type type) +{ + /* + * Note that this always allocates enough space for the largest hash. + * The small extra amount of memory does not make it worth complicating + * this code. + */ + struct cryptohash_ctx *ctx = xcalloc(1, sizeof(*ctx)); + ctx->type = type; + return ctx; +} + +void +cryptohash_init(struct cryptohash_ctx *ctx) +{ + switch (ctx->type) { + case CRYPTOHASH_MD5: + md5_init(&ctx->data.md5); + break; + default: + unreachable(); + } +} + +void +cryptohash_update(struct cryptohash_ctx *ctx, const uint8_t *data, size_t len) +{ + switch (ctx->type) { + case CRYPTOHASH_MD5: + md5_update(&ctx->data.md5, data, len); + break; + default: + unreachable(); + } +} + +void +cryptohash_final(struct cryptohash_ctx *ctx, uint8_t *dest, size_t len) +{ + (void)len; + switch (ctx->type) { + case CRYPTOHASH_MD5: + assert(len >= MD5_DIGEST_LENGTH); + md5_final(&ctx->data.md5, dest); + break; + default: + unreachable(); + } +} + +void +cryptohash_free(struct cryptohash_ctx *ctx) +{ + if (ctx == NULL) + return; + + TRASH(ctx); + free(ctx); +} diff --git a/src/lib/core/cryptohash.h b/src/lib/core/cryptohash.h new file mode 100644 index 0000000000..10269e2f62 --- /dev/null +++ b/src/lib/core/cryptohash.h @@ -0,0 +1,57 @@ +/** + * PostgreSQL Database Management System + * (formerly known as Postgres, then as Postgres95) + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * + * Portions Copyright (c) 1994, The Regents of the University of California + * + * Permission to use, copy, modify, and distribute this software and its + * documentation for any purpose, without fee, and without a written agreement + * is hereby granted, provided that the above copyright notice and this + * paragraph and the following two paragraphs appear in all copies. + * + * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR + * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING + * LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS + * DOCUMENTATION, EVEN IF THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS + * ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATIONS TO + * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. + */ +#pragma once + +#include <stdint.h> +#include <stddef.h> + +/** Type of cryptohash context. It is used by cryptohash_create. */ +enum cryptohash_type { + /** Context for MD5 algorithm. */ + CRYPTOHASH_MD5 = 0, +}; + +struct cryptohash_ctx; + +/* Allocate a cryptohash context. Never fails. */ +struct cryptohash_ctx * +cryptohash_create(enum cryptohash_type type); + +/** Initialize a cryptohash context. Never fails. */ +void +cryptohash_init(struct cryptohash_ctx *ctx); + +/** Update a cryptohash context. Never fails. */ +void +cryptohash_update(struct cryptohash_ctx *ctx, const uint8_t *data, size_t len); + +/** Finalize a cryptohash context. Never fails. */ +void +cryptohash_final(struct cryptohash_ctx *ctx, uint8_t *dest, size_t len); + +/** Free cryptohash context resources. */ +void +cryptohash_free(struct cryptohash_ctx *ctx); diff --git a/src/lib/core/md5.c b/src/lib/core/md5.c new file mode 100644 index 0000000000..1e98490937 --- /dev/null +++ b/src/lib/core/md5.c @@ -0,0 +1,298 @@ +/** + * PostgreSQL Database Management System + * (formerly known as Postgres, then as Postgres95) + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * + * Portions Copyright (c) 1994, The Regents of the University of California + * + * Permission to use, copy, modify, and distribute this software and its + * documentation for any purpose, without fee, and without a written agreement + * is hereby granted, provided that the above copyright notice and this + * paragraph and the following two paragraphs appear in all copies. + * + * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR + * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING + * LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS + * DOCUMENTATION, EVEN IF THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS + * ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATIONS TO + * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. + */ +#include "md5.h" + +#include "trivia/util.h" + +#define md5_n md5_count.md5_count64 +#define md5_n8 md5_count.md5_count8 +#define md5_sta md5_st.md5_state32[0] +#define md5_stb md5_st.md5_state32[1] +#define md5_stc md5_st.md5_state32[2] +#define md5_std md5_st.md5_state32[3] +#define md5_st8 md5_st.md5_state8 + +#define SHIFT(X, s) (((X) << (s)) | ((X) >> (32 - (s)))) + +#define F(X, Y, Z) (((X) & (Y)) | ((~X) & (Z))) +#define G(X, Y, Z) (((X) & (Z)) | ((Y) & (~(Z)))) +#define H(X, Y, Z) ((X) ^ (Y) ^ (Z)) +#define I(X, Y, Z) ((Y) ^ ((X) | (~(Z)))) + +#define ROUND1(a, b, c, d, k, s, i) \ +do { \ + (a) = (a) + F((b), (c), (d)) + X[(k)] + T[(i)]; \ + (a) = SHIFT((a), (s)); \ + (a) = (a) + (b); \ +} while (0) + +#define ROUND2(a, b, c, d, k, s, i) \ +do { \ + (a) = (a) + G((b), (c), (d)); \ + (a) = (a) + X[(k)]; \ + (a) = (a) + T[(i)]; \ + (a) = SHIFT((a), (s)); \ + (a) = (a) + (b); \ +} while (0) + +#define ROUND3(a, b, c, d, k, s, i) \ +do { \ + (a) = (a) + H((b), (c), (d)); \ + (a) = (a) + X[(k)]; \ + (a) = (a) + T[(i)]; \ + (a) = SHIFT((a), (s)); \ + (a) = (a) + (b); \ +} while (0) + +#define ROUND4(a, b, c, d, k, s, i) \ +do { \ + (a) = (a) + I((b), (c), (d)); \ + (a) = (a) + X[(k)]; \ + (a) = (a) + T[(i)]; \ + (a) = SHIFT((a), (s)); \ + (a) = (a) + (b); \ +} while (0) + +#define Sa 7 +#define Sb 12 +#define Sc 17 +#define Sd 22 + +#define Se 5 +#define Sf 9 +#define Sg 14 +#define Sh 20 + +#define Si 4 +#define Sj 11 +#define Sk 16 +#define Sl 23 + +#define Sm 6 +#define Sn 10 +#define So 15 +#define Sp 21 + +#define MD5_A0 0x67452301 +#define MD5_B0 0xefcdab89 +#define MD5_C0 0x98badcfe +#define MD5_D0 0x10325476 + +/* Integer part of 4294967296 times abs(sin(i)), where i is in radians. */ +static const uint32_t T[65] = { + 0, + 0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, + 0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501, + 0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be, + 0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821, + + 0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa, + 0xd62f105d, 0x2441453, 0xd8a1e681, 0xe7d3fbc8, + 0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed, + 0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a, + + 0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c, + 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70, + 0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x4881d05, + 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665, + + 0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039, + 0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1, + 0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1, + 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391, +}; + +static const uint8_t md5_paddat[MD5_BUFLEN] = { + 0x80, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, +}; + +static_assert(__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__, + "md5 hash calculation only supports little endian architecture"); + +/** Calculate a single md5 hash interation. */ +static void +md5_calc(const uint8_t *b64, struct md5_ctx *ctx) +{ + uint32_t A = ctx->md5_sta; + uint32_t B = ctx->md5_stb; + uint32_t C = ctx->md5_stc; + uint32_t D = ctx->md5_std; + + const uint32_t *X = (const uint32_t *)b64; + ROUND1(A, B, C, D, 0, Sa, 1); + ROUND1(D, A, B, C, 1, Sb, 2); + ROUND1(C, D, A, B, 2, Sc, 3); + ROUND1(B, C, D, A, 3, Sd, 4); + ROUND1(A, B, C, D, 4, Sa, 5); + ROUND1(D, A, B, C, 5, Sb, 6); + ROUND1(C, D, A, B, 6, Sc, 7); + ROUND1(B, C, D, A, 7, Sd, 8); + ROUND1(A, B, C, D, 8, Sa, 9); + ROUND1(D, A, B, C, 9, Sb, 10); + ROUND1(C, D, A, B, 10, Sc, 11); + ROUND1(B, C, D, A, 11, Sd, 12); + ROUND1(A, B, C, D, 12, Sa, 13); + ROUND1(D, A, B, C, 13, Sb, 14); + ROUND1(C, D, A, B, 14, Sc, 15); + ROUND1(B, C, D, A, 15, Sd, 16); + + ROUND2(A, B, C, D, 1, Se, 17); + ROUND2(D, A, B, C, 6, Sf, 18); + ROUND2(C, D, A, B, 11, Sg, 19); + ROUND2(B, C, D, A, 0, Sh, 20); + ROUND2(A, B, C, D, 5, Se, 21); + ROUND2(D, A, B, C, 10, Sf, 22); + ROUND2(C, D, A, B, 15, Sg, 23); + ROUND2(B, C, D, A, 4, Sh, 24); + ROUND2(A, B, C, D, 9, Se, 25); + ROUND2(D, A, B, C, 14, Sf, 26); + ROUND2(C, D, A, B, 3, Sg, 27); + ROUND2(B, C, D, A, 8, Sh, 28); + ROUND2(A, B, C, D, 13, Se, 29); + ROUND2(D, A, B, C, 2, Sf, 30); + ROUND2(C, D, A, B, 7, Sg, 31); + ROUND2(B, C, D, A, 12, Sh, 32); + + ROUND3(A, B, C, D, 5, Si, 33); + ROUND3(D, A, B, C, 8, Sj, 34); + ROUND3(C, D, A, B, 11, Sk, 35); + ROUND3(B, C, D, A, 14, Sl, 36); + ROUND3(A, B, C, D, 1, Si, 37); + ROUND3(D, A, B, C, 4, Sj, 38); + ROUND3(C, D, A, B, 7, Sk, 39); + ROUND3(B, C, D, A, 10, Sl, 40); + ROUND3(A, B, C, D, 13, Si, 41); + ROUND3(D, A, B, C, 0, Sj, 42); + ROUND3(C, D, A, B, 3, Sk, 43); + ROUND3(B, C, D, A, 6, Sl, 44); + ROUND3(A, B, C, D, 9, Si, 45); + ROUND3(D, A, B, C, 12, Sj, 46); + ROUND3(C, D, A, B, 15, Sk, 47); + ROUND3(B, C, D, A, 2, Sl, 48); + + ROUND4(A, B, C, D, 0, Sm, 49); + ROUND4(D, A, B, C, 7, Sn, 50); + ROUND4(C, D, A, B, 14, So, 51); + ROUND4(B, C, D, A, 5, Sp, 52); + ROUND4(A, B, C, D, 12, Sm, 53); + ROUND4(D, A, B, C, 3, Sn, 54); + ROUND4(C, D, A, B, 10, So, 55); + ROUND4(B, C, D, A, 1, Sp, 56); + ROUND4(A, B, C, D, 8, Sm, 57); + ROUND4(D, A, B, C, 15, Sn, 58); + ROUND4(C, D, A, B, 6, So, 59); + ROUND4(B, C, D, A, 13, Sp, 60); + ROUND4(A, B, C, D, 4, Sm, 61); + ROUND4(D, A, B, C, 11, Sn, 62); + ROUND4(C, D, A, B, 2, So, 63); + ROUND4(B, C, D, A, 9, Sp, 64); + + ctx->md5_sta += A; + ctx->md5_stb += B; + ctx->md5_stc += C; + ctx->md5_std += D; +} + +/** Add padding to md5 buffer. */ +static void +md5_pad(struct md5_ctx *ctx) +{ + unsigned int gap; + + /* Don't count up padding. Keep md5_n. */ + gap = MD5_BUFLEN - ctx->md5_i; + if (gap > 8) { + memmove(ctx->md5_buf + ctx->md5_i, md5_paddat, + gap - sizeof(ctx->md5_n)); + } else { + /* including gap == 8 */ + memmove(ctx->md5_buf + ctx->md5_i, md5_paddat, gap); + md5_calc(ctx->md5_buf, ctx); + memmove(ctx->md5_buf, md5_paddat + gap, + MD5_BUFLEN - sizeof(ctx->md5_n)); + } + + /* 8 byte word */ + memmove(&ctx->md5_buf[56], &ctx->md5_n8[0], 8); + + md5_calc(ctx->md5_buf, ctx); +} + +static void +md5_result(uint8_t *digest, struct md5_ctx *ctx) +{ + /* 4 byte words */ + memmove(digest, &ctx->md5_st8[0], 16); +} + +void +md5_init(struct md5_ctx *ctx) +{ + ctx->md5_n = 0; + ctx->md5_i = 0; + ctx->md5_sta = MD5_A0; + ctx->md5_stb = MD5_B0; + ctx->md5_stc = MD5_C0; + ctx->md5_std = MD5_D0; + memset(ctx->md5_buf, 0, sizeof(ctx->md5_buf)); +} + +void +md5_update(struct md5_ctx *ctx, const uint8_t *data, size_t len) +{ + unsigned int gap, i; + + ctx->md5_n += len * 8; /* byte to bit */ + gap = MD5_BUFLEN - ctx->md5_i; + + if (len >= gap) { + memmove(ctx->md5_buf + ctx->md5_i, data, gap); + md5_calc(ctx->md5_buf, ctx); + + for (i = gap; i + MD5_BUFLEN <= len; i += MD5_BUFLEN) + md5_calc(data + i, ctx); + + ctx->md5_i = len - i; + memmove(ctx->md5_buf, data + i, ctx->md5_i); + } else { + memmove(ctx->md5_buf + ctx->md5_i, data, len); + ctx->md5_i += len; + } +} + +void +md5_final(struct md5_ctx *ctx, uint8_t *dest) +{ + md5_pad(ctx); + md5_result(dest, ctx); +} diff --git a/src/lib/core/md5.h b/src/lib/core/md5.h new file mode 100644 index 0000000000..03ae0f1705 --- /dev/null +++ b/src/lib/core/md5.h @@ -0,0 +1,84 @@ +/** + * PostgreSQL Database Management System + * (formerly known as Postgres, then as Postgres95) + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * + * Portions Copyright (c) 1994, The Regents of the University of California + * + * Permission to use, copy, modify, and distribute this software and its + * documentation for any purpose, without fee, and without a written agreement + * is hereby granted, provided that the above copyright notice and this + * paragraph and the following two paragraphs appear in all copies. + * + * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR + * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING + * LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS + * DOCUMENTATION, EVEN IF THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS + * ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATIONS TO + * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. + */ +#pragma once + +#include <stdint.h> +#include <stddef.h> + +#include "box/authentication.h" /* for AUTH_SALT_SIZE */ + +enum { + /* len of a buffer used by md5 hash context. */ + MD5_BUFLEN = 64, + /* Size of result generated by MD5 computation */ + MD5_DIGEST_LENGTH = 16, + /** + * Length of a buffer that can store a password hashed by md5 algorithm. + * "md5" + 32-hex-digit MD5 checksum. + */ + MD5_PASSWD_LEN = 3 + 32, + /** length of salt sent to remote side. */ + MD5_SALT_LEN = 4 +}; + +/** Allowed symbols set in md5 checksum. */ +#define MD5_PASSWD_CHARSET "0123456789abcdef" + +static_assert((int)MD5_SALT_LEN <= (int)AUTH_SALT_SIZE, + "MD5 password salt is too big"); + +/* Context data for MD5 */ +struct md5_ctx { + /** Current md5 state. */ + union { + uint32_t md5_state32[4]; + uint8_t md5_state8[16]; + } md5_st; + + /** md5 counter */ + union { + /** counter as a 64-bit integer */ + uint64_t md5_count64; + /** counter as an array of 1-byte integers */ + uint8_t md5_count8[8]; + } md5_count; + + /** processed len of the buffer */ + unsigned int md5_i; + uint8_t md5_buf[MD5_BUFLEN]; +}; + +/** Initialize a MD5 context. */ +void +md5_init(struct md5_ctx *ctx); + +/** Update a MD5 context. */ +void +md5_update(struct md5_ctx *ctx, const uint8_t *data, size_t len); + +/** Finalize a MD5 context. */ +void +md5_final(struct md5_ctx *ctx, uint8_t *dest); diff --git a/test/box-luatest/md5_auth_test.lua b/test/box-luatest/md5_auth_test.lua new file mode 100644 index 0000000000..cbd813d199 --- /dev/null +++ b/test/box-luatest/md5_auth_test.lua @@ -0,0 +1,107 @@ +local net = require('net.box') +local server = require('luatest.server') +local t = require('luatest') +local urilib = require('uri') + +local g = t.group() + +g.before_all(function(cg) + cg.server = server:new({alias = 'master'}) + cg.server:start() + cg.server:exec(function() + box.cfg{auth_type = 'md5'} + box.schema.user.create('test', {password = 'secret'}) + box.session.su('admin', box.schema.user.grant, 'test', 'super') + end) +end) + +g.after_all(function(cg) + cg.server:drop() +end) + +g.test_box_cfg = function(cg) + cg.server:exec(function() + t.assert_equals(box.cfg.auth_type, 'md5') + t.assert_error_msg_equals( + "Incorrect value for option 'auth_type': should be of type string", + box.cfg, {auth_type = 42}) + t.assert_error_msg_equals( + "Incorrect value for option 'auth_type': md55", + box.cfg, {auth_type = 'md55'}) + end) +end + +g.test_net_box = function(cg) + local parsed_uri = urilib.parse(cg.server.net_box_uri) + parsed_uri.login = 'test' + parsed_uri.password = 'secret' + parsed_uri.params = parsed_uri.params or {} + parsed_uri.params.auth_type = {'md6'} + local uri = urilib.format(parsed_uri, true) + t.assert_error_msg_equals( + "Unknown authentication method 'md6'", + net.connect, uri) + parsed_uri.params.auth_type = {'md5'} + uri = urilib.format(parsed_uri, true) + local conn = net.connect(cg.server.net_box_uri, uri) + t.assert_equals(conn.error, nil) + conn:close() + t.assert_error_msg_equals( + "Unknown authentication method 'md6'", + net.connect, uri, {auth_type = 'md6'}) + t.assert_error_msg_equals( + "Unknown authentication method 'md6'", + net.connect, cg.server.net_box_uri, { + user = 'test', + password = 'secret', + auth_type = 'md6', + }) + conn = net.connect(cg.server.net_box_uri, { + user = 'test', + password = 'secret', + auth_type = 'md5', + }) + t.assert_equals(conn.error, nil) + conn:close() + conn = net.connect(cg.server.net_box_uri, { + user = 'test', + password = 'not-secret', + auth_type = 'md5', + }) + t.assert_not_equals(conn.error, nil) +end + +g.before_test('test_replication', function(cg) + cg.replica = server:new({ + alias = 'replica', + box_cfg = { + replication = server.build_listen_uri('master', cg.server.id), + }, + }) + cg.replica:start() +end) + +g.after_test('test_replication', function(cg) + cg.replica:drop() +end) + +g.test_replication = function(cg) + cg.replica:exec(function(uri) + local urilib = require('uri') + local parsed_uri = urilib.parse(uri) + parsed_uri.login = 'test' + parsed_uri.password = 'secret' + parsed_uri.params = parsed_uri.params or {} + parsed_uri.params.auth_type = {'md6'} + uri = urilib.format(parsed_uri, true) + box.cfg({replication = {}}) + t.assert_error_msg_matches( + "Incorrect value for option 'replication': " .. + "bad URI '.*%?auth_type=md6': " .. + "unknown authentication method", + box.cfg, {replication = uri}) + parsed_uri.params.auth_type = {'md5'} + uri = urilib.format(parsed_uri, true) + box.cfg({replication = uri}) + end, {server.build_listen_uri('master')}) +end -- GitLab