Esempio n. 1
0
void KeePass2Writer::writeDatabase(QIODevice* device, Database* db)
{
    m_error = false;
    m_errorStr.clear();

    QByteArray masterSeed = Random::randomArray(32);
    QByteArray encryptionIV = Random::randomArray(16);
    QByteArray protectedStreamKey = Random::randomArray(32);
    QByteArray startBytes = Random::randomArray(32);
    QByteArray endOfHeader = "\r\n\r\n";

    CryptoHash hash(CryptoHash::Sha256);
    hash.addData(masterSeed);
    Q_ASSERT(!db->transformedMasterKey().isEmpty());
    hash.addData(db->transformedMasterKey());
    QByteArray finalKey = hash.result();

    QBuffer header;
    header.open(QIODevice::WriteOnly);
    m_device = &header;

    CHECK_RETURN(writeData(Endian::int32ToBytes(KeePass2::SIGNATURE_1, KeePass2::BYTEORDER)));
    CHECK_RETURN(writeData(Endian::int32ToBytes(KeePass2::SIGNATURE_2, KeePass2::BYTEORDER)));
    CHECK_RETURN(writeData(Endian::int32ToBytes(KeePass2::FILE_VERSION, KeePass2::BYTEORDER)));

    CHECK_RETURN(writeHeaderField(KeePass2::CipherID, db->cipher().toByteArray()));
    CHECK_RETURN(writeHeaderField(KeePass2::CompressionFlags,
                                  Endian::int32ToBytes(db->compressionAlgo(),
                                                       KeePass2::BYTEORDER)));
    CHECK_RETURN(writeHeaderField(KeePass2::MasterSeed, masterSeed));
    CHECK_RETURN(writeHeaderField(KeePass2::TransformSeed, db->transformSeed()));
    CHECK_RETURN(writeHeaderField(KeePass2::TransformRounds,
                                  Endian::int64ToBytes(db->transformRounds(),
                                                       KeePass2::BYTEORDER)));
    CHECK_RETURN(writeHeaderField(KeePass2::EncryptionIV, encryptionIV));
    CHECK_RETURN(writeHeaderField(KeePass2::ProtectedStreamKey, protectedStreamKey));
    CHECK_RETURN(writeHeaderField(KeePass2::StreamStartBytes, startBytes));
    CHECK_RETURN(writeHeaderField(KeePass2::InnerRandomStreamID,
                                  Endian::int32ToBytes(KeePass2::Salsa20,
                                                       KeePass2::BYTEORDER)));
    CHECK_RETURN(writeHeaderField(KeePass2::EndOfHeader, endOfHeader));

    header.close();
    m_device = device;
    QByteArray headerHash = CryptoHash::hash(header.data(), CryptoHash::Sha256);
    CHECK_RETURN(writeData(header.data()));

    SymmetricCipherStream cipherStream(device, SymmetricCipher::Aes256, SymmetricCipher::Cbc,
                                       SymmetricCipher::Encrypt, finalKey, encryptionIV);
    cipherStream.open(QIODevice::WriteOnly);
    m_device = &cipherStream;
    CHECK_RETURN(writeData(startBytes));

    HashedBlockStream hashedStream(&cipherStream);
    hashedStream.open(QIODevice::WriteOnly);

    QScopedPointer<QtIOCompressor> ioCompressor;

    if (db->compressionAlgo() == Database::CompressionNone) {
        m_device = &hashedStream;
    }
    else {
        ioCompressor.reset(new QtIOCompressor(&hashedStream));
        ioCompressor->setStreamFormat(QtIOCompressor::GzipFormat);
        ioCompressor->open(QIODevice::WriteOnly);
        m_device = ioCompressor.data();
    }

    KeePass2RandomStream randomStream(protectedStreamKey);

    KeePass2XmlWriter xmlWriter;
    xmlWriter.writeDatabase(m_device, db, &randomStream, headerHash);
}
Esempio n. 2
0
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();
}