Update gomatrixserverlib

This commit is contained in:
Mark Haines 2017-05-24 15:05:42 +01:00
parent 3b9222e8f7
commit e6835660b0
9 changed files with 320 additions and 31 deletions

2
vendor/manifest vendored
View file

@ -98,7 +98,7 @@
{ {
"importpath": "github.com/matrix-org/gomatrixserverlib", "importpath": "github.com/matrix-org/gomatrixserverlib",
"repository": "https://github.com/matrix-org/gomatrixserverlib", "repository": "https://github.com/matrix-org/gomatrixserverlib",
"revision": "785a53c41170526aa7a91a1fc534afac6ce01a9b", "revision": "9cefcd6c3a00bff51e719a33e19a16edf52cdd6f",
"branch": "master" "branch": "master"
}, },
{ {

View file

@ -28,9 +28,9 @@ const (
// ClientEvent is an event which is fit for consumption by clients, in accordance with the specification. // ClientEvent is an event which is fit for consumption by clients, in accordance with the specification.
type ClientEvent struct { type ClientEvent struct {
Content rawJSON `json:"content"` Content rawJSON `json:"content"`
EventID string `json:"event_id"` EventID string `json:"event_id"`
OriginServerTS int64 `json:"origin_server_ts"` OriginServerTS Timestamp `json:"origin_server_ts"`
// RoomID is omitted on /sync responses // RoomID is omitted on /sync responses
RoomID string `json:"room_id,omitempty"` RoomID string `json:"room_id,omitempty"`
Sender string `json:"sender"` Sender string `json:"sender"`

View file

@ -103,7 +103,8 @@ type eventFields struct {
Redacts string `json:"redacts"` Redacts string `json:"redacts"`
Depth int64 `json:"depth"` Depth int64 `json:"depth"`
Unsigned rawJSON `json:"unsigned"` Unsigned rawJSON `json:"unsigned"`
OriginServerTS int64 `json:"origin_server_ts"` OriginServerTS Timestamp `json:"origin_server_ts"`
Origin ServerName `json:"origin"`
} }
var emptyEventReferenceList = []EventReference{} var emptyEventReferenceList = []EventReference{}
@ -145,8 +146,6 @@ func (eb *EventBuilder) Build(eventID string, now time.Time, origin ServerName,
event.PrevState = &emptyEventReferenceList event.PrevState = &emptyEventReferenceList
} }
// TODO: Check size limits.
var eventJSON []byte var eventJSON []byte
if eventJSON, err = json.Marshal(&event); err != nil { if eventJSON, err = json.Marshal(&event); err != nil {
@ -166,7 +165,14 @@ func (eb *EventBuilder) Build(eventID string, now time.Time, origin ServerName,
} }
result.eventJSON = eventJSON result.eventJSON = eventJSON
err = json.Unmarshal(eventJSON, &result.fields) if err = json.Unmarshal(eventJSON, &result.fields); err != nil {
return
}
if err = result.CheckFields(); err != nil {
return
}
return return
} }
@ -185,9 +191,6 @@ func NewEventFromUntrustedJSON(eventJSON []byte) (result Event, err error) {
delete(event, "destinations") delete(event, "destinations")
delete(event, "age_ts") delete(event, "age_ts")
// TODO: Check that the event fields are correctly defined.
// TODO: Check size limits.
if eventJSON, err = json.Marshal(event); err != nil { if eventJSON, err = json.Marshal(event); err != nil {
return return
} }
@ -206,7 +209,14 @@ func NewEventFromUntrustedJSON(eventJSON []byte) (result Event, err error) {
} }
result.eventJSON = eventJSON result.eventJSON = eventJSON
err = json.Unmarshal(eventJSON, &result.fields) if err = json.Unmarshal(eventJSON, &result.fields); err != nil {
return
}
if err = result.CheckFields(); err != nil {
return
}
return return
} }
@ -303,6 +313,125 @@ func (e Event) StateKeyEquals(stateKey string) bool {
return *e.fields.StateKey == stateKey return *e.fields.StateKey == stateKey
} }
const (
// The event ID, room ID, sender, event type and state key fields cannot be
// bigger than this.
// https://github.com/matrix-org/synapse/blob/v0.21.0/synapse/event_auth.py#L173-L182
maxIDLength = 255
// The entire event JSON, including signatures cannot be bigger than this.
// https://github.com/matrix-org/synapse/blob/v0.21.0/synapse/event_auth.py#L183-184
maxEventLength = 65536
)
// CheckFields checks that the event fields are valid.
// Returns an error if the IDs have the wrong format or too long.
// Returns an error if the total length of the event JSON is too long.
// Returns an error if the event ID doesn't match the origin of the event.
// https://matrix.org/docs/spec/client_server/r0.2.0.html#size-limits
func (e Event) CheckFields() error {
if len(e.eventJSON) > maxEventLength {
return fmt.Errorf(
"gomatrixserverlib: event is too long, length %d > maximum %d",
len(e.eventJSON), maxEventLength,
)
}
if len(e.fields.Type) > maxIDLength {
return fmt.Errorf(
"gomatrixserverlib: event type is too long, length %d > maximum %d",
len(e.fields.Type), maxIDLength,
)
}
if e.fields.StateKey != nil && len(*e.fields.StateKey) > maxIDLength {
return fmt.Errorf(
"gomatrixserverlib: state key is too long, length %d > maximum %d",
len(*e.fields.StateKey), maxIDLength,
)
}
_, err := checkID(e.fields.RoomID, "room", '!')
if err != nil {
return err
}
senderDomain, err := checkID(e.fields.Sender, "user", '@')
if err != nil {
return err
}
eventDomain, err := checkID(e.fields.EventID, "event", '$')
if err != nil {
return err
}
// Synapse requires that the event ID domain has a valid signature.
// https://github.com/matrix-org/synapse/blob/v0.21.0/synapse/event_auth.py#L66-L68
// Synapse requires that the event origin has a valid signature.
// https://github.com/matrix-org/synapse/blob/v0.21.0/synapse/federation/federation_base.py#L133-L136
// Since both domains must be valid domains, and there is no good reason for them
// to be different we might as well ensure that they are the same since it
// makes the signature checks simpler.
if e.fields.Origin != ServerName(eventDomain) {
return fmt.Errorf(
"gomatrixserverlib: event ID domain doesn't match origin: %q != %q",
eventDomain, e.fields.Origin,
)
}
if eventDomain != senderDomain {
// For the most part all events should be sent by a user on the
// originating server
// However "m.room.member" events created from third-party invites
// are allowed to have a different sender because they have the same
// sender as the "m.room.third_party_invite" event they derived from.
// https://github.com/matrix-org/synapse/blob/v0.21.0/synapse/event_auth.py#L58-L64
if e.fields.Type != "m.room.member" {
return fmt.Errorf(
"gomatrixserverlib: sender domain doesn't match origin: %q != %q",
eventDomain, e.fields.Origin,
)
}
c, err := newMemberContentFromEvent(e)
if err != nil {
return err
}
if c.Membership != invite || c.ThirdPartyInvite == nil {
return fmt.Errorf(
"gomatrixserverlib: sender domain doesn't match origin: %q != %q",
eventDomain, e.fields.Origin,
)
}
}
return nil
}
func checkID(id, kind string, sigil byte) (domain string, err error) {
domain, err = domainFromID(id)
if err != nil {
return
}
if id[0] != sigil {
err = fmt.Errorf(
"gomatrixserverlib: invalid %s ID, wanted first byte to be '%c' got '%c'",
kind, sigil, id[0],
)
return
}
if len(id) > maxIDLength {
err = fmt.Errorf(
"gomatrixserverlib: %s ID is too long, length %d > maximum %d",
kind, len(id), maxIDLength,
)
return
}
return
}
// Origin returns the name of the server that sent the event
func (e Event) Origin() ServerName { return e.fields.Origin }
// EventID returns the event ID of the event. // EventID returns the event ID of the event.
func (e Event) EventID() string { func (e Event) EventID() string {
return e.fields.EventID return e.fields.EventID
@ -319,7 +448,7 @@ func (e Event) Type() string {
} }
// OriginServerTS returns the unix timestamp when this event was created on the origin server, with millisecond resolution. // OriginServerTS returns the unix timestamp when this event was created on the origin server, with millisecond resolution.
func (e Event) OriginServerTS() int64 { func (e Event) OriginServerTS() Timestamp {
return e.fields.OriginServerTS return e.fields.OriginServerTS
} }

View file

@ -247,7 +247,7 @@ type AuthEvents struct {
// the event is replaced with the new event. Only returns an error if the event is not a state event. // the event is replaced with the new event. Only returns an error if the event is not a state event.
func (a *AuthEvents) AddEvent(event *Event) error { func (a *AuthEvents) AddEvent(event *Event) error {
if event.StateKey() == nil { if event.StateKey() == nil {
return fmt.Errorf("AddEvent: event %s does not have a state key", event.Type()) return fmt.Errorf("AddEvent: event %q does not have a state key", event.Type())
} }
a.events[StateKeyTuple{event.Type(), *event.StateKey()}] = event a.events[StateKeyTuple{event.Type(), *event.StateKey()}] = event
return nil return nil

View file

@ -184,3 +184,55 @@ func verifyEventSignature(signingName string, keyID KeyID, publicKey ed25519.Pub
return VerifyJSON(signingName, keyID, publicKey, redactedJSON) return VerifyJSON(signingName, keyID, publicKey, redactedJSON)
} }
// VerifyEventSignatures checks that each event in a list of events has valid
// signatures from the server that sent it.
func VerifyEventSignatures(events []Event, keyRing KeyRing) error {
var toVerify []VerifyJSONRequest
for _, event := range events {
redactedJSON, err := redactEvent(event.eventJSON)
if err != nil {
return err
}
v := VerifyJSONRequest{
Message: redactedJSON,
AtTS: event.OriginServerTS(),
ServerName: event.Origin(),
}
toVerify = append(toVerify, v)
// "m.room.member" invite events are signed by both the server sending
// the invite and the server the invite is for.
if event.Type() == "m.room.member" && event.StateKey() != nil {
targetDomain, err := domainFromID(*event.StateKey())
if err != nil {
return err
}
if ServerName(targetDomain) != event.Origin() {
c, err := newMemberContentFromEvent(event)
if err != nil {
return err
}
if c.Membership == invite {
v.ServerName = ServerName(targetDomain)
toVerify = append(toVerify, v)
}
}
}
}
results, err := keyRing.VerifyJSONs(toVerify)
if err != nil {
return err
}
// Check that all the event JSON was correctly signed.
for _, result := range results {
if result.Error != nil {
return result.Error
}
}
// Everything was okay.
return nil
}

View file

@ -34,6 +34,53 @@ type RespState struct {
AuthEvents []Event `json:"auth_chain"` AuthEvents []Event `json:"auth_chain"`
} }
// Check that a response to /state is valid.
func (r RespState) Check(keyRing KeyRing) error {
var allEvents []Event
for _, event := range r.AuthEvents {
if event.StateKey() == nil {
return fmt.Errorf("gomatrixserverlib: event %q does not have a state key", event.EventID())
}
allEvents = append(allEvents, event)
}
stateTuples := map[StateKeyTuple]bool{}
for _, event := range r.StateEvents {
if event.StateKey() == nil {
return fmt.Errorf("gomatrixserverlib: event %q does not have a state key", event.EventID())
}
stateTuple := StateKeyTuple{event.Type(), *event.StateKey()}
if stateTuples[stateTuple] {
return fmt.Errorf(
"gomatrixserverlib: duplicate state key tuple (%q, %q)",
event.Type(), *event.StateKey(),
)
}
stateTuples[stateTuple] = true
allEvents = append(allEvents, event)
}
// Check if the events pass signature checks.
if err := VerifyEventSignatures(allEvents, keyRing); err != nil {
return nil
}
eventsByID := map[string]*Event{}
// Collect a map of event reference to event
for i := range allEvents {
eventsByID[allEvents[i].EventID()] = &allEvents[i]
}
// Check whether the events are allowed by the auth rules.
for _, event := range allEvents {
if err := checkAllowedByAuthEvents(event, eventsByID); err != nil {
return err
}
}
return nil
}
// A RespMakeJoin is the content of a response to GET /_matrix/federation/v1/make_join/{roomID}/{userID} // A RespMakeJoin is the content of a response to GET /_matrix/federation/v1/make_join/{roomID}/{userID}
type RespMakeJoin struct { type RespMakeJoin struct {
// An incomplete m.room.member event for a user on the requesting server // An incomplete m.room.member event for a user on the requesting server
@ -43,6 +90,7 @@ type RespMakeJoin struct {
} }
// A RespSendJoin is the content of a response to PUT /_matrix/federation/v1/send_join/{roomID}/{eventID} // A RespSendJoin is the content of a response to PUT /_matrix/federation/v1/send_join/{roomID}/{eventID}
// It has the same data as a response to /state, but in a slightly different wire format.
type RespSendJoin RespState type RespSendJoin RespState
// MarshalJSON implements json.Marshaller // MarshalJSON implements json.Marshaller
@ -93,6 +141,43 @@ type respSendJoinFields struct {
AuthEvents []Event `json:"auth_chain"` AuthEvents []Event `json:"auth_chain"`
} }
// Check that a reponse to /send_join is valid.
// This checks that it would be valid as a response to /state
// This also checks that the join event is allowed by the state.
func (r RespSendJoin) Check(keyRing KeyRing, joinEvent Event) error {
// First check that the state is valid.
// The response to /send_join has the same data as a response to /state
// and the checks for a response to /state also apply.
if err := RespState(r).Check(keyRing); err != nil {
return err
}
stateEventsByID := map[string]*Event{}
authEvents := NewAuthEvents(nil)
for i, event := range r.StateEvents {
stateEventsByID[event.EventID()] = &r.StateEvents[i]
if err := authEvents.AddEvent(&r.StateEvents[i]); err != nil {
return err
}
}
// Now check that the join event is valid against its auth events.
if err := checkAllowedByAuthEvents(joinEvent, stateEventsByID); err != nil {
return err
}
// Now check that the join event is valid against the supplied state.
if err := Allowed(joinEvent, &authEvents); err != nil {
return fmt.Errorf(
"gomatrixserverlib: event with ID %q is not allowed by the supplied state: %s",
joinEvent.EventID(), err.Error(),
)
}
return nil
}
// A RespDirectory is the content of a response to GET /_matrix/federation/v1/query/directory // A RespDirectory is the content of a response to GET /_matrix/federation/v1/query/directory
// This is returned when looking up a room alias from a remote server. // This is returned when looking up a room alias from a remote server.
// See https://matrix.org/docs/spec/server_server/unstable.html#directory // See https://matrix.org/docs/spec/server_server/unstable.html#directory
@ -104,3 +189,26 @@ type RespDirectory struct {
// before it finds one that it can use to join the room. // before it finds one that it can use to join the room.
Servers []ServerName `json:"servers"` Servers []ServerName `json:"servers"`
} }
func checkAllowedByAuthEvents(event Event, eventsByID map[string]*Event) error {
authEvents := NewAuthEvents(nil)
for _, authRef := range event.AuthEvents() {
authEvent := eventsByID[authRef.EventID]
if authEvent == nil {
return fmt.Errorf(
"gomatrixserverlib: missing auth event with ID %q for event %q",
authRef.EventID, event.EventID(),
)
}
if err := authEvents.AddEvent(authEvent); err != nil {
return err
}
}
if err := Allowed(event, &authEvents); err != nil {
return fmt.Errorf(
"gomatrixserverlib: event with ID %q is not allowed by its auth_events: %s",
event.EventID(), err.Error(),
)
}
return nil
}

View file

@ -58,7 +58,7 @@ type VerifyJSONResult struct {
// Whether the message passed the signature checks. // Whether the message passed the signature checks.
// This will be nil if the message passed the checks. // This will be nil if the message passed the checks.
// This will have an error if the message did not pass the checks. // This will have an error if the message did not pass the checks.
Result error Error error
} }
// VerifyJSONs performs bulk JSON signature verification for a list of VerifyJSONRequests. // VerifyJSONs performs bulk JSON signature verification for a list of VerifyJSONRequests.
@ -73,7 +73,7 @@ func (k *KeyRing) VerifyJSONs(requests []VerifyJSONRequest) ([]VerifyJSONResult,
for i := range requests { for i := range requests {
ids, err := ListKeyIDs(string(requests[i].ServerName), requests[i].Message) ids, err := ListKeyIDs(string(requests[i].ServerName), requests[i].Message)
if err != nil { if err != nil {
results[i].Result = fmt.Errorf("gomatrixserverlib: error extracting key IDs") results[i].Error = fmt.Errorf("gomatrixserverlib: error extracting key IDs")
continue continue
} }
for _, keyID := range ids { for _, keyID := range ids {
@ -82,7 +82,7 @@ func (k *KeyRing) VerifyJSONs(requests []VerifyJSONRequest) ([]VerifyJSONResult,
} }
} }
if len(keyIDs[i]) == 0 { if len(keyIDs[i]) == 0 {
results[i].Result = fmt.Errorf( results[i].Error = fmt.Errorf(
"gomatrixserverlib: not signed by %q with a supported algorithm", requests[i].ServerName, "gomatrixserverlib: not signed by %q with a supported algorithm", requests[i].ServerName,
) )
continue continue
@ -91,7 +91,7 @@ func (k *KeyRing) VerifyJSONs(requests []VerifyJSONRequest) ([]VerifyJSONResult,
// This will be unset if one of the signature checks passes. // This will be unset if one of the signature checks passes.
// This will be overwritten if one of the signature checks fails. // This will be overwritten if one of the signature checks fails.
// Therefore this will only remain in place if the keys couldn't be downloaded. // Therefore this will only remain in place if the keys couldn't be downloaded.
results[i].Result = fmt.Errorf( results[i].Error = fmt.Errorf(
"gomatrixserverlib: could not download key for %q", requests[i].ServerName, "gomatrixserverlib: could not download key for %q", requests[i].ServerName,
) )
} }
@ -139,7 +139,7 @@ func (k *KeyRing) isAlgorithmSupported(keyID KeyID) bool {
func (k *KeyRing) publicKeyRequests(requests []VerifyJSONRequest, results []VerifyJSONResult, keyIDs [][]KeyID) map[PublicKeyRequest]Timestamp { func (k *KeyRing) publicKeyRequests(requests []VerifyJSONRequest, results []VerifyJSONResult, keyIDs [][]KeyID) map[PublicKeyRequest]Timestamp {
keyRequests := map[PublicKeyRequest]Timestamp{} keyRequests := map[PublicKeyRequest]Timestamp{}
for i := range requests { for i := range requests {
if results[i].Result == nil { if results[i].Error == nil {
// We've already verified this message, we don't need to refetch the keys for it. // We've already verified this message, we don't need to refetch the keys for it.
continue continue
} }
@ -165,7 +165,7 @@ func (k *KeyRing) checkUsingKeys(
keys map[PublicKeyRequest]ServerKeys, keys map[PublicKeyRequest]ServerKeys,
) { ) {
for i := range requests { for i := range requests {
if results[i].Result == nil { if results[i].Error == nil {
// We've already checked this message and it passed the signature checks. // We've already checked this message and it passed the signature checks.
// So we can skip to the next message. // So we can skip to the next message.
continue continue
@ -180,7 +180,7 @@ func (k *KeyRing) checkUsingKeys(
if publicKey == nil { if publicKey == nil {
// The key wasn't valid at the timestamp we needed it to be valid at. // The key wasn't valid at the timestamp we needed it to be valid at.
// So skip onto the next key. // So skip onto the next key.
results[i].Result = fmt.Errorf( results[i].Error = fmt.Errorf(
"gomatrixserverlib: key with ID %q for %q not valid at %d", "gomatrixserverlib: key with ID %q for %q not valid at %d",
keyID, requests[i].ServerName, requests[i].AtTS, keyID, requests[i].ServerName, requests[i].AtTS,
) )
@ -190,11 +190,11 @@ func (k *KeyRing) checkUsingKeys(
string(requests[i].ServerName), keyID, ed25519.PublicKey(publicKey), requests[i].Message, string(requests[i].ServerName), keyID, ed25519.PublicKey(publicKey), requests[i].Message,
); err != nil { ); err != nil {
// The signature wasn't valid, record the error and try the next key ID. // The signature wasn't valid, record the error and try the next key ID.
results[i].Result = err results[i].Error = err
continue continue
} }
// The signature is valid, set the result to nil. // The signature is valid, set the result to nil.
results[i].Result = nil results[i].Error = nil
break break
} }
} }

View file

@ -70,8 +70,8 @@ func TestVerifyJSONsSuccess(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
if len(results) != 1 || results[0].Result != nil { if len(results) != 1 || results[0].Error != nil {
t.Fatalf("VerifyJSON(): Wanted [{Result: nil}] got %#v", results) t.Fatalf("VerifyJSON(): Wanted [{Error: nil}] got %#v", results)
} }
} }
@ -86,8 +86,8 @@ func TestVerifyJSONsUnknownServerFails(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
if len(results) != 1 || results[0].Result == nil { if len(results) != 1 || results[0].Error == nil {
t.Fatalf("VerifyJSON(): Wanted [{Result: <some error>}] got %#v", results) t.Fatalf("VerifyJSON(): Wanted [{Error: <some error>}] got %#v", results)
} }
} }
@ -103,8 +103,8 @@ func TestVerifyJSONsDistantFutureFails(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
if len(results) != 1 || results[0].Result == nil { if len(results) != 1 || results[0].Error == nil {
t.Fatalf("VerifyJSON(): Wanted [{Result: <some error>}] got %#v", results) t.Fatalf("VerifyJSON(): Wanted [{Error: <some error>}] got %#v", results)
} }
} }

View file

@ -224,9 +224,9 @@ func VerifyHTTPRequest(
util.GetLogger(req.Context()).WithError(err).Print(message) util.GetLogger(req.Context()).WithError(err).Print(message)
return nil, util.MessageResponse(500, message) return nil, util.MessageResponse(500, message)
} }
if results[0].Result != nil { if results[0].Error != nil {
message := "Invalid request signature" message := "Invalid request signature"
util.GetLogger(req.Context()).WithError(results[0].Result).Print(message) util.GetLogger(req.Context()).WithError(results[0].Error).Print(message)
return nil, util.MessageResponse(401, message) return nil, util.MessageResponse(401, message)
} }