You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
194 lines
4.7 KiB
194 lines
4.7 KiB
2 years ago
|
package main
|
||
|
|
||
|
import (
|
||
|
"bytes"
|
||
|
"errors"
|
||
|
"net"
|
||
|
)
|
||
|
|
||
|
type Winbox struct {
|
||
|
task *Task
|
||
|
conn net.Conn
|
||
|
stage int
|
||
|
|
||
|
user, pass string
|
||
|
w *WCurve
|
||
|
sa, xwa, xwb, j, z, secret, clientCC, serverCC, i, msg, resp []byte
|
||
|
xwaParity, xwbParity bool
|
||
|
}
|
||
|
|
||
|
func NewWinbox(task *Task, conn net.Conn) *Winbox {
|
||
|
winbox := Winbox{}
|
||
|
winbox.task = task
|
||
|
winbox.conn = conn
|
||
|
winbox.xwaParity = false
|
||
|
winbox.xwbParity = false
|
||
|
winbox.w = NewWCurve()
|
||
|
winbox.stage = -1
|
||
|
winbox.user = task.login
|
||
|
winbox.pass = task.password
|
||
|
return &winbox
|
||
|
}
|
||
|
|
||
|
func (winbox *Winbox) genSharedSecret(salt []byte) error {
|
||
|
winbox.i = genPasswordValidatorPriv(winbox.user, winbox.pass, salt)
|
||
|
xGamma, _ := winbox.w.genPublicKey(winbox.i)
|
||
|
v := winbox.w.redp1(xGamma, true)
|
||
|
|
||
|
wb := winbox.w.liftX(NewBigintFromBytes(winbox.xwb), winbox.xwbParity)
|
||
|
if wb == nil {
|
||
|
winbox.stage = -1
|
||
|
return errors.New("liftX failed")
|
||
|
}
|
||
|
|
||
|
wb = wb.Add(v)
|
||
|
|
||
|
xwaCombined := append(winbox.xwa, winbox.xwb...)
|
||
|
winbox.j = getSHA2Digest(xwaCombined)
|
||
|
pt := NewBigintFromBytes(winbox.i).Mul(NewBigintFromBytes(winbox.j))
|
||
|
pt = pt.Add(NewBigintFromBytes(winbox.sa))
|
||
|
pt = winbox.w.finiteFieldValue(pt)
|
||
|
|
||
|
mp := wb.Mul(pt)
|
||
|
|
||
|
winbox.z, _ = winbox.w.toMontgomery(mp)
|
||
|
winbox.secret = getSHA2Digest(winbox.z)
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func (winbox *Winbox) publicKeyExchange() {
|
||
|
winbox.sa, _ = genRandomBytes(32)
|
||
|
winbox.xwa, winbox.xwaParity = winbox.w.genPublicKey(winbox.sa)
|
||
|
|
||
|
lx := winbox.w.liftX(NewBigintFromBytes(winbox.xwa), winbox.xwaParity)
|
||
|
if lx == nil {
|
||
|
log("winbox", 1, "liftX failed in PKE")
|
||
|
winbox.stage = -1
|
||
|
return
|
||
|
}
|
||
|
|
||
|
if !winbox.w.check(lx) {
|
||
|
log("winbox", 1, "curve check failed")
|
||
|
winbox.stage = -1
|
||
|
return
|
||
|
}
|
||
|
|
||
|
winbox.msg = append([]byte(winbox.user), byte(0))
|
||
|
winbox.msg = append(winbox.msg, winbox.xwa...)
|
||
|
if winbox.xwaParity {
|
||
|
winbox.msg = append(winbox.msg, byte(1))
|
||
|
} else {
|
||
|
winbox.msg = append(winbox.msg, byte(0))
|
||
|
}
|
||
|
|
||
|
header := append([]byte{byte(len(winbox.msg))}, byte(6))
|
||
|
winbox.msg = append(header, winbox.msg...)
|
||
|
winbox.stage = 1
|
||
|
}
|
||
|
|
||
|
func (winbox *Winbox) confirmation() error {
|
||
|
if len(winbox.resp) <= 2 {
|
||
|
log("winbox", 1, "response size must be greater than 2 (got %v)", len(winbox.resp))
|
||
|
winbox.stage = -1
|
||
|
return errors.New("invalid response size")
|
||
|
}
|
||
|
|
||
|
respLen := winbox.resp[0]
|
||
|
winbox.resp = winbox.resp[2:]
|
||
|
if len(winbox.resp) != int(respLen) {
|
||
|
log("winbox", 1, "invalid challenge response size: got %v, expected %v", len(winbox.resp), respLen)
|
||
|
winbox.stage = -1
|
||
|
return errors.New("invalid challenge response size")
|
||
|
}
|
||
|
|
||
|
winbox.xwb = winbox.resp[:32]
|
||
|
if winbox.resp[32] == 0 {
|
||
|
winbox.xwbParity = false
|
||
|
} else {
|
||
|
winbox.xwbParity = true
|
||
|
}
|
||
|
|
||
|
salt := winbox.resp[33:]
|
||
|
if len(salt) != 16 {
|
||
|
// this means that there is no such login,
|
||
|
// or that its an old routeros version
|
||
|
log("winbox", 1, "invalid salt size: got %v, expected 16", len(salt))
|
||
|
|
||
|
// report this finding to endpoint manager
|
||
|
winbox.task.EventWithParm(TH_NoSuchLogin, winbox.user)
|
||
|
|
||
|
winbox.stage = -1
|
||
|
return nil // this is not a fatal error for an endpoint
|
||
|
}
|
||
|
|
||
|
err := winbox.genSharedSecret(salt)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
winbox.j = getSHA2Digest(append(winbox.xwa, winbox.xwb...))
|
||
|
winbox.clientCC = getSHA2Digest(append(winbox.j, winbox.z...))
|
||
|
|
||
|
header := append([]byte{byte(len(winbox.clientCC))}, byte(6))
|
||
|
winbox.msg = append(header, winbox.clientCC...)
|
||
|
winbox.stage = 2
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func (winbox *Winbox) sendAndRecv() error {
|
||
|
if len(winbox.msg) > 0 && winbox.conn != nil {
|
||
|
_, err := winbox.conn.Write(winbox.msg)
|
||
|
winbox.msg = []byte{}
|
||
|
|
||
|
if err != nil {
|
||
|
log("winbox", 1, "failed to send: %v", err.Error())
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
winbox.resp = make([]byte, 1024)
|
||
|
n, err := winbox.conn.Read(winbox.resp)
|
||
|
if err != nil {
|
||
|
log("winbox", 1, "failed to recv: %v", err.Error())
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
winbox.resp = winbox.resp[:n]
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func (winbox *Winbox) TryLogin() (result bool, err error) {
|
||
|
log("winbox", 2, "login: stage 1, PKE")
|
||
|
winbox.publicKeyExchange()
|
||
|
|
||
|
log("winbox", 2, "login: stage 1, PKE OK, sending")
|
||
|
err = winbox.sendAndRecv()
|
||
|
if err != nil {
|
||
|
return false, err
|
||
|
}
|
||
|
|
||
|
log("winbox", 2, "login: stage 2, confirmation")
|
||
|
err = winbox.confirmation()
|
||
|
if err != nil {
|
||
|
return false, err
|
||
|
}
|
||
|
|
||
|
if winbox.stage == -1 { // confirmation failed but no error?
|
||
|
return false, nil // report that its a bad login
|
||
|
}
|
||
|
|
||
|
log("winbox", 2, "login: stage 2, confirmation OK, sending")
|
||
|
err = winbox.sendAndRecv()
|
||
|
if err != nil {
|
||
|
return false, err
|
||
|
}
|
||
|
|
||
|
log("winbox", 2, "login: stage 3")
|
||
|
a1 := append(winbox.j, winbox.clientCC...)
|
||
|
winbox.serverCC = getSHA2Digest(append(a1, winbox.z...))
|
||
|
|
||
|
return bytes.Equal(winbox.resp[2:], winbox.serverCC), nil
|
||
|
}
|