Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions badgeverify/badgeverify.go
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,13 @@ func verifyAt(badgeStr, sigB64 string, now time.Time) (Badge, error) {
if err != nil {
return Badge{}, err
}
// Reject non-canonical encodings (leading zeros, '+' signs, etc.). The
// issuer always signs the canonical form, so a string that does not
// round-trip is malformed — this forecloses any byte-string-vs-parsed-
// fields malleability for downstream consumers.
if canon, cerr := Canonical(b); cerr != nil || canon != badgeStr {
return b, fmt.Errorf("%w: non-canonical encoding", ErrMalformed)
}
if err := verifyDetached(badgeStr, sigB64, b.Kid); err != nil {
return b, err
}
Expand Down
19 changes: 19 additions & 0 deletions badgeverify/badgeverify_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -229,3 +229,22 @@ func TestColonRejectedInFields(t *testing.T) {
t.Fatalf("colon in subject must be rejected, got %v", err)
}
}

// TestRejectsNonCanonicalBadge pins the malleability guard: a badge whose
// encoding does not round-trip through Canonical (e.g. a leading-zero node_id)
// is rejected as malformed BEFORE the signature is even checked.
func TestRejectsNonCanonicalBadge(t *testing.T) {
priv := newIssuer(t, "v1")
// validBadge() fields, but node_id 109517 written as "0109517".
nonCanon := "pilotbadge:v1:0109517:github:1700000000:0:v1:"
sig := base64.StdEncoding.EncodeToString(ed25519.Sign(priv, []byte(nonCanon)))
if _, err := Verify(nonCanon, sig); !errors.Is(err, ErrMalformed) {
t.Fatalf("non-canonical badge must reject with ErrMalformed, got %v", err)
}
// The canonical form of the same badge still verifies.
canon, _ := Canonical(validBadge())
csig := base64.StdEncoding.EncodeToString(ed25519.Sign(priv, []byte(canon)))
if _, err := Verify(canon, csig); err != nil {
t.Fatalf("canonical badge must still verify: %v", err)
}
}
Loading