sum7
/
yaja
Archived
1
0
Fork 0
This repository has been archived on 2020-09-27. You can view files and clone it, but cannot push or open issues or pull requests.
yaja/client/auth.go

137 lines
3.6 KiB
Go
Raw Permalink Normal View History

2018-02-10 13:34:42 +01:00
package client
import (
"crypto/md5"
"crypto/rand"
"encoding/base64"
"errors"
"fmt"
"math/big"
"strings"
"dev.sum7.eu/genofire/yaja/xmpp"
2018-02-10 13:34:42 +01:00
)
func (client *Client) auth(password string) error {
logCTX := client.Logging.WithField("type", "auth")
2018-02-10 13:34:42 +01:00
f, err := client.startStream()
if err != nil {
return err
}
//auth:
mechanism := ""
challenge := &xmpp.SASLChallenge{}
response := &xmpp.SASLResponse{}
2018-02-10 13:34:42 +01:00
for _, m := range f.Mechanisms.Mechanism {
logCTX.Infof("try auth with '%s'", m)
2018-02-11 19:35:32 +01:00
if m == "SCRAM-SHA-1" {
/*
mechanism = m
TODO
break
*/
2018-02-10 13:34:42 +01:00
}
2018-02-11 19:35:32 +01:00
2018-02-10 13:34:42 +01:00
if m == "DIGEST-MD5" {
mechanism = m
// Digest-MD5 authentication
client.Send(&xmpp.SASLAuth{
2018-02-11 19:35:32 +01:00
Mechanism: m,
})
2018-02-11 22:03:58 +01:00
if err := client.ReadDecode(challenge); err != nil {
2018-02-10 13:34:42 +01:00
return err
}
2018-02-11 19:35:32 +01:00
b, err := base64.StdEncoding.DecodeString(challenge.Body)
2018-02-10 13:34:42 +01:00
if err != nil {
return err
}
tokens := map[string]string{}
for _, token := range strings.Split(string(b), ",") {
kv := strings.SplitN(strings.TrimSpace(token), "=", 2)
if len(kv) == 2 {
if kv[1][0] == '"' && kv[1][len(kv[1])-1] == '"' {
kv[1] = kv[1][1 : len(kv[1])-1]
}
tokens[kv[0]] = kv[1]
}
}
realm, _ := tokens["realm"]
nonce, _ := tokens["nonce"]
qop, _ := tokens["qop"]
charset, _ := tokens["charset"]
cnonceStr := cnonce()
digestURI := "xmpp/" + client.JID.Domain
nonceCount := fmt.Sprintf("%08x", 1)
2018-02-15 22:03:49 +01:00
digest := saslDigestResponse(client.JID.Local, realm, password, nonce, cnonceStr, "AUTHENTICATE", digestURI, nonceCount)
message := "username=\"" + client.JID.Local + "\", realm=\"" + realm + "\", nonce=\"" + nonce + "\", cnonce=\"" + cnonceStr +
2018-02-10 13:34:42 +01:00
"\", nc=" + nonceCount + ", qop=" + qop + ", digest-uri=\"" + digestURI + "\", response=" + digest + ", charset=" + charset
2018-02-11 19:35:32 +01:00
response.Body = base64.StdEncoding.EncodeToString([]byte(message))
2018-02-11 22:03:58 +01:00
client.Send(response)
2018-02-11 19:35:32 +01:00
break
}
if m == "PLAIN" {
mechanism = m
// Plain authentication: send base64-encoded \x00 user \x00 password.
2018-02-15 22:03:49 +01:00
raw := "\x00" + client.JID.Local + "\x00" + password
2018-02-11 19:35:32 +01:00
enc := make([]byte, base64.StdEncoding.EncodedLen(len(raw)))
base64.StdEncoding.Encode(enc, []byte(raw))
client.Send(&xmpp.SASLAuth{
2018-02-11 19:35:32 +01:00
Mechanism: "PLAIN",
Body: string(enc),
})
2018-02-10 13:34:42 +01:00
break
}
}
if mechanism == "" {
2018-02-11 19:35:32 +01:00
return fmt.Errorf("PLAIN authentication is not an option: %s", f.Mechanisms.Mechanism)
2018-02-10 13:34:42 +01:00
}
logCTX.Infof("used auth with '%s'", mechanism)
2018-02-11 22:03:58 +01:00
2018-02-10 13:34:42 +01:00
element, err := client.Read()
if err != nil {
return err
}
fail := xmpp.SASLFailure{}
2018-02-11 22:03:58 +01:00
if err := client.Decode(&fail, element); err == nil {
2018-02-13 20:05:18 +01:00
if txt := fail.Text; txt != nil {
return errors.New(xmpp.XMLChildrenString(fail) + " : " + txt.Body)
2018-02-13 20:05:18 +01:00
}
return errors.New(xmpp.XMLChildrenString(fail))
2018-02-11 19:35:32 +01:00
}
if err := client.Decode(&xmpp.SASLSuccess{}, element); err != nil {
2018-02-11 19:35:32 +01:00
return errors.New("auth failed - with unexpected answer")
2018-02-10 13:34:42 +01:00
}
return nil
}
func saslDigestResponse(username, realm, passwd, nonce, cnonceStr, authenticate, digestURI, nonceCountStr string) string {
h := func(text string) []byte {
h := md5.New()
h.Write([]byte(text))
return h.Sum(nil)
}
hex := func(bytes []byte) string {
return fmt.Sprintf("%x", bytes)
}
kd := func(secret, data string) []byte {
return h(secret + ":" + data)
}
a1 := string(h(username+":"+realm+":"+passwd)) + ":" + nonce + ":" + cnonceStr
a2 := authenticate + ":" + digestURI
response := hex(kd(hex(h(a1)), nonce+":"+nonceCountStr+":"+cnonceStr+":auth:"+hex(h(a2))))
return response
}
func cnonce() string {
randSize := big.NewInt(0)
randSize.Lsh(big.NewInt(1), 64)
cn, err := rand.Int(rand.Reader, randSize)
if err != nil {
return ""
}
return fmt.Sprintf("%016x", cn)
}