[prev in list] [next in list] [prev in thread] [next in thread]
List: kde-commits
Subject: [KSecretService] 6c02d6f: Add the new backend for so-called .ksecret
From: Michael Leupold <lemma () confuego ! org>
Date: 2010-11-09 19:14:25
Message-ID: 20101109191425.8AAFBA60D7 () git ! kde ! org
[Download RAW message or body]
A backend/ksecret/ksecretitem.h [License: GPL(v2)]
A backend/ksecret/ksecretitem.cpp [License: GPL(v2)]
A backend/ksecret/ksecretfile.h [License: GPL(v2)]
A backend/ksecret/ksecretfile.cpp [License: GPL(v2)]
A backend/ksecret/ksecretcollectionmanager.h [License: GPL(v2)]
A backend/ksecret/ksecretcollectionmanager.cpp [License: GPL(v2)]
A backend/ksecret/ksecretcollection.h [License: GPL(v2)]
A backend/ksecret/ksecretcollection.cpp [License: GPL(v2)]
A backend/ksecret/FORMAT [License: UNKNOWN]
commit 6c02d6fde7a22b322599696076378dc9305dda42
Author: Michael Leupold <lemma@confuego.org>
Date: Sun Jul 11 14:57:55 2010 +0000
Add the new backend for so-called .ksecret files. Untested, not very pretty, but \
it compiles. Need unit-tests soon.
svn path=/trunk/playground/base/ksecretservice/; revision=1148680
diff --git a/CMakeLists.txt b/CMakeLists.txt
index c140252..e09c3c4 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -25,6 +25,10 @@ SET (ksecretservice_backend_SRCS
backend/temporarycollectionmanager.cpp
backend/temporaryitem.cpp
backend/securebuffer.cpp
+ backend/ksecret/ksecretfile.cpp
+ backend/ksecret/ksecretcollectionmanager.cpp
+ backend/ksecret/ksecretcollection.cpp
+ backend/ksecret/ksecretitem.cpp
)
KDE4_ADD_LIBRARY (ksecretservicebackend STATIC ${ksecretservice_backend_SRCS})
diff --git a/backend/ksecret/FORMAT b/backend/ksecret/FORMAT
new file mode 100644
index 0000000..0dbddce
--- /dev/null
+++ b/backend/ksecret/FORMAT
@@ -0,0 +1,252 @@
+This document describes the ksecret format for storing ksecretservice items and \
collection. +
+The ksecret format is a binary format. TODO
+
+
+Basic types
+===========
+
+BYTE
+ an 8-bit byte
+
+UINT
+ a 32-bit unsigned integer stored in big endian format
+
+DATETIME
+ a 64-bit unsigned integer stored in big endian format
+
+BYTEARRAY = UINT *BYTE
+ a byte-array is stored as its length followed by each of the bytes
+
+STRING = BYTEARRAY
+ a string is stored as a bytearray using UTF-8 encoding
+
+
+ksecret file
+============
+
+ksecret = header *part
+
+
+file header
+===========
+
+The file header contains some magic to identify this file as a ksecret file, the
+version number, the algorithms used and a lookup table for the other parts of the
+file.
+The parts are basically containers which can be interpreted on their own. If a
+program reading a ksecret file can't handle a certain part, it should ignore it
+and one saving rewrite it as it was.
+When adding new features that make it impossible for an older version of \
ksecretservice +to interpret the file's contents, version-major has to be increased. \
Like this +forward- and backward-compatibility among the same major version can be \
sustained. +
+
+header = magic version algorithms coll-props part-table
+
+magic = "KSECRET\n\r\0\r\n" ;; Magic to identify the file \
format +
+version = version-major version-minor
+
+ version-major = UINT ;; Major version of the file \
format +
+ version-minor = UINT ;; Minor version of the file \
format +
+algorithms = algo-hash algo-encrypt
+
+ algo-hash = UINT ;; Identifier for the hash \
algorithm +
+ algo-encrypt = UINT ;; Identifer for the sym. \
encryption algorithm +
+part-table = num-parts *part-desc
+
+ num-parts = UINT ;; number of data parts in \
this file +
+part-desc = part-type part-pos part-length ;; describes a part of the \
file +
+ part-type = UINT ;; describes the type of the \
part +
+ part-pos = UINT ;; describes the part's file \
position +
+ part-length = UINT ;; describes the part's length
+
+
+parts
+=====
+
+Parts come in various flavours, including a part for hashes for item attribute \
lookup, +a part containing the encrypted, symmetric keys as well as parts for the \
actual encrypted +data. Parts can be identified using the part-table. Each part's \
semantics is defined by +its type.
+
+
+part = part-item-hashes / part-symkey / encrypted-part / mac-part
+
+
+Item hashes
+===========
+
+Each item's attributes are hashed using algo-hash and stored so the collection can \
be +searched even without being decrypted. An attribute hash (hash-attrib) is derived \
by +concatenating the property key with the property value and creating this string's \
hash using +algo-hash.
+
+
+part-item-hashes = num-items *item-hash
+
+ num-items = UINT ;; number of items inside this \
part +
+item-hash = item-id num-attribs *hash-attrib
+
+ item-id = STRING ;; unique item identifier
+
+ num-attribs = UINT ;; number of attributes this \
item hash +
+ hash-attrib = BYTEARRAY ;; attribute hash
+
+
+Symmetric Keys
+==============
+
+All of the encrypted parts are encrypted using a symmetric key (master key). However \
contrary +to kwallet this key is not derived directly from a passphrase. Instead the \
master key +is created from random data and encrypted itself using different methods, \
eg. +using symmetric or assymmetric encryption (hash from passphrase, smartcard, \
possibly +fingerprint). As several methods to encrypt the master key exist, it could \
get stored inside +a ksecret file several times. The application is responsible for \
making sure only valid +encrypted keys are contained withing the ksecret file.
+
+enc-symkey should also contain a method to verify if the master key was derived \
successfully, +eg. a hash of the key.
+
+// TODO: add a method to figure out if decrypting a key worked (ie. add
+// a hash of the key after enc-symkey.
+
+part-symkey = key-type enc-symkey
+
+ key-type = UINT ;; method for encrypting the \
key +
+ enc-symkey = BYTEARRAY ;; the encrypted symmetric \
master key +
+
+Encrypted parts
+===============
+
+part-items is an encrypted part. The structure described below applies to the \
structure of +the data AFTER it has been decrypted using the key with the encryption \
algorithm (algo-encrypt). +To verify if the master key used for decryption was the \
right one, each encrypted part also +contains a hash of the decrypted data to \
validate with. The algorithm used to create the hash is +algo-hash.
+
+Contrary to the other representation, ENCRYPT{} is meant to be the result of the \
encryption +function stored as a BYTEARRAY.
+
+encrypted-part = init-vector ENCRYPT{ part-to-encrypt }
+
+ init-vector = BYTEARRAY ;; initialization-vector used \
for encryption +
+ part-to-encrypt = part-items
+
+
+Authenticated parts
+===================
+
+Configuration values and ACLs get signed (authenticated) by creating a message \
authentication code +using the master key. Like this any tampering can be detected \
once the master-key is decrypted. +
+Contrary to the other representation, MAC{} is meant to be the message \
authentication code +generated from the data enclosed using the master-key which is \
stored as a BYTEARRAY. +
+mac-part = auth-size part-to-auth MAC{ part-to-auth }
+
+ auth-size = UINT ;; length of the data the mac \
is created for +
+ part-to-auth = part-config / part-acls / part-collprops
+
+
+Collection properties
+=====================
+
+Collection properties are stored in a signed part so tampering with them
+can be detected.
+
+part-collprops = coll-id coll-label coll-created coll-modified
+
+ coll-id = STRING ;; unique collection \
identifier +
+ coll-label = STRING ;; The name of this collection
+
+ coll-created = DATETIME ;; The creation time of this \
collection +
+ coll-modified = DATETIME ;; The time this collection \
was last modified +
+
+Configuration
+=============
+
+Collection-specific configuration values are stored directly inside the ksecret \
file. Like +this security-related configuration changes can be protected from being \
changed without +authentication.
+
+
+part-config = num-cfg-values *config-item
+
+ num-cfg-values = UINT ;; number of config-values \
stored +
+ config-item = config-key config-value
+
+ config-key = STRING ;; the key of a configuration \
item +
+ config-value = BYTEARRAY ;; the value of a \
configuration item +
+
+ACLs
+====
+
+acls = num-acls *acl-item ;; acl part
+
+ num-acls = UINT ;; number of entries in the \
acl +
+ acl-item = acl-path acl-value
+
+ acl-path = STRING ;; path of the application
+
+ acl-value = UINT ;; access of the application
+
+
+Items
+=====
+
+part-items = num-items *item
+
+item = item-id item-size item-label item-created item-modified \
attributes secret +
+ item-size = UINT ;; byte length of the \
remainder of the item +
+ item-label = STRING ;; human-readable label of \
this item +
+ item-created = DATETIME ;; time the item was created
+
+ item-modified = DATETIME ;; time the item was last \
modified +
+ secret = STRING ;; the actual secret
+
+attributes = num-attribs *attrib
+
+attrib = attrib-key attrib-value
+
+ attrib-key = STRING ;; attribute key
+
+ attrib-value = STRING ;; attribute value
+
+
+ACLs
+====
+
+ACLs are stored verbatim and authenticated so they can only be modified while the \
configuration is +unlocked.
+
+TODO
+
+
+Michael Leupold <lemma@confuego.org>
diff --git a/backend/ksecret/ksecretcollection.cpp \
b/backend/ksecret/ksecretcollection.cpp new file mode 100644
index 0000000..671a7b7
--- /dev/null
+++ b/backend/ksecret/ksecretcollection.cpp
@@ -0,0 +1,1168 @@
+/*
+ * Copyright 2010, Michael Leupold <lemma@confuego.org>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License or (at your option) version 3 or any later version
+ * accepted by the membership of KDE e.V. (or its successor approved
+ * by the membership of KDE e.V.), which shall act as a proxy
+ * defined in Section 14 of version 3 of the license.
+ *
+ * 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "ksecretcollection.h"
+#include "ksecretcollectionmanager.h"
+#include "ksecretfile.h"
+#include "../securebuffer.h"
+#include "secrettool.h"
+
+#include <QtCore/QBuffer>
+#include <QtCore/QFile>
+#include <QtCore/QSet>
+
+#include <klocalizedstring.h>
+#include <ksavefile.h>
+
+static const QString genericLoadingErrorMessage()
+{
+ return i18nc("Error message: Generic error loading the ksecret file",
+ "There was an error reading the ksecret file.");
+}
+
+static const QString genericSavingErrorMessage()
+{
+ return i18nc("Error message: Generic error saving the ksecret file",
+ "There was an error writing the ksecret file.");
+}
+
+KSecretCollection *KSecretCollection::create(const QString &id, \
BackendCollectionManager *parent, + \
QString &errorMessage) +{
+ KSecretCollection *coll = new KSecretCollection(parent);
+ coll->m_id = id;
+ coll->m_algoHash = KSecretFile::SHA256;
+ coll->m_algoCipher = KSecretFile::AES256;
+
+ // initialize default encryption algorithms
+ if (!coll->setupAlgorithms(errorMessage)) {
+ delete coll;
+ return 0;
+ }
+
+ // create a new symmetric key
+ // TODO: is minimum() right in all cases?
+ coll->m_symmetricKey = new \
QCA::SymmetricKey(coll->m_cipher->keyLength().minimum()); +
+ // FIXME: this is bogus. Actually the unlocking method needs to be chosen.
+ // here we just use a bogus key to encrypt the symmetric key using
+ // blowfish.
+ QCA::SymmetricKey *keyUnlockKey = new \
QCA::SymmetricKey(QByteArray("12345678901234567890")); + EncryptedKey *key = new \
EncryptedKey; + key->m_type = KSecretFile::KeyBogus;
+ QCA::Cipher keyCipher("blowfish", QCA::Cipher::CBC);
+ keyCipher.setup(QCA::Encode, *keyUnlockKey);
+ key->m_key.append(keyCipher.update(*coll->m_symmetricKey).toByteArray());
+ key->m_key.append(keyCipher.final().toByteArray());
+ coll->m_encryptedSymKeys.append(key);
+}
+
+KSecretCollection::KSecretCollection(BackendCollectionManager *parent)
+ : BackendCollection(parent), m_hash(0), m_cipher(0),
+ m_symmetricKey(0)
+{
+}
+
+KSecretCollection::~KSecretCollection()
+{
+ // TODO: delete items?
+ delete m_hash;
+ delete m_cipher;
+ delete m_mac;
+ delete m_symmetricKey;
+ qDeleteAll(m_unknownParts);
+ qDeleteAll(m_encryptedSymKeys);
+ // TODO: make sure there's nothing else to delete
+}
+
+QString KSecretCollection::id() const
+{
+ return m_id;
+}
+
+BackendReturn<QString> KSecretCollection::label() const
+{
+ return m_label;
+}
+
+BackendReturn<void> KSecretCollection::setLabel(const QString &label)
+{
+ // label can only be set if the collection is unlocked
+ if (isLocked()) {
+ return ErrorIsLocked;
+ }
+
+ m_label = label;
+ return NoError;
+}
+
+QDateTime KSecretCollection::created() const
+{
+ return m_created;
+}
+
+QDateTime KSecretCollection::modified() const
+{
+ return m_modified;
+}
+
+bool KSecretCollection::isLocked() const
+{
+ // a collection is unlocked if m_cipher is initialized
+ return (m_symmetricKey != 0);
+}
+
+BackendReturn<QList<BackendItem*> > KSecretCollection::items() const
+{
+ QList<BackendItem*> itemList;
+ QHash<QString, KSecretItem*>::const_iterator it = m_items.constBegin();
+ QHash<QString, KSecretItem*>::const_iterator end = m_items.constEnd();
+ for ( ; it != end; ++it) {
+ itemList.append(it.value());
+ }
+
+ return itemList;
+}
+
+bool KSecretCollection::isCallImmediate(AsyncCall::AsyncType type) const
+{
+ switch (type) {
+
+ case AsyncCall::AsyncUnlock:
+ // unlock is immediate if the collection is already unlocked
+ return !isLocked();
+
+ case AsyncCall::AsyncChangeAuthentication:
+ // changing the authentication is never immediate
+ return false;
+
+ default:
+ // all other calls are immediate
+ return true;
+ }
+}
+
+BackendReturn<QList<BackendItem*> > KSecretCollection::searchItems(
+ const QMap<QString, QString> &attributes) const
+{
+ QList<BackendItem*> itemList;
+ // create hashes for each of the attributes queried
+ QSet<QByteArray> attributeHashes = KSecretItem::createHashes(attributes, m_hash);
+ if (attributeHashes.isEmpty()) {
+ return items();
+ } else {
+ // use the first attribute hash to build an initial list of items matching
+ QHash<QByteArray, KSecretItem*>::const_iterator resit =
+ m_itemHashes.constFind(*attributeHashes.constBegin());
+ QHash<QByteArray, KSecretItem*>::const_iterator resend = \
m_itemHashes.constEnd(); + // now check which of the items in resit match the \
remainder of the attributes + // to do this the actual attributes are used \
instead of the hashes + for ( ; resit != resend; ++resit) {
+ if (resit.value()->matches(attributes)) {
+ itemList.append(resit.value());
+ }
+ }
+ return itemList;
+ }
+}
+
+BackendReturn<bool> KSecretCollection::unlock()
+{
+ if (!isLocked()) {
+ return true;
+ } else {
+ // TODO: make it actually unlock, needs UI
+
+ // TODO: check authenticated parts
+
+ // TODO: check if the key is actually correct
+
+ // FIXME: this is bogus
+
+ // create a key to unlock the actual symmetric key
+ m_symmetricKey = new QCA::SymmetricKey;
+ QCA::SymmetricKey *keyUnlockKey = new \
QCA::SymmetricKey(QByteArray("12345678901234567890")); + Q_FOREACH(EncryptedKey \
*key, m_encryptedSymKeys) { + if (key->m_type == KSecretFile::KeyBogus) {
+ QCA::Cipher keyCipher("blowfish", QCA::Cipher::CBC);
+ keyCipher.setup(QCA::Decode, *keyUnlockKey);
+ *m_symmetricKey += QCA::SecureArray(keyCipher.update(key->m_key));
+ *m_symmetricKey += QCA::SecureArray(keyCipher.final());
+ break;
+ }
+ }
+
+ Q_ASSERT(m_symmetricKey);
+
+ QCA::SecureArray decryptedPart;
+ Q_FOREACH(const QByteArray &partContents, m_encryptedItemParts) {
+ if (!deserializePartEncrypted(partContents, decryptedPart)) {
+ return false;
+ }
+
+ // deserialize the items inside the decrypted part
+ SecureBuffer device(&decryptedPart);
+ KSecretFile itemFile(&device, KSecretFile::Read);
+ if (!deserializeItemsUnlocked(itemFile)) {
+ return false;
+ }
+ }
+ return false;
+ }
+}
+
+BackendReturn<bool> KSecretCollection::lock()
+{
+ // TODO: reference/open counting?
+
+ // TODO: emit signals?
+
+ if (isLocked()) {
+ return true;
+ } else {
+ // TODO: only serialize if dirty
+ QString errorMessage;
+ if (!serialize(errorMessage)) {
+ return BackendReturn<bool>(false, ErrorOther, errorMessage);
+ }
+
+ // then remove the key
+ delete m_hash;
+ m_hash = 0;
+ delete m_cipher;
+ m_cipher = 0;
+
+ // remove individual item secrets
+ QHash<QString, KSecretItem*>::iterator it = m_items.begin();
+ QHash<QString, KSecretItem*>::iterator end = m_items.end();
+ for ( ; it != end; ++it) {
+ // FIXME: it.value()->removeSecrets();
+ }
+
+ return true;
+ }
+}
+
+BackendReturn<bool> KSecretCollection::deleteCollection()
+{
+ // remove the ksecret file
+ if (!QFile::remove(m_path)) {
+ return false;
+ }
+
+ // emit signals and actually delete
+ emit collectionDeleted(this);
+ deleteLater();
+ return true;
+}
+
+BackendReturn<BackendItem*> KSecretCollection::createItem(const QString &label,
+ const QMap<QString, \
QString> &attributes, + \
const QCA::SecureArray &secret, + \
bool replace, bool locked) +{
+ // TODO: use locked argument
+ Q_UNUSED(locked);
+
+ // only works if unlocked
+ if (isLocked()) {
+ return BackendReturn<BackendItem*>(0, ErrorIsLocked);
+ } else {
+ KSecretItem *item = 0;
+ bool replacing = false;
+
+ // check for duplicates
+ BackendReturn<QList<BackendItem*> > foundItems = searchItems(attributes);
+ if (!foundItems.isError() && foundItems.value().size() > 0) {
+ QList<BackendItem*> oldlist = foundItems.value();
+ Q_FOREACH(BackendItem *olditem, oldlist) {
+ if (olditem->attributes().value() == attributes) {
+ if (replace) {
+ // replace an existing item
+ item = qobject_cast<KSecretItem*>(olditem);
+ replacing = true;
+ } else {
+ // item existing but should not be replaced
+ return BackendReturn<BackendItem*>(0, ErrorAlreadyExists);
+ }
+ break;
+ }
+ }
+ }
+
+ if (!item) {
+ item = new KSecretItem(createId(), this);
+ connect(item, SIGNAL(attributesChanged(KSecretItem*)),
+ SLOT(changeAttributeHashes(KSecretItem*)));
+ }
+ item->m_label = label;
+ item->m_attributes = attributes;
+ item->m_secret = secret;
+
+ // insert new item's hashes
+ changeAttributeHashes(item);
+
+ if (replacing) {
+ emit itemChanged(item);
+ } else {
+ m_items.insert(item->id(), item);
+ // new item, signals need to be wired
+ connect(item, SIGNAL(itemDeleted(BackendItem*)), \
SLOT(slotItemDeleted(BackendItem*))); + connect(item, \
SIGNAL(itemChanged(BackendItem*)), SIGNAL(itemChanged(BackendItem*))); + emit \
itemCreated(item); + }
+
+ return item;
+ }
+}
+
+BackendReturn<bool> KSecretCollection::changeAuthentication()
+{
+ // TODO: needs ui
+ return false;
+}
+
+const QString &KSecretCollection::path() const
+{
+ return m_path;
+}
+
+void KSecretCollection::slotItemDeleted(BackendItem *item)
+{
+ KSecretItem *kitem = qobject_cast<KSecretItem*>(item);
+ Q_ASSERT(kitem);
+
+ // remove the item as well as item hashes
+ if (m_reverseItemHashes.contains(kitem)) {
+ Q_FOREACH(const QByteArray &hash, m_reverseItemHashes.value(kitem)) {
+ m_itemHashes.remove(hash);
+ }
+ m_reverseItemHashes.remove(kitem);
+ }
+ m_items.remove(kitem->id());
+
+ emit itemDeleted(item);
+}
+
+void KSecretCollection::changeAttributeHashes(KSecretItem *item)
+{
+ Q_ASSERT(item);
+
+ // remove previous item hashes
+ if (m_reverseItemHashes.contains(item)) {
+ QSet<QByteArray> oldHashes = m_reverseItemHashes.value(item);
+ Q_FOREACH(const QByteArray &hash, oldHashes) {
+ m_itemHashes.remove(hash, item);
+ }
+ }
+
+ // insert new hashes
+ QSet<QByteArray> attributeHashes = item->createAttributeHashes(m_hash);
+ Q_FOREACH(const QByteArray &hash, attributeHashes) {
+ m_itemHashes.insert(hash, item);
+ }
+ m_reverseItemHashes.insert(item, attributeHashes);
+}
+
+KSecretCollection *KSecretCollection::deserialize(const QString &path,
+ KSecretCollectionManager *parent,
+ QString &errorMessage)
+{
+ QFile device(path);
+ KSecretFile file(&device, KSecretFile::Read);
+ if (!file.isValid()) {
+ errorMessage = i18nc("Error message: collection file to be opened doesn't \
exist", + "Collection does not exist.");
+ return 0;
+ }
+
+ KSecretCollection *coll = new KSecretCollection(parent);
+ coll->m_path = path;
+
+ if (!coll->deserializeHeader(file, errorMessage) ||
+ !coll->deserializeParts(file, errorMessage)) {
+ delete coll;
+ return 0;
+ }
+
+ return coll;
+}
+
+bool KSecretCollection::setupAlgorithms(QString &errorMessage)
+{
+ // figure out algorithms to use
+ switch (m_algoHash) {
+
+ case KSecretFile::SHA256:
+ if (!QCA::isSupported("sha256") || !QCA::isSupported("hmac(sha256))")) {
+ errorMessage = i18nc("Error message: unsupported hashing algorithm SHA256 \
used", + "The hashing algorithm SHA256 is not supported \
by your installation."); + return false;
+ }
+ m_hash = new QCA::Hash("sha256");
+ m_mac = new QCA::MessageAuthenticationCode("hmac(sha256))", \
QCA::SymmetricKey()); + break;
+
+ default:
+ errorMessage = i18nc("Error message: unknown hashing algorithm used",
+ "The file uses an unknown hashing algorithm.");
+ return false;
+ }
+
+ switch (m_algoCipher) {
+
+ case KSecretFile::AES256:
+ if (!QCA::isSupported("aes256")) {
+ errorMessage = i18nc("Error message: unsupported encryption algorithm \
AES256 used", + "The encryption algorithm AES256 is not \
suppored by your installation."); + return false;
+ }
+ m_cipher = new QCA::Cipher("aes256", QCA::Cipher::CBC);
+ break;
+
+ default:
+ errorMessage = i18nc("Error message: unknown encryption algorithm used",
+ "The file uses an unknown encryption algorithm.");
+ return false;
+ }
+
+ return true;
+}
+
+bool KSecretCollection::deserializeHeader(KSecretFile &file, QString &errorMessage)
+{
+ // magic, version numbers
+ quint32 versionMajor;
+ quint32 versionMinor;
+ if (!file.readMagic() || !file.readUint(&versionMajor) || \
!file.readUint(&versionMinor)) { + errorMessage = i18nc("Error message: \
collection has wrong file format or is corrupted", + \
"Collection is not a ksecret file or is corrupted."); + return false;
+ }
+
+ // check if version number matches something we understand
+ if (versionMajor > VERSION_MAJOR) {
+ errorMessage = i18nc("Error message: collection's file format is too recent",
+ "The file format used is too recent.");
+ return false;
+ }
+
+ // algorithms
+ if (!file.readUint(&m_algoHash) || !file.readUint(&m_algoCipher)) {
+ errorMessage = genericLoadingErrorMessage();
+ return false;
+ }
+ if (!setupAlgorithms(errorMessage)) {
+ return false;
+ }
+
+ return true;
+}
+
+bool KSecretCollection::deserializeParts(KSecretFile &file, QString &errorMessage)
+{
+ // parts table
+ quint32 numParts;
+ if (!file.readUint(&numParts)) {
+ errorMessage = genericLoadingErrorMessage();
+ return false;
+ }
+ QList<FilePartEntry> filePartEntries;
+ quint32 partType;
+ quint32 partPos;
+ quint32 partLength;
+ for (quint32 i = 0; i < numParts; ++i) {
+ if (!file.readUint(&partType) || !file.readUint(&partPos) || \
!file.readUint(&partLength)) { + errorMessage = genericLoadingErrorMessage();
+ return false;
+ }
+
+ FilePartEntry part;
+ part.m_type = partType;
+ part.m_position = partPos;
+ part.m_length = partLength;
+ filePartEntries.append(part);
+ }
+
+ // read parts
+ Q_FOREACH(const FilePartEntry &filePartEntry, filePartEntries) {
+ // read the contents into a bytearray
+ QByteArray contents;
+ if (!file.readPart(&contents, filePartEntry.m_position, \
filePartEntry.m_length)) { + errorMessage = genericLoadingErrorMessage();
+ return false;
+ }
+
+ bool rc;
+ switch (filePartEntry.m_type) {
+
+ case KSecretFile::PartItemHashes:
+ rc = deserializePartItemHashes(contents);
+ break;
+
+ case KSecretFile::PartSymKey:
+ rc = deserializePartSymKey(contents);
+ break;
+
+ case KSecretFile::PartItems:
+ m_encryptedItemParts.append(contents);
+ rc = true;
+ break;
+
+ case KSecretFile::PartAcls:
+ rc = deserializePartAcls(contents);
+ break;
+
+ case KSecretFile::PartConfig:
+ rc = deserializePartConfig(contents);
+ break;
+
+ case KSecretFile::PartCollProps:
+ rc = deserializePartCollProps(contents);
+ break;
+
+ default: // unknown part type
+ UnknownFilePart *ufp = new UnknownFilePart;
+ ufp->m_type = filePartEntry.m_type;
+ ufp->m_contents = contents;
+ m_unknownParts.append(ufp);
+ rc = true;
+ break;
+ }
+
+ if (!rc) {
+ errorMessage = genericLoadingErrorMessage();
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool KSecretCollection::deserializePartCollProps(const QByteArray &partContents)
+{
+ QBuffer buffer;
+ buffer.setData(partContents);
+ KSecretFile file(&buffer, KSecretFile::Read);
+
+ if (!file.readString(&m_id) || !file.readString(&m_label) ||
+ !file.readDatetime(&m_created) || !file.readDatetime(&m_modified)) {
+ return false;
+ }
+
+ return true;
+}
+
+bool KSecretCollection::deserializePartItemHashes(const QByteArray &partContents)
+{
+ QBuffer buffer;
+ buffer.setData(partContents);
+ KSecretFile file(&buffer, KSecretFile::Read);
+ quint32 numItems;
+ if (!file.isValid() || !file.readUint(&numItems)) {
+ return false;
+ }
+
+ // read each item hash
+ KSecretItem *item;
+ for (quint32 i = 0; i < numItems; ++i) {
+ QString itemId;
+ quint32 numAttribs;
+ if (!file.readString(&itemId) || !file.readUint(&numAttribs)) {
+ return false;
+ }
+
+ // read each attribute
+ QSet<QByteArray> attributeHashes;
+ for (quint32 j = 0; j < numAttribs; ++j) {
+ QByteArray attributeHash;
+ if (!file.readBytearray(&attributeHash)) {
+ return false;
+ }
+ attributeHashes.insert(attributeHash);
+ }
+
+ item = new KSecretItem(itemId, this);
+ connect(item, SIGNAL(attributesChanged(KSecretItem*)),
+ SLOT(changeAttributeHashes(KSecretItem*)));
+ Q_FOREACH(const QByteArray &hash, attributeHashes) {
+ m_itemHashes.insert(hash, item);
+ }
+
+ // append hashes to the list of reverse hashes
+ m_reverseItemHashes.insert(item, attributeHashes);
+ }
+
+ return true;
+}
+
+bool KSecretCollection::deserializePartSymKey(const QByteArray &partContents)
+{
+ QBuffer buffer;
+ buffer.setData(partContents);
+ KSecretFile file(&buffer, KSecretFile::Read);
+ quint32 keyType;
+ QByteArray keyData;
+ if (!file.isValid() || !file.readUint(&keyType) || !file.readBytearray(&keyData)) \
{ + return false;
+ }
+ EncryptedKey *key = new EncryptedKey;
+ key->m_type = keyType;
+ key->m_key = keyData;
+ m_encryptedSymKeys.append(key);
+ return true;
+}
+
+bool KSecretCollection::deserializePartAcls(const QByteArray &partContents)
+{
+ QBuffer buffer;
+ buffer.setData(partContents);
+ KSecretFile file(&buffer, KSecretFile::Read);
+ if (!file.isValid()) {
+ return false;
+ }
+
+ QByteArray partAcls;
+ if (!file.readBytearray(&partAcls) || !file.readBytearray(&m_aclsMac)) {
+ return false;
+ }
+
+ QBuffer aclsBuffer(&partAcls);
+ KSecretFile aclsFile(&aclsBuffer, KSecretFile::Read);
+ quint32 numAcls;
+ if (!aclsFile.isValid() || !aclsFile.readUint(&numAcls)) {
+ return false;
+ }
+
+ QString path;
+ quint32 value;
+ ApplicationPermission perm;
+ for (quint32 i = 0; i < numAcls; ++i) {
+ if (!aclsFile.readString(&path) || !file.readUint(&value)) {
+ return false;
+ }
+
+ switch (value) {
+
+ case PermissionAsk:
+ perm = PermissionAsk;
+ break;
+
+ case PermissionDeny:
+ perm = PermissionDeny;
+ break;
+
+ case PermissionAllow:
+ perm = PermissionAllow;
+ break;
+
+ default:
+ // unknown acl rule
+ m_unknownAcls.insert(path, value);
+ continue;
+ }
+
+ m_acls.insert(path, perm);
+ }
+
+ return true;
+}
+
+bool KSecretCollection::deserializePartConfig(const QByteArray &partContents)
+{
+ QBuffer buffer;
+ buffer.setData(partContents);
+ KSecretFile file(&buffer, KSecretFile::Read);
+ if (!file.isValid()) {
+ return false;
+ }
+
+ QByteArray partConfig;
+ if (!file.readBytearray(&partConfig) || !file.readBytearray(&m_configValuesMac)) \
{ + return false;
+ }
+
+ QBuffer configBuffer(&partConfig);
+ KSecretFile configFile(&configBuffer, KSecretFile::Read);
+ quint32 numConfigValues;
+ if (!configFile.isValid() || !configFile.readUint(&numConfigValues)) {
+ return false;
+ }
+
+ QString configKey;
+ QByteArray configValue;
+ for (quint32 i = 0; i < numConfigValues; ++i) {
+ if (!configFile.readString(&configKey) || \
!configFile.readBytearray(&configValue)) { + return false;
+ }
+
+ if (configKey == QLatin1String("CloseScreensaver")) {
+ if (configValue.size() == 1 && configValue[0] == '0') {
+ m_cfgCloseScreensaver = false;
+ }
+ } else if (configKey == QLatin1String("CloseIfUnused")) {
+ if (configValue.size() == 1 && configValue[0] == '0') {
+ m_cfgCloseIfUnused = false;
+ }
+ } else if (configKey == QLatin1String("CloseUnusedTimeout")) {
+ bool ok;
+ m_cfgCloseUnusedTimeout = configValue.toUInt(&ok);
+ if (!ok) {
+ m_cfgCloseUnusedTimeout = 30;
+ }
+ } else {
+ m_cfgUnknownKeys.insert(configKey, configValue);
+ }
+ }
+
+ return true;
+}
+
+bool KSecretCollection::deserializeItemsUnlocked(KSecretFile &file)
+{
+ Q_ASSERT(file.isValid());
+
+ KSecretItem *item;
+ quint32 numItems;
+ if (!file.readUint(&numItems)) {
+ return false;
+ }
+
+ QString itemId;
+ for (quint32 i = 0; i < numItems; ++i) {
+ // read the identifier of the item
+ if (!file.readString(&itemId)) {
+ return false;
+ }
+
+ // find the item matching the identifier
+ bool recreateHashes = false;
+ if (!m_items.contains(itemId)) {
+ // item without a hash. ignore this inconsistency and simply
+ // load the item, creating the hashes on-the-go.
+ item = new KSecretItem(itemId, this);
+ connect(item, SIGNAL(attributesChanged(KSecretItem*)),
+ SLOT(changeAttributeHashes(KSecretItem*)));
+ m_items.insert(itemId, item);
+ recreateHashes = true;
+ } else {
+ item = m_items.value(itemId);
+ }
+
+ QCA::SecureArray itemData;
+ if (!file.readSecret(&itemData)) {
+ return false;
+ }
+
+ SecureBuffer device(&itemData);
+ KSecretFile itemFile(&device, KSecretFile::Read);
+ if (!item->deserializeUnlocked(itemFile)) {
+ return false;
+ }
+
+ if (recreateHashes) {
+ changeAttributeHashes(item);
+ }
+ }
+}
+
+bool KSecretCollection::deserializePartEncrypted(const QByteArray &partContents,
+ QCA::SecureArray &decryptedPart)
+{
+ Q_ASSERT(m_symmetricKey);
+
+ QBuffer buffer;
+ buffer.setData(partContents);
+ KSecretFile file(&buffer, KSecretFile::Read);
+ if (!file.isValid()) {
+ return false;
+ }
+
+ QCA::SecureArray ivdata;
+ if (!file.readSecret(&ivdata)) {
+ return false;
+ }
+ QCA::InitializationVector iv(ivdata);
+
+ QCA::SecureArray encryptedPart;
+ if (!file.readSecret(&encryptedPart)) {
+ return false;
+ }
+
+ // decrypt the data
+ m_cipher->setup(QCA::Decode, *m_symmetricKey, iv);
+ decryptedPart = m_cipher->update(encryptedPart);
+ if (!m_cipher->ok()) {
+ return false;
+ }
+ decryptedPart.append(m_cipher->final());
+ if (!m_cipher->ok()) {
+ return false;
+ }
+
+ return true;
+}
+
+bool KSecretCollection::serialize(QString &errorMessage) const
+{
+ KSaveFile device(m_path);
+ KSecretFile file(&device, KSecretFile::Write);
+ if (!file.isValid()) {
+ errorMessage = i18nc("Error message: collection file couldn't be opened for \
writing", + "Collection file couldn't be opened for \
writing"); + return false;
+ }
+
+ if (!serializeHeader(file) || !serializeParts(file)) {
+ errorMessage = genericSavingErrorMessage();
+ return false;
+ }
+
+ return true;
+}
+
+bool KSecretCollection::serializeHeader(KSecretFile &file) const
+{
+ // write the ksecret file header
+ if (!file.writeMagic() || !file.writeUint(VERSION_MAJOR) || \
!file.writeUint(VERSION_MINOR)) { + return false;
+ }
+
+ // algorithms
+ if (!file.writeUint(m_algoHash) && !file.writeUint(m_algoCipher)) {
+ return false;
+ }
+
+ return true;
+}
+
+bool KSecretCollection::serializeParts(KSecretFile &file) const
+{
+ QString errorMessage;
+ // TODO: only save if the collection is open.
+
+ // determine the number of parts
+ // item hash part + items part + acls part + config part + number of keys +
+ // number of unknown parts = 4 + number of keys + number of unknown parts
+ int curFilePartEntry = 0;
+ quint32 numParts = 4 + m_encryptedSymKeys.size() + m_unknownParts.size();
+ quint32 partTableSize = 4 + numParts * 12;
+ QList<FilePartEntry> filePartEntries;
+ for (quint32 i = 0; i < numParts; ++i) {
+ filePartEntries.append(FilePartEntry());
+ }
+ // remember the part table position and skip the part table so it can be written
+ // later.
+ quint64 partTablePos = file.pos();
+ if (!file.seek(partTablePos + partTableSize)) {
+ return false;
+ }
+
+ // collection properties part
+ filePartEntries[curFilePartEntry].m_type = KSecretFile::PartCollProps;
+ filePartEntries[curFilePartEntry].m_position = (quint32)file.pos();
+ if (!file.writeString(m_id) || !file.writeString(m_label) ||
+ !file.writeDatetime(m_created) || !file.writeDatetime(m_modified)) {
+ return false;
+ }
+ curFilePartEntry++;
+
+ // config part
+ if (!serializeConfigPart(file, filePartEntries[curFilePartEntry])) {
+ return false;
+ }
+ curFilePartEntry++;
+
+ // acls part
+ filePartEntries[curFilePartEntry].m_type = KSecretFile::PartAcls;
+ filePartEntries[curFilePartEntry].m_position = (quint32)file.pos();
+ QBuffer buffer;
+ KSecretFile device(&buffer, KSecretFile::Write);
+ QHash<QString, KSecretCollection::ApplicationPermission>::const_iterator it = \
m_acls.constBegin(); + QHash<QString, \
KSecretCollection::ApplicationPermission>::const_iterator end = m_acls.constEnd(); + \
for ( ; it != end; ++it) { + if (!file.writeString(it.key()) || \
!file.writeUint((quint32)it.value())) { + return false;
+ }
+ }
+ if (!serializeAuthenticated(buffer.data(), file)) {
+ return false;
+ }
+ filePartEntries[curFilePartEntry].m_length =
+ (quint32)file.pos() - filePartEntries[curFilePartEntry].m_position;
+ curFilePartEntry++;
+
+ // item hash part
+ if (!serializeItemHashes(file, filePartEntries[curFilePartEntry])) {
+ return false;
+ }
+ curFilePartEntry++;
+
+ // items part
+ if (!serializeItems(file, filePartEntries[curFilePartEntry])) {
+ return false;
+ }
+ curFilePartEntry++;
+
+ // keys
+ Q_FOREACH(EncryptedKey *key, m_encryptedSymKeys) {
+ filePartEntries[curFilePartEntry].m_type = KSecretFile::PartSymKey;
+ filePartEntries[curFilePartEntry].m_position = (quint32)file.pos();
+ if (!file.writeUint(key->m_type) || !file.writeBytearray(key->m_key)) {
+ return false;
+ }
+ filePartEntries[curFilePartEntry].m_length =
+ (quint32)file.pos() - filePartEntries[curFilePartEntry].m_position;
+ curFilePartEntry++;
+ }
+
+ // unknown parts
+ Q_FOREACH(UnknownFilePart *unknown, m_unknownParts) {
+ filePartEntries[curFilePartEntry].m_type = unknown->m_type;
+ filePartEntries[curFilePartEntry].m_position = (quint32)file.pos();
+ if (!file.writeBytearray(unknown->m_contents)) {
+ return false;
+ }
+ filePartEntries[curFilePartEntry].m_length =
+ (quint32)file.pos() - filePartEntries[curFilePartEntry].m_position;
+ curFilePartEntry++;
+ }
+
+ // write part table
+ if (!file.seek(partTablePos) || !file.writeUint(numParts)) {
+ return false;
+ }
+
+ Q_FOREACH(const FilePartEntry &part, filePartEntries) {
+ if (!file.writeUint(part.m_type) || !file.writeUint(part.m_position) ||
+ !file.writeUint(part.m_length)) {
+ return false;
+ }
+ }
+
+ file.close();
+ return true;
+}
+
+bool KSecretCollection::serializeAclsPart(KSecretFile &file, FilePartEntry &entry) \
const +{
+ entry.m_type = KSecretFile::PartAcls;
+ entry.m_position = (quint32)file.pos();
+
+ // build temporary buffer containing acls
+ QBuffer aclsBuffer;
+ KSecretFile tempFile(&aclsBuffer, KSecretFile::Write);
+ if (!tempFile.writeUint(m_acls.size())) {
+ return false;
+ }
+
+ QHash<QString, ApplicationPermission>::const_iterator it = m_acls.constBegin();
+ QHash<QString, ApplicationPermission>::const_iterator end = m_acls.constEnd();
+ for ( ; it != end; ++it) {
+ if (!tempFile.writeString(it.key()) || \
!tempFile.writeUint((quint32)it.value())) { + return false;
+ }
+ }
+
+ QMap<QString, int>::const_iterator it2 = m_unknownAcls.constBegin();
+ QMap<QString, int>::const_iterator end2 = m_unknownAcls.constEnd();
+ for ( ; it2 != end2; ++it2) {
+ if (!tempFile.writeString(it2.key()) || !tempFile.writeUint(it2.value())) {
+ return false;
+ }
+ }
+
+ if (!serializeAuthenticated(aclsBuffer.data(), file)) {
+ return false;
+ }
+ entry.m_length = (quint32)file.pos() - entry.m_position;
+ return true;
+}
+
+bool KSecretCollection::serializeConfigPart(KSecretFile &file, FilePartEntry &entry) \
const +{
+ entry.m_type = KSecretFile::PartConfig;
+ entry.m_position = (quint32)file.pos();
+
+ // build temporary buffer containing config keys and values
+ QBuffer configBuffer;
+ KSecretFile tempFile(&configBuffer, KSecretFile::Write);
+ if (!tempFile.writeUint(3 + m_cfgUnknownKeys.size())) {
+ return false;
+ }
+
+ // save known configuration values
+ QByteArray valCloseScreensaver;
+ valCloseScreensaver.setNum((int)m_cfgCloseScreensaver);
+ QByteArray valCloseIfUnused;
+ valCloseIfUnused.setNum((int)m_cfgCloseIfUnused);
+ QByteArray valCloseUnusedTimeout;
+ valCloseUnusedTimeout.setNum(m_cfgCloseUnusedTimeout);
+ if (!tempFile.writeString("CloseScreensaver") ||
+ !tempFile.writeBytearray(valCloseScreensaver) ||
+ !tempFile.writeString("CloseIfUnused") ||
+ !tempFile.writeBytearray(valCloseIfUnused) ||
+ !tempFile.writeString("CloseUnusedTimeout") ||
+ !tempFile.writeBytearray(valCloseUnusedTimeout)) {
+ return false;
+ }
+
+ // save unknown configuration values
+ QMap<QString, QByteArray>::const_iterator it = m_cfgUnknownKeys.constBegin();
+ QMap<QString, QByteArray>::const_iterator end = m_cfgUnknownKeys.constEnd();
+ for ( ; it != end; ++it) {
+ if (!tempFile.writeString(it.key()) || !tempFile.writeBytearray(it.value())) {
+ return false;
+ }
+ }
+
+ if (!serializeAuthenticated(configBuffer.data(), file)) {
+ return false;
+ }
+ entry.m_length = (quint32)file.pos() - entry.m_position;
+
+ return true;
+}
+
+bool KSecretCollection::serializeItemHashes(KSecretFile &file, FilePartEntry &entry) \
const +{
+ entry.m_type = KSecretFile::PartItemHashes;
+ entry.m_position = (quint32)file.pos();
+
+ // as the hashes are stored as hash-value => item and they have to be written
+ // to the file the other way round (item => hash-value, hash-value, ...), we
+ // have to manually reverse them.
+
+ QHash<KSecretItem*, QList<QByteArray> > hashes;
+
+ QMultiHash<QByteArray, KSecretItem*>::const_iterator it = \
m_itemHashes.constBegin(); + const QMultiHash<QByteArray, \
KSecretItem*>::const_iterator end = m_itemHashes.constEnd(); + for ( ; it != end; \
++it) { + hashes[it.value()].append(it.key());
+ }
+
+ if (!file.writeUint(hashes.size())) {
+ return false;
+ }
+
+ QHash<KSecretItem*, QList<QByteArray> >::const_iterator it2 = \
hashes.constBegin(); + QHash<KSecretItem*, QList<QByteArray> >::const_iterator end2 \
= hashes.constEnd(); + for ( ; it2 != end2; ++it2) {
+ if (!file.writeString(it2.key()->id()) || !file.writeUint(it2.value().size())) \
{ + return false;
+ }
+ QList<QByteArray> hv = it2.value();
+ Q_FOREACH(const QByteArray &hash, hv) {
+ if (!file.writeBytearray(hash)) {
+ return false;
+ }
+ }
+ }
+
+ entry.m_length = (quint32)file.pos() - entry.m_position;
+
+ return true;
+}
+
+bool KSecretCollection::serializeItems(KSecretFile &file, FilePartEntry &entry) \
const +{
+ Q_ASSERT(file.isValid());
+
+ entry.m_type = KSecretFile::PartItems;
+ entry.m_position = (quint32)file.pos();
+
+ // construct a file to write the unlocked items to
+ SecureBuffer device;
+ KSecretFile tempFile(&device, KSecretFile::Write);
+
+ if (!tempFile.writeUint(m_items.size())) {
+ return false;
+ }
+
+ QHash<QString, KSecretItem*>::const_iterator it = m_items.constBegin();
+ const QHash<QString, KSecretItem*>::const_iterator end = m_items.constEnd();
+ for ( ; it != end; ++it) {
+ if (!it.value()->serializeUnlocked(tempFile)) {
+ return false;
+ }
+ }
+
+ bool rc = serializeEncrypted(device.buffer(), file);
+ if (rc) {
+ entry.m_length = (quint32)file.pos() - entry.m_position;
+ return true;
+ } else {
+ return false;
+ }
+}
+
+bool KSecretCollection::serializeEncrypted(const QCA::SecureArray &data, KSecretFile \
&file) const +{
+ Q_ASSERT(file.isValid());
+ // TODO: those errors either have to be caught before calling this method
+ // or inside.
+ Q_ASSERT(m_symmetricKey);
+ Q_ASSERT(m_hash);
+ Q_ASSERT(m_cipher);
+
+ QCA::InitializationVector iv(m_cipher->blockSize());
+ if (!file.writeSecret(iv)) {
+ return false;
+ }
+
+ // TODO: random padding?
+
+ // encrypt the data
+ m_cipher->setup(QCA::Encode, *m_symmetricKey, iv);
+ QCA::SecureArray encryptedPart = m_cipher->update(data);
+ if (!m_cipher->ok()) {
+ return false;
+ }
+ encryptedPart.append(m_cipher->final());
+ if (!m_cipher->ok()) {
+ return false;
+ }
+
+ // write the encrypted data to file
+ if (!file.writeSecret(encryptedPart)) {
+ return false;
+ }
+
+ return true;
+}
+
+bool KSecretCollection::serializeAuthenticated(const QByteArray &data, KSecretFile \
&file) const +{
+ Q_ASSERT(m_mac);
+
+ // write actual data first
+ if (!file.writeUint(data.size()) || !file.writeBytearray(data)) {
+ return false;
+ }
+
+ // compute the HMAC for the data
+ m_mac->clear();
+ m_mac->update(QCA::SecureArray(data));
+ QCA::SecureArray dataHash = m_mac->final();
+
+ return file.writeSecret(dataHash);
+}
+
+#include "ksecretcollection.moc"
diff --git a/backend/ksecret/ksecretcollection.h \
b/backend/ksecret/ksecretcollection.h new file mode 100644
index 0000000..c796009
--- /dev/null
+++ b/backend/ksecret/ksecretcollection.h
@@ -0,0 +1,480 @@
+/*
+ * Copyright 2010, Michael Leupold <lemma@confuego.org>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License or (at your option) version 3 or any later version
+ * accepted by the membership of KDE e.V. (or its successor approved
+ * by the membership of KDE e.V.), which shall act as a proxy
+ * defined in Section 14 of version 3 of the license.
+ *
+ * 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef KSECRETCOLLECTION_H
+#define KSECRETCOLLECTION_H
+
+#include "../backendcollection.h"
+#include "ksecretitem.h"
+#include "ksecretfile.h"
+
+class KSecretCollectionManager;
+
+/**
+ * Represents a part of the ksecret file stored in memory. The part's type
+ * is stored as unsigned integer as the part is unknown to this version of
+ * ksecretservice.
+ */
+struct UnknownFilePart
+{
+ quint32 m_type;
+ QByteArray m_contents;
+};
+
+/**
+ * Represents an encrypted key as stored inside the ksecret file. The key
+ * type is stored as unsigned integer as the key type might not be known
+ * to this version of ksecretservice.
+ */
+struct EncryptedKey
+{
+ quint32 m_type;
+ QByteArray m_key;
+};
+
+/**
+ * Holds a description of a ksecret file part.
+ */
+struct FilePartEntry
+{
+ quint32 m_type;
+ quint32 m_position;
+ quint32 m_length;
+};
+
+/**
+ * A collection stored on disk using the ksecret file format.
+ */
+class KSecretCollection : public BackendCollection
+{
+ Q_OBJECT
+
+private:
+ /**
+ * Constructor for loading an existing collection from a file or creating
+ * a new collection using create().
+ *
+ * @param parent the collection manager that loads this collection
+ */
+ KSecretCollection(BackendCollectionManager *parent);
+
+public:
+ /**
+ * Create a new collection.
+ *
+ * @param id id for the new collection
+ * @param parent parent collection manager
+ * @param errorMessage set in case of an error
+ * @return the new collection or 0 in case of an error
+ */
+ static KSecretCollection *create(const QString &id, BackendCollectionManager \
*parent, + QString &errorMessage);
+
+ /**
+ * Destructor
+ */
+ virtual ~KSecretCollection();
+
+ /**
+ * The unique identifier for this collection
+ */
+ virtual QString id() const;
+
+ /**
+ * The human-readable label for this collection.
+ * @todo error
+ */
+ virtual BackendReturn<QString> label() const;
+
+ /**
+ * Set this collection's label human-readable label.
+ *
+ * @todo error
+ * @param label the new label for this collection
+ */
+ virtual BackendReturn<void> setLabel(const QString &label);
+
+ /**
+ * The time this collection was created.
+ */
+ virtual QDateTime created() const;
+
+ /**
+ * The time this collection was last modified.
+ */
+ virtual QDateTime modified() const;
+
+ /**
+ * Check whether this collection is locked.
+ *
+ * @return true if the collection is locked, false if the collection
+ * is unlocked.
+ */
+ virtual bool isLocked() const;
+
+ /**
+ * List all items inside this backend.
+ *
+ * @return a list containing all items inside this backend. An empty list
+ * either means that no items were found or that an error occurred
+ * (eg. collection needs unlocking before listing the items).
+ * @todo error
+ */
+ virtual BackendReturn<QList<BackendItem*> > items() const;
+
+ /**
+ * Check if a type of call can be handled immediately
+ * (synchronously) without requiring an async call.
+ *
+ * @param type the type of call to check
+ * @return true if the type of call can be handled
+ * immediately, false if an async call is
+ * required.
+ */
+ virtual bool isCallImmediate(AsyncCall::AsyncType type) const;
+
+ /**
+ * Return all items whose attributes match the search terms.
+ *
+ * @param attributes attributes against which the items should be matched
+ * @return a list of items matching the attributes. An empty list either means \
that + * no items were found or that an error occurred (eg. collection \
needs + * unlocking before listing the items).
+ * @todo error
+ */
+ virtual BackendReturn<QList<BackendItem*> > searchItems(
+ const QMap<QString, QString> &attributes) const;
+
+ /**
+ * Unlock this collection.
+ *
+ * @return true if the collection is now unlocked, false if unlocking
+ * failed.
+ */
+ virtual BackendReturn<bool> unlock();
+
+ /**
+ * Lock this collection.
+ *
+ * @return true if the collection is now locked, false if locking
+ * failed (the collection couldn't be locked or unlocking is not
+ * supported by this backend).
+ */
+ virtual BackendReturn<bool> lock();
+
+ /**
+ * Delete this collection.
+ *
+ * @return true if the collection was deleted, false if it wasn't.
+ */
+ virtual BackendReturn<bool> deleteCollection();
+
+ /**
+ * Create an item.
+ *
+ * @param label label to assign to the new item
+ * @param attributes attributes to store for the new item
+ * @param secret the secret to store
+ * @param replace if true, an existing item with the same attributes
+ * will be replaced, if false no item will be created
+ * if one with the same attributes already exists
+ * @param locked if true, the item will be locked after creation
+ * @return the item created or 0 if an error occurred.
+ */
+ virtual BackendReturn<BackendItem*> createItem(const QString &label,
+ const QMap<QString, QString> \
&attributes, + const \
QCA::SecureArray &secret, bool replace, + \
bool locked); +
+ /**
+ * Change this collection's authentication.
+ *
+ * @return Always false as TemporaryCollection doesn't support authentication
+ */
+ virtual BackendReturn<bool> changeAuthentication();
+
+ /**
+ * Get the path of the ksecret file the collection is stored inside.
+ *
+ * @return the path of the collection file
+ */
+ const QString &path() const;
+
+private Q_SLOTS:
+ /**
+ * Remove an item from our list of known items.
+ *
+ * @param item Item to remove
+ */
+ void slotItemDeleted(BackendItem *item);
+
+ /**
+ * This slot is called whenever an Item's attributes change to rebuild the item \
lookup + * hashes the collection uses to search items.
+ *
+ * @param item Item whose attributes changed
+ */
+ void changeAttributeHashes(KSecretItem *item);
+
+public:
+ /**
+ * Deserialize a ksecret collection from a KSecretFile.
+ *
+ * @param path Path to load the collection from
+ * @param parent parent collection manager
+ * @param errorMessage set if there's an error
+ * @return the KSecretCollection on success, 0 in case of an error
+ */
+ static KSecretCollection *deserialize(const QString &path, \
KSecretCollectionManager *parent, + QString \
&errorMessage); +
+private:
+ /**
+ * Application permissions stored inside the ksecret file.
+ */
+ enum ApplicationPermission {
+ PermissionAsk = 0, /// ask every time
+ PermissionDeny = 1, /// deny every time
+ PermissionAllow = 2 /// always allow
+ };
+
+ /**
+ * Set-up the encryption to be used by the secret collection.
+ * This creates hash and cipher functors as configured.
+ *
+ * @param errorMessage set in case of an error
+ * @return true if setting up the encryption worked, false if
+ * there were errors (ie. unsupported encryption methods.
+ */
+ bool setupAlgorithms(QString &errorMessage);
+
+ /**
+ * Deserialize the ksecret file header.
+ *
+ * @param file ksecret file to read from
+ * @param errorMessage set if there's an error
+ * @return true on success, false in case of an error
+ */
+ bool deserializeHeader(KSecretFile &file, QString &errorMessage);
+
+ /**
+ * Deserialize the algorithms used by a collection and check whether they
+ * are supported.
+ *
+ * @param file ksecret file to read from
+ * @param errorMessage set if there's an error
+ * @return true on success, false in case of an error
+ */
+ bool deserializeAlgorithms(KSecretFile &file, QString &errorMessage);
+
+ /**
+ * Deserialize the parts inside the ksecret file.
+ *
+ * @param file ksecret file to read from
+ * @param errorMessage set if there's an error
+ * @return true on success, false in case of an error
+ */
+ bool deserializeParts(KSecretFile &file, QString &errorMessage);
+
+ /**
+ * Deserialize the collection property part inside a ksecret file.
+ *
+ * @param partContents contents of the part to deserialize
+ * @return true on success, false in case of an error
+ */
+ bool deserializePartCollProps(const QByteArray &partContents);
+
+ /**
+ * Deserialize an item hashes part inside a ksecret file.
+ *
+ * @param partContents contents of the part to deserialize
+ * @return true on success, false in case of an error
+ */
+ bool deserializePartItemHashes(const QByteArray &partContents);
+
+ /**
+ * Deserialize a symmetric key part inside a ksecret file.
+ *
+ * @param partContents contents of the part to deserialize
+ * @return true on success, false in case of an error
+ */
+ bool deserializePartSymKey(const QByteArray &partContents);
+
+ /**
+ * Deserialize an acl part inside a ksecret file.
+ *
+ * @param partContents contents of the part to deserialize
+ * @return true on success, false in case of an error
+ */
+ bool deserializePartAcls(const QByteArray &partContents);
+
+ /**
+ * Deserialize a config part inside a ksecret file.
+ *
+ * @param partContents contents of the part to deserialize
+ * @return true on success, false in case of an error
+ */
+ bool deserializePartConfig(const QByteArray &partContents);
+
+ /**
+ * Deserialize the unlocked items part contained in file.
+ *
+ * @param file File to read the unlocked items from
+ * @return true if reading the items was successful, false else
+ * @remarks if the return value of this method is false, some of the
+ * items might already have been overwritten. So in this case
+ * it's wise to clear the items' data and re-lock them.
+ */
+ bool deserializeItemsUnlocked(KSecretFile &file);
+
+ /**
+ * Deserialize the contents of an encrypted part.
+ *
+ * @param partContents the contents of the part
+ * @param decryptedPart the decrypted part contents
+ * @return true if decrypting and deserializing was successful, false else
+ */
+ bool deserializePartEncrypted(const QByteArray &partContents, QCA::SecureArray \
&decryptedPart); +
+ /**
+ * Serialize this ksecret collection back to a KSecretFile.
+ *
+ * @param errorMessage set if there's an error
+ * @return true on success, false in case of an error
+ */
+ bool serialize(QString &errorMessage) const;
+
+ /**
+ * Serialize a ksecret file's headers.
+ *
+ * @param file ksecret file to write to
+ * @return true if serialization was successful, false else
+ */
+ bool serializeHeader(KSecretFile &file) const;
+
+ /**
+ * Serialize a collection's parts to a ksecret file.
+ *
+ * @param file ksecret file to write to
+ * @return true if serialization was successful, false else
+ */
+ bool serializeParts(KSecretFile &file) const;
+
+ /**
+ * Serialize a collection's configuration to a ksecret file.
+ *
+ * @param file ksecret file to serialize the configuration values to
+ * @param entry file part descriptor to put part information to
+ * @return true if serialization was successful, false else
+ */
+ bool serializeConfigPart(KSecretFile &file, FilePartEntry &entry) const;
+
+ /**
+ * Serialize a collection's acls to a ksecret file.
+ *
+ * @param file ksecret file to serialize the acls to
+ * @param entry file part descriptor to put part information to
+ * @return true if serialization was successful, false else
+ */
+ bool serializeAclsPart(KSecretFile &file, FilePartEntry &entry) const;
+
+ /**
+ * Serialize the item hashes of this collection to the ksecret file.
+ *
+ * @param file ksecret file to write to
+ * @return true on success, false in case of an error
+ */
+ bool serializeItemHashes(KSecretFile &file, FilePartEntry &entry) const;
+
+ /**
+ * Serialize the unlocked items of this collection to the ksecret file.
+ *
+ * @param file ksecret file to write to
+ * @return true on success, false in case of an error
+ */
+ bool serializeItems(KSecretFile &file, FilePartEntry &entry) const;
+
+ /**
+ * Write an encrypted part.
+ *
+ * @param data data to be encrypted and written
+ * @param file ksecret file to write to
+ * @param true on success, false in case of an error
+ */
+ bool serializeEncrypted(const QCA::SecureArray &data, KSecretFile &file) const;
+
+ /**
+ * Serialize a file part by writing it to the ksecret file and appending a hash \
mac + * to sign its contents.
+ *
+ * @param data data to write to the part
+ * @param file ksecret file to write the data to
+ * @return true if serializing was successful, false else
+ */
+ bool serializeAuthenticated(const QByteArray &data, KSecretFile &file) const;
+
+ QString m_id;
+ QString m_label;
+ QDateTime m_created;
+ QDateTime m_modified;
+
+ QString m_path; // path of the ksecret file on disk
+
+ // configuration values
+ bool m_cfgCloseScreensaver; // close when the screensaver starts
+ bool m_cfgCloseIfUnused; // close when the last application stops using it
+ quint32 m_cfgCloseUnusedTimeout; // timeout the collection will be closed after \
if unused (secs) + QMap<QString, QByteArray> m_cfgUnknownKeys; // unknown \
configuration keys +
+ quint32 m_algoHash; // hashing/mac algorithm identifier
+ QCA::Hash *m_hash; // hashing algorithm
+ QCA::MessageAuthenticationCode *m_mac; // message authentication code algorithm
+ quint32 m_algoCipher; // encryption algorithm identifier
+ QCA::Cipher *m_cipher; // encryption algorithm
+
+ QCA::SymmetricKey *m_symmetricKey; // the symmetric key used for \
encryption/decryption +
+ // the configuration values message authentication code.
+ QByteArray m_configValuesMac;
+
+ // the acls message authentication code
+ QHash<QString, ApplicationPermission> m_acls;
+ QMap<QString, int> m_unknownAcls;
+ QByteArray m_aclsMac;
+
+ // unknown file parts stored as-is
+ QList<UnknownFilePart*> m_unknownParts;
+
+ // contains the encrypted item parts
+ QList<QByteArray> m_encryptedItemParts;
+
+ // contains the encrypted symmetric keys
+ QList<EncryptedKey*> m_encryptedSymKeys;
+
+ // maps lookup attribute hashes to items
+ QMultiHash<QByteArray, KSecretItem*> m_itemHashes;
+ // maps item ids to their hashes for changing/removal
+ QHash<KSecretItem*, QSet<QByteArray> > m_reverseItemHashes;
+
+ // maps item identifiers to items
+ QHash<QString, KSecretItem*> m_items;
+};
+
+#endif
diff --git a/backend/ksecret/ksecretcollectionmanager.cpp \
b/backend/ksecret/ksecretcollectionmanager.cpp new file mode 100644
index 0000000..7dc31d6
--- /dev/null
+++ b/backend/ksecret/ksecretcollectionmanager.cpp
@@ -0,0 +1,99 @@
+/*
+ * Copyright 2010, Michael Leupold <lemma@confuego.org>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License or (at your option) version 3 or any later version
+ * accepted by the membership of KDE e.V. (or its successor approved
+ * by the membership of KDE e.V.), which shall act as a proxy
+ * defined in Section 14 of version 3 of the license.
+ *
+ * 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "ksecretcollectionmanager.h"
+#include "ksecretcollection.h"
+
+#include "secrettool.h"
+
+#include <QtCore/QTimer>
+#include <QtCore/QDir>
+
+KSecretCollectionManager::KSecretCollectionManager(const QString &path, QObject \
*parent) + : BackendCollectionManager(parent), m_watcher(QStringList(path))
+{
+ connect(&m_watcher, SIGNAL(directoryChanged(QString)), \
SLOT(slotDirectoryChanged(QString))); + // list directory contents to discover \
existing collections on startup + QTimer::singleShot(0, this, \
SLOT(slotStartupDiscovery())); +}
+
+KSecretCollectionManager::~KSecretCollectionManager()
+{
+ // TODO: cleanup?
+}
+
+bool KSecretCollectionManager::isCallImmediate(AsyncCall::AsyncType type) const
+{
+ // TODO: based on available authentication methods this may return true
+ if (type == AsyncCall::AsyncCreateCollectionMaster) {
+ return false;
+ } else {
+ return true;
+ }
+}
+
+BackendReturn<BackendCollection*> KSecretCollectionManager::createCollection(const \
QString &label, + \
bool locked) +{
+ // TODO: collection needs authentication methods, filenames, ...
+ QString errorMessage;
+ KSecretCollection *coll = KSecretCollection::create(createId(), this, \
errorMessage); + coll->setLabel(label);
+
+ // TODO: coll has be added to m_collections before serializing it for the first
+ // time, so when slotDirectoryChanged is called, the collection is already
+ // known.
+ m_collections.insert(coll->path(), coll);
+
+ connect(coll, SIGNAL(collectionDeleted(BackendCollection*)),
+ SIGNAL(collectionDeleted(BackendCollection*)));
+ emit collectionCreated(coll);
+
+ if (locked) {
+ coll->lock();
+ }
+
+ return coll;
+}
+
+void KSecretCollectionManager::slotDirectoryChanged(const QString &path)
+{
+ // list all collections in the directory and check if there's a collection
+ // which we don't know yet.
+ QDir dir(path);
+ QStringList entries = dir.entryList(QStringList("*.ksecret"), QDir::Files);
+ Q_FOREACH(const QString &file, entries) {
+ if (!m_collections.contains(file)) {
+ QString errorMessage;
+ KSecretCollection *coll = KSecretCollection::deserialize(file, this, \
errorMessage); + if (coll) {
+ m_collections.insert(file, coll);
+ }
+ }
+ }
+}
+
+void KSecretCollectionManager::slotStartupDiscovery()
+{
+ Q_ASSERT(m_watcher.directories().count() == 1);
+ slotDirectoryChanged(m_watcher.directories().at(0));
+}
+
+#include "ksecretcollectionmanager.moc"
diff --git a/backend/ksecret/ksecretcollectionmanager.h \
b/backend/ksecret/ksecretcollectionmanager.h new file mode 100644
index 0000000..4633dc8
--- /dev/null
+++ b/backend/ksecret/ksecretcollectionmanager.h
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2010, Michael Leupold <lemma@confuego.org>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License or (at your option) version 3 or any later version
+ * accepted by the membership of KDE e.V. (or its successor approved
+ * by the membership of KDE e.V.), which shall act as a proxy
+ * defined in Section 14 of version 3 of the license.
+ *
+ * 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef KSECRETCOLLECTIONMANAGER_H
+#define KSECRETCOLLECTIONMANAGER_H
+
+#include "../backendcollectionmanager.h"
+
+#include <QtCore/QFileSystemWatcher>
+
+class KSecretCollection;
+
+/**
+ * Manager for collections stored in ksecret files.
+ */
+class KSecretCollectionManager : public BackendCollectionManager
+{
+ Q_OBJECT
+
+public:
+ /**
+ * Constructor
+ *
+ * @param path Path to detect ksecret files in
+ * @param parent parent object
+ */
+ KSecretCollectionManager(const QString &path, QObject *parent = 0);
+
+ /**
+ * Destructor
+ */
+ virtual ~KSecretCollectionManager();
+
+ /**
+ * Check if a type of call can be handled immediately
+ * (synchronously) without requiring an async call.
+ *
+ * @param type the type of call to check
+ * @return true if the type of call can be handled
+ * immediately, false if an async call is required.
+ */
+ virtual bool isCallImmediate(AsyncCall::AsyncType type) const;
+
+ /**
+ * Create a new collection.
+ *
+ * @param label the label of the new collection
+ * @param lock if true, the collection should be locked after creation,
+ * if false it should stay unlocked
+ * @return the collection or 0 on error
+ */
+ virtual BackendReturn<BackendCollection*> createCollection(const QString &label, \
bool locked); +
+private Q_SLOTS:
+ /**
+ * Connected to a filesystem watcher this slot is called whenever
+ * a ksecret file is added or removed.
+ *
+ * @param path directory that changed
+ */
+ void slotDirectoryChanged(const QString &path);
+
+ /**
+ * Collection discovery method which can be called using a single-shot timer.
+ */
+ void slotStartupDiscovery();
+
+private:
+ // filesystem watcher to detect new/removed ksecret files
+ QFileSystemWatcher m_watcher;
+
+ // map of paths pointing to the respective collection objects
+ QMap<QString, KSecretCollection*> m_collections;
+};
+
+#endif
diff --git a/backend/ksecret/ksecretfile.cpp b/backend/ksecret/ksecretfile.cpp
new file mode 100644
index 0000000..7f0e76c
--- /dev/null
+++ b/backend/ksecret/ksecretfile.cpp
@@ -0,0 +1,287 @@
+/*
+ * Copyright 2010, Michael Leupold <lemma@confuego.org>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License or (at your option) version 3 or any later version
+ * accepted by the membership of KDE e.V. (or its successor approved
+ * by the membership of KDE e.V.), which shall act as a proxy
+ * defined in Section 14 of version 3 of the license.
+ *
+ * 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "ksecretfile.h"
+
+#include <QtCore/QtEndian>
+
+#define KSECRET_MAGIC "KSECRET\n\r\0\r\n"
+#define KSECRET_MAGIC_LEN 12
+
+// FIXME: currently the daemon could be tricked into reading a large portion of a \
forged +// ksecret file wasting a lot of memory and making the pc unusable.
+
+KSecretFile::KSecretFile(QIODevice *device, OpenMode mode)
+ : m_device(device), m_mode(mode)
+{
+ m_valid = m_device->open((mode == Read) ? QIODevice::ReadOnly : \
QIODevice::WriteOnly); +}
+
+KSecretFile::~KSecretFile()
+{
+ m_device->close();
+ delete m_device;
+}
+
+void KSecretFile::close()
+{
+ m_device->close();
+}
+
+bool KSecretFile::isValid() const
+{
+ return m_valid;
+}
+
+qint64 KSecretFile::pos() const
+{
+ return m_device->pos();
+}
+
+bool KSecretFile::seek(qint64 pos)
+{
+ return m_device->seek(pos);
+}
+
+bool KSecretFile::readMagic()
+{
+ Q_ASSERT(m_mode == Read);
+
+ if (!m_valid) {
+ return false;
+ }
+
+ m_readBuffer.resize(KSECRET_MAGIC_LEN);
+ if (m_device->read(m_readBuffer.data(), KSECRET_MAGIC_LEN) != KSECRET_MAGIC_LEN) \
{ + m_valid = false;
+ return false;
+ }
+ return m_readBuffer == KSECRET_MAGIC;
+}
+
+bool KSecretFile::writeMagic()
+{
+ Q_ASSERT(m_mode == Write);
+
+ if (!m_valid) {
+ return false;
+ }
+
+ if (m_device->write(KSECRET_MAGIC) != KSECRET_MAGIC_LEN) {
+ m_valid = false;
+ }
+ return m_valid;
+}
+
+bool KSecretFile::readUint(quint32 *value)
+{
+ Q_ASSERT(value);
+ Q_ASSERT(m_mode == Read);
+
+ if (!m_valid) {
+ return false;
+ }
+
+ m_readBuffer.resize(4);
+ if (m_device->read(m_readBuffer.data(), 4) != 4) {
+ m_valid = false;
+ return false;
+ }
+ *value = qFromBigEndian<quint32>((const uchar*)m_readBuffer.constData());
+ return true;
+}
+
+bool KSecretFile::writeUint(quint32 value)
+{
+ Q_ASSERT(m_mode == Write);
+
+ if (!m_valid) {
+ return false;
+ }
+
+ uchar outputBuffer[4];
+ qToBigEndian<quint32>(value, outputBuffer);
+ if (m_device->write((const char*)outputBuffer, 4) != 4) {
+ m_valid = false;
+ }
+ return m_valid;
+}
+
+bool KSecretFile::readDatetime(QDateTime *value)
+{
+ Q_ASSERT(value);
+ Q_ASSERT(m_mode == Read);
+
+ if (!m_valid) {
+ return false;
+ }
+
+ m_readBuffer.resize(8);
+ if (m_device->read(m_readBuffer.data(), 8) != 8) {
+ m_valid = false;
+ return false;
+ }
+ value->setTime_t(qFromBigEndian<quint64>((const \
uchar*)m_readBuffer.constData())); + return true;
+}
+
+bool KSecretFile::writeDatetime(const QDateTime &value)
+{
+ Q_ASSERT(m_mode == Write);
+
+ if (!m_valid) {
+ return false;
+ }
+
+ uchar outputBuffer[8];
+ qToBigEndian<quint64>(value.toTime_t(), outputBuffer);
+ if (m_device->write((const char*)outputBuffer, 8) != 8) {
+ m_valid = false;
+ }
+ return m_valid;
+}
+
+bool KSecretFile::readBytearray(QByteArray *value)
+{
+ Q_ASSERT(value);
+ Q_ASSERT(m_mode == Read);
+
+ if (!m_valid) {
+ return false;
+ }
+
+ quint32 length;
+ if (!readUint(&length)) {
+ m_valid = false;
+ return false;
+ }
+
+ value->resize(length);
+ if (m_device->read(value->data(), length) != length) {
+ m_valid = false;
+ }
+ return m_valid;
+}
+
+bool KSecretFile::writeBytearray(const QByteArray &value)
+{
+ Q_ASSERT(m_mode == Write);
+
+ if (!m_valid) {
+ return false;
+ }
+
+ if (!writeUint(value.size()) || m_device->write(value) != value.size()) {
+ m_valid = false;
+ }
+ return m_valid;
+}
+
+bool KSecretFile::readString(QString *value)
+{
+ Q_ASSERT(value);
+ Q_ASSERT(m_mode == Read);
+
+ if (!m_valid) {
+ return false;
+ }
+
+ QByteArray bytearray;
+ if (!readBytearray(&bytearray)) {
+ m_valid = false;
+ return false;
+ }
+
+ // TODO: detect invalid Utf-8 strings!
+ *value = QString::fromUtf8(bytearray);
+ return true;
+}
+
+bool KSecretFile::writeString(const QString &value)
+{
+ return writeBytearray(value.toUtf8());
+}
+
+bool KSecretFile::readSecret(QCA::SecureArray *value)
+{
+ Q_ASSERT(value);
+ Q_ASSERT(m_mode == Read);
+
+ if (!m_valid) {
+ return false;
+ }
+
+ quint32 length;
+ if (!readUint(&length)) {
+ m_valid = false;
+ return false;
+ }
+
+ value->resize(length);
+ if (m_device->read(value->data(), length) != length) {
+ m_valid = false;
+ return false;
+ }
+ return true;
+}
+
+bool KSecretFile::writeSecret(const QCA::SecureArray &value)
+{
+ Q_ASSERT(m_mode == Write);
+
+ if (!m_valid) {
+ return false;
+ }
+
+ if (!writeUint(value.size()) ||
+ m_device->write(value.constData(), value.size()) != value.size()) {
+ m_valid = false;
+ }
+ return m_valid;
+}
+
+bool KSecretFile::readPart(QByteArray *value, quint32 position, quint32 length)
+{
+ Q_ASSERT(value);
+ Q_ASSERT(m_mode == Read);
+
+ if (!m_valid) {
+ return false;
+ }
+
+ // remember current position
+ qint64 oldPos = m_device->pos();
+
+ bool rc = true;
+
+ // seek new position
+ if (m_device->seek(position)) {
+ value->resize(length);
+ if (m_device->read(value->data(), length) != length) {
+ rc = false;
+ }
+ } else {
+ rc = false;
+ }
+
+ m_device->seek(oldPos);
+ m_valid = rc;
+ return rc;
+}
diff --git a/backend/ksecret/ksecretfile.h b/backend/ksecret/ksecretfile.h
new file mode 100644
index 0000000..0b7385c
--- /dev/null
+++ b/backend/ksecret/ksecretfile.h
@@ -0,0 +1,235 @@
+/*
+ * Copyright 2010, Michael Leupold <lemma@confuego.org>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License or (at your option) version 3 or any later version
+ * accepted by the membership of KDE e.V. (or its successor approved
+ * by the membership of KDE e.V.), which shall act as a proxy
+ * defined in Section 14 of version 3 of the license.
+ *
+ * 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef KSECRETFILE_H
+#define KSECRETFILE_H
+
+#define VERSION_MAJOR 0
+#define VERSION_MINOR 1
+
+#include <QtCore/QIODevice>
+#include <QtCore/QDateTime>
+#include <QtCrypto>
+
+/**
+ * Encapsulates reading from and writing to a ksecret file.
+ */
+class KSecretFile
+{
+public:
+ /**
+ * Modes for opening the ksecret file.
+ */
+ enum OpenMode {
+ Read, /// Open file for reading
+ Write /// Open file for writing
+ };
+
+ /**
+ * Known hashing and MAC algorithms.
+ */
+ enum AlgorithmHash {
+ SHA256 = 0 /// SHA256
+ };
+
+ /**
+ * Known encryption algorithms.
+ */
+ enum AlgorithmEncryption {
+ AES256 = 0 /// AES256 using CBC and default padding
+ };
+
+ /**
+ * Known types of parts in the ksecret file.
+ */
+ enum PartType {
+ PartItemHashes = 0, /// Attribute hashes for item lookup
+ PartSymKey = 1, /// Encrypted symmetric master-key
+ PartItems = 2, /// Encrypted items
+ PartAcls = 3, /// Signed ACLs
+ PartConfig = 4, /// Signed collection configuration
+ PartCollProps = 5 /// Signed collection properties
+ };
+
+ /**
+ * Known encryption types for the symmetric key.
+ */
+ enum KeyType {
+ KeyPassword = 0, /// Key encrypted using a password
+ KeyBogus = 666 /// TODO: remove this once other keys are implemented
+ };
+
+ /**
+ * Constructor.
+ *
+ * @param device device to read the contents from
+ * @param mode set whether to read or to write
+ */
+ KSecretFile(QIODevice *device, OpenMode mode);
+
+ /**
+ * Destructor.
+ */
+ ~KSecretFile();
+
+ /**
+ * Check if the secret file was constructed validly.
+ * This DOES NOT check if the file contents are valid.
+ *
+ * @return true if the object can be worked with, false if an
+ * error occurred during opening or reading/writing data.
+ */
+ bool isValid() const;
+
+ /**
+ * Close this ksecret file.
+ */
+ void close();
+
+ /**
+ * Get the current position withing the file.
+ *
+ * @return the current file position
+ */
+ qint64 pos() const;
+
+ /**
+ * Set the current position withing the file to pos.
+ *
+ * @param pos position to seek inside the underlying device
+ * @return true on success, false on error
+ */
+ bool seek(qint64 pos);
+
+ /**
+ * Read the ksecret file's magic value.
+ *
+ * @return true if the magic indicates this file is a ksecret file,
+ * false else
+ */
+ bool readMagic();
+
+ /**
+ * Write the ksecret file's magic value.
+ *
+ * @return true if the magic was written successfull, false else
+ */
+ bool writeMagic();
+
+ /**
+ * Read a UINT from the file.
+ *
+ * @param value pointer the UINT will be written to
+ * @return true if reading was successful, false else
+ */
+ bool readUint(quint32 *value);
+
+ /**
+ * Write a UINT to the file.
+ *
+ * @param value the UINT that should be written
+ * @return true if writing was successful, false else
+ */
+ bool writeUint(quint32 value);
+
+ /**
+ * Read a DATETIME from the file.
+ *
+ * @param value pointer the DATETIME will be written to
+ * @return true if reading was successful, false else
+ */
+ bool readDatetime(QDateTime *value);
+
+ /**
+ * Write a DATETIME to the file.
+ *
+ * @param value DATETIME to be written
+ * @return true if writing was successful, false else
+ */
+ bool writeDatetime(const QDateTime &value);
+
+ /**
+ * Read a BYTEARRAY from the file.
+ *
+ * @param value pointer the BYTEARRAY will be written to
+ * @return true if reading was successful, false else
+ */
+ bool readBytearray(QByteArray *value);
+
+ /**
+ * Write a BYTEARRAY to the file.
+ *
+ * @param value BYTEARRAY to be written
+ * @return true if writing was successful, false else
+ */
+ bool writeBytearray(const QByteArray &value);
+
+ /**
+ * Read a STRING from the file.
+ *
+ * @param value pointer the STRING will be written to
+ * @return true if reading was successful, false else
+ */
+ bool readString(QString *value);
+
+ /**
+ * Write a STRING to the file.
+ *
+ * @param value the STRING to be written
+ * @return true if writing was successful, false else
+ */
+ bool writeString(const QString &value);
+
+ /**
+ * Read a secret BYTEARRAY/STRING from the file.
+ *
+ * @param value pointer the secret will be written to
+ * @return true if reading was successul, false else
+ * @remarks basically the same as \sa readBytearray
+ * but writes the results into a SecureArray.
+ */
+ bool readSecret(QCA::SecureArray *value);
+
+ /**
+ * Write a secret BYTEARRAY/STRING to the file.
+ *
+ * @param value the secret to be written
+ * @return true if writing was successful, false else
+ */
+ bool writeSecret(const QCA::SecureArray &value);
+
+ /**
+ * Read an entire part of the ksecret file.
+ *
+ * @param value pointer to the bytearray that should be filled
+ * @param position position inside the ksecret file
+ * @param length length of the part to read
+ */
+ bool readPart(QByteArray *value, quint32 position, quint32 length);
+
+private:
+ QIODevice *m_device;
+ OpenMode m_mode;
+ bool m_valid;
+
+ QByteArray m_readBuffer;
+};
+
+#endif
diff --git a/backend/ksecret/ksecretitem.cpp b/backend/ksecret/ksecretitem.cpp
new file mode 100644
index 0000000..8d63d34
--- /dev/null
+++ b/backend/ksecret/ksecretitem.cpp
@@ -0,0 +1,294 @@
+/*
+ * Copyright 2010, Michael Leupold <lemma@confuego.org>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License or (at your option) version 3 or any later version
+ * accepted by the membership of KDE e.V. (or its successor approved
+ * by the membership of KDE e.V.), which shall act as a proxy
+ * defined in Section 14 of version 3 of the license.
+ *
+ * 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "ksecretitem.h"
+#include "ksecretcollection.h"
+#include "../securebuffer.h"
+
+KSecretItem::KSecretItem(const QString &id, KSecretCollection *parent)
+ : BackendItem(parent), m_collection(parent), m_id(id)
+{
+ Q_ASSERT(parent);
+}
+
+KSecretItem::~KSecretItem()
+{
+}
+
+QString KSecretItem::id() const
+{
+ return m_id;
+}
+
+BackendReturn<QString> KSecretItem::label() const
+{
+ if (isLocked()) {
+ return BackendReturn<QString>(QString(), ErrorIsLocked);
+ } else {
+ markAsUsed();
+ return m_label;
+ }
+}
+
+BackendReturn<void> KSecretItem::setLabel(const QString &label)
+{
+ if (isLocked()) {
+ return BackendReturn<void>(ErrorIsLocked);
+ } else {
+ m_label = label;
+ markAsModified();
+ return BackendReturn<void>();
+ }
+}
+
+BackendReturn<QCA::SecureArray> KSecretItem::secret() const
+{
+ if (isLocked()) {
+ return BackendReturn<QCA::SecureArray>(QCA::SecureArray(), ErrorIsLocked);
+ } else {
+ markAsUsed();
+ return m_secret;
+ }
+}
+
+BackendReturn<void> KSecretItem::setSecret(const QCA::SecureArray &secret)
+{
+ if (isLocked()) {
+ return BackendReturn<void>(ErrorIsLocked);
+ } else {
+ m_secret = secret;
+ markAsModified();
+ return BackendReturn<void>();
+ }
+}
+
+BackendReturn<QMap<QString, QString> > KSecretItem::attributes() const
+{
+ if (isLocked()) {
+ return BackendReturn<QMap<QString, QString> >(QMap<QString, QString>(), \
ErrorIsLocked); + } else {
+ markAsUsed();
+ return m_attributes;
+ }
+}
+
+BackendReturn<void> KSecretItem::setAttributes(const QMap<QString, QString> \
&attributes) +{
+ if (isLocked()) {
+ return BackendReturn<void>(ErrorIsLocked);
+ } else {
+ m_attributes = attributes;
+ markAsModified();
+ emit attributesChanged(this);
+ return BackendReturn<void>();
+ }
+}
+
+QDateTime KSecretItem::created() const
+{
+ markAsUsed();
+ return m_created;
+}
+
+QDateTime KSecretItem::modified() const
+{
+ markAsUsed();
+ return m_modified;
+}
+
+bool KSecretItem::isLocked() const
+{
+ return m_collection->isLocked();
+}
+
+bool KSecretItem::isCallImmediate(AsyncCall::AsyncType type) const
+{
+ Q_ASSERT(m_collection);
+
+ if (type == AsyncCall::AsyncDeleteItem) {
+ // delete item calls are always immediate
+ return true;
+ }
+
+ // all other relevant async calls are forwarded to the collection.
+ return m_collection->isCallImmediate(type);
+}
+
+BackendReturn<bool> KSecretItem::unlock()
+{
+ Q_ASSERT(m_collection);
+ // pass the unlock call on to the collection
+ return m_collection->unlock();
+}
+
+BackendReturn<bool> KSecretItem::lock()
+{
+ Q_ASSERT(m_collection);
+ // pass the lock call on to the collection
+ return m_collection->lock();
+}
+
+BackendReturn<bool> KSecretItem::deleteItem()
+{
+ emit itemDeleted(this);
+ deleteLater();
+ return true;
+}
+
+BackendReturn<bool> KSecretItem::changeAuthentication()
+{
+ // changing individual items' authentication is not supported
+ return BackendReturn<bool>(false, ErrorNotSupported);
+}
+
+bool KSecretItem::matches(const QMap<QString, QString> &attributes)
+{
+ QMap<QString, QString>::const_iterator it = attributes.constBegin();
+ const QMap<QString, QString>::const_iterator end = attributes.constEnd();
+ for ( ; it != end; ++it) {
+ if (!m_attributes.contains(it.key()) ||
+ m_attributes.value(it.key()) != it.value()) {
+ return false;
+ }
+ }
+ return true;
+}
+
+bool KSecretItem::deserializeUnlocked(KSecretFile &file)
+{
+ Q_ASSERT(file.isValid());
+
+ // deserialize everything to temporary variables/objects and write it
+ // into the members in one go once everything could be read successfully.
+
+ // file currently points at item-label
+ QString itemLabel;
+ QDateTime itemCreated;
+ QDateTime itemModified;
+ quint32 numAttribs;
+ if (!file.readString(&itemLabel) || !file.readDatetime(&itemCreated) ||
+ !file.readDatetime(&itemModified) || !file.readUint(&numAttribs)) {
+ return false;
+ }
+
+ // read the attributes
+ QMap<QString, QString> attributes;
+ for (quint32 i = 0; i < numAttribs; ++i) {
+ QString attribKey;
+ QString attribValue;
+ if (!file.readString(&attribKey) || !file.readString(&attribValue)) {
+ return false;
+ }
+ attributes.insert(attribKey, attribValue);
+ }
+
+ // read the secret
+ QCA::SecureArray secret;
+ if (!file.readSecret(&secret)) {
+ return false;
+ }
+
+ m_label = itemLabel;
+ m_created = itemCreated;
+ m_modified = itemModified;
+ m_attributes = attributes;
+ m_secret = secret;
+
+ return true;
+}
+
+bool KSecretItem::serializeUnlocked(KSecretFile &file)
+{
+ Q_ASSERT(file.isValid());
+
+ if (!file.writeString(m_id)) {
+ return false;
+ }
+
+ // serialize data to a temporary array
+ SecureBuffer device;
+ KSecretFile tempFile(&device, KSecretFile::Write);
+ if (!tempFile.isValid()) {
+ return false;
+ }
+
+ if (!tempFile.writeString(m_label) || !tempFile.writeDatetime(m_created) ||
+ !tempFile.writeDatetime(m_modified) || \
!tempFile.writeUint(m_attributes.size())) { + return false;
+ }
+
+ // serialize attributes
+ QMap<QString, QString>::const_iterator it = m_attributes.constBegin();
+ const QMap<QString, QString>::const_iterator end = m_attributes.constEnd();
+ for ( ; it != end; ++it) {
+ if (!tempFile.writeString(it.key()) || !tempFile.writeString(it.value())) {
+ return false;
+ }
+ }
+
+ // serialize secret
+ if (!tempFile.writeSecret(m_secret)) {
+ return false;
+ }
+
+ // now take the tempFile and write its contents to the actual file
+ if (!file.writeSecret(device.buffer())) {
+ return false;
+ }
+ return true;
+}
+
+QSet<QByteArray> KSecretItem::createHashes(const QMap<QString, QString> &attributes,
+ QCA::Hash *hash)
+{
+ Q_ASSERT(hash);
+
+ QSet<QByteArray> hashSet;
+ QMap<QString, QString>::const_iterator it = attributes.constBegin();
+ QMap<QString, QString>::const_iterator end = attributes.constEnd();
+ for ( ; it != end; ++it) {
+ hash->clear();
+ hash->update(it.key().toUtf8());
+ hash->update(it.value().toUtf8());
+ hashSet.insert(hash->final().toByteArray());
+ }
+
+ return hashSet;
+}
+
+QSet<QByteArray> KSecretItem::createAttributeHashes(QCA::Hash *hash) const
+{
+ return createHashes(m_attributes, hash);
+}
+
+void KSecretItem::markAsModified()
+{
+ m_modified = QDateTime::currentDateTime();
+ emit itemUsed(this);
+ emit itemChanged(this);
+}
+
+void KSecretItem::markAsUsed() const
+{
+ // TODO: figure out what to do. this method exists so a "close-if-unused" timer
+ // can be implemented.
+}
+
+#include "ksecretitem.moc"
diff --git a/backend/ksecret/ksecretitem.h b/backend/ksecret/ksecretitem.h
new file mode 100644
index 0000000..678db96
--- /dev/null
+++ b/backend/ksecret/ksecretitem.h
@@ -0,0 +1,253 @@
+/*
+ * Copyright 2010, Michael Leupold <lemma@confuego.org>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License or (at your option) version 3 or any later version
+ * accepted by the membership of KDE e.V. (or its successor approved
+ * by the membership of KDE e.V.), which shall act as a proxy
+ * defined in Section 14 of version 3 of the license.
+ *
+ * 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef KSECRETITEM_H
+#define KSECRETITEM_H
+
+#include "../backenditem.h"
+#include "ksecretfile.h"
+
+#include <QtCore/QSet>
+
+class KSecretCollection;
+
+/**
+ * Represents an item stored inside a ksecret file.
+ */
+class KSecretItem : public BackendItem
+{
+ Q_OBJECT
+
+public:
+ /**
+ * Constructor.
+ *
+ * @param id unique identifier of the new item
+ * @param collection collection that created this item
+ */
+ KSecretItem(const QString &id, KSecretCollection *parent);
+
+ /**
+ * Destructor.
+ */
+ ~KSecretItem();
+
+ /**
+ * The unique identifer for this item.
+ */
+ virtual QString id() const;
+
+ /**
+ * The human-readable label for this item.
+ * @todo error
+ */
+ virtual BackendReturn<QString> label() const;
+
+ /**
+ * Set the human-readable label for this item.
+ *
+ * @param label the new label for this item
+ * @todo error
+ */
+ virtual BackendReturn<void> setLabel(const QString &label);
+
+ /**
+ * Get the secret stored inside this item.
+ *
+ * @return the secret
+ * @todo this will most likely become non-const as that might make sense
+ * with certain backends me reckons.
+ */
+ virtual BackendReturn<QCA::SecureArray> secret() const;
+
+ /**
+ * Set the secret stored inside this item.
+ *
+ * @param secret the secret to store
+ */
+ virtual BackendReturn<void> setSecret(const QCA::SecureArray &secret);
+
+ /**
+ * The attributes of the item.
+ *
+ * @return the item's attributes
+ */
+ virtual BackendReturn<QMap<QString, QString> > attributes() const;
+
+ /**
+ * Set the attributes of this item.
+ *
+ * @param attributes attributes to assign to this item
+ */
+ virtual BackendReturn<void> setAttributes(const QMap<QString, QString> \
&attributes); +
+ /**
+ * The time this item was created.
+ */
+ virtual QDateTime created() const;
+
+ /**
+ * The time this item was last modified.
+ */
+ virtual QDateTime modified() const;
+
+ /**
+ * Check whether this item is locked.
+ *
+ * @return true if the item is locked, false else
+ */
+ virtual bool isLocked() const;
+
+ /**
+ * Check if a type of call can be handled immediately
+ * (synchronously) without requiring an async call.
+ *
+ * @param type the type of call to check
+ * @return true if the type of call can be handled
+ * immediately, false if an async call is
+ * required.
+ */
+ virtual bool isCallImmediate(AsyncCall::AsyncType type) const;
+
+ /**
+ * Unlock this item.
+ *
+ * @return true if the item was unlocked successfully, false if unlocking failed
+ * or an error occurred.
+ * @remarks this is only called by processCall. To set an unlock call
+ * erroneous, use the Base::setError() call.
+ */
+ virtual BackendReturn<bool> unlock();
+
+ /**
+ * Lock this item.
+ *
+ * @return true if the item was locked successfully, false if locking failed
+ * or an error occurred.
+ * @remarks this is only called by processCall. To set a lock call
+ * erroneous, use the Base::setError() call.
+ */
+ virtual BackendReturn<bool> lock();
+
+ /**
+ * Delete this item.
+ *
+ * @return true if the item was deleted, false if it wasn't or an error occurred.
+ * @remarks this is only called by processCall. To set a delete call
+ * erroneous, use the Base::setError() call.
+ */
+ virtual BackendReturn<bool> deleteItem();
+
+ /**
+ * Change this item's authentication.
+ *
+ * @return always false as TemporaryItem doesn't support authentication
+ */
+ virtual BackendReturn<bool> changeAuthentication();
+
+ /**
+ * Check whether this item matches the attributes given.
+ *
+ * @param attributes attributes to match against
+ * @return true if this item matches the attributes, false
+ * if the item doesn't match the attributes.
+ */
+ bool matches(const QMap<QString, QString> &attributes);
+
+protected:
+ /**
+ * Fill this item using the values contained in the unlocked file.
+ *
+ * @param file the file to read the values from
+ * @return true on success, false on error
+ */
+ bool deserializeUnlocked(KSecretFile &file);
+
+ /**
+ * Serialize this item's unlocked contents to the file given.
+ *
+ * @param file the file to write the values to
+ * @return true on success, false on error
+ */
+ bool serializeUnlocked(KSecretFile &file);
+
+ /**
+ * Create a list of hashes out of this item's attributes.
+ *
+ * @param hash the hash function to use
+ * @return a list of hashes for each of the item's attributes
+ * @remarks called by KSecretCollection on-demand
+ */
+ QSet<QByteArray> createAttributeHashes(QCA::Hash *hash) const;
+
+ /**
+ * Create a list of hashes out of some attributes.
+ *
+ * @param attributes the attributes to create the hashes for
+ * @param hash the hash function to use
+ * @returns a list of hashes for each of the attributes
+ */
+ static QSet<QByteArray> createHashes(const QMap<QString, QString> &attributes,
+ QCA::Hash *hash);
+
+Q_SIGNALS:
+ /**
+ * Emitted when the item has been "used" ie. one of its values has been
+ * read or written.
+ *
+ * @param item the item that was used
+ * @remarks this is used internally to implement the "close-if-unused" timer
+ */
+ void itemUsed(BackendItem *item);
+
+ /**
+ * This signal is emitted when an item's attributes change so the
+ * collection can rebuild the attribute lookup hashes related to this
+ * item.
+ *
+ * @param item Item whose attributes changed
+ */
+ void attributesChanged(KSecretItem *item);
+
+private:
+ friend class KSecretCollection;
+
+ /**
+ * Mark this item as modified and emit the \sa itemChanged signal.
+ */
+ void markAsModified();
+
+ /**
+ * Mark this item as used and emit the \sa itemUsed signal.
+ */
+ void markAsUsed() const;
+
+ KSecretCollection *m_collection;
+
+ QString m_id;
+ QString m_label;
+ QDateTime m_created;
+ QDateTime m_modified;
+ QMap<QString, QString> m_attributes;
+
+ QCA::SecureArray m_secret;
+};
+
+#endif
diff --git a/backend/temporarycollection.cpp b/backend/temporarycollection.cpp
index b9ce79a..d8cddba 100644
--- a/backend/temporarycollection.cpp
+++ b/backend/temporarycollection.cpp
@@ -125,7 +125,7 @@ BackendReturn<BackendItem*> TemporaryCollection::createItem(const \
QString &label BackendReturn<QList<BackendItem*> > foundItems = \
searchItems(attributes); if (!foundItems.isError() && foundItems.value().size() > 0) \
{ QList<BackendItem*> oldlist = foundItems.value();
- Q_FOREACH(BackendItem* olditem, oldlist) {
+ Q_FOREACH(BackendItem *olditem, oldlist) {
if (olditem->attributes().value() == attributes) {
if (replace) {
// replacing an existing item
[prev in list] [next in list] [prev in thread] [next in thread]
Configure |
About |
News |
Add a list |
Sponsored by KoreLogic