Files
authentik/internal/outpost/radius/eap/protocol/mschapv2/payload.go
Jens Langhammer 67f627a925 mostly working
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-07-01 22:43:03 +02:00

178 lines
4.0 KiB
Go

package mschapv2
import (
"encoding/binary"
"fmt"
"github.com/gorilla/securecookie"
log "github.com/sirupsen/logrus"
"goauthentik.io/internal/outpost/radius/eap/debug"
"goauthentik.io/internal/outpost/radius/eap/protocol"
"goauthentik.io/internal/outpost/radius/eap/protocol/eap"
"goauthentik.io/internal/outpost/radius/eap/protocol/peap"
"layeh.com/radius"
"layeh.com/radius/vendors/microsoft"
)
const TypeMSCHAPv2 protocol.Type = 26
func Protocol() protocol.Payload {
return &Payload{}
}
const (
challengeValueSize = 16
responseValueSize = 49
responseReservedSize = 8
responseNTResponseSize = 24
)
type OpCode uint8
const (
OpChallenge OpCode = 1
OpResponse OpCode = 2
OpSuccess OpCode = 3
)
type Payload struct {
OpCode OpCode
MSCHAPv2ID uint8
MSLength uint16
ValueSize uint8
Challenge []byte
Response []byte
Name []byte
st *State
}
func (p *Payload) Type() protocol.Type {
return TypeMSCHAPv2
}
func (p *Payload) Decode(raw []byte) error {
log.WithField("raw", debug.FormatBytes(raw)).Debugf("MSCHAPv2: decode raw")
p.OpCode = OpCode(raw[0])
if p.OpCode == OpSuccess {
return nil
}
// TODO: Validate against root EAP packet
p.MSCHAPv2ID = raw[1]
p.MSLength = binary.BigEndian.Uint16(raw[2:])
p.ValueSize = raw[4]
if p.ValueSize != responseValueSize {
return fmt.Errorf("MSCHAPv2: incorrect value size: %d", p.ValueSize)
}
p.Response = raw[5 : p.ValueSize+5]
p.Name = raw[5+p.ValueSize:]
if int(p.MSLength) != len(raw) {
return fmt.Errorf("MSCHAPv2: incorrect MS-Length: %d, should be %d", p.MSLength, len(raw))
}
return nil
}
func (p *Payload) Encode() ([]byte, error) {
encoded := []byte{
byte(p.OpCode),
p.MSCHAPv2ID,
0,
0,
byte(len(p.Challenge)),
}
encoded = append(encoded, p.Challenge...)
encoded = append(encoded, p.Name...)
p.MSLength = uint16(len(encoded))
binary.BigEndian.PutUint16(encoded[2:], p.MSLength)
return encoded, nil
}
func (p *Payload) Handle(ctx protocol.Context) protocol.Payload {
defer func() {
ctx.SetProtocolState(TypeMSCHAPv2, p.st)
}()
rootEap := ctx.RootPayload().(*eap.Payload)
if ctx.IsProtocolStart(TypeMSCHAPv2) {
ctx.Log().Debug("MSCHAPv2: Empty state, starting")
p.st = &State{
Challenge: securecookie.GenerateRandomKey(challengeValueSize),
}
return &Payload{
OpCode: OpChallenge,
MSCHAPv2ID: rootEap.ID + 1,
Challenge: p.st.Challenge,
Name: []byte("authentik"),
}
}
p.st = ctx.GetProtocolState(TypeMSCHAPv2).(*State)
response := &Payload{
MSCHAPv2ID: rootEap.ID + 1,
}
ctx.Log().Debugf("MSCHAPv2: OpCode: %d", p.OpCode)
if p.OpCode == OpResponse {
res, err := ParseResponse(p.Response)
if err != nil {
ctx.Log().WithError(err).Warning("MSCHAPv2: failed to parse response")
return nil
}
p.st.PeerChallenge = res.Challenge
auth, err := p.checkChapPassword(res)
if err != nil {
ctx.Log().WithError(err).Warning("MSCHAPv2: failed to check password")
return nil
}
ctx.Log().Info("MSCHAPv2: Successfully checked password")
p.st.Authenticated = true
succ := &SuccessRequest{
Payload: &Payload{
OpCode: OpSuccess,
},
Authenticator: auth,
}
return succ
} else if p.OpCode == OpSuccess && p.st.Authenticated {
ep := &peap.ExtensionPayload{
AVPs: []peap.ExtensionAVP{
{
Mandatory: true,
Type: peap.AVPAckResult,
Value: []byte{0, 1},
},
},
}
p.st.IsProtocolEnded = true
return ep
} else if p.st.IsProtocolEnded {
ctx.EndInnerProtocol(protocol.StatusSuccess, func(r *radius.Packet) *radius.Packet {
if len(microsoft.MSMPPERecvKey_Get(r, ctx.Packet().Packet)) < 1 {
microsoft.MSMPPERecvKey_Set(r, p.st.recvKey)
}
if len(microsoft.MSMPPESendKey_Get(r, ctx.Packet().Packet)) < 1 {
microsoft.MSMPPESendKey_Set(r, p.st.sendKey)
}
return r
})
return &Payload{}
}
return response
}
func (p *Payload) Offerable() bool {
return true
}
func (p *Payload) String() string {
return fmt.Sprintf(
"<MSCHAPv2 Packet OpCode=%d, MSCHAPv2ID=%d>",
p.OpCode,
p.MSCHAPv2ID,
)
}