/* * gnome-keyring * * Copyright (C) 2010 Stefan Walter * * 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, see . */ #include "config.h" #include "gcr-secret-exchange.h" #include "egg/egg-dh.h" #include "egg/egg-hkdf.h" #include "egg/egg-libgcrypt.h" #include "egg/egg-padding.h" #include "egg/egg-secure-memory.h" #include #include EGG_SECURE_DECLARE (secret_exchange); /** * SECTION:gcr-secret-exchange * @title: GcrSecretExchange * @short_description: Exchange secrets between processes in an unexposed way. * * Allows exchange of secrets between two processes on the same system without * exposing those secrets to things like loggers, non-pageable memory etc. * * This does not protect against active attacks like MITM attacks. * * Each side creates a #GcrSecretExchange object, and one of the sides calls * gcr_secret_exchange_begin(). This creates a string, which should be passed * to the other side. Each side passes the strings it receives into * gcr_secret_exchange_receive(). * * In order to send a reply (either with or without a secret) use * gcr_secret_exchange_send(). A side must have had gcr_secret_exchange_receive() * successfully called before it can use gcr_secret_exchange_send(). * * The #GcrSecretExchange objects can be used for multiple iterations of the * conversation, or for just one request/reply. The only limitation being that * the initial request cannot contain a secret. * * Caveat: Information about the approximate length (rounded up to the nearest * 16 bytes) may be leaked. If this is considered inacceptable, do not use * #GcrSecretExchange. */ /** * GcrSecretExchange: * * An object representing one side of a secret exchange. */ /** * GcrSecretExchangeClass: * * The class for #GcrSecretExchange */ /** * 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 SECRET_EXCHANGE_PROTOCOL_1_PREFIX "[" GCR_SECRET_EXCHANGE_PROTOCOL_1 "]\n" enum { PROP_0, PROP_PROTOCOL }; typedef struct _GcrSecretExchangeDefault GcrSecretExchangeDefault; struct _GcrSecretExchangePrivate { GcrSecretExchangeDefault *default_exchange; GDestroyNotify destroy_exchange; gboolean explicit_protocol; gboolean generated; guchar *publi; gsize n_publi; gboolean derived; gchar *secret; gsize n_secret; }; G_DEFINE_TYPE (GcrSecretExchange, gcr_secret_exchange, G_TYPE_OBJECT); static void key_file_set_base64 (GKeyFile *key_file, const gchar *section, const gchar *field, gconstpointer data, gsize n_data) { gchar *value; value = g_base64_encode (data, n_data); g_key_file_set_value (key_file, section, field, value); g_free (value); } static gpointer key_file_get_base64 (GKeyFile *key_file, const gchar *section, const gchar *field, gsize *n_result) { gpointer result = NULL; gchar *data; g_return_val_if_fail (key_file, NULL); g_return_val_if_fail (section, NULL); g_return_val_if_fail (field, NULL); g_return_val_if_fail (n_result, NULL); data = g_key_file_get_value (key_file, section, field, NULL); if (data != NULL) result = g_base64_decode (data, n_result); g_free (data); return result; } static void gcr_secret_exchange_init (GcrSecretExchange *self) { self->pv = G_TYPE_INSTANCE_GET_PRIVATE (self, GCR_TYPE_SECRET_EXCHANGE, GcrSecretExchangePrivate); } 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) { g_debug ("automatically selecting secret exchange protocol"); } else { if (g_str_equal (protocol, GCR_SECRET_EXCHANGE_PROTOCOL_1)) { g_debug ("explicitly using secret exchange protocol: %s", 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_get_property (GObject *obj, guint prop_id, GValue *value, GParamSpec *pspec) { GcrSecretExchange *self = GCR_SECRET_EXCHANGE (obj); 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) { 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; } static void 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); G_OBJECT_CLASS (gcr_secret_exchange_parent_class)->finalize (obj); } /** * 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 (const gchar *protocol) { return g_object_new (GCR_TYPE_SECRET_EXCHANGE, "protocol", protocol, NULL); } /** * gcr_secret_exchange_get_protocol: * @self: a #GcrSecretExchange object * 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; } /** * gcr_secret_exchange_begin: * @self: a #GcrSecretExchange object * * Begin the secret exchange. The resulting string should be sent to the other * side of the exchange. The other side should use gcr_secret_exchange_receive() * to process the string. * * Returns: (transfer full): A newly allocated string to be sent to the other * side of the secret exchange */ 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); output = g_key_file_new (); 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_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); g_strchug (result); gchar *string = g_strescape (result, ""); g_debug ("beginning the secret exchange: %s", string); g_free (string); if (!g_str_has_prefix (result, SECRET_EXCHANGE_PROTOCOL_1_PREFIX)) g_warning ("the prepared data does not have the correct protocol prefix"); g_key_file_free (output); return result; } static gboolean derive_key (GcrSecretExchange *self, GKeyFile *input) { 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); g_debug ("deriving shared transport key"); 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; } ret = (klass->derive_transport_key) (self, peer, n_peer); self->pv->derived = ret; g_free (peer); return ret; } static gboolean perform_decrypt (GcrSecretExchange *self, GKeyFile *input, guchar **secret, gsize *n_secret) { GcrSecretExchangeClass *klass; gpointer iv, value; guchar *result; gsize n_result, n_iv, n_value; gboolean ret; klass = GCR_SECRET_EXCHANGE_GET_CLASS (self); g_return_val_if_fail (klass->decrypt_transport_data, FALSE); 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 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); if (!ret) return FALSE; /* Reallocate a null terminator */ if (result) { result = egg_secure_realloc (result, n_result + 1); result[n_result] = 0; } *secret = result; *n_secret = n_result; return TRUE; } /** * gcr_secret_exchange_receive: * @self: a #GcrSecretExchange object * @exchange: the string received * * Receive a string from the other side of secret exchange. This string will * have been created by gcr_secret_exchange_begin() or gcr_secret_exchange_send(). * * After this call completes successfully the value returned from * gcr_secret_exchange_get_secret() will have changed. * * Returns: whether the string was successfully parsed and received */ gboolean gcr_secret_exchange_receive (GcrSecretExchange *self, const gchar *exchange) { GcrSecretExchangeClass *klass; gchar *secret = NULL; gsize n_secret = 0; GKeyFile *input; 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); gchar *string = g_strescape (exchange, ""); g_debug ("receiving secret exchange: %s", string); g_free (string); /* Parse the input */ input = g_key_file_new (); if (!g_key_file_load_from_data (input, exchange, strlen (exchange), G_KEY_FILE_NONE, NULL)) { g_key_file_free (input); g_message ("couldn't parse secret exchange data"); return FALSE; } 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; } ret = TRUE; if (!self->pv->derived) { if (!derive_key (self, input)) ret = FALSE; } if (ret && g_key_file_has_key (input, GCR_SECRET_EXCHANGE_PROTOCOL_1, "secret", NULL)) ret = perform_decrypt (self, input, (guchar **)&secret, &n_secret); if (ret) { egg_secure_free (self->pv->secret); self->pv->secret = secret; self->pv->n_secret = n_secret; } g_key_file_free (input); return ret; } /** * gcr_secret_exchange_get_secret: * @self: a #GcrSecretExchange object * @secret_len: (allow-none): optionally, a location to store the length of returned secret * * Returns the last secret received. If no secret has yet been received this * will return %NULL. The string is owned by the #GcrSecretExchange object * and will be valid until the next time that gcr_secret_exchange_receive() * is called on this object, or the object is destroyed. * * Depending on the secret passed into the other side of the secret exchange, * the result may be a binary string. It does however have a null terminator, * so if you're certain that it is does not contain arbitrary binary data, * it can be used as a string. * * Returns: (transfer none) (array length=secret_len): the last secret received */ const gchar * gcr_secret_exchange_get_secret (GcrSecretExchange *self, gsize *secret_len) { g_return_val_if_fail (GCR_IS_SECRET_EXCHANGE (self), NULL); if (secret_len) *secret_len = self->pv->n_secret; return self->pv->secret; } static gboolean perform_encrypt (GcrSecretExchange *self, GKeyFile *output, const gchar *secret, gsize n_secret) { GcrSecretExchangeClass *klass; guchar *result, *iv; gsize n_result, n_iv; klass = GCR_SECRET_EXCHANGE_GET_CLASS (self); g_return_val_if_fail (klass->encrypt_transport_data, FALSE); if (!(klass->encrypt_transport_data) (self, g_realloc, (const guchar *)secret, n_secret, &iv, &n_iv, &result, &n_result)) return FALSE; 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); g_free (result); g_free (iv); return TRUE; } /** * gcr_secret_exchange_send: * @self: a #GcrSecretExchange object * @secret: (allow-none): optionally, a secret to send to the other side * @secret_len: length of @secret, or -1 if null terminated * * Send a reply to the other side of the secret exchange, optionally sending a * secret. * * gcr_secret_exchange_receive() must have been successfully called at least * once on this object. In other words this object must have received data * from the other side of the secret exchange, before we can send a secret. * * Returns: (transfer full): a newly allocated string to be sent to the other * side of the secret exchange */ gchar * gcr_secret_exchange_send (GcrSecretExchange *self, const gchar *secret, gssize secret_len) { GKeyFile *output; gchar *result; g_return_val_if_fail (GCR_IS_SECRET_EXCHANGE (self), 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_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_encrypt (self, output, secret, secret_len)) { g_key_file_free (output); return NULL; } } result = g_key_file_to_data (output, NULL, NULL); g_return_val_if_fail (result != NULL, NULL); g_strchug (result); gchar *string = g_strescape (result, ""); g_debug ("sending the secret exchange: %s", string); g_free (string); if (!g_str_has_prefix (result, SECRET_EXCHANGE_PROTOCOL_1_PREFIX)) g_warning ("the prepared data does not have the correct protocol prefix: %s", result); 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; g_debug ("generating public key"); 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_debug ("deriving transport key"); 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) { g_debug ("invalid peer mpi"); 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); g_debug ("encrypting data"); 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); g_debug ("decrypting data"); 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)); }