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
This commit is contained in:
ionutboangiu
2026-01-08 19:13:08 +02:00
committed by Dan Christian Bogos
parent d73e9c0803
commit fe4d8b5924
3 changed files with 107 additions and 2 deletions

View File

@@ -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 {

View File

@@ -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"

View File

@@ -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 {