diff --git a/PdfForQtLib/sources/pdfsignaturehandler.cpp b/PdfForQtLib/sources/pdfsignaturehandler.cpp index 7d5a824..ec3e607 100644 --- a/PdfForQtLib/sources/pdfsignaturehandler.cpp +++ b/PdfForQtLib/sources/pdfsignaturehandler.cpp @@ -383,6 +383,23 @@ void PDFPublicKeySignatureHandler::verifyCertificate(PDFSignatureVerificationRes result.addCertificateOtherError(error); break; } + + // We will add certificate info for all certificates + const int count = sk_X509_num(certificates); + for (int i = 0; i < count; ++i) + { + result.addCertificateInfo(getCertificateInfo(sk_X509_value(certificates, i))); + } + } + else + { + STACK_OF(X509)* validChain = X509_STORE_CTX_get1_chain(context); + const int count = sk_X509_num(validChain); + for (int i = 0; i < count; ++i) + { + result.addCertificateInfo(getCertificateInfo(sk_X509_value(validChain, i))); + } + sk_X509_pop_free(validChain, X509_free); } X509_STORE_CTX_cleanup(context); } @@ -401,6 +418,11 @@ void PDFPublicKeySignatureHandler::verifyCertificate(PDFSignatureVerificationRes { result.addInvalidCertificateError(); } + + if (!result.hasCertificateError()) + { + result.setFlag(PDFSignatureVerificationResult::Certificate_OK, true); + } } void PDFPublicKeySignatureHandler::verifySignature(PDFSignatureVerificationResult& result) const @@ -417,6 +439,175 @@ PDFSignatureVerificationResult PDFSignatureHandler_adbe_pkcs7_detached::verify() return result; } +PDFCertificateInfo PDFPublicKeySignatureHandler::getCertificateInfo(X509* certificate) +{ + PDFCertificateInfo info; + + 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); + + EVP_PKEY_bits(); + EVP_PKEY_security_bits(); + } + + return info; +} + +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; +} + +long PDFCertificateInfo::getVersion() const +{ + return m_version; +} + +void PDFCertificateInfo::setVersion(long version) +{ + m_version = version; +} + +PDFCertificateInfo::PublicKey PDFCertificateInfo::getPublicKey() const +{ + return m_publicKey; +} + +void PDFCertificateInfo::setPublicKey(const PublicKey& publicKey) +{ + m_publicKey = publicKey; +} + +QString PDFPublicKeySignatureHandler::getStringFromX509Name(X509_NAME* name, int nid) +{ + QString result; + + const int stringLocation = X509_NAME_get_index_by_NID(name, nid, -1); + X509_NAME_ENTRY* entry = X509_NAME_get_entry(name, stringLocation); + if (ASN1_STRING* string = X509_NAME_ENTRY_get_data(entry)) + { + // 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; +} + +QDateTime pdf::PDFPublicKeySignatureHandler::getDateTimeFromASN(const ASN1_TIME* time) +{ + QDateTime result; + + if (time) + { + tm internalTime = { }; + if (ASN1_TIME_to_tm(time, &internalTime) > 0) + { + time_t localTime = mktime(&internalTime); + result = QDateTime::fromSecsSinceEpoch(localTime, Qt::LocalTime); + } + } + + return result; +} + } // namespace pdf #ifdef Q_OS_WIN diff --git a/PdfForQtLib/sources/pdfsignaturehandler.h b/PdfForQtLib/sources/pdfsignaturehandler.h index c31a9a6..bfd40be 100644 --- a/PdfForQtLib/sources/pdfsignaturehandler.h +++ b/PdfForQtLib/sources/pdfsignaturehandler.h @@ -21,6 +21,9 @@ #include "pdfglobal.h" #include "pdfobject.h" +#include +#include + #include namespace pdf @@ -144,6 +147,69 @@ private: AuthentificationType m_propType = AuthentificationType::Invalid; }; +/// Info about certificate, various details etc. +class PDFFORQTLIBSHARED_EXPORT PDFCertificateInfo +{ +public: + explicit inline PDFCertificateInfo() = default; + + /// These entries are taken from RFC 5280, section 4.1.2.4, + /// they are supported and loaded from the certificate, with + /// exception of Email entry. + enum NameEntry + { + CountryName, + OrganizationName, + OrganizationalUnitName, + DistinguishedName, + StateOrProvinceName, + CommonName, + SerialNumber, + LocalityName, + Title, + Surname, + GivenName, + Initials, + Pseudonym, + GenerationalQualifier, + Email, + NameEnd + }; + + enum PublicKey + { + KeyRSA, + KeyDSA, + KeyEC, + KeyDH, + KeyUnknown + }; + + const QString& getName(NameEntry name) const { return m_nameEntries[name]; } + void setName(NameEntry name, QString string) { m_nameEntries[name] = qMove(string); } + + QDateTime getNotValidBefore() const; + void setNotValidBefore(const QDateTime& notValidBefore); + + QDateTime getNotValidAfter() const; + void setNotValidAfter(const QDateTime& notValidAfter); + + long getVersion() const; + void setVersion(long version); + + PublicKey getPublicKey() const; + void setPublicKey(const PublicKey& publicKey); + +private: + long m_version = 0; + PublicKey m_publicKey = KeyUnknown; + std::array m_nameEntries; + QDateTime m_notValidBefore; + QDateTime m_notValidAfter; +}; + +using PDFCertificateInfos = std::vector; + class PDFFORQTLIBSHARED_EXPORT PDFSignatureVerificationResult { public: @@ -159,19 +225,25 @@ public: { None = 0x00000, ///< Used only for initialization OK = 0x00001, ///< Both certificate and signature is OK - Error_NoHandler = 0x00002, ///< No signature handler for given signature - Error_Generic = 0x00004, ///< Generic error (uknown general error) + Certificate_OK = 0x00002, ///< Certificate is OK + Signature_OK = 0x00004, ///< Signature is OK + Error_NoHandler = 0x00008, ///< No signature handler for given signature + Error_Generic = 0x00010, ///< Generic error (uknown general error) - Error_Certificate_Invalid = 0x00008, ///< Certificate is invalid - Error_Certificate_NoSignatures = 0x00010, ///< No signature found in certificate data - Error_Certificate_Missing = 0x00020, ///< Certificate is missing - Error_Certificate_Generic = 0x00040, ///< Generic error during certificate verification - Error_Certificate_Expired = 0x00080, ///< Certificate has expired - Error_Certificate_SelfSigned = 0x00100, ///< Self signed certificate - Error_Certificate_SelfSignedChain = 0x00200, ///< Self signed certificate in chain - Error_Certificate_TrustedNotFound = 0x00400, ///< No trusted certificate was found - Error_Certificate_Revoked = 0x00800, ///< Certificate has been revoked - Error_Certificate_Other = 0x01000, ///< Other certificate error. See OpenSSL code for details. + Error_Certificate_Invalid = 0x00020, ///< Certificate is invalid + Error_Certificate_NoSignatures = 0x00040, ///< No signature found in certificate data + Error_Certificate_Missing = 0x00080, ///< Certificate is missing + Error_Certificate_Generic = 0x00100, ///< Generic error during certificate verification + Error_Certificate_Expired = 0x00200, ///< Certificate has expired + Error_Certificate_SelfSigned = 0x00400, ///< Self signed certificate + Error_Certificate_SelfSignedChain = 0x00800, ///< Self signed certificate in chain + Error_Certificate_TrustedNotFound = 0x01000, ///< No trusted certificate was found + Error_Certificate_Revoked = 0x02000, ///< Certificate has been revoked + Error_Certificate_Other = 0x04000, ///< Other certificate error. See OpenSSL code for details. + + Error_Certificates_Mask = Error_Certificate_Invalid | Error_Certificate_NoSignatures | Error_Certificate_Missing | Error_Certificate_Generic | + Error_Certificate_Expired | Error_Certificate_SelfSigned | Error_Certificate_SelfSignedChain | Error_Certificate_TrustedNotFound | + Error_Certificate_Revoked | Error_Certificate_Other }; Q_DECLARE_FLAGS(VerificationFlags, VerificationFlag) @@ -191,8 +263,12 @@ public: void addCertificateOtherError(int error); bool isValid() const { return hasFlag(OK); } + bool isCertificateValid() const { return hasFlag(Certificate_OK); } + bool isSignatureValid() const { return hasFlag(Signature_OK); } bool hasError() const { return !isValid(); } + bool hasCertificateError() const { return m_flags & Error_Certificates_Mask; } bool hasFlag(VerificationFlag flag) const { return m_flags.testFlag(flag); } + void setFlag(VerificationFlag flag, bool value) { m_flags.setFlag(flag, value); } PDFObjectReference getSignatureFieldReference() const { return m_signatureFieldReference; } const QString& getSignatureFieldQualifiedName() const { return m_signatureFieldQualifiedName; } @@ -201,11 +277,14 @@ public: void setSignatureFieldQualifiedName(const QString& signatureFieldQualifiedName); void setSignatureFieldReference(PDFObjectReference signatureFieldReference); + void addCertificateInfo(PDFCertificateInfo info) { m_certificateInfos.emplace_back(qMove(info)); } + private: VerificationFlags m_flags = None; PDFObjectReference m_signatureFieldReference; QString m_signatureFieldQualifiedName; QStringList m_errors; + PDFCertificateInfos m_certificateInfos; }; /// Signature handler. Can verify both certificate and signature validity. diff --git a/PdfForQtLib/sources/pdfsignaturehandler_impl.h b/PdfForQtLib/sources/pdfsignaturehandler_impl.h index 240e948..d337be9 100644 --- a/PdfForQtLib/sources/pdfsignaturehandler_impl.h +++ b/PdfForQtLib/sources/pdfsignaturehandler_impl.h @@ -46,6 +46,16 @@ protected: /// Return a list of certificates from PKCS7 object static STACK_OF(X509)* getCertificates(PKCS7* pkcs7); + /// Return certificate info for given certificate + static PDFCertificateInfo getCertificateInfo(X509* certificate); + + /// Returns name converted to QString + static QString getStringFromX509Name(X509_NAME* name, int nid); + + /// Converts ASN time to QDateTime. If conversion fails, then invalid + /// datetime is returned. + static QDateTime getDateTimeFromASN(const ASN1_TIME* time); + protected: const PDFFormFieldSignature* m_signatureField; QByteArray m_sourceData;