nextcloud-desktop/src/libsync/foldermetadata.h

246 lines
8.6 KiB
C++

#pragma once
/*
* Copyright (C) 2024 by Oleksandr Zolotov <alex@nextcloud.com>
*
* 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 of the License, or
* (at your option) any later version.
*
* 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.
*/
#include "accountfwd.h"
#include "encryptedfoldermetadatahandler.h"
#include "csync.h"
#include "rootencryptedfolderinfo.h"
#include <QByteArray>
#include <QHash>
#include <QJsonObject>
#include <QObject>
#include <QSet>
#include <QSslKey>
#include <QString>
#include <QVector>
class QSslCertificate;
class QJsonDocument;
class TestClientSideEncryptionV2;
class TestSecureFileDrop;
namespace OCC
{
// Handles parsing and altering the metadata, encryption and decryption. Setup of the instance is always asynchronouse and emits void setupComplete()
class OWNCLOUDSYNC_EXPORT FolderMetadata : public QObject
{
friend class ::TestClientSideEncryptionV2;
friend class ::TestSecureFileDrop;
Q_OBJECT
struct UserWithFolderAccess {
QString userId;
QByteArray certificatePem;
QByteArray encryptedMetadataKey;
};
// based on api-version and "version" key in metadata JSON
enum MetadataVersion {
VersionUndefined = -1,
Version1,
Version1_2,
Version2_0,
};
struct UserWithFileDropEntryAccess {
QString userId;
QByteArray decryptedFiledropKey;
inline bool isValid() const
{
return !userId.isEmpty() && !decryptedFiledropKey.isEmpty();
}
};
struct FileDropEntry {
QString encryptedFilename;
QByteArray cipherText;
QByteArray nonce;
QByteArray authenticationTag;
UserWithFileDropEntryAccess currentUser;
inline bool isValid() const
{
return !cipherText.isEmpty() && !nonce.isEmpty() && !authenticationTag.isEmpty();
}
};
public:
struct EncryptedFile {
QByteArray encryptionKey;
QByteArray mimetype;
QByteArray initializationVector;
QByteArray authenticationTag;
QString encryptedFilename;
QString originalFilename;
bool isDirectory() const;
};
enum class FolderType {
Nested = 0,
Root = 1,
};
Q_ENUM(FolderType)
FolderMetadata(AccountPtr account, FolderType folderType = FolderType::Nested);
/*
* construct metadata based on RootEncryptedFolderInfo
* as per E2EE V2, the encryption key and users that have access are only stored in root(top-level) encrypted folder's metadata
* see: https://github.com/nextcloud/end_to_end_encryption_rfc/blob/v2.1/RFC.md
*/
FolderMetadata(AccountPtr account,
const QByteArray &metadata,
const RootEncryptedFolderInfo &rootEncryptedFolderInfo,
const QByteArray &signature,
QObject *parent = nullptr);
[[nodiscard]] QVector<EncryptedFile> files() const;
[[nodiscard]] bool isValid() const;
[[nodiscard]] bool isFileDropPresent() const;
[[nodiscard]] bool isRootEncryptedFolder() const;
[[nodiscard]] bool encryptedMetadataNeedUpdate() const;
[[nodiscard]] bool moveFromFileDropToFiles();
// adds a user to have access to this folder (always generates new metadata key)
[[nodiscard]] bool addUser(const QString &userId, const QSslCertificate &certificate);
// removes a user from this folder and removes and generates a new metadata key
[[nodiscard]] bool removeUser(const QString &userId);
[[nodiscard]] const QByteArray metadataKeyForEncryption() const;
[[nodiscard]] const QByteArray metadataKeyForDecryption() const;
[[nodiscard]] const QSet<QByteArray> &keyChecksums() const;
[[nodiscard]] QByteArray encryptedMetadata();
[[nodiscard]] EncryptionStatusEnums::ItemEncryptionStatus existingMetadataEncryptionStatus() const;
[[nodiscard]] EncryptionStatusEnums::ItemEncryptionStatus encryptedMetadataEncryptionStatus() const;
[[nodiscard]] bool isVersion2AndUp() const;
[[nodiscard]] quint64 newCounter() const;
[[nodiscard]] QByteArray metadataSignature() const;
[[nodiscard]] QByteArray initialMetadata() const;
public slots:
void addEncryptedFile(const EncryptedFile &f);
void removeEncryptedFile(const EncryptedFile &f);
void removeAllEncryptedFiles();
private:
[[nodiscard]] QByteArray encryptedMetadataLegacy();
[[nodiscard]] bool verifyMetadataKey(const QByteArray &metadataKey) const;
[[nodiscard]] QByteArray encryptDataWithPublicKey(const QByteArray &data, const QSslKey &key) const;
[[nodiscard]] QByteArray decryptDataWithPrivateKey(const QByteArray &data) const;
[[nodiscard]] QByteArray encryptJsonObject(const QByteArray& obj, const QByteArray pass) const;
[[nodiscard]] QByteArray decryptJsonObject(const QByteArray& encryptedJsonBlob, const QByteArray& pass) const;
[[nodiscard]] bool checkMetadataKeyChecksum(const QByteArray &metadataKey, const QByteArray &metadataKeyChecksum) const;
[[nodiscard]] QByteArray computeMetadataKeyChecksum(const QByteArray &metadataKey) const;
[[nodiscard]] EncryptedFile parseEncryptedFileFromJson(const QString &encryptedFilename, const QJsonValue &fileJSON) const;
[[nodiscard]] QJsonObject convertFileToJsonObject(const EncryptedFile *encryptedFile) const;
[[nodiscard]] MetadataVersion latestSupportedMetadataVersion() const;
[[nodiscard]] bool parseFileDropPart(const QJsonDocument &doc);
void setFileDrop(const QJsonObject &fileDrop);
static EncryptionStatusEnums::ItemEncryptionStatus fromMedataVersionToItemEncryptionStatus(const MetadataVersion metadataVersion);
static MetadataVersion fromItemEncryptionStatusToMedataVersion(const EncryptionStatusEnums::ItemEncryptionStatus encryptionStatus);
static QByteArray prepareMetadataForSignature(const QJsonDocument &fullMetadata);
private slots:
void initMetadata();
void initEmptyMetadata();
void initEmptyMetadataLegacy();
void setupExistingMetadata(const QByteArray &metadata);
void setupExistingMetadataLegacy(const QByteArray &metadata);
void setupVersionFromExistingMetadata(const QByteArray &metadata);
void startFetchRootE2eeFolderMetadata(const QString &path);
void slotRootE2eeFolderMetadataReceived(int statusCode, const QString &message);
void updateUsersEncryptedMetadataKey();
void createNewMetadataKeyForEncryption();
void emitSetupComplete();
signals:
void setupComplete();
private:
AccountPtr _account;
QByteArray _initialMetadata;
bool _isRootEncryptedFolder = false;
// always contains the last generated metadata key (non-encrypted and non-base64)
QByteArray _metadataKeyForEncryption;
// used for storing initial metadataKey to use for decryption, especially in nested folders when changing the metadataKey and re-encrypting nested dirs
QByteArray _metadataKeyForDecryption;
QByteArray _metadataNonce;
// metadatakey checksums for validation during setting up from existing metadata
QSet<QByteArray> _keyChecksums;
// filedrop part non-parsed, for upload in case parsing can not be done (due to not having access for the current user, etc.)
QJsonObject _fileDrop;
// used by unit tests, must get assigned simultaneously with _fileDrop and never erased
QJsonObject _fileDropFromServer;
// legacy, remove after migration is done
QMap<int, QByteArray> _metadataKeys;
// users that have access to current folder's "ciphertext", except "filedrop" part
QHash<QString, UserWithFolderAccess> _folderUsers;
// must increment on each metadata upload
quint64 _counter = 0;
MetadataVersion _existingMetadataVersion = MetadataVersion::VersionUndefined;
MetadataVersion _encryptedMetadataVersion = MetadataVersion::VersionUndefined;
// generated each time QByteArray encryptedMetadata() is called, and will later be used for validation if uploaded
QByteArray _metadataSignature;
// signature from server-side metadata
QByteArray _initialSignature;
// both files and folders info
QVector<EncryptedFile> _files;
// parsed filedrop entries ready for move
QVector<FileDropEntry> _fileDropEntries;
// sets to "true" on successful parse
bool _isMetadataValid = false;
QScopedPointer<EncryptedFolderMetadataHandler> _encryptedFolderMetadataHandler;
};
} // namespace OCC