/** * mongoc_database_add_user: * @database: A #mongoc_database_t. * @username: A string containing the username. * @password: (allow-none): A string containing password, or NULL. * @roles: (allow-none): An optional bson_t of roles. * @custom_data: (allow-none): An optional bson_t of data to store. * @error: (out) (allow-none): A location for a bson_error_t or %NULL. * * Creates a new user with access to @database. * * Returns: None. * Side effects: None. */ bool mongoc_database_add_user (mongoc_database_t *database, const char *username, const char *password, const bson_t *roles, const bson_t *custom_data, bson_error_t *error) { bson_error_t lerror; bson_t cmd; bson_t ar; char *input; char *hashed_password; bool ret = false; ENTRY; BSON_ASSERT (database); BSON_ASSERT (username); /* * CDRIVER-232: * * Perform a (slow and tedious) round trip to mongod to determine if * we can safely call createUser. Otherwise, we will fallback and * perform legacy insertion into users collection. */ bson_init (&cmd); BSON_APPEND_UTF8 (&cmd, "usersInfo", username); ret = mongoc_database_command_simple (database, &cmd, NULL, NULL, &lerror); bson_destroy (&cmd); if (!ret && (lerror.code == MONGOC_ERROR_QUERY_COMMAND_NOT_FOUND)) { ret = mongoc_database_add_user_legacy (database, username, password, error); } else if (ret || (lerror.code == 13)) { /* usersInfo succeeded or failed with auth err, we're on modern mongod */ input = bson_strdup_printf ("%s:mongo:%s", username, password); hashed_password = _mongoc_hex_md5 (input); bson_free (input); bson_init (&cmd); BSON_APPEND_UTF8 (&cmd, "createUser", username); BSON_APPEND_UTF8 (&cmd, "pwd", hashed_password); BSON_APPEND_BOOL (&cmd, "digestPassword", false); if (custom_data) { BSON_APPEND_DOCUMENT (&cmd, "customData", custom_data); } if (roles) { BSON_APPEND_ARRAY (&cmd, "roles", roles); } else { bson_append_array_begin (&cmd, "roles", 5, &ar); bson_append_array_end (&cmd, &ar); } ret = mongoc_database_command_simple (database, &cmd, NULL, NULL, error); bson_free (hashed_password); bson_destroy (&cmd); } else if (error) { memcpy (error, &lerror, sizeof *error); } RETURN (ret); }
/* Parse server-first-message of the form: * r=client-nonce|server-nonce,s=user-salt,i=iteration-count * * Generate client-final-message of the form: * c=channel-binding(base64),r=client-nonce|server-nonce,p=client-proof */ static bool _mongoc_scram_step2 (mongoc_scram_t *scram, const uint8_t *inbuf, uint32_t inbuflen, uint8_t *outbuf, uint32_t outbufmax, uint32_t *outbuflen, bson_error_t *error) { uint8_t *val_r = NULL; uint32_t val_r_len; uint8_t *val_s = NULL; uint32_t val_s_len; uint8_t *val_i = NULL; uint32_t val_i_len; uint8_t **current_val; uint32_t *current_val_len; const uint8_t *ptr; const uint8_t *next_comma; char *tmp; char *hashed_password; uint8_t decoded_salt[MONGOC_SCRAM_B64_HASH_SIZE] = {0}; int32_t decoded_salt_len; bool rval = true; int iterations; BSON_ASSERT (scram); BSON_ASSERT (outbuf); BSON_ASSERT (outbufmax); BSON_ASSERT (outbuflen); /* all our passwords go through md5 thanks to MONGODB-CR */ tmp = bson_strdup_printf ("%s:mongo:%s", scram->user, scram->pass); hashed_password = _mongoc_hex_md5 (tmp); bson_zero_free (tmp, strlen (tmp)); /* we need all of the incoming message for the final client proof */ if (!_mongoc_scram_buf_write ((char *) inbuf, inbuflen, scram->auth_message, scram->auth_messagemax, &scram->auth_messagelen)) { goto BUFFER_AUTH; } if (!_mongoc_scram_buf_write (",", -1, scram->auth_message, scram->auth_messagemax, &scram->auth_messagelen)) { goto BUFFER_AUTH; } for (ptr = inbuf; ptr < inbuf + inbuflen;) { switch (*ptr) { case 'r': current_val = &val_r; current_val_len = &val_r_len; break; case 's': current_val = &val_s; current_val_len = &val_s_len; break; case 'i': current_val = &val_i; current_val_len = &val_i_len; break; default: bson_set_error (error, MONGOC_ERROR_SCRAM, MONGOC_ERROR_SCRAM_PROTOCOL_ERROR, "SCRAM Failure: unknown key (%c) in sasl step 2", *ptr); goto FAIL; break; } ptr++; if (*ptr != '=') { bson_set_error (error, MONGOC_ERROR_SCRAM, MONGOC_ERROR_SCRAM_PROTOCOL_ERROR, "SCRAM Failure: invalid parse state in sasl step 2"); goto FAIL; } ptr++; next_comma = (const uint8_t *) memchr (ptr, ',', (inbuf + inbuflen) - ptr); if (next_comma) { *current_val_len = (uint32_t) (next_comma - ptr); } else { *current_val_len = (uint32_t) ((inbuf + inbuflen) - ptr); } *current_val = (uint8_t *) bson_malloc (*current_val_len + 1); memcpy (*current_val, ptr, *current_val_len); (*current_val)[*current_val_len] = '\0'; if (next_comma) { ptr = next_comma + 1; } else { break; } } if (!val_r) { bson_set_error (error, MONGOC_ERROR_SCRAM, MONGOC_ERROR_SCRAM_PROTOCOL_ERROR, "SCRAM Failure: no r param in sasl step 2"); goto FAIL; } if (!val_s) { bson_set_error (error, MONGOC_ERROR_SCRAM, MONGOC_ERROR_SCRAM_PROTOCOL_ERROR, "SCRAM Failure: no s param in sasl step 2"); goto FAIL; } if (!val_i) { bson_set_error (error, MONGOC_ERROR_SCRAM, MONGOC_ERROR_SCRAM_PROTOCOL_ERROR, "SCRAM Failure: no i param in sasl step 2"); goto FAIL; } /* verify our nonce */ if (val_r_len < scram->encoded_nonce_len || mongoc_memcmp (val_r, scram->encoded_nonce, scram->encoded_nonce_len)) { bson_set_error ( error, MONGOC_ERROR_SCRAM, MONGOC_ERROR_SCRAM_PROTOCOL_ERROR, "SCRAM Failure: client nonce not repeated in sasl step 2"); } *outbuflen = 0; if (!_mongoc_scram_buf_write ( "c=biws,r=", -1, outbuf, outbufmax, outbuflen)) { goto BUFFER; } if (!_mongoc_scram_buf_write ( (char *) val_r, val_r_len, outbuf, outbufmax, outbuflen)) { goto BUFFER; } if (!_mongoc_scram_buf_write ((char *) outbuf, *outbuflen, scram->auth_message, scram->auth_messagemax, &scram->auth_messagelen)) { goto BUFFER_AUTH; } if (!_mongoc_scram_buf_write (",p=", -1, outbuf, outbufmax, outbuflen)) { goto BUFFER; } decoded_salt_len = bson_b64_pton ((char *) val_s, decoded_salt, sizeof (decoded_salt)); if (-1 == decoded_salt_len) { bson_set_error (error, MONGOC_ERROR_SCRAM, MONGOC_ERROR_SCRAM_PROTOCOL_ERROR, "SCRAM Failure: unable to decode salt in sasl step2"); goto FAIL; } if (16 != decoded_salt_len) { bson_set_error (error, MONGOC_ERROR_SCRAM, MONGOC_ERROR_SCRAM_PROTOCOL_ERROR, "SCRAM Failure: invalid salt length of %d in sasl step2", decoded_salt_len); goto FAIL; } iterations = (int) bson_ascii_strtoll ((char *) val_i, &tmp, 10); /* tmp holds the location of the failed to parse character. So if it's * null, we got to the end of the string and didn't have a parse error */ if (*tmp) { bson_set_error ( error, MONGOC_ERROR_SCRAM, MONGOC_ERROR_SCRAM_PROTOCOL_ERROR, "SCRAM Failure: unable to parse iterations in sasl step2"); goto FAIL; } if (iterations < 0) { bson_set_error (error, MONGOC_ERROR_SCRAM, MONGOC_ERROR_SCRAM_PROTOCOL_ERROR, "SCRAM Failure: iterations is negative in sasl step2"); goto FAIL; } /* Save the presecrets for caching */ scram->hashed_password = bson_strdup (hashed_password); scram->iterations = iterations; memcpy (scram->decoded_salt, decoded_salt, sizeof (scram->decoded_salt)); if (scram->cache && _mongoc_scram_cache_has_presecrets (scram->cache, scram)) { _mongoc_scram_cache_apply_secrets (scram->cache, scram); } if (!*scram->salted_password) { _mongoc_scram_salt_password (scram, hashed_password, (uint32_t) strlen (hashed_password), decoded_salt, decoded_salt_len, (uint32_t) iterations); } _mongoc_scram_generate_client_proof (scram, outbuf, outbufmax, outbuflen); goto CLEANUP; BUFFER_AUTH: bson_set_error ( error, MONGOC_ERROR_SCRAM, MONGOC_ERROR_SCRAM_PROTOCOL_ERROR, "SCRAM Failure: could not buffer auth message in sasl step2"); goto FAIL; BUFFER: bson_set_error (error, MONGOC_ERROR_SCRAM, MONGOC_ERROR_SCRAM_PROTOCOL_ERROR, "SCRAM Failure: could not buffer sasl step2"); goto FAIL; FAIL: rval = false; CLEANUP: bson_free (val_r); bson_free (val_s); bson_free (val_i); if (hashed_password) { bson_zero_free (hashed_password, strlen (hashed_password)); } return rval; }
static bool mongoc_database_add_user_legacy (mongoc_database_t *database, const char *username, const char *password, bson_error_t *error) { mongoc_collection_t *collection; mongoc_cursor_t *cursor = NULL; const bson_t *doc; bool ret = false; bson_t query; bson_t user; char *input; char *pwd = NULL; ENTRY; bson_return_val_if_fail(database, false); bson_return_val_if_fail(username, false); bson_return_val_if_fail(password, false); /* * Users are stored in the <dbname>.system.users virtual collection. */ collection = mongoc_client_get_collection(database->client, database->name, "system.users"); BSON_ASSERT(collection); /* * Hash the users password. */ input = bson_strdup_printf("%s:mongo:%s", username, password); pwd = _mongoc_hex_md5(input); bson_free(input); /* * Check to see if the user exists. If so, we will update the * password instead of inserting a new user. */ bson_init(&query); bson_append_utf8(&query, "user", 4, username, -1); cursor = mongoc_collection_find(collection, MONGOC_QUERY_NONE, 0, 1, 0, &query, NULL, NULL); if (!mongoc_cursor_next(cursor, &doc)) { if (mongoc_cursor_error(cursor, error)) { GOTO (failure); } bson_init(&user); bson_append_utf8(&user, "user", 4, username, -1); bson_append_bool(&user, "readOnly", 8, false); bson_append_utf8(&user, "pwd", 3, pwd, -1); } else { bson_init(&user); bson_copy_to_excluding_noinit(doc, &user, "pwd", (char *)NULL); bson_append_utf8(&user, "pwd", 3, pwd, -1); } if (!mongoc_collection_save(collection, &user, NULL, error)) { GOTO (failure_with_user); } ret = true; failure_with_user: bson_destroy(&user); failure: if (cursor) { mongoc_cursor_destroy(cursor); } mongoc_collection_destroy(collection); bson_destroy(&query); bson_free(pwd); RETURN (ret); }
/* Parse server-first-message of the form: * r=client-nonce|server-nonce,s=user-salt,i=iteration-count * * Generate client-final-message of the form: * c=channel-binding(base64),r=client-nonce|server-nonce,p=client-proof */ static bool _mongoc_scram_step2 (mongoc_scram_t *scram, const uint8_t *inbuf, uint32_t inbuflen, uint8_t *outbuf, uint32_t outbufmax, uint32_t *outbuflen, bson_error_t *error) { uint8_t *val_r = NULL; uint32_t val_r_len; uint8_t *val_s = NULL; uint32_t val_s_len; uint8_t *val_i = NULL; uint32_t val_i_len; uint8_t **current_val; uint32_t *current_val_len; const uint8_t *ptr; const uint8_t *next_comma; char *tmp; char *hashed_password; uint8_t decoded_salt[MONGOC_SCRAM_B64_HASH_MAX_SIZE] = {0}; int32_t decoded_salt_len; /* the decoded salt leaves four trailing bytes to add the int32 0x00000001 */ const int32_t expected_salt_length = _scram_hash_size (scram) - 4; bool rval = true; int iterations; BSON_ASSERT (scram); BSON_ASSERT (outbuf); BSON_ASSERT (outbufmax); BSON_ASSERT (outbuflen); if (scram->crypto.algorithm == MONGOC_CRYPTO_ALGORITHM_SHA_1) { /* Auth spec for SCRAM-SHA-1: "The password variable MUST be the mongodb * hashed variant. The mongo hashed variant is computed as hash = HEX( * MD5( UTF8( username + ':mongo:' + plain_text_password )))" */ tmp = bson_strdup_printf ("%s:mongo:%s", scram->user, scram->pass); hashed_password = _mongoc_hex_md5 (tmp); bson_zero_free (tmp, strlen (tmp)); } else if (scram->crypto.algorithm == MONGOC_CRYPTO_ALGORITHM_SHA_256) { /* Auth spec for SCRAM-SHA-256: "Passwords MUST be prepared with SASLprep, * per RFC 5802. Passwords are used directly for key derivation; they * MUST NOT be digested as they are in SCRAM-SHA-1." */ hashed_password = _mongoc_sasl_prep (scram->pass, (int) strlen (scram->pass), error); if (!hashed_password) { goto FAIL; } } else { BSON_ASSERT (false); } /* we need all of the incoming message for the final client proof */ if (!_mongoc_scram_buf_write ((char *) inbuf, inbuflen, scram->auth_message, scram->auth_messagemax, &scram->auth_messagelen)) { goto BUFFER_AUTH; } if (!_mongoc_scram_buf_write (",", -1, scram->auth_message, scram->auth_messagemax, &scram->auth_messagelen)) { goto BUFFER_AUTH; } for (ptr = inbuf; ptr < inbuf + inbuflen;) { switch (*ptr) { case 'r': current_val = &val_r; current_val_len = &val_r_len; break; case 's': current_val = &val_s; current_val_len = &val_s_len; break; case 'i': current_val = &val_i; current_val_len = &val_i_len; break; default: bson_set_error (error, MONGOC_ERROR_SCRAM, MONGOC_ERROR_SCRAM_PROTOCOL_ERROR, "SCRAM Failure: unknown key (%c) in sasl step 2", *ptr); goto FAIL; } ptr++; if (*ptr != '=') { bson_set_error (error, MONGOC_ERROR_SCRAM, MONGOC_ERROR_SCRAM_PROTOCOL_ERROR, "SCRAM Failure: invalid parse state in sasl step 2"); goto FAIL; } ptr++; next_comma = (const uint8_t *) memchr (ptr, ',', (inbuf + inbuflen) - ptr); if (next_comma) { *current_val_len = (uint32_t) (next_comma - ptr); } else { *current_val_len = (uint32_t) ((inbuf + inbuflen) - ptr); } *current_val = (uint8_t *) bson_malloc (*current_val_len + 1); memcpy (*current_val, ptr, *current_val_len); (*current_val)[*current_val_len] = '\0'; if (next_comma) { ptr = next_comma + 1; } else { break; } } if (!val_r) { bson_set_error (error, MONGOC_ERROR_SCRAM, MONGOC_ERROR_SCRAM_PROTOCOL_ERROR, "SCRAM Failure: no r param in sasl step 2"); goto FAIL; } if (!val_s) { bson_set_error (error, MONGOC_ERROR_SCRAM, MONGOC_ERROR_SCRAM_PROTOCOL_ERROR, "SCRAM Failure: no s param in sasl step 2"); goto FAIL; } if (!val_i) { bson_set_error (error, MONGOC_ERROR_SCRAM, MONGOC_ERROR_SCRAM_PROTOCOL_ERROR, "SCRAM Failure: no i param in sasl step 2"); goto FAIL; } /* verify our nonce */ if (val_r_len < scram->encoded_nonce_len || mongoc_memcmp (val_r, scram->encoded_nonce, scram->encoded_nonce_len)) { bson_set_error ( error, MONGOC_ERROR_SCRAM, MONGOC_ERROR_SCRAM_PROTOCOL_ERROR, "SCRAM Failure: client nonce not repeated in sasl step 2"); } *outbuflen = 0; if (!_mongoc_scram_buf_write ( "c=biws,r=", -1, outbuf, outbufmax, outbuflen)) { goto BUFFER; } if (!_mongoc_scram_buf_write ( (char *) val_r, val_r_len, outbuf, outbufmax, outbuflen)) { goto BUFFER; } if (!_mongoc_scram_buf_write ((char *) outbuf, *outbuflen, scram->auth_message, scram->auth_messagemax, &scram->auth_messagelen)) { goto BUFFER_AUTH; } if (!_mongoc_scram_buf_write (",p=", -1, outbuf, outbufmax, outbuflen)) { goto BUFFER; } decoded_salt_len = bson_b64_pton ((char *) val_s, decoded_salt, sizeof (decoded_salt)); if (-1 == decoded_salt_len) { bson_set_error (error, MONGOC_ERROR_SCRAM, MONGOC_ERROR_SCRAM_PROTOCOL_ERROR, "SCRAM Failure: unable to decode salt in sasl step2"); goto FAIL; } if (expected_salt_length != decoded_salt_len) { bson_set_error (error, MONGOC_ERROR_SCRAM, MONGOC_ERROR_SCRAM_PROTOCOL_ERROR, "SCRAM Failure: invalid salt length of %d in sasl step2", decoded_salt_len); goto FAIL; } iterations = (int) bson_ascii_strtoll ((char *) val_i, &tmp, 10); /* tmp holds the location of the failed to parse character. So if it's * null, we got to the end of the string and didn't have a parse error */ if (*tmp) { bson_set_error ( error, MONGOC_ERROR_SCRAM, MONGOC_ERROR_SCRAM_PROTOCOL_ERROR, "SCRAM Failure: unable to parse iterations in sasl step2"); goto FAIL; } if (iterations < 0) { bson_set_error (error, MONGOC_ERROR_SCRAM, MONGOC_ERROR_SCRAM_PROTOCOL_ERROR, "SCRAM Failure: iterations is negative in sasl step2"); goto FAIL; } /* drivers MUST enforce a minimum iteration count of 4096 and MUST error if * the authentication conversation specifies a lower count. This mitigates * downgrade attacks by a man-in-the-middle attacker. */ if (iterations < 4096) { bson_set_error (error, MONGOC_ERROR_SCRAM, MONGOC_ERROR_SCRAM_PROTOCOL_ERROR, "SCRAM Failure: iterations must be at least 4096"); goto FAIL; } /* Save the presecrets for caching */ scram->hashed_password = bson_strdup (hashed_password); scram->iterations = iterations; memcpy (scram->decoded_salt, decoded_salt, sizeof (scram->decoded_salt)); if (scram->cache && _mongoc_scram_cache_has_presecrets (scram->cache, scram)) { _mongoc_scram_cache_apply_secrets (scram->cache, scram); } if (!*scram->salted_password) { _mongoc_scram_salt_password (scram, hashed_password, (uint32_t) strlen (hashed_password), decoded_salt, decoded_salt_len, (uint32_t) iterations); } _mongoc_scram_generate_client_proof (scram, outbuf, outbufmax, outbuflen); goto CLEANUP; BUFFER_AUTH: bson_set_error ( error, MONGOC_ERROR_SCRAM, MONGOC_ERROR_SCRAM_PROTOCOL_ERROR, "SCRAM Failure: could not buffer auth message in sasl step2"); goto FAIL; BUFFER: bson_set_error (error, MONGOC_ERROR_SCRAM, MONGOC_ERROR_SCRAM_PROTOCOL_ERROR, "SCRAM Failure: could not buffer sasl step2"); goto FAIL; FAIL: rval = false; CLEANUP: bson_free (val_r); bson_free (val_s); bson_free (val_i); if (hashed_password) { bson_zero_free (hashed_password, strlen (hashed_password)); } return rval; }