Add support for various algorithms for kdbx4
* Add SHA512 support to CryptoHash * Add ChaCha20 support * Add HMAC support * Add new HmacBlockStream, used in KDBX 4 * Add support for ChaCha20 protected stream
This commit is contained in:
267
src/streams/HmacBlockStream.cpp
Normal file
267
src/streams/HmacBlockStream.cpp
Normal file
@@ -0,0 +1,267 @@
|
||||
/*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "HmacBlockStream.h"
|
||||
|
||||
#include "core/Endian.h"
|
||||
#include "crypto/CryptoHash.h"
|
||||
|
||||
const QSysInfo::Endian HmacBlockStream::ByteOrder = QSysInfo::LittleEndian;
|
||||
|
||||
HmacBlockStream::HmacBlockStream(QIODevice* baseDevice, QByteArray key)
|
||||
: LayeredStream(baseDevice)
|
||||
, m_blockSize(1024*1024)
|
||||
, m_key(key)
|
||||
{
|
||||
init();
|
||||
}
|
||||
|
||||
HmacBlockStream::HmacBlockStream(QIODevice* baseDevice, QByteArray key, qint32 blockSize)
|
||||
: LayeredStream(baseDevice)
|
||||
, m_blockSize(blockSize)
|
||||
, m_key(key)
|
||||
{
|
||||
init();
|
||||
}
|
||||
|
||||
HmacBlockStream::~HmacBlockStream()
|
||||
{
|
||||
close();
|
||||
}
|
||||
|
||||
void HmacBlockStream::init()
|
||||
{
|
||||
m_buffer.clear();
|
||||
m_bufferPos = 0;
|
||||
m_blockIndex = 0;
|
||||
m_eof = false;
|
||||
m_error = false;
|
||||
}
|
||||
|
||||
bool HmacBlockStream::reset()
|
||||
{
|
||||
// Write final block(s) only if device is writable and we haven't
|
||||
// already written a final block.
|
||||
if (isWritable() && (!m_buffer.isEmpty() || m_blockIndex != 0)) {
|
||||
if (!m_buffer.isEmpty()) {
|
||||
if (!writeHashedBlock()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// write empty final block
|
||||
if (!writeHashedBlock()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
init();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void HmacBlockStream::close()
|
||||
{
|
||||
// Write final block(s) only if device is writable and we haven't
|
||||
// already written a final block.
|
||||
if (isWritable() && (!m_buffer.isEmpty() || m_blockIndex != 0)) {
|
||||
if (!m_buffer.isEmpty()) {
|
||||
writeHashedBlock();
|
||||
}
|
||||
|
||||
// write empty final block
|
||||
writeHashedBlock();
|
||||
}
|
||||
|
||||
LayeredStream::close();
|
||||
}
|
||||
|
||||
qint64 HmacBlockStream::readData(char* data, qint64 maxSize)
|
||||
{
|
||||
if (m_error) {
|
||||
return -1;
|
||||
}
|
||||
else if (m_eof) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
qint64 bytesRemaining = maxSize;
|
||||
qint64 offset = 0;
|
||||
|
||||
while (bytesRemaining > 0) {
|
||||
if (m_bufferPos == m_buffer.size()) {
|
||||
if (!readHashedBlock()) {
|
||||
if (m_error) {
|
||||
return -1;
|
||||
}
|
||||
else {
|
||||
return maxSize - bytesRemaining;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int bytesToCopy = qMin(bytesRemaining, static_cast<qint64>(m_buffer.size() - m_bufferPos));
|
||||
|
||||
memcpy(data + offset, m_buffer.constData() + m_bufferPos, bytesToCopy);
|
||||
|
||||
offset += bytesToCopy;
|
||||
m_bufferPos += bytesToCopy;
|
||||
bytesRemaining -= bytesToCopy;
|
||||
}
|
||||
|
||||
return maxSize;
|
||||
}
|
||||
|
||||
bool HmacBlockStream::readHashedBlock()
|
||||
{
|
||||
if (m_eof) {
|
||||
return false;
|
||||
}
|
||||
QByteArray hmac = m_baseDevice->read(32);
|
||||
if (hmac.size() != 32) {
|
||||
m_error = true;
|
||||
setErrorString("Invalid HMAC size.");
|
||||
return false;
|
||||
}
|
||||
|
||||
QByteArray blockSizeBytes = m_baseDevice->read(4);
|
||||
if (blockSizeBytes.size() != 4) {
|
||||
m_error = true;
|
||||
setErrorString("Invalid block size size.");
|
||||
return false;
|
||||
}
|
||||
qint32 blockSize = Endian::bytesToInt32(blockSizeBytes, ByteOrder);
|
||||
if (blockSize < 0) {
|
||||
m_error = true;
|
||||
setErrorString("Invalid block size.");
|
||||
return false;
|
||||
}
|
||||
|
||||
m_buffer = m_baseDevice->read(blockSize);
|
||||
if (m_buffer.size() != blockSize) {
|
||||
m_error = true;
|
||||
setErrorString("Block too short.");
|
||||
return false;
|
||||
}
|
||||
|
||||
CryptoHash hasher(CryptoHash::Sha256, true);
|
||||
hasher.setKey(getCurrentHmacKey());
|
||||
hasher.addData(Endian::uint64ToBytes(m_blockIndex, ByteOrder));
|
||||
hasher.addData(blockSizeBytes);
|
||||
hasher.addData(m_buffer);
|
||||
|
||||
if (hmac != hasher.result()) {
|
||||
m_error = true;
|
||||
setErrorString("Mismatch between hash and data.");
|
||||
return false;
|
||||
}
|
||||
|
||||
m_bufferPos = 0;
|
||||
m_blockIndex++;
|
||||
|
||||
if (blockSize == 0) {
|
||||
m_eof = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
qint64 HmacBlockStream::writeData(const char* data, qint64 maxSize)
|
||||
{
|
||||
Q_ASSERT(maxSize >= 0);
|
||||
|
||||
if (m_error) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
qint64 bytesRemaining = maxSize;
|
||||
qint64 offset = 0;
|
||||
|
||||
while (bytesRemaining > 0) {
|
||||
int bytesToCopy = qMin(bytesRemaining, static_cast<qint64>(m_blockSize - m_buffer.size()));
|
||||
|
||||
m_buffer.append(data + offset, bytesToCopy);
|
||||
|
||||
offset += bytesToCopy;
|
||||
bytesRemaining -= bytesToCopy;
|
||||
|
||||
if (m_buffer.size() == m_blockSize) {
|
||||
if (!writeHashedBlock()) {
|
||||
if (m_error) {
|
||||
return -1;
|
||||
}
|
||||
else {
|
||||
return maxSize - bytesRemaining;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return maxSize;
|
||||
}
|
||||
|
||||
bool HmacBlockStream::writeHashedBlock()
|
||||
{
|
||||
CryptoHash hasher(CryptoHash::Sha256, true);
|
||||
hasher.setKey(getCurrentHmacKey());
|
||||
hasher.addData(Endian::uint64ToBytes(m_blockIndex, ByteOrder));
|
||||
hasher.addData(Endian::int32ToBytes(m_buffer.size(), ByteOrder));
|
||||
hasher.addData(m_buffer);
|
||||
QByteArray hash = hasher.result();
|
||||
|
||||
if (m_baseDevice->write(hash) != hash.size()) {
|
||||
m_error = true;
|
||||
setErrorString(m_baseDevice->errorString());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!Endian::writeInt32(m_buffer.size(), m_baseDevice, ByteOrder)) {
|
||||
m_error = true;
|
||||
setErrorString(m_baseDevice->errorString());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!m_buffer.isEmpty()) {
|
||||
if (m_baseDevice->write(m_buffer) != m_buffer.size()) {
|
||||
m_error = true;
|
||||
setErrorString(m_baseDevice->errorString());
|
||||
return false;
|
||||
}
|
||||
|
||||
m_buffer.clear();
|
||||
}
|
||||
m_blockIndex++;
|
||||
return true;
|
||||
}
|
||||
|
||||
QByteArray HmacBlockStream::getCurrentHmacKey() const {
|
||||
return getHmacKey(m_blockIndex, m_key);
|
||||
}
|
||||
|
||||
QByteArray HmacBlockStream::getHmacKey(quint64 blockIndex, QByteArray key) {
|
||||
Q_ASSERT(key.size() == 64);
|
||||
QByteArray indexBytes = Endian::uint64ToBytes(blockIndex, ByteOrder);
|
||||
CryptoHash hasher(CryptoHash::Sha512);
|
||||
hasher.addData(indexBytes);
|
||||
hasher.addData(key);
|
||||
return hasher.result();
|
||||
}
|
||||
|
||||
bool HmacBlockStream::atEnd() const {
|
||||
return m_eof;
|
||||
}
|
||||
Reference in New Issue
Block a user