|
root / src / util / checksum.reef
checksum.reef Reef 139 lines 4.3 KB
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
/******************************************************************************
               __               ____                __
              / /   ___  ____ _/ __/_____________ _/ /__
             / /   / _ \/ __ `/ /_/ ___/ ___/ __ `/ / _ \
            / /___/  __/ /_/ / __(__  ) /__/ /_/ / /  __/
           /_____/\___/\__,_/_/ /____/\___/\__,_/_/\___/

   (C)opyright 2025, Leafscale, LLC -  https://www.leafscale.com

   Project: Zygaena
  Filename: checksum.reef
   Authors: Chris Tusa <chris.tusa@leafscale.com>
   License: <see LICENSE file included with this source code>
Description: SHA256 checksum verification utilities

******************************************************************************/

module util.checksum

import io.file
import core.str
import sys.process
import crypto.sha256 as sha

export
    // Compute SHA256 hash of a file, returns hex string (lowercase)
    fn sha256_file(path: string): string

    // Compute SHA256 hash of a string, returns hex string (lowercase)
    fn sha256_string(data: string): string

    // Verify a file matches expected checksum
    fn verify_checksum(path: string, expected: string): bool

    // Parse checksum format "algo:hash" and verify
    fn verify_checksum_with_algo(path: string, checksum_str: string): bool
end export

// Compute SHA256 of a file
// Uses Reef's native streaming sha256_file() (Reef 0.1.14+)
// Falls back to sha256sum command if native fails
fn sha256_file(path: string): string
    if not file.fileExists(path)
        return ""
    end if

    // Use native streaming API (memory efficient, works on all file sizes)
    // Reef 0.1.14 fixed the large file hang/segfault issue
    let hash = sha.sha256_file(path)
    if str.length(hash) == 64
        return str.to_lower(hash)
    end if

    // Fallback to sha256sum command if native fails
    return sha256_file_cmd(path)
end sha256_file

// Compute SHA256 using sha256sum command (for large files)
fn sha256_file_cmd(path: string): string
    let output_file = "/tmp/coral_checksum_" + extract_basename(path)
    let cmd = "sha256sum \"" + path + "\" | cut -d' ' -f1 > \"" + output_file + "\""
    let pid = process.process_spawn_shell(cmd)
    if pid < 0
        return ""
    end if
    process.process_wait(pid)

    // Read the hash from output file
    if not file.fileExists(output_file)
        return ""
    end if

    let hash = str.trim_ws(file.readFile(output_file))

    // Clean up
    let rm_pid = process.process_spawn_shell("rm -f \"" + output_file + "\"")
    if rm_pid > 0
        process.process_wait(rm_pid)
    end if

    return hash
end sha256_file_cmd

// Extract basename from path for temp file naming
fn extract_basename(path: string): string
    let last_slash = str.last_index_of_char(path, '/')
    if last_slash < 0
        return path
    end if
    return str.substring(path, last_slash + 1, str.length(path) - last_slash - 1)
end extract_basename

// Compute SHA256 of a string using native crypto
fn sha256_string(data: string): string
    let hash = sha.sha256(data)
    return str.to_lower(hash)
end sha256_string

// Verify that a file's SHA256 matches the expected value
fn verify_checksum(path: string, expected: string): bool
    let computed = sha256_file(path)
    if str.length(computed) == 0
        return false
    end if

    // Compare case-insensitively (normalize to lowercase)
    let computed_lower = str.to_lower(computed)
    let expected_lower = str.to_lower(expected)

    return computed_lower == expected_lower
end verify_checksum

// Parse and verify checksum in format "algo:hash" or just "hash"
// Supported algorithms: sha256, sha256sum
// If no algorithm prefix, assumes sha256
fn verify_checksum_with_algo(path: string, checksum_str: string): bool
    // Check for algorithm prefix
    let colon_idx = str.index_of_char(checksum_str, ':')

    if colon_idx < 0
        // No prefix, assume SHA256
        return verify_checksum(path, checksum_str)
    end if

    // Extract algorithm and hash
    let algo = str.to_lower(str.substring(checksum_str, 0, colon_idx))
    let hash = str.substring(checksum_str, colon_idx + 1, str.length(checksum_str) - colon_idx - 1)

    // Verify based on algorithm
    if algo == "sha256" or algo == "sha256sum"
        return verify_checksum(path, hash)
    end if

    // Unsupported algorithm
    return false
end verify_checksum_with_algo

end module