From f215ffa3fa540074ef5c3fe956f3223f266bb3ba Mon Sep 17 00:00:00 2001 From: Felix Geyer Date: Wed, 21 Dec 2011 23:22:07 +0100 Subject: [PATCH] Implement support for key files. Closes #5. --- src/CMakeLists.txt | 1 + src/keys/FileKey.cpp | 275 ++++++++++++++++++++++++++++++++++ src/keys/FileKey.h | 50 +++++++ tests/TestKeys.cpp | 94 ++++++++++++ tests/TestKeys.h | 4 + tests/data/FileKeyBinary.kdbx | Bin 0 -> 1582 bytes tests/data/FileKeyBinary.key | 1 + tests/data/FileKeyHashed.kdbx | Bin 0 -> 1582 bytes tests/data/FileKeyHashed.key | Bin 0 -> 1696 bytes tests/data/FileKeyHex.kdbx | Bin 0 -> 1614 bytes tests/data/FileKeyHex.key | 1 + tests/data/FileKeyXml.kdbx | Bin 0 -> 1582 bytes tests/data/FileKeyXml.key | 9 ++ 13 files changed, 435 insertions(+) create mode 100644 src/keys/FileKey.cpp create mode 100644 src/keys/FileKey.h create mode 100644 tests/data/FileKeyBinary.kdbx create mode 100644 tests/data/FileKeyBinary.key create mode 100644 tests/data/FileKeyHashed.kdbx create mode 100644 tests/data/FileKeyHashed.key create mode 100644 tests/data/FileKeyHex.kdbx create mode 100644 tests/data/FileKeyHex.key create mode 100644 tests/data/FileKeyXml.kdbx create mode 100644 tests/data/FileKeyXml.key diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index c10a19d93..4f8e41b29 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -54,6 +54,7 @@ set(keepassx_SOURCES gui/KeyOpenDialog.cpp gui/MainWindow.cpp keys/CompositeKey.cpp + keys/FileKey.cpp keys/Key.h keys/PasswordKey.cpp streams/HashedBlockStream.cpp diff --git a/src/keys/FileKey.cpp b/src/keys/FileKey.cpp new file mode 100644 index 000000000..40f9db64e --- /dev/null +++ b/src/keys/FileKey.cpp @@ -0,0 +1,275 @@ +/* +* Copyright (C) 2011 Felix Geyer +* +* 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 . +*/ + +#include "FileKey.h" + +#include +#include + +#include "crypto/CryptoHash.h" +#include "crypto/Random.h" + +FileKey::FileKey() +{ +} + +bool FileKey::load(QIODevice* device) +{ + // we may need to read the file multiple times + if (device->isSequential()) { + return false; + } + + if (device->size() == 0) { + return false; + } + + if (!device->reset()) { + return false; + } + if (loadXml(device)) { + return true; + } + + if (!device->reset()) { + return false; + } + if (loadBinary(device)) { + return true; + } + + if (!device->reset()) { + return false; + } + if (loadHex(device)) { + return true; + } + + if (!device->reset()) { + return false; + } + if (loadHashed(device)) { + return true; + } + + return false; +} + +bool FileKey::load(const QString& fileName, QString* errorMsg) +{ + QFile file(fileName); + if (!file.open(QFile::ReadOnly)) { + if (errorMsg) { + *errorMsg = file.errorString(); + } + return false; + } + bool result = load(&file); + + file.close(); + + if (file.error()) { + result = false; + if (errorMsg) { + *errorMsg = file.errorString(); + } + } + + return result; +} + +QByteArray FileKey::rawKey() const +{ + return m_key; +} + +FileKey* FileKey::clone() const +{ + return new FileKey(*this); +} + +void FileKey::create(QIODevice* device) +{ + QXmlStreamWriter xmlWriter(device); + + xmlWriter.writeStartDocument("1.0"); + + xmlWriter.writeStartElement("KeyFile"); + + xmlWriter.writeStartElement("Meta"); + + xmlWriter.writeTextElement("Version", "1.00"); + + xmlWriter.writeEndElement(); + + xmlWriter.writeStartElement("Key"); + + QByteArray data = Random::randomArray(32); + xmlWriter.writeTextElement("Data", QString::fromAscii(data.toBase64())); + + xmlWriter.writeEndElement(); + + xmlWriter.writeEndDocument(); +} + +bool FileKey::create(const QString& fileName, QString* errorMsg) +{ + QFile file(fileName); + if (!file.open(QFile::WriteOnly)) { + if (errorMsg) { + *errorMsg = file.errorString(); + } + return false; + } + create(&file); + + file.close(); + + if (file.error()) { + if (errorMsg) { + *errorMsg = file.errorString(); + } + + return false; + } + else { + return true; + } +} + +bool FileKey::loadXml(QIODevice *device) +{ + QXmlStreamReader xmlReader(device); + + if (!xmlReader.error() && xmlReader.readNextStartElement()) { + if (xmlReader.name() != "KeyFile") { + return false; + } + } + else { + return false; + } + + bool correctMeta = false; + QByteArray data; + + while (!xmlReader.error() && xmlReader.readNextStartElement()) { + if (xmlReader.name() == "Meta") { + correctMeta = loadXmlMeta(xmlReader); + } + else if (xmlReader.name() == "Key") { + data = loadXmlKey(xmlReader); + } + } + + if (!xmlReader.error() && correctMeta && !data.isEmpty()) { + m_key = data; + return true; + } + else { + return false; + } +} + +bool FileKey::loadXmlMeta(QXmlStreamReader& xmlReader) +{ + bool corectVersion = false; + + while (!xmlReader.error() && xmlReader.readNextStartElement()) { + if (xmlReader.name() == "Version") { + // TODO error message about incompatible key file version + if (xmlReader.readElementText() == "1.00") { + corectVersion = true; + } + } + } + + return corectVersion; +} + +QByteArray FileKey::loadXmlKey(QXmlStreamReader& xmlReader) +{ + QByteArray data; + + while (!xmlReader.error() && xmlReader.readNextStartElement()) { + if (xmlReader.name() == "Data") { + // TODO do we need to enforce a specific data.size()? + data = QByteArray::fromBase64(xmlReader.readElementText().toAscii()); + } + } + + return data; +} + +bool FileKey::loadBinary(QIODevice *device) +{ + if (device->size() != 32) { + return false; + } + + m_key = device->readAll(); + return true; +} + +bool FileKey::loadHex(QIODevice *device) +{ + if (device->size() != 64) { + return false; + } + + QByteArray data = device->readAll(); + + if (!isHex(data)) { + return false; + } + + QByteArray key = QByteArray::fromHex(data); + + if (key.size() != 32) { + return false; + } + + m_key = key; + return true; +} + +bool FileKey::loadHashed(QIODevice *device) +{ + CryptoHash cryptoHash(CryptoHash::Sha256); + + QByteArray buffer; + + while (!device->atEnd()) { + buffer = device->read(1024); + cryptoHash.addData(buffer); + } + + m_key = cryptoHash.result(); + + return true; +} + +bool FileKey::isHex(const QByteArray& ba) +{ + Q_FOREACH (char c, ba) { + if ( !( (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F') ) ) { + return false; + } + } + + return true; +} diff --git a/src/keys/FileKey.h b/src/keys/FileKey.h new file mode 100644 index 000000000..cc2c87255 --- /dev/null +++ b/src/keys/FileKey.h @@ -0,0 +1,50 @@ +/* +* Copyright (C) 2011 Felix Geyer +* +* 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 . +*/ + +#ifndef KEEPASSX_FILEKEY_H +#define KEEPASSX_FILEKEY_H + +#include + +#include "keys/Key.h" + +class QIODevice; + +class FileKey : public Key +{ +public: + FileKey(); + bool load(QIODevice* device); + bool load(const QString& fileName, QString* errorMsg = 0); + QByteArray rawKey() const; + FileKey* clone() const; + static void create(QIODevice* device); + static bool create(const QString& fileName, QString* errorMsg = 0); + +private: + bool loadXml(QIODevice* device); + bool loadXmlMeta(QXmlStreamReader& xmlReader); + QByteArray loadXmlKey(QXmlStreamReader& xmlReader); + bool loadBinary(QIODevice* device); + bool loadHex(QIODevice* device); + bool loadHashed(QIODevice* device); + static bool isHex(const QByteArray& ba); + + QByteArray m_key; +}; + +#endif // KEEPASSX_FILEKEY_H diff --git a/tests/TestKeys.cpp b/tests/TestKeys.cpp index 0698f8701..3854efe2f 100644 --- a/tests/TestKeys.cpp +++ b/tests/TestKeys.cpp @@ -17,10 +17,17 @@ #include "TestKeys.h" +#include #include +#include "config-keepassx-tests.h" +#include "core/Database.h" +#include "core/Metadata.h" #include "crypto/Crypto.h" +#include "format/KeePass2Reader.h" +#include "format/KeePass2Writer.h" #include "keys/CompositeKey.h" +#include "keys/FileKey.h" #include "keys/PasswordKey.h" void TestKeys::initTestCase() @@ -50,4 +57,91 @@ void TestKeys::testComposite() delete compositeKey2; } +void TestKeys::testFileKey() +{ + QFETCH(QString, type); + + QString name = QString("FileKey").append(type); + + KeePass2Reader reader; + + QString dbFilename = QString("%1/%2.kdbx").arg(QString(KEEPASSX_TEST_DATA_DIR), name); + QString keyFilename = QString("%1/%2.key").arg(QString(KEEPASSX_TEST_DATA_DIR), name); + + CompositeKey compositeKey; + FileKey fileKey; + QVERIFY(fileKey.load(keyFilename)); + QCOMPARE(fileKey.rawKey().size(), 32); + compositeKey.addKey(fileKey); + + Database* db = reader.readDatabase(dbFilename, compositeKey); + QVERIFY(db); + QVERIFY(!reader.error()); + QCOMPARE(db->metadata()->name(), QString("%1 Database").arg(name)); + + delete db; +} + +void TestKeys::testFileKey_data() +{ + QTest::addColumn("type"); + QTest::newRow("Xml") << QString("Xml"); + QTest::newRow("Binary") << QString("Binary"); + QTest::newRow("Hex") << QString("Hex"); + QTest::newRow("Hashed") << QString("Hashed"); +} + +void TestKeys::testCreateFileKey() +{ + const QString dbName("testCreateFileKey database"); + + QBuffer keyBuffer; + keyBuffer.open(QBuffer::ReadWrite); + + FileKey::create(&keyBuffer); + keyBuffer.reset(); + + FileKey fileKey; + QVERIFY(fileKey.load(&keyBuffer)); + CompositeKey compositeKey; + compositeKey.addKey(fileKey); + + Database* dbOrg = new Database(); + dbOrg->setKey(compositeKey); + dbOrg->metadata()->setName(dbName); + + QBuffer dbBuffer; + dbBuffer.open(QBuffer::ReadWrite); + + KeePass2Writer writer; + writer.writeDatabase(&dbBuffer, dbOrg); + dbBuffer.reset(); + delete dbOrg; + + KeePass2Reader reader; + Database* dbRead = reader.readDatabase(&dbBuffer, compositeKey); + QVERIFY(dbRead); + QVERIFY(!reader.error()); + QCOMPARE(dbRead->metadata()->name(), dbName); + delete dbRead; +} + +void TestKeys::testFileKeyError() +{ + bool result; + QString errorMsg; + const QString fileName(QString(KEEPASSX_TEST_DATA_DIR).append("/does/not/exist")); + + FileKey fileKey; + result = fileKey.load(fileName, &errorMsg); + QVERIFY(!result); + QVERIFY(!errorMsg.isEmpty()); + errorMsg = ""; + + result = FileKey::create(fileName, &errorMsg); + QVERIFY(!result); + QVERIFY(!errorMsg.isEmpty()); + errorMsg = ""; +} + QTEST_MAIN(TestKeys); diff --git a/tests/TestKeys.h b/tests/TestKeys.h index d525c706a..d1b62db82 100644 --- a/tests/TestKeys.h +++ b/tests/TestKeys.h @@ -27,6 +27,10 @@ class TestKeys : public QObject private Q_SLOTS: void initTestCase(); void testComposite(); + void testFileKey(); + void testFileKey_data(); + void testCreateFileKey(); + void testFileKeyError(); }; #endif // KEEPASSX_TESTKEYS_H diff --git a/tests/data/FileKeyBinary.kdbx b/tests/data/FileKeyBinary.kdbx new file mode 100644 index 0000000000000000000000000000000000000000..fb9493f72189fe53e8e0cb0b8df6d27cfe694bfb GIT binary patch literal 1582 zcmV+}2GRKg*`k_f`%AS100IC45CAd3^5(yBLr}h01tDtuTK@wC0096100bZah-eFw z5D_S>-BMPTsph_n((Vg1ymQasR|Am<_WZEf1t0(bdND2c^>EqGSo|d$)iZNtm_V(? zC_xpYp-X=GT)WQ(2mo*w00000000LN0MRs_*``B53xY?dD!_K0W(Xhvu?3h*2_OKb@e!5`V*)2jf&Ih0Lt?{-=u3?jcYquK zr+HhzOjt1r1ONg60000401XNa3IWhrrjco)o4#u-EdP>D+7fdm^*&mlO=m-F_ORlP zvt(b7&bU?SiiK{i)<7uDv&ayD_7xG^NoBm9uANPqxw6gOob+sj;UD`~4GykUH5T(3)N0{3iQDvd~1? z--=ggv4n0o8`^KnRlz{EVQ4LfHk$Ep9l8Hw+5>9&^>)fOMiF1ykEVK+c$Vm$9pD25ks>tbLt`qo1PjkN;D74UcKJ(V8|n$I}%wh7VXiYzmWbH5!% zC{#^RU)y?N@MyfOe}d<<(ui{wsq+qV@Sl#o?$gSkH+`lw)F$ z727U87(saaSt?IC5CAFhZq9>ANO2Tc(2q^-2#h+L>M8)ZPLYufbh70(qrReWhGUWu zNN0K&`IP3tc-0orf)>s4pLz*wC)ND@X&esX))CDyDUJ?@aUZcqX{;M0&Z?bzFDJIg zIqTR;D}@9iagQ+D_toN!&(H06RMJY)M;arCkbo_d$2&MoPDW~p)+Tc!?OT#5?7k1O zc}gbgv2!MBK#pwLuL`{!d=+onm8c-_hWnMup*81gn^Q3YqkOFjKOQc8T+pbWr z#x-qD`_`1_2}3VXP&(@u*oOiQVAMFAeE5ut1YAdHhW{jt%#O7GS@|*HhjYspQcr_haKjf z5axBZ_F@U|Gxt6Q2mo*w00000000LN03|D}X`816uz3ZCa!dUc5eOgvDRyt|BE`!( z707FPh&f74&w#~$5$fL5*{06bB#iZq2_OI;_&0&xRr*iGl%`9nxPfEPQQ#iXPred= z9E`!Qwl1{_1ONg60000401XNa3X%kr>8>%TmOtv0e>RAwuMC{f^h=(vtLB-ZKt1vI z>1bPUwZjDjZqwDydMjI~sVq0tzu}$>$6wO4*s(2KOofPg+uD;3Tn?FlHEIwcpt*ps9pSQjwm6m>Mo7qCrBNjMCcMrV|54X3D_If zim1pJYRU+<2FC|aGDNN5CABg`mhR0P?F9V;m9l3+Fz_2xr_@aR5D=y2^tQCsR0$s^ zduAu*8v-z64N4L>TQ0htAKh*vxQI~i96Q7Wm8W3Dv{z5&1j8QSmLRg_x|aOc9;OR_ zLyWP^$$vrt;B9j;zC0Ctk7K;$r(4cCfu=q}x1Vy`x%#Fp%LCQNn{5NhXA3I5`y+pX&saovb{i6m^iom2T)J!VkE@f#fP%MqHIu?%XY*=bzFhD7+L&)6P9DAQ%~L@~ z_?!c5!y#^*!~cgqnf%VGb6DAJ=kp& zGQ>U~$`c|~&}iUSbs~(F5Z03-Exj<0dYcCJUaQ0=2ZpeTKq2MyYsQvDoSQ6}%lg0O ze^B*YdXp7_5%88fT1kAcB}vDa;5d#n7t+_#ZD;n@Mo_ z*fcUXoF@641U=?-rLg&!T(g6Nt5SsF&_HP-Bj~Q$uND3-|M3;xNOnx z9yt|WPpR-wCVyQ@L@ut4=5LG| zoJ?>3ex@>{wruEldSOV1!>|MT;on9>3)zu7$^qvO#95iM3BXfeNS{i_o}>!Y%*;!d zI?h5n9(fI&+$U!~v8gO%uJyg#RB))dcqW2LAa+~cMEOC}kbIEy!W1W8Wt>$rPMEgw zE{Eb2cN={zs2pLoSLAQ6lJUzG?(@``8i|s-1a@#a&bUBZ(~|$LmD#_BkzN7Z2jMVB zG5zx&R;S6BLv2Cv%n-sxhmE+Fs36PrE@mzjP4ChDNjv-CSBul?9A_8$B7&>2f%Ea3 zso~YpwHRE5o#xF&K|zM}$O9{9)T*4jt*AO*dX&hthaql?_cw_<$j9G-o? zkq1h{rwJQ%y=v`+azYI=X_j;Nus=G_tgtE~;g#~ox#{Xx3+A4`$R;W8c2 z;S&=;SVF}gtzCQ+dUiUdAG>YUAF?-J_{_^AwX_J0z)W-0v9axA=Vy%!IjedKkGyn+ zEohspq`Dz^>4!tJ0NWDQZ(&liybAjWVBy7GFN&A5xs-JAtg!UiO~iCn%&D1lQjmGN zFKbGt%Y{tGY;GFm-u(kYkKNGADL*<$ZSU>&+9rK}W*8`=L`WDm&x?M_aeX%w0MFI$ zp3{$20;WYK=#i_RUs+pdu}8svpFPXw9BE`*0S7F`MHvjq3BcQrTs=uo%+fwg7S)oo z-IWLYyP{8Wc%ZO;l-s?RHy|-;+f2$ZWxn+@0Yf)*J2T~213JwBen#&C5+y8LR`#kUadEWPj_j%sWd%JrM<*2YtWg7qh3Qh>S^B^XG zY6F!8ty0=hE(p>@TPHUtDA7>=B+!?;iSQ%>fa1qrDwPgXR0RN8TPHgkw}>3U&}9Vr zhT7J7q3Pg7FO9d)`fs=Fw)@^;pDJ?|>b%RnU@~dZ_9#nE>@s2Bg-nwP=h`2ziP&R% z4smC%Z^E8!oPwlXt}m4i6y6z|YM)=b^btiMhXs5XX{3Y_8$`SwY=PEHM?YO zO*=@;d)1wrXLrj#)n@N>{y*Tr_Jo&#orXxJu$hWcoAfky{^wO-S`}EnX*RLlLr`wMJBTS+WVKg1)0gWXvvg53=8D>(pwynUMtU@(|}%F6Up zCu;r_KF-c|0cK}rvL+R&tR_qW7xmmdJSu8xPS0UD@Ceb&1$Q^Mo1dTAhnc7y z)78cL_#8ZBMZq(#{hr_9h~!>p3^J*k6bKI_dKFC6r6aaVUBD{s-Mb5LadtK_G)!|d zHZ^VjKwd`G?9+w849=c2qoc`5PaZ$!@pu|Vign=HpP+vuf}*+3nIjdr3{8~;ArBG zL(tfUhK9giltA-+KmJtpFrlZrdttIQGo;BXa`85uP7kXYt{fDEG_^f3XrjT69@PYT zy1M4)$2hEzaoN#2>LKHd^enrkltaeT*;Z6bM@Pp1+UC2f=E{`1d~lEejJJ0yLVLHz z{+eq1Db*XNRKtCWO88xd$LD5H3{udJ*F&{2rwR9;)NZVa#Z~zJ^5^T`48kNMc=TBB zneM?s7oXn1ivFr7Q4j>{=Qn;icNyQ@f^N<>4z9woArM}k1z9=Sp{=^l&$IaW@W4m) zYv;YZoB@1b;L}lIfUeSErwy^l#WcZJd6v)j^Y&Kd^ZBO1Bj%8TUOw_of_775;ZoH`HFWSGtt+#Ni~lV8%mW{RvLa+CB63-wFOYAb&->m zm6Z>)gGX3Syk*ter6m$Yvhon@ynG_(=h`}pvTkOK?|VQJgju|zU1WY?UD*-`P$esD za7Oelu}lK*BePGH6&A*GI2=Ry_`_$QC?Kk#5$s88hTqh@>{27DmEG4oA=JN} iD*cD96a2q`^(|(F3mFgR#;<@c25_=R*_GI0Z~Y70f;E5u literal 0 HcmV?d00001 diff --git a/tests/data/FileKeyHex.kdbx b/tests/data/FileKeyHex.kdbx new file mode 100644 index 0000000000000000000000000000000000000000..33f1fb1f3b4e89a1cf9b950f912d4ab1fd7137d8 GIT binary patch literal 1614 zcmV-U2C?}A*`k_f`%AS100IC45CAd3^5(yBLr}h01tDtuTK@wC0096100bZa@JdUL zmvTqJwq2fwz4ONAzqN>2P!af?tDd`xtVKo61t0*X9R%2_OK{*y6U6bOvjitBl-UX4Iqku)> z2dRezA}^f^1ONg60000401XNa3V_P2i~o})1d__EPDW%@@GTuc4lXV@H#ApTZY>R2 zZ_`%Pm0PK)zIUz?x!k=m3cxiTfw4+{!(I_j?nlSaXhbbSD*;WfFXOyH?Ivu_klshc zn@g+L5WW|2!Z3;m3e`O;?K@-2bNxLD9S2zG-1xXn6`Q`RD@JvR(jUC;CAL%*q~f zWpZZi)NHR@@AGWn?hQx5yL;xiS!^skL_EdPr$33SPmX1HiHpRelv%)yq~QHHZU`qa z>7LQuzxap1X7pga%-k=j^n$utmLtI7=EsY=o9IfR8bgol7h{KMzj4qb7sWsF231(WkDcHFFi%3- zZ-zptelwiJPr?VE5*E)$LVgQ`2^M7rYnQrrekmrZ*r!KiLWcJrKVQiTFKn_1XJB|s z(TUMA#LmRfUCanMy^u(f6vmtvcpHJH9HaZ?Zc@)6#7KCaMSlw2XPAUVNE)Juo7yl+=C;=*F zR|^{4Isbd>a|Hn9l5&!E3O}iv3~1rps!RagjF$)LoRsD#>}|v0@lUAHT3hwrs@28< z_PU^U9aJe5{Y`*9)3X&Q=83~s3@R}usjLTNou^H_TZk?k=xe z>Y2pe7fft=xz=FeR)KuXXXH8NwbhYAXvQT266T6LQshvz%Ba*5-*H-gzyYw4Y-0yX z9R*g%G?UvagC*Aj=WK%VTeLzvQ^ufbkezm)@)0ytZ=~T-EoN}>;BtAOxC*{D7t<<* z?OW>`XC|oG2N-_n3vd`F`f zETUi5+jDV*2?Nf&17byJ?1n z#)f|W`kbdzq7JJUZjd5AgO9Sh4FJxz|Gj6L@0ZKeY!r}<=cV9t70}T{n}Nd-f+&J! z*A&Tr4cVcV)0zzNREaWY?;vXKJ^}35H#BG1MSEIUwk;bl4JikqxxM?fnM?yP_IiDc z6|X0N&q*fE7*wO?etNuS1OX2Y?L_dr4|XIr2jPjwhT4z9cJ4&I5&1wa4gefg&vCuU z_%t8_hio%@BJAc4?ObXkMyfM|U5}4y!q)m9ZAkE+fy`q1V7KscE+v>M_cm~%ir0_W z^UX{Ia;9NZtDF$tG&WMKX3KV0a?2TU-bO?#j}2l{>?P~tUGXj3(RGT3Mwt$OiCNKa z)7sI$!>1b4wpuA>4O_vKqw!b>rg4}C23<<^v%>RfY^Z|YBog5Kp7WdZGDsJW7iO+t M!svXml68s>HL(K_djJ3c literal 0 HcmV?d00001 diff --git a/tests/data/FileKeyHex.key b/tests/data/FileKeyHex.key new file mode 100644 index 000000000..1bf8e5db3 --- /dev/null +++ b/tests/data/FileKeyHex.key @@ -0,0 +1 @@ +0123456789abcdeffedcba98765432100123456789abcdeffedcba9876543210 \ No newline at end of file diff --git a/tests/data/FileKeyXml.kdbx b/tests/data/FileKeyXml.kdbx new file mode 100644 index 0000000000000000000000000000000000000000..a9db3ac40f0d8f9bbd91626c0c5b54c822db3283 GIT binary patch literal 1582 zcmV+}2GRKg*`k_f`%AS100IC45CAd3^5(yBLr}h01tDtuTK@wC0096100bZa#aI8o z(3J}$(^qa(_Gg266m0)U*p2`lZpJc1*m0~P1t0*9F;U(yq{_5wts_*# zAIwwI3s1vIK;pLs2mo*w00000000LN0GhK6A1(&G@8Ho9h;Rt&YzQC#yJ~e(txbZ? zKdD}4*OB(T`A}E^91Of~>6%AKRrn`k2_OL9UyTh1Lty1n8-B_A6;)|n!An}hyc@|6x*~d}`-e74iF)QrhQjzgxq98{_ z2gkNf<*0mc^`)O6hUd~x%0Dtr1+Fp?eIVwhGlsWUzF~$EItz70O2R8fxMt<3Q5xOa z>`8!*>7tI6G%p;vdYAd)3#Or-DwJK(L>%8Zm|b7i!t`ZeiI@Vz&xTUwg|1N zK0pL9z(1v(#JB~Lb z;^hmL5}4_f#dTb(MTTVobdZO{;@I=fva04_GUOoGk%3?XGNe5y*Xy`~X94(%o?*;k z9rmU>4jxdydzbBWt5Ytv%rfQFMlhd3eDKF?lmoBn9`>)<+T%-wvQfI&j;!?ROc2c^13GaR8leyYQK5E`xa327#5t@-6 z2)Gz?y^$$7BrHbXk5lmcUw0CohsFq-V@1wlj%QO3ZOj(#Q5s6n66!ePMbII|?472Kz4F~waYZ5sxzqt=I>$SLt! zl&ZV>KAj4Tu0Vv*1?ott^&7zP)PrJsH92EteJwrHr$(i3zm6v7x2fBum2Pov$ae=sCZH`q=Q=t!kP8asU7T literal 0 HcmV?d00001 diff --git a/tests/data/FileKeyXml.key b/tests/data/FileKeyXml.key new file mode 100644 index 000000000..57138d91f --- /dev/null +++ b/tests/data/FileKeyXml.key @@ -0,0 +1,9 @@ + + + + 1.00 + + + nhNal+U9p6h1rWAAJ5YrNkMazMTZkIWLi3WC4JQv5jk= + +