diff --git a/changelogs/unreleased/support-md5-authentication b/changelogs/unreleased/support-md5-authentication new file mode 100644 index 0000000000000000000000000000000000000000..471456d423e365d46c7f449d767941a87cbd1efe --- /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 0532fb10059888859539fc23decaa84726fa5dcb..90164a0ea31a785769a983aa8796ec94dd42a4a3 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 0000000000000000000000000000000000000000..17ce39087ccb323cd098b0088d00b416a6cf4f53 --- /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 0000000000000000000000000000000000000000..1c32009cf4b864e4fa509e41c84c1889412d027e --- /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 fadd4defdc251460202419599c6d5d0170c55776..602e59d2934eb1a039f3d0e7f749e60d066d0b7b 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 f9cfdabae24ee724a983cae87b357eb3bf44ef32..9a32e1969e1e26ba67c30874197fbdb3a320893b 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 0000000000000000000000000000000000000000..412e70a8cbea52f4d6f49f0e926d9ffa01fc5a97 --- /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 0000000000000000000000000000000000000000..86122ee00a86b8cb56a59c966e49c72134f70503 --- /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 0000000000000000000000000000000000000000..d017ece27c18a4045a2dbfc6f1b217a660bb4f8a --- /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 0000000000000000000000000000000000000000..10269e2f627e283f67f084f873b2f37e741dae2c --- /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 0000000000000000000000000000000000000000..1e98490937739397e7ba4a160b1ebd9ebf6f0d74 --- /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 0000000000000000000000000000000000000000..03ae0f1705ee608c49bd3ac77fb6a05ccbe11b39 --- /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 0000000000000000000000000000000000000000..cbd813d1998f31e9397d7b53db09ffe61682228d --- /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