libsecret/libsecret/secret-session.c
Niels De Graef af389ad6f8 Prefer g_info() over g_message()
Libraries shouldn't really use `g_message()` as it will show up in
consumer application outputs. Let's use `g_info()` instead.

Fixes https://gitlab.gnome.org/GNOME/libsecret/issues/40
2020-03-11 05:35:33 +00:00

664 lines
17 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* libsecret - GLib wrapper for Secret Service
*
* Copyright 2011 Collabora Ltd.
* Copyright 2012 Red Hat Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation; either version 2.1 of the licence or (at
* your option) any later version.
*
* See the included COPYING file for more information.
*
* Author: Stef Walter <stefw@gnome.org>
*/
#include "config.h"
#include "secret-private.h"
#ifdef WITH_GCRYPT
#include "egg/egg-dh.h"
#include "egg/egg-hkdf.h"
#include "egg/egg-libgcrypt.h"
#endif
#include "egg/egg-hex.h"
#include "egg/egg-secure-memory.h"
#include <glib/gi18n-lib.h>
EGG_SECURE_DECLARE (secret_session);
#define ALGORITHMS_AES "dh-ietf1024-sha256-aes128-cbc-pkcs7"
#define ALGORITHMS_PLAIN "plain"
struct _SecretSession {
gchar *path;
const gchar *algorithms;
#ifdef WITH_GCRYPT
gcry_mpi_t prime;
gcry_mpi_t privat;
gcry_mpi_t publi;
#endif
gpointer key;
gsize n_key;
};
void
_secret_session_free (gpointer data)
{
SecretSession *session = data;
if (session == NULL)
return;
g_free (session->path);
#ifdef WITH_GCRYPT
gcry_mpi_release (session->publi);
gcry_mpi_release (session->privat);
gcry_mpi_release (session->prime);
#endif
egg_secure_free (session->key);
g_free (session);
}
#ifdef WITH_GCRYPT
static GVariant *
request_open_session_aes (SecretSession *session)
{
gcry_error_t gcry;
gcry_mpi_t base;
unsigned char *buffer;
size_t n_buffer;
GVariant *argument;
g_assert (session->prime == NULL);
g_assert (session->privat == NULL);
g_assert (session->publi == NULL);
egg_libgcrypt_initialize ();
/* Initialize our local parameters and values */
if (!egg_dh_default_params ("ietf-ike-grp-modp-1024",
&session->prime, &base))
g_return_val_if_reached (NULL);
#if 0
g_printerr ("\n lib prime: ");
gcry_mpi_dump (session->prime);
g_printerr ("\n lib base: ");
gcry_mpi_dump (base);
g_printerr ("\n");
#endif
if (!egg_dh_gen_pair (session->prime, base, 0,
&session->publi, &session->privat))
g_return_val_if_reached (NULL);
gcry_mpi_release (base);
gcry = gcry_mpi_aprint (GCRYMPI_FMT_USG, &buffer, &n_buffer, session->publi);
g_return_val_if_fail (gcry == 0, NULL);
argument = g_variant_new_from_data (G_VARIANT_TYPE ("ay"),
buffer, n_buffer, TRUE,
gcry_free, buffer);
return g_variant_new ("(sv)", ALGORITHMS_AES, argument);
}
static gboolean
response_open_session_aes (SecretSession *session,
GVariant *response)
{
gconstpointer buffer;
GVariant *argument;
const gchar *sig;
gsize n_buffer;
gcry_mpi_t peer;
gcry_error_t gcry;
gpointer ikm;
gsize n_ikm;
sig = g_variant_get_type_string (response);
g_return_val_if_fail (sig != NULL, FALSE);
if (!g_str_equal (sig, "(vo)")) {
g_warning ("invalid OpenSession() response from daemon with signature: %s", sig);
return FALSE;
}
g_assert (session->path == NULL);
g_variant_get (response, "(vo)", &argument, &session->path);
buffer = g_variant_get_fixed_array (argument, &n_buffer, sizeof (guchar));
gcry = gcry_mpi_scan (&peer, GCRYMPI_FMT_USG, buffer, n_buffer, NULL);
g_return_val_if_fail (gcry == 0, FALSE);
g_variant_unref (argument);
#if 0
g_printerr (" lib publi: ");
gcry_mpi_dump (session->publi);
g_printerr ("\n lib peer: ");
gcry_mpi_dump (peer);
g_printerr ("\n");
#endif
ikm = egg_dh_gen_secret (peer, session->privat, session->prime, &n_ikm);
gcry_mpi_release (peer);
#if 0
g_printerr (" lib ikm: %s\n", egg_hex_encode (ikm, n_ikm));
#endif
if (ikm == NULL) {
g_warning ("couldn't negotiate a valid AES session key");
g_free (session->path);
session->path = NULL;
return FALSE;
}
session->n_key = 16;
session->key = egg_secure_alloc (session->n_key);
if (!egg_hkdf_perform ("sha256", ikm, n_ikm, NULL, 0, NULL, 0,
session->key, session->n_key))
g_return_val_if_reached (FALSE);
egg_secure_free (ikm);
session->algorithms = ALGORITHMS_AES;
return TRUE;
}
#endif /* WITH_GCRYPT */
static GVariant *
request_open_session_plain (SecretSession *session)
{
GVariant *argument = g_variant_new_string ("");
return g_variant_new ("(sv)", "plain", argument);
}
static gboolean
response_open_session_plain (SecretSession *session,
GVariant *response)
{
GVariant *argument;
const gchar *sig;
sig = g_variant_get_type_string (response);
g_return_val_if_fail (sig != NULL, FALSE);
if (!g_str_equal (sig, "(vo)")) {
g_warning ("invalid OpenSession() response from daemon with signature: %s",
g_variant_get_type_string (response));
return FALSE;
}
g_assert (session->path == NULL);
g_variant_get (response, "(vo)", &argument, &session->path);
g_variant_unref (argument);
g_assert (session->key == NULL);
g_assert (session->n_key == 0);
session->algorithms = ALGORITHMS_PLAIN;
return TRUE;
}
typedef struct {
SecretSession *session;
} OpenSessionClosure;
static void
open_session_closure_free (gpointer data)
{
OpenSessionClosure *closure = data;
g_assert (closure);
_secret_session_free (closure->session);
g_free (closure);
}
static void
on_service_open_session_plain (GObject *source,
GAsyncResult *result,
gpointer user_data)
{
GTask *task = G_TASK (user_data);
OpenSessionClosure *closure = g_task_get_task_data (task);
SecretService *service = SECRET_SERVICE (source);
GError *error = NULL;
GVariant *response;
response = g_dbus_proxy_call_finish (G_DBUS_PROXY (service), result, &error);
/* A successful response, decode it */
if (response != NULL) {
if (response_open_session_plain (closure->session, response)) {
_secret_service_take_session (service, closure->session);
closure->session = NULL;
g_task_return_boolean (task, TRUE);
} else {
g_task_return_new_error (task, SECRET_ERROR, SECRET_ERROR_PROTOCOL,
_("Couldnt communicate with the secret storage"));
}
g_variant_unref (response);
} else {
g_task_return_error (task, g_steal_pointer (&error));
}
g_object_unref (task);
}
#ifdef WITH_GCRYPT
static void
on_service_open_session_aes (GObject *source,
GAsyncResult *result,
gpointer user_data)
{
GTask *task = G_TASK (user_data);
OpenSessionClosure * closure = g_task_get_task_data (task);
SecretService *service = SECRET_SERVICE (source);
GError *error = NULL;
GVariant *response;
response = g_dbus_proxy_call_finish (G_DBUS_PROXY (service), result, &error);
/* A successful response, decode it */
if (response != NULL) {
if (response_open_session_aes (closure->session, response)) {
_secret_service_take_session (service, closure->session);
closure->session = NULL;
g_task_return_boolean (task, TRUE);
} else {
g_task_return_new_error (task, SECRET_ERROR, SECRET_ERROR_PROTOCOL,
_("Couldnt communicate with the secret storage"));
}
g_variant_unref (response);
} else {
/* AES session not supported, request a plain session */
if (g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_NOT_SUPPORTED)) {
g_dbus_proxy_call (G_DBUS_PROXY (source), "OpenSession",
request_open_session_plain (closure->session),
G_DBUS_CALL_FLAGS_NONE, -1,
g_task_get_cancellable (task),
on_service_open_session_plain,
g_object_ref (task));
g_error_free (error);
/* Other errors result in a failure */
} else {
g_task_return_error (task, g_steal_pointer (&error));
}
}
g_object_unref (task);
}
#endif /* WITH_GCRYPT */
void
_secret_session_open (SecretService *service,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
GTask *task;
OpenSessionClosure *closure;
task = g_task_new (service, cancellable, callback, user_data);
g_task_set_source_tag (task, _secret_session_open);
closure = g_new (OpenSessionClosure, 1);
closure->session = g_new0 (SecretSession, 1);
g_task_set_task_data (task, closure, open_session_closure_free);
g_dbus_proxy_call (G_DBUS_PROXY (service), "OpenSession",
#ifdef WITH_GCRYPT
request_open_session_aes (closure->session),
G_DBUS_CALL_FLAGS_NONE, -1,
cancellable, on_service_open_session_aes,
#else
request_open_session_plain (closure->session),
G_DBUS_CALL_FLAGS_NONE, -1,
cancellable, on_service_open_session_plain,
#endif
g_object_ref (task));
g_object_unref (task);
}
gboolean
_secret_session_open_finish (GAsyncResult *result,
GError **error)
{
if (!g_task_propagate_boolean (G_TASK (result), error)) {
_secret_util_strip_remote_error (error);
return FALSE;
}
return TRUE;
}
#ifdef WITH_GCRYPT
static gboolean
pkcs7_unpad_bytes_in_place (guchar *padded,
gsize *n_padded)
{
gsize n_pad, i;
if (*n_padded == 0)
return FALSE;
n_pad = padded[*n_padded - 1];
/* Validate the padding */
if (n_pad == 0 || n_pad > 16)
return FALSE;
if (n_pad > *n_padded)
return FALSE;
for (i = *n_padded - n_pad; i < *n_padded; ++i) {
if (padded[i] != n_pad)
return FALSE;
}
/* The last bit of data */
*n_padded -= n_pad;
/* Null teriminate as a courtesy */
padded[*n_padded] = 0;
return TRUE;
}
static SecretValue *
service_decode_aes_secret (SecretSession *session,
gconstpointer param,
gsize n_param,
gconstpointer value,
gsize n_value,
const gchar *content_type)
{
gcry_cipher_hd_t cih;
gsize n_padded;
gcry_error_t gcry;
guchar *padded;
gsize pos;
if (n_param != 16) {
g_info ("received an encrypted secret structure with invalid parameter");
return NULL;
}
if (n_value == 0 || n_value % 16 != 0) {
g_info ("received an encrypted secret structure with bad secret length");
return NULL;
}
gcry = gcry_cipher_open (&cih, GCRY_CIPHER_AES, GCRY_CIPHER_MODE_CBC, 0);
if (gcry != 0) {
g_warning ("couldn't create AES cipher: %s", gcry_strerror (gcry));
return NULL;
}
#if 0
g_printerr (" lib iv: %s\n", egg_hex_encode (param, n_param));
#endif
gcry = gcry_cipher_setiv (cih, param, n_param);
g_return_val_if_fail (gcry == 0, NULL);
#if 0
g_printerr (" lib key: %s\n", egg_hex_encode (session->key, session->n_key));
#endif
gcry = gcry_cipher_setkey (cih, session->key, session->n_key);
g_return_val_if_fail (gcry == 0, NULL);
/* Copy the memory buffer */
n_padded = n_value;
padded = egg_secure_alloc (n_padded);
memcpy (padded, value, n_padded);
/* Perform the decryption */
for (pos = 0; pos < n_padded; pos += 16) {
gcry = gcry_cipher_decrypt (cih, (guchar*)padded + pos, 16, NULL, 0);
g_return_val_if_fail (gcry == 0, FALSE);
}
gcry_cipher_close (cih);
/* Unpad the resulting value */
if (!pkcs7_unpad_bytes_in_place (padded, &n_padded)) {
egg_secure_clear (padded, n_padded);
egg_secure_free (padded);
g_info ("received an invalid or unencryptable secret");
return FALSE;
}
return secret_value_new_full ((gchar *)padded, n_padded, content_type, egg_secure_free);
}
#endif /* WITH_GCRYPT */
static SecretValue *
service_decode_plain_secret (SecretSession *session,
gconstpointer param,
gsize n_param,
gconstpointer value,
gsize n_value,
const gchar *content_type)
{
if (n_param != 0) {
g_info ("received a plain secret structure with invalid parameter");
return NULL;
}
return secret_value_new (value, n_value, content_type);
}
SecretValue *
_secret_session_decode_secret (SecretSession *session,
GVariant *encoded)
{
SecretValue *result;
gconstpointer param;
gconstpointer value;
gchar *session_path;
gchar *content_type;
gsize n_param;
gsize n_value;
GVariant *vparam;
GVariant *vvalue;
g_return_val_if_fail (session != NULL, NULL);
g_return_val_if_fail (encoded != NULL, NULL);
/* Parsing (oayays) */
g_variant_get_child (encoded, 0, "o", &session_path);
if (session_path == NULL || !g_str_equal (session_path, session->path)) {
g_info ("received a secret encoded with wrong session: %s != %s",
session_path, session->path);
g_free (session_path);
return NULL;
}
vparam = g_variant_get_child_value (encoded, 1);
param = g_variant_get_fixed_array (vparam, &n_param, sizeof (guchar));
vvalue = g_variant_get_child_value (encoded, 2);
value = g_variant_get_fixed_array (vvalue, &n_value, sizeof (guchar));
g_variant_get_child (encoded, 3, "s", &content_type);
#ifdef WITH_GCRYPT
if (session->key != NULL)
result = service_decode_aes_secret (session, param, n_param,
value, n_value, content_type);
else
#endif
result = service_decode_plain_secret (session, param, n_param,
value, n_value, content_type);
g_variant_unref (vparam);
g_variant_unref (vvalue);
g_free (content_type);
g_free (session_path);
return result;
}
#ifdef WITH_GCRYPT
static guchar*
pkcs7_pad_bytes_in_secure_memory (gconstpointer secret,
gsize length,
gsize *n_padded)
{
gsize n_pad;
guchar *padded;
/* Pad the secret */
*n_padded = ((length + 16) / 16) * 16;
g_assert (length < *n_padded);
g_assert (*n_padded > 0);
n_pad = *n_padded - length;
g_assert (n_pad > 0 && n_pad <= 16);
padded = egg_secure_alloc (*n_padded);
memcpy (padded, secret, length);
memset (padded + length, n_pad, n_pad);
return padded;
}
static gboolean
service_encode_aes_secret (SecretSession *session,
SecretValue *value,
GVariantBuilder *builder)
{
gcry_cipher_hd_t cih;
guchar *padded;
gsize n_padded, pos;
gcry_error_t gcry;
gpointer iv;
gconstpointer secret;
gsize n_secret;
GVariant *child;
g_variant_builder_add (builder, "o", session->path);
/* Create the cipher */
gcry = gcry_cipher_open (&cih, GCRY_CIPHER_AES, GCRY_CIPHER_MODE_CBC, 0);
if (gcry != 0) {
g_warning ("couldn't create AES cipher: %s", gcry_strerror (gcry));
return FALSE;
}
secret = secret_value_get (value, &n_secret);
/* Perform the encoding here */
padded = pkcs7_pad_bytes_in_secure_memory (secret, n_secret, &n_padded);
g_assert (padded != NULL);
/* Setup the IV */
iv = g_malloc0 (16);
gcry_create_nonce (iv, 16);
gcry = gcry_cipher_setiv (cih, iv, 16);
g_return_val_if_fail (gcry == 0, FALSE);
/* Setup the key */
gcry = gcry_cipher_setkey (cih, session->key, session->n_key);
g_return_val_if_fail (gcry == 0, FALSE);
/* Perform the encryption */
for (pos = 0; pos < n_padded; pos += 16) {
gcry = gcry_cipher_encrypt (cih, (guchar*)padded + pos, 16, NULL, 0);
g_return_val_if_fail (gcry == 0, FALSE);
}
gcry_cipher_close (cih);
child = g_variant_new_from_data (G_VARIANT_TYPE ("ay"), iv, 16, TRUE, g_free, iv);
g_variant_builder_add_value (builder, child);
child = g_variant_new_from_data (G_VARIANT_TYPE ("ay"), padded, n_padded, TRUE, egg_secure_free, padded);
g_variant_builder_add_value (builder, child);
g_variant_builder_add (builder, "s", secret_value_get_content_type (value));
return TRUE;
}
#endif /* WITH_GCRYPT */
static gboolean
service_encode_plain_secret (SecretSession *session,
SecretValue *value,
GVariantBuilder *builder)
{
gconstpointer secret;
gsize n_secret;
GVariant *child;
g_variant_builder_add (builder, "o", session->path);
secret = secret_value_get (value, &n_secret);
child = g_variant_new_from_data (G_VARIANT_TYPE ("ay"), "", 0, TRUE, NULL, NULL);
g_variant_builder_add_value (builder, child);
child = g_variant_new_from_data (G_VARIANT_TYPE ("ay"), secret, n_secret, TRUE,
secret_value_unref, secret_value_ref (value));
g_variant_builder_add_value (builder, child);
g_variant_builder_add (builder, "s", secret_value_get_content_type (value));
return TRUE;
}
GVariant *
_secret_session_encode_secret (SecretSession *session,
SecretValue *value)
{
GVariantBuilder *builder;
GVariant *result = NULL;
GVariantType *type;
gboolean ret;
g_return_val_if_fail (session != NULL, NULL);
g_return_val_if_fail (value != NULL, NULL);
type = g_variant_type_new ("(oayays)");
builder = g_variant_builder_new (type);
#ifdef WITH_GCRYPT
if (session->key)
ret = service_encode_aes_secret (session, value, builder);
else
#endif
ret = service_encode_plain_secret (session, value, builder);
if (ret)
result = g_variant_builder_end (builder);
g_variant_builder_unref (builder);
g_variant_type_free (type);
return result;
}
const gchar *
_secret_session_get_algorithms (SecretSession *session)
{
g_return_val_if_fail (session != NULL, NULL);
return session->algorithms;
}
const gchar *
_secret_session_get_path (SecretSession *session)
{
g_return_val_if_fail (session != NULL, NULL);
return session->path;
}