Skip to content

Commit 846cd4e

Browse files
authored
Add BitLength validation for SuccinctRoles (#716)
Signed-off-by: Radoslav Dimitrov <radoslav@stacklok.com>
1 parent b38d91f commit 846cd4e

File tree

2 files changed

+48
-0
lines changed

2 files changed

+48
-0
lines changed

metadata/marshal.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
"encoding/hex"
2222
"encoding/json"
2323
"errors"
24+
"fmt"
2425
)
2526

2627
// The following marshal/unmarshal methods override the default behavior for for each TUF type
@@ -522,6 +523,15 @@ func (role *SuccinctRoles) UnmarshalJSON(data []byte) error {
522523
}
523524
*role = SuccinctRoles(a)
524525

526+
// Validate BitLength: must be between 1 and 32 inclusive.
527+
// - BitLength determines the number of bins as 2^BitLength
528+
// - We use the leftmost BitLength bits of a SHA-256 hash (32 bits max from 4 bytes)
529+
// - BitLength < 1 would result in 0 or fractional bins
530+
// - BitLength > 32 would cause a negative shift value in GetRolesForTarget
531+
if role.BitLength < 1 || role.BitLength > 32 {
532+
return fmt.Errorf("invalid bit_length: %d, must be between 1 and 32", role.BitLength)
533+
}
534+
525535
var dict map[string]any
526536
if err := json.Unmarshal(data, &dict); err != nil {
527537
return err

metadata/metadata_api_test.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1069,3 +1069,41 @@ func TestGetRolesInSuccinctRoles(t *testing.T) {
10691069
assert.Equal(t, fmt.Sprintf("bin-%s", expectedBinSuffix), roleName)
10701070
}
10711071
}
1072+
1073+
func TestSuccinctRolesBitLengthValidation(t *testing.T) {
1074+
tests := []struct {
1075+
name string
1076+
bitLength int
1077+
wantErr bool
1078+
}{
1079+
{"valid minimum", 1, false},
1080+
{"valid typical", 8, false},
1081+
{"valid maximum", 32, false},
1082+
{"invalid zero", 0, true},
1083+
{"invalid negative", -1, true},
1084+
{"invalid too large", 33, true},
1085+
{"invalid very large", 100, true},
1086+
}
1087+
1088+
for _, tt := range tests {
1089+
t.Run(tt.name, func(t *testing.T) {
1090+
jsonData := fmt.Sprintf(`{
1091+
"keyids": ["abc123"],
1092+
"threshold": 1,
1093+
"bit_length": %d,
1094+
"name_prefix": "bin"
1095+
}`, tt.bitLength)
1096+
1097+
var role SuccinctRoles
1098+
err := json.Unmarshal([]byte(jsonData), &role)
1099+
1100+
if tt.wantErr {
1101+
assert.Error(t, err)
1102+
assert.Contains(t, err.Error(), "invalid bit_length")
1103+
} else {
1104+
assert.NoError(t, err)
1105+
assert.Equal(t, tt.bitLength, role.BitLength)
1106+
}
1107+
})
1108+
}
1109+
}

0 commit comments

Comments
 (0)