[gnome-keyring] gcr: More work on the GcrSecretExchange



commit 03191799af4ee3bd7109151118e77593f6e6b445
Author: Stef Walter <stefw collabora co uk>
Date:   Sat Oct 1 12:20:07 2011 +0200

    gcr: More work on the GcrSecretExchange
    
     * Expose the concept of which protocol is being used although
       currently only one is supported.
     * Add virtual method hooks so all crypto stuff can be swapped
       out with other implementations.
     * Build, documentation and test fixes
    
    https://bugzilla.gnome.org/show_bug.cgi?id=656955

 gcr/Makefile.am                  |    2 +-
 gcr/gcr-base.symbols             |    7 +
 gcr/gcr-secret-exchange.c        |  699 ++++++++++++++++++++++++++------------
 gcr/gcr-secret-exchange.h        |   38 ++-
 gcr/tests/test-secret-exchange.c |    4 +-
 5 files changed, 526 insertions(+), 224 deletions(-)
---
diff --git a/gcr/Makefile.am b/gcr/Makefile.am
index c29fd71..be21651 100644
--- a/gcr/Makefile.am
+++ b/gcr/Makefile.am
@@ -184,7 +184,6 @@ libgcr_ GCR_MAJOR@_la_LDFLAGS = \
 
 libgcr_base_ GCR_MAJOR@_la_LIBADD = \
 	$(top_builddir)/egg/libegg.la \
-	$(top_builddir)/egg/libegg-entry-buffer.la \
 	$(top_builddir)/gck/libgck- GCK_MAJOR@.la \
 	$(GOBJECT_LIBS) \
 	$(GLIB_LIBS) \
@@ -194,6 +193,7 @@ libgcr_base_ GCR_MAJOR@_la_LIBADD = \
 libgcr_ GCR_MAJOR@_la_LIBADD = \
 	$(GTK_LIBS) \
 	$(libgcr_base_ GCR_MAJOR@_la_LIBADD) \
+	$(top_builddir)/egg/libegg-entry-buffer.la \
 	$(builddir)/libgcr-base-$(GCR_MAJOR).la
 
 noinst_LTLIBRARIES = libgcr-testable.la
diff --git a/gcr/gcr-base.symbols b/gcr/gcr-base.symbols
index d1fca06..233ec15 100644
--- a/gcr/gcr-base.symbols
+++ b/gcr/gcr-base.symbols
@@ -111,6 +111,13 @@ gcr_pkcs11_initialize_finish
 gcr_pkcs11_set_modules
 gcr_pkcs11_set_trust_lookup_uris
 gcr_pkcs11_set_trust_store_uri
+gcr_secret_exchange_begin
+gcr_secret_exchange_get_protocol
+gcr_secret_exchange_get_secret
+gcr_secret_exchange_get_type
+gcr_secret_exchange_new
+gcr_secret_exchange_receive
+gcr_secret_exchange_send
 gcr_simple_certificate_get_type
 gcr_simple_certificate_new
 gcr_simple_certificate_new_static
diff --git a/gcr/gcr-secret-exchange.c b/gcr/gcr-secret-exchange.c
index 869b6de..6f6d5c6 100644
--- a/gcr/gcr-secret-exchange.c
+++ b/gcr/gcr-secret-exchange.c
@@ -32,6 +32,8 @@
 #include <string.h>
 #include <gcrypt.h>
 
+EGG_SECURE_DECLARE (secret_exchange);
+
 /**
  * SECTION:gcr-secret-exchange
  * @title: GcrSecretExchange
@@ -72,29 +74,29 @@
  * The class for #GcrSecretExchange
  */
 
-/*
- * This is the only set we support so far. It includes:
- *  - DH with the 1536 ike modp group for key exchange
- *  - HKDF SHA256 for hashing of the key to appropriate size
- *  - AES 128 CBC for encryption
- *  - PKCS#7 style padding
+/**
+ * GCR_SECRET_EXCHANGE_PROTOCOL_1:
+ *
+ * The current secret exchange protocol. Key agreement is done using DH with the
+ * 1536 bit IKE parameter group. Keys are derived using SHA256 with HKDF. The
+ * transport encryption is done with 128 bit AES.
  */
 
-#define EXCHANGE_VERSION "secret-exchange-1"
+enum {
+	PROP_0,
+	PROP_PROTOCOL
+};
 
-#define EXCHANGE_1_IKE_NAME     "ietf-ike-grp-modp-1536"
-#define EXCHANGE_1_KEY_LENGTH   16
-#define EXCHANGE_1_IV_LENGTH    16
-#define EXCHANGE_1_HASH_ALGO    "sha256"
-#define EXCHANGE_1_CIPHER_ALGO  GCRY_CIPHER_AES128
-#define EXCHANGE_1_CIPHER_MODE  GCRY_CIPHER_MODE_CBC
+typedef struct _GcrSecretExchangeDefault GcrSecretExchangeDefault;
 
 struct _GcrSecretExchangePrivate {
-	gcry_mpi_t prime;
-	gcry_mpi_t base;
-	gcry_mpi_t pub;
-	gcry_mpi_t priv;
-	gpointer key;
+	GcrSecretExchangeDefault *default_exchange;
+	GDestroyNotify destroy_exchange;
+	gboolean explicit_protocol;
+	gboolean generated;
+	guchar *publi;
+	gsize n_publi;
+	gboolean derived;
 	gchar *secret;
 	gsize n_secret;
 };
@@ -132,69 +134,65 @@ key_file_get_base64 (GKeyFile *key_file, const gchar *section,
 }
 
 static void
-key_file_set_mpi (GKeyFile *key_file, const gchar *section,
-                  const gchar *field, gcry_mpi_t mpi)
+gcr_secret_exchange_init (GcrSecretExchange *self)
 {
-	gcry_error_t gcry;
-	guchar *data;
-	gsize n_data;
-
-	/* Get the size */
-	gcry = gcry_mpi_print (GCRYMPI_FMT_USG, NULL, 0, &n_data, mpi);
-	g_return_if_fail (gcry == 0);
-
-	data = g_malloc0 (n_data);
-
-	/* Write into buffer */
-	gcry = gcry_mpi_print (GCRYMPI_FMT_USG, data, n_data, &n_data, mpi);
-	g_return_if_fail (gcry == 0);
-
-	key_file_set_base64 (key_file, section, field, data, n_data);
-	g_free (data);
+	self->pv = G_TYPE_INSTANCE_GET_PRIVATE (self, GCR_TYPE_SECRET_EXCHANGE,
+	                                        GcrSecretExchangePrivate);
 }
 
-static gcry_mpi_t
-key_file_get_mpi (GKeyFile *key_file, const gchar *section,
-                  const gchar *field)
-{
-	gcry_mpi_t mpi;
-	gcry_error_t gcry;
-	gpointer data;
-	gsize n_data;
-
-	g_return_val_if_fail (key_file, FALSE);
-	g_return_val_if_fail (section, FALSE);
-	g_return_val_if_fail (field, FALSE);
 
-	data = key_file_get_base64 (key_file, section, field, &n_data);
-	if (data == NULL)
-		return FALSE;
-
-	gcry = gcry_mpi_scan (&mpi, GCRYMPI_FMT_USG, data, n_data, NULL);
-	g_free (data);
-
-	return (gcry == 0) ? mpi : NULL;
+static void
+gcr_secret_exchange_set_property (GObject *obj,
+                                  guint prop_id,
+                                  const GValue *value,
+                                  GParamSpec *pspec)
+{
+	GcrSecretExchange *self = GCR_SECRET_EXCHANGE (obj);
+	const gchar *protocol;
+
+	switch (prop_id) {
+	case PROP_PROTOCOL:
+		protocol = g_value_get_string (value);
+		if (protocol != NULL) {
+			if (g_str_equal (protocol, GCR_SECRET_EXCHANGE_PROTOCOL_1))
+				self->pv->explicit_protocol = TRUE;
+			else
+				g_warning ("the GcrSecretExchange protocol %s is unsupported defaulting to %s",
+				           protocol, GCR_SECRET_EXCHANGE_PROTOCOL_1);
+		}
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, prop_id, pspec);
+		break;
+	}
 }
 
 static void
-gcr_secret_exchange_init (GcrSecretExchange *self)
+gcr_secret_exchange_get_property (GObject *obj,
+                                  guint prop_id,
+                                  GValue *value,
+                                  GParamSpec *pspec)
 {
-	self->pv = G_TYPE_INSTANCE_GET_PRIVATE (self, GCR_TYPE_SECRET_EXCHANGE,
-	                                        GcrSecretExchangePrivate);
+	GcrSecretExchange *self = GCR_SECRET_EXCHANGE (obj);
 
-	if (!egg_dh_default_params (EXCHANGE_1_IKE_NAME, &self->pv->prime, &self->pv->base))
-		g_return_if_reached ();
+	switch (prop_id) {
+	case PROP_PROTOCOL:
+		g_value_set_string (value, gcr_secret_exchange_get_protocol (self));
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, prop_id, pspec);
+		break;
+	}
 }
 
 static void
 clear_secret_exchange (GcrSecretExchange *self)
 {
-	gcry_mpi_release (self->pv->priv);
-	self->pv->priv = NULL;
-	gcry_mpi_release (self->pv->pub);
-	self->pv->pub = NULL;
-	egg_secure_free (self->pv->key);
-	self->pv->key = NULL;
+	g_free (self->pv->publi);
+	self->pv->publi = NULL;
+	self->pv->n_publi = 0;
+	self->pv->derived = FALSE;
+	self->pv->generated = TRUE;
 	egg_secure_free (self->pv->secret);
 	self->pv->secret = NULL;
 	self->pv->n_secret = 0;
@@ -205,35 +203,53 @@ gcr_secret_exchange_finalize (GObject *obj)
 {
 	GcrSecretExchange *self = GCR_SECRET_EXCHANGE (obj);
 
+	if (self->pv->destroy_exchange)
+		(self->pv->destroy_exchange) (self->pv->default_exchange);
+
 	clear_secret_exchange (self);
-	gcry_mpi_release (self->pv->prime);
-	gcry_mpi_release (self->pv->base);
 
 	G_OBJECT_CLASS (gcr_secret_exchange_parent_class)->finalize (obj);
 }
 
-static void
-gcr_secret_exchange_class_init (GcrSecretExchangeClass *klass)
-{
-	GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
-
-	gobject_class->finalize = gcr_secret_exchange_finalize;
-	g_type_class_add_private (gobject_class, sizeof (GcrSecretExchangePrivate));
-
-	egg_libgcrypt_initialize ();
-}
-
 /**
  * gcr_secret_exchange_new:
+ * @protocol: (allow-none): the exchange protocol to use
  *
  * Create a new secret exchange object.
  *
+ * Specify a protocol of %NULL to allow any protocol. This is especially
+ * relevant on the side of the exchange that does not call
+ * gcr_secret_exchange_begin(), that is the originator. Currently the only
+ * protocol supported is %GCR_SECRET_EXCHANGE_PROTOCOL_1.
+ *
  * Returns: (transfer full): A new #GcrSecretExchange object
  */
 GcrSecretExchange *
-gcr_secret_exchange_new (void)
+gcr_secret_exchange_new (const gchar *protocol)
 {
-	return g_object_new (GCR_TYPE_SECRET_EXCHANGE, NULL);
+	return g_object_new (GCR_TYPE_SECRET_EXCHANGE,
+	                     "protocol", protocol,
+	                     NULL);
+}
+
+/**
+ * gcr_secret_exchange_get_protocol:
+ *
+ * Get the secret exchange protocol.
+ *
+ * Will return %NULL if no protocol was specified, and either
+ * gcr_secret_exchange_begin() or gcr_secret_exchange_receive() have not been
+ * called successfully.
+ *
+ * Returns: the protocol or %NULL
+ */
+const gchar *
+gcr_secret_exchange_get_protocol (GcrSecretExchange *self)
+{
+	g_return_val_if_fail (GCR_IS_SECRET_EXCHANGE (self), NULL);
+	if (self->pv->explicit_protocol || self->pv->generated)
+		return GCR_SECRET_EXCHANGE_PROTOCOL_1;
+	return NULL;
 }
 
 /**
@@ -250,21 +266,26 @@ gcr_secret_exchange_new (void)
 gchar *
 gcr_secret_exchange_begin (GcrSecretExchange *self)
 {
+	GcrSecretExchangeClass *klass;
 	GKeyFile *output;
 	gchar *result;
 
 	g_return_val_if_fail (GCR_IS_SECRET_EXCHANGE (self), NULL);
 
+	klass = GCR_SECRET_EXCHANGE_GET_CLASS (self);
+	g_return_val_if_fail (klass->generate_exchange_key, NULL);
+
 	clear_secret_exchange (self);
-	g_assert (self->pv->priv == NULL);
 
 	output = g_key_file_new ();
 
-	if (!egg_dh_gen_pair (self->pv->prime, self->pv->base, 0,
-	                      &self->pv->pub, &self->pv->priv))
+	if (!(klass->generate_exchange_key) (self, GCR_SECRET_EXCHANGE_PROTOCOL_1,
+	                                     &self->pv->publi, &self->pv->n_publi))
 		g_return_val_if_reached (NULL);
+	self->pv->generated = TRUE;
 
-	key_file_set_mpi (output, EXCHANGE_VERSION, "public", self->pv->pub);
+	key_file_set_base64 (output, GCR_SECRET_EXCHANGE_PROTOCOL_1, "public",
+	                     self->pv->publi, self->pv->n_publi);
 
 	result = g_key_file_to_data (output, NULL, NULL);
 	g_return_val_if_fail (result != NULL, NULL);
@@ -278,98 +299,70 @@ static gboolean
 calculate_key (GcrSecretExchange *self,
                GKeyFile *input)
 {
-	gcry_mpi_t peer;
-	gpointer ikm;
-	gsize n_ikm;
+	GcrSecretExchangeClass *klass;
+	gboolean ret;
+	guchar *peer;
+	gsize n_peer;
+
+	klass = GCR_SECRET_EXCHANGE_GET_CLASS (self);
+	g_return_val_if_fail (klass->derive_transport_key, FALSE);
 
-	peer = key_file_get_mpi (input, EXCHANGE_VERSION, "public");
+	peer = key_file_get_base64 (input, GCR_SECRET_EXCHANGE_PROTOCOL_1, "public", &n_peer);
 	if (peer == NULL) {
 		g_message ("secret-exchange: invalid or missing 'public' argument");
 		return FALSE;
 	}
 
-	/* Build up a key we can use */
-	ikm = egg_dh_gen_secret (peer, self->pv->priv, self->pv->prime, &n_ikm);
-	g_return_val_if_fail (ikm != NULL, FALSE);
-
-	if (self->pv->key == NULL)
-		self->pv->key = egg_secure_alloc (EXCHANGE_1_KEY_LENGTH);
-
-	if (!egg_hkdf_perform (EXCHANGE_1_HASH_ALGO, ikm, n_ikm, NULL, 0,
-	                       NULL, 0, self->pv->key, EXCHANGE_1_KEY_LENGTH))
-		g_return_val_if_reached (FALSE);
-
-	egg_secure_free (ikm);
-	gcry_mpi_release (peer);
+	ret = (klass->derive_transport_key) (self, peer, n_peer);
+	self->pv->derived = ret;
 
-	return TRUE;
+	g_free (peer);
+	return ret;
 }
 
-static gpointer
-perform_aes_decrypt (GcrSecretExchange *self,
-                     GKeyFile *input,
-                     gsize *n_secret)
+static gboolean
+perform_decrypt (GcrSecretExchange *self,
+                 GKeyFile *input,
+                 guchar **secret,
+                 gsize *n_secret)
 {
-	gcry_cipher_hd_t cih;
-	gcry_error_t gcry;
-	guchar* padded;
-	guchar* result;
-	gpointer iv;
-	gpointer value;
-	gsize n_result;
-	gsize n_iv;
-	gsize n_value;
-	gsize pos;
+	GcrSecretExchangeClass *klass;
+	gpointer iv, value;
+	guchar *result;
+	gsize n_result, n_iv, n_value;
+	gboolean ret;
 
-	iv = key_file_get_base64 (input, EXCHANGE_VERSION, "iv", &n_iv);
-	if (iv == NULL || n_iv != EXCHANGE_1_IV_LENGTH) {
-		g_message ("secret-exchange: invalid or missing iv");
-		return NULL;
-	}
+	klass = GCR_SECRET_EXCHANGE_GET_CLASS (self);
+	g_return_val_if_fail (klass->decrypt_transport_data, FALSE);
 
-	value = key_file_get_base64 (input, EXCHANGE_VERSION, "secret", &n_value);
+	iv = key_file_get_base64 (input, GCR_SECRET_EXCHANGE_PROTOCOL_1, "iv", &n_iv);
+
+	value = key_file_get_base64 (input, GCR_SECRET_EXCHANGE_PROTOCOL_1, "secret", &n_value);
 	if (value == NULL) {
 		g_message ("secret-exchange: invalid or missing value");
 		g_free (iv);
-		return NULL;
-	}
-
-	gcry = gcry_cipher_open (&cih, EXCHANGE_1_CIPHER_ALGO, EXCHANGE_1_CIPHER_MODE, 0);
-	if (gcry != 0) {
-		g_warning ("couldn't create aes cipher context: %s", gcry_strerror (gcry));
-		g_free (iv);
 		return FALSE;
 	}
 
-	/* 16 = 128 bits */
-	gcry = gcry_cipher_setkey (cih, self->pv->key, EXCHANGE_1_KEY_LENGTH);
-	g_return_val_if_fail (gcry == 0, FALSE);
-
-	/* 16 = 128 bits */
-	gcry = gcry_cipher_setiv (cih, iv, EXCHANGE_1_IV_LENGTH);
-	g_return_val_if_fail (gcry == 0, FALSE);
+	ret = (klass->decrypt_transport_data) (self, egg_secure_realloc, value, n_value,
+	                                       iv, n_iv, &result, &n_result);
 
+	g_free (value);
 	g_free (iv);
 
-	/* Allocate memory for the result */
-	padded = egg_secure_alloc (n_value);
+	if (!ret)
+		return FALSE;
 
-	for (pos = 0; pos < n_value; pos += 16) {
-		gcry = gcry_cipher_decrypt (cih, padded + pos, 16, (guchar*)value + pos, 16);
-		g_return_val_if_fail (gcry == 0, NULL);
+	/* Reallocate a null terminator */
+	if (result) {
+		result = egg_secure_realloc (result, n_result + 1);
+		result[n_result] = 0;
 	}
 
-	gcry_cipher_close (cih);
-
-	/* This does an extra null-terminator of output */
-	if (!egg_padding_pkcs7_unpad (egg_secure_realloc, 16, padded, n_value,
-	                              (gpointer*)&result, &n_result))
-		result = NULL;
-
-	egg_secure_free (padded);
-
+	*secret = result;
 	*n_secret = n_result;
-	return result;
+
+	return TRUE;
 }
 
 /**
@@ -386,11 +379,19 @@ gboolean
 gcr_secret_exchange_receive (GcrSecretExchange *self,
                              const gchar *exchange)
 {
+	GcrSecretExchangeClass *klass;
 	GKeyFile *input;
 	gchar *secret;
 	gsize n_secret;
 	gboolean ret;
 
+	g_return_val_if_fail (GCR_IS_SECRET_EXCHANGE (self), FALSE);
+	g_return_val_if_fail (exchange != NULL, FALSE);
+
+	klass = GCR_SECRET_EXCHANGE_GET_CLASS (self);
+	g_return_val_if_fail (klass->generate_exchange_key, FALSE);
+	g_return_val_if_fail (klass->derive_transport_key, FALSE);
+
 	/* Parse the input */
 	input = g_key_file_new ();
 	if (!g_key_file_load_from_data (input, exchange, strlen (exchange),
@@ -400,10 +401,11 @@ gcr_secret_exchange_receive (GcrSecretExchange *self,
 		return FALSE;
 	}
 
-	if (self->pv->priv == NULL) {
-		if (!egg_dh_gen_pair (self->pv->prime, self->pv->base, 0,
-		                      &self->pv->pub, &self->pv->priv))
+	if (!self->pv->generated) {
+		if (!(klass->generate_exchange_key) (self, GCR_SECRET_EXCHANGE_PROTOCOL_1,
+		                                     &self->pv->publi, &self->pv->n_publi))
 			g_return_val_if_reached (FALSE);
+		self->pv->generated = TRUE;
 	}
 
 	if (!calculate_key (self, input))
@@ -411,9 +413,10 @@ gcr_secret_exchange_receive (GcrSecretExchange *self,
 
 	ret = TRUE;
 
-	if (g_key_file_has_key (input, EXCHANGE_VERSION, "secret", NULL)) {
-		secret = perform_aes_decrypt (self, input, &n_secret);
-		if (secret == NULL) {
+	if (g_key_file_has_key (input, GCR_SECRET_EXCHANGE_PROTOCOL_1, "secret", NULL)) {
+
+		/* Remember that this can return a NULL secret */
+		if (!perform_decrypt (self, input, (guchar **)&secret, &n_secret)) {
 			ret = FALSE;
 		} else {
 			egg_secure_free (self->pv->secret);
@@ -440,7 +443,7 @@ gcr_secret_exchange_receive (GcrSecretExchange *self,
  * so if you're certain that it is does not contain arbitrary binary data,
  * it can be used as a string.
  *
- * Returns: (transfer none): The last secret received.
+ * Returns: (transfer none) (array length=secret_len): the last secret received
  */
 const gchar *
 gcr_secret_exchange_get_secret (GcrSecretExchange *self,
@@ -453,70 +456,28 @@ gcr_secret_exchange_get_secret (GcrSecretExchange *self,
 	return self->pv->secret;
 }
 
-static gpointer
-calculate_iv (GKeyFile *output)
-{
-	gpointer iv;
-
-	iv = g_malloc0 (EXCHANGE_1_IV_LENGTH);
-	gcry_create_nonce (iv, EXCHANGE_1_IV_LENGTH);
-	key_file_set_base64 (output, EXCHANGE_VERSION, "iv", iv, EXCHANGE_1_IV_LENGTH);
-
-	return iv;
-}
-
 static gboolean
-perform_aes_encrypt (GKeyFile *output,
-                     gconstpointer key,
-                     const gchar *secret,
-                     gsize n_secret)
+perform_encrypt (GcrSecretExchange *self,
+                 GKeyFile *output,
+                 const gchar *secret,
+                 gsize n_secret)
 {
-	gcry_cipher_hd_t cih;
-	gcry_error_t gcry;
-	guchar* padded;
-	guchar* result;
-	gpointer iv;
-	gsize n_result;
-	gsize pos;
+	GcrSecretExchangeClass *klass;
+	guchar *result, *iv;
+	gsize n_result, n_iv;
 
-	iv = calculate_iv (output);
-	g_return_val_if_fail (iv != NULL, FALSE);
+	klass = GCR_SECRET_EXCHANGE_GET_CLASS (self);
+	g_return_val_if_fail (klass->encrypt_transport_data, FALSE);
 
-	gcry = gcry_cipher_open (&cih, EXCHANGE_1_CIPHER_ALGO, EXCHANGE_1_CIPHER_MODE, 0);
-	if (gcry != 0) {
-		g_warning ("couldn't create aes cipher context: %s", gcry_strerror (gcry));
-		g_free (iv);
+	if (!(klass->encrypt_transport_data) (self, g_realloc, (const guchar *)secret,
+	                                      n_secret, &iv, &n_iv, &result, &n_result))
 		return FALSE;
-	}
-
-	/* 16 = 128 bits */
-	gcry = gcry_cipher_setkey (cih, key, EXCHANGE_1_KEY_LENGTH);
-	g_return_val_if_fail (gcry == 0, FALSE);
 
-	/* 16 = 128 bits */
-	gcry = gcry_cipher_setiv (cih, iv, EXCHANGE_1_IV_LENGTH);
-	g_return_val_if_fail (gcry == 0, FALSE);
-
-	g_free (iv);
-
-	/* Pad the text properly */
-	if (!egg_padding_pkcs7_pad (egg_secure_realloc, 16, secret, n_secret,
-	                            (gpointer*)&padded, &n_result))
-		g_return_val_if_reached (FALSE);
-	result = g_malloc0 (n_result);
-
-	for (pos = 0; pos < n_result; pos += 16) {
-		gcry = gcry_cipher_encrypt (cih, result + pos, 16, padded + pos, 16);
-		g_return_val_if_fail (gcry == 0, FALSE);
-	}
-
-	gcry_cipher_close (cih);
+	key_file_set_base64 (output, GCR_SECRET_EXCHANGE_PROTOCOL_1, "secret", result, n_result);
+	key_file_set_base64 (output, GCR_SECRET_EXCHANGE_PROTOCOL_1, "iv", iv, n_iv);
 
-	egg_secure_clear (padded, n_result);
-	egg_secure_free (padded);
-
-	key_file_set_base64 (output, EXCHANGE_VERSION, "secret", result, n_result);
 	g_free (result);
+	g_free (iv);
 
 	return TRUE;
 }
@@ -547,19 +508,20 @@ gcr_secret_exchange_send (GcrSecretExchange *self,
 
 	g_return_val_if_fail (GCR_IS_SECRET_EXCHANGE (self), NULL);
 
-	if (self->pv->key == NULL) {
+	if (!self->pv->derived) {
 		g_warning ("gcr_secret_exchange_receive() must be called "
 		           "before calling this function");
 		return NULL;
 	}
 
 	output = g_key_file_new ();
-	key_file_set_mpi (output, EXCHANGE_VERSION, "public", self->pv->pub);
+	key_file_set_base64 (output, GCR_SECRET_EXCHANGE_PROTOCOL_1, "public", self->pv->publi,
+	                     self->pv->n_publi);
 
 	if (secret != NULL) {
 		if (secret_len < 0)
 			secret_len = strlen (secret);
-		if (!perform_aes_encrypt (output, self->pv->key, secret, secret_len)) {
+		if (!perform_encrypt (self, output, secret, secret_len)) {
 			g_key_file_free (output);
 			return NULL;
 		}
@@ -570,3 +532,302 @@ gcr_secret_exchange_send (GcrSecretExchange *self,
 	g_key_file_free (output);
 	return result;
 }
+
+/*
+ * This is the only set we support so far. It includes:
+ *  - DH with the 1536 ike modp group for key exchange
+ *  - HKDF SHA256 for hashing of the key to appropriate size
+ *  - AES 128 CBC for encryption
+ *  - PKCS#7 style padding
+ */
+
+#define EXCHANGE_1_IKE_NAME     "ietf-ike-grp-modp-1536"
+#define EXCHANGE_1_KEY_LENGTH   16
+#define EXCHANGE_1_IV_LENGTH    16
+#define EXCHANGE_1_HASH_ALGO    "sha256"
+#define EXCHANGE_1_CIPHER_ALGO  GCRY_CIPHER_AES128
+#define EXCHANGE_1_CIPHER_MODE  GCRY_CIPHER_MODE_CBC
+
+struct _GcrSecretExchangeDefault {
+	gcry_mpi_t prime;
+	gcry_mpi_t base;
+	gcry_mpi_t pub;
+	gcry_mpi_t priv;
+	gpointer key;
+};
+
+static guchar *
+mpi_to_data (gcry_mpi_t mpi,
+             gsize *n_data)
+{
+	gcry_error_t gcry;
+	guchar *data;
+
+	/* Get the size */
+	gcry = gcry_mpi_print (GCRYMPI_FMT_USG, NULL, 0, n_data, mpi);
+	g_return_val_if_fail (gcry == 0, NULL);
+
+	data = g_malloc0 (*n_data);
+
+	/* Write into buffer */
+	gcry = gcry_mpi_print (GCRYMPI_FMT_USG, data, *n_data, n_data, mpi);
+	g_return_val_if_fail (gcry == 0, NULL);
+
+	return data;
+}
+
+static gcry_mpi_t
+mpi_from_data (const guchar *data,
+               gsize n_data)
+{
+	gcry_mpi_t mpi;
+	gcry_error_t gcry;
+
+	gcry = gcry_mpi_scan (&mpi, GCRYMPI_FMT_USG, data, n_data, NULL);
+	return (gcry == 0) ? mpi : NULL;
+}
+
+static void
+gcr_secret_exchange_default_free (gpointer to_free)
+{
+	GcrSecretExchangeDefault *data = to_free;
+	gcry_mpi_release (data->prime);
+	gcry_mpi_release (data->base);
+	gcry_mpi_release (data->pub);
+	gcry_mpi_release (data->priv);
+	if (data->key) {
+		egg_secure_clear (data->key, EXCHANGE_1_KEY_LENGTH);
+		egg_secure_free (data->key);
+	}
+	g_free (data);
+}
+
+static gboolean
+gcr_secret_exchange_default_generate_exchange_key (GcrSecretExchange *exchange,
+                                                   const gchar *scheme,
+                                                   guchar **public_key,
+                                                   gsize *n_public_key)
+{
+	GcrSecretExchangeDefault *data = exchange->pv->default_exchange;
+
+	if (data == NULL) {
+		data = g_new0 (GcrSecretExchangeDefault, 1);
+		if (!egg_dh_default_params (EXCHANGE_1_IKE_NAME, &data->prime, &data->base))
+			g_return_val_if_reached (FALSE);
+
+		exchange->pv->default_exchange = data;
+		exchange->pv->destroy_exchange = gcr_secret_exchange_default_free;
+	}
+
+	gcry_mpi_release (data->priv);
+	data->priv = NULL;
+	gcry_mpi_release (data->pub);
+	data->pub = NULL;
+	egg_secure_free (data->key);
+	data->key = NULL;
+
+	if (!egg_dh_gen_pair (data->prime, data->base, 0,
+	                      &data->pub, &data->priv))
+		g_return_val_if_reached (FALSE);
+
+	*public_key = mpi_to_data (data->pub, n_public_key);
+	return *public_key != NULL;
+}
+
+static gboolean
+gcr_secret_exchange_default_derive_transport_key (GcrSecretExchange *exchange,
+                                                  const guchar *peer,
+                                                  gsize n_peer)
+{
+	GcrSecretExchangeDefault *data = exchange->pv->default_exchange;
+	gpointer ikm;
+	gsize n_ikm;
+	gcry_mpi_t mpi;
+
+	g_return_val_if_fail (data != NULL, FALSE);
+	g_return_val_if_fail (data->priv != NULL, FALSE);
+
+	mpi = mpi_from_data (peer, n_peer);
+	if (mpi == NULL)
+		return FALSE;
+
+	/* Build up a key we can use */
+	ikm = egg_dh_gen_secret (mpi, data->priv, data->prime, &n_ikm);
+	g_return_val_if_fail (ikm != NULL, FALSE);
+
+	if (data->key == NULL)
+		data->key = egg_secure_alloc (EXCHANGE_1_KEY_LENGTH);
+
+	if (!egg_hkdf_perform (EXCHANGE_1_HASH_ALGO, ikm, n_ikm, NULL, 0,
+	                       NULL, 0, data->key, EXCHANGE_1_KEY_LENGTH))
+		g_return_val_if_reached (FALSE);
+
+	egg_secure_free (ikm);
+	gcry_mpi_release (mpi);
+
+	return TRUE;
+}
+
+static gboolean
+gcr_secret_exchange_default_encrypt_transport_data (GcrSecretExchange *exchange,
+                                                    GckAllocator allocator,
+                                                    const guchar *plain_text,
+                                                    gsize n_plain_text,
+                                                    guchar **iv,
+                                                    gsize *n_iv,
+                                                    guchar **cipher_text,
+                                                    gsize *n_cipher_text)
+{
+	GcrSecretExchangeDefault *data = exchange->pv->default_exchange;
+	gcry_cipher_hd_t cih;
+	gcry_error_t gcry;
+	guchar *padded;
+	gsize n_result;
+	guchar *result;
+	gsize pos;
+
+	g_return_val_if_fail (data != NULL, FALSE);
+	g_return_val_if_fail (data->key != NULL, FALSE);
+
+	gcry = gcry_cipher_open (&cih, EXCHANGE_1_CIPHER_ALGO, EXCHANGE_1_CIPHER_MODE, 0);
+	if (gcry != 0) {
+		g_warning ("couldn't create aes cipher context: %s", gcry_strerror (gcry));
+		g_free (iv);
+		return FALSE;
+	}
+
+	*iv = (allocator) (NULL, EXCHANGE_1_IV_LENGTH);
+	g_return_val_if_fail (*iv != NULL, FALSE);
+	gcry_create_nonce (*iv, EXCHANGE_1_IV_LENGTH);
+	*n_iv = EXCHANGE_1_IV_LENGTH;
+
+	/* 16 = 128 bits */
+	gcry = gcry_cipher_setkey (cih, data->key, EXCHANGE_1_KEY_LENGTH);
+	g_return_val_if_fail (gcry == 0, FALSE);
+
+	/* 16 = 128 bits */
+	gcry = gcry_cipher_setiv (cih, *iv, EXCHANGE_1_IV_LENGTH);
+	g_return_val_if_fail (gcry == 0, FALSE);
+
+	/* Pad the text properly */
+	if (!egg_padding_pkcs7_pad (egg_secure_realloc, 16, plain_text, n_plain_text,
+	                            (gpointer*)&padded, &n_result))
+		g_return_val_if_reached (FALSE);
+	result = (allocator) (NULL, n_result);
+	g_return_val_if_fail (result != NULL, FALSE);
+
+	for (pos = 0; pos < n_result; pos += 16) {
+		gcry = gcry_cipher_encrypt (cih, result + pos, 16, padded + pos, 16);
+		g_return_val_if_fail (gcry == 0, FALSE);
+	}
+
+	gcry_cipher_close (cih);
+
+	egg_secure_clear (padded, n_result);
+	egg_secure_free (padded);
+
+	*cipher_text = result;
+	*n_cipher_text = n_result;
+	return TRUE;
+}
+
+static gboolean
+gcr_secret_exchange_default_decrypt_transport_data (GcrSecretExchange *exchange,
+                                                    GckAllocator allocator,
+                                                    const guchar *cipher_text,
+                                                    gsize n_cipher_text,
+                                                    const guchar *iv,
+                                                    gsize n_iv,
+                                                    guchar **plain_text,
+                                                    gsize *n_plain_text)
+{
+	GcrSecretExchangeDefault *data = exchange->pv->default_exchange;
+	guchar* padded;
+	guchar* result;
+	gsize n_result;
+	gsize pos;
+	gcry_cipher_hd_t cih;
+	gcry_error_t gcry;
+
+	g_return_val_if_fail (data != NULL, FALSE);
+	g_return_val_if_fail (data->key != NULL, FALSE);
+
+	if (iv == NULL || n_iv != EXCHANGE_1_IV_LENGTH) {
+		g_message ("secret-exchange: invalid or missing iv");
+		return FALSE;
+	}
+
+	if (n_cipher_text % 16 != 0) {
+		g_message ("secret-message: invalid length for cipher text");
+		return FALSE;
+	}
+
+	gcry = gcry_cipher_open (&cih, EXCHANGE_1_CIPHER_ALGO, EXCHANGE_1_CIPHER_MODE, 0);
+	if (gcry != 0) {
+		g_warning ("couldn't create aes cipher context: %s", gcry_strerror (gcry));
+		return FALSE;
+	}
+
+	/* 16 = 128 bits */
+	gcry = gcry_cipher_setkey (cih, data->key, EXCHANGE_1_KEY_LENGTH);
+	g_return_val_if_fail (gcry == 0, FALSE);
+
+	/* 16 = 128 bits */
+	gcry = gcry_cipher_setiv (cih, iv, n_iv);
+	g_return_val_if_fail (gcry == 0, FALSE);
+
+	/* Allocate memory for the result */
+	padded = (allocator) (NULL, n_cipher_text);
+	g_return_val_if_fail (padded != NULL, FALSE);
+
+	for (pos = 0; pos < n_cipher_text; pos += 16) {
+		gcry = gcry_cipher_decrypt (cih, padded + pos, 16, (guchar *)cipher_text + pos, 16);
+		g_return_val_if_fail (gcry == 0, FALSE);
+	}
+
+	gcry_cipher_close (cih);
+
+	if (!egg_padding_pkcs7_unpad (allocator, 16, padded, n_cipher_text,
+	                              (gpointer*)&result, &n_result))
+		result = NULL;
+
+	/* Free the padded text */
+	(allocator) (padded, 0);
+
+	*plain_text = result;
+	*n_plain_text = n_result;
+	return TRUE;
+}
+
+static void
+gcr_secret_exchange_class_init (GcrSecretExchangeClass *klass)
+{
+	GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+	gobject_class->get_property = gcr_secret_exchange_get_property;
+	gobject_class->set_property = gcr_secret_exchange_set_property;
+	gobject_class->finalize = gcr_secret_exchange_finalize;
+
+	klass->generate_exchange_key = gcr_secret_exchange_default_generate_exchange_key;
+	klass->derive_transport_key = gcr_secret_exchange_default_derive_transport_key;
+	klass->decrypt_transport_data = gcr_secret_exchange_default_decrypt_transport_data;
+	klass->encrypt_transport_data = gcr_secret_exchange_default_encrypt_transport_data;
+
+	g_type_class_add_private (gobject_class, sizeof (GcrSecretExchangePrivate));
+
+	egg_libgcrypt_initialize ();
+
+	/**
+	 * GcrSecretExchange:protocol:
+	 *
+	 * The protocol being used for the exchange.
+	 *
+	 * Will be %NULL if no protocol was specified when creating this object,
+	 * and either gcr_secret_exchange_begin() or gcr_secret_exchange_receive()
+	 * have not been called successfully.
+	 */
+	g_object_class_install_property (gobject_class, PROP_PROTOCOL,
+	           g_param_spec_string ("protocol", "Protocol", "Exchange protocol",
+	                                GCR_SECRET_EXCHANGE_PROTOCOL_1,
+	                                G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+}
diff --git a/gcr/gcr-secret-exchange.h b/gcr/gcr-secret-exchange.h
index 41ff8b3..5608f63 100644
--- a/gcr/gcr-secret-exchange.h
+++ b/gcr/gcr-secret-exchange.h
@@ -24,12 +24,14 @@
 #ifndef __GCR_SECRET_EXCHANGE_H__
 #define __GCR_SECRET_EXCHANGE_H__
 
-#include "gcr.h"
+#include "gcr-base.h"
 
 #include <glib-object.h>
 
 G_BEGIN_DECLS
 
+#define GCR_SECRET_EXCHANGE_PROTOCOL_1 "sx-aes-1"
+
 #define GCR_TYPE_SECRET_EXCHANGE               (gcr_secret_exchange_get_type ())
 #define GCR_SECRET_EXCHANGE(obj)               (G_TYPE_CHECK_INSTANCE_CAST ((obj), GCR_TYPE_SECRET_EXCHANGE, GcrSecretExchange))
 #define GCR_SECRET_EXCHANGE_CLASS(klass)       (G_TYPE_CHECK_CLASS_CAST ((klass), GCR_TYPE_SECRET_EXCHANGE, GcrSecretExchangeClass))
@@ -50,13 +52,45 @@ struct _GcrSecretExchange {
 struct _GcrSecretExchangeClass {
 	/*< private >*/
 	GObjectClass parent_class;
+
+	/* virtual methods, not used publicly */
+	gboolean        (*generate_exchange_key)   (GcrSecretExchange *exchange,
+	                                            const gchar *scheme,
+	                                            guchar **public_key,
+	                                            gsize *n_public_key);
+
+	gboolean        (*derive_transport_key)    (GcrSecretExchange *exchange,
+	                                            const guchar *peer,
+	                                            gsize n_peer);
+
+	gboolean        (*encrypt_transport_data)  (GcrSecretExchange *exchange,
+	                                            GckAllocator allocator,
+	                                            const guchar *plain_text,
+	                                            gsize n_plain_text,
+	                                            guchar **parameter,
+	                                            gsize *n_parameter,
+	                                            guchar **cipher_text,
+	                                            gsize *n_cipher_text);
+
+	gboolean        (*decrypt_transport_data)  (GcrSecretExchange *exchange,
+	                                            GckAllocator allocator,
+	                                            const guchar *cipher_text,
+	                                            gsize n_cipher_text,
+	                                            const guchar *parameter,
+	                                            gsize n_parameter,
+	                                            guchar **plain_text,
+	                                            gsize *n_plain_text);
+
+	gpointer dummy[6];
 };
 
 /* Caller side functions */
 
 GType               gcr_secret_exchange_get_type        (void);
 
-GcrSecretExchange * gcr_secret_exchange_new             (void);
+GcrSecretExchange * gcr_secret_exchange_new             (const gchar *protocol);
+
+const gchar *       gcr_secret_exchange_get_protocol    (GcrSecretExchange *self);
 
 gchar *             gcr_secret_exchange_begin           (GcrSecretExchange *self);
 
diff --git a/gcr/tests/test-secret-exchange.c b/gcr/tests/test-secret-exchange.c
index 48581fe..7964745 100644
--- a/gcr/tests/test-secret-exchange.c
+++ b/gcr/tests/test-secret-exchange.c
@@ -36,9 +36,9 @@ typedef struct {
 static void
 setup (Test *test, gconstpointer unused)
 {
-	test->caller = gcr_secret_exchange_new ();
+	test->caller = gcr_secret_exchange_new (NULL);
 	g_assert (GCR_IS_SECRET_EXCHANGE (test->caller));
-	test->callee = gcr_secret_exchange_new ();
+	test->callee = gcr_secret_exchange_new (NULL);
 	g_assert (GCR_IS_SECRET_EXCHANGE (test->callee));
 }
 



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]