package federation import ( "context" "fmt" "net/http" ) // AuthenticateRequest checks the X-Matrix signature on an inbound request // and returns the server it came from. // // content is the request body, already read; the signature covers it, so // the caller reads it once and hands the same bytes here and to its own // decoder. A request may carry one Authorization header per key it is // signed with, and any one that verifies is enough. func (k *KeyRing) AuthenticateRequest(ctx context.Context, r *http.Request, content []byte) (string, error) { headers := r.Header.Values("Authorization") if len(headers) == 0 { return "", fmt.Errorf("%w: no Authorization header", ErrBadAuthorization) } var ( origin string lastErr error ) for _, h := range headers { x, err := ParseXMatrix(h) if err != nil { lastErr = err continue } if origin != "" && x.Origin != origin { // Two headers naming two origins is one request claiming to // be from two servers; neither claim is worth checking. return "", fmt.Errorf("%w: headers name both %s and %s", ErrBadAuthorization, origin, x.Origin) } origin = x.Origin if x.Destination != "" && x.Destination != k.serverName { lastErr = fmt.Errorf("%w: addressed to %s, not to this server", ErrBadAuthorization, x.Destination) continue } if x.Origin == k.serverName { lastErr = fmt.Errorf("%w: a request claiming to be from this server", ErrBadAuthorization) continue } // The destination in the signed object is whatever the sender put // in the header, or this server's name when it put nothing: a // sender old enough to leave it out signed with it absent from // the header, not from the object. destination := x.Destination if destination == "" { destination = k.serverName } signed, err := requestSignedJSON(x, destination, r.Method, r.RequestURI, content) if err != nil { lastErr = err continue } if err := k.VerifyJSON(ctx, x.Origin, signed); err != nil { lastErr = fmt.Errorf("%w: %v", ErrBadAuthorization, err) continue } return x.Origin, nil } return "", lastErr }