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"
|
||||
nsBind = "urn:ietf:params:xml:ns:xmpp-bind"
|
||||
nsBind2 = "urn:xmpp:bind:0"
|
||||
nsFast = "urn:xmpp:fast:0"
|
||||
nsSASLCB = "urn:xmpp:sasl-cb:0"
|
||||
nsClient = "jabber:client"
|
||||
nsSession = "urn:ietf:params:xml:ns:xmpp-session"
|
||||
@ -75,6 +76,13 @@ func getCookie() Cookie {
|
||||
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
|
||||
type Client struct {
|
||||
conn net.Conn // connection to server
|
||||
@ -87,6 +95,7 @@ type Client struct {
|
||||
LimitMaxBytes int // Maximum stanza size (XEP-0478: Stream Limits Advertisement)
|
||||
LimitIdleSeconds int // Maximum idle seconds (XEP-0478: Stream Limits Advertisement)
|
||||
Mechanism string
|
||||
Fast Fast // XEP-0484 FAST Token, mechanism and expiry.
|
||||
}
|
||||
|
||||
func (c *Client) JID() string {
|
||||
@ -240,7 +249,7 @@ type Options struct {
|
||||
// XEP-0474: SASL SCRAM Downgrade Protection
|
||||
SSDP bool
|
||||
|
||||
// XEP-0388: XEP-0388: Extensible SASL Profile
|
||||
// XEP-0388: Extensible SASL Profile
|
||||
// Value for software
|
||||
UserAgentSW string
|
||||
|
||||
@ -248,10 +257,18 @@ type Options struct {
|
||||
// Value for device
|
||||
UserAgentDev string
|
||||
|
||||
// XEP-0388: XEP-0388: Extensible SASL Profile
|
||||
// XEP-0388: Extensible SASL Profile
|
||||
// Unique stable identifier for the client installation
|
||||
// MUST be a valid UUIDv4
|
||||
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.
|
||||
@ -431,7 +448,7 @@ func (c *Client) init(o *Options) error {
|
||||
return err
|
||||
}
|
||||
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 scramPlus, ok, tlsConnOK, tls13, serverEndPoint, sasl2, bind2 bool
|
||||
var cbsSlice, mechSlice []string
|
||||
@ -604,9 +621,68 @@ func (c *Client) init(o *Options) error {
|
||||
if 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,
|
||||
"<authenticate xmlns='%s' mechanism='%s'><initial-response>%s</initial-response><user-agent%s>%s%s</user-agent>%s</authenticate>\n",
|
||||
nsSASL2, mechanism, base64.StdEncoding.EncodeToString([]byte(clientFirstMessage)), userAgentID, userAgentSW, userAgentDev, bind2Data)
|
||||
"<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, fastAuth)
|
||||
} else {
|
||||
fmt.Fprintf(c.stanzaWriter, "<auth xmlns='%s' mechanism='%s'>%s</auth>\n",
|
||||
nsSASL, mechanism, base64.StdEncoding.EncodeToString([]byte(clientFirstMessage)))
|
||||
@ -633,6 +709,55 @@ func (c *Client) init(o *Options) error {
|
||||
errorMessage = v.Any.Local
|
||||
}
|
||||
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:
|
||||
sfm = v.Text
|
||||
case *saslChallenge:
|
||||
@ -829,6 +954,23 @@ func (c *Client) init(o *Options) error {
|
||||
if bind2 {
|
||||
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:
|
||||
if strings.HasPrefix(mechanism, "SCRAM-SHA") {
|
||||
successMsg, err := base64.StdEncoding.DecodeString(v.Text)
|
||||
@ -1488,9 +1630,16 @@ type sasl2Authentication struct {
|
||||
Inline struct {
|
||||
Text string `xml:",chardata"`
|
||||
Bind struct {
|
||||
Text string `xml:",chardata"`
|
||||
XMLName xml.Name `xml:"urn:xmpp:bind:0 bind"`
|
||||
Xmlns string `xml:"xmlns,attr"`
|
||||
Text string `xml:",chardata"`
|
||||
} `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"`
|
||||
}
|
||||
|
||||
@ -1521,8 +1670,14 @@ type sasl2Success struct {
|
||||
AuthorizationIdentifier string `xml:"authorization-identifier"`
|
||||
Bound struct {
|
||||
Text string `xml:",chardata"`
|
||||
Xmlns string `xml:"xmlns,attr"`
|
||||
Xmlns string `xml:"urn:xmpp:bind:0,attr"`
|
||||
} `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 {
|
||||
|
Loading…
Reference in New Issue
Block a user