From 080acb09dc7b671b250cd635e1492c79705d3920 Mon Sep 17 00:00:00 2001 From: Sylvain Fontaine Date: Thu, 28 Aug 2025 15:55:18 -0400 Subject: [PATCH 1/2] fix: Supports case insensitive auth-scheme --- challenge.go | 2 +- challenge_test.go | 20 +++++++++++++++++++- credentials.go | 2 +- digest.go | 13 ++++++++++++- digest_test.go | 24 ++++++++++++++++++++++++ 5 files changed, 57 insertions(+), 4 deletions(-) diff --git a/challenge.go b/challenge.go index e8b98fb..d8c8309 100644 --- a/challenge.go +++ b/challenge.go @@ -35,7 +35,7 @@ func (c *Challenge) SupportsQOP(qop string) bool { // ParseChallenge parses the WWW-Authenticate header challenge func ParseChallenge(s string) (*Challenge, error) { - s, ok := strings.CutPrefix(s, Prefix) + s, ok := CutPrefix(s) if !ok { return nil, errors.New("digest: invalid challenge prefix") } diff --git a/challenge_test.go b/challenge_test.go index e1a5282..b1c0ef1 100644 --- a/challenge_test.go +++ b/challenge_test.go @@ -33,17 +33,35 @@ func TestChallenge(t *testing.T) { QOP: []string{"auth"}, }, }, + { + input: `DIGEST realm="DLI LPC92601002528", nonce="NZAeQHhoCNifFjFa"`, + challenge: &Challenge{ + Realm: "DLI LPC92601002528", + Nonce: "NZAeQHhoCNifFjFa", + }, + }, } for i, tt := range tests { t.Run(strconv.Itoa(i), func(t *testing.T) { c, err := ParseChallenge(tt.input) assert.NilError(t, err) assert.DeepEqual(t, tt.challenge, c) - assert.DeepEqual(t, c.String(), tt.input) + assertChallengeStringEqual(t, tt.input, c.String()) }) } } +func assertChallengeStringEqual(t *testing.T, expected, actual string) { + t.Helper() + + ep, ok := CutPrefix(expected) + assert.Equal(t, ok, true, "expected should have prefix") + ap, ok := CutPrefix(actual) + assert.Equal(t, ok, true, "actual should have prefix") + + assert.DeepEqual(t, ep, ap) +} + func TestFindChallenge(t *testing.T) { bad1 := &Challenge{ Realm: "test", diff --git a/credentials.go b/credentials.go index 56ff1df..dc86828 100644 --- a/credentials.go +++ b/credentials.go @@ -26,7 +26,7 @@ type Credentials struct { // ParseCredentials parses the Authorization header value into credentials func ParseCredentials(s string) (*Credentials, error) { - s, ok := strings.CutPrefix(s, Prefix) + s, ok := CutPrefix(s) if !ok { return nil, errors.New("digest: invalid credentials prefix") } diff --git a/digest.go b/digest.go index 1e6e082..2b87cb4 100644 --- a/digest.go +++ b/digest.go @@ -18,7 +18,18 @@ const Prefix = "Digest " // IsDigest returns true if the header value is a digest auth header func IsDigest(header string) bool { - return strings.HasPrefix(header, Prefix) + if len(header) < len(Prefix) { + return false + } + return strings.EqualFold(header[:len(Prefix)], Prefix) +} + +// CutPrefix removes the digest prefix from the header value +func CutPrefix(s string) (string, bool) { + if !IsDigest(s) { + return s, false + } + return s[len(Prefix):], true } // Options for creating a credentials diff --git a/digest_test.go b/digest_test.go index 73222ad..b2ba4bc 100644 --- a/digest_test.go +++ b/digest_test.go @@ -70,6 +70,30 @@ func TestDigestMD5(t *testing.T) { Nc: 1, }, }, + { + Options{ + Method: "REGISTER", + URI: "sip:182.82.132.122", + Username: "the-user", + Password: "********", + Cnonce: "104adc6bd71f49678798ee646edcaa9a", + }, + &Challenge{ + Realm: "SipPeer", + Nonce: "970a1b42-d8a7-4fce-91a1-4767e9ed561b", + QOP: []string{"auth"}, + }, + &Credentials{ + Username: "the-user", + Realm: "SipPeer", + Nonce: "970a1b42-d8a7-4fce-91a1-4767e9ed561b", + URI: "sip:182.82.132.122", + Response: "6cf0981e056709d40c8acc40c87e73c6", + Cnonce: "104adc6bd71f49678798ee646edcaa9a", + QOP: "auth", + Nc: 1, + }, + }, } for _, tt := range tests { t.Run("", func(t *testing.T) { From 5e2ba19248abf1bedc4233aeafb13a1e2a04bf57 Mon Sep 17 00:00:00 2001 From: Sylvain Fontaine Date: Fri, 29 Aug 2025 09:27:21 -0400 Subject: [PATCH 2/2] fix: Review fix --- challenge_test.go | 25 ++++++++++--------------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/challenge_test.go b/challenge_test.go index b1c0ef1..fc1aa28 100644 --- a/challenge_test.go +++ b/challenge_test.go @@ -12,6 +12,7 @@ import ( func TestChallenge(t *testing.T) { tests := []struct { input string + output string challenge *Challenge }{ { @@ -34,10 +35,11 @@ func TestChallenge(t *testing.T) { }, }, { - input: `DIGEST realm="DLI LPC92601002528", nonce="NZAeQHhoCNifFjFa"`, + input: `DIGEST realm="DLI LPC92601002528", nonce="NZAeQHhoCNifFjFa"`, + output: `Digest realm="DLI LPC92601002528", nonce="NZAeQHhoCNifFjFa"`, challenge: &Challenge{ - Realm: "DLI LPC92601002528", - Nonce: "NZAeQHhoCNifFjFa", + Realm: "DLI LPC92601002528", + Nonce: "NZAeQHhoCNifFjFa", }, }, } @@ -46,22 +48,15 @@ func TestChallenge(t *testing.T) { c, err := ParseChallenge(tt.input) assert.NilError(t, err) assert.DeepEqual(t, tt.challenge, c) - assertChallengeStringEqual(t, tt.input, c.String()) + output := tt.output + if output == "" { + output = tt.input + } + assert.DeepEqual(t, output, c.String()) }) } } -func assertChallengeStringEqual(t *testing.T, expected, actual string) { - t.Helper() - - ep, ok := CutPrefix(expected) - assert.Equal(t, ok, true, "expected should have prefix") - ap, ok := CutPrefix(actual) - assert.Equal(t, ok, true, "actual should have prefix") - - assert.DeepEqual(t, ep, ap) -} - func TestFindChallenge(t *testing.T) { bad1 := &Challenge{ Realm: "test",