static void gkm_secret_item_real_set_attribute (GkmObject *base, GkmSession *session, GkmTransaction *transaction, CK_ATTRIBUTE_PTR attr) { GkmSecretItem *self = GKM_SECRET_ITEM (base); const gchar *identifier; GkmSecretData *sdata; GHashTable *fields; gchar *schema_name; GkmSecret *secret; gchar *schema; CK_RV rv; if (!self->collection) { gkm_transaction_fail (transaction, CKR_GENERAL_ERROR); g_return_if_reached (); } /* Check that the object is not locked */ if (!gkm_secret_collection_unlocked_have (self->collection, session)) { gkm_transaction_fail (transaction, CKR_USER_NOT_LOGGED_IN); return; } switch (attr->type) { case CKA_VALUE: sdata = gkm_secret_collection_unlocked_use (self->collection, session); g_return_if_fail (sdata); identifier = gkm_secret_object_get_identifier (GKM_SECRET_OBJECT (self)); secret = gkm_secret_new (attr->pValue, attr->ulValueLen); gkm_secret_data_set_transacted (sdata, transaction, identifier, secret); g_object_unref (secret); g_object_unref (sdata); gkm_secret_object_begin_modified (GKM_SECRET_OBJECT (self), transaction); if (!gkm_transaction_get_failed (transaction)) gkm_transaction_add (transaction, self, complete_set_secret, NULL); return; case CKA_G_FIELDS: rv = gkm_secret_fields_parse (attr, &fields, &schema_name); if (rv != CKR_OK) { gkm_transaction_fail (transaction, rv); } else { begin_set_fields (self, transaction, fields); if (schema_name) begin_set_schema (self, transaction, schema_name); } return; case CKA_G_SCHEMA: rv = gkm_attribute_get_string (attr, &schema); if (rv != CKR_OK) gkm_transaction_fail (transaction, rv); else begin_set_schema (self, transaction, schema); return; } GKM_OBJECT_CLASS (gkm_secret_item_parent_class)->set_attribute (base, session, transaction, attr); }
static void gkm_secret_object_set_attribute (GkmObject *base, GkmSession *session, GkmTransaction *transaction, CK_ATTRIBUTE_PTR attr) { GkmSecretObject *self = GKM_SECRET_OBJECT (base); gchar *label; CK_RV rv; switch (attr->type) { case CKA_LABEL: /* Check that the object is not locked */ if (gkm_secret_object_is_locked (self, session)) rv = CKR_USER_NOT_LOGGED_IN; else rv = gkm_attribute_get_string (attr, &label); if (rv != CKR_OK) gkm_transaction_fail (transaction, rv); else begin_set_label (self, transaction, label); return; } GKM_OBJECT_CLASS (gkm_secret_object_parent_class)->set_attribute (base, session, transaction, attr); }
static CK_RV gkm_secret_object_get_attribute (GkmObject *base, GkmSession *session, CK_ATTRIBUTE_PTR attr) { GkmSecretObject *self = GKM_SECRET_OBJECT (base); switch (attr->type) { case CKA_MODIFIABLE: return gkm_attribute_set_bool (attr, TRUE); case CKA_ID: return gkm_attribute_set_string (attr, gkm_secret_object_get_identifier (self)); case CKA_LABEL: return gkm_attribute_set_string (attr, gkm_secret_object_get_label (self)); case CKA_G_LOCKED: return gkm_attribute_set_bool (attr, gkm_secret_object_is_locked (self, session)); case CKA_G_CREATED: return gkm_attribute_set_time (attr, gkm_secret_object_get_created (self)); case CKA_G_MODIFIED: return gkm_attribute_set_time (attr, gkm_secret_object_get_modified (self)); } return GKM_OBJECT_CLASS (gkm_secret_object_parent_class)->get_attribute (base, session, attr); }
static void setup_item_from_info (GkmSecretItem *item, GkmSecretData *data, ItemInfo *info) { GkmSecretObject *obj = GKM_SECRET_OBJECT (item); const gchar *schema_name; GkmSecret *secret; gkm_secret_object_set_label (obj, info->display_name); gkm_secret_object_set_created (obj, info->ctime); gkm_secret_object_set_modified (obj, info->mtime); schema_name = g_hash_table_lookup (info->attributes, GKM_SECRET_FIELD_SCHEMA); if (schema_name == NULL) schema_name = gkm_secret_compat_format_item_type (info->type); gkm_secret_item_set_schema (item, schema_name); gkm_secret_item_set_fields (item, info->attributes); /* Collection is locked */ if (!data) { g_object_set_data (G_OBJECT (item), "compat-acl", NULL); } else { secret = gkm_secret_new (info->ptr_secret, info->n_secret); gkm_secret_data_set_secret (data, gkm_secret_object_get_identifier (obj), secret); g_object_unref (secret); g_object_set_data_full (G_OBJECT (item), "compat-acl", info->acl, gkm_secret_compat_acl_free); info->acl = NULL; } }
static void gkm_secret_object_set_property (GObject *obj, guint prop_id, const GValue *value, GParamSpec *pspec) { GkmSecretObjectClass *klass = GKM_SECRET_OBJECT_GET_CLASS (obj); GkmSecretObject *self = GKM_SECRET_OBJECT (obj); const gchar *identifier; switch (prop_id) { case PROP_LABEL: gkm_secret_object_set_label (self, g_value_get_string (value)); break; case PROP_IDENTIFIER: g_return_if_fail (!self->pv->identifier); identifier = g_value_get_string (value); g_return_if_fail (identifier); self->pv->identifier = register_identifier (klass, identifier); break; case PROP_CREATED: gkm_secret_object_set_created (self, g_value_get_long (value)); break; case PROP_MODIFIED: gkm_secret_object_set_modified (self, g_value_get_long (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, prop_id, pspec); break; } }
static void was_notified (GObject *obj, GParamSpec *pspec, gpointer user_data) { gboolean* notified = user_data; g_assert (GKM_SECRET_OBJECT (obj) == object); g_assert (user_data); *notified = TRUE; }
static gboolean gkm_secret_item_real_is_locked (GkmSecretObject *obj, GkmSession *session) { GkmSecretItem *self = GKM_SECRET_ITEM (obj); if (!self->collection) return TRUE; return gkm_secret_object_is_locked (GKM_SECRET_OBJECT (self->collection), session); }
static void begin_set_fields (GkmSecretItem *self, GkmTransaction *transaction, GHashTable *fields) { g_assert (GKM_IS_SECRET_OBJECT (self)); g_assert (!gkm_transaction_get_failed (transaction)); gkm_secret_object_begin_modified (GKM_SECRET_OBJECT (self), transaction); gkm_transaction_add (transaction, self, complete_set_fields, self->fields); self->fields = fields; }
static CK_RV gkm_secret_item_real_get_attribute (GkmObject *base, GkmSession *session, CK_ATTRIBUTE_PTR attr) { GkmSecretItem *self = GKM_SECRET_ITEM (base); GkmSecretData *sdata; const gchar *identifier; const guchar *secret; gsize n_secret = 0; CK_RV rv; g_return_val_if_fail (self->collection, CKR_GENERAL_ERROR); switch (attr->type) { case CKA_CLASS: return gkm_attribute_set_ulong (attr, CKO_SECRET_KEY); case CKA_VALUE: sdata = gkm_secret_collection_unlocked_use (self->collection, session); if (sdata == NULL) return CKR_USER_NOT_LOGGED_IN; identifier = gkm_secret_object_get_identifier (GKM_SECRET_OBJECT (self)); secret = gkm_secret_data_get_raw (sdata, identifier, &n_secret); rv = gkm_attribute_set_data (attr, secret, n_secret); gkm_object_mark_used (base); g_object_unref (sdata); return rv; case CKA_G_COLLECTION: g_return_val_if_fail (self->collection, CKR_GENERAL_ERROR); identifier = gkm_secret_object_get_identifier (GKM_SECRET_OBJECT (self->collection)); return gkm_attribute_set_string (attr, identifier); case CKA_G_FIELDS: if (!self->fields) return gkm_attribute_set_data (attr, NULL, 0); return gkm_secret_fields_serialize (attr, self->fields, self->schema); case CKA_G_SCHEMA: return gkm_attribute_set_string (attr, self->schema); } return GKM_OBJECT_CLASS (gkm_secret_item_parent_class)->get_attribute (base, session, attr); }
static gboolean generate_encrypted_data (EggBuffer *buffer, GkmSecretCollection *collection, GkmSecretData *data) { GkmSecretObject *obj; GkmSecretItem *item; GList *items, *l; GHashTable *attributes; const gchar *label; GkmSecret *secret; GList *acl; int i; g_assert (buffer); g_assert (GKM_IS_SECRET_COLLECTION (collection)); g_assert (GKM_IS_SECRET_DATA (data)); /* Make sure we're using non-pageable memory */ egg_buffer_set_allocator (buffer, egg_secure_realloc); items = gkm_secret_collection_get_items (collection); for (l = items; l && !egg_buffer_has_error(buffer); l = g_list_next (l)) { item = GKM_SECRET_ITEM (l->data); obj = GKM_SECRET_OBJECT (l->data); label = gkm_secret_object_get_label (obj); buffer_add_utf8_string (buffer, label); secret = gkm_secret_data_get_secret (data, gkm_secret_object_get_identifier (obj)); buffer_add_secret (buffer, secret); if (!buffer_add_time (buffer, gkm_secret_object_get_created (obj)) || !buffer_add_time (buffer, gkm_secret_object_get_modified (obj))) break; /* reserved: */ if (!buffer_add_utf8_string (buffer, NULL)) break; for (i = 0; i < 4; i++) egg_buffer_add_uint32 (buffer, 0); attributes = gkm_secret_item_get_fields (item); if (!buffer_add_attributes (buffer, attributes, FALSE)) break; acl = g_object_get_data (G_OBJECT (item), "compat-acl"); if (!generate_acl_data (buffer, acl)) break; } g_list_free (items); /* Iteration completed prematurely == fail */ return (l == NULL); }
static GObject* gkm_secret_object_constructor (GType type, guint n_props, GObjectConstructParam *props) { GkmSecretObject *self = GKM_SECRET_OBJECT (G_OBJECT_CLASS (gkm_secret_object_parent_class)->constructor(type, n_props, props)); g_return_val_if_fail (self, NULL); /* Must be created with an identifier */ g_return_val_if_fail (self->pv->identifier, NULL); return G_OBJECT (self); }
static gboolean complete_set_secret (GkmTransaction *transaction, GObject *obj, gpointer user_data) { GkmSecretItem *self = GKM_SECRET_ITEM (obj); if (!gkm_transaction_get_failed (transaction)) { gkm_object_notify_attribute (GKM_OBJECT (obj), CKA_VALUE); gkm_secret_object_was_modified (GKM_SECRET_OBJECT (self)); } return TRUE; }
static void begin_set_schema (GkmSecretItem *self, GkmTransaction *transaction, gchar *schema) { g_assert (GKM_IS_SECRET_OBJECT (self)); g_assert (!gkm_transaction_get_failed (transaction)); if (self->schema != schema) { gkm_secret_object_begin_modified (GKM_SECRET_OBJECT (self), transaction); gkm_transaction_add (transaction, self, complete_set_schema, self->schema); self->schema = schema; } }
static gboolean complete_set_schema (GkmTransaction *transaction, GObject *obj, gpointer user_data) { GkmSecretItem *self = GKM_SECRET_ITEM (obj); gchar *old_schema = user_data; if (gkm_transaction_get_failed (transaction)) { g_free (self->schema); self->schema = old_schema; } else { gkm_object_notify_attribute (GKM_OBJECT (obj), CKA_G_SCHEMA); g_object_notify (G_OBJECT (obj), "schema"); gkm_secret_object_was_modified (GKM_SECRET_OBJECT (self)); g_free (old_schema); } return TRUE; }
static gboolean complete_set_label (GkmTransaction *transaction, GObject *obj, gpointer user_data) { GkmSecretObject *self = GKM_SECRET_OBJECT (obj); gchar *old_label = user_data; if (gkm_transaction_get_failed (transaction)) { g_free (self->pv->label); self->pv->label = old_label; } else { gkm_object_notify_attribute (GKM_OBJECT (obj), CKA_LABEL); g_object_notify (G_OBJECT (obj), "label"); gkm_secret_object_was_modified (self); g_free (old_label); } return TRUE; }
static void gkm_secret_object_finalize (GObject *obj) { GkmSecretObjectClass *klass = GKM_SECRET_OBJECT_GET_CLASS (obj); GkmSecretObject *self = GKM_SECRET_OBJECT (obj); if (self->pv->identifier) unregister_identifier (klass, self->pv->identifier); self->pv->identifier = NULL; g_free (self->pv->label); self->pv->label = NULL; self->pv->created = 0; self->pv->modified = 0; G_OBJECT_CLASS (gkm_secret_object_parent_class)->finalize (obj); }
static gboolean complete_set_fields (GkmTransaction *transaction, GObject *obj, gpointer user_data) { GkmSecretItem *self = GKM_SECRET_ITEM (obj); GHashTable *old_fields = user_data; if (gkm_transaction_get_failed (transaction)) { if (self->fields) g_hash_table_unref (self->fields); self->fields = old_fields; } else { gkm_object_notify_attribute (GKM_OBJECT (obj), CKA_G_FIELDS); g_object_notify (G_OBJECT (obj), "fields"); gkm_secret_object_was_modified (GKM_SECRET_OBJECT (self)); if (old_fields) g_hash_table_unref (old_fields); } return TRUE; }
static void gkm_secret_object_get_property (GObject *obj, guint prop_id, GValue *value, GParamSpec *pspec) { GkmSecretObject *self = GKM_SECRET_OBJECT (obj); switch (prop_id) { case PROP_LABEL: g_value_set_string (value, gkm_secret_object_get_label (self)); break; case PROP_IDENTIFIER: g_value_set_string (value, gkm_secret_object_get_identifier (self)); break; case PROP_CREATED: g_value_set_long (value, gkm_secret_object_get_created (self)); break; case PROP_MODIFIED: g_value_set_long (value, gkm_secret_object_get_modified (self)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, prop_id, pspec); break; } }
GkmDataResult gkm_secret_binary_read (GkmSecretCollection *collection, GkmSecretData *sdata, gconstpointer data, gsize n_data) { gsize offset; guchar major, minor, crypto, hash; guint32 flags; guint32 lock_timeout; time_t mtime, ctime; char *display_name; guint32 tmp; guint32 num_items; guint32 crypto_size; guint32 hash_iterations; guchar salt[8]; ItemInfo *items; GkmSecret* master; GkmSecretObject *obj; EggBuffer to_decrypt = EGG_BUFFER_EMPTY; GkmDataResult res = GKM_DATA_FAILURE; GHashTable *checks = NULL; GkmSecretItem *item; EggBuffer buffer; GList *l, *iteml; int i; display_name = NULL; items = 0; obj = GKM_SECRET_OBJECT (collection); /* The buffer we read from */ egg_buffer_init_static (&buffer, data, n_data); if (buffer.len < KEYRING_FILE_HEADER_LEN || memcmp (buffer.buf, KEYRING_FILE_HEADER, KEYRING_FILE_HEADER_LEN) != 0) { egg_buffer_uninit (&buffer); return GKM_DATA_UNRECOGNIZED; } offset = KEYRING_FILE_HEADER_LEN; major = buffer.buf[offset++]; minor = buffer.buf[offset++]; crypto = buffer.buf[offset++]; hash = buffer.buf[offset++]; if (major != 0 || minor != 0 || crypto != 0 || hash != 0) { egg_buffer_uninit (&buffer); return GKM_DATA_UNRECOGNIZED; } if (!buffer_get_utf8_string (&buffer, offset, &offset, &display_name) || !buffer_get_time (&buffer, offset, &offset, &ctime) || !buffer_get_time (&buffer, offset, &offset, &mtime) || !egg_buffer_get_uint32 (&buffer, offset, &offset, &flags) || !egg_buffer_get_uint32 (&buffer, offset, &offset, &lock_timeout) || !egg_buffer_get_uint32 (&buffer, offset, &offset, &hash_iterations) || !buffer_get_bytes (&buffer, offset, &offset, salt, 8)) goto bail; for (i = 0; i < 4; i++) { if (!egg_buffer_get_uint32 (&buffer, offset, &offset, &tmp)) goto bail; } if (!egg_buffer_get_uint32 (&buffer, offset, &offset, &num_items)) goto bail; items = g_new0 (ItemInfo, num_items + 1); /* Hashed data, without secrets */ if (!read_hashed_item_info (&buffer, &offset, items, num_items)) goto bail; if (!egg_buffer_get_uint32 (&buffer, offset, &offset, &crypto_size)) goto bail; /* Make the crypted part is the right size */ if (crypto_size % 16 != 0) goto bail; /* Ensure the file is large enough to hold all the data (in case it got truncated) */ if (buffer.len < offset + crypto_size) goto bail; /* Copy the data into to_decrypt into non-pageable memory */ egg_buffer_set_allocator (&to_decrypt, egg_secure_realloc); egg_buffer_reserve (&to_decrypt, crypto_size); memcpy (to_decrypt.buf, buffer.buf + offset, crypto_size); to_decrypt.len = crypto_size; if (sdata != NULL) { master = gkm_secret_data_get_master (sdata); if (!decrypt_buffer (&to_decrypt, master, salt, hash_iterations)) goto bail; if (!verify_decrypted_buffer (&to_decrypt)) { res = GKM_DATA_LOCKED; goto bail; } else { offset = 16; /* Skip hash */ if (!read_full_item_info (&to_decrypt, &offset, items, num_items)) goto bail; } } /* Correctly read all data, possibly including the decrypted data. * Now update the keyring and items: */ gkm_secret_object_set_label (obj, display_name); gkm_secret_object_set_modified (obj, mtime); gkm_secret_object_set_created (obj, ctime); if (flags & LOCK_ON_IDLE_FLAG) gkm_secret_collection_set_lock_idle (collection, lock_timeout); else if (flags & LOCK_AFTER_FLAG) gkm_secret_collection_set_lock_after (collection, lock_timeout); /* Build a Hash table where we can track ids we haven't yet seen */ checks = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); iteml = gkm_secret_collection_get_items (collection); for (l = iteml; l; l = g_list_next (l)) g_hash_table_insert (checks, g_strdup (gkm_secret_object_get_identifier (l->data)), "unused"); g_list_free (iteml); for (i = 0; i < num_items; i++) { /* We've seen this id */ g_hash_table_remove (checks, items[i].identifier); item = gkm_secret_collection_get_item (collection, items[i].identifier); if (item == NULL) item = gkm_secret_collection_new_item (collection, items[i].identifier); setup_item_from_info (item, sdata, &items[i]); } g_hash_table_foreach (checks, remove_unavailable_item, collection); res = GKM_DATA_SUCCESS; bail: egg_buffer_uninit (&to_decrypt); if (checks) g_hash_table_destroy (checks); g_free (display_name); for (i = 0; items && i < num_items; i++) free_item_info (&items[i]); g_free (items); return res; }
GkmDataResult gkm_secret_binary_write (GkmSecretCollection *collection, GkmSecretData *sdata, gpointer *data, gsize *n_data) { GkmSecretObject *obj; EggBuffer to_encrypt; GkmSecret *master; guchar digest[16]; EggBuffer buffer; gint hash_iterations; gint lock_timeout; guchar salt[8]; guint flags = 0; int i; g_return_val_if_fail (GKM_IS_SECRET_COLLECTION (collection), GKM_DATA_FAILURE); g_return_val_if_fail (GKM_IS_SECRET_DATA (sdata), GKM_DATA_LOCKED); g_return_val_if_fail (data && n_data, GKM_DATA_FAILURE); g_return_val_if_fail (gcry_md_get_algo_dlen (GCRY_MD_MD5) == sizeof (digest), GKM_DATA_FAILURE); obj = GKM_SECRET_OBJECT (collection); egg_buffer_init_full (&buffer, 256, g_realloc); /* Prepare the keyring for encryption */ hash_iterations = g_random_int_range (1000, 4096); gcry_create_nonce (salt, sizeof (salt)); egg_buffer_append (&buffer, (guchar*)KEYRING_FILE_HEADER, KEYRING_FILE_HEADER_LEN); egg_buffer_add_byte (&buffer, 0); /* Major version */ egg_buffer_add_byte (&buffer, 0); /* Minor version */ egg_buffer_add_byte (&buffer, 0); /* crypto (0 == AES) */ egg_buffer_add_byte (&buffer, 0); /* hash (0 == MD5) */ buffer_add_utf8_string (&buffer, gkm_secret_object_get_label (obj)); buffer_add_time (&buffer, gkm_secret_object_get_modified (obj)); buffer_add_time (&buffer, gkm_secret_object_get_created (obj)); lock_timeout = gkm_secret_collection_get_lock_idle (collection); if (lock_timeout) { flags |= LOCK_ON_IDLE_FLAG; } else { lock_timeout = gkm_secret_collection_get_lock_after (collection); if (lock_timeout) flags |= LOCK_AFTER_FLAG; } egg_buffer_add_uint32 (&buffer, flags); egg_buffer_add_uint32 (&buffer, lock_timeout); egg_buffer_add_uint32 (&buffer, hash_iterations); egg_buffer_append (&buffer, salt, 8); /* Reserved: */ for (i = 0; i < 4; i++) egg_buffer_add_uint32 (&buffer, 0); /* Hashed items: */ generate_hashed_items (collection, &buffer); /* Encrypted data. Use non-pageable memory */ egg_buffer_init_full (&to_encrypt, 4096, egg_secure_realloc); egg_buffer_append (&to_encrypt, (guchar*)digest, 16); /* Space for hash */ if (!generate_encrypted_data (&to_encrypt, collection, sdata)) { egg_buffer_uninit (&to_encrypt); egg_buffer_uninit (&buffer); return GKM_DATA_FAILURE; } /* Pad with zeros to multiple of 16 bytes */ while (to_encrypt.len % 16 != 0) egg_buffer_add_byte (&to_encrypt, 0); gcry_md_hash_buffer (GCRY_MD_MD5, (void*)digest, (guchar*)to_encrypt.buf + 16, to_encrypt.len - 16); memcpy (to_encrypt.buf, digest, 16); /* If no master password is set, we shouldn't be writing binary... */ master = gkm_secret_data_get_master (sdata); g_return_val_if_fail (master, GKM_DATA_FAILURE); if (!encrypt_buffer (&to_encrypt, master, salt, hash_iterations)) { egg_buffer_uninit (&buffer); egg_buffer_uninit (&to_encrypt); return GKM_DATA_FAILURE; } if (egg_buffer_has_error (&to_encrypt) || egg_buffer_has_error (&buffer)) { egg_buffer_uninit (&buffer); egg_buffer_uninit (&to_encrypt); return GKM_DATA_FAILURE; } egg_buffer_add_uint32 (&buffer, to_encrypt.len); egg_buffer_append (&buffer, to_encrypt.buf, to_encrypt.len); egg_buffer_uninit (&to_encrypt); *data = egg_buffer_uninit_steal (&buffer, n_data); return GKM_DATA_SUCCESS; }