/*************************************************************************
 *
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * Copyright 2000, 2010 Oracle and/or its affiliates.
 *
 * OpenOffice.org - a multi-platform office productivity suite
 *
 * This file is part of OpenOffice.org.
 *
 * OpenOffice.org is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License version 3
 * only, as published by the Free Software Foundation.
 *
 * OpenOffice.org 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 version 3 for more details
 * (a copy is included in the LICENSE file that accompanied this code).
 *
 * You should have received a copy of the GNU Lesser General Public License
 * version 3 along with OpenOffice.org.  If not, see
 * <http://www.openoffice.org/license.html>
 * for a copy of the LGPLv3 License.
 *
 ************************************************************************/

// MARKER(update_precomp.py): autogen include statement, do not remove

#include "precompiled_xmlsecurity.hxx"

#include <documentdigitalsignatures.hxx>
#include <xmlsecurity/digitalsignaturesdialog.hxx>
#include <xmlsecurity/certificateviewer.hxx>
#include <xmlsecurity/macrosecurity.hxx>
#include <xmlsecurity/biginteger.hxx>
#include <xmlsecurity/global.hrc>

#include <xmloff/xmluconv.hxx>

#include <../dialogs/resourcemanager.hxx>
#include <com/sun/star/embed/XStorage.hpp>
#include <com/sun/star/embed/XTransactedObject.hpp>
#include <com/sun/star/embed/ElementModes.hpp>
#include <com/sun/star/ucb/XContent.hpp>
#include <com/sun/star/ucb/XContentProvider.hpp>
#include <com/sun/star/ucb/XContentIdentifierFactory.hpp>
#include <com/sun/star/ucb/XCommandEnvironment.hpp>
#include <com/sun/star/ucb/XCommandProcessor.hpp>
#include <com/sun/star/ucb/Command.hpp>
#include <tools/urlobj.hxx>
#include <vcl/msgbox.hxx>
#include <unotools/securityoptions.hxx>
#include <com/sun/star/security/CertificateValidity.hpp>
#include <com/sun/star/security/SerialNumberAdapter.hpp>
#include <ucbhelper/contentbroker.hxx>
#include <unotools/ucbhelper.hxx>
#include <comphelper/componentcontext.hxx>
#include "comphelper/documentconstants.hxx"

#include "com/sun/star/lang/IllegalArgumentException.hpp"

#include <stdio.h>


using namespace ::com::sun::star;
using namespace ::com::sun::star::uno;
namespace css = ::com::sun::star;

#define OUSTR(x) ::rtl::OUString( RTL_CONSTASCII_USTRINGPARAM(x) )

DocumentDigitalSignatures::DocumentDigitalSignatures( const Reference< XComponentContext >& rxCtx ):
    mxCtx(rxCtx),
    m_sODFVersion(ODFVER_012_TEXT),
    m_nArgumentsCount(0),
    m_bHasDocumentSignature(false)
{
}

void DocumentDigitalSignatures::initialize( const Sequence< Any >& aArguments)
        throw (css::uno::Exception, css::uno::RuntimeException)
{
    if (aArguments.getLength() == 0 || aArguments.getLength() > 2)
        throw css::lang::IllegalArgumentException(
        OUSTR("DocumentDigitalSignatures::initialize requires one or two arguments"),
        Reference<XInterface>(static_cast<XInitialization*>(this), UNO_QUERY), 0);

    m_nArgumentsCount = aArguments.getLength();

    if (!(aArguments[0] >>= m_sODFVersion))
        throw css::lang::IllegalArgumentException(
        OUSTR("DocumentDigitalSignatures::initialize: the first arguments must be a string"),
        Reference<XInterface>(static_cast<XInitialization*>(this), UNO_QUERY), 0);

    if (aArguments.getLength() == 2
        && !(aArguments[1] >>= m_bHasDocumentSignature))
        throw css::lang::IllegalArgumentException(
        OUSTR("DocumentDigitalSignatures::initialize: the second arguments must be a bool"),
        Reference<XInterface>(static_cast<XInitialization*>(this), UNO_QUERY), 1);

    //the Version is supported as of ODF1.2, so for and 1.1 document or older we will receive the
    //an empty string. In this case we set it to ODFVER_010_TEXT. Then we can later check easily
    //if initialize was called. Only then m_sODFVersion.getLength() is greater than 0
    if (m_sODFVersion.getLength() == 0)
        m_sODFVersion = ODFVER_010_TEXT;
}

sal_Bool DocumentDigitalSignatures::signDocumentContent(
    const Reference< css::embed::XStorage >& rxStorage,
    const Reference< css::io::XStream >& xSignStream)
        throw (RuntimeException)
{
    OSL_ENSURE(m_sODFVersion.getLength(), "DocumentDigitalSignatures: ODF Version not set, assuming minimum 1.2");
    return ImplViewSignatures( rxStorage, xSignStream, SignatureModeDocumentContent, false );
}

Sequence< css::security::DocumentSignatureInformation >
DocumentDigitalSignatures::verifyDocumentContentSignatures(
    const Reference< css::embed::XStorage >& rxStorage,
    const Reference< css::io::XInputStream >& xSignInStream ) throw (RuntimeException)
{
    OSL_ENSURE(m_sODFVersion.getLength(),"DocumentDigitalSignatures: ODF Version not set, assuming minimum 1.2");
    return ImplVerifySignatures( rxStorage, xSignInStream, SignatureModeDocumentContent );
}

void DocumentDigitalSignatures::showDocumentContentSignatures(
    const Reference< css::embed::XStorage >& rxStorage,
    const Reference< css::io::XInputStream >& xSignInStream ) throw (RuntimeException)
{
    OSL_ENSURE(m_sODFVersion.getLength(),"DocumentDigitalSignatures: ODF Version not set, assuming minimum 1.2");
    ImplViewSignatures( rxStorage, xSignInStream, SignatureModeDocumentContent, true );
}

::rtl::OUString DocumentDigitalSignatures::getDocumentContentSignatureDefaultStreamName()
    throw (css::uno::RuntimeException)
{
    return DocumentSignatureHelper::GetDocumentContentSignatureDefaultStreamName();
}

sal_Bool DocumentDigitalSignatures::signScriptingContent(
    const Reference< css::embed::XStorage >& rxStorage,
    const Reference< css::io::XStream >& xSignStream ) throw (RuntimeException)
{
    OSL_ENSURE(m_sODFVersion.getLength(),"DocumentDigitalSignatures: ODF Version not set, assuming minimum 1.2");
    OSL_ENSURE(m_nArgumentsCount == 2, "DocumentDigitalSignatures: Service was not initialized properly");
    return ImplViewSignatures( rxStorage, xSignStream, SignatureModeMacros, false );
}

Sequence< css::security::DocumentSignatureInformation >
DocumentDigitalSignatures::verifyScriptingContentSignatures(
    const Reference< css::embed::XStorage >& rxStorage,
    const Reference< css::io::XInputStream >& xSignInStream ) throw (RuntimeException)
{
    OSL_ENSURE(m_sODFVersion.getLength(),"DocumentDigitalSignatures: ODF Version not set, assuming minimum 1.2");
    return ImplVerifySignatures( rxStorage, xSignInStream, SignatureModeMacros );
}

void DocumentDigitalSignatures::showScriptingContentSignatures(
    const Reference< css::embed::XStorage >& rxStorage,
    const Reference< css::io::XInputStream >& xSignInStream ) throw (RuntimeException)
{
    OSL_ENSURE(m_sODFVersion.getLength(),"DocumentDigitalSignatures: ODF Version not set, assuming minimum 1.2");
    ImplViewSignatures( rxStorage, xSignInStream, SignatureModeMacros, true );
}

::rtl::OUString DocumentDigitalSignatures::getScriptingContentSignatureDefaultStreamName()
    throw (css::uno::RuntimeException)
{
    return DocumentSignatureHelper::GetScriptingContentSignatureDefaultStreamName();
}


sal_Bool DocumentDigitalSignatures::signPackage(
    const Reference< css::embed::XStorage >& rxStorage,
    const Reference< css::io::XStream >& xSignStream  ) throw (RuntimeException)
{
    OSL_ENSURE(m_sODFVersion.getLength(),"DocumentDigitalSignatures: ODF Version not set, assuming minimum 1.2");
    return ImplViewSignatures( rxStorage, xSignStream, SignatureModePackage, false );
}

Sequence< css::security::DocumentSignatureInformation >
DocumentDigitalSignatures::verifyPackageSignatures(
    const Reference< css::embed::XStorage >& rxStorage,
    const Reference< css::io::XInputStream >& xSignInStream ) throw (RuntimeException)
{
    OSL_ENSURE(m_sODFVersion.getLength(),"DocumentDigitalSignatures: ODF Version not set, assuming minimum 1.2");
    return ImplVerifySignatures( rxStorage, xSignInStream, SignatureModePackage );
}

void DocumentDigitalSignatures::showPackageSignatures(
    const Reference< css::embed::XStorage >& rxStorage,
    const Reference< css::io::XInputStream >& xSignInStream ) throw (RuntimeException)
{
    OSL_ENSURE(m_sODFVersion.getLength(),"DocumentDigitalSignatures: ODF Version not set, assuming minimum 1.2");
    ImplViewSignatures( rxStorage, xSignInStream, SignatureModePackage, true );
}

::rtl::OUString DocumentDigitalSignatures::getPackageSignatureDefaultStreamName(  )
    throw (::com::sun::star::uno::RuntimeException)
{
    return DocumentSignatureHelper::GetPackageSignatureDefaultStreamName();
}


sal_Bool DocumentDigitalSignatures::ImplViewSignatures(
    const Reference< css::embed::XStorage >& rxStorage,
    const Reference< css::io::XInputStream >& xSignStream,
    DocumentSignatureMode eMode, bool bReadOnly ) throw (RuntimeException)
{
    Reference< io::XStream > xStream;
    if ( xSignStream.is() )
        xStream = Reference< io::XStream >( xSignStream, UNO_QUERY );
    return ImplViewSignatures( rxStorage, xStream, eMode, bReadOnly );
}

sal_Bool DocumentDigitalSignatures::ImplViewSignatures(
    const Reference< css::embed::XStorage >& rxStorage, const Reference< css::io::XStream >& xSignStream,
    DocumentSignatureMode eMode, bool bReadOnly ) throw (RuntimeException)
{
    sal_Bool bChanges = sal_False;
    DigitalSignaturesDialog aSignaturesDialog(
        NULL, mxCtx, eMode, bReadOnly, m_sODFVersion, m_bHasDocumentSignature);
    bool bInit = aSignaturesDialog.Init( rtl::OUString() );
    DBG_ASSERT( bInit, "Error initializing security context!" );
    if ( bInit )
    {
        aSignaturesDialog.SetStorage( rxStorage );
        aSignaturesDialog.SetSignatureStream( xSignStream );
        if ( aSignaturesDialog.Execute() )
        {
            if ( aSignaturesDialog.SignaturesChanged() )
            {
                bChanges = TRUE;
                // If we have a storage and no stream, we are responsible for commit
                if ( rxStorage.is() && !xSignStream.is() )
                {
                    uno::Reference< embed::XTransactedObject > xTrans( rxStorage, uno::UNO_QUERY );
                    xTrans->commit();
                }
            }
        }
    }
    else
    {
        WarningBox aBox( NULL, XMLSEC_RES( RID_XMLSECWB_NO_MOZILLA_PROFILE ) );
        aBox.Execute();
    }

    return bChanges;
}

Sequence< css::security::DocumentSignatureInformation >
DocumentDigitalSignatures::ImplVerifySignatures(
    const Reference< css::embed::XStorage >& rxStorage,
    const Reference< css::io::XInputStream >& xSignStream, DocumentSignatureMode eMode ) throw (RuntimeException)
{
    if (!rxStorage.is())
    {
        DBG_ASSERT(0, "Error, no XStorage provided");
        return Sequence<css::security::DocumentSignatureInformation>();
    }
    // First check for the InputStream, to avoid unnecessary initialization of the security environemnt...
    SignatureStreamHelper aStreamHelper;
    Reference< io::XInputStream > xInputStream = xSignStream;

    if ( !xInputStream.is() )
    {
        aStreamHelper = DocumentSignatureHelper::OpenSignatureStream( rxStorage, embed::ElementModes::READ, eMode );
        if ( aStreamHelper.xSignatureStream.is() )
            xInputStream = Reference< io::XInputStream >( aStreamHelper.xSignatureStream, UNO_QUERY );
    }

    if ( !xInputStream.is() )
        return Sequence< ::com::sun::star::security::DocumentSignatureInformation >(0);


    XMLSignatureHelper aSignatureHelper( mxCtx );

    bool bInit = aSignatureHelper.Init( rtl::OUString() );

    DBG_ASSERT( bInit, "Error initializing security context!" );

    if ( !bInit )
        return Sequence< ::com::sun::star::security::DocumentSignatureInformation >(0);

    aSignatureHelper.SetStorage(rxStorage, m_sODFVersion);

    aSignatureHelper.StartMission();

    aSignatureHelper.ReadAndVerifySignature( xInputStream );

    aSignatureHelper.EndMission();

    Reference< ::com::sun::star::xml::crypto::XSecurityEnvironment > xSecEnv = aSignatureHelper.GetSecurityEnvironment();

    SignatureInformations aSignInfos = aSignatureHelper.GetSignatureInformations();
    int nInfos = aSignInfos.size();
    Sequence< css::security::DocumentSignatureInformation > aInfos(nInfos);
    css::security::DocumentSignatureInformation* arInfos = aInfos.getArray();

    if ( nInfos )
    {
       Reference<security::XSerialNumberAdapter> xSerialNumberAdapter =
            ::com::sun::star::security::SerialNumberAdapter::create(mxCtx);

        for( int n = 0; n < nInfos; ++n )
        {
            DocumentSignatureAlgorithm mode = DocumentSignatureHelper::getDocumentAlgorithm(
                m_sODFVersion, aSignInfos[n]);
            const std::vector< rtl::OUString > aElementsToBeVerified =
                DocumentSignatureHelper::CreateElementList(
                rxStorage, ::rtl::OUString(), eMode, mode);

            const SignatureInformation& rInfo = aSignInfos[n];
            css::security::DocumentSignatureInformation& rSigInfo = arInfos[n];

            if (rInfo.ouX509Certificate.getLength())
               rSigInfo.Signer = xSecEnv->createCertificateFromAscii( rInfo.ouX509Certificate ) ;
            if (!rSigInfo.Signer.is())
                rSigInfo.Signer = xSecEnv->getCertificate( rInfo.ouX509IssuerName, xSerialNumberAdapter->toSequence( rInfo.ouX509SerialNumber ) );

            // --> PB 2004-12-14 #i38744# time support again
            Date aDate( rInfo.stDateTime.Day, rInfo.stDateTime.Month, rInfo.stDateTime.Year );
            Time aTime( rInfo.stDateTime.Hours, rInfo.stDateTime.Minutes,
                        rInfo.stDateTime.Seconds, rInfo.stDateTime.HundredthSeconds );
            rSigInfo.SignatureDate = aDate.GetDate();
            rSigInfo.SignatureTime = aTime.GetTime();

            // Verify certificate
            //We have patched our version of libxmlsec, so that it does not verify the certificates. This has two
            //reasons. First we want two separate status for signature and certificate. Second libxmlsec calls
            //CERT_VerifyCertificate (solaris, linux) falsly, so that it always regards the certificate as valid.
            //On Window the checking of the certificate path is buggy. It does name matching (issuer, subject name)
            //to find the parent certificate. It does not take into account that there can be several certificates
            //with the same subject name.
            if (rSigInfo.Signer.is())
            {
                try {
                    rSigInfo.CertificateStatus = xSecEnv->verifyCertificate(rSigInfo.Signer,
                        Sequence<Reference<css::security::XCertificate> >());
                } catch (SecurityException& ) {
                    OSL_ENSURE(0, "Verification of certificate failed");
                    rSigInfo.CertificateStatus = css::security::CertificateValidity::INVALID;
                }
            }
            else
            {
                //We should always be aible to get the certificates because it is contained in the document,
                //unless the document is damaged so that signature xml file could not be parsed.
                rSigInfo.CertificateStatus = css::security::CertificateValidity::INVALID;
            }

            rSigInfo.SignatureIsValid = ( rInfo.nStatus == ::com::sun::star::xml::crypto::SecurityOperationStatus_OPERATION_SUCCEEDED );


            if ( rSigInfo.SignatureIsValid )
            {
                 rSigInfo.SignatureIsValid =
                      DocumentSignatureHelper::checkIfAllFilesAreSigned(
                      aElementsToBeVerified, rInfo, mode);
            }
            if (eMode == SignatureModeDocumentContent)
                rSigInfo.PartialDocumentSignature =
                    ! DocumentSignatureHelper::isOOo3_2_Signature(aSignInfos[n]);

        }
    }
    return aInfos;

}

void DocumentDigitalSignatures::manageTrustedSources(  ) throw (RuntimeException)
{
    // MT: i45295
    // SecEnv is only needed to display certificate information from trusted sources.
    // Macro Security also has some options where no security environment is needed, so raise dialog anyway.
    // Later I should change the code so the Dialog creates the SecEnv on demand...

    Reference< dcss::xml::crypto::XSecurityEnvironment > xSecEnv;

    XMLSignatureHelper aSignatureHelper( mxCtx );
    if ( aSignatureHelper.Init( rtl::OUString() ) )
        xSecEnv = aSignatureHelper.GetSecurityEnvironment();

    MacroSecurity aDlg( NULL, mxCtx, xSecEnv );
    aDlg.Execute();
}

void DocumentDigitalSignatures::showCertificate(
    const Reference< css::security::XCertificate >& _Certificate ) throw (RuntimeException)
{
    XMLSignatureHelper aSignatureHelper( mxCtx );

    bool bInit = aSignatureHelper.Init( rtl::OUString() );

    DBG_ASSERT( bInit, "Error initializing security context!" );

    if ( bInit )
    {
        CertificateViewer aViewer( NULL, aSignatureHelper.GetSecurityEnvironment(), _Certificate, FALSE );
        aViewer.Execute();
    }

}

::sal_Bool DocumentDigitalSignatures::isAuthorTrusted(
    const Reference< css::security::XCertificate >& Author ) throw (RuntimeException)
{
    sal_Bool bFound = sal_False;

    Reference<security::XSerialNumberAdapter> xSerialNumberAdapter =
        ::com::sun::star::security::SerialNumberAdapter::create(mxCtx);

    ::rtl::OUString sSerialNum = xSerialNumberAdapter->toString( Author->getSerialNumber() );

    Sequence< SvtSecurityOptions::Certificate > aTrustedAuthors = SvtSecurityOptions().GetTrustedAuthors();
    const SvtSecurityOptions::Certificate* pAuthors = aTrustedAuthors.getConstArray();
    const SvtSecurityOptions::Certificate* pAuthorsEnd = pAuthors + aTrustedAuthors.getLength();
    for ( ; pAuthors != pAuthorsEnd; ++pAuthors )
    {
        SvtSecurityOptions::Certificate aAuthor = *pAuthors;
        if ( ( aAuthor[0] == Author->getIssuerName() ) && ( aAuthor[1] == sSerialNum ) )
        {
            bFound = sal_True;
            break;
        }
    }

    return bFound;
}

::sal_Bool DocumentDigitalSignatures::isLocationTrusted( const ::rtl::OUString& Location ) throw (RuntimeException)
{
    sal_Bool bFound = sal_False;
    INetURLObject aLocObj( Location );
    INetURLObject aLocObjLowCase( Location.toAsciiLowerCase() ); // will be used for case insensitive comparing

    ::com::sun::star::uno::Reference< ::com::sun::star::ucb::XContentProvider > xContentProvider;
    ::ucbhelper::ContentBroker* pBroker = NULL;

    //warning free code
    //if ( aLocObj.GetProtocol() == INET_PROT_FILE && ( pBroker = ::ucbhelper::ContentBroker::get() ) )
    //  xContentProvider = pBroker->getContentProviderInterface();
    if ( aLocObj.GetProtocol() == INET_PROT_FILE)
    {
        pBroker = ::ucbhelper::ContentBroker::get();
        if (pBroker)
            xContentProvider = pBroker->getContentProviderInterface();
    }

    Sequence< ::rtl::OUString > aSecURLs = SvtSecurityOptions().GetSecureURLs();
    const ::rtl::OUString* pSecURLs = aSecURLs.getConstArray();
    const ::rtl::OUString* pSecURLsEnd = pSecURLs + aSecURLs.getLength();
    for ( ; pSecURLs != pSecURLsEnd && !bFound; ++pSecURLs )
        bFound = ::utl::UCBContentHelper::IsSubPath( *pSecURLs, Location, xContentProvider );

    return bFound;
}

void DocumentDigitalSignatures::addAuthorToTrustedSources(
    const Reference< css::security::XCertificate >& Author ) throw (RuntimeException)
{
    SvtSecurityOptions aSecOpts;

    Reference<security::XSerialNumberAdapter> xSerialNumberAdapter =
        ::com::sun::star::security::SerialNumberAdapter::create(mxCtx);

    SvtSecurityOptions::Certificate aNewCert( 3 );
    aNewCert[ 0 ] = Author->getIssuerName();
    aNewCert[ 1 ] = xSerialNumberAdapter->toString( Author->getSerialNumber() );

    rtl::OUStringBuffer aStrBuffer;
    SvXMLUnitConverter::encodeBase64(aStrBuffer, Author->getEncoded());
    aNewCert[ 2 ] = aStrBuffer.makeStringAndClear();


    Sequence< SvtSecurityOptions::Certificate > aTrustedAuthors = aSecOpts.GetTrustedAuthors();
    sal_Int32 nCnt = aTrustedAuthors.getLength();
    aTrustedAuthors.realloc( nCnt + 1 );
    aTrustedAuthors[ nCnt ] = aNewCert;

    aSecOpts.SetTrustedAuthors( aTrustedAuthors );
}

void DocumentDigitalSignatures::addLocationToTrustedSources( const ::rtl::OUString& Location ) throw (RuntimeException)
{
    SvtSecurityOptions aSecOpt;

    Sequence< ::rtl::OUString > aSecURLs = aSecOpt.GetSecureURLs();
    sal_Int32 nCnt = aSecURLs.getLength();
    aSecURLs.realloc( nCnt + 1 );
    aSecURLs[ nCnt ] = Location;

    aSecOpt.SetSecureURLs( aSecURLs );
}

rtl::OUString DocumentDigitalSignatures::GetImplementationName() throw (RuntimeException)
{
    return rtl::OUString ( RTL_CONSTASCII_USTRINGPARAM ( "com.sun.star.security.DocumentDigitalSignatures" ) );
}

Sequence< rtl::OUString > DocumentDigitalSignatures::GetSupportedServiceNames() throw (cssu::RuntimeException)
{
    Sequence < rtl::OUString > aRet(1);
    rtl::OUString* pArray = aRet.getArray();
    pArray[0] =  rtl::OUString( RTL_CONSTASCII_USTRINGPARAM ( "com.sun.star.security.DocumentDigitalSignatures" ) );
    return aRet;
}


Reference< XInterface > DocumentDigitalSignatures_CreateInstance(
    const Reference< XComponentContext >& rCtx) throw ( Exception )
{
    return (cppu::OWeakObject*) new DocumentDigitalSignatures( rCtx );
}

