From 1cc65dbe1ac64af14d91fa1c1d19ed5d6b0316bf Mon Sep 17 00:00:00 2001 From: Stef Walter Date: Sat, 1 Oct 2011 12:20:07 +0200 Subject: [PATCH] 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 1723b8d..6b78303 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 #include +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 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)); } -- 2.7.4