Implement 1Password Vault Import

* Support importing 1Password vaults (.opvault folders) into KDBX database
* Entry attributes are filled based on section and field name
* Expiration dates are set for entries
* Entry URL's are set from a wider array of fields
This commit is contained in:
Jonathan White 2019-05-19 17:49:48 -04:00
parent e121f4bc28
commit 125a81f2ed
45 changed files with 2578 additions and 0 deletions

View File

@ -79,6 +79,11 @@ set(keepassx_SOURCES
format/Kdbx4Reader.cpp
format/Kdbx4Writer.cpp
format/KdbxXmlWriter.cpp
format/OpData01.cpp
format/OpVaultReader.cpp
format/OpVaultReaderAttachments.cpp
format/OpVaultReaderBandEntry.cpp
format/OpVaultReaderSections.cpp
gui/AboutDialog.cpp
gui/Application.cpp
gui/CategoryListWidget.cpp
@ -103,6 +108,7 @@ set(keepassx_SOURCES
gui/MainWindow.cpp
gui/MessageBox.cpp
gui/MessageWidget.cpp
gui/OpVaultOpenWidget.cpp
gui/PasswordEdit.cpp
gui/PasswordGeneratorWidget.cpp
gui/ApplicationSettingsWidget.cpp

137
src/format/OpData01.cpp Normal file
View File

@ -0,0 +1,137 @@
/*
* Copyright (C) 2019 KeePassXC Team <team@keepassxc.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 or (at your option)
* 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 "OpData01.h"
#include "crypto/CryptoHash.h"
#include "crypto/SymmetricCipher.h"
#include <QDataStream>
#include <QDebug>
OpData01::OpData01(QObject* parent)
: QObject(parent)
{
}
OpData01::~OpData01()
{
}
bool OpData01::decodeBase64(QString const& b64String, const QByteArray& key, const QByteArray& hmacKey)
{
const QByteArray b64Bytes = QByteArray::fromBase64(b64String.toUtf8());
return decode(b64Bytes, key, hmacKey);
}
bool OpData01::decode(const QByteArray& data, const QByteArray& key, const QByteArray& hmacKey)
{
/*!
* The first 8 bytes of the data are the string opdata01.
*/
const QByteArray header("opdata01");
if (!data.startsWith(header)) {
m_errorStr = tr("Invalid OpData01, does not contain header");
return false;
}
QDataStream in(data);
in.setByteOrder(QDataStream::LittleEndian);
in.skipRawData(header.size());
/*!
* The next 8 bytes contain the length in bytes of the plaintext as a little endian unsigned 64 bit integer.
*/
qlonglong len;
in >> len;
/*!
* The next 16 bytes are the randomly chosen initialization vector.
*/
QByteArray iv(16, '\0');
int read = in.readRawData(iv.data(), 16);
if (read != 16) {
m_errorStr = tr("Unable to read all IV bytes, wanted 16 but got %1").arg(iv.size());
return false;
}
SymmetricCipher cipher(SymmetricCipher::Aes256, SymmetricCipher::Cbc, SymmetricCipher::Decrypt);
if (!cipher.init(key, iv)) {
m_errorStr = tr("Unable to init cipher for opdata01: %1").arg(cipher.errorString());
return false;
}
/*!
* The plaintext is padded using the following scheme.
*
* If the size of the plaintext is an even multiple of the block size then 1 block of random data is prepended
* to the plaintext. Otherwise, between 1 and 15 (inclusive) bytes of random data are prepended to the plaintext
* to achieve an even multiple of blocks.
*/
const int blockSize = cipher.blockSize();
int randomBytes = blockSize - (len % blockSize);
if (randomBytes == 0) {
// add random block
randomBytes = blockSize;
}
qlonglong clear_len = len + randomBytes;
QByteArray qbaCT(clear_len, '\0');
in.readRawData(qbaCT.data(), clear_len);
/*!
* The HMAC-SHA256 is computed over the entirety of the opdata including header, length, IV and ciphertext
* using a 256-bit MAC key. The 256-bit MAC is not truncated. It is appended to the ciphertext.
*/
const int hmacLen = 256 / 8;
QByteArray hmacSig(hmacLen, '\0'); // 256 / 8, '\0');
in.readRawData(hmacSig.data(), hmacLen);
if (hmacSig.size() != hmacLen) {
m_errorStr = tr("Unable to read all HMAC signature bytes");
return false;
}
const QByteArray hmacData = data.mid(0, data.size() - hmacSig.size());
const QByteArray actualHmac = CryptoHash::hmac(hmacData, hmacKey, CryptoHash::Algorithm::Sha256);
if (actualHmac != hmacSig) {
m_errorStr = tr("Malformed OpData01 due to a failed HMAC");
return false;
}
if (!cipher.processInPlace(qbaCT)) {
m_errorStr = tr("Unable to process clearText in place");
return false;
}
// Remove random bytes
const QByteArray& clearText = qbaCT.mid(randomBytes);
if (clearText.size() != len) {
m_errorStr = tr("Expected %1 bytes of clear-text, found %2").arg(len, clearText.size());
return false;
}
m_clearText = clearText;
return true;
}
QByteArray OpData01::getClearText()
{
return m_clearText;
}
QString OpData01::errorString()
{
return m_errorStr;
}

57
src/format/OpData01.h Normal file
View File

@ -0,0 +1,57 @@
/*
* Copyright (C) 2019 KeePassXC Team <team@keepassxc.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 or (at your option)
* 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 KEEPASSXC_OPDATA01_H
#define KEEPASSXC_OPDATA01_H
#include <QObject>
/*!
* Packages and transports the AgileBits data structure called \c OpData01
* used to encypt and provide HMAC for encrypted data.
* \sa https://support.1password.com/opvault-design/#opdata01
*/
class OpData01 : public QObject
{
public:
explicit OpData01(QObject* parent = nullptr);
~OpData01() override;
/*!
* The convenience equivalent of decode01(OpData01,const QByteArray,const QByteArray,const QByteArray) that simply
* decodes the provided base64 string into its underlying \c QByteArray.
*/
bool decodeBase64(QString const& b64String, const QByteArray& key, const QByteArray& hmacKey);
/*!
* Populates the given \code OpData01 structure by decoding the provided blob of data,
* using the given key and then verifies using the given HMAC key.
* \returns true if things went well and \code m_clearText is usable, false and \code m_errorStr will contain
* details.
*/
bool decode(const QByteArray& data, const QByteArray& key, const QByteArray& hmacKey);
QByteArray getClearText();
QString errorString();
private:
QByteArray m_clearText;
QString m_errorStr;
};
#endif // KEEPASSXC_OPDATA01_H

View File

@ -0,0 +1,478 @@
/*
* Copyright (C) 2019 KeePassXC Team <team@keepassxc.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 or (at your option)
* 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 "OpVaultReader.h"
#include "OpData01.h"
#include "core/Group.h"
#include "core/Tools.h"
#include "crypto/CryptoHash.h"
#include "crypto/SymmetricCipher.h"
#include "keys/PasswordKey.h"
#include <QDebug>
#include <QJsonDocument>
#include <QJsonObject>
#include <QUuid>
#include <gcrypt.h>
OpVaultReader::OpVaultReader(QObject* parent)
: QObject(parent)
, m_error(false)
{
}
OpVaultReader::~OpVaultReader()
{
}
Database* OpVaultReader::readDatabase(QDir& opdataDir, const QString& password)
{
if (!opdataDir.exists()) {
m_error = true;
m_errorStr = tr("Directory .opvault must exist");
return nullptr;
}
if (!opdataDir.isReadable()) {
m_error = true;
m_errorStr = tr("Directory .opvault must be readable");
return nullptr;
}
// https://support.1password.com/opvault-design/#directory-layout
QDir defaultDir = QDir(opdataDir);
if (!defaultDir.cd("default")) {
m_error = true;
m_errorStr = tr("Directory .opvault/default must exist");
return nullptr;
}
if (!defaultDir.isReadable()) {
m_error = true;
m_errorStr = tr("Directory .opvault/default must be readable");
return nullptr;
}
auto key = QSharedPointer<CompositeKey>::create();
key->addKey(QSharedPointer<PasswordKey>::create(password));
QScopedPointer<Database> db(new Database());
db->setKdf(KeePass2::uuidToKdf(KeePass2::KDF_ARGON2));
db->setCipher(KeePass2::CIPHER_AES256);
db->setKey(key, true, false);
db->metadata()->setName(opdataDir.dirName());
auto rootGroup = db->rootGroup();
rootGroup->setTimeInfo({});
rootGroup->setUpdateTimeinfo(false);
rootGroup->setName("OPVault Root Group");
rootGroup->setUuid(QUuid::createUuid());
populateCategoryGroups(rootGroup);
QFile profileJsFile(defaultDir.absoluteFilePath("profile.js"));
QJsonObject profileJson = readAndAssertJsonFile(profileJsFile, "var profile=", ";");
if (profileJson.isEmpty()) {
return nullptr;
}
if (!processProfileJson(profileJson, password, rootGroup)) {
zeroKeys();
return nullptr;
}
if (profileJson.contains("uuid") and profileJson["uuid"].isString()) {
rootGroup->setUuid(Tools::hexToUuid(profileJson["uuid"].toString()));
}
QFile foldersJsFile(defaultDir.filePath("folders.js"));
if (foldersJsFile.exists()) {
QJsonObject foldersJs = readAndAssertJsonFile(foldersJsFile, "loadFolders(", ");");
if (!processFolderJson(foldersJs, rootGroup)) {
zeroKeys();
return nullptr;
}
}
const QString bandChars("0123456789ABCDEF");
QString bandPattern("band_%1.js");
for (QChar ch : bandChars) {
QFile bandFile(defaultDir.filePath(bandPattern.arg(ch)));
if (!bandFile.exists()) {
qWarning() << "Skipping missing file \"" << bandFile.fileName() << "\"";
continue;
}
// https://support.1password.com/opvault-design/#band-files
QJsonObject bandJs = readAndAssertJsonFile(bandFile, "ld(", ");");
const QStringList keys = bandJs.keys();
for (const QString& entryKey : keys) {
const QJsonObject bandEnt = bandJs[entryKey].toObject();
const QString uuid = bandEnt["uuid"].toString();
if (entryKey != uuid) {
qWarning() << QString("Mismatched Entry UUID, its JSON key <<%1>> and its UUID <<%2>>")
.arg(entryKey)
.arg(uuid);
}
QStringList requiredKeys({"d", "k", "hmac"});
bool ok = true;
for (const QString& requiredKey : asConst(requiredKeys)) {
if (!bandEnt.contains(requiredKey)) {
qCritical() << "Skipping malformed Entry UUID " << uuid << " without key " << requiredKey;
ok = false;
break;
}
}
if (!ok) {
continue;
}
// https://support.1password.com/opvault-design/#items
Entry* entry = processBandEntry(bandEnt, defaultDir, rootGroup);
if (!entry) {
qWarning() << "Unable to process Band Entry " << uuid;
}
}
}
zeroKeys();
return db.take();
}
bool OpVaultReader::hasError()
{
return m_error;
}
QString OpVaultReader::errorString()
{
return m_errorStr;
}
bool OpVaultReader::processProfileJson(QJsonObject& profileJson, const QString& password, Group* rootGroup)
{
unsigned long iterations = profileJson["iterations"].toInt();
// QString lastUpdatedBy = profileJson["lastUpdatedBy"].toString();
QString masterKeyB64 = profileJson["masterKey"].toString();
QString overviewKeyB64 = profileJson["overviewKey"].toString();
// QString profileName = profileJs["profileName"].toString();
QByteArray salt;
{
QString saltB64 = profileJson["salt"].toString();
salt = QByteArray::fromBase64(saltB64.toUtf8());
}
auto rootGroupTime = rootGroup->timeInfo();
auto createdAt = static_cast<uint>(profileJson["createdAt"].toInt());
rootGroupTime.setCreationTime(QDateTime::fromTime_t(createdAt, Qt::UTC));
auto updatedAt = static_cast<uint>(profileJson["updatedAt"].toInt());
rootGroupTime.setLastModificationTime(QDateTime::fromTime_t(updatedAt, Qt::UTC));
rootGroup->setUuid(Tools::hexToUuid(profileJson["uuid"].toString()));
const auto derivedKeys = deriveKeysFromPassPhrase(salt, password, iterations);
if (derivedKeys->error) {
m_error = true;
m_errorStr = derivedKeys->errorStr;
delete derivedKeys;
return false;
}
QByteArray encKey = derivedKeys->encrypt;
QByteArray hmacKey = derivedKeys->hmac;
delete derivedKeys;
auto masterKeys = decodeB64CompositeKeys(masterKeyB64, encKey, hmacKey);
if (masterKeys->error) {
m_error = true;
m_errorStr = masterKeys->errorStr;
delete masterKeys;
return false;
}
m_masterKey = masterKeys->encrypt;
m_masterHmacKey = masterKeys->hmac;
delete masterKeys;
auto overviewKeys = decodeB64CompositeKeys(overviewKeyB64, encKey, hmacKey);
if (overviewKeys->error) {
m_error = true;
m_errorStr = overviewKeys->errorStr;
delete overviewKeys;
return false;
}
m_overviewKey = overviewKeys->encrypt;
m_overviewHmacKey = overviewKeys->hmac;
delete overviewKeys;
return true;
}
bool OpVaultReader::processFolderJson(QJsonObject& foldersJson, Group* rootGroup)
{
const QStringList keys = foldersJson.keys();
bool result = true;
for (const QString& key : keys) {
const QJsonValueRef& folderValue = foldersJson[key];
if (!folderValue.isObject()) {
qWarning() << "Found non-Object folder with key \"" << key << "\"";
continue;
}
const QJsonObject folder = folderValue.toObject();
QJsonObject overviewJs;
const QString overviewStr = folder.value("overview").toString();
OpData01 foldOverview01;
if (!foldOverview01.decodeBase64(overviewStr, m_overviewKey, m_overviewHmacKey)) {
qCritical() << "Unable to decipher folder UUID \"" << key << "\": " << foldOverview01.errorString();
result = false;
continue;
}
auto foldOverview = foldOverview01.getClearText();
QJsonDocument fOverJSON = QJsonDocument::fromJson(foldOverview);
overviewJs = fOverJSON.object();
const QString& folderTitle = overviewJs["title"].toString();
auto myGroup = new Group();
myGroup->setParent(rootGroup);
myGroup->setName(folderTitle);
if (folder.contains("uuid")) {
myGroup->setUuid(Tools::hexToUuid(folder["uuid"].toString()));
}
if (overviewJs.contains("smart") && overviewJs["smart"].toBool()) {
if (!overviewJs.contains("predicate_b64")) {
const QString& errMsg =
QString(R"(Expected a predicate in smart folder[uuid="%1"; title="%2"]))").arg(key, folderTitle);
qWarning() << errMsg;
myGroup->setNotes(errMsg);
} else {
QByteArray pB64 = QByteArray::fromBase64(overviewJs["predicate_b64"].toString().toUtf8());
myGroup->setNotes(pB64.toHex());
}
}
TimeInfo ti;
bool timeInfoOk = false;
if (folder.contains("created")) {
auto createdTime = static_cast<uint>(folder["created"].toInt());
ti.setCreationTime(QDateTime::fromTime_t(createdTime, Qt::UTC));
timeInfoOk = true;
}
if (folder.contains("updated")) {
auto updateTime = static_cast<uint>(folder["updated"].toInt());
ti.setLastModificationTime(QDateTime::fromTime_t(updateTime, Qt::UTC));
timeInfoOk = true;
}
// "tx" is modified by sync, not by user; maybe a custom attribute?
if (timeInfoOk) {
myGroup->setTimeInfo(ti);
}
}
return result;
}
/*
* Asserts that the given file is an existing file, able to be read, contains JSON, and that
* the payload is a JSON object. Currently it just returns an empty QJsonObject as a means of
* indicating the error, although it will qCritical() if unable to actually open the file for reading.
*
* @param file the path containing the JSON file
* @param stripLeading any leading characters that might be present in file which should be removed
* @param stripTrailing the trailing characters that might be present in file which should be removed
* @return
*/
QJsonObject OpVaultReader::readAndAssertJsonFile(QFile& file, const QString& stripLeading, const QString& stripTrailing)
{
QByteArray filePayload;
const QFileInfo& fileInfo = QFileInfo(file);
auto absFilePath = fileInfo.absoluteFilePath();
if (!fileInfo.exists()) {
qCritical() << QString("File \"%1\" must exist").arg(absFilePath);
return QJsonObject();
}
if (!fileInfo.isReadable()) {
qCritical() << QString("File \"%1\" must be readable").arg(absFilePath);
return QJsonObject();
}
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
qCritical() << QString("Unable to open \"%1\" readonly+text").arg(absFilePath);
}
filePayload = file.readAll();
file.close();
if (!stripLeading.isEmpty()) {
QByteArray prefix = stripLeading.toUtf8();
if (filePayload.startsWith(prefix)) {
filePayload = filePayload.remove(0, prefix.size());
}
}
if (!stripTrailing.isEmpty()) {
QByteArray suffix = stripTrailing.toUtf8();
if (filePayload.endsWith(suffix)) {
const int delBytes = suffix.size();
filePayload = filePayload.remove(filePayload.length() - delBytes, delBytes);
}
}
QJsonParseError* error = Q_NULLPTR;
QJsonDocument jDoc = QJsonDocument::fromJson(filePayload, error);
if (!jDoc.isObject()) {
qCritical() << "Expected " << filePayload << "to be a JSON Object";
return QJsonObject();
}
return jDoc.object();
}
/* Convenience method for calling decodeCompositeKeys when you have a base64 encrypted composite key. */
OpVaultReader::DerivedKeyHMAC*
OpVaultReader::decodeB64CompositeKeys(const QString& b64, const QByteArray& encKey, const QByteArray& hmacKey)
{
auto result = new DerivedKeyHMAC();
OpData01 keyKey01;
if (!keyKey01.decodeBase64(b64, encKey, hmacKey)) {
result->error = true;
result->errorStr = tr("Unable to decode masterKey: %1").arg(keyKey01.errorString());
return result;
}
const QByteArray keyKey = keyKey01.getClearText();
return decodeCompositeKeys(keyKey);
}
/*
* Given a string of bytes, decompose it into its constituent parts, an encryption key and a HMAC key.
* The plaintext of the masterKey is 256 bytes of data selected randomly when the keychain was first created.
*
* The 256 byte (2048 bit) plaintext content of the masterKey is then hashed with SHA-512.
* The first 32 bytes (256-bits) of the resulting hash are the master encryption key,
* and the second 32 bytes are the master hmac key.
*/
OpVaultReader::DerivedKeyHMAC* OpVaultReader::decodeCompositeKeys(const QByteArray& keyKey)
{
const int encKeySize = 256 / 8;
const int hmacKeySize = 256 / 8;
const int digestSize = encKeySize + hmacKeySize;
auto result = new DerivedKeyHMAC;
result->error = false;
result->encrypt = QByteArray(encKeySize, '\0');
result->hmac = QByteArray(hmacKeySize, '\0');
const char* buffer_vp = keyKey.data();
auto buf_len = size_t(keyKey.size());
const int algo = GCRY_MD_SHA512;
unsigned char digest[digestSize];
gcry_md_hash_buffer(algo, digest, buffer_vp, buf_len);
unsigned char* cp = digest;
for (int i = 0, len = encKeySize; i < len; ++i) {
result->encrypt[i] = *(cp++);
}
for (int i = 0, len = hmacKeySize; i < len; ++i) {
result->hmac[i] = *(cp++);
}
return result;
}
/*
* Translates the provided salt and passphrase into a derived set of keys, one for encryption
* and one for use as a HMAC key. See https://support.1password.com/opvault-design/#key-derivation
* @param iterations the number of rounds to apply the derivation formula
* @return a non-null structure containing either the error or the two password-derived keys
*/
OpVaultReader::DerivedKeyHMAC*
OpVaultReader::deriveKeysFromPassPhrase(QByteArray& salt, const QString& password, unsigned long iterations)
{
const int derivedEncKeySize = 256 / 8;
const int derivedMACSize = 256 / 8;
const int keysize = derivedEncKeySize + derivedMACSize;
auto result = new DerivedKeyHMAC;
result->error = false;
QByteArray keybuffer(keysize, '\0');
auto err = gcry_kdf_derive(password.toUtf8().constData(),
password.size(),
GCRY_KDF_PBKDF2,
GCRY_MD_SHA512,
salt.constData(),
salt.size(),
iterations,
keysize,
keybuffer.data());
if (err != 0) {
result->error = true;
result->errorStr = tr("Unable to derive master key: %1").arg(gcry_strerror(err));
return result;
}
if (keysize != keybuffer.size()) {
qWarning() << "Calling PBKDF2(keysize=" << keysize << "yielded" << keybuffer.size() << "bytes";
}
QByteArray::const_iterator it = keybuffer.cbegin();
result->encrypt = QByteArray(derivedEncKeySize, '\0');
for (int i = 0, len = derivedEncKeySize; i < len && it != keybuffer.cend(); ++i, ++it) {
result->encrypt[i] = *it;
}
result->hmac = QByteArray(derivedMACSize, '\0');
for (int i = 0; i < derivedMACSize && it != keybuffer.cend(); ++i, ++it) {
result->hmac[i] = *it;
}
return result;
}
/*!
* \sa https://support.1password.com/opvault-design/#category
*/
void OpVaultReader::populateCategoryGroups(Group* rootGroup)
{
QMap<QString, QString> categoryMap;
categoryMap.insert("001", "Login");
categoryMap.insert("002", "Credit Card");
categoryMap.insert("003", "Secure Note");
categoryMap.insert("004", "Identity");
categoryMap.insert("005", "Password");
categoryMap.insert("099", "Tombstone");
categoryMap.insert("100", "Software License");
categoryMap.insert("101", "Bank Account");
categoryMap.insert("102", "Database");
categoryMap.insert("103", "Driver License");
categoryMap.insert("104", "Outdoor License");
categoryMap.insert("105", "Membership");
categoryMap.insert("106", "Passport");
categoryMap.insert("107", "Rewards");
categoryMap.insert("108", "SSN");
categoryMap.insert("109", "Router");
categoryMap.insert("110", "Server");
categoryMap.insert("111", "Email");
for (const QString& catNum : categoryMap.keys()) {
const QString& category = categoryMap[catNum];
auto g = new Group();
g->setName(category);
g->setProperty("code", catNum);
g->setUpdateTimeinfo(false);
// maybe make these stable, so folks can depend on them?
g->setUuid(QUuid::createUuid());
g->setParent(rootGroup);
}
}
void OpVaultReader::zeroKeys()
{
m_masterKey.fill('\0');
m_masterHmacKey.fill('\0');
m_overviewKey.fill('\0');
m_overviewHmacKey.fill('\0');
}

119
src/format/OpVaultReader.h Normal file
View File

@ -0,0 +1,119 @@
/*
* Copyright (C) 2019 KeePassXC Team <team@keepassxc.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 or (at your option)
* 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 OPVAULT_READER_H_
#define OPVAULT_READER_H_
#include <QDir>
#include "core/Database.h"
#include "core/Metadata.h"
/*!
* Imports a directory in the 1Password \c opvault format into a \c Database.
* \sa https://support.1password.com/opvault-overview/
* \sa https://support.1password.com/opvault-design/
* \sa https://cache.agilebits.com/security-kb/freddy-2013-12-04.tar.gz is the sample data used to test this class,
* and its password is \c freddy
*/
class OpVaultReader : public QObject
{
Q_OBJECT
public:
explicit OpVaultReader(QObject* parent = nullptr);
~OpVaultReader() override;
Database* readDatabase(QDir& opdataDir, const QString& password);
bool hasError();
QString errorString();
private:
struct DerivedKeyHMAC
{
QByteArray encrypt;
QByteArray hmac;
bool error;
QString errorStr;
};
QJsonObject readAndAssertJsonFile(QFile& file, const QString& stripLeading, const QString& stripTrailing);
DerivedKeyHMAC* deriveKeysFromPassPhrase(QByteArray& salt, const QString& password, unsigned long iterations);
DerivedKeyHMAC* decodeB64CompositeKeys(const QString& b64, const QByteArray& encKey, const QByteArray& hmacKey);
DerivedKeyHMAC* decodeCompositeKeys(const QByteArray& keyKey);
/*!
* \sa https://support.1password.com/opvault-design/#profile-js
* @param profileJson the contents of \c profile.js
* @return \c true if the profile data was decrypted successfully, \c false otherwise
*/
bool processProfileJson(QJsonObject& profileJson, const QString& password, Group* rootGroup);
/*!
* \sa https://support.1password.com/opvault-design/#folders-js
* @param foldersJson the map from a folder UUID to its data (name and any smart query)
* @return \c true if the folder data was decrypted successfully, \c false otherwise
*/
bool processFolderJson(QJsonObject& foldersJson, Group* rootGroup);
/*!
* Decrypts the provided band object into its interior structure,
* as well as the encryption key and HMAC key declared therein,
* which are used to decrypt the attachments, also.
* @returns \c nullptr if unable to do the decryption, otherwise the interior object and its keys
*/
bool decryptBandEntry(const QJsonObject& bandEntry, QJsonObject& data, QByteArray& key, QByteArray& hmacKey);
Entry* processBandEntry(const QJsonObject& bandEntry, const QDir& attachmentDir, Group* rootGroup);
bool readAttachment(const QString& filePath,
const QByteArray& itemKey,
const QByteArray& itemHmacKey,
QJsonObject& metadata,
QByteArray& payload);
void fillAttachment(Entry* entry,
const QFileInfo& attachmentFileInfo,
const QByteArray& entryKey,
const QByteArray& entryHmacKey);
void fillAttachments(Entry* entry,
const QDir& attachmentDir,
const QByteArray& entryKey,
const QByteArray& entryHmacKey);
bool fillAttributes(Entry* entry, const QJsonObject& bandEntry);
void fillFromSection(Entry* entry, const QJsonObject& section);
void fillFromSectionField(Entry* entry, const QString& sectionName, QJsonObject& field);
QString resolveAttributeName(const QString& section, const QString& name, const QString& text);
void populateCategoryGroups(Group* rootGroup);
/*! Used to blank the memory after the keys have been used. */
void zeroKeys();
bool m_error;
QString m_errorStr;
QByteArray m_masterKey;
QByteArray m_masterHmacKey;
/*! Used to decrypt overview text, such as folder names. */
QByteArray m_overviewKey;
QByteArray m_overviewHmacKey;
friend class TestOpVaultReader;
};
#endif /* OPVAULT_READER_H_ */

View File

@ -0,0 +1,250 @@
/*
* Copyright (C) 2019 KeePassXC Team <team@keepassxc.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 or (at your option)
* 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 "OpData01.h"
#include "OpVaultReader.h"
#include "core/Group.h"
#include "core/Tools.h"
#include <QDebug>
#include <QJsonDocument>
#include <QJsonObject>
#include <QUuid>
/*!
* This will \c qCritical() if unable to open the file for reading.
* @param file the \c .attachment file to decode
* @return \c nullptr if unable to take action, else a pair of metadata and the actual attachment bits
* \sa https://support.1password.com/opvault-design/#attachments
*/
bool OpVaultReader::readAttachment(const QString& filePath,
const QByteArray& itemKey,
const QByteArray& itemHmacKey,
QJsonObject& metadata,
QByteArray& payload)
{
QFile file(filePath);
if (!file.open(QIODevice::ReadOnly)) {
qCritical() << QString("Unable to open \"%s\" for reading").arg(file.fileName());
return false;
}
QString magic("OPCLDAT");
QByteArray magicBytes = file.read(7);
if (magicBytes != magic.toUtf8()) {
qCritical() << "Expected OPCLDAT but found <<" << magicBytes.toHex() << ">>";
return false;
}
QByteArray version = file.read(1);
if (version[0] != '\001' && version[0] != '\002') {
qCritical() << "Unexpected version number; wanted 1 or 2, got <<" << version << ">>";
return false;
}
const QByteArray& metadataLenBytes = file.read(2);
if (metadataLenBytes.size() != 2) {
qCritical() << "Unable to read all metadata length bytes; wanted 2 bytes, got " << metadataLenBytes.size()
<< ": <<" << metadataLenBytes.toHex() << ">>";
return false;
}
const auto b0 = static_cast<unsigned char>(metadataLenBytes[0]);
const auto b1 = static_cast<unsigned char>(metadataLenBytes[1]);
int metadataLen = ((0xFF & b1) << 8) | (0xFF & b0);
// no really: it's labeled "Junk" in the spec
int junkBytesRead = file.read(2).size();
if (junkBytesRead != 2) {
qCritical() << "Unable to read all \"junk\" bytes; wanted 2 bytes, got " << junkBytesRead;
return false;
}
const QByteArray& iconLenBytes = file.read(4);
if (iconLenBytes.size() != 4) {
qCritical() << "Unable to read all \"iconLen\" bytes; wanted 4 bytes, got " << iconLenBytes.size();
return false;
}
int iconLen = 0;
for (int i = 0, len = iconLenBytes.size(); i < len; ++i) {
char ch = iconLenBytes[i];
auto b = static_cast<unsigned char>(ch & 0xFF);
iconLen = (b << (i * 8)) | iconLen;
}
QByteArray metadataJsonBytes = file.read(metadataLen);
if (metadataJsonBytes.size() != metadataLen) {
qCritical() << "Unable to read all bytes of metadata JSON; wanted " << metadataLen << "but read "
<< metadataJsonBytes.size();
return false;
}
QByteArray iconBytes = file.read(iconLen);
if (iconBytes.size() != iconLen) {
qCritical() << "Unable to read all icon bytes; wanted " << iconLen << "but read " << iconBytes.size();
// apologies for the icon being fatal, but it would take some gear-turning
// to re-sync where in the attach header we are
return false;
}
// we don't actually _care_ what the icon bytes are,
// but they damn well better be valid opdata01 and pass its HMAC
OpData01 icon01;
if (!icon01.decode(iconBytes, itemKey, itemHmacKey)) {
qCritical() << "Unable to decipher attachment icon in " << filePath << ": " << icon01.errorString();
return false;
}
QJsonParseError jsError;
QJsonDocument jDoc = QJsonDocument::fromJson(metadataJsonBytes, &jsError);
if (jsError.error != QJsonParseError::ParseError::NoError) {
qCritical() << "Found invalid attachment metadata JSON at offset " << jsError.offset << ": error("
<< jsError.error << "): " << jsError.errorString() << "\n<<" << metadataJsonBytes << ">>";
return false;
}
if (!jDoc.isObject()) {
qCritical() << "Expected " << metadataJsonBytes << "to be a JSON Object";
return false;
}
metadata = jDoc.object();
if (metadata.contains("trashed") && metadata["trashed"].toBool()) {
return false;
}
if (!metadata.contains("contentsSize")) {
qWarning() << "Expected attachment metadata to contain \"contentsSize\" but nope: " << metadata;
return false;
} else if (!metadata["contentsSize"].isDouble()) {
qWarning() << "Expected attachment metadata to contain numeric \"contentsSize\" but nope: " << metadata;
return false;
}
int bytesLen = metadata["contentsSize"].toInt();
const QByteArray encData = file.readAll();
if (encData.size() < bytesLen) {
qCritical() << "Unable to read all of the attachment payload; wanted " << bytesLen << "but got"
<< encData.size();
return false;
}
OpData01 att01;
if (!att01.decode(encData, itemKey, itemHmacKey)) {
qCritical() << "Unable to decipher attachment payload: " << att01.errorString();
return false;
}
payload = att01.getClearText();
return true;
}
/*!
* \sa https://support.1password.com/opvault-design/#attachments
*/
void OpVaultReader::fillAttachments(Entry* entry,
const QDir& attachmentDir,
const QByteArray& entryKey,
const QByteArray& entryHmacKey)
{
/*!
* Attachment files are named with the UUID of the item that they are attached to followed by an underscore
* and then followed by the UUID of the attachment itself. The file is then given the extension .attachment.
*/
auto fileFilter = QString("%1_*.attachment").arg(entry->uuidToHex().toUpper());
const auto& attachInfoList = attachmentDir.entryInfoList(QStringList() << fileFilter, QDir::Files);
int attachmentCount = attachInfoList.size();
if (attachmentCount == 0) {
return;
}
for (const auto& info : attachInfoList) {
if (!info.isReadable()) {
qCritical() << QString("Attachment file \"%1\" is not readable").arg(info.absoluteFilePath());
continue;
}
fillAttachment(entry, info, entryKey, entryHmacKey);
}
}
void OpVaultReader::fillAttachment(Entry* entry,
const QFileInfo& info,
const QByteArray& entryKey,
const QByteArray& entryHmacKey)
{
QJsonObject attachMetadata;
QByteArray attachPayload;
if (!readAttachment(info.absoluteFilePath(), entryKey, entryHmacKey, attachMetadata, attachPayload)) {
return;
}
if (!attachMetadata.contains("overview")) {
qWarning() << "Expected \"overview\" in attachment metadata";
return;
}
const QString& overB64 = attachMetadata["overview"].toString();
OpData01 over01;
if (over01.decodeBase64(overB64, m_overviewKey, m_overviewHmacKey)) {
QByteArray overviewJson = over01.getClearText();
QJsonDocument overDoc = QJsonDocument::fromJson(overviewJson);
if (overDoc.isObject()) {
QJsonObject overObj = overDoc.object();
attachMetadata.remove("overview");
for (QString& key : overObj.keys()) {
const QJsonValueRef& value = overObj[key];
QString insertAs = key;
for (int aa = 0; attachMetadata.contains(insertAs) && aa < 5; ++aa) {
insertAs = QString("%1_%2").arg(key, aa);
}
attachMetadata[insertAs] = value;
}
} else {
qWarning() << "Expected JSON Object in \"overview\" but nope: " << overDoc;
}
} else {
qCritical()
<< QString("Unable to decode attach.overview for \"%1\": %2").arg(info.fileName(), over01.errorString());
}
QByteArray payload;
payload.append(QString("attachment file is actually %1 bytes\n").arg(info.size()).toUtf8());
for (QString& key : attachMetadata.keys()) {
const QJsonValueRef& value = attachMetadata[key];
QByteArray valueBytes;
if (value.isString()) {
valueBytes = value.toString().toUtf8();
} else if (value.isDouble()) {
valueBytes = QString("%1").arg(value.toInt()).toUtf8();
} else if (value.isBool()) {
valueBytes = value.toBool() ? "true" : "false";
} else {
valueBytes = QString("Unexpected metadata type in attachment: %1").arg(value.type()).toUtf8();
}
payload.append(key.toUtf8()).append(":=").append(valueBytes).append("\n");
}
QString attachKey = info.baseName();
if (attachMetadata.contains("filename")) {
QJsonValueRef attFilename = attachMetadata["filename"];
if (attFilename.isString()) {
attachKey = attFilename.toString();
} else {
qWarning() << QString("Unexpected type of attachment \"filename\": %1").arg(attFilename.type());
}
}
entry->attachments()->set(attachKey, attachPayload);
}

View File

@ -0,0 +1,266 @@
/*
* Copyright (C) 2019 KeePassXC Team <team@keepassxc.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 or (at your option)
* 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 "OpData01.h"
#include "OpVaultReader.h"
#include "core/Group.h"
#include "core/Tools.h"
#include "crypto/CryptoHash.h"
#include "crypto/SymmetricCipher.h"
#include <QDebug>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QUuid>
bool OpVaultReader::decryptBandEntry(const QJsonObject& bandEntry,
QJsonObject& data,
QByteArray& key,
QByteArray& hmacKey)
{
if (!bandEntry.contains("d")) {
qWarning() << "Band entries must contain a \"d\" key: " << bandEntry.keys();
return false;
}
if (!bandEntry.contains("k")) {
qWarning() << "Band entries must contain a \"k\" key: " << bandEntry.keys();
return false;
}
const QString uuid = bandEntry.value("uuid").toString();
/*!
* This is the encrypted item and MAC keys.
* It is encrypted with the master encryption key and authenticated with the master MAC key.
*
* The last 32 bytes comprise the HMAC-SHA256 of the IV and the encrypted data.
* The MAC is computed with the master MAC key.
* The data before the MAC is the AES-CBC encrypted item keys using unique random 16-byte IV.
* \code
* uint8_t crypto_key[32];
* uint8_t mac_key[32];
* \endcode
* \sa https://support.1password.com/opvault-design/#k
*/
const QString& entKStr = bandEntry["k"].toString();
QByteArray kBA = QByteArray::fromBase64(entKStr.toUtf8());
const int wantKsize = 16 + 32 + 32 + 32;
if (kBA.size() != wantKsize) {
qCritical("Malformed \"k\" size; expected %d got %d\n", wantKsize, kBA.size());
return false;
}
QByteArray hmacSig = kBA.mid(kBA.size() - 32, 32);
const QByteArray& realHmacSig =
CryptoHash::hmac(kBA.mid(0, kBA.size() - hmacSig.size()), m_masterHmacKey, CryptoHash::Sha256);
if (realHmacSig != hmacSig) {
qCritical() << QString(R"(Entry "k" failed its HMAC in UUID "%1", wanted "%2" got "%3")")
.arg(uuid)
.arg(QString::fromUtf8(hmacSig.toHex()))
.arg(QString::fromUtf8(realHmacSig));
return false;
}
QByteArray iv = kBA.mid(0, 16);
QByteArray keyAndMacKey = kBA.mid(iv.size(), 64);
SymmetricCipher cipher(SymmetricCipher::Aes256, SymmetricCipher::Cbc, SymmetricCipher::Decrypt);
if (!cipher.init(m_masterKey, iv)) {
qCritical() << "Unable to init cipher using masterKey in UUID " << uuid;
return false;
}
if (!cipher.processInPlace(keyAndMacKey)) {
qCritical() << "Unable to decipher \"k\"(key+hmac) in UUID " << uuid;
return false;
}
key = keyAndMacKey.mid(0, 32);
hmacKey = keyAndMacKey.mid(32);
QString dKeyB64 = bandEntry.value("d").toString();
OpData01 entD01;
if (!entD01.decodeBase64(dKeyB64, key, hmacKey)) {
qCritical() << R"(Unable to decipher "d" in UUID ")" << uuid << "\": " << entD01.errorString();
return false;
}
auto clearText = entD01.getClearText();
data = QJsonDocument::fromJson(clearText).object();
return true;
}
Entry* OpVaultReader::processBandEntry(const QJsonObject& bandEntry, const QDir& attachmentDir, Group* rootGroup)
{
const QString uuid = bandEntry.value("uuid").toString();
if (!(uuid.size() == 32 || uuid.size() == 36)) {
qWarning() << QString("Skipping suspicious band UUID <<%1>> with length %2").arg(uuid).arg(uuid.size());
return nullptr;
}
const auto entry = new Entry();
if (bandEntry.contains("category")) {
const QJsonValue& categoryValue = bandEntry["category"];
if (categoryValue.isString()) {
bool found = false;
const QString category = categoryValue.toString();
for (Group* group : rootGroup->children()) {
const QVariant& groupCode = group->property("code");
if (category == groupCode.toString()) {
entry->setGroup(group);
found = true;
break;
}
}
if (!found) {
qWarning() << QString("Unable to place Entry.Category \"%1\" so using the Root instead").arg(category);
entry->setGroup(rootGroup);
}
} else {
qWarning() << QString(R"(Skipping non-String Category type "%1" in UUID "%2")")
.arg(categoryValue.type())
.arg(uuid);
entry->setGroup(rootGroup);
}
} else {
qWarning() << "Using the root group because the entry is category-less: <<\n"
<< bandEntry << "\n>> in UUID " << uuid;
entry->setGroup(rootGroup);
}
entry->setUpdateTimeinfo(false);
TimeInfo ti;
bool timeInfoOk = false;
if (bandEntry.contains("created")) {
auto createdTime = static_cast<uint>(bandEntry["created"].toInt());
ti.setCreationTime(QDateTime::fromTime_t(createdTime, Qt::UTC));
timeInfoOk = true;
}
if (bandEntry.contains("updated")) {
auto updateTime = static_cast<uint>(bandEntry["updated"].toInt());
ti.setLastModificationTime(QDateTime::fromTime_t(updateTime, Qt::UTC));
timeInfoOk = true;
}
// "tx" is modified by sync, not by user; maybe a custom attribute?
if (timeInfoOk) {
entry->setTimeInfo(ti);
}
entry->setUuid(Tools::hexToUuid(uuid));
if (!fillAttributes(entry, bandEntry)) {
delete entry;
return nullptr;
}
QJsonObject data;
QByteArray entryKey;
QByteArray entryHmacKey;
if (!decryptBandEntry(bandEntry, data, entryKey, entryHmacKey)) {
return nullptr;
}
if (data.contains("notesPlain")) {
entry->setNotes(data.value("notesPlain").toString());
}
// it seems sometimes the password is a top-level field, and not in "fields" themselves
if (data.contains("password")) {
entry->setPassword(data.value("password").toString());
}
for (const auto& fieldValue : data.value("fields").toArray()) {
if (!fieldValue.isObject()) {
continue;
}
auto field = fieldValue.toObject();
auto designation = field["designation"].toString();
auto value = field["value"].toString();
if (designation == "password") {
entry->setPassword(value);
} else if (designation == "username") {
entry->setUsername(value);
}
}
const QJsonArray& sectionsArray = data["sections"].toArray();
for (const QJsonValue& sectionValue : sectionsArray) {
if (!sectionValue.isObject()) {
qWarning() << R"(Skipping non-Object in "sections" for UUID ")" << uuid << "\" << " << sectionsArray
<< ">>";
continue;
}
const QJsonObject& section = sectionValue.toObject();
fillFromSection(entry, section);
}
fillAttachments(entry, attachmentDir, entryKey, entryHmacKey);
return entry;
}
bool OpVaultReader::fillAttributes(Entry* entry, const QJsonObject& bandEntry)
{
const QString overviewStr = bandEntry.value("o").toString();
OpData01 entOver01;
if (!entOver01.decodeBase64(overviewStr, m_overviewKey, m_overviewHmacKey)) {
qCritical() << "Unable to decipher 'o' in UUID \"" << entry->uuid() << "\"\n"
<< ": " << entOver01.errorString();
return false;
}
QByteArray overviewJsonBytes = entOver01.getClearText();
QJsonDocument overviewDoc = QJsonDocument::fromJson(overviewJsonBytes);
QJsonObject overviewJson = overviewDoc.object();
QString title = overviewJson.value("title").toString();
entry->setTitle(title);
QString url = overviewJson["url"].toString();
entry->setUrl(url);
int i = 1;
for (const auto& urlV : overviewJson["URLs"].toArray()) {
auto urlName = QString("URL_%1").arg(i);
auto urlValue = urlV.toString();
if (urlV.isObject()) {
const auto& urlObj = urlV.toObject();
if (urlObj["l"].isString() && urlObj["u"].isString()) {
urlName = urlObj["l"].toString();
urlValue = urlObj["u"].toString();
} else {
continue;
}
}
if (!urlValue.isEmpty() && urlValue != url) {
entry->attributes()->set(urlName, urlValue);
++i;
}
}
QStringList tagsList;
for (const auto& tagV : overviewJson["tags"].toArray()) {
if (tagV.isString()) {
tagsList << tagV.toString();
}
}
entry->setTags(tagsList.join(','));
return true;
}

View File

@ -0,0 +1,136 @@
/*
* Copyright (C) 2019 KeePassXC Team <team@keepassxc.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 or (at your option)
* 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 "OpData01.h"
#include "OpVaultReader.h"
#include "core/Group.h"
#include "core/Tools.h"
#include "crypto/CryptoHash.h"
#include "crypto/SymmetricCipher.h"
#include "totp/totp.h"
#include <QDebug>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QUrlQuery>
#include <QUuid>
void OpVaultReader::fillFromSection(Entry* entry, const QJsonObject& section)
{
const auto uuid = entry->uuid();
const QString& sectionName = section["name"].toString();
if (!section.contains("fields")) {
auto sectionNameLC = sectionName.toLower();
auto sectionTitleLC = section["title"].toString("").toLower();
if (!(sectionNameLC == "linked items" && sectionTitleLC == "related items")) {
qWarning() << R"(Skipping "fields"-less Section in UUID ")" << uuid << "\": <<" << section << ">>";
}
return;
} else if (!section["fields"].isArray()) {
qWarning() << R"(Skipping non-Array "fields" in UUID ")" << uuid << "\"\n";
return;
}
QJsonArray sectionFields = section["fields"].toArray();
for (const QJsonValue sectionField : sectionFields) {
if (!sectionField.isObject()) {
qWarning() << R"(Skipping non-Object "fields" in UUID ")" << uuid << "\": << " << sectionField << ">>";
continue;
}
QJsonObject field = sectionField.toObject();
fillFromSectionField(entry, sectionName, field);
}
}
void OpVaultReader::fillFromSectionField(Entry* entry, const QString& sectionName, QJsonObject& field)
{
if (!field.contains("v")) {
// for our purposes, we don't care if there isn't a value in the field
return;
}
// Ignore "a" and "inputTraits" fields, they don't apply to KPXC
auto attrName = resolveAttributeName(sectionName, field["n"].toString(), field["t"].toString());
auto attrValue = field.value("v").toVariant().toString();
auto kind = field["k"].toString();
if (attrName.startsWith("TOTP_")) {
if (attrValue.startsWith("otpauth://")) {
QUrlQuery query(attrValue);
// at least as of 1Password 7, they don't append the digits= and period= which totp.cpp requires
if (!query.hasQueryItem("digits")) {
query.addQueryItem("digits", QString("%1").arg(Totp::DEFAULT_DIGITS));
}
if (!query.hasQueryItem("period")) {
query.addQueryItem("period", QString("%1").arg(Totp::DEFAULT_STEP));
}
attrValue = query.toString(QUrl::FullyEncoded);
}
entry->attributes()->set(Totp::ATTRIBUTE_SETTINGS, attrValue, true);
} else if (attrName.startsWith("expir", Qt::CaseInsensitive)) {
QDateTime expiry;
if (kind == "date") {
expiry = QDateTime::fromTime_t(attrValue.toUInt(), Qt::UTC);
} else {
expiry = QDateTime::fromString(attrValue, "yyyyMM");
expiry.setTimeSpec(Qt::UTC);
}
if (expiry.isValid()) {
entry->setExpiryTime(expiry);
entry->setExpires(true);
}
} else {
if (kind == "date") {
auto date = QDateTime::fromTime_t(attrValue.toUInt(), Qt::UTC);
if (date.isValid()) {
attrValue = date.toString();
}
}
entry->attributes()->set(attrName, attrValue, (kind == "password" || kind == "concealed"));
}
}
QString OpVaultReader::resolveAttributeName(const QString& section, const QString& name, const QString& text)
{
// Special case for TOTP
if (name.startsWith("TOTP_")) {
return name;
}
auto lowName = name.toLower();
auto lowText = text.toLower();
if (section.isEmpty()) {
// Empty section implies these are core attributes
// try to find username, password, url
if (lowName == "password" || lowText == "password") {
return EntryAttributes::PasswordKey;
} else if (lowName == "username" || lowText == "username") {
return EntryAttributes::UserNameKey;
} else if (lowName == "url" || lowText == "url" || lowName == "hostname" || lowText == "server"
|| lowName == "website") {
return EntryAttributes::URLKey;
}
return name;
}
return QString("%1_%2").arg(section, name);
}

View File

@ -262,6 +262,20 @@ void DatabaseTabWidget::importKeePass1Database()
dbWidget->switchToImportKeepass1(fileName);
}
void DatabaseTabWidget::importOpVaultDatabase()
{
QString fileName = fileDialog()->getExistingDirectory(this, "Open .opvault database");
if (fileName.isEmpty()) {
return;
}
auto db = QSharedPointer<Database>::create();
auto* dbWidget = new DatabaseWidget(db, this);
addDatabaseTab(dbWidget);
dbWidget->switchToImportOpVault(fileName);
}
/**
* Attempt to close the current database and remove its tab afterwards.
*

View File

@ -65,6 +65,7 @@ public slots:
void mergeDatabase();
void importCsv();
void importKeePass1Database();
void importOpVaultDatabase();
bool saveDatabase(int index = -1);
bool saveDatabaseAs(int index = -1);
void exportToCsv();

View File

@ -50,6 +50,7 @@
#include "gui/FileDialog.h"
#include "gui/KeePass1OpenWidget.h"
#include "gui/MessageBox.h"
#include "gui/OpVaultOpenWidget.h"
#include "gui/TotpDialog.h"
#include "gui/TotpExportSettingsDialog.h"
#include "gui/TotpSetupDialog.h"
@ -86,6 +87,7 @@ DatabaseWidget::DatabaseWidget(QSharedPointer<Database> db, QWidget* parent)
, m_databaseSettingDialog(new DatabaseSettingsDialog(this))
, m_databaseOpenWidget(new DatabaseOpenWidget(this))
, m_keepass1OpenWidget(new KeePass1OpenWidget(this))
, m_opVaultOpenWidget(new OpVaultOpenWidget(this))
, m_groupView(new GroupView(m_db.data(), m_mainSplitter))
, m_saveAttempts(0)
, m_fileWatcher(new DelayingFileWatcher(this))
@ -160,6 +162,7 @@ DatabaseWidget::DatabaseWidget(QSharedPointer<Database> db, QWidget* parent)
m_databaseSettingDialog->setObjectName("databaseSettingsDialog");
m_databaseOpenWidget->setObjectName("databaseOpenWidget");
m_keepass1OpenWidget->setObjectName("keepass1OpenWidget");
m_opVaultOpenWidget->setObjectName("opVaultOpenWidget");
addChildWidget(m_mainWidget);
addChildWidget(m_editEntryWidget);
@ -169,6 +172,7 @@ DatabaseWidget::DatabaseWidget(QSharedPointer<Database> db, QWidget* parent)
addChildWidget(m_databaseOpenWidget);
addChildWidget(m_csvImportWizard);
addChildWidget(m_keepass1OpenWidget);
addChildWidget(m_opVaultOpenWidget);
// clang-format off
connect(m_mainSplitter, SIGNAL(splitterMoved(int,int)), SIGNAL(mainSplitterSizesChanged()));
@ -188,6 +192,7 @@ DatabaseWidget::DatabaseWidget(QSharedPointer<Database> db, QWidget* parent)
connect(m_databaseSettingDialog, SIGNAL(editFinished(bool)), SLOT(switchToMainView(bool)));
connect(m_databaseOpenWidget, SIGNAL(dialogFinished(bool)), SLOT(loadDatabase(bool)));
connect(m_keepass1OpenWidget, SIGNAL(dialogFinished(bool)), SLOT(loadDatabase(bool)));
connect(m_opVaultOpenWidget, SIGNAL(dialogFinished(bool)), SLOT(loadDatabase(bool)));
connect(m_csvImportWizard, SIGNAL(importFinished(bool)), SLOT(csvImportFinished(bool)));
connect(m_fileWatcher.data(), SIGNAL(fileChanged()), this, SLOT(reloadDatabaseFile()));
connect(this, SIGNAL(currentChanged(int)), this, SLOT(emitCurrentModeChanged()));
@ -1031,6 +1036,13 @@ void DatabaseWidget::switchToImportKeepass1(const QString& filePath)
setCurrentWidget(m_keepass1OpenWidget);
}
void DatabaseWidget::switchToImportOpVault(const QString& fileName)
{
updateFilePath(fileName);
m_opVaultOpenWidget->load(fileName);
setCurrentWidget(m_opVaultOpenWidget);
}
void DatabaseWidget::switchToEntryEdit()
{
Entry* entry = m_entryView->currentEntry();

View File

@ -33,6 +33,7 @@
class DatabaseOpenWidget;
class KeePass1OpenWidget;
class OpVaultOpenWidget;
class DatabaseSettingsDialog;
class Database;
class DelayingFileWatcher;
@ -183,6 +184,7 @@ public slots:
void performUnlockDatabase(const QString& password, const QString& keyfile = {});
void csvImportFinished(bool accepted);
void switchToImportKeepass1(const QString& filePath);
void switchToImportOpVault(const QString& fileName);
void emptyRecycleBin();
// Search related slots
@ -246,6 +248,7 @@ private:
QPointer<DatabaseSettingsDialog> m_databaseSettingDialog;
QPointer<DatabaseOpenWidget> m_databaseOpenWidget;
QPointer<KeePass1OpenWidget> m_keepass1OpenWidget;
QPointer<OpVaultOpenWidget> m_opVaultOpenWidget;
QPointer<GroupView> m_groupView;
QPointer<EntryView> m_entryView;

View File

@ -347,6 +347,7 @@ MainWindow::MainWindow()
connect(m_ui->actionChangeDatabaseSettings, SIGNAL(triggered()), m_ui->tabWidget, SLOT(changeDatabaseSettings()));
connect(m_ui->actionImportCsv, SIGNAL(triggered()), m_ui->tabWidget, SLOT(importCsv()));
connect(m_ui->actionImportKeePass1, SIGNAL(triggered()), m_ui->tabWidget, SLOT(importKeePass1Database()));
connect(m_ui->actionImportOpVault, SIGNAL(triggered()), m_ui->tabWidget, SLOT(importOpVaultDatabase()));
connect(m_ui->actionExportCsv, SIGNAL(triggered()), m_ui->tabWidget, SLOT(exportToCsv()));
connect(m_ui->actionLockDatabases, SIGNAL(triggered()), m_ui->tabWidget, SLOT(lockDatabases()));
connect(m_ui->actionQuit, SIGNAL(triggered()), SLOT(appExit()));
@ -382,6 +383,7 @@ MainWindow::MainWindow()
connect(m_ui->welcomeWidget, SIGNAL(openDatabase()), SLOT(switchToOpenDatabase()));
connect(m_ui->welcomeWidget, SIGNAL(openDatabaseFile(QString)), SLOT(switchToDatabaseFile(QString)));
connect(m_ui->welcomeWidget, SIGNAL(importKeePass1Database()), SLOT(switchToKeePass1Database()));
connect(m_ui->welcomeWidget, SIGNAL(importOpVaultDatabase()), SLOT(switchToOpVaultDatabase()));
connect(m_ui->welcomeWidget, SIGNAL(importCsv()), SLOT(switchToCsvImport()));
connect(m_ui->actionAbout, SIGNAL(triggered()), SLOT(showAboutDialog()));
@ -815,6 +817,12 @@ void MainWindow::switchToKeePass1Database()
switchToDatabases();
}
void MainWindow::switchToOpVaultDatabase()
{
m_ui->tabWidget->importOpVaultDatabase();
switchToDatabases();
}
void MainWindow::switchToCsvImport()
{
m_ui->tabWidget->importCsv();

View File

@ -97,6 +97,7 @@ private slots:
void switchToOpenDatabase();
void switchToDatabaseFile(const QString& file);
void switchToKeePass1Database();
void switchToOpVaultDatabase();
void switchToCsvImport();
void closePasswordGen();
void databaseStatusChanged(DatabaseWidget* dbWidget);

View File

@ -200,6 +200,7 @@
<string>&amp;Import</string>
</property>
<addaction name="actionImportKeePass1"/>
<addaction name="actionImportOpVault"/>
<addaction name="actionImportCsv"/>
</widget>
<addaction name="actionDatabaseNew"/>
@ -603,6 +604,14 @@
<string>Import a KeePass 1 database</string>
</property>
</action>
<action name="actionImportOpVault">
<property name="text">
<string>1Password Vault...</string>
</property>
<property name="toolTip">
<string>Import a 1Password Vault</string>
</property>
</action>
<action name="actionImportCsv">
<property name="text">
<string>CSV file...</string>

View File

@ -0,0 +1,56 @@
/*
* Copyright (C) 2019 KeePassXC Team <team@keepassxc.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 or (at your option)
* 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 "OpVaultOpenWidget.h"
#include <QDir>
#include "core/Database.h"
#include "core/Metadata.h"
#include "format/OpVaultReader.h"
#include "gui/MessageBox.h"
#include "ui_DatabaseOpenWidget.h"
OpVaultOpenWidget::OpVaultOpenWidget(QWidget* parent)
: DatabaseOpenWidget(parent)
{
m_ui->labelHeadline->setText("Import 1Password database");
}
void OpVaultOpenWidget::openDatabase()
{
OpVaultReader reader;
QString password;
if (m_ui->checkPassword->isChecked()) {
password = m_ui->editPassword->text();
}
QDir opVaultDir(m_filename);
QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
m_db.reset(reader.readDatabase(opVaultDir, password));
QApplication::restoreOverrideCursor();
if (m_db) {
emit dialogFinished(true);
} else {
m_ui->messageWidget->showMessage(tr("Read Database did not produce an instance\n%1").arg(reader.errorString()),
MessageWidget::Error);
m_ui->editPassword->clear();
}
}

View File

@ -0,0 +1,34 @@
/*
* Copyright (C) 2019 KeePassXC Team <team@keepassxc.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 or (at your option)
* 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 KEEPASSXC_OPVAULTOPENWIDGET_H
#define KEEPASSXC_OPVAULTOPENWIDGET_H
#include "gui/DatabaseOpenWidget.h"
class OpVaultOpenWidget : public DatabaseOpenWidget
{
Q_OBJECT
public:
explicit OpVaultOpenWidget(QWidget* parent = nullptr);
protected:
void openDatabase() override;
};
#endif // KEEPASSXC_OPVAULTOPENWIDGET_H

View File

@ -48,6 +48,7 @@ WelcomeWidget::WelcomeWidget(QWidget* parent)
connect(m_ui->buttonNewDatabase, SIGNAL(clicked()), SIGNAL(newDatabase()));
connect(m_ui->buttonOpenDatabase, SIGNAL(clicked()), SIGNAL(openDatabase()));
connect(m_ui->buttonImportKeePass1, SIGNAL(clicked()), SIGNAL(importKeePass1Database()));
connect(m_ui->buttonImportOpVault, SIGNAL(clicked()), SIGNAL(importOpVaultDatabase()));
connect(m_ui->buttonImportCSV, SIGNAL(clicked()), SIGNAL(importCsv()));
connect(m_ui->recentListWidget,
SIGNAL(itemActivated(QListWidgetItem*)),

View File

@ -41,6 +41,7 @@ signals:
void openDatabase();
void openDatabaseFile(QString);
void importKeePass1Database();
void importOpVaultDatabase();
void importCsv();
protected:

View File

@ -126,6 +126,13 @@
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="buttonImportOpVault">
<property name="text">
<string>Import from 1Password</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="buttonImportCSV">
<property name="text">

View File

@ -159,6 +159,9 @@ add_unit_test(NAME testdeletedobjects SOURCES TestDeletedObjects.cpp
add_unit_test(NAME testkeepass1reader SOURCES TestKeePass1Reader.cpp
LIBS ${TEST_LIBRARIES})
add_unit_test(NAME testopvaultreader SOURCES TestOpVaultReader.cpp
LIBS ${TEST_LIBRARIES})
add_unit_test(NAME testwildcardmatcher SOURCES TestWildcardMatcher.cpp
LIBS ${TEST_LIBRARIES})

250
tests/TestOpVaultReader.cpp Normal file
View File

@ -0,0 +1,250 @@
/*
* Copyright (C) 2019 KeePassXC Team <team@keepassxc.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 or (at your option)
* 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 "TestOpVaultReader.h"
#include "config-keepassx-tests.h"
#include "core/Database.h"
#include "core/Group.h"
#include "core/Metadata.h"
#include "core/Tools.h"
#include "crypto/Crypto.h"
#include "format/OpVaultReader.h"
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QList>
#include <QPair>
#include <QStringList>
#include <QTest>
#include <QUuid>
QTEST_GUILESS_MAIN(TestOpVaultReader)
QPair<QString, QString>* split1PTextExportKV(QByteArray& line)
{
const auto eq = line.indexOf('=');
if (-1 == eq) {
qWarning() << "Bogus key=value pair: <<" << line << ">>";
return nullptr;
}
auto k = QString::fromUtf8(line.mid(0, eq));
const auto start = eq + 1;
auto v = QString::fromUtf8(line.mid(start), (line.size() - 1) - start);
return new QPair<QString, QString>(k, v);
}
QJsonArray* read1PasswordTextExport(QFile& f)
{
auto result = new QJsonArray;
auto current = new QJsonObject;
if (!f.open(QIODevice::ReadOnly)) {
qCritical("Unable to open your text export file for reading");
return nullptr;
}
while (!f.atEnd()) {
auto line = f.readLine(1024);
if (line.size() == 1 and line[0] == '\n') {
if (!current->isEmpty()) {
result->append(*current);
}
current = new QJsonObject;
continue;
}
const auto kv = split1PTextExportKV(line);
if (kv == nullptr) {
break;
}
QString k = kv->first;
const auto multiLine1 = line.indexOf("=\"\"");
const auto multiLine2 = line.indexOf("=\"");
const auto isML1 = -1 != multiLine1;
const auto isML2 = -1 != multiLine2;
if (isML1 or isML2) {
QStringList lines;
const int skipEQ = isML1 ? (multiLine1 + 3) : (multiLine2 + 2);
lines.append(QString::fromUtf8(line.mid(skipEQ)));
while (!f.atEnd()) {
line = f.readLine(1024);
const auto endMarker = line.indexOf(isML1 ? "\"\"\n" : "\"\n");
if (-1 != endMarker) {
line[endMarker] = '\n';
lines.append(QString::fromUtf8(line.mid(0, endMarker)));
break;
} else {
lines.append(QString::fromUtf8(line));
}
}
auto v = lines.join("");
(*current)[k] = v;
} else {
(*current)[k] = kv->second;
}
delete kv;
}
if (!current->isEmpty()) {
result->append(*current);
}
f.close();
return result;
}
void TestOpVaultReader::initTestCase()
{
QVERIFY(Crypto::init());
// https://cache.agilebits.com/security-kb/freddy-2013-12-04.tar.gz
m_opVaultPath = QString("%1/%2").arg(KEEPASSX_TEST_DATA_DIR, "/freddy-2013-12-04.opvault");
m_opVaultTextExportPath = QString(m_opVaultPath).replace(".opvault", ".opvault.txt");
m_password = "freddy";
QFile testData(m_opVaultTextExportPath);
QJsonArray* data = read1PasswordTextExport(testData);
QVERIFY(data);
QCOMPARE(data->size(), 27);
delete data;
m_categoryMap.insert("001", "Login");
m_categoryMap.insert("002", "Credit Card");
m_categoryMap.insert("003", "Secure Note");
m_categoryMap.insert("004", "Identity");
m_categoryMap.insert("005", "Password");
m_categoryMap.insert("099", "Tombstone");
m_categoryMap.insert("100", "Software License");
m_categoryMap.insert("101", "Bank Account");
m_categoryMap.insert("102", "Database");
m_categoryMap.insert("103", "Driver License");
m_categoryMap.insert("104", "Outdoor License");
m_categoryMap.insert("105", "Membership");
m_categoryMap.insert("106", "Passport");
m_categoryMap.insert("107", "Rewards");
m_categoryMap.insert("108", "SSN");
m_categoryMap.insert("109", "Router");
m_categoryMap.insert("110", "Server");
m_categoryMap.insert("111", "Email");
}
void TestOpVaultReader::testReadIntoDatabase()
{
QDir opVaultDir(m_opVaultPath);
auto reader = new OpVaultReader();
auto db = reader->readDatabase(opVaultDir, m_password);
QVERIFY2(!reader->hasError(), qPrintable(reader->errorString()));
QVERIFY(db);
QVERIFY(!db->children().isEmpty());
Group* rootGroup = db->rootGroup();
QVERIFY(rootGroup);
QFile testDataFile(m_opVaultTextExportPath);
auto testData = read1PasswordTextExport(testDataFile);
QVERIFY(testData);
QMap<QUuid, QJsonObject> objectsByUuid;
QMap<QString, QList<QJsonObject>> objectsByCategory;
for (QJsonArray::const_iterator it = testData->constBegin(); it != testData->constEnd(); ++it) {
QJsonObject value = (*it).toObject();
auto cat = value["category"].toString();
QVERIFY2(m_categoryMap.contains(cat), qPrintable(QString("BOGUS, unmapped category \"%1\"").arg(cat)));
auto catName = m_categoryMap[cat];
if (!objectsByCategory.contains(catName)) {
QList<QJsonObject> theList;
objectsByCategory[catName] = theList;
}
objectsByCategory[catName].append(value);
QUuid u = Tools::hexToUuid(value["uuid"].toString());
objectsByUuid[u] = value;
}
delete testData;
QCOMPARE(objectsByUuid.size(), 27);
for (QUuid u : objectsByUuid.keys()) {
QJsonObject o = objectsByUuid[u];
const auto e = db->rootGroup()->findEntryByUuid(u);
QVERIFY2(e, qPrintable(QString("Expected to find UUID %1").arg(u.toString())));
auto jsonTitle = o["title"].toString();
QCOMPARE(jsonTitle, e->title());
}
for (QString& catName : m_categoryMap.values()) {
const auto g = rootGroup->findChildByName(catName);
QVERIFY2(g, qPrintable(QString("Expected to find Group(%1)").arg(catName)));
for (QJsonObject testEntry : objectsByCategory[catName]) {
auto uuidStr = testEntry["uuid"].toString();
auto jsonTitle = testEntry["title"].toString();
QUuid u = Tools::hexToUuid(uuidStr);
const auto entry = g->findEntryByUuid(u);
QVERIFY2(entry, qPrintable(QString("Expected to find Group(%1).entry(%2)").arg(catName).arg(uuidStr)));
QCOMPARE(entry->title(), jsonTitle);
}
}
}
void TestOpVaultReader::testKeyDerivation()
{
OpVaultReader reader;
QDir opVaultDir(m_opVaultPath);
// yes, the reader checks this too, but in our case best to fail early
QVERIFY(opVaultDir.exists());
QVERIFY(opVaultDir.isReadable());
QDir defDir = QDir(opVaultDir);
defDir.cd("default");
QFile profileJs(defDir.absoluteFilePath("profile.js"));
QVERIFY(profileJs.exists());
auto profileObj = reader.readAndAssertJsonFile(profileJs, "var profile=", ";");
QByteArray salt = QByteArray::fromBase64(profileObj["salt"].toString().toUtf8());
unsigned long iter = profileObj["iterations"].toInt();
const auto derived = reader.deriveKeysFromPassPhrase(salt, m_password, iter);
QVERIFY(derived);
QVERIFY(!derived->error);
QByteArray encHex = derived->encrypt.toHex();
QByteArray hmacHex = derived->hmac.toHex();
delete derived;
QCOMPARE(QString::fromUtf8(encHex),
QStringLiteral("63b075de858949559d4faa9d348bf10bdaa0e567ad943d7803f2291c9342aaaa"));
QCOMPARE(QString::fromUtf8(hmacHex),
QStringLiteral("ff3ab426ce55bf097b252b3f2df1c4ba4312a6960180844d7a625bc0ab40c35e"));
}
void TestOpVaultReader::testBandEntry1()
{
auto reader = new OpVaultReader();
QByteArray json(R"({"hello": "world"})");
QJsonDocument doc = QJsonDocument::fromJson(json);
QJsonObject data;
QByteArray entryKey;
QByteArray entryHmacKey;
QVERIFY(!reader->decryptBandEntry(doc.object(), data, entryKey, entryHmacKey));
}

48
tests/TestOpVaultReader.h Normal file
View File

@ -0,0 +1,48 @@
/*
* Copyright (C) 2019 KeePassXC Team <team@keepassxc.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 or (at your option)
* 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 TEST_OPVAULT_READER_H_
#define TEST_OPVAULT_READER_H_
#include <QMap>
#include <QObject>
class TestOpVaultReader : public QObject
{
Q_OBJECT
private slots:
void initTestCase();
void testReadIntoDatabase();
void testBandEntry1();
void testKeyDerivation();
private:
// absolute path to the .opvault directory
QString m_opVaultPath;
/*
* Points to the file made by using the 1Password GUI to "Export all"
* to its text file format, which are almost key=value pairs
* except for multi-line strings.
*/
QString m_opVaultTextExportPath;
QString m_password;
QMap<QString, QString> m_categoryMap;
};
#endif /* TEST_OPVAULT_READER_H_ */

View File

@ -0,0 +1,427 @@
uuid=E0D293D29B10483F8DFDAC72ED0BE5C0
title=Wendy's passport
category=106
ainfo=ZZ200000
scope=Default
autoSubmit=Default
type=Passport
issuing country=Canada
number=ZZ200000
full name=Wendy Appleseed
sex=female
nationality=Canada
issuing authority=Home Office
date of birth=359100000
place of birth=Yellowknife, NT
issued on=954828000
expiry date=1585893600
uuid=F2DB5DA3FCA64372A751E0E85C67A538
title=A note with some attachments
category=003
ainfo=This note has two attachments.
scope=Default
autoSubmit=Default
notesPlain=This note has two attachments.
uuid=FF445AB1497241A28812363154E1A738
title=Johnny Appleseed Society
category=105
ainfo=Wendy Appleseed
scope=Default
autoSubmit=Default
website=http://www.urbana.edu/resources/community/johnny-appleseed/appleseed-society.html
member name=Wendy Appleseed
expiry date=2625
member ID=123456
password=B8HqCdCMAY8KxJqg
uuid=2A632FDD32F5445E91EB5636C7580447
title=Skype
category=001
ainfo=WendyAppleseed
scope=Default
autoSubmit=Default
tags=Sample
website=https://secure.skype.com/account/login?message=login_required
username=WendyAppleseed
password=dej3ur9unsh5ian1and5
uuid=1C7D72EFA19A4EE98DB7A9661D2F5732
title=Wendy's driver's license
category=103
ainfo=D6101-40706-60905
scope=Default
autoSubmit=Default
notesPlain=Picture really doesn't look like Wendy
full name=Wendy Appleseed
address=5-150 Hollidge Blvd Suite 150
date of birth=359100000
sex=female
height=175cm
number=D6101-40706-60905
license class=G2
conditions / restrictions=J
state=Ontario
country=Canada
expiry date=2515
uuid=67979020CCA54120BAFA2742C3F23F2B
title=Social Security
category=108
ainfo=Wendy Appleseed
scope=Default
autoSubmit=Default
name=Wendy Appleseed
number=555-55-1234
uuid=372E1D51AA1D44CB9F17D8AA70ADA9A6
title=example.com
category=110
ainfo=wappleseed
scope=Default
autoSubmit=Default
notesPlain=I should attach an SSH key, but maybe later.
URL=example.com
username=wappleseed
password=My4scQNoFw8JcvN
section=Admin Console
section=Hosting Provider
name=Example Hosting provider
website=http://services.example.com
uuid=8445A23B5740455DA360FEA379C3CC90
title=Tim Hortons
category=107
ainfo=Tim Hortens
scope=Default
autoSubmit=Default
company name=Tim Hortens
member name=Wendy Appleseed
member ID=12123123
PIN=Y7s8WaRGJBAz
section=More Information
uuid=A2D44483145F4B41A849FE5FEA4B504D
title=Snipe Hunting License
category=104
ainfo=Wendy Appleseed
scope=Default
autoSubmit=Default
notesPlain=""I went out and shot the maximum the game laws would allow.
Two game wardens, seven hunters, and a cow.
They took away my license, the worst punishment I ever endured.
Turns out there was a reason,
Cows were out of season,
And one of the hunters wasn't insured.""
full name=Wendy Appleseed
expires=1672470000
approved wildlife=North American Snipe
maximum quota=Two game wardens, seven hunters, and a cow
uuid=FD2EADB43C4F4FC7BEB35A1692DDFDEA
title=Email Account
category=111
ainfo=wendy.appleseed@me.com
scope=Default
autoSubmit=Default
type=imap
username=wendy.appleseed@me.com
server=imap.mail.me.com
port number=993
password=iINe4uig8suLny
security=SSL
auth method=password
section=SMTP
SMTP server=smtp.mail.me.com
port number=587
username=wendy.appleseed@me.com
password=iINe4uig8suLny
security=TLS
auth method=password
section=Contact Information
uuid=EC0A40400ABB4B16926B7417E95C9669
title=Bank of America
category=001
ainfo=WendyAppleseed
scope=Default
autoSubmit=Default
tags=Sample, Personal
website=https://www.bankofamerica.com/
previousPassword1=speg5nu5di1mol4niev9
username=WendyAppleseed
password=reTDx8KHhW8eAc
uuid=E482B70C038D4DD78A0940728FA737BF
title=Chase VISA ***4356
category=002
ainfo=1234 *********** 4356
scope=Default
autoSubmit=Default
tags=Sample
notesPlain=Sample data, not a real credit card number.
cardholder name=Wendy Appleseed
type=visa
number=1234 5678 9012 4356
verification number=543
expiry date=201905
section=Contact Information
issuing bank=Chase
phone (toll free)=1-888-888-8888
website=www.chase.com
section=Additional Details
PIN=000
credit limit=$5,000.00
cash withdrawal limit=$1,000.00
interest rate=29.9%
uuid=D1820AA8CB534AC6A4B5A2C0263FD3B2
title=What is a Secure Note?
category=003
scope=Default
autoSubmit=Default
tags=Sample
notesPlain="
Secure Notes enable you to keep any information in freeform text format while keeping it safely encrypted along with the rest of your 1Password data.
Just like the website passwords and credit card numbers you can store in 1Password, you (or someone else!) cannot get to your Secure Notes without entering your keychains Master Password.
This provides encrypted storage for your stuff that doesnt fit into other areas of 1Password.
"
uuid=D8F79F17D6384808848B213EB4946ECA
title=The Unofficial Apple Weblog
category=001
ainfo=WendyAppleseed
scope=Default
autoSubmit=Default
tags=Sample
website=http://www.tuaw.com
username=WendyAppleseed
password=tiac1nut2jab1eiv2oc5
uuid=F78CEC04078743B6975511A6FDDBED7E
title=1Password
category=100
ainfo=3.0
scope=Default
autoSubmit=Default
tags=Sample, Business
notesPlain="This is a sample software license.
1Password securely keeps track of online logins, generates strong passwords, enters personal and credit card information with one click, protects from fishing attacks, and more!
"
version=3.0
license key=1PW3-0000-000000-0000
section=Customer
licensed to=Wendy Appleseed
registered email=wendy@appleseed.com
section=Publisher
download page=http://agilebits.com/downloads
publisher=AgileBits
website=http://1password.com
support email=support@agilebits.com
section=Order
uuid=F5F099B210F248348E22934DDC3338B2
title=TextExpander
category=100
ainfo=1.3
scope=Default
autoSubmit=Default
tags=Sample
notesPlain="This is a sample software license.
TextExpander saves you countless keystrokes with customized abbreviations for your frequently-used text strings and images."
version=1.3
license key=TEXTEXP001-1234-ABCD-5678-EFGH
section=Customer
licensed to=Wendy Appleseed
registered email=wendy@appleseed.com
section=Publisher
download page=www.smileonmymac.com/TextExpander/download.html
publisher=Smile On My Mac, LLC
website=www.smileonmymac.com
retail price=29.95
support email=support@smileonmymac.com
section=Order
uuid=F3707FA58EA7480884BC6A662658E039
title=Business
category=004
ainfo=Wendy Appleseed
scope=Default
autoSubmit=Default
tags=Business, Sample
section=Identification
first name=Wendy
last name=Appleseed
sex=female
birth date=361778400
occupation=Customer Relations
company=AgileBits
department=Customer Care
job title=Manager
section=Address
default phone=(555) 555-5678
cell=(555) 555-1234
business=(555) 555-5678
section=Internet Details
username=WendyAppleseed
reminder question=What's your favorite application?
reminder answer=1Password
email=support@agilebits
website=www.agilebits.com
forum signature=1Password — Never forget your password again.
uuid=F7883ADDE5944B349ABB5CBEC20F39BE
title=MobileMe
category=001
ainfo=wendy.appleseed@me.com
scope=Default
autoSubmit=Default
tags=Sample
website=https://www.icloud.com/
notesPlain=Sample MobileMe account.
username=wendy.appleseed@me.com
password=iINe4uig8suLny
Member name=wendy.appleseed
iDisk Storage=10GB
uuid=4E36C011EE8348B1B24418218B04018C
title=Company's FTP
category=001
ainfo=admin
scope=Default
autoSubmit=Default
tags=Sample, Business
website=ftp://ftp.dreamhost.com
notesPlain=Sample FTP account.
username=admin
password=auj7r5?u61ww
path=/home/product/secert
section=Provider
uuid=5ADFF73C09004C448D45565BC4750DE2
title=Tumblr
category=001
ainfo=wendy@appleseed.com
scope=Default
autoSubmit=Default
tags=Sample, Social
website=http://www.tumblr.com/login
email=wendy@appleseed.com
password=vow6wem2wo
uuid=72366D161D9E43D98E58EB801DAD1EF8
title=Last.fm
category=001
ainfo=WendyAppleseed
scope=Default
autoSubmit=Default
tags=Sample
website=https://www.last.fm/login
username=WendyAppleseed
password=dowg1af5kam7oak9at
uuid=D06307ADA44C4031BA2FF4B174DE79CB
title=CapitalOne MasterCard ***3456
category=002
ainfo=1234 *********** 3456
scope=Default
autoSubmit=Default
tags=Sample, Business
notesPlain=Sample data, not a real credit card number.
cardholder name=Wendy Appleseed
type=mc
number=1234 5678 9012 3456
verification number=123
expiry date=201411
section=Contact Information
issuing bank=CapitalOne
phone (toll free)=1-888-888-8888
website=capitalone.com
section=Additional Details
PIN=234
credit limit=$8,000
cash withdrawal limit=$2,000
interest rate=19.8%
uuid=27DCFA2810B24083A3ECC7CEABC7C0A9
title=Orders
category=102
ainfo=10.0.1.50
scope=Default
autoSubmit=Default
tags=Sample
notesPlain=Sample database account.
type=mysql
server=10.0.1.50
port=3066
database=orders_production
username=orders_app
password=tgOhmpU9HgC5Hz
uuid=358B7411EB8B45CD9CE592ED16F3E9DE
title=YouTube
category=001
ainfo=wendy@appleseed.com
scope=Default
autoSubmit=Default
tags=Sample, Social
website=http://www.youtube.com/login?next=/index
username=wendy@appleseed.com
password=snaip5uc5keds7as5ocs
uuid=468B1E24F93B413DAD57ABE6F1C01DF6
title=Dropbox
category=001
ainfo=wendy@appleseed.com
scope=Default
autoSubmit=Default
tags=Sample
website=https://www.getdropbox.com/
email=wendy@appleseed.com
password=vet4juf4nim1ow6ay2ph
uuid=0EDE2B13D7AC4E2C9105842682ACB187
title=Personal
category=004
ainfo=Wendy Appleseed
scope=Default
autoSubmit=Default
tags=Sample, Personal
section=Identification
first name=Wendy
last name=Appleseed
sex=female
birth date=359100000
occupation=Customer Relations
company=AgileBits
department=Customer Care
job title=Manager
section=Address
default phone=(555) 555-4321
home=(555) 555-4321
cell=(555) 555-1234
section=Internet Details
username=WendyAppleseed
reminder question=What's your favorite application?
reminder answer=1Password
email=wendy@appleseed.com
skype=WendyAppleseed
AOL/AIM=WendyAppleseed76
uuid=13C8E12AC8E54B1F873BAB0824E521BC
title=Hulu
category=001
ainfo=wendy@appleseed.com
scope=Default
autoSubmit=Default
tags=Sample
website=http://www.hulu.com/
username=wendy@appleseed.com
password=frirp7i1ob7wig4d

View File

@ -0,0 +1 @@
ld({"0C4F27910A64488BB339AED63565D148":{"uuid":"0C4F27910A64488BB339AED63565D148","category":"099","o":"b3BkYXRhMDEIAAAAAAAAAMQDerODSnrtEVkZHp0tO5qokNWe+77F7yjsHcCvBEdxYL9DPSUuPV4FDv1F4E3VXWoY4BBYZrm8G3IUekJhL3E=","hmac":"SP8xH51\/qYBOoiCzKWDDmyNluCdPiP4bzOPR2+eTTh0=","updated":1386214150,"trashed":true,"k":"6MnmUT7fNchO0lIDNYGITOAO0cubw8Qsad1dEBZFCUSXrUOR7IkFUwddSA8QBJTH7P7iJytKB00KclFRNR\/zf+AC+VD6aCQiznj1zx8uKoxG9Wv1v4YsnH95NbC8UvRxCn+XA+6WRZII2kWN10IN9w==","d":"b3BkYXRhMDECAAAAAAAAAO\/uG7Zs+1OHwr82PByk3Scrlb7f1QGT0EThuhBdj50T3qyvt\/uoxBb8APNUDjTV81dTjBoNvLCpvuAEyQgdmlY=","created":1386214097,"tx":1386214431},"0EDE2B13D7AC4E2C9105842682ACB187":{"category":"004","k":"A4kIEzE7ypBL5lTeguPoFPlD21Uv5akEeosVZQ8u98BIBnMqScGmLJTlCoAgvfn+1YjgxQX3vZJTMDUcmt678UuBVMMehVg87Pys4hMFLNjwhhJaFGSRpSfWDlVB6Rb5PGrkkIDZBPkK4kFbYMN1tg==","updated":1325483949,"tx":1373753421,"d":"b3BkYXRhMDEYCAAAAAAAAOX\/h3yw\/qsvS8loinC\/IeaownXcDlKuIxDWIhQZJ+wZSmV43jY7n4iCxG6Fg8qIQm+l1Tu7M3oTOwsRREhbqqEsQHnJSts32+nxh5K9hgcCKYfKMbPB13pQlWamGUMX7tCLno8w+8XQnI8izoTE75klF8z+jF+LjGK3IhQ1wm4hCqWje0j9brjGId8KPrQoVIorzROtYfBKYjEMu5bvhCI62KWUbyBodAKoYdnHK7bSs01GvY\/tPyXPZ4qyQ7qrou5uDJNclYQ715Ajbm4sIDbfW0qtrYeSA6+uFT6ClxDccc9+RvW40vgaZekx8yEa6ytrZ744JlnKGdYQrecV8WDjIiVzgZrTV9GthzPzrUb8JUA\/naBufQNlQVISvnFQUXM+S+E8B+FR8OJDY0g0VNMkQ4BxeYyAlZB9395DcJfrzu7378PSy0egyNoWKM8PZH\/HHYhUlWMWMkP90r+iIIFnp9XpAXyetSUfIHV\/nRP0wBxvgBtcz7BBsjMwHa965K5KOQxZm9Nb9118IaUiXfG4jU65M1keJBa4fOUlka7QK8Q9cYHQZNY86PMrdYjDvG6YhL\/aNjQ+oWUpvtyZnFAdwe7+5Zw4TuAKXf2SiWcKzkGfbNLZxJJY95eVPfv9lSYrZay4LZKtD8WP\/X6G8w8+NlAMESiZkwhx+w33HgTzVIbLqvTFcIAgXbcCmNCfmIW+VlnvXtUZCjs9rI0KC3rXLE6OUBo3mJTy1+2iFHk3ed1gdlDWX0mWe4+CI\/4Q1pAxsXnqATgLM8dep6fySXKYXf44mj0t03jQXnm8t02FPK7lhPjjGddntqz5idk1jVFp\/wfDB4j+E9EvszWJyP14PYZRIyIOS67wWs0mKHeLdkoOeEGxFf\/h5IdDEOxm4xe\/+8ZfzTjPPrKX27XJlT\/XbVShvLbru\/ToP8qLqaBq\/7c6tmKhUvOLg0M5WX7oEEq4Rqk2qBaRmNSfd9ke\/AECrVzlqTRubVgfA95G7wAOERT6aa0wJZ7JxFj1ynQVgFrSTHyNSeW9n42TWGO\/\/6w\/hmDv5jJ\/IEJlc2eW0wPRBRCjWE\/cz384nVU0d0ixucYzLlsxyXn1GzseMa+u0WqyKHvJrXCj+6L1GKokp42yLDJVg6WO7EiS+sVcc\/WnTOdxfh8WMrexEfQS3jlL+d1IHt58c3kfjxhTX48Tlhj9Ih3dWW5xwK5JiVM+Lumk+IKhpEHpIu46YOQfbyK6ETqHNdKYiBOQByjCPq\/MftDPXKH8bAyOe5pMH89SYs8Y0TdSqIRsSyVWBKYkdcRp\/bpMB0CRJcSapkpQSMDOpioE6PkIuhGXNENT8EDBlM477yPxorYxHxLdzusOsxzzRBgc120ezJQALoWTgCy54LMYQlNj4Xajw00V8EnyVaKD7zfkhvqo6bTveR89mNQL213bGeOvEbOTDizNYgWpFGJb8WgD7Ji+Z6qd0vfBm17r0A2SNCrtHG8Fp1q+Qh0DR+94nLdN5R0Ann7LTgLbi2LhzQyr9KdBlLA73SRQFvaMsmPoopO46Bf21LbY3IeVjHDRa8253zs2oASHrTNFnki7j1byyVZQDRQwMoAXJnNZre+CzhCYdSA8pERPKihODRpXpq4NSSitWKMAKIqqoDYWzrZmBiTLFwF0SxmYGpkTn6AdMjexp1Xayx+7NIOTui6yaUmIf\/MEm7hfOfN0SHZOLYA3FMOa8mLCLU4qdnQTZlK\/v5QLcBTy2WB\/RZlbyX3nXb2ooE4kheMA6dtPI+OnBSIkbZ8nRzJx1eNfuOqpxS8H8M1oQ96I1g1LyX58VDjlHVcgrpVXcta4uXb4y9ZbWCiS5C3DEPlx9FAb3HwKgsvnArkNP8k0QIvX6w2xKAOv03bafVETG3LUh5OhQDKZkoRjR6sOcBstWLoyL8yJKj6YnoVNcLdHW3pvlbaKvETb\/Q5y1AE81XYADWWbMQo8AHg7lMpGyG98KuNr3WI9X1T7GyJAAaSXIYDepa\/l9icfqEUPyYyxxOUBjTjtdhSHFGCwoiu5rnGcA2Nq9v44ZGOGfy8tLzbzoMmZf3+qjmoSgmCRDTgCuWuSDixEfo65BNC9sRYPgf0JyYrnW9oBB\/\/g4lkzEv5B1V6leXQonJQ6vPKJbOWPDsZ1R8\/3\/dxPoNOjfp0J59ndoboOX5E52meVIQ99GqAAmLSWCNU76IZWsnGQHBmsaZqgHjE5E86D21rSVgOaKzN4ngvXd5fbaJn7zVvaQwh26uBT5vaTtZAc7UubBzj5FrnXC0j8Tha6nAQ4ZYkqhIQK\/FjWgpnF61D3v0TYwECNQU5xNOaSGaS4jMsrX77PnrNUnAq7Zc3ainZtZ1fK9A0UevqonpqkH3RDC1r5QcAU+aLTV4AyG50F16KMgv\/Hkib\/GoY67qO+3IJuYXPdhjHRgZajl3XC70d9Agw0uMEFhhvhaEEJ6hL6qKXDzQ\/CjddIiz2l2tb+7nnugCggc516CXoGQIkTEjS5vBeAqkhtcyBS3F\/W4toATCIZPPm8U1E7Q2tURWA9P+lKPoOvFxGLANTVh6BxiasOMKes8IH\/6E3umpV5ajzcZYFeoNDrUcYe0nXRbfOnhM9VyuIcoJnCfJHZLXJ1MUCdmht5sSy78SVHI8ngwOjukM60fHK4mqjHL6qqexVa0+7N\/iKNdF4m4\/Fpx5CKoy11nDEhAq15MrYk775hs98hRLX\/h+WPccbwxX3+iDVLiLrFoujVLbzKg1\/ZqP5NQmEuN3hi27rA6j6kyPOs5lxXqG6EgDBGVLyeFlacXK1tC1ELuW4\/HlVGC0GLACo7x1OfU3VK+y1efUrSTTzgZn4=","hmac":"NFYnSILBYIuaRNngAmgenlKVIzQjrNI58924O9wVtP0=","created":1325483949,"uuid":"0EDE2B13D7AC4E2C9105842682ACB187","o":"b3BkYXRhMDFSAAAAAAAAAFx\/NqIo8EXowE0JkyOXYU9TwZBTupG5WKRVaYrA\/nU6Jy2xC2eyZV0SGmRVS8yt0A0eRVEBXGww2UV928lrUYGpT62kMa54yPHQ6PJ\/SBw6BITIoZqX91ohdcm+vUDDwkoNx4Vm+0VMFkBHRnAtT+cavKUMMmjdWrQ+0rEoWIVtZF47tOOUhh6HdGiY43ihsA=="}});

View File

@ -0,0 +1,24 @@
ld({
"13C8E12AC8E54B1F873BAB0824E521BC": {
"category": "001",
"created": 1325483949,
"d": "b3BkYXRhMDF8AQAAAAAAAIj2+ycIIdHiuA8R2GDHcmD/kq9Bski/xVY/MzB5rKTOf2Ok7u9iGyq52/H02zob8xQAlMgVgCT3b6ZCkdNCiDM9G0Io+cOC1c9Z6KwY+AWjAf6N2gUdVthpHGSjSWHeQg2I+B9rZw8G+5hDWoKCNkz59sHLDVzb1utpTX/yqG//rBwdReGMcsMLdJ5i5z6rCNxDOzauJsPOmdND6Yl8qN8biW1Zi69j9yecKlGKbZGnkJ/BLGcyATw+kFTUjSIqpGQuJjRil25+iSyOQIRyaFZuI8LY0VzNwDXt630uVw4FCG/BlWDEFMxWD2MByKVFyMCXb/4jyL5EatoSfuUfcgUNjzcxMUDkZ5arCDy3nkycKUnnYEY/8mxw4TB8FmQNjtDcSuV+CxZXtEdnkQtxNh/j8DIFpxBqDcKmEkXDFkoBzz4JMttkJI+VUAH0rt6xFHEQBIrVMJiqc1oCLBpDCM2ttz5Bex687+zjo1PkqNC7rNKz5pc+2oT34i56z9BOSCUJNnPu3AbXqjTodWi/t9uQ8XbzozvLHgbz/YX4CwcjvNShZUywWScls8QwrCyHvQ==",
"hmac": "rBcqgatstCubnHbtCS4hTZ3iyh6Jx8c1pxJCwXy2dAs=",
"k": "Q/2/LDLfd1NAkd0nTzKgs3WlDvPtLU4iZ8RjpPCD4BCcuosWxud2r1vz07B7T2glcdW1Wm0eK1jGaJsSZ1mZARiUfkY8yyFjKHaw5K/S+TYHV2ypQaZwIH+qs9qpzRKgTIriMK6ZfWHOJAMPrB991A==",
"o": "b3BkYXRhMDGLAAAAAAAAAPKPZ8R5CHeY7YrgLUdA5b3Ay63vr9yrKHxyHmBgeHCmSQVM5RwqwEVQrbuNDNqQxekF6gKL1cB/P63ZSMfjYVtRGomifY3WBNsjbf8UYffI5mb+yOaKB9U6m300fbkVQshrVWTSwibhZe0nl6V+OqqyMJ1lWDRSt5fnuYvsDBUlggQ5JyZTGTadGAwpi12LihW/hpSEqQSoKX8KTkHivnTP47TETjYqTdP8XBUBQqz9XXb5R2o3GcRrID642HAzgg==",
"tx": 1373753421,
"updated": 1325483949,
"uuid": "13C8E12AC8E54B1F873BAB0824E521BC"
},
"1C7D72EFA19A4EE98DB7A9661D2F5732": {
"category": "103",
"created": 1370114995,
"d": "b3BkYXRhMDEKAwAAAAAAAKRrSzqtu6qyKtAvRqceTqZtWt2ehX/OlzSgl8+vLzoU0FjWtb6pTvUZd+YexvO/1GwwaI28vEWHiaZoBsXMWjJf7gdJkW7vEZyXvuXnpb7aIy7teoyvZz8t3xwugKIZ6pkdqoY6af+qRpWpIZHeKMfOX/2RCuh2V9c4tpO1ZfVqHHDY68yL35SknbRxS4oLdGASa8WiENbLdl4uWZhgEqkQsB0HqOACRKilT8+/TFC5Nhf7zKKIZk3b6mSgGmhgTshUs0VQG/IyvjN3GVfI6Q9cZyEMItow2XVauVAsLMry8x1qALt+C5yTmly2eAIhzh5EHgEE8IHGyYO8qHV0S/rwdAWqUlljVQ3jzxwpPkaBq4RM8zLiJ0he5scOSV/qxSLvBy2OJ4lx9tpuiehKKI0/kRjTXgirsEA4O0lMBn/J3jNdAco4DygSD8OUzhA0IZEz5TWv1UCrMQ0QVc7ljlQLb8H5KAZ4xIue5Wd5FPi+7SVk6VBhJZFZBtssy23tklpmIxvlrt9axmSgLxEMUuTSo7Hecexh5+/1LVQeDtHjOXA6XrSqcFkr1t2Q94mXa+hVtLLPCnf8DYSgdUMevMgNqgKi/TWh64GqoPvxx+HnrBLH4WEIpBZhgIKRNuww9vkMaAYw+RtpPMcZzxenJ5htM8ep9WRfwYg+Mn6yCijYZgQseZFnz5EAqTzXx1CzP/vPK6IQlGmZy0kte8VLPbngSMaUoQFMnlQNTZhHwm85N+wdvDVZStas1oFUUtHLLSt2KCPMwVmuoiFevO6Zlstys3uy9ATLOXiRTP3MBEu5D1ahC2+Mvi0FDtf+YSi9UbbCBF/4/gNt3eSveNBz6q9LsBLTdCMVz/HVjQpn350irDKr8+D+6PLx/yisia0L4kNKUOIgRATpIGgODaBDjslZRjntAzC9eRT52hbbSyvAsTgNnsriBMXE4UimwwHe5RxPBOshuHrCnHjljqYVdgd3UrrTWMwQSorEom3i2kyryOEWrlZGCz/aL2D7T0Mw1SLxDJvfkVDBhX4wdSLQXXa6smgyRagvbxFzx9r3fp5zLJuwzc9aS1tKB2Pzvj34xTcTl9lzqzDtiMNkNQLYkHg=",
"hmac": "/wRZl/I6nGCLlYVALC2sOHr7GTLXW8PViyX+S37Gnsc=",
"k": "NaCkXC9d/ohnFWkalmyV/CfGxg1f9JqCLOdN96hnybygx4lPuV7d+XOhNkpUybQwtzCoKwGVPkp6IqOfa0s4aIjxbmHZ7JKX4YolqyU/IJRIJK+zVFXKdkH4jvSnf9WLPw5odKypNLAkWwmz0rGNuw==",
"o": "b3BkYXRhMDFHAAAAAAAAAAz2opVfQ3X6CoxN8yqzudxCe4dcegs++FcJQKDDzGY6OsA/3yP3Duoj6lbDcAkFfZ5s7OGg7HqmiIh8q+MhMUWFjM20cstKN4ghssKJzaSJgYaA7vj3OQj7H0/Y+/frnfy8E1jxpVim3sdihUcw8TbzGHgLATwIsxdQCK+KqO/L",
"tx": 1373755270,
"updated": 1373755270,
"uuid": "1C7D72EFA19A4EE98DB7A9661D2F5732"
}
});

View File

@ -0,0 +1,24 @@
ld({
"27DCFA2810B24083A3ECC7CEABC7C0A9": {
"category": "102",
"created": 1325483949,
"d": "b3BkYXRhMDFDAgAAAAAAAEyAoreOe6YJDuid/4d+Iku6EpI5wiM21L7QyH4nQCdqsFff7G74sUOfg2959KS+c/m93Bzsq9cm4iczcQ031F0pI24q9u4Qdo7shN1lyglnMXdNq8SURd7pP80XACSeLvbMwV4AovNe2tQZwt04wIAXWenafWb3ooEg1Z+1///xsNu+LuNn1s1OVozao6Ko8CGmMkuNOEBbcSLJyWJoMubdqnTPZPZB/Hb6gYmJSuvEhgi5UlkJXQThT+zBU7OxjIyn3Se/Gbb8ULiW3SyHqkk+ND8eglFFowfg7DzTW1b/bodK6OoiYthSEMhS2YMOL2eCN7QMaWXRC006StHEbk6QsK2lVNY61aCj8aOxVDdxbJLVh2XWHrjyoXeUX5y5MZ6DMJP10KNYnhsB13GNmJoC6LgP9FkkhX3RmXY85B6xgP7pyuoc2qYJ8IhEtR8TUBclRd4OTtaW2ppP3rkGLpvAC34WvjgsXfJeGCLZl8s6hO0RfGvlL3ssFYfRk3YqazUX65gwgWZ+ahXcT5lPavGB43GSmKykNMKhpG7GGYc1rPCxef9PyhBhLXJdDyj7XOAA6EZuq2cP39+bitPfhbBO82vmXP8ozFYcCoRZfa/nuJzXZ0gsHIFlW+MmtfTQ2Ig1rBIcQVWssWuFgi8q0dCnJyotl1jvM70BwNP3bG54v3y7sglCN8rCQbk33ECGsAsaSDhQhYHN/JV86TGvYcF83WN3W6wyVfHaPlTGMttuQQFEUpJSyLHw7jipmTg1KgxhSqpTXDkAx8bS1960Ody1AOi6wQDk+XbHRR/lkF2FFd88JlD5V5+P4OVI1av9w8lGlbkZapT0px54tRzfCHg=",
"hmac": "u2yFnVEILAki+YP5dmN/gbSflq8vyKd9Aaby+/R82BI=",
"k": "2WbOqqNF5mqW9d7d/Npd7CpSTdEL8wwzUIHYpvJNsInJ1DuXkPnKxxWDiLVaVn/kMFo0ssD9U7+GPzp6gJ4kzxKw3OM5W6XfJdMiByDOwul6RZFAgAhH0oiv5JeYUsN7AJKkBWskZK35txYvHROFLA==",
"o": "b3BkYXRhMDE/AAAAAAAAAFlrCvy386Pid7oc4h8DdsFwoeK+rW0c3dtT75fhdL5CItIh7DHMrh1FAnXEeQ2jNlnxNjJpif5Eg9YD4+gzdYTMp9shvndwSHCgjGwZxkeoV/5Rf6TEDZq519yfwz2Yz+qHfoAJwogUokFwjP9i110=",
"tx": 1373753420,
"updated": 1325483950,
"uuid": "27DCFA2810B24083A3ECC7CEABC7C0A9"
},
"2A632FDD32F5445E91EB5636C7580447": {
"category": "001",
"created": 1325483950,
"d": "b3BkYXRhMDFDAQAAAAAAAFAC8wz9EOSY6x0PZoJnNWbYplXUkyusprslpTLDCTQc0hPEhAZRUQU416rdA/SutvsnG9AUhSWSQe8CX0gJqwCUGQRmo4+krdA7vrR+CmWzQb/AP0JiTew2aRE4lc6rq1NaO9JbnPWa9YwAv2PddpK50sN1tQJn4VYJGesuYIiS6bdiaY1KQ1AdHilZe2VBDUIHfN9KVfWb3fJmCq2nwnABfBytNXENHvOhFg/nJDr1Z0abxc0amS6oTJ2aXhL8EQ/3diLB2qklh8+LMdxvLeF6QLlIlZuxPwQr372sGPrJqoz8uvKVToEYNtoY27W17xEsB+hIDARkuJBXfElaQ36F3peUuaGrAhUCRL2g2lDkLXuKUnfHtanONBOD6dayQs7RiDlBmrPc5/YNLTCwTrxdgNECiYiAhD/Mm2D6Si/umojN08UbFhltKP9YDjM6jgXdGDbYuQ2IKRwZKpHFVNtohORfQHYmud0HqqmMIWrwSyLwq8KpjpmMi8TftOusng==",
"hmac": "MgjPy9MB80eoreKhbqI1tjwQ8EpU5W6PK2hPMcW60b8=",
"k": "btfq8UDLgzCLQw+xNAAIc8cdzXZUxIz6PILHwGhbY3ude0yfPSSksiYGa7p2weiM2dHv30hgIoBYqTgZbzVz04WmJOskO9CBjPPrqRzEm7OcqGS9UlnHv4+E/aBk74s350oTi36u2w7aIEjqOY8R6Q==",
"o": "b3BkYXRhMDHZAAAAAAAAAMvusYvnxIbWpwLZDSSorwy+jIlf0Og+FkkoIW5vtG+ASwB3oyLqlQHpsGFMhFjrqYcBr8lIz3aBUM559wAKzfLpY8cGjldTgRQaXwuYj+N2MhMgPC8OXAzn/oIEg2WF8tD2KXmbzBb7CwyW5wDJclx8XqGft/rQhKIjz1eZ5qqwK4OMbS8m5kSHp8AKivCFIiVX+7Y+QDUkqKuQyTVCHacKUpI47IEdNxWn/FxKNE1oxRKLv+biYaB4I9ugPvCr9UW/lpPZgnoVD407ZSe9GPBw75NL/tbyzK6s140qxXhscUKwesLuCoXhWyD/5DKHrtMlErWM8I8CGb92WQvVzrLyszr2Vfg1V6sdL9YmTkN6",
"tx": 1373755400,
"updated": 1373755400,
"uuid": "2A632FDD32F5445E91EB5636C7580447"
}
});

View File

@ -0,0 +1,25 @@
ld({
"358B7411EB8B45CD9CE592ED16F3E9DE": {
"category": "001",
"created": 1325483950,
"d": "b3BkYXRhMDEGAQAAAAAAAMxxfx9Ei8WUX9X10+6Kxgydhp1zbbdPIBJ/MLSii+gEhLcmDMUQA1AcTKEygw2nNqCrdJh98c2noGtV2qjsZnwiUCEhWMNCTpu0jeKXVBu36zZLwKb367D4cU20GDO8nMIXxlkFKwO/Ni9oAFGkl4ZSsSgTcmDqNtbTa+EXWTxBX5ZL3WawCtzYG3RmgVoU9hATk+kM5ZRJaR6s+mE+DFvVMW5U3g630EkGzf/gZnq3x2KHAuHt4PIWdSO+8lwCjbPKYbLoSS9FtqDkmLsiVUcOtW2uxwxKtBkJ0OUOLgkUCDCNG8sUKwtOtAs1+kO73m4DWAEOhe1994PNT8Qu48HkrmMh6KeG7buftl0vMf7Duq1JzAQ37zmPZ9cJlqpQRJ1H7oj6fVKPhfPFHxSKWcXUHJ4MwDc1KAxphi8gRzbt",
"folder": "379A3A7E5D5A47A6AA3A69C4D1E57D1B",
"hmac": "/FLC0nyA/eYKQ6vIRDDMScZLrJLTFExu4CQ/A2R3G6A=",
"k": "AGZoorj389dIbQQKUEgPR3gPuh0T7N3RNwbY5gBtD50yrLByRtc9moNTgeHeayynwiz52vspjCF9214k3CXUJOVRHcZnWFTb12xXomrfkTBR0cd/wLsoUZufzoG0vCUlYDrI5CaFBY4a0vgkdaGBpQ==",
"o": "b3BkYXRhMDG2AAAAAAAAAAUcSSE9Pr1ma4sGgZPQzjSCCDwKleyuTm34b0i1k1OSX7WJeqRo4B1ToIfoxSxgFhZ4wGvzkQvBS7/E3IZXND+b3iKiGYDWBElKz1wZiraRhjEaSs1turD7p35T91asK05uqQlVehHZuEGXgm87aQIMoUvgkAND0Q4jYd8aGMechyQURQ5vQdKaP4iqlYGaFqEOh1nRTfZUsyV6yuFJLAHLc6fb+eE7Hum5iwS9fS1JE4KZdgZmETY+0JvIhMEp+vsZIYdVlTTaWJxvU+imEee/V2cx9sn++G2nnuM9obrGaklaj5W1ysu4BGEKnYw9dw==",
"tx": 1373753420,
"updated": 1325483950,
"uuid": "358B7411EB8B45CD9CE592ED16F3E9DE"
},
"372E1D51AA1D44CB9F17D8AA70ADA9A6": {
"category": "110",
"created": 1370116532,
"d": "b3BkYXRhMDGFAwAAAAAAAJIAigy3ZztWl46Kx16K7KgQOG6mQpq9dv0LWtLF7vbFaK4wZU9bq9kv4FFt088kLAjAH2ToJMyF0QiUiQDxix56mahLDjee22iUbvVaza/QSK8SfHFEpyR1Ecg2MRsXvn2DrwUPNsIrJJ2X6kdZLN5duXZGuhqLDITxx7eBOF+J5UWyjIBGDJNs4q9kd/B+W30YBtolRhzaHNonaNAEwEKOYxBjOnEE1oO3TwVRYqq3IT/fqHpj2yVfTMKa8WtLy2g4rGGe+8NzkiXPMND2cYRo+8jwsCBY1Zxvqw0149k/Ly6cUam0nAlq7NuDfpoT9J5rCC1UdFNKjE88Cfoarxcl+Kr3ZbYFQA3POhVsgFusQX1YyKZdxZWlfyPWb/SvkiD1vQmM5DhOs5XLONnXDTKr1xbWL+zlJxruYSWRxM5qD9oexnv1U06FFOUVDGpFg9fzbWmbkS9KBSGsUIyeqmmNFMa+5WgJ2Q4olZXUE81WTsPi2FerncvHnGd95n5m5BW85icZwIH+0pPUUlYFljhruBVXa5+D/GMX1DAzpBHRaEmgDYMJrhsLgaArXJiDw9drHKP0gzqVM4Ma0TX9G1Cr/mMEW2DaVtGNVywiTNSMMoqazb7hxTgITiTttShLv9nBUiw94vDKhcigD05lVXAsbXPqnZUwwGz4yCkIkhB3dH6u/9UBDqtB5hXFz/3taMPu3dr2G61Aqe3EU40ihz5D7Bp147SoBcuCyPeiOGzdfGa5zuGwC/IWg2Ii8nJgfhBAD2Va8hsutTI7Yc4yU4Ufla4cX6d0NH/bU6ajZIb2oFw3ie5fzyk7pInlWUhR41z6CISwPRmC9SseTKQzZ3FsMqKZ5KcBNlYAu0v9IFawC+kjKrMwKl3W6NKUkAR8AgM4HCyskjx6cbs52Jac5J3UIaUwj3zjoV0dH+6fOEuu44Xr3sk9VBJ6zUHiSV8OgNV1XEFlrqal+XfP60Rr5RaeztFWT7y0q+CFoS3ZKzNItudi0y1zY0ZnNsUbll2RCYlHULXE3Idxy/gsQJ29Aj39DTfbxvqGB9u6PUxewfvHnLkNXp7cjl3wE4IsaVHCNsL/ZBNgqwcaVVos9Wx1BtIWWhkOnmt2nrRIFz9vVdry8nEW6G+/IIFe+1e34oQmBSkGD+4OCGxGq1/Yctu3yG4QlumKa7SDIATG7q0iX0IMfFE9ws2RMBa7YawjMkRItFw2gn4Egzz8IPyvh328JssIW9oo2K/O0if5+ITWKvHvDDbi3M3FjJKMxDGPvAr2GA==",
"hmac": "ebtQCFBh6f8fdxLuO/3B3K8qoQrH2U96t+5yFqrEc+w=",
"k": "3Yy1faUq8AlFc/zDAcePMDmbqw/Y6vAs4bjjW5Y7enTU/ww9XDh7HEpVFiffEI1ETzuBOF2mnj4pq5/Y2dOiIwFS0tUqLwTSrIwHx2bnIohKygGz/52SpsiAeo+AB5D7UEVCaQG+RENvlUcD99cZvw==",
"o": "b3BkYXRhMDEzAAAAAAAAACY1JR1jdIYnvKtyFND55QYVRufOfs+Ple73caqc7u6VWTX77qZlqmrp+ihfaQQIm0AWFLvXWZHsp/08THTxBVgx0GwQ3iXJnGfvkhmeKiJYy3Lmb07alTphswv73ZZq4mlkpVNimYnpjbg+4S37v+k=",
"tx": 1373753421,
"updated": 1370116687,
"uuid": "372E1D51AA1D44CB9F17D8AA70ADA9A6"
}
});

View File

@ -0,0 +1,26 @@
ld({
"468B1E24F93B413DAD57ABE6F1C01DF6": {
"category": "001",
"created": 1325483950,
"d": "b3BkYXRhMDFcAQAAAAAAAGlGNJ9in9DhzbvCPbVZnE5f5STx/5WJ7lB3irIT0npLHKxXvYi+bcYQK4kYbJbksYHsVS5mudX500yH5G155wpmlWfFpC5P+bHrZf0Ex6jykiOkpFOdOA7K9CIv66+z4613nyZujcslvAgKjIYB9zNbfep2kZwiW6ymkOvLiVrL31H+On6kDnF9K1cm/eoX75FJaVOVIZWHnTeyjbQ6BPxOTjTviDCFo9cKqlI5I7UzoeAXatgeyLhtZmwie2YAKRbZhzddo86sUnbG22Fb7PXxHpRQZC+rINqhyT1tM5a5dF1zpG3b56+d9APmaYm3bLtNvSV+z6/ueuwi+WQ5nDYXG5WzrkeDbidNkZQY01XU/YkNZt2rlZ+e49tDEvxA4Sr/0rFFh7j2+zSVYE4GJNiMMKXdHcnzyH+Rldf1zb3OJN2Pgtooklw2d1Omj1zmJAQu71FI1r7bGGxFR1/P910nnIpAHyD8nLO50srqkv1efJo46Biz7cp7/H5ZDMcKgfsBCsIEJCVLHd4Oz6nU3kU=",
"folder": "C8CE328220DF4157961787FBA30DAB96",
"hmac": "acLAZabMMQAHmZgNZ1nzTGzV3sBD974BHboOQr/EkBc=",
"k": "i/YaJr4KrTaO4/herbFX6rYcGmzFnBLyPSuyFhlMxLPcFP+c0a6x++BZ/DYQ8qPubQOa4HjHigcQFawcqkpUmAnP4lbeAPxXGjRcGp5TEORRp2WWzMItfL5f9IepUgjx8rlInWJEwSoBvz9uY2pjKA==",
"o": "b3BkYXRhMDGcAAAAAAAAADx6jShnR7SI/Qj/CJyyL6LSrCrFfvW52sSsdvg/fKG36swaSi62yrCrmXzKp03/bSlRSyNY5YsRzPU5weHdBk+LE/klsqnKyI/Pp/HHOFlZzr+HQoSA+PIK49HfFxRJNM7bFQQ3FQD6OXYmlY02hgdtfEFM+rXU4UrE2Zstv3lHMXLtzbNq4XY12qvoxjtDspQyMCeRmudOtzktiI0zzCs+tKUbd7vWPvwehx+/BpHmAmVkQ1RqFBsHmXWo3LRtEHBRrAsE1Vk0F6dh4+lUZsI=",
"tx": 1373753420,
"updated": 1325483950,
"uuid": "468B1E24F93B413DAD57ABE6F1C01DF6"
},
"4E36C011EE8348B1B24418218B04018C": {
"category": "001",
"created": 1325483950,
"d": "b3BkYXRhMDFcAQAAAAAAADP3hpn5gvc0E6c/jeTCZ3+WYfg5i0bvAV6/aL6Mj//jGn9Tn8pioTKNAEwqPqW4QyRDbBeNWasvnQc0W28JO0zVNBUGP3nJkboX7Wk0tNc6rOC1C916yhwxNbQtdXMzT9CFdanEnRxaoVEkOk6iz421A9qMyvDsx6d5PUJx46Q3dmIfvTvlmuaVvq8f0rkwjric5gINViNv9GzcmL8wOdBHuiB23uvoZZ5zsGo2IzfR/6xaVFipav45j0Dvgj0jY3dluqf68TdTfM4TyRHcgCIvd0dn5QmZIxB1PjRbH/9oECMKWcEWBCxISX8mpCVVqva44HErSF68ooPdelgLQkzMbv4Rf7seLnckyvfHqPagY+ENSixRcmMlh/eWtddkqj7uMSkPKJ3pVrymG8Oypw8o7AwEMP293S1fRLyt0kcq8srHcehEs3gVpnQR3pIYfvzxXv9YcxMeUmCgjCvy/B1VKSvoCRvMv3E+9d+djN595WiUKQ+dajfNFO5/cDYNtfeNzQopzuzGoKX3bMQUrm0=",
"folder": "617F428170E1455D9503EC75AA103859",
"hmac": "n/iVpXSy31QWaWlMvvurm9bWez9Xy4xJRZLklYbMIKo=",
"k": "E/icM7PfmTWXDfzNZ5qg9I8UFbhBl/PeE8lEAXbQWtaNA0ZcxHlpS/FA8g2orTpkHgdtWcGEhTYWLBacYJBKZojvFbBLg+5LQW6G4gXaqo1Axb8NWrH8s0e0a0S7r4smTZeOse4aBFnRPom2npSd+g==",
"o": "b3BkYXRhMDGMAAAAAAAAAOoU41poJnZBcj5J5rn8sA13uFYnvtvJsopeKlAgSByFPUXLoNDlieO3Z4WdQ1rJWc3+SgUybS6wbgXejydTAUxRIqEReKoVnN3PdLGRKix0LffTCNRwPHy8emssJSIz01QYG6b10eRQ/97tTrSyvUOY2LP6Ja1dm+TpT0AeTUpTIKtRrScbWBd67uPB2gnaBOVvf749JkG5/fukkIm6NM0oxUWfOEhFuneppHRgiYx03xxyOFLo0hkfhdI8pXeFMg==",
"tx": 1373753421,
"updated": 1325483951,
"uuid": "4E36C011EE8348B1B24418218B04018C"
}
});

View File

@ -0,0 +1,14 @@
ld({
"5ADFF73C09004C448D45565BC4750DE2": {
"category": "001",
"created": 1325483951,
"d": "b3BkYXRhMDFTAQAAAAAAAA9fJBca6be+oz0ye3FsSVyJCY0AnQXWfJgYQgYThr4c7eiW5POIskdjdle535X+cfFdriq6OUxNv9VIbUn0QUI1jP+V75VDoa7pDIL+zpR22VfXsR6RY5S5JINpEbZ+smIrMFphM9+ToD/Xli8zxmcqfywSekbLMkITwpFfyv31ZlB0I2WZCABQ5H/P7+UIRfK+jnwPc5VkUpQW+Bf81OfTNKa8N2OH0XIUzQEKcAG6ZpBohM+V8RdVIR+7Zg77uskQU28n4gIdJi0jsoFyarM1NeCoysX9cpGJATcNNI8XKKU0LxC18yBK0ST6INXXQ9hSrG9wEv6cpsdix/GNkma+XaQp74Dar55+DnauAZpMEfJcGe6PKSLA8QfUYtiqqD9Voh2F60dyyIVqXcgdpeBfVf7jdygOXuG07dKp5qW1w1eUH8I7zLX8Y+msuFGRyHXQ1WvB4qU+iiHyy9nxP+HH4fJbl27fUJ5q3L01o96Wdi/2MVhAYcoY6RD2DlRVUJgXDW/abRFmYnyXuL6MnnI=",
"folder": "379A3A7E5D5A47A6AA3A69C4D1E57D1B",
"hmac": "U1LfhLPcGrQT4s1vq83f5ikspRc6JZyUFmzpwyX0Jo4=",
"k": "CcSDvXgNE+Ro5U+MXx6VoYgA29o2mbTP45K5GORJaTgb3lGvFLZs0Gs7eecAaCQw5w/fJI9Frl5pl9/ntH+jJy/SOyg5KBxsGtnkjG3LXOcEJck8BBqWI/T2dfwfwSIcIji9dzZvACWifGNgnMdzBA==",
"o": "b3BkYXRhMDGbAAAAAAAAAKl92atkS8UPkld5AfENoWUe4WN4E8iVpJ/bj95sFeIthtaAHWk3OFHRB5XuBunuM43sUpiAIbCVuoosOMMpM1dJX7gctwSgpHen6ObUx85NGpgGvk2rhII1CzPhER+ACkIlopVBJd9tZsXf9sR24pO62soASghk397BuyaEkobgEnS7x8pBdQ3rKnXBSO5HMdLmb9Iw0YFZYHDORrJoQN19TFqIH2LQHe2/yvdMGD7r/gCbHZR/cqPCnIyYOqvT9GSVcsnLwxC9y5PKLV6Mng4=",
"tx": 1373753420,
"updated": 1325483951,
"uuid": "5ADFF73C09004C448D45565BC4750DE2"
}
});

View File

@ -0,0 +1,13 @@
ld({
"67979020CCA54120BAFA2742C3F23F2B": {
"category": "108",
"created": 1370129714,
"d": "b3BkYXRhMDHAAAAAAAAAALKcrmbSK3N10mz8SnKVCpdQS2cYLptNG47UL3OT3kJ3HFTlnEZUlC+RgPGWt1ZTSiC+vGBFMIltHU3o1sJ/LxO7k8nSuX3Iky4BadclqAur8ux/kH2TyfBdWTu+sRSskE5tMb3SB0z3Yfv+w5nj3c7amD2eClrxwFyjW/Jv1reHAI4p3HD9bbDxVlVxHFuqsVlwsb8fiAdIXmhtf1ZQv8XM+Vd1KBSHaKC/nVcwyG/ZS0r4CyGdiQUq2bEvdERssRR1nzjT+g/sFseD8q4jrXVXhezXQdstl81GM3WSvVSm5lT/z6qMbCUrcPW7AZsFIcAMqtRHexBvKwfjpn3Tj5M=",
"hmac": "AVY2ZVXViuYtgfnSKShK/ZbbVn6T9SMfugz7F89Kd2Q=",
"k": "NwsqfULiH/XRz0LPCNJ5u1Kv4Onmqmeu1Ye4UKmipo6YspWDQ9zswlSWqgtjhKVzsv+eq9G6qQftYwG4cHbid18RdZksQWqDCrnE7arx9zwR9mYdxB9Eymb/nSU4o03D9pkAk/niM23vS7qkbbap8A==",
"o": "b3BkYXRhMDE8AAAAAAAAAPnQNt3DIzXvm/rjmdk/NHmfWLgOs+/hvM6nFutXkkSPcWK2Xl9NAzyoMV86XJviJF2wYd74eJFXZgFDgflquGnrK6xQifFqMj6zxVF4r6EACcNtzHgsrv054MFtKKiZm073KEQStDhnI2dwtRWQQjM=",
"tx": 1373753420,
"updated": 1370129765,
"uuid": "67979020CCA54120BAFA2742C3F23F2B"
}
});

View File

@ -0,0 +1,13 @@
ld({
"72366D161D9E43D98E58EB801DAD1EF8": {
"category": "001",
"created": 1325483951,
"d": "b3BkYXRhMDEbAQAAAAAAAHuKL7sJ20Yz8sgns/j9LqJDQy9lms3XaDZBwYt8bmEKK3t2nQxNvUQVqxISzoRj/nX/axFvqcEOSOzZhxN7CvszP9eBPmTS2zTZvz4iu4NQ/LqXlUJ6wpf4HTjnhaqqcunas49y9ahK9xSICfo1mbmVyUI2raUoxMIQCzthfS/Wqr6J2uk6I0RraEO/eJBvOxO+buXfDQ5Bt9WoZREy+o0qcGEUs6kIMcTG5PmbOWV0DH3/Y29ggzzrUiaAbmvcu88e6warGI5Ii9gnW9iLt3AIFtIvuZQNhwyDDH7e8LPYOdusu7MfQGannWoc4QGTLnUkZrIozo3WTsoCEFv1Q9sYjyucSaR2Q2BEHVwiKzPsu4YaUADQSl65IgyfMRhjpU3qNsIxtu1gLjjRWwx8YV7BTOOSgz5MMwesZrMX5WcsulOgH6/TRH6mQtbi9d/kTw==",
"hmac": "A3fS6NKkoS6T1vTDvd+mVUWweXeNnWdPYb+T81WfgwY=",
"k": "du1CJ4AKSNBWoORyfTICsCJ9ltR/Jdy95IwZBXPsxD2fs+LmzTrFPB6sXeoB8Or7aaISaH6fzf5PJfhwIYs0WwtiJNMsHQOJ0aijvDMmpFvE1EHle+E/V9aPK0f3nws5opwfcUAxQVKAoZCg6VFXng==",
"o": "b3BkYXRhMDGTAAAAAAAAAExAg52C/fG2dWHCUgSx+8mg6eRc4M2Z0Qb9+ievEU6lNLuHqQQAEnJhe8zJTNfUm7bKMA9aqNrR9EpObuMR1j+uN2pIFJmD1pDtHsemM1vnSr5tZ5jUYPjOC7pWJyvC16ap4zBPfDMcrUjCVjgnZlppyZ3cJuxwVJNFRHUqShpX7oetObnVfOeixiUsvdSFjEGC9dbzvnQHrcv4G+nwmHtSLI9vN78SCWkX8I8DKZd4QZt/94Am9OWArX3r+s3Yvq4HTvNto/kC1q+a3k55AJw=",
"tx": 1373753420,
"updated": 1325483951,
"uuid": "72366D161D9E43D98E58EB801DAD1EF8"
}
});

View File

@ -0,0 +1,13 @@
ld({
"8445A23B5740455DA360FEA379C3CC90": {
"category": "107",
"created": 1370116459,
"d": "b3BkYXRhMDH7AgAAAAAAABhbMvlFeIwLjjtGPnVc640YpM/uYRO4JbNRIxb6cSpo+FU9mOanKkql1Ffwu3ZRPSQZ6vloEUDRMqhGr4YpWM/v6lIO1iCVtegauIEVdAy+uDSAQzvC2+2NL3X9s7WgAcYh2G94JVbj7AharSB4VCRxc5NwDCViTkj0TTYDa8wDfJfQPhhM/+tlfZgcD9ZIUteFMtTPjoJwduphbScjhyRzsQM9jJ2scrYjasrnp+tFjUFQdTmuvkSI/10hoo3htLLF4bYpUGa4LPzu/CkjIotaJrJTAXZq22SDtrmYZEDPyR+epZW/2NW8di0T8gnPbx57nBKmvx1QWej3noEFcDRwtjwlp9tapyC0VFTw8xG7GaVNHc/xzGNbzQhxseGo2MPyIaUfx/9/zt0X20Il+wxX3kfn97F3DXY8KQOAHBrTeME1HD7HAvHyDnMtFX6IjdfUU488OuGVPWypk623Lmdv9KRSJoYi3bLHhR9tD4Txl9/kL+gLoyPosnr0gkua8Lfss89hohkqLVh+/VlNgwob62oBhSuS3lIJwyNl0abVrQ7jWQhkuRCXcN2sJce68cWYgvYwe/4+h4o+yfURqiLTMvts257etCN2dBkRucuNWNQJAPYw5HJ5lhwgVWVXKqU0hx+TeCRaxYDlQT08M7Pen7tmNZL6rR2f/GCz2ek1ZSoErabcFOcGExNJoLS/yvBAVVv9qOAa+m+fH4rVRt5eVrie4z5Z0G4IiL9Y/aGKqZ9JJ00h32+k/SmHmoYn8z6u6jBTJmnIVSIRzhQpn7YCTT7bu1b/WlYIeYA+/bNAUaqO6yAjuIFiQipocGyvkmkxkC8kIgsomdpyllNeJYz7ACc1bJVKUs0ZdYWqk73KlQEXB9GrbxCtPqsDqnYwvFlNgfc9ebmxUIKK3HsG4rKk03dj8JHQa5K8E96/qPxNLaxCX3Gvx0vKI8zzn8vCmUkDzHTC0Yz2GT4zm0A8ps3HvuX7gRqU5vs/E/UkpTaaHIM3CabTRzPLTstxfkMo9Z4Kll+g3VvQeOHazW+ifGYhPVVFk3y82loPrqpxpy4H5tlAB3XKG3Xgai+3LWqFdQ==",
"hmac": "6NNDqDnF+PHFS/RYa84na8Uo0D8FFiKiBC3VhMTUfSk=",
"k": "PILmWgunfIvnhZ2SUnP85rywepsURtQxOXs+/+KV+o+0Of3dpWiH/2vbSuQtA3nWgTkzAFoKMQozy3ekHgIq3X56ggJZBaEGVUd0yvqZcQthcdDjCTdwo6jlj6yqnaV1gUxV+xxi2KNN+OEHoF/F9Q==",
"o": "b3BkYXRhMDE0AAAAAAAAADDeatvXdJlmViVGv/hYR+L8uv51h/sRlE4N8bLbsekQfnOq4Zjno1XOMGG96V/DdTut8NDlTpESo50Xr0kJDR6nhAZ2GibT1c1PEM2M+qYahg/vr0rbIThGlitfOmHwOXyzIqo/GQUrTJkNkR88Mz4=",
"tx": 1373753420,
"updated": 1370116516,
"uuid": "8445A23B5740455DA360FEA379C3CC90"
}
});

View File

@ -0,0 +1 @@
ld({"A2D44483145F4B41A849FE5FEA4B504D":{"category":"104","k":"AgC49BNLEAcpFwcIcZ4VAJN+tIHosv9RAxk0ROK6qilWWFELXJyJXP9KmH4pBREjmc6LcRw4BsFwKpVUm1MHbOG6khhzqCRwQ8rjIBY+f52L\/r\/YJo9XGX569D1AHqoIhPMTyvhiV2nhjhFxmVuERQ==","updated":1370116182,"tx":1373753420,"d":"b3BkYXRhMDH1AgAAAAAAAL0hkkKKdOztTeMZRdhtIPQtooMTL7xdcKM21KA+g\/K\/ZBOXRstNaIF7GPqycQOAOknUfsPUrPaNQabE3hwiPRQ7vAgqEDgtmHyrn0ohgYN8TteVANoMtsAo\/8B2UeiAigkhQMVY0OKEZE06PzHH1ez2bPprlLWocSk3y0PB06h2N9Rl6n+JuE\/bYxlb2cCpWrutM5Xb8U8s\/GmLkyTeDv5ZrqPP4zWaOPyPrnkSRn2AUxXspVzRKmCDuIKZmmXh6XkPe1I7yrGOiAH4G3rDz7AZbsdzEIKaT2CEk6MWet+U1mnJoVzmQxKJnwSzs\/atnQ+yOlSfDjRdK5YzYfyD5nOVuDJR84bnGEGMCe9cyI+jcmyCIkHP6HDmGFTLDhvvnXXIziuh05RPAIrCOG6gpxkHY34mTh5AYfsYnv4FZw6ceEegbHEABNejvzSv3fBkh99bA+h2Y9ueOMWtomYw7S8P7tjEPaTIHNtPLJy6llqgLQqksBu5YdkwSE004L\/qRJ7lNiVNn5opHyxXOeGMo+c9pcXm1N+cARTztjlns\/wCb9lWQKZDRCQus8zO4222VOVsgoFEUZ6YKrDdgz\/fraQOtSzcov47lq4cGwZo4EeMGRJmPQ2pBGQF4csHYTwNdN1dkdTT1KV0mLMYtwn\/6tbybjlxT5MtcEPAqwXP4khV9v5VathptiqsxnXl8r2A8ovQoo14l9JzyvMOlvTcJHe3L9AnDoWvUmXw8rzOoUdOifcz4dpJs5yJDVB28Aog671wt7gca0R0kAj15tbdS0RNjXN3rWdki7B9wj7f6l8JiJodvi4Xt3av7eRQSwCbfYdQ2jZ1yTaWpZ+yUF8kTR\/4zUYbemSbi\/LWIqcH9narEexyBE5mA64PXBpVvjjXS5d2JLwmXY1jK2fuofLTmNcBZsJ5cMWSDBaYjp76rgnGtG3UQ\/0AELPTKcv2n\/MVp8XFKwAsngiHP4dbfFOWjXIMMtfz3zJ11v5zJTrXuG1fCWQrewjYC3iwiWgQEk2Nv\/a7aUG87vcgXMipXMr8\/dt6+c8YHNP+cJN4TjmuTGCLa7RmfUV3I2qN5CaI0LzQ5g==","hmac":"xNhX2boeIjZSYRTzOQBw\/lj7VX\/iucniYCtky+gFnkI=","created":1370115875,"uuid":"A2D44483145F4B41A849FE5FEA4B504D","o":"b3BkYXRhMDFCAAAAAAAAAH87YuKjL7BH4gfLKRT0MfiJDJ1URxjfNmThc\/UkDXdoAD0jixu3Q30YG8xR3itHYp5Gdtr4T4scp4HAtiKJ0UYpkGbLt3bmhFGtpuH\/AJqVUiRW+t4kGzoLdV\/nvsL76oae7KgQVqSOabIUMvdIVapjb+uA9BVCOFV7fnj\/4+rj"},"AE272805811C450586BA3EDEAEF8AE19":{"uuid":"AE272805811C450586BA3EDEAEF8AE19","category":"003","o":"b3BkYXRhMDFzAAAAAAAAAJ4FWzFyJZW8+caJC7cqRq0prmapCi8G2cYsIvOxolmZ+O8WUXknbg\/IdgHcZvquPnWehKt1qlx3Q0b4wUjkH20uZBkJJADB4EixRs7gjcNEFYr5rJhcocoV\/LoXcFeNNZlSLSu9J0v1o1IO6dgdEgOdzF5irAdk\/0WxkZJ9jN5EYXCsIFFecabjAimXQZMZJw\/gGIOpSGDQWLKCDuk7pv5xRuII6pmR8jif4T35oM3R","hmac":"JMoh16y26Yghanste1vPLKPbTCOWBgBaY\/Eu08LRk+8=","updated":1386214835,"trashed":true,"k":"10078IRI0KYGRKgbidYmpqdgeLmsXzl\/Rr0t6Db0ZOV3GQGMk7n36Qcl5r6facvMwT1mgmCOSpLAsQ4MJ173G78UF9VPeqfzVxu11HQYXpEPr\/X4bRWfPI9jbWcL1sQNuYvoKFr6AGyeGxfRrPv31g==","d":"b3BkYXRhMDFXAAAAAAAAAM\/pW84fBukYAaFjZQIRf4CaloTNI\/UKT60EczrN8fSGcekBOnuxx3oNZkTIhRHhqimNXXjsikYJ015pU81u3S33G00SeJZGt2ybXPyQ9LshOaeqKg0kjIxKL+GYIk+rPkAIDixNuumgx6nb1C6s8\/JnryP4bk5mi4e2avIbO2ddFZCqzK48\/\/7nyb+zOVlukQ==","created":1386214759,"tx":1386214835}});

View File

@ -0,0 +1,37 @@
ld({
"D06307ADA44C4031BA2FF4B174DE79CB": {
"category": "002",
"created": 1325483951,
"d": "b3BkYXRhMDF1BQAAAAAAAJaT1yZYCCUlp8pOUU7XbdTy90boGVyyZAG71tm/5W3N5ktaZZFaL+OxS4LOpxWQGGcLrWTLXekyAwh8lKcGUtx+gmDlMpUR7teTvgX7jl0UOhS+NOcXZajrdPqhJ17XIeq7ze2HveKyLEpJ7zoNMkf6yHH+hEo5o6LVb0mDoskDx0Tb0FjSzDzvlxMInfFZrNPa/QdMg7au+2lj9GtTY43lX0CN51HrkJqqC5IJHti9PcXFglakVxATpT+h66xoCDreWoObCOdMlXVLhVOvYGelapHk1hIxSWdIgP0Is1ubEvivdhBVWuG+FubZVOk1WaLQJ2l0hw6fIL0yIRfeiA5eWEUB3zYYJbl1kLnYhiz93LMUbaaAiwPVRhFRjUBstgUVipLayTb3QCg0um4M4/qYIwPO6htj043Qn5iG4WSEuEjG0YjXytqdrIeS3h2ghzBPkMsW6OdPixfruix5+HSMV53+vzVLXi+ko7ScjsWgSZtYEPxc6kjcnp/4xIzhbCG7zCu3j4HBIQ4Cr1TYDWGh571zE+tfewVEOPriBLvTmcEdvQUZky3LesqXdJudKgAhGRmLLAKiyYJTOFkB36MLGh3oDqu1GIebQSi19hA+RPSZ6T+Ouz4GYdN/jHyA6/WqKv3fg/RdipaU1AbpryIoXd9xHprJ5poyaAiYdBNAuvM0a1SDndGaV2ABHwKTwzXmYhDvMdPgRg30zH7hw6LUGNEXDTlmmz4PjGndUQJaeWDGwexOOetoBLly5jYZ+/f+ZC6ZuNVyssODUDbySBVbTyrv8rnagVSRPWGryXtFH8YDfbRn3bflyX21vRYY82aCMezYuaLxAgeaX7+VD8APJtQIAm5b1Ob3OEm0CpJvn0h8OwDedebq6w/3pQPBz0z4jDugXFKhA7UQEs2w0kHvRI3H23h/TYP6VpBfu0Cc6RJ/6TcRHAJ8dyRl2ag7vEGMoGBafmik/68lEvG9CjbbOhbMw3JUcDglmSCuAFCR9etrxO7+PdlyOaCWOoBvF6+8rgRymdP9vXjnoZxTYmFINjY4OG2qRNQuDuDHPa9YYVonjCXPAJq/ty2uLD5JVEXJhsevWn5YCM7gOtR1g0lEywYHlUiQhGwO639Qoez6x/nmDDCap9AYaYumU0aAs5878aT5pXOHnWpdJJThIs2/pKYrlY8IMyM96mvBeT0odol7WhGCKEZ9O/LW+xqxqs8kyCmwnWxO0rJwuJfAH1BCYgynXr76Aus+wKqIF1cjgTsEVIaIeznKJpxMpio6OkSm5kXyEawOKW26yQS/KXeyXUWCcVrCp87Esx1EQ0i5thq5iVC6UzaG9i1AnJ340Fu875vvNg5Cfk29pUFLAMPm08V4Jlfq12341GiBs8zCrlq+3DJiqK4G2A6i4sMRblWYU/OJOhWpHTmCTs8Lhk79ZCBpJKtOdmHsyFcH15HqJ71YhX1r+Rba/beMaJlor4w+2a2Txvtb0/Khsi0RrfrHOr3juR+5h5x7cKWS0ep9wVTh0eRtuzxYRGpwE5Y2n2FuNfmPzDuCtagea3INjvp79r27UqxO61F1BxvnDRTHSE1P2tV/5SpkVrpA0BvEwpEUhRskw0LNGSmZxt0dZzFjAcxH3vopzPvxdvqOutoC2ISliInAxc8CiJw0rq2vLh/T+XXUd3hwy5vdn6leUu9pLvi385cHS1TixYXGJ3ewHa2V8fmglTjWsMjdU/v8JywYeND30/0BAY5trF5CTI3E9sO/JrZLLpTUjDhNjILGVD7fA2wRvmrmzCixzID1TukS9hdLxDnO/xvZMDjlvPdnls6MMEhi8sTAzzU0sKwX3GZBfE1H2lY4fotvF8OEkt98X6SMC8t0SQeiMx6/j3ikoVceUtkn7Lu53izp+H1KBRzxQDf6qvzw8j+7d1FkpvhZUoTXUuoIYfQwDXSiJAQ=",
"folder": "617F428170E1455D9503EC75AA103859",
"hmac": "Aw+IzC+E5wXzIONUOx2T3HkpEuOe9GJd9B2VKTDQE1U=",
"k": "XX/7JtI7dGhIowwC8kdeCJgSEQBz2473RpR/Sd/zBpefXOqSW3O2A2ar6JQoBKiFaniMFzJuCVyF3u5ho7imWT9JkvsB3N4vRNe5W4Ks7mTLnbZFjDPYu5NGqN2mRGH6i/W3wh4NHILNE5jTxMwvTw==",
"o": "b3BkYXRhMDFiAAAAAAAAAKLESLkbJBDmrKAD8sBqerT2BhZsYb2r1kEhA6bj0st8B8pOhDDAGeg33G+wDS49SSTIU0+qQd5V8NPPoXh2dOwGB/xj0h0LNOlx7sBjzb6dZ4JWpaazFoO3PSgeZBaeuD9VZ69LL5QO88Hv66yNuPKtwqxTe65lZKbX5OrVG38GxJj+qtLI32ORaZpby2N3A/UcvqKJBcd7jVK+xhf8huU=",
"tx": 1373753421,
"updated": 1325483951,
"uuid": "D06307ADA44C4031BA2FF4B174DE79CB"
},
"D1820AA8CB534AC6A4B5A2C0263FD3B2": {
"category": "003",
"created": 1325483952,
"d": "b3BkYXRhMDHSAQAAAAAAAOtI8mRSS2ZPodOzN1vWoS97ViCd1s7x2aO4FUF4U9fYznoHNE0UeNlZyj5HVpZ+LYcAm8/wHhFt5YH9rqIDtADcjRTzI+b5UZ56uUycJ9Oeh6V6oyMRyrv+0sknwYVlsQUZVXRaP7v6G0PxcJNpLH4RisxxVF2y+r023GT4EI5pncgs0J8LaqA4HG8yjy4ie0orQEyeSSmuqeaMIlGpxRapvakdpexjNMzWpdFUEnl4o+9MwE0QvgRilchQuGxD5hEUf/nQwjEU7BcQSEAbzn7aDhmuVMh6Laongd7K8XEDLDQSNJYZdY54xmIajkifMNXA40vSqhjRDB8vbLNUWS9h0uU9TV7mWUujZTGHhFPlrcxmisAW44tXuJ/nRNYO4gljVEWCdPEjHVPteECN0/50OvZvM5X9F1MYNGk/z4S3u7D1YBvBGnVxGn9TXR0nyT38dYq0GWW5xBbDUz5vgCe4w0gZ0EQ13z8VCjnM7v6uKAW9ajsQqIoYCVRv/bjXktb5CmzC4phNSH0RYYD8ByEY/Jahn4FdouLab/NoPQmx5ZgDZsKNX5D/iuXv/hQ/wkmgjaCppdPJeDmEHmwTqTvFrlCqvu0WJosC+PiOVkMq9oIXByNf5kFVLqpMEiKxPRieZWcZ8evSkrTPuSHE3EmKzzevDGNR4TEBP/zLv9leUecOqL2tlZ87iclIywOp+A==",
"hmac": "NtgrlVsQUjVOS+by88aMb2744M0TFC+MNv6h8gs4C0A=",
"k": "ELybuWAxQX3rIZCsJ5cgt9rImCIoDoqwGRzvspqEjs/Nh3m1LH6MIlI2L6NeVZNvQJvqfNQgjlxB1G7f7D/b0sUjv1DN18reNSYiEfC6J5i9OJPb6AFH+n3Pamu5azE171E3IOJy8Tcm5EonoZanxA==",
"o": "b3BkYXRhMDFGAAAAAAAAAI4XtY9Rhit3UaBvo32Nx9g9fSH7COUGvj/JrZfpPTfI975k26woxzapwHjdBVlYEmKWLaD1Cr1PJKuJtDJui2RT1q6hGv3+0yDsnCXsoM6OkSPlFisKjWA5FuaZjZVxKyZHSLHbyNiR+hj0PfSVVdFVoJs/WNfa8KF3NpD1+LCG",
"tx": 1373753420,
"updated": 1325483952,
"uuid": "D1820AA8CB534AC6A4B5A2C0263FD3B2"
},
"D8F79F17D6384808848B213EB4946ECA": {
"category": "001",
"created": 1325483951,
"d": "b3BkYXRhMDEHAQAAAAAAANiTotCslHjA3lDSFq67/PAjJqHIN4CLgBq7B9w3/vp41wIDjd7HeH1RlG1QShODDhMf+NGbN/TCK8snRjUfKS1F+XgAEOKjeTBpnY3mA8FIdmKwJm/VvYXWI1bzd/ndZA1wDniaFy6+qBOaCDRCh5ccg3PkghZqSzouDY6VpQ3RtrtCd0cIp61PyQh9fhsIcaV1kuzOamAtTRrJdfQ9Kfd3wN3WYmvQ76KV2CtV17eohR63tJk1uPxa7YfEd0zihPc7vVJX2yKyHmO9Xb2yva+CDVExKjv7sIh3D+oT2J7vqfJocuwjzhs5C1c5rxMipOot0FSEYQ76YPb+Rz22NjKfEcuu+49s6+kq3XoJBoqVohF+uFjM76v5gDeFPbV9Im5zxFtU+R0YtiGCFA7NPQ5AFL1oQt2fGxDw06jhUt+z",
"folder": "225014A4FC654BE19531C19E5A3F8D5A",
"hmac": "kH17UD/ltmyhXda3DaIpzQtW3NYXgzOcMCPbOvY21uY=",
"k": "nHYWK2zu3rqnDsrZV9y3WsH/Hd93Ci+j4GSMLNSW6S/6DQVn5UjhFIwLB8qHKCQSlZWkieGz4lMaN7F7SKAj/OjJE73NfsGgjQHhwBy0W3/Ty85XuGufE89gikFNs1sw64WQQG3ZM692YdDd2QY7EA==",
"o": "b3BkYXRhMDGbAAAAAAAAAP1DU3jTImWEWTH9q5cDG9fw5DAVO5fvavTSShwoDQmrLryQL9R4fMXL+r/xjUg2EiyPG1OPNkmPq5kF1vrGNTdblojCWmpvhh8DIV3h8vt5gwd5+JaoTeQbvYqayfPXpzVcZ0fDEZQd5HvpmQJJGHMpU2mU8W9XiXlDK66tpLIJwqPLSibSE/OV/FIUn2wb3G+eEKi6rPZIoIZjMMkvk8S3H965CKUr9T7QrpVP6zUP6oQLeRIuQt3WUGPfi5LWfnUY7suMxpAYRBCWVnti4s4=",
"tx": 1373753421,
"updated": 1325483952,
"uuid": "D8F79F17D6384808848B213EB4946ECA"
}
});

View File

@ -0,0 +1,37 @@
ld({
"E0D293D29B10483F8DFDAC72ED0BE5C0": {
"category": "106",
"created": 1370116210,
"d": "b3BkYXRhMDHvAgAAAAAAAMwXinLLoArWq5bfBXIorDeMP6Eg4AIrjDMcKauM87yrq8BgJB4yJlw0CmHzLiaul7vLEuTFoT8Y+ElWPIB6x6GOTYyXX+9g2O6ffOMAt/x+fBVtMxTfUnK8Slf9h+lHkkmFZWKd9Q19W6YN/DRQD65yO+PwQ5ErqAZayN31EijXzS5vbtnwXkDvK2wXwXu9l8ye+7kFGjDT9BDPCLdpawqS2BEMoH9w5dkxqenf1pixt12XaJVaZ4x7MPsVLGpS6Vwua6Y9+Y6OWSgV1xjUTz1DXGmT+13yG8d3lgAM+McJEHRihzq60NUIK/7XNdIDsZwyTMBAk8XM0gxvInQbRmOGAp3JH1UdvhMrRsXlF0b8yNJfVXb4H1QCkIzxlkEbDlsL0dP4TE47QZZnnQrPNSb51FxTCsdTIjnG4DClXijrR0YknI86cMsaHC91zzf0oIz7ETMZ8vkxmIwBlq559IOAns1hUyoQAWuAbOBNQqw1TYQzIT0CPtd5cDPEfuERj1/SGyXdFa7SWqvGNc5z4ET6tujoG7xZzJg/JvifW75E0xel0e8ZfHJvQUaZx2Mj0ukjiNsorB9O2p0DQq//P5W1A4rHAg/oeLQB1926u62k9+FcRG4ZrBgPI0HGszprHzF5T+lm7JCZamf6waPFsIgIAh+1RMq4RUqpmaQPsypkB2GtkO8h83wKDaX5fngts3CJH9OZI5NGqsOpXjUyOcqUFM7GMHKsXVLSXPXG1snKCSFph3iLoH/bnryPx4Mf1eYbgqDrdQoJ61Youiw/qo64xMA3HpW3EZ3p48tTOwvJBa335o9QdSgIOdnmhpAYJmhrS/iRyoAhamG6QxRxIk43e17UbQ1Y1Pv6lgMOfsoWhkRM2dL6rjUA6O1YUnPQNdrjYA/HL/qWEvDe1C0JbOR3xWLFSCIwkTFvYD3CQ9NmMXAnZ5Fu9vzgQRvjhxQSWPE6GwkykKYOS96RXOeP92HkB+Jt5lgKFSuDNwBYXDCXu1KwVvCRuqV67SLyJmOG+Rf/c8ThbDTIwYh0Q1EZoPMlx+/koEVZhn89lzrzqQJJ",
"hmac": "GPeCKCNyGzGIdpakTJAIjThf6j8ZLubbbsYDsyewllY=",
"k": "ikCOxk5d2VpVu7slo/9KBW5GNAsXlBKHJSIOV4rChQsWG+GdLzWekfMbc8liT3Xw08lR4aY1VFt6K6+nRHuazIvnIK415aD7wBIcL+Tw9gT/UESQv8ogns16SOXzCJjt6M61sO6jpzQzZENWfqK2CQ==",
"o": "b3BkYXRhMDE2AAAAAAAAANuLreKDkN5RxxpiuHlR+MvbU2JJls5F38JDUnuIqXvBVKYlExHOLSTB0gMjcTf1pvGglvQqqGaNox2GOnQ5F/MSFmHg/eFVsewgeXj+IMs8Bc5itgsYeSwZw7Po3fdc0RgxWvaWipXdhsejk6Fd1uY=",
"tx": 1373755376,
"updated": 1373755376,
"uuid": "E0D293D29B10483F8DFDAC72ED0BE5C0"
},
"E482B70C038D4DD78A0940728FA737BF": {
"category": "002",
"created": 1325483952,
"d": "b3BkYXRhMDF3BQAAAAAAALd6CWU7JR10MTbZRSdznA5VPiBVK3JYI8liAbhUQdjtsxVxazkwiPDhPTVeiD0F3hTXF+KzJjrTK8S2eoSaXHL1bFjwglj8r0hMEf/34/AytILXubb2MiW64wxsDMbuhDJFj9dobm5OWFKUYix7s2v8DapYgbcA2M7xRcZpqqUOJOz6hdLFVCfvdHfRjfxi4m4e2N/1XhhECDrtB7fuBCHDvollW1ohvC2wmXvcmp3CT2w5Dzm0FaxlysIIg9OJRgP+uxzEG3hYR2bp+cxF+x/ddPbEFJ7ckx/Mx5vb6TRtzjn/We0O+HfVYwCkaV1pN3EYnjMYlkfPR418qg+MSpRbSU2ufgIQm3TT+lGIB35d8LhMi2xd3geYwMCwDshvgGFusYyGlwLVqmC5BTYiMgpQFkestz+pA4Gx9Y9zrYCZ0X/hpZ6J5E34ss6XvnWVlRO3Y66d1MRN18y3+/zOeBphqxmruKxdojzm9wY9AxYNJuETV456Sx/dguePkOxCA115y4LOlcLijw1HRVEN6jLsuGpabENXmDyZoiPXnGiyHarlLqUfIRvW9uwWjdg+f6WFDae2rOjuOGTL8BsUhBE6xaV38qC39zbme45tpe+xGNatERPcnLPFehjpB45Oo84nGl11N7jSqqAlqjkFQfb78WeDlanx/EvXah+pTmTm7ubaoALOPFK4rCBsxiftOpgNaHR2kgkU0+TNKexbwtWflkfDrm94UAe+57n6jkW1EZ8Eb2lTQ4GgdwZXguYm4l+R9gcQ3ssePKkTzfxMhsNRJmYWIZZnzUc8uLYLnK3YUx6QzrI18fn7Pdx2S4BiR8xf91lhTl4ph0MAQ4Adquy8s+AmCMFYg5XfjHH+XxfxK1VB9jFYkJ1yQNNMOETPDXYs+ST+GnBYQrl139Duba4KMudGWgVFYtXIrroVJedzEekpf7nPfHsvWoeu+//cNR72WETUnGQAp+Mwa/lWHw/IUcifRSKbgkTEJ0JDCMfP3DJhT4VU6D7WbdGBfYE+ly9q60TTG1XUrdgBwVpswJhAOtxq6mjZ7s6ncxf+93dbmGV/ZxyH/w0iYNR2wk/RTZuhv7XRej8PWXkpvDl/EwDdCbwIXupp5FbNK00cqF7IPjqREYca4MeO7/D265K6CyfmsUyNEQj95X5W06vL3wIToaC+Cn9CMzQkgGOtq46FK5WXmDYaj1GFRLfC353ekN3kvdl9DfTGhAH+oPkbMWGFCIZIoMIZeeIfJJgQNxA6pRH9TyA08hkK7NiuLtCXLZQOjpCfB91qfz45eWHErLPxtJeDJw3M5lo/tejXYZz9z6RzMWFiVWagmS0gpfUfOJZlUk+oGM1vUKsK8PhdE/bA8/3WfXrlcHQ8M9A5meTE5Hn2sTuBdKVQI4r/sYOQjFcYIwBTdj222F8hAKnZ4RdjUYKIMrF16OZKqpAWxzfDiMcI1kfw3LUF45Flq9G5xdNxtrAtLmqwkutOTZ0qabET5qF9epae9fucCc9H3Y4q9Jk4xUoV0m5NrVjRDF7ZqicB/09UUI0n+3kd1M9kTcx1E1XPlhuUGtUGGCr/YzWuVxyLrn7zAzT7Sguih43vgm7kUg6p32SgIJt/6lThxWoHy2w3MO4zKHA3AoWSqrEZIXjADFJejG8sfrwuQg8oLSR80M/hiaozyostqfki5fQ/Cj2A6hJdkmJf4lcfwtgAbPuMsTsY0QRhm/EMfcsg/pNcwLd01h0XdsKsiYP+39QBYnupNpcxE3BGxnsQFj0CgOM6CVEWu/sfrS112SHiJXtg/FHAMC8MxJPZUAcA6XM45tuduPm9CaMP3ASX13LqJKDuhJMHdMy44vKi9P1R/Rav83H47MGyxXYwtvz6FkRVX1oZjcgD7RNNa2VcBIrHZfUp8M8Fp2PuKrR1RQDNN2Px5HkAblsKvwfPfzT1zCs=",
"fave": 2000,
"hmac": "4eHvwV3DwH+fmk3ISBpnFfYVlVlFTB5bJw3AXHKS2SA=",
"k": "yr2c+hmtrTg6kwcio/GTlS8bogJj0znYD3cNB7jXQdQICYG888AHzlB4UrGsMZPhLzUFLC7Vfd77Uw8x7TivyfjRNzTQpvTtGoIbhSoEYVU5TWz28CrwDZbmDYWpz7z4r1ElIWYQtRCrgILez6c8iQ==",
"o": "b3BkYXRhMDFXAAAAAAAAAGXKYq+ozGVTUMOS6QEFBbvmbltPopkg/3pQUwXR8pRRs90QqvsIk9JlL0zWrV33N8bWzAUWQyr5U14vEMZNyLBRSQ26SpAEkdQllj0SwD7Nm3p9SpR8TkvY4sbnm3rHzy4bm8DvkSNM5Lau2d/SDSjT1vjK/8Yvi2u+6pMdniBqkBy3uJiTof/cHPDd5KwH+A==",
"tx": 1373753421,
"updated": 1369148598,
"uuid": "E482B70C038D4DD78A0940728FA737BF"
},
"EC0A40400ABB4B16926B7417E95C9669": {
"category": "001",
"created": 1325483952,
"d": "b3BkYXRhMDFTAQAAAAAAAF09tCe7uipDcmMo71+IeR2y1xC3e7iX6qFyWSXfMHyYJ9jA08+3rVf8L96QYgjzLnfkyQi0++mIeDkclKhSVNQ3wQ2H41oRq+BB2sFpl9LZioIZCj9y512XR3eXHb4/fPRVhZm5Xy0EISl1lsKzykoA3CMiAILTTJeYMJQrwom3akyZnvrrM8iizDb+7xfIq+MRcvnJVgQsYCRJaL6QkXGK9RJJ4P0F8FyhpQXbDCZ0/x+dUch8bUHwRFjuW8a7uJfr4bHGINCq2JqrJMjWDtADgBZG3iipoiiRNsXbDYAGIVohqIMgbJh2NzAgyptIVnNFR7ArvBerV7Ka3g+s2PqFCO8LmPMgVvPW9fLQJdl+ZBQ4WRJCQh7ZDDm5s+MkyiKvCwOzI1RJeVpSh8F0aZiESZI6exIswxkErwK2tGV1JStQku0WhM/qCKbYENgRT1dwFKNCYSsSaa/PRdLZujwpb0TU+MijZja+fTkpLKWkpqa8P16mzpwFZxFM3MsOaGENe+xYFU4PfGBEH0LG5AU=",
"fave": 1000,
"hmac": "WJiNQqN3uDxc06QAWI23wNgn/FYniigGlgMfLZge/Mg=",
"k": "rIpJwTVdfuqy2sihZc7bJJHb50DVQIAyYbZdHPDUXveJiLdwSTzx3XmLrcSUcMUruoP8RdyU0NbK5ix4lFwC9vcb4y2WOmLsCxARe5VxfiupFqXKvnSILsSIo7AiwXUT59r7syS/s7/iejyIqKr3EQ==",
"o": "b3BkYXRhMDGwAAAAAAAAAJqgRaKvByRAogZbxxBOv8R6bNVhESt2/WFvCY6FFUoYP7hziYrQhOgBv+EM9n+x9CdkK8O1lhzHoc2U4RO+knh7VFTIJDtnZuedxgILfr+6izsiFJZn08KwwEKNGP5BdpuOzg/a58vhr5qPUO1hOVzVLmTYuGCvnaQ7REinkwLwaX4W22uYo7bFRo60sFcrVcva/KTEn81013B6Gc3fcDtFnjdG17QkxvG3rhc6yzJA9JPCi52UA7q+rLbsn1jd3fcTCykI9T5AfAki00RDXG9qSbZcQLjD2GBs5IvtTrdjtYaah2yrtc8lBpmLyUhFrw==",
"tx": 1373753420,
"updated": 1370115423,
"uuid": "EC0A40400ABB4B16926B7417E95C9669"
}
});

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,24 @@
loadFolders({
"379A3A7E5D5A47A6AA3A69C4D1E57D1B": {
"created": 0,
"overview": "b3BkYXRhMDESAAAAAAAAAHw2J+nRQ2h7a9jZ8kH4ser/wKowBqgkJxv+RPujmrB7X53ooYk2wxyfiM2par2J44pCxLcNesV9F+jFCIecxGouN+3F033Ktzm3fKC2pGXy",
"tx": 1373753421,
"updated": 0,
"uuid": "379A3A7E5D5A47A6AA3A69C4D1E57D1B"
},
"617F428170E1455D9503EC75AA103859": {
"created": 0,
"overview": "b3BkYXRhMDEUAAAAAAAAAETNJnQozPInk04UjWvSpyh9PSWcFbetAzkMB+Sh36BPB6nk/FyqwEp2jeuMA0GTuZ/6AChqo0DYSnj3F6E2890seFFtufva2t+j7CI4Ft6J",
"tx": 1373753420,
"updated": 0,
"uuid": "617F428170E1455D9503EC75AA103859"
},
"AC78552EB06A4F65BEBF58B4D9E32080": {
"created": 1373754128,
"overview": "b3BkYXRhMDGQBgAAAAAAAPk2tT/+vjtAMX6wkwAs8gU284CfdqWHKsN9kFXY40rSTv4f8E1daHNMt2hLAbvR3BjP29168o1taEIYcXVHaZFdYTQ5yl+AvSn+T8aqkQ4sPrggr2Zi2NKuo75Nd/Cd0cnDGBDE3y4q7Gqzoo/Hr0mSfjJFkIbg3PEdY6LRLR2rnHBiqKAshdVyKBXO2maK0O8M7a0fGQN5OG0iHYW8Fo8gJcfBzfVDaUaPhZHj630ONuIVOAXhk6zobzBuYEo9khv1w0ueBrJqvT6jiLa5LhwPfhQmGBBc4scBqJqXFEB8lHoj01V75wkyn1GgVo7CGoSqEbaogFYoa6F14rIFs5pJ3dSbK5qxrz3aAVVZsJ8ouq8fniiRPUmaC/2f2PCy3EB2P0YtlAoE5/fxobq532a+SkAPey5fPxrS7kdgZwBf6zqnbwe8/pmmcNviVxuDz9/19c+Q/UQu0nYg0MS0rnTb3Eui3/UZBMoNkUT9YKKSS4ZZCLEvq88QYCrDTq9OBIMYDyiq3t6Jlq2+ynz9V0rs2uk1o7UpmCr7V7HA2OH9RmpEk1f6LGf/dPocvjPpUD4RR/DUD69EhwCWNkkC2PjE5cRLNnRMjKwb5rhxjkihr9/jTQMqKJQT3/gYHtfZguWrXy1xPB6UmxLv0mwY3BTw+PZa2q/znhyam4xA9Jf425JttoaectShHyMTwsmlcHqkeMLvTdodgOsNAHP+rCy0uuq6CUOlZrX6BRbg6WBs/kPjL8Dg4BwHBw2wh7XvkJkLo8eyErD2jg2Dd3a4s8jErPRZEmDbQJbVYut9fTAJc09LAGAS4O9hg3Esvl5yEhQrC0zoRtHpm/eHKRy0uDTQm5+EhmEAX3zyWjM7O/BoW3q1ZrbVkgmK+814jBzo2/agpiNm9/ZDd7PJkZU1H6M3FMdPamx5qxe7rnrngaxnVvjL6f+1OgYHJ7Zq51Y21Mx4We4dwHU+czMpCcJNoaLtQ8ZBWhNpAYWey6eseazChk7HS2lRSQYdocIgNeWrziXksTIuMBfUsmIGqqzzSda3oMv82Tz3eKKSfgD5MNlB4rGxko9WHZO+pXYZJjhRIvEUTSMPwntfyN9jqZUKgCZgfCI0TjS8NHxzCnuonfgpzO7qNi2kG1U8N/JWf7IfVL5qOAHU5YwzDOgiDAS3Oy5CHvRVvNyi7D2EAP+oSgn9CfsK0OhhhyBkxygPbuhdvljIqJOhVItOtNYJYXeY3QM0Sb1xE5U04wA1cySTRwsiTMO/k5HyTU4V9WySQAB58WtneaAciRNzZUnFdhwC9JlkPNgyZjBrw84wpjdxAbJK1D0T7Kq8l5w5FgwVDGq07JsmDYx8W2ReyuwkLNq7oaSwj+wTpSy7uqVVafa2RoFOLVWNyPnrwHYgF65emnO30ZYYda0wVrOPVvoHB6PcftWdmnBPY2ktd2eGaYeEOE5Vhf26GMOrdbkH0aM0jDLEmFE/O3Rw4ILsRpLXcxxiS/y7MlEAuW6O//sZ9Z5/CjLfswBs9zU+EeduObgKUN78AsPXBzGHcY4QymlcUwMdVdptoa5rJ6+dzrm0l/xyqiAQWhvaVytc5zDXu1b8clfMe4xQEVAxpA5IJXXWUIUoxHEbG95P9rjVeY+Khgjcw0XMkwPBstYka1aZUKe06Cp8fc6XC59Ti5xQC5eywjfNZwHfXNovTx6jeiysDcuyTkDOqG8+B8ls+e73hCPbBljV+dIijAY7DY8+R3bziCRNX73PLH+LfLGBll/IsAY8m+Qoo5jT6t1GS6mVztnbNLwL/NPrOW1ZeEXvO1gR/b2WpjyEc/dmIo6zg8qbYxMRwyAeR7RJf+eRJ/AVj32eZPAGDhhqaE2QAdNvROKTZsLhchTJBN/Xk7oZPrCT2rZ6cJv8x/gHwiyQwEL/g4gldE9/PPFZv39XPEvA62jnHOyzoiToo/FMhtYxCKRxHzvhz8KcBXp5eW5hQMXgmUPouHTtc0S5+rMwKbRYcqEvkYHMWNyU+0gEOWTbvf7OLvFl37c4hCHencsGSeTzf1pbENjO9nx9nV92F6+KrSYXnNJlXUZvXvivr/wnruwf1IDWzPAuLRHfLiuXyPde767du9RVHYR7KPcQcLAAApM9njDlXyMRhW1vK8G5t/PvSdTcbk+lMdeMkW/DhlYWSJv2dOe3x9aFpvLMnP+P8j5XIyMFTDhopQ1LvUIGwFu6WjDpMIo+i/3sK0q4YFOhGF40BquOMsE3Z/Jp4xMRfoq5IrsL5vdOWK/NTswzN5zDOQUcLEgbm+Dt2jwciMm89XbiaLNTfdp/VLlY8M7PGlhsZC/RKBM=",
"smart": true,
"tx": 1373754523,
"updated": 1373754134,
"uuid": "AC78552EB06A4F65BEBF58B4D9E32080"
}
});

View File

@ -0,0 +1 @@
var profile={"lastUpdatedBy":"Dropbox","updatedAt":1370323483,"profileName":"default","salt":"P0pOMMN6Ow5wIKOOSsaSQg==","masterKey":"b3BkYXRhMDEAAQAAAAAAACN8JuE76yN6hbjqzEvd0RGnu3vufPcfAZ35JoyzdR1WPRvr8DMefe9MJu65DmHSwjObPC0jznXpafJQob6CNzKCNoeVC+GXIvLckvAuYUNSwILQQ1jEIcHdyQ0H2MbJ+0YlWEbvlQ8UVH5bcrMqDmTPPSRkbUG3/dV1NKHdgI0V6N/kKZ737oo+kj3ChJZQTKywvmR6RgB5et5stBaUwutNQbZ0znYtZumIlf3pjdqGK4RyCHSwmwgLUO+VFLTqDjoZ9dUcy4hQzSZiPlba3vK8vGJRlN0Qf2Y6dUj5kYAwdYdOzE/Ji3hbTNVsPOm8sjzPcPGQj8haW5UgzSDZ0mo7+ymsKJwSYjAsgvawh31WY2m5j7VR+50ERDTEyxxQ3LW7WgetAxX9l0LX0O3Jue1oW/p2l44ij9qiN9rkFScx","iterations":50000,"uuid":"2B894A18997C4638BACC55F2D56A4890","overviewKey":"b3BkYXRhMDFAAAAAAAAAAIy1hZwIGeiLn4mLE1R8lEwIOye95GEyfZcPKlyXkkb0IBTfCXM+aDxjD7hOliuTM/YMIqxK+firVvW3c5cp2QMgvQHpDW2AsAQpBqcgBgRUCSP+THMVg15ZeR9lI77mHBpTQ70D+bchvkSmw3hoEGot7YcnQCATbouhMXIMO52D","createdAt":1373753414};