mirror of
https://github.com/keepassxreboot/keepassxc.git
synced 2025-08-21 04:28:26 -04:00
Implement 1Password Vault Import
* Support importing 1Password vaults (.opvault folders) into KDBX database * Entry attributes are filled based on section and field name * Expiration dates are set for entries * Entry URL's are set from a wider array of fields
This commit is contained in:
parent
e121f4bc28
commit
125a81f2ed
45 changed files with 2578 additions and 0 deletions
137
src/format/OpData01.cpp
Normal file
137
src/format/OpData01.cpp
Normal file
|
@ -0,0 +1,137 @@
|
|||
/*
|
||||
* 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>
|
||||
#include <QDebug>
|
||||
|
||||
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(SymmetricCipher::Aes256, SymmetricCipher::Cbc, SymmetricCipher::Decrypt);
|
||||
if (!cipher.init(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();
|
||||
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.processInPlace(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;
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue