// Copyright (C) 2022-2023 Jakub Melka // // This file is part of PDF4QT. // // PDF4QT is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as published by // the Free Software Foundation, either version 3 of the License, or // with the written consent of the copyright owner, any later version. // // PDF4QT is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public License // along with PDF4QT. If not, see . #include "pdfcertificatestore.h" #include "pdfutils.h" #if defined(PDF4QT_COMPILER_MINGW) || defined(PDF4QT_COMPILER_GCC) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wdeprecated-declarations" #endif #if defined(PDF4QT_COMPILER_MSVC) #pragma warning(push) #pragma warning(disable: 4996) #endif #include #include #include #include #include #include #include #include #include #include #include #include "pdfdbgheap.h" #include #ifdef Q_OS_UNIX #include #endif namespace pdf { QRecursiveMutex PDFOpenSSLGlobalLock::s_globalOpenSSLMutex; PDFOpenSSLGlobalLock::PDFOpenSSLGlobalLock() : m_mutexLocker(&s_globalOpenSSLMutex) { } void PDFCertificateEntry::serialize(QDataStream& stream) const { stream << persist_version; stream << type; stream << info; } void PDFCertificateEntry::deserialize(QDataStream& stream) { int persistVersionDeserialized = 0; stream >> persistVersionDeserialized; stream >> type; stream >> info; } void PDFCertificateInfo::serialize(QDataStream& stream) const { stream << persist_version; stream << m_version; stream << m_keySize; stream << m_publicKey; stream << m_nameEntries; stream << m_notValidBefore; stream << m_notValidAfter; stream << m_keyUsage; stream << m_certificateData; } void PDFCertificateInfo::deserialize(QDataStream& stream) { int persistVersionDeserialized = 0; stream >> persistVersionDeserialized; stream >> m_version; stream >> m_keySize; stream >> m_publicKey; stream >> m_nameEntries; stream >> m_notValidBefore; stream >> m_notValidAfter; stream >> m_keyUsage; stream >> m_certificateData; } QDateTime PDFCertificateInfo::getNotValidBefore() const { return m_notValidBefore; } void PDFCertificateInfo::setNotValidBefore(const QDateTime& notValidBefore) { m_notValidBefore = notValidBefore; } QDateTime PDFCertificateInfo::getNotValidAfter() const { return m_notValidAfter; } void PDFCertificateInfo::setNotValidAfter(const QDateTime& notValidAfter) { m_notValidAfter = notValidAfter; } int32_t PDFCertificateInfo::getVersion() const { return m_version; } void PDFCertificateInfo::setVersion(int32_t version) { m_version = version; } PDFCertificateInfo::PublicKey PDFCertificateInfo::getPublicKey() const { return m_publicKey; } void PDFCertificateInfo::setPublicKey(const PublicKey& publicKey) { m_publicKey = publicKey; } int PDFCertificateInfo::getKeySize() const { return m_keySize; } void PDFCertificateInfo::setKeySize(int keySize) { m_keySize = keySize; } PDFCertificateInfo::KeyUsageFlags PDFCertificateInfo::getKeyUsage() const { return m_keyUsage; } void PDFCertificateInfo::setKeyUsage(KeyUsageFlags keyUsage) { m_keyUsage = keyUsage; } std::optional PDFCertificateInfo::getCertificateInfo(const QByteArray& certificateData) { std::optional result; PDFOpenSSLGlobalLock lock; const unsigned char* data = convertByteArrayToUcharPtr(certificateData); if (X509* certificate = d2i_X509(nullptr, &data, certificateData.length())) { result = getCertificateInfo(certificate); X509_free(certificate); } return result; } PDFCertificateInfo PDFCertificateInfo::getCertificateInfo(x509_st* certificate) { PDFCertificateInfo info; auto getStringFromASN1_STRING = [](ASN1_STRING* string) -> QString { QString result; if (string) { // Jakub Melka: we must convert entry to UTF8 encoding using function ASN1_STRING_to_UTF8 unsigned char* utf8Buffer = nullptr; int errorCodeOrLength = ASN1_STRING_to_UTF8(&utf8Buffer, string); if (errorCodeOrLength > 0) { result = QString::fromUtf8(reinterpret_cast(utf8Buffer), errorCodeOrLength); } OPENSSL_free(utf8Buffer); } return result; }; auto getStringFromX509Name = [&getStringFromASN1_STRING](X509_NAME* name, int nid) -> QString { QString result; const int stringLocation = X509_NAME_get_index_by_NID(name, nid, -1); X509_NAME_ENTRY* entry = X509_NAME_get_entry(name, stringLocation); return getStringFromASN1_STRING(X509_NAME_ENTRY_get_data(entry)); }; auto getDateTimeFromASN = [](const ASN1_TIME* time) -> QDateTime { QDateTime result; if (time) { tm internalTime = { }; if (ASN1_TIME_to_tm(time, &internalTime) > 0) { #if defined(Q_OS_WIN) time_t localTime = _mkgmtime(&internalTime); #elif defined(Q_OS_UNIX) time_t localTime = timegm(&internalTime); #else static_assert(false, "Implement this for another OS!"); #endif result = QDateTime::fromSecsSinceEpoch(localTime, Qt::UTC); } } return result; }; if (X509_NAME* subjectName = X509_get_subject_name(certificate)) { // List of these properties are in RFC 5280, section 4.1.2.4, these attributes // are standard and all implementations must be prepared to process them. QString countryName = getStringFromX509Name(subjectName, NID_countryName); QString organizationName = getStringFromX509Name(subjectName, NID_organizationName); QString organizationalUnitName = getStringFromX509Name(subjectName, NID_organizationalUnitName); QString distinguishedName = getStringFromX509Name(subjectName, NID_distinguishedName); QString stateOrProvinceName = getStringFromX509Name(subjectName, NID_stateOrProvinceName); QString commonName = getStringFromX509Name(subjectName, NID_commonName); QString serialNumber = getStringFromX509Name(subjectName, NID_serialNumber); // These attributes are defined also in section 4.1.2.4, they are not mandatory, // but application should be able to process them. QString localityName = getStringFromX509Name(subjectName, NID_localityName); QString title = getStringFromX509Name(subjectName, NID_title); QString surname = getStringFromX509Name(subjectName, NID_surname); QString givenName = getStringFromX509Name(subjectName, NID_givenName); QString initials = getStringFromX509Name(subjectName, NID_initials); QString pseudonym = getStringFromX509Name(subjectName, NID_pseudonym); QString generationQualifier = getStringFromX509Name(subjectName, NID_generationQualifier); // This entry is not defined in section 4.1.2.4, but is commonly used QString email = getStringFromX509Name(subjectName, NID_pkcs9_emailAddress); info.setName(PDFCertificateInfo::CountryName, qMove(countryName)); info.setName(PDFCertificateInfo::OrganizationName, qMove(organizationName)); info.setName(PDFCertificateInfo::OrganizationalUnitName, qMove(organizationalUnitName)); info.setName(PDFCertificateInfo::DistinguishedName, qMove(distinguishedName)); info.setName(PDFCertificateInfo::StateOrProvinceName, qMove(stateOrProvinceName)); info.setName(PDFCertificateInfo::CommonName, qMove(commonName)); info.setName(PDFCertificateInfo::SerialNumber, qMove(serialNumber)); info.setName(PDFCertificateInfo::LocalityName, qMove(localityName)); info.setName(PDFCertificateInfo::Title, qMove(title)); info.setName(PDFCertificateInfo::Surname, qMove(surname)); info.setName(PDFCertificateInfo::GivenName, qMove(givenName)); info.setName(PDFCertificateInfo::Initials, qMove(initials)); info.setName(PDFCertificateInfo::Pseudonym, qMove(pseudonym)); info.setName(PDFCertificateInfo::GenerationalQualifier, qMove(generationQualifier)); info.setName(PDFCertificateInfo::Email, qMove(email)); const long version = X509_get_version(certificate); info.setVersion(version); const ASN1_TIME* notBeforeTime = X509_get0_notBefore(certificate); const ASN1_TIME* notAfterTime = X509_get0_notAfter(certificate); info.setNotValidBefore(getDateTimeFromASN(notBeforeTime)); info.setNotValidAfter(getDateTimeFromASN(notAfterTime)); X509_PUBKEY* publicKey = X509_get_X509_PUBKEY(certificate); EVP_PKEY* evpKey = X509_PUBKEY_get(publicKey); const int keyType = EVP_PKEY_type(EVP_PKEY_base_id(evpKey)); PDFCertificateInfo::PublicKey key = PDFCertificateInfo::KeyUnknown; switch (keyType) { case EVP_PKEY_RSA: key = PDFCertificateInfo::KeyRSA; break; case EVP_PKEY_DSA: key = PDFCertificateInfo::KeyDSA; break; case EVP_PKEY_DH: key = PDFCertificateInfo::KeyDH; break; case EVP_PKEY_EC: key = PDFCertificateInfo::KeyEC; break; default: break; } info.setPublicKey(key); const int bits = EVP_PKEY_bits(evpKey); info.setKeySize(bits); uint32_t keyUsage = X509_get_key_usage(certificate); if (keyUsage != UINT32_MAX) { static_assert(PDFCertificateInfo::KeyUsageDigitalSignature == KU_DIGITAL_SIGNATURE, "Fix this code!"); static_assert(PDFCertificateInfo::KeyUsageNonRepudiation == KU_NON_REPUDIATION, "Fix this code!"); static_assert(PDFCertificateInfo::KeyUsageKeyEncipherment == KU_KEY_ENCIPHERMENT, "Fix this code!"); static_assert(PDFCertificateInfo::KeyUsageDataEncipherment == KU_DATA_ENCIPHERMENT, "Fix this code!"); static_assert(PDFCertificateInfo::KeyUsageAgreement == KU_KEY_AGREEMENT, "Fix this code!"); static_assert(PDFCertificateInfo::KeyUsageCertSign == KU_KEY_CERT_SIGN, "Fix this code!"); static_assert(PDFCertificateInfo::KeyUsageCrlSign == KU_CRL_SIGN, "Fix this code!"); static_assert(PDFCertificateInfo::KeyUsageEncipherOnly == KU_ENCIPHER_ONLY, "Fix this code!"); static_assert(PDFCertificateInfo::KeyUsageDecipherOnly == KU_DECIPHER_ONLY, "Fix this code!"); if (X509_get_extension_flags(certificate) & EXFLAG_XKUSAGE) { const uint32_t extendedKeyUsage = X509_get_extended_key_usage(certificate); Q_ASSERT(extendedKeyUsage != UINT32_MAX); static_assert(PDFCertificateInfo::KeyUsageExtended_SSL_SERVER >> 16 == XKU_SSL_SERVER, "Fix this code!"); static_assert(PDFCertificateInfo::KeyUsageExtended_SSL_CLIENT >> 16 == XKU_SSL_CLIENT, "Fix this code!"); static_assert(PDFCertificateInfo::KeyUsageExtended_SMIME >> 16 == XKU_SMIME, "Fix this code!"); static_assert(PDFCertificateInfo::KeyUsageExtended_CODE_SIGN >> 16 == XKU_CODE_SIGN, "Fix this code!"); static_assert(PDFCertificateInfo::KeyUsageExtended_SGC >> 16 == XKU_SGC, "Fix this code!"); static_assert(PDFCertificateInfo::KeyUsageExtended_OCSP_SIGN >> 16 == XKU_OCSP_SIGN, "Fix this code!"); static_assert(PDFCertificateInfo::KeyUsageExtended_TIMESTAMP >> 16 == XKU_TIMESTAMP, "Fix this code!"); static_assert(PDFCertificateInfo::KeyUsageExtended_DVCS >> 16 == XKU_DVCS, "Fix this code!"); static_assert(PDFCertificateInfo::KeyUsageExtended_ANYEKU >> 16 == XKU_ANYEKU, "Fix this code!"); keyUsage = keyUsage | (extendedKeyUsage << 16); } info.setKeyUsage(static_cast(keyUsage)); } unsigned char* buffer = nullptr; int length = i2d_X509(certificate, &buffer); if (length >= 0) { Q_ASSERT(buffer); info.setCertificateData(QByteArray(reinterpret_cast(buffer), length)); OPENSSL_free(buffer); } } return info; } QByteArray PDFCertificateInfo::getCertificateData() const { return m_certificateData; } void PDFCertificateInfo::setCertificateData(const QByteArray& certificateData) { m_certificateData = certificateData; } void PDFCertificateStore::serialize(QDataStream& stream) const { stream << persist_version; stream << m_certificates; } void PDFCertificateStore::deserialize(QDataStream& stream) { int persistVersionDeserialized = 0; stream >> persistVersionDeserialized; stream >> m_certificates; } bool PDFCertificateStore::add(PDFCertificateEntry::EntryType type, const QByteArray& certificate) { if (auto certificateInfo = PDFCertificateInfo::getCertificateInfo(certificate)) { return add(type, qMove(*certificateInfo)); } return false; } bool PDFCertificateStore::add(PDFCertificateEntry::EntryType type, PDFCertificateInfo info) { auto it = std::find_if(m_certificates.cbegin(), m_certificates.cend(), [&info](const auto& entry) { return entry.info == info; }); if (it == m_certificates.cend()) { m_certificates.push_back({ type, qMove(info), QByteArray(), QString() }); } return true; } bool PDFCertificateStore::contains(const PDFCertificateInfo& info) { return std::find_if(m_certificates.cbegin(), m_certificates.cend(), [&info](const auto& entry) { return entry.info == info; }) != m_certificates.cend(); } QString PDFCertificateStore::getDefaultCertificateStoreFileName() const { return QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation) + "/TrustedCertStorage.bin"; } void PDFCertificateStore::loadDefaultUserCertificates() { createDirectoryForDefaultUserCertificatesStore(); QString trustedCertificateStoreFileName = getDefaultCertificateStoreFileName(); QString trustedCertificateStoreLockFileName = trustedCertificateStoreFileName + ".lock"; QLockFile lockFile(trustedCertificateStoreLockFileName); if (lockFile.lock()) { QFile trustedCertificateStoreFile(trustedCertificateStoreFileName); if (trustedCertificateStoreFile.open(QFile::ReadOnly)) { QDataStream stream(&trustedCertificateStoreFile); deserialize(stream); trustedCertificateStoreFile.close(); } lockFile.unlock(); } } void PDFCertificateStore::saveDefaultUserCertificates() { createDirectoryForDefaultUserCertificatesStore(); QString trustedCertificateStoreFileName = getDefaultCertificateStoreFileName(); QString trustedCertificateStoreLockFileName = trustedCertificateStoreFileName + ".lock"; QFileInfo fileInfo(trustedCertificateStoreFileName); QDir dir = fileInfo.dir(); dir.mkpath(dir.path()); QLockFile lockFile(trustedCertificateStoreLockFileName); if (lockFile.lock()) { QFile trustedCertificateStoreFile(trustedCertificateStoreFileName); if (trustedCertificateStoreFile.open(QFile::WriteOnly | QFile::Truncate)) { QDataStream stream(&trustedCertificateStoreFile); serialize(stream); trustedCertificateStoreFile.close(); } lockFile.unlock(); } } void PDFCertificateStore::createDirectoryForDefaultUserCertificatesStore() { QFileInfo fileInfo(getDefaultCertificateStoreFileName()); QString path = fileInfo.path(); QDir().mkpath(path); } } // namespace pdf #ifdef Q_OS_WIN #include #include #if defined(PDF4QT_USE_PRAGMA_LIB) #pragma comment(lib, "crypt32.lib") #endif #endif pdf::PDFCertificateEntries pdf::PDFCertificateStore::getSystemCertificates() { PDFCertificateEntries result; #ifdef Q_OS_WIN HCERTSTORE certStore = CertOpenSystemStore(0, L"ROOT"); PCCERT_CONTEXT context = nullptr; if (certStore) { while (context = CertEnumCertificatesInStore(certStore, context)) { const unsigned char* pointer = context->pbCertEncoded; QByteArray data(reinterpret_cast(pointer), context->cbCertEncoded); std::optional info = PDFCertificateInfo::getCertificateInfo(data); if (info) { PDFCertificateEntry entry; entry.type = PDFCertificateEntry::EntryType::System; entry.info = qMove(*info); result.emplace_back(qMove(entry)); } } CertCloseStore(certStore, CERT_CLOSE_STORE_FORCE_FLAG); } #endif return result; } pdf::PDFCertificateEntries pdf::PDFCertificateStore::getPersonalCertificates() { PDFCertificateEntries result; #ifdef Q_OS_WIN HCERTSTORE certStore = CertOpenStore(CERT_STORE_PROV_SYSTEM, 0, NULL, CERT_SYSTEM_STORE_CURRENT_USER, L"MY"); PCCERT_CONTEXT context = nullptr; if (certStore) { while (context = CertEnumCertificatesInStore(certStore, context)) { const unsigned char* pointer = context->pbCertEncoded; QByteArray data(reinterpret_cast(pointer), context->cbCertEncoded); std::optional info = PDFCertificateInfo::getCertificateInfo(data); if (info) { PDFCertificateEntry entry; entry.type = PDFCertificateEntry::EntryType::System; entry.info = qMove(*info); result.emplace_back(qMove(entry)); } } CertCloseStore(certStore, CERT_CLOSE_STORE_FORCE_FLAG); } #endif return result; } #if defined(PDF4QT_COMPILER_MINGW) || defined(PDF4QT_COMPILER_GCC) #pragma GCC diagnostic pop #endif #if defined(PDF4QT_COMPILER_MSVC) #pragma warning(pop) #endif