mirror of
https://github.com/keepassxreboot/keepassxc.git
synced 2025-08-19 19:48:36 -04:00
136 lines
4.3 KiB
C++
136 lines
4.3 KiB
C++
/*
|
|
* Copyright (C) 2019 KeePassXC Team <team@keepassxc.org>
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 2 or (at your option)
|
|
* version 3 of the License.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "OpData01.h"
|
|
|
|
#include "crypto/CryptoHash.h"
|
|
#include "crypto/SymmetricCipher.h"
|
|
|
|
#include <QDataStream>
|
|
|
|
OpData01::OpData01(QObject* parent)
|
|
: QObject(parent)
|
|
{
|
|
}
|
|
|
|
OpData01::~OpData01()
|
|
{
|
|
}
|
|
|
|
bool OpData01::decodeBase64(QString const& b64String, const QByteArray& key, const QByteArray& hmacKey)
|
|
{
|
|
const QByteArray b64Bytes = QByteArray::fromBase64(b64String.toUtf8());
|
|
return decode(b64Bytes, key, hmacKey);
|
|
}
|
|
|
|
bool OpData01::decode(const QByteArray& data, const QByteArray& key, const QByteArray& hmacKey)
|
|
{
|
|
/*!
|
|
* The first 8 bytes of the data are the string “opdata01”.
|
|
*/
|
|
const QByteArray header("opdata01");
|
|
if (!data.startsWith(header)) {
|
|
m_errorStr = tr("Invalid OpData01, does not contain header");
|
|
return false;
|
|
}
|
|
|
|
QDataStream in(data);
|
|
in.setByteOrder(QDataStream::LittleEndian);
|
|
in.skipRawData(header.size());
|
|
|
|
/*!
|
|
* The next 8 bytes contain the length in bytes of the plaintext as a little endian unsigned 64 bit integer.
|
|
*/
|
|
qlonglong len;
|
|
in >> len;
|
|
|
|
/*!
|
|
* The next 16 bytes are the randomly chosen initialization vector.
|
|
*/
|
|
QByteArray iv(16, '\0');
|
|
int read = in.readRawData(iv.data(), 16);
|
|
if (read != 16) {
|
|
m_errorStr = tr("Unable to read all IV bytes, wanted 16 but got %1").arg(iv.size());
|
|
return false;
|
|
}
|
|
|
|
SymmetricCipher cipher;
|
|
if (!cipher.init(SymmetricCipher::Aes256_CBC, SymmetricCipher::Decrypt, key, iv)) {
|
|
m_errorStr = tr("Unable to init cipher for opdata01: %1").arg(cipher.errorString());
|
|
return false;
|
|
}
|
|
|
|
/*!
|
|
* The plaintext is padded using the following scheme.
|
|
*
|
|
* If the size of the plaintext is an even multiple of the block size then 1 block of random data is prepended
|
|
* to the plaintext. Otherwise, between 1 and 15 (inclusive) bytes of random data are prepended to the plaintext
|
|
* to achieve an even multiple of blocks.
|
|
*/
|
|
const int blockSize = cipher.blockSize(cipher.mode());
|
|
int randomBytes = blockSize - (len % blockSize);
|
|
if (randomBytes == 0) {
|
|
// add random block
|
|
randomBytes = blockSize;
|
|
}
|
|
qlonglong clear_len = len + randomBytes;
|
|
QByteArray qbaCT(clear_len, '\0');
|
|
in.readRawData(qbaCT.data(), clear_len);
|
|
|
|
/*!
|
|
* The HMAC-SHA256 is computed over the entirety of the opdata including header, length, IV and ciphertext
|
|
* using a 256-bit MAC key. The 256-bit MAC is not truncated. It is appended to the ciphertext.
|
|
*/
|
|
const int hmacLen = 256 / 8;
|
|
QByteArray hmacSig(hmacLen, '\0'); // 256 / 8, '\0');
|
|
in.readRawData(hmacSig.data(), hmacLen);
|
|
if (hmacSig.size() != hmacLen) {
|
|
m_errorStr = tr("Unable to read all HMAC signature bytes");
|
|
return false;
|
|
}
|
|
|
|
const QByteArray hmacData = data.mid(0, data.size() - hmacSig.size());
|
|
const QByteArray actualHmac = CryptoHash::hmac(hmacData, hmacKey, CryptoHash::Algorithm::Sha256);
|
|
if (actualHmac != hmacSig) {
|
|
m_errorStr = tr("Malformed OpData01 due to a failed HMAC");
|
|
return false;
|
|
}
|
|
|
|
if (!cipher.process(qbaCT)) {
|
|
m_errorStr = tr("Unable to process clearText in place");
|
|
return false;
|
|
}
|
|
|
|
// Remove random bytes
|
|
const QByteArray& clearText = qbaCT.mid(randomBytes);
|
|
if (clearText.size() != len) {
|
|
m_errorStr = tr("Expected %1 bytes of clear-text, found %2").arg(len, clearText.size());
|
|
return false;
|
|
}
|
|
m_clearText = clearText;
|
|
return true;
|
|
}
|
|
|
|
QByteArray OpData01::getClearText()
|
|
{
|
|
return m_clearText;
|
|
}
|
|
|
|
QString OpData01::errorString()
|
|
{
|
|
return m_errorStr;
|
|
}
|