/* -*- 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 <sal/config.h>
 
#include <cstddef>
#include <string.h>
 
#if !defined WIN32_LEAN_AND_MEAN
# define WIN32_LEAN_AND_MEAN
#endif
#include <Windows.h>
#include <WinCrypt.h>
#include <sal/macros.h>
#include <osl/thread.h>
#include "securityenvironment_mscryptimpl.hxx"
 
#include "x509certificate_mscryptimpl.hxx"
#include <comphelper/servicehelper.hxx>
#include <com/sun/star/lang/XMultiServiceFactory.hpp>
 
#include <xmlsec-wrapper.h>
#include "akmngr.hxx"
 
#include <biginteger.hxx>
 
#include <comphelper/windowserrorstring.hxx>
#include <sal/log.hxx>
#include <rtl/locale.h>
#include <osl/nlsupport.h>
#include <osl/process.h>
#include <o3tl/char16_t2wchar_t.hxx>
#include <svl/cryptosign.hxx>
 
using namespace ::com::sun::star;
using namespace ::com::sun::star::lang ;
using ::com::sun::star::lang::XMultiServiceFactory ;
using ::com::sun::star::lang::XSingleServiceFactory ;
 
using ::com::sun::star::xml::crypto::XSecurityEnvironment ;
using ::com::sun::star::security::XCertificate ;
 
static X509Certificate_MSCryptImpl* MswcryCertContextToXCert( PCCERT_CONTEXT cert ) ;
 
struct CertErrorToString{
    DWORD error;
    char const * name;
};
 
CertErrorToString const arErrStrings[] =
{
    { 0x00000000, "CERT_TRUST_NO_ERROR"},
    { 0x00000001, "CERT_TRUST_IS_NOT_TIME_VALID"},
    { 0x00000002, "CERT_TRUST_IS_NOT_TIME_NESTED"},
    { 0x00000004, "CERT_TRUST_IS_REVOKED" },
    { 0x00000008, "CERT_TRUST_IS_NOT_SIGNATURE_VALID" },
    { 0x00000010, "CERT_TRUST_IS_NOT_SIGNATURE_VALID"},
    { 0x00000020, "CERT_TRUST_IS_UNTRUSTED_ROOT"},
    { 0x00000040, "CERT_TRUST_REVOCATION_STATUS_UNKNOWN"},
    { 0x00000080, "CERT_TRUST_IS_CYCLIC"},
    { 0x00000100, "CERT_TRUST_INVALID_EXTENSION"},
    { 0x00000200, "CERT_TRUST_INVALID_POLICY_CONSTRAINTS"},
    { 0x00000400, "CERT_TRUST_INVALID_BASIC_CONSTRAINTS"},
    { 0x00000800, "CERT_TRUST_INVALID_NAME_CONSTRAINTS"},
    { 0x00001000, "CERT_TRUST_HAS_NOT_SUPPORTED_NAME_CONSTRAINT"},
    { 0x00002000, "CERT_TRUST_HAS_NOT_DEFINED_NAME_CONSTRAINT"},
    { 0x00004000, "CERT_TRUST_HAS_NOT_PERMITTED_NAME_CONSTRAINT"},
    { 0x00008000, "CERT_TRUST_HAS_EXCLUDED_NAME_CONSTRAINT"},
    { 0x01000000, "CERT_TRUST_IS_OFFLINE_REVOCATION"},
    { 0x02000000, "CERT_TRUST_NO_ISSUANCE_CHAIN_POLICY"},
    { 0x04000000, "CERT_TRUST_IS_EXPLICIT_DISTRUST"},
    { 0x08000000, "CERT_TRUST_HAS_NOT_SUPPORTED_CRITICAL_EXT"},
    //Chain errors
    { 0x00010000, "CERT_TRUST_IS_PARTIAL_CHAIN"},
    { 0x00020000, "CERT_TRUST_CTL_IS_NOT_TIME_VALID"},
    { 0x00040000, "CERT_TRUST_CTL_IS_NOT_SIGNATURE_VALID"},
    { 0x00080000, "CERT_TRUST_CTL_IS_NOT_VALID_FOR_USAGE"}
};
 
void traceTrustStatus(DWORD err)
{
    if (err == 0)
        SAL_INFO("xmlsecurity.xmlsec", "  " << arErrStrings[0].name);
    for (std::size_t i = 1; i < SAL_N_ELEMENTS(arErrStrings); i++)
    {
        if (arErrStrings[i].error & err)
            SAL_INFO("xmlsecurity.xmlsec", "  " << arErrStrings[i].name);
    }
}
 
SecurityEnvironment_MSCryptImpl::SecurityEnvironment_MSCryptImpl( const uno::Reference< uno::XComponentContext >& xContext ) : m_hProv( NULL ) , m_pszContainer( nullptr ) , m_hKeyStore( nullptr ), m_hCertStore( nullptr ), m_hMySystemStore(nullptr), m_hRootSystemStore(nullptr), m_hTrustSystemStore(nullptr), m_hCaSystemStore(nullptr), m_bEnableDefault( false ){
 
    m_xServiceManager.set(xContext, uno::UNO_QUERY);
}
 
SecurityEnvironment_MSCryptImpl::~SecurityEnvironment_MSCryptImpl() {
 
    if( m_hProv != NULL ) {
        CryptReleaseContext( m_hProv, 0 ) ;
        m_hProv = NULL ;
    }
 
    if( m_pszContainer != nullptr ) {
        //TODO: Don't know whether or not it should be released now.
        m_pszContainer = nullptr ;
    }
 
    if( m_hCertStore != nullptr ) {
        CertCloseStore( m_hCertStore, CERT_CLOSE_STORE_FORCE_FLAG ) ;
        m_hCertStore = nullptr ;
    }
 
    if( m_hKeyStore != nullptr ) {
        CertCloseStore( m_hKeyStore, CERT_CLOSE_STORE_FORCE_FLAG ) ;
        m_hKeyStore = nullptr ;
    }
 
    //i120675, close the store handles
    if( m_hMySystemStore != nullptr ) {
        CertCloseStore( m_hMySystemStore, CERT_CLOSE_STORE_CHECK_FLAG ) ;
        m_hMySystemStore = nullptr ;
    }
 
    if( m_hRootSystemStore != nullptr ) {
        CertCloseStore( m_hRootSystemStore, CERT_CLOSE_STORE_CHECK_FLAG ) ;
        m_hRootSystemStore = nullptr ;
    }
 
    if( m_hTrustSystemStore != nullptr ) {
        CertCloseStore( m_hTrustSystemStore, CERT_CLOSE_STORE_CHECK_FLAG ) ;
        m_hTrustSystemStore = nullptr ;
    }
 
    if( m_hCaSystemStore != nullptr ) {
        CertCloseStore( m_hCaSystemStore, CERT_CLOSE_STORE_CHECK_FLAG ) ;
        m_hCaSystemStore = nullptr ;
    }
}
 
/* XServiceInfo */
OUString SAL_CALL SecurityEnvironment_MSCryptImpl::getImplementationName() {
    return OUString("com.sun.star.xml.crypto.SecurityEnvironment");
}
 
/* XServiceInfo */
sal_Bool SAL_CALL SecurityEnvironment_MSCryptImpl::supportsService( const OUString& serviceName) {
    uno::Sequence< OUString > seqServiceNames = getSupportedServiceNames() ;
    const OUString* pArray = seqServiceNames.getConstArray() ;
    for( sal_Int32 i = 0 ; i < seqServiceNames.getLength() ; i ++ ) {
        if( *( pArray + i ) == serviceName )
            return true ;
    }
    return false ;
}
 
/* XServiceInfo */
uno::Sequence< OUString > SAL_CALL SecurityEnvironment_MSCryptImpl::getSupportedServiceNames() {
    uno::Sequence<OUString> seqServiceNames { "com.sun.star.xml.crypto.SecurityEnvironment" };
    return seqServiceNames ;
}
 
/* XUnoTunnel */
sal_Int64 SAL_CALL SecurityEnvironment_MSCryptImpl::getSomething( const uno::Sequence< sal_Int8 >& aIdentifier )
{
    if( aIdentifier.getLength() == 16 && 0 == memcmp( getUnoTunnelId().getConstArray(), aIdentifier.getConstArray(), 16 ) ) {
        return reinterpret_cast<sal_Int64>(this);
    }
    return 0 ;
}
 
/* XUnoTunnel extension */
 
 
namespace
{
    class theSecurityEnvironment_MSCryptImplUnoTunnelId : public rtl::Static< UnoTunnelIdInit, theSecurityEnvironment_MSCryptImplUnoTunnelId > {};
}
 
const uno::Sequence< sal_Int8>& SecurityEnvironment_MSCryptImpl::getUnoTunnelId() {
    return theSecurityEnvironment_MSCryptImplUnoTunnelId::get().getSeq();
}
 
/* XUnoTunnel extension */
SecurityEnvironment_MSCryptImpl* SecurityEnvironment_MSCryptImpl::getImplementation( const uno::Reference< XInterface >& rObj ) {
    uno::Reference< XUnoTunnel > xUT( rObj , uno::UNO_QUERY ) ;
    if( xUT.is() ) {
        return reinterpret_cast<SecurityEnvironment_MSCryptImpl*>(xUT->getSomething( getUnoTunnelId() ));
    } else
        return nullptr ;
}
 
HCRYPTPROV SecurityEnvironment_MSCryptImpl::getCryptoProvider() {
    return m_hProv ;
}
 
void SecurityEnvironment_MSCryptImpl::setCryptoProvider( HCRYPTPROV aProv ) {
    if( m_hProv != NULL ) {
        CryptReleaseContext( m_hProv, 0 ) ;
        m_hProv = NULL ;
    }
 
    if( aProv != NULL ) {
        m_hProv = aProv ;
    }
}
 
LPCTSTR SecurityEnvironment_MSCryptImpl::getKeyContainer() {
    return m_pszContainer ;
}
 
void SecurityEnvironment_MSCryptImpl::setKeyContainer( LPCTSTR aKeyContainer ) {
    //TODO: Don't know whether or not it should be copied.
    m_pszContainer = aKeyContainer ;
}
 
 
HCERTSTORE SecurityEnvironment_MSCryptImpl::getCryptoSlot() {
    return m_hKeyStore ;
}
 
void SecurityEnvironment_MSCryptImpl::setCryptoSlot( HCERTSTORE aSlot) {
    if( m_hKeyStore != nullptr ) {
        CertCloseStore( m_hKeyStore, CERT_CLOSE_STORE_FORCE_FLAG ) ;
        m_hKeyStore = nullptr ;
    }
 
    if( aSlot != nullptr ) {
        m_hKeyStore = CertDuplicateStore( aSlot ) ;
    }
}
 
HCERTSTORE SecurityEnvironment_MSCryptImpl::getCertDb() {
    return m_hCertStore ;
}
 
void SecurityEnvironment_MSCryptImpl::setCertDb( HCERTSTORE aCertDb ) {
    if( m_hCertStore != nullptr ) {
        CertCloseStore( m_hCertStore, CERT_CLOSE_STORE_FORCE_FLAG ) ;
        m_hCertStore = nullptr ;
    }
 
    if( aCertDb != nullptr ) {
        m_hCertStore = CertDuplicateStore( aCertDb ) ;
    }
}
 
#ifdef SAL_LOG_INFO
 
// Based on sample code from MSDN
 
static OUString get_system_name(const void *pvSystemStore,
                            DWORD dwFlags)
{
    LPCWSTR ppwszSystemName;
    if (dwFlags & CERT_SYSTEM_STORE_RELOCATE_FLAG)
    {
        _CERT_SYSTEM_STORE_RELOCATE_PARA const * pRelocatePara;
        pRelocatePara = static_cast<_CERT_SYSTEM_STORE_RELOCATE_PARA const *>(pvSystemStore);
        ppwszSystemName = pRelocatePara->pwszSystemStore;
    }
    else
    {
        ppwszSystemName = static_cast<LPCWSTR>(pvSystemStore);
    }
    return o3tl::toU(ppwszSystemName);
}
 
extern "C" BOOL WINAPI cert_enum_physical_store_callback(const void *,
                                                         DWORD dwFlags,
                                                         LPCWSTR pwszStoreName,
                                                         PCERT_PHYSICAL_STORE_INFO,
                                                         void *,
                                                         void *)
{
    OUString name(o3tl::toU(pwszStoreName));
    if (dwFlags & CERT_PHYSICAL_STORE_PREDEFINED_ENUM_FLAG)
        name += " (implicitly created)";
    SAL_INFO("xmlsecurity.xmlsec", "  Physical store: " << name);
 
    return TRUE;
}
 
extern "C" BOOL WINAPI cert_enum_system_store_callback(const void *pvSystemStore,
                                                       DWORD dwFlags,
                                                       PCERT_SYSTEM_STORE_INFO,
                                                       void *,
                                                       void *)
{
    SAL_INFO("xmlsecurity.xmlsec", "System store: " << get_system_name(pvSystemStore, dwFlags));
 
    if (!CertEnumPhysicalStore(pvSystemStore,
                               dwFlags,
                               nullptr,
                               cert_enum_physical_store_callback))
    {
        DWORD dwErr = GetLastError();
        if (!(ERROR_FILE_NOT_FOUND == dwErr ||
              ERROR_NOT_SUPPORTED == dwErr))
        {
            SAL_WARN("xmlsecurity.xmlsec", "CertEnumPhysicalStore failed:" << WindowsErrorString(GetLastError()));
        }
    }
    return TRUE;
}
 
#endif
 
//Methods from XSecurityEnvironment
uno::Sequence< uno::Reference < XCertificate > > SecurityEnvironment_MSCryptImpl::getPersonalCertificates()
{
    sal_Int32 length ;
    X509Certificate_MSCryptImpl* xcert ;
    std::list< X509Certificate_MSCryptImpl* > certsList ;
    PCCERT_CONTEXT pCertContext = nullptr;
 
    //firstly, we try to find private keys in given key store.
    if( m_hKeyStore != nullptr ) {
        pCertContext = CertEnumCertificatesInStore( m_hKeyStore, pCertContext );
        while (pCertContext)
        {
            xcert = MswcryCertContextToXCert( pCertContext ) ;
            if( xcert != nullptr )
                certsList.push_back( xcert ) ;
            pCertContext = CertEnumCertificatesInStore( m_hKeyStore, pCertContext );
        }
    }
 
    //Thirdly, we try to find certificate from system default key store.
    if( m_bEnableDefault ) {
        HCERTSTORE hSystemKeyStore ;
        DWORD      dwKeySpec;
        NCRYPT_KEY_HANDLE hCryptKey;
 
#ifdef SAL_LOG_INFO
        CertEnumSystemStore(CERT_SYSTEM_STORE_CURRENT_USER, nullptr, nullptr, cert_enum_system_store_callback);
#endif
 
        hSystemKeyStore = CertOpenSystemStoreW( 0, L"MY" ) ;
        if( hSystemKeyStore != nullptr ) {
            pCertContext = CertEnumCertificatesInStore( hSystemKeyStore, pCertContext );
            while (pCertContext)
            {
                // for checking whether the certificate is a personal certificate or not.
                DWORD dwFlags = CRYPT_ACQUIRE_COMPARE_KEY_FLAG | CRYPT_ACQUIRE_ONLY_NCRYPT_KEY_FLAG;
                HCRYPTPROV_OR_NCRYPT_KEY_HANDLE* phCryptProvOrNCryptKey = &hCryptKey;
                if(!(CryptAcquireCertificatePrivateKey(pCertContext,
                        dwFlags,
                        nullptr,
                        phCryptProvOrNCryptKey,
                        &dwKeySpec,
                        nullptr)))
                {
                    // Not Privatekey found. SKIP this one.
                    pCertContext = CertEnumCertificatesInStore( hSystemKeyStore, pCertContext );
                    continue;
                }
                // then TODO : Check the personal cert is valid or not.
 
                xcert = MswcryCertContextToXCert( pCertContext ) ;
                if( xcert != nullptr )
                    certsList.push_back( xcert ) ;
                pCertContext = CertEnumCertificatesInStore( hSystemKeyStore, pCertContext );
            }
        }
 
        CertCloseStore( hSystemKeyStore, CERT_CLOSE_STORE_CHECK_FLAG ) ;
    }
 
    length = certsList.size() ;
    if( length != 0 ) {
        int i = 0;
        uno::Sequence< uno::Reference< XCertificate > > certSeq( length ) ;
 
        for( const auto& rXCert : certsList ) {
            certSeq[i] = rXCert ;
            ++i;
        }
 
        return certSeq ;
    }
 
    return uno::Sequence< uno::Reference< XCertificate > >() ;
}
 
 
uno::Reference< XCertificate > SecurityEnvironment_MSCryptImpl::getCertificate( const OUString& issuerName, const uno::Sequence< sal_Int8 >& serialNumber ) {
    unsigned int i ;
    X509Certificate_MSCryptImpl *xcert = nullptr ;
    PCCERT_CONTEXT pCertContext = nullptr ;
    HCERTSTORE hCertStore = nullptr ;
    CRYPT_INTEGER_BLOB cryptSerialNumber ;
    CERT_INFO certInfo ;
 
    // for correct encoding
    sal_uInt16 encoding ;
    rtl_Locale *pLocale = nullptr ;
    osl_getProcessLocale( &pLocale ) ;
    encoding = osl_getTextEncodingFromLocale( pLocale ) ;
 
    //Create cert info from issue and serial
    LPCWSTR pszName = o3tl::toW( issuerName.getStr() );
 
    if( ! ( CertStrToNameW(
        X509_ASN_ENCODING | PKCS_7_ASN_ENCODING ,
        pszName ,
        CERT_X500_NAME_STR | CERT_NAME_STR_REVERSE_FLAG | CERT_NAME_STR_ENABLE_UTF8_UNICODE_FLAG,
        nullptr ,
        nullptr ,
        &certInfo.Issuer.cbData, nullptr ) )
    ) {
        return nullptr ;
    }
 
    certInfo.Issuer.pbData = static_cast<BYTE*>(malloc( certInfo.Issuer.cbData ));
    if(!certInfo.Issuer.pbData)
        throw uno::RuntimeException() ;
 
    if( ! ( CertStrToNameW(
        X509_ASN_ENCODING | PKCS_7_ASN_ENCODING ,
        pszName ,
        CERT_X500_NAME_STR | CERT_NAME_STR_REVERSE_FLAG | CERT_NAME_STR_ENABLE_UTF8_UNICODE_FLAG,
        nullptr ,
        certInfo.Issuer.pbData ,
        &certInfo.Issuer.cbData, nullptr ) )
    ) {
        free( certInfo.Issuer.pbData ) ;
        return nullptr ;
    }
 
    //Get the SerialNumber
    cryptSerialNumber.cbData = serialNumber.getLength() ;
    cryptSerialNumber.pbData = static_cast<BYTE*>(malloc( cryptSerialNumber.cbData));
    if (!cryptSerialNumber.pbData)
    {
        free( certInfo.Issuer.pbData ) ;
        throw uno::RuntimeException() ;
    }
    for( i = 0; i < cryptSerialNumber.cbData; i ++ )
        cryptSerialNumber.pbData[i] = serialNumber[ cryptSerialNumber.cbData - i - 1 ] ;
 
    certInfo.SerialNumber.cbData = cryptSerialNumber.cbData ;
    certInfo.SerialNumber.pbData = cryptSerialNumber.pbData ;
 
    // Get the Cert from all store.
    for( i = 0 ; i < 6 ; i ++ )
    {
        switch(i)
        {
        case 0:
            if(m_hKeyStore == nullptr) continue ;
            hCertStore = m_hKeyStore ;
            break;
        case 1:
            if(m_hCertStore == nullptr) continue ;
            hCertStore = m_hCertStore ;
            break;
        case 2:
            hCertStore = CertOpenSystemStoreW( 0, L"MY" ) ;
            if(hCertStore == nullptr || !m_bEnableDefault) continue ;
            break;
        case 3:
            hCertStore = CertOpenSystemStoreW( 0, L"Root" ) ;
            if(hCertStore == nullptr || !m_bEnableDefault) continue ;
            break;
        case 4:
            hCertStore = CertOpenSystemStoreW( 0, L"Trust" ) ;
            if(hCertStore == nullptr || !m_bEnableDefault) continue ;
            break;
        case 5:
            hCertStore = CertOpenSystemStoreW( 0, L"CA" ) ;
            if(hCertStore == nullptr || !m_bEnableDefault) continue ;
            break;
        default:
            i=6;
            continue;
        }
 
/*******************************************************************************
 * This code reserved for remind us there are another way to find one cert by
 * IssuerName&serialnumber. You can use the code to replaced the function
 * CertFindCertificateInStore IF and ONLY IF you must find one special cert in
 * certStore but can not be found by CertFindCertificateInStore , then , you
 * should also change the same part in libxmlsec/.../src/mscrypto/x509vfy.c#875.
 * By Chandler Peng(chandler.peng@sun.com)
 *****/
/*******************************************************************************
        pCertContext = NULL ;
        found = 0;
        do{
            //  1. enum the certs has same string in the issuer string.
            pCertContext = CertEnumCertificatesInStore( hCertStore , pCertContext ) ;
            if( pCertContext != NULL )
            {
                // 2. check the cert's issuer name .
                char* issuer = NULL ;
                DWORD cbIssuer = 0 ;
 
                cbIssuer = CertNameToStr(
                    X509_ASN_ENCODING | PKCS_7_ASN_ENCODING ,
                    &( pCertContext->pCertInfo->Issuer ),
                    CERT_X500_NAME_STR | CERT_NAME_STR_REVERSE_FLAG ,
                    NULL, 0
                ) ;
 
                if( cbIssuer == 0 ) continue ; // discard this cert;
 
                issuer = (char *)malloc( cbIssuer ) ;
                if( issuer == NULL )  // discard this cert;
                {
                    free( cryptSerialNumber.pbData) ;
                    free( certInfo.Issuer.pbData ) ;
                    CertFreeCertificateContext( pCertContext ) ;
                    if(i != 0 && i != 1) CertCloseStore( hCertStore, CERT_CLOSE_STORE_CHECK_FLAG ) ;
                    throw RuntimeException() ;
                }
 
                cbIssuer = CertNameToStr(
                    X509_ASN_ENCODING | PKCS_7_ASN_ENCODING ,
                    &( pCertContext->pCertInfo->Issuer ),
                    CERT_X500_NAME_STR | CERT_NAME_STR_REVERSE_FLAG ,
                    issuer, cbIssuer
                ) ;
 
                if( cbIssuer <= 0 )
                {
                    free( issuer ) ;
                    continue ;// discard this cert;
                }
 
                if(strncmp(pszName , issuer , cbIssuer) != 0)
                {
                    free( issuer ) ;
                    continue ;// discard this cert;
                }
                free( issuer ) ;
 
                // 3. check the serial number.
                if( memcmp( cryptSerialNumber.pbData , pCertContext->pCertInfo->SerialNumber.pbData  , cryptSerialNumber.cbData ) != 0 )
                {
                    continue ;// discard this cert;
                }
 
                // 4. confirm and break;
                found = 1;
                break ;
            }
 
        }while(pCertContext);
 
        if(i != 0 && i != 1) CertCloseStore( hCertStore, CERT_CLOSE_STORE_CHECK_FLAG ) ;
        if( found != 0 ) break; // Found the certificate.
********************************************************************************/
 
        pCertContext = CertFindCertificateInStore(
            hCertStore,
            X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
            0,
            CERT_FIND_SUBJECT_CERT,
            &certInfo,
            nullptr
        ) ;
 
        if(i != 0 && i != 1) CertCloseStore( hCertStore, CERT_CLOSE_STORE_CHECK_FLAG ) ;
        if( pCertContext != nullptr ) break ; // Found the certificate.
 
    }
 
    if( cryptSerialNumber.pbData ) free( cryptSerialNumber.pbData ) ;
    if( certInfo.Issuer.pbData ) free( certInfo.Issuer.pbData ) ;
 
    if( pCertContext != nullptr ) {
        xcert = MswcryCertContextToXCert( pCertContext ) ;
        if( pCertContext ) CertFreeCertificateContext( pCertContext ) ;
    } else {
        xcert = nullptr ;
    }
 
    return xcert ;
}
 
uno::Reference< XCertificate > SecurityEnvironment_MSCryptImpl::getCertificate( const OUString& issuerName, const OUString& serialNumber ) {
    uno::Sequence< sal_Int8 > serial = xmlsecurity::numericStringToBigInteger( serialNumber ) ;
    return getCertificate( issuerName, serial ) ;
}
 
uno::Sequence< uno::Reference < XCertificate > > SecurityEnvironment_MSCryptImpl::buildCertificatePath( const uno::Reference< XCertificate >& begin ) {
    PCCERT_CHAIN_CONTEXT pChainContext ;
    PCCERT_CONTEXT pCertContext ;
    const X509Certificate_MSCryptImpl* xcert ;
 
    CERT_ENHKEY_USAGE   enhKeyUsage ;
    CERT_USAGE_MATCH    certUsage ;
    CERT_CHAIN_PARA     chainPara ;
 
    enhKeyUsage.cUsageIdentifier = 0 ;
    enhKeyUsage.rgpszUsageIdentifier = nullptr ;
    certUsage.dwType = USAGE_MATCH_TYPE_AND ;
    certUsage.Usage = enhKeyUsage ;
    chainPara.cbSize = sizeof( CERT_CHAIN_PARA ) ;
    chainPara.RequestedUsage = certUsage ;
 
    uno::Reference< XUnoTunnel > xCertTunnel( begin, uno::UNO_QUERY_THROW ) ;
    xcert = reinterpret_cast<X509Certificate_MSCryptImpl*>(xCertTunnel->getSomething( X509Certificate_MSCryptImpl::getUnoTunnelId() ));
    if( xcert == nullptr ) {
        throw uno::RuntimeException() ;
    }
 
    pCertContext = xcert->getMswcryCert() ;
 
    pChainContext = nullptr ;
 
    BOOL bChain = FALSE;
    if( pCertContext != nullptr )
    {
        HCERTSTORE hAdditionalStore = nullptr;
        HCERTSTORE hCollectionStore = nullptr;
        if (m_hCertStore && m_hKeyStore)
        {
            //Merge m_hCertStore and m_hKeyStore into one store.
            hCollectionStore = CertOpenStore(
                CERT_STORE_PROV_COLLECTION ,
                0 ,
                NULL ,
                0 ,
                nullptr
                ) ;
            if (hCollectionStore != nullptr)
            {
                CertAddStoreToCollection (
                     hCollectionStore ,
                     m_hCertStore ,
                     CERT_PHYSICAL_STORE_ADD_ENABLE_FLAG ,
                     0) ;
                CertAddStoreToCollection (
                     hCollectionStore ,
                     m_hCertStore ,
                     CERT_PHYSICAL_STORE_ADD_ENABLE_FLAG ,
                     0) ;
                hAdditionalStore = hCollectionStore;
            }
 
        }
 
        //if the merge of both stores failed then we add only m_hCertStore
        if (hAdditionalStore == nullptr && m_hCertStore)
            hAdditionalStore = m_hCertStore;
        else if (hAdditionalStore == nullptr && m_hKeyStore)
            hAdditionalStore = m_hKeyStore;
        else
            hAdditionalStore = nullptr;
 
        //CertGetCertificateChain searches by default in MY, CA, ROOT and TRUST
        bChain = CertGetCertificateChain(
            nullptr ,
            pCertContext ,
            nullptr , //use current system time
            hAdditionalStore,
            &chainPara ,
            CERT_CHAIN_REVOCATION_CHECK_CHAIN | CERT_CHAIN_TIMESTAMP_TIME ,
            nullptr ,
            &pChainContext);
        if (!bChain)
            pChainContext = nullptr;
 
        //Close the additional store
       CertCloseStore(hCollectionStore, CERT_CLOSE_STORE_CHECK_FLAG);
    }
 
    if(bChain &&  pChainContext != nullptr && pChainContext->cChain > 0 )
    {
        PCCERT_CONTEXT pCertInChain ;
        PCERT_SIMPLE_CHAIN pCertChain ;
        X509Certificate_MSCryptImpl* pCert ;
 
        pCertChain = pChainContext->rgpChain[0] ;
        if( pCertChain->cElement ) {
            uno::Sequence< uno::Reference< XCertificate > > xCertChain( pCertChain->cElement ) ;
 
            for( unsigned int i = 0 ; i < pCertChain->cElement ; i ++ ) {
                if( pCertChain->rgpElement[i] )
                    pCertInChain = pCertChain->rgpElement[i]->pCertContext ;
                else
                    pCertInChain = nullptr ;
 
                if( pCertInChain != nullptr ) {
                    pCert = MswcryCertContextToXCert( pCertInChain ) ;
                    if( pCert != nullptr )
                        xCertChain[i] = pCert ;
                }
            }
 
            CertFreeCertificateChain( pChainContext ) ;
            pChainContext = nullptr ;
 
            return xCertChain ;
        }
    }
    if (pChainContext)
        CertFreeCertificateChain(pChainContext);
 
    return uno::Sequence< uno::Reference < XCertificate > >();
}
 
uno::Reference< XCertificate > SecurityEnvironment_MSCryptImpl::createCertificateFromRaw( const uno::Sequence< sal_Int8 >& rawCertificate ) {
    X509Certificate_MSCryptImpl* xcert ;
 
    if( rawCertificate.getLength() > 0 ) {
        xcert = new X509Certificate_MSCryptImpl() ;
        xcert->setRawCert( rawCertificate ) ;
    } else {
        xcert = nullptr ;
    }
 
    return xcert ;
}
 
uno::Reference< XCertificate > SecurityEnvironment_MSCryptImpl::createCertificateFromAscii( const OUString& asciiCertificate ) {
    xmlChar* chCert ;
    xmlSecSize certSize ;
 
    OString oscert = OUStringToOString( asciiCertificate , RTL_TEXTENCODING_ASCII_US ) ;
 
    chCert = xmlStrndup( reinterpret_cast<const xmlChar*>(oscert.getStr()), static_cast<int>(oscert.getLength()) ) ;
 
    certSize = xmlSecBase64Decode( chCert, chCert, xmlStrlen( chCert ) ) ;
 
    uno::Sequence< sal_Int8 > rawCert( certSize ) ;
    for( xmlSecSize i = 0 ; i < certSize ; i ++ )
        rawCert[i] = *( chCert + i ) ;
 
    xmlFree( chCert ) ;
 
    return createCertificateFromRaw( rawCert ) ;
}
 
 
HCERTSTORE getCertStoreForIntermediatCerts(
    const uno::Sequence< uno::Reference< css::security::XCertificate > >& seqCerts)
{
    HCERTSTORE store = nullptr;
    store = CertOpenStore(
        CERT_STORE_PROV_MEMORY, 0, NULL, 0, nullptr);
    if (store == nullptr)
        return nullptr;
 
    for (int i = 0; i < seqCerts.getLength(); i++)
    {
        SAL_INFO("xmlsecurity.xmlsec", "Added temporary certificate: " << seqCerts[i]->getSubjectName());
 
        uno::Sequence<sal_Int8> data = seqCerts[i]->getEncoded();
        PCCERT_CONTEXT cert = CertCreateCertificateContext(
            X509_ASN_ENCODING, reinterpret_cast<const BYTE*>(&data[0]), data.getLength());
        //Adding the certificate creates a copy and not just increases the ref count
        //Therefore we free later the certificate that we now add
        CertAddCertificateContextToStore(store, cert, CERT_STORE_ADD_ALWAYS, nullptr);
        CertFreeCertificateContext(cert);
    }
    return store;
}
 
//We return only valid or invalid, as long as the API documentation expresses
//explicitly that all validation steps are carried out even if one or several
//errors occur. See also
//http://wiki.openoffice.org/wiki/Certificate_Path_Validation#Validation_status
sal_Int32 SecurityEnvironment_MSCryptImpl::verifyCertificate(
    const uno::Reference< css::security::XCertificate >& aCert,
    const uno::Sequence< uno::Reference< css::security::XCertificate > >& seqCerts)
{
    sal_Int32 validity = 0;
    PCCERT_CHAIN_CONTEXT pChainContext = nullptr;
    PCCERT_CONTEXT pCertContext = nullptr;
 
    uno::Reference< XUnoTunnel > xCertTunnel( aCert, uno::UNO_QUERY_THROW ) ;
 
    SAL_INFO("xmlsecurity.xmlsec", "Start verification of certificate: " << aCert->getSubjectName());
 
    auto xcert = reinterpret_cast<const X509Certificate_MSCryptImpl*>
            (xCertTunnel->getSomething( X509Certificate_MSCryptImpl::getUnoTunnelId() ));
    if( xcert == nullptr ) {
        throw uno::RuntimeException() ;
    }
 
    pCertContext = xcert->getMswcryCert() ;
 
    CERT_ENHKEY_USAGE   enhKeyUsage ;
    CERT_USAGE_MATCH    certUsage ;
    CERT_CHAIN_PARA     chainPara ;
    memset(&chainPara, 0, sizeof(CERT_CHAIN_PARA));
 
    //Prepare parameter for CertGetCertificateChain
    enhKeyUsage.cUsageIdentifier = 0 ;
    enhKeyUsage.rgpszUsageIdentifier = nullptr ;
    certUsage.dwType = USAGE_MATCH_TYPE_AND ;
    certUsage.Usage = enhKeyUsage ;
    chainPara.cbSize = sizeof( CERT_CHAIN_PARA ) ;
    chainPara.RequestedUsage = certUsage ;
 
 
    HCERTSTORE hCollectionStore = nullptr;
    HCERTSTORE hIntermediateCertsStore = nullptr;
    BOOL bChain = FALSE;
    if( pCertContext != nullptr )
    {
        hIntermediateCertsStore =
            getCertStoreForIntermediatCerts(seqCerts);
 
        //Merge m_hCertStore and m_hKeyStore and the store of the intermediate
        //certificates into one store.
        hCollectionStore = CertOpenStore(
            CERT_STORE_PROV_COLLECTION ,
            0 ,
            NULL ,
            0 ,
            nullptr
            ) ;
        if (hCollectionStore != nullptr)
        {
            CertAddStoreToCollection (
                hCollectionStore ,
                m_hCertStore ,
                CERT_PHYSICAL_STORE_ADD_ENABLE_FLAG ,
                0) ;
            CertAddStoreToCollection (
                hCollectionStore ,
                m_hCertStore ,
                CERT_PHYSICAL_STORE_ADD_ENABLE_FLAG ,
                0) ;
            CertAddStoreToCollection (
                hCollectionStore,
                hIntermediateCertsStore,
                CERT_PHYSICAL_STORE_ADD_ENABLE_FLAG,
                0);
 
        }
 
        //CertGetCertificateChain searches by default in MY, CA, ROOT and TRUST
        //We do not check revocation of the root. In most cases there are none.
        //Then we would get CERT_TRUST_REVOCATION_STATUS_UNKNOWN
        SAL_INFO("xmlsecurity.xmlsec", "Verifying cert using revocation information.");
        bChain = CertGetCertificateChain(
            nullptr ,
            pCertContext ,
            nullptr , //use current system time
            hCollectionStore,
            &chainPara ,
            CERT_CHAIN_REVOCATION_CHECK_CHAIN | CERT_CHAIN_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT,
            nullptr ,
            &pChainContext);
 
        if (bChain && pChainContext->cChain > 0)
        {
            SAL_INFO("xmlsecurity.xmlsec", "Overall error status (all chains):");
            traceTrustStatus(pChainContext->TrustStatus.dwErrorStatus);
            //highest quality chains come first
            PCERT_SIMPLE_CHAIN pSimpleChain = pChainContext->rgpChain[0];
            SAL_INFO("xmlsecurity.xmlsec", "Error status of first chain:");
            traceTrustStatus(pSimpleChain->TrustStatus.dwErrorStatus);
 
            //CERT_TRUST_REVOCATION_STATUS_UNKNOWN is also set if a certificate
            //has no AIA(OCSP) or CRLDP extension and there is no CRL locally installed.
            DWORD revocationFlags = CERT_TRUST_REVOCATION_STATUS_UNKNOWN |
                CERT_TRUST_IS_OFFLINE_REVOCATION;
            DWORD otherErrorsMask = ~revocationFlags;
            if( !(pSimpleChain->TrustStatus.dwErrorStatus & otherErrorsMask))
 
            {
                //No errors except maybe those caused by missing revocation information
                //Check if there are errors
                if ( pSimpleChain->TrustStatus.dwErrorStatus & revocationFlags)
                {
                    //No revocation information. Because MSDN documentation is not
                    //clear about if all other tests are performed if an error occurs,
                    //we test again, without requiring revocation checking.
                    CertFreeCertificateChain(pChainContext);
                    pChainContext = nullptr;
                    SAL_INFO("xmlsecurity.xmlsec", "Checking again but without requiring revocation information.");
                    bChain = CertGetCertificateChain(
                        nullptr ,
                        pCertContext ,
                        nullptr , //use current system time
                        hCollectionStore,
                        &chainPara ,
                        0,
                        nullptr ,
                        &pChainContext);
                    if (bChain
                        && pChainContext->cChain > 0
                        && pChainContext->rgpChain[0]->TrustStatus.dwErrorStatus == CERT_TRUST_NO_ERROR)
                    {
                        SAL_INFO("xmlsecurity.xmlsec", "Certificate is valid.");
                        validity = css::security::CertificateValidity::VALID;
                    }
                    else
                    {
                        SAL_INFO("xmlsecurity.xmlsec", "Certificate is invalid.");
                    }
                }
                else
                {
                    //valid and revocation information available
                    SAL_INFO("xmlsecurity.xmlsec", "Certificate is valid.");
                    validity = css::security::CertificateValidity::VALID;
                }
            }
            else
            {
                //invalid
                SAL_INFO("xmlsecurity.xmlsec", "Certificate is invalid.");
                validity = css::security::CertificateValidity::INVALID ;
            }
        }
        else
        {
            SAL_INFO("xmlsecurity.xmlsec", "CertGetCertificateChaine failed.");
        }
    }
 
    if (pChainContext)
    {
        CertFreeCertificateChain(pChainContext);
        pChainContext = nullptr;
    }
 
    //Close the additional store, do not destroy the contained certs
    CertCloseStore(hCollectionStore, CERT_CLOSE_STORE_CHECK_FLAG);
    //Close the temporary store containing the intermediate certificates and make
    //sure all certificates are deleted.
    CertCloseStore(hIntermediateCertsStore, CERT_CLOSE_STORE_CHECK_FLAG);
 
    return validity ;
}
 
sal_Int32 SecurityEnvironment_MSCryptImpl::getCertificateCharacters( const css::uno::Reference< css::security::XCertificate >& aCert ) {
    sal_Int32 characters ;
    PCCERT_CONTEXT pCertContext ;
    const X509Certificate_MSCryptImpl* xcert ;
 
    uno::Reference< XUnoTunnel > xCertTunnel( aCert, uno::UNO_QUERY_THROW ) ;
    xcert = reinterpret_cast<X509Certificate_MSCryptImpl*>(xCertTunnel->getSomething( X509Certificate_MSCryptImpl::getUnoTunnelId() ));
    if( xcert == nullptr ) {
        throw uno::RuntimeException() ;
    }
 
    pCertContext = xcert->getMswcryCert() ;
 
    characters = 0x00000000 ;
 
    //Firstly, make sentence whether or not the cert is self-signed.
    if( CertCompareCertificateName( X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, &(pCertContext->pCertInfo->Subject), &(pCertContext->pCertInfo->Issuer) ) ) {
        characters |= css::security::CertificateCharacters::SELF_SIGNED ;
    } else {
        characters &= ~ css::security::CertificateCharacters::SELF_SIGNED ;
    }
 
    //Secondly, make sentence whether or not the cert has a private key.
    {
        BOOL    fCallerFreeProv ;
        DWORD   dwKeySpec ;
        NCRYPT_KEY_HANDLE hKey = 0;
        DWORD dwFlags = CRYPT_ACQUIRE_ONLY_NCRYPT_KEY_FLAG;
        HCRYPTPROV_OR_NCRYPT_KEY_HANDLE* phCryptProvOrNCryptKey = &hKey;
        if( CryptAcquireCertificatePrivateKey( pCertContext ,
                   dwFlags,
                   nullptr ,
                   phCryptProvOrNCryptKey,
                   &dwKeySpec,
                   &fCallerFreeProv )
        ) {
            characters |=  css::security::CertificateCharacters::HAS_PRIVATE_KEY ;
 
            if (hKey && fCallerFreeProv)
                NCryptFreeObject(hKey);
        } else {
            characters &= ~ css::security::CertificateCharacters::HAS_PRIVATE_KEY ;
        }
    }
    return characters ;
}
 
void SecurityEnvironment_MSCryptImpl::enableDefaultCrypt( bool enable ) {
    m_bEnableDefault = enable ;
}
 
bool SecurityEnvironment_MSCryptImpl::defaultEnabled() {
    return m_bEnableDefault ;
}
 
static X509Certificate_MSCryptImpl* MswcryCertContextToXCert( PCCERT_CONTEXT cert )
{
    X509Certificate_MSCryptImpl* xcert ;
 
    if( cert != nullptr ) {
        xcert = new X509Certificate_MSCryptImpl() ;
        xcert->setMswcryCert( cert ) ;
    } else {
        xcert = nullptr ;
    }
 
    return xcert ;
}
 
OUString SecurityEnvironment_MSCryptImpl::getSecurityEnvironmentInformation()
{
    return OUString("Microsoft Crypto API");
}
 
xmlSecKeysMngrPtr SecurityEnvironment_MSCryptImpl::createKeysManager() {
 
    xmlSecKeysMngrPtr pKeysMngr = nullptr ;
 
    /*-
     * The following lines is based on the of xmlsec-mscrypto crypto engine
     */
    pKeysMngr = xmlsecurity::MSCryptoAppliedKeysMngrCreate() ;
    if( pKeysMngr == nullptr )
        throw uno::RuntimeException() ;
 
    /*-
     * Adopt system default certificate store.
     */
    if( defaultEnabled() ) {
        //Add system key store into the keys manager.
        m_hMySystemStore = CertOpenSystemStoreW( 0, L"MY" ) ;
        if( m_hMySystemStore != nullptr ) {
            if( xmlsecurity::MSCryptoAppliedKeysMngrAdoptKeyStore( pKeysMngr, m_hMySystemStore ) < 0 ) {
                CertCloseStore( m_hMySystemStore, CERT_CLOSE_STORE_CHECK_FLAG ) ;
                m_hMySystemStore = nullptr;
                throw uno::RuntimeException() ;
            }
            m_hMySystemStore = nullptr;
        }
 
        //Add system root store into the keys manager.
        m_hRootSystemStore = CertOpenSystemStoreW( 0, L"Root" ) ;
        if( m_hRootSystemStore != nullptr ) {
            if( xmlsecurity::MSCryptoAppliedKeysMngrAdoptTrustedStore( pKeysMngr, m_hRootSystemStore ) < 0 ) {
                CertCloseStore( m_hRootSystemStore, CERT_CLOSE_STORE_CHECK_FLAG ) ;
                m_hRootSystemStore = nullptr;
                throw uno::RuntimeException() ;
            }
            m_hRootSystemStore = nullptr;
        }
 
        //Add system trusted store into the keys manager.
        m_hTrustSystemStore = CertOpenSystemStoreW( 0, L"Trust" ) ;
        if( m_hTrustSystemStore != nullptr ) {
            if( xmlsecurity::MSCryptoAppliedKeysMngrAdoptUntrustedStore( pKeysMngr, m_hTrustSystemStore ) < 0 ) {
                CertCloseStore( m_hTrustSystemStore, CERT_CLOSE_STORE_CHECK_FLAG ) ;
                m_hTrustSystemStore = nullptr;
                throw uno::RuntimeException() ;
            }
            m_hTrustSystemStore = nullptr;
        }
 
        //Add system CA store into the keys manager.
        m_hCaSystemStore = CertOpenSystemStoreW( 0, L"CA" ) ;
        if( m_hCaSystemStore != nullptr ) {
            if( xmlsecurity::MSCryptoAppliedKeysMngrAdoptUntrustedStore( pKeysMngr, m_hCaSystemStore ) < 0 ) {
                CertCloseStore( m_hCaSystemStore, CERT_CLOSE_STORE_CHECK_FLAG ) ;
                m_hCaSystemStore = nullptr;
                throw uno::RuntimeException() ;
            }
            m_hCaSystemStore = nullptr;
        }
    }
 
    return pKeysMngr ;
}
void SecurityEnvironment_MSCryptImpl::destroyKeysManager(xmlSecKeysMngrPtr pKeysMngr) {
    if( pKeysMngr != nullptr ) {
        xmlSecKeysMngrDestroy( pKeysMngr ) ;
    }
}
 
extern "C" SAL_DLLPUBLIC_EXPORT uno::XInterface*
com_sun_star_xml_crypto_SecurityEnvironment_get_implementation(
    uno::XComponentContext* pCtx, uno::Sequence<uno::Any> const& /*rSeq*/)
{
    return cppu::acquire(new SecurityEnvironment_MSCryptImpl(pCtx));
}
 
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */

V547 Expression 'certInfo.Issuer.pbData' is always true.

V547 Expression 'pCertContext' is always true.

V519 The 'store' variable is assigned values twice successively. Perhaps this is a mistake. Check lines: 756, 757.

V547 Expression 'cryptSerialNumber.pbData' is always true.