From e443cde45280512c39b918d60d98a933b9804751 Mon Sep 17 00:00:00 2001 From: Janek Bevendorff Date: Sun, 13 May 2018 23:21:43 +0200 Subject: [PATCH] Add a new database settings wizard This patch implements a new database wizard to guide users through the process of setting up a new database and choosing sane encryption settings. It also reimplements the master key settings to be more user-friendly. Users can now add, change, or remove individual composite key components instead of having to set all components at once. This avoids confusion about a password being reset if the user only wants to add a key file. With these changes comes a major refactor of how database composite keys and key components are handled. Copying of keys is prohibited and each key exists only once in memory and is referenced via shared pointers. GUI components for changing individual keys are encapsulated into separate classes to be more reusable. The password edit and generator widgets have also been refactored to be more reusable. --- share/CMakeLists.txt | 2 + share/wizard/background-pixmap.png | Bin 0 -> 45338 bytes share/wizard/background-pixmap.svg | 313 +++++++++++++ src/CMakeLists.txt | 23 +- src/cli/Extract.cpp | 16 +- src/cli/Merge.cpp | 2 +- src/core/Config.cpp | 1 + src/core/Database.cpp | 54 ++- src/core/Database.h | 14 +- src/crypto/kdf/Kdf.cpp | 2 +- src/format/Kdbx3Reader.cpp | 2 +- src/format/Kdbx3Reader.h | 2 +- src/format/Kdbx4Reader.cpp | 2 +- src/format/Kdbx4Reader.h | 2 +- src/format/KdbxReader.cpp | 2 +- src/format/KdbxReader.h | 7 +- src/format/KeePass1Reader.cpp | 10 +- src/format/KeePass2.cpp | 14 +- src/format/KeePass2Reader.cpp | 4 +- src/format/KeePass2Reader.h | 4 +- src/format/KeePass2Repair.cpp | 2 +- src/format/KeePass2Repair.h | 2 +- ...dget.cpp => ApplicationSettingsWidget.cpp} | 28 +- ...gsWidget.h => ApplicationSettingsWidget.h} | 14 +- ...ui => ApplicationSettingsWidgetGeneral.ui} | 8 +- ...i => ApplicationSettingsWidgetSecurity.ui} | 4 +- src/gui/CategoryListWidget.h | 5 + src/gui/ChangeMasterKeyWidget.cpp | 252 ----------- src/gui/ChangeMasterKeyWidget.h | 68 --- src/gui/ChangeMasterKeyWidget.ui | 238 ---------- src/gui/DatabaseOpenWidget.cpp | 12 +- src/gui/DatabaseOpenWidget.ui | 13 +- src/gui/DatabaseRepairWidget.cpp | 10 +- src/gui/DatabaseSettingsWidget.cpp | 315 -------------- src/gui/DatabaseSettingsWidgetEncryption.ui | 183 -------- src/gui/DatabaseTabWidget.cpp | 155 ++++--- src/gui/DatabaseTabWidget.h | 1 + src/gui/DatabaseWidget.cpp | 70 +-- src/gui/DatabaseWidget.h | 9 +- src/gui/MainWindow.cpp | 6 +- src/gui/MainWindow.h | 2 +- src/gui/MainWindow.ui | 6 +- src/gui/PasswordGeneratorWidget.cpp | 21 +- src/gui/PasswordGeneratorWidget.h | 3 +- src/gui/PasswordGeneratorWidget.ui | 71 ++- src/gui/csvImport/CsvImportWizard.cpp | 16 - src/gui/csvImport/CsvImportWizard.h | 6 +- src/gui/dbsettings/DatabaseSettingsDialog.cpp | 139 ++++++ .../DatabaseSettingsDialog.h} | 50 ++- .../DatabaseSettingsDialog.ui} | 21 +- src/gui/dbsettings/DatabaseSettingsWidget.cpp | 43 ++ src/gui/dbsettings/DatabaseSettingsWidget.h | 51 +++ .../DatabaseSettingsWidgetEncryption.cpp | 410 ++++++++++++++++++ .../DatabaseSettingsWidgetEncryption.h | 75 ++++ .../DatabaseSettingsWidgetEncryption.ui | 410 ++++++++++++++++++ .../DatabaseSettingsWidgetGeneral.cpp | 119 +++++ .../DatabaseSettingsWidgetGeneral.h | 54 +++ .../DatabaseSettingsWidgetGeneral.ui | 20 + .../DatabaseSettingsWidgetMasterKey.cpp | 247 +++++++++++ .../DatabaseSettingsWidgetMasterKey.h | 78 ++++ .../DatabaseSettingsWidgetMetaDataSimple.cpp | 62 +++ .../DatabaseSettingsWidgetMetaDataSimple.h | 55 +++ .../DatabaseSettingsWidgetMetaDataSimple.ui | 50 +++ src/gui/masterkey/KeyComponentWidget.cpp | 196 +++++++++ src/gui/masterkey/KeyComponentWidget.h | 130 ++++++ src/gui/masterkey/KeyComponentWidget.ui | 226 ++++++++++ src/gui/masterkey/KeyFileEditWidget.cpp | 123 ++++++ src/gui/masterkey/KeyFileEditWidget.h | 54 +++ src/gui/masterkey/KeyFileEditWidget.ui | 70 +++ src/gui/masterkey/PasswordEditWidget.cpp | 124 ++++++ src/gui/masterkey/PasswordEditWidget.h | 56 +++ src/gui/masterkey/PasswordEditWidget.ui | 90 ++++ src/gui/masterkey/YubiKeyEditWidget.cpp | 163 +++++++ src/gui/masterkey/YubiKeyEditWidget.h | 60 +++ src/gui/masterkey/YubiKeyEditWidget.ui | 90 ++++ src/gui/settings/SettingsWidget.cpp | 53 +++ src/gui/settings/SettingsWidget.h | 75 ++++ src/gui/wizard/NewDatabaseWizard.cpp | 76 ++++ src/gui/wizard/NewDatabaseWizard.h | 50 +++ src/gui/wizard/NewDatabaseWizardPage.cpp | 112 +++++ src/gui/wizard/NewDatabaseWizardPage.h | 61 +++ src/gui/wizard/NewDatabaseWizardPage.ui | 65 +++ .../NewDatabaseWizardPageEncryption.cpp | 33 ++ .../wizard/NewDatabaseWizardPageEncryption.h | 33 ++ .../wizard/NewDatabaseWizardPageMasterKey.cpp | 41 ++ .../wizard/NewDatabaseWizardPageMasterKey.h | 36 ++ .../wizard/NewDatabaseWizardPageMetaData.cpp | 32 ++ .../wizard/NewDatabaseWizardPageMetaData.h | 38 ++ src/keys/ChallengeResponseKey.h | 14 +- src/keys/CompositeKey.cpp | 70 +-- src/keys/CompositeKey.h | 16 +- src/keys/FileKey.cpp | 15 +- src/keys/FileKey.h | 4 +- src/keys/Key.h | 16 +- src/keys/PasswordKey.cpp | 15 +- src/keys/PasswordKey.h | 8 +- src/keys/YkChallengeResponseKey.cpp | 5 +- src/keys/YkChallengeResponseKey.h | 8 +- src/sshagent/AgentSettingsPage.h | 2 +- src/touchid/TouchID.h | 48 +- src/touchid/TouchID.mm | 113 +++-- tests/TestDatabase.cpp | 40 +- tests/TestKdbx2.cpp | 8 +- tests/TestKdbx3.cpp | 28 +- tests/TestKdbx3.h | 4 +- tests/TestKdbx4.cpp | 30 +- tests/TestKdbx4.h | 4 +- tests/TestKeePass1Reader.cpp | 13 +- tests/TestKeePass2Format.cpp | 16 +- tests/TestKeePass2Format.h | 4 +- tests/TestKeys.cpp | 108 ++--- tests/TestModified.cpp | 2 +- tests/gui/TestGui.cpp | 180 ++++++-- tests/gui/TestGui.h | 4 + tests/mock/MockChallengeResponseKey.cpp | 3 +- tests/mock/MockChallengeResponseKey.h | 1 + 116 files changed, 5054 insertions(+), 1692 deletions(-) create mode 100644 share/wizard/background-pixmap.png create mode 100644 share/wizard/background-pixmap.svg rename src/gui/{SettingsWidget.cpp => ApplicationSettingsWidget.cpp} (94%) rename src/gui/{SettingsWidget.h => ApplicationSettingsWidget.h} (80%) rename src/gui/{SettingsWidgetGeneral.ui => ApplicationSettingsWidgetGeneral.ui} (99%) rename src/gui/{SettingsWidgetSecurity.ui => ApplicationSettingsWidgetSecurity.ui} (98%) delete mode 100644 src/gui/ChangeMasterKeyWidget.cpp delete mode 100644 src/gui/ChangeMasterKeyWidget.h delete mode 100644 src/gui/ChangeMasterKeyWidget.ui delete mode 100644 src/gui/DatabaseSettingsWidget.cpp delete mode 100644 src/gui/DatabaseSettingsWidgetEncryption.ui create mode 100644 src/gui/dbsettings/DatabaseSettingsDialog.cpp rename src/gui/{DatabaseSettingsWidget.h => dbsettings/DatabaseSettingsDialog.h} (55%) rename src/gui/{DatabaseSettingsWidget.ui => dbsettings/DatabaseSettingsDialog.ui} (78%) create mode 100644 src/gui/dbsettings/DatabaseSettingsWidget.cpp create mode 100644 src/gui/dbsettings/DatabaseSettingsWidget.h create mode 100644 src/gui/dbsettings/DatabaseSettingsWidgetEncryption.cpp create mode 100644 src/gui/dbsettings/DatabaseSettingsWidgetEncryption.h create mode 100644 src/gui/dbsettings/DatabaseSettingsWidgetEncryption.ui create mode 100644 src/gui/dbsettings/DatabaseSettingsWidgetGeneral.cpp create mode 100644 src/gui/dbsettings/DatabaseSettingsWidgetGeneral.h rename src/gui/{ => dbsettings}/DatabaseSettingsWidgetGeneral.ui (91%) create mode 100644 src/gui/dbsettings/DatabaseSettingsWidgetMasterKey.cpp create mode 100644 src/gui/dbsettings/DatabaseSettingsWidgetMasterKey.h create mode 100644 src/gui/dbsettings/DatabaseSettingsWidgetMetaDataSimple.cpp create mode 100644 src/gui/dbsettings/DatabaseSettingsWidgetMetaDataSimple.h create mode 100644 src/gui/dbsettings/DatabaseSettingsWidgetMetaDataSimple.ui create mode 100644 src/gui/masterkey/KeyComponentWidget.cpp create mode 100644 src/gui/masterkey/KeyComponentWidget.h create mode 100644 src/gui/masterkey/KeyComponentWidget.ui create mode 100644 src/gui/masterkey/KeyFileEditWidget.cpp create mode 100644 src/gui/masterkey/KeyFileEditWidget.h create mode 100644 src/gui/masterkey/KeyFileEditWidget.ui create mode 100644 src/gui/masterkey/PasswordEditWidget.cpp create mode 100644 src/gui/masterkey/PasswordEditWidget.h create mode 100644 src/gui/masterkey/PasswordEditWidget.ui create mode 100644 src/gui/masterkey/YubiKeyEditWidget.cpp create mode 100644 src/gui/masterkey/YubiKeyEditWidget.h create mode 100644 src/gui/masterkey/YubiKeyEditWidget.ui create mode 100644 src/gui/settings/SettingsWidget.cpp create mode 100644 src/gui/settings/SettingsWidget.h create mode 100644 src/gui/wizard/NewDatabaseWizard.cpp create mode 100644 src/gui/wizard/NewDatabaseWizard.h create mode 100644 src/gui/wizard/NewDatabaseWizardPage.cpp create mode 100644 src/gui/wizard/NewDatabaseWizardPage.h create mode 100644 src/gui/wizard/NewDatabaseWizardPage.ui create mode 100644 src/gui/wizard/NewDatabaseWizardPageEncryption.cpp create mode 100644 src/gui/wizard/NewDatabaseWizardPageEncryption.h create mode 100644 src/gui/wizard/NewDatabaseWizardPageMasterKey.cpp create mode 100644 src/gui/wizard/NewDatabaseWizardPageMasterKey.h create mode 100644 src/gui/wizard/NewDatabaseWizardPageMetaData.cpp create mode 100644 src/gui/wizard/NewDatabaseWizardPageMetaData.h diff --git a/share/CMakeLists.txt b/share/CMakeLists.txt index 81bb26938..816966036 100644 --- a/share/CMakeLists.txt +++ b/share/CMakeLists.txt @@ -39,6 +39,8 @@ if(APPLE) install(FILES macosx/keepassxc.icns DESTINATION ${DATA_INSTALL_DIR}) endif() +install(DIRECTORY wizard/ DESTINATION ${DATA_INSTALL_DIR}/wizard FILES_MATCHING PATTERN "*.png") + install(DIRECTORY icons/application/ DESTINATION ${DATA_INSTALL_DIR}/icons/application FILES_MATCHING PATTERN "*.png" PATTERN "*.svgz") diff --git a/share/wizard/background-pixmap.png b/share/wizard/background-pixmap.png new file mode 100644 index 0000000000000000000000000000000000000000..e64d87041879cad998833cc4c1403803a75ecce4 GIT binary patch literal 45338 zcmc$F1y>twv~_TIifi!VTHM`Rinh4B7S}>?3tGHLp}4!d7I!c1?m_On_gnWje6!Z9 zB(o-YWRC2;&q;*3svHI?2`UH#!cdTx{saQS#Q%39BLY_fJNd?d7kF1mh0n;q;frh* z4tz#&lGk+wfiV02J7JQUF-d`o#BMS=ZW@jjZXU)i<{%Fb4>oHDTUS$KCv!GO7t4$@ zVG5Xp-$YCCSxf2pz05I)34gc*|dLup-nNH zf)5n2!6VSW&wGPSLJr(#sNnZK5Ae6m7=N0m^#9}Vj0_|{gCPq#;7Xdqd}Jg84g#WB z@dP3e2l6k5{R0Gbm`xc=*vI04>fyCl$sjV?}J|#6RL|J;Uz0EI+alS(lC*WnG5|CU9I2Ld2=sL9uWb zJn{+rsGstU4$1~NC{L*?Dag;**UsV7&0IJH$%^^;`Nv3=AmWHHWR&EC5;jDUEP+?_ zMRlvWv$D%x{uE1#Bg&vftW9w@<=*b@?p)dw*dQ{5OTd}=?2ncyx zfPbG1j?Johm<0)tctihK1WIVOW%3(%H$Ws>_5no=rZFAET=l z#3Bc!{bfj~`e$uz%|Kf_)v)-p=kHIK?~_rl{c;{VCPXH-v-d9*fA~TQOzDgKIil5yg`ZcGX5c{P2;!(izxse(?czGBz@d%_Y!y=vPTJ zS_U?eik>||#Qn@~9v<3ID3n=5L}ad`qod;N>`W2Z5MJGFGY8nj@2nG?LE%5aiH}3l z6Vc}U`z&A~wHWevdamD7W8>rVmKGKyPdK5*d)%}6s0OJ>kN3I}h0V4f0`=q5BLOSY zzz@@d6Cmj#291CJs)x3iX2Mk|uzzrTd`v<{c5-%ldU|wvTJHYsn`W*xj8%e6UR+WN zRh@!BBde*h)hRAXxJ^bv5L?kLd*18Sn4uRvm$VB?xg0W1>Vi%E0I=JB-rg;uqM`;F z+1Z#b>(@2()e0ar*hRSg56(`WBmdN~hrB_Su%2vJ3R0cpWu>3rw>3643i9(Wu54^< ztSu}ooI*uEDDTmR1g8ICMegJOSjr{!1tLeqM2vrHxG9F?1&^B?#S=uNY=RFT6wZ_U z^zh*6XJV2^5o)Z5R!fKd;tiW1f7gS+E66WQSEUX=jZtj=j8Y&)Hk9pKbA${Mo-pn0 z?G=%dlJa|bc>(rVLGP((yT z?>Ssuq9NFLdOA|rVK508JaKJxdo)+)uqLS2?#&C_W$o#ygN}tYGdYRP`lTWIzbjT& z$iSLDmuqoH4B!2kv*8gGRNUU)CL$+)hlhv9!^bBJycAUA_+BZTthZ-ilJR3#ZcYzi zT1o#JvoJ@IPQ~a|9~&E+m6emTMZzTeGk1K;aeL3Som44hL)S{}$ME0Gu>~c7h?0%8 z`6GU6QQ!&AguE$iC6h8)89*U2b+|uwQA2X|P|R2hLF7Ha(bj zdxnRHmplBR7GN$;9Qi4qr9j$*wRn>Md=pnj2>uEk6oA>BuBh_Ix@Slj3j^b}>}#`& z(^8Fja5nl&4^D{ZB!ER0*oQF_4)xFCH69$ix#6*2s`+u& zes9q7_6j{*_c{L!Otb+-8tM}cOnPc+YI$*SarNNfPlWQ-kPgm~sJvkWx1#_*i^L)> zGSUSS=#`W<=s+G%Mrmnt)9C(at~4wxG&DLrG@+In8{{_oQEuw6C`Yv@$G^pMzBc;@ z1}R?w+?%rWKNM4X37)e&_|Uc>V09eXavyr z@Xz1TG3cF7R+|&~9M_|vXRT_he)ns2rh~EYkU#fQ?S%BO5d$Rb@>p%I(d~&5jiTM-mR`==eB)U+nFX|9;i=aEyDR07$YdJ%2)s7iFa1flGdU z+rT!!~P6w%t*L#=o10c~>=(oD}10gBJbsP@` zL&Xe+hc~vOf-Tc^T;L`lwJjw3zHR@o^!L_qXm|-EW$K+Zu|1A71f33#sX>a0oDL$4 zJ46+aKrQfh_V;*1grAE`fbe?NUgx%w}hiyZyQ*TOO zeK_%YGI8*((h~tlH!6uAj2G7wSo7uSRlxL?w*aQSc?Dl<<$o%krM^c{7*bJF zi^_T4VR*P}>{R*E=%9iv@10f!#@4rRaj5}*F>XRtMWwjIm`JU~4z~+0z5^EtDXcey z1y*f>7948l(@EXc9@9V#6rSNhUrd2U4%4tHUF|CJi_2k_nn1GFa-J#ML%r>B{}guWqvsHl)0s?7wgJ$JBxbdoY4Ag`O08Aq@VT9#OD_y*}aN2mfJzfAX z!EOE=RswzS!&5<`Y7eu)C93z*sKNXeIs@1c87EP;yArCZJl@PTb?xehp_D1UOp+5J zeiQ6U;WB=ly+1e21>gg>8I=!H9v#=@%QfavfYHyOr`P z2#kIJkDE9M#MA#BT)MEN&RR+C*?cc^20+77ga)3}SuP_?C2X%fa(l)8)Y7RI6in$4 z1ewy)c;@rc?On7rKT&8KMfNknF$HH{27-y~Ye2lbDR=aX@_!sP)WZo+NXyQ4I9aTg zVn|?t{W!={kiT}Sw6*CJ9#0hyp#61V&6O%Ra`TETuz~CA22Z1%&r066rG_d%`d0Dn zrm@dgL!Of3GM6`T`IMN~zq5*ht2+k&Ngh`B;}xI%3h@_W*r_9#g5=q$XkKb@*)Btm zwQo0T=l-cjx8Br2plN*Xi_Pw3zzy~+EG=ub?0=6AP)GEgZl7&dbs2#=rqaiBv(u;CEHJWUdY zsp6$1z(4TJF@whLAHx#G730d`06@tBPG~VOD4jb96MLrC_u5V7_q_PU2*ytCbDqc+ zacy-!Eg{W@>%FDT8&4s>RaXVUj< zfFP=hy|`rtygmZ#rB@0|B*a^P{8m~zn)8p;tp?wv9F-K zU@4?YNh$#LpTs!&(m*G}MEfTAx6uphFcS`Cv_g%WxPtlJbW|9l;>9EBkBZ(0na!ut z#Y-C}%Krr8ZrnRR9Tsi>7l4j#1u6U=jHT(z2w-s}yRL-+`gEgJX>d9*J{}2h1Y_@% zPy>*DJc+_o6M#5euOoOcRf} zKp6p#tBqR?tkRgM#wyD2=;)|t$ucJ~>-&0p z3nKS^{ZW8wggu=($4Jx~94v*{26(Qa+tn~bEtda9uqWWMf1jYxy@u=^~H9^A~R4W_Vu!M69Dlca)Jqqn!WuLFSYg$mgU z7)y=R81zKl$nlevC4EziR<_kgtu+`_H8~TJtsp7L?_Thw|F3uib`H4;9et-q7xv$vYT*d`_Jz1({NEGkkz>nr! z22jU#^&o|Y5E#7e1>%4)l5X{IavB0*eK+#TpW;_Up3{%d#P>@aHgPdOUUl^yUoOX75xEemR z4Y(Vm9vmH&NTk=pgS>YjatD6rK9v7uKv@jw$9P}~0CXxpU#!ljbLF6&{f2BkT>?b> zZsaepFnTEPjMG1YPGke_Q0tm+9`7`~I{S@SwbX@z3uyr!)?6H9Ipx zp`m@^+F}^@7*Xc!qy6d|zjWqX(T_tHrds}e(-Epm!MMfjFE&n*=UbPOp@I(-p0IuB zFx2*gmk`%B@S>~ecsk;@_f7u~+rY!9sHh;si9Q{CE!P?aV@topN;>>L-rS6r#0lzh z!l3Q6J6gpIBAb5^$5bXmrdWc?KMcLf^$gyL(NnCv5v_daz=AT|*HWQ_5_C-@s9;ez zmSTe-EU-aA;#7$A;k=_d(p303$jIO`W>%!32CF+PSWRZlY6~lsrvFakzlh6KHgF<- zw=+wZwy~DIBE3)04j}JNG;*=S2GK&W#nhE0_1%* zkMWKL7KSz`8o*)HFnB>Acarc!G)pBauqlGp;1G4GKlJsqyVL*Puosx|fW^KAj>?53 z4JvK8M9Dia8#gvG9GhkEJcKTYQ2>#C)-xO@C`jw30(-DrknQ=vR&*rHJ2Y~A z@DW?y;S1@^SJ_T-YV5i7>o%%sS!B@P@$qQ@oO@jYsGyWCECq}`aV-r99U?w=vmU3* zzhO~T;3eZnedX%7kiv?D`Sm*E1+Ru+n7qDR$&1mT6<(SR>n?0G9(P=HGf><2`Z2P>a9u#ExjcV+J@*{_vYxO~(b$-p<9$@D z4=hZP`$IQe73?P(K>kvsIgT>n(%4O_id5F6FF5_z@RP7xc}%5hs8&kdhoeM5?^4lC zEi;`9(GlA;daJ?P@UJM-T9JS3Gx1-(V$QxprJIJ~>S;53uH)Y$`}?K_w-8F(k3IK( z1fjboTQ&?mt5g53!BC-Mtl{E_*?eSJ!@&(VSw)-`|5r zM4%_N|GTvSyBxFADc5p7sUFmd6}i(9(~vj87B?*ZD8x@DAulxb&Q4i_Z~W#l&(83N z3$+X0ak0Uk$eq=#CSN4-kPA9F(UGLFM14-ereH*wUOlSzS7!u+%Si1VKD}1=^0#P9 z;ryYEDN1roOf`WWYbssQSQZyfm7D6e-6Lkw8Eu1B$!3R6D$}SK3r@p9FQi|{C7nfL zYR9AJCdS8vq>F$M32caG;4Bwg`^~4eWBqf<`t+}Xb5=ZC3k`NlT<3K&s!_uXaP3aC zTNn7!%MqBTqJHDt#!FT#ATI8UhSKV!c~!&~q;MD`78el?uRxg!GA-2j0M zA;_RM0JZiIc3dE^7Ht zl3u)OWwHka|fR4o;v*l z=bsLifzOk2uRG*)R?*I@N86)fy)Yo1{HZ@wPncw`W1J6Lgq`7*qSt9@7~$dnv9Dx+ z!wcgU_gI55)o_0DV1FB@!O{@nTCVHjdYk+Hvp&1y4*qo1Q35qEd9PdW-(QFPIK266 z^`z{!Tc!$wAYPIf#dd>zP?hhai|?6hPQKX7jM!hh)wMMYIWQ(Bt}coEQ>JCl2u)Jm zUA1}@edf%3&Ys7kejl&yyGRJ;;3j%szCzaTo%jUq+vFtbX(@ia9U>Z2q4wzbcma!D zP7|3o)b?S?4F6bpj)7Wq)K$63>#Z;ZqHwnsVndm{^^?VdLG@jBT z1E*nvhr7B{e)a7;_*P^O6yWVt{px&G+K~|2F^cw@YS09{qG54Y>@in;AO)L_gXvSuunqcje9Lih{l0jyqp%+umNzCz1#ytG46B)(z`z zG$Hg`DI)@8kZ?aLL|mryx+r;CzpOhy79Osg+oGa3-QQatxiVQ)stWaecJbh?+4M(I zCb>=c0}CO2)pwjD9G<+R$cV&7b6tv;C)?SlFj}1_gzfG%I_i}RUhM{P3OfK9>J1aU z$yN4}0hel5j?+NcnpSl&<*3{!!>zwh%t%h}7W%78LVvQCL66Um_5)n5nu9V+iu)-O z()Th_N2InG>MU0tR0*87$rn3v0Z3Am`ub}(V&hMgUc{!~_)iN& z7;#}h!utXf-GIRp??8qCdH58NL-6|}nm%7(%Axbe>rk76A${=VAuxWYDV5_QRnVYx zgH(D7zd6kjVT+S{Y-;+JZ7quZEWe&>xF`a%cV^+9Z`WTrGNfY>&PM~@@~?YyJE${g zBcp=IqMOV|S+@qD{`l@D=x9U_LjX1YtyKyW4-;ni&_l=}ES!lwEq2V1<(3d3ee>G- zkMFk&za(}>l`?IDLGHJ6y!&~B``-)moe$USpJOS6m$`?259j~Js`J+svLC5AgRTW! zI#i}ZNTZNL@}k=~S$6F*uMzyp$#j_3FP_ot>3_+TXQAJrZ?$i{fC5%D9Wg0a-gMSo zF?mZ;=-DCfgjoXE34MH7+XFrtgbxWv{1qQ)81ej@@ z&gDQ?HapK=BxIyF>P@vN7~<3a@Lqk?r+=EQAPi<=9oS<-+H z)WmNsUo{kl?#2*iKlgx1y&33VCC5H+)KxAIYTZ;4fYh;$o38elAAe{+XHDLFibL~Lp1QbRhf zk}&L&tGvV~pHuf5$G!A3qh47qhdGn9PjnBb=07Y*jFj&yA9K;E;b7hF2R44;?d)m+;lt`n zZ)DvtLtMR-93+DOMl_xlt6-Pw%=k~Bq(=4s^#U;4X1`x^=~LA-Wu5d4sNtJAmFx0Cp9MnrGaK=D%bEu%dgC^xkH8xS?cR!Df44Shj)PqL6R)?iqoE+#d%%;xWiZwnxNoD+Ic443`_*GVI-xq-*q5~3D)?OF5cjxq z%=M?a4|ARFquS5$k{q)5l;Pz}@PYH_6|n&*q~SDi!gjpzQYPbyV&~0+Ifrl1V^MNU zH>~h7s%|%88UiFj0>K$#NKF1$ftv-4%6Eosn@artbCZPIxC zRD4S3jvpQ{D9zq(%J0L|mX*^cy1&~p)a%7aWZX;$oeQ_^vN{eY88$kQivbGkHw-S3 zP}EF~HRFgNxUTXqj^&0AzZYhtS}Br(X1Pc|U&D=u->(JACgx7CB~E(U5qX zM~ej_IwG#If?a){tXw?qys@$Ka#8wsJiHuJMO$WY43t8?OB+C!7q*_SCnmlcIRO=; zOF-e+hlFsO3$QSGmX7N>Pcb+?OaCUA-Nh`m3D6oK3Vz%`2zZvg(fV>HC66aL-z*Qu zB!#WpXQILvUGJZhddq4WaEZijsit~jFI?k6iezfUvK*EwPrO^CgN5XB*EwjV?LRk}+wrr`tR}b93d%i)OU|m+9tb-NDTHCdUOo6BqYS zr~`Z~h5RrjUw!tGrze7Lz6_`L9KD}cRn-kpd~ghaI(r{-8}5}l6Vo55+Timg7Jrl_ z)n3_7E1#J52!N7f({Pyd5rIq+)a4V(+sus60sYS*;AX+o#4VNo`p~k5kx*w#AalQ& zgg|x1hKHo=Q!z5gvm4DIaK)h&!_?H275-D@Lbc7@`x78bcmK7+#6-hjOGNq9CjBA3 zfzbtAnj`(!d!YD8GssSoBdweuk*Tyw?PcN0^+Tp^^Pc_2&FkgF#u^|5@QtCfMf*~J zQn0>FtwY4XEspuaSGJ7Z@#eTr>3jH{9ZIU5W8_kpGB;$mgW0M)S31%d&R{<6DE{u(`OJ(jw=qUhKkzeDQ@JU0sC<{)9An(2 zxc9&kM%?9e2e!Z`1)e!Y!uE?ab`;UqBehV&fMecQG&O(cA8Gs1s_A)lyOJk1ah5pj%QVqtOjih`er zR!dmIWU#E^;_;|7Xu&ae5m>ImwU5I8_D8vd-epCF)5a-dX4stjh#GiaMKQArKlby8 za#O4~zkKM{k6UA+So9KG;jM|9>uDj&^1iQgb=0op_gUWLx8TU0Y+k5fG zmjHAG7ixH*J~A~BLwX5lWn_fDeT~!+3C9el>F$5=y>c!k zaC#B&dgM)(s~a@OqQ`bE<6sQD8lD){+^V;A5ETfr5)3}7>I1dneLRU6onJb!l7XK@<``(|e>C)zw<%}1f zRWata#dCTcEEL}C-2O7)sV=HqnG&D&TTYPHz1B>zv*sA6DhWt#1Ws?PZ6eaB>QuV! zACrQZOkN=I_UtTSNE%NZ4}Nz@7em0+$GgpJ)~#u=&ZI~GldtD}o-0qnfHv<5NUJcQ zw%6OrqW|bg)9w71FFRW0dOx_w`GeQKE$Hh`eC$Z>E%XxNHw$9kkacrRU&W(brLyCo z-l9}4MByrBDSf;7wpZ)=B$jFABP_62nrFE*LSfVViL3UODPNp1($oUpG5K*yWW)5$ z?Zu}Lh_i>)V{l%6eQ6Y;&;PJYtr&&Q%wL{vz3BP()jf2Qtmrky=y6}NLFy{Ay_wzx z<6a>qE#oud%0u3=MaHu_drN9dd|62%C(8;VcHd&T*Y)OMgEjR3&9gRMDRtapata9P z+OXJ($^JuZjBskl8=7! zVa+Y${M^BNkO7gEbK_Kl00oMOu?%6whp4UH_M$9zlJ(12kG|w&MY^dlW6czx^A6FH ztEokXdouN`kaVtqQhGR}F3NqFCXQ#CA74+d_2@A4#vFVg`vjP;4ZiAX4* z=Ois-MU{+s;mGf_zMzao$34P;Z4*vTZsVu6-59S9u81c9t%vf3)iFGDS_PrjaHtSVVez1O=RGXZB&O*ukLjr%WzDZjXjjIlp0XE;0E z{rdZ9(W|q9#hw|JF*>!sb6@1V+A}G*cMFkWfN`W&Vl&)hMXA8mQrejf6Z3?0qjWiy z4&}?$5KRmZX8rsW8z3OpoTh}h*EqTaXe4uF5hn_X?+sH;tA6$^{nzGaCdBr-ykTnM z4tD)Y9vw6cM@mWx2g(GbhNgqOC@zHm$~2Z0E;={ichZ*9Hfm05#uXBZL)%?D9gpc% zH?C9lc0+Sz0kO%JQ28`!A?PJ+`n*>=mtWRmDHi9DBFX7WJtT|*^NmYKE z5!tSBi`YMxcq*ikn+h$88NN}ZTLWDul<%)Vpm%7voo8`=C#L*B)g1%^3OW&@<0AK8 zGuX(5fCtcgZ%80H@KxfOpf|bS9jO+~AuZBI3t?E{C z+njeFxzxRhPK%sbaYgDlnFTrZzw3W;_Nw@0ju_pb~x(icr{z<{t0T zwahY~iUF(z7Ks6tA@YvjrlKcu{w_VX<&Ww95Z;p_%MY$)xhG*taKtDp-bNbz^&_@2 zWRHt-E4?zb2K#E>MK>U>Tul@|b)~SE@On~snoF$d9_g96(Vx@NTuW{!zq3SZ-tP#h~(fm?^AX2^DPc^jl2d5BP8gQwW@g1yQo?2@f<#}@oA zzxltMzs^X5$*JS=?!N&AYVm>}Wrm#;srv3|{ift(1XyNHcrtIG_>R@c-yNXY=lh`! zd#^+s@$3FT)#y^zX~w0(?d9PHzZFI^f>%tm(-3D_EBGH1X2zx4 zFSQ^1Q{ImL8U7YCY(8*WhAtBRCx=K*gw@;3mGoZ;d~@RX&Rw{a&HG&))>`u8U~ntt zB>7==le=R}19eJ`vD&7of7DVp&CQalA$Kl$|? z#ZEvqs6-klh-{!W&W|Y=Rk;^$xPut9d_iok+_*ZxpN5~+TH#w}31PX56H|cF-w&&$ zQS-+ai%g@zB1;t_5L>|Gh8o~U|D@q2R;~7U$%(S|_FlGm&Po9~iBx>sowo;8zv^OQ zMxneHQxitv;Fyjp3$3pjpjdnnkGC-ll~&$&)3pW{l@q>GEVVw-ypyUt+0mpgX$pBihD z^ajbhWJkcD%b}m7lU95998KszH!Fi@ELXwet!!Y3usv<*1{aCE9<)m0pO5<9LDf7r zWexLMg&CPj#feDV2A*Z%J20(|4c?ff5DI)os{7SkM(0RC{ch2f&_^~Yi9z#BrcvYS z{Qi*N;eFzate&3dQm>!wN0QPO+;Z4Hs08YUBq)&S8oHpJnu_9GLR(*T zBBI;$wARZsb^5|3ai-ZRpLA)CMtPR-t;uyQnZXKvUgdRhnfH0&s|5JEUM+i%&wv$@ zr@r-{2fROtfHPZ$uENvnr5>c4!A89n_FA{xv?zV7T)6H-g$|>Em2VSMloG>fu)<_e znxhHzHmUNABoJ!^{2wnSYUF8@j>WqL^etM(TPNm3m!pLzGFHnmqh4eMgC1`LC^7o= zUiUjYnW}vk@X3aFxIY_T7}*dzB9vcD&8jNjIvZx<#D!b!cgQVSIzHOX+cNw9&5C>{ z@b-L7?gey{$>R~2!fUXE%P5|N9?4HxpwJToc}$!`(VBY?T8SW^iLt$(=>uL z0e&)Xr`;3KBrUFpv3|Pxr#JZ!Koex2wpK|muT`M7g=GQ9vCOn*l28rV7LdNFrt}9v8SdaxD&Meiql0^m<=RN- zC1}MHlu=`IRs-_aiz2YE>SEhmr@n1<6x>aXhh)n6r^!Jc-w(b`p^O!&`T3gXq>2-T zKFEytB7kytkKX^vOw7DA$wA(S;YH0B;o}|`eYuWX=AR83#|Z|W3mwe307Otn3%(^% z^#eHwkVIh|zi>C3v|tfc*iW!7=oT{f`fVv$<@&K8#+Y^-^x5iQW0=pFHijY>qs<3sa`FI*~*5&n=Bkm~i=po4O^ zow7lx7kETuALExiY5vkwBelc|(`bd9uR+P*eg4VYoI&647%#V_OYDr=f;joffyj{r zt(bTWDT`vkS0TS@&BNi1-ZT@3MmE>{$-P*?ZT@RNe63zYzlbc0osB_G>m}aFa5aO< zshvVMB&-)FY%wI69zILiE;i$=wVF#mr(rlo^Rq>X(n<>60u!Fne1VoMlu>dXe%%Eh zA^#oAhRXdE6KsZ>vcjSJ3YHz=y|7o#EnJ9^3sW-&EL9zcHYdem#t6Dk|QgT911gl@8e4*^K>+OBO9YDH)r6 zA>KqL;gaw9*)hCi6b6W?4wKF7`^TyA;Fhu&GC~+$mnUDV1*ww(pI67U`)+G;OgWyW z$bBR6Y3GV_irlP&anvWJ6M>s`0XEffj7l_-QEPpB?P{4>Tjb_uOd4W*tm&1%S86|TbtfxwIs3X&+oVnYb z2e-WgZ&Q2MS4gJxzo*ym>&deyep9Yp>t{2vB*LM6+D2Al-Kgi${t^9C`$Wh(B!)m^ zN$u=~q7~3(SaYgzMRDXPFy-L+-CX1$5wVh=B9>#uqpK(#Ui}5dQV#XlxnEp93?G_% zy;!oY)X#}tImy#MV&*0md~S)eb?8)_?G=HdZ5}+mezD)@82Ui2rDGDD)4} zvZaS1;Cc9AIe|ZL+y>~^4q2DG)6D%W(fe z)>Zd$k!fn97vcZ}pEzWA3B6AS3*Q)sgutub>7W=XdI7>(ADJ%$>0Vo|L!%DahO3<_Qv`>g;gA46mG%E_oC9wiDz=kBuqZsmBBmA zOB>QAjDx^Vqf6}J3+OkV1T>B2xErI>rju%-xt^NcSEm^oxzvpC6bT7WPen%{B;lU@jPod;n&hT9$Df5+$ zM%t9Ic1|?X=?`yM%03}k)=4V@##EQ3{}bijJOplSz7d9pd^@*_RA@UI)|irKNJKV< z%utSnkb?^{R(aO?Lqzo2GjG)eg?%%;*Ek|SZG;vgkF(c=l>rxcbnX$ivAfoAN+6=a zeq$>YXyoluLl;-Kksr=qLobmr?B0Rr!Z4;C7Wqr@{^g~$63c^ZbzM1~2og%=gCwZV z7K5TbeuxPgLmR?(c96_==R(`OB4yWZBsYxMH4!Hx;N~a#sMTd;fY~oC+Fu5elD#R; z5DhI}{%oS_7&|}ZGER+U_BFM6VN}`hbH*^Y?UtdU6?GmTEOND_8-w5Jf}wFVno03) zi@`IcNvFIZHQ1DEKG<2-UzH=*f5LN7lI)#66;RX=ncvzUg1sm03ofv0^q+yd0X5kU zvU<85O{rR(u+r>rIxSME=nylPxaG*LLk>9SCJx3V?k*_<{@`ft{-(1QAdHvOOFXPY zOpskN+?K_lra9cs70=em#uP~AL;GI{1=9{|iru}ZY?(H+H6l>-b~ZMfB5pP+nJP`Sl~Ln)ZBPZ;#& ziRX!=Uwl;U(@lKz9n6E{&&cSYc=ewMZjNDniz}ZkzYA=n zKD_cE1q#&Wzm(NbYW3J}KUrru{|hZ!?7|7_O@IyC12sm7exR-;=lt{%@MKAOkEwi$ z^%+Bt;pKs_rCSaZ?CTJ^o^XHYTwu(LE<3DU%GgEuEUew#>d-?72# zq=6_yO|gquTitS8cfDbjO{sLzGPckrwZGoCot0}JDw852QDhIxw39zmlqueHPOF<) zYeGBUt?u{r>YYJrZX;Mv&KS?{q2QMX3~A(2SIE1fFS%# z`F;uf%Wq0CUM`#Rw?24kbR=HkYtFQ&-4vG>d;H`3(hV7Och2i??Dc7Y8MzqNYMg{Z}+S-MRKoDT-1F@9+|$ z#`tj+$XZsh&L1lRdi^%y6r{OzVg9lwv3hlFd5etzASbDbhsQYH(JX5fUuk6gKKAaRij`>vWIke1|(_XG}2 zgSl#Y@#f(4Bv&7+_=_AFrW^}yX_fM_%psLz?|T-F*2_bj*UrjLezG*tU(;Ds$957F zRZR`e?D;_liLM05I!^nK)N?isl&frP(6irv7WUJ>NaKW~$ciFyQX^-6bb&HPNDwzIK>xf8Qm6|LFfAMoqAv11_7_a!K!G; zS0N)&M8GM8auV^+5}X)yC2spX_*D3W{rs6WG%am6Y)TDyGwLvHQHOj-csK#v!RAfJ z#oOauKM&^zrpDZN(ILy*Qc+xuB7>^;ZFO}1cWIYZZVs#qhlkS;d>Cj;idqG}A?$nZ#7}z;)qII6NCqLATvmmYA(WJpJhkbZl6JwuMwI2jJ%Za*gBSbhaGs62r z$h_tnBlz817vO{_FWA~&$-B)|Y`}UWiDe4haq5v3fWmE8A1CaV4yt+LvF5w>z8f0z zZ@GDTQ-BV5=q4i35R})TqS#xZIEy*+8OV+??<*No(9px8Dn6)_Q0KdA8~t{EIbEo& zuC8{grU9}sVl_k|=R}jO?*UsNjK1g|Cov)AG*u!>4+%Sr4EEvW4BD<#*~(9pK|BYC zVLyIY^JqoIkwiSoT^CoA=r&K=X;tN8SJd=C>X=K8lx+mU)lwMv@!VQ6r>QKTqkYr+ zs|>48Y6mI8m#KxaUX>; ziKXp7w6Y7*4mzA2&-EJq!hLNcL@>al#{X*(5k#@nIM=$<64e7a47Lf1!-!b4qA`!? zlVu)GM2c^wok#qUE)&tQ3w!c ztNEQ|`O6Pa?dt8@X~t5aoTIhXi$x4)6ks{mXLNPWME}hCf3_b{3$oC`Y|vr@!T&;xmLJ_o68XOpZ1<4hCU@Zlm>)Yd19oyuMY+yoL%n z4B2b}P)8y77|(a)Uc#Pis^hc5GzcU@AHof;@MTS>q#fT;inh7c_1*M&>#JOM)5Lfz z2#OY*sb__lrI<@+ttdaA#s`-pPT}y7VE8_!u*2uPJj!{|B(0VYx^j#U#raW87wp=K zqP=^|os7kj7G*%Cn&pWGQ_7QYMLCKK|IRJYJ}Er+TBJJT+@dZz`>g*YO5srl%WTWy zVP{K?{a=KGZ_0Mb0*wexW0ad|ZhU-`-4_g$R#sp^1B2e04;9}`KXf#T5{~T(EC7#d zrNzsd%Gy;OQSeaMkvh3%ukpVtP|tk4 z8`o_&KSU50)4jL`+j&l#`ysyH+@*Cb=k2xVb)~}J{sAg-Z<$tiHF7lsOZ;u1CNopp zi&j09mf-6s$GG3aV7x@X2DoCd8j-YhZcP z0skuaZlG?o#z$zhFe@@FUFpI7QyK+8HBP{E8`mc_a#2V(uyPYE-u#xm7T@QmlJB$I zyr1$Rav5)GezxScDCb99EW%uDIOjE2uK6ViwZ_-?BW6uFF3Ku<^gutnbv&u-Eg{1)`R=YhG> zCP@U=EySm1PROX?)M93>78WZhN-|M)T%CQEXNXc-EH_Ix7O)@Ss@S-5+IM!^G$`!y zrqoBZq&!kAC43=P$ED$_@34t>R)$KsDBv|MWOIzs`3(A#tK*xdZt!R)y*)P5^_@?unEJ}dr^2Qwq?{5*b){%5koI#*1WkPU=sa%+?Tg( zJ@#4%d@`QS6I1KqzjHcCq^3*d%eDE`s>>F zc>D9B@Jm_hr$PuhuZiul_3~!M`clQ|lrodTh5}R2_20@roTkBoFGqzJzVL}w)>%!% zqle~K+4B9^gSj?UJVLGtqAd&kiF`5DI z134Op>jz2kCkzl7Hi#hLS2Mk4oV2av4@FS%ig0^ww^h&I?)Ac5zV#^ky3V_#3!Ar# z&YfSdjy;SMSCK>KXZxunh@;NQ_bg=1W+!3A&^z4K_3~ih#9<<)>*bX`AhK!yt>?{u zjZY*bG1X8H`Gfj2ajHD@WyEh0841ti3s%blro8ZIS?BDt!8$%iU8sU@Abw%phafTy z^p8Z7s5f(;shOMHuE^$@G>KBr0X)2?TGl=ZwSiRTz-IeHru@Fw-&Y79-Si`3f={7L z&pFy0DP0~63SidV6uh}u1oeHH*jC-Ex$Nid{xhPtBv6owt#d%oUgBYIK?AvDoa0M2 zBi@5F><|Jt+x{JUrM2_Fbi5)!P)M@Q)!4r`>9)9H>OP3b>or=^m!keYdUHK=4zaz9 z0K*#np2wA#1Ht#dSUul;V2kVFs(9_qqrkg7j)B0%Gm)nN`cRx7x@MvroPUqe3VE9k zydQx4*7iKO?kpGvrHL}9fDCG;F0%IzGQJcu*cCFk8ZjzHu6O4FH$s<(-#+QQGz15< zyk>sm(<8)#jP`Nbuh~(b*->(qm$oI-R4+0I-3|U&HF7-&gHM)0{#zrR%d*Dr-Xx`+ zeT&)3p+jxcrn2({$nY6%(`I1Plt5Qau)Ymiycc6jscQpdDz`uz%h__h6^`I>6c0EW zDUL#48vBAeK|wS01O9R9{B@u_AD5wbm#Tf^(0177@}(<4#c2w1xR3GIeQ|iHDqBQ5 z)-kBnw`a7uGDE&nzfX`hpEDcy`;vIQAdOkcPA^K*mdvs4wG{?!Wg^4h&58~lL_&&P z*1CGZh42$5$1t}qper+kJ(AcW;%#V}Km&!BruFy+bTazoP1^~f1q)MshZx5KKo*>N zr8=Q(Oi$C?&t!~x4sZ*hl6k2^BM}8_v9MbN+HXI4yXiaqXi+uW$n9Wk@*g$-zM^|q zmw&hrIxGF79Q(nyot6-wK*46jZAmkVmXDB+Pm*nzYRpDpeI41>$*-zB!B^?@M`&MT z#moBfs7DHf>mSX!0rG&tb2YGkKHgiFmLs`!n zY}vm7GODbv8Ew0fu&b+)o}5Mu!%2le#GFNW@p307_YcRf9Lob476KUYY&e;u)zR=W z{obcWrjS`blKM%ec$sXV4E0Gjw&H2fm@^nFEKn~Xl@s%iRl@{;V#VH}8fJh4sRt7a z$|viUDg(I}A(F+OcPQP%X>!+9847V}%KL3onJCYN8qocbf{7@?t@G^y-}PA0S+n@Ga*3mn$1UB(6Rtl z`OW4e@zZS;mDvZ@Wf~YbWDAJL6BL@N_}Q?o%gpF_of%Z%U-a3jvtnBIiKm2ehCZvn zetH~I$O|w3xq%o`&97FV<5yU(N$6#u^)@sYjTXd^Cy5}9BanUTp@><6Nnw>h0@AeR zEy0`6o^Oqak4_+yWTt|)4*~e>OMj(tHl1h;g%)O>1`>uqtu)@i#EG*>V!LkoS9UJ; zb+qW%uBUcBVHsMKv4kHqnLLW+RA23{Xc!iTK^qV;MfrGOY+=QNQN3V_kg{IoL?4)) zl`g#(?Q5=frK48tlL$$Qw3%$b)MYI@DaE)*d8#8^J8B8EDROa z;KiE?E+aH>fVd0=e8sWeAv9e2)b@qe@bz7jjDa+s+Wy<09OLcN%%b0fzLDv~ZnXyI zPpPBw!I8E63!#OLJs3&S6j_qKE*vN+Dmcht6Ui9%X~-#R6n=&%f@&>ej4D(cUlJlp zvPqw*9=&%Zt-D(p@$#-zanS*D%9cvGP2l$mtpEd0AWFu;kiy|JHM+Q&`v#1C*#6YX zNz(knj`At>nLGGl>hFf=9Ay^Wv#;jrLY9?cdUohfjCsCT=nKWXbGk^{?L-EX!9MPT z^um5B%i{R3A;LtLT86ju)3_{0`X%P{thF5t(E)o!;J$dU%$!BP(vwugO)$Zu1V_n} z6q!r`?A^S_yOA@8wwnC3r>?@S%-&vKtb<5IL^#C$h>*-#X82hl;TX1Q$b>2*F;$i> z;;#p^bEi*!&zl36*7v!aYD9g|9E@bsEKqm^?n>eZmSqA`Xz^D`mGwHvz$*+I3W%ASiE3tbD zzo;|CP;~X4Ms1~)%cSV(K8H{fA=SVq4_}nGazGe_nVTCw_@*J`Z-Fkz8KN#WH`RWT z+o(PiDrpht3&Rb$fAG`&+X6c2?>rFfk9f&6N>dNY>(Vpm5E#OM^h4^-8J~@z^=%^Q z`=2LMK)5vo4m45LJ!d$Iv<4*khb_hpOv9@gvL$T0fWdKN#H|3ry!O%%SuWw1RxB26 z^za6!5AcfAGs|t=4{;b!u0jwR{@$}5^5-lnW7P4?UIUqMw&Ij0Ol=bapLF@I_9kMGAj$i z?m9~$utMY4^#0r@RC zsw90W;j$^`gHfXC$%8CdUG|@H?nmVs-iKPdZ}gTaR54H6hz6yA^Orh$G-E30<{_M< zLa87kR)$8bARx)U^1XufSsG+}hk;gjF@}$aP(C+ZX%m~YoeSp-QmCIw_`=y*I_!lviNW5bL;GK4vka90YzDihDv}!C-Z_xn|CFRFjF66)moM%Jo8xb^O+BAe0~K1t{?< z{^&9d!zF~VFVboLp;q{^xUKhykh~5h92E61@tnj1+-&E~&cvX;TN|}Rh;Zcb$g(Nv zQX;3&h9c<#OIe^*s0~>*lf~x92SfwD3a4XD< z2mCqZ7g#(?HFf0F!y_Y?ljK*4B_R@Y8sG5i~EY>0K*`^*Ym- zl(9Yez)}8`%<#XXDYFa#Q#2xI6DI4HoOS;$U$8+h5*IN6oqa!EYQlGMbW}f@i?SFm zSZ4V&zl%ww9#a_`kD&+$jKRpt3l)y4pe`56y9)-Rd@9+YM_>z47o9U9DH+y`-uT<@ zyY7OE87LngJ_vC!9D$4yxjmTeDo--B+QLa|&%(uFZ?#aX)(hQ`IQ_6tg)iz=Y4&-6PGpVa;)6F8|&MSLK|F_lj?$ z^5XhrVWqgYyZ5QL`zCCw7pd2;#NUMHuT&{XO1=#mX5M4sGxZa5H&}Ge`3cr>#`XtV ziHk!&6S3ScG}Nk0#fg2BpkrX?AeUi`ON1XSIqF5l(IuBv6gZvwQs~n1$hmnm$5?So+9qUyf^!v3;#(7(Zg(uTX{TN|zr9y;JC<0s5)t zG^dD-tzLSV6!dxxs1GI2UA>mKLwy8OlI*BMFzE#7uFgqGZ?oHz`^5tLsCXTDO3XLXA>swHMWhf3@cz>mRcDtd9IIV|In>%6Q->DYh*w z0iEmWh0QKoSgj@;-}zvWB)(Ns7!409HqT7sbRRcj(99)Bi#8V?;?T-+P<6=vgiatK z`yu#MT&O6lq@Dn}zXS(>Y1#8_obAaRq79%K(87`=`etT?QX7fGD{$68{E{AEL8snA znfpf1-)I@C?il-)Q}l)aM#lM`C(*}gyLU?NrrNIS66SJmz zRNfZz-F!l%ZS%!}4ot{ZBN2YWIqvuah`4%Nv--tMapgW|7C_ms61O3Q&-z&7PeX~V z&kJUOwlQ4G?#fm=*|N>jTg~Jor{L?o0*f?q^*_yp8zPh6z~xN&E{2+Gf3n4Y1^$vQ zns(az;bujH&M@uqDZK!#?!S)A^?2%>W-NZ$p#?^)v>rfjRi743Fba-QTs@^ zCY**1-^e>wO-Ew|W`PvthVuG5IDpn?e21Uz8T}^ey2eS^iZc~o^g|LWGMeO}&0p-e zW-<@h6-!Xt#;?bF39WaX&*fRH;Y*&W-FsBA1o6?{K%Jn>Ts5tBNbQZ!e;5}(nh}3i zF@MZLW#Vwf5KdDthQ22Y2^HDTNlUsO@xmDq*B5fQF{83 zzqphyikOQ_-!6q3K{M$2s`{yCoSRM{+E43AKew*+s`yU&F!Uz)ua2rGC2Wt0yhRVE zB2p)yF5UR=<4GGd>zab6KG54s#Z-!pye7;4C}wh1Dn$+9nAUwXKtf}p_~no-zwme% z>vPhrC7%{R{adKQ;lXHm*gnmP4WE&TMO)@NnTN+hJfp69l0j8F7RxUKTfrW|5J0=< z(N45yZbO6zRg#kUZ!OmoifFk(|b{)X$;b%AoIjp49g(b;|`cgIABv^G#5 z|0!VWjh6Drx4G{Xb~P=10Q8&0J7+YFAXSd6#g6sp&XD(1$)czVRk;-C8!`nh1h!Um9$qZKLrhu{fg_yC0jNKN-NNXCa8sf<6=QHS)W+`ne=Y z3J%9zFrqpAU9g*!KUx!ONvSNbHE=H+%%q__77Uxdw0FcX_NnmqE!f|c%y~+q04)d? zYWroc=N>C>jhmjo}tpyrG0^$jYwW>C@MYD^olANOWFeh7R>=q=5{$l{gQo<3I*qD=?Sm6{$8b zS7fQ&HUqw~70KMW{wWmgsFOyuoBq97D+%}!96W>4+|ir|Fbc$pBz~Z4OKE$=G1&!u z7ma(?vrfcDOJiVR-xWbbo1CR9f|Sna8Cdtn#dS1z$kr|*a7NZM78sHVP)^}W_-G0;PyVi>z zjp4g5QV-_!oac4P?n}|ONqD+`hGY9}lsO0NI^DX^Ccd$NOwxn8C0^5ts0TJ@9#cl> z&KvDs0b?uk3aHKP``()w&n!RKlyrmdC++W%==S7y*nhIymkkIh+e>0)s~>(N#>-Yy zs!!%L1-;7(;lN`dW6;74#rY+kH2kjZZ&^3-uj{2J?Qfw=?ZJ)@?H%tKbdAQQ`*nM? zL~#LbcB{0Pn^Lc|8$E5ztLq;tIMT?7aJO&)wDEN6C*NXz+&IcV(!oa{4)U0y8I1UY z6)<_ttR*`7N!!Nz+Mj9L}Z@N>AD75kn= zTW3_$Sz8Ka%e7qQK3(R_->bKpFZ24clx^rE+-Rt>gIQ8 zUa~wrI~(5Q&_EjLU}Tr-dk>;vKGQ_HIJ|+kJ>>!BaCf0%KJ#`zVdEQ;9a*xzm~i=Z zqqJz=q74zo>Ppqzq=-rFih;aj@whM1DY@%JscqFj*B-UNoO|PLpBmpcwP%!i?#A_C zs!zOCX};vCr_Va&OP~+c$sJF!bCu0}qICBBnU?)*GY0n7vwYKfCx*5C93@MFre9;b zxjz3PI^Y<7qg~|8C3{Ua7|{FHOq4x7nrM$H%P*8Pj&qy=Lc$$UzPb)vO3gJ^^bJmHK!p@i(@$%W*nat&_p{~i z8wPrHy#>gRzqxO1QTLbyGbLOTi$#7==e>Cl@k~F|V#X3|C3pX;6P+zCUh^Y-UjsKM zTHLDrKVsEqE~|N?&>K4hv1;IT|BEBaFO;C&;Wdfv?7Wj<&tNp?{?;WBFk)J-^6`j& z2O8BL`0-K%tAp)rjQ1{>hf5ud8oMj!`FiB{(|ay&*4JL-Yefi7T0{y1Eo~$ND+7ep zd09g~F8i8jFCtetS}F=$MhAplP=_flndQ+BP&A#h``idFNovrNh1=)u(M`rJm&n=X-E}_?{HWA-h9pgmez-hNK(Q<=2S6Vc52`*wlX2T@;_S|BJBxOPOH1&-r`S`%`*lPD(y{bXT>qMoEigjI8;- zf&*Uph94JL^V2M|mB5T1#oOSQxjD`hw=A_#_400}MBUxFz&|+AEYXJhaQ-Dtlqy#& zPZU=;ij-b>EH8Yh_z}sdI46!VA|IO?+~ICKh)Pl zvuBjGOWF#t>e1|Lg8K|?|JtukUzq4^XDu*mtDV@Z=eEhd>xB9~*?8xZpICBPd6?a5 zUiK5sbI|@@FM#l~q%QIOzH^O|S7V)rp03)PqHa!;yatOBQ46KDM$I+q>C>1O_o|ew zF(73nv**b5u&fFb+MEFJ5p1Tzx@8mGWtkYx@9ZVMEtU-|GC#PgP4n1nP8LVh`KAFu z?o?n1%>lNx`*ob%G2v5(QmuK{`0ktr>(wXTS=W44qDdjRW`3h1Ge_ z4)pRArTWuywZmVkJSsPt>VC8&ihs&!n!c6{R2f+m;1cMt?_CadTebf33yYe_@xA}a z-lTq#QXt|1o4NA-7M0aVrmwGN2+Ye{!Yd%u{R<0XIw$-r)%5PcyVoZ&E)y*{Ip_7-J%2bHM_1gWWI)UoWC|qIa@0|9q z@M6azIw+BL9Z!{+N?zq0v*UL@4M&eWE_^7n%h06OL}w%Ynl31u%y!ttgyiT%v)H() zhlE|Ji(~GP+q6sP%cFL7u`vG{rS(yRl@362ALMP=4u_LDPe0%k<{4&%J`K?Zk(T?lcFhk%Fv||{6b@tyw>5! zoT%k7Rex{=m)Eslx2H!UMue4_@|?27Gpn!m0Y zQb(rEi9=-DiUELSwMCnXUl%{awK6cUM-zEHD`t*OQ82Uc5g)FUb@%udo+WD>2&J7{ zxX%83$30%Zd@+CD-O#{o?)!vYZwfQwVNu*ee3zQRd;CL&(u#zX3eUUpx4JGD``J6b z7)Bv+@@1&w4q5dN*#d+q9@CoIuv+vIUFzk)4lGCGr{Ln5r;-zpAH1}~7lyUr1NPBK zc(k^h;s9%uL^VC{QoKJpMWq3>nbe0883Sd1>~mzIrGLG-g`v;f+gF42-_;@-F7wA1 zDIYuH!VaSFgw>W=Pr2;1kGY@guoiZ-1|r{@-^~v$hwtw6U&>i{n|BK8g6Vr2yD~cl zcWBG$!a39+%o@rwCI484IWF!?>j zD1ZEx3H%*AwYU#$G9OUSyXal^MSu+*n(;?XnoXD=KDM&LYIRKH6}6qe@j^KnkVzJc zPsl5Z4InkyHTXu$DQWBEhzPJHgbb?kcDVz{p$s zWeY4vU4!6~8DR6aofPzGJ~KZh6j166IRe!Mc`X!OyQ zxN@CR4F_1)!f@h7%1CUSHW1phP>INst}EZ}T}rGGfybMb{wavx zN5x!BsQk;AI^A;Lr_KzX?WTIB7!P3`TO`dD31&iVR6tkiFFm3p@56qftD_2OLuI`J z$grH*!P@M}-uURx|BXRt5#Krx2SN$~81ORr%z7)~YX$zt@AD)7bcq30_Jsds+&{hi zQGm4F0IU3uU-3d33h#5i!2f5t`K4%3WED3&hDP3cDEH7+C5T@sk!Q)%kzpB3(Oo9i zFpdDAiF6&jNsA6?u^gA9(rva>P&zl3tm^XHhJwXDbhghdd?nR4arBm zLZ-Z2ZX@2;sbdjHUQ7uc@(EN6fDRc7X$f?bExl_!0jeID160iN{pno_h`#1Vzyygf zX3U*RlT-%jYHsxBZ`ik3bevRGecvcdZhw+%B`wC4sZ*D zr}5|S^IPo-nzsTUCS)bP_I+c(S{q6Ar`SIcjpE16yvVV-M6j!7fyx)4oLoM6EXeoE`SOXc~}usfa~U3#>}y~2V?4L05x1BLJsqQ zi^EcbK#a7kE525iA6m_j5GQ`)V`8aC1W0MSl8TI}O+T4i<18phR>04e9_uxL>2)1+J!FSlQO4oiDhuOgq}n#)CMhrdT|WT zA3b8k-X+!wIrqmQr&UHiu(3_9zz}0eIanyKDeRjoLS7RIF{%}=yK}HjQG;YQZxJ?5 z8$1%+7ac55yz+_R=}2pvCq_o8YM~nQM5Ja}BEbbh2DcxTyX`>Q?S8he?zAhTGUB> zunzqM?C7A3fV`hB$l>#7)+?nQ4LO@KZjQ||WE=NpJ9gE?+AXyEwAjV!>>+o0DO8Q1 zs3bgT?df##OPW&O`|s+s(i&BBQl{KU3uWBCl;fStkW7m8FH%ouYHzM0?G+s-4jXfk zrN-^Woa#eb{X&ZE-+^Y2ikfs~k}=5BX%-0opPga351t&x*ry0S}65Kr+=^Il z7)eunm;v|_$fjkuT`vA974+W)+&2l6MqB$bl)_%Aw;v4`M9dwk1PW!+UA9#jjCO30W%IpIk67)3IMxA1L=~5oKvv&aCco~;5w*}|)KYcV zvgzLe)jdn6z$rMa&=Z7^G>XRb55q_A(XTTpz)BH&;?yo08MA|?9=vP2U(I~g8Mp5^ z|9Z;u2$hEl^xpgJ$~lZ^+_8J&i&f*uXuE7KU$2aZODj@<@%<19!JvULaRP9nfY%c& zpg-RS-bq;SNmM|HWZ5bh^V!0?OU;;X=W*~Y(p0lCd+amPrsKP*qhO$#JEH%f?x*fF zTiD=Hh#^ghB2O_-NC!YxkmXN3(=Lwr;U@^ytSWc0fOUHsN->d=rwg^;{!g+~7bEJc zKb+5YNBsRg!n?hx##8|(h0iaY{UGHIA=d0J;j%_u>IFHH#1jPXe|w3*=;Ks@f&E9V ztj6d<_Up_V$?oqvC>0M5)?kqEdcaq+zc^EqnET~y$M?x4qe58s>8|d4qjgGALhd=` znvX01%dw0;&NWDYUEyj{*TZvZDJ|(fTuB{8sY<9o>ibPUTcicr-Cjj5Ak~Y{-Mh|L za@aV~s_zWYVoQ@ODIHvU$)Tz(;^HR!#t}}J1 z`~9=vl;f^V0ICnjyd()UZ1^ie^mBPX=rN0l1h>ItwVSSu3mCO$J(sz8Kk|tgG#*0< zNJaO5_P*B}VtJyh^3!XzV@$)g1cXqDKc4+Y_?L&Fg&cI#*K}At*VoH}v;MWo%08Y6 zM)v9sn?9%2qTeuX=Y5KRjSp;83IZ@`BHUiS3iIDkK8r^Weo4WJCi)2IHvYt%b4fb* zU>wUG(s6UHdtOf$3=vhuPx_?}6zdKg+ZETmM+-?Imq`>;MAqKdR^Ztw_FzIv~`mK+gnVGqv zs6sE0KqjJVmAf>-75)eLbuirZ}1BRNx?_nTlA%KQU}HmGisfGo4&3>;<4r?%CmQ!3DY#i%=Gr>DfgHHEO(UI zJNbQ+*33}VK~E1YXUP;O(EAO7g8>y1^ATC?x3%#B@6H$M=wV*TQgzC&5t2hSQ0-|X zkoF09&F?b@Aw3ctJX@7k9a^2Z9s?0AaTj5+-MKVg2ikk;&2a?IdoS9|%JRha%jlwR zVOX9W2!&`aU9%t{#w&%0#k*(+NZo&9OQQ8q%N!I@{NR`zDx<5E!Dnv4P_0y@b2gQx zaA2Z=qfi@b0ifyhv|-(e@hF`7qR!?nduz;MX(leXp}lZt$70V0lY^*S78rDrJR2m{ zhsA0~7%`E{Fr4sLuz!zSr9~>z+b^j&%#4(b=}RWy_~i^N%Z0HJXR?9@K$#mOah&!Z zrNfItr(Ho`h1xA{gMSTfUd`NOEZvHGv@R=tSi0xgRJ#3{lMnk@w=3BdX*G*7IlCB- z<8>K`;_$Jqh=i~UG*T3yDPbJ?WhmuhS7wRUu{&MsC=WO63I5xFfrmCcTm&O7D%jN3 zg9*z%1KwrKQ`~;Pj|EMYyXJlKEHXaq&9Bgw_E1 zC{wv1)%u)Ly14Ia&elv|ju*-6^H8tH-C&yWAl>Xy0g^uD@y^th#Mx>fadE{e?hDcxRG~@AAyL0!!XFgMh9fL+~V0S}{Rm|?56O?GPY+l%zhJrR3S(E|4KOe~xc8CQ7 z;Hy$^rKag!8SBLT4|=k7Kz?a;b;YiF{19Dgx0K_eA$g(zC2zN?Nr)2WQy45nGqXd>sT= zgj$QlQ!O@K21xZ_Bi=W+dh&juJ%8a(LD`rs_*ei12mM6ZVT_c2jC2js)P)OEq)fIb zuaJy$7rZqmcxG9S{>7okf+pv?GzLiVQ>uKNJ8L%^(N81DyzW(Y=2Pq|9+zFV$!$`; z)$q?QG`l7Y*8u(I`W;%JUw~GYPTwCZa3k)5Q}!oKCPt*q5Vvq=-x0KM%j1;~nS;8x z?wx_D`fWQ4?vEXNUmwV~Ni}lIO}|`x9`dNv%B)iA%(PdeShmw8j|dmPsFsAszVdi- zDUl4YoP0pmRLKAKL;QX$2$qshVai2WSR4Xx{=VlguAQ;7dkS-s-8-}bLzCE(9ufYZKY82S?};!blezNtx1`9jqfJJK7p3G97ugLh1P{=i;x z_YR?J8jLE3OAs2ZX_Eo9nWBSQB>JMiDX0XB1Ytp|80aB2-w4Dwz(X3;?e0iQhh1fo zlW_4omUx7^0EQ6jenTAQ*aOj%PRSm_Ec`Z8en_hKeXz_(L5D%vZI+b*Ti&9`geUiLJG#m)1F_o?r65cy3D z>$FiAVDqifgMeLo))jPH!hdnHao`dxs_MGMxdgJyk5Vcp(a4`Y7r)5pjuRy#DH$hD!Uu5>WV(;@?fh{cd;Q%px7*F*!_1GsBg_V6Q zR~kL5tk#aAi-GUj%73Y`f?Mp}o3_bX13p?;;$_yd@~x$DjLs-hq23DbcRA3->(beG zjlTQvR~5?aclY&4iv7l;D3g~)XNZ*+2~u0dK7mfx^3ev1hEAtcS4eDw5L%w~NFqag ztUl{$Pz(Hsjh}TL0GlDja`Yf4vNGKtb;7;@>=aM)H_FK2B{&$gG5I9PT=H{Bg}GGA z^u6Ey~$lT>NPhW2gsWy9e(WhoPAVAsN3lgK2L38AY6WxzbIGlaHByKU#?kEiDhTlM*edodQSS0grV zCwRzZl#>z5f-G+2sE@iYw;DEAC@=s?H=--TvYnqyy1Y_>apr4hd*?ej1uozCL6#)B4moUgcr@`;G;8aLzgj0Cfrw#fC zwCpu%`fjw8&Vf`c}{U_YU2xx60$ht567u zvru5MEc?bu(XNxzWFZ&$9}jWWN$4<& zRY^ij(V!RRX;EaZBC|8@h1yKj-21;o2``S*P(=6Viyc%^U<*{7zqGje$+NT^|B@zK9-9j)0>^5Vn%P%z3?+Ev7nYISFW|K zxe`mZvJ`E~lKltkN=&ECjhFagx*zYKH-V7pT9GIa7RZ@;88{p0*$sPRxRBS!el@GN zU#HZ#Vw=t5nS0+U_O+;M1N~3vvHnU5K<)mg5xBEWXj7NCGdav&%fcMi>_Ic_nCt&s zbkB zFI7?zKKZBmKm|ln=(1%duw0W20bszO!-mhyC9lY;@u3FDe^;hW-EvduoPC*5+N(+J zGN72D#J3#1IS}SRf8j=3Sw7*njr0$?IIyVbJ7c{1%pIv+BJyg2osS%IDr>v>O(Z@a zG=)eCpJZh_4{1<(U*)XFZg-dv7y9R-!WD`JdLlK&#{3&l>;DE6o2xdgMP5F=M!p{J zPjqZ}*dh9UQ~|ddyB`?*mht~ZoXhGMNi{mM1p1BN&ECTM;;uc(gJvvHzWLwf=D;DMD8{gR$rIz@463u^rU~1-jC2JJ*-b-}+xu>HKpF zN@3x~U(rJ&)3x28AreUnN?O0{Wv!Z1G52^0?3OoQ5d`h0!yGpU#z4TGoEf* zF5;Vf2dkEb0)5V74dWreNjY4^augZ0TV;Cj(D(h7a~5h>s#u2h)aVx6;!J@#Wvaj> z@L50)gc##Wy{jJcYOazUcFuuomVe0OREgIC9wtSC*nk!Kj-hT;(bhzxZpEtar62d5Aa1X|Q{IwRv6Vnc(mJlPD*K zh4nVOF1A(WJx*WVIG;W@1(%Z(Ixz*iBl(g*!fP%%78Auc^`D+P`RNSmUsS%}!Bj^CFLH?~V+i(D6z~wP{m3dYICLes+}X5u{By4uoMk^l@X#VmRnhitxXm}YUjAq=`_6LSr3EdVo&5L^-cYM_AG z7q5IpBVHB%O6PwCZa!gc`xnEhO+D!=oFJ}(Xj5lO#G)p04-SToT= zX#DnFg6bFLsLuUo4V~fubB7lXzOSYOuPCyhbaKmhSq)?Zkb#7B%1J-A0fUNHII`nO znY3Yb^7!5p*aD(KHS1Zi6kYp-t4UpX9ng$3->l&GZynbpEv`$ahNLiHT~t1I^jO7E zky_qFN%XozX)EYg`2EzU#jZNhXLqW{wF=I)o!xtBxgsXu6hoCmewA)FUIiWBo^A0G zNU_Gf=;op=j={QX%cd46!m|wOQ>#n1=UM2jgGKB0ysq*XbNI@@=5KLZo<~|2dn--lcqPXc1t0CRjMU%MqgXI(}or z!T1c=Qby4)wfT{x#?BK8iHp{n#JVlPXC9S<7`19rqne2{bet#UH}pKgIgzYV_|2M4 zV^2jacl`Mlrpp`%TwuAdr@;>VkdwDTjTfsw2b3e$jXaLnjfK#-B(M`I9+Yeblutwj)iGme{w6VDwjaJBas;=^f=eJ z)~zf=zx1e05KxkdXRjk?s_e>6S4{q~`3AdtJsafz>jl_~6jb{=4i78fb6~QQLLB$D ze@G^bBxYlLc;?SL=Bqz_YSdTNxD%#3*RjT_APEv4L1jIcOc|5Lj^34%;gFjp>1;nG z15UF!b!zOkM=-?#&vJBAKS(^IEmNotcqT5jPJ7zudg`+1i~K)sa{{koQ|}W29{Qv> zZm{#UWII$Of+Jqq8wO*>$zbj5>-#00mpq=v z^)$8}*HV~P0~g&Jm!qNN@9TiVJucut0F3(eOAkrHXaEqf;n%nfL%hiEzL6#b#cVOe z1O`w_e)&yL7KSRv#Lq9H4rmuQ@u4X>(F%&swXZwt4D^rU@ z$8!p`w|0xYp-Mb%X#kGncIfB!mbNwd{p1lj_5iij=d_+!hhc`T%By5+OY+rA8m|#G034yjK5a4 zGT{=LXAC}G1m2;a`G<9$^O1a}x#K*U>1oNG|B8D7lO$Ei{+k{NHRi0Q#n19n)l|ZZ zBEWc%yLy~$>@Uatod;@Rr7QhvmkNmaE@&dEP-<0Zu<2G|S&7$G{~&EV)&G{m;c5~#XZQ7OyKihF`th0Z2Pfzld_() zhCy@uUaw4GdHWrIljMrciE*V9034>rrt);OWG8zn_s_ z|8AZ2A~4F#}4c(!n%PtvH36Xo0;cUB(DqO2phm`PJ`UF(sr z=WG~Q-bG^~q*Rw&?eu;r0Lfgw%cRif!@po#f<+ny%tK0ejoFGM0oWsA|cn!Q_f zG>V>*mQ^#k`+v1v^;eY5+g}b>#%H@y4;=WxzFGgrrFt_gzl-wS7&9?bwNWvYBsv^b*mR(Kk4g?zKm_8BvAZn-Sob0_p{?nba``JS!(^5h7MKK zOS}&5ghU9?nO-VW@%v<_%d`xu|DoWKB|=-Rl=@ApQ!b0ym@2cj5PuAm7O51=jO?U$ zFe)=!0&T6ZNEf=I{;rHZqg`vh40>SL^9w|U*nk6&JLS~YJf82%vOMGZ@XRSc+SFyp z)E3(cL}IoElR}bt%86FZk>#9y!NiNA>D6OudjcGhX=$WGpr2TOengEd>*s>qb7m__ z_yiA?6noHHJGKZl$x(W}@J+6?R8b*mM8n=+UZ?vYS`@Es-lvjG!-CBcd9Lv!7<(wD zI8ObI%epfVclJ}1%d&=RtbLXBy$1a-0s1^UY`<^4Y|7v1kt)i|1d@RcN8|>+fB!;{ zX94{K-99vKq+4{ z8^}1xOkyZeQl0DB#6tq>&Dcm)zcTD;KFo80Zc&Db1kTrdnGA8jhOKFdd){y-JjSQF zl+5X?T%ri0+v9o=-ST;N{sq;_e7{N7rN2*o-M)7(&bKYtK#JIbt2 zGE1tYW1ZyG?}~p#0@GoGf2KTIFf*bv^RzyJH(mDTrWJu7qLmrMjy~&C^*?8A3vA8R zZ0NEYTISUK>zZL-_4P%3HzbA##7}I13*vPUa=iu{m`0Ry3ZvYTq+(qC+8vud3MWBE zMa>*f;)n}>Lv;P7cWzz>N4FshQo_caOGxjWoe10S`%S&u2E{u-q{#a5^^HddC~Y!K z8Y%KymBG%ZbPEVQ9ug^`5Y*|f*?<>OkSGpBu_(vQ7X^i{`s~lXQqH>B+KszUhPR>g zsb}15>r_(9qJavRv;E0W%M9t__{T$%DI>Odh3^|>$At2R=anP3zyUQ)0{Hrgo-{1> z)o?9g3MwpC(v&i6eG-#MtMEP?c^G)LxjUi?^76~zAdztA9!p+{SK^mM8}>5{7GK_x^3Vi8b z`cmffg`ZoEbY|&gKemp$;utzqs^`N%^|ao2Oz9858qt^eD5O0HV&avwnpAf_Fk7@t zH-Ty@?#sY3M52DBANm#LLtI#EY6urEaCl+JH2!D;)#qGHy3q8?a)7`9HY*Ty!{=~R z@FrPw!fW<`-=^a2wmhrc;iswPG{50t?G-eua`))@V0Hv6`F-^D8a!rYG;yJI^U_k% zd|cT+E<8df-iGf=rNrZlkXg6;xW2m?@m?CzkF^4R}T&_@!Tijj$0!C7lSM z`0^X2WdT0QipNfNwo5Eg!dad7*!9W<;10LS?noGe_nQe0a6VM7Vf)e2e--wGMRIP$ zr-RixR=VDvlB9zsu+EQt^h=m$@f)3~U*<82C$ ztfQHiyCMyk0JuSU^L-G-C-Mh{qP2Q`tc9ET+`9DI#FqDefy8qrQI;&64=>;pKnR3` z#6Up*!Ib>?DBtl1cJ`#0y!++jVg<< z#0PrYXOLGqjNnxejn1?$=-_0%(+DGQWgOoth1a!1P8Z*R*1IOI@H4*gBw^=2ety~%XO5hdZ@N_769N493jz;4K@i+3uK?xRNdsO`Z28KM-umtu!b3{ z5xHUp2Xb->F|4v3AO_0M`;c)b6CX_iZ>`2MmFT=qgp07_6XROOdUf!^-ghA*(0{Rb z`nAIa#yB<`%AZLSpXap*)_K}Go>=k3hzdqngP4PLm~dcmRhmYnr0BZ&X$|A=Wm}ns zTb*G#OwWB>&G|DKM*F+(A8(AkFW+lwu4FS{BJS~BELUqt*ifd$$54dJCf}6T=NzR{ z^?h0)|GO;@Zae#$iCliz|Gs+(2=CYpM0?qybfv#T;|rd%x$2~(6qddhK)F4%ji=yJ z6N3A)g2?okto4QZ-;jPKm0xdpb!QzOckKpgrL<<0KaF57d%{SBE$-OoP}5t^$WX7a=wc?( zA8|L~H&iA#L+A?R%v zLIqs7d2b|c6_1?wp>|?nDLJ!RBW8TbS{^p85JkbsmFPONB#2wwVnLMk2g*+JC zDSv0UzN(enRDyFm=Pxce*>4WcD|fUeh9P8w`%1>ExDXBZ^5Y0;mh`dRjAuRC!^O?? z{o=;}4n6=fTOqR(_l$HPXl%GM91PjhKZEW_B@#aLA*7mBcqqLWXiSQ=rHXU%3K)g zFh+htw9-hU{*1#pEnVT^X|n9^-?CS43BWpZr>!S{8z)^AEd2fx3HlE8xqPsf7)JeC zhf+F6s(+azHvGgK7XKex{jG<`y63HSlh#pLFj{FXJ{NpCen*BUUfpc?-aPNF&rHR)JRumWAppU@UzRT1)SvFDmMS1BH)a9cyu zbke=lI@xKmD?2$zWXx%jG5w3})XJl_Vjjsym*(fCLGU$ zm8$@LTSCD*WX=3|&!LGf&-~J~lyuzf;rk5IgalFCZ^kvDbvv(5s@@+2O%84SL#(<| zu*=jAHGqaK3;LE9g*o{D4>MWde_BD`kmwq6A|_MpITgdr(TynUUpVDS%0Dl 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 ce + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index faeb669b9..0246d1075 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -99,12 +99,10 @@ set(keepassx_SOURCES gui/AboutDialog.cpp gui/Application.cpp gui/CategoryListWidget.cpp - gui/ChangeMasterKeyWidget.cpp gui/Clipboard.cpp gui/CloneDialog.cpp gui/DatabaseOpenWidget.cpp gui/DatabaseRepairWidget.cpp - gui/DatabaseSettingsWidget.cpp gui/DatabaseTabWidget.cpp gui/DatabaseWidget.cpp gui/DatabaseWidgetStateSync.cpp @@ -115,6 +113,7 @@ set(keepassx_SOURCES gui/EditWidgetIcons.cpp gui/EditWidgetProperties.cpp gui/FileDialog.cpp + gui/masterkey/KeyComponentWidget.cpp gui/Font.cpp gui/IconModels.cpp gui/KeePass1OpenWidget.cpp @@ -125,7 +124,7 @@ set(keepassx_SOURCES gui/MessageWidget.cpp gui/PasswordEdit.cpp gui/PasswordGeneratorWidget.cpp - gui/SettingsWidget.cpp + gui/ApplicationSettingsWidget.cpp gui/SearchWidget.cpp gui/SortFilterHideProxyModel.cpp gui/TotpSetupDialog.cpp @@ -151,6 +150,21 @@ set(keepassx_SOURCES gui/group/EditGroupWidget.cpp gui/group/GroupModel.cpp gui/group/GroupView.cpp + gui/masterkey/PasswordEditWidget.cpp + gui/masterkey/YubiKeyEditWidget.cpp + gui/masterkey/KeyFileEditWidget.cpp + gui/settings/SettingsWidget.cpp + gui/dbsettings/DatabaseSettingsWidget.cpp + gui/dbsettings/DatabaseSettingsDialog.cpp + gui/dbsettings/DatabaseSettingsWidgetGeneral.cpp + gui/dbsettings/DatabaseSettingsWidgetMetaDataSimple.cpp + gui/dbsettings/DatabaseSettingsWidgetEncryption.cpp + gui/dbsettings/DatabaseSettingsWidgetMasterKey.cpp + gui/wizard/NewDatabaseWizard.cpp + gui/wizard/NewDatabaseWizardPage.cpp + gui/wizard/NewDatabaseWizardPageMetaData.cpp + gui/wizard/NewDatabaseWizardPageEncryption.cpp + gui/wizard/NewDatabaseWizardPageMasterKey.cpp keys/ChallengeResponseKey.h keys/CompositeKey.cpp keys/drivers/YubiKey.h @@ -165,8 +179,7 @@ set(keepassx_SOURCES streams/StoreDataStream.cpp streams/SymmetricCipherStream.cpp totp/totp.h - totp/totp.cpp -) + totp/totp.cpp) if(APPLE) set(keepassx_SOURCES ${keepassx_SOURCES} core/ScreenLockListenerMac.h diff --git a/src/cli/Extract.cpp b/src/cli/Extract.cpp index 089a784d2..f0004e688 100644 --- a/src/cli/Extract.cpp +++ b/src/cli/Extract.cpp @@ -65,31 +65,31 @@ int Extract::execute(const QStringList& arguments) out << QObject::tr("Insert password to unlock %1: ").arg(args.at(0)); out.flush(); - CompositeKey compositeKey; + auto compositeKey = QSharedPointer::create(); QString line = Utils::getPassword(); - PasswordKey passwordKey; - passwordKey.setPassword(line); - compositeKey.addKey(passwordKey); + auto passwordKey = QSharedPointer::create(); + passwordKey->setPassword(line); + compositeKey->addKey(passwordKey); QString keyFilePath = parser.value(keyFile); if (!keyFilePath.isEmpty()) { - FileKey fileKey; + auto fileKey = QSharedPointer::create(); QString errorMsg; - if (!fileKey.load(keyFilePath, &errorMsg)) { + if (!fileKey->load(keyFilePath, &errorMsg)) { errorTextStream << QObject::tr("Failed to load key file %1 : %2").arg(keyFilePath).arg(errorMsg); errorTextStream << endl; return EXIT_FAILURE; } - if (fileKey.type() != FileKey::Hashed) { + if (fileKey->type() != FileKey::Hashed) { errorTextStream << QObject::tr("WARNING: You are using a legacy key file format which may become\n" "unsupported in the future.\n\n" "Please consider generating a new key file."); errorTextStream << endl; } - compositeKey.addKey(fileKey); + compositeKey->addKey(fileKey); } QString databaseFilename = args.at(0); diff --git a/src/cli/Merge.cpp b/src/cli/Merge.cpp index 6b114bff3..8248c45a0 100644 --- a/src/cli/Merge.cpp +++ b/src/cli/Merge.cpp @@ -76,7 +76,7 @@ int Merge::execute(const QStringList& arguments) if (!parser.isSet("same-credentials")) { db2 = Database::unlockFromStdin(args.at(1), parser.value(keyFileFrom)); } else { - db2 = Database::openDatabaseFile(args.at(1), *(db1->key().clone())); + db2 = Database::openDatabaseFile(args.at(1), db1->key()); } if (db2 == nullptr) { return EXIT_FAILURE; diff --git a/src/core/Config.cpp b/src/core/Config.cpp index 187878d9f..1a0f7b43a 100644 --- a/src/core/Config.cpp +++ b/src/core/Config.cpp @@ -160,6 +160,7 @@ void Config::init(const QString& fileName) m_defaults.insert("GUI/MinimizeOnClose", false); m_defaults.insert("GUI/HideUsernames", false); m_defaults.insert("GUI/HidePasswords", true); + m_defaults.insert("GUI/AdvancedSettings", false); } Config* Config::instance() diff --git a/src/core/Database.cpp b/src/core/Database.cpp index 21108f41a..bc0a1b302 100644 --- a/src/core/Database.cpp +++ b/src/core/Database.cpp @@ -98,6 +98,16 @@ const Metadata* Database::metadata() const return m_metadata; } +QString Database::filePath() const +{ + return m_filePath; +} + +void Database::setFilePath(const QString& filePath) +{ + m_filePath = filePath; +} + Entry* Database::resolveEntry(const QUuid& uuid) { return findEntryRecursive(uuid, m_rootGroup); @@ -244,7 +254,7 @@ QByteArray Database::challengeResponseKey() const bool Database::challengeMasterSeed(const QByteArray& masterSeed) { m_data.masterSeed = masterSeed; - return m_data.key.challenge(masterSeed, m_data.challengeResponseKey); + return m_data.key->challenge(masterSeed, m_data.challengeResponseKey); } void Database::setCipher(const QUuid& cipher) @@ -264,13 +274,20 @@ void Database::setCompressionAlgo(Database::CompressionAlgorithm algo) /** * Set and transform a new encryption key. * - * @param key key to set and transform + * @param key key to set and transform or nullptr to reset the key * @param updateChangedTime true to update database change time * @param updateTransformSalt true to update the transform salt * @return true on success */ -bool Database::setKey(const CompositeKey& key, bool updateChangedTime, bool updateTransformSalt) +bool Database::setKey(QSharedPointer key, bool updateChangedTime, bool updateTransformSalt) { + if (!key) { + m_data.key.reset(); + m_data.transformedMasterKey = {}; + m_data.hasKey = false; + return true; + } + if (updateTransformSalt) { m_data.kdf->randomizeSeed(); Q_ASSERT(!m_data.kdf->seed().isEmpty()); @@ -278,7 +295,7 @@ bool Database::setKey(const CompositeKey& key, bool updateChangedTime, bool upda QByteArray oldTransformedMasterKey = m_data.transformedMasterKey; QByteArray transformedMasterKey; - if (!key.transform(*m_data.kdf, transformedMasterKey)) { + if (!key->transform(*m_data.kdf, transformedMasterKey)) { return false; } @@ -301,14 +318,14 @@ bool Database::hasKey() const return m_data.hasKey; } -bool Database::verifyKey(const CompositeKey& key) const +bool Database::verifyKey(QSharedPointer key) const { Q_ASSERT(hasKey()); if (!m_data.challengeResponseKey.isEmpty()) { QByteArray result; - if (!key.challenge(m_data.masterSeed, result)) { + if (!key->challenge(m_data.masterSeed, result)) { // challenge failed, (YubiKey?) removed? return false; } @@ -319,7 +336,7 @@ bool Database::verifyKey(const CompositeKey& key) const } } - return (m_data.key.rawKey() == key.rawKey()); + return (m_data.key->rawKey() == key->rawKey()); } QVariantMap& Database::publicCustomData() @@ -430,12 +447,12 @@ void Database::startModifiedTimer() m_timer->start(150); } -const CompositeKey& Database::key() const +QSharedPointer Database::key() const { return m_data.key; } -Database* Database::openDatabaseFile(QString fileName, CompositeKey key) +Database* Database::openDatabaseFile(const QString& fileName, QSharedPointer key) { QFile dbFile(fileName); @@ -461,7 +478,7 @@ Database* Database::openDatabaseFile(QString fileName, CompositeKey key) Database* Database::unlockFromStdin(QString databaseFilename, QString keyFilename) { - CompositeKey compositeKey; + auto compositeKey = QSharedPointer::create(); QTextStream outputTextStream(stdout); QTextStream errorTextStream(stderr); @@ -469,19 +486,19 @@ Database* Database::unlockFromStdin(QString databaseFilename, QString keyFilenam outputTextStream.flush(); QString line = Utils::getPassword(); - PasswordKey passwordKey; - passwordKey.setPassword(line); - compositeKey.addKey(passwordKey); + auto passwordKey = QSharedPointer::create(); + passwordKey->setPassword(line); + compositeKey->addKey(passwordKey); if (!keyFilename.isEmpty()) { - FileKey fileKey; + auto fileKey = QSharedPointer::create(); QString errorMessage; - if (!fileKey.load(keyFilename, &errorMessage)) { + if (!fileKey->load(keyFilename, &errorMessage)) { errorTextStream << QObject::tr("Failed to load key file %1: %2").arg(keyFilename, errorMessage); errorTextStream << endl; return nullptr; } - compositeKey.addKey(fileKey); + compositeKey->addKey(fileKey); } return Database::openDatabaseFile(databaseFilename, compositeKey); @@ -607,7 +624,10 @@ bool Database::changeKdf(QSharedPointer kdf) { kdf->randomizeSeed(); QByteArray transformedMasterKey; - if (!m_data.key.transform(*kdf, transformedMasterKey)) { + if (!m_data.key) { + m_data.key = QSharedPointer::create(); + } + if (!m_data.key->transform(*kdf, transformedMasterKey)) { return false; } diff --git a/src/core/Database.h b/src/core/Database.h index c712faa38..912f95073 100644 --- a/src/core/Database.h +++ b/src/core/Database.h @@ -59,7 +59,7 @@ public: CompressionAlgorithm compressionAlgo; QByteArray transformedMasterKey; QSharedPointer kdf; - CompositeKey key; + QSharedPointer key; bool hasKey; QByteArray masterSeed; QByteArray challengeResponseKey; @@ -82,6 +82,8 @@ public: Metadata* metadata(); const Metadata* metadata() const; + QString filePath() const; + void setFilePath(const QString& filePath); Entry* resolveEntry(const QUuid& uuid); Entry* resolveEntry(const QString& text, EntryReferenceType referenceType); Group* resolveGroup(const QUuid& uuid); @@ -93,16 +95,16 @@ public: Database::CompressionAlgorithm compressionAlgo() const; QSharedPointer kdf() const; QByteArray transformedMasterKey() const; - const CompositeKey& key() const; + QSharedPointer key() const; QByteArray challengeResponseKey() const; bool challengeMasterSeed(const QByteArray& masterSeed); void setCipher(const QUuid& cipher); void setCompressionAlgo(Database::CompressionAlgorithm algo); void setKdf(QSharedPointer kdf); - bool setKey(const CompositeKey& key, bool updateChangedTime = true, bool updateTransformSalt = false); + bool setKey(QSharedPointer key, bool updateChangedTime = true, bool updateTransformSalt = false); bool hasKey() const; - bool verifyKey(const CompositeKey& key) const; + bool verifyKey(QSharedPointer key) const; QVariantMap& publicCustomData(); const QVariantMap& publicCustomData() const; void setPublicCustomData(const QVariantMap& customData); @@ -120,7 +122,7 @@ public: bool changeKdf(QSharedPointer kdf); static Database* databaseByUuid(const QUuid& uuid); - static Database* openDatabaseFile(QString fileName, CompositeKey key); + static Database* openDatabaseFile(const QString& fileName, QSharedPointer key); static Database* unlockFromStdin(QString databaseFilename, QString keyFilename = QString("")); signals: @@ -154,6 +156,8 @@ private: DatabaseData m_data; bool m_emitModified; + QString m_filePath; + QUuid m_uuid; static QHash m_uuidMap; }; diff --git a/src/crypto/kdf/Kdf.cpp b/src/crypto/kdf/Kdf.cpp index 2e1ad2ec9..fcedc488f 100644 --- a/src/crypto/kdf/Kdf.cpp +++ b/src/crypto/kdf/Kdf.cpp @@ -80,7 +80,7 @@ int Kdf::benchmark(int msec) const thread1.wait(); thread2.wait(); - return qMax(1, qMin(thread1.rounds(), thread2.rounds())); + return qMax(1, (thread1.rounds() + thread2.rounds()) / 2); } Kdf::BenchmarkThread::BenchmarkThread(int msec, const Kdf* kdf) diff --git a/src/format/Kdbx3Reader.cpp b/src/format/Kdbx3Reader.cpp index 00b0adebe..aeacaad3d 100644 --- a/src/format/Kdbx3Reader.cpp +++ b/src/format/Kdbx3Reader.cpp @@ -31,7 +31,7 @@ Database* Kdbx3Reader::readDatabaseImpl(QIODevice* device, const QByteArray& headerData, - const CompositeKey& key, + QSharedPointer key, bool keepDatabase) { Q_ASSERT(m_kdbxVersion <= KeePass2::FILE_VERSION_3_1); diff --git a/src/format/Kdbx3Reader.h b/src/format/Kdbx3Reader.h index 41916d0e5..d0dd4c1b7 100644 --- a/src/format/Kdbx3Reader.h +++ b/src/format/Kdbx3Reader.h @@ -31,7 +31,7 @@ class Kdbx3Reader : public KdbxReader public: Database* readDatabaseImpl(QIODevice* device, const QByteArray& headerData, - const CompositeKey& key, + QSharedPointer key, bool keepDatabase) override; protected: diff --git a/src/format/Kdbx4Reader.cpp b/src/format/Kdbx4Reader.cpp index 92c8187cd..7b94d34f8 100644 --- a/src/format/Kdbx4Reader.cpp +++ b/src/format/Kdbx4Reader.cpp @@ -30,7 +30,7 @@ Database* Kdbx4Reader::readDatabaseImpl(QIODevice* device, const QByteArray& headerData, - const CompositeKey& key, + QSharedPointer key, bool keepDatabase) { Q_ASSERT(m_kdbxVersion == KeePass2::FILE_VERSION_4); diff --git a/src/format/Kdbx4Reader.h b/src/format/Kdbx4Reader.h index ce965a7bb..f415543e7 100644 --- a/src/format/Kdbx4Reader.h +++ b/src/format/Kdbx4Reader.h @@ -32,7 +32,7 @@ class Kdbx4Reader : public KdbxReader public: Database* readDatabaseImpl(QIODevice* device, const QByteArray& headerData, - const CompositeKey& key, + QSharedPointer key, bool keepDatabase) override; QHash binaryPoolInverse() const; QHash binaryPool() const; diff --git a/src/format/KdbxReader.cpp b/src/format/KdbxReader.cpp index a8127509b..72f0f1375 100644 --- a/src/format/KdbxReader.cpp +++ b/src/format/KdbxReader.cpp @@ -57,7 +57,7 @@ bool KdbxReader::readMagicNumbers(QIODevice* device, quint32& sig1, quint32& sig * @param keepDatabase keep database in case of read failure * @return pointer to the read database, nullptr on failure */ -Database* KdbxReader::readDatabase(QIODevice* device, const CompositeKey& key, bool keepDatabase) +Database* KdbxReader::readDatabase(QIODevice* device, QSharedPointer key, bool keepDatabase) { device->seek(0); diff --git a/src/format/KdbxReader.h b/src/format/KdbxReader.h index 23050d28f..d42927c0f 100644 --- a/src/format/KdbxReader.h +++ b/src/format/KdbxReader.h @@ -40,7 +40,7 @@ public: virtual ~KdbxReader() = default; static bool readMagicNumbers(QIODevice* device, quint32& sig1, quint32& sig2, quint32& version); - Database* readDatabase(QIODevice* device, const CompositeKey& key, bool keepDatabase = false); + Database* readDatabase(QIODevice* device, QSharedPointer key, bool keepDatabase = false); bool hasError() const; QString errorString() const; @@ -62,7 +62,10 @@ protected: * @return pointer to the read database, nullptr on failure */ virtual Database* - readDatabaseImpl(QIODevice* device, const QByteArray& headerData, const CompositeKey& key, bool keepDatabase) = 0; + readDatabaseImpl(QIODevice* device, + const QByteArray& headerData, + QSharedPointer key, + bool keepDatabase) = 0; /** * Read next header field from stream. diff --git a/src/format/KeePass1Reader.cpp b/src/format/KeePass1Reader.cpp index 1db6e5d16..7c0ea652e 100644 --- a/src/format/KeePass1Reader.cpp +++ b/src/format/KeePass1Reader.cpp @@ -63,7 +63,7 @@ Database* KeePass1Reader::readDatabase(QIODevice* device, const QString& passwor m_errorStr.clear(); QByteArray keyfileData; - FileKey newFileKey; + auto newFileKey = QSharedPointer::create(); if (keyfileDevice) { keyfileData = readKeyfile(keyfileDevice); @@ -77,7 +77,7 @@ Database* KeePass1Reader::readDatabase(QIODevice* device, const QString& passwor return nullptr; } - if (!newFileKey.load(keyfileDevice)) { + if (!newFileKey->load(keyfileDevice)) { raiseError(tr("Unable to read keyfile.").append("\n").append(keyfileDevice->errorString())); return nullptr; } @@ -233,12 +233,12 @@ Database* KeePass1Reader::readDatabase(QIODevice* device, const QString& passwor entry->setUpdateTimeinfo(true); } - CompositeKey key; + auto key = QSharedPointer::create(); if (!password.isEmpty()) { - key.addKey(PasswordKey(password)); + key->addKey(QSharedPointer::create(password)); } if (keyfileDevice) { - key.addKey(newFileKey); + key->addKey(newFileKey); } if (!db->setKey(key)) { diff --git a/src/format/KeePass2.cpp b/src/format/KeePass2.cpp index dd9ffc0c6..5aad1f7f2 100644 --- a/src/format/KeePass2.cpp +++ b/src/format/KeePass2.cpp @@ -23,15 +23,15 @@ #define UUID_LENGTH 16 -const QUuid KeePass2::CIPHER_AES = QUuid::fromRfc4122(QByteArray::fromHex("31c1f2e6bf714350be5805216afc5aff")); -const QUuid KeePass2::CIPHER_TWOFISH = QUuid::fromRfc4122(QByteArray::fromHex("ad68f29f576f4bb9a36ad47af965346c")); -const QUuid KeePass2::CIPHER_CHACHA20 = QUuid::fromRfc4122(QByteArray::fromHex("D6038A2B8B6F4CB5A524339A31DBB59A")); +const QUuid KeePass2::CIPHER_AES = QUuid("31c1f2e6-bf71-4350-be58-05216afc5aff"); +const QUuid KeePass2::CIPHER_TWOFISH = QUuid("ad68f29f-576f-4bb9-a36a-d47af965346c"); +const QUuid KeePass2::CIPHER_CHACHA20 = QUuid("d6038a2b-8b6f-4cb5-a524-339a31dbb59a"); -const QUuid KeePass2::KDF_AES_KDBX3 = QUuid::fromRfc4122(QByteArray::fromHex("C9D9F39A628A4460BF740D08C18A4FEA")); -const QUuid KeePass2::KDF_AES_KDBX4 = QUuid::fromRfc4122(QByteArray::fromHex("7C02BB8279A74AC0927D114A00648238")); -const QUuid KeePass2::KDF_ARGON2 = QUuid::fromRfc4122(QByteArray::fromHex("EF636DDF8C29444B91F7A9A403E30A0C")); +const QUuid KeePass2::KDF_AES_KDBX3 = QUuid("c9d9f39a-628a-4460-bf74-0d08c18a4fea"); +const QUuid KeePass2::KDF_AES_KDBX4 = QUuid("7c02bb82-79a7-4ac0-927d-114a00648238"); +const QUuid KeePass2::KDF_ARGON2 = QUuid("ef636ddf-8c29-444b-91f7-a9a403e30a0c"); -const QByteArray KeePass2::INNER_STREAM_SALSA20_IV("\xE8\x30\x09\x4B\x97\x20\x5D\x2A"); +const QByteArray KeePass2::INNER_STREAM_SALSA20_IV("\xe8\x30\x09\x4b\x97\x20\x5d\x2a"); const QString KeePass2::KDFPARAM_UUID("$UUID"); // AES parameters diff --git a/src/format/KeePass2Reader.cpp b/src/format/KeePass2Reader.cpp index 1c16e76d6..2a6d61118 100644 --- a/src/format/KeePass2Reader.cpp +++ b/src/format/KeePass2Reader.cpp @@ -29,7 +29,7 @@ * @param key database encryption composite key * @return pointer to the read database, nullptr on failure */ -Database* KeePass2Reader::readDatabase(const QString& filename, const CompositeKey& key) +Database* KeePass2Reader::readDatabase(const QString& filename, QSharedPointer key) { QFile file(filename); if (!file.open(QFile::ReadOnly)) { @@ -55,7 +55,7 @@ Database* KeePass2Reader::readDatabase(const QString& filename, const CompositeK * @param keepDatabase keep database in case of read failure * @return pointer to the read database, nullptr on failure */ -Database* KeePass2Reader::readDatabase(QIODevice* device, const CompositeKey& key, bool keepDatabase) +Database* KeePass2Reader::readDatabase(QIODevice* device, QSharedPointer key, bool keepDatabase) { m_error = false; m_errorStr.clear(); diff --git a/src/format/KeePass2Reader.h b/src/format/KeePass2Reader.h index c4947c732..5ed720d4a 100644 --- a/src/format/KeePass2Reader.h +++ b/src/format/KeePass2Reader.h @@ -35,8 +35,8 @@ class KeePass2Reader Q_DECLARE_TR_FUNCTIONS(KdbxReader) public: - Database* readDatabase(const QString& filename, const CompositeKey& key); - Database* readDatabase(QIODevice* device, const CompositeKey& key, bool keepDatabase = false); + Database* readDatabase(const QString& filename, QSharedPointer key); + Database* readDatabase(QIODevice* device, QSharedPointer key, bool keepDatabase = false); bool hasError() const; QString errorString() const; diff --git a/src/format/KeePass2Repair.cpp b/src/format/KeePass2Repair.cpp index 508a9517b..2e0bc53cd 100644 --- a/src/format/KeePass2Repair.cpp +++ b/src/format/KeePass2Repair.cpp @@ -27,7 +27,7 @@ #include "format/KeePass2RandomStream.h" #include "format/KeePass2Reader.h" -KeePass2Repair::RepairOutcome KeePass2Repair::repairDatabase(QIODevice* device, const CompositeKey& key) +KeePass2Repair::RepairOutcome KeePass2Repair::repairDatabase(QIODevice* device, QSharedPointer key) { m_errorStr.clear(); diff --git a/src/format/KeePass2Repair.h b/src/format/KeePass2Repair.h index e7f2c8435..2f822dd0f 100644 --- a/src/format/KeePass2Repair.h +++ b/src/format/KeePass2Repair.h @@ -40,7 +40,7 @@ public: }; using RepairOutcome = QPair; - RepairOutcome repairDatabase(QIODevice* device, const CompositeKey& key); + RepairOutcome repairDatabase(QIODevice* device, QSharedPointer key); QString errorString() const; private: diff --git a/src/gui/SettingsWidget.cpp b/src/gui/ApplicationSettingsWidget.cpp similarity index 94% rename from src/gui/SettingsWidget.cpp rename to src/gui/ApplicationSettingsWidget.cpp index 8c84917b5..c46804571 100644 --- a/src/gui/SettingsWidget.cpp +++ b/src/gui/ApplicationSettingsWidget.cpp @@ -16,9 +16,9 @@ * along with this program. If not, see . */ -#include "SettingsWidget.h" -#include "ui_SettingsWidgetGeneral.h" -#include "ui_SettingsWidgetSecurity.h" +#include "ApplicationSettingsWidget.h" +#include "ui_ApplicationSettingsWidgetGeneral.h" +#include "ui_ApplicationSettingsWidgetSecurity.h" #include "autotype/AutoType.h" #include "config-keepassx.h" @@ -29,7 +29,7 @@ #include "touchid/TouchID.h" -class SettingsWidget::ExtraPage +class ApplicationSettingsWidget::ExtraPage { public: ExtraPage(ISettingsPage* page, QWidget* widget) @@ -53,12 +53,12 @@ private: QWidget* widget; }; -SettingsWidget::SettingsWidget(QWidget* parent) +ApplicationSettingsWidget::ApplicationSettingsWidget(QWidget* parent) : EditWidget(parent) , m_secWidget(new QWidget()) , m_generalWidget(new QWidget()) - , m_secUi(new Ui::SettingsWidgetSecurity()) - , m_generalUi(new Ui::SettingsWidgetGeneral()) + , m_secUi(new Ui::ApplicationSettingsWidgetSecurity()) + , m_generalUi(new Ui::ApplicationSettingsWidgetGeneral()) , m_globalAutoTypeKey(static_cast(0)) , m_globalAutoTypeModifiers(Qt::NoModifier) { @@ -109,11 +109,11 @@ SettingsWidget::SettingsWidget(QWidget* parent) } } -SettingsWidget::~SettingsWidget() +ApplicationSettingsWidget::~ApplicationSettingsWidget() { } -void SettingsWidget::addSettingsPage(ISettingsPage* page) +void ApplicationSettingsWidget::addSettingsPage(ISettingsPage* page) { QWidget* widget = page->createWidget(); widget->setParent(this); @@ -121,7 +121,7 @@ void SettingsWidget::addSettingsPage(ISettingsPage* page) addPage(page->name(), page->icon(), widget); } -void SettingsWidget::loadSettings() +void ApplicationSettingsWidget::loadSettings() { if (config()->hasAccessError()) { @@ -204,7 +204,7 @@ void SettingsWidget::loadSettings() setCurrentPage(0); } -void SettingsWidget::saveSettings() +void ApplicationSettingsWidget::saveSettings() { if (config()->hasAccessError()) { @@ -282,7 +282,7 @@ void SettingsWidget::saveSettings() } } -void SettingsWidget::reject() +void ApplicationSettingsWidget::reject() { // register the old key again as it might have changed if (m_globalAutoTypeKey > 0 && m_globalAutoTypeModifiers > 0) { @@ -290,12 +290,12 @@ void SettingsWidget::reject() } } -void SettingsWidget::enableAutoSaveOnExit(bool checked) +void ApplicationSettingsWidget::enableAutoSaveOnExit(bool checked) { m_generalUi->autoSaveOnExitCheckBox->setEnabled(!checked); } -void SettingsWidget::enableSystray(bool checked) +void ApplicationSettingsWidget::enableSystray(bool checked) { m_generalUi->systrayDarkIconCheckBox->setEnabled(checked); m_generalUi->systrayMinimizeToTrayCheckBox->setEnabled(checked); diff --git a/src/gui/SettingsWidget.h b/src/gui/ApplicationSettingsWidget.h similarity index 80% rename from src/gui/SettingsWidget.h rename to src/gui/ApplicationSettingsWidget.h index d6a1a7e63..5fedabfcd 100644 --- a/src/gui/SettingsWidget.h +++ b/src/gui/ApplicationSettingsWidget.h @@ -23,8 +23,8 @@ namespace Ui { - class SettingsWidgetGeneral; - class SettingsWidgetSecurity; + class ApplicationSettingsWidgetGeneral; + class ApplicationSettingsWidgetSecurity; } class ISettingsPage @@ -40,13 +40,13 @@ public: virtual void saveSettings(QWidget* widget) = 0; }; -class SettingsWidget : public EditWidget +class ApplicationSettingsWidget : public EditWidget { Q_OBJECT public: - explicit SettingsWidget(QWidget* parent = nullptr); - ~SettingsWidget(); + explicit ApplicationSettingsWidget(QWidget* parent = nullptr); + ~ApplicationSettingsWidget(); void addSettingsPage(ISettingsPage* page); void loadSettings(); @@ -59,8 +59,8 @@ private slots: private: QWidget* const m_secWidget; QWidget* const m_generalWidget; - const QScopedPointer m_secUi; - const QScopedPointer m_generalUi; + const QScopedPointer m_secUi; + const QScopedPointer m_generalUi; Qt::Key m_globalAutoTypeKey; Qt::KeyboardModifiers m_globalAutoTypeModifiers; class ExtraPage; diff --git a/src/gui/SettingsWidgetGeneral.ui b/src/gui/ApplicationSettingsWidgetGeneral.ui similarity index 99% rename from src/gui/SettingsWidgetGeneral.ui rename to src/gui/ApplicationSettingsWidgetGeneral.ui index 34c7bc9b1..f88206d60 100644 --- a/src/gui/SettingsWidgetGeneral.ui +++ b/src/gui/ApplicationSettingsWidgetGeneral.ui @@ -1,13 +1,13 @@ - SettingsWidgetGeneral - + ApplicationSettingsWidgetGeneral + 0 0 684 - 794 + 842 @@ -26,7 +26,7 @@ - 1 + 0 diff --git a/src/gui/SettingsWidgetSecurity.ui b/src/gui/ApplicationSettingsWidgetSecurity.ui similarity index 98% rename from src/gui/SettingsWidgetSecurity.ui rename to src/gui/ApplicationSettingsWidgetSecurity.ui index f90fb4c3d..bef64dc77 100644 --- a/src/gui/SettingsWidgetSecurity.ui +++ b/src/gui/ApplicationSettingsWidgetSecurity.ui @@ -1,7 +1,7 @@ - SettingsWidgetSecurity - + ApplicationSettingsWidgetSecurity + 0 diff --git a/src/gui/CategoryListWidget.h b/src/gui/CategoryListWidget.h index 911b5cc98..3f08fe384 100644 --- a/src/gui/CategoryListWidget.h +++ b/src/gui/CategoryListWidget.h @@ -15,6 +15,9 @@ * along with this program. If not, see . */ +#ifndef KEEPASSXC_GUI_CATEGORYLISTWIDGET_H +#define KEEPASSXC_GUI_CATEGORYLISTWIDGET_H + #include #include #include @@ -86,3 +89,5 @@ private: Q_DISABLE_COPY(CategoryListWidgetDelegate) }; + +#endif \ No newline at end of file diff --git a/src/gui/ChangeMasterKeyWidget.cpp b/src/gui/ChangeMasterKeyWidget.cpp deleted file mode 100644 index b91495f78..000000000 --- a/src/gui/ChangeMasterKeyWidget.cpp +++ /dev/null @@ -1,252 +0,0 @@ -/* - * Copyright (C) 2012 Felix Geyer - * Copyright (C) 2017 KeePassXC Team - * - * 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 "ChangeMasterKeyWidget.h" -#include "ui_ChangeMasterKeyWidget.h" - -#include "MainWindow.h" -#include "core/FilePath.h" -#include "crypto/Random.h" -#include "gui/FileDialog.h" -#include "gui/MessageBox.h" -#include "keys/FileKey.h" -#include "keys/PasswordKey.h" -#include "keys/YkChallengeResponseKey.h" - -#include "config-keepassx.h" - -#include -#include - -ChangeMasterKeyWidget::ChangeMasterKeyWidget(QWidget* parent) - : DialogyWidget(parent) - , m_ui(new Ui::ChangeMasterKeyWidget()) -{ - m_ui->setupUi(this); - - m_ui->messageWidget->setHidden(true); - - m_ui->togglePasswordButton->setIcon(filePath()->onOffIcon("actions", "password-show")); - m_ui->repeatPasswordEdit->enableVerifyMode(m_ui->enterPasswordEdit); - - connect(m_ui->passwordGroup, SIGNAL(clicked(bool)), SLOT(setOkEnabled())); - connect(m_ui->togglePasswordButton, SIGNAL(toggled(bool)), m_ui->enterPasswordEdit, SLOT(setShowPassword(bool))); - - connect(m_ui->keyFileGroup, SIGNAL(clicked(bool)), SLOT(setOkEnabled())); - connect(m_ui->createKeyFileButton, SIGNAL(clicked()), SLOT(createKeyFile())); - connect(m_ui->browseKeyFileButton, SIGNAL(clicked()), SLOT(browseKeyFile())); - connect(m_ui->keyFileCombo, SIGNAL(editTextChanged(QString)), SLOT(setOkEnabled())); - - connect(m_ui->buttonBox, SIGNAL(accepted()), SLOT(generateKey())); - connect(m_ui->buttonBox, SIGNAL(rejected()), SLOT(reject())); - -#ifdef WITH_XC_YUBIKEY - m_ui->yubikeyProgress->setVisible(false); - QSizePolicy sp = m_ui->yubikeyProgress->sizePolicy(); - sp.setRetainSizeWhenHidden(true); - m_ui->yubikeyProgress->setSizePolicy(sp); - - connect(m_ui->challengeResponseGroup, SIGNAL(clicked(bool)), SLOT(challengeResponseGroupToggled(bool))); - connect(m_ui->challengeResponseGroup, SIGNAL(clicked(bool)), SLOT(setOkEnabled())); - connect(m_ui->buttonRedetectYubikey, SIGNAL(clicked()), SLOT(pollYubikey())); - - connect(YubiKey::instance(), SIGNAL(detected(int, bool)), SLOT(yubikeyDetected(int, bool)), Qt::QueuedConnection); - connect(YubiKey::instance(), SIGNAL(notFound()), SLOT(noYubikeyFound()), Qt::QueuedConnection); -#else - m_ui->challengeResponseGroup->setVisible(false); -#endif -} - -ChangeMasterKeyWidget::~ChangeMasterKeyWidget() -{ -} - -void ChangeMasterKeyWidget::createKeyFile() -{ - QString filters = QString("%1 (*.key);;%2 (*)").arg(tr("Key files"), tr("All files")); - QString fileName = fileDialog()->getSaveFileName(this, tr("Create Key File..."), QString(), filters); - - if (!fileName.isEmpty()) { - QString errorMsg; - bool created = FileKey::create(fileName, &errorMsg); - if (!created) { - m_ui->messageWidget->showMessage(tr("Unable to create key file: %1").arg(errorMsg), MessageWidget::Error); - } else { - m_ui->keyFileCombo->setEditText(fileName); - } - } -} - -void ChangeMasterKeyWidget::browseKeyFile() -{ - QString filters = QString("%1 (*.key);;%2 (*)").arg(tr("Key files"), tr("All files")); - QString fileName = fileDialog()->getOpenFileName(this, tr("Select a key file"), QString(), filters); - - if (!fileName.isEmpty()) { - m_ui->keyFileCombo->setEditText(fileName); - } -} - -void ChangeMasterKeyWidget::clearForms() -{ - m_key.clear(); - - m_ui->passwordGroup->setChecked(true); - m_ui->enterPasswordEdit->setText(""); - m_ui->repeatPasswordEdit->setText(""); - m_ui->keyFileGroup->setChecked(false); - m_ui->togglePasswordButton->setChecked(false); - -#ifdef WITH_XC_YUBIKEY - m_ui->challengeResponseGroup->setChecked(false); - m_ui->comboChallengeResponse->clear(); -#endif - - m_ui->enterPasswordEdit->setFocus(); -} - -CompositeKey ChangeMasterKeyWidget::newMasterKey() -{ - return m_key; -} - -QLabel* ChangeMasterKeyWidget::headlineLabel() -{ - return m_ui->headlineLabel; -} - -void ChangeMasterKeyWidget::generateKey() -{ - m_key.clear(); - - if (m_ui->passwordGroup->isChecked()) { - if (m_ui->enterPasswordEdit->text() == m_ui->repeatPasswordEdit->text()) { - if (m_ui->enterPasswordEdit->text().isEmpty()) { - if (MessageBox::warning(this, - tr("Empty password"), - tr("Do you really want to use an empty string as password?"), - QMessageBox::Yes | QMessageBox::No) - != QMessageBox::Yes) { - return; - } - } - m_key.addKey(PasswordKey(m_ui->enterPasswordEdit->text())); - } else { - m_ui->messageWidget->showMessage(tr("Different passwords supplied."), MessageWidget::Error); - m_ui->enterPasswordEdit->setText(""); - m_ui->repeatPasswordEdit->setText(""); - return; - } - } - if (m_ui->keyFileGroup->isChecked()) { - FileKey fileKey; - QString errorMsg; - QString fileKeyName = m_ui->keyFileCombo->currentText(); - if (!fileKey.load(fileKeyName, &errorMsg)) { - m_ui->messageWidget->showMessage(tr("Failed to set %1 as the key file:\n%2").arg(fileKeyName, errorMsg), - MessageWidget::Error); - return; - } - if (fileKey.type() != FileKey::Hashed) { - QMessageBox::warning(this, - tr("Legacy key file format"), - tr("You are using a legacy key file format which may become\n" - "unsupported in the future.\n\n" - "Please consider generating a new key file."), - QMessageBox::Ok); - } - m_key.addKey(fileKey); - } - -#ifdef WITH_XC_YUBIKEY - if (m_ui->challengeResponseGroup->isChecked()) { - int selectionIndex = m_ui->comboChallengeResponse->currentIndex(); - int comboPayload = m_ui->comboChallengeResponse->itemData(selectionIndex).toInt(); - - if (0 == comboPayload) { - m_ui->messageWidget->showMessage(tr("Changing master key failed: no YubiKey inserted."), - MessageWidget::Error); - return; - } - - // read blocking mode from LSB and slot index number from second LSB - bool blocking = comboPayload & 1; - int slot = comboPayload >> 1; - auto key = QSharedPointer(new YkChallengeResponseKey(slot, blocking)); - m_key.addChallengeResponseKey(key); - } -#endif - - m_ui->messageWidget->hideMessage(); - emit editFinished(true); -} - -void ChangeMasterKeyWidget::reject() -{ - emit editFinished(false); -} - -void ChangeMasterKeyWidget::challengeResponseGroupToggled(bool checked) -{ - if (checked) - pollYubikey(); -} - -void ChangeMasterKeyWidget::pollYubikey() -{ - m_ui->buttonRedetectYubikey->setEnabled(false); - m_ui->comboChallengeResponse->setEnabled(false); - m_ui->comboChallengeResponse->clear(); - m_ui->yubikeyProgress->setVisible(true); - setOkEnabled(); - - // YubiKey init is slow, detect asynchronously to not block the UI - QtConcurrent::run(YubiKey::instance(), &YubiKey::detect); -} - -void ChangeMasterKeyWidget::yubikeyDetected(int slot, bool blocking) -{ - YkChallengeResponseKey yk(slot, blocking); - // add detected YubiKey to combo box and encode blocking mode in LSB, slot number in second LSB - m_ui->comboChallengeResponse->addItem(yk.getName(), QVariant((slot << 1) | blocking)); - m_ui->comboChallengeResponse->setEnabled(m_ui->challengeResponseGroup->isChecked()); - m_ui->buttonRedetectYubikey->setEnabled(m_ui->challengeResponseGroup->isChecked()); - m_ui->yubikeyProgress->setVisible(false); - setOkEnabled(); -} - -void ChangeMasterKeyWidget::noYubikeyFound() -{ - m_ui->buttonRedetectYubikey->setEnabled(m_ui->challengeResponseGroup->isChecked()); - m_ui->yubikeyProgress->setVisible(false); - setOkEnabled(); -} - -void ChangeMasterKeyWidget::setOkEnabled() -{ - bool ok = m_ui->passwordGroup->isChecked() - || (m_ui->challengeResponseGroup->isChecked() && !m_ui->comboChallengeResponse->currentText().isEmpty()) - || (m_ui->keyFileGroup->isChecked() && !m_ui->keyFileCombo->currentText().isEmpty()); - - m_ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(ok); -} - -void ChangeMasterKeyWidget::setCancelEnabled(bool enabled) -{ - m_ui->buttonBox->button(QDialogButtonBox::Cancel)->setEnabled(enabled); -} diff --git a/src/gui/ChangeMasterKeyWidget.h b/src/gui/ChangeMasterKeyWidget.h deleted file mode 100644 index 2a4e0cc4a..000000000 --- a/src/gui/ChangeMasterKeyWidget.h +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright (C) 2012 Felix Geyer - * Copyright (C) 2017 KeePassXC Team - * - * 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_CHANGEMASTERKEYWIDGET_H -#define KEEPASSX_CHANGEMASTERKEYWIDGET_H - -#include - -#include "gui/DialogyWidget.h" -#include "keys/CompositeKey.h" - -class QLabel; -namespace Ui -{ - class ChangeMasterKeyWidget; -} - -class ChangeMasterKeyWidget : public DialogyWidget -{ - Q_OBJECT - -public: - explicit ChangeMasterKeyWidget(QWidget* parent = nullptr); - ~ChangeMasterKeyWidget(); - void clearForms(); - CompositeKey newMasterKey(); - QLabel* headlineLabel(); - -public slots: - void setOkEnabled(); - void setCancelEnabled(bool enabled); - -signals: - void editFinished(bool accepted); - -private slots: - void generateKey(); - void reject(); - void createKeyFile(); - void browseKeyFile(); - void yubikeyDetected(int slot, bool blocking); - void noYubikeyFound(); - void challengeResponseGroupToggled(bool checked); - void pollYubikey(); - -private: - const QScopedPointer m_ui; - CompositeKey m_key; - - Q_DISABLE_COPY(ChangeMasterKeyWidget) -}; - -#endif // KEEPASSX_CHANGEMASTERKEYWIDGET_H diff --git a/src/gui/ChangeMasterKeyWidget.ui b/src/gui/ChangeMasterKeyWidget.ui deleted file mode 100644 index 693d8ac1d..000000000 --- a/src/gui/ChangeMasterKeyWidget.ui +++ /dev/null @@ -1,238 +0,0 @@ - - - ChangeMasterKeyWidget - - - - 0 - 0 - 818 - 471 - - - - - - - - - - - - - Qt::Vertical - - - QSizePolicy::Fixed - - - - 1 - 3 - - - - - - - - Password - - - true - - - true - - - - - - Enter password: - - - - - - - - - QLineEdit::Password - - - - - - - true - - - - - - - - - Repeat password: - - - - - - - QLineEdit::Password - - - - - - - - - - &Key file - - - true - - - - - - Browse - - - - - - - Create - - - - - - - - 0 - 0 - - - - true - - - - - - - - - - true - - - Cha&llenge Response - - - true - - - true - - - - - - 0 - - - - - Refresh - - - - - - - - 0 - 0 - - - - - - - - - 16777215 - 2 - - - - 0 - - - -1 - - - false - - - - - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - - - - - - - PasswordEdit - QLineEdit -
gui/PasswordEdit.h
-
- - MessageWidget - QWidget -
gui/MessageWidget.h
- 1 -
-
- - passwordGroup - enterPasswordEdit - repeatPasswordEdit - togglePasswordButton - keyFileGroup - keyFileCombo - browseKeyFileButton - createKeyFileButton - buttonBox - - - -
diff --git a/src/gui/DatabaseOpenWidget.cpp b/src/gui/DatabaseOpenWidget.cpp index ebb07bf12..d1ae52780 100644 --- a/src/gui/DatabaseOpenWidget.cpp +++ b/src/gui/DatabaseOpenWidget.cpp @@ -197,7 +197,7 @@ void DatabaseOpenWidget::openDatabase() delete m_db; } QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); - m_db = reader.readDatabase(&file, *masterKey); + m_db = reader.readDatabase(&file, masterKey); QApplication::restoreOverrideCursor(); if (m_db) { @@ -240,7 +240,7 @@ QSharedPointer DatabaseOpenWidget::databaseKey() auto masterKey = QSharedPointer::create(); if (m_ui->checkPassword->isChecked()) { - masterKey->addKey(PasswordKey(m_ui->editPassword->text())); + masterKey->addKey(QSharedPointer::create(m_ui->editPassword->text())); } #ifdef WITH_XC_TOUCHID @@ -262,14 +262,14 @@ QSharedPointer DatabaseOpenWidget::databaseKey() QHash lastChallengeResponse = config()->get("LastChallengeResponse").toHash(); if (m_ui->checkKeyFile->isChecked()) { - FileKey key; + auto key = QSharedPointer::create(); QString keyFilename = m_ui->comboKeyFile->currentText(); QString errorMsg; - if (!key.load(keyFilename, &errorMsg)) { + if (!key->load(keyFilename, &errorMsg)) { m_ui->messageWidget->showMessage(tr("Can't open key file:\n%1").arg(errorMsg), MessageWidget::Error); - return QSharedPointer(); + return {}; } - if (key.type() != FileKey::Hashed && !config()->get("Messages/NoLegacyKeyFileWarning").toBool()) { + if (key->type() != FileKey::Hashed && !config()->get("Messages/NoLegacyKeyFileWarning").toBool()) { QMessageBox legacyWarning; legacyWarning.setWindowTitle(tr("Legacy key file format")); legacyWarning.setText(tr("You are using a legacy key file format which may become\n" diff --git a/src/gui/DatabaseOpenWidget.ui b/src/gui/DatabaseOpenWidget.ui index b603ed32a..641d67da0 100644 --- a/src/gui/DatabaseOpenWidget.ui +++ b/src/gui/DatabaseOpenWidget.ui @@ -6,11 +6,11 @@ 0 0 - 596 - 302 + 841 + 467
- + 8 @@ -55,10 +55,13 @@ Qt::Vertical + + QSizePolicy::Fixed + 20 - 40 + 20 @@ -216,7 +219,7 @@ - Use TouchID for quick unlock + TouchID for quick unlock diff --git a/src/gui/DatabaseRepairWidget.cpp b/src/gui/DatabaseRepairWidget.cpp index b4be71247..7a90cf7dd 100644 --- a/src/gui/DatabaseRepairWidget.cpp +++ b/src/gui/DatabaseRepairWidget.cpp @@ -39,22 +39,22 @@ DatabaseRepairWidget::DatabaseRepairWidget(QWidget* parent) void DatabaseRepairWidget::openDatabase() { - CompositeKey masterKey; + auto masterKey = QSharedPointer::create(); if (m_ui->checkPassword->isChecked()) { - masterKey.addKey(PasswordKey(m_ui->editPassword->text())); + masterKey->addKey(QSharedPointer::create(m_ui->editPassword->text())); } if (m_ui->checkKeyFile->isChecked()) { - FileKey key; + auto key = QSharedPointer::create(); QString keyFilename = m_ui->comboKeyFile->currentText(); QString errorMsg; - if (!key.load(keyFilename, &errorMsg)) { + if (!key->load(keyFilename, &errorMsg)) { MessageBox::warning(this, tr("Error"), tr("Can't open key file:\n%1").arg(errorMsg)); emit editFinished(false); return; } - masterKey.addKey(key); + masterKey->addKey(key); } KeePass2Repair repair; diff --git a/src/gui/DatabaseSettingsWidget.cpp b/src/gui/DatabaseSettingsWidget.cpp deleted file mode 100644 index 8aea47266..000000000 --- a/src/gui/DatabaseSettingsWidget.cpp +++ /dev/null @@ -1,315 +0,0 @@ -/* - * Copyright (C) 2018 KeePassXC Team - * Copyright (C) 2012 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 "DatabaseSettingsWidget.h" -#include "ui_DatabaseSettingsWidget.h" -#include "ui_DatabaseSettingsWidgetEncryption.h" -#include "ui_DatabaseSettingsWidgetGeneral.h" - -#include -#include -#include - -#include "MessageBox.h" -#include "core/AsyncTask.h" -#include "core/Database.h" -#include "core/FilePath.h" -#include "core/Global.h" -#include "core/Group.h" -#include "core/Metadata.h" -#include "crypto/SymmetricCipher.h" -#include "crypto/kdf/Argon2Kdf.h" - -DatabaseSettingsWidget::DatabaseSettingsWidget(QWidget* parent) - : DialogyWidget(parent) - , m_ui(new Ui::DatabaseSettingsWidget()) - , m_uiGeneral(new Ui::DatabaseSettingsWidgetGeneral()) - , m_uiEncryption(new Ui::DatabaseSettingsWidgetEncryption()) - , m_uiGeneralPage(new QWidget()) - , m_uiEncryptionPage(new QWidget()) - , m_db(nullptr) -{ - m_ui->setupUi(this); - m_uiGeneral->setupUi(m_uiGeneralPage); - m_uiEncryption->setupUi(m_uiEncryptionPage); - - connect(m_ui->buttonBox, SIGNAL(accepted()), SLOT(save())); - connect(m_ui->buttonBox, SIGNAL(rejected()), SLOT(reject())); - connect(m_uiGeneral->historyMaxItemsCheckBox, - SIGNAL(toggled(bool)), - m_uiGeneral->historyMaxItemsSpinBox, - SLOT(setEnabled(bool))); - connect(m_uiGeneral->historyMaxSizeCheckBox, - SIGNAL(toggled(bool)), - m_uiGeneral->historyMaxSizeSpinBox, - SLOT(setEnabled(bool))); - connect(m_uiEncryption->transformBenchmarkButton, SIGNAL(clicked()), SLOT(transformRoundsBenchmark())); - connect(m_uiEncryption->kdfComboBox, SIGNAL(currentIndexChanged(int)), SLOT(kdfChanged(int))); - - connect(m_uiEncryption->memorySpinBox, SIGNAL(valueChanged(int)), this, SLOT(memoryChanged(int))); - connect(m_uiEncryption->parallelismSpinBox, SIGNAL(valueChanged(int)), this, SLOT(parallelismChanged(int))); - - m_ui->categoryList->addCategory(tr("General"), FilePath::instance()->icon("categories", "preferences-other")); - m_ui->categoryList->addCategory(tr("Encryption"), FilePath::instance()->icon("actions", "document-encrypt")); - m_ui->stackedWidget->addWidget(m_uiGeneralPage); - m_ui->stackedWidget->addWidget(m_uiEncryptionPage); - - connect(m_ui->categoryList, SIGNAL(categoryChanged(int)), m_ui->stackedWidget, SLOT(setCurrentIndex(int))); -} - -DatabaseSettingsWidget::~DatabaseSettingsWidget() -{ -} - -void DatabaseSettingsWidget::load(Database* db) -{ - m_db = db; - - Metadata* meta = m_db->metadata(); - - m_uiGeneral->dbNameEdit->setText(meta->name()); - m_uiGeneral->dbDescriptionEdit->setText(meta->description()); - m_uiGeneral->recycleBinEnabledCheckBox->setChecked(meta->recycleBinEnabled()); - m_uiGeneral->defaultUsernameEdit->setText(meta->defaultUserName()); - m_uiGeneral->compressionCheckbox->setChecked(m_db->compressionAlgo() != Database::CompressionNone); - - if (meta->historyMaxItems() > -1) { - m_uiGeneral->historyMaxItemsSpinBox->setValue(meta->historyMaxItems()); - m_uiGeneral->historyMaxItemsCheckBox->setChecked(true); - } else { - m_uiGeneral->historyMaxItemsSpinBox->setValue(Metadata::DefaultHistoryMaxItems); - m_uiGeneral->historyMaxItemsCheckBox->setChecked(false); - } - int historyMaxSizeMiB = qRound(meta->historyMaxSize() / qreal(1048576)); - if (historyMaxSizeMiB > 0) { - m_uiGeneral->historyMaxSizeSpinBox->setValue(historyMaxSizeMiB); - m_uiGeneral->historyMaxSizeCheckBox->setChecked(true); - } else { - m_uiGeneral->historyMaxSizeSpinBox->setValue(Metadata::DefaultHistoryMaxSize); - m_uiGeneral->historyMaxSizeCheckBox->setChecked(false); - } - - m_uiEncryption->algorithmComboBox->clear(); - for (auto& cipher: asConst(KeePass2::CIPHERS)) { - m_uiEncryption->algorithmComboBox->addItem(QCoreApplication::translate("KeePass2", cipher.second.toUtf8()), cipher.first); - } - int cipherIndex = m_uiEncryption->algorithmComboBox->findData(m_db->cipher().toRfc4122()); - if (cipherIndex > -1) { - m_uiEncryption->algorithmComboBox->setCurrentIndex(cipherIndex); - } - - // Setup kdf combo box - m_uiEncryption->kdfComboBox->blockSignals(true); - m_uiEncryption->kdfComboBox->clear(); - for (auto& kdf: asConst(KeePass2::KDFS)) { - m_uiEncryption->kdfComboBox->addItem(QCoreApplication::translate("KeePass2", kdf.second.toUtf8()), kdf.first); - } - m_uiEncryption->kdfComboBox->blockSignals(false); - - auto kdfUuid = m_db->kdf()->uuid(); - int kdfIndex = m_uiEncryption->kdfComboBox->findData(kdfUuid); - if (kdfIndex > -1) { - m_uiEncryption->kdfComboBox->setCurrentIndex(kdfIndex); - kdfChanged(kdfIndex); - } - - m_uiEncryption->memorySpinBox->setValue(64); - m_uiEncryption->parallelismSpinBox->setValue(QThread::idealThreadCount()); - - // Setup kdf parameters - auto kdf = m_db->kdf(); - m_uiEncryption->transformRoundsSpinBox->setValue(kdf->rounds()); - if (kdfUuid == KeePass2::KDF_ARGON2) { - auto argon2Kdf = kdf.staticCast(); - m_uiEncryption->memorySpinBox->setValue(static_cast(argon2Kdf->memory()) / (1 << 10)); - m_uiEncryption->parallelismSpinBox->setValue(argon2Kdf->parallelism()); - } - - m_uiGeneral->dbNameEdit->setFocus(); - m_ui->categoryList->setCurrentCategory(0); -} - -void DatabaseSettingsWidget::save() -{ - // first perform safety check for KDF rounds - auto kdf = KeePass2::uuidToKdf(m_uiEncryption->kdfComboBox->currentData().value()); - if (kdf->uuid() == KeePass2::KDF_ARGON2 && m_uiEncryption->transformRoundsSpinBox->value() > 10000) { - QMessageBox warning; - warning.setIcon(QMessageBox::Warning); - warning.setWindowTitle(tr("Number of rounds too high", "Key transformation rounds")); - warning.setText(tr("You are using a very high number of key transform rounds with Argon2.\n\n" - "If you keep this number, your database may take hours or days (or even longer) to open!")); - auto ok = warning.addButton(tr("Understood, keep number"), QMessageBox::ButtonRole::AcceptRole); - auto cancel = warning.addButton(tr("Cancel"), QMessageBox::ButtonRole::RejectRole); - warning.setDefaultButton(cancel); - warning.exec(); - if (warning.clickedButton() != ok) { - return; - } - } else if ((kdf->uuid() == KeePass2::KDF_AES_KDBX3 || kdf->uuid() == KeePass2::KDF_AES_KDBX4) - && m_uiEncryption->transformRoundsSpinBox->value() < 100000) { - QMessageBox warning; - warning.setIcon(QMessageBox::Warning); - warning.setWindowTitle(tr("Number of rounds too low", "Key transformation rounds")); - warning.setText(tr("You are using a very low number of key transform rounds with AES-KDF.\n\n" - "If you keep this number, your database may be too easy to crack!")); - auto ok = warning.addButton(tr("Understood, keep number"), QMessageBox::ButtonRole::AcceptRole); - auto cancel = warning.addButton(tr("Cancel"), QMessageBox::ButtonRole::RejectRole); - warning.setDefaultButton(cancel); - warning.exec(); - if (warning.clickedButton() != ok) { - return; - } - } - - m_db->setCompressionAlgo(m_uiGeneral->compressionCheckbox->isChecked() ? Database::CompressionGZip - : Database::CompressionNone); - - Metadata* meta = m_db->metadata(); - - meta->setName(m_uiGeneral->dbNameEdit->text()); - meta->setDescription(m_uiGeneral->dbDescriptionEdit->text()); - meta->setDefaultUserName(m_uiGeneral->defaultUsernameEdit->text()); - meta->setRecycleBinEnabled(m_uiGeneral->recycleBinEnabledCheckBox->isChecked()); - meta->setSettingsChanged(QDateTime::currentDateTimeUtc()); - - bool truncate = false; - - int historyMaxItems; - if (m_uiGeneral->historyMaxItemsCheckBox->isChecked()) { - historyMaxItems = m_uiGeneral->historyMaxItemsSpinBox->value(); - } else { - historyMaxItems = -1; - } - if (historyMaxItems != meta->historyMaxItems()) { - meta->setHistoryMaxItems(historyMaxItems); - truncate = true; - } - - int historyMaxSize; - if (m_uiGeneral->historyMaxSizeCheckBox->isChecked()) { - historyMaxSize = m_uiGeneral->historyMaxSizeSpinBox->value() * 1048576; - } else { - historyMaxSize = -1; - } - if (historyMaxSize != meta->historyMaxSize()) { - meta->setHistoryMaxSize(historyMaxSize); - truncate = true; - } - - if (truncate) { - truncateHistories(); - } - - m_db->setCipher(m_uiEncryption->algorithmComboBox->currentData().value()); - - // Save kdf parameters - kdf->setRounds(m_uiEncryption->transformRoundsSpinBox->value()); - if (kdf->uuid() == KeePass2::KDF_ARGON2) { - auto argon2Kdf = kdf.staticCast(); - argon2Kdf->setMemory(static_cast(m_uiEncryption->memorySpinBox->value()) * (1 << 10)); - argon2Kdf->setParallelism(static_cast(m_uiEncryption->parallelismSpinBox->value())); - } - - QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); - // TODO: we should probably use AsyncTask::runAndWaitForFuture() here, - // but not without making Database thread-safe - bool ok = m_db->changeKdf(kdf); - QApplication::restoreOverrideCursor(); - - if (!ok) { - MessageBox::warning(this, - tr("KDF unchanged"), - tr("Failed to transform key with new KDF parameters; KDF unchanged."), - QMessageBox::Ok); - } - - emit editFinished(true); -} - -void DatabaseSettingsWidget::reject() -{ - emit editFinished(false); -} - -void DatabaseSettingsWidget::transformRoundsBenchmark() -{ - QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); - m_uiEncryption->transformBenchmarkButton->setEnabled(false); - m_uiEncryption->transformRoundsSpinBox->setFocus(); - - // Create a new kdf with the current parameters - auto kdf = KeePass2::uuidToKdf(m_uiEncryption->kdfComboBox->currentData().value()); - kdf->setRounds(m_uiEncryption->transformRoundsSpinBox->value()); - if (kdf->uuid() == KeePass2::KDF_ARGON2) { - auto argon2Kdf = kdf.staticCast(); - if (!argon2Kdf->setMemory(static_cast(m_uiEncryption->memorySpinBox->value()) * (1 << 10))) { - m_uiEncryption->memorySpinBox->setValue(static_cast(argon2Kdf->memory() / (1 << 10))); - } - if (!argon2Kdf->setParallelism(static_cast(m_uiEncryption->parallelismSpinBox->value()))) { - m_uiEncryption->parallelismSpinBox->setValue(argon2Kdf->parallelism()); - } - } - - // Determine the number of rounds required to meet 1 second delay - int rounds = AsyncTask::runAndWaitForFuture([&kdf]() { return kdf->benchmark(1000); }); - - m_uiEncryption->transformRoundsSpinBox->setValue(rounds); - m_uiEncryption->transformBenchmarkButton->setEnabled(true); - QApplication::restoreOverrideCursor(); -} - -void DatabaseSettingsWidget::truncateHistories() -{ - const QList allEntries = m_db->rootGroup()->entriesRecursive(false); - for (Entry* entry : allEntries) { - entry->truncateHistory(); - } -} - -void DatabaseSettingsWidget::kdfChanged(int index) -{ - QUuid id(m_uiEncryption->kdfComboBox->itemData(index).value()); - - bool memoryEnabled = id == KeePass2::KDF_ARGON2; - m_uiEncryption->memoryUsageLabel->setEnabled(memoryEnabled); - m_uiEncryption->memorySpinBox->setEnabled(memoryEnabled); - - bool parallelismEnabled = id == KeePass2::KDF_ARGON2; - m_uiEncryption->parallelismLabel->setEnabled(parallelismEnabled); - m_uiEncryption->parallelismSpinBox->setEnabled(parallelismEnabled); - - transformRoundsBenchmark(); -} - -/** - * Update memory spin box suffix on value change. - */ -void DatabaseSettingsWidget::memoryChanged(int value) -{ - m_uiEncryption->memorySpinBox->setSuffix(tr(" MiB", "Abbreviation for Mebibytes (KDF settings)", value)); -} - -/** - * Update parallelism spin box suffix on value change. - */ -void DatabaseSettingsWidget::parallelismChanged(int value) -{ - m_uiEncryption->parallelismSpinBox->setSuffix( - tr(" thread(s)", "Threads for parallel execution (KDF settings)", value)); -} diff --git a/src/gui/DatabaseSettingsWidgetEncryption.ui b/src/gui/DatabaseSettingsWidgetEncryption.ui deleted file mode 100644 index 502bc012c..000000000 --- a/src/gui/DatabaseSettingsWidgetEncryption.ui +++ /dev/null @@ -1,183 +0,0 @@ - - - DatabaseSettingsWidgetEncryption - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Encryption Algorithm: - - - - - - - - 0 - 0 - - - - - AES: 256 Bit (default) - - - - - Twofish: 256 Bit - - - - - - - - Key Derivation Function: - - - - - - - - 0 - 0 - - - - - - - - Transform rounds: - - - - - - - - - - 150 - 0 - - - - - 150 - 16777215 - - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - 1 - - - 1000000000 - - - - - - - Qt::WheelFocus - - - Benchmark 1-second delay - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - Memory Usage: - - - - - - - - 150 - 0 - - - - - 150 - 16777215 - - - - 1 - - - 1048576 - - - - - - - Parallelism: - - - - - - - - 150 - 0 - - - - - 150 - 16777215 - - - - 1 - - - 128 - - - - - - - - diff --git a/src/gui/DatabaseTabWidget.cpp b/src/gui/DatabaseTabWidget.cpp index ce90a0d57..71bd74814 100644 --- a/src/gui/DatabaseTabWidget.cpp +++ b/src/gui/DatabaseTabWidget.cpp @@ -39,6 +39,7 @@ #include "gui/UnlockDatabaseDialog.h" #include "gui/entry/EntryView.h" #include "gui/group/GroupView.h" +#include "gui/wizard/NewDatabaseWizard.h" DatabaseManagerStruct::DatabaseManagerStruct() : dbWidget(nullptr) @@ -85,24 +86,53 @@ void DatabaseTabWidget::toggleTabbar() } } +/** + * Helper method for invoking the new database wizard. + * The user of this method MUST take ownership of the returned pointer. + * + * @return pointer to the configured new database, nullptr on failure + */ +Database* DatabaseTabWidget::execNewDatabaseWizard() +{ + // use QScopedPointer to ensure deletion after scope ends, but still parent + // it to this to make it modal and allow easier access in unit tests + QScopedPointer wizard(new NewDatabaseWizard(this)); + if (!wizard->exec()) { + return nullptr; + } + + auto* db = wizard->takeDatabase(); + if (!db) { + return nullptr; + } + Q_ASSERT(db->key()); + Q_ASSERT(db->kdf()); + if (!db->key() || !db->kdf()) { + MessageBox::critical(this, tr("Database creation error"), + tr("The created database has no key or KDF, refusing to save it.\n" + "This is definitely a bug, please report it to the developers."), + QMessageBox::Ok, QMessageBox::Ok); + return nullptr; + } + + return db; +} + void DatabaseTabWidget::newDatabase() { - DatabaseManagerStruct dbStruct; - Database* db = new Database(); - db->rootGroup()->setName(tr("Root", "Root group")); - dbStruct.dbWidget = new DatabaseWidget(db, this); - - CompositeKey emptyKey; - db->setKey(emptyKey); - - insertDatabase(db, dbStruct); - - if (!saveDatabaseAs(db)) { - closeDatabase(db); + auto* db = execNewDatabaseWizard(); + if (!db) { return; } - dbStruct.dbWidget->switchToMasterKeyChange(true); + DatabaseManagerStruct dbStruct; + dbStruct.dbWidget = new DatabaseWidget(db, this); + insertDatabase(db, dbStruct); + + if (!saveDatabaseAs(db)) { + // mark database as dirty if user canceled save dialog + emit db->modifiedImmediate(); + } } void DatabaseTabWidget::openDatabase() @@ -178,18 +208,21 @@ void DatabaseTabWidget::openDatabase(const QString& fileName, const QString& pw, void DatabaseTabWidget::importCsv() { QString filter = QString("%1 (*.csv);;%2 (*)").arg(tr("CSV file"), tr("All files")); - QString fileName = fileDialog()->getOpenFileName(this, tr("Open CSV file"), QString(), filter); + QString fileName = fileDialog()->getOpenFileName(this, tr("Select CSV file"), {}, filter); if (fileName.isEmpty()) { return; } - Database* db = new Database(); + auto* db = execNewDatabaseWizard(); + if (!db) { + return; + } + DatabaseManagerStruct dbStruct; dbStruct.dbWidget = new DatabaseWidget(db, this); - insertDatabase(db, dbStruct); - dbStruct.dbWidget->switchToImportCsv(fileName); + dbStruct.dbWidget->switchToCsvImport(fileName); } void DatabaseTabWidget::mergeDatabase() @@ -322,53 +355,53 @@ bool DatabaseTabWidget::saveDatabase(Database* db, QString filePath) return true; } - if (!dbStruct.readOnly) { - if (filePath.isEmpty()) { - filePath = dbStruct.fileInfo.canonicalFilePath(); - } + if (filePath.isEmpty()) { + filePath = dbStruct.fileInfo.canonicalFilePath(); + } - dbStruct.dbWidget->blockAutoReload(true); - // TODO: Make this async, but lock out the database widget to prevent re-entrance - bool useAtomicSaves = config()->get("UseAtomicSaves", true).toBool(); - QString errorMessage = db->saveToFile(filePath, useAtomicSaves, config()->get("BackupBeforeSave").toBool()); - dbStruct.dbWidget->blockAutoReload(false); - - if (errorMessage.isEmpty()) { - // successfully saved database file - dbStruct.modified = false; - dbStruct.saveAttempts = 0; - dbStruct.fileInfo = QFileInfo(filePath); - dbStruct.dbWidget->databaseSaved(); - updateTabName(db); - emit messageDismissTab(); - return true; - } else { - dbStruct.modified = true; - updateTabName(db); - - if (++dbStruct.saveAttempts > 2 && useAtomicSaves) { - // Saving failed 3 times, issue a warning and attempt to resolve - auto choice = MessageBox::question(this, - tr("Disable safe saves?"), - tr("KeePassXC has failed to save the database multiple times. " - "This is likely caused by file sync services holding a lock on " - "the save file.\nDisable safe saves and try again?"), - QMessageBox::Yes | QMessageBox::No, - QMessageBox::Yes); - if (choice == QMessageBox::Yes) { - config()->set("UseAtomicSaves", false); - return saveDatabase(db, filePath); - } - // Reset save attempts without changing anything - dbStruct.saveAttempts = 0; - } - - emit messageTab(tr("Writing the database failed.").append("\n").append(errorMessage), MessageWidget::Error); - return false; - } - } else { + if (dbStruct.readOnly || filePath.isEmpty()) { return saveDatabaseAs(db); } + + dbStruct.dbWidget->blockAutoReload(true); + // TODO: Make this async, but lock out the database widget to prevent re-entrance + bool useAtomicSaves = config()->get("UseAtomicSaves", true).toBool(); + QString errorMessage = db->saveToFile(filePath, useAtomicSaves, config()->get("BackupBeforeSave").toBool()); + dbStruct.dbWidget->blockAutoReload(false); + + if (errorMessage.isEmpty()) { + // successfully saved database file + dbStruct.modified = false; + dbStruct.saveAttempts = 0; + dbStruct.fileInfo = QFileInfo(filePath); + dbStruct.dbWidget->databaseSaved(); + updateTabName(db); + emit messageDismissTab(); + return true; + } else { + dbStruct.modified = true; + updateTabName(db); + + if (++dbStruct.saveAttempts > 2 && useAtomicSaves) { + // Saving failed 3 times, issue a warning and attempt to resolve + auto choice = MessageBox::question(this, + tr("Disable safe saves?"), + tr("KeePassXC has failed to save the database multiple times. " + "This is likely caused by file sync services holding a lock on " + "the save file.\nDisable safe saves and try again?"), + QMessageBox::Yes | QMessageBox::No, + QMessageBox::Yes); + if (choice == QMessageBox::Yes) { + config()->set("UseAtomicSaves", false); + return saveDatabase(db, filePath); + } + // Reset save attempts without changing anything + dbStruct.saveAttempts = 0; + } + + emit messageTab(tr("Writing the database failed.").append("\n").append(errorMessage), MessageWidget::Error); + return false; + } } bool DatabaseTabWidget::saveDatabaseAs(Database* db) diff --git a/src/gui/DatabaseTabWidget.h b/src/gui/DatabaseTabWidget.h index f4cdf2953..dcb4a62da 100644 --- a/src/gui/DatabaseTabWidget.h +++ b/src/gui/DatabaseTabWidget.h @@ -103,6 +103,7 @@ private slots: void emitDatabaseUnlockedFromDbWidgetSender(); private: + Database* execNewDatabaseWizard(); bool saveDatabase(Database* db, QString filePath = ""); bool saveDatabaseAs(Database* db); bool closeDatabase(Database* db); diff --git a/src/gui/DatabaseWidget.cpp b/src/gui/DatabaseWidget.cpp index f83211323..45fc058eb 100644 --- a/src/gui/DatabaseWidget.cpp +++ b/src/gui/DatabaseWidget.cpp @@ -39,11 +39,10 @@ #include "core/Metadata.h" #include "core/Tools.h" #include "format/KeePass2Reader.h" -#include "gui/ChangeMasterKeyWidget.h" #include "gui/Clipboard.h" #include "gui/CloneDialog.h" #include "gui/DatabaseOpenWidget.h" -#include "gui/DatabaseSettingsWidget.h" +#include "gui/dbsettings/DatabaseSettingsDialog.h" #include "gui/DetailsWidget.h" #include "gui/KeePass1OpenWidget.h" #include "gui/MessageBox.h" @@ -69,7 +68,6 @@ DatabaseWidget::DatabaseWidget(Database* db, QWidget* parent) , m_newGroup(nullptr) , m_newEntry(nullptr) , m_newParent(nullptr) - , m_importingCsv(false) { m_mainWidget = new QWidget(this); @@ -150,17 +148,10 @@ DatabaseWidget::DatabaseWidget(Database* db, QWidget* parent) m_historyEditEntryWidget = new EditEntryWidget(); m_editGroupWidget = new EditGroupWidget(); m_editGroupWidget->setObjectName("editGroupWidget"); - m_changeMasterKeyWidget = new ChangeMasterKeyWidget(); - m_changeMasterKeyWidget->setObjectName("changeMasterKeyWidget"); - m_changeMasterKeyWidget->headlineLabel()->setText(tr("Change master key")); - QFont headlineLabelFont = m_changeMasterKeyWidget->headlineLabel()->font(); - headlineLabelFont.setBold(true); - headlineLabelFont.setPointSize(headlineLabelFont.pointSize() + 2); - m_changeMasterKeyWidget->headlineLabel()->setFont(headlineLabelFont); m_csvImportWizard = new CsvImportWizard(); m_csvImportWizard->setObjectName("csvImportWizard"); - m_databaseSettingsWidget = new DatabaseSettingsWidget(); - m_databaseSettingsWidget->setObjectName("databaseSettingsWidget"); + m_databaseSettingDialog = new DatabaseSettingsDialog(); + m_databaseSettingDialog->setObjectName("databaseSettingsDialog"); m_databaseOpenWidget = new DatabaseOpenWidget(); m_databaseOpenWidget->setObjectName("databaseOpenWidget"); m_databaseOpenMergeWidget = new DatabaseOpenWidget(); @@ -174,8 +165,7 @@ DatabaseWidget::DatabaseWidget(Database* db, QWidget* parent) addWidget(m_mainWidget); addWidget(m_editEntryWidget); addWidget(m_editGroupWidget); - addWidget(m_changeMasterKeyWidget); - addWidget(m_databaseSettingsWidget); + addWidget(m_databaseSettingDialog); addWidget(m_historyEditEntryWidget); addWidget(m_databaseOpenWidget); addWidget(m_csvImportWizard); @@ -196,8 +186,7 @@ DatabaseWidget::DatabaseWidget(Database* db, QWidget* parent) connect(m_editEntryWidget, SIGNAL(historyEntryActivated(Entry*)), SLOT(switchToHistoryView(Entry*))); connect(m_historyEditEntryWidget, SIGNAL(editFinished(bool)), SLOT(switchBackToEntryEdit())); connect(m_editGroupWidget, SIGNAL(editFinished(bool)), SLOT(switchToView(bool))); - connect(m_changeMasterKeyWidget, SIGNAL(editFinished(bool)), SLOT(updateMasterKey(bool))); - connect(m_databaseSettingsWidget, SIGNAL(editFinished(bool)), SLOT(switchToView(bool))); + connect(m_databaseSettingDialog, SIGNAL(editFinished(bool)), SLOT(switchToView(bool))); connect(m_databaseOpenWidget, SIGNAL(editFinished(bool)), SLOT(openDatabase(bool))); connect(m_databaseOpenMergeWidget, SIGNAL(editFinished(bool)), SLOT(mergeDatabase(bool))); connect(m_keepass1OpenWidget, SIGNAL(editFinished(bool)), SLOT(openDatabase(bool))); @@ -810,34 +799,6 @@ void DatabaseWidget::switchToGroupEdit(Group* group, bool create) setCurrentWidget(m_editGroupWidget); } -void DatabaseWidget::updateMasterKey(bool accepted) -{ - if (m_importingCsv) { - setCurrentWidget(m_csvImportWizard); - m_csvImportWizard->keyFinished(accepted, m_changeMasterKeyWidget->newMasterKey()); - return; - } - - if (accepted) { - QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); - bool result = m_db->setKey(m_changeMasterKeyWidget->newMasterKey(), true, true); -#ifdef WITH_XC_TOUCHID - TouchID::getInstance().reset(m_filePath); -#endif - QApplication::restoreOverrideCursor(); - - if (!result) { - m_messageWidget->showMessage(tr("Unable to calculate master key"), MessageWidget::Error); - return; - } - } else if (!m_db->hasKey()) { - emit closeRequest(); - return; - } - - setCurrentWidget(m_mainWidget); -} - void DatabaseWidget::openDatabase(bool accepted) { if (accepted) { @@ -975,18 +936,16 @@ void DatabaseWidget::switchToGroupEdit() switchToGroupEdit(group, false); } -void DatabaseWidget::switchToMasterKeyChange(bool disableCancel) +void DatabaseWidget::switchToMasterKeyChange() { - m_changeMasterKeyWidget->clearForms(); - m_changeMasterKeyWidget->setCancelEnabled(!disableCancel); - setCurrentWidget(m_changeMasterKeyWidget); - m_importingCsv = false; + switchToDatabaseSettings(); + m_databaseSettingDialog->showMasterKeySettings(); } void DatabaseWidget::switchToDatabaseSettings() { - m_databaseSettingsWidget->load(m_db); - setCurrentWidget(m_databaseSettingsWidget); + m_databaseSettingDialog->load(m_db); + setCurrentWidget(m_databaseSettingDialog); } void DatabaseWidget::switchToOpenDatabase(const QString& filePath) @@ -1012,14 +971,10 @@ void DatabaseWidget::switchToOpenDatabase(const QString& filePath, const QString } } -void DatabaseWidget::switchToImportCsv(const QString& filePath) +void DatabaseWidget::switchToCsvImport(const QString& filePath) { - updateFilePath(filePath); + setCurrentWidget(m_csvImportWizard); m_csvImportWizard->load(filePath, m_db); - m_changeMasterKeyWidget->clearForms(); - m_changeMasterKeyWidget->setCancelEnabled(false); - setCurrentWidget(m_changeMasterKeyWidget); - m_importingCsv = true; } void DatabaseWidget::switchToOpenMergeDatabase(const QString& filePath) @@ -1220,6 +1175,7 @@ void DatabaseWidget::updateFilePath(const QString& filePath) m_fileWatcher.addPath(filePath); m_filePath = filePath; + m_db->setFilePath(filePath); } void DatabaseWidget::blockAutoReload(bool block) diff --git a/src/gui/DatabaseWidget.h b/src/gui/DatabaseWidget.h index 78b162c3a..896703eb6 100644 --- a/src/gui/DatabaseWidget.h +++ b/src/gui/DatabaseWidget.h @@ -31,7 +31,7 @@ class ChangeMasterKeyWidget; class DatabaseOpenWidget; -class DatabaseSettingsWidget; +class DatabaseSettingsDialog; class Database; class EditEntryWidget; class EditGroupWidget; @@ -157,11 +157,11 @@ public slots: void switchToView(bool accepted); void switchToEntryEdit(); void switchToGroupEdit(); - void switchToMasterKeyChange(bool disableCancel = false); + void switchToMasterKeyChange(); void switchToDatabaseSettings(); void switchToOpenDatabase(const QString& filePath); void switchToOpenDatabase(const QString& filePath, const QString& password, const QString& keyFile); - void switchToImportCsv(const QString& filePath); + void switchToCsvImport(const QString& filePath); void csvImportFinished(bool accepted); void switchToOpenMergeDatabase(const QString& filePath); void switchToOpenMergeDatabase(const QString& filePath, const QString& password, const QString& keyFile); @@ -195,7 +195,6 @@ private slots: void emitPressedEntry(); void emitPressedEntry(Entry* currentEntry); void emitPressedGroup(Group* currentGroup); - void updateMasterKey(bool accepted); void openDatabase(bool accepted); void mergeDatabase(bool accepted); void unlockDatabase(bool accepted); @@ -218,7 +217,7 @@ private: EditGroupWidget* m_editGroupWidget; ChangeMasterKeyWidget* m_changeMasterKeyWidget; CsvImportWizard* m_csvImportWizard; - DatabaseSettingsWidget* m_databaseSettingsWidget; + DatabaseSettingsDialog* m_databaseSettingDialog; DatabaseOpenWidget* m_databaseOpenWidget; DatabaseOpenWidget* m_databaseOpenMergeWidget; KeePass1OpenWidget* m_keepass1OpenWidget; diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index 2072fb585..f3e974724 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -58,7 +58,7 @@ #endif #include "gui/PasswordGeneratorWidget.h" -#include "gui/SettingsWidget.h" +#include "gui/ApplicationSettingsWidget.h" #include "touchid/TouchID.h" @@ -313,7 +313,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(importCsv()), SLOT(switchToImportCsv())); + connect(m_ui->welcomeWidget, SIGNAL(importCsv()), SLOT(switchToCsvImport())); connect(m_ui->actionAbout, SIGNAL(triggered()), SLOT(showAboutDialog())); connect(m_ui->actionDonate, SIGNAL(triggered()), SLOT(openDonateUrl())); @@ -690,7 +690,7 @@ void MainWindow::switchToKeePass1Database() switchToDatabases(); } -void MainWindow::switchToImportCsv() +void MainWindow::switchToCsvImport() { m_ui->tabWidget->importCsv(); switchToDatabases(); diff --git a/src/gui/MainWindow.h b/src/gui/MainWindow.h index 79fb311c0..c47700726 100644 --- a/src/gui/MainWindow.h +++ b/src/gui/MainWindow.h @@ -90,7 +90,7 @@ private slots: void switchToOpenDatabase(); void switchToDatabaseFile(QString file); void switchToKeePass1Database(); - void switchToImportCsv(); + void switchToCsvImport(); void closePasswordGen(); void databaseStatusChanged(DatabaseWidget* dbWidget); void databaseTabChanged(int tabIndex); diff --git a/src/gui/MainWindow.ui b/src/gui/MainWindow.ui index 4acb0a534..78d5178ba 100644 --- a/src/gui/MainWindow.ui +++ b/src/gui/MainWindow.ui @@ -116,7 +116,7 @@ 0 - +
@@ -629,9 +629,9 @@ 1 - SettingsWidget + ApplicationSettingsWidget QWidget -
gui/SettingsWidget.h
+
gui/ApplicationSettingsWidget.h
1
diff --git a/src/gui/PasswordGeneratorWidget.cpp b/src/gui/PasswordGeneratorWidget.cpp index fed34754b..d594cba8e 100644 --- a/src/gui/PasswordGeneratorWidget.cpp +++ b/src/gui/PasswordGeneratorWidget.cpp @@ -41,7 +41,7 @@ PasswordGeneratorWidget::PasswordGeneratorWidget(QWidget* parent) connect(m_ui->editNewPassword, SIGNAL(textChanged(QString)), SLOT(updateButtonsEnabled(QString))); connect(m_ui->editNewPassword, SIGNAL(textChanged(QString)), SLOT(updatePasswordStrength(QString))); - connect(m_ui->togglePasswordButton, SIGNAL(toggled(bool)), SLOT(togglePasswordShown(bool))); + connect(m_ui->togglePasswordButton, SIGNAL(toggled(bool)), SLOT(setPasswordVisible(bool))); connect(m_ui->buttonSimpleMode, SIGNAL(clicked()), SLOT(selectSimpleMode())); connect(m_ui->buttonAdvancedMode, SIGNAL(clicked()), SLOT(selectAdvancedMode())); connect(m_ui->buttonAddHex, SIGNAL(clicked()), SLOT(excludeHexChars())); @@ -183,7 +183,7 @@ void PasswordGeneratorWidget::reset() { m_ui->editNewPassword->setText(""); setStandaloneMode(false); - togglePasswordShown(config()->get("security/passwordscleartext").toBool()); + setPasswordVisible(config()->get("security/passwordscleartext").toBool()); updateGenerator(); } @@ -192,9 +192,9 @@ void PasswordGeneratorWidget::setStandaloneMode(bool standalone) m_standalone = standalone; if (standalone) { m_ui->buttonApply->setText(tr("Close")); - togglePasswordShown(true); + setPasswordVisible(true); } else { - m_ui->buttonApply->setText(tr("Apply")); + m_ui->buttonApply->setText(tr("Accept")); } } @@ -205,7 +205,7 @@ QString PasswordGeneratorWidget::getGeneratedPassword() void PasswordGeneratorWidget::keyPressEvent(QKeyEvent* e) { - if (e->key() == Qt::Key_Escape && m_standalone == true) { + if (e->key() == Qt::Key_Escape && m_standalone) { emit dialogTerminated(); } else { e->ignore(); @@ -309,14 +309,19 @@ void PasswordGeneratorWidget::dicewareSpinBoxChanged() updateGenerator(); } -void PasswordGeneratorWidget::togglePasswordShown(bool showing) +void PasswordGeneratorWidget::setPasswordVisible(bool visible) { - m_ui->editNewPassword->setShowPassword(showing); + m_ui->editNewPassword->setShowPassword(visible); bool blockSignals = m_ui->togglePasswordButton->blockSignals(true); - m_ui->togglePasswordButton->setChecked(showing); + m_ui->togglePasswordButton->setChecked(visible); m_ui->togglePasswordButton->blockSignals(blockSignals); } +bool PasswordGeneratorWidget::isPasswordVisible() const +{ + return m_ui->togglePasswordButton->isChecked(); +} + void PasswordGeneratorWidget::selectSimpleMode() { m_ui->advancedBar->hide(); diff --git a/src/gui/PasswordGeneratorWidget.h b/src/gui/PasswordGeneratorWidget.h index 5b65d7d22..0607f0fe8 100644 --- a/src/gui/PasswordGeneratorWidget.h +++ b/src/gui/PasswordGeneratorWidget.h @@ -51,11 +51,13 @@ public: void reset(); void setStandaloneMode(bool standalone); QString getGeneratedPassword(); + bool isPasswordVisible() const; public slots: void regeneratePassword(); void applyPassword(); void copyPassword(); + void setPasswordVisible(bool visible); signals: void appliedPassword(const QString& password); @@ -64,7 +66,6 @@ signals: private slots: void updateButtonsEnabled(const QString& password); void updatePasswordStrength(const QString& password); - void togglePasswordShown(bool hidden); void selectSimpleMode(); void selectAdvancedMode(); void excludeHexChars(); diff --git a/src/gui/PasswordGeneratorWidget.ui b/src/gui/PasswordGeneratorWidget.ui index e196ff89f..ada94d653 100644 --- a/src/gui/PasswordGeneratorWidget.ui +++ b/src/gui/PasswordGeneratorWidget.ui @@ -6,8 +6,8 @@ 0 0 - 571 - 394 + 716 + 468 @@ -19,12 +19,15 @@ - + QLayout::SetMinimumSize + + QLayout::SetMinimumSize + 0 @@ -173,8 +176,17 @@ QProgressBar::chunk { + + QLayout::SetMinimumSize + + + + 0 + 0 + + QTabWidget::North @@ -191,12 +203,24 @@ QProgressBar::chunk { + + QLayout::SetMinimumSize + + + + 580 + 0 + + Character Types + + QLayout::SetMinimumSize + @@ -214,6 +238,9 @@ QProgressBar::chunk { + + QLayout::SetMinimumSize + @@ -405,6 +432,9 @@ QProgressBar::chunk { + + QLayout::SetMinimumSize + @@ -459,6 +489,9 @@ QProgressBar::chunk { + + QLayout::SetMinimumSize + @@ -513,6 +546,9 @@ QProgressBar::chunk { + + QLayout::SetMinimumSize + @@ -567,6 +603,9 @@ QProgressBar::chunk { + + QLayout::SetMinimumSize + @@ -621,6 +660,9 @@ QProgressBar::chunk { + + QLayout::SetMinimumSize + @@ -688,6 +730,9 @@ QProgressBar::chunk { + + QLayout::SetMinimumSize + @@ -818,6 +863,9 @@ QProgressBar::chunk { 15 + + QLayout::SetMinimumSize + 6 @@ -905,7 +953,7 @@ QProgressBar::chunk { - Word Count: + Word Co&unt: spinBoxLength @@ -1010,7 +1058,7 @@ QProgressBar::chunk { - Generate + Regenerate @@ -1035,6 +1083,19 @@ QProgressBar::chunk { + + + + Qt::Vertical + + + + 0 + 0 + + + + diff --git a/src/gui/csvImport/CsvImportWizard.cpp b/src/gui/csvImport/CsvImportWizard.cpp index d1d152154..9b255f941 100644 --- a/src/gui/csvImport/CsvImportWizard.cpp +++ b/src/gui/csvImport/CsvImportWizard.cpp @@ -42,22 +42,6 @@ void CsvImportWizard::load(const QString& filename, Database* database) m_parse->load(filename, database); } -void CsvImportWizard::keyFinished(bool accepted, CompositeKey key) -{ - if (!accepted) { - emit importFinished(false); - return; - } - - QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); - bool result = m_db->setKey(key); - QApplication::restoreOverrideCursor(); - - if (!result) { - MessageBox::critical(this, tr("Error"), tr("Unable to calculate master key")); - emit importFinished(false); - } -} void CsvImportWizard::parseFinished(bool accepted) { diff --git a/src/gui/csvImport/CsvImportWizard.h b/src/gui/csvImport/CsvImportWizard.h index 9c11e9d11..9f7bb720c 100644 --- a/src/gui/csvImport/CsvImportWizard.h +++ b/src/gui/csvImport/CsvImportWizard.h @@ -25,7 +25,6 @@ #include #include "core/Database.h" -#include "gui/ChangeMasterKeyWidget.h" #include "gui/DialogyWidget.h" class CsvImportWidget; @@ -36,9 +35,8 @@ class CsvImportWizard : public DialogyWidget public: explicit CsvImportWizard(QWidget* parent = nullptr); - ~CsvImportWizard(); + ~CsvImportWizard() override; void load(const QString& filename, Database* database); - void keyFinished(bool accepted, CompositeKey key); signals: void importFinished(bool accepted); @@ -47,7 +45,7 @@ private slots: void parseFinished(bool accepted); private: - Database* m_db; + QPointer m_db; CsvImportWidget* m_parse; QGridLayout* m_layout; }; diff --git a/src/gui/dbsettings/DatabaseSettingsDialog.cpp b/src/gui/dbsettings/DatabaseSettingsDialog.cpp new file mode 100644 index 000000000..b2624a425 --- /dev/null +++ b/src/gui/dbsettings/DatabaseSettingsDialog.cpp @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2018 KeePassXC Team + * Copyright (C) 2012 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 "DatabaseSettingsDialog.h" +#include "ui_DatabaseSettingsDialog.h" +#include "DatabaseSettingsWidgetGeneral.h" +#include "DatabaseSettingsWidgetEncryption.h" +#include "DatabaseSettingsWidgetMasterKey.h" + +#include "core/Config.h" +#include "core/FilePath.h" +#include "core/Database.h" + +DatabaseSettingsDialog::DatabaseSettingsDialog(QWidget* parent) + : DialogyWidget(parent) + , m_ui(new Ui::DatabaseSettingsDialog()) + , m_generalWidget(new DatabaseSettingsWidgetGeneral(this)) + , m_securityTabWidget(new QTabWidget(this)) + , m_masterKeyWidget(new DatabaseSettingsWidgetMasterKey(this)) + , m_encryptionWidget(new DatabaseSettingsWidgetEncryption(this)) +{ + m_ui->setupUi(this); + + connect(m_ui->buttonBox, SIGNAL(accepted()), SLOT(save())); + connect(m_ui->buttonBox, SIGNAL(rejected()), SLOT(reject())); + + m_ui->categoryList->addCategory(tr("General"), FilePath::instance()->icon("categories", "preferences-other")); + m_ui->categoryList->addCategory(tr("Security"), FilePath::instance()->icon("actions", "document-encrypt")); + m_ui->stackedWidget->addWidget(m_generalWidget); + + m_ui->stackedWidget->addWidget(m_securityTabWidget); + m_securityTabWidget->addTab(m_masterKeyWidget, tr("Master Key")); + m_securityTabWidget->addTab(m_encryptionWidget, tr("Encryption Settings")); + + m_ui->stackedWidget->setCurrentIndex(0); + m_securityTabWidget->setCurrentIndex(0); + + connect(m_securityTabWidget, SIGNAL(currentChanged(int)), SLOT(pageChanged())); + connect(m_ui->categoryList, SIGNAL(categoryChanged(int)), m_ui->stackedWidget, SLOT(setCurrentIndex(int))); + connect(m_ui->advancedSettingsToggle, SIGNAL(toggled(bool)), SLOT(toggleAdvancedMode(bool))); + + pageChanged(); +} + +DatabaseSettingsDialog::~DatabaseSettingsDialog() +{ +} + +void DatabaseSettingsDialog::load(Database* db) +{ + m_ui->categoryList->setCurrentCategory(0); + m_generalWidget->load(db); + m_masterKeyWidget->load(db); + m_encryptionWidget->load(db); + m_ui->advancedSettingsToggle->setChecked(config()->get("GUI/AdvancedSettings", false).toBool()); + m_db = db; +} + +/** + * Show page and tab with database master key settings. + */ +void DatabaseSettingsDialog::showMasterKeySettings() +{ + m_ui->categoryList->setCurrentCategory(1); + m_securityTabWidget->setCurrentIndex(0); +} + +void DatabaseSettingsDialog::save() +{ + if (!m_generalWidget->save()) { + return; + } + + if (!m_masterKeyWidget->save()) { + return; + } + + if (!m_encryptionWidget->save()) { + return; + } + +#ifdef WITH_XC_TOUCHID + TouchID::getInstance().reset(m_db ? m_db->filePath() : ""); +#endif + + emit editFinished(true); +} + +void DatabaseSettingsDialog::reject() +{ + emit editFinished(false); +} + +void DatabaseSettingsDialog::pageChanged() +{ + int pageIndex = m_ui->stackedWidget->currentIndex(); + + bool enabled = (pageIndex == Page::General && m_generalWidget->hasAdvancedMode()); + + if (Page::Security == pageIndex) { + int tabIndex = m_securityTabWidget->currentIndex(); + enabled = (tabIndex == 0 && m_masterKeyWidget->hasAdvancedMode()); + enabled |= (tabIndex == 1 && m_encryptionWidget->hasAdvancedMode()); + } + + m_ui->advancedSettingsToggle->setEnabled(enabled); +} + +void DatabaseSettingsDialog::toggleAdvancedMode(bool advanced) +{ + if (m_generalWidget->hasAdvancedMode()) { + m_generalWidget->setAdvancedMode(advanced); + } + + if (m_masterKeyWidget->hasAdvancedMode()) { + m_masterKeyWidget->setAdvancedMode(advanced); + } + + if (m_encryptionWidget->hasAdvancedMode()) { + m_encryptionWidget->setAdvancedMode(advanced); + } + + config()->set("GUI/AdvancedSettings", advanced); +} diff --git a/src/gui/DatabaseSettingsWidget.h b/src/gui/dbsettings/DatabaseSettingsDialog.h similarity index 55% rename from src/gui/DatabaseSettingsWidget.h rename to src/gui/dbsettings/DatabaseSettingsDialog.h index 5abad8e05..50fec32d6 100644 --- a/src/gui/DatabaseSettingsWidget.h +++ b/src/gui/dbsettings/DatabaseSettingsDialog.h @@ -18,33 +18,33 @@ #ifndef KEEPASSX_DATABASESETTINGSWIDGET_H #define KEEPASSX_DATABASESETTINGSWIDGET_H -#include -#include -#include -#include - -#include "crypto/kdf/Kdf.h" #include "gui/DialogyWidget.h" +#include +#include + class Database; +class DatabaseSettingsWidgetGeneral; +class DatabaseSettingsWidgetEncryption; +class DatabaseSettingsWidgetMasterKey; +class QTabWidget; namespace Ui { - class DatabaseSettingsWidget; - class DatabaseSettingsWidgetGeneral; - class DatabaseSettingsWidgetEncryption; + class DatabaseSettingsDialog; } -class DatabaseSettingsWidget : public DialogyWidget +class DatabaseSettingsDialog : public DialogyWidget { Q_OBJECT public: - explicit DatabaseSettingsWidget(QWidget* parent = nullptr); - ~DatabaseSettingsWidget(); - Q_DISABLE_COPY(DatabaseSettingsWidget) + explicit DatabaseSettingsDialog(QWidget* parent = nullptr); + ~DatabaseSettingsDialog() override; + Q_DISABLE_COPY(DatabaseSettingsDialog); void load(Database* db); + void showMasterKeySettings(); signals: void editFinished(bool accepted); @@ -52,20 +52,22 @@ signals: private slots: void save(); void reject(); - void transformRoundsBenchmark(); - void kdfChanged(int index); - void memoryChanged(int value); - void parallelismChanged(int value); + void pageChanged(); + void toggleAdvancedMode(bool advanced); private: - void truncateHistories(); + enum Page + { + General = 0, + Security = 1 + }; - const QScopedPointer m_ui; - const QScopedPointer m_uiGeneral; - const QScopedPointer m_uiEncryption; - QWidget* m_uiGeneralPage; - QWidget* m_uiEncryptionPage; - Database* m_db; + QPointer m_db; + const QScopedPointer m_ui; + QPointer m_generalWidget; + QPointer m_securityTabWidget; + QPointer m_masterKeyWidget; + QPointer m_encryptionWidget; }; #endif // KEEPASSX_DATABASESETTINGSWIDGET_H diff --git a/src/gui/DatabaseSettingsWidget.ui b/src/gui/dbsettings/DatabaseSettingsDialog.ui similarity index 78% rename from src/gui/DatabaseSettingsWidget.ui rename to src/gui/dbsettings/DatabaseSettingsDialog.ui index 9b45feaa7..1e10e4f1d 100644 --- a/src/gui/DatabaseSettingsWidget.ui +++ b/src/gui/dbsettings/DatabaseSettingsDialog.ui @@ -1,15 +1,7 @@ - DatabaseSettingsWidget - - - - 0 - 0 - 1082 - 578 - - + DatabaseSettingsDialog + @@ -27,6 +19,13 @@ + + + + Advanced Settings + + + @@ -48,4 +47,4 @@ -
\ No newline at end of file +
diff --git a/src/gui/dbsettings/DatabaseSettingsWidget.cpp b/src/gui/dbsettings/DatabaseSettingsWidget.cpp new file mode 100644 index 000000000..992101fd4 --- /dev/null +++ b/src/gui/dbsettings/DatabaseSettingsWidget.cpp @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2018 KeePassXC Team + * + * 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 "DatabaseSettingsWidget.h" +#include "core/Database.h" + +#include +#include + +DatabaseSettingsWidget::DatabaseSettingsWidget(QWidget* parent) + : SettingsWidget(parent) +{ +} + +DatabaseSettingsWidget::~DatabaseSettingsWidget() +{ +} + +/** + * Load the database to be configured by this page and initialize the page. + * The page will NOT take ownership of the database. + * + * @param db database object to be configured + */ +void DatabaseSettingsWidget::load(Database* db) +{ + m_db = db; + initialize(); +} diff --git a/src/gui/dbsettings/DatabaseSettingsWidget.h b/src/gui/dbsettings/DatabaseSettingsWidget.h new file mode 100644 index 000000000..61082d5c7 --- /dev/null +++ b/src/gui/dbsettings/DatabaseSettingsWidget.h @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2018 KeePassXC Team + * + * 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 KEEPASSXC_DATABASESETTINGSWIDGET_H +#define KEEPASSXC_DATABASESETTINGSWIDGET_H + +#include "gui/settings/SettingsWidget.h" + +#include + +class Database; + +/** + * Pure-virtual base class for KeePassXC database settings widgets. + */ +class DatabaseSettingsWidget : public SettingsWidget +{ + Q_OBJECT + +public: + explicit DatabaseSettingsWidget(QWidget* parent = nullptr); + Q_DISABLE_COPY(DatabaseSettingsWidget); + ~DatabaseSettingsWidget() override; + + virtual void load(Database* db); + +signals: + /** + * Can be emitted to indicate size changes and allow parents widgets to adjust properly. + */ + void sizeChanged(); + +protected: + QPointer m_db; +}; + +#endif //KEEPASSXC_DATABASESETTINGSWIDGET_H diff --git a/src/gui/dbsettings/DatabaseSettingsWidgetEncryption.cpp b/src/gui/dbsettings/DatabaseSettingsWidgetEncryption.cpp new file mode 100644 index 000000000..2a3cf7cbb --- /dev/null +++ b/src/gui/dbsettings/DatabaseSettingsWidgetEncryption.cpp @@ -0,0 +1,410 @@ +/* + * Copyright (C) 2018 KeePassXC Team + * + * 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 "DatabaseSettingsWidgetEncryption.h" +#include "ui_DatabaseSettingsWidgetEncryption.h" +#include "core/Database.h" +#include "core/Metadata.h" +#include "core/Global.h" +#include "core/AsyncTask.h" +#include "gui/MessageBox.h" +#include "crypto/kdf/Argon2Kdf.h" +#include "format/KeePass2.h" + +#include +#include + +const char* DatabaseSettingsWidgetEncryption::CD_DECRYPTION_TIME_PREFERENCE_KEY = "KPXC_DECRYPTION_TIME_PREFERENCE"; + +DatabaseSettingsWidgetEncryption::DatabaseSettingsWidgetEncryption(QWidget* parent) + : DatabaseSettingsWidget(parent) + , m_ui(new Ui::DatabaseSettingsWidgetEncryption()) +{ + m_ui->setupUi(this); + + connect(m_ui->transformBenchmarkButton, SIGNAL(clicked()), SLOT(benchmarkTransformRounds())); + connect(m_ui->kdfComboBox, SIGNAL(currentIndexChanged(int)), SLOT(changeKdf(int))); + + connect(m_ui->memorySpinBox, SIGNAL(valueChanged(int)), this, SLOT(memoryChanged(int))); + connect(m_ui->parallelismSpinBox, SIGNAL(valueChanged(int)), this, SLOT(parallelismChanged(int))); + + m_ui->compatibilitySelection->addItem(tr("KDBX 4.0 (recommended)"), KeePass2::KDF_ARGON2.toByteArray()); + m_ui->compatibilitySelection->addItem(tr("KDBX 3.1"), KeePass2::KDF_AES_KDBX3.toByteArray()); + m_ui->decryptionTimeSlider->setValue(10); + updateDecryptionTime(m_ui->decryptionTimeSlider->value()); + + connect(m_ui->activateChangeDecryptionTimeButton, SIGNAL(clicked()), SLOT(activateChangeDecryptionTime())); + connect(m_ui->decryptionTimeSlider, SIGNAL(valueChanged(int)), SLOT(updateDecryptionTime(int))); + connect(m_ui->compatibilitySelection, SIGNAL(currentIndexChanged(int)), SLOT(updateFormatCompatibility(int))); + + // conditions under which a key re-transformation is needed + connect(m_ui->decryptionTimeSlider, SIGNAL(valueChanged(int)), SLOT(markDirty())); + connect(m_ui->compatibilitySelection, SIGNAL(currentIndexChanged(int)), SLOT(markDirty())); + connect(m_ui->activateChangeDecryptionTimeButton, SIGNAL(clicked()), SLOT(markDirty())); + connect(m_ui->algorithmComboBox, SIGNAL(currentIndexChanged(int)), SLOT(markDirty())); + connect(m_ui->kdfComboBox, SIGNAL(currentIndexChanged(int)), SLOT(markDirty())); + connect(m_ui->transformRoundsSpinBox, SIGNAL(valueChanged(int)), SLOT(markDirty())); + connect(m_ui->memorySpinBox, SIGNAL(valueChanged(int)), SLOT(markDirty())); + connect(m_ui->parallelismSpinBox, SIGNAL(valueChanged(int)), SLOT(markDirty())); +} + +DatabaseSettingsWidgetEncryption::~DatabaseSettingsWidgetEncryption() +{ +} + +void DatabaseSettingsWidgetEncryption::initialize() +{ + Q_ASSERT(m_db); + if (!m_db) { + return; + } + + bool isDirty = false; + + if (!m_db->kdf()) { + m_db->setKdf(KeePass2::uuidToKdf(KeePass2::KDF_ARGON2)); + isDirty = true; + } + if (!m_db->key()) { + m_db->setKey(QSharedPointer::create()); + m_db->setCipher(KeePass2::CIPHER_AES); + isDirty = true; + } + + // check if the DB's custom data has a decryption time setting stored + // and set the slider to it, otherwise just state that the time is unchanged + // (we cannot infer the time from the raw KDF settings) + auto* cd = m_db->metadata()->customData(); + if (cd->hasKey(CD_DECRYPTION_TIME_PREFERENCE_KEY)) { + int decryptionTime = qMax(100, cd->value(CD_DECRYPTION_TIME_PREFERENCE_KEY).toInt()); + bool block = m_ui->decryptionTimeSlider->blockSignals(true); + m_ui->decryptionTimeSlider->setValue(decryptionTime / 100); + updateDecryptionTime(decryptionTime / 100); + m_ui->decryptionTimeSlider->blockSignals(block); + m_ui->activateChangeDecryptionTimeButton->setVisible(false); + } else { + m_ui->decryptionTimeSettings->setVisible(isDirty); + m_ui->activateChangeDecryptionTimeButton->setVisible(!isDirty); + if (!isDirty) { + m_ui->decryptionTimeValueLabel->setText(tr("unchanged", "Database decryption time is unchanged")); + } + } + + updateFormatCompatibility(m_db->kdf()->uuid() == KeePass2::KDF_AES_KDBX3 ? KDBX3 : KDBX4, isDirty); + setupAlgorithmComboBox(); + setupKdfComboBox(); + loadKdfParameters(); + + m_isDirty = isDirty; +} + +void DatabaseSettingsWidgetEncryption::uninitialize() +{ +} + +void DatabaseSettingsWidgetEncryption::showEvent(QShowEvent* event) +{ + QWidget::showEvent(event); + m_ui->decryptionTimeSlider->setFocus(); +} + +void DatabaseSettingsWidgetEncryption::setupAlgorithmComboBox() +{ + m_ui->algorithmComboBox->clear(); + for (auto& cipher : asConst(KeePass2::CIPHERS)) { + m_ui->algorithmComboBox->addItem(QCoreApplication::translate("KeePass2", cipher.second.toUtf8()), + cipher.first.toByteArray()); + } + int cipherIndex = m_ui->algorithmComboBox->findData(m_db->cipher().toByteArray()); + if (cipherIndex > -1) { + m_ui->algorithmComboBox->setCurrentIndex(cipherIndex); + } +} + +void DatabaseSettingsWidgetEncryption::setupKdfComboBox() +{ + // Setup kdf combo box + bool block = m_ui->kdfComboBox->blockSignals(true); + m_ui->kdfComboBox->clear(); + for (auto& kdf : asConst(KeePass2::KDFS)) { + m_ui->kdfComboBox->addItem(QCoreApplication::translate("KeePass2", kdf.second.toUtf8()), + kdf.first.toByteArray()); + } + m_ui->kdfComboBox->blockSignals(block); +} + +void DatabaseSettingsWidgetEncryption::loadKdfParameters() +{ + Q_ASSERT(m_db); + if (!m_db) { + return; + } + + auto kdf = m_db->kdf(); + Q_ASSERT(kdf); + if (!kdf) { + return; + } + + int kdfIndex = m_ui->kdfComboBox->findData(m_db->kdf()->uuid().toByteArray()); + if (kdfIndex > -1) { + bool block = m_ui->kdfComboBox->blockSignals(true); + m_ui->kdfComboBox->setCurrentIndex(kdfIndex); + m_ui->kdfComboBox->blockSignals(block); + } + + m_ui->transformRoundsSpinBox->setValue(kdf->rounds()); + if (m_db->kdf()->uuid() == KeePass2::KDF_ARGON2) { + auto argon2Kdf = kdf.staticCast(); + m_ui->memorySpinBox->setValue(static_cast(argon2Kdf->memory()) / (1 << 10)); + m_ui->parallelismSpinBox->setValue(argon2Kdf->parallelism()); + } + + updateKdfFields(); +} + +void DatabaseSettingsWidgetEncryption::updateKdfFields() +{ + QUuid id = m_db->kdf()->uuid(); + + bool memoryVisible = (id == KeePass2::KDF_ARGON2); + m_ui->memoryUsageLabel->setVisible(memoryVisible); + m_ui->memorySpinBox->setVisible(memoryVisible); + + bool parallelismVisible = (id == KeePass2::KDF_ARGON2); + m_ui->parallelismLabel->setVisible(parallelismVisible); + m_ui->parallelismSpinBox->setVisible(parallelismVisible); +} + +void DatabaseSettingsWidgetEncryption::activateChangeDecryptionTime() +{ + m_ui->decryptionTimeSettings->setVisible(true); + m_ui->activateChangeDecryptionTimeButton->setVisible(false); + updateDecryptionTime(m_ui->decryptionTimeSlider->value()); +} + +void DatabaseSettingsWidgetEncryption::markDirty() +{ + m_isDirty = true; +} + +bool DatabaseSettingsWidgetEncryption::save() +{ + Q_ASSERT(m_db); + if (!m_db) { + return false; + } + + if (m_db->key() && !m_db->key()->keys().isEmpty() && !m_isDirty) { + // nothing has changed, don't re-transform + return true; + } + + auto kdf = m_db->kdf(); + Q_ASSERT(kdf); + + if (!advancedMode()) { + if (kdf && !m_isDirty && !m_ui->decryptionTimeSettings->isVisible()) { + return true; + } + + int time = m_ui->decryptionTimeSlider->value() * 100; + updateFormatCompatibility(m_ui->compatibilitySelection->currentIndex(), false); + + QApplication::setOverrideCursor(Qt::BusyCursor); + + int rounds = AsyncTask::runAndWaitForFuture([&kdf, time]() { return kdf->benchmark(time); }); + kdf->setRounds(rounds); + + // TODO: we should probably use AsyncTask::runAndWaitForFuture() here, + // but not without making Database thread-safe + bool ok = m_db->changeKdf(kdf); + + QApplication::restoreOverrideCursor(); + + m_db->metadata()->customData()->set(CD_DECRYPTION_TIME_PREFERENCE_KEY, QString("%1").arg(time)); + + return ok; + } + + // remove a stored decryption time from custom data when advanced settings are used + // we don't know it until we actually run the KDF + m_db->metadata()->customData()->remove(CD_DECRYPTION_TIME_PREFERENCE_KEY); + + // first perform safety check for KDF rounds + if (kdf->uuid() == KeePass2::KDF_ARGON2 && m_ui->transformRoundsSpinBox->value() > 10000) { + QMessageBox warning; + warning.setIcon(QMessageBox::Warning); + warning.setWindowTitle(tr("Number of rounds too high", "Key transformation rounds")); + warning.setText(tr("You are using a very high number of key transform rounds with Argon2.\n\n" + "If you keep this number, your database may take hours or days (or even longer) to open!")); + auto ok = warning.addButton(tr("Understood, keep number"), QMessageBox::ButtonRole::AcceptRole); + auto cancel = warning.addButton(tr("Cancel"), QMessageBox::ButtonRole::RejectRole); + warning.setDefaultButton(cancel); + warning.exec(); + if (warning.clickedButton() != ok) { + return false; + } + } else if ((kdf->uuid() == KeePass2::KDF_AES_KDBX3 || kdf->uuid() == KeePass2::KDF_AES_KDBX4) + && m_ui->transformRoundsSpinBox->value() < 100000) { + QMessageBox warning; + warning.setIcon(QMessageBox::Warning); + warning.setWindowTitle(tr("Number of rounds too low", "Key transformation rounds")); + warning.setText(tr("You are using a very low number of key transform rounds with AES-KDF.\n\n" + "If you keep this number, your database may be too easy to crack!")); + auto ok = warning.addButton(tr("Understood, keep number"), QMessageBox::ButtonRole::AcceptRole); + auto cancel = warning.addButton(tr("Cancel"), QMessageBox::ButtonRole::RejectRole); + warning.setDefaultButton(cancel); + warning.exec(); + if (warning.clickedButton() != ok) { + return false; + } + } + + m_db->setCipher(QUuid(m_ui->algorithmComboBox->currentData().toByteArray())); + + // Save kdf parameters + kdf->setRounds(m_ui->transformRoundsSpinBox->value()); + if (kdf->uuid() == KeePass2::KDF_ARGON2) { + auto argon2Kdf = kdf.staticCast(); + argon2Kdf->setMemory(static_cast(m_ui->memorySpinBox->value()) * (1 << 10)); + argon2Kdf->setParallelism(static_cast(m_ui->parallelismSpinBox->value())); + } + + QApplication::setOverrideCursor(Qt::WaitCursor); + // TODO: we should probably use AsyncTask::runAndWaitForFuture() here, + // but not without making Database thread-safe + bool ok = m_db->changeKdf(kdf); + QApplication::restoreOverrideCursor(); + + if (!ok) { + MessageBox::warning(this, + tr("KDF unchanged"), + tr("Failed to transform key with new KDF parameters; KDF unchanged."), + QMessageBox::Ok); + } + + return ok; +} + +void DatabaseSettingsWidgetEncryption::benchmarkTransformRounds(int millisecs) +{ + QApplication::setOverrideCursor(Qt::BusyCursor); + m_ui->transformBenchmarkButton->setEnabled(false); + m_ui->transformRoundsSpinBox->setFocus(); + + // Create a new kdf with the current parameters + auto kdf = KeePass2::uuidToKdf(QUuid(m_ui->kdfComboBox->currentData().toByteArray())); + kdf->setRounds(m_ui->transformRoundsSpinBox->value()); + if (kdf->uuid() == KeePass2::KDF_ARGON2) { + auto argon2Kdf = kdf.staticCast(); + if (!argon2Kdf->setMemory(static_cast(m_ui->memorySpinBox->value()) * (1 << 10))) { + m_ui->memorySpinBox->setValue(static_cast(argon2Kdf->memory() / (1 << 10))); + } + if (!argon2Kdf->setParallelism(static_cast(m_ui->parallelismSpinBox->value()))) { + m_ui->parallelismSpinBox->setValue(argon2Kdf->parallelism()); + } + } + + // Determine the number of rounds required to meet 1 second delay + int rounds = AsyncTask::runAndWaitForFuture([&kdf, millisecs]() { return kdf->benchmark(millisecs); }); + + m_ui->transformRoundsSpinBox->setValue(rounds); + m_ui->transformBenchmarkButton->setEnabled(true); + m_ui->decryptionTimeSlider->setValue(millisecs / 100); + QApplication::restoreOverrideCursor(); +} + +void DatabaseSettingsWidgetEncryption::changeKdf(int index) +{ + Q_ASSERT(m_db); + if (!m_db) { + return; + } + + QUuid id(m_ui->kdfComboBox->itemData(index).toByteArray()); + m_db->setKdf(KeePass2::uuidToKdf(id)); + updateKdfFields(); + activateChangeDecryptionTime(); + benchmarkTransformRounds(); +} + +/** + * Update memory spin box suffix on value change. + */ +void DatabaseSettingsWidgetEncryption::memoryChanged(int value) +{ + m_ui->memorySpinBox->setSuffix(tr(" MiB", "Abbreviation for Mebibytes (KDF settings)", value)); +} + +/** + * Update parallelism spin box suffix on value change. + */ +void DatabaseSettingsWidgetEncryption::parallelismChanged(int value) +{ + m_ui->parallelismSpinBox->setSuffix(tr(" thread(s)", "Threads for parallel execution (KDF settings)", value)); +} + +void DatabaseSettingsWidgetEncryption::setAdvancedMode(bool advanced) +{ + DatabaseSettingsWidget::setAdvancedMode(advanced); + + if (advanced) { + loadKdfParameters(); + m_ui->stackedWidget->setCurrentIndex(1); + } else { + m_ui->compatibilitySelection->setCurrentIndex(m_db->kdf()->uuid() == KeePass2::KDF_AES_KDBX3 ? KDBX3 : KDBX4); + m_ui->stackedWidget->setCurrentIndex(0); + } +} + +void DatabaseSettingsWidgetEncryption::updateDecryptionTime(int value) +{ + if (value < 10) { + m_ui->decryptionTimeValueLabel->setText(tr("%1 ms", "milliseconds", value * 100).arg(value * 100)); + } else { + m_ui->decryptionTimeValueLabel->setText(tr("%1 s", "seconds", value / 10).arg(value / 10.0, 0, 'f', 1)); + } +} + +void DatabaseSettingsWidgetEncryption::updateFormatCompatibility(int index, bool retransform) +{ + Q_ASSERT(m_db); + if (!m_db) { + return; + } + + if (m_ui->compatibilitySelection->currentIndex() != index) { + bool block = m_ui->compatibilitySelection->blockSignals(true); + m_ui->compatibilitySelection->setCurrentIndex(index); + m_ui->compatibilitySelection->blockSignals(block); + } + + if (retransform) { + QUuid kdfUuid(m_ui->compatibilitySelection->itemData(index).toByteArray()); + auto kdf = KeePass2::uuidToKdf(kdfUuid); + m_db->setKdf(kdf); + + if (kdf->uuid() == KeePass2::KDF_ARGON2) { + auto argon2Kdf = kdf.staticCast(); + argon2Kdf->setMemory(128 * 1024); + argon2Kdf->setParallelism(static_cast(QThread::idealThreadCount())); + } + + activateChangeDecryptionTime(); + } +} diff --git a/src/gui/dbsettings/DatabaseSettingsWidgetEncryption.h b/src/gui/dbsettings/DatabaseSettingsWidgetEncryption.h new file mode 100644 index 000000000..8b1e42e13 --- /dev/null +++ b/src/gui/dbsettings/DatabaseSettingsWidgetEncryption.h @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2018 KeePassXC Team + * + * 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 KEEPASSXC_DATABASESETTINGSWIDGETENCRYPTION_H +#define KEEPASSXC_DATABASESETTINGSWIDGETENCRYPTION_H + +#include "DatabaseSettingsWidget.h" + +#include +#include + +class Database; +namespace Ui +{ +class DatabaseSettingsWidgetEncryption; +} + +class DatabaseSettingsWidgetEncryption: public DatabaseSettingsWidget +{ + Q_OBJECT + +public: + explicit DatabaseSettingsWidgetEncryption(QWidget* parent = nullptr); + Q_DISABLE_COPY(DatabaseSettingsWidgetEncryption); + ~DatabaseSettingsWidgetEncryption() override; + + inline bool hasAdvancedMode() const override { return true; } + void setAdvancedMode(bool advanced) override; + +public slots: + void initialize() override; + void uninitialize() override; + bool save() override; + +protected: + void showEvent(QShowEvent* event) override; + +private slots: + void benchmarkTransformRounds(int millisecs = 1000); + void changeKdf(int index); + void memoryChanged(int value); + void parallelismChanged(int value); + void updateDecryptionTime(int value); + void updateFormatCompatibility(int index, bool retransform = true); + void setupAlgorithmComboBox(); + void setupKdfComboBox(); + void loadKdfParameters(); + void updateKdfFields(); + void activateChangeDecryptionTime(); + void markDirty(); + +private: + enum FormatSelection { KDBX4, KDBX3 }; + static const char* CD_DECRYPTION_TIME_PREFERENCE_KEY; + + bool m_isDirty = false; + bool m_formatCompatibilityDirty = false; + const QScopedPointer m_ui; +}; + +#endif //KEEPASSXC_DATABASESETTINGSWIDGETENCRYPTION_H diff --git a/src/gui/dbsettings/DatabaseSettingsWidgetEncryption.ui b/src/gui/dbsettings/DatabaseSettingsWidgetEncryption.ui new file mode 100644 index 000000000..1de060e9a --- /dev/null +++ b/src/gui/dbsettings/DatabaseSettingsWidgetEncryption.ui @@ -0,0 +1,410 @@ + + + DatabaseSettingsWidgetEncryption + + + + 0 + 0 + 585 + 339 + + + + + + + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 10 + + + + + + + + + + Decryption Time: + + + + + + + ?? s + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Change + + + + + + + + + + + + 1 + + + 50 + + + 1 + + + 10 + + + 10 + + + Qt::Horizontal + + + QSlider::TicksBelow + + + 5 + + + + + + + + + 100 ms + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + 5 s + + + + + + + + + + 0 + 0 + + + + Higher values offer more protection, but opening the database will take longer. + + + true + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 20 + + + + + + + + + + + + + Database format: + + + + + + + + 0 + 0 + + + + + + + + + + This is only important if you need to use your database with other programs. + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Encryption Algorithm: + + + + + + + + 0 + 0 + + + + + AES: 256 Bit (default) + + + + + Twofish: 256 Bit + + + + + + + + Key Derivation Function: + + + + + + + + 0 + 0 + + + + + + + + Transform rounds: + + + + + + + + + + 150 + 0 + + + + + 150 + 16777215 + + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + 1 + + + 1000000000 + + + + + + + Qt::WheelFocus + + + Benchmark 1-second delay + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Memory Usage: + + + + + + + + 150 + 0 + + + + + 150 + 16777215 + + + + 1 + + + 1048576 + + + + + + + Parallelism: + + + + + + + + 150 + 0 + + + + + 150 + 16777215 + + + + 1 + + + 128 + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + diff --git a/src/gui/dbsettings/DatabaseSettingsWidgetGeneral.cpp b/src/gui/dbsettings/DatabaseSettingsWidgetGeneral.cpp new file mode 100644 index 000000000..187dccc50 --- /dev/null +++ b/src/gui/dbsettings/DatabaseSettingsWidgetGeneral.cpp @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2018 KeePassXC Team + * + * 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 "DatabaseSettingsWidgetGeneral.h" +#include "ui_DatabaseSettingsWidgetGeneral.h" +#include "core/Database.h" +#include "core/Entry.h" +#include "core/Group.h" +#include "core/Metadata.h" + +DatabaseSettingsWidgetGeneral::DatabaseSettingsWidgetGeneral(QWidget* parent) + : DatabaseSettingsWidget(parent), m_ui(new Ui::DatabaseSettingsWidgetGeneral()) +{ + m_ui->setupUi(this); + + connect(m_ui->historyMaxItemsCheckBox, SIGNAL(toggled(bool)), m_ui->historyMaxItemsSpinBox, SLOT(setEnabled(bool))); + connect(m_ui->historyMaxSizeCheckBox, SIGNAL(toggled(bool)), m_ui->historyMaxSizeSpinBox, SLOT(setEnabled(bool))); +} + +DatabaseSettingsWidgetGeneral::~DatabaseSettingsWidgetGeneral() +{ +} + +void DatabaseSettingsWidgetGeneral::initialize() +{ + Metadata* meta = m_db->metadata(); + + m_ui->dbNameEdit->setText(meta->name()); + m_ui->dbDescriptionEdit->setText(meta->description()); + m_ui->recycleBinEnabledCheckBox->setChecked(meta->recycleBinEnabled()); + m_ui->defaultUsernameEdit->setText(meta->defaultUserName()); + m_ui->compressionCheckbox->setChecked(m_db->compressionAlgo() != Database::CompressionNone); + + if (meta->historyMaxItems() > -1) { + m_ui->historyMaxItemsSpinBox->setValue(meta->historyMaxItems()); + m_ui->historyMaxItemsCheckBox->setChecked(true); + } else { + m_ui->historyMaxItemsSpinBox->setValue(Metadata::DefaultHistoryMaxItems); + m_ui->historyMaxItemsCheckBox->setChecked(false); + } + int historyMaxSizeMiB = qRound(meta->historyMaxSize()/qreal(1048576)); + if (historyMaxSizeMiB > 0) { + m_ui->historyMaxSizeSpinBox->setValue(historyMaxSizeMiB); + m_ui->historyMaxSizeCheckBox->setChecked(true); + } else { + m_ui->historyMaxSizeSpinBox->setValue(Metadata::DefaultHistoryMaxSize); + m_ui->historyMaxSizeCheckBox->setChecked(false); + } +} + +void DatabaseSettingsWidgetGeneral::uninitialize() +{ +} + +void DatabaseSettingsWidgetGeneral::showEvent(QShowEvent* event) +{ + QWidget::showEvent(event); + m_ui->dbNameEdit->setFocus(); +} + +bool DatabaseSettingsWidgetGeneral::save() +{ + m_db->setCompressionAlgo(m_ui->compressionCheckbox->isChecked() ? Database::CompressionGZip + : Database::CompressionNone); + Metadata* meta = m_db->metadata(); + + meta->setName(m_ui->dbNameEdit->text()); + meta->setDescription(m_ui->dbDescriptionEdit->text()); + meta->setDefaultUserName(m_ui->defaultUsernameEdit->text()); + meta->setRecycleBinEnabled(m_ui->recycleBinEnabledCheckBox->isChecked()); + meta->setSettingsChanged(QDateTime::currentDateTimeUtc()); + + bool truncate = false; + + int historyMaxItems; + if (m_ui->historyMaxItemsCheckBox->isChecked()) { + historyMaxItems = m_ui->historyMaxItemsSpinBox->value(); + } else { + historyMaxItems = -1; + } + if (historyMaxItems != meta->historyMaxItems()) { + meta->setHistoryMaxItems(historyMaxItems); + truncate = true; + } + + int historyMaxSize; + if (m_ui->historyMaxSizeCheckBox->isChecked()) { + historyMaxSize = m_ui->historyMaxSizeSpinBox->value()*1048576; + } else { + historyMaxSize = -1; + } + if (historyMaxSize != meta->historyMaxSize()) { + meta->setHistoryMaxSize(historyMaxSize); + truncate = true; + } + + if (truncate) { + const QList allEntries = m_db->rootGroup()->entriesRecursive(false); + for (Entry* entry : allEntries) { + entry->truncateHistory(); + } + } + + return true; +} diff --git a/src/gui/dbsettings/DatabaseSettingsWidgetGeneral.h b/src/gui/dbsettings/DatabaseSettingsWidgetGeneral.h new file mode 100644 index 000000000..009b6b643 --- /dev/null +++ b/src/gui/dbsettings/DatabaseSettingsWidgetGeneral.h @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2018 KeePassXC Team + * + * 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 KEEPASSXC_DATABASESETTINGSWIDGETGENERAL_H +#define KEEPASSXC_DATABASESETTINGSWIDGETGENERAL_H + +#include "DatabaseSettingsWidget.h" + +#include +#include + +class Database; +namespace Ui +{ +class DatabaseSettingsWidgetGeneral; +} + +class DatabaseSettingsWidgetGeneral : public DatabaseSettingsWidget +{ + Q_OBJECT + +public: + explicit DatabaseSettingsWidgetGeneral(QWidget* parent = nullptr); + Q_DISABLE_COPY(DatabaseSettingsWidgetGeneral); + ~DatabaseSettingsWidgetGeneral() override; + + inline bool hasAdvancedMode() const override { return false; } + +public slots: + void initialize() override; + void uninitialize() override; + bool save() override; + +protected: + void showEvent(QShowEvent* event) override; + + const QScopedPointer m_ui; +}; + +#endif //KEEPASSXC_DATABASESETTINGSWIDGETGENERAL_H diff --git a/src/gui/DatabaseSettingsWidgetGeneral.ui b/src/gui/dbsettings/DatabaseSettingsWidgetGeneral.ui similarity index 91% rename from src/gui/DatabaseSettingsWidgetGeneral.ui rename to src/gui/dbsettings/DatabaseSettingsWidgetGeneral.ui index d3baf6132..00c1437bb 100644 --- a/src/gui/DatabaseSettingsWidgetGeneral.ui +++ b/src/gui/dbsettings/DatabaseSettingsWidgetGeneral.ui @@ -2,6 +2,26 @@ DatabaseSettingsWidgetGeneral + + + 0 + 0 + 453 + 374 + + + + + 0 + 0 + + + + + 450 + 0 + + 0 diff --git a/src/gui/dbsettings/DatabaseSettingsWidgetMasterKey.cpp b/src/gui/dbsettings/DatabaseSettingsWidgetMasterKey.cpp new file mode 100644 index 000000000..d64a89787 --- /dev/null +++ b/src/gui/dbsettings/DatabaseSettingsWidgetMasterKey.cpp @@ -0,0 +1,247 @@ +/* + * Copyright (C) 2018 KeePassXC Team + * + * 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 "DatabaseSettingsWidgetMasterKey.h" +#include "core/Database.h" +#include "keys/PasswordKey.h" +#include "keys/FileKey.h" +#include "keys/YkChallengeResponseKey.h" +#include "gui/MessageBox.h" +#include "gui/masterkey/PasswordEditWidget.h" +#include "gui/masterkey/KeyFileEditWidget.h" +#include "gui/masterkey/YubiKeyEditWidget.h" + +#include +#include +#include + +DatabaseSettingsWidgetMasterKey::DatabaseSettingsWidgetMasterKey(QWidget* parent) + : DatabaseSettingsWidget(parent) + , m_additionalKeyOptionsToggle(new QPushButton(tr("Add additional protection..."), this)) + , m_additionalKeyOptions(new QWidget(this)) + , m_passwordEditWidget(new PasswordEditWidget(this)) + , m_keyFileEditWidget(new KeyFileEditWidget(this)) +#ifdef WITH_XC_YUBIKEY + , m_yubiKeyEditWidget(new YubiKeyEditWidget(this)) +#endif +{ + auto* vbox = new QVBoxLayout(this); + vbox->setSizeConstraint(QLayout::SetMinimumSize); + + // primary password option + vbox->addWidget(m_passwordEditWidget); + + // additional key options + m_additionalKeyOptionsToggle->setObjectName("additionalKeyOptionsToggle"); + vbox->addWidget(m_additionalKeyOptionsToggle); + vbox->addWidget(m_additionalKeyOptions); + vbox->setSizeConstraint(QLayout::SetMinimumSize); + m_additionalKeyOptions->setLayout(new QVBoxLayout()); + m_additionalKeyOptions->layout()->setMargin(0); + m_additionalKeyOptions->layout()->addWidget(m_keyFileEditWidget); +#ifdef WITH_XC_YUBIKEY + m_additionalKeyOptions->layout()->addWidget(m_yubiKeyEditWidget); +#endif + m_additionalKeyOptions->setVisible(false); + + connect(m_additionalKeyOptionsToggle, SIGNAL(clicked()), SLOT(showAdditionalKeyOptions())); + + vbox->addStretch(); + setLayout(vbox); +} + +DatabaseSettingsWidgetMasterKey::~DatabaseSettingsWidgetMasterKey() +{ +} + +void DatabaseSettingsWidgetMasterKey::load(Database* db) +{ + DatabaseSettingsWidget::load(db); + + if (!m_db->key() || m_db->key()->keys().isEmpty()) { + // database has no key, we are about to add a new one + m_passwordEditWidget->changeVisiblePage(KeyComponentWidget::Page::Edit); + m_passwordEditWidget->setPasswordVisible(true); + m_isDirty = true; + return; + } + + bool isDirty = false; + bool hasAdditionalKeys = false; + for (const auto& key: m_db->key()->keys()) { + if (key->uuid() == PasswordKey::UUID) { + m_passwordEditWidget->setComponentAdded(true); + } else if (key->uuid() == FileKey::UUID) { + m_keyFileEditWidget->setComponentAdded(true); + hasAdditionalKeys = true; + } + } + +#ifdef WITH_XC_YUBIKEY + for (const auto& key: m_db->key()->challengeResponseKeys()) { + if (key->uuid() == YkChallengeResponseKey::UUID) { + m_yubiKeyEditWidget->setComponentAdded(true); + hasAdditionalKeys = true; + } + } +#endif + + setAdditionalKeyOptionsVisible(hasAdditionalKeys); + + m_isDirty = isDirty; +} + +void DatabaseSettingsWidgetMasterKey::initialize() +{ + bool blocked = blockSignals(true); + m_passwordEditWidget->setComponentAdded(false); + m_keyFileEditWidget->setComponentAdded(false); +#ifdef WITH_XC_YUBIKEY + m_yubiKeyEditWidget->setComponentAdded(false); +#endif + blockSignals(blocked); +} + +void DatabaseSettingsWidgetMasterKey::uninitialize() +{ +} + +bool DatabaseSettingsWidgetMasterKey::save() +{ + m_isDirty |= (m_passwordEditWidget->visiblePage() == KeyComponentWidget::Page::Edit); + m_isDirty |= (m_keyFileEditWidget->visiblePage() == KeyComponentWidget::Page::Edit); +#ifdef WITH_XC_YUBIKEY + m_isDirty |= (m_yubiKeyEditWidget->visiblePage() == KeyComponentWidget::Page::Edit); +#endif + + if (m_db->key() && ! m_db->key()->keys().isEmpty() && !m_isDirty) { + // key unchanged + return true; + } + + auto newKey = QSharedPointer::create(); + + QSharedPointer passwordKey; + QSharedPointer fileKey; + QSharedPointer ykCrKey; + + for (const auto& key: m_db->key()->keys()) { + if (key->uuid() == PasswordKey::UUID) { + passwordKey = key; + } else if (key->uuid() == FileKey::UUID) { + fileKey = key; + } + } + + for (const auto& key: m_db->key()->challengeResponseKeys()) { + if (key->uuid() == YkChallengeResponseKey::UUID) { + ykCrKey = key; + } + } + + if (!addToCompositeKey(m_passwordEditWidget, newKey, passwordKey)) { + return false; + } + + if (!addToCompositeKey(m_keyFileEditWidget, newKey, fileKey)) { + return false; + } + +#ifdef WITH_XC_YUBIKEY + if (!addToCompositeKey(m_yubiKeyEditWidget, newKey, ykCrKey)) { + return false; + } +#endif + + if (newKey->keys().isEmpty() && newKey->challengeResponseKeys().isEmpty()) { + MessageBox::critical(this, tr("No encryption key added"), + tr("You must add at least one encryption key to secure your database!"), + QMessageBox::Ok, QMessageBox::Ok); + return false; + } + + if (m_passwordEditWidget->visiblePage() == KeyComponentWidget::AddNew) { + auto answer = MessageBox::warning(this, tr("No password set"), + tr("WARNING! You have not set a password. Using a database without " + "a password is strongly discouraged!\n\n" + "Are you sure you want to continue without a password?"), + QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Cancel); + if (answer != QMessageBox::Yes) { + return false; + } + } + + m_db->setKey(newKey); + + emit editFinished(true); + return true; +} + +void DatabaseSettingsWidgetMasterKey::discard() +{ + emit editFinished(false); +} + +void DatabaseSettingsWidgetMasterKey::showAdditionalKeyOptions() +{ + setAdditionalKeyOptionsVisible(true); +} + +void DatabaseSettingsWidgetMasterKey::setAdditionalKeyOptionsVisible(bool show) +{ + m_additionalKeyOptionsToggle->setVisible(!show); + m_additionalKeyOptions->setVisible(show); + m_additionalKeyOptions->layout()->setSizeConstraint(QLayout::SetMinimumSize); + emit sizeChanged(); +} + +bool DatabaseSettingsWidgetMasterKey::addToCompositeKey(KeyComponentWidget* widget, + QSharedPointer& newKey, QSharedPointer& oldKey) +{ + if (widget->visiblePage() == KeyComponentWidget::Edit) { + QString error = tr("Unknown error"); + if (!widget->validate(error) || !widget->addToCompositeKey(newKey)) { + QMessageBox::critical(this, tr("Failed to change master key"), error, QMessageBox::Ok); + return false; + } + } else if (widget->visiblePage() == KeyComponentWidget::LeaveOrRemove) { + Q_ASSERT(oldKey); + newKey->addKey(oldKey); + } + return true; +} + +bool DatabaseSettingsWidgetMasterKey::addToCompositeKey(KeyComponentWidget* widget, + QSharedPointer& newKey, QSharedPointer& oldKey) +{ + if (widget->visiblePage() == KeyComponentWidget::Edit) { + QString error = tr("Unknown error"); + if (!widget->validate(error) || !widget->addToCompositeKey(newKey)) { + QMessageBox::critical(this, tr("Failed to change master key"), error, QMessageBox::Ok); + return false; + } + } else if (widget->visiblePage() == KeyComponentWidget::LeaveOrRemove) { + Q_ASSERT(oldKey); + newKey->addChallengeResponseKey(oldKey); + } + return true; +} + +void DatabaseSettingsWidgetMasterKey::markDirty() +{ + m_isDirty = true; +} diff --git a/src/gui/dbsettings/DatabaseSettingsWidgetMasterKey.h b/src/gui/dbsettings/DatabaseSettingsWidgetMasterKey.h new file mode 100644 index 000000000..89e077895 --- /dev/null +++ b/src/gui/dbsettings/DatabaseSettingsWidgetMasterKey.h @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2018 KeePassXC Team + * + * 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 KEEPASSXC_DATABASESETTINGSPAGECHANGEMASTERKEY_H +#define KEEPASSXC_DATABASESETTINGSPAGECHANGEMASTERKEY_H + +#include "config-keepassx.h" +#include "DatabaseSettingsWidget.h" +#include + +class Database; +class Key; +class CompositeKey; +class ChallengeResponseKey; +class KeyComponentWidget; +class PasswordEditWidget; +class KeyFileEditWidget; +class YubiKeyEditWidget; +class QPushButton; + +class DatabaseSettingsWidgetMasterKey: public DatabaseSettingsWidget +{ + Q_OBJECT + +public: + explicit DatabaseSettingsWidgetMasterKey(QWidget* parent = nullptr); + Q_DISABLE_COPY(DatabaseSettingsWidgetMasterKey); + ~DatabaseSettingsWidgetMasterKey() override; + + void load(Database* db) override; + + inline bool hasAdvancedMode() const override { return false; } + +public slots: + void initialize() override; + void uninitialize() override; + bool save() override; + void discard() override; + +private slots: + void showAdditionalKeyOptions(); + void markDirty(); + +private: + void setAdditionalKeyOptionsVisible(bool show); + + bool addToCompositeKey(KeyComponentWidget* widget, + QSharedPointer& newKey, + QSharedPointer& oldKey); + bool addToCompositeKey(KeyComponentWidget* widget, + QSharedPointer& newKey, + QSharedPointer& oldKey); + + bool m_isDirty = false; + const QPointer m_additionalKeyOptionsToggle; + const QPointer m_additionalKeyOptions; + const QPointer m_passwordEditWidget; + const QPointer m_keyFileEditWidget; +#ifdef WITH_XC_YUBIKEY + const QPointer m_yubiKeyEditWidget; +#endif +}; + +#endif //KEEPASSXC_DATABASESETTINGSPAGECHANGEMASTERKEY_H diff --git a/src/gui/dbsettings/DatabaseSettingsWidgetMetaDataSimple.cpp b/src/gui/dbsettings/DatabaseSettingsWidgetMetaDataSimple.cpp new file mode 100644 index 000000000..6805d7b65 --- /dev/null +++ b/src/gui/dbsettings/DatabaseSettingsWidgetMetaDataSimple.cpp @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2018 KeePassXC Team + * + * 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 "DatabaseSettingsWidgetMetaDataSimple.h" +#include "ui_DatabaseSettingsWidgetMetaDataSimple.h" +#include "core/Database.h" +#include "core/Metadata.h" + +DatabaseSettingWidgetMetaData::DatabaseSettingWidgetMetaData(QWidget* parent) + : DatabaseSettingsWidget(parent) + , m_ui(new Ui::DatabaseSettingsWidgetMetaDataSimple()) +{ + m_ui->setupUi(this); +} + +DatabaseSettingWidgetMetaData::~DatabaseSettingWidgetMetaData() +{ +} + +void DatabaseSettingWidgetMetaData::initialize() +{ + Metadata* meta = m_db->metadata(); + auto name = meta->name(); + m_ui->databaseName->setText(name.isEmpty() ? tr("Passwords") : name); + m_ui->databaseDescription->setText(meta->description()); + + m_ui->databaseName->setFocus(); + m_ui->databaseName->selectAll(); +} + +void DatabaseSettingWidgetMetaData::showEvent(QShowEvent* event) +{ + QWidget::showEvent(event); + m_ui->databaseName->setFocus(); +} + +void DatabaseSettingWidgetMetaData::uninitialize() +{ +} + +bool DatabaseSettingWidgetMetaData::save() +{ + Metadata* meta = m_db->metadata(); + meta->setName(m_ui->databaseName->text()); + meta->setDescription(m_ui->databaseDescription->text()); + + return true; +} diff --git a/src/gui/dbsettings/DatabaseSettingsWidgetMetaDataSimple.h b/src/gui/dbsettings/DatabaseSettingsWidgetMetaDataSimple.h new file mode 100644 index 000000000..bc32c1ee0 --- /dev/null +++ b/src/gui/dbsettings/DatabaseSettingsWidgetMetaDataSimple.h @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2018 KeePassXC Team + * + * 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 KEEPASSXC_DATABASESETTINGSWIDGEMETADATA_H +#define KEEPASSXC_DATABASESETTINGSWIDGEMETADATA_H + +#include "DatabaseSettingsWidget.h" + +#include +#include + +class Database; +namespace Ui +{ +class DatabaseSettingsWidgetMetaDataSimple; +} + +class DatabaseSettingWidgetMetaData : public DatabaseSettingsWidget +{ +Q_OBJECT + +public: + explicit DatabaseSettingWidgetMetaData(QWidget* parent = nullptr); + Q_DISABLE_COPY(DatabaseSettingWidgetMetaData); + ~DatabaseSettingWidgetMetaData() override; + + inline bool hasAdvancedMode() const override { return false; } + +public slots: + void initialize() override; + void uninitialize() override; + bool save() override; + +protected: + void showEvent(QShowEvent* event) override; + +private: + const QScopedPointer m_ui; +}; + +#endif //KEEPASSXC_DATABASESETTINGSWIDGEMETADATA_H diff --git a/src/gui/dbsettings/DatabaseSettingsWidgetMetaDataSimple.ui b/src/gui/dbsettings/DatabaseSettingsWidgetMetaDataSimple.ui new file mode 100644 index 000000000..b4ac30b9b --- /dev/null +++ b/src/gui/dbsettings/DatabaseSettingsWidgetMetaDataSimple.ui @@ -0,0 +1,50 @@ + + + DatabaseSettingsWidgetMetaDataSimple + + + + 0 + 0 + 450 + 86 + + + + + 0 + 0 + + + + + 450 + 0 + + + + + + + Database Name: + + + + + + + + + + Description: + + + + + + + + + + + diff --git a/src/gui/masterkey/KeyComponentWidget.cpp b/src/gui/masterkey/KeyComponentWidget.cpp new file mode 100644 index 000000000..45f133b44 --- /dev/null +++ b/src/gui/masterkey/KeyComponentWidget.cpp @@ -0,0 +1,196 @@ +/* + * Copyright (C) 2018 KeePassXC Team + * + * 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 "KeyComponentWidget.h" +#include "ui_KeyComponentWidget.h" +#include +#include + +KeyComponentWidget::KeyComponentWidget(QWidget* parent) + : KeyComponentWidget({}, parent) +{ +} + +KeyComponentWidget::KeyComponentWidget(const QString& name, QWidget* parent) + : QWidget(parent) + , m_ui(new Ui::KeyComponentWidget()) +{ + m_ui->setupUi(this); + + connect(m_ui->addButton, SIGNAL(clicked(bool)), SIGNAL(componentAddRequested())); + connect(m_ui->changeButton, SIGNAL(clicked(bool)), SIGNAL(componentEditRequested())); + connect(m_ui->removeButton, SIGNAL(clicked(bool)), SIGNAL(componentRemovalRequested())); + connect(m_ui->cancelButton, SIGNAL(clicked(bool)), SLOT(cancelEdit())); + + connect(m_ui->stackedWidget, SIGNAL(currentChanged(int)), SLOT(reset())); + + connect(this, SIGNAL(nameChanged(const QString&)), SLOT(updateComponentName(const QString&))); + connect(this, SIGNAL(descriptionChanged(const QString&)), SLOT(updateComponentDescription(const QString&))); + connect(this, SIGNAL(componentAddRequested()), SLOT(doAdd())); + connect(this, SIGNAL(componentEditRequested()), SLOT(doEdit())); + connect(this, SIGNAL(componentRemovalRequested()), SLOT(doRemove())); + connect(this, SIGNAL(componentAddChanged(bool)), SLOT(updateAddStatus(bool))); + + blockSignals(true); + setComponentName(name); + m_ui->stackedWidget->setCurrentIndex(Page::AddNew); + updateSize(); + blockSignals(false); +} + +KeyComponentWidget::~KeyComponentWidget() +{ +} + +/** + * @param name display name for the key component + */ +void KeyComponentWidget::setComponentName(const QString& name) +{ + if (name == m_componentName) { + return; + } + + m_componentName = name; + emit nameChanged(name); +} + +/** + * @return The key component's display name + */ +QString KeyComponentWidget::componentName() const +{ + return m_componentName; +} + +void KeyComponentWidget::setComponentDescription(const QString& description) +{ + if (description == m_componentDescription) { + return; + } + + m_componentDescription = description; + emit descriptionChanged(description); +} + +QString KeyComponentWidget::componentDescription() const +{ + return m_componentDescription; +} + +void KeyComponentWidget::setComponentAdded(bool added) +{ + if (m_isComponentAdded == added) { + return; + } + + m_isComponentAdded = added; + emit componentAddChanged(added); +} + +bool KeyComponentWidget::componentAdded() const +{ + return m_isComponentAdded; +} + +void KeyComponentWidget::changeVisiblePage(KeyComponentWidget::Page page) +{ + m_previousPage = static_cast(m_ui->stackedWidget->currentIndex()); + m_ui->stackedWidget->setCurrentIndex(page); +} + +KeyComponentWidget::Page KeyComponentWidget::visiblePage() const +{ + return static_cast(m_ui->stackedWidget->currentIndex()); +} + +void KeyComponentWidget::updateComponentName(const QString& name) +{ + m_ui->groupBox->setTitle(name); + m_ui->addButton->setText(tr("Add %1", "Add a key component").arg(name)); + m_ui->changeButton->setText(tr("Change %1", "Change a key component").arg(name)); + m_ui->removeButton->setText(tr("Remove %1", "Remove a key component").arg(name)); + m_ui->changeOrRemoveLabel->setText(tr("%1 set, click to change or remove", "Change or remove a key component").arg(name)); +} + +void KeyComponentWidget::updateComponentDescription(const QString& description) +{ + m_ui->componentDescription->setText(description); +} + +void KeyComponentWidget::updateAddStatus(bool added) +{ + if (added) { + m_ui->stackedWidget->setCurrentIndex(Page::LeaveOrRemove); + } else { + m_ui->stackedWidget->setCurrentIndex(Page::AddNew); + } +} + +void KeyComponentWidget::doAdd() +{ + changeVisiblePage(Page::Edit); +} + +void KeyComponentWidget::doEdit() +{ + changeVisiblePage(Page::Edit); +} + +void KeyComponentWidget::doRemove() +{ + changeVisiblePage(Page::AddNew); +} + +void KeyComponentWidget::cancelEdit() +{ + m_ui->stackedWidget->setCurrentIndex(m_previousPage); + emit editCanceled(); +} + +void KeyComponentWidget::reset() +{ + if (static_cast(m_ui->stackedWidget->currentIndex()) == Page::Edit) { + if (!m_ui->componentWidgetLayout->isEmpty()) { + auto* item = m_ui->componentWidgetLayout->takeAt(0); + if (item->widget()) { + delete item->widget(); + } + delete item; + } + + QWidget* widget = componentEditWidget(); + m_ui->componentWidgetLayout->addWidget(widget); + + initComponentEditWidget(widget); + } + + QTimer::singleShot(0, this, SLOT(updateSize())); +} + +void KeyComponentWidget::updateSize() +{ + for (int i = 0; i < m_ui->stackedWidget->count(); ++i) { + if (m_ui->stackedWidget->currentIndex() == i) { + m_ui->stackedWidget->widget(i)->setSizePolicy( + m_ui->stackedWidget->widget(i)->sizePolicy().horizontalPolicy(), QSizePolicy::Preferred); + } else { + m_ui->stackedWidget->widget(i)->setSizePolicy( + m_ui->stackedWidget->widget(i)->sizePolicy().horizontalPolicy(), QSizePolicy::Ignored); + } + } +} diff --git a/src/gui/masterkey/KeyComponentWidget.h b/src/gui/masterkey/KeyComponentWidget.h new file mode 100644 index 000000000..5d5188d6e --- /dev/null +++ b/src/gui/masterkey/KeyComponentWidget.h @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2018 KeePassXC Team + * + * 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 KEEPASSXC_KEYCOMPONENTWIDGET_H +#define KEEPASSXC_KEYCOMPONENTWIDGET_H + +#include +#include + +namespace Ui +{ +class KeyComponentWidget; +} +class CompositeKey; +class QStackedWidget; + +class KeyComponentWidget : public QWidget +{ + Q_OBJECT + Q_PROPERTY(QString componentName READ m_componentName READ componentName + WRITE setComponentName NOTIFY nameChanged) + Q_PROPERTY(QString componentDescription READ m_componentDescription READ componentDescription + WRITE setComponentDescription NOTIFY descriptionChanged) + Q_PROPERTY(bool componentAdded READ m_isComponentAdded READ componentAdded + WRITE setComponentAdded NOTIFY componentAddChanged) + +public: + enum Page + { + AddNew = 0, + Edit = 1, + LeaveOrRemove = 2 + }; + + explicit KeyComponentWidget(QWidget* parent = nullptr); + explicit KeyComponentWidget(const QString& name, QWidget* parent = nullptr); + Q_DISABLE_COPY(KeyComponentWidget); + ~KeyComponentWidget() override; + + /** + * Add the new key component to the given \link CompositeKey. + * A caller should always check first with \link validate() if + * the new key data is actually valid before adding it to a CompositeKey. + * + * @param key CompositeKey to add new key to + * @return true if added successfully + */ + virtual bool addToCompositeKey(QSharedPointer key) = 0; + + /** + * Validate key component data to check if key component + * may be added to a CompositeKey. + * + * @param errorMessage error message in case data is not valid + * @return true if data is valid + */ + virtual bool validate(QString& errorMessage) const = 0; + + void setComponentName(const QString& name); + QString componentName() const; + void setComponentDescription(const QString& name); + QString componentDescription() const; + void setComponentAdded(bool added); + bool componentAdded() const; + void changeVisiblePage(Page page); + Page visiblePage() const; + +protected: + /** + * Construct and return an instance of the key component edit widget + * which is to be inserted into the component management UI. + * Since previous widgets will be destroyed, every successive call to + * this function must return a new widget. + * + * @return edit widget instance + */ + virtual QWidget* componentEditWidget() = 0; + + /** + * Initialize the key component widget created by \link componentEditWidget(). + * This method is called every time the component edit widget is shown. + * + * @param widget pointer to the widget + */ + virtual void initComponentEditWidget(QWidget* widget) = 0; + +signals: + void nameChanged(const QString& newName); + void descriptionChanged(const QString& newDescription); + void componentAddChanged(bool added); + void componentAddRequested(); + void componentEditRequested(); + void editCanceled(); + void componentRemovalRequested(); + +private slots: + void updateComponentName(const QString& name); + void updateComponentDescription(const QString& decription); + void updateAddStatus(bool added); + void doAdd(); + void doEdit(); + void doRemove(); + void cancelEdit(); + void reset(); + void updateSize(); + +private: + bool m_isComponentAdded = false; + Page m_previousPage = Page::AddNew; + QString m_componentName; + QString m_componentDescription; + + const QScopedPointer m_ui; +}; + +#endif //KEEPASSXC_KEYCOMPONENTWIDGET_H diff --git a/src/gui/masterkey/KeyComponentWidget.ui b/src/gui/masterkey/KeyComponentWidget.ui new file mode 100644 index 000000000..5036655e0 --- /dev/null +++ b/src/gui/masterkey/KeyComponentWidget.ui @@ -0,0 +1,226 @@ + + + KeyComponentWidget + + + + 0 + 0 + 354 + 106 + + + + + QLayout::SetMinimumSize + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + QGroupBox { font-weight: bold; } + + + Key Component + + + + 15 + + + + + 1 + + + + + 0 + 0 + + + + + 10 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Key Component Description + + + Qt::RichText + + + true + + + Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse + + + + + + + Add Key Component + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + + QLayout::SetMinimumSize + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Cancel + + + + + + + + + + + 0 + 0 + + + + + 10 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Key Component set, click to change or remove + + + + + + + + + Change Key Component + + + + + + + Remove Key Component + + + + + + + + + + + + + + + + + diff --git a/src/gui/masterkey/KeyFileEditWidget.cpp b/src/gui/masterkey/KeyFileEditWidget.cpp new file mode 100644 index 000000000..14ac4879c --- /dev/null +++ b/src/gui/masterkey/KeyFileEditWidget.cpp @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2018 KeePassXC Team + * + * 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 "KeyFileEditWidget.h" +#include "ui_KeyFileEditWidget.h" +#include "gui/MainWindow.h" +#include "gui/FileDialog.h" +#include "gui/MessageBox.h" +#include "keys/CompositeKey.h" +#include "keys/FileKey.h" + +KeyFileEditWidget::KeyFileEditWidget(QWidget* parent) + : KeyComponentWidget(parent) + , m_compUi(new Ui::KeyFileEditWidget()) +{ + setComponentName(tr("Key File")); + setComponentDescription(tr("

You can add a key file containing random bytes for additional security.

" + "

You must keep it secret and never lose it or you will be locked out!

")); +} + +KeyFileEditWidget::~KeyFileEditWidget() +{ +} + +bool KeyFileEditWidget::addToCompositeKey(QSharedPointer key) +{ + auto fileKey = QSharedPointer::create(); + QString fileKeyName = m_compUi->keyFileCombo->currentText(); + if (!fileKey->load(fileKeyName, nullptr)) { + return false; + } + + if (fileKey->type() != FileKey::Hashed) { + QMessageBox::warning(KEEPASSXC_MAIN_WINDOW, + tr("Legacy key file format"), + tr("You are using a legacy key file format which may become\n" + "unsupported in the future.\n\n" + "Please go to the master key settings and generate a new key file."), + QMessageBox::Ok); + } + + key->addKey(fileKey); + return true; +} + +bool KeyFileEditWidget::validate(QString& errorMessage) const +{ + FileKey fileKey; + QString fileKeyError; + QString fileKeyName = m_compUi->keyFileCombo->currentText(); + if (!fileKey.load(fileKeyName, &fileKeyError)) { + errorMessage = tr("Error loading the key file '%1'\nMessage: %2").arg(fileKeyName, fileKeyError); + return false; + } + return true; +} + +QWidget* KeyFileEditWidget::componentEditWidget() +{ + m_compEditWidget = new QWidget(); + m_compUi->setupUi(m_compEditWidget); + + connect(m_compUi->createKeyFileButton, SIGNAL(clicked()), SLOT(createKeyFile())); + connect(m_compUi->browseKeyFileButton, SIGNAL(clicked()), SLOT(browseKeyFile())); + + return m_compEditWidget; +} + +void KeyFileEditWidget::initComponentEditWidget(QWidget* widget) +{ + Q_UNUSED(widget); + Q_ASSERT(m_compEditWidget); + m_compUi->keyFileCombo->setFocus(); +} + +void KeyFileEditWidget::createKeyFile() +{ + Q_ASSERT(m_compEditWidget); + if (!m_compEditWidget) { + return; + } + QString filters = QString("%1 (*.key);;%2 (*)").arg(tr("Key files"), tr("All files")); + QString fileName = fileDialog()->getSaveFileName(this, tr("Create Key File..."), QString(), filters); + + if (!fileName.isEmpty()) { + QString errorMsg; + bool created = FileKey::create(fileName, &errorMsg); + if (!created) { + MessageBox::critical(KEEPASSXC_MAIN_WINDOW, tr("Error creating key file"), + tr("Unable to create key file: %1").arg(errorMsg), QMessageBox::Button::Ok); + } else { + m_compUi->keyFileCombo->setEditText(fileName); + } + } +} + +void KeyFileEditWidget::browseKeyFile() +{ + Q_ASSERT(m_compEditWidget); + if (!m_compEditWidget) { + return; + } + QString filters = QString("%1 (*.key);;%2 (*)").arg(tr("Key files"), tr("All files")); + QString fileName = fileDialog()->getOpenFileName(this, tr("Select a key file"), QString(), filters); + + if (!fileName.isEmpty()) { + m_compUi->keyFileCombo->setEditText(fileName); + } +} diff --git a/src/gui/masterkey/KeyFileEditWidget.h b/src/gui/masterkey/KeyFileEditWidget.h new file mode 100644 index 000000000..aa6c71a58 --- /dev/null +++ b/src/gui/masterkey/KeyFileEditWidget.h @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2018 KeePassXC Team + * + * 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 KEEPASSXC_KEYFILEEDITWIDGET_H +#define KEEPASSXC_KEYFILEEDITWIDGET_H + +#include "KeyComponentWidget.h" +#include + +namespace Ui +{ +class KeyFileEditWidget; +} + +class KeyFileEditWidget : public KeyComponentWidget +{ +Q_OBJECT + +public: + explicit KeyFileEditWidget(QWidget* parent = nullptr); + Q_DISABLE_COPY(KeyFileEditWidget); + ~KeyFileEditWidget() override; + + bool addToCompositeKey(QSharedPointer key) override; + bool validate(QString& errorMessage) const override; + +protected: + QWidget* componentEditWidget() override; + void initComponentEditWidget(QWidget* widget) override; + +private slots: + void createKeyFile(); + void browseKeyFile(); + +private: + const QScopedPointer m_compUi; + QPointer m_compEditWidget; +}; + +#endif //KEEPASSXC_KEYFILEEDITWIDGET_H diff --git a/src/gui/masterkey/KeyFileEditWidget.ui b/src/gui/masterkey/KeyFileEditWidget.ui new file mode 100644 index 000000000..a267935b5 --- /dev/null +++ b/src/gui/masterkey/KeyFileEditWidget.ui @@ -0,0 +1,70 @@ + + + KeyFileEditWidget + + + + 0 + 0 + 364 + 76 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + true + + + + + + + Browse + + + + + + + Generate + + + + + + + Qt::Vertical + + + + 20 + 0 + + + + + + + + + diff --git a/src/gui/masterkey/PasswordEditWidget.cpp b/src/gui/masterkey/PasswordEditWidget.cpp new file mode 100644 index 000000000..dc43f9159 --- /dev/null +++ b/src/gui/masterkey/PasswordEditWidget.cpp @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2018 KeePassXC Team + * + * 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 "PasswordEditWidget.h" +#include "ui_PasswordEditWidget.h" +#include "core/FilePath.h" +#include "gui/PasswordGeneratorWidget.h" +#include "keys/PasswordKey.h" +#include "keys/CompositeKey.h" + +#include + +PasswordEditWidget::PasswordEditWidget(QWidget* parent) + : KeyComponentWidget(parent) + , m_compUi(new Ui::PasswordEditWidget()) +{ + setComponentName(tr("Password")); + setComponentDescription(tr("

A password is the primary method for securing your database.

" + "

Good passwords are long and unique. KeePassXC can generate one for you.

")); +} + +PasswordEditWidget::~PasswordEditWidget() +{ +} + +bool PasswordEditWidget::addToCompositeKey(QSharedPointer key) +{ + key->addKey(QSharedPointer::create(m_compUi->enterPasswordEdit->text())); + return true; +} + +/** + * @param visible changed password visibility state + */ +void PasswordEditWidget::setPasswordVisible(bool visible) +{ + m_compUi->togglePasswordButton->setChecked(visible); +} + +/** + * @return password visibility state + */ +bool PasswordEditWidget::isPasswordVisible() const +{ + return m_compUi->togglePasswordButton->isChecked(); +} + +QWidget* PasswordEditWidget::componentEditWidget() +{ + m_compEditWidget = new QWidget(); + m_compUi->setupUi(m_compEditWidget); + m_compUi->togglePasswordButton->setIcon(filePath()->onOffIcon("actions", "password-show")); + m_compUi->passwordGeneratorButton->setIcon(filePath()->icon("actions", "password-generator", false)); + m_compUi->repeatPasswordEdit->enableVerifyMode(m_compUi->enterPasswordEdit); + + connect(m_compUi->togglePasswordButton, SIGNAL(toggled(bool)), m_compUi->enterPasswordEdit, SLOT(setShowPassword(bool))); + connect(m_compUi->passwordGeneratorButton, SIGNAL(clicked(bool)), SLOT(showPasswordGenerator())); + + return m_compEditWidget; +} + +void PasswordEditWidget::initComponentEditWidget(QWidget* widget) +{ + Q_UNUSED(widget); + Q_ASSERT(m_compEditWidget); + m_compUi->enterPasswordEdit->setFocus(); +} + +bool PasswordEditWidget::validate(QString& errorMessage) const +{ + if (m_compUi->enterPasswordEdit->text().isEmpty()) { + errorMessage = tr("Password cannot be empty."); + return false; + } + + if (m_compUi->enterPasswordEdit->text() != m_compUi->repeatPasswordEdit->text()) { + errorMessage = tr("Passwords do not match."); + return false; + } + + return true; +} + +void PasswordEditWidget::showPasswordGenerator() +{ + QDialog pwDialog; + pwDialog.setWindowTitle(tr("Generate master password")); + + auto layout = new QVBoxLayout(); + pwDialog.setLayout(layout); + + auto pwGenerator = new PasswordGeneratorWidget(&pwDialog); + layout->addWidget(pwGenerator); + + pwGenerator->setStandaloneMode(false); + connect(pwGenerator, SIGNAL(appliedPassword(const QString&)), SLOT(setPassword(const QString&))); + connect(pwGenerator, SIGNAL(dialogTerminated()), &pwDialog, SLOT(close())); + + pwGenerator->setPasswordVisible(isPasswordVisible()); + + pwDialog.exec(); +} + +void PasswordEditWidget::setPassword(const QString& password) +{ + Q_ASSERT(m_compEditWidget); + + m_compUi->enterPasswordEdit->setText(password); + m_compUi->repeatPasswordEdit->setText(password); +} diff --git a/src/gui/masterkey/PasswordEditWidget.h b/src/gui/masterkey/PasswordEditWidget.h new file mode 100644 index 000000000..ac10c5122 --- /dev/null +++ b/src/gui/masterkey/PasswordEditWidget.h @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2018 KeePassXC Team + * + * 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 KEEPASSXC_PASSWORDEDITWIDGET_H +#define KEEPASSXC_PASSWORDEDITWIDGET_H + +#include "KeyComponentWidget.h" +#include + +namespace Ui +{ +class PasswordEditWidget; +} + +class PasswordEditWidget : public KeyComponentWidget +{ + Q_OBJECT + +public: + explicit PasswordEditWidget(QWidget* parent = nullptr); + Q_DISABLE_COPY(PasswordEditWidget); + ~PasswordEditWidget() override; + + bool addToCompositeKey(QSharedPointer key) override; + void setPasswordVisible(bool visible); + bool isPasswordVisible() const; + bool validate(QString& errorMessage) const override; + +protected: + QWidget* componentEditWidget() override; + void initComponentEditWidget(QWidget* widget) override; + +private slots: + void showPasswordGenerator(); + void setPassword(const QString& password); + +private: + const QScopedPointer m_compUi; + QPointer m_compEditWidget; +}; + +#endif //KEEPASSXC_PASSWORDEDITWIDGET_H diff --git a/src/gui/masterkey/PasswordEditWidget.ui b/src/gui/masterkey/PasswordEditWidget.ui new file mode 100644 index 000000000..e435e9901 --- /dev/null +++ b/src/gui/masterkey/PasswordEditWidget.ui @@ -0,0 +1,90 @@ + + + PasswordEditWidget + + + + 0 + 0 + 571 + 78 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Enter password: + + + + + + + + + QLineEdit::Password + + + + + + + true + + + + + + + + + + + QLineEdit::Password + + + + + + + + + + + + Confirm password: + + + + + + + + PasswordEdit + QLineEdit +
gui/PasswordEdit.h
+ 1 +
+
+ + enterPasswordEdit + repeatPasswordEdit + togglePasswordButton + passwordGeneratorButton + + + +
diff --git a/src/gui/masterkey/YubiKeyEditWidget.cpp b/src/gui/masterkey/YubiKeyEditWidget.cpp new file mode 100644 index 000000000..6991af427 --- /dev/null +++ b/src/gui/masterkey/YubiKeyEditWidget.cpp @@ -0,0 +1,163 @@ +/* + * Copyright (C) 2018 KeePassXC Team + * + * 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 "YubiKeyEditWidget.h" +#include "ui_YubiKeyEditWidget.h" +#include "gui/MessageBox.h" +#include "gui/MainWindow.h" +#include "keys/CompositeKey.h" +#include "keys/YkChallengeResponseKey.h" +#include "config-keepassx.h" + +#include + +YubiKeyEditWidget::YubiKeyEditWidget(QWidget* parent) + : KeyComponentWidget(parent) + , m_compUi(new Ui::YubiKeyEditWidget()) +{ + setComponentName(tr("YubiKey Challenge-Response")); + setComponentDescription(tr("

If you own a YubiKey, you can use it " + "for additional security.

The YubiKey requires one of its slots to be programmed as " + "" + "HMAC-SHA1 Challenge-Response.

")); +} + +YubiKeyEditWidget::~YubiKeyEditWidget() +{ +} + +bool YubiKeyEditWidget::addToCompositeKey(QSharedPointer key) +{ + QSharedPointer keyPtr; + if (!createCrKey(keyPtr, false)) { + return false; + } + key->addChallengeResponseKey(keyPtr); + + return true; +} + +bool YubiKeyEditWidget::validate(QString& errorMessage) const +{ + QSharedPointer keyPtr; + if (!createCrKey(keyPtr)) { + errorMessage = tr("No YubiKey detected, please ensure it's plugged in."); + return false; + } + + return true; +} + +QWidget* YubiKeyEditWidget::componentEditWidget() +{ + m_compEditWidget = new QWidget(); + m_compUi->setupUi(m_compEditWidget); + + QSizePolicy sp = m_compUi->yubikeyProgress->sizePolicy(); + sp.setRetainSizeWhenHidden(true); + m_compUi->yubikeyProgress->setSizePolicy(sp); + m_compUi->yubikeyProgress->setVisible(false); + +#ifdef WITH_XC_YUBIKEY + connect(m_compUi->buttonRedetectYubikey, SIGNAL(clicked()), SLOT(pollYubikey())); + + connect(YubiKey::instance(), SIGNAL(detected(int, bool)), SLOT(yubikeyDetected(int, bool)), Qt::QueuedConnection); + connect(YubiKey::instance(), SIGNAL(notFound()), SLOT(noYubikeyFound()), Qt::QueuedConnection); + + pollYubikey(); +#endif + + return m_compEditWidget; +} + +void YubiKeyEditWidget::initComponentEditWidget(QWidget* widget) +{ + Q_UNUSED(widget); + Q_ASSERT(m_compEditWidget); + m_compUi->comboChallengeResponse->setFocus(); +} + +void YubiKeyEditWidget::pollYubikey() +{ +#ifdef WITH_XC_YUBIKEY + if (!m_compEditWidget) { + return; + } + m_compUi->buttonRedetectYubikey->setEnabled(false); + m_compUi->comboChallengeResponse->setEnabled(false); + m_compUi->comboChallengeResponse->clear(); + m_compUi->yubikeyProgress->setVisible(true); + + // YubiKey init is slow, detect asynchronously to not block the UI + QtConcurrent::run(YubiKey::instance(), &YubiKey::detect); +#endif +} + +void YubiKeyEditWidget::yubikeyDetected(int slot, bool blocking) +{ +#ifdef WITH_XC_YUBIKEY + if (!m_compEditWidget) { + return; + } + YkChallengeResponseKey yk(slot, blocking); + m_compUi->comboChallengeResponse->clear(); + // add detected YubiKey to combo box and encode blocking mode in LSB, slot number in second LSB + m_compUi->comboChallengeResponse->addItem(yk.getName(), QVariant((slot << 1u) | blocking)); + m_compUi->comboChallengeResponse->setEnabled(true); + m_compUi->buttonRedetectYubikey->setEnabled(true); + m_compUi->yubikeyProgress->setVisible(false); + m_isDetected = true; +#endif +} + +void YubiKeyEditWidget::noYubikeyFound() +{ +#ifdef WITH_XC_YUBIKEY + if (!m_compEditWidget) { + return; + } + m_compUi->comboChallengeResponse->clear(); + m_compUi->comboChallengeResponse->setEnabled(false); + m_compUi->comboChallengeResponse->addItem(tr("No YubiKey inserted.")); + m_compUi->buttonRedetectYubikey->setEnabled(true); + m_compUi->yubikeyProgress->setVisible(false); + m_isDetected = false; +#endif +} + +bool YubiKeyEditWidget::createCrKey(QSharedPointer& key, bool testChallenge) const +{ + Q_ASSERT(m_compEditWidget); + if (!m_isDetected || !m_compEditWidget) { + return false; + } + + int selectionIndex = m_compUi->comboChallengeResponse->currentIndex(); + int comboPayload = m_compUi->comboChallengeResponse->itemData(selectionIndex).toInt(); + + if (0 == comboPayload) { + return false; + } + + auto blocking = static_cast(comboPayload & 1u); + int slot = comboPayload >> 1u; + key.reset(new YkChallengeResponseKey(slot, blocking)); + if (testChallenge) { + return key->challenge(QByteArray("0000")); + } + return true; +} diff --git a/src/gui/masterkey/YubiKeyEditWidget.h b/src/gui/masterkey/YubiKeyEditWidget.h new file mode 100644 index 000000000..82fc8b35a --- /dev/null +++ b/src/gui/masterkey/YubiKeyEditWidget.h @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2018 KeePassXC Team + * + * 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 KEEPASSXC_YUBIKEYEDITWIDGET_H +#define KEEPASSXC_YUBIKEYEDITWIDGET_H + +#include "KeyComponentWidget.h" +#include + +namespace Ui +{ +class YubiKeyEditWidget; +} + +class YkChallengeResponseKey; + +class YubiKeyEditWidget : public KeyComponentWidget +{ + Q_OBJECT + +public: + explicit YubiKeyEditWidget(QWidget* parent = nullptr); + Q_DISABLE_COPY(YubiKeyEditWidget); + ~YubiKeyEditWidget() override; + + bool addToCompositeKey(QSharedPointer key) override; + bool validate(QString& errorMessage) const override; + +protected: + QWidget* componentEditWidget() override; + void initComponentEditWidget(QWidget* widget) override; + +private slots: + void yubikeyDetected(int slot, bool blocking); + void noYubikeyFound(); + void pollYubikey(); + +private: + bool createCrKey(QSharedPointer& key, bool testChallenge = true) const; + + const QScopedPointer m_compUi; + QPointer m_compEditWidget; + bool m_isDetected = false; +}; + +#endif //KEEPASSXC_YUBIKEYEDITWIDGET_H diff --git a/src/gui/masterkey/YubiKeyEditWidget.ui b/src/gui/masterkey/YubiKeyEditWidget.ui new file mode 100644 index 000000000..08508739a --- /dev/null +++ b/src/gui/masterkey/YubiKeyEditWidget.ui @@ -0,0 +1,90 @@ + + + YubiKeyEditWidget + + + + 0 + 0 + 381 + 64 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 0 + + + + + Refresh + + + + + + + + 0 + 0 + + + + + + + + + 16777215 + 2 + + + + 0 + + + -1 + + + false + + + + + + + + + Qt::Vertical + + + + 20 + 0 + + + + + + + + comboChallengeResponse + buttonRedetectYubikey + + + + diff --git a/src/gui/settings/SettingsWidget.cpp b/src/gui/settings/SettingsWidget.cpp new file mode 100644 index 000000000..5053bf983 --- /dev/null +++ b/src/gui/settings/SettingsWidget.cpp @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2018 KeePassXC Team + * + * 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 "SettingsWidget.h" + +SettingsWidget::SettingsWidget(QWidget* parent) + : QWidget(parent) +{ +} + +SettingsWidget::~SettingsWidget() +{ +} + +/** + * Switch between simple mode (the default) and advanced mode. + * Subclasses which implement an advanced mode, need to override this method, + * \link advancedMode() and \link hasAdvancedMode. + * + * When overriding this method, make sure to also emit the + * \link advancedModeChanged() signal. + * + * @param advanced whether advanced mode is enabled + */ +void SettingsWidget::setAdvancedMode(bool advanced) +{ + if (hasAdvancedMode() && advanced != advancedMode()) { + m_advancedMode = advanced; + emit advancedModeChanged(advanced); + } +} + +/** + * @return true if advanced mode is on (default: false) + */ +bool SettingsWidget::advancedMode() const +{ + return m_advancedMode; +} diff --git a/src/gui/settings/SettingsWidget.h b/src/gui/settings/SettingsWidget.h new file mode 100644 index 000000000..4630a776e --- /dev/null +++ b/src/gui/settings/SettingsWidget.h @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2018 KeePassXC Team + * + * 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 KEEPASSXC_SETTINGSWIDGET_H +#define KEEPASSXC_SETTINGSWIDGET_H + +#include + +class Database; + +/** + * Pure-virtual base class for KeePassXC settings widgets. + */ +class SettingsWidget : public QWidget +{ + Q_OBJECT + +public: + explicit SettingsWidget(QWidget* parent = nullptr); + Q_DISABLE_COPY(SettingsWidget); + ~SettingsWidget() override; + + /** + * @return true if widget has an advanced mode + */ + virtual bool hasAdvancedMode() const = 0; + virtual void setAdvancedMode(bool advanced); + virtual bool advancedMode() const; + +public slots: + /** + * Initialize settings widget. + */ + virtual void initialize() = 0; + + /** + * Perform needed clean-up operations before widget is destroyed or re-initialized. + */ + virtual void uninitialize() = 0; + + /** + * Save settings. + * + * @return true on success, false on failure + */ + virtual bool save() = 0; + + /** + * Discard settings. + */ + virtual void discard() {}; + +signals: + void editFinished(bool saved); + void advancedModeChanged(bool advanced); + +private: + bool m_advancedMode = false; +}; + +#endif //KEEPASSXC_SETTINGSWIDGET_H diff --git a/src/gui/wizard/NewDatabaseWizard.cpp b/src/gui/wizard/NewDatabaseWizard.cpp new file mode 100644 index 000000000..96aa06629 --- /dev/null +++ b/src/gui/wizard/NewDatabaseWizard.cpp @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2018 KeePassXC Team + * + * 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 "NewDatabaseWizard.h" +#include "NewDatabaseWizardPageMetaData.h" +#include "NewDatabaseWizardPageEncryption.h" +#include "NewDatabaseWizardPageMasterKey.h" + +#include "core/Global.h" +#include "core/Database.h" +#include "core/Group.h" +#include "core/FilePath.h" +#include "format/KeePass2.h" + +#include + +NewDatabaseWizard::NewDatabaseWizard(QWidget* parent) + : QWizard(parent) + , m_pages() +{ + setWizardStyle(QWizard::MacStyle); + setOption(QWizard::WizardOption::HaveHelpButton, false); + + m_pages << new NewDatabaseWizardPageMetaData() + << new NewDatabaseWizardPageEncryption() + << new NewDatabaseWizardPageMasterKey(); + + for (auto const& page: asConst(m_pages)) { + addPage(page); + } + + setWindowTitle(tr("Create a new KeePassXC database...")); + + setPixmap(QWizard::BackgroundPixmap, QPixmap(filePath()->dataPath("wizard/background-pixmap.png"))); +} + +NewDatabaseWizard::~NewDatabaseWizard() +{ +} + +bool NewDatabaseWizard::validateCurrentPage() +{ + return m_pages[currentId()]->validatePage(); +} + +Database* NewDatabaseWizard::takeDatabase() +{ + return m_db.take(); +} + +void NewDatabaseWizard::initializePage(int id) +{ + if (id == startId()) { + m_db.reset(new Database()); + m_db->rootGroup()->setName(tr("Root", "Root group")); + m_db->setKdf({}); + m_db->setKey({}); + } + + m_pages[id]->setDatabase(m_db.data()); + m_pages[id]->initializePage(); +} diff --git a/src/gui/wizard/NewDatabaseWizard.h b/src/gui/wizard/NewDatabaseWizard.h new file mode 100644 index 000000000..5c3b49c01 --- /dev/null +++ b/src/gui/wizard/NewDatabaseWizard.h @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2018 KeePassXC Team + * + * 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 KEEPASSXC_NEWDATABASEWIZARD_H +#define KEEPASSXC_NEWDATABASEWIZARD_H + +#include +#include +#include + +class Database; +class NewDatabaseWizardPage; + +/** + * Setup wizard for creating a new database. + */ +class NewDatabaseWizard : public QWizard +{ +Q_OBJECT + +public: + explicit NewDatabaseWizard(QWidget* parent = nullptr); + ~NewDatabaseWizard() override; + + Database* takeDatabase(); + bool validateCurrentPage() override; + +protected: + void initializePage(int id) override; + +private: + QScopedPointer m_db; + QList> m_pages; +}; + +#endif //KEEPASSXC_NEWDATABASEWIZARD_H diff --git a/src/gui/wizard/NewDatabaseWizardPage.cpp b/src/gui/wizard/NewDatabaseWizardPage.cpp new file mode 100644 index 000000000..e38cb8641 --- /dev/null +++ b/src/gui/wizard/NewDatabaseWizardPage.cpp @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2018 KeePassXC Team + * + * 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 "NewDatabaseWizardPage.h" +#include "ui_NewDatabaseWizardPage.h" +#include "core/Database.h" +#include "gui/dbsettings/DatabaseSettingsWidget.h" + +#include + +NewDatabaseWizardPage::NewDatabaseWizardPage(QWidget* parent) + : QWizardPage(parent) + , m_ui(new Ui::NewDatabaseWizardPage()) +{ + m_ui->setupUi(this); + + connect(m_ui->advancedSettingsButton, SIGNAL(clicked()), SLOT(toggleAdvancedSettings())); +} + +NewDatabaseWizardPage::~NewDatabaseWizardPage() +{ +} + +/** + * Set the database settings page widget for this wizard page. + * The wizard page will take ownership of the settings page widget. + * + * @param page database settings page widget + */ +void NewDatabaseWizardPage::setPageWidget(DatabaseSettingsWidget* page) +{ + m_pageWidget = page; + if (!m_ui->pageContentLayout->isEmpty()) { + delete m_ui->pageContentLayout->takeAt(0); + } + m_ui->pageContentLayout->addWidget(m_pageWidget); + m_ui->pageContentLayout->setSizeConstraint(QLayout::SetMinimumSize); + m_ui->advancedSettingsButton->setVisible(m_pageWidget->hasAdvancedMode()); +} + +/** + * @return database settings widget of this page widget. + */ +DatabaseSettingsWidget* NewDatabaseWizardPage::pageWidget() +{ + return m_pageWidget; +} + +/** + * Set the database to be configured by the wizard page. + * The wizard will NOT take ownership of the database object. + * + * @param db database object to be configured + */ +void NewDatabaseWizardPage::setDatabase(Database* db) +{ + m_db = db; +} + +void NewDatabaseWizardPage::initializePage() +{ + Q_ASSERT(m_pageWidget && m_db); + if (!m_pageWidget || !m_db) { + return; + } + + m_pageWidget->load(m_db); +} + +bool NewDatabaseWizardPage::validatePage() +{ + Q_ASSERT(m_pageWidget && m_db); + if (!m_pageWidget || !m_db) { + return false; + } + + bool valid = m_pageWidget->save(); + m_pageWidget->uninitialize(); + return valid; +} + +/** + * Toggle settings page widget between simple and advanced mode. + */ +void NewDatabaseWizardPage::toggleAdvancedSettings() +{ + if (!m_pageWidget || !m_pageWidget->hasAdvancedMode()) { + return; + } + + if (m_pageWidget->advancedMode()) { + m_pageWidget->setAdvancedMode(false); + m_ui->advancedSettingsButton->setText(tr("Advanced Settings")); + } else { + m_pageWidget->setAdvancedMode(true); + m_ui->advancedSettingsButton->setText(tr("Simple Settings")); + } +} diff --git a/src/gui/wizard/NewDatabaseWizardPage.h b/src/gui/wizard/NewDatabaseWizardPage.h new file mode 100644 index 000000000..39b47940e --- /dev/null +++ b/src/gui/wizard/NewDatabaseWizardPage.h @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2018 KeePassXC Team + * + * 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 KEEPASSXC_NEWDATABASEWIZARDPAGE_H +#define KEEPASSXC_NEWDATABASEWIZARDPAGE_H + +#include +#include +#include + +class Database; +class DatabaseSettingsWidget; +namespace Ui +{ +class NewDatabaseWizardPage; +} + +/** + * Pure-virtual base class for "New Database" setup wizard pages + */ +class NewDatabaseWizardPage : public QWizardPage +{ +Q_OBJECT + +public: + explicit NewDatabaseWizardPage(QWidget* parent = nullptr); + Q_DISABLE_COPY(NewDatabaseWizardPage); + ~NewDatabaseWizardPage() override; + + void setPageWidget(DatabaseSettingsWidget* page); + DatabaseSettingsWidget* pageWidget(); + void setDatabase(Database* db); + + void initializePage() override; + bool validatePage() override; + +public slots: + void toggleAdvancedSettings(); + +protected: + QPointer m_pageWidget; + QPointer m_db; + + const QScopedPointer m_ui; +}; + +#endif //KEEPASSXC_NEWDATABASEWIZARDPAGE_H diff --git a/src/gui/wizard/NewDatabaseWizardPage.ui b/src/gui/wizard/NewDatabaseWizardPage.ui new file mode 100644 index 000000000..6b69e85b5 --- /dev/null +++ b/src/gui/wizard/NewDatabaseWizardPage.ui @@ -0,0 +1,65 @@ + + + NewDatabaseWizardPage + + + + 0 + 0 + + + + WizardPage + + + En&cryption Settings + + + Here you can adjust the database encryption settings. Don't worry, you can change them later in the database settings. + + + + + + + + + Qt::Vertical + + + + 20 + 15 + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Advanced Settings + + + + + + + + + + diff --git a/src/gui/wizard/NewDatabaseWizardPageEncryption.cpp b/src/gui/wizard/NewDatabaseWizardPageEncryption.cpp new file mode 100644 index 000000000..743f7bcc2 --- /dev/null +++ b/src/gui/wizard/NewDatabaseWizardPageEncryption.cpp @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2018 KeePassXC Team + * + * 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 "NewDatabaseWizardPageEncryption.h" +#include "gui/dbsettings/DatabaseSettingsWidgetEncryption.h" + +NewDatabaseWizardPageEncryption::NewDatabaseWizardPageEncryption(QWidget* parent) + : NewDatabaseWizardPage(parent) +{ + setPageWidget(new DatabaseSettingsWidgetEncryption()); + + setTitle(tr("Encryption Settings")); + setSubTitle(tr("Here you can adjust the database encryption settings. " + "Don't worry, you can change them later in the database settings.")); +} + +NewDatabaseWizardPageEncryption::~NewDatabaseWizardPageEncryption() +{ +} diff --git a/src/gui/wizard/NewDatabaseWizardPageEncryption.h b/src/gui/wizard/NewDatabaseWizardPageEncryption.h new file mode 100644 index 000000000..c10e84dba --- /dev/null +++ b/src/gui/wizard/NewDatabaseWizardPageEncryption.h @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2018 KeePassXC Team + * + * 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 KEEPASSXC_NEWDATABASEWIZARDPAGEENCRYPTION_H +#define KEEPASSXC_NEWDATABASEWIZARDPAGEENCRYPTION_H + +#include "NewDatabaseWizardPage.h" + +class NewDatabaseWizardPageEncryption : public NewDatabaseWizardPage +{ +Q_OBJECT + +public: + explicit NewDatabaseWizardPageEncryption(QWidget* parent = nullptr); + Q_DISABLE_COPY(NewDatabaseWizardPageEncryption); + ~NewDatabaseWizardPageEncryption() override; +}; + +#endif //KEEPASSXC_NEWDATABASEWIZARDPAGEENCRYPTION_H diff --git a/src/gui/wizard/NewDatabaseWizardPageMasterKey.cpp b/src/gui/wizard/NewDatabaseWizardPageMasterKey.cpp new file mode 100644 index 000000000..130560e27 --- /dev/null +++ b/src/gui/wizard/NewDatabaseWizardPageMasterKey.cpp @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2018 KeePassXC Team + * + * 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 "NewDatabaseWizardPageMasterKey.h" +#include "gui/dbsettings/DatabaseSettingsWidgetMasterKey.h" +#include + +NewDatabaseWizardPageMasterKey::NewDatabaseWizardPageMasterKey(QWidget* parent) + : NewDatabaseWizardPage(parent) +{ + setPageWidget(new DatabaseSettingsWidgetMasterKey()); + + setTitle(tr("Database Master Key")); + setSubTitle(tr("A master key known only to you protects your database.")); + + connect(pageWidget(), SIGNAL(sizeChanged()), SLOT(updateWindowSize())); +} + +NewDatabaseWizardPageMasterKey::~NewDatabaseWizardPageMasterKey() +{ +} + +void NewDatabaseWizardPageMasterKey::updateWindowSize() +{ + // ugly workaround for QWizard not managing to react to size changes automatically + QApplication::activeWindow()->adjustSize(); +} diff --git a/src/gui/wizard/NewDatabaseWizardPageMasterKey.h b/src/gui/wizard/NewDatabaseWizardPageMasterKey.h new file mode 100644 index 000000000..c6fa53cea --- /dev/null +++ b/src/gui/wizard/NewDatabaseWizardPageMasterKey.h @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2018 KeePassXC Team + * + * 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 KEEPASSXC_NEWDATABASEWIZARDPAGEMASTERKEY_H +#define KEEPASSXC_NEWDATABASEWIZARDPAGEMASTERKEY_H + +#include "NewDatabaseWizardPage.h" + +class NewDatabaseWizardPageMasterKey : public NewDatabaseWizardPage +{ +Q_OBJECT + +public: + explicit NewDatabaseWizardPageMasterKey(QWidget* parent = nullptr); + Q_DISABLE_COPY(NewDatabaseWizardPageMasterKey); + ~NewDatabaseWizardPageMasterKey() override; + +private slots: + void updateWindowSize(); +}; + +#endif //KEEPASSXC_NEWDATABASEWIZARDPAGEMASTERKEY_H diff --git a/src/gui/wizard/NewDatabaseWizardPageMetaData.cpp b/src/gui/wizard/NewDatabaseWizardPageMetaData.cpp new file mode 100644 index 000000000..f4e2712fb --- /dev/null +++ b/src/gui/wizard/NewDatabaseWizardPageMetaData.cpp @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2018 KeePassXC Team + * + * 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 "NewDatabaseWizardPageMetaData.h" +#include "gui/dbsettings/DatabaseSettingsWidgetMetaDataSimple.h" + +NewDatabaseWizardPageMetaData::NewDatabaseWizardPageMetaData(QWidget* parent) + : NewDatabaseWizardPage(parent) +{ + setPageWidget(new DatabaseSettingWidgetMetaData()); + + setTitle(tr("General Database Information")); + setSubTitle(tr("Please fill in the display name and an optional description for your new database:")); +} + +NewDatabaseWizardPageMetaData::~NewDatabaseWizardPageMetaData() +{ +} \ No newline at end of file diff --git a/src/gui/wizard/NewDatabaseWizardPageMetaData.h b/src/gui/wizard/NewDatabaseWizardPageMetaData.h new file mode 100644 index 000000000..44e8f1941 --- /dev/null +++ b/src/gui/wizard/NewDatabaseWizardPageMetaData.h @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2018 KeePassXC Team + * + * 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 KEEPASSXC_NEWDATABASEWIZARDPAGEMETADATA_H +#define KEEPASSXC_NEWDATABASEWIZARDPAGEMETADATA_H + +#include "NewDatabaseWizardPage.h" + +#include +#include + +class Database; + +class NewDatabaseWizardPageMetaData : public NewDatabaseWizardPage +{ +Q_OBJECT + +public: + explicit NewDatabaseWizardPageMetaData(QWidget* parent = nullptr); + Q_DISABLE_COPY(NewDatabaseWizardPageMetaData); + ~NewDatabaseWizardPageMetaData() override; +}; + +#endif //KEEPASSXC_NEWDATABASEWIZARDPAGEMETADATA_H diff --git a/src/keys/ChallengeResponseKey.h b/src/keys/ChallengeResponseKey.h index 702ee2511..2f7e7f4ca 100644 --- a/src/keys/ChallengeResponseKey.h +++ b/src/keys/ChallengeResponseKey.h @@ -20,15 +20,23 @@ #define KEEPASSX_CHALLENGE_RESPONSE_KEY_H #include +#include class ChallengeResponseKey { public: - virtual ~ChallengeResponseKey() - { - } + explicit ChallengeResponseKey(const QUuid& uuid) : m_uuid(uuid) {} + Q_DISABLE_COPY(ChallengeResponseKey); + virtual ~ChallengeResponseKey() {} virtual QByteArray rawKey() const = 0; virtual bool challenge(const QByteArray& challenge) = 0; + virtual QUuid uuid() const + { + return m_uuid; + } + +private: + QUuid m_uuid; }; #endif // KEEPASSX_CHALLENGE_RESPONSE_KEY_H diff --git a/src/keys/CompositeKey.cpp b/src/keys/CompositeKey.cpp index 3bce1559a..4fca7d320 100644 --- a/src/keys/CompositeKey.cpp +++ b/src/keys/CompositeKey.cpp @@ -1,6 +1,6 @@ /* +* Copyright (C) 2018 KeePassXC Team * Copyright (C) 2010 Felix Geyer -* Copyright (C) 2017 KeePassXC Team * * 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 @@ -25,13 +25,11 @@ #include "crypto/CryptoHash.h" #include "crypto/kdf/AesKdf.h" -CompositeKey::CompositeKey() -{ -} +QUuid CompositeKey::UUID("76a7ae25-a542-4add-9849-7c06be945b94"); -CompositeKey::CompositeKey(const CompositeKey& key) +CompositeKey::CompositeKey() + : Key(UUID) { - *this = key; } CompositeKey::~CompositeKey() @@ -41,7 +39,6 @@ CompositeKey::~CompositeKey() void CompositeKey::clear() { - qDeleteAll(m_keys); m_keys.clear(); m_challengeResponseKeys.clear(); } @@ -51,30 +48,6 @@ bool CompositeKey::isEmpty() const return m_keys.isEmpty() && m_challengeResponseKeys.isEmpty(); } -CompositeKey* CompositeKey::clone() const -{ - return new CompositeKey(*this); -} - -CompositeKey& CompositeKey::operator=(const CompositeKey& key) -{ - // handle self assignment as that would break when calling clear() - if (this == &key) { - return *this; - } - - clear(); - - for (const Key* subKey : asConst(key.m_keys)) { - addKey(*subKey); - } - for (const auto subKey : asConst(key.m_challengeResponseKeys)) { - addChallengeResponseKey(subKey); - } - - return *this; -} - /** * Get raw key hash as bytes. * @@ -104,7 +77,7 @@ QByteArray CompositeKey::rawKey(const QByteArray* transformSeed, bool* ok) const { CryptoHash cryptoHash(CryptoHash::Sha256); - for (const Key* key : m_keys) { + for (auto const& key : m_keys) { cryptoHash.addData(key->rawKey()); } @@ -174,12 +147,41 @@ bool CompositeKey::challenge(const QByteArray& seed, QByteArray& result) const return true; } -void CompositeKey::addKey(const Key& key) +/** + * Add a \link Key to this composite key. + * Keys will be hashed in the order they were initially added. + * + * @param key the key + */ +void CompositeKey::addKey(QSharedPointer key) { - m_keys.append(key.clone()); + m_keys.append(key); } +/** + * @return list of Keys which are part of this CompositeKey + */ +const QList>& CompositeKey::keys() const +{ + return m_keys; +} + +/** + * Add a \link ChallengeResponseKey to this composite key. + * ChallengeResponseKeys will be hashed in the order they were initially added, + * but will always come after normal \link Keys. + * + * @param key the key + */ void CompositeKey::addChallengeResponseKey(QSharedPointer key) { m_challengeResponseKeys.append(key); } + +/** + * @return list of ChallengeResponseKeys which are part of this CompositeKey + */ +const QList>& CompositeKey::challengeResponseKeys() const +{ + return m_challengeResponseKeys; +} diff --git a/src/keys/CompositeKey.h b/src/keys/CompositeKey.h index 50d9dbd37..d81c46986 100644 --- a/src/keys/CompositeKey.h +++ b/src/keys/CompositeKey.h @@ -1,6 +1,6 @@ /* +* Copyright (C) 2018 KeePassXC Team * Copyright (C) 2010 Felix Geyer -* Copyright (C) 2017 KeePassXC Team * * 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 @@ -30,24 +30,26 @@ class CompositeKey : public Key { public: + static QUuid UUID; + CompositeKey(); - CompositeKey(const CompositeKey& key); ~CompositeKey() override; void clear(); bool isEmpty() const; - CompositeKey* clone() const override; - CompositeKey& operator=(const CompositeKey& key); QByteArray rawKey() const override; QByteArray rawKey(const QByteArray* transformSeed, bool* ok = nullptr) const; bool transform(const Kdf& kdf, QByteArray& result) const Q_REQUIRED_RESULT; bool challenge(const QByteArray& seed, QByteArray& result) const; - void addKey(const Key& key); - void addChallengeResponseKey(QSharedPointer key); + void addKey(QSharedPointer key); + const QList>& keys() const; + + void addChallengeResponseKey(QSharedPointer key);\ + const QList>& challengeResponseKeys() const; private: - QList m_keys; + QList> m_keys; QList> m_challengeResponseKeys; }; diff --git a/src/keys/FileKey.cpp b/src/keys/FileKey.cpp index b491fc51e..6751b7877 100644 --- a/src/keys/FileKey.cpp +++ b/src/keys/FileKey.cpp @@ -24,6 +24,13 @@ #include "crypto/CryptoHash.h" #include "crypto/Random.h" +QUuid FileKey::UUID("a584cbc4-c9b4-437e-81bb-362ca9709273"); + +FileKey::FileKey() + : Key(UUID) +{ +} + /** * Read key file from device while trying to detect its file format. * @@ -144,14 +151,6 @@ QByteArray FileKey::rawKey() const return m_key; } -/** - * @return cloned \link FileKey instance - */ -FileKey* FileKey::clone() const -{ - return new FileKey(*this); -} - /** * Generate a new key file from random bytes. * diff --git a/src/keys/FileKey.h b/src/keys/FileKey.h index 3f04edede..295fe5565 100644 --- a/src/keys/FileKey.h +++ b/src/keys/FileKey.h @@ -28,6 +28,8 @@ class QIODevice; class FileKey : public Key { public: + static QUuid UUID; + enum Type { None, @@ -37,10 +39,10 @@ public: FixedBinaryHex }; + FileKey(); bool load(QIODevice* device); bool load(const QString& fileName, QString* errorMsg = nullptr); QByteArray rawKey() const override; - FileKey* clone() const override; Type type() const; static void create(QIODevice* device, int size = 128); static bool create(const QString& fileName, QString* errorMsg = nullptr, int size = 128); diff --git a/src/keys/Key.h b/src/keys/Key.h index 4014e3907..db7864ec3 100644 --- a/src/keys/Key.h +++ b/src/keys/Key.h @@ -1,4 +1,5 @@ /* +* Copyright (C) 2018 KeePassXC Team * Copyright (C) 2010 Felix Geyer * * This program is free software: you can redistribute it and/or modify @@ -19,15 +20,22 @@ #define KEEPASSX_KEY_H #include +#include class Key { public: - virtual ~Key() - { - } + explicit Key(const QUuid& uuid) : m_uuid(uuid) {}; + Q_DISABLE_COPY(Key); + virtual ~Key() = default; virtual QByteArray rawKey() const = 0; - virtual Key* clone() const = 0; + inline virtual QUuid uuid() const + { + return m_uuid; + } + +private: + QUuid m_uuid; }; #endif // KEEPASSX_KEY_H diff --git a/src/keys/PasswordKey.cpp b/src/keys/PasswordKey.cpp index ea1440480..9fecc7637 100644 --- a/src/keys/PasswordKey.cpp +++ b/src/keys/PasswordKey.cpp @@ -19,19 +19,23 @@ #include "crypto/CryptoHash.h" +QUuid PasswordKey::UUID("77e90411-303a-43f2-b773-853b05635ead"); + PasswordKey::PasswordKey() + : Key(UUID) { } PasswordKey::PasswordKey(const QString& password) + : Key(UUID) { setPassword(password); } -PasswordKey PasswordKey::fromRawKey(const QByteArray& rawKey) +QSharedPointer PasswordKey::fromRawKey(const QByteArray& rawKey) { - PasswordKey result; - result.m_key = rawKey; + auto result = QSharedPointer::create(); + result->m_key = rawKey; return result; } @@ -44,8 +48,3 @@ void PasswordKey::setPassword(const QString& password) { m_key = CryptoHash::hash(password.toUtf8(), CryptoHash::Sha256); } - -PasswordKey* PasswordKey::clone() const -{ - return new PasswordKey(*this); -} diff --git a/src/keys/PasswordKey.h b/src/keys/PasswordKey.h index 821510703..46c47e6d3 100644 --- a/src/keys/PasswordKey.h +++ b/src/keys/PasswordKey.h @@ -19,19 +19,21 @@ #define KEEPASSX_PASSWORDKEY_H #include +#include #include "keys/Key.h" class PasswordKey : public Key { public: + static QUuid UUID; + PasswordKey(); explicit PasswordKey(const QString& password); - QByteArray rawKey() const; + QByteArray rawKey() const override; void setPassword(const QString& password); - PasswordKey* clone() const; - static PasswordKey fromRawKey(const QByteArray& rawKey); + static QSharedPointer fromRawKey(const QByteArray& rawKey); private: QByteArray m_key; }; diff --git a/src/keys/YkChallengeResponseKey.cpp b/src/keys/YkChallengeResponseKey.cpp index 913e2d32a..b2a40bd23 100644 --- a/src/keys/YkChallengeResponseKey.cpp +++ b/src/keys/YkChallengeResponseKey.cpp @@ -30,8 +30,11 @@ #include #include +QUuid YkChallengeResponseKey::UUID("e092495c-e77d-498b-84a1-05ae0d955508"); + YkChallengeResponseKey::YkChallengeResponseKey(int slot, bool blocking) - : m_slot(slot) + : ChallengeResponseKey(UUID) + , m_slot(slot) , m_blocking(blocking) { if (KEEPASSXC_MAIN_WINDOW) { diff --git a/src/keys/YkChallengeResponseKey.h b/src/keys/YkChallengeResponseKey.h index 912896e9d..0ce915271 100644 --- a/src/keys/YkChallengeResponseKey.h +++ b/src/keys/YkChallengeResponseKey.h @@ -29,10 +29,12 @@ class YkChallengeResponseKey : public QObject, public ChallengeResponseKey Q_OBJECT public: - YkChallengeResponseKey(int slot = -1, bool blocking = false); + static QUuid UUID; - QByteArray rawKey() const; - bool challenge(const QByteArray& challenge); + explicit YkChallengeResponseKey(int slot = -1, bool blocking = false); + + QByteArray rawKey() const override; + bool challenge(const QByteArray& challenge) override; bool challenge(const QByteArray& challenge, unsigned retries); QString getName() const; bool isBlocking() const; diff --git a/src/sshagent/AgentSettingsPage.h b/src/sshagent/AgentSettingsPage.h index 1cd4d9a57..09d898048 100644 --- a/src/sshagent/AgentSettingsPage.h +++ b/src/sshagent/AgentSettingsPage.h @@ -20,7 +20,7 @@ #define AGENTSETTINGSPAGE_H #include "gui/DatabaseTabWidget.h" -#include "gui/SettingsWidget.h" +#include "gui/ApplicationSettingsWidget.h" class AgentSettingsPage : public ISettingsPage { diff --git a/src/touchid/TouchID.h b/src/touchid/TouchID.h index cae108b64..115943a27 100644 --- a/src/touchid/TouchID.h +++ b/src/touchid/TouchID.h @@ -12,44 +12,32 @@ class TouchID { - public: - static TouchID& getInstance(); +public: + static TouchID& getInstance(); - private: - TouchID() {} // Constructor? (the {} brackets) are needed here. +private: + TouchID() {} - // C++ 03 - // ======== - // Don't forget to declare these two. You want to make sure they - // are unacceptable otherwise you may accidentally get copies of - // your singleton appearing. + // TouchID(TouchID const&); // Don't Implement + // void operator=(TouchID const&); // Don't implement - // TouchID(TouchID const&); // Don't Implement - // void operator=(TouchID const&); // Don't implement + QHash m_encryptedMasterKeys; + int m_available = TOUCHID_UNDEFINED; - QHash m_encryptedMasterKeys; - int m_available = TOUCHID_UNDEFINED; +public: + TouchID(TouchID const&) = delete; - public: - // C++ 11 - // ======= - // We can use the better technique of deleting the methods - // we don't want. + void operator=(TouchID const&) = delete; - TouchID(TouchID const&) = delete; - void operator=(TouchID const&) = delete; + bool storeKey(const QString& databasePath, const QByteArray& passwordKey); - // Note: Scott Meyers mentions in his Effective Modern - // C++ book, that deleted functions should generally - // be public as it results in better error messages - // due to the compilers behavior to check accessibility - // before deleted status + QSharedPointer getKey(const QString& databasePath) const; - bool storeKey(const QString& databasePath, const QByteArray& passwordKey); - QSharedPointer getKey(const QString& databasePath) const; - bool isAvailable(); - bool authenticate(const QString& message = "") const; - void reset(const QString& databasePath = ""); + bool isAvailable(); + + bool authenticate(const QString& message = "") const; + + void reset(const QString& databasePath = ""); }; #endif // KEEPASSX_TOUCHID_H diff --git a/src/touchid/TouchID.mm b/src/touchid/TouchID.mm index 45f2de0f7..9ef72189b 100644 --- a/src/touchid/TouchID.mm +++ b/src/touchid/TouchID.mm @@ -24,20 +24,28 @@ inline QString hash(const QString& value) return QString(result); } -/* Singleton */ +/** + * Singleton + */ TouchID& TouchID::getInstance() { static TouchID instance; // Guaranteed to be destroyed. - // Instantiated on first use. + // Instantiated on first use. return instance; } -/* Generates a random AES 256bit key and uses it to encrypt the PasswordKey that protects the database. The encrypted PasswordKey is kept in memory while the AES key is stored in the macOS KeyChain protected by TouchID. */ +/** + * Generates a random AES 256bit key and uses it to encrypt the PasswordKey that + * protects the database. The encrypted PasswordKey is kept in memory while the + * AES key is stored in the macOS KeyChain protected by TouchID. + */ bool TouchID::storeKey(const QString& databasePath, const QByteArray& passwordKey) { if (databasePath.isEmpty() || passwordKey.isEmpty()) { // illegal arguments - debug("TouchID::storeKey - Illegal arguments: databasePath = %s, len(passwordKey) = %d", databasePath.toUtf8().constData(), passwordKey.length()); + debug("TouchID::storeKey - Illegal arguments: databasePath = %s, len(passwordKey) = %d", + databasePath.toUtf8().constData(), + passwordKey.length()); return false; } @@ -56,7 +64,8 @@ bool TouchID::storeKey(const QString& databasePath, const QByteArray& passwordKe SymmetricCipher aes256Encrypt(SymmetricCipher::Aes256, SymmetricCipher::Cbc, SymmetricCipher::Encrypt); if (!aes256Encrypt.init(randomKey, randomIV)) { - debug("TouchID::storeKey - Error initializing encryption: %s", aes256Encrypt.errorString().toUtf8().constData()); + debug("TouchID::storeKey - Error initializing encryption: %s", + aes256Encrypt.errorString().toUtf8().constData()); return false; } @@ -73,23 +82,24 @@ bool TouchID::storeKey(const QString& databasePath, const QByteArray& passwordKe NSString* accountName = (SECURITY_ACCOUNT_PREFIX + hash(databasePath)).toNSString(); // autoreleased // try to delete an existing entry - CFMutableDictionaryRef query = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); - + CFMutableDictionaryRef + query = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); + CFDictionarySetValue(query, kSecClass, kSecClassGenericPassword); - CFDictionarySetValue(query, kSecAttrAccount, (__bridge CFStringRef)accountName); + CFDictionarySetValue(query, kSecAttrAccount, (__bridge CFStringRef) accountName); CFDictionarySetValue(query, kSecReturnData, kCFBooleanFalse); - + // get data from the KeyChain - OSStatus status = SecItemDelete(query); + OSStatus status = SecItemDelete(query); debug("TouchID::storeKey - Status deleting existing entry: %d", status); // prepare adding secure entry to the macOS KeyChain CFErrorRef error = NULL; SecAccessControlRef sacObject = SecAccessControlCreateWithFlags(kCFAllocatorDefault, - kSecAttrAccessibleWhenUnlockedThisDeviceOnly, - kSecAccessControlTouchIDCurrentSet, // depr: kSecAccessControlBiometryCurrentSet, - &error); + kSecAttrAccessibleWhenUnlockedThisDeviceOnly, + kSecAccessControlTouchIDCurrentSet, // depr: kSecAccessControlBiometryCurrentSet, + &error); if (sacObject == NULL || error != NULL) { NSError* e = (__bridge NSError*) error; @@ -97,22 +107,24 @@ bool TouchID::storeKey(const QString& databasePath, const QByteArray& passwordKe return false; } - CFMutableDictionaryRef attributes = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); - + CFMutableDictionaryRef attributes = + CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); + // prepare data (key) to be stored QByteArray dataBytes = (randomKey + randomIV).toHex(); - CFDataRef valueData = CFDataCreateWithBytesNoCopy(NULL, reinterpret_cast(dataBytes.data()), dataBytes.length(), NULL); + CFDataRef valueData = + CFDataCreateWithBytesNoCopy(NULL, reinterpret_cast(dataBytes.data()), dataBytes.length(), NULL); CFDictionarySetValue(attributes, kSecClass, kSecClassGenericPassword); - CFDictionarySetValue(attributes, kSecAttrAccount, (__bridge CFStringRef)accountName); + CFDictionarySetValue(attributes, kSecAttrAccount, (__bridge CFStringRef) accountName); CFDictionarySetValue(attributes, kSecValueData, valueData); CFDictionarySetValue(attributes, kSecAttrSynchronizable, kCFBooleanFalse); CFDictionarySetValue(attributes, kSecUseAuthenticationUI, kSecUseAuthenticationUIAllow); CFDictionarySetValue(attributes, kSecAttrAccessControl, sacObject); // add to KeyChain - status = SecItemAdd(attributes, NULL); + status = SecItemAdd(attributes, NULL); debug("TouchID::storeKey - Status adding new entry: %d", status); // read w/ e.g. "security error -50" in shell @@ -128,8 +140,11 @@ bool TouchID::storeKey(const QString& databasePath, const QByteArray& passwordKe return true; } -/* Checks if an encrypted PasswordKey is available for the given database, tries to decrypt it using the KeyChain and if successful, returns it. */ -QSharedPointer TouchID::getKey(const QString& databasePath) const +/** + * Checks if an encrypted PasswordKey is available for the given database, tries to + * decrypt it using the KeyChain and if successful, returns it. + */ +QSharedPointer TouchID::getKey(const QString& databasePath) const { if (databasePath.isEmpty()) { // illegal arguments @@ -142,21 +157,24 @@ QSharedPointer TouchID::getKey(const QString& databasePath) const debug("TouchID::getKey - No stored key found"); return NULL; } - + // query the KeyChain for the AES key - CFMutableDictionaryRef query = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); - + CFMutableDictionaryRef + query = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); + NSString* accountName = (SECURITY_ACCOUNT_PREFIX + hash(databasePath)).toNSString(); // autoreleased - NSString* touchPromptMessage = QCoreApplication::translate("DatabaseOpenWidget", "authenticate to access the database").toNSString(); // autoreleased + NSString* touchPromptMessage = + QCoreApplication::translate("DatabaseOpenWidget", "authenticate to access the database") + .toNSString(); // autoreleased CFDictionarySetValue(query, kSecClass, kSecClassGenericPassword); - CFDictionarySetValue(query, kSecAttrAccount, (__bridge CFStringRef)accountName); + CFDictionarySetValue(query, kSecAttrAccount, (__bridge CFStringRef) accountName); CFDictionarySetValue(query, kSecReturnData, kCFBooleanTrue); - CFDictionarySetValue(query, kSecUseOperationPrompt, (__bridge CFStringRef)touchPromptMessage); - + CFDictionarySetValue(query, kSecUseOperationPrompt, (__bridge CFStringRef) touchPromptMessage); + // get data from the KeyChain CFTypeRef dataTypeRef = NULL; - OSStatus status = SecItemCopyMatching(query, &dataTypeRef); + OSStatus status = SecItemCopyMatching(query, &dataTypeRef); CFRelease(query); if (status == errSecUserCanceled) { @@ -169,9 +187,10 @@ QSharedPointer TouchID::getKey(const QString& databasePath) const } CFDataRef valueData = static_cast(dataTypeRef); - QByteArray dataBytes = QByteArray::fromHex(QByteArray(reinterpret_cast(CFDataGetBytePtr(valueData)), CFDataGetLength(valueData))); + QByteArray dataBytes = QByteArray::fromHex(QByteArray(reinterpret_cast(CFDataGetBytePtr(valueData)), + CFDataGetLength(valueData))); CFRelease(valueData); - + // extract AES key and IV from data bytes QByteArray key = dataBytes.left(32); QByteArray iv = dataBytes.right(16); @@ -194,7 +213,9 @@ QSharedPointer TouchID::getKey(const QString& databasePath) const return QSharedPointer::create(result); } -/* Dynamic check if TouchID is available on the current machine. */ +/** + * Dynamic check if TouchID is available on the current machine. + */ bool TouchID::isAvailable() { // cache result @@ -208,47 +229,53 @@ bool TouchID::isAvailable() this->m_available = canAuthenticate ? TOUCHID_AVAILABLE : TOUCHID_NOT_AVAILABLE; return canAuthenticate; } - @catch(NSException*) { + @catch (NSException*) { this->m_available = TOUCHID_NOT_AVAILABLE; return false; } } -typedef enum { +typedef enum +{ kTouchIDResultNone, kTouchIDResultAllowed, kTouchIDResultFailed } TouchIDResult; -/* Performs a simple authentication using TouchID. */ +/** + * Performs a simple authentication using TouchID. + */ bool TouchID::authenticate(const QString& message) const { // message must not be an empty string QString msg = message; if (message.length() == 0) msg = QCoreApplication::translate("DatabaseOpenWidget", "authenticate a privileged operation"); - + @try { LAContext* context = [[LAContext alloc] init]; __block TouchIDResult result = kTouchIDResultNone; NSString* authMessage = msg.toNSString(); // autoreleased - [context evaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics localizedReason:authMessage reply:^(BOOL success, NSError* error) { - result = success ? kTouchIDResultAllowed : kTouchIDResultFailed; - CFRunLoopWakeUp(CFRunLoopGetCurrent()); - }]; - + [context evaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics + localizedReason:authMessage reply:^(BOOL success, NSError* error) { + result = success ? kTouchIDResultAllowed : kTouchIDResultFailed; + CFRunLoopWakeUp(CFRunLoopGetCurrent()); + }]; + while (result == kTouchIDResultNone) CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, true); - + [context release]; return result == kTouchIDResultAllowed; } - @catch(NSException*) { + @catch (NSException*) { return false; } } -/* Resets the inner state either for all or for the given database */ +/** + * Resets the inner state either for all or for the given database + */ void TouchID::reset(const QString& databasePath) { if (databasePath.isEmpty()) { diff --git a/tests/TestDatabase.cpp b/tests/TestDatabase.cpp index 51304b1fb..78e1b10a4 100644 --- a/tests/TestDatabase.cpp +++ b/tests/TestDatabase.cpp @@ -38,60 +38,54 @@ void TestDatabase::initTestCase() void TestDatabase::testEmptyRecycleBinOnDisabled() { QString filename = QString(KEEPASSX_TEST_DATA_DIR).append("/RecycleBinDisabled.kdbx"); - CompositeKey key; - key.addKey(PasswordKey("123")); - Database* db = Database::openDatabaseFile(filename, key); + auto key = QSharedPointer::create(); + key->addKey(QSharedPointer::create("123")); + QScopedPointer db(Database::openDatabaseFile(filename, key)); QVERIFY(db); - QSignalSpy spyModified(db, SIGNAL(modifiedImmediate())); + QSignalSpy spyModified(db.data(), SIGNAL(modifiedImmediate())); db->emptyRecycleBin(); // The database must be unmodified in this test after emptying the recycle bin. QCOMPARE(spyModified.count(), 0); - - delete db; } void TestDatabase::testEmptyRecycleBinOnNotCreated() { QString filename = QString(KEEPASSX_TEST_DATA_DIR).append("/RecycleBinNotYetCreated.kdbx"); - CompositeKey key; - key.addKey(PasswordKey("123")); - Database* db = Database::openDatabaseFile(filename, key); + auto key = QSharedPointer::create(); + key->addKey(QSharedPointer::create("123")); + QScopedPointer db(Database::openDatabaseFile(filename, key)); QVERIFY(db); - QSignalSpy spyModified(db, SIGNAL(modifiedImmediate())); + QSignalSpy spyModified(db.data(), SIGNAL(modifiedImmediate())); db->emptyRecycleBin(); // The database must be unmodified in this test after emptying the recycle bin. QCOMPARE(spyModified.count(), 0); - - delete db; } void TestDatabase::testEmptyRecycleBinOnEmpty() { QString filename = QString(KEEPASSX_TEST_DATA_DIR).append("/RecycleBinEmpty.kdbx"); - CompositeKey key; - key.addKey(PasswordKey("123")); - Database* db = Database::openDatabaseFile(filename, key); + auto key = QSharedPointer::create(); + key->addKey(QSharedPointer::create("123")); + QScopedPointer db(Database::openDatabaseFile(filename, key)); QVERIFY(db); - QSignalSpy spyModified(db, SIGNAL(modifiedImmediate())); + QSignalSpy spyModified(db.data(), SIGNAL(modifiedImmediate())); db->emptyRecycleBin(); // The database must be unmodified in this test after emptying the recycle bin. QCOMPARE(spyModified.count(), 0); - - delete db; } void TestDatabase::testEmptyRecycleBinWithHierarchicalData() { QString filename = QString(KEEPASSX_TEST_DATA_DIR).append("/RecycleBinWithData.kdbx"); - CompositeKey key; - key.addKey(PasswordKey("123")); - Database* db = Database::openDatabaseFile(filename, key); + auto key = QSharedPointer::create(); + key->addKey(QSharedPointer::create("123")); + QScopedPointer db(Database::openDatabaseFile(filename, key)); QVERIFY(db); QFile originalFile(filename); @@ -104,8 +98,6 @@ void TestDatabase::testEmptyRecycleBinWithHierarchicalData() QTemporaryFile afterCleanup; KeePass2Writer writer; - writer.writeDatabase(&afterCleanup, db); + writer.writeDatabase(&afterCleanup, db.data()); QVERIFY(afterCleanup.size() < initialSize); - - delete db; } diff --git a/tests/TestKdbx2.cpp b/tests/TestKdbx2.cpp index 9fe90ae62..ef944f7fd 100644 --- a/tests/TestKdbx2.cpp +++ b/tests/TestKdbx2.cpp @@ -65,8 +65,8 @@ void TestKdbx2::verifyKdbx2Db(Database* db) void TestKdbx2::testFormat200() { QString filename = QString(KEEPASSX_TEST_DATA_DIR).append("/Format200.kdbx"); - CompositeKey key; - key.addKey(PasswordKey("a")); + auto key = QSharedPointer::create(); + key->addKey(QSharedPointer::create("a")); KeePass2Reader reader; QScopedPointer db(reader.readDatabase(filename, key)); QCOMPARE(reader.version(), KeePass2::FILE_VERSION_2 & KeePass2::FILE_VERSION_CRITICAL_MASK); @@ -78,8 +78,8 @@ void TestKdbx2::testFormat200() void TestKdbx2::testFormat200Upgrade() { QString filename = QString(KEEPASSX_TEST_DATA_DIR).append("/Format200.kdbx"); - CompositeKey key; - key.addKey(PasswordKey("a")); + auto key = QSharedPointer::create(); + key->addKey(QSharedPointer::create("a")); KeePass2Reader reader; QScopedPointer db(reader.readDatabase(filename, key)); QVERIFY2(!reader.hasError(), reader.errorString().toStdString().c_str()); diff --git a/tests/TestKdbx3.cpp b/tests/TestKdbx3.cpp index 6df13d380..1c8526b3e 100644 --- a/tests/TestKdbx3.cpp +++ b/tests/TestKdbx3.cpp @@ -63,7 +63,7 @@ void TestKdbx3::writeXml(QBuffer* buf, Database* db, bool& hasError, QString& er } void TestKdbx3::readKdbx(QIODevice* device, - CompositeKey const& key, + QSharedPointer key, QScopedPointer& db, bool& hasError, QString& errorString) @@ -78,7 +78,7 @@ void TestKdbx3::readKdbx(QIODevice* device, } void TestKdbx3::readKdbx(const QString& path, - CompositeKey const& key, + QSharedPointer key, QScopedPointer& db, bool& hasError, QString& errorString) @@ -106,8 +106,8 @@ void TestKdbx3::writeKdbx(QIODevice* device, Database* db, bool& hasError, QStri void TestKdbx3::testFormat300() { QString filename = QString(KEEPASSX_TEST_DATA_DIR).append("/Format300.kdbx"); - CompositeKey key; - key.addKey(PasswordKey("a")); + auto key = QSharedPointer::create(); + key->addKey(QSharedPointer::create("a")); KeePass2Reader reader; QScopedPointer db(reader.readDatabase(filename, key)); QCOMPARE(reader.version(), KeePass2::FILE_VERSION_3); @@ -121,8 +121,8 @@ void TestKdbx3::testFormat300() void TestKdbx3::testNonAscii() { QString filename = QString(KEEPASSX_TEST_DATA_DIR).append("/NonAscii.kdbx"); - CompositeKey key; - key.addKey(PasswordKey(QString::fromUtf8("\xce\x94\xc3\xb6\xd8\xb6"))); + auto key = QSharedPointer::create(); + key->addKey(QSharedPointer::create(QString::fromUtf8("\xce\x94\xc3\xb6\xd8\xb6"))); KeePass2Reader reader; QScopedPointer db(reader.readDatabase(filename, key)); QVERIFY(db.data()); @@ -134,8 +134,8 @@ void TestKdbx3::testNonAscii() void TestKdbx3::testCompressed() { QString filename = QString(KEEPASSX_TEST_DATA_DIR).append("/Compressed.kdbx"); - CompositeKey key; - key.addKey(PasswordKey("")); + auto key = QSharedPointer::create(); + key->addKey(QSharedPointer::create("")); KeePass2Reader reader; QScopedPointer db(reader.readDatabase(filename, key)); QVERIFY(db.data()); @@ -147,8 +147,8 @@ void TestKdbx3::testCompressed() void TestKdbx3::testProtectedStrings() { QString filename = QString(KEEPASSX_TEST_DATA_DIR).append("/ProtectedStrings.kdbx"); - CompositeKey key; - key.addKey(PasswordKey("masterpw")); + auto key = QSharedPointer::create(); + key->addKey(QSharedPointer::create("masterpw")); KeePass2Reader reader; QScopedPointer db(reader.readDatabase(filename, key)); QVERIFY(db.data()); @@ -174,8 +174,8 @@ void TestKdbx3::testBrokenHeaderHash() // Make sure the database won't open. QString filename = QString(KEEPASSX_TEST_DATA_DIR).append("/BrokenHeaderHash.kdbx"); - CompositeKey key; - key.addKey(PasswordKey("")); + auto key = QSharedPointer::create(); + key->addKey(QSharedPointer::create("")); KeePass2Reader reader; QScopedPointer db(reader.readDatabase(filename, key)); QVERIFY(!db.data()); @@ -188,8 +188,8 @@ void TestKdbx3::testKdbxRepair() // master password = test // entry username: testuser\x10\x20AC // entry password: testpw - CompositeKey key; - key.addKey(PasswordKey("test")); + auto key = QSharedPointer::create(); + key->addKey(QSharedPointer::create("test")); // test that we can't open the broken database bool hasError; diff --git a/tests/TestKdbx3.h b/tests/TestKdbx3.h index 298cefc6b..f96e16061 100644 --- a/tests/TestKdbx3.h +++ b/tests/TestKdbx3.h @@ -40,12 +40,12 @@ protected: void writeXml(QBuffer* buf, Database* db, bool& hasError, QString& errorString) override; void readKdbx(QIODevice* device, - CompositeKey const& key, + QSharedPointer key, QScopedPointer& db, bool& hasError, QString& errorString) override; void readKdbx(const QString& path, - CompositeKey const& key, + QSharedPointer key, QScopedPointer& db, bool& hasError, QString& errorString) override; diff --git a/tests/TestKdbx4.cpp b/tests/TestKdbx4.cpp index 9758ac13f..72c2d4c83 100644 --- a/tests/TestKdbx4.cpp +++ b/tests/TestKdbx4.cpp @@ -66,7 +66,7 @@ void TestKdbx4::writeXml(QBuffer* buf, Database* db, bool& hasError, QString& er } void TestKdbx4::readKdbx(QIODevice* device, - CompositeKey const& key, + QSharedPointer key, QScopedPointer& db, bool& hasError, QString& errorString) @@ -81,7 +81,7 @@ void TestKdbx4::readKdbx(QIODevice* device, } void TestKdbx4::readKdbx(const QString& path, - CompositeKey const& key, + QSharedPointer key, QScopedPointer& db, bool& hasError, QString& errorString) @@ -113,8 +113,8 @@ Q_DECLARE_METATYPE(QUuid) void TestKdbx4::testFormat400() { QString filename = QString(KEEPASSX_TEST_DATA_DIR).append("/Format400.kdbx"); - CompositeKey key; - key.addKey(PasswordKey("t")); + auto key = QSharedPointer::create(); + key->addKey(QSharedPointer::create("t")); KeePass2Reader reader; QScopedPointer db(reader.readDatabase(filename, key)); QCOMPARE(reader.version(), KeePass2::FILE_VERSION_4); @@ -146,8 +146,8 @@ void TestKdbx4::testFormat400Upgrade() sourceDb->metadata()->setName("Wubba lubba dub dub"); QCOMPARE(sourceDb->kdf()->uuid(), KeePass2::KDF_AES_KDBX3); // default is legacy AES-KDF - CompositeKey key; - key.addKey(PasswordKey("I am in great pain, please help me!")); + auto key = QSharedPointer::create(); + key->addKey(QSharedPointer::create("I am in great pain, please help me!")); sourceDb->setKey(key, true, true); QBuffer buffer; @@ -228,20 +228,20 @@ void TestKdbx4::testUpgradeMasterKeyIntegrity() QFETCH(quint32, expectedVersion); // prepare composite key - PasswordKey passwordKey("turXpGMQiUE6CkPvWacydAKsnp4cxz"); + auto passwordKey = QSharedPointer::create("turXpGMQiUE6CkPvWacydAKsnp4cxz"); QByteArray fileKeyBytes("Ma6hHov98FbPeyAL22XhcgmpJk8xjQ"); QBuffer fileKeyBuffer(&fileKeyBytes); fileKeyBuffer.open(QBuffer::ReadOnly); - FileKey fileKey; - fileKey.load(&fileKeyBuffer); + auto fileKey = QSharedPointer::create(); + fileKey->load(&fileKeyBuffer); auto crKey = QSharedPointer::create(QByteArray("azdJnbVCFE76vV6t9RJ2DS6xvSS93k")); - CompositeKey compositeKey; - compositeKey.addKey(passwordKey); - compositeKey.addKey(fileKey); - compositeKey.addChallengeResponseKey(crKey); + auto compositeKey = QSharedPointer::create(); + compositeKey->addKey(passwordKey); + compositeKey->addKey(fileKey); + compositeKey->addChallengeResponseKey(crKey); QScopedPointer db(new Database()); db->changeKdf(fastKdf(db->kdf())); @@ -293,7 +293,7 @@ void TestKdbx4::testUpgradeMasterKeyIntegrity() buffer.seek(0); KeePass2Reader reader; QScopedPointer db2; - db2.reset(reader.readDatabase(&buffer, CompositeKey())); + db2.reset(reader.readDatabase(&buffer, QSharedPointer::create())); QVERIFY(reader.hasError()); // check that we can read back the database with the original composite key, @@ -396,7 +396,7 @@ void TestKdbx4::testCustomData() // read buffer back buffer.seek(0); KeePass2Reader reader; - QSharedPointer newDb(reader.readDatabase(&buffer, CompositeKey())); + QSharedPointer newDb(reader.readDatabase(&buffer, QSharedPointer::create())); // test all custom data are read back successfully from KDBX QCOMPARE(newDb->publicCustomData(), publicCustomData); diff --git a/tests/TestKdbx4.h b/tests/TestKdbx4.h index 1b677b4ab..edf319a96 100644 --- a/tests/TestKdbx4.h +++ b/tests/TestKdbx4.h @@ -40,12 +40,12 @@ protected: void writeXml(QBuffer* buf, Database* db, bool& hasError, QString& errorString) override; void readKdbx(const QString& path, - CompositeKey const& key, + QSharedPointer key, QScopedPointer& db, bool& hasError, QString& errorString) override; void readKdbx(QIODevice* device, - CompositeKey const& key, + QSharedPointer key, QScopedPointer& db, bool& hasError, QString& errorString) override; diff --git a/tests/TestKeePass1Reader.cpp b/tests/TestKeePass1Reader.cpp index 3cf95286d..a3c148324 100644 --- a/tests/TestKeePass1Reader.cpp +++ b/tests/TestKeePass1Reader.cpp @@ -273,19 +273,18 @@ void TestKeePass1Reader::reopenDatabase(Database* db, const QString& password, c QVERIFY(!writer.hasError()); QVERIFY(buffer.seek(0)); - CompositeKey key; + auto key = QSharedPointer::create(); if (!password.isNull()) { - key.addKey(PasswordKey(password)); + key->addKey(QSharedPointer::create(password)); } if (!keyfileName.isEmpty()) { - FileKey fileKey; - QVERIFY(fileKey.load(keyfileName)); - key.addKey(fileKey); + auto fileKey = QSharedPointer::create(); + QVERIFY(fileKey->load(keyfileName)); + key->addKey(fileKey); } KeePass2Reader reader; - Database* newDb = reader.readDatabase(&buffer, key); + QScopedPointer newDb(reader.readDatabase(&buffer, key)); QVERIFY(newDb); QVERIFY(!reader.hasError()); - delete newDb; } diff --git a/tests/TestKeePass2Format.cpp b/tests/TestKeePass2Format.cpp index c5f22ef6d..37b5b7838 100644 --- a/tests/TestKeePass2Format.cpp +++ b/tests/TestKeePass2Format.cpp @@ -40,8 +40,8 @@ void TestKeePass2Format::initTestCase() QVERIFY(m_xmlDb.data()); // construct and write KDBX to buffer - CompositeKey key; - key.addKey(PasswordKey("test")); + auto key = QSharedPointer::create(); + key->addKey(QSharedPointer::create("test")); m_kdbxSourceDb.reset(new Database()); m_kdbxSourceDb->setKey(key); @@ -495,8 +495,8 @@ void TestKeePass2Format::testXmlRepairUuidHistoryItem() void TestKeePass2Format::testReadBackTargetDb() { // read back previously constructed KDBX - CompositeKey key; - key.addKey(PasswordKey("test")); + auto key = QSharedPointer::create(); + key->addKey(QSharedPointer::create("test")); bool hasError; QString errorString; @@ -542,8 +542,8 @@ void TestKeePass2Format::testKdbxNonAsciiPasswords() void TestKeePass2Format::testKdbxDeviceFailure() { - CompositeKey key; - key.addKey(PasswordKey("test")); + auto key = QSharedPointer::create(); + key->addKey(QSharedPointer::create("test")); QScopedPointer db(new Database()); db->setKey(key); // Disable compression so we write a predictable number of bytes. @@ -569,7 +569,7 @@ void TestKeePass2Format::testKdbxDeviceFailure() void TestKeePass2Format::testDuplicateAttachments() { QScopedPointer db(new Database()); - db->setKey(CompositeKey()); + db->setKey(QSharedPointer::create()); const QByteArray attachment1("abc"); const QByteArray attachment2("def"); @@ -612,7 +612,7 @@ void TestKeePass2Format::testDuplicateAttachments() } buffer.seek(0); - readKdbx(&buffer, CompositeKey(), db, hasError, errorString); + readKdbx(&buffer, QSharedPointer::create(), db, hasError, errorString); if (hasError) { QFAIL(qPrintable(QString("Error while reading database: %1").arg(errorString))); } diff --git a/tests/TestKeePass2Format.h b/tests/TestKeePass2Format.h index d29fa2c13..19b0fb649 100644 --- a/tests/TestKeePass2Format.h +++ b/tests/TestKeePass2Format.h @@ -72,12 +72,12 @@ protected: virtual void writeXml(QBuffer* buf, Database* db, bool& hasError, QString& errorString) = 0; virtual void readKdbx(QIODevice* device, - CompositeKey const& key, + QSharedPointer key, QScopedPointer& db, bool& hasError, QString& errorString) = 0; virtual void readKdbx(const QString& path, - CompositeKey const& key, + QSharedPointer key, QScopedPointer& db, bool& hasError, QString& errorString) = 0; diff --git a/tests/TestKeys.cpp b/tests/TestKeys.cpp index f04aabcb5..84c202914 100644 --- a/tests/TestKeys.cpp +++ b/tests/TestKeys.cpp @@ -43,13 +43,13 @@ void TestKeys::initTestCase() void TestKeys::testComposite() { - QScopedPointer compositeKey1(new CompositeKey()); - QScopedPointer passwordKey1(new PasswordKey()); - QScopedPointer passwordKey2(new PasswordKey("test")); + auto compositeKey1 = QSharedPointer::create(); + auto passwordKey1 = QSharedPointer::create(); + auto passwordKey2 = QSharedPointer::create("test"); // make sure that addKey() creates a copy of the keys - compositeKey1->addKey(*passwordKey1); - compositeKey1->addKey(*passwordKey2); + compositeKey1->addKey(passwordKey1); + compositeKey1->addKey(passwordKey2); AesKdf kdf; kdf.setRounds(1); @@ -57,29 +57,13 @@ void TestKeys::testComposite() QVERIFY(compositeKey1->transform(kdf, transformed1)); QCOMPARE(transformed1.size(), 32); - // make sure the subkeys are copied - QScopedPointer compositeKey2(compositeKey1->clone()); - QByteArray transformed2; - QVERIFY(compositeKey2->transform(kdf, transformed2)); - QCOMPARE(transformed2.size(), 32); - QCOMPARE(transformed1, transformed2); - QScopedPointer compositeKey3(new CompositeKey()); QScopedPointer compositeKey4(new CompositeKey()); // test clear() - compositeKey3->addKey(PasswordKey("test")); + compositeKey3->addKey(QSharedPointer::create("test")); compositeKey3->clear(); QCOMPARE(compositeKey3->rawKey(), compositeKey4->rawKey()); - - // test assignment operator - compositeKey3->addKey(PasswordKey("123")); - *compositeKey4 = *compositeKey3; - QCOMPARE(compositeKey3->rawKey(), compositeKey4->rawKey()); - - // test self-assignment - *compositeKey3 = *compositeKey3; - QCOMPARE(compositeKey3->rawKey(), compositeKey4->rawKey()); } void TestKeys::testFileKey() @@ -94,14 +78,14 @@ void TestKeys::testFileKey() 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); + auto compositeKey = QSharedPointer::create(); + auto fileKey = QSharedPointer::create(); + QVERIFY(fileKey->load(keyFilename)); + QCOMPARE(fileKey->rawKey().size(), 32); - QCOMPARE(fileKey.type(), type); + QCOMPARE(fileKey->type(), type); - compositeKey.addKey(fileKey); + compositeKey->addKey(fileKey); QScopedPointer db(reader.readDatabase(dbFilename, compositeKey)); QVERIFY(db); @@ -146,10 +130,10 @@ void TestKeys::testCreateAndOpenFileKey() FileKey::create(&keyBuffer); keyBuffer.reset(); - FileKey fileKey; - QVERIFY(fileKey.load(&keyBuffer)); - CompositeKey compositeKey; - compositeKey.addKey(fileKey); + auto fileKey = QSharedPointer::create(); + QVERIFY(fileKey->load(&keyBuffer)); + auto compositeKey = QSharedPointer::create(); + compositeKey->addKey(fileKey); QScopedPointer dbOrg(new Database()); QVERIFY(dbOrg->setKey(compositeKey)); @@ -218,10 +202,10 @@ void TestKeys::benchmarkTransformKey() QSKIP("Benchmark skipped. Set env variable BENCHMARK=1 to enable."); } - PasswordKey pwKey; - pwKey.setPassword("password"); - CompositeKey compositeKey; - compositeKey.addKey(pwKey); + auto pwKey = QSharedPointer::create(); + pwKey->setPassword("password"); + auto compositeKey = QSharedPointer::create(); + compositeKey->addKey(pwKey); QByteArray seed(32, '\x4B'); @@ -232,25 +216,25 @@ void TestKeys::benchmarkTransformKey() QBENCHMARK { - Q_UNUSED(compositeKey.transform(kdf, result)); + Q_UNUSED(compositeKey->transform(kdf, result)); }; } void TestKeys::testCompositeKeyComponents() { - PasswordKey passwordKeyEnc("password"); - FileKey fileKeyEnc; + auto passwordKeyEnc = QSharedPointer::create("password"); + auto fileKeyEnc = QSharedPointer::create(); QString error; - fileKeyEnc.load(QString("%1/%2").arg(QString(KEEPASSX_TEST_DATA_DIR), "FileKeyHashed.key"), &error); + fileKeyEnc->load(QString("%1/%2").arg(QString(KEEPASSX_TEST_DATA_DIR), "FileKeyHashed.key"), &error); if (!error.isNull()) { QFAIL(qPrintable(error)); } auto challengeResponseKeyEnc = QSharedPointer::create(QByteArray(16, 0x10)); - CompositeKey compositeKeyEnc; - compositeKeyEnc.addKey(passwordKeyEnc); - compositeKeyEnc.addKey(fileKeyEnc); - compositeKeyEnc.addChallengeResponseKey(challengeResponseKeyEnc); + auto compositeKeyEnc = QSharedPointer::create(); + compositeKeyEnc->addKey(passwordKeyEnc); + compositeKeyEnc->addKey(fileKeyEnc); + compositeKeyEnc->addChallengeResponseKey(challengeResponseKeyEnc); QScopedPointer db1(new Database()); db1->setKey(compositeKeyEnc); @@ -263,23 +247,23 @@ void TestKeys::testCompositeKeyComponents() buffer.seek(0); QScopedPointer db2; KeePass2Reader reader; - CompositeKey compositeKeyDec1; + auto compositeKeyDec1 = QSharedPointer::create(); // try decryption and subsequently add key components until decryption is successful db2.reset(reader.readDatabase(&buffer, compositeKeyDec1)); QVERIFY(reader.hasError()); - compositeKeyDec1.addKey(passwordKeyEnc); + compositeKeyDec1->addKey(passwordKeyEnc); buffer.seek(0); db2.reset(reader.readDatabase(&buffer, compositeKeyDec1)); QVERIFY(reader.hasError()); - compositeKeyDec1.addKey(fileKeyEnc); + compositeKeyDec1->addKey(fileKeyEnc); buffer.seek(0); db2.reset(reader.readDatabase(&buffer, compositeKeyDec1)); QVERIFY(reader.hasError()); - compositeKeyDec1.addChallengeResponseKey(challengeResponseKeyEnc); + compositeKeyDec1->addChallengeResponseKey(challengeResponseKeyEnc); buffer.seek(0); db2.reset(reader.readDatabase(&buffer, compositeKeyDec1)); // now we should be able to open the database @@ -288,31 +272,31 @@ void TestKeys::testCompositeKeyComponents() } // try the same again, but this time with one wrong key component each time - CompositeKey compositeKeyDec2; - compositeKeyDec2.addKey(PasswordKey("wrong password")); - compositeKeyDec2.addKey(fileKeyEnc); - compositeKeyDec2.addChallengeResponseKey(challengeResponseKeyEnc); + auto compositeKeyDec2 = QSharedPointer::create(); + compositeKeyDec2->addKey(QSharedPointer::create("wrong password")); + compositeKeyDec2->addKey(fileKeyEnc); + compositeKeyDec2->addChallengeResponseKey(challengeResponseKeyEnc); buffer.seek(0); db2.reset(reader.readDatabase(&buffer, compositeKeyDec2)); QVERIFY(reader.hasError()); - CompositeKey compositeKeyDec3; - compositeKeyDec3.addKey(passwordKeyEnc); - FileKey fileKeyWrong; - fileKeyWrong.load(QString("%1/%2").arg(QString(KEEPASSX_TEST_DATA_DIR), "FileKeyHashed2.key"), &error); + auto compositeKeyDec3 = QSharedPointer::create(); + compositeKeyDec3->addKey(passwordKeyEnc); + auto fileKeyWrong = QSharedPointer::create(); + fileKeyWrong->load(QString("%1/%2").arg(QString(KEEPASSX_TEST_DATA_DIR), "FileKeyHashed2.key"), &error); if (!error.isNull()) { QFAIL(qPrintable(error)); } - compositeKeyDec3.addKey(fileKeyWrong); - compositeKeyDec3.addChallengeResponseKey(challengeResponseKeyEnc); + compositeKeyDec3->addKey(fileKeyWrong); + compositeKeyDec3->addChallengeResponseKey(challengeResponseKeyEnc); buffer.seek(0); db2.reset(reader.readDatabase(&buffer, compositeKeyDec3)); QVERIFY(reader.hasError()); - CompositeKey compositeKeyDec4; - compositeKeyDec4.addKey(passwordKeyEnc); - compositeKeyDec4.addKey(fileKeyEnc); - compositeKeyDec4.addChallengeResponseKey(QSharedPointer::create(QByteArray(16, 0x20))); + auto compositeKeyDec4 = QSharedPointer::create(); + compositeKeyDec4->addKey(passwordKeyEnc); + compositeKeyDec4->addKey(fileKeyEnc); + compositeKeyDec4->addChallengeResponseKey(QSharedPointer::create(QByteArray(16, 0x20))); buffer.seek(0); db2.reset(reader.readDatabase(&buffer, compositeKeyDec4)); QVERIFY(reader.hasError()); diff --git a/tests/TestModified.cpp b/tests/TestModified.cpp index 66227e3ad..6e033f25e 100644 --- a/tests/TestModified.cpp +++ b/tests/TestModified.cpp @@ -37,7 +37,7 @@ void TestModified::testSignals() int spyCount = 0; int spyCount2 = 0; - CompositeKey compositeKey; + auto compositeKey = QSharedPointer::create(); QScopedPointer db(new Database()); auto* root = db->rootGroup(); diff --git a/tests/gui/TestGui.cpp b/tests/gui/TestGui.cpp index 930dac561..731eadcaf 100644 --- a/tests/gui/TestGui.cpp +++ b/tests/gui/TestGui.cpp @@ -48,6 +48,13 @@ #include "crypto/Crypto.h" #include "crypto/kdf/AesKdf.h" #include "format/KeePass2Reader.h" +#include "keys/PasswordKey.h" +#include "keys/FileKey.h" +#include "gui/ApplicationSettingsWidget.h" +#include "gui/dbsettings/DatabaseSettingsDialog.h" +#include "gui/masterkey/PasswordEditWidget.h" +#include "gui/masterkey/KeyFileEditWidget.h" +#include "gui/CategoryListWidget.h" #include "gui/CloneDialog.h" #include "gui/DatabaseTabWidget.h" #include "gui/DatabaseWidget.h" @@ -63,6 +70,8 @@ #include "gui/group/EditGroupWidget.h" #include "gui/group/GroupModel.h" #include "gui/group/GroupView.h" +#include "gui/wizard/NewDatabaseWizard.h" +#include "gui/masterkey/KeyComponentWidget.h" #include "keys/PasswordKey.h" void TestGui::initTestCase() @@ -70,9 +79,11 @@ void TestGui::initTestCase() QVERIFY(Crypto::init()); Config::createTempFileInstance(); // Disable autosave so we can test the modified file indicator - Config::instance()->set("AutoSaveAfterEveryChange", false); + config()->set("AutoSaveAfterEveryChange", false); // Enable the tray icon so we can test hiding/restoring the window - Config::instance()->set("GUI/ShowTrayIcon", true); + config()->set("GUI/ShowTrayIcon", true); + // Disable advanced settings mode (activate within individual tests to test advanced settings) + config()->set("GUI/AdvancedSettings", false); m_mainWindow = new MainWindow(); m_tabWidget = m_mainWindow->findChild("tabWidget"); @@ -135,43 +146,114 @@ void TestGui::cleanup() m_dbWidget = nullptr; } +void TestGui::testSettingsDefaultTabOrder() +{ + // check application settings default tab order + triggerAction("actionSettings"); + auto* settingsWidget = m_mainWindow->findChild(); + QVERIFY(settingsWidget->isVisible()); + QCOMPARE(settingsWidget->findChild("categoryList")->currentCategory(), 0); + for (auto* w: settingsWidget->findChildren()) { + if (w->currentIndex() != 0) { + QFAIL("Application settings contain QTabWidgets whose default index is not 0"); + } + } + QTest::keyClick(settingsWidget, Qt::Key::Key_Escape); + + // check database settings default tab order + triggerAction("actionChangeDatabaseSettings"); + auto* dbSettingsWidget = m_mainWindow->findChild(); + QVERIFY(dbSettingsWidget->isVisible()); + QCOMPARE(dbSettingsWidget->findChild("categoryList")->currentCategory(), 0); + for (auto* w: dbSettingsWidget->findChildren()) { + if (w->currentIndex() != 0) { + QFAIL("Database settings contain QTabWidgets whose default index is not 0"); + } + } + QTest::keyClick(dbSettingsWidget, Qt::Key::Key_Escape); +} + void TestGui::testCreateDatabase() { - QTemporaryFile tmpFile; - QVERIFY(tmpFile.open()); - QString tmpFileName = tmpFile.fileName(); - tmpFile.remove(); - - fileDialog()->setNextFileName(tmpFileName); + QTimer::singleShot(0, this, SLOT(createDatabaseCallback())); triggerAction("actionDatabaseNew"); - DatabaseWidget* dbWidget = m_tabWidget->currentDatabaseWidget(); - - QWidget* databaseNewWidget = dbWidget->findChild("changeMasterKeyWidget"); - PasswordEdit* editPassword = databaseNewWidget->findChild("enterPasswordEdit"); - QVERIFY(editPassword->isVisible()); - - QLineEdit* editPasswordRepeat = databaseNewWidget->findChild("repeatPasswordEdit"); - QVERIFY(editPasswordRepeat->isVisible()); - - m_tabWidget->currentDatabaseWidget()->setCurrentWidget(databaseNewWidget); - - QTest::keyClicks(editPassword, "test"); - QTest::keyClicks(editPasswordRepeat, "test"); - QTest::keyClick(editPasswordRepeat, Qt::Key_Enter); - - // Auto-save after every change is enabled by default, ensure the db saves right away - QTRY_VERIFY(m_tabWidget->tabText(m_tabWidget->currentIndex()).contains("*")); - - m_db = m_tabWidget->currentDatabaseWidget()->database(); - // there is a new empty db + m_db = m_tabWidget->currentDatabaseWidget()->database(); QCOMPARE(m_db->rootGroup()->children().size(), 0); + // check meta data + QCOMPARE(m_db->metadata()->name(), QString("Test Name")); + QCOMPARE(m_db->metadata()->description(), QString("Test Description")); + + // check key and encryption + QCOMPARE(m_db->key()->keys().size(), 2); + QCOMPARE(m_db->kdf()->uuid(), KeePass2::KDF_ARGON2); + QCOMPARE(m_db->cipher(), KeePass2::CIPHER_AES); + auto compositeKey = QSharedPointer::create(); + compositeKey->addKey(QSharedPointer::create("test")); + auto fileKey = QSharedPointer::create(); + fileKey->load(QString("%1/%2").arg(QString(KEEPASSX_TEST_DATA_DIR), "FileKeyHashed.key")); + compositeKey->addKey(fileKey); + QCOMPARE(m_db->key()->rawKey(), compositeKey->rawKey()); + // close the new database MessageBox::setNextAnswer(QMessageBox::No); triggerAction("actionDatabaseClose"); - Tools::wait(100); +} + +void TestGui::createDatabaseCallback() +{ + auto* wizard = m_tabWidget->findChild(); + QVERIFY(wizard); + + QTest::keyClicks(wizard->currentPage()->findChild("databaseName"), "Test Name"); + QTest::keyClicks(wizard->currentPage()->findChild("databaseDescription"), "Test Description"); + QCOMPARE(wizard->currentId(), 0); + + QTest::keyClick(wizard, Qt::Key_Enter); + QCOMPARE(wizard->currentId(), 1); + + QTest::keyClick(wizard, Qt::Key_Enter); + QCOMPARE(wizard->currentId(), 2); + + // enter password + auto* passwordWidget = wizard->currentPage()->findChild(); + QCOMPARE(passwordWidget->visiblePage(), KeyFileEditWidget::Page::Edit); + auto* passwordEdit = passwordWidget->findChild("enterPasswordEdit"); + auto* passwordRepeatEdit = passwordWidget->findChild("repeatPasswordEdit"); + QTRY_VERIFY(passwordEdit->isVisible()); + QVERIFY(passwordEdit->hasFocus()); + QTest::keyClicks(passwordEdit, "test"); + QTest::keyClick(passwordEdit, Qt::Key::Key_Tab); + QTest::keyClicks(passwordRepeatEdit, "test"); + + // add key file + auto* additionalOptionsButton = wizard->currentPage()->findChild("additionalKeyOptionsToggle"); + auto* keyFileWidget = wizard->currentPage()->findChild(); + QVERIFY(additionalOptionsButton->isVisible()); + QTest::mouseClick(additionalOptionsButton, Qt::MouseButton::LeftButton); + QTRY_VERIFY(keyFileWidget->isVisible()); + QTRY_VERIFY(!additionalOptionsButton->isVisible()); + QCOMPARE(passwordWidget->visiblePage(), KeyFileEditWidget::Page::Edit); + QTest::mouseClick(keyFileWidget->findChild("addButton"), Qt::MouseButton::LeftButton); + auto* fileCombo = keyFileWidget->findChild("keyFileCombo"); + QTRY_VERIFY(fileCombo); + QTRY_VERIFY(fileCombo->isVisible()); + fileDialog()->setNextFileName(QString("%1/%2").arg(QString(KEEPASSX_TEST_DATA_DIR), "FileKeyHashed.key")); + QTest::keyClick(keyFileWidget->findChild("addButton"), Qt::Key::Key_Enter); + QVERIFY(fileCombo->hasFocus()); + auto* browseButton = keyFileWidget->findChild("browseKeyFileButton"); + QTest::keyClick(browseButton, Qt::Key::Key_Enter); + QCOMPARE(fileCombo->currentText(), QString("%1/%2").arg(QString(KEEPASSX_TEST_DATA_DIR), "FileKeyHashed.key")); + + // save database to temporary file + TemporaryFile tmpFile; + QVERIFY(tmpFile.open()); + tmpFile.close(); + fileDialog()->setNextFileName(tmpFile.filePath()); + + QTest::keyClick(fileCombo, Qt::Key::Key_Enter); } void TestGui::testMergeDatabase() @@ -390,7 +472,7 @@ void TestGui::testSearchEditEntry() // Find buttons for group creation EditGroupWidget* editGroupWidget = m_dbWidget->findChild("editGroupWidget"); - QLineEdit* nameEdit = editGroupWidget->findChild("nameEdit"); + QLineEdit* nameEdit = editGroupWidget->findChild("editName"); QDialogButtonBox* editGroupWidgetButtonBox = editGroupWidget->findChild("buttonBox"); // Add groups "Good" and "Bad" @@ -965,7 +1047,7 @@ void TestGui::testSaveAs() QFileInfo fileInfo(m_dbFilePath); QDateTime lastModified = fileInfo.lastModified(); - m_db->metadata()->setName("SaveAs"); + m_db->metadata()->setName("testSaveAs"); // open temporary file so it creates a filename QTemporaryFile tmpFile; @@ -977,7 +1059,7 @@ void TestGui::testSaveAs() triggerAction("actionDatabaseSaveAs"); - QCOMPARE(m_tabWidget->tabText(m_tabWidget->currentIndex()), QString("SaveAs")); + QCOMPARE(m_tabWidget->tabText(m_tabWidget->currentIndex()), QString("testSaveAs")); checkDatabase(tmpFileName); @@ -987,31 +1069,40 @@ void TestGui::testSaveAs() void TestGui::testSave() { - m_db->metadata()->setName("Save"); + m_db->metadata()->setName("testSave"); + // wait for modified timer - QTRY_COMPARE(m_tabWidget->tabText(m_tabWidget->currentIndex()), QString("Save*")); + QTRY_COMPARE(m_tabWidget->tabText(m_tabWidget->currentIndex()), QString("testSave*")); triggerAction("actionDatabaseSave"); - QCOMPARE(m_tabWidget->tabText(m_tabWidget->currentIndex()), QString("Save")); + QCOMPARE(m_tabWidget->tabText(m_tabWidget->currentIndex()), QString("testSave")); checkDatabase(); } void TestGui::testDatabaseSettings() { - m_db->metadata()->setName("Save"); + m_db->metadata()->setName("testDatabaseSettings"); triggerAction("actionChangeDatabaseSettings"); - QWidget* dbSettingsWidget = m_dbWidget->findChild("databaseSettingsWidget"); - QSpinBox* transformRoundsSpinBox = dbSettingsWidget->findChild("transformRoundsSpinBox"); + auto* dbSettingsDialog = m_dbWidget->findChild("databaseSettingsDialog"); + auto* transformRoundsSpinBox = dbSettingsDialog->findChild("transformRoundsSpinBox"); + auto advancedToggle = dbSettingsDialog->findChild("advancedSettingsToggle"); + + advancedToggle->setChecked(true); + QApplication::processEvents(); + QVERIFY(transformRoundsSpinBox != nullptr); transformRoundsSpinBox->setValue(123456); QTest::keyClick(transformRoundsSpinBox, Qt::Key_Enter); // wait for modified timer - QTRY_COMPARE(m_tabWidget->tabText(m_tabWidget->currentIndex()), QString("Save*")); + QTRY_COMPARE(m_tabWidget->tabText(m_tabWidget->currentIndex()), QString("testDatabaseSettings*")); QCOMPARE(m_db->kdf()->rounds(), 123456); triggerAction("actionDatabaseSave"); - QCOMPARE(m_tabWidget->tabText(m_tabWidget->currentIndex()), QString("Save")); + QCOMPARE(m_tabWidget->tabText(m_tabWidget->currentIndex()), QString("testDatabaseSettings")); + + advancedToggle->setChecked(false); + QApplication::processEvents(); checkDatabase(); } @@ -1021,8 +1112,8 @@ void TestGui::testKeePass1Import() fileDialog()->setNextFileName(QString(KEEPASSX_TEST_DATA_DIR).append("/basic.kdb")); triggerAction("actionImportKeePass1"); - QWidget* keepass1OpenWidget = m_mainWindow->findChild("keepass1OpenWidget"); - QLineEdit* editPassword = keepass1OpenWidget->findChild("editPassword"); + auto* keepass1OpenWidget = m_mainWindow->findChild("keepass1OpenWidget"); + auto* editPassword = keepass1OpenWidget->findChild("editPassword"); QVERIFY(editPassword); QTest::keyClicks(editPassword, "masterpw"); @@ -1174,8 +1265,8 @@ void TestGui::checkDatabase(QString dbFileName) if (dbFileName.isEmpty()) dbFileName = m_dbFilePath; - CompositeKey key; - key.addKey(PasswordKey("a")); + auto key = QSharedPointer::create(); + key->addKey(QSharedPointer::create("a")); KeePass2Reader reader; QScopedPointer dbSaved(reader.readDatabase(dbFileName, key)); QVERIFY(dbSaved); @@ -1189,6 +1280,7 @@ void TestGui::triggerAction(const QString& name) QVERIFY(action); QVERIFY(action->isEnabled()); action->trigger(); + QApplication::processEvents(); } void TestGui::dragAndDropGroup(const QModelIndex& sourceIndex, diff --git a/tests/gui/TestGui.h b/tests/gui/TestGui.h index 2a3708c42..dc8a05e9b 100644 --- a/tests/gui/TestGui.h +++ b/tests/gui/TestGui.h @@ -35,12 +35,16 @@ class TestGui : public QObject { Q_OBJECT +protected slots: + void createDatabaseCallback(); + private slots: void initTestCase(); void init(); void cleanup(); void cleanupTestCase(); + void testSettingsDefaultTabOrder(); void testCreateDatabase(); void testMergeDatabase(); void testAutoreloadDatabase(); diff --git a/tests/mock/MockChallengeResponseKey.cpp b/tests/mock/MockChallengeResponseKey.cpp index 9cfdc0501..628504d85 100644 --- a/tests/mock/MockChallengeResponseKey.cpp +++ b/tests/mock/MockChallengeResponseKey.cpp @@ -18,7 +18,8 @@ #include "MockChallengeResponseKey.h" MockChallengeResponseKey::MockChallengeResponseKey(const QByteArray& secret) - : m_secret(secret) + : ChallengeResponseKey(QUuid("aac5b480-cdc0-411e-9cb8-962062dcc1fd")) + , m_secret(secret) { } diff --git a/tests/mock/MockChallengeResponseKey.h b/tests/mock/MockChallengeResponseKey.h index 011b06e5d..1ef11bf60 100644 --- a/tests/mock/MockChallengeResponseKey.h +++ b/tests/mock/MockChallengeResponseKey.h @@ -28,6 +28,7 @@ class MockChallengeResponseKey : public ChallengeResponseKey { public: explicit MockChallengeResponseKey(const QByteArray& secret); + Q_DISABLE_COPY(MockChallengeResponseKey); ~MockChallengeResponseKey() override; QByteArray rawKey() const override; bool challenge(const QByteArray& challenge) override;