diff --git a/client/CDocSupport.cpp b/client/CDocSupport.cpp index 19cf47812..94a0a58f6 100644 --- a/client/CDocSupport.cpp +++ b/client/CDocSupport.cpp @@ -20,10 +20,15 @@ #include #include #include +#include #include #include #include +#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0) +#include +#endif #include +#include #include #include #include @@ -49,10 +54,14 @@ QDebug operator<<(QDebug d, std::string_view str) { } #endif -static QByteArray toByteArray(const std::vector &data) { +static QByteArray asByteArray(const std::vector &data) { return QByteArray::fromRawData(reinterpret_cast(data.data()), data.size()); } +static QByteArray asByteArray(QByteArrayView data) { + return QByteArray::fromRawData(data.data(), data.size()); +} + std::vector CDocSupport::getCDocFileList(const QString &filename) { @@ -129,8 +138,7 @@ DDCryptoBackend::decryptRSA(std::vector& dst, const std::vectordecrypt(toByteArray(data), oaep); - dst.assign(decryptedKey.cbegin(), decryptedKey.cend()); + dst = backend->decrypt(asByteArray(data), oaep); return getDecryptResultStatus(dst, std::move(backend)); } @@ -149,9 +157,8 @@ DDCryptoBackend::deriveConcatKDF(std::vector& dst, const std::vectorderiveConcatKDF(toByteArray(publicKey), SHA_MTH.value(digest), - toByteArray(algorithmID), toByteArray(partyUInfo), toByteArray(partyVInfo)); - dst.assign(decryptedKey.cbegin(), decryptedKey.cend()); + dst = backend->deriveConcatKDF(asByteArray(publicKey), SHA_MTH.value(digest), + asByteArray(algorithmID), asByteArray(partyUInfo), asByteArray(partyVInfo)); return getDecryptResultStatus(dst, std::move(backend)); } @@ -164,15 +171,46 @@ DDCryptoBackend::deriveHMACExtract(std::vector& dst, const std::vector< return getDecryptStatus(val.error()); backend.reset(val.value()); } - QByteArray decryptedKey = backend->deriveHMACExtract(toByteArray(key_material), toByteArray(salt), ECC_KEY_LEN); - dst.assign(decryptedKey.cbegin(), decryptedKey.cend()); + dst = backend->deriveHMACExtract(asByteArray(key_material), asByteArray(salt), ECC_KEY_LEN); return getDecryptResultStatus(dst, std::move(backend)); } libcdoc::result_t -DDCryptoBackend::getSecret(std::vector& _secret, unsigned int idx) +DDCryptoBackend::extractHKDF(std::vector &dst, const std::vector &salt, + const std::vector &pwSalt, int32_t kdfIter, unsigned int idx) { - _secret = secret; + if(secret.isEmpty() || salt.empty() || kdfIter < 0 || (kdfIter > 0 && pwSalt.empty())) + return INVALID_PARAMS; + + QByteArray derivedKeyMaterial; + auto keyMaterialGuard = qScopeGuard([&] { derivedKeyMaterial.fill(0); }); + QByteArrayView keyMaterial(secret); + if(kdfIter > 0) { + derivedKeyMaterial = QPasswordDigestor::deriveKeyPbkdf2(QCryptographicHash::Sha256, + secret, asByteArray(pwSalt), kdfIter, 32); + keyMaterial = QByteArrayView(derivedKeyMaterial); + } else if(secret.size() != 32) { + return INVALID_PARAMS; + } + if(keyMaterial.size() != 32) + return libcdoc::CRYPTO_ERROR; + +#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0) + dst.resize(32); + const QByteArrayView result = QMessageAuthenticationCode::hashInto( + dst, keyMaterial, salt, QCryptographicHash::Sha256); + if(result.size() != 32) { + std::fill(dst.begin(), dst.end(), 0); + dst.clear(); + return libcdoc::CRYPTO_ERROR; + } +#else + QByteArray kekPreMaster = QMessageAuthenticationCode::hash(asByteArray(keyMaterial), asByteArray(salt), QCryptographicHash::Sha256); + auto kekPreMasterGuard = qScopeGuard([&] { kekPreMaster.fill(0); }); + if(kekPreMaster.size() != 32) + return libcdoc::CRYPTO_ERROR; + dst.assign(kekPreMaster.cbegin(), kekPreMaster.cend()); +#endif return libcdoc::OK; } @@ -278,8 +316,8 @@ libcdoc::result_t DDNetworkBackend::sendKey( } QScopedPointer nam(CheckConnection::setupNAM(req, Settings::CDOC2_POST_CERT)); QNetworkReply *reply = nam->post(req, QJsonDocument({ - {QLatin1String("recipient_id"), QLatin1String(toByteArray(rcpt_key).toBase64())}, - {QLatin1String("ephemeral_key_material"), QLatin1String(toByteArray(key_material).toBase64())}, + {QLatin1String("recipient_id"), QLatin1String(asByteArray(rcpt_key).toBase64())}, + {QLatin1String("ephemeral_key_material"), QLatin1String(asByteArray(key_material).toBase64())}, {QLatin1String("capsule_type"), QLatin1String(type.c_str())}, }).toJson()); QEventLoop e; diff --git a/client/CDocSupport.h b/client/CDocSupport.h index 7a9fe5573..64b7b91b5 100644 --- a/client/CDocSupport.h +++ b/client/CDocSupport.h @@ -75,18 +75,29 @@ struct DDCryptoBackend final : public libcdoc::CryptoBackend { const std::vector &publicKey, const std::vector &salt, unsigned int idx) final; - libcdoc::result_t getSecret(std::vector &secret, - unsigned int idx) final; + libcdoc::result_t extractHKDF(std::vector &dst, + const std::vector &salt, + const std::vector &pwSalt, + int32_t kdfIter, + unsigned int idx) final; std::string getLastErrorStr(libcdoc::result_t code) const final; std::unique_ptr backend; - std::vector secret; + QByteArray secret; explicit DDCryptoBackend() = default; void setBackend(std::unique_ptr &&backend) { this->backend = std::move(backend); } + void setSecret(QByteArray &&value) { + clearSecret(); + secret = std::move(value); + } + void clearSecret() { + secret.fill(0); + secret.clear(); + } }; // diff --git a/client/CryptoDoc.cpp b/client/CryptoDoc.cpp index a83b10d91..9dd85460e 100644 --- a/client/CryptoDoc.cpp +++ b/client/CryptoDoc.cpp @@ -278,8 +278,12 @@ ContainerState CryptoDoc::state() const return d->isEncrypted() ? EncryptedContainer : UnencryptedContainer; } -bool CryptoDoc::decrypt(const libcdoc::Lock *lock, const QByteArray& secret) +bool CryptoDoc::decrypt(const libcdoc::Lock *lock, QByteArray secret) { + auto secretGuard = qScopeGuard([&] { + d->crypto.clearSecret(); + secret.fill(0); + }); if(!d->reader) { WarningDialog::create() @@ -321,8 +325,7 @@ bool CryptoDoc::decrypt(const libcdoc::Lock *lock, const QByteArray& secret) return false; } - d->crypto.secret.assign(secret.cbegin(), secret.cend()); - + d->crypto.setSecret(std::move(secret)); TempListConsumer cons; libcdoc::result_t result = waitFor([&]{ std::vector fmk; @@ -337,6 +340,7 @@ bool CryptoDoc::decrypt(const libcdoc::Lock *lock, const QByteArray& secret) qCDebug(CRYPTO) << "Decryption result: " << result << " " << QString::fromStdString(d->reader->getLastErrorStr()); return result; }); + d->crypto.clearSecret(); if (result != libcdoc::OK) { QString str; const std::string &msg = d->reader->getLastErrorStr(); @@ -390,8 +394,13 @@ bool CryptoDoc::decrypt(const libcdoc::Lock *lock, const QByteArray& secret) DocumentModel *CryptoDoc::documentModel() const { return d->documents; } -bool CryptoDoc::encrypt(const QString &filename, const QString& label, const QByteArray& secret) +bool CryptoDoc::encrypt(const QString &filename, const QString& label, QByteArray secret) { + const bool encryptWithPassword = !secret.isEmpty(); + auto secretGuard = qScopeGuard([&] { + d->crypto.clearSecret(); + secret.fill(0); + }); // I think the correct semantics is to fail if container is already encrypted if(d->reader) return false; @@ -405,7 +414,7 @@ bool CryptoDoc::encrypt(const QString &filename, const QString& label, const QBy ->open(); return false; } - if(secret.isEmpty() && d->keys.empty()) + if(!encryptWithPassword && d->keys.empty()) { WarningDialog::create() ->withTitle(tr("Failed to encrypt document")) @@ -414,6 +423,7 @@ bool CryptoDoc::encrypt(const QString &filename, const QString& label, const QBy return false; } QString writer_last_error; + d->crypto.setSecret(std::move(secret)); libcdoc::result_t result = waitFor([&] -> libcdoc::result_t { qCDebug(CRYPTO) << "Encrypt" << d->fileName; auto writer = std::unique_ptr(libcdoc::CDocWriter::createWriter(d->version, d->fileName.toStdString(), &d->conf, &d->crypto, &d->network)); @@ -442,9 +452,8 @@ bool CryptoDoc::encrypt(const QString &filename, const QString& label, const QBy } } // Encrypt with symmetric key - if (!secret.isEmpty()) { + if (encryptWithPassword) { // NIST recommends at least 600000 iterations for PBKDF2 with SHA-256, see https://csrc.nist.gov/publications/detail/sp/800-132/final - d->crypto.secret.assign(secret.cbegin(), secret.cend()); libcdoc::Recipient rcpt = libcdoc::Recipient::makeSymmetric({}, 600000); rcpt.setLabelValue(libcdoc::CDoc2::Label::LABEL, label.toStdString()); enc_keys.push_back(std::move(rcpt)); @@ -458,7 +467,7 @@ bool CryptoDoc::encrypt(const QString &filename, const QString& label, const QBy QFile::remove(d->fileName); return result; }); - d->crypto.secret.clear(); + d->crypto.clearSecret(); if (result != libcdoc::OK) { WarningDialog::create() ->withTitle(tr("Failed to encrypt document")) diff --git a/client/CryptoDoc.h b/client/CryptoDoc.h index 3a0c2da71..7ed183e23 100644 --- a/client/CryptoDoc.h +++ b/client/CryptoDoc.h @@ -63,8 +63,8 @@ class CryptoDoc final: public QObject bool addEncryptionKey(const CKey& key); bool canDecrypt(const QSslCertificate &cert); void clear(const QString &file = {}, int version = -1); - bool decrypt(const libcdoc::Lock *lock, const QByteArray& secret); - bool encrypt(const QString &filename = {}, const QString& label = {}, const QByteArray& secret = {}); + bool decrypt(const libcdoc::Lock *lock, QByteArray secret); + bool encrypt(const QString &filename = {}, const QString& label = {}, QByteArray secret = {}); DocumentModel* documentModel() const; QString fileName() const; const std::vector& keys() const; diff --git a/client/QCNG.cpp b/client/QCNG.cpp index 7da8c4b1a..b712a5b7b 100644 --- a/client/QCNG.cpp +++ b/client/QCNG.cpp @@ -28,6 +28,8 @@ #include #include +#include + using namespace Qt::Literals::StringLiterals; Q_LOGGING_CATEGORY(CNG, "qdigidoc4.QCNG") @@ -67,9 +69,9 @@ QCNG::Status QCNG::login(const TokenData &token) return PinOK; } -QByteArray QCNG::decrypt(const QByteArray &data, bool oaep) const +std::vector QCNG::decrypt(const QByteArray &data, bool oaep) const { - return exec([&](NCRYPT_PROV_HANDLE prov, NCRYPT_KEY_HANDLE key, QByteArray &result) { + return exec>([&](NCRYPT_PROV_HANDLE prov, NCRYPT_KEY_HANDLE key, std::vector &result) { BCRYPT_OAEP_PADDING_INFO padding {BCRYPT_SHA256_ALGORITHM, nullptr, 0}; PVOID paddingInfo = oaep ? &padding : nullptr; DWORD flags = oaep ? NCRYPT_PAD_OAEP_FLAG : NCRYPT_PAD_PKCS1_FLAG; @@ -78,19 +80,19 @@ QByteArray QCNG::decrypt(const QByteArray &data, bool oaep) const paddingInfo, nullptr, 0, &size, flags); if(FAILED(err)) return err; - result.resize(int(size)); + result.resize(size); err = NCryptDecrypt(key, PBYTE(data.constData()), DWORD(data.size()), paddingInfo, PBYTE(result.data()), DWORD(result.size()), &size, flags); if(SUCCEEDED(err)) - result.resize(int(size)); + result.resize(size); return err; }); } template -QByteArray QCNG::derive(const QByteArray &publicKey, F &&func) const +std::vector QCNG::derive(const QByteArray &publicKey, F &&func) const { - return exec([&](NCRYPT_PROV_HANDLE prov, NCRYPT_KEY_HANDLE key, QByteArray &derived) { + return exec>([&](NCRYPT_PROV_HANDLE prov, NCRYPT_KEY_HANDLE key, std::vector &derived) { BCRYPT_ECCKEY_BLOB oh { BCRYPT_ECDH_PUBLIC_P384_MAGIC, ULONG((publicKey.size() - 1) / 2) }; switch((publicKey.size() - 1) * 4) { @@ -111,10 +113,10 @@ QByteArray QCNG::derive(const QByteArray &publicKey, F &&func) const }); } -QByteArray QCNG::deriveConcatKDF(const QByteArray &publicKey, QCryptographicHash::Algorithm digest, +std::vector QCNG::deriveConcatKDF(const QByteArray &publicKey, QCryptographicHash::Algorithm digest, const QByteArray &algorithmID, const QByteArray &partyUInfo, const QByteArray &partyVInfo) const { - return derive(publicKey, [&](NCRYPT_SECRET_HANDLE sharedSecret, QByteArray &derived) { + return derive(publicKey, [&](NCRYPT_SECRET_HANDLE sharedSecret, std::vector &derived) { std::array paramValues{ BCryptBuffer{ULONG(algorithmID.size()), KDF_ALGORITHMID, PBYTE(algorithmID.data())}, BCryptBuffer{ULONG(partyUInfo.size()), KDF_PARTYUINFO, PBYTE(partyUInfo.data())}, @@ -135,16 +137,16 @@ QByteArray QCNG::deriveConcatKDF(const QByteArray &publicKey, QCryptographicHash SECURITY_STATUS err {}; if(FAILED(err = NCryptDeriveKey(sharedSecret, BCRYPT_KDF_SP80056A_CONCAT, ¶ms, nullptr, 0, &size, 0))) return err; - derived.resize(int(size)); + derived.resize(size); if(SUCCEEDED(err = NCryptDeriveKey(sharedSecret, BCRYPT_KDF_SP80056A_CONCAT, ¶ms, PBYTE(derived.data()), size, &size, 0))) derived.resize(32); return err; }); } -QByteArray QCNG::deriveHMACExtract(const QByteArray &publicKey, const QByteArray &salt, int keySize) const +std::vector QCNG::deriveHMACExtract(const QByteArray &publicKey, const QByteArray &salt, int keySize) const { - return derive(publicKey, [&](NCRYPT_SECRET_HANDLE sharedSecret, QByteArray &derived) { + return derive(publicKey, [&](NCRYPT_SECRET_HANDLE sharedSecret, std::vector &derived) { std::array paramValues{ BCryptBuffer{ULONG(salt.size()), KDF_HMAC_KEY, PBYTE(salt.data())}, BCryptBuffer{ULONG(sizeof(BCRYPT_SHA256_ALGORITHM)), KDF_HASH_ALGORITHM, PBYTE(BCRYPT_SHA256_ALGORITHM)}, @@ -154,20 +156,20 @@ QByteArray QCNG::deriveHMACExtract(const QByteArray &publicKey, const QByteArray SECURITY_STATUS err = 0; if(FAILED(err = NCryptDeriveKey(sharedSecret, BCRYPT_KDF_HMAC, ¶ms, nullptr, 0, &size, 0))) return err; - derived.resize(int(size)); + derived.resize(size); if(SUCCEEDED(err = NCryptDeriveKey(sharedSecret, BCRYPT_KDF_HMAC, ¶ms, PBYTE(derived.data()), size, &size, 0))) derived.resize(keySize); return err; }); } -template -QByteArray QCNG::exec(F &&func) const +template +Result QCNG::exec(F &&func) const { if (!d) return {}; status = UnknownError; - QByteArray result; + Result result; switch(func(d->prov, d->key, result)) { case ERROR_SUCCESS: @@ -177,6 +179,7 @@ QByteArray QCNG::exec(F &&func) const case ERROR_CANCELLED: status = PinCanceled; default: + std::fill(result.begin(), result.end(), 0); return {}; } } diff --git a/client/QCNG.h b/client/QCNG.h index 7b060e9e7..0e1f64d55 100644 --- a/client/QCNG.h +++ b/client/QCNG.h @@ -33,18 +33,18 @@ class QCNG final: public QCryptoBackend Status login(const TokenData &token) final; - QByteArray decrypt(const QByteArray &data, bool oaep) const final; - QByteArray deriveConcatKDF(const QByteArray &publicKey, QCryptographicHash::Algorithm digest, + std::vector decrypt(const QByteArray &data, bool oaep) const final; + std::vector deriveConcatKDF(const QByteArray &publicKey, QCryptographicHash::Algorithm digest, const QByteArray &algorithmID, const QByteArray &partyUInfo, const QByteArray &partyVInfo) const final; - QByteArray deriveHMACExtract(const QByteArray &publicKey, const QByteArray &salt, int keySize) const final; + std::vector deriveHMACExtract(const QByteArray &publicKey, const QByteArray &salt, int keySize) const final; QByteArray sign(QCryptographicHash::Algorithm type, const QByteArray &digest) const final; static QList tokens(); private: template - QByteArray derive(const QByteArray &publicKey, F &&func) const; - template - QByteArray exec(F &&func) const; + std::vector derive(const QByteArray &publicKey, F &&func) const; + template + Result exec(F &&func) const; struct Private; std::unique_ptr d; diff --git a/client/QCryptoBackend.h b/client/QCryptoBackend.h index cac9b42ca..a4d6c38f8 100644 --- a/client/QCryptoBackend.h +++ b/client/QCryptoBackend.h @@ -23,7 +23,9 @@ #include +#include #include +#include class TokenData; class QSslKey; @@ -45,10 +47,10 @@ class QCryptoBackend virtual ~QCryptoBackend(); - virtual QByteArray decrypt(const QByteArray &data, bool oaep) const = 0; - virtual QByteArray deriveConcatKDF(const QByteArray &publicKey, QCryptographicHash::Algorithm digest, + virtual std::vector decrypt(const QByteArray &data, bool oaep) const = 0; + virtual std::vector deriveConcatKDF(const QByteArray &publicKey, QCryptographicHash::Algorithm digest, const QByteArray &algorithmID, const QByteArray &partyUInfo, const QByteArray &partyVInfo) const = 0; - virtual QByteArray deriveHMACExtract(const QByteArray &publicKey, const QByteArray &salt, int keySize) const = 0; + virtual std::vector deriveHMACExtract(const QByteArray &publicKey, const QByteArray &salt, int keySize) const = 0; virtual QByteArray sign(QCryptographicHash::Algorithm method, const QByteArray &digest) const = 0; /** diff --git a/client/QPCSC.cpp b/client/QPCSC.cpp index 3999ecd5e..a40258519 100644 --- a/client/QPCSC.cpp +++ b/client/QPCSC.cpp @@ -30,6 +30,16 @@ static Q_LOGGING_CATEGORY(APDU,"QPCSC.APDU") static Q_LOGGING_CATEGORY(SCard,"QPCSC.SCard") +static QByteArray formatAPDU(const QByteArray &apdu, bool sensitive) +{ + if(!sensitive || apdu.size() <= 5) + return apdu.toHex(); + + const qsizetype dataSize = quint8(apdu[4]); + return apdu.left(5).toHex() + QByteArrayLiteral("[redacted]") + + apdu.mid(5 + dataSize).toHex(); +} + static quint16 toUInt16(const QByteArray &data, int size) { return size >= 2 ? quint16((quint16(data[size - 2]) << 8) | quint8(data[size - 1])) : 0; @@ -284,12 +294,12 @@ QStringList QPCSCReader::state() const return stateToString(d->state.dwEventState); } -QPCSCReader::Result QPCSCReader::transfer( const QByteArray &apdu ) const +QPCSCReader::Result QPCSCReader::transfer(const QByteArray &apdu, bool sensitive) const { QByteArray data( 1024, 0 ); auto size = DWORD(data.size()); - qCDebug(APDU).nospace().noquote() << 'T' << d->io.dwProtocol - 1 << "> " << apdu.toHex(); + qCDebug(APDU).nospace().noquote() << 'T' << d->io.dwProtocol - 1 << "> " << formatAPDU(apdu, sensitive); LONG ret = SC(Transmit, d->card, &d->io, LPCBYTE(apdu.constData()), DWORD(apdu.size()), nullptr, LPBYTE(data.data()), &size); if( ret != SCARD_S_SUCCESS ) @@ -304,16 +314,18 @@ QPCSCReader::Result QPCSCReader::transfer( const QByteArray &apdu ) const case 0x6100: // Read more { QByteArray cmd( "\x00\xC0\x00\x00\x00", 5 ); + auto cmdGuard = qScopeGuard([&] { if(sensitive) cmd.fill(0); }); cmd[4] = char(result.SW); - Result result2 = transfer( cmd ); + Result result2 = transfer(cmd, sensitive); result2.data.prepend(result.data); return result2; } case 0x6C00: // Excpected lenght { QByteArray cmd = apdu; + auto cmdGuard = qScopeGuard([&] { if(sensitive) cmd.fill(0); }); cmd[4] = char(result.SW); - return transfer(cmd); + return transfer(cmd, sensitive); } default: return result; } @@ -347,6 +359,7 @@ QPCSCReader::Result QPCSCReader::transferCTL(const QByteArray &apdu, bool verify }; QByteArray cmd; + auto cmdGuard = qScopeGuard([&] { cmd.fill(0); }); if( verify ) { PIN_VERIFY_STRUCTURE data{}; @@ -381,8 +394,8 @@ QPCSCReader::Result QPCSCReader::transferCTL(const QByteArray &apdu, bool verify if( !ioctl ) ioctl = features.value( verify ? FEATURE_VERIFY_PIN_DIRECT : FEATURE_MODIFY_PIN_DIRECT ); - qCDebug(APDU).nospace().noquote() << 'T' << d->io.dwProtocol - 1 << "> " << apdu.toHex(); - qCDebug(APDU).nospace().noquote() << "CTL" << "> " << cmd.toHex(); + qCDebug(APDU).nospace().noquote() << 'T' << d->io.dwProtocol - 1 << "> " << formatAPDU(apdu, true); + qCDebug(APDU).nospace().noquote() << "CTL> " << cmd.left(cmd.size() - apdu.size()).toHex() << formatAPDU(apdu, true); QByteArray data( 255 + 3, 0 ); auto size = DWORD(data.size()); LONG err = SC(Control, d->card, ioctl, cmd.constData(), DWORD(cmd.size()), LPVOID(data.data()), DWORD(data.size()), &size); diff --git a/client/QPCSC.h b/client/QPCSC.h index 5e313b30a..26cd2ba2d 100644 --- a/client/QPCSC.h +++ b/client/QPCSC.h @@ -108,7 +108,7 @@ class QPCSCReader final: public QObject QStringList state() const; bool connect( Connect connect = Shared, Mode mode = Mode(T0|T1) ); - Result transfer( const QByteArray &apdu ) const; + Result transfer(const QByteArray &apdu, bool sensitive = false) const; Result transferCTL(const QByteArray &apdu, bool verify, quint16 lang = 0, quint8 minlen = 4, quint8 newPINOffset = 0, bool requestCurrentPIN = true) const; diff --git a/client/QPKCS11.cpp b/client/QPKCS11.cpp index 919ce2eaf..aea6bc224 100644 --- a/client/QPKCS11.cpp +++ b/client/QPKCS11.cpp @@ -31,6 +31,10 @@ #include #include #include +#include +#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0) +#include +#endif #include @@ -38,6 +42,7 @@ #include #include +#include #include #include @@ -52,7 +57,8 @@ struct QPKCS11Library explicit QPKCS11Library(const QString &driver); ~QPKCS11Library(); - QByteArray attribute(CK_SESSION_HANDLE session, CK_OBJECT_HANDLE obj, CK_ATTRIBUTE_TYPE type) const; + template + Result attribute(CK_SESSION_HANDLE session, CK_OBJECT_HANDLE obj, CK_ATTRIBUTE_TYPE type) const; std::vector findObject(CK_SESSION_HANDLE session, CK_OBJECT_CLASS cls, const QByteArray &id = {}) const; static std::shared_ptr current(); @@ -180,9 +186,10 @@ std::shared_ptr QPKCS11Library::current() return loadLibrary(drivers.key({})); } -QByteArray QPKCS11Library::attribute(CK_SESSION_HANDLE session, CK_OBJECT_HANDLE obj, CK_ATTRIBUTE_TYPE type) const +template +Result QPKCS11Library::attribute(CK_SESSION_HANDLE session, CK_OBJECT_HANDLE obj, CK_ATTRIBUTE_TYPE type) const { - QByteArray data; + Result data; CK_ATTRIBUTE attr { type, nullptr, 0 }; if(!f || f->C_GetAttributeValue( session, obj, &attr, 1 ) != CKR_OK) return data; @@ -247,7 +254,7 @@ QPKCS11::~QPKCS11() noexcept d->closeSession(); } -QByteArray QPKCS11::decrypt(const QByteArray &data, bool oaep) const +std::vector QPKCS11::decrypt(const QByteArray &data, bool oaep) const { auto key = d->l->findObject(d->session, CKO_PRIVATE_KEY, d->id); if(key.size() != 1){ @@ -269,16 +276,18 @@ QByteArray QPKCS11::decrypt(const QByteArray &data, bool oaep) const status = GeneralError; return {}; } - QByteArray result(int(size), 0); + std::vector result(size); if(d->l->f->C_Decrypt(d->session, CK_BYTE_PTR(data.constData()), CK_ULONG(data.size()), CK_BYTE_PTR(result.data()), &size) != CKR_OK) { + std::fill(result.begin(), result.end(), 0); status = GeneralError; return {}; } + result.resize(size); return result; } -QByteArray QPKCS11::derive(const QByteArray &publicKey) const +std::vector QPKCS11::derive(const QByteArray &publicKey) const { std::vector key = d->l->findObject(d->session, CKO_PRIVATE_KEY, d->id); if(key.size() != 1) { @@ -307,41 +316,73 @@ QByteArray QPKCS11::derive(const QByteArray &publicKey) const return {}; } - return d->l->attribute(d->session, newkey, CKA_VALUE); + return d->l->attribute>(d->session, newkey, CKA_VALUE); } -QByteArray QPKCS11::deriveConcatKDF(const QByteArray &publicKey, QCryptographicHash::Algorithm digest, +std::vector QPKCS11::deriveConcatKDF(const QByteArray &publicKey, QCryptographicHash::Algorithm digest, const QByteArray &algorithmID, const QByteArray &partyUInfo, const QByteArray &partyVInfo) const { - QByteArray z = derive(publicKey); - if(z.isEmpty()) + auto z = derive(publicKey); + if(z.empty()) return z; - QByteArray otherInfo = algorithmID + partyUInfo + partyVInfo; - quint32 keyDataLen = 32; - auto hashLen = quint32(QCryptographicHash::hashLength(digest)); - auto reps = quint32(std::ceil(double(keyDataLen) / double(hashLen))); + auto zGuard = qScopeGuard([&] { std::fill(z.begin(), z.end(), 0); }); + constexpr size_t keyDataLen = 32; + const int hashLen = QCryptographicHash::hashLength(digest); + if(hashLen <= 0) { + status = GeneralError; + return {}; + } + const size_t reps = (keyDataLen + size_t(hashLen) - 1) / size_t(hashLen); + std::vector key; + key.reserve(reps * size_t(hashLen)); +#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0) + key.resize(reps * size_t(hashLen)); + for(size_t i = 0; i < reps; ++i) { + quint32 counter = qToBigEndian(quint32(i + 1)); + const std::array input{ + QByteArrayView(reinterpret_cast(&counter), sizeof(counter)), + QByteArrayView(z), + QByteArrayView(algorithmID), + QByteArrayView(partyUInfo), + QByteArrayView(partyVInfo) + }; + QSpan output(reinterpret_cast(key.data() + i * size_t(hashLen)), hashLen); + if(QCryptographicHash::hashInto(output, QSpan(input), digest).size() != hashLen) { + std::fill(key.begin(), key.end(), 0); + status = GeneralError; + return {}; + } + } +#else QCryptographicHash md(digest); - QByteArray key; - for(quint32 i = 1; i <= reps; i++) - { - quint32 intToFourBytes = qToBigEndian(i); + for(size_t i = 0; i < reps; ++i) { + quint32 counter = qToBigEndian(quint32(i + 1)); md.reset(); - md.addData((const char*)&intToFourBytes, 4); - md.addData(z); - md.addData(otherInfo); - key += md.result(); + md.addData(reinterpret_cast(&counter), sizeof(counter)); + md.addData(QByteArray::fromRawData(reinterpret_cast(z.data()), qsizetype(z.size()))); + md.addData(algorithmID); + md.addData(partyUInfo); + md.addData(partyVInfo); + QByteArray block = md.result(); + auto blockGuard = qScopeGuard([&] { block.fill(0); }); + key.insert(key.end(), reinterpret_cast(block.constData()), + reinterpret_cast(block.constData() + block.size())); } - return key.left(int(keyDataLen)); +#endif + std::fill(key.begin() + keyDataLen, key.end(), 0); + key.resize(keyDataLen); + return key; } -QByteArray QPKCS11::deriveHMACExtract(const QByteArray &publicKey, const QByteArray &salt, int keySize) const +std::vector QPKCS11::deriveHMACExtract(const QByteArray &publicKey, const QByteArray &salt, int keySize) const { - QByteArray key = derive(publicKey); - if(key.isEmpty()) + auto key = derive(publicKey); + if(key.empty()) return key; + auto keyGuard = qScopeGuard([&] { std::fill(key.begin(), key.end(), 0); }); auto ctx = libcdoc::make_unique_ptr(EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, nullptr)); - QByteArray out(keySize, 0); - auto outlen = size_t(out.length()); + std::vector out(static_cast(keySize)); + auto outlen = out.size(); auto isError = [this](int err) { if(err < 1) { @@ -358,8 +399,12 @@ QByteArray QPKCS11::deriveHMACExtract(const QByteArray &publicKey, const QByteAr isError(EVP_PKEY_CTX_set_hkdf_md(ctx.get(), EVP_sha256())) || isError(EVP_PKEY_CTX_set1_hkdf_key(ctx.get(), (const unsigned char*)key.data(), int(key.size()))) || isError(EVP_PKEY_CTX_set1_hkdf_salt(ctx.get(), (const unsigned char*)salt.data(), int(salt.size()))) || - isError(EVP_PKEY_derive(ctx.get(), (unsigned char*)out.data(), &outlen))) + isError(EVP_PKEY_derive(ctx.get(), out.data(), &outlen))) { + std::fill(out.begin(), out.end(), 0); out.clear(); + } else { + out.resize(outlen); + } return out; } @@ -407,7 +452,8 @@ QPKCS11::Status QPKCS11::login(const TokenData &t) p.setPinLen(token.ulMinPinLen, token.ulMaxPinLen < 12 ? 12 : token.ulMaxPinLen); if(!p.exec()) return CKR_FUNCTION_CANCELED; - QByteArray pin = p.pin().toUtf8(); + QByteArray pin = p.takePin(); + auto pinGuard = qScopeGuard([&] { pin.fill(0); }); return d->l->f->C_Login(d->session, CKU_USER, CK_UTF8CHAR_PTR(pin.constData()), CK_ULONG(pin.size())); } }); diff --git a/client/QPKCS11.h b/client/QPKCS11.h index c01994018..293eed942 100644 --- a/client/QPKCS11.h +++ b/client/QPKCS11.h @@ -29,11 +29,11 @@ class QPKCS11 final: public QCryptoBackend explicit QPKCS11(); ~QPKCS11() noexcept final; - QByteArray decrypt(const QByteArray &data, bool oaep) const final; - QByteArray derive(const QByteArray &publicKey) const; - QByteArray deriveConcatKDF(const QByteArray &publicKey, QCryptographicHash::Algorithm digest, + std::vector decrypt(const QByteArray &data, bool oaep) const final; + std::vector derive(const QByteArray &publicKey) const; + std::vector deriveConcatKDF(const QByteArray &publicKey, QCryptographicHash::Algorithm digest, const QByteArray &algorithmID, const QByteArray &partyUInfo, const QByteArray &partyVInfo) const final; - QByteArray deriveHMACExtract(const QByteArray &publicKey, const QByteArray &salt, int keySize) const final; + std::vector deriveHMACExtract(const QByteArray &publicKey, const QByteArray &salt, int keySize) const final; QByteArray sign(QCryptographicHash::Algorithm type, const QByteArray &digest) const final; Status login(const TokenData &t) final; diff --git a/client/QSmartCard.cpp b/client/QSmartCard.cpp index 764cc0ef4..8fc2e8a88 100644 --- a/client/QSmartCard.cpp +++ b/client/QSmartCard.cpp @@ -111,7 +111,7 @@ QPCSCReader::Result Card::transfer(bool verify, QByteArray &&apdu, apdu.fill('0'); }); if(!reader.isPinPad()) - return reader.transfer(apdu); + return reader.transfer(apdu, true); quint16 language = 0x0000; if(Settings::LANGUAGE == QLatin1String("en")) language = 0x0409; else if(Settings::LANGUAGE == QLatin1String("et")) language = 0x0425; @@ -133,9 +133,8 @@ QByteArrayView Card::parseFCI(QByteArrayView data, quint8 expectedTag) return {}; } -QByteArray Card::pinTemplate(const QString &data) const +QByteArray Card::pinTemplate(QByteArray pin) const { - QByteArray pin = data.toUtf8(); #if QT_VERSION >= QT_VERSION_CHECK(6, 4, 0) pin.resize(12, fillChar); #else @@ -553,17 +552,9 @@ bool QSmartCard::pinChange(QSmartCardData::PinType type, QSmartCard::PinAction a d->t.data(QSmartCardData::Id).toString(), d->t.isPUKReplacable(), parent); if (!p.exec()) return false; - QString oldPinString = p.firstCodeText(); - QString newPinString = p.newCodeText(); - oldPin = card->pinTemplate(oldPinString); - newPin = card->pinTemplate(newPinString); - // Try to clean QLineEdit internal PIN copy using constData that does not detach memory - auto chars = const_cast(oldPinString.constData()); - for (int i = 0; i < oldPinString.length(); ++i) - chars[i] = '\0'; - chars = const_cast(newPinString.constData()); - for (int i = 0; i < newPinString.length(); ++i) - chars[i] = '\0'; + auto [oldCode, newCode] = p.takeCodes(); + oldPin = card->pinTemplate(std::move(oldCode)); + newPin = card->pinTemplate(std::move(newCode)); } auto clean = qScopeGuard([&oldPin, &newPin] { oldPin.fill('\0'); @@ -692,4 +683,3 @@ void QSmartCard::reloadCard(const TokenData &token, bool reloadCounters) } TokenData QSmartCard::tokenData() const { return d->token; } - diff --git a/client/QSmartCard_p.h b/client/QSmartCard_p.h index af62b11d8..a606fd533 100644 --- a/client/QSmartCard_p.h +++ b/client/QSmartCard_p.h @@ -35,7 +35,7 @@ struct Card virtual ~Card() noexcept = default; virtual QPCSCReader::Result change(QSmartCardData::PinType type, const QByteArray &pin, const QByteArray &newpin) const = 0; virtual bool loadPerso(QSmartCardDataPrivate *d) const = 0; - virtual QByteArray pinTemplate(const QString &pin) const; + virtual QByteArray pinTemplate(QByteArray pin) const; virtual QPCSCReader::Result replace(QSmartCardData::PinType type, const QByteArray &puk, const QByteArray &pin) const = 0; QPCSCReader::Result transfer(bool verify, QByteArray &&apdu, QSmartCardData::PinType type, quint8 newPINOffset, bool requestCurrentPIN) const; diff --git a/client/dialogs/PinPopup.cpp b/client/dialogs/PinPopup.cpp index 5a0646a08..bda7a230d 100644 --- a/client/dialogs/PinPopup.cpp +++ b/client/dialogs/PinPopup.cpp @@ -122,13 +122,23 @@ PinPopup::PinPopup(QSmartCardData::PinType type, TokenFlags flags, const SslCert pinInput = ui.pin; } +PinPopup::~PinPopup() +{ + pinInput->clearSensitive(); +} + void PinPopup::setPinLen(unsigned long minLen, unsigned long maxLen) { QString charPattern = regexp->regularExpression().pattern().startsWith(QLatin1String("^.")) ? QStringLiteral(".") : QStringLiteral("\\d"); regexp->setRegularExpression(QRegularExpression(QStringLiteral("^%1{%2,%3}$").arg(charPattern).arg(minLen).arg(maxLen))); } -QString PinPopup::pin() const { return pinInput->text(); } +QByteArray PinPopup::takePin() +{ + QByteArray pin = pinInput->text().toUtf8(); + pinInput->clearSensitive(); + return pin; +} void PinPopup::reject() { diff --git a/client/dialogs/PinPopup.h b/client/dialogs/PinPopup.h index 08f8d9e3a..58bfa9683 100644 --- a/client/dialogs/PinPopup.h +++ b/client/dialogs/PinPopup.h @@ -24,7 +24,7 @@ #include "QSmartCard.h" #include "WaitDialog.h" -class QLineEdit; +class LineEdit; class QRegularExpressionValidator; class SslCertificate; @@ -44,9 +44,10 @@ class PinPopup final : public QDialog Q_DECLARE_FLAGS(TokenFlags, TokenFlag) PinPopup(QSmartCardData::PinType type, TokenFlags flags, const SslCertificate &cert, QWidget *parent = {}, QString text = {}); + ~PinPopup() final; void setPinLen(unsigned long minLen, unsigned long maxLen = 12); - QString pin() const; + QByteArray takePin(); signals: void startTimer(); @@ -54,9 +55,8 @@ class PinPopup final : public QDialog private: void reject() final; - QLineEdit *pinInput; + LineEdit *pinInput; QRegularExpressionValidator *regexp {}; WaitDialogHider hider; bool isPinPad = false; }; - diff --git a/client/dialogs/PinUnblock.cpp b/client/dialogs/PinUnblock.cpp index 5830d060d..290f08031 100644 --- a/client/dialogs/PinUnblock.cpp +++ b/client/dialogs/PinUnblock.cpp @@ -197,10 +197,20 @@ PinUnblock::PinUnblock(QSmartCardData::PinType type, QSmartCard::PinAction actio PinUnblock::~PinUnblock() { + clearCodes(); delete ui; } -QString PinUnblock::firstCodeText() const { return ui->puk->text(); } - -QString PinUnblock::newCodeText() const { return ui->pin->text(); } +std::pair PinUnblock::takeCodes() +{ + std::pair codes{ui->puk->text().toUtf8(), ui->pin->text().toUtf8()}; + clearCodes(); + return codes; +} +void PinUnblock::clearCodes() +{ + ui->puk->clearSensitive(); + ui->pin->clearSensitive(); + ui->repeat->clearSensitive(); +} diff --git a/client/dialogs/PinUnblock.h b/client/dialogs/PinUnblock.h index 862e6c929..9238db152 100644 --- a/client/dialogs/PinUnblock.h +++ b/client/dialogs/PinUnblock.h @@ -35,9 +35,10 @@ class PinUnblock final : public QDialog short leftAttempts, QDate birthDate, const QString &personalCode, bool isPUKReplacable, QWidget *parent); ~PinUnblock() final; - QString firstCodeText() const; - QString newCodeText() const; + std::pair takeCodes(); private: + void clearCodes(); + Ui::PinUnblock *ui; }; diff --git a/client/widgets/ContainerPage.cpp b/client/widgets/ContainerPage.cpp index b10c37130..292e7444f 100644 --- a/client/widgets/ContainerPage.cpp +++ b/client/widgets/ContainerPage.cpp @@ -207,9 +207,9 @@ void ContainerPage::changeEvent(QEvent* event) QWidget::changeEvent(event); } -void ContainerPage::decrypt(CryptoDoc *container, const libcdoc::Lock *lock, const QByteArray &secret) { +void ContainerPage::decrypt(CryptoDoc *container, const libcdoc::Lock *lock, QByteArray secret) { WaitDialogHolder waitDialog(this, tr("Decrypting")); - if (!container->decrypt(lock, secret)) + if (!container->decrypt(lock, std::move(secret))) return; transition(container, QSslCertificate{}); emit action(DecryptContainerSuccess, {}, {}); diff --git a/client/widgets/ContainerPage.h b/client/widgets/ContainerPage.h index e301dfae7..5f6550262 100644 --- a/client/widgets/ContainerPage.h +++ b/client/widgets/ContainerPage.h @@ -60,7 +60,7 @@ class ContainerPage final : public QWidget private: void changeEvent(QEvent* event) final; void clear(int code); - void decrypt(CryptoDoc *container, const libcdoc::Lock *lock, const QByteArray &secret); + void decrypt(CryptoDoc *container, const libcdoc::Lock *lock, QByteArray secret); template void deleteConfirm(C *c, int index); void elideFileName(); diff --git a/client/widgets/LineEdit.cpp b/client/widgets/LineEdit.cpp index a75135f1c..ab3046e6e 100644 --- a/client/widgets/LineEdit.cpp +++ b/client/widgets/LineEdit.cpp @@ -26,6 +26,17 @@ LineEdit::LineEdit(QWidget *parent) : QLineEdit(parent) {} +void LineEdit::clearSensitive() +{ + QString value = text(); + // QString is implicitly shared with QLineEdit here. Bypass detachment to + // overwrite the widget's backing storage before clearing its text history. + QChar *chars = const_cast(value.constData()); + for(qsizetype i = 0; i < value.size(); ++i) + chars[i] = QChar(); + setText(QString()); +} + void LineEdit::paintEvent(QPaintEvent *event) { #if QT_VERSION < QT_VERSION_CHECK(6, 4, 1) diff --git a/client/widgets/LineEdit.h b/client/widgets/LineEdit.h index ce74aa521..8f5d0a985 100644 --- a/client/widgets/LineEdit.h +++ b/client/widgets/LineEdit.h @@ -29,6 +29,7 @@ class LineEdit: public QLineEdit explicit LineEdit(QWidget *parent = nullptr); + void clearSensitive(); QString label() const; void setLabel(QString _label);