[libsecret/wip/dueno/local-file: 2/8] egg: Add egg-jwe module for file encryption
- From: Daiki Ueno <dueno src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [libsecret/wip/dueno/local-file: 2/8] egg: Add egg-jwe module for file encryption
- Date: Tue, 2 Oct 2018 19:04:40 +0000 (UTC)
commit a022bd9cfdbbf6b028c933b8fe901017a2ab9dba
Author: Daiki Ueno <dueno src gnome org>
Date: Thu Sep 20 15:21:24 2018 +0200
egg: Add egg-jwe module for file encryption
configure.ac | 6 +
egg/Makefile.am | 7 +-
egg/egg-jwe.c | 440 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
egg/egg-jwe.h | 44 ++++++
egg/test-jwe.c | 136 ++++++++++++++++++
5 files changed, 632 insertions(+), 1 deletion(-)
---
diff --git a/configure.ac b/configure.ac
index c4d2b89..aed99bf 100644
--- a/configure.ac
+++ b/configure.ac
@@ -159,6 +159,12 @@ fi
AM_CONDITIONAL(WITH_GCRYPT, test "$enable_gcrypt" = "yes")
+# --------------------------------------------------------------------
+# json-glib
+PKG_CHECK_MODULES(JSON_GLIB, json-glib-1.0)
+LIBS="$LIBS $JSON_GLIB_LIBS"
+CFLAGS="$CFLAGS $JSON_GLIB_CFLAGS"
+
# --------------------------------------------------------------------
# Compilation options
diff --git a/egg/Makefile.am b/egg/Makefile.am
index 2c647f7..c26708f 100644
--- a/egg/Makefile.am
+++ b/egg/Makefile.am
@@ -6,6 +6,7 @@ EXTRA_DIST += egg/egg-testing.h
if WITH_GCRYPT
ENCRYPTION_SRCS = egg/egg-dh.c egg/egg-dh.h
ENCRYPTION_SRCS += egg/egg-hkdf.c egg/egg-hkdf.h
+ENCRYPTION_SRCS += egg/egg-jwe.c egg/egg-jwe.h
ENCRYPTION_SRCS += egg/egg-libgcrypt.c egg/egg-libgcrypt.h
else
ENCRYPTION_SRCS =
@@ -21,6 +22,7 @@ libegg_la_SOURCES = \
egg_LIBS = \
libegg.la \
$(LIBGCRYPT_LIBS) \
+ $(JSON_GLIB_LIBS) \
$(GLIB_LIBS)
egg_TESTS = \
@@ -38,11 +40,14 @@ test_secmem_SOURCES = egg/test-secmem.c
test_secmem_LDADD = $(egg_LIBS)
if WITH_GCRYPT
-egg_TESTS += test-hkdf test-dh
+egg_TESTS += test-hkdf test-dh test-jwe
test_hkdf_SOURCES = egg/test-hkdf.c
test_hkdf_LDADD = $(egg_LIBS)
+test_jwe_SOURCES = egg/test-jwe.c
+test_jwe_LDADD = $(egg_LIBS)
+
test_dh_SOURCES = egg/test-dh.c
test_dh_LDADD = $(egg_LIBS)
endif
diff --git a/egg/egg-jwe.c b/egg/egg-jwe.c
new file mode 100644
index 0000000..715c33e
--- /dev/null
+++ b/egg/egg-jwe.c
@@ -0,0 +1,440 @@
+/*
+ * libsecret
+ *
+ * Copyright (C) 2018 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 License, or (at your option) any later version.
+ *
+ * This program 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ * MA 02110-1301 USA
+ *
+ * Author: Daiki Ueno
+ */
+
+#include "config.h"
+
+#include "egg-jwe.h"
+#include "egg-base64.h"
+
+#include <gcrypt.h>
+
+/* IV size is always 12 when GCM */
+#define IV_SIZE 12
+#define TAG_SIZE 16
+
+static gint
+enc_to_cipher (const gchar *enc)
+{
+ if (g_str_equal (enc, "A128GCM"))
+ return GCRY_CIPHER_AES128;
+ else if (g_str_equal (enc, "A192GCM"))
+ return GCRY_CIPHER_AES192;
+ else if (g_str_equal (enc, "A256GCM"))
+ return GCRY_CIPHER_AES256;
+
+ /* FIXME: support CBC ciphersuites */
+ return -1;
+}
+
+JsonNode *
+egg_jwe_symmetric_encrypt (const guchar *input,
+ gsize n_input,
+ const gchar *enc,
+ const guchar *key,
+ gsize n_key,
+ const guchar *iv,
+ gsize n_iv,
+ GError **error)
+{
+ gcry_cipher_hd_t cipher;
+ gcry_error_t gcry;
+ guchar random[IV_SIZE];
+ guchar tag[TAG_SIZE];
+ JsonBuilder *builder;
+ JsonGenerator *generator;
+ JsonNode *result;
+ gchar *protected;
+ gchar *encoded;
+ guchar *ciphertext;
+ gint algo;
+ gsize length;
+
+ algo = enc_to_cipher (enc);
+ if (algo < 0) {
+ g_set_error_literal (error,
+ G_IO_ERROR,
+ G_IO_ERROR_INVALID_ARGUMENT,
+ "unknown encryption algorithm");
+ return NULL;
+ }
+
+ gcry = gcry_cipher_open (&cipher, algo, GCRY_CIPHER_MODE_GCM, 0);
+ if (gcry) {
+ g_set_error_literal (error,
+ G_IO_ERROR,
+ G_IO_ERROR_FAILED,
+ "couldn't open cipher");
+ return NULL;
+ }
+
+ gcry = gcry_cipher_setkey (cipher, key, n_key);
+ if (gcry) {
+ gcry_cipher_close (cipher);
+ g_set_error_literal (error,
+ G_IO_ERROR,
+ G_IO_ERROR_FAILED,
+ "couldn't set key");
+ return NULL;
+ }
+
+ if (!iv) {
+ gcry_randomize (random, IV_SIZE, GCRY_STRONG_RANDOM);
+ iv = random;
+ n_iv = IV_SIZE;
+ }
+ gcry = gcry_cipher_setiv (cipher, iv, n_iv);
+ if (gcry) {
+ gcry_cipher_close (cipher);
+ g_set_error_literal (error,
+ G_IO_ERROR,
+ G_IO_ERROR_FAILED,
+ "couldn't set IV");
+ return NULL;
+ }
+
+ /* generate protected header */
+ builder = json_builder_new ();
+ json_builder_begin_object (builder);
+ json_builder_set_member_name (builder, "enc");
+ json_builder_add_string_value (builder, enc);
+ json_builder_end_object (builder);
+ result = json_builder_get_root (builder);
+ g_object_unref (builder);
+
+ generator = json_generator_new ();
+ json_generator_set_root (generator, result);
+ json_node_unref (result);
+ protected = json_generator_to_data (generator, &length);
+ g_object_unref (generator);
+ encoded = egg_base64_encode ((const guchar *) protected, length);
+ g_free (protected);
+ protected = encoded;
+
+ gcry = gcry_cipher_authenticate (cipher, protected, strlen (protected));
+ if (gcry) {
+ g_free (protected);
+ gcry_cipher_close (cipher);
+ g_set_error_literal (error,
+ G_IO_ERROR,
+ G_IO_ERROR_FAILED,
+ "couldn't set authentication data");
+ return NULL;
+ }
+
+ ciphertext = g_new (guchar, n_input);
+ gcry = gcry_cipher_encrypt (cipher, ciphertext, n_input, input, n_input);
+ if (gcry) {
+ g_free (protected);
+ gcry_cipher_close (cipher);
+ g_set_error_literal (error,
+ G_IO_ERROR,
+ G_IO_ERROR_FAILED,
+ "couldn't encrypt data");
+ return NULL;
+ }
+
+ gcry = gcry_cipher_gettag (cipher, tag, TAG_SIZE);
+ if (gcry) {
+ g_free (protected);
+ gcry_cipher_close (cipher);
+ g_set_error_literal (error,
+ G_IO_ERROR,
+ G_IO_ERROR_FAILED,
+ "couldn't get tag");
+ return NULL;
+ }
+
+ gcry_cipher_close (cipher);
+
+ builder = json_builder_new ();
+ json_builder_begin_object (builder);
+
+ json_builder_set_member_name (builder, "ciphertext");
+ encoded = egg_base64_encode (ciphertext, n_input);
+ g_free (ciphertext);
+ json_builder_add_string_value (builder, encoded);
+ g_free (encoded);
+
+ json_builder_set_member_name (builder, "encrypted_key");
+ json_builder_add_string_value (builder, "");
+
+ json_builder_set_member_name (builder, "iv");
+ encoded = egg_base64_encode (iv, IV_SIZE);
+ json_builder_add_string_value (builder, encoded);
+ g_free (encoded);
+
+ json_builder_set_member_name (builder, "tag");
+ encoded = egg_base64_encode (tag, TAG_SIZE);
+ json_builder_add_string_value (builder, encoded);
+ g_free (encoded);
+
+ json_builder_set_member_name (builder, "protected");
+ json_builder_add_string_value (builder, protected);
+ g_free (protected);
+
+ json_builder_set_member_name (builder, "header");
+ json_builder_begin_object (builder);
+ json_builder_set_member_name (builder, "alg");
+ json_builder_add_string_value (builder, "dir");
+ json_builder_end_object (builder);
+
+ json_builder_end_object (builder);
+ result = json_builder_get_root (builder);
+ g_object_unref (builder);
+
+ return result;
+}
+
+guchar *
+egg_jwe_symmetric_decrypt (JsonNode *root,
+ const guchar *key,
+ gsize n_key,
+ gsize *length,
+ GError **error)
+{
+ gcry_cipher_hd_t cipher;
+ gcry_error_t gcry;
+ guchar iv[(IV_SIZE / 3 + 1) *4 + 1];
+ guchar tag[(TAG_SIZE / 3 + 1) * 4 + 1];
+ JsonParser *parser;
+ JsonObject *object;
+ const gchar *string;
+ const gchar *protected;
+ JsonNode *protected_root;
+ JsonObject *protected_object;
+ gchar *buffer;
+ guchar *decoded;
+ gsize n_decoded;
+ gint algo;
+ gboolean ret;
+
+ object = json_node_get_object (root);
+ if (!object) {
+ g_set_error_literal (error,
+ G_IO_ERROR,
+ G_IO_ERROR_INVALID_ARGUMENT,
+ "the root element is not an object");
+ return NULL;
+ }
+
+ protected = json_object_get_string_member (object, "protected");
+ if (!protected) {
+ g_set_error_literal (error,
+ G_IO_ERROR,
+ G_IO_ERROR_INVALID_ARGUMENT,
+ "the root element doesn't contain \"protected\" element");
+ return NULL;
+ }
+
+ buffer = g_strdup (protected);
+ decoded = egg_base64_decode_inplace (buffer, &n_decoded);
+ if (!decoded) {
+ g_free (buffer);
+ g_set_error_literal (error,
+ G_IO_ERROR,
+ G_IO_ERROR_INVALID_ARGUMENT,
+ "couldn't decode \"protected\" element");
+ return NULL;
+ }
+
+ parser = json_parser_new ();
+ ret = json_parser_load_from_data (parser, (gchar *) decoded, n_decoded, error);
+ g_free (buffer);
+ if (!ret) {
+ g_object_unref (parser);
+ return NULL;
+ }
+
+ protected_root = json_parser_steal_root (parser);
+ g_object_unref (parser);
+
+ protected_object = json_node_get_object (protected_root);
+ if (!protected_object) {
+ json_node_unref (protected_root);
+ g_set_error_literal (error,
+ G_IO_ERROR,
+ G_IO_ERROR_INVALID_ARGUMENT,
+ "the \"protected\" element is not an object");
+ return NULL;
+ }
+
+ string = json_object_get_string_member (protected_object, "enc");
+ if (!string) {
+ json_node_unref (protected_root);
+ g_set_error_literal (error,
+ G_IO_ERROR,
+ G_IO_ERROR_INVALID_ARGUMENT,
+ "the \"protected\" element doesn't contain \"enc\"");
+ return NULL;
+ }
+
+ algo = enc_to_cipher (string);
+ json_node_unref (protected_root);
+ if (algo < 0) {
+ g_set_error_literal (error,
+ G_IO_ERROR,
+ G_IO_ERROR_INVALID_ARGUMENT,
+ "unknown encryption algorithm");
+ return NULL;
+ }
+
+ string = json_object_get_string_member (object, "iv");
+ if (!string) {
+ g_set_error_literal (error,
+ G_IO_ERROR,
+ G_IO_ERROR_INVALID_ARGUMENT,
+ "the root element doesn't contain \"iv\" element");
+ return NULL;
+ }
+ if (strlen (string) > sizeof (iv) - 1) {
+ g_set_error_literal (error,
+ G_IO_ERROR,
+ G_IO_ERROR_INVALID_ARGUMENT,
+ "IV is too large");
+ return NULL;
+ }
+
+ memset (iv, 0, sizeof (iv));
+ memcpy (iv, string, strlen (string));
+ decoded = egg_base64_decode_inplace ((gchar *) iv, &n_decoded);
+ if (!decoded) {
+ g_set_error_literal (error,
+ G_IO_ERROR,
+ G_IO_ERROR_INVALID_ARGUMENT,
+ "couldn't decode \"iv\" element");
+ return NULL;
+ }
+
+ string = json_object_get_string_member (object, "tag");
+ if (!string) {
+ g_set_error_literal (error,
+ G_IO_ERROR,
+ G_IO_ERROR_INVALID_ARGUMENT,
+ "the root element doesn't contain \"tag\" element");
+ return NULL;
+ }
+ if (strlen (string) > sizeof (tag) - 1) {
+ g_set_error_literal (error,
+ G_IO_ERROR,
+ G_IO_ERROR_INVALID_ARGUMENT,
+ "tag is too large");
+ return NULL;
+ }
+
+ memset (tag, 0, sizeof (tag));
+ memcpy (tag, string, strlen (string));
+ decoded = egg_base64_decode_inplace ((gchar *) tag, &n_decoded);
+ if (!decoded) {
+ g_set_error_literal (error,
+ G_IO_ERROR,
+ G_IO_ERROR_INVALID_ARGUMENT,
+ "couldn't decode \"tag\" element");
+ return NULL;
+ }
+
+ string = json_object_get_string_member (object, "ciphertext");
+ if (!string) {
+ g_set_error_literal (error,
+ G_IO_ERROR,
+ G_IO_ERROR_INVALID_ARGUMENT,
+ "the root element doesn't contain \"ciphertext\"");
+ return NULL;
+ }
+
+ buffer = g_strdup (string);
+ decoded = egg_base64_decode_inplace (buffer, &n_decoded);
+ if (!decoded) {
+ g_free (buffer);
+ g_set_error_literal (error,
+ G_IO_ERROR,
+ G_IO_ERROR_INVALID_ARGUMENT,
+ "couldn't decode \"ciphertext\" element");
+ return NULL;
+ }
+
+ gcry = gcry_cipher_open (&cipher, algo, GCRY_CIPHER_MODE_GCM, 0);
+ if (gcry) {
+ g_set_error_literal (error,
+ G_IO_ERROR,
+ G_IO_ERROR_FAILED,
+ "couldn't open cipher");
+ return NULL;
+ }
+
+ gcry = gcry_cipher_setkey (cipher, key, n_key);
+ if (gcry) {
+ gcry_cipher_close (cipher);
+ g_set_error_literal (error,
+ G_IO_ERROR,
+ G_IO_ERROR_FAILED,
+ "couldn't set key");
+ return NULL;
+ }
+
+ gcry = gcry_cipher_setiv (cipher, iv, IV_SIZE);
+ if (gcry) {
+ gcry_cipher_close (cipher);
+ g_set_error_literal (error,
+ G_IO_ERROR,
+ G_IO_ERROR_FAILED,
+ "couldn't set IV");
+ return NULL;
+ }
+
+ gcry = gcry_cipher_authenticate (cipher, protected, strlen (protected));
+ if (gcry) {
+ gcry_cipher_close (cipher);
+ g_set_error_literal (error,
+ G_IO_ERROR,
+ G_IO_ERROR_FAILED,
+ "couldn't set authentication data");
+ return NULL;
+ }
+
+ gcry = gcry_cipher_decrypt (cipher, decoded, n_decoded, decoded, n_decoded);
+ if (gcry) {
+ g_free (decoded);
+ gcry_cipher_close (cipher);
+ g_set_error_literal (error,
+ G_IO_ERROR,
+ G_IO_ERROR_FAILED,
+ "couldn't decrypt data");
+ return NULL;
+ }
+
+ gcry = gcry_cipher_checktag (cipher, tag, TAG_SIZE);
+ if (gcry) {
+ g_free (decoded);
+ gcry_cipher_close (cipher);
+ g_set_error_literal (error,
+ G_IO_ERROR,
+ G_IO_ERROR_FAILED,
+ "couldn't check tag");
+ return NULL;
+ }
+
+ gcry_cipher_close (cipher);
+
+ *length = n_decoded;
+ return decoded;
+}
diff --git a/egg/egg-jwe.h b/egg/egg-jwe.h
new file mode 100644
index 0000000..f03baf6
--- /dev/null
+++ b/egg/egg-jwe.h
@@ -0,0 +1,44 @@
+/*
+ * libsecret
+ *
+ * Copyright (C) 2018 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 License, or (at your option) any later version.
+ *
+ * This program 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ * MA 02110-1301 USA
+ *
+ * Author: Daiki Ueno
+ */
+
+#ifndef EGG_JWE_H_
+#define EGG_JWE_H_
+
+#include <json-glib/json-glib.h>
+
+JsonNode *egg_jwe_symmetric_encrypt (const guchar *input,
+ gsize n_input,
+ const gchar *enc,
+ const guchar *key,
+ gsize n_key,
+ const guchar *iv,
+ gsize n_iv,
+ GError **error);
+
+guchar *egg_jwe_symmetric_decrypt (JsonNode *root,
+ const guchar *key,
+ gsize n_key,
+ gsize *length,
+ GError **error);
+
+#endif
diff --git a/egg/test-jwe.c b/egg/test-jwe.c
new file mode 100644
index 0000000..898d745
--- /dev/null
+++ b/egg/test-jwe.c
@@ -0,0 +1,136 @@
+/* test-base64.c: Test egg-base64.c
+
+ Copyright (C) 2018 Red Hat, Inc.
+
+ The Gnome Keyring Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version.
+
+ The Gnome Keyring Library 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
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public
+ License along with the Gnome Library; see the file COPYING.LIB. If not,
+ see <http://www.gnu.org/licenses/>.
+
+ Author: Daiki Ueno
+*/
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "egg/egg-jwe.h"
+#include "egg/egg-base64.h"
+#include "egg/egg-testing.h"
+
+#define PLAINTEXT "test test\n"
+#define KEY "7IYHpL3E0SApQ3Uk58_Liw"
+#define IV "aeZrw-VuRzycKDEu"
+#define CIPHERTEXT "sWMnFnG4OcjdpA"
+#define TAG "Jx1MqdYjb2n-0-zXTGUHZw"
+#define PROTECTED "eyJlbmMiOiJBMTI4R0NNIn0"
+
+#define MSG "{\"ciphertext\":\"" CIPHERTEXT "\"," \
+ "\"encrypted_key\":\"\"," \
+ "\"header\":{\"alg\":\"dir\"}," \
+ "\"iv\":\"" IV "\"," \
+ "\"protected\":\"" PROTECTED "\"," \
+ "\"tag\":\"" TAG "\"}"
+
+static void
+test_symmetric_encrypt (void)
+{
+ gchar *buffer;
+ guchar *key;
+ gsize n_key;
+ guchar *iv;
+ gsize n_iv;
+ GError *error;
+ JsonNode *root;
+ JsonObject *object;
+ const gchar *string;
+
+ buffer = g_strdup (KEY);
+ key = egg_base64_decode_inplace (buffer, &n_key);
+ buffer = g_strdup (IV);
+ iv = egg_base64_decode_inplace (buffer, &n_iv);
+ error = NULL;
+ root = egg_jwe_symmetric_encrypt ((const guchar *) PLAINTEXT,
+ sizeof (PLAINTEXT)-1,
+ "A128GCM", key, n_key, iv, n_iv,
+ &error);
+ g_assert_nonnull (root);
+ g_assert_no_error (error);
+ g_free (key);
+ g_free (iv);
+
+ object = json_node_get_object (root);
+ g_assert_nonnull (object);
+
+ string = json_object_get_string_member (object, "ciphertext");
+ g_assert_cmpstr (string, ==, CIPHERTEXT);
+ string = json_object_get_string_member (object, "iv");
+ g_assert_cmpstr (string, ==, IV);
+ string = json_object_get_string_member (object, "tag");
+ g_assert_cmpstr (string, ==, TAG);
+ string = json_object_get_string_member (object, "protected");
+ g_assert_cmpstr (string, ==, PROTECTED);
+ json_node_unref (root);
+}
+
+static void
+test_symmetric_decrypt (void)
+{
+ JsonParser *parser;
+ JsonNode *root;
+ gchar *buffer;
+ guchar *key;
+ gsize n_key;
+ guchar *plaintext;
+ gsize n_plaintext;
+ GError *error;
+ gboolean ret;
+
+ parser = json_parser_new ();
+ error = NULL;
+ ret = json_parser_load_from_data (parser, MSG, sizeof (MSG)-1, &error);
+ g_assert_true (ret);
+ g_assert_no_error (error);
+
+ buffer = g_strdup (KEY);
+ key = egg_base64_decode_inplace (buffer, &n_key);
+
+ root = json_parser_steal_root (parser);
+ g_object_unref (parser);
+
+ error = NULL;
+ plaintext = egg_jwe_symmetric_decrypt (root, key, n_key, &n_plaintext,
+ &error);
+ g_assert_nonnull (plaintext);
+ g_assert_no_error (error);
+
+ g_free (key);
+ json_node_unref (root);
+ g_assert_nonnull (plaintext);
+ g_assert_no_error (error);
+
+ g_assert_cmpmem (plaintext, n_plaintext, PLAINTEXT, sizeof(PLAINTEXT)-1);
+ g_free (plaintext);
+}
+
+int
+main (int argc, char **argv)
+{
+ g_test_init (&argc, &argv, NULL);
+
+ g_test_add_func ("/jwe/test-symmetric-encrypt", test_symmetric_encrypt);
+ g_test_add_func ("/jwe/test-symmetric-decrypt", test_symmetric_decrypt);
+
+ return g_test_run ();
+}
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]