-
-
Notifications
You must be signed in to change notification settings - Fork 1.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
KdbxXmlReader: Support Protected attribute on most nodes #1646
KdbxXmlReader: Support Protected attribute on most nodes #1646
Conversation
A workaround for users is to switch to KDBX 4, but in theory some application could decide to use |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is something I noticed, too while writing the KeePass2 XML format RFC: https://github.com/keepassxreboot/keepassxc-specs/blob/master/kdbx-xml/rfc.txt
ALL non-complex elements can be protected.
src/format/KdbxXmlReader.cpp
Outdated
raiseError(tr("Unable to decrypt entry string")); | ||
continue; | ||
} | ||
|
||
bool isProtected = attr.value("Protected") == "True"; | ||
bool protectInMemory = attr.value("ProtectInMemory") == "True"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You should remove this here. Both Protected and ProtectInMemory should be handled by the readString() method. That method should have additional output parameters to indicate whether the attribute was protected and should be protected in memory.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure how to handle that. Two output references? But readString and readBinary are called without requiring the result. Should I add 0 and 2 parameter overloads?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Two output references, yes. You may add overloads, but I ideally, all callers of this method should use those parameters.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Dozens of occurences and (iirc) only 2 need the output, so I chose the overloaded version.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK.
src/format/KdbxXmlReader.cpp
Outdated
QXmlStreamAttributes attr = m_xml.attributes(); | ||
QString value = m_xml.readElementText(); | ||
bool isProtected = attr.hasAttribute("Protected") | ||
&& (attr.value("Protected") == "True"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You should compare case-insensitively and also allow 1 and 0 in addition to True and False.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was wondering and reused the existing code. This seems to be handled differently in different places. Should I introduce a static helper function isTruthy or similar?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you don't have to change too many lines of code, you can do that. Otherwise just handle it locally. KeePass seems to be somewhat inconsistent and sometimes uses True, or true or 1.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For reference, KeePass2 does bProtect = ((strProtect != null) && (strProtect == ValTrue)); there. For strict compatibility an equality check may be preferable, but it's unlikely to encounter incompatibilities with real data there.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ye, it dos it here, but while writing the RFC I tested various configurations and I noticed that from other boolean elements/attributes, KeePass2 sometimes writes true/false or 1/0. I therefore made that a feature of the spec. For compatibility we should always write it as True/False, but should support reading true/false and 1/0.
src/format/KdbxXmlReader.cpp
Outdated
QString value = m_xml.readElementText(); | ||
QByteArray data = QByteArray::fromBase64(value.toLatin1()); | ||
bool isProtected = attr.hasAttribute("Protected") | ||
&& (attr.value("Protected") == "True"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here.
src/format/KdbxXmlReader.cpp
Outdated
@@ -814,26 +814,14 @@ void KdbxXmlReader::parseEntryString(Entry* entry) | |||
QXmlStreamAttributes attr = m_xml.attributes(); | |||
value = readString(); | |||
|
|||
if (m_error) { | |||
raiseError(tr("Unable to decrypt entry string")); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This discards the randomStream error.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's intended to preserve the previous behavior and return another message in this case. Should I just remove this? In that case, also remove the translations?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it makes more sense to return the actual stream error message.
src/format/KdbxXmlReader.cpp
Outdated
value.clear(); | ||
raiseError(m_randomStream->errorString()); | ||
} else { | ||
value = QString::fromUtf8(plaintext); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
return after raiseError and remove else.
share/translations/keepassx_ar.ts
Outdated
@@ -2346,10 +2346,6 @@ This is a one-way migration. You won't be able to open the imported databas | |||
<source>History element with different uuid</source> | |||
<translation>سجل العنصر مع uuid مختلف</translation> | |||
</message> | |||
<message> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please remove all TS files from the PR. We will update them before the release.
src/format/KdbxXmlReader.cpp
Outdated
@@ -188,6 +188,15 @@ QString KdbxXmlReader::errorString() const | |||
return QString(); | |||
} | |||
|
|||
bool KdbxXmlReader::isTruthyAttribute(const QString& name) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wouldn't call this isTruthyAttribute
. Call is isTrueValue
and return true if the string matches any of the True values. Move the attribute logic outside, so we can reuse the method for boolean elements, not just attributes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks really good now. Thanks!
Please squash the commits and rebase against release/2.3.1, so I can merge it there.
73614a8
to
3846495
Compare
Similar to KeePass2 most elements are checked for a Protected="True" attribute. Previously, files KDBX 3.1 files with protected Binary entries, as seen in the wild, could not be read (mangled passwords and file attachments).
3846495
to
aebe2d9
Compare
- Fix unnecessary automatic upgrade to KDBX 4.0 and prevent challenge-response key being stripped [#1568] - Abort saving and show an error message when challenge-response fails [#1659] - Support inner stream protection on all string attributes [#1646] - Fix favicon downloads not finishing on some websites [#1657] - Fix freeze due to invalid STDIN data [#1628] - Correct issue with encrypted RSA SSH keys [#1587] - Fix crash on macOS due to QTBUG-54832 [#1607] - Show error message if ssh-agent communication fails [#1614] - Fix --pw-stdin and filename parameters being ignored [#1608] - Fix Auto-Type syntax check not allowing spaces and special characters [#1626] - Fix reference placeholders in combination with Auto-Type [#1649] - Fix qtbase translations not being loaded [#1611] - Fix startup crash on Windows due to missing SVG libraries [#1662] - Correct database tab order regression [#1610] - Fix GCC 8 compilation error [#1612] - Fix copying of advanced attributes on KDE [#1640] - Fix member initialization of CategoryListWidgetDelegate [#1613] - Fix inconsistent toolbar icon sizes and provide higher-quality icons [#1616] - Improve preview panel geometry [#1609]
Description
When parsing KDBX XML, the KdbXmlReader now supports Protected="true" on all attributes where
readString
orreadBinary
is used.Motivation and context
I had a KDBX 3.1 file which contained Binary entries with Protected="true". This lead to KeePassXC mostly reading the file fine, but because of the differences in the state of the random stream all passwords and attached files were silently mangled.
I am not sure which application created those (KeePass2, KyPass or Keepass2Android), but KeePass2 seems to parse Protected="true" in a similar way in its
KdbxFile::ProcessNode
method. To solve this specific case, handling Protected="true" only on<Binary>
elements would have sufficed, but this could have lead to additional bugs later on if any other element is ever protected. This could corrupt the database. It would be helpful if the file format had a checksum for this stream (or its final state) to detect such errors.How has this been tested?
I did check that attachments and passwords work in the KDBX 3.1 file and its KDBX 4 counterpart (contains a couple of hundred entries and a few dozen attached files). I created a Mingw build on Windows 10 Insider Preview with everything but YubiKey support. The automated test suite passed, GUI and CLI also seem to work fine.
Types of changes
I consider this a bug fix which should allow KeePassXC to load more files successfully instead of silently failing (and possibly corrupting them on save). The code should probably be more thoroughly tested with various databases and test cases.
Checklist:
Not needed from what I can tell:
BUT, I failed to do these:
-DWITH_ASAN=ON
Existing tests work (including those with protected) and the Windows build environment I tested with didn't allow
-DWITH_ASAN=ON
.