Skip to content

Commit

Permalink
migrate of encrypted metadata from a previous to the current certificate
Browse files Browse the repository at this point in the history
search for a previous certificate matching the certifcate having been
used when encoding the metadata to recover them and next time we need to
encrypt the metadata, the current selected certificate is going to be
used

Signed-off-by: Matthieu Gallien <[email protected]>
  • Loading branch information
mgallien committed Dec 8, 2023
1 parent 61de2d6 commit 35c5dde
Show file tree
Hide file tree
Showing 4 changed files with 57 additions and 36 deletions.
77 changes: 47 additions & 30 deletions src/libsync/clientsideencryption.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -678,11 +678,13 @@ namespace internals {

}

std::optional<QByteArray> encryptStringAsymmetric(const ClientSideEncryption &encryptionEngine, const QByteArray &binaryData)
std::optional<QByteArray> encryptStringAsymmetric(const CertificateInformation &selectedCertificate,

Check warning on line 681 in src/libsync/clientsideencryption.cpp

View workflow job for this annotation

GitHub Actions / build

/src/libsync/clientsideencryption.cpp:681:27 [modernize-use-trailing-return-type]

use a trailing return type for this function
const ClientSideEncryption &encryptionEngine,
const QByteArray &binaryData)
{
if (encryptionEngine.useTokenBasedEncryption()) {
auto encryptedBase64Result = internals::encryptStringAsymmetricWithToken(encryptionEngine.sslEngine(),
encryptionEngine.getTokenCertificate().getPublicKey(),
selectedCertificate.getPublicKey(),
binaryData);

if (!encryptedBase64Result) {
Expand Down Expand Up @@ -724,23 +726,24 @@ std::optional<QByteArray> encryptStringAsymmetric(const ClientSideEncryption &en
}
}

std::optional<QByteArray> decryptStringAsymmetric(const QByteArray &certificateSha256Fingerprint,
std::optional<QByteArray> decryptStringAsymmetric(const CertificateInformation &selectedCertificate,

Check warning on line 729 in src/libsync/clientsideencryption.cpp

View workflow job for this annotation

GitHub Actions / build

/src/libsync/clientsideencryption.cpp:729:27 [modernize-use-trailing-return-type]

use a trailing return type for this function
const ClientSideEncryption &encryptionEngine,
const QByteArray &base64Data)
const QByteArray &base64Data,

Check warning on line 731 in src/libsync/clientsideencryption.cpp

View workflow job for this annotation

GitHub Actions / build

/src/libsync/clientsideencryption.cpp:731:51 [bugprone-easily-swappable-parameters]

2 adjacent parameters of 'decryptStringAsymmetric' of similar type ('const int &') are easily swapped by mistake
const QByteArray &expectedCertificateSha256Fingerprint)
{
if (!encryptionEngine.isInitialized()) {
qCWarning(lcCse()) << "end-to-end encryption is disabled";
return {};
}

if (encryptionEngine.useTokenBasedEncryption()) {
if (encryptionEngine.getTokenCertificate().sha256Fingerprint() != certificateSha256Fingerprint) {
qCWarning(lcCse()) << "wrong certificate: cannot decrypt what has been encrypted with another certificate:" << certificateSha256Fingerprint << "current certificate" << encryptionEngine.getTokenCertificate().sha256Fingerprint();
if (selectedCertificate.sha256Fingerprint() != expectedCertificateSha256Fingerprint) {
qCWarning(lcCse()) << "wrong certificate: cannot decrypt what has been encrypted with another certificate:" << expectedCertificateSha256Fingerprint << "current certificate" << selectedCertificate.sha256Fingerprint();
return {};
}

const auto decryptBase64Result = internals::decryptStringAsymmetricWithToken(encryptionEngine.sslEngine(),
encryptionEngine.getTokenCertificate().getPrivateKey(),
selectedCertificate.getPrivateKey(),
QByteArray::fromBase64(base64Data));
if (!decryptBase64Result) {
qCWarning(lcCse()) << "decrypt failed";
Expand Down Expand Up @@ -1030,6 +1033,26 @@ const CertificateInformation &ClientSideEncryption::getTokenCertificate() const
return _encryptionCertificate;
}

CertificateInformation ClientSideEncryption::getTokenCertificateByFingerprint(const QByteArray &expectedFingerprint) const
{
CertificateInformation result;

if (_encryptionCertificate.sha256Fingerprint() == expectedFingerprint) {
result = _encryptionCertificate;
return result;
}

const auto itCertificate = std::find_if(_otherCertificates.begin(), _otherCertificates.end(), [expectedFingerprint] (const auto &oneCertificate) {
return oneCertificate.sha256Fingerprint() == expectedFingerprint;
});
if (itCertificate != _otherCertificates.end()) {
result = *itCertificate;
return result;
}

return result;
}

bool ClientSideEncryption::useTokenBasedEncryption() const
{
return _encryptionCertificate.getPublicKey() && _encryptionCertificate.getPrivateKey();
Expand Down Expand Up @@ -1343,7 +1366,7 @@ bool ClientSideEncryption::checkPublicKeyValidity(const AccountPtr &account) con
BIO_write(publicKeyBio, publicKeyPem.constData(), publicKeyPem.size());
auto publicKey = PKey::readPublicKey(publicKeyBio);

auto encryptedData = EncryptionHelper::encryptStringAsymmetric(*account->e2e(), data.toBase64());
auto encryptedData = EncryptionHelper::encryptStringAsymmetric(account->e2e()->getTokenCertificate(), *account->e2e(), data.toBase64());
if (!encryptedData) {
qCWarning(lcCse()) << "encryption error";
return false;
Expand All @@ -1354,7 +1377,7 @@ bool ClientSideEncryption::checkPublicKeyValidity(const AccountPtr &account) con
BIO_write(privateKeyBio, privateKeyPem.constData(), privateKeyPem.size());
auto key = PKey::readPrivateKey(privateKeyBio);

const auto decryptionResult = EncryptionHelper::decryptStringAsymmetric(account->e2e()->certificateSha256Fingerprint(), *account->e2e(), *encryptedData);
const auto decryptionResult = EncryptionHelper::decryptStringAsymmetric(account->e2e()->getTokenCertificate(), *account->e2e(), *encryptedData, account->e2e()->certificateSha256Fingerprint());
if (!decryptionResult) {
qCWarning(lcCse()) << "encryption error";
return false;
Expand All @@ -1373,13 +1396,13 @@ bool ClientSideEncryption::checkEncryptionIsWorking() const
{
QByteArray data = EncryptionHelper::generateRandom(64);

auto encryptedData = EncryptionHelper::encryptStringAsymmetric(*this, data);
auto encryptedData = EncryptionHelper::encryptStringAsymmetric(getTokenCertificate(), *this, data);
if (!encryptedData) {
qCWarning(lcCse()) << "encryption error";
return false;
}

const auto decryptionResult = EncryptionHelper::decryptStringAsymmetric(certificateSha256Fingerprint(), *this, *encryptedData);
const auto decryptionResult = EncryptionHelper::decryptStringAsymmetric(getTokenCertificate(), *this, *encryptedData, getTokenCertificate().sha256Fingerprint());
if (!decryptionResult) {
qCWarning(lcCse()) << "encryption error";
return false;
Expand Down Expand Up @@ -2249,14 +2272,21 @@ void FolderMetadata::setupExistingMetadata(const QByteArray& metadata)
// And because inside of the meta-data there's an object called metadata, without '-'
// make it really different.

QString metaDataStr = doc.object()["ocs"]
const auto &metaDataStr = doc.object()["ocs"]
.toObject()["data"]
.toObject()["meta-data"]
.toString();

QJsonDocument metaDataDoc = QJsonDocument::fromJson(metaDataStr.toLocal8Bit());
QJsonObject metadataObj = metaDataDoc.object()["metadata"].toObject();
QJsonObject metadataKeys = metadataObj["metadataKeys"].toObject();
const auto &metaDataDoc = QJsonDocument::fromJson(metaDataStr.toLocal8Bit());
const auto &metadataObj = metaDataDoc.object()["metadata"].toObject();
const auto &metadataKeys = metadataObj["metadataKeys"].toObject();

_metadataCertificateSha256Fingerprint = metadataObj[certificateSha256FingerprintKey].toString().toLatin1();

if (_metadataCertificateSha256Fingerprint.isEmpty() && _account->e2e()->useTokenBasedEncryption()) {
qCWarning(lcCseMetadata()) << "e2ee metadata are missing proper information about the certificate used to encrypt them";
return;
}

const auto metadataKeyFromJson = metadataObj[metadataKeyJsonKey].toString().toLocal8Bit();
if (!metadataKeyFromJson.isEmpty()) {
Expand Down Expand Up @@ -2295,19 +2325,6 @@ void FolderMetadata::setupExistingMetadata(const QByteArray& metadata)
const auto files = metaDataDoc.object()["files"].toObject();
const auto metadataKey = metaDataDoc.object()["metadata"].toObject()["metadataKey"].toString().toUtf8();
const auto metadataKeyChecksum = metaDataDoc.object()["metadata"].toObject()["checksum"].toString().toUtf8();
_metadataCertificateSha256Fingerprint = metadataObj[certificateSha256FingerprintKey].toString().toLatin1();

if (!_metadataCertificateSha256Fingerprint.isEmpty()) {
if (!_account->e2e()->useTokenBasedEncryption()) {
qCWarning(lcCseMetadata()) << "e2ee metadata are missing proper information about the certificate used to encrypt them";
return;
}

if (_metadataCertificateSha256Fingerprint != _account->e2e()->usbTokenInformation()->sha256Fingerprint()) {
qCInfo(lcCseMetadata()) << "migration of the certificate used to encrypt metadata is needed";
return;
}
}

_fileDrop = metaDataDoc.object().value("filedrop").toObject();
// for unit tests
Expand Down Expand Up @@ -2391,7 +2408,7 @@ void FolderMetadata::setupExistingMetadata(const QByteArray& metadata)
// RSA/ECB/OAEPWithSHA-256AndMGF1Padding using private / public key.
std::optional<QByteArray> FolderMetadata::encryptData(const QByteArray& binaryDatadata) const
{
const auto encryptBase64Result = EncryptionHelper::encryptStringAsymmetric(*_account->e2e(), binaryDatadata);
const auto encryptBase64Result = EncryptionHelper::encryptStringAsymmetric(_account->e2e()->getTokenCertificate(), *_account->e2e(), binaryDatadata);

if (!encryptBase64Result || encryptBase64Result->isEmpty())
{
Expand All @@ -2404,7 +2421,7 @@ std::optional<QByteArray> FolderMetadata::encryptData(const QByteArray& binaryDa

std::optional<QByteArray> FolderMetadata::decryptData(const QByteArray &base64Data) const
{
const auto decryptBase64Result = EncryptionHelper::decryptStringAsymmetric(certificateSha256Fingerprint(), *_account->e2e(), base64Data);
const auto decryptBase64Result = EncryptionHelper::decryptStringAsymmetric(_account->e2e()->getTokenCertificateByFingerprint(_metadataCertificateSha256Fingerprint), *_account->e2e(), base64Data, _metadataCertificateSha256Fingerprint);

if (!decryptBase64Result || decryptBase64Result->isEmpty())
{
Expand Down
10 changes: 7 additions & 3 deletions src/libsync/clientsideencryption.h
Original file line number Diff line number Diff line change
Expand Up @@ -121,12 +121,14 @@ OWNCLOUDSYNC_EXPORT QByteArray decryptStringSymmetric(
const QByteArray& data
);

[[nodiscard]] OWNCLOUDSYNC_EXPORT std::optional<QByteArray> encryptStringAsymmetric(const ClientSideEncryption &encryptionEngine,
[[nodiscard]] OWNCLOUDSYNC_EXPORT std::optional<QByteArray> encryptStringAsymmetric(const CertificateInformation &selectedCertificate,
const ClientSideEncryption &encryptionEngine,
const QByteArray &binaryData);

[[nodiscard]] OWNCLOUDSYNC_EXPORT std::optional<QByteArray> decryptStringAsymmetric(const QByteArray &certificateSha256Fingerprint,
[[nodiscard]] OWNCLOUDSYNC_EXPORT std::optional<QByteArray> decryptStringAsymmetric(const CertificateInformation &selectedCertificate,
const ClientSideEncryption &encryptionEngine,
const QByteArray &base64Data);
const QByteArray &base64Data,
const QByteArray &expectedCertificateSha256Fingerprint);

QByteArray privateKeyToPem(const QByteArray key);

Expand Down Expand Up @@ -208,6 +210,8 @@ class OWNCLOUDSYNC_EXPORT ClientSideEncryption : public QObject {

[[nodiscard]] const CertificateInformation& getTokenCertificate() const;

[[nodiscard]] CertificateInformation getTokenCertificateByFingerprint(const QByteArray &expectedFingerprint) const;

[[nodiscard]] bool useTokenBasedEncryption() const;

[[nodiscard]] const QString &getMnemonic() const;
Expand Down
4 changes: 2 additions & 2 deletions src/libsync/clientsideencryptionjobs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,7 @@ void LockEncryptFolderApiJob::start()

if (!folderTokenEncrypted.isEmpty()) {
qCInfo(lcCseJob()) << "lock folder started for:" << path() << " for fileId: " << _fileId << " but we need to first lift the previous lock";
const auto folderToken = EncryptionHelper::decryptStringAsymmetric(_certificateSha256Fingerprint, *_account->e2e(), folderTokenEncrypted);
const auto folderToken = EncryptionHelper::decryptStringAsymmetric(_account->e2e()->getTokenCertificate(), *_account->e2e(), folderTokenEncrypted, _certificateSha256Fingerprint);
if (!folderToken) {
qCWarning(lcCseJob()) << "decrypt failed";
return;
Expand Down Expand Up @@ -300,7 +300,7 @@ bool LockEncryptFolderApiJob::finished()
qCInfo(lcCseJob()) << "lock folder finished with code" << retCode << " for:" << path() << " for fileId: " << _fileId << " token:" << token;

if (!_account->e2e()->getPublicKey().isNull()) {
const auto folderTokenEncrypted = EncryptionHelper::encryptStringAsymmetric(*_account->e2e(), token);
const auto folderTokenEncrypted = EncryptionHelper::encryptStringAsymmetric(_account->e2e()->getTokenCertificate(), *_account->e2e(), token);
if (!folderTokenEncrypted) {
qCWarning(lcCseJob()) << "decrypt failed";
return false;
Expand Down
2 changes: 1 addition & 1 deletion src/libsync/syncengine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -508,7 +508,7 @@ void SyncEngine::startSync()
for (const auto &e2EeLockedFolder : e2EeLockedFolders) {
const auto folderId = e2EeLockedFolder.first;
qCInfo(lcEngine()) << "start unlock job for folderId:" << folderId;
const auto folderToken = EncryptionHelper::decryptStringAsymmetric(_account->e2e()->certificateSha256Fingerprint(), *_account->e2e(), e2EeLockedFolder.second);
const auto folderToken = EncryptionHelper::decryptStringAsymmetric(_account->e2e()->getTokenCertificate(), *_account->e2e(), e2EeLockedFolder.second, _account->e2e()->certificateSha256Fingerprint());
if (!folderToken) {
qCWarning(lcEngine()) << "decrypt failed";
return;
Expand Down

0 comments on commit 35c5dde

Please sign in to comment.