wip: scuttler is a gobject, authentication
This commit is contained in:
parent
661867e0ba
commit
c59f8ab201
@ -5,9 +5,13 @@ i18n = import('i18n')
|
|||||||
|
|
||||||
glib_required = '>= 2.40'
|
glib_required = '>= 2.40'
|
||||||
gtk_required = '>= 3.20'
|
gtk_required = '>= 3.20'
|
||||||
|
json_glib_required = '>= 1.4.0'
|
||||||
|
libsodium_required = '>= 1.0.0'
|
||||||
|
|
||||||
glib = dependency('glib-2.0', version: glib_required)
|
glib = dependency('glib-2.0', version: glib_required)
|
||||||
gtk = dependency('gtk+-3.0', version: gtk_required)
|
gtk = dependency('gtk+-3.0', version: gtk_required)
|
||||||
|
json_glib = dependency('json-glib-1.0', version: json_glib_required)
|
||||||
|
libsodium = dependency('libsodium', version: libsodium_required)
|
||||||
|
|
||||||
subdir('data')
|
subdir('data')
|
||||||
subdir('ssb-gtk')
|
subdir('ssb-gtk')
|
||||||
|
@ -6,5 +6,5 @@ sources = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
executable('ssb-gtk', sources, ssb_resources,
|
executable('ssb-gtk', sources, ssb_resources,
|
||||||
dependencies: [glib, gtk],
|
dependencies: [glib, gtk, json_glib, libsodium],
|
||||||
install: true)
|
install: true)
|
||||||
|
674
ssb-gtk/sbot.c
674
ssb-gtk/sbot.c
@ -1,22 +1,678 @@
|
|||||||
#include <glib.h>
|
#include <glib.h>
|
||||||
|
#include <json-glib/json-glib.h>
|
||||||
|
#include <sodium.h>
|
||||||
|
|
||||||
gboolean do_scuttling = TRUE;
|
#include "sbot.h"
|
||||||
|
|
||||||
|
struct _SsbScuttler {
|
||||||
|
GObject parent_instance;
|
||||||
|
|
||||||
|
// If TRUE, the scuttler is already initialised
|
||||||
|
gboolean initialised;
|
||||||
|
|
||||||
|
// The SSB directory
|
||||||
|
gchar *ssb_dir;
|
||||||
|
|
||||||
|
guchar *app_key;
|
||||||
|
guchar *private_key;
|
||||||
|
guchar *encrypt_key;
|
||||||
|
guchar *decrypt_key;
|
||||||
|
guchar nonce1[24];
|
||||||
|
guchar nonce2[24];
|
||||||
|
guchar rx_nonce[24];
|
||||||
|
gsize rx_buf_pos;
|
||||||
|
gsize rx_buf_len;
|
||||||
|
gboolean noauth;
|
||||||
|
gboolean wrote_goodbye;
|
||||||
|
GSocketClient *socket_client;
|
||||||
|
GSocketConnection *connection;
|
||||||
|
};
|
||||||
|
|
||||||
|
G_DEFINE_QUARK(ssb_scttler_error_quark, ssb_scuttler_error);
|
||||||
|
G_DEFINE_TYPE(SsbScuttler, ssb_scuttler, G_TYPE_INITIALLY_UNOWNED);
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
gboolean valid;
|
||||||
|
guint line;
|
||||||
|
guint column;
|
||||||
|
} ParseData;
|
||||||
|
|
||||||
|
static const guchar zeros[24] = {0};
|
||||||
|
|
||||||
|
static const guchar ssb_cap[] = {
|
||||||
|
0xd4, 0xa1, 0xcb, 0x88, 0xa6, 0x6f, 0x02, 0xf8,
|
||||||
|
0xdb, 0x63, 0x5c, 0xe2, 0x64, 0x41, 0xcc, 0x5d,
|
||||||
|
0xac, 0x1b, 0x08, 0x42, 0x0c, 0xea, 0xac, 0x23,
|
||||||
|
0x08, 0x39, 0xb7, 0x55, 0x84, 0x5a, 0x9f, 0xfb
|
||||||
|
};
|
||||||
|
|
||||||
|
GMainLoop *scuttle_loop = NULL;
|
||||||
|
SsbScuttler *singleton = NULL;
|
||||||
|
|
||||||
|
static gchar *
|
||||||
|
read_config(const gchar *base_dir, const gchar *file_name, GError **error) {
|
||||||
|
gchar *config_file_name;
|
||||||
|
GFile *config_file;
|
||||||
|
GFileInfo *config_info;
|
||||||
|
goffset config_size;
|
||||||
|
GFileInputStream *config_stream;
|
||||||
|
GError *err = NULL;
|
||||||
|
gchar *config_data = NULL;
|
||||||
|
|
||||||
|
config_file_name = g_strdup_printf("%s/%s", base_dir, file_name);
|
||||||
|
config_file = g_file_new_for_path(config_file_name);
|
||||||
|
g_free(config_file_name);
|
||||||
|
|
||||||
|
if ((config_info = g_file_query_info(config_file, G_FILE_ATTRIBUTE_STANDARD_SIZE, G_FILE_QUERY_INFO_NONE, NULL, &err)) == NULL) {
|
||||||
|
g_propagate_error(error, err);
|
||||||
|
|
||||||
|
goto ret_err1;
|
||||||
|
}
|
||||||
|
|
||||||
|
config_size = g_file_info_get_size(config_info);
|
||||||
|
|
||||||
|
config_data = g_new0(gchar, config_size);
|
||||||
|
|
||||||
|
if ((config_stream = g_file_read(config_file, NULL, &err)) == NULL) {
|
||||||
|
g_propagate_error(error, err);
|
||||||
|
g_clear_pointer(&config_data, g_free);
|
||||||
|
|
||||||
|
goto ret_err2;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!g_input_stream_read_all(G_INPUT_STREAM(config_stream), config_data, config_size, NULL, NULL, &err)) {
|
||||||
|
g_propagate_error(error, err);
|
||||||
|
g_clear_pointer(&config_data, g_free);
|
||||||
|
|
||||||
|
goto ret_err3;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!g_input_stream_close(G_INPUT_STREAM(config_stream), NULL, &err)) {
|
||||||
|
g_propagate_error(error, err);
|
||||||
|
g_clear_pointer(&config_data, g_free);
|
||||||
|
|
||||||
|
goto ret_err3;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret_err3:
|
||||||
|
g_object_unref(config_info);
|
||||||
|
|
||||||
|
ret_err2:
|
||||||
|
g_object_unref(config_stream);
|
||||||
|
|
||||||
|
ret_err1:
|
||||||
|
g_object_unref(config_file);
|
||||||
|
|
||||||
|
return config_data;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
json_error(JsonParser *parser, GError *error, ParseData *parse_data)
|
||||||
|
{
|
||||||
|
if ((error->domain != JSON_PARSER_ERROR) || (error->code != JSON_PARSER_ERROR_INVALID_BAREWORD)) {
|
||||||
|
parse_data->valid = FALSE;
|
||||||
|
} else {
|
||||||
|
parse_data->valid = TRUE;
|
||||||
|
parse_data->line = json_parser_get_current_line(parser) - 1;
|
||||||
|
parse_data->column = json_parser_get_current_pos(parser) - 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static JsonNode *
|
||||||
|
parse_commented_json(gchar *json_data, GError **error)
|
||||||
|
{
|
||||||
|
ParseData parse_data;
|
||||||
|
JsonParser *parser;
|
||||||
|
GError *err = NULL;
|
||||||
|
JsonNode *result = NULL;
|
||||||
|
|
||||||
|
parse_data.valid = FALSE;
|
||||||
|
parser = json_parser_new();
|
||||||
|
g_signal_connect(parser, "error", G_CALLBACK(json_error), &parse_data);
|
||||||
|
|
||||||
|
while (!json_parser_load_from_data(parser, json_data, -1, &err)) {
|
||||||
|
gchar **config_lines;
|
||||||
|
|
||||||
|
if (parse_data.valid != TRUE) {
|
||||||
|
g_propagate_error(error, err);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// XXX: Will this work under OS/X and Windows?
|
||||||
|
config_lines = g_strsplit(json_data, "\n", 0);
|
||||||
|
|
||||||
|
if (config_lines[parse_data.line][parse_data.column] == '#') {
|
||||||
|
guint i;
|
||||||
|
guint start_pos = 0;
|
||||||
|
guint end_pos;
|
||||||
|
|
||||||
|
for (i = 0; i <= parse_data.line; i++) {
|
||||||
|
if (i < parse_data.line) {
|
||||||
|
start_pos += strlen(config_lines[i]) + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
end_pos = start_pos;
|
||||||
|
end_pos += strlen(config_lines[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
start_pos += parse_data.column;
|
||||||
|
|
||||||
|
for (i = start_pos; i < end_pos; i++) {
|
||||||
|
json_data[i] = ' ';
|
||||||
|
}
|
||||||
|
|
||||||
|
g_clear_error(&err);
|
||||||
|
parse_data.valid = FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_strfreev(config_lines);
|
||||||
|
}
|
||||||
|
|
||||||
|
result = json_parser_steal_root(parser);
|
||||||
|
|
||||||
|
g_object_unref(parser);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
initialise(SsbScuttler *scuttler, gchar *ssb_dir)
|
||||||
|
{
|
||||||
|
gchar *config_data = NULL;
|
||||||
|
GError *err = NULL;
|
||||||
|
JsonNode *json_content = NULL;
|
||||||
|
|
||||||
|
g_return_if_fail(scuttler != NULL);
|
||||||
|
|
||||||
|
if (scuttler->initialised == TRUE) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((config_data = read_config(ssb_dir, "config", &err)) != NULL) {
|
||||||
|
if ((json_content = parse_commented_json(config_data, &err)) == NULL) {
|
||||||
|
g_critical("Could not parse configuration data");
|
||||||
|
|
||||||
|
g_free(config_data);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_free(config_data);
|
||||||
|
g_clear_pointer(&json_content, json_node_free);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((config_data = read_config(ssb_dir, "secret", &err)) != NULL) {
|
||||||
|
JsonObject *secret_object;
|
||||||
|
gchar *private_key = NULL;
|
||||||
|
guchar *decoded_private_key = NULL;
|
||||||
|
size_t private_key_len;
|
||||||
|
gsize key_len;
|
||||||
|
|
||||||
|
if ((json_content = parse_commented_json(config_data, &err)) == NULL) {
|
||||||
|
g_critical("Could not parse secret data");
|
||||||
|
|
||||||
|
g_free(config_data);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
secret_object = json_node_get_object(json_content);
|
||||||
|
|
||||||
|
if ((private_key = g_strdup(json_object_get_string_member(secret_object, "private"))) == NULL) {
|
||||||
|
g_critical("Could not read private key, can not continue");
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
private_key_len = strlen(private_key);
|
||||||
|
|
||||||
|
if ((strlen(private_key) > 8) &&
|
||||||
|
(strcmp(private_key + private_key_len - 8, ".ed25519") == 0)) {
|
||||||
|
private_key[private_key_len - 8] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((decoded_private_key = g_base64_decode(private_key, &key_len)) == NULL) {
|
||||||
|
g_critical("Could not decode private key, can not continue");
|
||||||
|
g_free(private_key);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_free(private_key);
|
||||||
|
g_free(config_data);
|
||||||
|
g_clear_pointer(&json_content, json_node_free);
|
||||||
|
|
||||||
|
g_clear_pointer(&(scuttler->private_key), g_free);
|
||||||
|
scuttler->private_key = decoded_private_key;
|
||||||
|
}
|
||||||
|
|
||||||
|
scuttler->app_key = g_new0(guchar, sizeof(ssb_cap));
|
||||||
|
memcpy(scuttler->app_key, ssb_cap, sizeof(ssb_cap));
|
||||||
|
|
||||||
|
scuttler->initialised = TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ensure_scuttler():
|
||||||
|
* @ssb_dir: (nullable): the SSB directory
|
||||||
|
*
|
||||||
|
* Ensure the scuttler singleton is initialised with @ssb_dir as its base directory.
|
||||||
|
*/
|
||||||
|
static inline gboolean
|
||||||
|
ensure_scuttler(gchar *ssb_dir)
|
||||||
|
{
|
||||||
|
if (singleton != NULL) {
|
||||||
|
if ((ssb_dir != NULL) && g_strcmp0(ssb_dir, singleton->ssb_dir)) {
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
singleton = g_object_new(SSB_TYPE_SCUTTLER, NULL);
|
||||||
|
|
||||||
|
if (ssb_dir != NULL) {
|
||||||
|
initialise(singleton, ssb_dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
second_hook_cb()
|
||||||
|
{
|
||||||
|
g_print("Scuttling…\n");
|
||||||
|
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
ssb_scuttler_send(SsbScuttler *scuttler, gpointer data, gsize data_len, GError **error)
|
||||||
|
{
|
||||||
|
GOutputStream *stream = g_io_stream_get_output_stream(G_IO_STREAM(scuttler->connection));
|
||||||
|
GError *err = NULL;
|
||||||
|
|
||||||
|
if (!g_output_stream_write_all(stream, data, data_len, NULL, NULL, &err)) {
|
||||||
|
g_propagate_error(error, err);
|
||||||
|
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
ssb_scuttler_read(SsbScuttler *scuttler, gpointer buffer, gsize buffer_len, GError **error)
|
||||||
|
{
|
||||||
|
GInputStream *stream = g_io_stream_get_input_stream(G_IO_STREAM(scuttler->connection));
|
||||||
|
GError *err = NULL;
|
||||||
|
|
||||||
|
if (g_input_stream_read(stream, buffer, buffer_len, NULL, &err) < 0) {
|
||||||
|
g_propagate_error(error, err);
|
||||||
|
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
ssb_scuttler_shs_connect(SsbScuttler *scuttler, GError **error)
|
||||||
|
{
|
||||||
|
guchar kx_pk[crypto_box_PUBLICKEYBYTES];
|
||||||
|
guchar kx_sk[crypto_box_SECRETKEYBYTES];
|
||||||
|
guchar local_app_mac[crypto_box_PUBLICKEYBYTES];
|
||||||
|
guchar remote_app_mac[crypto_box_PUBLICKEYBYTES];
|
||||||
|
guchar remote_kx_pk[crypto_box_PUBLICKEYBYTES];
|
||||||
|
guchar buf[2 * crypto_box_PUBLICKEYBYTES];
|
||||||
|
guchar secret[crypto_box_SECRETKEYBYTES];
|
||||||
|
guchar remote_pk_curve[crypto_box_PUBLICKEYBYTES];
|
||||||
|
guchar a_bob[crypto_box_PUBLICKEYBYTES];
|
||||||
|
guchar secret2a[crypto_box_PUBLICKEYBYTES * 3];
|
||||||
|
guchar secret2[crypto_box_PUBLICKEYBYTES];
|
||||||
|
guchar shash[crypto_box_PUBLICKEYBYTES];
|
||||||
|
guchar signed1[3 * crypto_box_PUBLICKEYBYTES];
|
||||||
|
guchar sig[2 * crypto_box_PUBLICKEYBYTES];
|
||||||
|
guchar hello[3 * crypto_box_PUBLICKEYBYTES];
|
||||||
|
guchar boxed_auth[112];
|
||||||
|
guchar boxed_response[80];
|
||||||
|
guchar local_sk_curve[crypto_box_SECRETKEYBYTES];
|
||||||
|
guchar b_alice[crypto_box_SECRETKEYBYTES];
|
||||||
|
guchar secret3a[4 * crypto_box_SECRETKEYBYTES];
|
||||||
|
guchar secret3[crypto_box_SECRETKEYBYTES];
|
||||||
|
guchar signed2[160];
|
||||||
|
guchar enc_key_hashed[crypto_box_PUBLICKEYBYTES + crypto_box_SECRETKEYBYTES];
|
||||||
|
guchar dec_key_hashed[crypto_box_PUBLICKEYBYTES + crypto_box_SECRETKEYBYTES];
|
||||||
|
GError *err = NULL;
|
||||||
|
|
||||||
|
if (crypto_box_keypair(kx_pk, kx_sk) < 0) {
|
||||||
|
g_set_error(error, SSB_SCUTTLER_ERROR, SSB_SCUTTLER_ERROR_KEYGEN, "Could not generate auth keypair");
|
||||||
|
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (crypto_auth(local_app_mac, kx_pk, crypto_box_PUBLICKEYBYTES, scuttler->app_key) < 0) {
|
||||||
|
g_set_error(error, SSB_SCUTTLER_ERROR, SSB_SCUTTLER_ERROR_KEYAUTH, "Failed to generate app mac");
|
||||||
|
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send challenge
|
||||||
|
memcpy(buf, local_app_mac, crypto_box_PUBLICKEYBYTES);
|
||||||
|
memcpy(buf + crypto_box_PUBLICKEYBYTES, kx_pk, crypto_box_PUBLICKEYBYTES);
|
||||||
|
|
||||||
|
if (!ssb_scuttler_send(scuttler, buf, sizeof(buf), &err)) {
|
||||||
|
g_propagate_error(error, err);
|
||||||
|
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Receive challenge response
|
||||||
|
if (!ssb_scuttler_read(scuttler, buf, sizeof(buf), &err)) {
|
||||||
|
g_propagate_error(error, err);
|
||||||
|
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(remote_app_mac, buf, crypto_box_PUBLICKEYBYTES);
|
||||||
|
memcpy(remote_kx_pk, buf + crypto_box_PUBLICKEYBYTES, crypto_box_PUBLICKEYBYTES);
|
||||||
|
|
||||||
|
if (crypto_auth_verify(buf, remote_kx_pk, crypto_box_PUBLICKEYBYTES, scuttler->app_key) < 0) {
|
||||||
|
g_set_error(error, SSB_SCUTTLER_ERROR, SSB_SCUTTLER_ERROR_KEYVERIFY, "Wrong protocol (version?)");
|
||||||
|
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send auth
|
||||||
|
if (crypto_scalarmult(secret, kx_sk, remote_kx_pk) < 0) {
|
||||||
|
g_set_error(error, SSB_SCUTTLER_ERROR, SSB_SCUTTLER_ERROR_AUTH, "Failed to derive shared secret");
|
||||||
|
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (crypto_sign_ed25519_pk_to_curve25519(remote_pk_curve, scuttler->private_key + crypto_box_SECRETKEYBYTES) < 0) {
|
||||||
|
g_set_error(error, SSB_SCUTTLER_ERROR, SSB_SCUTTLER_ERROR_AUTH, "Failed to curvify remote public key");
|
||||||
|
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (crypto_scalarmult(a_bob, kx_sk, remote_pk_curve) < 0) {
|
||||||
|
g_set_error(error, SSB_SCUTTLER_ERROR, SSB_SCUTTLER_ERROR_AUTH, "Failed to derive a_bob");
|
||||||
|
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(secret2a, scuttler->app_key, crypto_box_PUBLICKEYBYTES);
|
||||||
|
memcpy(secret2a + crypto_box_PUBLICKEYBYTES, secret, crypto_box_PUBLICKEYBYTES);
|
||||||
|
memcpy(secret2a + 2 * crypto_box_PUBLICKEYBYTES, a_bob, crypto_box_PUBLICKEYBYTES);
|
||||||
|
|
||||||
|
if (crypto_hash_sha256(secret2, secret2a, sizeof(secret2a)) < 0) {
|
||||||
|
g_set_error(error, SSB_SCUTTLER_ERROR, SSB_SCUTTLER_ERROR_AUTH, "Failed to hash secret2");
|
||||||
|
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (crypto_hash_sha256(shash, secret, sizeof(secret)) < 0) {
|
||||||
|
g_set_error(error, SSB_SCUTTLER_ERROR, SSB_SCUTTLER_ERROR_AUTH, "Failed to hash secret");
|
||||||
|
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(signed1, scuttler->app_key, crypto_box_PUBLICKEYBYTES);
|
||||||
|
memcpy(signed1 + crypto_box_PUBLICKEYBYTES, scuttler->private_key + crypto_box_SECRETKEYBYTES, crypto_box_PUBLICKEYBYTES);
|
||||||
|
memcpy(signed1 + 2 * crypto_box_PUBLICKEYBYTES, shash, crypto_box_PUBLICKEYBYTES);
|
||||||
|
|
||||||
|
if (crypto_sign_detached(sig, NULL, signed1, sizeof(signed1), scuttler->private_key) < 0) {
|
||||||
|
g_set_error(error, SSB_SCUTTLER_ERROR, SSB_SCUTTLER_ERROR_AUTH, "Failed to sign inner hello");
|
||||||
|
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(hello, sig, 2 * crypto_box_PUBLICKEYBYTES);
|
||||||
|
memcpy(hello + 2 * crypto_box_PUBLICKEYBYTES, scuttler->private_key + crypto_box_SECRETKEYBYTES, crypto_box_PUBLICKEYBYTES);
|
||||||
|
|
||||||
|
if (crypto_secretbox_easy(boxed_auth, hello, sizeof(hello), zeros, secret2) < 0) {
|
||||||
|
g_set_error(error, SSB_SCUTTLER_ERROR, SSB_SCUTTLER_ERROR_AUTH, "Failed to box hello");
|
||||||
|
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ssb_scuttler_send(scuttler, boxed_auth, sizeof(boxed_auth), &err)) {
|
||||||
|
g_propagate_error(error, err);
|
||||||
|
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify the auth response
|
||||||
|
|
||||||
|
if (!ssb_scuttler_read(scuttler, boxed_response, sizeof(boxed_response), &err)) {
|
||||||
|
g_propagate_error(error, err);
|
||||||
|
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (crypto_sign_ed25519_sk_to_curve25519(local_sk_curve, scuttler->private_key) < 0) {
|
||||||
|
g_set_error(error, SSB_SCUTTLER_ERROR, SSB_SCUTTLER_ERROR_AUTH, "Failed to curvify local secret key");
|
||||||
|
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (crypto_scalarmult(b_alice, local_sk_curve, remote_kx_pk) < 0) {
|
||||||
|
g_set_error(error, SSB_SCUTTLER_ERROR, SSB_SCUTTLER_ERROR_AUTH, "Failed to derive b_alice");
|
||||||
|
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(secret3a, scuttler->app_key, 32);
|
||||||
|
memcpy(secret3a + crypto_box_SECRETKEYBYTES, secret, crypto_box_SECRETKEYBYTES);
|
||||||
|
memcpy(secret3a + 2 * crypto_box_SECRETKEYBYTES, a_bob, crypto_box_PUBLICKEYBYTES);
|
||||||
|
memcpy(secret3a + 2 * crypto_box_SECRETKEYBYTES + crypto_box_PUBLICKEYBYTES, b_alice, crypto_box_SECRETKEYBYTES);
|
||||||
|
|
||||||
|
if (crypto_hash_sha256(secret3, secret3a, sizeof(secret3a)) < 0) {
|
||||||
|
g_set_error(error, SSB_SCUTTLER_ERROR, SSB_SCUTTLER_ERROR_AUTH, "Failed to hash secret3");
|
||||||
|
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (crypto_secretbox_open_easy(sig, boxed_response, sizeof(boxed_response), zeros, secret3) < 0) {
|
||||||
|
g_set_error(error, SSB_SCUTTLER_ERROR, SSB_SCUTTLER_ERROR_AUTH, "Failed to unbox the okay");
|
||||||
|
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(signed2, scuttler->app_key, crypto_box_PUBLICKEYBYTES);
|
||||||
|
memcpy(signed2 + crypto_box_PUBLICKEYBYTES, hello, 96);
|
||||||
|
memcpy(signed2 + 128, shash, 32);
|
||||||
|
|
||||||
|
if (crypto_sign_verify_detached(sig, signed2, sizeof(signed2), scuttler->private_key + crypto_box_SECRETKEYBYTES) < 0) {
|
||||||
|
g_set_error(error, SSB_SCUTTLER_ERROR, SSB_SCUTTLER_ERROR_AUTH, "Server not authenticated");
|
||||||
|
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (crypto_hash_sha256(secret, secret3, crypto_box_PUBLICKEYBYTES) < 0) {
|
||||||
|
g_set_error(error, SSB_SCUTTLER_ERROR, SSB_SCUTTLER_ERROR_AUTH, "Failed to hash secret3");
|
||||||
|
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(enc_key_hashed, secret, crypto_box_SECRETKEYBYTES);
|
||||||
|
memcpy(enc_key_hashed + crypto_box_SECRETKEYBYTES, scuttler->private_key + crypto_box_SECRETKEYBYTES, crypto_box_PUBLICKEYBYTES);
|
||||||
|
|
||||||
|
g_clear_pointer(&(scuttler->encrypt_key), g_free);
|
||||||
|
scuttler->encrypt_key = g_new0(guchar, crypto_box_SECRETKEYBYTES);
|
||||||
|
|
||||||
|
if (crypto_hash_sha256(scuttler->encrypt_key, enc_key_hashed, 64) < 0) {
|
||||||
|
g_set_error(error, SSB_SCUTTLER_ERROR, SSB_SCUTTLER_ERROR_AUTH, "Failed to hash the encrypt key");
|
||||||
|
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_clear_pointer(&(scuttler->decrypt_key), g_free);
|
||||||
|
scuttler->decrypt_key = g_new0(guchar, crypto_box_SECRETKEYBYTES);
|
||||||
|
|
||||||
|
memcpy(dec_key_hashed, secret, crypto_box_SECRETKEYBYTES);
|
||||||
|
memcpy(dec_key_hashed + crypto_box_SECRETKEYBYTES, scuttler->private_key + crypto_box_SECRETKEYBYTES, crypto_box_PUBLICKEYBYTES);
|
||||||
|
|
||||||
|
if (crypto_hash_sha256(scuttler->decrypt_key, dec_key_hashed, 64) < 0) {
|
||||||
|
g_set_error(error, SSB_SCUTTLER_ERROR, SSB_SCUTTLER_ERROR_AUTH, "Failed to hash the decrypt_key");
|
||||||
|
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(scuttler->nonce1, remote_app_mac, 24);
|
||||||
|
memcpy(scuttler->nonce2, remote_app_mac, 24);
|
||||||
|
memcpy(scuttler->rx_nonce, local_app_mac, 24);
|
||||||
|
scuttler->rx_buf_pos = 0;
|
||||||
|
scuttler->rx_buf_len = 0;
|
||||||
|
scuttler->noauth = FALSE;
|
||||||
|
scuttler->wrote_goodbye = FALSE;
|
||||||
|
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
ssb_scuttler_connect(SsbScuttler *scuttler, GError **error)
|
||||||
|
{
|
||||||
|
GInetAddress *address;
|
||||||
|
GSocketAddress *destination;
|
||||||
|
GError *err = NULL;
|
||||||
|
|
||||||
|
scuttler->socket_client = g_socket_client_new();
|
||||||
|
|
||||||
|
// TODO: Implement the UNIX socket version, too! Also, read this stuff from the
|
||||||
|
// config/command line!
|
||||||
|
address = g_inet_address_new_from_string("127.0.0.1");
|
||||||
|
destination = g_inet_socket_address_new(address, 8008);
|
||||||
|
g_object_unref(address);
|
||||||
|
|
||||||
|
scuttler->connection = g_socket_client_connect(scuttler->socket_client,
|
||||||
|
G_SOCKET_CONNECTABLE(destination),
|
||||||
|
NULL,
|
||||||
|
&err);
|
||||||
|
g_object_unref(destination);
|
||||||
|
|
||||||
|
// TODO: This is only required if noauth is not set
|
||||||
|
if (!ssb_scuttler_shs_connect(scuttler, &err)) {
|
||||||
|
g_propagate_error(error, err);
|
||||||
|
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
ssb_scuttler_dispose(GObject *gobject)
|
||||||
|
{
|
||||||
|
SsbScuttler *scuttler = SSB_SCUTTLER(gobject);
|
||||||
|
GError *err = NULL;
|
||||||
|
|
||||||
|
if (scuttler->connection && !g_io_stream_is_closed(G_IO_STREAM(scuttler->connection))) {
|
||||||
|
g_io_stream_close(G_IO_STREAM(scuttler->connection), NULL, &err);
|
||||||
|
scuttler->connection = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_clear_pointer(&(scuttler->ssb_dir), g_free);
|
||||||
|
g_clear_pointer(&(scuttler->app_key), g_free);
|
||||||
|
g_clear_pointer(&(scuttler->private_key), g_free);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
ssb_scuttler_finalize(GObject *gobject)
|
||||||
|
{
|
||||||
|
SsbScuttler *scuttler = SSB_SCUTTLER(gobject);
|
||||||
|
|
||||||
|
g_clear_pointer(&(scuttler->socket_client), g_object_unref);
|
||||||
|
g_clear_pointer(&(scuttler->ssb_dir), g_free);
|
||||||
|
g_clear_pointer(&(scuttler->encrypt_key), g_free);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
ssb_scuttler_class_init(SsbScuttlerClass *klass)
|
||||||
|
{
|
||||||
|
GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
|
||||||
|
|
||||||
|
gobject_class->dispose = ssb_scuttler_dispose;
|
||||||
|
gobject_class->finalize = ssb_scuttler_finalize;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
ssb_scuttler_init(SsbScuttler *scuttler)
|
||||||
|
{
|
||||||
|
scuttler->initialised = FALSE;
|
||||||
|
scuttler->ssb_dir = NULL;
|
||||||
|
scuttler->socket_client = NULL;
|
||||||
|
scuttler->connection = NULL;
|
||||||
|
scuttler->app_key = NULL;
|
||||||
|
scuttler->private_key = NULL;
|
||||||
|
scuttler->encrypt_key = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
gpointer
|
gpointer
|
||||||
scuttle(gchar *ssb_dir)
|
scuttle(gchar *ssb_dir)
|
||||||
{
|
{
|
||||||
gchar *config_file = g_strdup_printf("%s/config", ssb_dir);
|
GMainContext *scuttle_context;
|
||||||
g_print("Read config file %s\n", config_file);
|
GSource *second_hook;
|
||||||
g_free(config_file);
|
GError *err = NULL;
|
||||||
|
|
||||||
g_print("Starting scuttle\n");
|
if (sodium_init() < 0) {
|
||||||
|
g_critical("Can not initialise sodium");
|
||||||
|
|
||||||
while (do_scuttling) {
|
return NULL;
|
||||||
g_usleep(G_USEC_PER_SEC);
|
|
||||||
g_print("Scuttle…\n");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
g_print("Scuttling stopped\n");
|
// Since scuttler is a singleton global to this file, we don’t need the return value of this
|
||||||
|
// function now.
|
||||||
|
if (G_UNLIKELY(!ensure_scuttler(ssb_dir))) {
|
||||||
|
g_critical("Can not reinitialise scuttler with a new SSB directory.");
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_object_ref_sink(singleton);
|
||||||
|
|
||||||
|
scuttle_context = g_main_context_new();
|
||||||
|
scuttle_loop = g_main_loop_new(scuttle_context, FALSE);
|
||||||
|
|
||||||
|
// TODO: This is for debugging purposes only, let’s remove it as soon as the scuttler works
|
||||||
|
second_hook = g_timeout_source_new_seconds(1);
|
||||||
|
g_source_set_callback(second_hook,
|
||||||
|
G_SOURCE_FUNC(second_hook_cb),
|
||||||
|
g_main_loop_ref(scuttle_loop),
|
||||||
|
(GDestroyNotify)g_main_loop_unref);
|
||||||
|
g_source_attach(second_hook, scuttle_context);
|
||||||
|
|
||||||
|
// Set scuttle_context as the default context for this thread
|
||||||
|
g_main_context_push_thread_default(scuttle_context);
|
||||||
|
|
||||||
|
if (!ssb_scuttler_connect(singleton, &err))
|
||||||
|
{
|
||||||
|
g_critical("Could not connect: %s", err->message);
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_debug("Starting scuttle");
|
||||||
|
|
||||||
|
g_main_loop_run(scuttle_loop);
|
||||||
|
g_main_context_pop_thread_default(scuttle_context);
|
||||||
|
g_main_loop_unref(scuttle_loop);
|
||||||
|
g_object_unref(singleton);
|
||||||
|
|
||||||
|
g_debug("Scuttling stopped");
|
||||||
|
|
||||||
|
scuttle_loop = NULL;
|
||||||
|
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
stop_scuttling(void)
|
||||||
|
{
|
||||||
|
if (scuttle_loop) {
|
||||||
|
g_main_loop_quit(scuttle_loop);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,10 +1,26 @@
|
|||||||
#ifndef __SBOT_H__
|
#ifndef __SBOT_H__
|
||||||
# define __SBOT_H__
|
# define __SBOT_H__
|
||||||
|
|
||||||
# include <glib.h>
|
# include <glib-object.h>
|
||||||
|
|
||||||
extern gboolean do_scuttling;
|
# define SSB_TYPE_SCUTTLER ssb_scuttler_get_type()
|
||||||
|
G_DECLARE_FINAL_TYPE(SsbScuttler, ssb_scuttler, SSB, SCUTTLER, GInitiallyUnowned)
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
SSB_SCUTTLER_ERROR_KEYGEN,
|
||||||
|
SSB_SCUTTLER_ERROR_KEYAUTH,
|
||||||
|
SSB_SCUTTLER_ERROR_KEYVERIFY,
|
||||||
|
SSB_SCUTTLER_ERROR_AUTH,
|
||||||
|
} SsbScuttlerError;
|
||||||
|
|
||||||
|
G_BEGIN_DECLS
|
||||||
|
|
||||||
gpointer scuttle(gchar *ssb_dir);
|
gpointer scuttle(gchar *ssb_dir);
|
||||||
|
void stop_scuttling(void);
|
||||||
|
|
||||||
|
# define SSB_SCUTTLER_ERROR (ssb_scuttler_error_quark())
|
||||||
|
GQuark ssb_scuttler_error_quark(void);
|
||||||
|
|
||||||
|
G_END_DECLS
|
||||||
|
|
||||||
#endif /* __SBOT_H__ */
|
#endif /* __SBOT_H__ */
|
||||||
|
@ -115,7 +115,7 @@ ssb_app_shutdown(GApplication *gapp)
|
|||||||
{
|
{
|
||||||
SsbApp *app = SSB_APP(gapp);
|
SsbApp *app = SSB_APP(gapp);
|
||||||
|
|
||||||
do_scuttling = FALSE;
|
stop_scuttling();
|
||||||
|
|
||||||
(void)g_thread_join(app->scuttle_thread);
|
(void)g_thread_join(app->scuttle_thread);
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user