Clarion
Verify the digital certificate of your binary files

In the good ol' times everybody trusted anybody else. We all downloaded programs from folks we never heard of and we did not even know, that those programs might pose a risk to our computers. But one day Malware was invented. First as Viruses, later then Worms and the recent threats dujour are Trojans (actually Trojan Horses).
Once we were kicked out of our Shangri-La, someone got the idea to somehow protect software, by issuing a so-called "digital certificate". The idea is that there is an honorable authority, handing out a certificate, once they have vetted the person or company, which requested such a certificate to be issued on their name. The are no samaritans, they charge for their work. And it has become a big business.
When you have purchased such a precious gem and then digitally signed your binaries with this code-signing certificate issued on your name, then your customers can look them up by doing a right-click on the file in the Windows Explorer, whether this binary file (EXE, DLL, OCX) has your certificate and they will can be sure, that this software is from you or your company and that it is in exactly in that state, as it have left your office. Not tampered by a virus, nor manipulated a bad person with a HEX-editor, having changed some internal data.
More on code-signing on Wikipedia.

However, no user will ever check your program for integrity! Most people do not even know, what that is (pun intended).

The challenge now is, to include this integrity check into our own products and, in case a fraudulent manipulation has been found, give out a warning to the user, to prohibit further harm.

On 01. March 2019 we did a ClarionLive!-Webinar on this topic. You can load the entire webinar here for watching.

The example, which got built during this webinar, was based on source by fellow programmer Thomas Glomb with some additional input by Graham Dawson. it will be described on this page. There will also be a downloadable example for Clarion 10 at the bottom of this page.

Your first step is to open Global Properties -> Embeds and insert this into your Global Map:

    module('wintrust')
        tgWinVerifyTrust( long, *string, *group), long, pascal, raw, proc, name( 'WinVerifyTrust')
    end
    module('win32 api')
        tgMultiByteToWideChar( long, long, *cstring, long, *cstring, long), long, pascal, raw, name( 'MultiByteToWideChar')
        tgGetLastError(), long, pascal, name('GetLastError')
    end
This will include the function WinVerifyTrust(), MultiByteToWideChar() and GetLastError() into your EXE. They are provided by MS-Windows in the system-file WinTrust.DLL and in the general Win32-API. This WinTrust.DLL holds some more functions, which are not needed for this purpose. So Thomas has stripped off the rest, made it a LIB on its own. You find this WinTrust.LIB in the attached ZIP. You also can make your own by using Clarions LibMaker.
It is mandatory to add this file to the solution. Go to the Solution Explorer in the IDE, search for "Libraries, Objects and Resource Files", do a right-click and use "Add Libraries, Objects and Resource File(s)" for that step.

NB: The function have gotten a prepending tg, to avoid name collision with the existing function from Windows itself.

The next step is to put the following into an embed like in After Globale Includes:

! #######################################################################################
! #                                                                                     #
! # EQUATEs for WinTrustVerify SignatureCheck() => LOC:ResultSignatureCheck             #
! # The returning GROUP will hold the contents of these Equates                         #
! # RetSignCheck:nStatus                                                                #
! # RetSignCheck:cStatus                                                                #
! #                                                                                     #
! #######################################################################################
! * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 	

EQ:Signature_trusted_OK                         EQUATE(1)  
EQ:Signature_No_Signature                       EQUATE(0)   ! when TRUST_E_NOSIGNATURE 
EQ:Signature_NOT_trusted                        EQUATE(-1)  ! when TRUST_E_SUBJECT_NOT_TRUSTED 
EQ:Signature_Provider_unkown                    EQUATE(-2)  ! when TRUST_E_PROVIDER_UNKNOWN 
EQ:Signature_Action_unknown                     EQUATE(-3)  ! when TRUST_E_ACTION_UNKNOWN 
EQ:Signature_Subject_Form_unknown               EQUATE(-4)  ! when TRUST_E_SUBJECT_FORM_UNKNOWN 
EQ:Signature_Invalid_Signature                  EQUATE(-5)  ! when some other reason, why ever! 
EQ:Signature_WideChar_Conversion_Problem        EQUATE(-6)  !  
   
! * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 

What we now need is the PROCEDURE, that does the verification check.
In this procedure, we add the following to the DATA SECTION

lresult              long, auto
lcStatus             STRING(200)

l:ReturnGROUP     GROUP,PRE(RETGROUP)
nStatus                  LONG
cStatus                  STRING(200)
                    END

WVTGuid                      group
Data1                            long( 0AAC56Bh)
Data2                            short( 0CD44h)
Data3                            short( 011D0h)
Data4                            byte, dim( 8)
Data4Str                         string( 8), over( Data4) ! The contents will be assigned in the code itself 
                            end

WTDgroup                     group
cbStruct                         long( 52)  !dword cbStruct = sizeof(WinTrustData) 
pPolicyCallbackData              long( 0)   !lpvoid pPolicyCallbackData = NULL 
pSIPClientData                   long( 0)   !lpvoid pSIPClientData = NULL 
dwUIChoice                       long( 2)   !dword dwUIChoice = WTD_UI_NONE 2 
fdwRevocationChecks              long( 0)   !dword fdwRevocationChecks = WTD_REVOKE_NONE 0 
dwUnionChoice                    long( 1)   !dword dwUnionChoice = WTD_CHOICE_FILE 1 
pFile                            long( 0)   !union {*pFile *pCatalog *pBlob *pSgnr *pCert} 
dwStateAction                    long( 1)   !dword dwStateAction = WTD_STATEACTION_VERIFY 1 
hWVTStateData                    long( 0)   !handle hWVTStateData = NULL  
pwszURLReference                 long( 0)   !wchar *pwszURLReference = NULL  
dwProvFlags                      long( 0)   !dwProvFlags ? 
dwUIContext                      long( 0)   !dwUIContext = 0  
pSignatureSettings               long( 0)   !*pSignatureSettings 
                            end

FDgroup                      group 
cbStruct                         long( 16)  !cbStruct = sizeof(WINTRUST_FILE_INFO) 
pcwszFilePath                    long( 0)   !pcwszFilePath = pwszSourceFile; 
hFile                            long( 0)   !hFile = NULL; 
pgKnownSubject                   long( 0)   !pgKnownSubject = NULL; 
                            end 

cs                           cstring( 256), auto
ws                           cstring( 512), auto

Now we come to the center of it all - the verification itself.

Add this snippet to Processed Code

    cs = clip( command( '0'))
    ! cs holds the name of the actual program we will compile and run.
	! Because this verification has been outsourced to its own procedure, you may
	! pass a parameter, holding the name of another binary file to be tested.
	! You then should be able to verify all your DLLs in the project, too.
	
    if tgMultiByteToWideChar( 0, 1, cs, -1, ws, size( ws)) <> 0
        WVTguid:Data4Str= '<08Ch,0C2h,000h,0C0h,04Fh,0C2h,095h,0EEh>'
        FDgroup.pcwszFilePath= address( ws)
        WTDgroup.pFile= address( FDgroup)
                
                ! lresult= tgWinVerifyTrust( -1, WVTguid, WTDgroup)
                
        case tgWinVerifyTrust( -1, WVTguid, WTDgroup)
            of 0 ! ERROR_SUCCESS - signature Ok !
                        
                lresult  = EQ:Signature_trusted_OK
                lcStatus = 'EQ:Signature_trusted_OK'
                        
            of 800B0100h ! TRUST_E_NOSIGNATURE
                                            
                case tgGetLastError() ! GetLastError()
                    of 800B0001h ! TRUST_E_PROVIDER_UNKNOWN
                        lresult  = EQ:Signature_Provider_unkown
                        lcStatus = 'EQ:Signature_Provider_unkown'
                                
                    of 800B0003h ! TRUST_E_SUBJECT_FORM_UNKNOWN
                        lresult  = EQ:Signature_Subject_Form_unknown
                        lcStatus = 'EQ:Signature_Subject_Form_unknown'
                                
                    of 800B0100h ! TRUST_E_NOSIGNATURE
                        lresult  = EQ:Signature_No_Signature
                        lcStatus = 'EQ:Signature_No_Signature'
                                
                    else
                        lresult  = EQ:Signature_Invalid_Signature 
                        lcStatus = 'EQ:Signature_Invalid_Signature - but who knows why'
                end
            else
                lresult = EQ:Signature_Invalid_Signature 
                lcStatus = 'EQ:Signature_Invalid_Signature for whatever reason'
        end
        WTDgroup.dwStateAction= 2
        tgWinVerifyTrust( -1, WVTguid, WTDgroup) 
    else
        lresult = EQ:Signature_WideChar_Conversion_Problem
        lcStatus = 'EQ:Signature_WideChar_Conversion_Problem'
    end
            
    RETGROUP:nStatus = lresult 
    RETGROUP:cStatus = lcStatus 

    ! dbgview('RETGROUP:nStatus = ' & RETGROUP:nStatus)
    ! dbgview('RETGROUP:cStatus = ' & RETGROUP:cStatus)

    RETURN (l:ReturnGROUP)
You are free to change the text, which gets returned to the calling procedure, into something meaningful for your users.

NB: The two variables lresult and nresult were written by Thomas, who gave me his source, which was integrated into the MAIN-procedure. Because I wanted to separate this source into its own procedure for this example, I then wanteded to return a GROUP. And I also did not want to fiddle with Thomas' original code. So I simply added this variable.
Now we can use this procedure for any other binary. See the comment at the beginning of this snippet. I am aware that this is not an example for clean coding. But I wanted to leave Thomas' code intact and also make it versatile and get it going for the webinar.
It is made to understand and learn. Learning from bad code, it is.

Now we have all stuff laid out, we will put it together to work.

All we have to do now to put these data somewhere into our DATA SECTION

LOC:ResultSignatureCheck GROUP,PRE(RetSignCheck)           ! 
nStatus              LONG                                  ! 
cStatus              STRING(200)                           ! 
                     END 

and this somewhere close to the begin of the MAIN-procedure, like in ThisWindow.Init
  LOC:ResultSignatureCheck = CheckSignature()
  
  ! LOC:ResultSignatureCheck is the GROUP, that holds the results we got back:
  ! RetSignCheck:nStatus
  ! RetSignCheck:cStatus

  IF RetSignCheck:nStatus < 0 THEN    ! MESSAGE(some Warning)   =>   RETURN Level:Break
      MESSAGE('A problem with the integrity of this program was found!|'  & |
          'This program has been modified, probably by a virus.||'    & |
          'Please inform your IT-department!||'                           & |
          'Result: ' & RetSignCheck:nStatus & ' - ' & CLIP(RetSignCheck:cStatus) & '||'                & |
          'The program will now be closed.', 'W A R N I N G!',                                 |
          ICON:Hand)
      RETURN Level:Fatal  !  Level:Break
  END

Use at your own risk!

In a following webinar we will make this source into a template. So stay tuned!

Join us on ClarionLive!, its free!

Thats all! Add salt and pepper as you like....

You can download the sample source (Clarion 10).
Jane Fleming converted this procedure into a class, made a fully functional demo and allowed to download it from here. Thank you, Jane!

This page was made on 06.03.2019, updated 25. 05. 2019.

The HEX-Editor XVI32 we used during the webinar can be found here.


Here you find some more samples