file-collection: Move low-level crypto functions to egg

This moves low-level cryptographic functions into egg/egg-keyring1.c,
to make it easy to support multiple crypto backend libraries.

Signed-off-by: Daiki Ueno <dueno@src.gnome.org>
This commit is contained in:
Daiki Ueno 2023-12-01 09:36:02 +09:00
parent 0b4769f871
commit 564874beb0
4 changed files with 277 additions and 178 deletions

193
egg/egg-keyring1.c Normal file
View File

@ -0,0 +1,193 @@
/* libsecret - GLib wrapper for Secret Service
*
* Copyright 2019 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: Daiki Ueno
*/
#include "config.h"
#include "egg-keyring1.h"
#include "egg/egg-secure-memory.h"
EGG_SECURE_DECLARE (egg_keyring1);
#include <gcrypt.h>
#define PBKDF2_HASH_ALGO GCRY_MD_SHA256
#define MAC_ALGO GCRY_MAC_HMAC_SHA256
#define CIPHER_ALGO GCRY_CIPHER_AES128
void
egg_keyring1_create_nonce (guint8 *nonce,
gsize nonce_size)
{
gcry_create_nonce (nonce, nonce_size);
}
GBytes *
egg_keyring1_derive_key (const gchar *password,
gsize n_password,
GBytes *salt,
guint32 iteration_count)
{
guint8 *buffer;
gcry_error_t gcry;
buffer = egg_secure_alloc (KEY_SIZE);
g_return_val_if_fail (buffer, NULL);
gcry = gcry_kdf_derive (password,
n_password,
GCRY_KDF_PBKDF2, PBKDF2_HASH_ALGO,
g_bytes_get_data (salt, NULL),
g_bytes_get_size (salt),
iteration_count,
KEY_SIZE, buffer);
if (gcry != 0) {
egg_secure_free (buffer);
return NULL;
}
return g_bytes_new_with_free_func (buffer,
KEY_SIZE,
egg_secure_free,
buffer);
}
gboolean
egg_keyring1_calculate_mac (GBytes *key,
const guint8 *value,
gsize n_value,
guint8 *buffer)
{
gcry_mac_hd_t hd;
gcry_error_t gcry;
gconstpointer secret;
gsize n_secret;
gboolean ret = FALSE;
gcry = gcry_mac_open (&hd, MAC_ALGO, 0, NULL);
g_return_val_if_fail (gcry == 0, FALSE);
secret = g_bytes_get_data (key, &n_secret);
gcry = gcry_mac_setkey (hd, secret, n_secret);
if (gcry != 0)
goto out;
gcry = gcry_mac_write (hd, value, n_value);
if (gcry != 0)
goto out;
n_value = MAC_SIZE;
gcry = gcry_mac_read (hd, buffer, &n_value);
if (gcry != 0)
goto out;
if (n_value != MAC_SIZE)
goto out;
ret = TRUE;
out:
gcry_mac_close (hd);
return ret;
}
gboolean
egg_keyring1_verify_mac (GBytes *key,
const guint8 *value,
gsize n_value,
const guint8 *data)
{
guint8 buffer[MAC_SIZE];
guint8 status = 0;
gsize i;
if (!egg_keyring1_calculate_mac (key, value, n_value, buffer)) {
return FALSE;
}
for (i = 0; i < MAC_SIZE; i++) {
status |= data[i] ^ buffer[i];
}
return status == 0;
}
gboolean
egg_keyring1_decrypt (GBytes *key,
guint8 *data,
gsize n_data)
{
gcry_cipher_hd_t hd;
gcry_error_t gcry;
gconstpointer secret;
gsize n_secret;
gboolean ret = FALSE;
gcry = gcry_cipher_open (&hd, CIPHER_ALGO, GCRY_CIPHER_MODE_CBC, 0);
if (gcry != 0)
goto out;
secret = g_bytes_get_data (key, &n_secret);
gcry = gcry_cipher_setkey (hd, secret, n_secret);
if (gcry != 0)
goto out;
gcry = gcry_cipher_setiv (hd, data + n_data, IV_SIZE);
if (gcry != 0)
goto out;
gcry = gcry_cipher_decrypt (hd, data, n_data, NULL, 0);
if (gcry != 0)
goto out;
ret = TRUE;
out:
gcry_cipher_close (hd);
return ret;
}
gboolean
egg_keyring1_encrypt (GBytes *key,
guint8 *data,
gsize n_data)
{
gcry_cipher_hd_t hd;
gcry_error_t gcry;
gconstpointer secret;
gsize n_secret;
gboolean ret = FALSE;
gcry = gcry_cipher_open (&hd, CIPHER_ALGO, GCRY_CIPHER_MODE_CBC, 0);
if (gcry != 0)
goto out;
secret = g_bytes_get_data (key, &n_secret);
gcry = gcry_cipher_setkey (hd, secret, n_secret);
if (gcry != 0)
goto out;
egg_keyring1_create_nonce (data + n_data, IV_SIZE);
gcry = gcry_cipher_setiv (hd, data + n_data, IV_SIZE);
if (gcry != 0)
goto out;
gcry = gcry_cipher_encrypt (hd, data, n_data, NULL, 0);
if (gcry != 0)
goto out;
ret = TRUE;
out:
gcry_cipher_close (hd);
return ret;
}

55
egg/egg-keyring1.h Normal file
View File

@ -0,0 +1,55 @@
/* libsecret - GLib wrapper for Secret Service
*
* Copyright 2019 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: Daiki Ueno
*/
#ifndef EGG_KEYRING1_H_
#define EGG_KEYRING1_H_
#include <glib.h>
#define SALT_SIZE 32
#define ITERATION_COUNT 100000
#define MAC_SIZE 32
#define CIPHER_BLOCK_SIZE 16
#define KEY_SIZE 16
#define IV_SIZE CIPHER_BLOCK_SIZE
void egg_keyring1_create_nonce (guint8 *nonce,
gsize nonce_size);
GBytes *egg_keyring1_derive_key (const char *password,
gsize n_password,
GBytes *salt,
guint32 iteration_count);
gboolean egg_keyring1_calculate_mac (GBytes *key,
const guint8 *value,
gsize n_value,
guint8 *buffer);
gboolean egg_keyring1_verify_mac (GBytes *key,
const guint8 *value,
gsize n_value,
const guint8 *data);
gboolean egg_keyring1_decrypt (GBytes *key,
guint8 *data,
gsize n_data);
gboolean egg_keyring1_encrypt (GBytes *key,
guint8 *data,
gsize n_data);
#endif /* EGG_KEYRING1_H_ */

View File

@ -12,6 +12,7 @@ if get_option('gcrypt')
libegg_sources += [
'egg-dh.c',
'egg-hkdf.c',
'egg-keyring1.c',
'egg-libgcrypt.c',
]

View File

@ -16,27 +16,12 @@
#include "secret-file-collection.h"
#include "egg/egg-keyring1.h"
#include "egg/egg-libgcrypt.h"
#include "egg/egg-secure-memory.h"
EGG_SECURE_DECLARE (secret_file_collection);
#ifdef WITH_GCRYPT
#include <gcrypt.h>
#endif
#define PBKDF2_HASH_ALGO GCRY_MD_SHA256
#define SALT_SIZE 32
#define ITERATION_COUNT 100000
#define MAC_ALGO GCRY_MAC_HMAC_SHA256
#define MAC_SIZE 32
#define CIPHER_ALGO GCRY_CIPHER_AES128
#define CIPHER_BLOCK_SIZE 16
#define KEY_SIZE 16
#define IV_SIZE CIPHER_BLOCK_SIZE
#define KEYRING_FILE_HEADER "GnomeKeyring\n\r\0\n"
#define KEYRING_FILE_HEADER_LEN 16
@ -86,158 +71,6 @@ get_file_last_modified (SecretFileCollection *self)
return res;
}
static gboolean
do_derive_key (SecretFileCollection *self)
{
const gchar *password;
gsize n_password;
gchar *key;
gsize n_salt;
gcry_error_t gcry;
password = secret_value_get (self->password, &n_password);
key = egg_secure_alloc (KEY_SIZE);
self->key = g_bytes_new_with_free_func (key,
KEY_SIZE,
egg_secure_free,
key);
n_salt = g_bytes_get_size (self->salt);
gcry = gcry_kdf_derive (password, n_password,
GCRY_KDF_PBKDF2, PBKDF2_HASH_ALGO,
g_bytes_get_data (self->salt, NULL), n_salt,
self->iteration_count, KEY_SIZE, key);
return (gcry != 0) ? FALSE : TRUE;
}
static gboolean
do_calculate_mac (SecretFileCollection *self,
const guint8 *value, gsize n_value,
guint8 *buffer)
{
gcry_mac_hd_t hd;
gcry_error_t gcry;
gconstpointer secret;
gsize n_secret;
gboolean ret = FALSE;
gcry = gcry_mac_open (&hd, MAC_ALGO, 0, NULL);
g_return_val_if_fail (gcry == 0, FALSE);
secret = g_bytes_get_data (self->key, &n_secret);
gcry = gcry_mac_setkey (hd, secret, n_secret);
if (gcry != 0)
goto out;
gcry = gcry_mac_write (hd, value, n_value);
if (gcry != 0)
goto out;
n_value = MAC_SIZE;
gcry = gcry_mac_read (hd, buffer, &n_value);
if (gcry != 0)
goto out;
if (n_value != MAC_SIZE)
goto out;
ret = TRUE;
out:
gcry_mac_close (hd);
return ret;
}
static gboolean
do_verify_mac (SecretFileCollection *self,
const guint8 *value, gsize n_value,
const guint8 *data)
{
guint8 buffer[MAC_SIZE];
guint8 status = 0;
gsize i;
if (!do_calculate_mac (self, value, n_value, buffer)) {
return FALSE;
}
for (i = 0; i < MAC_SIZE; i++) {
status |= data[i] ^ buffer[i];
}
return status == 0;
}
static gboolean
do_decrypt (SecretFileCollection *self,
guint8 *data,
gsize n_data)
{
gcry_cipher_hd_t hd;
gcry_error_t gcry;
gconstpointer secret;
gsize n_secret;
gboolean ret = FALSE;
gcry = gcry_cipher_open (&hd, CIPHER_ALGO, GCRY_CIPHER_MODE_CBC, 0);
if (gcry != 0)
goto out;
secret = g_bytes_get_data (self->key, &n_secret);
gcry = gcry_cipher_setkey (hd, secret, n_secret);
if (gcry != 0)
goto out;
gcry = gcry_cipher_setiv (hd, data + n_data, IV_SIZE);
if (gcry != 0)
goto out;
gcry = gcry_cipher_decrypt (hd, data, n_data, NULL, 0);
if (gcry != 0)
goto out;
ret = TRUE;
out:
(void) gcry_cipher_close (hd);
return ret;
}
static gboolean
do_encrypt (SecretFileCollection *self,
guint8 *data,
gsize n_data)
{
gcry_cipher_hd_t hd;
gcry_error_t gcry;
gconstpointer secret;
gsize n_secret;
gboolean ret = FALSE;
gcry = gcry_cipher_open (&hd, CIPHER_ALGO, GCRY_CIPHER_MODE_CBC, 0);
if (gcry != 0)
goto out;
secret = g_bytes_get_data (self->key, &n_secret);
gcry = gcry_cipher_setkey (hd, secret, n_secret);
if (gcry != 0)
goto out;
gcry_create_nonce (data + n_data, IV_SIZE);
gcry = gcry_cipher_setiv (hd, data + n_data, IV_SIZE);
if (gcry != 0)
goto out;
gcry = gcry_cipher_encrypt (hd, data, n_data, NULL, 0);
if (gcry != 0)
goto out;
ret = TRUE;
out:
(void) gcry_cipher_close (hd);
return ret;
}
static void
secret_file_collection_init (SecretFileCollection *self)
{
@ -329,6 +162,8 @@ load_contents (SecretFileCollection *self,
guint64 usage_count;
gconstpointer data;
gsize n_data;
const gchar *password;
gsize n_password;
p = contents;
if (length < KEYRING_FILE_HEADER_LEN ||
@ -380,7 +215,12 @@ load_contents (SecretFileCollection *self,
g_variant_unref (salt_array);
g_variant_unref (variant);
if (!do_derive_key (self)) {
password = secret_value_get (self->password, &n_password);
self->key = egg_keyring1_derive_key (password,
n_password,
self->salt,
self->iteration_count);
if (!self->key) {
g_set_error_literal (error,
SECRET_ERROR,
SECRET_ERROR_PROTOCOL,
@ -396,15 +236,22 @@ init_empty_file (SecretFileCollection *self,
GError **error)
{
GVariantBuilder builder;
const gchar *password;
gsize n_password;
guint8 salt[SALT_SIZE];
gcry_create_nonce (salt, sizeof(salt));
egg_keyring1_create_nonce (salt, sizeof(salt));
self->salt = g_bytes_new (salt, sizeof(salt));
self->iteration_count = ITERATION_COUNT;
self->modified = g_date_time_new_now_utc ();
self->usage_count = 0;
if (!do_derive_key (self)) {
password = secret_value_get (self->password, &n_password);
self->key = egg_keyring1_derive_key (password,
n_password,
self->salt,
self->iteration_count);
if (!self->key) {
g_set_error_literal (error,
SECRET_ERROR,
SECRET_ERROR_PROTOCOL,
@ -556,7 +403,10 @@ hash_attributes (SecretFileCollection *self,
GVariant *variant;
value = g_hash_table_lookup (attributes, l->data);
if (!do_calculate_mac (self, (guint8 *)value, strlen (value), buffer)) {
if (!egg_keyring1_calculate_mac (self->key,
(const guint8 *)value,
strlen (value),
buffer)) {
g_list_free (keys);
return NULL;
}
@ -598,7 +448,7 @@ hashed_attributes_match (SecretFileCollection *self,
return FALSE;
}
if (!do_verify_mac (self, value, strlen ((char *)value), data)) {
if (!egg_keyring1_verify_mac (self->key, value, strlen ((char *)value), data)) {
g_variant_unref (hashed_attribute);
return FALSE;
}
@ -694,7 +544,7 @@ secret_file_collection_replace (SecretFileCollection *self,
g_variant_store (serialized_item, data);
g_variant_unref (serialized_item);
memset (data + n_data, n_padded - n_data, n_padded - n_data);
if (!do_encrypt (self, data, n_padded)) {
if (!egg_keyring1_encrypt (self->key, data, n_padded)) {
egg_secure_free (data);
g_set_error (error,
SECRET_ERROR,
@ -703,8 +553,8 @@ secret_file_collection_replace (SecretFileCollection *self,
return FALSE;
}
if (!do_calculate_mac (self, data, n_padded + IV_SIZE,
data + n_padded + IV_SIZE)) {
if (!egg_keyring1_calculate_mac (self->key, data, n_padded + IV_SIZE,
data + n_padded + IV_SIZE)) {
egg_secure_free (data);
g_set_error (error,
SECRET_ERROR,
@ -792,7 +642,7 @@ _secret_file_item_decrypt (GVariant *encrypted,
}
n_padded -= MAC_SIZE;
if (!do_verify_mac (collection, data, n_padded, data + n_padded)) {
if (!egg_keyring1_verify_mac (collection->key, data, n_padded, data + n_padded)) {
egg_secure_free (data);
g_set_error (error,
SECRET_ERROR,
@ -802,7 +652,7 @@ _secret_file_item_decrypt (GVariant *encrypted,
}
n_padded -= IV_SIZE;
if (!do_decrypt (collection, data, n_padded)) {
if (!egg_keyring1_decrypt (collection->key, data, n_padded)) {
egg_secure_free (data);
g_set_error (error,
SECRET_ERROR,