I can't sort out how to edit this Radius MSCHAPv2 example and get the password from the radius client. The go library I'm using is https://github.com/layeh/radius.
Does MSCHAPv2 send the username with the password? How do I get the password from the client to the server?
package microsoft
import (
"log"
"reflect"
"layeh.com/radius"
"layeh.com/radius/rfc2759"
"layeh.com/radius/rfc2865"
"layeh.com/radius/rfc2868"
"layeh.com/radius/rfc2869"
"layeh.com/radius/rfc3079"
)
const (
radiusSecret = "secret"
)
func RunRadiusServer() {
handler := func(w radius.ResponseWriter, r *radius.Request) {
username := rfc2865.UserName_GetString(r.Packet)
challenge := MSCHAPChallenge_Get(r.Packet)
response := MSCHAP2Response_Get(r.Packet)
// TODO: look up user in local database.
// The password must be stored in the clear for CHAP mechanisms to work.
// In theory, it would be possible to use a password hashed with MD4 as
// all the functions in MSCHAPv2 use the MD4 hash of the password anyway,
// but given that MD4 is so vulerable that breaking a hash is almost as
// fast as computing it, it's just not worth it.
password := "password-from-database"
if len(challenge) == 16 && len(response) == 50 {
// See rfc2548 - 2.3.2. MS-CHAP2-Response
ident := response[0]
peerChallenge := response[2:18]
peerResponse := response[26:50]
ntResponse, err := rfc2759.GenerateNTResponse(challenge, peerChallenge, username, password)
if err != nil {
log.Printf("Cannot generate ntResponse for %s: %v", username, err)
w.Write(r.Response(radius.CodeAccessReject))
return
}
if reflect.DeepEqual(ntResponse, peerResponse) {
responsePacket := r.Response(radius.CodeAccessAccept)
recvKey, err := rfc3079.MakeKey(ntResponse, password, false)
if err != nil {
log.Printf("Cannot make recvKey for %s: %v", username, err)
w.Write(r.Response(radius.CodeAccessReject))
return
}
sendKey, err := rfc3079.MakeKey(ntResponse, password, true)
if err != nil {
log.Printf("Cannot make sendKey for %s: %v", username, err)
w.Write(r.Response(radius.CodeAccessReject))
return
}
authenticatorResponse, err := rfc2759.GenerateAuthenticatorResponse(challenge, peerChallenge, ntResponse, username, password)
if err != nil {
log.Printf("Cannot generate authenticator response for %s: %v", username, err)
w.Write(r.Response(radius.CodeAccessReject))
return
}
success := make([]byte, 43)
success[0] = ident
copy(success[1:], authenticatorResponse)
rfc2869.AcctInterimInterval_Add(responsePacket, rfc2869.AcctInterimInterval(3600))
rfc2868.TunnelType_Add(responsePacket, 0, rfc2868.TunnelType_Value_L2TP)
rfc2868.TunnelMediumType_Add(responsePacket, 0, rfc2868.TunnelMediumType_Value_IPv4)
MSCHAP2Success_Add(responsePacket, []byte(success))
MSMPPERecvKey_Add(responsePacket, recvKey)
MSMPPESendKey_Add(responsePacket, sendKey)
MSMPPEEncryptionPolicy_Add(responsePacket, MSMPPEEncryptionPolicy_Value_EncryptionAllowed)
MSMPPEEncryptionTypes_Add(responsePacket, MSMPPEEncryptionTypes_Value_RC440or128BitAllowed)
log.Printf("Access granted to %s", username)
w.Write(responsePacket)
return
}
}
log.Printf("Access denied for %s", username)
w.Write(r.Response(radius.CodeAccessReject))
}
server := radius.PacketServer{
Handler: radius.HandlerFunc(handler),
SecretSource: radius.StaticSecretSource([]byte(radiusSecret)),
}
log.Printf("Starting Radius server on :1812")
if err := server.ListenAndServe(); err != nil {
log.Fatal(err)
}
}
I edited the top of the function with:
username := rfc2865.UserName_GetString(r.Packet)
password := rfc2865.UserPassword_GetString(r.Packet)
log.Printf("bytes %+v", r.Get(1), string(r.Get(1)))
log.Printf("bytes %+v", r.Get(2), string(r.Get(2)))
log.Printf("u/p %v, %v\n", username, password)
Username is correct. password is empty. 1 is the username. I can't sort what 2 is because it won't convert the bytes to string.
There is a similar, but not Go specific question here that is not answered correctly as well. Thanks!
It's not a mechanism I'm particularly familiar with, but my expectation is that the CHAP protocol would not send the password over the wire, so you can't get it from the client. Instead you generate an expected response knowing the password the client should use and compare with the response you get from the client - if they match, then the same secret password was used to generate the two responses, and all is good, else there's a mismatch somewhere and you don't grant access.