1
0
mirror of https://github.com/weechat/weechat.git synced 2026-06-28 05:46:38 +02:00

irc: add support of "ecdsa-nist256p-challenge" SASL mechanism (closes #251)

This commit is contained in:
Sébastien Helleu
2015-01-19 23:52:33 +01:00
parent 083a6c741d
commit e2be01833f
32 changed files with 1096 additions and 248 deletions
+8
View File
@@ -4477,6 +4477,14 @@ irc_command_display_server (struct t_irc_server *server, int with_detail)
weechat_printf (NULL, " sasl_password. . . . : %s%s",
IRC_COLOR_CHAT_VALUE,
_("(hidden)"));
/* sasl_key */
if (weechat_config_option_is_null (server->options[IRC_SERVER_OPTION_SASL_KEY]))
weechat_printf (NULL, " sasl_key. . . . . . : ('%s')",
IRC_SERVER_OPTION_STRING(server, IRC_SERVER_OPTION_SASL_KEY));
else
weechat_printf (NULL, " sasl_key. . . . . . : %s'%s'",
IRC_COLOR_CHAT_VALUE,
weechat_config_string (server->options[IRC_SERVER_OPTION_SASL_KEY]));
/* sasl_timeout */
if (weechat_config_option_is_null (server->options[IRC_SERVER_OPTION_SASL_TIMEOUT]))
weechat_printf (NULL, " sasl_timeout . . . . : (%d %s)",
+32 -7
View File
@@ -1723,11 +1723,18 @@ irc_config_server_new_option (struct t_config_file *config_file,
new_option = weechat_config_new_option (
config_file, section,
option_name, "integer",
N_("mechanism for SASL authentication: \"plain\" for plain text "
"password, \"dh-blowfish\" for blowfish crypted password, "
"\"dh-aes\" for AES crypted password, \"external\" "
"for authentication using client side SSL cert"),
"plain|dh-blowfish|dh-aes|external", 0, 0,
N_("mechanism for SASL authentication: "
"\"plain\" for plain text password, "
"\"ecdsa-nist256p-challenge\" for key-based "
"challenge authentication, "
"\"external\" for authentication using client side SSL "
"cert, "
"\"dh-blowfish\" for blowfish crypted password "
"(insecure, not recommended), "
"\"dh-aes\" for AES crypted password "
"(insecure, not recommended)"),
"plain|ecdsa-nist256p-challenge|external|dh-blowfish|dh-aes",
0, 0,
default_value, value,
null_value_allowed,
callback_check_value, callback_check_value_data,
@@ -1738,7 +1745,8 @@ irc_config_server_new_option (struct t_config_file *config_file,
new_option = weechat_config_new_option (
config_file, section,
option_name, "string",
N_("username for SASL authentication "
N_("username for SASL authentication; this option is not used "
"for mechanism \"external\" "
"(note: content is evaluated, see /help eval)"),
NULL, 0, 0,
default_value, value,
@@ -1751,7 +1759,9 @@ irc_config_server_new_option (struct t_config_file *config_file,
new_option = weechat_config_new_option (
config_file, section,
option_name, "string",
N_("password for SASL authentication "
N_("password for SASL authentication; this option is not used "
"for mechanisms \"ecdsa-nist256p-challenge\" and "
"\"external\" "
"(note: content is evaluated, see /help eval)"),
NULL, 0, 0,
default_value, value,
@@ -1760,6 +1770,21 @@ irc_config_server_new_option (struct t_config_file *config_file,
callback_change, callback_change_data,
NULL, NULL);
break;
case IRC_SERVER_OPTION_SASL_KEY:
new_option = weechat_config_new_option (
config_file, section,
option_name, "string",
N_("file with ECC private key for mechanism "
"\"ecdsa-nist256p-challenge\" "
"(\"%h\" will be replaced by WeeChat home, "
"\"~/.weechat\" by default)"),
NULL, 0, 0,
default_value, value,
null_value_allowed,
callback_check_value, callback_check_value_data,
callback_change, callback_change_data,
NULL, NULL);
break;
case IRC_SERVER_OPTION_SASL_TIMEOUT:
new_option = weechat_config_new_option (
config_file, section,
+36 -40
View File
@@ -205,6 +205,7 @@ IRC_PROTOCOL_CALLBACK(authenticate)
{
int sasl_mechanism;
char *sasl_username, *sasl_password, *answer;
const char *sasl_key;
IRC_PROTOCOL_MIN_ARGS(2);
@@ -218,26 +219,28 @@ IRC_PROTOCOL_CALLBACK(authenticate)
sasl_password = weechat_string_eval_expression (
IRC_SERVER_OPTION_STRING(server, IRC_SERVER_OPTION_SASL_PASSWORD),
NULL, NULL, NULL);
sasl_key = IRC_SERVER_OPTION_STRING(server, IRC_SERVER_OPTION_SASL_KEY);
answer = NULL;
switch (sasl_mechanism)
{
case IRC_SASL_MECHANISM_DH_BLOWFISH:
answer = irc_sasl_mechanism_dh_blowfish (argv_eol[1],
sasl_username,
sasl_password);
case IRC_SASL_MECHANISM_PLAIN:
answer = irc_sasl_mechanism_plain (sasl_username,
sasl_password);
break;
case IRC_SASL_MECHANISM_DH_AES:
answer = irc_sasl_mechanism_dh_aes (argv_eol[1],
sasl_username,
sasl_password);
case IRC_SASL_MECHANISM_ECDSA_NIST256P_CHALLENGE:
answer = irc_sasl_mechanism_ecdsa_nist256p_challenge (
server, argv[1], sasl_username, sasl_key);
break;
case IRC_SASL_MECHANISM_EXTERNAL:
answer = strdup ("+");
break;
case IRC_SASL_MECHANISM_PLAIN:
default:
answer = irc_sasl_mechanism_plain (sasl_username,
sasl_password);
case IRC_SASL_MECHANISM_DH_BLOWFISH:
answer = irc_sasl_mechanism_dh_blowfish (
argv[1], sasl_username, sasl_password);
break;
case IRC_SASL_MECHANISM_DH_AES:
answer = irc_sasl_mechanism_dh_aes (
argv[1], sasl_username, sasl_password);
break;
}
if (answer)
@@ -303,8 +306,10 @@ IRC_PROTOCOL_CALLBACK(away)
IRC_PROTOCOL_CALLBACK(cap)
{
char *ptr_caps, **caps_supported, **caps_requested, *cap_option, *cap_req;
char str_msg_auth[512];
const char *ptr_cap_option;
int num_caps_supported, num_caps_requested, sasl_requested, sasl_to_do;
int num_caps_supported, num_caps_requested;
int sasl_requested, sasl_to_do, sasl_mechanism;
int i, j, timeout, length;
IRC_PROTOCOL_MIN_ARGS(4);
@@ -437,35 +442,26 @@ IRC_PROTOCOL_CALLBACK(cap)
}
if (sasl_to_do)
{
switch (IRC_SERVER_OPTION_INTEGER(server,
IRC_SERVER_OPTION_SASL_MECHANISM))
sasl_mechanism = IRC_SERVER_OPTION_INTEGER(
server, IRC_SERVER_OPTION_SASL_MECHANISM);
if ((sasl_mechanism >= 0)
&& (sasl_mechanism < IRC_NUM_SASL_MECHANISMS))
{
case IRC_SASL_MECHANISM_DH_BLOWFISH:
irc_server_sendf (server, 0, NULL,
"AUTHENTICATE DH-BLOWFISH");
break;
case IRC_SASL_MECHANISM_DH_AES:
irc_server_sendf (server, 0, NULL,
"AUTHENTICATE DH-AES");
break;
case IRC_SASL_MECHANISM_EXTERNAL:
irc_server_sendf (server, 0, NULL,
"AUTHENTICATE EXTERNAL");
break;
case IRC_SASL_MECHANISM_PLAIN:
default:
irc_server_sendf (server, 0, NULL,
"AUTHENTICATE PLAIN");
break;
snprintf (str_msg_auth, sizeof (str_msg_auth),
"AUTHENTICATE %s",
irc_sasl_mechanism_string[sasl_mechanism]);
weechat_string_toupper(str_msg_auth);
irc_server_sendf (server, 0, NULL, str_msg_auth);
if (server->hook_timer_sasl)
weechat_unhook (server->hook_timer_sasl);
timeout = IRC_SERVER_OPTION_INTEGER(
server, IRC_SERVER_OPTION_SASL_TIMEOUT);
server->hook_timer_sasl = weechat_hook_timer (
timeout * 1000,
0, 1,
&irc_server_timer_sasl_cb,
server);
}
if (server->hook_timer_sasl)
weechat_unhook (server->hook_timer_sasl);
timeout = IRC_SERVER_OPTION_INTEGER(server,
IRC_SERVER_OPTION_SASL_TIMEOUT);
server->hook_timer_sasl = weechat_hook_timer (timeout * 1000,
0, 1,
&irc_server_timer_sasl_cb,
server);
}
}
}
+225 -3
View File
@@ -25,13 +25,23 @@
#include <arpa/inet.h>
#include <gcrypt.h>
#ifdef HAVE_GNUTLS
#include <gnutls/gnutls.h>
#include <gnutls/abstract.h>
#endif
#include "../weechat-plugin.h"
#include "irc.h"
#include "irc-sasl.h"
#include "irc-server.h"
/*
* these names are sent to the IRC server (as upper case), so they must be
* valid values for the AUTHENTICATE command (example: "AUTHENTICATE PLAIN")
*/
char *irc_sasl_mechanism_string[IRC_NUM_SASL_MECHANISMS] =
{ "plain", "dh-blowfish", "dh-aes", "external" };
{ "plain", "ecdsa-nist256p-challenge", "external", "dh-blowfish", "dh-aes" };
/*
@@ -67,6 +77,216 @@ irc_sasl_mechanism_plain (const char *sasl_username, const char *sasl_password)
return answer_base64;
}
/*
* Returns the content of file with SASL key.
*
* Note: result must be freed after use.
*/
char *
irc_sasl_get_key_content (struct t_irc_server *server, const char *sasl_key)
{
const char *weechat_dir;
char *key_path1, *key_path2, *content;
if (!sasl_key)
return NULL;
content = NULL;
weechat_dir = weechat_info_get ("weechat_dir", "");
key_path1 = weechat_string_replace (sasl_key, "%h", weechat_dir);
key_path2 = (key_path1) ?
weechat_string_expand_home (key_path1) : NULL;
if (key_path2)
content = weechat_file_get_content (key_path2);
if (!content)
{
weechat_printf (
server->buffer,
_("%s%s: unable to read private key in file \"%s\""),
weechat_prefix ("error"),
IRC_PLUGIN_NAME,
(key_path2) ? key_path2 : ((key_path1) ? key_path1 : sasl_key));
}
if (key_path1)
free (key_path1);
if (key_path2)
free (key_path2);
return content;
}
/*
* Builds answer for SASL authentication, using mechanism
* "ECDSA-NIST256P-CHALLENGE".
*
* Note: result must be freed after use.
*/
char *
irc_sasl_mechanism_ecdsa_nist256p_challenge (struct t_irc_server *server,
const char *data_base64,
const char *sasl_username,
const char *sasl_key)
{
char *data, *string, *answer_base64;
int length_data, length_username, length, ret;
char *str_privkey, *pubkey, *pubkey_base64;
gnutls_x509_privkey_t x509_privkey;
gnutls_privkey_t privkey;
gnutls_ecc_curve_t curve;
gnutls_datum_t filedatum, x, y, k, decoded_data, signature;
answer_base64 = NULL;
string = NULL;
length = 0;
if (strcmp (data_base64, "+") == 0)
{
/* send "username" + '\0' + "username" */
answer_base64 = NULL;
length_username = strlen (sasl_username);
length = length_username + 1 + length_username;
string = malloc (length + 1);
if (string)
{
snprintf (string, length + 1, "%s|%s", sasl_username, sasl_username);
string[length_username] = '\0';
}
}
else
{
/* sign the challenge with the private key and return the result */
/* decode the challenge */
data = malloc (strlen (data_base64) + 1);
if (!data)
return NULL;
length_data = weechat_string_decode_base64 (data_base64, data);
/* read file with private key */
str_privkey = irc_sasl_get_key_content (server, sasl_key);
if (!str_privkey)
{
free (data);
return NULL;
}
/* import key */
gnutls_x509_privkey_init (&x509_privkey);
gnutls_privkey_init (&privkey);
filedatum.data = (unsigned char *)str_privkey;
filedatum.size = strlen (str_privkey);
ret = gnutls_x509_privkey_import (x509_privkey, &filedatum,
GNUTLS_X509_FMT_PEM);
free (str_privkey);
if (ret != GNUTLS_E_SUCCESS)
{
weechat_printf (
server->buffer,
_("%sgnutls: invalid private key file: error %d %s"),
weechat_prefix ("error"),
ret,
gnutls_strerror (ret));
gnutls_x509_privkey_deinit (x509_privkey);
gnutls_privkey_deinit (privkey);
free (data);
return NULL;
}
/* read raw values in key, to display public key */
ret = gnutls_x509_privkey_export_ecc_raw (x509_privkey,
&curve, &x, &y, &k);
if (ret == GNUTLS_E_SUCCESS)
{
pubkey = malloc (x.size + 1);
if (pubkey)
{
pubkey[0] = (y.data[y.size - 1] & 1) ? 0x03 : 0x02;
memcpy (pubkey + 1, x.data, x.size);
pubkey_base64 = malloc ((x.size + 1 + 1) * 4);
if (pubkey_base64)
{
weechat_string_encode_base64 (pubkey, x.size + 1,
pubkey_base64);
weechat_printf (
server->buffer,
_("%s%s: signing the challenge with ECC public key: "
"%s"),
weechat_prefix ("network"),
IRC_PLUGIN_NAME,
pubkey_base64);
free (pubkey_base64);
}
free (pubkey);
}
gnutls_free (x.data);
gnutls_free (y.data);
gnutls_free (k.data);
}
/* import private key in an abstract key structure */
ret = gnutls_privkey_import_x509 (privkey, x509_privkey, 0);
if (ret != GNUTLS_E_SUCCESS)
{
weechat_printf (
server->buffer,
_("%sgnutls: unable to import the private key: error %d %s"),
weechat_prefix ("error"),
ret,
gnutls_strerror (ret));
gnutls_x509_privkey_deinit (x509_privkey);
gnutls_privkey_deinit (privkey);
free (data);
return NULL;
}
decoded_data.data = (unsigned char *)data;
decoded_data.size = length_data;
ret = gnutls_privkey_sign_hash (privkey, GNUTLS_DIG_SHA256, 0,
&decoded_data, &signature);
if (ret != GNUTLS_E_SUCCESS)
{
weechat_printf (
server->buffer,
_("%sgnutls: unable to sign the hashed data: error %d %s"),
weechat_prefix ("error"),
ret,
gnutls_strerror (ret));
gnutls_x509_privkey_deinit (x509_privkey);
gnutls_privkey_deinit (privkey);
free (data);
return NULL;
}
gnutls_x509_privkey_deinit (x509_privkey);
gnutls_privkey_deinit (privkey);
string = malloc (signature.size);
if (string)
memcpy (string, signature.data, signature.size);
length = signature.size;
gnutls_free (signature.data);
free (data);
}
if (string && (length > 0))
{
answer_base64 = malloc ((length + 1) * 4);
if (answer_base64)
weechat_string_encode_base64 (string, length, answer_base64);
free (string);
}
return answer_base64;
}
/*
* Reads key sent by server (Diffie-Hellman key exchange).
*
@@ -186,6 +406,7 @@ dhend:
*
* Note: result must be freed after use.
*/
char *
irc_sasl_mechanism_dh_blowfish (const char *data_base64,
const char *sasl_username,
@@ -249,7 +470,7 @@ irc_sasl_mechanism_dh_blowfish (const char *data_base64,
memcpy (ptr_answer, password_crypted, length_password);
/* encode answer to base64 */
answer_base64 = malloc (length_answer * 4);
answer_base64 = malloc ((length_answer + 1) * 4);
if (answer_base64)
weechat_string_encode_base64 (answer, length_answer, answer_base64);
@@ -279,6 +500,7 @@ bfend:
*
* Note: result must be freed after use.
*/
char *
irc_sasl_mechanism_dh_aes (const char *data_base64,
const char *sasl_username,
@@ -370,7 +592,7 @@ irc_sasl_mechanism_dh_aes (const char *data_base64,
memcpy (ptr_answer, userpass_crypted, length_userpass);
/* encode answer to base64 */
answer_base64 = malloc (length_answer * 4);
answer_base64 = malloc ((length_answer + 1) * 4);
if (answer_base64)
weechat_string_encode_base64 (answer, length_answer, answer_base64);
+8 -1
View File
@@ -20,14 +20,17 @@
#ifndef WEECHAT_IRC_SASL_H
#define WEECHAT_IRC_SASL_H 1
struct t_irc_server;
/* SASL authentication mechanisms */
enum t_irc_sasl_mechanism
{
IRC_SASL_MECHANISM_PLAIN = 0,
IRC_SASL_MECHANISM_ECDSA_NIST256P_CHALLENGE,
IRC_SASL_MECHANISM_EXTERNAL,
IRC_SASL_MECHANISM_DH_BLOWFISH,
IRC_SASL_MECHANISM_DH_AES,
IRC_SASL_MECHANISM_EXTERNAL,
/* number of SASL mechanisms */
IRC_NUM_SASL_MECHANISMS,
};
@@ -36,6 +39,10 @@ extern char *irc_sasl_mechanism_string[];
extern char *irc_sasl_mechanism_plain (const char *sasl_username,
const char *sasl_password);
extern char *irc_sasl_mechanism_ecdsa_nist256p_challenge (struct t_irc_server *server,
const char *data_base64,
const char *sasl_username,
const char *sasl_key);
extern char *irc_sasl_mechanism_dh_blowfish (const char *data_base64,
const char *sasl_username,
const char *sasl_password);
+21 -3
View File
@@ -89,6 +89,7 @@ char *irc_server_options[IRC_SERVER_NUM_OPTIONS][2] =
{ "sasl_mechanism", "plain" },
{ "sasl_username", "" },
{ "sasl_password", "" },
{ "sasl_key", "", },
{ "sasl_timeout", "15" },
{ "sasl_fail", "continue" },
{ "autoconnect", "off" },
@@ -335,6 +336,7 @@ irc_server_sasl_enabled (struct t_irc_server *server)
{
int sasl_mechanism, rc;
char *sasl_username, *sasl_password;
const char *sasl_key;
sasl_mechanism = IRC_SERVER_OPTION_INTEGER(
server, IRC_SERVER_OPTION_SASL_MECHANISM);
@@ -344,12 +346,18 @@ irc_server_sasl_enabled (struct t_irc_server *server)
sasl_password = weechat_string_eval_expression (
IRC_SERVER_OPTION_STRING(server, IRC_SERVER_OPTION_SASL_PASSWORD),
NULL, NULL, NULL);
sasl_key = IRC_SERVER_OPTION_STRING(server, IRC_SERVER_OPTION_SASL_KEY);
/*
* SASL is enabled if using mechanism "external"
* or if both username AND password are set
* SASL is enabled if one of these conditions is true:
* - mechanism is "external"
* - mechanism is "ecdsa-nist256p-challenge" with username/key set
* - another mechanism with username/password set
*/
rc = ((sasl_mechanism == IRC_SASL_MECHANISM_EXTERNAL)
|| ((sasl_mechanism == IRC_SASL_MECHANISM_ECDSA_NIST256P_CHALLENGE)
&& sasl_username && sasl_username[0]
&& sasl_key && sasl_key[0])
|| (sasl_username && sasl_username[0]
&& sasl_password && sasl_password[0])) ? 1 : 0;
@@ -5220,6 +5228,9 @@ irc_server_add_to_infolist (struct t_infolist *infolist,
if (!weechat_infolist_new_var_string (ptr_item, "sasl_password",
IRC_SERVER_OPTION_STRING(server, IRC_SERVER_OPTION_SASL_PASSWORD)))
return 0;
if (!weechat_infolist_new_var_string (ptr_item, "sasl_key",
IRC_SERVER_OPTION_STRING(server, IRC_SERVER_OPTION_SASL_KEY)))
return 0;
if (!weechat_infolist_new_var_integer (ptr_item, "sasl_fail",
IRC_SERVER_OPTION_INTEGER(server, IRC_SERVER_OPTION_SASL_FAIL)))
return 0;
@@ -5475,12 +5486,19 @@ irc_server_print_log ()
IRC_SERVER_OPTION_STRING(ptr_server, IRC_SERVER_OPTION_SASL_USERNAME));
else
weechat_log_printf (" sasl_username. . . . : '%s'",
weechat_config_string (ptr_server->options[IRC_SERVER_OPTION_USERNAME]));
weechat_config_string (ptr_server->options[IRC_SERVER_OPTION_SASL_USERNAME]));
/* sasl_password */
if (weechat_config_option_is_null (ptr_server->options[IRC_SERVER_OPTION_SASL_PASSWORD]))
weechat_log_printf (" sasl_password. . . . : null");
else
weechat_log_printf (" sasl_password. . . . : (hidden)");
/* sasl_key */
if (weechat_config_option_is_null (ptr_server->options[IRC_SERVER_OPTION_SASL_KEY]))
weechat_log_printf (" sasl_key. . . . . . : null ('%s')",
IRC_SERVER_OPTION_STRING(ptr_server, IRC_SERVER_OPTION_SASL_KEY));
else
weechat_log_printf (" sasl_key. . . . . . : '%s'",
weechat_config_string (ptr_server->options[IRC_SERVER_OPTION_SASL_KEY]));
/* sasl_fail */
if (weechat_config_option_is_null (ptr_server->options[IRC_SERVER_OPTION_SASL_FAIL]))
weechat_log_printf (" sasl_fail. . . . . . : null ('%s')",
+1
View File
@@ -57,6 +57,7 @@ enum t_irc_server_option
IRC_SERVER_OPTION_SASL_MECHANISM,/* mechanism for SASL authentication */
IRC_SERVER_OPTION_SASL_USERNAME, /* username for SASL authentication */
IRC_SERVER_OPTION_SASL_PASSWORD, /* password for SASL authentication */
IRC_SERVER_OPTION_SASL_KEY, /* key for ECDSA-NIST256P-CHALLENCE */
IRC_SERVER_OPTION_SASL_TIMEOUT, /* timeout for SASL authentication */
IRC_SERVER_OPTION_SASL_FAIL, /* action on SASL fail */
IRC_SERVER_OPTION_AUTOCONNECT, /* autoconnect to server at startup */