mirror of
https://github.com/keepassxreboot/keepassxc.git
synced 2024-10-01 01:26:01 -04:00
Fix padding handling in SymmetricCipherStream.
The implementation had two issues: - It didn't add a block full of padding when the input size was a multiple of the block size. - It didn't strip the padding when reading data.
This commit is contained in:
parent
7790f2e7ba
commit
38e421d9c1
@ -36,7 +36,7 @@ SymmetricCipherStream::~SymmetricCipherStream()
|
|||||||
bool SymmetricCipherStream::reset()
|
bool SymmetricCipherStream::reset()
|
||||||
{
|
{
|
||||||
if (isWritable()) {
|
if (isWritable()) {
|
||||||
if (!writeBlock()) {
|
if (!writeBlock(true)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -53,7 +53,7 @@ bool SymmetricCipherStream::reset()
|
|||||||
void SymmetricCipherStream::close()
|
void SymmetricCipherStream::close()
|
||||||
{
|
{
|
||||||
if (isWritable()) {
|
if (isWritable()) {
|
||||||
writeBlock();
|
writeBlock(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
LayeredStream::close();
|
LayeredStream::close();
|
||||||
@ -63,13 +63,22 @@ qint64 SymmetricCipherStream::readData(char* data, qint64 maxSize)
|
|||||||
{
|
{
|
||||||
Q_ASSERT(maxSize >= 0);
|
Q_ASSERT(maxSize >= 0);
|
||||||
|
|
||||||
|
if (m_error) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
qint64 bytesRemaining = maxSize;
|
qint64 bytesRemaining = maxSize;
|
||||||
qint64 offset = 0;
|
qint64 offset = 0;
|
||||||
|
|
||||||
while (bytesRemaining > 0) {
|
while (bytesRemaining > 0) {
|
||||||
if ((m_bufferPos == m_buffer.size()) || m_bufferFilling) {
|
if ((m_bufferPos == m_buffer.size()) || m_bufferFilling) {
|
||||||
if (!readBlock()) {
|
if (!readBlock()) {
|
||||||
return maxSize - bytesRemaining;
|
if (m_error) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return maxSize - bytesRemaining;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -102,7 +111,32 @@ bool SymmetricCipherStream::readBlock()
|
|||||||
m_cipher->processInPlace(m_buffer);
|
m_cipher->processInPlace(m_buffer);
|
||||||
m_bufferPos = 0;
|
m_bufferPos = 0;
|
||||||
m_bufferFilling = false;
|
m_bufferFilling = false;
|
||||||
return true;
|
|
||||||
|
if (m_baseDevice->atEnd()) {
|
||||||
|
// PKCS7 padding
|
||||||
|
quint8 padLength = m_buffer.at(m_buffer.size() - 1);
|
||||||
|
|
||||||
|
if (padLength == m_cipher->blockSize()) {
|
||||||
|
Q_ASSERT(m_buffer == QByteArray(m_cipher->blockSize(), m_cipher->blockSize()));
|
||||||
|
// full block with just padding: discard
|
||||||
|
m_buffer.clear();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else if (padLength > m_cipher->blockSize()) {
|
||||||
|
// invalid padding
|
||||||
|
m_error = true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Q_ASSERT(m_buffer.right(padLength) == QByteArray(padLength, padLength));
|
||||||
|
// resize buffer to strip padding
|
||||||
|
m_buffer.resize(m_cipher->blockSize() - padLength);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -111,7 +145,7 @@ qint64 SymmetricCipherStream::writeData(const char* data, qint64 maxSize)
|
|||||||
Q_ASSERT(maxSize >= 0);
|
Q_ASSERT(maxSize >= 0);
|
||||||
|
|
||||||
if (m_error) {
|
if (m_error) {
|
||||||
return 0;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
qint64 bytesRemaining = maxSize;
|
qint64 bytesRemaining = maxSize;
|
||||||
@ -126,7 +160,7 @@ qint64 SymmetricCipherStream::writeData(const char* data, qint64 maxSize)
|
|||||||
bytesRemaining -= bytesToCopy;
|
bytesRemaining -= bytesToCopy;
|
||||||
|
|
||||||
if (m_buffer.size() == m_cipher->blockSize()) {
|
if (m_buffer.size() == m_cipher->blockSize()) {
|
||||||
if (!writeBlock()) {
|
if (!writeBlock(false)) {
|
||||||
if (m_error) {
|
if (m_error) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
@ -140,18 +174,18 @@ qint64 SymmetricCipherStream::writeData(const char* data, qint64 maxSize)
|
|||||||
return maxSize;
|
return maxSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SymmetricCipherStream::writeBlock()
|
bool SymmetricCipherStream::writeBlock(bool lastBlock)
|
||||||
{
|
{
|
||||||
if (m_buffer.isEmpty()) {
|
if (lastBlock) {
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else if (m_buffer.size() != m_cipher->blockSize()) {
|
|
||||||
// PKCS7 padding
|
// PKCS7 padding
|
||||||
int padLen = m_cipher->blockSize() - m_buffer.size();
|
int padLen = m_cipher->blockSize() - m_buffer.size();
|
||||||
for (int i = m_buffer.size(); i < m_cipher->blockSize(); i++) {
|
for (int i = 0; i < padLen; i++) {
|
||||||
m_buffer.append(static_cast<char>(padLen));
|
m_buffer.append(static_cast<char>(padLen));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if (m_buffer.isEmpty()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
m_cipher->processInPlace(m_buffer);
|
m_cipher->processInPlace(m_buffer);
|
||||||
|
|
||||||
|
@ -41,7 +41,7 @@ protected:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
bool readBlock();
|
bool readBlock();
|
||||||
bool writeBlock();
|
bool writeBlock(bool lastBlock);
|
||||||
|
|
||||||
const QScopedPointer<SymmetricCipher> m_cipher;
|
const QScopedPointer<SymmetricCipher> m_cipher;
|
||||||
QByteArray m_buffer;
|
QByteArray m_buffer;
|
||||||
|
@ -53,18 +53,20 @@ void TestSymmetricCipher::testAes256CbcEncryption()
|
|||||||
SymmetricCipher::Encrypt, key, iv);
|
SymmetricCipher::Encrypt, key, iv);
|
||||||
buffer.open(QIODevice::WriteOnly);
|
buffer.open(QIODevice::WriteOnly);
|
||||||
stream.open(QIODevice::WriteOnly);
|
stream.open(QIODevice::WriteOnly);
|
||||||
|
|
||||||
QVERIFY(stream.reset());
|
QVERIFY(stream.reset());
|
||||||
|
|
||||||
buffer.reset();
|
buffer.reset();
|
||||||
buffer.buffer().clear();
|
buffer.buffer().clear();
|
||||||
stream.write(plainText.left(16));
|
stream.write(plainText.left(16));
|
||||||
QCOMPARE(buffer.data(), cipherText.left(16));
|
QCOMPARE(buffer.data(), cipherText.left(16));
|
||||||
|
|
||||||
QVERIFY(stream.reset());
|
QVERIFY(stream.reset());
|
||||||
|
// make sure padding is written
|
||||||
|
QCOMPARE(buffer.data().size(), 32);
|
||||||
|
|
||||||
buffer.reset();
|
buffer.reset();
|
||||||
buffer.buffer().clear();
|
buffer.buffer().clear();
|
||||||
stream.write(plainText.left(10));
|
stream.write(plainText.left(10));
|
||||||
QCOMPARE(buffer.data(), QByteArray());
|
QVERIFY(buffer.data().isEmpty());
|
||||||
|
|
||||||
QVERIFY(stream.reset());
|
QVERIFY(stream.reset());
|
||||||
buffer.reset();
|
buffer.reset();
|
||||||
@ -89,7 +91,9 @@ void TestSymmetricCipher::testAes256CbcDecryption()
|
|||||||
QCOMPARE(cipher.process(cipherText),
|
QCOMPARE(cipher.process(cipherText),
|
||||||
plainText);
|
plainText);
|
||||||
|
|
||||||
QBuffer buffer(&cipherText);
|
// padded with 16 0x16 bytes
|
||||||
|
QByteArray cipherTextPadded = cipherText + QByteArray::fromHex("3a3aa5e0213db1a9901f9036cf5102d2");
|
||||||
|
QBuffer buffer(&cipherTextPadded);
|
||||||
SymmetricCipherStream stream(&buffer, SymmetricCipher::Aes256, SymmetricCipher::Cbc,
|
SymmetricCipherStream stream(&buffer, SymmetricCipher::Aes256, SymmetricCipher::Cbc,
|
||||||
SymmetricCipher::Decrypt, key, iv);
|
SymmetricCipher::Decrypt, key, iv);
|
||||||
buffer.open(QIODevice::ReadOnly);
|
buffer.open(QIODevice::ReadOnly);
|
||||||
@ -164,4 +168,29 @@ void TestSymmetricCipher::testSalsa20()
|
|||||||
QCOMPARE(cipherTextB.mid(448, 64), expectedCipherText4);
|
QCOMPARE(cipherTextB.mid(448, 64), expectedCipherText4);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TestSymmetricCipher::testPadding()
|
||||||
|
{
|
||||||
|
QByteArray key = QByteArray::fromHex("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4");
|
||||||
|
QByteArray iv = QByteArray::fromHex("000102030405060708090a0b0c0d0e0f");
|
||||||
|
QByteArray plainText = QByteArray::fromHex("6bc1bee22e409f96e93d");
|
||||||
|
|
||||||
|
QBuffer buffer;
|
||||||
|
buffer.open(QIODevice::ReadWrite);
|
||||||
|
|
||||||
|
SymmetricCipherStream streamEnc(&buffer, SymmetricCipher::Aes256, SymmetricCipher::Cbc,
|
||||||
|
SymmetricCipher::Encrypt, key, iv);
|
||||||
|
streamEnc.open(QIODevice::WriteOnly);
|
||||||
|
streamEnc.write(plainText);
|
||||||
|
streamEnc.close();
|
||||||
|
buffer.reset();
|
||||||
|
// make sure padding is written
|
||||||
|
QCOMPARE(buffer.buffer().size(), 16);
|
||||||
|
|
||||||
|
SymmetricCipherStream streamDec(&buffer, SymmetricCipher::Aes256, SymmetricCipher::Cbc,
|
||||||
|
SymmetricCipher::Decrypt, key, iv);
|
||||||
|
streamDec.open(QIODevice::ReadOnly);
|
||||||
|
QByteArray decrypted = streamDec.readAll();
|
||||||
|
QCOMPARE(decrypted, plainText);
|
||||||
|
}
|
||||||
|
|
||||||
KEEPASSX_QTEST_CORE_MAIN(TestSymmetricCipher)
|
KEEPASSX_QTEST_CORE_MAIN(TestSymmetricCipher)
|
||||||
|
@ -29,6 +29,7 @@ private Q_SLOTS:
|
|||||||
void testAes256CbcEncryption();
|
void testAes256CbcEncryption();
|
||||||
void testAes256CbcDecryption();
|
void testAes256CbcDecryption();
|
||||||
void testSalsa20();
|
void testSalsa20();
|
||||||
|
void testPadding();
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // KEEPASSX_TESTSYMMETRICCIPHER_H
|
#endif // KEEPASSX_TESTSYMMETRICCIPHER_H
|
||||||
|
Loading…
Reference in New Issue
Block a user