From fe4d8b5924a6f27d4ea3396585a4307e4186adac Mon Sep 17 00:00:00 2001 From: ionutboangiu Date: Thu, 8 Jan 2026 19:13:08 +0200 Subject: [PATCH] radagent: check CHAP-Challenge AVP for CHAP auth CHAP authentication was always using the Request Authenticator as challenge, ignoring CHAP-Challenge AVP when present. Per RFC 2865, the CHAP-Challenge attribute takes precedence if included in the packet. Ref: #4963 --- agents/librad.go | 8 ++- agents/radagent.go | 1 + agents/radagent_it_test.go | 100 +++++++++++++++++++++++++++++++++++++ 3 files changed, 107 insertions(+), 2 deletions(-) diff --git a/agents/librad.go b/agents/librad.go index ef9cad0c7..78caf44e8 100644 --- a/agents/librad.go +++ b/agents/librad.go @@ -120,8 +120,12 @@ func radauthReq(flags utils.FlagsWithParams, req *radigo.Packet, aReq *AgentRequ if len(chapAVPs) == 0 { return false, utils.NewErrMandatoryIeMissing(CHAPPasswordAVP) } - return radigo.AuthenticateCHAP([]byte(pass), - req.Authenticator[:], chapAVPs[0].RawValue), nil + // RFC 2865: Use CHAP-Challenge AVP if present, otherwise Request Authenticator. + challenge := req.Authenticator[:] + if chapChallengeAVPs := req.AttributesWithName(CHAPChallengeAVP, utils.EmptyString); len(chapChallengeAVPs) > 0 { + challenge = chapChallengeAVPs[0].RawValue + } + return radigo.AuthenticateCHAP([]byte(pass), challenge, chapAVPs[0].RawValue), nil case flags.Has(utils.MetaMSCHAPV2): msChallenge := req.AttributesWithName(MSCHAPChallengeAVP, MicrosoftVendor) if len(msChallenge) == 0 { diff --git a/agents/radagent.go b/agents/radagent.go index 0442374a4..ad0d21abe 100644 --- a/agents/radagent.go +++ b/agents/radagent.go @@ -44,6 +44,7 @@ const ( MetaRadReplyCode = "*radReplyCode" UserPasswordAVP = "User-Password" CHAPPasswordAVP = "CHAP-Password" + CHAPChallengeAVP = "CHAP-Challenge" MSCHAPChallengeAVP = "MS-CHAP-Challenge" MSCHAP2ResponseAVP = "MS-CHAP2-Response" MicrosoftVendor = "Microsoft" diff --git a/agents/radagent_it_test.go b/agents/radagent_it_test.go index ba4e64ff4..01360cdb6 100644 --- a/agents/radagent_it_test.go +++ b/agents/radagent_it_test.go @@ -64,6 +64,8 @@ var ( testRAitAuthCHAPSuccessTCP, testRAitAuthCHAPFail, testRAitAuthCHAPFailTCP, + testRAitAuthCHAPWithChallengeAVP, + testRAitAuthCHAPWithChallengeAVPTCP, testRAitAuthMSCHAPV2Success, testRAitAuthMSCHAPV2SuccessTCP, testRAitAuthMSCHAPV2Fail, @@ -683,6 +685,104 @@ func testRAitAuthCHAPFailTCP(t *testing.T) { } } +func testRAitAuthCHAPWithChallengeAVP(t *testing.T) { + if raAuthClnt, err = radigo.NewClient(utils.UDP, "127.0.0.1:1812", "CGRateS.org", dictRad, 1, nil, utils.Logger); err != nil { + t.Fatal(err) + } + authReq := raAuthClnt.NewRequest(radigo.AccessRequest, 1) + if err := authReq.AddAVPWithName("User-Name", "1001", ""); err != nil { + t.Error(err) + } + // Use a challenge different from the Request Authenticator + chapChallenge := []byte("customchallenge!") + if err := authReq.AddAVPWithName("CHAP-Challenge", string(chapChallenge), ""); err != nil { + t.Error(err) + } + if err := authReq.AddAVPWithName("CHAP-Password", "CGRateSPassword1", ""); err != nil { + t.Error(err) + } + authReq.AVPs[2].RawValue = radigo.EncodeCHAPPassword([]byte("CGRateSPassword1"), chapChallenge) + if err := authReq.AddAVPWithName("Service-Type", "SIP-Caller-AVPs", ""); err != nil { + t.Error(err) + } + if err := authReq.AddAVPWithName("Called-Station-Id", "1002", ""); err != nil { + t.Error(err) + } + if err := authReq.AddAVPWithName("Acct-Session-Id", "e4921177ab0e3586c37f6a185864b71a@0:0:0:0:0:0:0:0", ""); err != nil { + t.Error(err) + } + if err := authReq.AddAVPWithName("Sip-From-Tag", "51585362", ""); err != nil { + t.Error(err) + } + if err := authReq.AddAVPWithName("NAS-IP-Address", "127.0.0.1", ""); err != nil { + t.Error(err) + } + if err := authReq.AddAVPWithName("Event-Timestamp", "1497106115", ""); err != nil { + t.Error(err) + } + reply, err := raAuthClnt.SendRequest(authReq) + if err != nil { + t.Fatal(err) + } + if reply.Code != radigo.AccessAccept { + t.Errorf("Received reply: %+v", utils.ToJSON(reply)) + } + if len(reply.AVPs) != 1 { + t.Errorf("Received AVPs: %+v", utils.ToJSON(reply.AVPs)) + } else if !bytes.Equal([]byte("session_max_time#10800"), reply.AVPs[0].RawValue) { + t.Errorf("Received: %s", string(reply.AVPs[0].RawValue)) + } +} + +func testRAitAuthCHAPWithChallengeAVPTCP(t *testing.T) { + if raAuthClnt, err = radigo.NewClient(utils.TCP, "127.0.0.1:1812", "CGRateS.org", dictRad, 1, nil, utils.Logger); err != nil { + t.Fatal(err) + } + authReq := raAuthClnt.NewRequest(radigo.AccessRequest, 1) + if err := authReq.AddAVPWithName("User-Name", "1001", ""); err != nil { + t.Error(err) + } + // Use a challenge different from the Request Authenticator + chapChallenge := []byte("customchallenge!") + if err := authReq.AddAVPWithName("CHAP-Challenge", string(chapChallenge), ""); err != nil { + t.Error(err) + } + if err := authReq.AddAVPWithName("CHAP-Password", "CGRateSPassword1", ""); err != nil { + t.Error(err) + } + authReq.AVPs[2].RawValue = radigo.EncodeCHAPPassword([]byte("CGRateSPassword1"), chapChallenge) + if err := authReq.AddAVPWithName("Service-Type", "SIP-Caller-AVPs", ""); err != nil { + t.Error(err) + } + if err := authReq.AddAVPWithName("Called-Station-Id", "1002", ""); err != nil { + t.Error(err) + } + if err := authReq.AddAVPWithName("Acct-Session-Id", "e4921177ab0e3586c37f6a185864b71a@0:0:0:0:0:0:0:0", ""); err != nil { + t.Error(err) + } + if err := authReq.AddAVPWithName("Sip-From-Tag", "51585362", ""); err != nil { + t.Error(err) + } + if err := authReq.AddAVPWithName("NAS-IP-Address", "127.0.0.1", ""); err != nil { + t.Error(err) + } + if err := authReq.AddAVPWithName("Event-Timestamp", "1497106115", ""); err != nil { + t.Error(err) + } + reply, err := raAuthClnt.SendRequest(authReq) + if err != nil { + t.Fatal(err) + } + if reply.Code != radigo.AccessAccept { + t.Errorf("Received reply: %+v", utils.ToJSON(reply)) + } + if len(reply.AVPs) != 1 { + t.Errorf("Received AVPs: %+v", utils.ToJSON(reply.AVPs)) + } else if !bytes.Equal([]byte("session_max_time#10800"), reply.AVPs[0].RawValue) { + t.Errorf("Received: %s", string(reply.AVPs[0].RawValue)) + } +} + func testRAitAuthMSCHAPV2Success(t *testing.T) { for _, dictPath := range raCfg.RadiusAgentCfg().ClientDictionaries { if dictRad, err = radigo.NewDictionaryFromFoldersWithRFC2865(dictPath); err != nil {