Skip to content

Commit abf888a

Browse files
committed
test: add comprehensive ValidateHash tests with strict Nix32 validation
This commit hardens the narinfo hash validation to strictly enforce the Nix32 encoding specification: - Enforce exactly 32-character hash length (previously allowed any length) - Restrict to valid Nix32 alphabet: 0-9, a-d, f-n, p-s, v-z (previously allowed any lowercase letter and digit) - Explicitly reject forbidden characters: e, o, u, t - Reject uppercase letters and special characters Added 18 comprehensive test cases covering: - Valid hashes with different character combinations - Invalid hashes with forbidden characters - Invalid hashes with wrong lengths - Invalid hashes with special characters and uppercase letters This ensures that only valid Nix32 hashes are accepted, preventing potential data corruption or compatibility issues in the narinfo processing pipeline.
1 parent b226ff0 commit abf888a

File tree

2 files changed

+143
-6
lines changed

2 files changed

+143
-6
lines changed

pkg/narinfo/hash.go

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,25 @@ import (
55
"regexp"
66
)
77

8+
// narInfoHashPattern defines the valid characters for a Nix32 encoded hash.
9+
// Nix32 uses a 32-character alphabet excluding 'e', 'o', 'u', and 't'.
10+
// Valid characters: 0-9, a-d, f-n, p-s, v-z
11+
// Hashes must be exactly 32 characters long.
12+
const HashPattern = `[0-9a-df-np-sv-z]{32}`
13+
814
var (
915
// ErrInvalidHash is returned if the hash is invalid.
1016
ErrInvalidHash = errors.New("invalid narinfo hash")
1117

12-
// narInfoHashPattern defines the valid characters for a narinfo hash.
13-
//nolint:gochecknoglobals // This is used in other regexes to ensure they validate the same thing.
14-
narInfoHashPattern = `[a-z0-9]+`
15-
1618
// hashRegexp is used to validate hashes.
17-
hashRegexp = regexp.MustCompile(`^` + narInfoHashPattern + `$`)
19+
hashRegexp = regexp.MustCompile(`^` + HashPattern + `$`)
1820
)
1921

20-
// ValidateHash validates the given hash.
22+
// ValidateHash validates the given hash according to Nix32 encoding requirements.
23+
// A valid hash must:
24+
// - Be exactly 32 characters long
25+
// - Contain only characters from the Nix32 alphabet: 0-9a-bcd-fgh-ijkl-mn-pqrs-vwxyz
26+
// - Exclude the letters: e, o, u, t.
2127
func ValidateHash(hash string) error {
2228
if !hashRegexp.MatchString(hash) {
2329
return ErrInvalidHash

pkg/narinfo/hash_test.go

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
package narinfo_test
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/assert"
7+
8+
"github.com/kalbasit/ncps/pkg/narinfo"
9+
)
10+
11+
func TestValidateHash(t *testing.T) {
12+
t.Parallel()
13+
14+
tests := []struct {
15+
name string
16+
hash string
17+
shouldErr bool
18+
}{
19+
// Valid Nix32 hashes (32 characters from the allowed alphabet)
20+
{
21+
name: "valid hash with all allowed characters",
22+
hash: "n5glp21rsz314qssw9fbvfswgy3kc68f",
23+
shouldErr: false,
24+
},
25+
{
26+
name: "valid hash with numbers",
27+
hash: "01234567890123456789012345678901",
28+
shouldErr: false,
29+
},
30+
{
31+
name: "valid hash with mixed characters",
32+
hash: "abcdfghijklmnpqrsvwxyzabcdfghijk",
33+
shouldErr: false,
34+
},
35+
36+
// Invalid: contains forbidden letters (e, o, u, t)
37+
{
38+
name: "invalid hash contains 'e'",
39+
hash: "n5glp21rsz314qssw9fbvfswgy3kc68e",
40+
shouldErr: true,
41+
},
42+
{
43+
name: "invalid hash contains 'o'",
44+
hash: "n5glp21rsz314qssw9fbvfswgy3kc68o",
45+
shouldErr: true,
46+
},
47+
{
48+
name: "invalid hash contains 'u'",
49+
hash: "n5glp21rsz314qssw9fbvfswgy3kc68u",
50+
shouldErr: true,
51+
},
52+
{
53+
name: "invalid hash contains 't'",
54+
hash: "n5glp21rsz314qssw9fbvfswgy3kc68t",
55+
shouldErr: true,
56+
},
57+
58+
// Invalid: contains uppercase letters
59+
{
60+
name: "invalid hash contains uppercase",
61+
hash: "N5glp21rsz314qssw9fbvfswgy3kc68f",
62+
shouldErr: true,
63+
},
64+
{
65+
name: "invalid hash all uppercase",
66+
hash: "N5GLP21RSZ314QSSW9FBVFSWGY3KC68F",
67+
shouldErr: true,
68+
},
69+
70+
// Invalid: contains special characters
71+
{
72+
name: "invalid hash with exclamation mark",
73+
hash: "n5glp21rsz314qssw9fbvfswgy3kc68!",
74+
shouldErr: true,
75+
},
76+
{
77+
name: "invalid hash with hyphen",
78+
hash: "n5glp21rsz314qssw9fbvfswgy3kc-8f",
79+
shouldErr: true,
80+
},
81+
{
82+
name: "invalid hash with underscore",
83+
hash: "n5glp21rsz314qssw9fbvfswgy3kc_8f",
84+
shouldErr: true,
85+
},
86+
{
87+
name: "invalid hash with space",
88+
hash: "n5glp21rsz314qssw9fbvfswgy3kc 8f",
89+
shouldErr: true,
90+
},
91+
92+
// Invalid: wrong length
93+
{
94+
name: "invalid hash too short",
95+
hash: "n5glp21rsz314qssw9fbvfswgy3kc68",
96+
shouldErr: true,
97+
},
98+
{
99+
name: "invalid hash too long",
100+
hash: "n5glp21rsz314qssw9fbvfswgy3kc68ff",
101+
shouldErr: true,
102+
},
103+
104+
// Invalid: empty string
105+
{
106+
name: "invalid hash empty string",
107+
hash: "",
108+
shouldErr: true,
109+
},
110+
111+
// Invalid: only one character
112+
{
113+
name: "invalid hash single character",
114+
hash: "a",
115+
shouldErr: true,
116+
},
117+
}
118+
119+
for _, test := range tests {
120+
t.Run(test.name, func(t *testing.T) {
121+
t.Parallel()
122+
123+
err := narinfo.ValidateHash(test.hash)
124+
if test.shouldErr {
125+
assert.ErrorIs(t, err, narinfo.ErrInvalidHash)
126+
} else {
127+
assert.NoError(t, err)
128+
}
129+
})
130+
}
131+
}

0 commit comments

Comments
 (0)