Skip to content

Commit

Permalink
Add support for EAP-OTP authentication method
Browse files Browse the repository at this point in the history
  • Loading branch information
jamiesun committed Jan 29, 2024
1 parent e9a7acd commit bbea64e
Show file tree
Hide file tree
Showing 7 changed files with 393 additions and 12 deletions.
8 changes: 5 additions & 3 deletions assets/static/views/settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ settingsUi.getConfigView = function (citem) {
return {id: "settings_form_view"}
}


settingsUi.getSystemConfigView = function (citem) {
let formid = webix.uid().toString();
return {
Expand Down Expand Up @@ -109,12 +108,15 @@ settingsUi.getRadiusConfigView = function (citem) {
},
{
view: "radio", name: "RadiusEapMethod", labelPosition: "top", label: tr("settings", "EAP certification methodology"),
options: ["noeap","eap-md5", "eap-mschapv2"],
options: ["noeap","eap-md5", "eap-mschapv2", "eap-otp"],
bottomLabel: tr("settings", "eap certification methodology")
},
{
view: "radio", name: "RadiusIgnorePwd", labelPosition: "top", label: tr("settings", "Ignore Passowrd check"),
options: [{id: 'enabled', value: gtr("Yes")}, {id: 'disabled', value: gtr("No")}],
options: [
{id: 'enabled', value: gtr("Yes")},
{id: 'disabled', value: gtr("No")}
],
bottomLabel: tr("settings", "Password authentication is ignored, but does not apply to MsChapv2 authentication mode.")
},
{}
Expand Down
2 changes: 1 addition & 1 deletion assets/static/views/settings.min.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

109 changes: 109 additions & 0 deletions common/totp/totp.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
package totp

import (
"bytes"
"crypto/hmac"
"crypto/sha1"
"encoding/base32"
"encoding/binary"
"fmt"
"net/url"
"strings"
"time"
)

type GoogleAuth struct {
}

func NewGoogleAuth() *GoogleAuth {
return &GoogleAuth{}
}

func (ga *GoogleAuth) un() int64 {
return time.Now().UnixNano() / 1000 / 30
}

func (ga *GoogleAuth) hmacSha1(key, data []byte) []byte {
h := hmac.New(sha1.New, key)
if total := len(data); total > 0 {
h.Write(data)
}
return h.Sum(nil)
}

func (ga *GoogleAuth) base32encode(src []byte) string {
return base32.StdEncoding.EncodeToString(src)
}

func (ga *GoogleAuth) base32decode(s string) ([]byte, error) {
return base32.StdEncoding.DecodeString(s)
}

func (ga *GoogleAuth) toBytes(value int64) []byte {
var result []byte
mask := int64(0xFF)
shifts := [8]uint16{56, 48, 40, 32, 24, 16, 8, 0}
for _, shift := range shifts {
result = append(result, byte((value>>shift)&mask))
}
return result
}

func (ga *GoogleAuth) toUint32(bts []byte) uint32 {
return (uint32(bts[0]) << 24) + (uint32(bts[1]) << 16) +
(uint32(bts[2]) << 8) + uint32(bts[3])
}

func (ga *GoogleAuth) oneTimePassword(key []byte, data []byte) uint32 {
hash := ga.hmacSha1(key, data)
offset := hash[len(hash)-1] & 0x0F
hashParts := hash[offset : offset+4]
hashParts[0] = hashParts[0] & 0x7F
number := ga.toUint32(hashParts)
return number % 1000000
}

// 获取秘钥
func (ga *GoogleAuth) GetSecret() string {
var buf bytes.Buffer
binary.Write(&buf, binary.BigEndian, ga.un())
return strings.ToUpper(ga.base32encode(ga.hmacSha1(buf.Bytes(), nil)))
}

// 获取动态码
func (ga *GoogleAuth) GetCode(secret string) (string, error) {
secretUpper := strings.ToUpper(secret)
secretKey, err := ga.base32decode(secretUpper)
if err != nil {
return "", err
}
number := ga.oneTimePassword(secretKey, ga.toBytes(time.Now().Unix()/30))
return fmt.Sprintf("%06d", number), nil
}

// 获取动态码二维码内容
func (ga *GoogleAuth) GetQrcode(user, secret, stype string) string {
return fmt.Sprintf("otpauth://totp/%s:%s?issuer=%s&secret=%s", stype, user, stype, secret)
}

// 获取动态码二维码图片地址,这里是第三方二维码api
func (ga *GoogleAuth) GetQrcodeUrl(user, secret, stype string) string {
qrcode := ga.GetQrcode(user, secret, stype)
width := "200"
height := "200"
data := url.Values{}
data.Set("data", qrcode)
return "https://api.qrserver.com/v1/create-qr-code/?" + data.Encode() + "&size=" + width + "x" + height + "&ecc=M";
}

// 验证动态码
func (ga *GoogleAuth) VerifyCode(secret, code string) (bool, error) {
_code, err := ga.GetCode(secret)
fmt.Println(_code, code, err)
if err != nil {
return false, err
}
return _code == code, nil
}


49 changes: 49 additions & 0 deletions common/totp/totp_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package totp

import (
"fmt"
"testing"
)


// 开启二次认证
func _initAuth(user string) (secret, code string) {

ng := NewGoogleAuth()
// 秘钥
secret = ng.GetSecret()
fmt.Println("Secret:", secret)

// 动态码(每隔30s会动态生成一个6位数的数字)
code, err := ng.GetCode(secret)
fmt.Println("Code:", code, err)

// 用户名
qrCode := ng.GetQrcode(user, code, "ToughDemo")
fmt.Println("Qrcode", qrCode)

// 打印二维码地址
qrCodeUrl := ng.GetQrcodeUrl(user, secret, "ToughDemo")
fmt.Println("QrcodeUrl", qrCodeUrl)

return
}


func TestOTP(t *testing.T) {
// fmt.Println("-----------------开启二次认证----------------------")
user := "[email protected]"
secret, code := _initAuth(user)
fmt.Println(secret, code)

fmt.Println("-----------------信息校验----------------------")

// secret最好持久化保存在
// 验证,动态码(从谷歌验证器获取或者freeotp获取)
bool, err := NewGoogleAuth().VerifyCode(secret, code)
if bool {
fmt.Println("√")
} else {
fmt.Println("X", err)
}
}
71 changes: 64 additions & 7 deletions toughradius/radius_auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,23 +83,64 @@ func (s *AuthService) ServeRADIUS(w radius.ResponseWriter, r *radius.Request) {
s.CheckRadAuthError(username, ip, s.CheckAuthRateLimit(username))
}

if isEap && eapmsg.Code == EAPCodeResponse && eapmsg.Type == EAPTypeIdentity {
switch eapMethod {
case EapMd5Method:
processEapType := func(eaptype byte) error {
switch eaptype {
case EAPTypeMD5Challenge:
// 发送EAP-Request/MD5-Challenge消息
err = s.sendEapMD5ChallengeRequest(w, r, vpe.Secret)
if err != nil {
s.CheckRadAuthError(username, ip, fmt.Errorf("eap: send eap request error: %s", err))
return fmt.Errorf("eap: sendEapMD5ChallengeRequest error: %s", err)
}
return
case EapMschapv2Method:
case EAPTypeMSCHAPv2:
// 发送EAP-Request/MSCHAPv2-Challenge消息
err = s.sendEapMsChapV2Request(w, r, vpe.Secret)
if err != nil {
s.CheckRadAuthError(username, ip, fmt.Errorf("eap: send eap request error: %s", err))
return fmt.Errorf("eap: sendEapMsChapV2Request error: %s", err)
}
case EAPTypeOTP:
// 发送 EAP-Request/OTP-Challenge
err = s.sendEapOTPChallengeRequest(w, r, vpe.Secret)
if err != nil {
return fmt.Errorf("eap: sendEapOTPChallengeRequest error: %s", err)
}
default:
return fmt.Errorf("eap: unsupported eap type: %d", eaptype)
}
return nil
}

// process EAP-Response/Identity
if isEap && eapmsg.Code == EAPCodeResponse && eapmsg.Type == EAPTypeIdentity {
eaptype := byte(0x00)
switch eapMethod {
case EapMd5Method:
eaptype = EAPTypeMD5Challenge
case EapMschapv2Method:
eaptype = EAPTypeMSCHAPv2
case EapOTPMethod:
eaptype = EAPTypeOTP
}
err := processEapType(eaptype)
if err != nil {
s.CheckRadAuthError(username, ip, fmt.Errorf("eap: processEapType error: %s", err))
} else {
return
}
}

// Process EAPTypeNak
if isEap && eapmsg.Type == EAPTypeNak {
if len(eapmsg.Data) == 0 {
fmt.Println("No alternative EAP methods suggested.")
return
}
for _, eapMethod := range eapmsg.Data {
err := processEapType(eapMethod)
if err != nil {
s.CheckRadAuthError(username, ip, fmt.Errorf("eap: processEapType error: %s", err))
} else {
return
}
}
}

Expand Down Expand Up @@ -167,6 +208,22 @@ func (s *AuthService) ServeRADIUS(w radius.ResponseWriter, r *radius.Request) {
eapState.Success = true
sendAccept()

case EAPTypeOTP:
stateid := rfc2865.State_GetString(r.Packet)
eapState, err := s.GetEapState(stateid)
if err != nil {
s.SendEapFailureReject(w, r, vpe.Secret, fmt.Errorf("eap: get eap state error"))
return
}

otpPassword := "123456"
if string(eapmsg.Data) != otpPassword {
s.SendEapFailureReject(w, r, vpe.Secret, fmt.Errorf("eap: verify otp response error"))
return
}
eapState.Success = true
sendAccept()

case EAPTypeMSCHAPv2:
opcode, err := parseEAPMSCHAPv2OpCode(r.Packet)
if err != nil {
Expand Down
42 changes: 41 additions & 1 deletion toughradius/radius_eap.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ const (
EapSakeMethod = "eap-sake"
EapIkev2Method = "eap-ikev2"
EapTncMethod = "eap-tnc"
EapOTPMethod = "eap-otp"
)

const (
Expand Down Expand Up @@ -207,7 +208,7 @@ func (s *AuthService) sendEapMD5ChallengeRequest(w radius.ResponseWriter, r *rad
rfc2865.State_SetString(resp, state)

// 创建EAP-Request/MD5-Challenge消息
eapMessage := []byte{0x01, r.Identifier, 0x00, 0x16, 0x04, 0x10}
eapMessage := []byte{0x01, r.Identifier, 0x00, 0x16, EAPTypeMD5Challenge, 0x10}
eapMessage = append(eapMessage, eapChallenge...)

// 设置EAP-Message属性
Expand Down Expand Up @@ -235,3 +236,42 @@ func (s *AuthService) verifyEapMD5Response(eapid uint8, password string, challen
expectedResponse := hash.Sum(nil)
return bytes.Equal(expectedResponse, response[1:])
}


// sendEapOTPChallengeRequest
func (s *AuthService) sendEapOTPChallengeRequest(w radius.ResponseWriter, r *radius.Request, secret string) error {
// 创建一个新的RADIUS响应
var resp = r.Response(radius.CodeAccessChallenge)

eapChallenge := []byte("Please enter a one-time password")

state := common.UUID()
s.AddEapState(state, rfc2865.UserName_GetString(r.Packet), eapChallenge, EapOTPMethod)

rfc2865.State_SetString(resp, state)

// 创建EAP-Request/OTP-Challenge消息
eapMessage := []byte{0x01, r.Identifier}
eapMessage = append(eapMessage, []byte{0x00, 0x00}...) // Length, will be set later
eapMessage = append(eapMessage, EAPTypeOTP) // Type for EAP-OTP
eapMessage = append(eapMessage, eapChallenge...)

// Set the length
binary.BigEndian.PutUint16(eapMessage[2:4], uint16(len(eapMessage)))

// 设置EAP-Message属性
rfc2869.EAPMessage_Set(resp, eapMessage)
rfc2869.MessageAuthenticator_Set(resp, make([]byte, 16))

authenticator := generateMessageAuthenticator(resp, secret)
// 设置Message-Authenticator属性
rfc2869.MessageAuthenticator_Set(resp, authenticator)

// debug message
if app.GConfig().Radiusd.Debug {
log.Info(FmtResponse(resp, r.RemoteAddr))
}

// 发送RADIUS响应
return w.Write(resp)
}
Loading

0 comments on commit bbea64e

Please sign in to comment.