Database* KeePass2Reader::readDatabase(QIODevice* device, const CompositeKey& key, bool keepDatabase) { QScopedPointer<Database> db(new Database()); m_db = db.data(); m_device = device; m_error = false; m_errorStr.clear(); m_headerEnd = false; m_xmlData.clear(); m_masterSeed.clear(); m_transformSeed.clear(); m_encryptionIV.clear(); m_streamStartBytes.clear(); m_protectedStreamKey.clear(); StoreDataStream headerStream(m_device); headerStream.open(QIODevice::ReadOnly); m_headerStream = &headerStream; bool ok; quint32 signature1 = Endian::readUInt32(m_headerStream, KeePass2::BYTEORDER, &ok); if (!ok || signature1 != KeePass2::SIGNATURE_1) { raiseError(tr("Not a KeePass database.")); return nullptr; } quint32 signature2 = Endian::readUInt32(m_headerStream, KeePass2::BYTEORDER, &ok); if (ok && signature2 == KeePass1::SIGNATURE_2) { raiseError(tr("The selected file is an old KeePass 1 database (.kdb).\n\n" "You can import it by clicking on Database > 'Import KeePass 1 database'.\n" "This is a one-way migration. You won't be able to open the imported " "database with the old KeePassX 0.4 version.")); return nullptr; } else if (!ok || signature2 != KeePass2::SIGNATURE_2) { raiseError(tr("Not a KeePass database.")); return nullptr; } quint32 version = Endian::readUInt32(m_headerStream, KeePass2::BYTEORDER, &ok) & KeePass2::FILE_VERSION_CRITICAL_MASK; quint32 maxVersion = KeePass2::FILE_VERSION & KeePass2::FILE_VERSION_CRITICAL_MASK; if (!ok || (version < KeePass2::FILE_VERSION_MIN) || (version > maxVersion)) { raiseError(tr("Unsupported KeePass database version.")); return nullptr; } while (readHeaderField() && !hasError()) { } headerStream.close(); if (hasError()) { return nullptr; } // check if all required headers were present if (m_masterSeed.isEmpty() || m_transformSeed.isEmpty() || m_encryptionIV.isEmpty() || m_streamStartBytes.isEmpty() || m_protectedStreamKey.isEmpty() || m_db->cipher().isNull()) { raiseError("missing database headers"); return nullptr; } if (!m_db->setKey(key, m_transformSeed, false)) { raiseError(tr("Unable to calculate master key")); return nullptr; } CryptoHash hash(CryptoHash::Sha256); hash.addData(m_masterSeed); hash.addData(m_db->transformedMasterKey()); QByteArray finalKey = hash.result(); SymmetricCipherStream cipherStream(m_device, SymmetricCipher::Aes256, SymmetricCipher::Cbc, SymmetricCipher::Decrypt); if (!cipherStream.init(finalKey, m_encryptionIV)) { raiseError(cipherStream.errorString()); return nullptr; } if (!cipherStream.open(QIODevice::ReadOnly)) { raiseError(cipherStream.errorString()); return nullptr; } QByteArray realStart = cipherStream.read(32); if (realStart != m_streamStartBytes) { raiseError(tr("Wrong key or database file is corrupt.")); return nullptr; } HashedBlockStream hashedStream(&cipherStream); if (!hashedStream.open(QIODevice::ReadOnly)) { raiseError(hashedStream.errorString()); return nullptr; } QIODevice* xmlDevice; QScopedPointer<QtIOCompressor> ioCompressor; if (m_db->compressionAlgo() == Database::CompressionNone) { xmlDevice = &hashedStream; } else { ioCompressor.reset(new QtIOCompressor(&hashedStream)); ioCompressor->setStreamFormat(QtIOCompressor::GzipFormat); if (!ioCompressor->open(QIODevice::ReadOnly)) { raiseError(ioCompressor->errorString()); return nullptr; } xmlDevice = ioCompressor.data(); } KeePass2RandomStream randomStream; if (!randomStream.init(m_protectedStreamKey)) { raiseError(randomStream.errorString()); return nullptr; } QScopedPointer<QBuffer> buffer; if (m_saveXml) { m_xmlData = xmlDevice->readAll(); buffer.reset(new QBuffer(&m_xmlData)); buffer->open(QIODevice::ReadOnly); xmlDevice = buffer.data(); } KeePass2XmlReader xmlReader; xmlReader.readDatabase(xmlDevice, m_db, &randomStream); if (xmlReader.hasError()) { raiseError(xmlReader.errorString()); if (keepDatabase) { return db.take(); } else { return nullptr; } } Q_ASSERT(version < 0x00030001 || !xmlReader.headerHash().isEmpty()); if (!xmlReader.headerHash().isEmpty()) { QByteArray headerHash = CryptoHash::hash(headerStream.storedData(), CryptoHash::Sha256); if (headerHash != xmlReader.headerHash()) { raiseError("Header doesn't match hash"); return nullptr; } } return db.take(); }
Database* KeePass2Reader::readDatabase(QIODevice* device, const CompositeKey& key) { QScopedPointer<Database> db(new Database()); m_db = db.data(); QPointer<QIODevice> m_device = device; m_error = false; m_errorStr = QString(); m_headerEnd = false; m_xmlData.clear(); m_masterSeed.clear(); m_transformSeed.clear(); m_encryptionIV.clear(); m_streamStartBytes.clear(); m_protectedStreamKey.clear(); StoreDataStream headerStream(m_device); headerStream.open(QIODevice::ReadOnly); m_headerStream = &headerStream; bool ok; quint32 signature1 = Endian::readUInt32(m_headerStream, KeePass2::BYTEORDER, &ok); if (!ok || signature1 != KeePass2::SIGNATURE_1) { raiseError(tr("Not a KeePass database.")); return Q_NULLPTR; } quint32 signature2 = Endian::readUInt32(m_headerStream, KeePass2::BYTEORDER, &ok); if (!ok || signature2 != KeePass2::SIGNATURE_2) { raiseError(tr("Not a KeePass database.")); return Q_NULLPTR; } quint32 version = Endian::readUInt32(m_headerStream, KeePass2::BYTEORDER, &ok) & KeePass2::FILE_VERSION_CRITICAL_MASK; quint32 maxVersion = KeePass2::FILE_VERSION & KeePass2::FILE_VERSION_CRITICAL_MASK; if (!ok || (version < KeePass2::FILE_VERSION_MIN) || (version > maxVersion)) { raiseError(tr("Unsupported KeePass database version.")); return Q_NULLPTR; } while (readHeaderField() && !hasError()) { } headerStream.close(); // check if all required headers were present if (m_masterSeed.isEmpty() || m_transformSeed.isEmpty() || m_encryptionIV.isEmpty() || m_streamStartBytes.isEmpty() || m_protectedStreamKey.isEmpty() || m_db->cipher().isNull()) { raiseError(""); return Q_NULLPTR; } m_db->setKey(key, m_transformSeed, false); CryptoHash hash(CryptoHash::Sha256); hash.addData(m_masterSeed); hash.addData(m_db->transformedMasterKey()); QByteArray finalKey = hash.result(); SymmetricCipherStream cipherStream(m_device, SymmetricCipher::Aes256, SymmetricCipher::Cbc, SymmetricCipher::Decrypt, finalKey, m_encryptionIV); cipherStream.open(QIODevice::ReadOnly); QByteArray realStart = cipherStream.read(32); if (realStart != m_streamStartBytes) { raiseError(tr("Wrong key or database file is corrupt.")); return Q_NULLPTR; } HashedBlockStream hashedStream(&cipherStream); hashedStream.open(QIODevice::ReadOnly); QIODevice* xmlDevice; QScopedPointer<QtIOCompressor> ioCompressor; if (m_db->compressionAlgo() == Database::CompressionNone) { xmlDevice = &hashedStream; } else { ioCompressor.reset(new QtIOCompressor(&hashedStream)); ioCompressor->setStreamFormat(QtIOCompressor::GzipFormat); ioCompressor->open(QIODevice::ReadOnly); xmlDevice = ioCompressor.data(); } KeePass2RandomStream randomStream(m_protectedStreamKey); QScopedPointer<QBuffer> buffer; if (m_saveXml) { m_xmlData = xmlDevice->readAll(); buffer.reset(new QBuffer(&m_xmlData)); buffer->open(QIODevice::ReadOnly); xmlDevice = buffer.data(); } KeePass2XmlReader xmlReader; xmlReader.readDatabase(xmlDevice, m_db, &randomStream); if (xmlReader.hasError()) { raiseError(xmlReader.errorString()); return Q_NULLPTR; } Q_ASSERT(version < 0x00030001 || !xmlReader.headerHash().isEmpty()); if (!xmlReader.headerHash().isEmpty()) { QByteArray headerHash = CryptoHash::hash(headerStream.storedData(), CryptoHash::Sha256); if (headerHash != xmlReader.headerHash()) { raiseError(""); return Q_NULLPTR; } } return db.take(); }