diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f7742071..b6b7a53d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -143,10 +143,8 @@ jobs: runs-on: ${{ matrix.image }} strategy: matrix: - vcver: [142, 143] + vcver: [143] include: - - vcver: 142 - image: windows-2019 - vcver: 143 image: windows-2022 env: diff --git a/client/CDoc1.cpp b/client/CDoc1.cpp index 78596eda..d99e495d 100644 --- a/client/CDoc1.cpp +++ b/client/CDoc1.cpp @@ -68,6 +68,16 @@ const QHash CDoc1::SHA_MTH{ }; const QHash CDoc1::KWAES_SIZE{{KWAES128_MTH, 16}, {KWAES192_MTH, 24}, {KWAES256_MTH, 32}}; +const QByteArray XML_TAG = ""; + +bool CDoc1::isCDoc1File(const QString& path) +{ + + if(QFile f(path); f.open(QFile::ReadOnly)) + return f.read(XML_TAG.length()) == XML_TAG; + return false; +} + CDoc1::CDoc1(const QString &path) : QFile(path) { @@ -107,9 +117,10 @@ CDoc1::CDoc1(const QString &path) if(xml.name() != QLatin1String("EncryptedKey")) return; - CKey key; - key.id = xml.attributes().value(QLatin1String("Id")).toString(); - key.recipient = xml.attributes().value(QLatin1String("Recipient")).toString(); + std::shared_ptr key = std::make_shared(); + // Id is never used + //key->id = xml.attributes().value(QLatin1String("Id")).toString(); + key->label = xml.attributes().value(QLatin1String("Recipient")).toString(); while(!xml.atEnd()) { xml.readNext(); @@ -118,50 +129,57 @@ CDoc1::CDoc1(const QString &path) if(!xml.isStartElement()) continue; // EncryptedData/KeyInfo/KeyName - if(xml.name() == QLatin1String("KeyName")) - key.name = xml.readElementText(); + // Name is never used + //if(xml.name() == QLatin1String("KeyName")) + // key->name = xml.readElementText(); // EncryptedData/KeyInfo/EncryptedKey/EncryptionMethod - else if(xml.name() == QLatin1String("EncryptionMethod")) - key.method = xml.attributes().value(QLatin1String("Algorithm")).toString(); + if(xml.name() == QLatin1String("EncryptionMethod")) + key->method = xml.attributes().value(QLatin1String("Algorithm")).toString(); // EncryptedData/KeyInfo/EncryptedKey/KeyInfo/AgreementMethod - else if(xml.name() == QLatin1String("AgreementMethod")) - key.agreement = xml.attributes().value(QLatin1String("Algorithm")).toString(); - // EncryptedData/KeyInfo/EncryptedKey/KeyInfo/AgreementMethod/KeyDerivationMethod - else if(xml.name() == QLatin1String("KeyDerivationMethod")) - key.derive = xml.attributes().value(QLatin1String("Algorithm")).toString(); - // EncryptedData/KeyInfo/EncryptedKey/KeyInfo/AgreementMethod/KeyDerivationMethod/ConcatKDFParams - else if(xml.name() == QLatin1String("ConcatKDFParams")) + else if(xml.name() == QLatin1String("AgreementMethod")) { + // fixme: handle error + if (xml.attributes().value(QLatin1String("Algorithm")).toString() != AGREEMENT_MTH) + return; + //key->agreement = xml.attributes().value(QLatin1String("Algorithm")).toString(); + // EncryptedData/KeyInfo/EncryptedKey/KeyInfo/AgreementMethod/KeyDerivationMethod + } else if(xml.name() == QLatin1String("KeyDerivationMethod")) { + // fixme: handle error + if (xml.attributes().value(QLatin1String("Algorithm")).toString() != CONCATKDF_MTH) + return; + // key->derive = xml.attributes().value(QLatin1String("Algorithm")).toString(); + // EncryptedData/KeyInfo/EncryptedKey/KeyInfo/AgreementMethod/KeyDerivationMethod/ConcatKDFParams + } else if(xml.name() == QLatin1String("ConcatKDFParams")) { - key.AlgorithmID = QByteArray::fromHex(xml.attributes().value(QLatin1String("AlgorithmID")).toUtf8()); - if(key.AlgorithmID.front() == char(0x00)) key.AlgorithmID.remove(0, 1); - key.PartyUInfo = QByteArray::fromHex(xml.attributes().value(QLatin1String("PartyUInfo")).toUtf8()); - if(key.PartyUInfo.front() == char(0x00)) key.PartyUInfo.remove(0, 1); - key.PartyVInfo = QByteArray::fromHex(xml.attributes().value(QLatin1String("PartyVInfo")).toUtf8()); - if(key.PartyVInfo.front() == char(0x00)) key.PartyVInfo.remove(0, 1); + key->AlgorithmID = QByteArray::fromHex(xml.attributes().value(QLatin1String("AlgorithmID")).toUtf8()); + if(key->AlgorithmID.front() == char(0x00)) key->AlgorithmID.remove(0, 1); + key->PartyUInfo = QByteArray::fromHex(xml.attributes().value(QLatin1String("PartyUInfo")).toUtf8()); + if(key->PartyUInfo.front() == char(0x00)) key->PartyUInfo.remove(0, 1); + key->PartyVInfo = QByteArray::fromHex(xml.attributes().value(QLatin1String("PartyVInfo")).toUtf8()); + if(key->PartyVInfo.front() == char(0x00)) key->PartyVInfo.remove(0, 1); } // EncryptedData/KeyInfo/EncryptedKey/KeyInfo/AgreementMethod/KeyDerivationMethod/ConcatKDFParams/DigestMethod else if(xml.name() == QLatin1String("DigestMethod")) - key.concatDigest = xml.attributes().value(QLatin1String("Algorithm")).toString(); + key->concatDigest = xml.attributes().value(QLatin1String("Algorithm")).toString(); // EncryptedData/KeyInfo/EncryptedKey/KeyInfo/AgreementMethod/OriginatorKeyInfo/KeyValue/ECKeyValue/PublicKey else if(xml.name() == QLatin1String("PublicKey")) { xml.readNext(); - key.publicKey = fromBase64(xml.text()); + key->publicKey = fromBase64(xml.text()); } // EncryptedData/KeyInfo/EncryptedKey/KeyInfo/X509Data/X509Certificate else if(xml.name() == QLatin1String("X509Certificate")) { xml.readNext(); - key.setCert(QSslCertificate(fromBase64(xml.text()), QSsl::Der)); + key->setCert(QSslCertificate(fromBase64(xml.text()), QSsl::Der)); } // EncryptedData/KeyInfo/EncryptedKey/KeyInfo/CipherData/CipherValue else if(xml.name() == QLatin1String("CipherValue")) { xml.readNext(); - key.cipher = fromBase64(xml.text()); + key->encrypted_fmk = fromBase64(xml.text()); } } - keys.append(std::move(key)); + keys.append(key); }); if(!keys.isEmpty()) setLastError({}); @@ -178,6 +196,15 @@ CDoc1::CDoc1(const QString &path) } } +std::unique_ptr +CDoc1::load(const QString& path) +{ + auto cdoc = std::unique_ptr(new CDoc1(path)); + if (cdoc->keys.isEmpty()) + cdoc.reset(); + return cdoc; +} + bool CDoc1::decryptPayload(const QByteArray &key) { if(!isOpen()) @@ -267,22 +294,32 @@ bool CDoc1::decryptPayload(const QByteArray &key) return !files.empty(); } -CKey CDoc1::canDecrypt(const QSslCertificate &cert) const +CKey::DecryptionStatus +CDoc1::canDecrypt(const QSslCertificate &cert) const { - for(const CKey &k: qAsConst(keys)) + if(getDecryptionKey(cert)) + return CKey::DecryptionStatus::CAN_DECRYPT; + return CKey::DecryptionStatus::CANNOT_DECRYPT; +} + +std::shared_ptr CDoc1::getDecryptionKey(const QSslCertificate &cert) const +{ + for(std::shared_ptr key: qAsConst(keys)) { + if (key->type != CKey::Type::CDOC1) continue; + std::shared_ptr k = std::static_pointer_cast(key); if(!ENC_MTH.contains(method) || - k.cert != cert || - k.cipher.isEmpty()) + k->cert != cert || + k->encrypted_fmk.isEmpty()) continue; if(cert.publicKey().algorithm() == QSsl::Rsa && - k.method == RSA_MTH) + k->method == RSA_MTH) return k; if(cert.publicKey().algorithm() == QSsl::Ec && - !k.publicKey.isEmpty() && - KWAES_SIZE.contains(k.method) && - k.derive == CONCATKDF_MTH && - k.agreement == AGREEMENT_MTH) + !k->publicKey.isEmpty() && + KWAES_SIZE.contains(k->method) /* && + k->derive == CONCATKDF_MTH && + k->agreement == AGREEMENT_MTH*/ ) return k; } return {}; @@ -425,37 +462,42 @@ bool CDoc1::save(const QString &path) if(!mime.isEmpty()) w.writeAttribute(QStringLiteral("MimeType"), mime); writeElement(w, DENC, QStringLiteral("EncryptionMethod"), { - {QStringLiteral("Algorithm"), method}, - }); + {QStringLiteral("Algorithm"), method}, + }); w.writeNamespace(DS, QStringLiteral("ds")); writeElement(w, DS, QStringLiteral("KeyInfo"), [&]{ - for(const CKey &k: qAsConst(keys)) + for(std::shared_ptr key: qAsConst(keys)) { + // Only certificate-based keys can be used in CDoc1 + if (key->type != CKey::Type::CERTIFICATE) return; + std::shared_ptr ckey = std::static_pointer_cast(key); writeElement(w, DENC, QStringLiteral("EncryptedKey"), [&]{ - if(!k.id.isEmpty()) - w.writeAttribute(QStringLiteral("Id"), k.id); - if(!k.recipient.isEmpty()) - w.writeAttribute(QStringLiteral("Recipient"), k.recipient); + // Id is never used + //if(!k->id.isEmpty()) + // w.writeAttribute(QStringLiteral("Id"), k->id); + if(!ckey->label.isEmpty()) + w.writeAttribute(QStringLiteral("Recipient"), ckey->label); QByteArray cipher; - if(k.isRSA) + if(ckey->pk_type == CKey::PKType::RSA) { - cipher = Crypto::encrypt(X509_get0_pubkey((const X509*)k.cert.handle()), RSA_PKCS1_PADDING, transportKey); + cipher = Crypto::encrypt(X509_get0_pubkey((const X509*)ckey->cert.handle()), RSA_PKCS1_PADDING, transportKey); if(cipher.isEmpty()) return; writeElement(w, DENC, QStringLiteral("EncryptionMethod"), { - {QStringLiteral("Algorithm"), RSA_MTH}, - }); + {QStringLiteral("Algorithm"), RSA_MTH}, + }); writeElement(w, DS, QStringLiteral("KeyInfo"), [&]{ - if(!k.name.isEmpty()) - w.writeTextElement(DS, QStringLiteral("KeyName"), k.name); + // Name is never used + //if(!k->name.isEmpty()) + // w.writeTextElement(DS, QStringLiteral("KeyName"), k->name); writeElement(w, DS, QStringLiteral("X509Data"), [&]{ - writeBase64Element(w, DS, QStringLiteral("X509Certificate"), k.cert.toDer()); + writeBase64Element(w, DS, QStringLiteral("X509Certificate"), ckey->cert.toDer()); }); }); } else { - EVP_PKEY *peerPKey = X509_get0_pubkey((const X509*)k.cert.handle()); + EVP_PKEY *peerPKey = X509_get0_pubkey((const X509*)ckey->cert.handle()); auto priv = Crypto::genECKey(peerPKey); QByteArray sharedSecret = Crypto::derive(priv.get(), peerPKey); if(sharedSecret.isEmpty()) @@ -472,14 +514,14 @@ bool CDoc1::save(const QString &path) default: concatDigest = SHA512_MTH; break; } QByteArray encryptionKey = Crypto::concatKDF(SHA_MTH[concatDigest], KWAES_SIZE[encryptionMethod], - sharedSecret, props.value(QStringLiteral("DocumentFormat")).toUtf8() + SsDer + k.cert.toDer()); + sharedSecret, props.value(QStringLiteral("DocumentFormat")).toUtf8() + SsDer + ckey->cert.toDer()); #ifndef NDEBUG qDebug() << "ENC Ss" << SsDer.toHex(); qDebug() << "ENC Ksr" << sharedSecret.toHex(); qDebug() << "ENC ConcatKDF" << encryptionKey.toHex(); #endif - cipher = Crypto::aes_wrap(encryptionKey, transportKey, true); + cipher = Crypto::aes_wrap(encryptionKey, transportKey); if(cipher.isEmpty()) return; @@ -497,7 +539,7 @@ bool CDoc1::save(const QString &path) writeElement(w, XENC11, QStringLiteral("ConcatKDFParams"), { {QStringLiteral("AlgorithmID"), QStringLiteral("00") + props.value(QStringLiteral("DocumentFormat")).toUtf8().toHex()}, {QStringLiteral("PartyUInfo"), QStringLiteral("00") + SsDer.toHex()}, - {QStringLiteral("PartyVInfo"), QStringLiteral("00") + k.cert.toDer().toHex()}, + {QStringLiteral("PartyVInfo"), QStringLiteral("00") + ckey->cert.toDer().toHex()}, }, [&]{ writeElement(w, DS, QStringLiteral("DigestMethod"), { {QStringLiteral("Algorithm"), concatDigest}, @@ -517,7 +559,7 @@ bool CDoc1::save(const QString &path) }); writeElement(w, DENC, QStringLiteral("RecipientKeyInfo"), [&]{ writeElement(w, DS, QStringLiteral("X509Data"), [&]{ - writeBase64Element(w, DS, QStringLiteral("X509Certificate"), k.cert.toDer()); + writeBase64Element(w, DS, QStringLiteral("X509Certificate"), ckey->cert.toDer()); }); }); }); @@ -528,6 +570,7 @@ bool CDoc1::save(const QString &path) }); }); }}); + // This is actual content, for some weird reason named cipherData/cipherValue writeElement(w,DENC, QStringLiteral("CipherData"), [&]{ writeBase64Element(w, DENC, QStringLiteral("CipherValue"), Crypto::cipher(ENC_MTH[method], transportKey, data.buffer(), true) @@ -546,27 +589,32 @@ bool CDoc1::save(const QString &path) return true; } -QByteArray CDoc1::transportKey(const CKey &key) +QByteArray CDoc1::getFMK(const CKey &key, const QByteArray& /*secret*/) { + if (key.type != CKey::Type::CDOC1) { + setLastError(QStringLiteral("Not a CDoc1 key")); + return {}; + } + const auto& ckey = static_cast(key); setLastError({}); - QByteArray decryptedKey = qApp->signer()->decrypt([&key](QCryptoBackend *backend) { - if(key.isRSA) - return backend->decrypt(key.cipher, false); - return backend->deriveConcatKDF(key.publicKey, SHA_MTH[key.concatDigest], - int(KWAES_SIZE[key.method]), key.AlgorithmID, key.PartyUInfo, key.PartyVInfo); + QByteArray decryptedKey = qApp->signer()->decrypt([&ckey](QCryptoBackend *backend) { + if(ckey.pk_type == CKey::PKType::RSA) + return backend->decrypt(ckey.encrypted_fmk, false); + return backend->deriveConcatKDF(ckey.publicKey, SHA_MTH[ckey.concatDigest], + int(KWAES_SIZE[ckey.method]), ckey.AlgorithmID, ckey.PartyUInfo, ckey.PartyVInfo); }); if(decryptedKey.isEmpty()) { setLastError(QStringLiteral("Failed to decrypt/derive key")); return {}; } - if(key.isRSA) + if(ckey.pk_type == CKey::PKType::RSA) return decryptedKey; #ifndef NDEBUG - qDebug() << "DEC Ss" << key.publicKey.toHex(); + qDebug() << "DEC Ss" << ckey.publicKey.toHex(); qDebug() << "DEC ConcatKDF" << decryptedKey.toHex(); #endif - return Crypto::aes_wrap(decryptedKey, key.cipher, false); + return Crypto::aes_unwrap(decryptedKey, ckey.encrypted_fmk); } int CDoc1::version() diff --git a/client/CDoc1.h b/client/CDoc1.h index 82d333c1..8b7a7e73 100644 --- a/client/CDoc1.h +++ b/client/CDoc1.h @@ -33,14 +33,20 @@ class CDoc1 final: public CDoc, private QFile { public: CDoc1() = default; - CDoc1(const QString &path); - CKey canDecrypt(const QSslCertificate &cert) const final; + + static bool isCDoc1File(const QString& path); + + CKey::DecryptionStatus canDecrypt(const QSslCertificate &cert) const final; + std::shared_ptr getDecryptionKey(const QSslCertificate &cert) const final; bool decryptPayload(const QByteArray &key) final; bool save(const QString &path) final; - QByteArray transportKey(const CKey &key) final; + QByteArray getFMK(const CKey &key, const QByteArray& secret) final; int version() final; + static std::unique_ptr load(const QString& path); private: + CDoc1(const QString &path); + void writeDDoc(QIODevice *ddoc); static QByteArray fromBase64(QStringView data); diff --git a/client/CDoc2.cpp b/client/CDoc2.cpp index f0645e70..7b7d310c 100644 --- a/client/CDoc2.cpp +++ b/client/CDoc2.cpp @@ -29,6 +29,7 @@ #include "Utils.h" #include "header_generated.h" #include "effects/FadeInNotification.h" +#include "openssl/kdf.h" #include #include @@ -39,11 +40,17 @@ #include #include #include +#include #include #include +using cdoc20::recipients::KeyServerCapsule; +using cdoc20::recipients::KeyDetailsUnion; +using cdoc20::recipients::EccKeyDetails; +using cdoc20::recipients::RsaKeyDetails; + const QByteArray CDoc2::LABEL = "CDOC\x02"; const QByteArray CDoc2::CEK = "CDOC20cek"; const QByteArray CDoc2::HMAC = "CDOC20hmac"; @@ -392,24 +399,36 @@ namespace cdoc20 { const int TAR::Header::Size = int(sizeof(TAR::Header)); } -CDoc2::CDoc2(const QString &path) - : QFile(path) +bool +CDoc2::isCDoc2File(const QString& path) +{ + QFile f(path); + if(!f.open(QFile::ReadOnly)) return false; + if (f.read(LABEL.length()) != LABEL) return false; + return true; +} + +CDoc2::CDoc2(QString _path) + : path(std::move(_path)) + /* : QFile(path) */ { - using namespace cdoc20::Recipients; - using namespace cdoc20::Header; + using namespace cdoc20::recipients; + using namespace cdoc20::header; setLastError(QStringLiteral("Invalid CDoc 2.0 header")); uint32_t header_len = 0; - if(!open(QFile::ReadOnly) || - read(LABEL.length()) != LABEL || - read((char*)&header_len, int(sizeof(header_len))) != int(sizeof(header_len))) + QFile ifs(path); + if(!ifs.open(QFile::ReadOnly) || + ifs.read(LABEL.length()) != LABEL || + ifs.read((char*)&header_len, int(sizeof(header_len))) != int(sizeof(header_len))) return; - header_data = read(qFromBigEndian(header_len)); + header_data = ifs.read(qFromBigEndian(header_len)); if(header_data.size() != qFromBigEndian(header_len)) return; - headerHMAC = read(KEY_LEN); + headerHMAC = ifs.read(KEY_LEN); if(headerHMAC.size() != KEY_LEN) return; - noncePos = pos(); + noncePos = ifs.pos(); + ifs.close(); flatbuffers::Verifier verifier(reinterpret_cast(header_data.data()), header_data.size()); if(!VerifyHeaderBuffer(verifier)) return; @@ -435,10 +454,10 @@ CDoc2::CDoc2(const QString &path) qWarning() << "Unsupported FMK encryption method: skipping"; continue; } - auto fillRecipient = [&] (auto key, bool isRSA) { - CKey k(toByteArray(key->recipient_public_key()), isRSA); - k.recipient = toString(recipient->key_label()); - k.cipher = toByteArray(recipient->encrypted_fmk()); + auto fillRecipientPK = [&] (CKey::PKType pk_type, auto key) { + std::shared_ptr k(new CKeyPublicKey(pk_type, toByteArray(key->recipient_public_key()))); + k->label = toString(recipient->key_label()); + k->encrypted_fmk = toByteArray(recipient->encrypted_fmk()); return k; }; switch(recipient->capsule_type()) @@ -451,44 +470,83 @@ CDoc2::CDoc2(const QString &path) qWarning() << "Unsupported ECC curve: skipping"; continue; } - CKey k = fillRecipient(key, false); - k.publicKey = toByteArray(key->sender_public_key()); - keys.append(std::move(k)); + std::shared_ptr k = fillRecipientPK(CKey::PKType::ECC, key); + k->key_material = toByteArray(key->sender_public_key()); + qDebug() << "Load PK:" << k->rcpt_key.toHex(); + keys.append(k); } break; case Capsule::RSAPublicKeyCapsule: if(const auto *key = recipient->capsule_as_RSAPublicKeyCapsule()) { - CKey k = fillRecipient(key, true); - k.encrypted_kek = toByteArray(key->encrypted_kek()); - keys.append(std::move(k)); + std::shared_ptr k = fillRecipientPK(CKey::PKType::RSA, key); + k->key_material = toByteArray(key->encrypted_kek()); + keys.append(k); } break; case Capsule::KeyServerCapsule: - if(const auto *server = recipient->capsule_as_KeyServerCapsule()) - { - auto fillKeyServer = [&] (auto key, bool isRSA) { - CKey k = fillRecipient(key, isRSA); - k.keyserver_id = toString(server->keyserver_id()); - k.transaction_id = toString(server->transaction_id()); - return k; - }; - switch(server->recipient_key_details_type()) - { - case ServerDetailsUnion::ServerEccDetails: - if(const auto *eccDetails = server->recipient_key_details_as_ServerEccDetails()) - { - if(eccDetails->curve() == EllipticCurve::secp384r1) - keys.append(fillKeyServer(eccDetails, false)); + if (const KeyServerCapsule *server = recipient->capsule_as_KeyServerCapsule()) { + KeyDetailsUnion details = server->recipient_key_details_type(); + std::shared_ptr ckey = nullptr; + switch (details) { + case KeyDetailsUnion::EccKeyDetails: + if(const EccKeyDetails *eccDetails = server->recipient_key_details_as_EccKeyDetails()) { + if(eccDetails->curve() == EllipticCurve::secp384r1) { + ckey = CKeyServer::fromKey(toByteArray(eccDetails->recipient_public_key()), CKey::PKType::ECC); + } else { + qWarning() << "Unsupported elliptic curve key type"; + } + } else { + qWarning() << "Invalid file format"; } break; - case ServerDetailsUnion::ServerRsaDetails: - if(const auto *rsaDetails = server->recipient_key_details_as_ServerRsaDetails()) - keys.append(fillKeyServer(rsaDetails, true)); + case KeyDetailsUnion::RsaKeyDetails: + if(const RsaKeyDetails *rsaDetails = server->recipient_key_details_as_RsaKeyDetails()) { + ckey = CKeyServer::fromKey(toByteArray(rsaDetails->recipient_public_key()), CKey::PKType::RSA); + } else { + qWarning() << "Invalid file format"; + } break; default: qWarning() << "Unsupported Key Server Details: skipping"; } + if (ckey) { + ckey->label = toString(recipient->key_label()); + ckey->encrypted_fmk = toByteArray(recipient->encrypted_fmk()); + ckey->keyserver_id = toString(server->keyserver_id()); + ckey->transaction_id = toString(server->transaction_id()); + keys.append(ckey); + } + } else { + qWarning() << "Invalid file format"; + } + break; + case Capsule::SymmetricKeyCapsule: + if(const auto *capsule = recipient->capsule_as_SymmetricKeyCapsule()) + { + auto salt = capsule->salt(); + std::shared_ptr key(new CKeySymmetric(toByteArray(salt))); + key->label = toString(recipient->key_label()); + key->encrypted_fmk = toByteArray(recipient->encrypted_fmk()); + keys.append(key); + } + break; + case Capsule::PBKDF2Capsule: + if(const auto *capsule = recipient->capsule_as_PBKDF2Capsule()) { + KDFAlgorithmIdentifier kdf_id = capsule->kdf_algorithm_identifier(); + if (kdf_id != KDFAlgorithmIdentifier::PBKDF2WithHmacSHA256) { + qWarning() << "Unsupported KDF algorithm: skipping"; + continue; + } + auto salt = capsule->salt(); + auto pw_salt = capsule->password_salt(); + int32_t kdf_iter = capsule->kdf_iterations(); + std::shared_ptr key(new CKeySymmetric(toByteArray(salt))); + key->label = toString(recipient->key_label()); + key->encrypted_fmk = toByteArray(recipient->encrypted_fmk()); + key->pw_salt = toByteArray(pw_salt); + key->kdf_iter = kdf_iter; + keys.append(key); } break; default: @@ -497,19 +555,43 @@ CDoc2::CDoc2(const QString &path) } } -CKey CDoc2::canDecrypt(const QSslCertificate &cert) const +std::unique_ptr +CDoc2::load(const QString& path) +{ + auto *cdoc = new CDoc2(path); + return std::unique_ptr(cdoc); +} + +CKey::DecryptionStatus +CDoc2::canDecrypt(const QSslCertificate &cert) const +{ + CKey::DecryptionStatus status = CKey::DecryptionStatus::CANNOT_DECRYPT; + for (const std::shared_ptr &key: keys) { + if (key->isTheSameRecipient(cert)) return CKey::CAN_DECRYPT; + if (key->isSymmetric()) status = CKey::DecryptionStatus::NEED_KEY; + } + return status; +} + +std::shared_ptr CDoc2::getDecryptionKey(const QSslCertificate &cert) const { - return keys.value(keys.indexOf(CKey(cert))); + std::shared_ptr best = {}; + for (std::shared_ptr key: keys) { + if (key->isTheSameRecipient(cert)) return key; + if (key->isSymmetric()) best = key; + } + return best; } bool CDoc2::decryptPayload(const QByteArray &fmk) { - if(!isOpen() || noncePos == -1) - return false; + QFile ifs(path); + if(!ifs.open(QFile::ReadOnly)) return false; + if (noncePos < 0) return false; setLastError({}); - seek(noncePos); + ifs.seek(noncePos); QByteArray cek = Crypto::expand(fmk, CEK); - QByteArray nonce = read(NONCE_LEN); + QByteArray nonce = ifs.read(NONCE_LEN); #ifndef NDEBUG qDebug() << "cek" << cek.toHex(); qDebug() << "nonce" << nonce.toHex(); @@ -518,10 +600,11 @@ bool CDoc2::decryptPayload(const QByteArray &fmk) if(!dec.updateAAD(PAYLOAD + header_data + headerHMAC)) return false; bool warning = false; - files = cdoc20::TAR(std::unique_ptr(new cdoc20::stream(this, &dec))).files(warning); + files = cdoc20::TAR(std::unique_ptr(new cdoc20::stream(&ifs, &dec))).files(warning); if(warning) setLastError(tr("CDoc contains additional payload data that is not part of content")); - QByteArray tag = read(16); + QByteArray tag = ifs.read(16); + ifs.close(); #ifndef NDEBUG qDebug() << "tag" << tag.toHex(); #endif @@ -531,7 +614,7 @@ bool CDoc2::decryptPayload(const QByteArray &fmk) return !files.empty(); } -bool CDoc2::save(const QString &path) +bool CDoc2::save(const QString &_path) { setLastError({}); QByteArray fmk = Crypto::extract(Crypto::random(KEY_LEN), SALT); @@ -544,23 +627,30 @@ bool CDoc2::save(const QString &path) #endif flatbuffers::FlatBufferBuilder builder; - std::vector> recipients; + std::vector> recipients; + auto toVector = [&builder](const QByteArray &data) { return builder.CreateVector((const uint8_t*)data.data(), size_t(data.length())); }; + auto toString = [&builder](const QString &data) { QByteArray utf8 = data.toUtf8(); return builder.CreateString(utf8.data(), size_t(utf8.length())); }; - auto sendToServer = [this](CKey &key, const QByteArray &recipient_id, const QByteArray &key_material, QLatin1String type) { - key.keyserver_id = Settings::CDOC2_DEFAULT_KEYSERVER; - if(key.keyserver_id.isEmpty()) - return setLastError(QStringLiteral("keyserver_id cannot be empty")); - QNetworkRequest req = cdoc20::req(key.keyserver_id); - if(req.url().isEmpty()) - return setLastError(QStringLiteral("No valid config found for keyserver_id: %1").arg(key.keyserver_id)); - if(!cdoc20::checkConnection()) - return false; + + auto sendToServer = [this] (const QString& keyserver_id, const QByteArray &recipient_id, const QByteArray &key_material, QLatin1String type) -> QString { + if(keyserver_id.isEmpty()) { + setLastError(QStringLiteral("keyserver_id cannot be empty")); + return {}; + } + QNetworkRequest req = cdoc20::req(keyserver_id); + if(req.url().isEmpty()) { + setLastError(QStringLiteral("No valid config found for keyserver_id: %1").arg(keyserver_id)); + return {}; + } + if(!cdoc20::checkConnection()) { + return {}; + } QScopedPointer nam(CheckConnection::setupNAM(req, Settings::CDOC2_POST_CERT)); QEventLoop e; QNetworkReply *reply = nam->post(req, QJsonDocument({ @@ -570,96 +660,209 @@ bool CDoc2::save(const QString &path) }).toJson()); connect(reply, &QNetworkReply::finished, &e, &QEventLoop::quit); e.exec(); + QString transaction_id; if(reply->error() == QNetworkReply::NoError && - reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() == 201) - key.transaction_id = QString::fromLatin1(reply->rawHeader("Location")).remove(QLatin1String("/key-capsules/")); - else - return setLastError(reply->errorString()); - if(key.transaction_id.isEmpty()) - return setLastError(QStringLiteral("Failed to post key capsule")); - return true; + reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() == 201) { + transaction_id = QString::fromLatin1(reply->rawHeader("Location")).remove(QLatin1String("/key-capsules/")); + } else { + setLastError(reply->errorString()); + return {}; + } + if(transaction_id.isEmpty()) + setLastError(QStringLiteral("Failed to post key capsule")); + return transaction_id; }; - for(CKey &key: keys) - { - if(key.isRSA) - { + for(const std::shared_ptr &key: keys) { + if (!key->isPKI()) { + return setLastError(QStringLiteral("Invalid key type")); + } + const auto& pki = static_cast(*key); + if(pki.pk_type == CKey::PKType::RSA) { QByteArray kek = Crypto::random(fmk.size()); QByteArray xor_key = Crypto::xor_data(fmk, kek); - auto publicKey = Crypto::fromRSAPublicKeyDer(key.key); + auto publicKey = Crypto::fromRSAPublicKeyDer(pki.rcpt_key); if(!publicKey) return false; QByteArray encrytpedKek = Crypto::encrypt(publicKey.get(), RSA_PKCS1_OAEP_PADDING, kek); #ifndef NDEBUG - qDebug() << "publicKeyDer" << key.key.toHex(); + qDebug() << "publicKeyDer" << pki.rcpt_key.toHex(); qDebug() << "kek" << kek.toHex(); qDebug() << "xor" << xor_key.toHex(); qDebug() << "encrytpedKek" << encrytpedKek.toHex(); #endif - if(!Settings::CDOC2_USE_KEYSERVER) - { - auto rsaPublicKey = cdoc20::Recipients::CreateRSAPublicKeyCapsule(builder, - toVector(key.key), toVector(encrytpedKek)); - recipients.push_back(cdoc20::Header::CreateRecipientRecord(builder, - cdoc20::Recipients::Capsule::RSAPublicKeyCapsule, rsaPublicKey.Union(), - toString(key.recipient), toVector(xor_key), cdoc20::Header::FMKEncryptionMethod::XOR)); - continue; + if(!Settings::CDOC2_USE_KEYSERVER) { + auto rsaPublicKey = cdoc20::recipients::CreateRSAPublicKeyCapsule(builder, + toVector(pki.rcpt_key), toVector(encrytpedKek)); + auto offs = cdoc20::header::CreateRecipientRecord(builder, + cdoc20::recipients::Capsule::RSAPublicKeyCapsule, rsaPublicKey.Union(), + toString(pki.label), toVector(xor_key), cdoc20::header::FMKEncryptionMethod::XOR); + recipients.push_back(offs); + } else { + QString keyserver_id = Settings::CDOC2_DEFAULT_KEYSERVER; + QString transaction_id = sendToServer(keyserver_id, pki.rcpt_key, encrytpedKek, QLatin1String("rsa")); + if (transaction_id.isEmpty()) return false; + auto rsaKeyServer = cdoc20::recipients::CreateRsaKeyDetails(builder, toVector(pki.rcpt_key)); + auto keyServer = cdoc20::recipients::CreateKeyServerCapsule(builder, + cdoc20::recipients::KeyDetailsUnion::RsaKeyDetails, + rsaKeyServer.Union(), toString(keyserver_id), toString(transaction_id)); + auto offs = cdoc20::header::CreateRecipientRecord(builder, + cdoc20::recipients::Capsule::KeyServerCapsule, keyServer.Union(), + toString(pki.label), toVector(xor_key), cdoc20::header::FMKEncryptionMethod::XOR); + recipients.push_back(offs); + } + } else { + auto publicKey = Crypto::fromECPublicKeyDer(pki.rcpt_key, NID_secp384r1); + if(!publicKey) return false; + auto ephKey = Crypto::genECKey(publicKey.get()); + QByteArray sharedSecret = Crypto::derive(ephKey.get(), publicKey.get()); + QByteArray ephPublicKeyDer = Crypto::toPublicKeyDer(ephKey.get()); + QByteArray kekPm = Crypto::extract(sharedSecret, KEKPREMASTER); + QByteArray info = KEK + cdoc20::header::EnumNameFMKEncryptionMethod(cdoc20::header::FMKEncryptionMethod::XOR) + pki.rcpt_key + ephPublicKeyDer; + QByteArray kek = Crypto::expand(kekPm, info, fmk.size()); + QByteArray xor_key = Crypto::xor_data(fmk, kek); +#ifndef NDEBUG + qDebug() << "publicKeyDer" << pki.rcpt_key.toHex(); + qDebug() << "ephPublicKeyDer" << ephPublicKeyDer.toHex(); + qDebug() << "sharedSecret" << sharedSecret.toHex(); + qDebug() << "kekPm" << kekPm.toHex(); + qDebug() << "kek" << kek.toHex(); + qDebug() << "xor" << xor_key.toHex(); +#endif + if(!Settings::CDOC2_USE_KEYSERVER) { + auto eccPublicKey = cdoc20::recipients::CreateECCPublicKeyCapsule(builder, + cdoc20::recipients::EllipticCurve::secp384r1, toVector(pki.rcpt_key), toVector(ephPublicKeyDer)); + auto offs = cdoc20::header::CreateRecipientRecord(builder, + cdoc20::recipients::Capsule::ECCPublicKeyCapsule, eccPublicKey.Union(), + toString(pki.label), toVector(xor_key), cdoc20::header::FMKEncryptionMethod::XOR); + recipients.push_back(offs); + } else { + QString keyserver_id = Settings::CDOC2_DEFAULT_KEYSERVER; + QString transaction_id = sendToServer(keyserver_id, pki.rcpt_key, ephPublicKeyDer, QLatin1String("ecc_secp384r1")); + if (transaction_id.isEmpty()) return false; + auto eccKeyServer = cdoc20::recipients::CreateEccKeyDetails(builder, + cdoc20::recipients::EllipticCurve::secp384r1, toVector(pki.rcpt_key)); + auto keyServer = cdoc20::recipients::CreateKeyServerCapsule(builder, + cdoc20::recipients::KeyDetailsUnion::EccKeyDetails, + eccKeyServer.Union(), toString(keyserver_id), toString(transaction_id)); + auto offs = cdoc20::header::CreateRecipientRecord(builder, + cdoc20::recipients::Capsule::KeyServerCapsule, keyServer.Union(), + toString(pki.label), toVector(xor_key), cdoc20::header::FMKEncryptionMethod::XOR); + recipients.push_back(offs); } - - if(!sendToServer(key, key.key, encrytpedKek, QLatin1String("rsa"))) - return false; - auto rsaKeyServer = cdoc20::Recipients::CreateServerRsaDetails(builder, toVector(key.key)); - auto keyServer = cdoc20::Recipients::CreateKeyServerCapsule(builder, - cdoc20::Recipients::ServerDetailsUnion::ServerRsaDetails, - rsaKeyServer.Union(), toString(key.keyserver_id), toString(key.transaction_id)); - recipients.push_back(cdoc20::Header::CreateRecipientRecord(builder, - cdoc20::Recipients::Capsule::KeyServerCapsule, keyServer.Union(), - toString(key.recipient), toVector(xor_key), cdoc20::Header::FMKEncryptionMethod::XOR)); - continue; } + } - auto publicKey = Crypto::fromECPublicKeyDer(key.key, NID_secp384r1); - if(!publicKey) - return false; - auto ephKey = Crypto::genECKey(publicKey.get()); - QByteArray sharedSecret = Crypto::derive(ephKey.get(), publicKey.get()); - QByteArray ephPublicKeyDer = Crypto::toPublicKeyDer(ephKey.get()); - QByteArray kekPm = Crypto::extract(sharedSecret, KEKPREMASTER); - QByteArray info = KEK + cdoc20::Header::EnumNameFMKEncryptionMethod(cdoc20::Header::FMKEncryptionMethod::XOR) + key.key + ephPublicKeyDer; - QByteArray kek = Crypto::expand(kekPm, info, fmk.size()); - QByteArray xor_key = Crypto::xor_data(fmk, kek); + auto offset = cdoc20::header::CreateHeader(builder, builder.CreateVector(recipients), + cdoc20::header::PayloadEncryptionMethod::CHACHA20POLY1305); + builder.Finish(offset); + + QByteArray header = QByteArray::fromRawData((const char*)builder.GetBufferPointer(), int(builder.GetSize())); + QByteArray headerHMAC = Crypto::sign_hmac(hhk, header); + QByteArray nonce = Crypto::random(NONCE_LEN); #ifndef NDEBUG - qDebug() << "publicKeyDer" << key.key.toHex(); - qDebug() << "ephPublicKeyDer" << ephPublicKeyDer.toHex(); - qDebug() << "sharedSecret" << sharedSecret.toHex(); - qDebug() << "kekPm" << kekPm.toHex(); - qDebug() << "kek" << kek.toHex(); - qDebug() << "xor" << xor_key.toHex(); + qDebug() << "hmac" << headerHMAC.toHex(); + qDebug() << "nonce" << nonce.toHex(); #endif - if(!Settings::CDOC2_USE_KEYSERVER) - { - auto eccPublicKey = cdoc20::Recipients::CreateECCPublicKeyCapsule(builder, - cdoc20::Recipients::EllipticCurve::secp384r1, toVector(key.key), toVector(ephPublicKeyDer)); - recipients.push_back(cdoc20::Header::CreateRecipientRecord(builder, - cdoc20::Recipients::Capsule::ECCPublicKeyCapsule, eccPublicKey.Union(), - toString(key.recipient), toVector(xor_key), cdoc20::Header::FMKEncryptionMethod::XOR)); - continue; - } + Crypto::Cipher enc(EVP_chacha20_poly1305(), cek, nonce, true); + enc.updateAAD(PAYLOAD + header + headerHMAC); + auto header_len = qToBigEndian(uint32_t(header.size())); - if(!sendToServer(key, key.key, ephPublicKeyDer, QLatin1String("ecc_secp384r1"))) - return false; - auto eccKeyServer = cdoc20::Recipients::CreateServerEccDetails(builder, - cdoc20::Recipients::EllipticCurve::secp384r1, toVector(key.key)); - auto keyServer = cdoc20::Recipients::CreateKeyServerCapsule(builder, - cdoc20::Recipients::ServerDetailsUnion::ServerEccDetails, - eccKeyServer.Union(), toString(key.keyserver_id), toString(key.transaction_id)); - recipients.push_back(cdoc20::Header::CreateRecipientRecord(builder, - cdoc20::Recipients::Capsule::KeyServerCapsule, keyServer.Union(), - toString(key.recipient), toVector(xor_key), cdoc20::Header::FMKEncryptionMethod::XOR)); + /* Use TmpFile wrapper so the original will not be lost during error */ + QSaveFile tmp(_path); + tmp.setDirectWriteFallback(true); + if (!tmp.open(QFile::WriteOnly)) { + setLastError(tmp.errorString()); + return false; + } + /* Write contents to temporary */ + tmp.write(LABEL); + tmp.write((const char*)&header_len, qint64(sizeof(header_len))); + tmp.write(header); + tmp.write(headerHMAC); + tmp.write(nonce); + if(!cdoc20::TAR(std::unique_ptr(new cdoc20::stream(&tmp, &enc))).save(files)) { + return false; + } + if(!enc.result()) { + return false; + } + QByteArray tag = enc.tag(); +#ifndef NDEBUG + qDebug() << "tag" << tag.toHex(); +#endif + tmp.write(tag); + tmp.commit(); + this->path = _path; + return true; +} + +bool +CDoc2::save(const QString& _path, const std::vector& files, const QString& label, const QByteArray& secret, unsigned int kdf_iter) +{ + QByteArray fmk = Crypto::extract(Crypto::random(KEY_LEN), SALT); + QByteArray cek = Crypto::expand(fmk, CEK); + QByteArray hhk = Crypto::expand(fmk, HMAC); +#ifndef NDEBUG + qDebug() << "fmk" << fmk.toHex(); + qDebug() << "cek" << cek.toHex(); + qDebug() << "hhk" << hhk.toHex(); +#endif + + flatbuffers::FlatBufferBuilder builder; + std::vector> recipients; + + auto toVector = [&builder](const QByteArray &data) { + return builder.CreateVector((const uint8_t*)data.data(), size_t(data.length())); + }; + + auto toString = [&builder](const QString &data) { + QByteArray utf8 = data.toUtf8(); + return builder.CreateString(utf8.data(), size_t(utf8.length())); + }; + + if (kdf_iter > 0) { + // PasswordSalt_i = CSRNG() + QByteArray pw_salt = Crypto::random(); + // PasswordKeyMaterial_i = PBKDF2(Password_i, PasswordSalt_i) + QByteArray key_material = Crypto::pbkdf2_sha256(secret, pw_salt, kdf_iter); +#ifndef NDEBUG + qDebug() << "Key material: " << key_material.toHex(); +#endif \ + // KeyMaterialSalt_i = CSRNG() + QByteArray salt = Crypto::random(); + // KEK_i = HKDF(KeyMaterialSalt_i, PasswordKeyMaterial_i) + QByteArray info = KEK + cdoc20::header::EnumNameFMKEncryptionMethod(cdoc20::header::FMKEncryptionMethod::XOR) + secret; + QByteArray tmp = Crypto::extract(key_material, salt, 32); + QByteArray kek = Crypto::expand(tmp, info, 32); + + QByteArray xor_key = Crypto::xor_data(fmk, kek); + + auto capsule = cdoc20::recipients::CreatePBKDF2Capsule(builder, toVector(salt), toVector(pw_salt), cdoc20::recipients::KDFAlgorithmIdentifier::PBKDF2WithHmacSHA256, kdf_iter); + auto offs = cdoc20::header::CreateRecipientRecord(builder, + cdoc20::recipients::Capsule::PBKDF2Capsule, capsule.Union(), + toString(label), toVector(xor_key), cdoc20::header::FMKEncryptionMethod::XOR); + recipients.push_back(offs); + } else { + // KeyMaterialSalt_i = CSRNG() + QByteArray salt = Crypto::random(); + // KeyMaterialSalt_i = CSRNG() + // KEK_i = HKDF(KeyMaterialSalt_i, S_i) + QByteArray info = KEK + cdoc20::header::EnumNameFMKEncryptionMethod(cdoc20::header::FMKEncryptionMethod::XOR) + label.toUtf8(); + QByteArray tmp = Crypto::extract(secret, salt, 32); + QByteArray kek = Crypto::expand(tmp, info, 32); + + QByteArray xor_key = Crypto::xor_data(fmk, kek); + + auto capsule = cdoc20::recipients::CreateSymmetricKeyCapsule(builder, toVector(salt)); + auto offs = cdoc20::header::CreateRecipientRecord(builder, + cdoc20::recipients::Capsule::SymmetricKeyCapsule, capsule.Union(), + toString(label), toVector(xor_key), cdoc20::header::FMKEncryptionMethod::XOR); + recipients.push_back(offs); } - auto offset = cdoc20::Header::CreateHeader(builder, builder.CreateVector(recipients), - cdoc20::Header::PayloadEncryptionMethod::CHACHA20POLY1305); + auto offset = cdoc20::header::CreateHeader(builder, builder.CreateVector(recipients), + cdoc20::header::PayloadEncryptionMethod::CHACHA20POLY1305); builder.Finish(offset); QByteArray header = QByteArray::fromRawData((const char*)builder.GetBufferPointer(), int(builder.GetSize())); @@ -672,102 +875,144 @@ bool CDoc2::save(const QString &path) Crypto::Cipher enc(EVP_chacha20_poly1305(), cek, nonce, true); enc.updateAAD(PAYLOAD + header + headerHMAC); auto header_len = qToBigEndian(uint32_t(header.size())); - remove(); - QFile file(path); - if(!file.open(QFile::WriteOnly)) - return setLastError(file.errorString()); - file.write(LABEL); - file.write((const char*)&header_len, int(sizeof(header_len))); - file.write(header); - file.write(headerHMAC); - file.write(nonce); - if(!cdoc20::TAR(std::unique_ptr(new cdoc20::stream(&file, &enc))).save(files)) - { - file.remove(); + + /* Use TmpFile wrapper so the original will not be lost during error */ + QSaveFile tmp(_path); + tmp.setDirectWriteFallback(true); + if (!tmp.open(QFile::WriteOnly)) { return false; } - if(!enc.result()) - { - file.remove(); + /* Write contents to temporary */ + tmp.write(LABEL); + tmp.write((const char*)&header_len, qint64(sizeof(header_len))); + tmp.write(header); + tmp.write(headerHMAC); + tmp.write(nonce); + if(!cdoc20::TAR(std::unique_ptr(new cdoc20::stream(&tmp, &enc))).save(files)) { + return false; + } + if(!enc.result()) { return false; } QByteArray tag = enc.tag(); #ifndef NDEBUG qDebug() << "tag" << tag.toHex(); #endif - file.write(tag); + tmp.write(tag); + tmp.commit(); return true; } -QByteArray CDoc2::transportKey(const CKey &_key) + +QByteArray CDoc2::fetchKeyMaterial(const CKeyServer& key) +{ + QNetworkRequest req = cdoc20::req(key.keyserver_id, key.transaction_id); + if(req.url().isEmpty()) { + setLastError(QStringLiteral("No valid config found for keyserver_id: %1").arg(key.keyserver_id)); + return {}; + } + if(!cdoc20::checkConnection()) { + return {}; + } + auto authKey = dispatchToMain(&QSigner::key, qApp->signer()); + QScopedPointer nam( + CheckConnection::setupNAM(req, qApp->signer()->tokenauth().cert(), authKey, Settings::CDOC2_GET_CERT)); + QEventLoop e; + QNetworkReply *reply = nam->get(req); + connect(reply, &QNetworkReply::finished, &e, &QEventLoop::quit); + e.exec(); + if(authKey.handle()) { + qApp->signer()->logout(); + } + if(reply->error() != QNetworkReply::NoError && reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() != 201) { + setLastError(reply->errorString()); + return {}; + } + QJsonObject json = QJsonDocument::fromJson(reply->readAll()).object(); + QByteArray key_material = QByteArray::fromBase64(json.value(QLatin1String("ephemeral_key_material")).toString().toLatin1()); + return std::move(key_material); +} + +QByteArray CDoc2::getFMK(const CKey &key, const QByteArray& secret) { setLastError({}); - CKey key = _key; - if(!key.transaction_id.isEmpty()) - { - QNetworkRequest req = cdoc20::req(key.keyserver_id, key.transaction_id); - if(req.url().isEmpty()) - { - setLastError(QStringLiteral("No valid config found for keyserver_id: %1").arg(key.keyserver_id)); - return {}; + QByteArray kek; + if (key.isSymmetric()) { + // Symmetric key + const auto &sk = static_cast(key); + if (sk.kdf_iter > 0) { +#ifndef NDEBUG + qDebug() << "Password based symmetric key: " << key.label; +#endif + // KEY_MATERIAL = PBKDF2(PASSWORD, PASSWORD_SALT) + QByteArray key_material = Crypto::pbkdf2_sha256(secret, sk.pw_salt, sk.kdf_iter); +#ifndef NDEBUG + qDebug() << "Key material: " << key_material.toHex(); +#endif + // KEK = HKDF(SALT, KEY_MATERIAL) + QByteArray info = KEK + cdoc20::header::EnumNameFMKEncryptionMethod(cdoc20::header::FMKEncryptionMethod::XOR) + secret; + QByteArray tmp = Crypto::extract(key_material, sk.salt, 32); + kek = Crypto::expand(tmp, info, 32); + } else { +#ifndef NDEBUG + qDebug() << "Plain symmetric key: " << key.label; +#endif + QByteArray info = KEK + cdoc20::header::EnumNameFMKEncryptionMethod(cdoc20::header::FMKEncryptionMethod::XOR) + sk.label.toUtf8(); + QByteArray tmp = Crypto::extract(secret, sk.salt, 32); + kek = Crypto::expand(tmp, info, 32); } - if(!cdoc20::checkConnection()) - return {}; - auto authKey = dispatchToMain(&QSigner::key, qApp->signer()); - QScopedPointer nam( - CheckConnection::setupNAM(req, qApp->signer()->tokenauth().cert(), authKey, Settings::CDOC2_GET_CERT)); - QEventLoop e; - QNetworkReply *reply = nam->get(req); - connect(reply, &QNetworkReply::finished, &e, &QEventLoop::quit); - e.exec(); - if(authKey.handle()) - qApp->signer()->logout(); - if(reply->error() != QNetworkReply::NoError && - reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() != 201) - { - setLastError(reply->errorString()); - return {}; + } else { + // Public/private key + const auto &pki = static_cast(key); + QByteArray key_material; + if(key.type == CKey::Type::SERVER) { + const auto &sk = static_cast(key); + key_material = fetchKeyMaterial(sk); + } else if (key.type == CKey::PUBLIC_KEY) { + const auto& pk = static_cast(key); + key_material = pk.key_material; } - QJsonObject json = QJsonDocument::fromJson(reply->readAll()).object(); - QByteArray key_material = QByteArray::fromBase64( - json.value(QLatin1String("ephemeral_key_material")).toString().toLatin1()); - if(json.value(QLatin1String("capsule_type")) == QLatin1String("rsa")) - key.encrypted_kek = std::move(key_material); - else - key.publicKey = std::move(key_material); - } #ifndef NDEBUG - qDebug() << "publicKeyDer" << key.key.toHex(); - qDebug() << "ephPublicKeyDer" << key.publicKey.toHex(); + qDebug() << "Public key" << pki.rcpt_key.toHex(); + qDebug() << "Key material" << key_material.toHex(); #endif - QByteArray kek = qApp->signer()->decrypt([&key](QCryptoBackend *backend) { - if(key.isRSA) - return backend->decrypt(key.encrypted_kek, true); - QByteArray kekPm = backend->deriveHMACExtract(key.publicKey, KEKPREMASTER, KEY_LEN); + if (pki.pk_type == CKey::PKType::RSA) { + kek = qApp->signer()->decrypt([&key_material](QCryptoBackend *backend) { + return backend->decrypt(key_material, true); + }); + } else { + kek = qApp->signer()->decrypt([&pki, &key_material](QCryptoBackend *backend) { + QByteArray kekPm = backend->deriveHMACExtract(key_material, KEKPREMASTER, KEY_LEN); #ifndef NDEBUG - qDebug() << "kekPm" << kekPm.toHex(); + qDebug() << "kekPm" << kekPm.toHex(); #endif - QByteArray info = KEK + cdoc20::Header::EnumNameFMKEncryptionMethod(cdoc20::Header::FMKEncryptionMethod::XOR) + key.key + key.publicKey; - return Crypto::expand(kekPm, info, KEY_LEN); - }); - if(kek.isEmpty()) - { + QByteArray info = KEK + cdoc20::header::EnumNameFMKEncryptionMethod(cdoc20::header::FMKEncryptionMethod::XOR) + pki.rcpt_key + key_material; + return Crypto::expand(kekPm, info, KEY_LEN); + }); + } + } +#ifndef NDEBUG + qDebug() << "KEK: " << kek.toHex(); +#endif + + if(kek.isEmpty()) { setLastError(QStringLiteral("Failed to derive key")); return {}; } - QByteArray fmk = Crypto::xor_data(key.cipher, kek); + QByteArray fmk = Crypto::xor_data(key.encrypted_fmk, kek); QByteArray hhk = Crypto::expand(fmk, HMAC); #ifndef NDEBUG qDebug() << "kek" << kek.toHex(); - qDebug() << "xor" << key.cipher.toHex(); + qDebug() << "xor" << key.encrypted_fmk.toHex(); qDebug() << "fmk" << fmk.toHex(); qDebug() << "hhk" << hhk.toHex(); qDebug() << "hmac" << headerHMAC.toHex(); #endif - if(Crypto::sign_hmac(hhk, header_data) == headerHMAC) - return fmk; - setLastError(QStringLiteral("CDoc 2.0 hash mismatch")); - return {}; + if(Crypto::sign_hmac(hhk, header_data) != headerHMAC) { + setLastError(QStringLiteral("CDoc 2.0 hash mismatch")); + return {}; + } + return fmk; } int CDoc2::version() diff --git a/client/CDoc2.h b/client/CDoc2.h index 54bcf58d..5213c200 100644 --- a/client/CDoc2.h +++ b/client/CDoc2.h @@ -23,23 +23,35 @@ #include -class CDoc2 final: public CDoc, private QFile { +class CDoc2 final: public CDoc, private QObject /*, private QFile */ { public: explicit CDoc2() = default; - explicit CDoc2(const QString &path); - CKey canDecrypt(const QSslCertificate &cert) const final; + static bool isCDoc2File(const QString& path); + + CKey::DecryptionStatus canDecrypt(const QSslCertificate &cert) const final; + std::shared_ptr getDecryptionKey(const QSslCertificate &cert) const final; bool decryptPayload(const QByteArray &fmk) final; QByteArray deriveFMK(const QByteArray &priv, const CKey &key); - bool isSupported(); + bool save(const QString &path) final; - QByteArray transportKey(const CKey &key) final; + // Write payload encrypted with sinbgle symmetric key + static bool save(const QString& path, const std::vector& files, const QString& label, const QByteArray& secret, unsigned int kdf_iter); + + QByteArray getFMK(const CKey &key, const QByteArray& secret) final; int version() final; + static std::unique_ptr load(const QString& _path); + private: + CDoc2(QString path); + + QString path; QByteArray header_data, headerHMAC; qint64 noncePos = -1; static const QByteArray LABEL, CEK, HMAC, KEK, KEKPREMASTER, PAYLOAD, SALT; static constexpr int KEY_LEN = 32, NONCE_LEN = 12; + + QByteArray fetchKeyMaterial(const CKeyServer& key); }; diff --git a/client/Crypto.cpp b/client/Crypto.cpp index 607e961e..92784abf 100644 --- a/client/Crypto.cpp +++ b/client/Crypto.cpp @@ -93,9 +93,17 @@ bool Crypto::Cipher::setTag(const QByteArray &data) const return !isError(EVP_CIPHER_CTX_ctrl(ctx.get(), EVP_CTRL_AEAD_SET_TAG, int(data.size()), const_cast(data.data()))); } -QByteArray Crypto::aes_wrap(const QByteArray &key, const QByteArray &data, bool encrypt) +QByteArray Crypto::aes_wrap(const QByteArray &key, const QByteArray &data) { - Cipher c(key.size() == 32 ? EVP_aes_256_wrap() : EVP_aes_128_wrap(), key, {}, encrypt); + Cipher c(key.size() == 32 ? EVP_aes_256_wrap() : EVP_aes_128_wrap(), key, {}, true); + if(QByteArray result = c.update(data); c.result()) + return result; + return {}; +} + +QByteArray Crypto::aes_unwrap(const QByteArray &key, const QByteArray &data) +{ + Cipher c(key.size() == 32 ? EVP_aes_256_wrap() : EVP_aes_128_wrap(), key, {}, false); if(QByteArray result = c.update(data); c.result()) return result; return {}; @@ -372,3 +380,14 @@ QByteArray Crypto::xor_data(const QByteArray &a, const QByteArray &b) result[i] = char(a[i] ^ b[i]); return result; } + +QByteArray +Crypto::pbkdf2_sha256(const QByteArray& pw, const QByteArray& salt, uint32_t iter) +{ + QByteArray key(32, 0); + PKCS5_PBKDF2_HMAC(pw.data(), pw.length(), + (const unsigned char *) salt.data(), int(salt.length()), + iter, EVP_sha256(), int(key.size()), (unsigned char *)key.data()); + return key; +} + diff --git a/client/Crypto.h b/client/Crypto.h index 58e8972b..94f1635e 100644 --- a/client/Crypto.h +++ b/client/Crypto.h @@ -50,7 +50,8 @@ class Crypto bool setTag(const QByteArray &data) const; }; - static QByteArray aes_wrap(const QByteArray &key, const QByteArray &data, bool encrypt); + static QByteArray aes_wrap(const QByteArray &key, const QByteArray &data); + static QByteArray aes_unwrap(const QByteArray &key, const QByteArray &data); static QByteArray cipher(const EVP_CIPHER *cipher, const QByteArray &key, QByteArray &data, bool encrypt); static QByteArray curve_oid(EVP_PKEY *key); static QByteArray concatKDF(QCryptographicHash::Algorithm digestMethod, @@ -71,6 +72,7 @@ class Crypto static QByteArray random(int len = 32); static QByteArray xor_data(const QByteArray &a, const QByteArray &b); + static QByteArray pbkdf2_sha256(const QByteArray& pw, const QByteArray& salt, uint32_t iter); private: static bool isError(int err); }; diff --git a/client/CryptoDoc.cpp b/client/CryptoDoc.cpp index 2d04f1f2..fcba866f 100644 --- a/client/CryptoDoc.cpp +++ b/client/CryptoDoc.cpp @@ -60,10 +60,15 @@ class CryptoDoc::Private final: public QThread std::unique_ptr cdoc; QString fileName; - QByteArray key; bool isEncrypted = false; CDocumentModel *documents = new CDocumentModel(this); QStringList tempFiles; + // Decryption data + QByteArray fmk; + // Encryption data + QString label; + QByteArray secret; + uint32_t kdf_iter; }; bool CryptoDoc::Private::isEncryptedWarning() const @@ -80,12 +85,16 @@ void CryptoDoc::Private::run() if(isEncrypted) { qCDebug(CRYPTO) << "Decrypt" << fileName; - isEncrypted = !cdoc->decryptPayload(key); + isEncrypted = !cdoc->decryptPayload(fmk); } else { qCDebug(CRYPTO) << "Encrypt" << fileName; - isEncrypted = cdoc->save(fileName); + if (secret.isEmpty()) { + isEncrypted = cdoc->save(fileName); + } else { + isEncrypted = CDoc2::save(fileName, cdoc->files, label, secret, kdf_iter); + } } } @@ -217,10 +226,41 @@ QString CDocumentModel::save(int row, const QString &path) const return fileName; } -CKey::CKey(const QSslCertificate &c) +bool +CKey::isTheSameRecipient(const CKey& other) const +{ + QByteArray this_key, other_key; + if (this->isCertificate()) { + const auto& ckc = static_cast(*this); + this_key = ckc.cert.publicKey().toDer(); + } + if (other.isCertificate()) { + const auto& ckc = static_cast(other); + other_key = ckc.cert.publicKey().toDer(); + } + if (this_key.isEmpty() || other_key.isEmpty()) return false; + return this_key == other_key; +} + +bool +CKey::isTheSameRecipient(const QSslCertificate &cert) const +{ + if (!isPKI()) return false; + const auto& pki = static_cast(*this); + QByteArray this_key = pki.rcpt_key; + QSslKey k = cert.publicKey(); + QByteArray other_key = Crypto::toPublicKeyDer(k); + qDebug() << "This key:" << this_key.toHex(); + qDebug() << "Other key:" << other_key.toHex(); + if (this_key.isEmpty() || other_key.isEmpty()) return false; + return this_key == other_key; +} + +CKeyCert::CKeyCert(Type _type, const QSslCertificate &c) + : CKeyPKI(_type) { setCert(c); - recipient = [](const SslCertificate &c) { + label = [](const SslCertificate &c) { QString cn = c.subjectInfo(QSslCertificate::CommonName); QString gn = c.subjectInfo("GN"); QString sn = c.subjectInfo("SN"); @@ -238,14 +278,19 @@ CKey::CKey(const QSslCertificate &c) }(c); } -void CKey::setCert(const QSslCertificate &c) +void CKeyCert::setCert(const QSslCertificate &c) { - QSslKey k = c.publicKey(); cert = c; - key = Crypto::toPublicKeyDer(k); - isRSA = k.algorithm() == QSsl::Rsa; + QSslKey k = c.publicKey(); + rcpt_key = Crypto::toPublicKeyDer(k); + qDebug() << "Set cert PK:" << rcpt_key.toHex(); + pk_type = (k.algorithm() == QSsl::Rsa) ? PKType::RSA : PKType::ECC; } +std::shared_ptr +CKeyServer::fromKey(const QByteArray &_key, PKType _pk_type) { + return std::shared_ptr(new CKeyServer(_key, _pk_type)); +} CryptoDoc::CryptoDoc( QObject *parent ) : QObject(parent) @@ -257,14 +302,21 @@ CryptoDoc::CryptoDoc( QObject *parent ) CryptoDoc::~CryptoDoc() { clear(); delete d; } -bool CryptoDoc::addKey( const CKey &key ) +bool +CryptoDoc::supportsSymmetricKeys() const +{ + return d->cdoc->version() >= 2; +} + +bool CryptoDoc::addKey(const std::shared_ptr &key) { if(d->isEncryptedWarning()) return false; - if(d->cdoc->keys.contains(key)) - { - WarningDialog::show(tr("Key already exists")); - return false; + for (const std::shared_ptr &k: d->cdoc->keys) { + if (k->isTheSameRecipient(*key)) { + WarningDialog::show(tr("Key already exists")); + return false; + } } d->cdoc->keys.append(key); return true; @@ -272,7 +324,8 @@ bool CryptoDoc::addKey( const CKey &key ) bool CryptoDoc::canDecrypt(const QSslCertificate &cert) { - return !d->cdoc->canDecrypt(cert).key.isEmpty(); + CKey::DecryptionStatus dec_stat = d->cdoc->canDecrypt(cert); + return (dec_stat == CKey::CAN_DECRYPT) || (dec_stat == CKey::DecryptionStatus::NEED_KEY); } void CryptoDoc::clear( const QString &file ) @@ -297,7 +350,7 @@ ContainerState CryptoDoc::state() const return d->isEncrypted ? EncryptedContainer : UnencryptedContainer; } -bool CryptoDoc::decrypt() +bool CryptoDoc::decrypt(std::shared_ptr key, const QByteArray& secret) { if( d->fileName.isEmpty() ) { @@ -307,14 +360,15 @@ bool CryptoDoc::decrypt() if(!d->isEncrypted) return true; - CKey key = d->cdoc->canDecrypt(qApp->signer()->tokenauth().cert()); - if(key.key.isEmpty()) - { + if (key == nullptr) { + key = d->cdoc->getDecryptionKey(qApp->signer()->tokenauth().cert()); + } + if((key == nullptr) || (key->isSymmetric() && secret.isEmpty())) { WarningDialog::show(tr("You do not have the key to decrypt this document")); return false; } - if(d->cdoc->version() == 2 && !key.transaction_id.isEmpty() && !Settings::CDOC2_NOTIFICATION.isSet()) + if(d->cdoc->version() == 2 && (key->type == CKey::Type::SERVER) && !Settings::CDOC2_NOTIFICATION.isSet()) { auto *dlg = new WarningDialog(tr("You must enter your PIN code twice in order to decrypt the CDOC2 container. " "The first PIN entry is required for authentication to the key server referenced in the CDOC2 container. " @@ -332,12 +386,11 @@ bool CryptoDoc::decrypt() } } - d->key = d->cdoc->transportKey(key); + d->fmk = d->cdoc->getFMK(*key, secret); #ifndef NDEBUG - qDebug() << "Transport key" << d->key.toHex(); + qDebug() << "FMK (Transport key)" << d->fmk.toHex(); #endif - if(d->key.isEmpty()) - { + if(d->fmk.isEmpty()) { WarningDialog::show(tr("Failed to decrypt document. Please check your internet connection and network settings."), d->cdoc->lastError); return false; } @@ -353,24 +406,31 @@ bool CryptoDoc::decrypt() DocumentModel* CryptoDoc::documentModel() const { return d->documents; } -bool CryptoDoc::encrypt( const QString &filename ) +bool CryptoDoc::encrypt( const QString &filename, const QString& label, const QByteArray& secret, uint32_t kdf_iter) { - if( !filename.isEmpty() ) - d->fileName = filename; - if( d->fileName.isEmpty() ) - { + if(!filename.isEmpty()) d->fileName = filename; + if(d->fileName.isEmpty()) { WarningDialog::show(tr("Container is not open")); return false; } - if(d->isEncrypted) - return true; - if(d->cdoc->keys.isEmpty()) - { - WarningDialog::show(tr("No keys specified")); - return false; + // I think the correct semantics is to fail if container is already encrypted + if(d->isEncrypted) return false; + if (secret.isEmpty()) { + // Encrypt for address list + if(d->cdoc->keys.isEmpty()) + { + WarningDialog::show(tr("No keys specified")); + return false; + } + } else { + // Encrypt with symmetric key + d->label = label; + d->secret = secret; + d->kdf_iter = kdf_iter; } - d->waitForFinished(); + d->label.clear(); + d->secret.clear(); if(d->isEncrypted) open(d->fileName); else @@ -380,7 +440,7 @@ bool CryptoDoc::encrypt( const QString &filename ) QString CryptoDoc::fileName() const { return d->fileName; } -QList CryptoDoc::keys() const +QList> CryptoDoc::keys() const { return d->cdoc->keys; } @@ -399,11 +459,17 @@ bool CryptoDoc::move(const QString &to) bool CryptoDoc::open( const QString &file ) { clear(file); - d->cdoc = std::make_unique(d->fileName); - if(d->cdoc->keys.isEmpty()) - d->cdoc = std::make_unique(d->fileName); + if (CDoc2::isCDoc2File(d->fileName)) { + d->cdoc = CDoc2::load(d->fileName); + } else if (CDoc1::isCDoc1File(d->fileName)) { + d->cdoc = CDoc1::load(d->fileName); + } else { + WarningDialog::show(tr("Failed to open document"), tr("Unsupported file format")); + return false; + } d->isEncrypted = bool(d->cdoc); - if(!d->isEncrypted || d->cdoc->keys.isEmpty()) + // fixme: This seems wrong + if(!d->isEncrypted) { WarningDialog::show(tr("Failed to open document"), d->cdoc->lastError); return false; diff --git a/client/CryptoDoc.h b/client/CryptoDoc.h index 8929550e..9a834405 100644 --- a/client/CryptoDoc.h +++ b/client/CryptoDoc.h @@ -29,29 +29,132 @@ class QSslKey; -class CKey +struct CKey { public: - CKey() = default; - CKey(QByteArray _key, bool _isRSA): key(std::move(_key)), isRSA(_isRSA) {} - CKey(const QSslCertificate &cert); - bool operator==(const CKey &other) const { return other.key == key; } + enum Type { + SYMMETRIC_KEY, + PUBLIC_KEY, + CERTIFICATE, + CDOC1, + SERVER + }; - void setCert(const QSslCertificate &c); + enum PKType { + ECC, + RSA + }; + + enum DecryptionStatus { + CANNOT_DECRYPT, + CAN_DECRYPT, + NEED_KEY + }; + + Type type; + QString label; + + // Decryption data + QByteArray encrypted_fmk; + + // Recipients public key + // QByteArray key; + + bool isSymmetric() const { return type == Type::SYMMETRIC_KEY; } + bool isPKI() const { return (type == Type::CERTIFICATE) || (type == Type::CDOC1) || (type == Type::PUBLIC_KEY) || (type == Type::SERVER); } + bool isCertificate() const { return (type == Type::CERTIFICATE) || (type == Type::CDOC1); } + bool isCDoc1() const { return type == Type::CDOC1; } + + bool isTheSameRecipient(const CKey &key) const; + bool isTheSameRecipient(const QSslCertificate &cert) const; + +protected: + CKey(Type _type) : type(_type) {} +private: + bool operator==(const CKey &other) const { return false; } +}; + +// Symmetric key (plain or PBKDF) +// Usage: +// CDoc2:encrypt/decrypt LT + +struct CKeySymmetric : public CKey { + QByteArray salt; + // PBKDF + QByteArray pw_salt; + // 0 symmetric key, >0 password + int32_t kdf_iter{}; + + CKeySymmetric(QByteArray _salt) : CKey(Type::SYMMETRIC_KEY), salt(std::move(_salt)) {} +}; + +// Base PKI key +// Usage: +// CDoc2:encrypt + +struct CKeyPKI : public CKey { + // Recipient's public key + PKType pk_type; + QByteArray rcpt_key; + +protected: + CKeyPKI(Type _type) : CKey(_type), pk_type(PKType::ECC) {} + CKeyPKI(Type _type, PKType _pk_type, QByteArray _rcpt_key) : CKey(_type), pk_type(_pk_type), rcpt_key(std::move(_rcpt_key)) {} +}; + + +// Public key with additonal information +// Usage: +// CDoc1:encrypt - QByteArray key, cipher, publicKey; +struct CKeyCert : public CKeyPKI { QSslCertificate cert; - bool isRSA = false; - QString recipient; - // CDoc1 - QString agreement, concatDigest, derive, method, id, name; + + CKeyCert(const QSslCertificate &cert) : CKeyCert(CKey::Type::CERTIFICATE, cert) {} + + void setCert(const QSslCertificate &c); + +protected: + CKeyCert(Type _type) : CKeyPKI(_type) {} + CKeyCert(Type _type, const QSslCertificate &cert); +}; + +// CDoc1 decryption key (with additional information from file) +// Usage: +// CDoc1:decrypt + +struct CKeyCDoc1 : public CKeyCert { + + QByteArray publicKey; + QString concatDigest, method; QByteArray AlgorithmID, PartyUInfo, PartyVInfo; - // CDoc2 - QByteArray encrypted_kek; - QString keyserver_id, transaction_id; + + CKeyCDoc1() : CKeyCert(Type::CDOC1) {} +}; + +// CDoc2 PKI key with key material +// Usage: +// CDoc2: decrypt + +struct CKeyPublicKey : public CKeyPKI { + // Either ECC public key or RSA encrypted kek + QByteArray key_material; + + CKeyPublicKey(PKType _pk_type, QByteArray _rcpt_key) : CKeyPKI(Type::PUBLIC_KEY, _pk_type, std::move(_rcpt_key)) {} }; +// CDoc2 PKI key with server info +// Usage: +// CDoc2: decrypt +struct CKeyServer : public CKeyPKI { + // Server info + QString keyserver_id, transaction_id; + + static std::shared_ptr fromKey(const QByteArray &_key, PKType _pk_type); +protected: + CKeyServer(QByteArray _rcpt_key, PKType _pk_type) : CKeyPKI(Type::SERVER, _pk_type, std::move(_rcpt_key)) {} +}; class CDoc { @@ -64,14 +167,16 @@ class CDoc }; virtual ~CDoc() = default; - virtual CKey canDecrypt(const QSslCertificate &cert) const = 0; - virtual bool decryptPayload(const QByteArray &key) = 0; + // Return affirmative if keys match or NEED_KEY if document includes symmetric key */ + virtual CKey::DecryptionStatus canDecrypt(const QSslCertificate &cert) const = 0; + virtual std::shared_ptr getDecryptionKey(const QSslCertificate &cert) const = 0; + virtual bool decryptPayload(const QByteArray &fmk) = 0; virtual bool save(const QString &path) = 0; bool setLastError(const QString &msg) { return (lastError = msg).isEmpty(); } - virtual QByteArray transportKey(const CKey &key) = 0; + virtual QByteArray getFMK(const CKey &key, const QByteArray& secret) = 0; virtual int version() = 0; - QList keys; + QList> keys; std::vector files; QString lastError; }; @@ -83,14 +188,15 @@ class CryptoDoc final: public QObject CryptoDoc(QObject *parent = nullptr); ~CryptoDoc() final; - bool addKey( const CKey &key ); + bool supportsSymmetricKeys() const; + bool addKey(const std::shared_ptr &key); bool canDecrypt(const QSslCertificate &cert); void clear(const QString &file = {}); - bool decrypt(); + bool decrypt(std::shared_ptr key, const QByteArray& secret); + bool encrypt(const QString &filename = {}, const QString& label = {}, const QByteArray& secret = {}, uint32_t kdf_iter = 0); DocumentModel* documentModel() const; - bool encrypt(const QString &filename = {}); QString fileName() const; - QList keys() const; + QList> keys() const; bool move(const QString &to); bool open( const QString &file ); void removeKey( int id ); diff --git a/client/MainWindow.cpp b/client/MainWindow.cpp index cacd5983..a9b42b73 100644 --- a/client/MainWindow.cpp +++ b/client/MainWindow.cpp @@ -35,6 +35,7 @@ #include "effects/Overlay.h" #include "dialogs/FileDialog.h" #include "dialogs/MobileProgress.h" +#include "dialogs/PasswordDialog.h" #include "dialogs/RoleAddressDialog.h" #include "dialogs/SettingsDialog.h" #include "dialogs/SmartIDProgress.h" @@ -154,6 +155,8 @@ MainWindow::MainWindow( QWidget *parent ) connect(ui->cryptoContainerPage, &ContainerPage::keysSelected, this, &MainWindow::updateKeys); connect(ui->cryptoContainerPage, &ContainerPage::removed, this, &MainWindow::removeAddress); + connect(ui->cryptoContainerPage, &ContainerPage::decryptReq, this, &MainWindow::decryptClicked); + connect(ui->accordion, &Accordion::changePin1Clicked, this, &MainWindow::changePin1Clicked); connect(ui->accordion, &Accordion::changePin2Clicked, this, &MainWindow::changePin2Clicked); connect(ui->accordion, &Accordion::changePukClicked, this, &MainWindow::changePukClicked); @@ -233,14 +236,32 @@ ContainerState MainWindow::currentState() return ContainerState::Uninitialized; } -bool MainWindow::decrypt() +void MainWindow::decrypt(std::shared_ptr key) { - if(!cryptoDoc) - return false; + if(!cryptoDoc) return; + + QByteArray secret; + if (key && (key->type == CKey::Type::SYMMETRIC_KEY)) { + std::shared_ptr skey = std::static_pointer_cast(key); + PasswordDialog p; + p.setLabel(key->label); + if (skey->kdf_iter > 0) { + p.setMode(PasswordDialog::Mode::DECRYPT, PasswordDialog::Type::PASSWORD); + if(!p.exec()) return; + secret = p.secret(); + } else { + p.setMode(PasswordDialog::Mode::DECRYPT, PasswordDialog::Type::KEY); + if(!p.exec()) return; + secret = p.secret(); + } + } WaitDialogHolder waitDialog(this, tr("Decrypting")); - return cryptoDoc->decrypt(); + if (cryptoDoc->decrypt(key, secret)) { + ui->cryptoContainerPage->transition(cryptoDoc, qApp->signer()->tokenauth().cert()); + FadeInNotification::success(ui->topBar, tr("Decryption succeeded!")); + } } void MainWindow::dragEnterEvent(QDragEnterEvent *event) @@ -288,7 +309,7 @@ QStringList MainWindow::dropEventFiles(QDropEvent *event) return files; } -bool MainWindow::encrypt() +bool MainWindow::encrypt(bool askForKey) { if(!cryptoDoc) return false; @@ -299,14 +320,27 @@ bool MainWindow::encrypt() dlg->addButton(WarningDialog::YES, QMessageBox::Yes); if(dlg->exec() == QMessageBox::Yes) { moveCryptoContainer(); - return encrypt(); + return encrypt(askForKey); } return false; } + if (askForKey) { + PasswordDialog p; + p.setMode(PasswordDialog::Mode::ENCRYPT, PasswordDialog::Type::PASSWORD); + if(!p.exec()) return false; + QString label = p.label(); + QByteArray secret = p.secret(); + if (p.type == PasswordDialog::Type::PASSWORD) { + WaitDialogHolder waitDialog(this, tr("Encrypting")); + return cryptoDoc->encrypt(cryptoDoc->fileName(), label, secret, 65536); + } + WaitDialogHolder waitDialog(this, tr("Encrypting")); + return cryptoDoc->encrypt(cryptoDoc->fileName(), label, secret, 0); + } WaitDialogHolder waitDialog(this, tr("Encrypting")); - return cryptoDoc->encrypt(); + } void MainWindow::mouseReleaseEvent(QMouseEvent *event) @@ -476,7 +510,7 @@ void MainWindow::convertToCDoc() auto cardData = qApp->signer()->tokenauth(); if(!cardData.cert().isNull()) - cryptoContainer->addKey(CKey(cardData.cert())); + cryptoContainer->addKey(std::make_shared(cardData.cert())); resetCryptoDoc(cryptoContainer.release()); resetDigiDoc(nullptr, false); @@ -514,14 +548,17 @@ void MainWindow::onCryptoAction(int action, const QString &/*id*/, const QString break; case DecryptContainer: case DecryptToken: - if(decrypt()) + decrypt(nullptr); + break; + case EncryptContainer: + if(encrypt()) { ui->cryptoContainerPage->transition(cryptoDoc, qApp->signer()->tokenauth().cert()); - FadeInNotification::success(ui->topBar, tr("Decryption succeeded!")); + FadeInNotification::success(ui->topBar, tr("Encryption succeeded!")); } break; - case EncryptContainer: - if(encrypt()) + case EncryptLT: + if(encrypt(true)) { ui->cryptoContainerPage->transition(cryptoDoc, qApp->signer()->tokenauth().cert()); FadeInNotification::success(ui->topBar, tr("Encryption succeeded!")); @@ -1068,7 +1105,7 @@ void MainWindow::updateSelectorData(TokenData data) showCardMenu(false); } -void MainWindow::updateKeys(const QList &keys) +void MainWindow::updateKeys(const QList> &keys) { if(!cryptoDoc) return; @@ -1100,3 +1137,11 @@ void MainWindow::containerSummary() dialog->exec(); dialog->deleteLater(); } + +void +MainWindow::decryptClicked(std::shared_ptr key) +{ + qDebug() << "Decrypt clicked:"; + decrypt(key); +} + diff --git a/client/MainWindow.h b/client/MainWindow.h index e00de699..392a54ec 100644 --- a/client/MainWindow.h +++ b/client/MainWindow.h @@ -28,7 +28,7 @@ namespace Ui { class MainWindow; } -class CKey; +struct CKey; class CryptoDoc; class DigiDoc; class DocumentModel; @@ -71,8 +71,8 @@ private Q_SLOTS: void convertToBDoc(); void convertToCDoc(); ria::qdigidoc4::ContainerState currentState(); - bool decrypt(); - bool encrypt(); + void decrypt(std::shared_ptr key); + bool encrypt(bool askForKey = false); void loadPicture(); void moveCryptoContainer(); void moveSignatureContainer(); @@ -102,7 +102,7 @@ private Q_SLOTS: void showPinBlockedWarning(const QSmartCardData& t); void updateSelector(); void updateSelectorData(TokenData data); - void updateKeys(const QList &keys); + void updateKeys(const QList> &keys); void updateMyEID(const TokenData &t); void updateMyEid(const QSmartCardData &data); bool wrap(const QString& wrappedFile, bool enclose); @@ -116,4 +116,6 @@ private Q_SLOTS: CryptoDoc* cryptoDoc = nullptr; DigiDoc* digiDoc = nullptr; Ui::MainWindow *ui; + + void decryptClicked(std::shared_ptr key); }; diff --git a/client/common_enums.h b/client/common_enums.h index 69c1b176..f38fc2e7 100644 --- a/client/common_enums.h +++ b/client/common_enums.h @@ -54,7 +54,9 @@ enum Actions { SignatureMobile, SignatureSmartID, SignatureToken, - ClearSignatureWarning + ClearSignatureWarning, + + EncryptLT }; enum ItemType { diff --git a/client/dialogs/AddRecipients.cpp b/client/dialogs/AddRecipients.cpp index 24675aa7..2caebd1a 100644 --- a/client/dialogs/AddRecipients.cpp +++ b/client/dialogs/AddRecipients.cpp @@ -19,6 +19,7 @@ #include "AddRecipients.h" +#include "dialogs/PasswordDialog.h" #include "ui_AddRecipients.h" #include "Application.h" @@ -33,6 +34,7 @@ #include "TokenData.h" #include "dialogs/WarningDialog.h" #include "effects/Overlay.h" +#include "Crypto.h" #include #include @@ -109,7 +111,11 @@ void AddRecipients::addAllRecipientToRightPane() if(rightList.contains(value->getKey())) continue; addRecipientToRightPane(value); - history.append(value->getKey().cert); + std::shared_ptr key = value->getKey(); + if (key->type == CKey::Type::CDOC1) { + std::shared_ptr kd = std::static_pointer_cast(key); + history.append(kd->cert); + } } ui->confirm->setDisabled(rightList.isEmpty()); historyCertData.addAndSave(history); @@ -169,10 +175,17 @@ AddressItem * AddRecipients::addRecipientToLeftPane(const QSslCertificate& cert) if(leftItem) return leftItem; - leftItem = new AddressItem(CKey(cert), ui->leftPane); + leftItem = new AddressItem(std::make_shared(cert), ui->leftPane); leftList.insert(cert, leftItem); ui->leftPane->addWidget(leftItem); - bool contains = rightList.contains(cert); + + bool contains = false; + for (std::shared_ptr k: rightList) { + if (k->isTheSameRecipient(cert)) { + contains = true; + break; + } + } leftItem->setDisabled(contains); leftItem->showButton(contains ? AddressItem::Added : AddressItem::Add); @@ -186,40 +199,44 @@ AddressItem * AddRecipients::addRecipientToLeftPane(const QSslCertificate& cert) return leftItem; } -bool AddRecipients::addRecipientToRightPane(const CKey &key, bool update) +bool AddRecipients::addRecipientToRightPane(std::shared_ptr key, bool update) { - if (rightList.contains(key)) - return false; + for (std::shared_ptr k: rightList) { + if (k->isTheSameRecipient(*key)) return false; + } if(update) { - if(auto expiryDate = key.cert.expiryDate(); expiryDate <= QDateTime::currentDateTime()) - { - if(Settings::CDOC2_DEFAULT && Settings::CDOC2_USE_KEYSERVER) + if (key->type == CKey::Type::CDOC1) { + std::shared_ptr kd = std::static_pointer_cast(key); + if(auto expiryDate = kd->cert.expiryDate(); expiryDate <= QDateTime::currentDateTime()) { - WarningDialog::show(this, tr("Failed to add certificate. An expired certificate cannot be used for encryption.")); - return false; + if(Settings::CDOC2_DEFAULT && Settings::CDOC2_USE_KEYSERVER) + { + WarningDialog::show(this, tr("Failed to add certificate. An expired certificate cannot be used for encryption.")); + return false; + } + auto *dlg = new WarningDialog(tr("Are you sure that you want use certificate for encrypting, which expired on %1?
" + "When decrypter has updated certificates then decrypting is impossible.") + .arg(expiryDate.toString(QStringLiteral("dd.MM.yyyy hh:mm:ss"))), this); + dlg->setCancelText(WarningDialog::NO); + dlg->addButton(WarningDialog::YES, QMessageBox::Yes); + if(dlg->exec() != QMessageBox::Yes) + return false; + } + QSslConfiguration backup = QSslConfiguration::defaultConfiguration(); + QSslConfiguration::setDefaultConfiguration(CheckConnection::sslConfiguration()); + QList errors = QSslCertificate::verify({ kd->cert }); + QSslConfiguration::setDefaultConfiguration(backup); + errors.removeAll(QSslError(QSslError::CertificateExpired, kd->cert)); + if(!errors.isEmpty()) + { + auto *dlg = new WarningDialog(tr("Recipient’s certification chain contains certificates that are not trusted. Continue with encryption?"), this); + dlg->setCancelText(WarningDialog::NO); + dlg->addButton(WarningDialog::YES, QMessageBox::Yes); + if(dlg->exec() != QMessageBox::Yes) + return false; } - auto *dlg = new WarningDialog(tr("Are you sure that you want use certificate for encrypting, which expired on %1?
" - "When decrypter has updated certificates then decrypting is impossible.") - .arg(expiryDate.toString(QStringLiteral("dd.MM.yyyy hh:mm:ss"))), this); - dlg->setCancelText(WarningDialog::NO); - dlg->addButton(WarningDialog::YES, QMessageBox::Yes); - if(dlg->exec() != QMessageBox::Yes) - return false; - } - QSslConfiguration backup = QSslConfiguration::defaultConfiguration(); - QSslConfiguration::setDefaultConfiguration(CheckConnection::sslConfiguration()); - QList errors = QSslCertificate::verify({ key.cert }); - QSslConfiguration::setDefaultConfiguration(backup); - errors.removeAll(QSslError(QSslError::CertificateExpired, key.cert)); - if(!errors.isEmpty()) - { - auto *dlg = new WarningDialog(tr("Recipient’s certification chain contains certificates that are not trusted. Continue with encryption?"), this); - dlg->setCancelText(WarningDialog::NO); - dlg->addButton(WarningDialog::YES, QMessageBox::Yes); - if(dlg->exec() != QMessageBox::Yes) - return false; } } updated = update; @@ -230,7 +247,10 @@ bool AddRecipients::addRecipientToRightPane(const CKey &key, bool update) connect(rightItem, &AddressItem::remove, this, &AddRecipients::removeRecipientFromRightPane); ui->rightPane->addWidget(rightItem); ui->confirm->setDisabled(rightList.isEmpty()); - historyCertData.addAndSave({key.cert}); + if (key->type == CKey::Type::CDOC1) { + std::shared_ptr kd = std::static_pointer_cast(key); + historyCertData.addAndSave({kd->cert}); + } return true; } @@ -270,9 +290,9 @@ bool AddRecipients::isUpdated() const return updated; } -QList AddRecipients::keys() +QList> AddRecipients::keys() { - QList recipients; + QList> recipients; for(auto *item: ui->rightPane->items) { if(auto *address = qobject_cast(item)) @@ -284,10 +304,14 @@ QList AddRecipients::keys() void AddRecipients::removeRecipientFromRightPane(Item *toRemove) { auto *rightItem = qobject_cast(toRemove); - if(auto it = leftList.find(rightItem->getKey().cert); it != leftList.end()) - { - it.value()->setDisabled(false); - it.value()->showButton(AddressItem::Add); + std::shared_ptr key = rightItem->getKey(); + if (key->type == CKey::Type::CDOC1) { + std::shared_ptr kd = std::static_pointer_cast(key); + if(auto it = leftList.find(kd->cert); it != leftList.end()) + { + it.value()->setDisabled(false); + it.value()->showButton(AddressItem::Add); + } } rightList.removeAll(rightItem->getKey()); updated = true; diff --git a/client/dialogs/AddRecipients.h b/client/dialogs/AddRecipients.h index 4f9e5d2d..67ece87e 100644 --- a/client/dialogs/AddRecipients.h +++ b/client/dialogs/AddRecipients.h @@ -41,7 +41,7 @@ class AddRecipients final : public QDialog explicit AddRecipients(ItemList* itemList, QWidget *parent = nullptr); ~AddRecipients() final; - QList keys(); + QList> keys(); bool isUpdated() const; private: @@ -50,7 +50,7 @@ class AddRecipients final : public QDialog void addRecipientFromFile(); void addRecipientFromHistory(); AddressItem * addRecipientToLeftPane(const QSslCertificate& cert); - bool addRecipientToRightPane(const CKey &key, bool update = true); + bool addRecipientToRightPane(std::shared_ptr key, bool update = true); void addRecipientToRightPane(AddressItem *leftItem, bool update = true); void addSelectedCerts(const QList& selectedCertData); void enableRecipientFromCard(); @@ -64,7 +64,7 @@ class AddRecipients final : public QDialog Ui::AddRecipients *ui; QHash leftList; - QList rightList; + QList> rightList; LdapSearch *ldap_person, *ldap_corp; bool updated = false; diff --git a/client/dialogs/KeyDialog.cpp b/client/dialogs/KeyDialog.cpp index b9b662ca..043e8514 100644 --- a/client/dialogs/KeyDialog.cpp +++ b/client/dialogs/KeyDialog.cpp @@ -49,10 +49,15 @@ KeyDialog::KeyDialog( const CKey &k, QWidget *parent ) d->view->setHeaderLabels({tr("Attribute"), tr("Value")}); connect(d->close, &QPushButton::clicked, this, &KeyDialog::accept); - connect(d->showCert, &QPushButton::clicked, this, [this, cert=k.cert] { - CertificateDetails::showCertificate(cert, this); - }); - d->showCert->setHidden(k.cert.isNull()); + if (k.type == CKey::CDOC1) { + const CKeyCDoc1& kd = static_cast(k); + connect(d->showCert, &QPushButton::clicked, this, [this, cert=kd.cert] { + CertificateDetails::showCertificate(cert, this); + }); + d->showCert->setHidden(kd.cert.isNull()); + } else { + d->showCert->setHidden(true); + } auto addItem = [&](const QString ¶meter, const QString &value) { if(value.isEmpty()) @@ -63,16 +68,22 @@ KeyDialog::KeyDialog( const CKey &k, QWidget *parent ) d->view->addTopLevelItem(i); }; - addItem(tr("Recipient"), k.recipient); - addItem(tr("Crypto method"), k.method); - addItem(tr("Agreement method"), k.agreement); - addItem(tr("Key derivation method"), k.derive); - addItem(tr("ConcatKDF digest method"), k.concatDigest); - addItem(tr("Key server ID"), k.keyserver_id); - addItem(tr("Transaction ID"), k.transaction_id); - addItem(tr("Expiry date"), k.cert.expiryDate().toLocalTime().toString(QStringLiteral("dd.MM.yyyy hh:mm:ss"))); - addItem(tr("Issuer"), SslCertificate(k.cert).issuerInfo(QSslCertificate::CommonName)); + bool adjust_size = false; + if (k.type == CKey::Type::CDOC1) { + const CKeyCDoc1& cd1key = static_cast(k); + addItem(tr("Recipient"), cd1key.label); + addItem(tr("Crypto method"), cd1key.method); + addItem(tr("ConcatKDF digest method"), cd1key.concatDigest); + addItem(tr("Expiry date"), cd1key.cert.expiryDate().toLocalTime().toString(QStringLiteral("dd.MM.yyyy hh:mm:ss"))); + addItem(tr("Issuer"), SslCertificate(cd1key.cert).issuerInfo(QSslCertificate::CommonName)); + adjust_size = !cd1key.concatDigest.isEmpty(); + } + addItem(tr("Label"), k.label); + if (k.type == CKey::SERVER) { + const CKeyServer& sk = static_cast(k); + addItem(tr("Key server ID"), sk.keyserver_id); + addItem(tr("Transaction ID"), sk.transaction_id); + } d->view->resizeColumnToContents( 0 ); - if(!k.agreement.isEmpty()) - adjustSize(); + if(adjust_size) adjustSize(); } diff --git a/client/dialogs/KeyDialog.h b/client/dialogs/KeyDialog.h index f65afe7c..a80600c7 100644 --- a/client/dialogs/KeyDialog.h +++ b/client/dialogs/KeyDialog.h @@ -21,7 +21,8 @@ #include -class CKey; +struct CKey; + class KeyDialog final: public QDialog { Q_OBJECT diff --git a/client/dialogs/PasswordDialog.cpp b/client/dialogs/PasswordDialog.cpp new file mode 100644 index 00000000..c34dc007 --- /dev/null +++ b/client/dialogs/PasswordDialog.cpp @@ -0,0 +1,136 @@ +#include "PasswordDialog.h" +#include "Crypto.h" +#include "ui_PasswordDialog.h" + +PasswordDialog::PasswordDialog(QWidget *parent) + : QDialog(parent), mode(Mode::DECRYPT), type(Type::PASSWORD) + , ui(new Ui::PasswordDialog) +{ + ui->setupUi(this); + connect(ui->generateKey, &QPushButton::clicked, this, &PasswordDialog::genKeyClicked); + connect(ui->typeSelector, &QTabWidget::currentChanged, this, &PasswordDialog::typeChanged); + connect(ui->passwordLine, &QLineEdit::textChanged, this, &PasswordDialog::lineChanged); + connect(ui->password2Line, &QLineEdit::textChanged, this, &PasswordDialog::lineChanged); + connect(ui->keyEdit, &QPlainTextEdit::textChanged, this, &PasswordDialog::editChanged); +} + +PasswordDialog::~PasswordDialog() +{ + delete ui; +} + +void +PasswordDialog::setMode(Mode _mode, Type _type) +{ + mode = _mode; + type = _type; + updateUI(); +} + +void +PasswordDialog::setLabel(const QString& label) +{ + ui->labelLine->setText(label); +} + +QString +PasswordDialog::label() +{ + return ui->labelLine->text(); +} + +QByteArray +PasswordDialog::secret() const +{ + if (type == Type::PASSWORD) { + return ui->passwordLine->text().toUtf8(); + } else { + QString hex = ui->keyEdit->toPlainText(); + return QByteArray::fromHex(hex.toUtf8()); + } +} + +void +PasswordDialog::typeChanged(int index) +{ + Type new_type = static_cast(index); + if (new_type != type) setMode(mode, new_type); +} + +void +PasswordDialog::lineChanged(const QString& text) +{ + updateOK(); +} + +void +PasswordDialog::editChanged() +{ + updateOK(); +} + +void +PasswordDialog::genKeyClicked() +{ + QByteArray key = Crypto::random(); + ui->keyEdit->clear(); + ui->keyEdit->appendPlainText(key.toHex()); +} + +void +PasswordDialog::updateUI() +{ + ui->typeSelector->setCurrentIndex(type); + if (mode == Mode::DECRYPT) { + ui->labelLine->setReadOnly(true); + if (type == Type::PASSWORD) { + ui->typeSelector->setTabEnabled(Type::PASSWORD, true); + ui->typeSelector->setTabEnabled(Type::KEY, false); + ui->passwordLabel->setText("Enter password to decrypt the document"); + ui->passwordLine->setEchoMode(QLineEdit::EchoMode::Password); + ui->password2Label->hide(); + ui->password2Line->hide(); + } else { + ui->typeSelector->setTabEnabled(Type::PASSWORD, false); + ui->typeSelector->setTabEnabled(Type::KEY, true); + ui->keyLabel->setText("Enter key to decrypt the document"); + ui->generateKey->hide(); + } + } else { + ui->labelLine->setReadOnly(false); + if (type == Type::PASSWORD) { + ui->typeSelector->setTabEnabled(Type::PASSWORD, true); + ui->typeSelector->setTabEnabled(Type::KEY, true); + ui->passwordLabel->setText("Enter a password to encrypt the document"); + ui->passwordLine->setEchoMode(QLineEdit::EchoMode::Password); + ui->password2Label->show(); + ui->password2Line->show(); + } else { + ui->typeSelector->setTabEnabled(Type::PASSWORD, true); + ui->typeSelector->setTabEnabled(Type::KEY, true); + ui->keyLabel->setText("Enter a key to encrypt the document"); + ui->generateKey->show(); + } + } + updateOK(); +} + +void +PasswordDialog::updateOK() +{ + bool active = false; + if (mode == Mode::DECRYPT) { + if (type == Type::PASSWORD) { + active = !ui->passwordLine->text().isEmpty(); + } else { + active = !ui->keyEdit->toPlainText().isEmpty(); + } + } else { + if (type == Type::PASSWORD) { + active = !ui->passwordLine->text().isEmpty() && ui->passwordLine->text() == ui->password2Line->text(); + } else { + active = !ui->keyEdit->toPlainText().isEmpty(); + } + } + ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(active); +} diff --git a/client/dialogs/PasswordDialog.h b/client/dialogs/PasswordDialog.h new file mode 100644 index 00000000..451144c2 --- /dev/null +++ b/client/dialogs/PasswordDialog.h @@ -0,0 +1,47 @@ +#ifndef PASSWORDDIALOG_H +#define PASSWORDDIALOG_H + +#include + +namespace Ui { +class PasswordDialog; +} + +class PasswordDialog : public QDialog +{ + Q_OBJECT + +public: + enum Mode { + ENCRYPT, + DECRYPT + }; + + enum Type { + PASSWORD, + KEY + }; + + Mode mode; + Type type; + + explicit PasswordDialog(QWidget *parent = nullptr); + ~PasswordDialog(); + + void setMode(Mode mode, Type type); + + void setLabel(const QString& label); + QString label(); + QByteArray secret() const; +private: + Ui::PasswordDialog *ui; + + void typeChanged(int index); + void lineChanged(const QString& text); + void editChanged(); + void genKeyClicked(); + void updateUI(); + void updateOK(); +}; + +#endif // PASSWORDDIALOG_H diff --git a/client/dialogs/PasswordDialog.ui b/client/dialogs/PasswordDialog.ui new file mode 100644 index 00000000..dd8d2049 --- /dev/null +++ b/client/dialogs/PasswordDialog.ui @@ -0,0 +1,175 @@ + + + PasswordDialog + + + + 0 + 0 + 400 + 322 + + + + Dialog + + + + + + Key label (recipient name or id) + + + Qt::AlignCenter + + + + + + + + + + 0 + + + + Password + + + + + + Password + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + QLineEdit::Password + + + + + + + Repeat password + + + + + + + QLineEdit::Password + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + Symmetric key + + + + + + Symmetric key + + + + + + + + + + Generate Key + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + false + + + + + + + + + buttonBox + accepted() + PasswordDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + PasswordDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/client/widgets/AddressItem.cpp b/client/widgets/AddressItem.cpp index 4cc51efc..cb4fa92a 100644 --- a/client/widgets/AddressItem.cpp +++ b/client/widgets/AddressItem.cpp @@ -17,6 +17,8 @@ * */ +#include + #include "AddressItem.h" #include "ui_AddressItem.h" @@ -32,16 +34,16 @@ class AddressItem::Private: public Ui::AddressItem { public: QString code; - CKey key; + std::shared_ptr key; QString label; bool yourself = false; }; -AddressItem::AddressItem(CKey k, QWidget *parent, bool showIcon) +AddressItem::AddressItem(const std::shared_ptr& key, QWidget *parent, bool showIcon) : Item(parent) , ui(new Private) { - ui->key = std::move(k); + ui->key = key; ui->setupUi(this); if(showIcon) ui->icon->load(QStringLiteral(":/images/icon_Krypto_small.svg")); @@ -57,15 +59,30 @@ AddressItem::AddressItem(CKey k, QWidget *parent, bool showIcon) connect(ui->add, &QToolButton::clicked, this, [this]{ emit add(this);}); connect(ui->remove, &LabelButton::clicked, this, [this]{ emit remove(this);}); + if (key->isSymmetric()) { + ui->decrypt->show(); + connect(ui->decrypt, &QToolButton::clicked, this, [this]{ emit decrypt(ui->key);}); + } else { + ui->decrypt->hide(); + } + ui->add->setFont(Styles::font(Styles::Condensed, 12)); ui->added->setFont(ui->add->font()); - ui->code = SslCertificate(ui->key.cert).personalCode().toHtmlEscaped(); - ui->label = (!ui->key.cert.subjectInfo("GN").isEmpty() && !ui->key.cert.subjectInfo("SN").isEmpty() ? - ui->key.cert.subjectInfo("GN").join(' ') + " " + ui->key.cert.subjectInfo("SN").join(' ') : - ui->key.cert.subjectInfo("CN").join(' ')).toHtmlEscaped(); - if(ui->label.isEmpty()) - ui->label = ui->key.recipient.toHtmlEscaped(); + if (ui->key->isCDoc1()) { + std::shared_ptr keycd1 = std::static_pointer_cast(ui->key); + ui->code = SslCertificate(keycd1->cert).personalCode().toHtmlEscaped(); + ui->label = (!keycd1->cert.subjectInfo("GN").isEmpty() && !keycd1->cert.subjectInfo("SN").isEmpty() ? + keycd1->cert.subjectInfo("GN").join(' ') + " " + keycd1->cert.subjectInfo("SN").join(' ') : + keycd1->cert.subjectInfo("CN").join(' ')).toHtmlEscaped(); + } else { + ui->code.clear(); + ui->label = key->label.toHtmlEscaped(); + } + if(ui->label.isEmpty() && ui->key->type == CKey::PUBLIC_KEY) { + const auto& pk = static_cast(*ui->key); + ui->label = pk.key_material; + } setIdType(); showButton(AddressItem::Remove); } @@ -90,26 +107,26 @@ bool AddressItem::eventFilter(QObject *o, QEvent *e) { if((o == ui->name || o == ui->idType) && e->type() == QEvent::MouseButtonRelease) { - (new KeyDialog(ui->key, this))->open(); + (new KeyDialog(*ui->key, this))->open(); return true; } return Item::eventFilter(o, e); } -const CKey& AddressItem::getKey() const +std::shared_ptr AddressItem::getKey() const { return ui->key; } -void AddressItem::idChanged(const CKey &key) +void AddressItem::idChanged(const std::shared_ptr& key) { - ui->yourself = !key.key.isNull() && ui->key == key; + ui->yourself = key->isTheSameRecipient(*ui->key); setName(); } void AddressItem::idChanged(const SslCertificate &cert) { - idChanged(CKey(cert)); + idChanged(std::make_shared(cert)); } void AddressItem::initTabOrder(QWidget *item) @@ -128,13 +145,14 @@ QWidget* AddressItem::lastTabWidget() void AddressItem::mouseReleaseEvent(QMouseEvent * /*event*/) { - (new KeyDialog(ui->key, this))->open(); + (new KeyDialog(*ui->key, this))->open(); } void AddressItem::setName() { - ui->name->setText(QStringLiteral("%1 %2") - .arg(ui->label, ui->yourself ? ui->code + tr(" (Yourself)") : ui->code)); + QString str = QStringLiteral("%1 %2").arg(ui->label, ui->yourself ? ui->code + tr(" (Yourself)") : ui->code); + qDebug() << "SetName:" << str; + ui->name->setText(str); } void AddressItem::showButton(ShowToolButton show) @@ -151,33 +169,51 @@ void AddressItem::stateChange(ContainerState state) void AddressItem::setIdType() { - ui->idType->setHidden(ui->key.cert.isNull()); - if(ui->key.cert.isNull()) - return; - - QString str; - SslCertificate cert(ui->key.cert); - SslCertificate::CertType type = cert.type(); - if(type & SslCertificate::DigiIDType) - str = tr("digi-ID"); - else if(type & SslCertificate::EstEidType) - str = tr("ID-card"); - else if(type & SslCertificate::MobileIDType) - str = tr("mobile-ID"); - else if(type & SslCertificate::TempelType) - { - if(cert.keyUsage().contains(SslCertificate::NonRepudiation)) - str = tr("e-Seal"); - else if(cert.enhancedKeyUsage().contains(SslCertificate::ClientAuth)) - str = tr("Authentication certificate"); - else - str = tr("Certificate for Encryption"); + if (ui->key->isPKI()) { + std::shared_ptr pki = std::static_pointer_cast(ui->key); + if (ui->key->isCertificate()) { + std::shared_ptr ckd = std::static_pointer_cast(ui->key); + ui->idType->setHidden(false); + QString str; + SslCertificate cert(ckd->cert); + SslCertificate::CertType type = cert.type(); + if(type & SslCertificate::DigiIDType) + str = tr("digi-ID"); + else if(type & SslCertificate::EstEidType) + str = tr("ID-card"); + else if(type & SslCertificate::MobileIDType) + str = tr("mobile-ID"); + else if(type & SslCertificate::TempelType) + { + if(cert.keyUsage().contains(SslCertificate::NonRepudiation)) + str = tr("e-Seal"); + else if(cert.enhancedKeyUsage().contains(SslCertificate::ClientAuth)) + str = tr("Authentication certificate"); + else + str = tr("Certificate for Encryption"); + } + + if(!str.isEmpty()) + str += QStringLiteral(" - "); + DateTime date(cert.expiryDate().toLocalTime()); + ui->idType->setText(QStringLiteral("%1%2 %3").arg(str, + cert.isValid() ? tr("Expires on") : tr("Expired on"), + date.formatDate(QStringLiteral("dd. MMMM yyyy")))); + } else { + QString type = (pki->pk_type == CKey::PKType::RSA) ? QStringLiteral("RSA") : QStringLiteral("ECC"); + ui->idType->setHidden(false); + ui->idType->setText(type + tr(" public key")); + } + } else if (ui->key->isSymmetric()) { + std::shared_ptr ckd = std::static_pointer_cast(ui->key); + ui->idType->setHidden(false); + if (ckd->kdf_iter > 0) { + ui->idType->setText(tr("Password derived key")); + } else { + ui->idType->setText(tr("Symmetric key")); + } + } else { + ui->idType->setHidden(false); + ui->idType->setText(tr("Unknown key type")); } - - if(!str.isEmpty()) - str += QStringLiteral(" - "); - DateTime date(cert.expiryDate().toLocalTime()); - ui->idType->setText(QStringLiteral("%1%2 %3").arg(str, - cert.isValid() ? tr("Expires on") : tr("Expired on"), - date.formatDate(QStringLiteral("dd. MMMM yyyy")))); } diff --git a/client/widgets/AddressItem.h b/client/widgets/AddressItem.h index 54d3788d..a88ed531 100644 --- a/client/widgets/AddressItem.h +++ b/client/widgets/AddressItem.h @@ -19,9 +19,11 @@ #pragma once +#include + #include "widgets/Item.h" -class CKey; +struct CKey; class AddressItem final : public Item { @@ -36,17 +38,20 @@ class AddressItem final : public Item Added, }; - explicit AddressItem(CKey k, QWidget *parent = {}, bool showIcon = false); + explicit AddressItem(const std::shared_ptr& k, QWidget *parent = {}, bool showIcon = false); ~AddressItem() final; - const CKey& getKey() const; - void idChanged(const CKey &cert); + std::shared_ptr getKey() const; + void idChanged(const std::shared_ptr& cert); void idChanged(const SslCertificate &cert) final; void initTabOrder(QWidget *item) final; QWidget* lastTabWidget() final; void showButton(ShowToolButton show); void stateChange(ria::qdigidoc4::ContainerState state) final; +signals: + void decrypt(std::shared_ptr key); + private: void changeEvent(QEvent *event) final; bool eventFilter(QObject *o, QEvent *e) final; diff --git a/client/widgets/AddressItem.ui b/client/widgets/AddressItem.ui index ecbe557e..de3704ed 100644 --- a/client/widgets/AddressItem.ui +++ b/client/widgets/AddressItem.ui @@ -52,7 +52,15 @@ color: #006EB5; } QToolButton:disabled { color: #727679; -} +} +#decrypt { +color: white; +background-color: #006EB5; +} +#decrypt:hover { +background-color: #00AEE5; +} + @@ -138,6 +146,13 @@ color: #727679; + + + DECRYPT + + + + @@ -173,7 +188,7 @@ color: #727679; - + false @@ -198,7 +213,7 @@ color: #727679; - + diff --git a/client/widgets/ContainerPage.cpp b/client/widgets/ContainerPage.cpp index f2039181..bdd78397 100644 --- a/client/widgets/ContainerPage.cpp +++ b/client/widgets/ContainerPage.cpp @@ -270,6 +270,26 @@ void ContainerPage::showMainAction(const QList &actions) ui->navigationArea->layout()->invalidate(); } +void ContainerPage::showMainActionEncrypt(bool showLT) +{ + if(!mainAction) { + mainAction = std::make_unique(this); + connect(mainAction.get(), &MainAction::action, this, &ContainerPage::forward); + } + if (showLT) { + if (ui->rightPane->findChildren().isEmpty()) { + mainAction->showActions({ EncryptLT }); + } else { + mainAction->showActions({ EncryptContainer, EncryptLT }); + } + } else { + mainAction->showActions({ EncryptContainer }); + } + mainAction->setButtonEnabled(isSupported && !hasEmptyFile); + ui->mainActionSpacer->changeSize(198, 20, QSizePolicy::Fixed); + ui->navigationArea->layout()->invalidate(); +} + void ContainerPage::showSigningButton() { if (!isSupported || hasEmptyFile) @@ -290,14 +310,19 @@ void ContainerPage::showSigningButton() void ContainerPage::transition(CryptoDoc *container, const QSslCertificate &cert) { clear(); - isSupported = container && (container->state() & UnencryptedContainer || container->canDecrypt(cert)); - if(!container) + if (!container) { + isSupported = false; return; + } + isSupported = (container->state() & UnencryptedContainer) || container->canDecrypt(cert); setHeader(container->fileName()); - for(const CKey &key: container->keys()) - ui->rightPane->addWidget(new AddressItem(key, ui->rightPane, true)); + for(std::shared_ptr& key: container->keys()) { + AddressItem *addr = new AddressItem(key, ui->rightPane, true); + connect(addr, &AddressItem::decrypt, this, [this,key]{emit decryptReq(key);}); + ui->rightPane->addWidget(addr); + } ui->leftPane->setModel(container->documentModel()); - updatePanes(container->state()); + updatePanes(container->state(), container); } void ContainerPage::transition(DigiDoc* container) @@ -355,7 +380,7 @@ void ContainerPage::transition(DigiDoc* container) showSigningButton(); ui->leftPane->setModel(container->documentModel()); - updatePanes(container->state()); + updatePanes(container->state(), nullptr); } void ContainerPage::update(bool canDecrypt, CryptoDoc* container) @@ -369,10 +394,13 @@ void ContainerPage::update(bool canDecrypt, CryptoDoc* container) hasEmptyFile = false; ui->rightPane->clear(); - for(const CKey &key: container->keys()) - ui->rightPane->addWidget(new AddressItem(key, ui->rightPane, true)); + for(std::shared_ptr& key: container->keys()) { + AddressItem *addr = new AddressItem(key, ui->rightPane, true); + connect(addr, &AddressItem::decrypt, this, [this,key]{emit decryptReq(key);}); + ui->rightPane->addWidget(addr); + } if(container->state() & UnencryptedContainer) - showMainAction({ EncryptContainer }); + showMainActionEncrypt(container->supportsSymmetricKeys()); } void ContainerPage::updateDecryptionButton() @@ -380,7 +408,7 @@ void ContainerPage::updateDecryptionButton() showMainAction({ isSeal ? DecryptToken : DecryptContainer }); } -void ContainerPage::updatePanes(ContainerState state) +void ContainerPage::updatePanes(ria::qdigidoc4::ContainerState state, CryptoDoc *crypto_container) { ui->leftPane->stateChange(state); ui->rightPane->stateChange(state); @@ -433,7 +461,7 @@ void ContainerPage::updatePanes(ContainerState state) ui->changeLocation->show(); ui->leftPane->init(fileName); showRightPane(ItemAddress, QT_TRANSLATE_NOOP("ItemList", "Recipients")); - showMainAction({ EncryptContainer }); + showMainActionEncrypt(crypto_container && crypto_container->supportsSymmetricKeys()); setButtonsVisible({ ui->cancel, ui->convert }, true); setButtonsVisible({ ui->save, ui->saveAs, ui->email, ui->summary }, false); break; diff --git a/client/widgets/ContainerPage.h b/client/widgets/ContainerPage.h index f035d2b6..248c19f1 100644 --- a/client/widgets/ContainerPage.h +++ b/client/widgets/ContainerPage.h @@ -28,7 +28,7 @@ namespace Ui { class ContainerPage; } -class CKey; +struct CKey; class CryptoDoc; class DigiDoc; class QSslCertificate; @@ -58,11 +58,13 @@ class ContainerPage final : public QWidget void action(int code, const QString &info1 = {}, const QString &info2 = {}); void addFiles(const QStringList &files); void fileRemoved(int row); - void keysSelected(const QList &keys); + void keysSelected(const QList> &keys); void moved(const QString &to); void removed(int row); void warning(const WarningText &warningText); + void decryptReq(std::shared_ptr key); + private: void addressSearch(); void changeEvent(QEvent* event) final; @@ -72,10 +74,11 @@ class ContainerPage final : public QWidget void forward(int code); void hideRightPane(); void showMainAction(const QList &actions); + void showMainActionEncrypt(bool showLT); void showRightPane(ria::qdigidoc4::ItemType itemType, const char *header); void showSigningButton(); void updateDecryptionButton(); - void updatePanes(ria::qdigidoc4::ContainerState state); + void updatePanes(ria::qdigidoc4::ContainerState state, CryptoDoc *crypto_container); void translateLabels(); Ui::ContainerPage *ui; diff --git a/client/widgets/MainAction.cpp b/client/widgets/MainAction.cpp index 3635e24e..442ac23d 100644 --- a/client/widgets/MainAction.cpp +++ b/client/widgets/MainAction.cpp @@ -135,6 +135,7 @@ QString MainAction::label(Actions action) case SignatureSmartID: return tr("SignatureSmartID"); case SignatureToken: return tr("SignatureToken"); case EncryptContainer: return tr("EncryptContainer"); + case EncryptLT: return tr("EncryptLongTerm"); case DecryptContainer: return tr("DecryptContainer"); case DecryptToken: return tr("DECRYPT"); default: return tr("SignatureAdd"); diff --git a/common b/common index 27319130..fbe6fab8 160000 --- a/common +++ b/common @@ -1 +1 @@ -Subproject commit 27319130f517cc085531d59beda5779b5b223b4e +Subproject commit fbe6fab8bddab463e101b857c31c69ee9595c16b diff --git a/schema/header.fbs b/schema/header.fbs index 6a95a668..4c385ee0 100644 --- a/schema/header.fbs +++ b/schema/header.fbs @@ -25,7 +25,7 @@ include "recipients.fbs"; -namespace cdoc20.Header; +namespace cdoc20.header; // FMK encryption method enum. enum FMKEncryptionMethod:byte { @@ -44,7 +44,7 @@ enum PayloadEncryptionMethod:byte { // Thus it is better to have an an array of tables that // contains the union as a field. table RecipientRecord { - capsule: cdoc20.Recipients.Capsule; + capsule: recipients.Capsule; key_label: string (required); encrypted_fmk: [ubyte] (required); fmk_encryption_method: FMKEncryptionMethod = UNKNOWN; @@ -53,6 +53,7 @@ table RecipientRecord { // Header structure. table Header { recipients: [RecipientRecord]; + payload_encryption_method: PayloadEncryptionMethod = UNKNOWN; } diff --git a/schema/recipients.fbs b/schema/recipients.fbs index 8325864c..b17eb77f 100644 --- a/schema/recipients.fbs +++ b/schema/recipients.fbs @@ -23,20 +23,20 @@ * */ -namespace cdoc20.Recipients; +namespace cdoc20.recipients; // Union for communicating the recipient type union Capsule { ECCPublicKeyCapsule, RSAPublicKeyCapsule, KeyServerCapsule, - SymmetricKeyCapsule + SymmetricKeyCapsule, + PBKDF2Capsule } -//for future proofing and data type -union ServerDetailsUnion { - ServerEccDetails, - ServerRsaDetails +//server recipient type +union KeyDetailsUnion { + EccKeyDetails, RsaKeyDetails } // Elliptic curve type enum for ECCPublicKey recipient @@ -45,17 +45,24 @@ enum EllipticCurve:byte { secp384r1 } -table ServerRsaDetails { - //RSA pub key in DER - recipient_public_key: [ubyte] (required); +// KDF algorithm identifier enum +enum KDFAlgorithmIdentifier:byte { + UNKNOWN, + PBKDF2WithHmacSHA256 } -table ServerEccDetails { - // Elliptic curve type enum - curve: EllipticCurve = UNKNOWN; - //EC pub key in TLS format - //for secp384r1 curve: 0x04 + X 48 coord bytes + Y coord 48 bytes) - recipient_public_key: [ubyte] (required); +table RsaKeyDetails { + //RSA pub key in DER - RFC8017 RSA Public Key Syntax (A.1.1) https://www.rfc-editor.org/rfc/rfc8017#page-54 + recipient_public_key: [ubyte] (required); +} + +table EccKeyDetails { + // Elliptic curve type enum + curve: EllipticCurve = UNKNOWN; + + //EC pub key in TLS 1.3 format https://datatracker.ietf.org/doc/html/rfc8446#section-4.2.8.2 + //for secp384r1 curve: 0x04 + X 48 coord bytes + Y coord 48 bytes) + recipient_public_key: [ubyte] (required); } // ECC public key recipient @@ -70,8 +77,10 @@ table RSAPublicKeyCapsule { encrypted_kek: [ubyte] (required); } +// recipient where ephemeral key material is download from server (server scenarios) table KeyServerCapsule { - recipient_key_details: ServerDetailsUnion; + // recipient id - key type specific. For public key cryptography this is usually recipient public key + recipient_key_details: KeyDetailsUnion; keyserver_id: string (required); transaction_id: string (required); } @@ -80,3 +89,12 @@ table KeyServerCapsule { table SymmetricKeyCapsule { salt: [ubyte] (required); } + +// password derived key +table PBKDF2Capsule { + // HKDF salt to derive KEK + salt: [ubyte] (required); + password_salt: [ubyte] (required); + kdf_algorithm_identifier: KDFAlgorithmIdentifier = UNKNOWN; + kdf_iterations: int32; +}