nip/tests/test_hash_verifier.nim

406 lines
14 KiB
Nim

## tests/test_hash_verifier.nim
## Comprehensive unit tests for hash verification module
##
## Includes golden test vectors and edge case testing
import std/[unittest, os, streams, strutils, tempfiles, options]
import ../src/nimpak/security/hash_verifier
suite "Hash Algorithm Detection":
test "detect BLAKE2b hash formats":
check detectHashAlgorithm("blake2b-abc123") == HashBlake2b
check detectHashAlgorithm("a".repeat(128)) == HashBlake2b # 128 hex chars = BLAKE2b-512
test "detect BLAKE3 hash formats":
# BLAKE3 is detected but falls back to BLAKE2b
check detectHashAlgorithm("blake3-abc123") == HashBlake3
test "detect SHA256 hash formats":
check detectHashAlgorithm("sha256-abc123") == HashSha256
check detectHashAlgorithm("a".repeat(64)) == HashSha256 # 64 hex chars = SHA256
test "invalid hash formats":
expect ValueError:
discard detectHashAlgorithm("invalid-hash")
expect ValueError:
discard detectHashAlgorithm("abc") # Too short
suite "Hash String Parsing":
test "parse BLAKE2b hash strings":
let (alg1, digest1) = parseHashString("blake2b-abc123def456")
check alg1 == HashBlake2b
check digest1 == "abc123def456"
# Test with a valid 128-character BLAKE2b hash (without prefix)
let validBlake2bHash = "a".repeat(128)
let (alg2, digest2) = parseHashString(validBlake2bHash)
check alg2 == HashBlake2b
check digest2 == validBlake2bHash
test "format hash strings":
check formatHashString(HashBlake2b, "abc123") == "blake2b-abc123"
check formatHashString(HashBlake3, "def456") == "blake3-def456"
check formatHashString(HashSha256, "789abc") == "sha256-789abc"
suite "Golden Test Vectors":
# These are known-good test vectors for BLAKE2b-512
const BLAKE2B_GOLDEN_VECTORS = [
("", "blake2b-786a02f742015903c6c6fd852552d272912f4740e15847618a86e217f71f5419d25e1031afee585313896444934eb04b903a685b1448b755d56f701afe9be2ce"),
("a", "blake2b-333fcb4ee1aa7c115355ec66ceac917c8bfd815bf7587d325aec1864edd24e34d5abe2c6b1b5ee3face62fed78dbef802f2a85cb91d455a8f5249d330853cb3c"),
("abc", "blake2b-ba80a53f981c4d0d6a2797b69f12f6e94c212f14685ac4b74b12bb6fdbffa2d17d87c5392aab792dc252d5de4533cc9518d38aa8dbf1925ab92386edd4009923"),
("The quick brown fox jumps over the lazy dog", "blake2b-a8add4bdddfd93e4877d2746e62817b116364a1fa7bc148d95090bc7333b3673f82401cf7aa2e4cb1ecd90296e3f14cb5413f8ed77be73045b13914cdcd6a918"),
("NimPak", "blake2b-5274ed32fc344107b69805a6e77c9ab0dfc4c9b83d94a400bce3cc70d11f9d5c06738c319dace5b81859d56667550744b240f52d493dfc2e8afbbe76b00df1ae"),
("1234567890".repeat(10), "blake2b-800bb78cd4da18995c8074713bb6743cd94b2b6490a693fe4000ed00833b88b7b474d94af9cfed246b1b4ce1935a76154d7ea7c410493557741d18ec3a08da75")
]
# SHA256 test vectors for legacy support
const SHA256_GOLDEN_VECTORS = [
("", "sha256-e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"),
("a", "sha256-ca978112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bb"),
("abc", "sha256-ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"),
("The quick brown fox jumps over the lazy dog", "sha256-d7a8fbb307d7809469ca9abcb0082e4f8d5651e46d3cdb762d02d0bf37c9e592"),
("NimPak", "sha256-1ba47abd74914c637330282fad2a2eb807970548b532a552128d6de4af131cbf"),
("1234567890".repeat(10), "sha256-b20e12a7bcf7a0bcc5150265aab9c40b1d673781c143a73be76232d81e6038ec")
]
test "BLAKE2b golden vectors":
for (input, expectedHash) in BLAKE2B_GOLDEN_VECTORS:
let result = computeStringHash(input, HashBlake2b)
let formattedHash = formatHashString(result.algorithm, result.digest)
check formattedHash == expectedHash
check result.verified == false # Not verified yet
# Test verification
let verifyResult = verifyStringHash(input, expectedHash)
check verifyResult.verified == true
check verifyResult.digest == result.digest
test "SHA256 golden vectors":
for (input, expectedHash) in SHA256_GOLDEN_VECTORS:
let result = computeStringHash(input, HashSha256)
let formattedHash = formatHashString(result.algorithm, result.digest)
check formattedHash == expectedHash
check result.verified == false # Not verified yet
# Test verification
let verifyResult = verifyStringHash(input, expectedHash)
check verifyResult.verified == true
check verifyResult.digest == result.digest
suite "Streaming Hash Computation":
test "streaming hasher initialization":
let hasher = initStreamingHasher(HashBlake2b)
check hasher.algorithm == HashBlake2b
check hasher.bytesProcessed == 0
test "streaming hash with multiple updates":
var hasher = initStreamingHasher(HashBlake2b)
hasher.update("The quick ")
hasher.update("brown fox ")
hasher.update("jumps over ")
hasher.update("the lazy dog")
let result = hasher.finalize()
check hasher.bytesProcessed == 43 # Length of the string
# Should match the golden vector for the full string
let expectedResult = computeStringHash("The quick brown fox jumps over the lazy dog", HashBlake2b)
check result.digest == expectedResult.digest
test "streaming hash with byte arrays":
var hasher = initStreamingHasher(HashBlake2b)
let dataStr = "test data"
hasher.update(dataStr.toOpenArrayByte(0, dataStr.high))
let result = hasher.finalize()
check hasher.bytesProcessed == 9
# Should match direct string hash
let expectedResult = computeStringHash("test data", HashBlake2b)
check result.digest == expectedResult.digest
suite "File Hash Computation":
test "compute hash of existing file":
# Create a temporary file with known content
let (tempFile, tempPath) = createTempFile("nimpak_test_", ".txt")
tempFile.write("Hello, NimPak!")
tempFile.close()
try:
let result = computeFileHash(tempPath, HashBlake2b)
check result.algorithm == HashBlake2b
check result.digest.len == 128 # BLAKE2b-512 hex length
check result.computeTime >= 0.0
# Verify the hash
let expectedHash = formatHashString(result.algorithm, result.digest)
let verifyResult = verifyFileHash(tempPath, expectedHash)
check verifyResult.verified == true
finally:
removeFile(tempPath)
test "file not found error":
expect IOError:
discard computeFileHash("/nonexistent/file.txt")
test "large file handling":
# Create a larger temporary file (1MB)
let (tempFile, tempPath) = createTempFile("nimpak_large_", ".bin")
let chunkData = "A".repeat(1024) # 1KB chunk
for i in 0..<1024: # Write 1MB total
tempFile.write(chunkData)
tempFile.close()
try:
let result = computeFileHash(tempPath, HashBlake2b)
check result.algorithm == HashBlake2b
check result.digest.len == 128
check result.computeTime > 0.0
# Check that we can format the hash rate
let stats = getHashStatistics(result, 1024 * 1024)
check "MB/s" in stats or "KB/s" in stats or "GB/s" in stats
finally:
removeFile(tempPath)
suite "Stream Hash Computation":
test "compute hash from string stream":
let data = "Stream test data"
var stream = newStringStream(data)
let result = computeStreamHash(stream, HashBlake2b)
check result.algorithm == HashBlake2b
# Should match direct string hash
let expectedResult = computeStringHash(data, HashBlake2b)
check result.digest == expectedResult.digest
stream.close()
suite "Hash Verification":
test "successful hash verification":
let data = "Verification test"
let hashResult = computeStringHash(data, HashBlake2b)
let hashString = formatHashString(hashResult.algorithm, hashResult.digest)
let verifyResult = verifyStringHash(data, hashString)
check verifyResult.verified == true
check verifyResult.digest == hashResult.digest
test "failed hash verification":
let data = "Verification test"
let wrongHash = "blake2b-" & "0".repeat(128)
expect HashVerificationError:
discard verifyStringHash(data, wrongHash)
test "file hash verification":
let (tempFile, tempPath) = createTempFile("nimpak_verify_", ".txt")
tempFile.write("File verification test")
tempFile.close()
try:
# Compute correct hash
let hashResult = computeFileHash(tempPath, HashBlake2b)
let hashString = formatHashString(hashResult.algorithm, hashResult.digest)
# Verify should succeed
let verifyResult = verifyFileHash(tempPath, hashString)
check verifyResult.verified == true
# Wrong hash should fail
let wrongHash = "blake2b-" & "1".repeat(128)
expect HashVerificationError:
discard verifyFileHash(tempPath, wrongHash)
finally:
removeFile(tempPath)
suite "Batch Verification":
test "verify multiple files":
# Create multiple temporary files
var entries: seq[FileHashEntry] = @[]
var tempPaths: seq[string] = @[]
for i in 0..<3:
let (tempFile, tempPath) = createTempFile("nimpak_batch_", ".txt")
tempFile.write($"File content " & $i)
tempFile.close()
tempPaths.add(tempPath)
# Compute correct hash
let hashResult = computeFileHash(tempPath, HashBlake2b)
let hashString = formatHashString(hashResult.algorithm, hashResult.digest)
entries.add(FileHashEntry(
filePath: tempPath,
expectedHash: hashString,
result: none(HashResult),
error: ""
))
try:
let (verified, failed) = verifyMultipleFiles(entries)
check verified == 3
check failed == 0
for entry in entries:
check entry.result.isSome()
check entry.result.get().verified == true
check entry.error == ""
finally:
for path in tempPaths:
removeFile(path)
test "batch verification with failures":
var entries: seq[FileHashEntry] = @[
FileHashEntry(
filePath: "/nonexistent/file.txt",
expectedHash: "blake2b-" & "0".repeat(128),
result: none(HashResult),
error: ""
)
]
let (verified, failed) = verifyMultipleFiles(entries)
check verified == 0
check failed == 1
check entries[0].result.isNone()
check entries[0].error != ""
suite "Performance and Statistics":
test "hash rate formatting":
check formatHashRate(1000, 1.0) == "1.0 KB/s"
check formatHashRate(1_000_000, 1.0) == "1.0 MB/s"
check formatHashRate(1_000_000_000, 1.0) == "1.0 GB/s"
check formatHashRate(500, 1.0) == "500 B/s"
check formatHashRate(1000, 0.0) == "N/A"
test "hash statistics formatting":
let result = HashResult(
algorithm: HashBlake2b,
digest: "abc123",
verified: true,
computeTime: 0.5
)
let stats = getHashStatistics(result, 1000)
check "blake2b" in stats.toLower()
check "0.500s" in stats
check "KB/s" in stats
suite "Algorithm Fallback and Support":
test "algorithm fallback":
check getFallbackAlgorithm(HashBlake3) == HashBlake2b
check getFallbackAlgorithm(HashBlake2b) == HashBlake2b
check getFallbackAlgorithm(HashSha256) == HashSha256
test "algorithm support detection":
check isAlgorithmSupported(HashBlake2b) == true
check isAlgorithmSupported(HashSha256) == true
check isAlgorithmSupported(HashBlake3) == false # Not natively supported
test "BLAKE3 fallback behavior":
# BLAKE3 should fallback to BLAKE2b
let result = computeStringHash("test", HashBlake3)
check result.algorithm == HashBlake2b # Should have fallen back
# Compare with direct BLAKE2b computation
let blake2bResult = computeStringHash("test", HashBlake2b)
check result.digest == blake2bResult.digest
suite "Large File Handling":
test "large file hash computation":
# Create a file larger than the threshold for testing
let (tempFile, tempPath) = createTempFile("nimpak_large_", ".bin")
let chunkData = "X".repeat(1024) # 1KB chunk
# Write 2MB total (smaller than 1GB threshold for testing)
for i in 0..<2048:
tempFile.write(chunkData)
tempFile.close()
try:
var progressCalls = 0
let result = computeLargeFileHash(tempPath, HashBlake2b) do (processed: int64, total: int64):
inc progressCalls
check processed <= total
check processed >= 0
check result.algorithm == HashBlake2b
check result.digest.len == 128
check result.computeTime > 0.0
check progressCalls > 0 # Progress callback should have been called
# Verify the result matches regular file hash
let regularResult = computeFileHash(tempPath, HashBlake2b)
check result.digest == regularResult.digest
finally:
removeFile(tempPath)
test "chunk size optimization":
# Test that large files use larger chunk sizes
let (tempFile, tempPath) = createTempFile("nimpak_chunk_", ".bin")
let smallData = "small"
tempFile.write(smallData)
tempFile.close()
try:
let result = computeFileHash(tempPath, HashBlake2b)
check result.algorithm == HashBlake2b
check result.digest.len == 128
finally:
removeFile(tempPath)
suite "Utility Functions":
test "hash string validation":
check isValidHashString("blake2b-" & "a".repeat(128)) == true
check isValidHashString("sha256-" & "b".repeat(64)) == true
check isValidHashString("invalid-hash") == false
check isValidHashString("") == false
test "preferred algorithm":
check getPreferredHashAlgorithm() == HashBlake2b
test "supported algorithms":
let supported = getSupportedAlgorithms()
check HashBlake2b in supported
check HashSha256 in supported
# BLAKE3 not yet natively supported
check HashBlake3 notin supported
suite "Edge Cases and Error Handling":
test "empty string hash":
let result = computeStringHash("", HashBlake2b)
check result.digest.len == 128
check result.algorithm == HashBlake2b
test "very long string hash":
let longString = "A".repeat(100_000) # 100KB string
let result = computeStringHash(longString, HashBlake2b)
check result.digest.len == 128
check result.computeTime >= 0.0
test "unsupported algorithm error":
# BLAKE3 falls back to BLAKE2b, so no error
let hasher1 = initStreamingHasher(HashBlake3)
check hasher1.algorithm == HashBlake2b
# SHA256 is supported
let hasher2 = initStreamingHasher(HashSha256)
check hasher2.algorithm == HashSha256
test "hash verification error details":
try:
discard verifyStringHash("test", "blake2b-wrong")
fail()
except HashVerificationError as e:
check e.algorithm == HashBlake2b
check "wrong" in e.expectedHash
check e.actualHash != e.expectedHash