Basic FAST support.

This commit is contained in:
Martin Dosch 2024-04-10 15:13:20 +02:00
parent 7486b7a363
commit b0f55a8f7f

171
xmpp.go
View File

@ -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"`
Xmlns string `xml:"xmlns,attr"`
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 {