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(); }
bool convertRecording(const QString &inputfilename, const QString &outputfilename, const QString &outputFormat) { // Open input file Reader reader(inputfilename); Compatibility compat = reader.open(); switch(compat) { case INCOMPATIBLE: fprintf( stderr, "This recording is incompatible (format version %s). It was made with Drawpile version %s.\n", qPrintable(reader.formatVersion().asString()), qPrintable(reader.writerVersion()) ); return false; case NOT_DPREC: fprintf(stderr, "Input file is not a Drawpile recording!\n"); return false; case CANNOT_READ: fprintf(stderr, "Unable to read input file: %s\n", reader.errorString().toLocal8Bit().constData()); return false; case COMPATIBLE: case MINOR_INCOMPATIBILITY: case UNKNOWN_COMPATIBILITY: // OK to proceed break; } // Open output file (stdout if no filename given) QScopedPointer<Writer> writer; if(outputfilename.isEmpty()) { // No output filename given? Write to stdout QFile *out = new QFile(); out->open(stdout, QFile::WriteOnly); writer.reset(new Writer(out)); out->setParent(writer.data()); writer->setEncoding(Writer::Encoding::Text); } else { writer.reset(new Writer(outputfilename)); } // Output format override if(outputFormat == "text") writer->setEncoding(Writer::Encoding::Text); else if(outputFormat == "binary") writer->setEncoding(Writer::Encoding::Binary); else if(!outputFormat.isEmpty()) { fprintf(stderr, "Invalid output format: %s\n", qPrintable(outputFormat)); return false; } // Convert input to output if(!writer->open()) { fprintf(stderr, "Couldn't open %s: %s\n", outputfilename.toLocal8Bit().constData(), writer->errorString().toLocal8Bit().constData() ); return false; } if(!writer->writeHeader()) { fprintf(stderr, "Error while writing header: %s\n", writer->errorString().toLocal8Bit().constData() ); return false; } bool notEof = true; do { MessageRecord mr = reader.readNext(); switch(mr.status) { case MessageRecord::OK: if(!writer->writeMessage(*mr.message)) { fprintf(stderr, "Error while writing message: %s\n", writer->errorString().toLocal8Bit().constData() ); return false; } delete mr.message; break; case MessageRecord::INVALID: writer->writeComment(QStringLiteral("WARNING: Unrecognized message type %1 of length %2 at offset 0x%3") .arg(int(mr.error.type)) .arg(mr.error.len) .arg(reader.currentPosition()) ); break; case MessageRecord::END_OF_RECORDING: notEof = false; break; } } while(notEof); return true; }