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
57 changes: 57 additions & 0 deletions rfc2865/chap.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package rfc2865

import (
"crypto/md5"
"crypto/subtle"

"layeh.com/radius"
)

// CHAPMatch reports whether the given CHAP-Password value corresponds to
// password and challenge.
//
// chapPassword must be the raw 17-octet CHAP-Password attribute value: a
// 1-octet CHAP identifier followed by the 16-octet MD5 response, as described
// in RFC 1994 and RFC 2865, Section 2.2. challenge is the CHAP challenge,
// i.e. the CHAP-Challenge attribute value, or the request Authenticator when
// the request does not carry a CHAP-Challenge attribute.
//
// The comparison of the expected and received responses is done in constant
// time.
func CHAPMatch(password, chapPassword, challenge []byte) bool {
if len(chapPassword) != 17 {
return false
}

h := md5.New()
h.Write(chapPassword[:1]) // CHAP identifier
h.Write(password)
h.Write(challenge)

var sum [md5.Size]byte
return subtle.ConstantTimeCompare(h.Sum(sum[:0]), chapPassword[1:]) == 1
}

// CHAPVerify reports whether password is the correct password for the CHAP
// authentication carried in p.
//
// It combines the CHAP-Password attribute with the CHAP-Challenge attribute. If
// p does not contain a CHAP-Challenge attribute, the packet's Authenticator is
// used as the challenge, as described in RFC 2865, Section 2.2.
//
// CHAPVerify returns false if p does not contain a validly sized CHAP-Password
// attribute. It only handles standard CHAP (MD5); MS-CHAP and MS-CHAPv2 use
// different attributes and algorithms.
func CHAPVerify(p *radius.Packet, password []byte) bool {
chapPassword := CHAPPassword_Get(p)
if len(chapPassword) == 0 {
return false
}

challenge := CHAPChallenge_Get(p)
if len(challenge) == 0 {
challenge = p.Authenticator[:]
}

return CHAPMatch(password, chapPassword, challenge)
}
85 changes: 85 additions & 0 deletions rfc2865/chap_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package rfc2865

import (
"crypto/md5"
"testing"

"layeh.com/radius"
)

func makeCHAPPassword(ident byte, password, challenge []byte) []byte {
h := md5.New()
h.Write([]byte{ident})
h.Write(password)
h.Write(challenge)

out := make([]byte, 17)
out[0] = ident
copy(out[1:], h.Sum(nil))
return out
}

func TestCHAPMatch(t *testing.T) {
password := []byte("hello")
challenge := []byte("0123456789abcdef")
chapPassword := makeCHAPPassword(0x42, password, challenge)

if !CHAPMatch(password, chapPassword, challenge) {
t.Fatal("CHAPMatch returned false for a valid password")
}
if CHAPMatch([]byte("wrong"), chapPassword, challenge) {
t.Fatal("CHAPMatch returned true for an invalid password")
}
if CHAPMatch(password, chapPassword, []byte("different-chal")) {
t.Fatal("CHAPMatch returned true for a mismatched challenge")
}
if CHAPMatch(password, chapPassword[:16], challenge) {
t.Fatal("CHAPMatch returned true for a malformed CHAP-Password")
}
}

func TestCHAPVerify_withCHAPChallenge(t *testing.T) {
secret := []byte("secret")
password := []byte("testing123")
challenge := []byte("a-random-challenge")

p := radius.New(radius.CodeAccessRequest, secret)
if err := CHAPChallenge_Add(p, challenge); err != nil {
t.Fatal(err)
}
if err := CHAPPassword_Add(p, makeCHAPPassword(0x01, password, challenge)); err != nil {
t.Fatal(err)
}

if !CHAPVerify(p, password) {
t.Fatal("CHAPVerify returned false for a valid password")
}
if CHAPVerify(p, []byte("nope")) {
t.Fatal("CHAPVerify returned true for an invalid password")
}
}

func TestCHAPVerify_authenticatorAsChallenge(t *testing.T) {
secret := []byte("secret")
password := []byte("testing123")

p := radius.New(radius.CodeAccessRequest, secret)
// No CHAP-Challenge attribute: the Authenticator is the challenge.
if err := CHAPPassword_Add(p, makeCHAPPassword(0x07, password, p.Authenticator[:])); err != nil {
t.Fatal(err)
}

if !CHAPVerify(p, password) {
t.Fatal("CHAPVerify returned false for a valid password")
}
if CHAPVerify(p, []byte("nope")) {
t.Fatal("CHAPVerify returned true for an invalid password")
}
}

func TestCHAPVerify_noCHAPPassword(t *testing.T) {
p := radius.New(radius.CodeAccessRequest, []byte("secret"))
if CHAPVerify(p, []byte("anything")) {
t.Fatal("CHAPVerify returned true for a packet without a CHAP-Password")
}
}