diff --git a/extra/exports b/extra/exports index ad930e0cde77bfd1a62c3b6b7a4924bb4c07a582..5094a93caa3785fe46710f49c5abbcf638dea231 100644 --- a/extra/exports +++ b/extra/exports @@ -415,7 +415,6 @@ lua_upvaluejoin lua_xmove lua_yield memtx_tx_story_gc_step -password_prepare PMurHash32 PMurHash32_Process PMurHash32_Result diff --git a/src/box/CMakeLists.txt b/src/box/CMakeLists.txt index 2c566f2066706f65d2b1ba42439e94dc21412b99..7e98ea664686375ddc4852db7d962bea65cb6163 100644 --- a/src/box/CMakeLists.txt +++ b/src/box/CMakeLists.txt @@ -206,6 +206,7 @@ set(box_sources user_def.c user.cc authentication.c + auth_chap_sha1.c replication.cc recovery.cc xstream.cc diff --git a/src/box/alter.cc b/src/box/alter.cc index 83703742248b23ce058524b1e9cced1cd133dcf5..5a290a2829de3ac7864ae02be347103723cda47e 100644 --- a/src/box/alter.cc +++ b/src/box/alter.cc @@ -59,6 +59,7 @@ #include "constraint_id.h" #include "space_upgrade.h" #include "box.h" +#include "authentication.h" /* {{{ Auxiliary functions and methods. */ @@ -2883,11 +2884,9 @@ user_has_data(struct user *user, bool *has_data) } /** - * Supposedly a user may have many authentication mechanisms - * defined, but for now we only support chap-sha1. Get - * password of chap-sha1 from the _user space. + * Initialize the user authenticator from the _user space data. */ -int +static int user_def_fill_auth_data(struct user_def *user, const char *auth_data) { uint8_t type = mp_typeof(*auth_data); @@ -2908,35 +2907,36 @@ user_def_fill_auth_data(struct user_def *user, const char *auth_data) "use box.schema.user.passwd() to reset password"); return -1; } - uint32_t mech_count = mp_decode_map(&auth_data); - for (uint32_t i = 0; i < mech_count; i++) { + uint32_t method_count = mp_decode_map(&auth_data); + for (uint32_t i = 0; i < method_count; i++) { if (mp_typeof(*auth_data) != MP_STR) { mp_next(&auth_data); mp_next(&auth_data); continue; } - uint32_t len; - const char *mech_name = mp_decode_str(&auth_data, &len); - if (strncasecmp(mech_name, "chap-sha1", 9) != 0) { - mp_next(&auth_data); + uint32_t method_name_len; + const char *method_name = mp_decode_str(&auth_data, + &method_name_len); + const char *auth_data_end = auth_data; + mp_next(&auth_data_end); + const struct auth_method *method = auth_method_by_name( + method_name, method_name_len); + if (method == NULL) { + auth_data = auth_data_end; continue; } - const char *hash2_base64 = mp_decode_str(&auth_data, &len); - if (len != 0 && len != SCRAMBLE_BASE64_SIZE) { - diag_set(ClientError, ER_CREATE_USER, - user->name, "invalid user password"); + struct authenticator *auth = authenticator_new( + method, auth_data, auth_data_end); + if (auth == NULL) + return -1; + /* The guest user may only have an empty password. */ + if (user->uid == GUEST && + !authenticate_password(auth, "", 0)) { + authenticator_delete(auth); + diag_set(ClientError, ER_GUEST_USER_PASSWORD); return -1; } - if (user->uid == GUEST) { - /** Guest user is permitted to have empty password */ - if (strncmp(hash2_base64, CHAP_SHA1_EMPTY_PASSWORD, len)) { - diag_set(ClientError, ER_GUEST_USER_PASSWORD); - return -1; - } - } - - base64_decode(hash2_base64, len, user->hash2, - sizeof(user->hash2)); + user->auth = auth; break; } return 0; @@ -2981,9 +2981,8 @@ user_def_new_from_tuple(struct tuple *tuple) * Check for trivial errors when a plain text * password is saved in this field instead. */ - if (tuple_field_count(tuple) > BOX_USER_FIELD_AUTH_MECH_LIST) { - const char *auth_data = - tuple_field(tuple, BOX_USER_FIELD_AUTH_MECH_LIST); + if (tuple_field_count(tuple) > BOX_USER_FIELD_AUTH) { + const char *auth_data = tuple_field(tuple, BOX_USER_FIELD_AUTH); const char *tmp = auth_data; bool is_auth_empty; if (mp_typeof(*auth_data) == MP_ARRAY && diff --git a/src/box/applier.cc b/src/box/applier.cc index ea144296cfe4bde5f51b03700f1edf54b28840fd..69c3a3b529827521c689903d0bdb0561f5c004c4 100644 --- a/src/box/applier.cc +++ b/src/box/applier.cc @@ -32,6 +32,7 @@ #include <msgpuck.h> +#include "authentication.h" #include "xlog.h" #include "fiber.h" #include "fiber_cond.h" @@ -429,11 +430,17 @@ applier_connect(struct applier *applier) /* Authenticate */ applier_set_state(applier, APPLIER_AUTH); + const char *password = uri->password; + if (password == NULL) + password = ""; RegionGuard region_guard(&fiber()->gc); - xrow_encode_auth(&row, greeting.salt, greeting.salt_len, uri->login, - strlen(uri->login), - uri->password != NULL ? uri->password : "", - uri->password != NULL ? strlen(uri->password) : 0); + const struct auth_method *method = AUTH_METHOD_DEFAULT; + const char *auth_request, *auth_request_end; + auth_request_prepare(method, password, strlen(password), greeting.salt, + &auth_request, &auth_request_end); + xrow_encode_auth(&row, uri->login, strlen(uri->login), + method->name, strlen(method->name), + auth_request, auth_request_end); coio_write_xrow(io, &row); coio_read_xrow(io, ibuf, &row); applier->last_row_time = ev_monotonic_now(loop()); diff --git a/src/box/auth_chap_sha1.c b/src/box/auth_chap_sha1.c new file mode 100644 index 0000000000000000000000000000000000000000..a593c7ab3a6c108fa860ec4de7f0047f5f6ba198 --- /dev/null +++ b/src/box/auth_chap_sha1.c @@ -0,0 +1,186 @@ +/* + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright 2010-2022, Tarantool AUTHORS, please see AUTHORS file. + */ +#include "auth_chap_sha1.h" + +#include <assert.h> +#include <stddef.h> +#include <stdint.h> +#include <stdlib.h> + +#include "authentication.h" +#include "base64.h" +#include "diag.h" +#include "errcode.h" +#include "error.h" +#include "fiber.h" +#include "msgpuck.h" +#include "scramble.h" +#include "small/region.h" +#include "trivia/util.h" + +#define AUTH_CHAP_SHA1_NAME "chap-sha1" + +/** chap-sha1 authenticator implementation. */ +struct auth_chap_sha1_authenticator { + /** Base class. */ + struct authenticator base; + /** sha1(sha1(password)). */ + char hash2[SCRAMBLE_SIZE]; +}; + +/** auth_method::auth_method_delete */ +static void +auth_chap_sha1_delete(struct auth_method *method) +{ + TRASH(method); + free(method); +} + +/** auth_method::auth_data_prepare */ +static void +auth_chap_sha1_data_prepare(const struct auth_method *method, + const char *password, int password_len, + const char **auth_data, + const char **auth_data_end) +{ + (void)method; + struct region *region = &fiber()->gc; + size_t size = mp_sizeof_str(SCRAMBLE_BASE64_SIZE); + char *p = xregion_alloc(region, size); + *auth_data = p; + *auth_data_end = p + size; + p = mp_encode_strl(p, SCRAMBLE_BASE64_SIZE); + password_prepare(password, password_len, p, SCRAMBLE_BASE64_SIZE); +} + +/** auth_method::auth_request_prepare */ +static void +auth_chap_sha1_request_prepare(const struct auth_method *method, + const char *password, int password_len, + 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(SCRAMBLE_SIZE); + char *p = xregion_alloc(region, size); + *auth_request = p; + *auth_request_end = p + size; + p = mp_encode_strl(p, SCRAMBLE_SIZE); + scramble_prepare(p, salt, password, password_len); +} + +/** auth_method::auth_request_check */ +static int +auth_chap_sha1_request_check(const struct auth_method *method, + const char *auth_request, + const char *auth_request_end) +{ + (void)method; + uint32_t scramble_len; + if (mp_typeof(*auth_request) == MP_STR) { + scramble_len = mp_decode_strl(&auth_request); + } else if (mp_typeof(*auth_request) == MP_BIN) { + /* + * Scramble is not a character stream, so some codecs + * automatically pack it as MP_BIN. + */ + scramble_len = mp_decode_binl(&auth_request); + } else { + diag_set(ClientError, ER_INVALID_AUTH_REQUEST, + AUTH_CHAP_SHA1_NAME, "scramble must be string"); + return -1; + } + assert(auth_request + scramble_len == auth_request_end); + (void)auth_request_end; + if (scramble_len != SCRAMBLE_SIZE) { + diag_set(ClientError, ER_INVALID_AUTH_REQUEST, + AUTH_CHAP_SHA1_NAME, "invalid scramble size"); + return -1; + } + return 0; +} + +/** auth_method::authenticator_new */ +static struct authenticator * +auth_chap_sha1_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_CHAP_SHA1_NAME, "scramble must be string"); + return NULL; + } + uint32_t hash2_base64_len; + const char *hash2_base64 = mp_decode_str(&auth_data, + &hash2_base64_len); + assert(auth_data == auth_data_end); + (void)auth_data_end; + if (hash2_base64_len != SCRAMBLE_BASE64_SIZE) { + diag_set(ClientError, ER_INVALID_AUTH_DATA, + AUTH_CHAP_SHA1_NAME, "invalid scramble size"); + return NULL; + } + struct auth_chap_sha1_authenticator *auth = xmalloc(sizeof(*auth)); + auth->base.method = method; + int hash2_len = base64_decode(hash2_base64, hash2_base64_len, + auth->hash2, sizeof(auth->hash2)); + assert(hash2_len == sizeof(auth->hash2)); + (void)hash2_len; + return (struct authenticator *)auth; +} + +/** auth_method::authenticator_delete */ +static void +auth_chap_sha1_authenticator_delete(struct authenticator *auth_) +{ + struct auth_chap_sha1_authenticator *auth = + (struct auth_chap_sha1_authenticator *)auth_; + TRASH(auth); + free(auth); +} + +/** auth_method::authenticator_check_request */ +static bool +auth_chap_sha1_authenticate_request(const struct authenticator *auth_, + const char *salt, + const char *auth_request, + const char *auth_request_end) +{ + const struct auth_chap_sha1_authenticator *auth = + (const struct auth_chap_sha1_authenticator *)auth_; + uint32_t scramble_len; + const char *scramble; + if (mp_typeof(*auth_request) == MP_STR) { + scramble = mp_decode_str(&auth_request, &scramble_len); + } else if (mp_typeof(*auth_request) == MP_BIN) { + scramble = mp_decode_bin(&auth_request, &scramble_len); + } else { + unreachable(); + } + assert(auth_request == auth_request_end); + (void)auth_request_end; + assert(scramble_len == SCRAMBLE_SIZE); + (void)scramble_len; + return scramble_check(scramble, salt, auth->hash2) == 0; +} + +struct auth_method * +auth_chap_sha1_new(void) +{ + struct auth_method *method = xmalloc(sizeof(*method)); + method->name = AUTH_CHAP_SHA1_NAME; + method->auth_method_delete = auth_chap_sha1_delete; + method->auth_data_prepare = auth_chap_sha1_data_prepare; + method->auth_request_prepare = auth_chap_sha1_request_prepare; + method->auth_request_check = auth_chap_sha1_request_check; + method->authenticator_new = auth_chap_sha1_authenticator_new; + method->authenticator_delete = auth_chap_sha1_authenticator_delete; + method->authenticate_request = auth_chap_sha1_authenticate_request; + return method; +} diff --git a/src/box/auth_chap_sha1.h b/src/box/auth_chap_sha1.h new file mode 100644 index 0000000000000000000000000000000000000000..9f1ec25155d4b8d00cab53a61178c1b880926da5 --- /dev/null +++ b/src/box/auth_chap_sha1.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 'chap-sha1' authentication method. + * This function never fails. + */ +struct auth_method * +auth_chap_sha1_new(void); + +#if defined(__cplusplus) +} /* extern "C" */ +#endif /* defined(__cplusplus) */ diff --git a/src/box/authentication.c b/src/box/authentication.c index ad330ecaf22ba08847f536fb999ad7637e346a98..454b026c8054b11bf0c98454df1cdcd000b858a7 100644 --- a/src/box/authentication.c +++ b/src/box/authentication.c @@ -5,21 +5,54 @@ */ #include "authentication.h" +#include <assert.h> #include <stdbool.h> #include <stdint.h> #include <stddef.h> #include <string.h> +#include "assoc.h" +#include "auth_chap_sha1.h" #include "base64.h" #include "diag.h" #include "errcode.h" #include "error.h" +#include "fiber.h" #include "msgpuck.h" #include "scramble.h" #include "session.h" +#include "small/region.h" +#include "tt_static.h" #include "user.h" #include "user_def.h" +const struct auth_method *AUTH_METHOD_DEFAULT; + +/** Map of all registered authentication methods: name -> auth_method. */ +static struct mh_strnptr_t *auth_methods = NULL; + +bool +authenticate_password(const struct authenticator *auth, + const char *password, int password_len) +{ + /* + * We don't really need to zero the salt here, because any salt would + * do as long as we use the same salt in auth_request_prepare and + * authenticate_request. We zero it solely to avoid address sanitizer + * complaints about usage of uninitialized memory. + */ + const char salt[SCRAMBLE_SIZE] = {0}; + struct region *region = &fiber()->gc; + size_t region_svp = region_used(region); + const char *auth_request, *auth_request_end; + auth_request_prepare(auth->method, password, password_len, salt, + &auth_request, &auth_request_end); + bool ret = authenticate_request(auth, salt, auth_request, + auth_request_end); + region_truncate(region, region_svp); + return ret; +} + int authenticate(const char *user_name, uint32_t user_name_len, const char *salt, const char *tuple) @@ -37,46 +70,42 @@ authenticate(const char *user_name, uint32_t user_name_len, * to prevent user enumeration by analyzing error codes. */ diag_clear(diag_get()); - uint32_t part_count; - uint32_t scramble_len; - const char *scramble; /* * Allow authenticating back to the guest user without a password, * because the guest user isn't allowed to have a password, anyway. * This is useful for connection pooling. */ - part_count = mp_decode_array(&tuple); + uint32_t part_count = mp_decode_array(&tuple); if (part_count == 0 && user != NULL && user->def->uid == GUEST) goto ok; - + /* Expected: authentication method name and data. */ if (part_count < 2) { - /* Expected at least: authentication mechanism and data. */ diag_set(ClientError, ER_INVALID_MSGPACK, "authentication request body"); return -1; } - mp_next(&tuple); /* Skip authentication mechanism. */ - if (mp_typeof(*tuple) == MP_STR) { - scramble = mp_decode_str(&tuple, &scramble_len); - } else if (mp_typeof(*tuple) == MP_BIN) { - /* - * scramble is not a character stream, so some - * codecs automatically pack it as MP_BIN - */ - scramble = mp_decode_bin(&tuple, &scramble_len); - } else { + if (mp_typeof(*tuple) != MP_STR) { diag_set(ClientError, ER_INVALID_MSGPACK, - "authentication scramble"); + "authentication request body"); return -1; } - if (scramble_len != SCRAMBLE_SIZE) { - /* Authentication mechanism, data. */ - diag_set(ClientError, ER_INVALID_MSGPACK, - "invalid scramble size"); + uint32_t method_name_len; + const char *method_name = mp_decode_str(&tuple, &method_name_len); + const struct auth_method *method = auth_method_by_name( + method_name, method_name_len); + if (method == NULL) { + diag_set(ClientError, ER_UNKNOWN_AUTH_METHOD, + tt_cstr(method_name, method_name_len)); return -1; } - if (user == NULL || - scramble_check(scramble, salt, user->def->hash2) != 0) { + const char *auth_request = tuple; + const char *auth_request_end = tuple; + mp_next(&auth_request_end); + if (auth_request_check(method, auth_request, auth_request_end) != 0) + return -1; + if (user == NULL || user->def->auth == NULL || + !authenticate_request(user->def->auth, salt, + auth_request, auth_request_end)) { auth_res.is_authenticated = false; if (session_run_on_auth_triggers(&auth_res) != 0) return -1; @@ -93,3 +122,48 @@ authenticate(const char *user_name, uint32_t user_name_len, credentials_reset(¤t_session()->credentials, user); return 0; } + +const struct auth_method * +auth_method_by_name(const char *name, uint32_t name_len) +{ + struct mh_strnptr_t *h = auth_methods; + mh_int_t i = mh_strnptr_find_str(h, name, name_len); + return i != mh_end(h) ? mh_strnptr_node(h, i)->val : NULL; +} + +void +auth_method_register(struct auth_method *method) +{ + struct mh_strnptr_t *h = auth_methods; + const char *name = method->name; + uint32_t name_len = strlen(name); + uint32_t name_hash = mh_strn_hash(name, name_len); + struct mh_strnptr_node_t n = {name, name_len, name_hash, method}; + struct mh_strnptr_node_t prev; + struct mh_strnptr_node_t *prev_ptr = &prev; + mh_strnptr_put(h, &n, &prev_ptr, NULL); + assert(prev_ptr == NULL); +} + +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); +} + +void +auth_free(void) +{ + struct mh_strnptr_t *h = auth_methods; + auth_methods = NULL; + AUTH_METHOD_DEFAULT = NULL; + mh_int_t i; + mh_foreach(h, i) { + struct auth_method *method = mh_strnptr_node(h, i)->val; + method->auth_method_delete(method); + } + mh_strnptr_delete(h); +} diff --git a/src/box/authentication.h b/src/box/authentication.h index f645e313761b06b857dc24315c224d8f9216dc1f..f64f87250c0880c37ca6caaaba1ce7143445d5ce 100644 --- a/src/box/authentication.h +++ b/src/box/authentication.h @@ -5,6 +5,7 @@ */ #pragma once +#include <assert.h> #include <stdbool.h> #include <stddef.h> #include <stdint.h> @@ -13,6 +14,11 @@ extern "C" { #endif /* defined(__cplusplus) */ +struct auth_method; + +/** Default authentication method. */ +extern const struct auth_method *AUTH_METHOD_DEFAULT; + /** * State passed to authentication trigger. */ @@ -25,6 +31,170 @@ struct on_auth_trigger_ctx { bool is_authenticated; }; +/** + * Abstract authenticator class. + * + * A concrete instance of this class is created for each user that + * enabled authentication. + */ +struct authenticator { + /** Authentication method used by this authenticator. */ + const struct auth_method *method; +}; + +/** + * Abstract authentication method class. + * + * A concrete instance of this class is created for each supported + * authentication method. + */ +struct auth_method { + /** Unique authentication method name. */ + const char *name; + /** Destroys an authentication method object. */ + void + (*auth_method_delete)(struct auth_method *method); + /** + * Given a password, this function prepares MsgPack data that can be + * used to check an authentication request. The data is allocated on + * the fiber region. + * + * We store the data as a value in the 'auth' field of the '_user' + * system table, using the authentication method name as a key. + */ + void + (*auth_data_prepare)(const struct auth_method *method, + const char *password, int password_len, + const char **auth_data, + const char **auth_data_end); + /** + * Given a password and a connection salt (sent in the greeting), + * this function prepares MsgPack data that can be used to perform + * an authentication request. The data is allocated on the fiber + * region. + * + * We store the data in the second field of IPROTO_TUPLE sent in + * IPROTO_AUTH request body. The first IPROTO_TUPLE field is set to + * the authentication method name. + */ + void + (*auth_request_prepare)(const struct auth_method *method, + const char *password, int password_len, + const char *salt, + const char **auth_request, + const char **auth_request_end); + /** + * Checks the format of an authentication request. + * + * Returns 0 on success. If the request is malformed, sets diag to + * ER_INVALID_AUTH_REQUEST and returns -1. + * + * See also auth_request_prepare. + */ + int + (*auth_request_check)(const struct auth_method *method, + const char *auth_request, + const char *auth_request_end); + /** + * Constructs a new authenticator object from an authentication data. + * + * Returns a pointer on success. If the data is malformed, sets diag to + * ER_INVALID_AUTH_DATA and returns NULL. + * + * See also auth_data_prepare. + */ + struct authenticator * + (*authenticator_new)(const struct auth_method *method, + const char *auth_data, const char *auth_data_end); + /** Destroys an authenticator object. */ + void + (*authenticator_delete)(struct authenticator *auth); + /** + * Authenticates a request. + * + * Returns true if authentication was successful. + * + * The request must be well-formed. + * The salt must match the salt used to prepare the request. + * + * See also auth_request_prepare, auth_request_check. + */ + bool + (*authenticate_request)(const struct authenticator *auth, + const char *salt, + const char *auth_request, + const char *auth_request_end); +}; + +static inline void +auth_data_prepare(const struct auth_method *method, + const char *password, int password_len, + const char **auth_data, const char **auth_data_end) +{ + method->auth_data_prepare(method, password, password_len, + auth_data, auth_data_end); +} + +static inline void +auth_request_prepare(const struct auth_method *method, + const char *password, int password_len, const char *salt, + const char **auth_request, const char **auth_request_end) +{ + method->auth_request_prepare(method, password, password_len, salt, + auth_request, auth_request_end); +} + +static inline int +auth_request_check(const struct auth_method *method, + const char *auth_request, const char *auth_request_end) +{ + return method->auth_request_check(method, auth_request, + auth_request_end); +} + +static inline struct authenticator * +authenticator_new(const struct auth_method *method, + const char *auth_data, const char *auth_data_end) +{ + return method->authenticator_new(method, auth_data, auth_data_end); +} + +static inline void +authenticator_delete(struct authenticator *auth) +{ + auth->method->authenticator_delete(auth); +} + +/** + * Authenticates a request. + * + * Returns true if authentication was successful. + * + * NOTE: the request must be well-formed (checked by auth_request_check). + */ +static inline bool +authenticate_request(const struct authenticator *auth, const char *salt, + const char *auth_request, const char *auth_request_end) +{ + assert(auth->method->auth_request_check(auth->method, auth_request, + auth_request_end) == 0); + return auth->method->authenticate_request( + auth, salt, auth_request, auth_request_end); +} + +/** + * Authenticates a password. + * + * Returns true if authentication was successful. + * + * This is a helper function that prepares an authentication request with + * auth_request_prepare and then checks it with authenticate_request using + * zero salt. + */ +bool +authenticate_password(const struct authenticator *auth, + const char *password, int password_len); + /** * Authenticates a user. * @@ -35,11 +205,37 @@ struct on_auth_trigger_ctx { * tuple: value of the IPROTO_TUPLE key sent in the IPROTO_AUTH request body. * * Returns 0 on success. On error, sets diag and returns -1. + * + * Errors: + * ER_INVALID_MSGPACK: missing authentication method name or data + * ER_UNKNOWN_AUTH_METHOD: unknown authentication method name + * ER_INVALID_AUTH_REQUEST: malformed authentication request + * ER_CREDS_MISMATCH: authentication denied */ int authenticate(const char *user_name, uint32_t user_name_len, const char *salt, const char *tuple); +/** + * Looks up an authentication method by name. + * If not found, returns NULL (diag NOT set). + */ +const struct auth_method * +auth_method_by_name(const char *name, uint32_t name_len); + +/** + * Registers an authentication method. + * There must not be another method with the same name. + */ +void +auth_method_register(struct auth_method *method); + +void +auth_init(void); + +void +auth_free(void); + #if defined(__cplusplus) } /* extern "C" */ #endif /* defined(__cplusplus) */ diff --git a/src/box/box.cc b/src/box/box.cc index 490d9826bb606b8af28521c69cc2a1fed93516ae..461e16865f6e8932a6ab2f66c564eb85d74c1d86 100644 --- a/src/box/box.cc +++ b/src/box/box.cc @@ -3739,6 +3739,7 @@ box_free(void) schema_module_free(); tuple_free(); #endif + auth_free(); wal_ext_free(); box_watcher_free(); box_raft_free(); @@ -4285,6 +4286,7 @@ box_init(void) msgpack_init(); fiber_cond_create(&ro_cond); + auth_init(); user_cache_init(); /* * The order is important: to initialize sessions, diff --git a/src/box/errcode.h b/src/box/errcode.h index 9362b8ee586df0fb748cf17750ee967b0e35457a..f093b018e299f74a67224c78592bedb7e1982d71 100644 --- a/src/box/errcode.h +++ b/src/box/errcode.h @@ -301,6 +301,9 @@ struct errcode_record { /*246 */_(ER_INTERFERING_ELECTIONS, "Interfering elections started")\ /*247 */_(ER_ITERATOR_POSITION, "Iterator position is invalid") \ /*248 */_(ER_DDL_NOT_ALLOWED, "DDL operations are not allowed: %s") \ + /*249 */_(ER_UNKNOWN_AUTH_METHOD, "Unknown authentication method '%s'") \ + /*250 */_(ER_INVALID_AUTH_DATA, "Invalid '%s' data: %s") \ + /*251 */_(ER_INVALID_AUTH_REQUEST, "Invalid '%s' request: %s") \ /* * !IMPORTANT! Please follow instructions at start of the file diff --git a/src/box/lua/misc.cc b/src/box/lua/misc.cc index 591519308612285a787cf4c74176fa57a4b3561a..720267e438f7471814b687ad4ae2264ab35ff131 100644 --- a/src/box/lua/misc.cc +++ b/src/box/lua/misc.cc @@ -35,6 +35,7 @@ #include "lua/utils.h" #include "lua/msgpack.h" +#include "box/authentication.h" #include "box/box.h" #include "box/port.h" #include "box/tuple.h" @@ -43,6 +44,7 @@ #include "box/xrow.h" #include "box/txn.h" #include "mpstream/mpstream.h" +#include "tt_static.h" static uint32_t CTID_STRUCT_TUPLE_FORMAT_PTR; @@ -204,6 +206,41 @@ port_msgpack_dump_lua(struct port *base, struct lua_State *L, bool is_flat) /* }}} */ +/** {{{ Helper that generates user auth data. **/ + +/** + * Takes authentication method name (e.g. 'chap-sha1') and a password. + * Returns authentication data that can be stored in the _user space. + * Raises Lua error if the specified authentication method doesn't exist. + */ +static int +lbox_prepare_auth(lua_State *L) +{ + size_t method_name_len; + const char *method_name = luaL_checklstring(L, 1, &method_name_len); + size_t password_len; + const char *password = luaL_checklstring(L, 2, &password_len); + const struct auth_method *method = auth_method_by_name(method_name, + method_name_len); + if (method == NULL) { + diag_set(ClientError, ER_UNKNOWN_AUTH_METHOD, + tt_cstr(method_name, method_name_len)); + return luaT_error(L); + } + struct region *region = &fiber()->gc; + size_t region_svp = region_used(region); + const char *auth_data, *auth_data_end; + auth_data_prepare(method, password, password_len, + &auth_data, &auth_data_end); + luamp_decode(L, luaL_msgpack_default, &auth_data); + assert(auth_data == auth_data_end); + (void)auth_data_end; + region_truncate(region, region_svp); + return 1; +} + +/* }}} */ + /** {{{ Lua/C implementation of index:select(): used only by Vinyl **/ static int @@ -388,6 +425,7 @@ void box_lua_misc_init(struct lua_State *L) { static const struct luaL_Reg boxlib_internal[] = { + {"prepare_auth", lbox_prepare_auth}, {"select", lbox_select}, {"new_tuple_format", lbox_tuple_format_new}, {"txn_set_isolation", lbox_txn_set_isolation}, diff --git a/src/box/lua/net_box.c b/src/box/lua/net_box.c index e08521ff6e2437f2a82144689639ee4f261d1a5e..e95523c2ce53440507809e4652df85cb72abb611 100644 --- a/src/box/lua/net_box.c +++ b/src/box/lua/net_box.c @@ -38,10 +38,8 @@ #include <sys/socket.h> #include <sys/types.h> -#include <small/ibuf.h> -#include <msgpuck.h> /* mp_store_u32() */ -#include "scramble.h" - +#include "box/authentication.h" +#include "box/errcode.h" #include "box/iproto_constants.h" #include "box/iproto_features.h" #include "box/lua/tuple.h" /* luamp_convert_tuple() / luamp_convert_key() */ @@ -51,18 +49,18 @@ #include "box/error.h" #include "box/schema_def.h" -#include "lua/msgpack.h" -#include <base64.h> - #include "assoc.h" #include "coio.h" #include "fiber.h" #include "fiber_cond.h" #include "iostream.h" -#include "box/errcode.h" #include "lua/fiber.h" #include "lua/fiber_cond.h" +#include "lua/msgpack.h" #include "lua/uri.h" +#include "msgpuck.h" +#include "small/ibuf.h" +#include "small/region.h" #include "mpstream/mpstream.h" #include "misc.h" /* lbox_check_tuple_format() */ #include "uri/uri.h" @@ -687,9 +685,14 @@ static void netbox_encode_auth(struct lua_State *L, struct ibuf *ibuf, uint64_t sync, const char *user, const char *password, const char *salt) { - char scramble[SCRAMBLE_SIZE]; - scramble_prepare(scramble, salt, password != NULL ? password : "", - password != NULL ? strlen(password) : 0); + if (password == NULL) + password = ""; + struct region *region = &fiber()->gc; + size_t region_svp = region_used(region); + const struct auth_method *method = AUTH_METHOD_DEFAULT; + const char *auth_request, *auth_request_end; + auth_request_prepare(method, password, strlen(password), salt, + &auth_request, &auth_request_end); struct mpstream stream; mpstream_init(&stream, ibuf, ibuf_reserve_cb, ibuf_alloc_cb, luamp_error, L); @@ -699,9 +702,10 @@ netbox_encode_auth(struct lua_State *L, struct ibuf *ibuf, uint64_t sync, mpstream_encode_strn(&stream, user, strlen(user)); mpstream_encode_uint(&stream, IPROTO_TUPLE); mpstream_encode_array(&stream, 2); - mpstream_encode_str(&stream, "chap-sha1"); - mpstream_encode_strn(&stream, scramble, SCRAMBLE_SIZE); + mpstream_encode_str(&stream, method->name); + mpstream_memcpy(&stream, auth_request, auth_request_end - auth_request); netbox_end_encode(&stream, svp); + region_truncate(region, region_svp); } /** diff --git a/src/box/lua/schema.lua b/src/box/lua/schema.lua index 1a9ecc972cd04457efef5c37b928de9abf1bcce5..0f4cc7bfb1f39261a58b0f18b0d7295637c87694 100644 --- a/src/box/lua/schema.lua +++ b/src/box/lua/schema.lua @@ -121,9 +121,6 @@ ffi.cdef[[ const char **after, const char **after_end, bool update_pos, struct port *port); - void password_prepare(const char *password, int len, - char *out, int out_len); - enum priv_type { PRIV_R = 1, PRIV_W = 2, @@ -3376,20 +3373,16 @@ end box.schema.user = {} box.schema.user.password = function(password) - local BUF_SIZE = 128 - local ibuf = cord_ibuf_take() - local buf = ibuf:alloc(BUF_SIZE) - builtin.password_prepare(password, #password, buf, BUF_SIZE) - buf = ffi.string(buf) - cord_ibuf_put(ibuf) - return buf + return internal.prepare_auth('chap-sha1', password) +end + +local function prepare_auth_list(password) + return {['chap-sha1'] = internal.prepare_auth('chap-sha1', password)} end local function chpasswd(uid, new_password) local _user = box.space[box.schema.USER_ID] - local auth_mech_list = {} - auth_mech_list["chap-sha1"] = box.schema.user.password(new_password) - _user:update({uid}, {{"=", 5, auth_mech_list}}) + _user:update({uid}, {{"=", 5, prepare_auth_list(new_password)}}) end box.schema.user.passwd = function(name, new_password) @@ -3420,12 +3413,14 @@ box.schema.user.create = function(name, opts) end return end - local auth_mech_list = setmap({}) + local auth_list if opts.password then - auth_mech_list["chap-sha1"] = box.schema.user.password(opts.password) + auth_list = prepare_auth_list(opts.password) + else + auth_list = setmap({}) end local _user = box.space[box.schema.USER_ID] - uid = _user:auto_increment{session.euid(), name, 'user', auth_mech_list}.id + uid = _user:auto_increment{session.euid(), name, 'user', auth_list}.id -- grant role 'public' to the user box.schema.user.grant(uid, 'public') -- Grant privilege 'alter' on itself, so that it can diff --git a/src/box/schema_def.h b/src/box/schema_def.h index 21da0d668ca6e5471fb9e690bb30b8197a7b767a..cf7fc7ece80f2382e478ef648fd3182adb338881 100644 --- a/src/box/schema_def.h +++ b/src/box/schema_def.h @@ -153,7 +153,7 @@ enum { BOX_USER_FIELD_UID = 1, BOX_USER_FIELD_NAME = 2, BOX_USER_FIELD_TYPE = 3, - BOX_USER_FIELD_AUTH_MECH_LIST = 4, + BOX_USER_FIELD_AUTH = 4, }; /** _priv fields. */ diff --git a/src/box/user_def.c b/src/box/user_def.c index c9b7452acea41c35f69d41b7f405009d796b983b..db3d719861148826666f44a9dc0d8df20c5b7ac6 100644 --- a/src/box/user_def.c +++ b/src/box/user_def.c @@ -8,13 +8,11 @@ #include <assert.h> #include <stdint.h> #include <stdlib.h> -#include <string.h> +#include "authentication.h" #include "salad/grp_alloc.h" #include "trivia/util.h" -const char *CHAP_SHA1_EMPTY_PASSWORD = "vhvewKp0tNyweZQ+cFKAlsyphfg="; - const char * priv_name(user_access_t access) { @@ -54,7 +52,7 @@ user_def_new(uint32_t uid, uint32_t owner, enum schema_object_type type, def->uid = uid; def->owner = owner; def->type = type; - memset(def->hash2, 0, sizeof(def->hash2)); + def->auth = NULL; def->name = grp_alloc_create_str(&all, name, name_len); assert(grp_alloc_size(&all) == 0); return def; @@ -63,6 +61,8 @@ user_def_new(uint32_t uid, uint32_t owner, enum schema_object_type type, void user_def_delete(struct user_def *def) { + if (def->auth != NULL) + authenticator_delete(def->auth); TRASH(def); free(def); } diff --git a/src/box/user_def.h b/src/box/user_def.h index ea2959022812fcb66164e1d44ecceed2102e113f..efeb9774cdcfd5bdeebd9ec34166e9b61c9c84e2 100644 --- a/src/box/user_def.h +++ b/src/box/user_def.h @@ -8,7 +8,6 @@ #include <stdint.h> #include "schema_def.h" /* for SCHEMA_OBJECT_TYPE */ -#include "scramble.h" /* for SCRAMBLE_SIZE */ #define RB_COMPACT 1 #include "small/rb.h" #include "small/rlist.h" @@ -17,10 +16,7 @@ extern "C" { #endif /* defined(__cplusplus) */ -/** - * chap-sha1 of empty string, i.e. base64_encode(sha1(sha1(""), 0) - */ -extern const char *CHAP_SHA1_EMPTY_PASSWORD; +struct authenticator; typedef uint16_t user_access_t; /** @@ -141,8 +137,15 @@ struct user_def { uint32_t owner; /** 'user' or 'role' */ enum schema_object_type type; - /** User password - hash2 */ - char hash2[SCRAMBLE_SIZE]; + /** + * Authentication data or NULL if auth method is unset. + * + * XXX: Strictly speaking, this doesn't belong here. + * Ideally, we should store raw authentication data in + * the user_def struct while the authenticator should + * reside in the user struct. + */ + struct authenticator *auth; /** User name - for error messages and debugging */ char *name; }; diff --git a/src/box/xrow.c b/src/box/xrow.c index e7097c3301a8f4f4f2f764039b241bc2ad8238a4..9eb6076a0dde997d25128c24d8ba0493b8baadb5 100644 --- a/src/box/xrow.c +++ b/src/box/xrow.c @@ -1530,30 +1530,26 @@ xrow_decode_auth(const struct xrow_header *row, struct auth_request *request) } void -xrow_encode_auth(struct xrow_header *packet, const char *salt, size_t salt_len, +xrow_encode_auth(struct xrow_header *packet, const char *login, size_t login_len, - const char *password, size_t password_len) + const char *method, size_t method_len, + const char *data, const char *data_end) { assert(login != NULL); + assert(data != NULL); memset(packet, 0, sizeof(*packet)); - - size_t buf_size = XROW_BODY_LEN_MAX + login_len + SCRAMBLE_SIZE; + size_t data_size = data_end - data; + size_t buf_size = XROW_BODY_LEN_MAX + login_len + data_size; char *buf = xregion_alloc(&fiber()->gc, buf_size); char *d = buf; - d = mp_encode_map(d, password != NULL ? 2 : 1); + d = mp_encode_map(d, 2); d = mp_encode_uint(d, IPROTO_USER_NAME); d = mp_encode_str(d, login, login_len); - if (password != NULL) { /* password can be omitted */ - assert(salt_len >= SCRAMBLE_SIZE); /* greetingbuf_decode */ - (void) salt_len; - char scramble[SCRAMBLE_SIZE]; - scramble_prepare(scramble, salt, password, password_len); - d = mp_encode_uint(d, IPROTO_TUPLE); - d = mp_encode_array(d, 2); - d = mp_encode_str(d, "chap-sha1", strlen("chap-sha1")); - d = mp_encode_str(d, scramble, SCRAMBLE_SIZE); - } - + d = mp_encode_uint(d, IPROTO_TUPLE); + d = mp_encode_array(d, 2); + d = mp_encode_str(d, method, method_len); + memcpy(d, data, data_size); + d += data_size; assert(d <= buf + buf_size); packet->body[0].iov_base = buf; packet->body[0].iov_len = (d - buf); diff --git a/src/box/xrow.h b/src/box/xrow.h index 7ddc1795847324f83f7d048c899317aa5c64aa55..cded47d322738204f2dc42deb611d005caa37bc5 100644 --- a/src/box/xrow.h +++ b/src/box/xrow.h @@ -416,17 +416,18 @@ xrow_decode_auth(const struct xrow_header *row, struct auth_request *request); /** * Encode AUTH command. * @param[out] Row. - * @param salt Salt from IPROTO greeting. - * @param salt_len Length of @salt. * @param login User login. * @param login_len Length of @login. - * @param password User password. - * @param password_len Length of @password. -*/ + * @param method Authentication method. + * @param method_len Length of @method. + * @param data Authentication request data. + * @param data_end End of @data. + */ void -xrow_encode_auth(struct xrow_header *row, const char *salt, size_t salt_len, - const char *login, size_t login_len, const char *password, - size_t password_len); +xrow_encode_auth(struct xrow_header *row, + const char *login, size_t login_len, + const char *method, size_t method_len, + const char *data, const char *data_end); /** Reply to IPROTO_VOTE request. */ struct ballot { diff --git a/test/box-luatest/ghs_16_user_enumeration_test.lua b/test/box-luatest/ghs_16_user_enumeration_test.lua index d66207a3e8973319e5019872eeb789fe519b4d01..6171ded111b34c20f985700f88e44713d53c4a43 100644 --- a/test/box-luatest/ghs_16_user_enumeration_test.lua +++ b/test/box-luatest/ghs_16_user_enumeration_test.lua @@ -95,13 +95,17 @@ g.test_user_enum_on_malformed_auth = function() box.error.INVALID_MSGPACK, 'Invalid MsgPack - authentication request body', }, msg) + t.assert_equals(auth(uri, user, {'foobar', 'foobar'}), { + box.error.UNKNOWN_AUTH_METHOD, + "Unknown authentication method 'foobar'", + }, msg) t.assert_equals(auth(uri, user, {'chap-sha1', 42}), { - box.error.INVALID_MSGPACK, - 'Invalid MsgPack - authentication scramble', + box.error.INVALID_AUTH_REQUEST, + "Invalid 'chap-sha1' request: scramble must be string", }, msg) t.assert_equals(auth(uri, user, {'chap-sha1', 'foobar'}), { - box.error.INVALID_MSGPACK, - 'Invalid MsgPack - invalid scramble size', + box.error.INVALID_AUTH_REQUEST, + "Invalid 'chap-sha1' request: invalid scramble size", }, msg) t.assert_equals(auth(uri, user, {'chap-sha1', string.rep('x', 20)}), { box.error.CREDS_MISMATCH, diff --git a/test/box/error.result b/test/box/error.result index d162cfd26440a509d34462212f4a16241fc95cd8..8dff49d04a0123cbc08b30b3be5c4020fff4959b 100644 --- a/test/box/error.result +++ b/test/box/error.result @@ -467,6 +467,9 @@ t; | 246: box.error.INTERFERING_ELECTIONS | 247: box.error.ITERATOR_POSITION | 248: box.error.DDL_NOT_ALLOWED + | 249: box.error.UNKNOWN_AUTH_METHOD + | 250: box.error.INVALID_AUTH_DATA + | 251: box.error.INVALID_AUTH_REQUEST | ... test_run:cmd("setopt delimiter ''");