forked from jshiffer/go-xmpp
Basic FAST support.
This commit is contained in:
parent
7486b7a363
commit
b0f55a8f7f
169
xmpp.go
169
xmpp.go
@ -52,6 +52,7 @@ const (
|
|||||||
nsSASL2 = "urn:xmpp:sasl:2"
|
nsSASL2 = "urn:xmpp:sasl:2"
|
||||||
nsBind = "urn:ietf:params:xml:ns:xmpp-bind"
|
nsBind = "urn:ietf:params:xml:ns:xmpp-bind"
|
||||||
nsBind2 = "urn:xmpp:bind:0"
|
nsBind2 = "urn:xmpp:bind:0"
|
||||||
|
nsFast = "urn:xmpp:fast:0"
|
||||||
nsSASLCB = "urn:xmpp:sasl-cb:0"
|
nsSASLCB = "urn:xmpp:sasl-cb:0"
|
||||||
nsClient = "jabber:client"
|
nsClient = "jabber:client"
|
||||||
nsSession = "urn:ietf:params:xml:ns:xmpp-session"
|
nsSession = "urn:ietf:params:xml:ns:xmpp-session"
|
||||||
@ -75,6 +76,13 @@ func getCookie() Cookie {
|
|||||||
return Cookie(binary.LittleEndian.Uint64(buf[:]))
|
return Cookie(binary.LittleEndian.Uint64(buf[:]))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fast holds the XEP-0484 fast token, mechanism and expiry date
|
||||||
|
type Fast struct {
|
||||||
|
Token string
|
||||||
|
Mechanism string
|
||||||
|
Expiry time.Time
|
||||||
|
}
|
||||||
|
|
||||||
// Client holds XMPP connection options
|
// Client holds XMPP connection options
|
||||||
type Client struct {
|
type Client struct {
|
||||||
conn net.Conn // connection to server
|
conn net.Conn // connection to server
|
||||||
@ -87,6 +95,7 @@ type Client struct {
|
|||||||
LimitMaxBytes int // Maximum stanza size (XEP-0478: Stream Limits Advertisement)
|
LimitMaxBytes int // Maximum stanza size (XEP-0478: Stream Limits Advertisement)
|
||||||
LimitIdleSeconds int // Maximum idle seconds (XEP-0478: Stream Limits Advertisement)
|
LimitIdleSeconds int // Maximum idle seconds (XEP-0478: Stream Limits Advertisement)
|
||||||
Mechanism string
|
Mechanism string
|
||||||
|
Fast Fast // XEP-0484 FAST Token, mechanism and expiry.
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) JID() string {
|
func (c *Client) JID() string {
|
||||||
@ -240,7 +249,7 @@ type Options struct {
|
|||||||
// XEP-0474: SASL SCRAM Downgrade Protection
|
// XEP-0474: SASL SCRAM Downgrade Protection
|
||||||
SSDP bool
|
SSDP bool
|
||||||
|
|
||||||
// XEP-0388: XEP-0388: Extensible SASL Profile
|
// XEP-0388: Extensible SASL Profile
|
||||||
// Value for software
|
// Value for software
|
||||||
UserAgentSW string
|
UserAgentSW string
|
||||||
|
|
||||||
@ -248,10 +257,18 @@ type Options struct {
|
|||||||
// Value for device
|
// Value for device
|
||||||
UserAgentDev string
|
UserAgentDev string
|
||||||
|
|
||||||
// XEP-0388: XEP-0388: Extensible SASL Profile
|
// XEP-0388: Extensible SASL Profile
|
||||||
// Unique stable identifier for the client installation
|
// Unique stable identifier for the client installation
|
||||||
// MUST be a valid UUIDv4
|
// MUST be a valid UUIDv4
|
||||||
UserAgentID string
|
UserAgentID string
|
||||||
|
|
||||||
|
// XEP-0484: Fast Authentication Streamlining Tokens
|
||||||
|
// Fast Token
|
||||||
|
FastToken string
|
||||||
|
|
||||||
|
// XEP-0484: Fast Authentication Streamlining Tokens
|
||||||
|
// Fast Mechanism
|
||||||
|
FastMechanism string
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewClient establishes a new Client connection based on a set of Options.
|
// NewClient establishes a new Client connection based on a set of Options.
|
||||||
@ -431,7 +448,7 @@ func (c *Client) init(o *Options) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
var mechanism, channelBinding, clientFirstMessage, clientFinalMessageBare, authMessage string
|
var mechanism, channelBinding, clientFirstMessage, clientFinalMessageBare, authMessage string
|
||||||
var bind2Data, resource, userAgentSW, userAgentDev, userAgentID string
|
var bind2Data, resource, userAgentSW, userAgentDev, userAgentID, fastAuth string
|
||||||
var serverSignature, keyingMaterial []byte
|
var serverSignature, keyingMaterial []byte
|
||||||
var scramPlus, ok, tlsConnOK, tls13, serverEndPoint, sasl2, bind2 bool
|
var scramPlus, ok, tlsConnOK, tls13, serverEndPoint, sasl2, bind2 bool
|
||||||
var cbsSlice, mechSlice []string
|
var cbsSlice, mechSlice []string
|
||||||
@ -604,9 +621,68 @@ func (c *Client) init(o *Options) error {
|
|||||||
if o.UserAgentID != "" {
|
if o.UserAgentID != "" {
|
||||||
userAgentID = fmt.Sprintf(" id='%s'", o.UserAgentID)
|
userAgentID = fmt.Sprintf(" id='%s'", o.UserAgentID)
|
||||||
}
|
}
|
||||||
|
if f.Authentication.Inline.Fast.Mechanism != nil && o.UserAgentID != "" {
|
||||||
|
var mech string
|
||||||
|
if o.FastToken == "" {
|
||||||
|
m := f.Authentication.Inline.Fast.Mechanism
|
||||||
|
switch {
|
||||||
|
case slices.Contains(m, "HT-SHA-256-EXPR") && tls13:
|
||||||
|
mech = "HT-SHA-256-EXPR"
|
||||||
|
case slices.Contains(m, "HT-SHA-256-UNIQ") && !tls13:
|
||||||
|
mech = "HT-SHA-256-UNIQ"
|
||||||
|
case slices.Contains(m, "HT-SHA-256-ENDP"):
|
||||||
|
mech = "HT-SHA-256-ENDP"
|
||||||
|
case slices.Contains(m, "HT-SHA-256-NONE"):
|
||||||
|
mech = "HT-SHA-256-NONE"
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("fast: unsupported auth mechanism %s", m)
|
||||||
|
}
|
||||||
|
fastAuth = fmt.Sprintf("<request-token xmlns='%s' mechanism='%s'/>", nsFast, mech)
|
||||||
|
} else {
|
||||||
|
fastAuth = fmt.Sprintf("<fast xmlns='%s' />", nsFast)
|
||||||
|
tlsState := tlsConn.ConnectionState()
|
||||||
|
mechanism = o.FastMechanism
|
||||||
|
switch mechanism {
|
||||||
|
case "HT-SHA-256-EXPR":
|
||||||
|
keyingMaterial, err = tlsState.ExportKeyingMaterial("EXPORTER-Channel-Binding", nil, 32)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
case "HT-SHA-256-UNIQ":
|
||||||
|
keyingMaterial = tlsState.TLSUnique
|
||||||
|
case "HT-SHA-256-ENDP":
|
||||||
|
var h hash.Hash
|
||||||
|
switch tlsState.PeerCertificates[0].SignatureAlgorithm {
|
||||||
|
case x509.SHA1WithRSA, x509.SHA256WithRSA, x509.ECDSAWithSHA1,
|
||||||
|
x509.ECDSAWithSHA256, x509.SHA256WithRSAPSS:
|
||||||
|
h = sha256.New()
|
||||||
|
case x509.SHA384WithRSA, x509.ECDSAWithSHA384, x509.SHA384WithRSAPSS:
|
||||||
|
h = sha512.New384()
|
||||||
|
case x509.SHA512WithRSA, x509.ECDSAWithSHA512, x509.SHA512WithRSAPSS:
|
||||||
|
h = sha512.New()
|
||||||
|
}
|
||||||
|
h.Write(tlsState.PeerCertificates[0].Raw)
|
||||||
|
keyingMaterial = h.Sum(nil)
|
||||||
|
h.Reset()
|
||||||
|
case "HT-SHA-256-NONE":
|
||||||
|
keyingMaterial = []byte("")
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("fast: unsupported auth mechanism %s", mechanism)
|
||||||
|
}
|
||||||
|
h := hmac.New(sha256.New, []byte(o.FastToken))
|
||||||
|
initiator := append([]byte("Initiator")[:], keyingMaterial[:]...)
|
||||||
|
_, err = h.Write(initiator)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
initiatorHashedToken := h.Sum(nil)
|
||||||
|
user := strings.Split(o.User, "@")[0]
|
||||||
|
clientFirstMessage = user + "\x00" + string(initiatorHashedToken)
|
||||||
|
}
|
||||||
|
}
|
||||||
fmt.Fprintf(c.stanzaWriter,
|
fmt.Fprintf(c.stanzaWriter,
|
||||||
"<authenticate xmlns='%s' mechanism='%s'><initial-response>%s</initial-response><user-agent%s>%s%s</user-agent>%s</authenticate>\n",
|
"<authenticate xmlns='%s' mechanism='%s'><initial-response>%s</initial-response><user-agent%s>%s%s</user-agent>%s%s</authenticate>\n",
|
||||||
nsSASL2, mechanism, base64.StdEncoding.EncodeToString([]byte(clientFirstMessage)), userAgentID, userAgentSW, userAgentDev, bind2Data)
|
nsSASL2, mechanism, base64.StdEncoding.EncodeToString([]byte(clientFirstMessage)), userAgentID, userAgentSW, userAgentDev, bind2Data, fastAuth)
|
||||||
} else {
|
} else {
|
||||||
fmt.Fprintf(c.stanzaWriter, "<auth xmlns='%s' mechanism='%s'>%s</auth>\n",
|
fmt.Fprintf(c.stanzaWriter, "<auth xmlns='%s' mechanism='%s'>%s</auth>\n",
|
||||||
nsSASL, mechanism, base64.StdEncoding.EncodeToString([]byte(clientFirstMessage)))
|
nsSASL, mechanism, base64.StdEncoding.EncodeToString([]byte(clientFirstMessage)))
|
||||||
@ -633,6 +709,55 @@ func (c *Client) init(o *Options) error {
|
|||||||
errorMessage = v.Any.Local
|
errorMessage = v.Any.Local
|
||||||
}
|
}
|
||||||
return errors.New("auth failure: " + errorMessage)
|
return errors.New("auth failure: " + errorMessage)
|
||||||
|
case *sasl2Success:
|
||||||
|
if strings.HasPrefix(mechanism, "SCRAM-SHA") {
|
||||||
|
successMsg, err := base64.StdEncoding.DecodeString(v.AdditionalData)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !strings.HasPrefix(string(successMsg), "v=") {
|
||||||
|
return errors.New("server sent unexpected content in SCRAM success message")
|
||||||
|
}
|
||||||
|
c.Mechanism = mechanism
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(mechanism, "HT-SHA") {
|
||||||
|
// TODO: Check whether server implementations already support
|
||||||
|
// https://www.ietf.org/archive/id/draft-schmaus-kitten-sasl-ht-09.html#section-3.3
|
||||||
|
h := hmac.New(sha256.New, []byte(o.FastToken))
|
||||||
|
responder := append([]byte("Responder")[:], keyingMaterial[:]...)
|
||||||
|
_, err = h.Write(responder)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
responderMsgRcv, err := base64.StdEncoding.DecodeString(v.AdditionalData)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
responderMsgCalc := h.Sum(nil)
|
||||||
|
if string(responderMsgCalc) != string(responderMsgRcv) {
|
||||||
|
return fmt.Errorf("server sent unexpected content in FAST success message")
|
||||||
|
}
|
||||||
|
c.Mechanism = mechanism
|
||||||
|
}
|
||||||
|
if bind2 {
|
||||||
|
c.jid = v.AuthorizationIdentifier
|
||||||
|
}
|
||||||
|
if v.Token.Token != "" {
|
||||||
|
m := f.Authentication.Inline.Fast.Mechanism
|
||||||
|
switch {
|
||||||
|
case slices.Contains(m, "HT-SHA-256-EXPR") && tls13:
|
||||||
|
c.Fast.Mechanism = "HT-SHA-256-EXPR"
|
||||||
|
case slices.Contains(m, "HT-SHA-256-UNIQ") && !tls13:
|
||||||
|
c.Fast.Mechanism = "HT-SHA-256-UNIQ"
|
||||||
|
case slices.Contains(m, "HT-SHA-256-ENDP"):
|
||||||
|
c.Fast.Mechanism = "HT-SHA-256-ENDP"
|
||||||
|
case slices.Contains(m, "HT-SHA-256-NONE"):
|
||||||
|
c.Fast.Mechanism = "HT-SHA-256-NONE"
|
||||||
|
}
|
||||||
|
c.Fast.Token = v.Token.Token
|
||||||
|
c.Fast.Expiry, _ = time.Parse(time.RFC3339, v.Token.Expiry)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
case *sasl2Challenge:
|
case *sasl2Challenge:
|
||||||
sfm = v.Text
|
sfm = v.Text
|
||||||
case *saslChallenge:
|
case *saslChallenge:
|
||||||
@ -829,6 +954,23 @@ func (c *Client) init(o *Options) error {
|
|||||||
if bind2 {
|
if bind2 {
|
||||||
c.jid = v.AuthorizationIdentifier
|
c.jid = v.AuthorizationIdentifier
|
||||||
}
|
}
|
||||||
|
if v.Token.Token != "" {
|
||||||
|
if v.Token.Token != "" {
|
||||||
|
m := f.Authentication.Inline.Fast.Mechanism
|
||||||
|
switch {
|
||||||
|
case slices.Contains(m, "HT-SHA-256-EXPR") && tls13:
|
||||||
|
c.Fast.Mechanism = "HT-SHA-256-EXPR"
|
||||||
|
case slices.Contains(m, "HT-SHA-256-UNIQ") && !tls13:
|
||||||
|
c.Fast.Mechanism = "HT-SHA-256-UNIQ"
|
||||||
|
case slices.Contains(m, "HT-SHA-256-ENDP"):
|
||||||
|
c.Fast.Mechanism = "HT-SHA-256-ENDP"
|
||||||
|
case slices.Contains(m, "HT-SHA-256-NONE"):
|
||||||
|
c.Fast.Mechanism = "HT-SHA-256-NONE"
|
||||||
|
}
|
||||||
|
c.Fast.Token = v.Token.Token
|
||||||
|
c.Fast.Expiry, _ = time.Parse(time.RFC3339, v.Token.Expiry)
|
||||||
|
}
|
||||||
|
}
|
||||||
case *saslSuccess:
|
case *saslSuccess:
|
||||||
if strings.HasPrefix(mechanism, "SCRAM-SHA") {
|
if strings.HasPrefix(mechanism, "SCRAM-SHA") {
|
||||||
successMsg, err := base64.StdEncoding.DecodeString(v.Text)
|
successMsg, err := base64.StdEncoding.DecodeString(v.Text)
|
||||||
@ -1488,9 +1630,16 @@ type sasl2Authentication struct {
|
|||||||
Inline struct {
|
Inline struct {
|
||||||
Text string `xml:",chardata"`
|
Text string `xml:",chardata"`
|
||||||
Bind struct {
|
Bind struct {
|
||||||
Text string `xml:",chardata"`
|
XMLName xml.Name `xml:"urn:xmpp:bind:0 bind"`
|
||||||
Xmlns string `xml:"xmlns,attr"`
|
Xmlns string `xml:"xmlns,attr"`
|
||||||
|
Text string `xml:",chardata"`
|
||||||
} `xml:"bind"`
|
} `xml:"bind"`
|
||||||
|
Fast struct {
|
||||||
|
XMLName xml.Name `xml:"urn:xmpp:fast:0 fast"`
|
||||||
|
Text string `xml:",chardata"`
|
||||||
|
Tls0rtt string `xml:"tls-0rtt,attr"`
|
||||||
|
Mechanism []string `xml:"mechanism"`
|
||||||
|
} `xml:"fast"`
|
||||||
} `xml:"inline"`
|
} `xml:"inline"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1521,8 +1670,14 @@ type sasl2Success struct {
|
|||||||
AuthorizationIdentifier string `xml:"authorization-identifier"`
|
AuthorizationIdentifier string `xml:"authorization-identifier"`
|
||||||
Bound struct {
|
Bound struct {
|
||||||
Text string `xml:",chardata"`
|
Text string `xml:",chardata"`
|
||||||
Xmlns string `xml:"xmlns,attr"`
|
Xmlns string `xml:"urn:xmpp:bind:0,attr"`
|
||||||
} `xml:"bound"`
|
} `xml:"bound"`
|
||||||
|
Token struct {
|
||||||
|
Text string `xml:",chardata"`
|
||||||
|
Xmlns string `xml:"urn:xmpp:fast:0,attr"`
|
||||||
|
Expiry string `xml:"expiry,attr"`
|
||||||
|
Token string `xml:"token,attr"`
|
||||||
|
} `xml:"token"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type saslSuccess struct {
|
type saslSuccess struct {
|
||||||
|
Loading…
Reference in New Issue
Block a user