0

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!

Chemdream
  • 618
  • 2
  • 9
  • 25

2 Answers2

1

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.

Gwyn Evans
  • 1,361
  • 7
  • 17
1

In MSCHAPv2 the client sends user password hash. The clear text password is not possible to access. If you need to do authentication - you need to get the password from user identities storage, hash it in the same way and compare two hashes - your and the one got from the client.

In details, client sends:

  • MSCHAP-Challenge contains 16 byte challenge
  • MSCHAP-Response contains:
    • 16 byte client challenge
    • 8 byte reserved
    • 24 byte NT response = DesEncrypt( SHA1( client challenge , server challenge , username ) , MD4Hash(password) )
    • 1 byte flag "use NT challenge response or LAN challenge response"

Server gets MSCHAP-Challenge, client challenge and user password from ID store and calculates NT-response. If it is equal to received – server sends Access Accept, else Access Reject

Oleg
  • 726
  • 5
  • 11