/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   Licensed to the Apache Software Foundation (ASF) under one or more
 *   contributor license agreements. See the NOTICE file distributed
 *   with this work for additional information regarding copyright
 *   ownership. The ASF licenses this file to you under the Apache
 *   License, Version 2.0 (the "License"); you may not use this file
 *   except in compliance with the License. You may obtain a copy of
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
 */

#include <rtl/ustring.hxx>
#include <sal/config.h>

#include <string_view>

#include <digitalsignaturesdialog.hxx>
#include <certificatechooser.hxx>
#include <certificateviewer.hxx>
#include <biginteger.hxx>
#include <comphelper/diagnose_ex.hxx>
#include <comphelper/configuration.hxx>
#include <officecfg/Office/Common.hxx>

#include <com/sun/star/uno/SecurityException.hpp>
#include <com/sun/star/embed/XStorage.hpp>
#include <com/sun/star/container/XNameAccess.hpp>
#include <com/sun/star/security/CertificateValidity.hpp>
#include <com/sun/star/security/CertificateKind.hpp>
#include <com/sun/star/system/SystemShellExecute.hpp>
#include <com/sun/star/system/SystemShellExecuteFlags.hpp>

#include <osl/file.hxx>
#include <osl/process.h>
#include <sal/log.hxx>
#include <unotools/datetime.hxx>

#include <bitmaps.hlst>
#include <strings.hrc>
#include <resourcemanager.hxx>
#include <comphelper/lok.hxx>
#include <comphelper/xmlsechelper.hxx>
#include <comphelper/processfactory.hxx>

#include <utility>
#include <vcl/weld.hxx>
#include <vcl/svapp.hxx>
#include <sfx2/viewsh.hxx>
#include <svl/cryptosign.hxx>

#ifdef _WIN32
#include <o3tl/char16_t2wchar_t.hxx>
#include <systools/win32/comtools.hxx>
#include <Shlobj.h>
#endif

#if defined MACOSX
#include <sys/stat.h>
#endif

using namespace comphelper;
using namespace css::security;
using namespace css::uno;
using namespace css;

namespace
{

#ifdef _WIN32
    constexpr std::u16string_view aGUIServers[]
        = { u"Gpg4win\\kleopatra.exe",
            u"Gpg4win\\bin\\kleopatra.exe",
            u"GNU\\GnuPG\\kleopatra.exe",
            u"GNU\\GnuPG\\launch-gpa.exe",
            u"GNU\\GnuPG\\gpa.exe",
            u"GnuPG\\bin\\gpa.exe",
            u"GNU\\GnuPG\\bin\\kleopatra.exe",
            u"GNU\\GnuPG\\bin\\launch-gpa.exe",
            u"GNU\\GnuPG\\bin\\gpa.exe"};
#elif defined MACOSX
    constexpr std::u16string_view aGUIServers[]
        = { u"/Applications/GPG Keychain.app",
            u"/Applications/Trusted Key Manager.app", // tdf#147291
            u"/Applications/SCinterface/scManager.app", // tdf#147291
            u"/System/Applications/Utilities/Keychain Access.app"};
#else
    constexpr std::u16string_view aGUIServers[]
        = { u"kleopatra", u"seahorse", u"gpa", u"kgpg"};
#endif

bool GetPathAllOS(OUString& aPath)
{
#ifdef _WIN32
    sal::systools::CoTaskMemAllocated<wchar_t> sPath;
    HRESULT hr
        = SHGetKnownFolderPath(FOLDERID_ProgramFilesX86, KF_FLAG_DEFAULT, nullptr, &sPath);

    if (FAILED(hr))
        return false;
    aPath = o3tl::toU(sPath);
#else
    osl_getEnvironment(u"PATH"_ustr.pData, &aPath.pData);
#endif
    return (!aPath.isEmpty());
}

void GetCertificateManager(OUString& sExecutable)
{
    OUString aPath, aFoundGUIServer;
    if (!GetPathAllOS(aPath))
        return;

    OUString aCetMgrConfig = officecfg::Office::Common::Security::Scripting::CertMgrPath::get();
    if (!aCetMgrConfig.isEmpty())
    {
        if (aCetMgrConfig.indexOf('/') != -1
#ifdef _WIN32
            || aCetMgrConfig.indexOf('\\') != -1
#endif
           )
        {
            sExecutable = aCetMgrConfig;
            return;
        }
        osl::FileBase::RC searchError = osl::File::searchFileURL(
            aCetMgrConfig, aPath,
            aFoundGUIServer);
        if (searchError == osl::FileBase::E_None)
        {
            osl::File::getSystemPathFromFileURL(aFoundGUIServer, sExecutable);
            return;
        }
    }

    for (const auto& rServer: aGUIServers)
    {
        bool bSetCertMgrPath = false;

#ifdef MACOSX
        // On macOS, the list of default certificate manager applications
        // includes absolute paths so check if the path exists and is a
        // directory
        if (rServer.starts_with('/'))
        {
            OString aSysPath = OUString(rServer).toUtf8();
            if (struct stat st; stat(aSysPath.getStr(), &st) == 0 && S_ISDIR(st.st_mode))
            {
                bSetCertMgrPath = true;
                sExecutable = rServer;
            }
        }
#endif

        if (!bSetCertMgrPath)
        {
            osl::FileBase::RC searchError = osl::File::searchFileURL(
                OUString(rServer), aPath,
                aFoundGUIServer);
            if (searchError == osl::FileBase::E_None && osl::File::getSystemPathFromFileURL(aFoundGUIServer, sExecutable) == osl::FileBase::E_None)
                bSetCertMgrPath = true;
        }

        if (bSetCertMgrPath)
        {
            std::shared_ptr<comphelper::ConfigurationChanges> pBatch(
                comphelper::ConfigurationChanges::create());
            officecfg::Office::Common::Security::Scripting::CertMgrPath::set(sExecutable,
                                                                                pBatch);
            pBatch->commit();

            return;
        }
    }
}

bool IsThereCertificateMgr()
{
    OUString sExecutable;
    GetCertificateManager(sExecutable);
    return (!sExecutable.isEmpty());
}
}//anonymous namespace

DigitalSignaturesDialog::DigitalSignaturesDialog(
    weld::Window* pParent,
    const uno::Reference< uno::XComponentContext >& rxCtx, DocumentSignatureMode eMode,
    bool bReadOnly, OUString sODFVersion, bool bHasDocumentSignature,
    SfxViewShell* pViewShell)
    : GenericDialogController(pParent, u"xmlsec/ui/digitalsignaturesdialog.ui"_ustr, u"DigitalSignaturesDialog"_ustr)
    , maSignatureManager(rxCtx, eMode)
    , m_sODFVersion (std::move(sODFVersion))
    , m_bHasDocumentSignature(bHasDocumentSignature)
    , m_bWarningShowSignMacro(false)
    , m_pViewShell(pViewShell)
    , m_xHintDocFT(m_xBuilder->weld_label(u"dochint"_ustr))
    , m_xHintBasicFT(m_xBuilder->weld_label(u"macrohint"_ustr))
    , m_xSignaturesLB(m_xBuilder->weld_tree_view(u"signatures"_ustr))
    , m_xSigsValidImg(m_xBuilder->weld_image(u"validimg"_ustr))
    , m_xSigsValidFI(m_xBuilder->weld_label(u"validft"_ustr))
    , m_xSigsInvalidImg(m_xBuilder->weld_image(u"invalidimg"_ustr))
    , m_xSigsInvalidFI(m_xBuilder->weld_label(u"invalidft"_ustr))
    , m_xSigsNotvalidatedImg(m_xBuilder->weld_image(u"notvalidatedimg"_ustr))
    , m_xSigsNotvalidatedFI(m_xBuilder->weld_label(u"notvalidatedft"_ustr))
    , m_xSigsOldSignatureImg(m_xBuilder->weld_image(u"oldsignatureimg"_ustr))
    , m_xSigsOldSignatureFI(m_xBuilder->weld_label(u"oldsignatureft"_ustr))
    , m_xViewBtn(m_xBuilder->weld_button(u"view"_ustr))
    , m_xAddBtn(m_xBuilder->weld_button(u"sign"_ustr))
    , m_xRemoveBtn(m_xBuilder->weld_button(u"remove"_ustr))
    , m_xStartCertMgrBtn(m_xBuilder->weld_button(u"start_certmanager"_ustr))
    , m_xCloseBtn(m_xBuilder->weld_button(u"close"_ustr))
{
    auto nControlWidth = m_xSignaturesLB->get_approximate_digit_width() * 105;
    m_xSignaturesLB->set_size_request(nControlWidth, m_xSignaturesLB->get_height_rows(10));

    // Give the first column 6 percent, try to distribute the rest equally.
    std::vector<int> aWidths;
    aWidths.push_back(6*nControlWidth/100);
    auto nColWidth = (nControlWidth - aWidths[0]) / 5;
    aWidths.push_back(nColWidth);
    aWidths.push_back(nColWidth);
    aWidths.push_back(nColWidth);
    aWidths.push_back(nColWidth);
    m_xSignaturesLB->set_column_fixed_widths(aWidths);

    mbVerifySignatures = true;
    mbSignaturesChanged = false;

    m_xSignaturesLB->connect_selection_changed(
        LINK(this, DigitalSignaturesDialog, SignatureHighlightHdl));
    m_xSignaturesLB->connect_row_activated( LINK( this, DigitalSignaturesDialog, SignatureSelectHdl ) );

    m_xViewBtn->connect_clicked( LINK( this, DigitalSignaturesDialog, ViewButtonHdl ) );
    m_xViewBtn->set_sensitive(false);

    m_xAddBtn->connect_clicked( LINK( this, DigitalSignaturesDialog, AddButtonHdl ) );
    if ( bReadOnly  )
        m_xAddBtn->set_sensitive(false);

    m_xRemoveBtn->connect_clicked( LINK( this, DigitalSignaturesDialog, RemoveButtonHdl ) );
    m_xRemoveBtn->set_sensitive(false);

    m_xStartCertMgrBtn->connect_clicked( LINK( this, DigitalSignaturesDialog, CertMgrButtonHdl ) );

    m_xCloseBtn->connect_clicked( LINK( this, DigitalSignaturesDialog, OKButtonHdl) );

    switch( maSignatureManager.getSignatureMode() )
    {
        case DocumentSignatureMode::Content:
            m_xHintDocFT->show();
            break;
        case DocumentSignatureMode::Macros:
            m_xHintBasicFT->show();
            break;
    }

    if (comphelper::LibreOfficeKit::isActive())
    {
        // If the view has a signing certificate, then allow adding a signature.
        if (!pViewShell || !pViewShell->GetSigningCertificate().m_xCertificate.is())
        {
            m_xAddBtn->hide();
        }
        m_xStartCertMgrBtn->hide();
    }

    if (!IsThereCertificateMgr())
    {
        m_xStartCertMgrBtn->set_sensitive(false);
    }
}

DigitalSignaturesDialog::~DigitalSignaturesDialog()
{
    if (m_xViewer)
        m_xViewer->response(RET_OK);

    if (m_xInfoBox)
        m_xInfoBox->response(RET_OK);
}

bool DigitalSignaturesDialog::Init()
{
    bool bInit = maSignatureManager.init();

    SAL_WARN_IF( !bInit, "xmlsecurity.dialogs", "Error initializing security context!" );

    if ( bInit )
    {
        maSignatureManager.getSignatureHelper().SetStartVerifySignatureHdl( LINK( this, DigitalSignaturesDialog, StartVerifySignatureHdl ) );
    }

    return bInit;
}

void DigitalSignaturesDialog::SetStorage( const css::uno::Reference < css::embed::XStorage >& rxStore )
{
    if (!rxStore.is())
    {
        // PDF supports AdES.
        m_bAdESCompliant = true;
        return;
    }

    // only ODF 1.1 wants to be non-XAdES (m_sODFVersion="1.0" for OOXML somehow?)
    m_bAdESCompliant = !rxStore->hasByName(u"META-INF"_ustr) // it's a Zip storage
                    || !DocumentSignatureHelper::isODFPre_1_2(m_sODFVersion);

    maSignatureManager.setStore(rxStore);
    maSignatureManager.getSignatureHelper().SetStorage( maSignatureManager.getStore(), m_sODFVersion, {});
}

void DigitalSignaturesDialog::SetSignatureStream( const css::uno::Reference < css::io::XStream >& rxStream )
{
    maSignatureManager.setSignatureStream(rxStream);
}

void DigitalSignaturesDialog::SetScriptingSignatureStream( const css::uno::Reference < css::io::XStream >& rxStream )
{
    if (!rxStream.is())
    {
        return;
    }

    uno::Reference<uno::XComponentContext> xContext = comphelper::getProcessComponentContext();
    moScriptSignatureManager.emplace(xContext, DocumentSignatureMode::Macros);
    if (!moScriptSignatureManager->init())
    {
        return;
    }
    moScriptSignatureManager->setStore(maSignatureManager.getStore());
    // This is the storage used by UriBindingHelper::getUriBinding().
    moScriptSignatureManager->getSignatureHelper().SetStorage(maSignatureManager.getStore(), m_sODFVersion);
    maSignatureManager.getSignatureHelper().SetStorage(maSignatureManager.getStore(), m_sODFVersion, rxStream);
    moScriptSignatureManager->setSignatureStream(rxStream);
}

bool DigitalSignaturesDialog::canAddRemove()
{
    //FIXME: this func needs some cleanup, such as real split between
    //'canAdd' and 'canRemove' case
    uno::Reference<container::XNameAccess> xNameAccess = maSignatureManager.getStore();
    if (xNameAccess.is() && xNameAccess->hasByName(u"[Content_Types].xml"_ustr))
        // It's always possible to append an OOXML signature.
        return true;

    if (!maSignatureManager.getStore().is())
        // It's always possible to append a PDF signature.
        return true;

    OSL_ASSERT(maSignatureManager.getStore().is());
    bool bDoc1_1 = DocumentSignatureHelper::isODFPre_1_2(m_sODFVersion);

    // see specification
    //cvs: specs/www/appwide/security/Electronic_Signatures_and_Security.sxw
    //Paragraph 'Behavior with regard to ODF 1.2'
    //For both, macro and document
    if ( bDoc1_1 )
    {
        //#4
        std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(m_xDialog.get(),
                                                  VclMessageType::Warning, VclButtonsType::Ok,
                                                  XsResId(STR_XMLSECDLG_OLD_ODF_FORMAT)));
        xBox->run();
        return false;
    }

    //As of OOo 3.2 the document signature includes in macrosignatures.xml. That is
    //adding a macro signature will break an existing document signature.
    //The sfx2 will remove the documentsignature when the user adds a macro signature
    if (maSignatureManager.getSignatureMode() == DocumentSignatureMode::Macros)
    {
        if (m_bHasDocumentSignature && !m_bWarningShowSignMacro)
        {
            //The warning says that the document signatures will be removed if the user
            //continues. He can then either press 'OK' or 'NO'
            //It the user presses 'Add' or 'Remove' several times then, then the warning
            //is shown every time until the user presses 'OK'. From then on, the warning
            //is not displayed anymore as long as the signatures dialog is alive.
            std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(
                m_xDialog.get(), VclMessageType::Question, VclButtonsType::YesNo,
                XsResId(STR_XMLSECDLG_QUERY_REMOVEDOCSIGNBEFORESIGN)));
            if (xBox->run() == RET_NO)
                return false;

            m_bWarningShowSignMacro = true;
        }
    }
    return true;
}

bool DigitalSignaturesDialog::canAdd() { return canAddRemove(); }

void DigitalSignaturesDialog::canRemove(const std::function<void(bool)>& rCallback)
{
    auto onFinished = [this, rCallback](bool bRet) {
        rCallback(bRet && canAddRemove());
    };
    bool bRet = true;

    if ( maSignatureManager.getSignatureMode() == DocumentSignatureMode::Content )
    {
        std::shared_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(m_xDialog.get(),
                                                  VclMessageType::Question, VclButtonsType::YesNo,
                                                  XsResId(STR_XMLSECDLG_QUERY_REALLYREMOVE)));
        xBox->runAsync(xBox, [onFinished=std::move(onFinished)](sal_Int32 nDlgRet) {
                onFinished(nDlgRet == RET_YES);
        });
        return;
    }

    onFinished(bRet);
}

void DigitalSignaturesDialog::beforeRun()
{
    // Verify Signatures and add certificates to ListBox...
    mbVerifySignatures = true;
    ImplGetSignatureInformations(/*bUseTempStream=*/false, /*bCacheLastSignature=*/true);
    ImplFillSignaturesBox();

    // FIXME: Disable the "Use XAdES compliant signatures" checkbox if it is irrelevant. If it is
    // enabled, set its initial state based on existing signatures, if any.

    // If it is OOXML, the checkbox is irrelevant.

    // How to find out here whether it is OOXML? I don't want to create a SignatureStreamHelper and
    // check its nStorageFormat as that seems overly complicated and seems to have weird indirect
    // consequences, as I noticed when I tried to use DocumentSignatureManager::IsXAdESRelevant()
    // (which now is in #if 0).

    if (!maSignatureManager.getCurrentSignatureInformations().empty())
    {
        // If the document has only SHA-1 signatures we probably want it to stay that way?
    }

    // Only verify once, content will not change.
    // But for refreshing signature information, StartVerifySignatureHdl will be called after each add/remove
    mbVerifySignatures = false;
}

short DigitalSignaturesDialog::run()
{
    beforeRun();
    return GenericDialogController::run();
}

IMPL_LINK_NOARG(DigitalSignaturesDialog, SignatureHighlightHdl, weld::TreeView&, void)
{
    bool bSel = m_xSignaturesLB->get_selected_index() != -1;
    m_xViewBtn->set_sensitive( bSel );
    if ( m_xAddBtn->get_sensitive() ) // not read only
        m_xRemoveBtn->set_sensitive( bSel );
}

IMPL_LINK_NOARG(DigitalSignaturesDialog, OKButtonHdl, weld::Button&, void)
{
    if (mbSignaturesChanged)
        maSignatureManager.write(m_bAdESCompliant);

    m_xDialog->response(RET_OK);
}

IMPL_LINK_NOARG(DigitalSignaturesDialog, SignatureSelectHdl, weld::TreeView&, bool)
{
    ImplShowSignaturesDetails();
    return true;
}

IMPL_LINK_NOARG(DigitalSignaturesDialog, ViewButtonHdl, weld::Button&, void)
{
    ImplShowSignaturesDetails();
}

IMPL_LINK_NOARG(DigitalSignaturesDialog, AddButtonHdl, weld::Button&, void)
{
    if( ! canAdd())
        return;

    // Separate function, so the function can call itself when the certificate choosing was
    // successful, but the actual signing was not.
    AddButtonHdlImpl();
}

void DigitalSignaturesDialog::AddButtonHdlImpl()
{
    std::vector<uno::Reference<xml::crypto::XXMLSecurityContext>> xSecContexts
    {
        maSignatureManager.getSecurityContext()
    };
    // Gpg signing is only possible with ODF >= 1.2 documents
    if (DocumentSignatureHelper::CanSignWithGPG(maSignatureManager.getStore(), m_sODFVersion))
        xSecContexts.push_back(maSignatureManager.getGpgSecurityContext());

    std::shared_ptr<CertificateChooser> aChooser = CertificateChooser::getInstance(m_xDialog.get(), m_pViewShell, std::move(xSecContexts), CertificateChooserUserAction::Sign);
    aChooser->BeforeRun();
    weld::DialogController::runAsync(aChooser, [this, aChooser](sal_Int32 nRet) {
        if (nRet != RET_OK)
        {
            return;
        }

        try
        {
            sal_Int32 nSecurityId;

            svl::crypto::SigningContext aSigningContext;
            aSigningContext.m_xCertificate = aChooser->GetSelectedCertificates()[0];
            if (moScriptSignatureManager)
            {
                if (!moScriptSignatureManager->add(aSigningContext,
                            aChooser->GetSelectedSecurityContext(),
                            aChooser->GetDescription(), nSecurityId,
                            m_bAdESCompliant))
                {
                    return;
                }

                moScriptSignatureManager->read(/*bUseTempStream=*/true, /*bCacheLastSignature=*/false);
                moScriptSignatureManager->write(m_bAdESCompliant);

                maSignatureManager.setScriptingSignatureStream(moScriptSignatureManager->getSignatureStream());
            }

            if (!maSignatureManager.add(aSigningContext, aChooser->GetSelectedSecurityContext(),
                        aChooser->GetDescription(), nSecurityId, m_bAdESCompliant))
                return;
            mbSignaturesChanged = true;

            xml::crypto::SecurityOperationStatus nStatus = xml::crypto::SecurityOperationStatus_OPERATION_SUCCEEDED;

            if (maSignatureManager.getStore().is())
                // In the PDF case the signature information is only available after parsing.
                nStatus = maSignatureManager.getSignatureHelper().GetSignatureInformation( nSecurityId ).nStatus;

            if ( nStatus == css::xml::crypto::SecurityOperationStatus_OPERATION_SUCCEEDED )
            {
                mbSignaturesChanged = true;

                // Can't simply remember current information, need parsing for getting full information :(
                // We need to verify the signatures again, otherwise the status in the signature information
                // will not contain
                // SecurityOperationStatus_OPERATION_SUCCEEDED
                mbVerifySignatures = true;
                ImplGetSignatureInformations(/*bUseTempStream=*/true, /*bCacheLastSignature=*/false);
                ImplFillSignaturesBox();
            }
            else
            {
                AddButtonHdlImpl();
            }
        }
        catch ( uno::Exception& )
        {
            TOOLS_WARN_EXCEPTION( "xmlsecurity.dialogs", "adding a signature!" );
            std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(m_xDialog.get(),
                        VclMessageType::Error, VclButtonsType::Ok,
                        XsResId(STR_XMLSECDLG_SIGNING_FAILED)));
            xBox->run();
            // Don't keep invalid entries...
            ImplGetSignatureInformations(/*bUseTempStream=*/true, /*bCacheLastSignature=*/false);
            ImplFillSignaturesBox();
        }
    });
}

IMPL_LINK_NOARG(DigitalSignaturesDialog, RemoveButtonHdl, weld::Button&, void)
{
    canRemove([this](bool bRet) {
        if (!bRet)
            return;

        int nEntry = m_xSignaturesLB->get_selected_index();
        if (nEntry == -1)
            return;

        try
        {
            sal_uInt16 nSelected = m_xSignaturesLB->get_id(nEntry).toUInt32();
            maSignatureManager.remove(nSelected);

            mbSignaturesChanged = true;

            ImplFillSignaturesBox();
        }
        catch ( uno::Exception& )
        {
            TOOLS_WARN_EXCEPTION( "xmlsecurity.dialogs", "Exception while removing a signature!" );
            // Don't keep invalid entries...
            ImplGetSignatureInformations(/*bUseTempStream=*/true, /*bCacheLastSignature=*/true);
            ImplFillSignaturesBox();
        }
    });
}


IMPL_LINK_NOARG(DigitalSignaturesDialog, CertMgrButtonHdl, weld::Button&, void)
{
    OUString sExecutable;
    GetCertificateManager(sExecutable);

    if (!sExecutable.isEmpty())
    {
        const uno::Reference<uno::XComponentContext>& xContext
            = ::comphelper::getProcessComponentContext();
        uno::Reference<css::system::XSystemShellExecute> xSystemShell(
            css::system::SystemShellExecute::create(xContext));

        try
        {
            xSystemShell->execute(sExecutable, OUString(),
                                  css::system::SystemShellExecuteFlags::DEFAULTS);
        }
        catch (...)
        {
            // Related tdf#159307 fix uncloseable windows due to uncaught exception
            // XSystemShellExecute::execute() throws an exception for a variety
            // of common error conditions such as files or directories that
            // are non-existent or non-executable. Failure to catch such
            // exceptions would cause the document window to be uncloseable
            // and the application to be unquittable.
            TOOLS_WARN_EXCEPTION( "xmlsecurity.dialogs", "executable failed!" );
            sExecutable = OUString();
        }
    }

    OUString sDialogText = (sExecutable.isEmpty() ?
        XsResId(STR_XMLSECDLG_NO_CERT_MANAGER) : XsResId(STR_XMLSECDLG_OPENED_CRTMGR) + sExecutable);

    std::unique_ptr<weld::MessageDialog> xInfoBox(Application::CreateMessageDialog(
        m_xDialog.get(), VclMessageType::Info, VclButtonsType::Ok,
        sDialogText));
    xInfoBox->run();
}

IMPL_LINK_NOARG(DigitalSignaturesDialog, StartVerifySignatureHdl, LinkParamNone*, bool)
{
    return mbVerifySignatures;
}

void DigitalSignaturesDialog::ImplFillSignaturesBox()
{
    m_xSignaturesLB->clear();

    size_t nInfos = maSignatureManager.getCurrentSignatureInformations().size();
    size_t nValidSigs = 0, nValidCerts = 0;
    bool bAllNewSignatures = true;
    bool bSomePartial = false;

    if( nInfos )
    {
        for( size_t n = 0; n < nInfos; ++n )
        {
            DocumentSignatureAlgorithm mode = DocumentSignatureHelper::getDocumentAlgorithm(
                m_sODFVersion, maSignatureManager.getCurrentSignatureInformations()[n]);
            std::vector< OUString > aElementsToBeVerified;
            if (maSignatureManager.getStore().is())
                aElementsToBeVerified = DocumentSignatureHelper::CreateElementList(maSignatureManager.getStore(), maSignatureManager.getSignatureMode(), mode);

            const SignatureInformation& rInfo = maSignatureManager.getCurrentSignatureInformations()[n];
            uno::Reference< css::security::XCertificate > xCert = getCertificate(rInfo);

            OUString aSubject;
            OUString aIssuer;
            OUString aDateTimeStr;
            OUString aDescription;
            OUString aType;

            bool bCertValid = false;
            if( xCert.is() )
            {
                //check the validity of the cert
                try {
                    sal_Int32 certResult = getSecurityEnvironmentForCertificate(xCert)->verifyCertificate(xCert,
                                                                                                          Sequence<uno::Reference<security::XCertificate> >());

                    bCertValid = certResult == css::security::CertificateValidity::VALID;
                    if ( bCertValid )
                        nValidCerts++;

                } catch (css::uno::SecurityException& ) {
                    OSL_FAIL("Verification of certificate failed");
                    bCertValid = false;
                }

                aSubject = xmlsec::GetContentPart( xCert->getSubjectName(), xCert->getCertificateKind() );
                aIssuer = xmlsec::GetContentPart( xCert->getIssuerName(), xCert->getCertificateKind() );
            }
            else if (!rInfo.ouGpgCertificate.isEmpty())
            {
                // In case we don't have the gpg key locally, get some data from the document
                aIssuer = rInfo.ouGpgOwner;
            }

            aDateTimeStr = utl::GetDateTimeString( rInfo.stDateTime );
            aDescription = rInfo.ouDescription;

            // Decide type string.
            if (maSignatureManager.getStore().is())
            {
                // OpenPGP
                if (!rInfo.ouGpgCertificate.isEmpty())
                    aType = "OpenPGP";
                // XML based: XAdES or not.
                else if (rInfo.GetSigningCertificate() && !rInfo.GetSigningCertificate()->CertDigest.isEmpty())
                    aType = "XAdES";
                else
                    aType = "XML-DSig";
            }
            else
            {
                // Assume PDF: PAdES or not.
                if (rInfo.bHasSigningCertificate)
                    aType = "PAdES";
                else
                    aType = "PDF";
            }

            bool bSigValid = rInfo.nStatus == css::xml::crypto::SecurityOperationStatus_OPERATION_SUCCEEDED;

            if ( bSigValid )
            {
                if (maSignatureManager.getStore().is())
                {
                    // ZIP based.
                    bSigValid = DocumentSignatureHelper::checkIfAllFilesAreSigned(
                          aElementsToBeVerified, rInfo, mode);
                }
                else
                {
                    // Assume PDF.
                    bSigValid = !rInfo.bPartialDocumentSignature;
                }

                if( bSigValid )
                    nValidSigs++;
                else
                {
                    bSomePartial = true;
                }
            }

            OUString sImage;
            if (!bSigValid)
            {
                sImage = BMP_SIG_INVALID;
            }
            else if (!bCertValid)
            {
                sImage = BMP_SIG_NOT_VALIDATED;
            }
            //Check if the signature is a "old" document signature, that is, which was created
            //by a version of OOo previous to 3.2
            // If there is no storage, then it's pointless to check storage
            // stream references.
            else if (maSignatureManager.getSignatureMode() == DocumentSignatureMode::Content
                && (maSignatureManager.getStore().is() && !DocumentSignatureHelper::isOOo3_2_Signature(
                maSignatureManager.getCurrentSignatureInformations()[n])))
            {
                sImage = BMP_SIG_NOT_VALIDATED;
                bAllNewSignatures = false;
            }
            else if (maSignatureManager.getSignatureMode() == DocumentSignatureMode::Content
                && DocumentSignatureHelper::isOOo3_2_Signature(
                maSignatureManager.getCurrentSignatureInformations()[n]))
            {
                sImage = BMP_SIG_VALID;
            }
            else if (maSignatureManager.getSignatureMode() == DocumentSignatureMode::Macros)
            {
                sImage = BMP_SIG_VALID;
            }

            m_xSignaturesLB->insert(nullptr, n, nullptr, nullptr,
                                    &sImage, nullptr, false, nullptr);
            m_xSignaturesLB->set_text(n, aSubject, 1);
            m_xSignaturesLB->set_text(n, aIssuer, 2);
            m_xSignaturesLB->set_text(n, aDateTimeStr, 3);
            m_xSignaturesLB->set_text(n, aDescription, 4);
            m_xSignaturesLB->set_text(n, aType, 5);
            m_xSignaturesLB->set_id(n, OUString::number(n)); // misuse user data as index
        }
    }

    bool bAllSigsValid = (nValidSigs == nInfos);
    bool bAllCertsValid = (nValidCerts == nInfos);
    bool bShowValidState = nInfos && (bAllSigsValid && bAllCertsValid && bAllNewSignatures);

    m_xSigsValidImg->set_visible( bShowValidState);
    m_xSigsValidFI->set_visible( bShowValidState );

    bool bShowInvalidState = nInfos && !bAllSigsValid;

    m_xSigsInvalidImg->set_visible( bShowInvalidState && !bSomePartial);
    m_xSigsInvalidFI->set_visible( bShowInvalidState && !bSomePartial);

    bool bShowNotValidatedState = nInfos && bAllSigsValid && !bAllCertsValid;

    m_xSigsNotvalidatedImg->set_visible(bShowNotValidatedState);
    m_xSigsNotvalidatedFI->set_visible(bShowNotValidatedState);

    //bAllNewSignatures is always true if we are not in document mode
    bool bShowOldSignature = nInfos && bAllSigsValid && bAllCertsValid && !bAllNewSignatures;
    m_xSigsOldSignatureImg->set_visible(bShowOldSignature || bSomePartial);
    m_xSigsOldSignatureFI->set_visible(bShowOldSignature || bSomePartial);

    SignatureHighlightHdl(*m_xSignaturesLB);
}

uno::Reference<security::XCertificate> DigitalSignaturesDialog::getCertificate(const SignatureInformation& rInfo)
{
    uno::Reference<xml::crypto::XSecurityEnvironment> xSecEnv = maSignatureManager.getSecurityEnvironment();
    uno::Reference<xml::crypto::XSecurityEnvironment> xGpgSecEnv = maSignatureManager.getGpgSecurityEnvironment();
    uno::Reference<security::XCertificate> xCert;

    //First we try to get the certificate which is embedded in the XML Signature
    if (xSecEnv.is() && rInfo.GetSigningCertificate() && !rInfo.GetSigningCertificate()->X509Certificate.isEmpty())
        xCert = xSecEnv->createCertificateFromAscii(rInfo.GetSigningCertificate()->X509Certificate);
    else {
        //There must be an embedded certificate because we use it to get the
        //issuer name. We cannot use /Signature/KeyInfo/X509Data/X509IssuerName
        //because it could be modified by an attacker. The issuer is displayed
        //in the digital signature dialog.
        //Comparing the X509IssuerName with the one from the X509Certificate in order
        //to find out if the X509IssuerName was modified does not work. See #i62684
        SAL_WARN( "xmlsecurity.dialogs", "Could not find embedded certificate!");
    }

    //In case there is no embedded certificate we try to get it from a local store
    if (!xCert.is() && xSecEnv.is() && rInfo.GetSigningCertificate())
    {
        xCert = xSecEnv->getCertificate(rInfo.GetSigningCertificate()->X509IssuerName,
            xmlsecurity::numericStringToBigInteger(rInfo.GetSigningCertificate()->X509SerialNumber));
    }
    if (!xCert.is() && xGpgSecEnv.is() && !rInfo.ouGpgKeyID.isEmpty())
        xCert = xGpgSecEnv->getCertificate( rInfo.ouGpgKeyID, xmlsecurity::numericStringToBigInteger(u"") );

    SAL_WARN_IF( !xCert.is(), "xmlsecurity.dialogs", "Certificate not found and can't be created!" );

    return xCert;
}

uno::Reference<xml::crypto::XSecurityEnvironment> DigitalSignaturesDialog::getSecurityEnvironmentForCertificate(const uno::Reference<security::XCertificate>& xCert)
{
    switch(xCert->getCertificateKind())
    {
        case CertificateKind_OPENPGP:
            return maSignatureManager.getGpgSecurityEnvironment();
        case CertificateKind_X509:
            return maSignatureManager.getSecurityEnvironment();
        default:
            throw RuntimeException(u"Unknown certificate kind"_ustr);
    }
}

//If bUseTempStream is true then the temporary signature stream is used.
//Otherwise the real signature stream is used.
void DigitalSignaturesDialog::ImplGetSignatureInformations(bool bUseTempStream, bool bCacheLastSignature)
{
    maSignatureManager.read(bUseTempStream, bCacheLastSignature);
    mbVerifySignatures = false;
}

void DigitalSignaturesDialog::ImplShowSignaturesDetails()
{
    int nEntry = m_xSignaturesLB->get_selected_index();
    if (nEntry == -1)
        return;

    sal_uInt16 nSelected = m_xSignaturesLB->get_id(nEntry).toUInt32();
    const SignatureInformation& rInfo = maSignatureManager.getCurrentSignatureInformations()[ nSelected ];
    uno::Reference<security::XCertificate> xCert = getCertificate(rInfo);

    if ( xCert.is() )
    {
        if (m_xViewer)
            m_xViewer->response(RET_OK);

        uno::Reference<xml::crypto::XSecurityEnvironment> xSecEnv = getSecurityEnvironmentForCertificate(xCert);
        m_xViewer = std::make_shared<CertificateViewer>(m_xDialog.get(), xSecEnv, xCert, false, nullptr);
        weld::DialogController::runAsync(m_xViewer, [this] (sal_Int32) { m_xViewer = nullptr; });
    }
    else
    {
        if (m_xInfoBox)
            m_xInfoBox->response(RET_OK);

        m_xInfoBox = std::shared_ptr<weld::MessageDialog>(Application::CreateMessageDialog(m_xDialog.get(),
                                                          VclMessageType::Info, VclButtonsType::Ok,
                                                          XsResId(STR_XMLSECDLG_NO_CERT_FOUND)));
        m_xInfoBox->runAsync(m_xInfoBox, [this] (sal_Int32) { m_xInfoBox = nullptr; });
    }
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
