/* [Windows 1251]
 * [Use `iconv -f WINDOWS-1251', if needed]
 */
/*
 * Copyright(C) 2013
 *
 * Этот файл содержит информацию, являющуюся
 * собственностью компании Крипто-Про.
 *
 * Любая часть этого файла не может быть скопирована,
 * исправлена, переведена на другие языки,
 * локализована или модифицирована любым способом,
 * откомпилирована, передана по сети с или на
 * любую компьютерную систему без предварительного
 * заключения соглашения с компанией Крипто-Про.
 *
 * This is proprietary information of
 * Crypto-Pro company.
 *
 * Any part of this file can not be copied,
 * corrected, translated into other languages,
 * localized or modified by any means,
 * compiled, transferred over a network from or to
 * any computer system without preliminary
 * agreement with Crypto-Pro company
 */

/*!
 * \version $
 * \date $Date: $
 * \author $Author: shakhanov $
 *
 * \brief Интерфейс запросов на сертификат к УЦ 2.0
 *
 */

#ifndef __CPCA20_REQUEST_H__
#define __CPCA20_REQUEST_H__
#include "UnixRequestImpl.h"
#define UR20_DISP_NEW 3
#define UR20_DISP_ACCEPTED 4
#define UR20_DISP_REJECTED 5
#define UR20_DISP_UNDER_SUBMISSION 6
#define UR20_DISP_COMPLETED 7
#define UR20_DISP_APPROVED 8

#ifdef DEBUG
#include <iostream>
#endif

#ifdef UNIX
#define CPCA20_ENCODING CP_UTF8
#else
#define CPCA20_ENCODING CP_ACP
#endif // UNIX

/*! \ingroup EnrollAPI
 *  \class CPCA20Request::UnixRequestImpl
 *  \brief Реализация интерфеса взаимодействия с "КриптоПро УЦ 2.0"
 *
 * В случае вызовов методов RegisterUser и GetUserRegisterInfo в URL следует включать папку \n
 * Например, https://cpca20.external.example.ru/UI/folderName 
 *
 */

class CPCA20Request: public UnixRequestImpl
{
    public:
        CPCA20Request();
	virtual ~CPCA20Request() {
	    SysFreeString(bstrRequestHTML);
	    SysFreeString(bstrCertHTML);
            if ( hUserProv )
                CryptReleaseContext( hUserProv, 0 );
            if ( pUserCert )
                CertFreeCertificateContext( pUserCert );
            if ( hUserStore )
                CertCloseStore( hUserStore, 0 );
	}

        virtual HRESULT SetCredential(
            /* [in] */ UserCallbacks *_pCallbacks,
            /* [in] */ X509EnrollmentAuthFlags _AuthType,
            /* [in] */ X509EnrollmentCheckChainFlags _CheckChainType,
            /* [in] */ const BSTR strCredential,
            /* [in][out] */ CSecurePin *_sbPassword,
            /* [in] */ BOOL UseLocalMachineCert )
        {
            HRESULT res = UnixRequestImpl::SetCredential( _pCallbacks,
                          _AuthType, _CheckChainType, strCredential, _sbPassword, UseLocalMachineCert );
            if ( res != S_OK )
            { return res; }

	    if (strCredential) {
		const char *strToken = ConvertBSTRToString( strCredential );
		token = strToken;
		delete[] strToken;
	    } else {
		token = "";
	    }
            password = _sbPassword ? _sbPassword->ptr() : "";

            dwCertKeySpec = 0;
            if ( fNeedFreeProv )
            {
                CryptReleaseContext( hUserProv, 0 );
                hUserProv = 0;
                fNeedFreeProv = FALSE;
            }
            if ( pUserCert )
            {
                CertFreeCertificateContext( pUserCert );
                pUserCert = 0;
            }
            if ( hUserStore )
            {
                CertCloseStore( hUserStore, 0 );
                hUserStore = 0;
            }

            if ( _AuthType != X509AuthCertificate )
            {
                return S_OK;
            }

	    hUserStore = CertOpenSystemStore(0, _TEXT("My"));

            if ( hUserStore == 0 )
            {
                res = GetLastError();
                goto err;
            }

            pUserCert = GetCertContext( hUserStore );

            if ( pUserCert == 0 )
            {
                CertCloseStore( hUserStore, 0 );
                hUserStore = CertOpenStore(
                                 CERT_STORE_PROV_SYSTEM,
                                 0,
                                 0,//hProv,
                                 CERT_STORE_OPEN_EXISTING_FLAG | CERT_SYSTEM_STORE_LOCAL_MACHINE,
                                 L"My" );
                if ( hUserStore == 0 )
                {
                    res = NTE_FAIL;
                    goto err;
                }
                pUserCert = GetCertContext( hUserStore );
                if ( pUserCert == 0 )
                {
                    res = NTE_FAIL;
                    goto err;
                }
            }

            if ( !CryptAcquireCertificatePrivateKey(
                        pUserCert,
                        0, 0, &hUserProv, &dwCertKeySpec, &fNeedFreeProv ) )
            {
                res = GetLastError();
                goto err;
            }
            if ( _sbPassword && !CryptSetProvParam( hUserProv, PP_KEYEXCHANGE_PIN,
                                                    ( const BYTE * )_sbPassword->ptr(), 0 ) )
            {
                res = GetLastError();
                goto err;
            }
            return S_OK;
err:
            if ( fNeedFreeProv && hUserProv )
            {
                CryptReleaseContext( hUserProv, 0 );
                hUserProv = 0;
            }
            fNeedFreeProv = FALSE;
            if ( pUserCert )
            {
                CertFreeCertificateContext( pUserCert );
                pUserCert = 0;
            }
            if ( hUserStore )
            {
                CertCloseStore( hUserStore, 0 );
                hUserStore = 0;
            }
            return res;
        };

        virtual HRESULT SetCredential(
            /* [in] */ LONG hWnd,
            /* [in] */ X509EnrollmentAuthFlags anAuthType,
            /* [in] */ BSTR strCredential,
            /* [in] */ BSTR strPassword )
        {
            return UnixRequestImpl::SetCredential( hWnd, anAuthType, strCredential, strPassword );
        };

        static BOOL HexToBin( const char * src, DWORD sLen, BYTE * dst, DWORD *pdLen )
        {
            int i, j, k;
            for ( i = sLen, j = *pdLen; i > 0 && j > 0; i -= 2, j-- )
            {
                BYTE b = 0;
                for ( k = 0; k < 2; k++ )
                {
                    BYTE c = ( BYTE )tolower( *src++ );
                    b <<= 4;
                    if ( c >= 'a' && c <= 'f' )
                    { b |= c - 'a' + 10; }
                    else if ( isdigit( c ) )
                    { b |= c - '0'; }
                    else
                    { return FALSE; }
                }
                *dst++ = b;
            }
            *pdLen -= j;
            return TRUE;
        }

        PCCERT_CONTEXT GetCertContext( HCERTSTORE hStore )
        {

            CRYPT_HASH_BLOB bHash;

            BYTE bSHA1Digest[20];

            bHash.cbData = 20;
            bHash.pbData = bSHA1Digest;

            if ( !HexToBin( Credential.c_str(), 40, bHash.pbData, &bHash.cbData ) )
            { return NULL; }
            return CertFindCertificateInStore( hStore,
                                               X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
                                               0,
                                               CERT_FIND_HASH,
                                               &bHash,
                                               0 );
        }

        virtual HRESULT GetRequestParams( BSTR strConfig, BSTR strTemplate, BSTR *pstrRDN, BSTR *pstrEKUsage, DWORD *pKeySpec, std::vector<AttrTriple> *pAttrs );

        virtual HRESULT ListRequests( BSTR strConfig, RequestMap &Requests );

        virtual HRESULT ListRequestsEx( BSTR strConfig, RequestMapEx & Requests, ReqType type = CA15Request );

        virtual HRESULT GetRequest( LONG Flags, BSTR *pstrRequest );

        virtual HRESULT Submit( LONG Flags, const BSTR strRequest, const BSTR strAttributes, const BSTR strConfig, LONG *pDisposition );

        virtual HRESULT RetrievePending( LONG RequestId, const BSTR strConfig, LONG *pDisposition );

        virtual HRESULT AcknowledgeInstallCert( LONG RequestId, const BSTR strConfig );

        virtual HRESULT GetLastStatus( LONG *pStatus );

        virtual HRESULT GetRequestId( LONG *pRequestId );

        virtual HRESULT GetRequestStrId ( uintptr_t *pRequestId );

        virtual HRESULT GetCACertificate( LONG fExchangeCertificate, const BSTR strConfig, LONG Flags, BSTR *pstrCertificate );

        virtual HRESULT GetCertificate( LONG Flags, BSTR *pstrCertificate );

        virtual HRESULT GetIssuedCertificate( const BSTR strConfig, LONG RequestId, const BSTR strSerialNumber, LONG *pDisposition );

        virtual HRESULT GetErrorMessageText( LONG hrMessage, LONG Flags, BSTR *pstrErrorMessageText );

        virtual HRESULT RegisterUser( BSTR bstrConfig, void * pUserInfo );

        virtual HRESULT GetUserRegisterInfo( BSTR bstrConfig, void * pUserInfo );

        virtual HRESULT GetUserRegisterStatus( BSTR bstrConfig, LONG * pUserRegisterId, LONG * pUserRegisterStatus );

        virtual HRESULT GetCAProperty( const BSTR strConfig, LONG PropId, LONG PropIndex, LONG PropType, LONG Flags, VARIANT *pvarPropertyValue );

        virtual HRESULT GetCAPropertyFlags( const BSTR strConfig, LONG PropId, LONG *pPropFlags );

        virtual HRESULT GetCAPropertyDisplayName( const BSTR strConfig, LONG PropId, BSTR *pstrDisplayName );

        virtual HRESULT GetFullResponseProperty( LONG PropId, LONG PropIndex, LONG PropType, LONG Flags, VARIANT *pvarPropertyValue );

        virtual HRESULT GetRequestIdString( BSTR *pstrRequestId );

        virtual HRESULT GetIssuedCertificate2( BSTR strConfig, BSTR strRequestId, BSTR strSerialNumber, LONG *pDisposition );

        virtual HRESULT GetRefreshPolicy( VARIANT_BOOL *pValue );

        virtual HRESULT CA20GetEnrollParams( const std::string & UIURL,
                                             std::vector<std::string> &templates,
                                             std::vector<std::string> &EKUs,
                                             std::vector<std::string> &keySpecs,
                                             std::string & strDN );

        static const std::string optionsControllerURL;
        static const std::string registrationRequestControllerURL;
        static const std::string certificateRequestControllerURL;
	static const std::string certificateGetControllerURL;
        static const std::string certificateTemplateControllerURL;
        static const std::string CACertificateControllerURL;

    private:
        std::string token;
        std::string password;
        std::string certRequestID;
        std::string pkcs7;

	BSTR bstrRequestHTML;
	BSTR bstrCertHTML;
        std::string strCertificate;
        std::string strSerial;
        HCERTSTORE hUserStore;
        PCCERT_CONTEXT pUserCert;
        BOOL fNeedFreeProv;
        DWORD dwCertKeySpec;
        HCRYPTPROV hUserProv;
        unsigned int CPCA20_URL_RETRIEVER_TIMEOUT;

        std::string removeBackSlashFromUrl( std::string const& UIURL )
        {
            std::string ret = UIURL + "/";
            ret.erase( ret.find_last_not_of( '/' ) + 1 );
            return ret;
        }

        std::string makeURLwithFolder( std::string const& UIURLwithFolder, std::string const& ContollerURL )
        {
            std::string urlGet = removeBackSlashFromUrl( UIURLwithFolder );
            size_t pos = urlGet.find_last_of( '/' );
            std::string tmp = UIURLwithFolder.substr( 0, pos  );
            tmp += "/api/"            ;
            tmp += urlGet.substr( pos + 1 );
            tmp += "/";
            tmp += ContollerURL;
            return tmp;
        }

        std::string makeURL( std::string const& UIURL, std::string const& ContollerURL )
        {
            std::string urlGet = removeBackSlashFromUrl( UIURL );
            std::string tmp = urlGet;
            tmp += "/api/";
            tmp += ContollerURL;
            return tmp;
        }

        std::string makeSecureURL( std::string const& UIURL, std::string const& ContollerURL )
        {
            std::string urlGet = removeBackSlashFromUrl( UIURL );
            std::string tmp = urlGet;
            tmp += "/2/api/";
            tmp += ContollerURL;
            return tmp;
        }

        std::string makeCertRequestURL( std::string const& UIURLwithID, std::string const& ContollerURL )
        {
            std::string urlGet = UIURLwithID;
            size_t pos = UIURLwithID.find_last_of( '/' );
            urlGet = UIURLwithID.substr( 0, pos );
            urlGet += "/api/"            ;
            urlGet += ContollerURL;
            urlGet += "/";
            urlGet += UIURLwithID.substr( pos + 1 );
            return urlGet;
        }

        std::string makeSecureCertRequestURL( std::string const& UIURLwithID, std::string const& ContollerURL )
        {
            std::string urlGet = UIURLwithID;
            size_t pos = UIURLwithID.find_last_of( '/' );
            urlGet = UIURLwithID.substr( 0, pos );
            pos = UIURLwithID.find_last_of( '/' );
            urlGet = UIURLwithID.substr( 0, pos );
            urlGet += "/2/api/"            ;
            urlGet += ContollerURL;
            urlGet += "/";
            urlGet += UIURLwithID.substr( pos + 1 );
            return urlGet;
        }

        bool ConvertJsonResponseCodepage( std::string & response )
        {
#ifdef DEBUG
	    std::cerr << response << std::endl;
#endif
	    bool ret(false);
            int wchars_num =  MultiByteToWideChar( CP_UTF8 , 0 , response.c_str () , -1, NULL , 0 );
	    if (!wchars_num)
		return ret;

            std::wstring outResponse;
            outResponse.resize( wchars_num );

            if ( !MultiByteToWideChar( CP_UTF8 , 0 , response.c_str () , -1,  &outResponse[0], wchars_num ) )
            { return ret; }

	    // response может занимать различное количество байт в зависимости от кодировки
	    response.clear();
	    int chars_num = WideCharToMultiByte(CPCA20_ENCODING, 0, outResponse.c_str(), -1, NULL, 0, 0, NULL);
	    if (!chars_num)
		return ret;

	    response.resize(chars_num);
	    if (!WideCharToMultiByte(CPCA20_ENCODING, 0, outResponse.c_str(), -1, &response[0], chars_num, 0, NULL))
	    { return ret; }

            return true;
        }

	// метод конвертирует полученные данные для регистрации пользователя в UTF8
	// считаем, что для windows данные пришли в кодировке CP_ACP, а для UNIX - CP_UTF8
        bool ConvertJsonMessageCodepage( std::string & message )
        {
            std::wstring wmessage( message.length(), L' ' );

            bool ret( false );
	    if (!MultiByteToWideChar(CPCA20_ENCODING, 0, message.c_str(), -1, &wmessage[0], int(wmessage.size() + 1)))
            { return ret; }

            message.resize( wmessage.size() * 2 + 1 );

	    if (!WideCharToMultiByte(CP_UTF8, 0, wmessage.c_str(), -1, &message[0], int(message.size() + 1), 0, NULL))
            { return ret; }

            return true;
        }

        std::string makeBasicAuth();
};
#endif  // __CPCA20_REQUEST_H__
