weechat/src/plugins/relay/relay-auth.c

459 lines
12 KiB
C

/*
* relay-auth.c - relay client authentication
*
* Copyright (C) 2003-2020 Sébastien Helleu <flashcode@flashtux.org>
*
* This file is part of WeeChat, the extensible chat client.
*
* WeeChat is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* WeeChat is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with WeeChat. If not, see <https://www.gnu.org/licenses/>.
*/
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <gcrypt.h>
#include "../weechat-plugin.h"
#include "relay.h"
#include "relay-auth.h"
#include "relay-client.h"
#include "relay-config.h"
/*
* this list is sorted from the least secure to the safest algorithm:
* "plain" is plain text password, the other values are hash algorithms;
* during negotiation with the client, the highest value in this list matching
* the client supported values is used
*/
char *relay_auth_password_name[] =
{ "plain", "sha256", "sha512", "pbkdf2+sha256", "pbkdf2+sha512" };
/*
* Searches for a password authentication.
*
* Returns index in enum t_relay_auth_password,
* -1 if password authentication is not found.
*/
int
relay_auth_password_search (const char *name)
{
int i;
for (i = 0; i < RELAY_NUM_PASSWORD_AUTHS; i++)
{
if (strcmp (relay_auth_password_name[i], name) == 0)
return i;
}
/* authentication password type found */
return -1;
}
/*
* Generates a nonce: a buffer of unpredictable bytes
*
* Note: result must be freed after use.
*/
char *
relay_auth_generate_nonce ()
{
int size;
char *nonce, *nonce_hexa;
size = weechat_config_integer (relay_config_network_nonce_size);
nonce = malloc (size);
if (!nonce)
return NULL;
nonce_hexa = malloc ((size * 2) + 1);
if (!nonce_hexa)
{
free (nonce);
return NULL;
}
gcry_create_nonce ((unsigned char *)nonce, size);
weechat_string_base_encode (16, nonce, size, nonce_hexa);
free (nonce);
return nonce_hexa;
}
/*
* Checks if password received as plain text is valid.
*
* Returns:
* 1: password is valid
* 0: password is not valid
*/
int
relay_auth_check_password_plain (const char *password,
const char *relay_password)
{
if (!password || !relay_password)
return 0;
return (strcmp (password, relay_password) == 0) ? 1 : 0;
}
/*
* Authenticates with password (plain text).
*
* Returns:
* 1: authentication OK
* 0: authentication failed
*/
int
relay_auth_password (struct t_relay_client *client,
const char *password, const char *relay_password)
{
if (client->auth_password != RELAY_AUTH_PASSWORD_PLAIN)
return 0;
return relay_auth_check_password_plain (password, relay_password);
}
/*
* Parses SHA256 or SHA512 parameters from string with format:
*
* salt:hash
*
* where:
*
* salt is the salt in hexadecimal
* hash is the hashed password with the parameters above, in hexadecimal
*/
void
relay_auth_parse_sha (const char *parameters,
char **salt_hexa, char **salt, int *salt_size,
char **hash)
{
char **argv;
int argc;
*salt_hexa = NULL;
*salt = NULL;
*salt_size = 0;
*hash = NULL;
if (!parameters)
return;
argv = weechat_string_split (parameters, ":", NULL, 0, 0, &argc);
if (!argv || (argc < 2))
{
/* not enough parameters */
if (argv)
weechat_string_free_split (argv);
return;
}
/* parameter 1: salt */
*salt = malloc (strlen (argv[0]) + 1);
if (*salt)
{
*salt_size = weechat_string_base_decode (16, argv[0], *salt);
if (*salt_size > 0)
*salt_hexa = strdup (argv[0]);
else
{
free (*salt);
*salt = NULL;
}
}
/* parameter 2: the SHA256 or SHA512 hash */
*hash = strdup (argv[1]);
weechat_string_free_split (argv);
}
/*
* Parses PBKDF2 parameters from string with format:
*
* salt:iterations:hash
*
* where:
*
* salt is the salt in hexadecimal
* iterations it the number of iterations (≥ 1)
* hash is the hashed password with the parameters above, in hexadecimal
*/
void
relay_auth_parse_pbkdf2 (const char *parameters,
char **salt_hexa, char **salt, int *salt_size,
int *iterations, char **hash)
{
char **argv, *error;
int argc;
*salt_hexa = NULL;
*salt = NULL;
*salt_size = 0;
*iterations = 0;
*hash = NULL;
if (!parameters)
return;
argv = weechat_string_split (parameters, ":", NULL, 0, 0, &argc);
if (!argv || (argc < 3))
{
/* not enough parameters */
if (argv)
weechat_string_free_split (argv);
return;
}
/* parameter 1: salt */
*salt = malloc (strlen (argv[0]) + 1);
if (*salt)
{
*salt_size = weechat_string_base_decode (16, argv[0], *salt);
if (*salt_size > 0)
*salt_hexa = strdup (argv[0]);
else
{
free (*salt);
*salt = NULL;
}
}
/* parameter 2: iterations */
*iterations = (int)strtol (argv[1], &error, 10);
if (!error || error[0])
*iterations = 0;
/* parameter 3: the PBKDF2 hash */
*hash = strdup (argv[2]);
weechat_string_free_split (argv);
}
/*
* Checks if the salt received from the client is valid.
*
* It is valid if both conditions are true:
* 1. the salt is longer than the server nonce, so it means it includes a
* client nonce
* 2. the salt begins with the server nonce (client->nonce)
*
* Returns:
* 1: salt is valid
* 0: salt is not valid
*/
int
relay_auth_check_salt (struct t_relay_client *client, const char *salt_hexa)
{
return (salt_hexa
&& client->nonce
&& (strlen (salt_hexa) > strlen (client->nonce))
&& (weechat_strncasecmp (salt_hexa, client->nonce,
strlen (client->nonce)) == 0)) ? 1 : 0;
}
/*
* Checks if password received as SHA256/SHA512 hash is valid.
*
* Returns:
* 1: password is valid
* 0: password is not valid
*/
int
relay_auth_check_hash_sha (const char *hash_algo,
const char *salt,
int salt_size,
const char *hash_sha,
const char *relay_password)
{
char *salt_password, hash[512 / 8], hash_hexa[((512 / 8) * 2) + 1];
int rc, length, hash_size;
rc = 0;
if (salt && (salt_size > 0) && hash_sha)
{
length = salt_size + strlen (relay_password);
salt_password = malloc (length);
if (salt_password)
{
memcpy (salt_password, salt, salt_size);
memcpy (salt_password + salt_size, relay_password,
strlen (relay_password));
if (weechat_crypto_hash (salt_password, length,
hash_algo,
hash, &hash_size))
{
weechat_string_base_encode (16, hash, hash_size,
hash_hexa);
if (weechat_strcasecmp (hash_hexa, hash_sha) == 0)
rc = 1;
}
free (salt_password);
}
}
return rc;
}
/*
* Checks if password received as PBKDF2 hash is valid.
*
* Returns:
* 1: password is valid
* 0: password is not valid
*/
int
relay_auth_check_hash_pbkdf2 (const char *hash_pbkdf2_algo,
const char *salt,
int salt_size,
int iterations,
const char *hash_pbkdf2,
const char *relay_password)
{
char hash[512 / 8], hash_hexa[((512 / 8) * 2) + 1];
int rc, hash_size;
rc = 0;
if (hash_pbkdf2_algo && salt && (salt_size > 0) && hash_pbkdf2)
{
if (weechat_crypto_hash_pbkdf2 (relay_password,
strlen (relay_password),
hash_pbkdf2_algo,
salt, salt_size,
iterations,
hash, &hash_size))
{
weechat_string_base_encode (16, hash, hash_size, hash_hexa);
if (weechat_strcasecmp (hash_hexa, hash_pbkdf2) == 0)
rc = 1;
}
}
return rc;
}
/*
* Authenticates with password hash.
*
* Returns:
* 1: authentication OK
* 0: authentication failed
*/
int
relay_auth_password_hash (struct t_relay_client *client,
const char *hashed_password, const char *relay_password)
{
const char *pos_hash;
char *str_hash_algo;
char *hash_pbkdf2_algo, *salt_hexa, *salt, *hash_sha, *hash_pbkdf2;
int rc, auth_password, salt_size, iterations;
rc = 0;
str_hash_algo = NULL;
/* no authentication supported at all? */
if (client->auth_password < 0)
goto end;
if (!hashed_password || !relay_password)
goto end;
pos_hash = strchr (hashed_password, ':');
if (!pos_hash)
goto end;
str_hash_algo = weechat_strndup (hashed_password,
pos_hash - hashed_password);
if (!str_hash_algo)
goto end;
pos_hash++;
auth_password = relay_auth_password_search (str_hash_algo);
if (auth_password != client->auth_password)
goto end;
switch (auth_password)
{
case RELAY_AUTH_PASSWORD_SHA256:
case RELAY_AUTH_PASSWORD_SHA512:
relay_auth_parse_sha (pos_hash, &salt_hexa, &salt, &salt_size,
&hash_sha);
if (relay_auth_check_salt (client, salt_hexa)
&& relay_auth_check_hash_sha (str_hash_algo, salt, salt_size,
hash_sha, relay_password))
{
rc = 1;
}
if (salt_hexa)
free (salt_hexa);
if (salt)
free (salt);
if (hash_sha)
free (hash_sha);
break;
case RELAY_AUTH_PASSWORD_PBKDF2_SHA256:
case RELAY_AUTH_PASSWORD_PBKDF2_SHA512:
hash_pbkdf2_algo = strdup (str_hash_algo + 7);
relay_auth_parse_pbkdf2 (pos_hash, &salt_hexa, &salt, &salt_size,
&iterations, &hash_pbkdf2);
if ((iterations == client->hash_iterations)
&& relay_auth_check_salt (client, salt_hexa)
&& relay_auth_check_hash_pbkdf2 (hash_pbkdf2_algo, salt,
salt_size, iterations,
hash_pbkdf2, relay_password))
{
rc = 1;
}
if (hash_pbkdf2_algo)
free (hash_pbkdf2_algo);
if (salt_hexa)
free (salt_hexa);
if (salt)
free (salt);
if (hash_pbkdf2)
free (hash_pbkdf2);
break;
case RELAY_NUM_PASSWORD_AUTHS:
break;
}
end:
if (str_hash_algo)
free (str_hash_algo);
return rc;
}