# ValidateFileReceivedByHashComparison.ps1 # # This example shows how to implement a variant of non-repudiation # using a system in which the following sequence is implemented. # # --> SFTP client a has a file that it desires to upload: DataFile.dat # # --> SFTP client first calculates a has of the file, and stores it in # a separate file named DataFile.dat#client_hash#.txt which has the # following single line: # [HASH_ALGO][SP]([FILE_NAME])[SP]=[SP][FILE_HASH_RESULT] # For example: # SHA256 (DataFile.dat) = 179dcc5ace4b7f4b1ffd02c65d33fb01b9ae7053e8e294e3a9a30b7244c1411d # # The above line is example output from issuing the following command # on an Ubuntu 16.10 linux host: # # sha256sum --tag DataFile.dat > DataFile.dat#client_hash#.txt # # # --> SFTP client uploads the data file: DataFile.dat # --> SFTP client then uploads the hash file: DataFile.dat#client_hash#.txt # # --> VShell server receives both files uploaded from SFTP client. # --> VShell trigger fires for any uploaded file. This code you're reading # represents an example trigger "script" written in powershell that # will perform the following actions: # # 1) Check to see if the name of the file that was just uploaded # (or renamed -- some SFTP clients will upload to a temp file with # a ".part" or a ".vs_temp" extension, and then rename the file # only after the transfer is completed successfully; such a # technique supports "resume" operations more reliably, and can # also support the concept of a "done" indicator) has a name # that ends with the following pattern (can be customized to # your liking/needs): #client_hash#.txt # # 2) Open the *#client_hash#.txt file and read in the first line, # which contains this pattern: # [HASH_ALGO][SP]([FILE_NAME])[SP]=[SP][FILE_HASH_RESULT] # For example: # SHA256 (DataFile.dat) = 179dcc5ace4b7f4b1ffd02c65d33fb01b9ae7053e8e294e3a9a30b7244c1411d # # The line is parsed into its several components: # ALGO_NAME, FILE_NAME, FILE_HASH_RESULT # # 3) Using the ALGO_NAME and FILE_NAME read in from the client- # provided hash file, this script will generate another hash # file named: DataFile.dat#server_hash#.txt # # 4) Compare the server-calculated hash value to the client-provide # hash value. # --> If different, # a) send warning email to designated admin address. # b) send failure message to designated client address. # # --> If same, # c) send success email message to designated client address. # # Note, since the server will have generated a #server_hash#.txt # checksum file, the client application will be at liberty to # download that file and perform a check of its own volition, if # desired for double-non-repudiation. # # VShell Configuration: # Assuming you save this script to your VShell program files folder, your # trigger (for file upload and file rename events) would be set up as # exemplified below: # --> Command: C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe # --> Params: -executionpolicy bypass -file "C:\Program Files\VanDyke Software\VShell\ValidateFileReceivedByHashComparison.ps1" -file %P -client_ip %I -client_username %U -admin_email admin@example.com -client_email client@example.com # The only parameter required to be passed to this script represents the # path to the file that was just uploaded to VShell (%P in VShell config # for setting up the trigger command parameters). Additional/optional # params may be specified as -admin_email address@domain.com and/or # -client_email address@domain.com param( [parameter(Mandatory=$true)][string]$file, [string]$admin_email = "", [string]$client_email = "", [string]$smtp_svr_address = "mail", [string]$smtp_svr_port = "25", [string]$client_ip = "", [string]$client_username = "" ) $parentFolder = Split-Path -Path $file $baseFilename = Split-Path -Path $file -Leaf $strLogFolder = Get-ItemPropertyValue -Name "Log Folder" "HKLM:\Software\VanDyke\VShell\Server" $strLogFileName = "ValidateFileReceivedByHashComparison_log.txt" $strLogFile = Join-Path $strLogFolder $strLogFileName #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ function Log-Line ([string]$strData="", [int]$debug_level) { $strData $timestamp = Get-Date -Format G ($timestamp + ": " + $strData) >> $strLogFile } #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # set up trap handlers so any syntax errors or other coding problems # will get logged to our log file instead of lost to the ether trap { $e = $_.Exception $line = $_.InvocationInfo.ScriptLineNumber $msg = $e.Message $strMsg = @" Script exception trapped: $e Line Number: $line $msg "@ $strMsg Log-Line $strMsg } #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # Function to make it easier to send an email using standard info function Send-Email($strBody) { if($admin_email -match '\') { $strMsg = "Unable to send email. Please specify '-admin_email desired_email@your_domain' parameter VShell's trigger setup." $strMsg Log-Line $strMsg } if($client_email -match '\') { $strMsg = "Unable to send email. Please specify '-client_email desire_email@client_domain' parameter in VShell's trigger setup." $strMsg Log-Line $strMsg } try { $emailMessage = New-Object System.Net.Mail.MailMessage $emailMessage.From = $admin_email $emailMessage.To.Add($client_email) $emailMessage.CC.Add($admin_email) $emailMessage.Subject = "File Hash Validation Warning" $emailMessage.Body = $strBody $SMTPClient = New-Object System.Net.Mail.SmtpClient($smtp_svr_address, $smtp_svr_port) $SMTPClient.Send($emailMessage) } catch { $e = $_.Exception $line = $_.InvocationInfo.ScriptLineNumber $msg = $e.Message $strMsg = @" ERROR: Send-MailMessage failed: $e Line Number: $line $msg "@ $strMsg Log-Line $strMsg } } #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # Main body of code to hash the file, if needed, and send email re warnings try { if(-Not ($baseFilename -match '#client_hash#.txt$')) { $strInfo = "Info: File '$file' doesn't match client_hash pattern. Exiting trigger script." $strInfo #Log-Line $strInfo Exit } $strClientHashFileContents = Get-Content $file # Parse out the hashing algorithm, file name, and hash value if (-Not ($strClientHashFileContents -match "^\s*(\S+?)\s+\(([^\r\n]+)\) = ([^\r\n]+)$")) { $strErr = "WARNING: File '$file' doesn't contain valid hash info pattern. Exiting trigger script." $strErr Log-Line $strErr Send-Email $strErr Exit } $algo = $matches[1] $filename = $matches[2] $client_hash_value = $matches[3] #"Algo: " + $algo #"File: " + $filename #"Hash: " + $client_hash_value $file_to_hash = Join-Path $parentFolder $filename $base_file_to_hash = Split-Path -Path $file_to_hash -Leaf if (![System.IO.File]::Exists($file_to_hash)) { $strErr = "WARNING: Received client_hash file '$file', but the required data file '$file_to_hash' does not exist. Exiting script." $strErr Log-Line $strErr Send-Email $strErr Exit } $cksumFilename = $base_file_to_hash + "#server_hash#.txt" $checksumOutFilename = Join-Path $parentFolder $cksumFilename $objHash = Get-FileHash -Algorithm $algo -Path $file_to_hash $server_hash_value = $objHash.Hash.ToLower() ($objHash.Algorithm + " (" + $base_file_to_hash + ") = " + $objHash.Hash.ToLower()) | out-file -encoding ascii -Force $checksumOutFilename #Get-Content $checksumOutFilename if ($objHash.Hash.ToLower() -ieq $client_hash_value) { $strMsg = "Client and server hashes match for '$file_to_hash': $client_hash_value" $strMsg Log-Line $strMsg } else { $strMsg = @" WARNING: Client and server hashes do *not* match for '$file_to_hash'. ############################################################################### Client Hash: $client_hash_value Server Hash: $server_hash_value SFTP User name: $client_username SFTP client IP: $client_ip ############################################################################### "@ $strMsg Log-Line $strMsg Send-Email $strMsg } } catch { $e = $_.Exception $line = $_.InvocationInfo.ScriptLineNumber $msg = $e.Message $strMsg = @" Script exception caught: $e Line Number: $line $msg "@ $strMsg Log-Line $strMsg Send-Email $strMsg }