-
Notifications
You must be signed in to change notification settings - Fork 0
/
loginchallenge.go
158 lines (133 loc) · 4.25 KB
/
loginchallenge.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
package handler
import (
"bytes"
"crypto/rand"
"encoding/binary"
"errors"
"io"
"log"
mrand "math/rand"
srp "github.com/kangaroux/go-wow-srp6"
"github.com/kangaroux/gomaggus/authd"
"github.com/kangaroux/gomaggus/internal"
"github.com/kangaroux/gomaggus/model"
"github.com/mixcode/binarystruct"
)
// https://gtker.com/wow_messages/docs/cmd_auth_logon_challenge_client.html
type loginChallengeRequest struct {
Opcode authd.Opcode // OpLoginChallenge
ProtocolVersion uint8
Size uint16
GameName [4]byte
Version [3]byte
Build uint16
OSArch [4]byte
OS [4]byte
Locale [4]byte
TimezoneBias uint32
IP [4]byte
UsernameLength uint8
Username string `binary:"string(UsernameLength)"`
}
// https://gtker.com/wow_messages/docs/cmd_auth_logon_challenge_server.html#protocol-version-8
type loginChallengeResponse struct {
Opcode authd.Opcode
ProtocolVersion uint8
ErrorCode authd.RespCode
PublicKey [srp.KeySize]byte
GeneratorSize uint8
Generator uint8
LargePrimeSize uint8
LargePrime [srp.LargePrimeSize]byte
Salt [srp.SaltSize]byte
CrcHash [16]byte
// Using any flags would require additional fields but this is set to zero for now
SecurityFlags byte
}
type LoginChallenge struct {
Client *authd.Client
Accounts model.AccountService
request loginChallengeRequest
}
func (h *LoginChallenge) Handle() error {
if h.Client.State != authd.StateAuthChallenge {
return &ErrWrongState{
Handler: "LoginChallenge",
Expected: authd.StateAuthChallenge,
Actual: h.Client.State,
}
}
log.Println("Starting login challenge")
log.Printf("client trying to login as '%s'", h.request.Username)
acct, err := h.Accounts.Get(&model.AccountGetParams{Username: h.request.Username})
if err != nil {
return err
}
var publicKey []byte
var salt []byte
faked := acct == nil
if faked {
publicKey = make([]byte, srp.KeySize)
if _, err := rand.Read(publicKey); err != nil {
return err
}
// A real account will always return the same salt, so the fake account needs to do that, too.
// Using the username as a seed for the fake salt is a clever way to always return the same
// salt for the same username.
//
// Ironically, using crypto/rand here is actually less secure. If the salt wasn't seeded and
// was random every time, a bad actor could abuse that to mine usernames.
seededRand := mrand.New(mrand.NewSource(internal.FastHash(h.Client.Username)))
salt = make([]byte, srp.SaltSize)
if _, err := seededRand.Read(salt); err != nil {
return err
}
} else {
if err := acct.DecodeSrp(); err != nil {
return err
}
publicKey = srp.ServerPublicKey(acct.Verifier(), h.Client.PrivateKey)
h.Client.ServerPublicKey = publicKey
salt = acct.Salt()
}
resp := loginChallengeResponse{
Opcode: authd.OpcodeLoginChallenge,
// Protocol version is always zero for server responses
ProtocolVersion: 0,
// Always return success to prevent a bad actor from mining usernames. See above for how
// fake data is generated when the username doesn't exist
ErrorCode: authd.Success,
GeneratorSize: 1,
Generator: srp.Generator,
LargePrimeSize: srp.LargePrimeSize,
SecurityFlags: 0,
}
copy(resp.PublicKey[:], publicKey)
copy(resp.LargePrime[:], srp.LargePrime())
copy(resp.Salt[:], salt)
copy(resp.CrcHash[:], []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0})
respBuf := bytes.Buffer{}
// The byte arrays are already little endian so the buffer can be used as-is
binary.Write(&respBuf, binary.BigEndian, &resp)
if _, err := h.Client.Conn.Write(respBuf.Bytes()); err != nil {
return err
}
log.Println("Replied to login challenge")
if !faked {
h.Client.Account = acct
h.Client.Username = h.request.Username
}
h.Client.State = authd.StateAuthProof
return nil
}
// Read reads the packet data and parses it as a login challenge request. If data is too small then
// Read returns ErrPacketReadEOF.
func (h *LoginChallenge) Read(data []byte) (int, error) {
n, err := binarystruct.Unmarshal(data, binary.LittleEndian, &h.request)
if err == io.EOF || errors.Is(err, io.ErrUnexpectedEOF) {
return 0, ErrPacketReadEOF
} else if err != nil {
return 0, err
}
return n, nil
}