From e6835660b0e974d25c4d0e289c8e47b81397ed7f Mon Sep 17 00:00:00 2001 From: Mark Haines Date: Wed, 24 May 2017 15:05:42 +0100 Subject: [PATCH] Update gomatrixserverlib --- vendor/manifest | 2 +- .../gomatrixserverlib/clientevent.go | 6 +- .../matrix-org/gomatrixserverlib/event.go | 147 ++++++++++++++++-- .../matrix-org/gomatrixserverlib/eventauth.go | 2 +- .../gomatrixserverlib/eventcrypto.go | 52 +++++++ .../gomatrixserverlib/federationtypes.go | 108 +++++++++++++ .../matrix-org/gomatrixserverlib/keyring.go | 18 +-- .../gomatrixserverlib/keyring_test.go | 12 +- .../matrix-org/gomatrixserverlib/request.go | 4 +- 9 files changed, 320 insertions(+), 31 deletions(-) diff --git a/vendor/manifest b/vendor/manifest index c94ef1957..8df0f6a97 100644 --- a/vendor/manifest +++ b/vendor/manifest @@ -98,7 +98,7 @@ { "importpath": "github.com/matrix-org/gomatrixserverlib", "repository": "https://github.com/matrix-org/gomatrixserverlib", - "revision": "785a53c41170526aa7a91a1fc534afac6ce01a9b", + "revision": "9cefcd6c3a00bff51e719a33e19a16edf52cdd6f", "branch": "master" }, { diff --git a/vendor/src/github.com/matrix-org/gomatrixserverlib/clientevent.go b/vendor/src/github.com/matrix-org/gomatrixserverlib/clientevent.go index d4dcac48c..5285fec44 100644 --- a/vendor/src/github.com/matrix-org/gomatrixserverlib/clientevent.go +++ b/vendor/src/github.com/matrix-org/gomatrixserverlib/clientevent.go @@ -28,9 +28,9 @@ const ( // ClientEvent is an event which is fit for consumption by clients, in accordance with the specification. type ClientEvent struct { - Content rawJSON `json:"content"` - EventID string `json:"event_id"` - OriginServerTS int64 `json:"origin_server_ts"` + Content rawJSON `json:"content"` + EventID string `json:"event_id"` + OriginServerTS Timestamp `json:"origin_server_ts"` // RoomID is omitted on /sync responses RoomID string `json:"room_id,omitempty"` Sender string `json:"sender"` diff --git a/vendor/src/github.com/matrix-org/gomatrixserverlib/event.go b/vendor/src/github.com/matrix-org/gomatrixserverlib/event.go index f761727e7..e26484d63 100644 --- a/vendor/src/github.com/matrix-org/gomatrixserverlib/event.go +++ b/vendor/src/github.com/matrix-org/gomatrixserverlib/event.go @@ -103,7 +103,8 @@ type eventFields struct { Redacts string `json:"redacts"` Depth int64 `json:"depth"` Unsigned rawJSON `json:"unsigned"` - OriginServerTS int64 `json:"origin_server_ts"` + OriginServerTS Timestamp `json:"origin_server_ts"` + Origin ServerName `json:"origin"` } var emptyEventReferenceList = []EventReference{} @@ -145,8 +146,6 @@ func (eb *EventBuilder) Build(eventID string, now time.Time, origin ServerName, event.PrevState = &emptyEventReferenceList } - // TODO: Check size limits. - var eventJSON []byte 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 - err = json.Unmarshal(eventJSON, &result.fields) + if err = json.Unmarshal(eventJSON, &result.fields); err != nil { + return + } + + if err = result.CheckFields(); err != nil { + return + } + return } @@ -185,9 +191,6 @@ func NewEventFromUntrustedJSON(eventJSON []byte) (result Event, err error) { delete(event, "destinations") 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 { return } @@ -206,7 +209,14 @@ func NewEventFromUntrustedJSON(eventJSON []byte) (result Event, err error) { } 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 } @@ -303,6 +313,125 @@ func (e Event) StateKeyEquals(stateKey string) bool { 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. func (e Event) EventID() string { 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. -func (e Event) OriginServerTS() int64 { +func (e Event) OriginServerTS() Timestamp { return e.fields.OriginServerTS } diff --git a/vendor/src/github.com/matrix-org/gomatrixserverlib/eventauth.go b/vendor/src/github.com/matrix-org/gomatrixserverlib/eventauth.go index 242cb6ab7..e3d790990 100644 --- a/vendor/src/github.com/matrix-org/gomatrixserverlib/eventauth.go +++ b/vendor/src/github.com/matrix-org/gomatrixserverlib/eventauth.go @@ -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. func (a *AuthEvents) AddEvent(event *Event) error { 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 return nil diff --git a/vendor/src/github.com/matrix-org/gomatrixserverlib/eventcrypto.go b/vendor/src/github.com/matrix-org/gomatrixserverlib/eventcrypto.go index 80ccf9207..beeddd3e2 100644 --- a/vendor/src/github.com/matrix-org/gomatrixserverlib/eventcrypto.go +++ b/vendor/src/github.com/matrix-org/gomatrixserverlib/eventcrypto.go @@ -184,3 +184,55 @@ func verifyEventSignature(signingName string, keyID KeyID, publicKey ed25519.Pub 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 +} diff --git a/vendor/src/github.com/matrix-org/gomatrixserverlib/federationtypes.go b/vendor/src/github.com/matrix-org/gomatrixserverlib/federationtypes.go index 1dbc96bc5..cfddab910 100644 --- a/vendor/src/github.com/matrix-org/gomatrixserverlib/federationtypes.go +++ b/vendor/src/github.com/matrix-org/gomatrixserverlib/federationtypes.go @@ -34,6 +34,53 @@ type RespState struct { 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} type RespMakeJoin struct { // 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} +// It has the same data as a response to /state, but in a slightly different wire format. type RespSendJoin RespState // MarshalJSON implements json.Marshaller @@ -93,6 +141,43 @@ type respSendJoinFields struct { 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 // This is returned when looking up a room alias from a remote server. // 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. 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 +} diff --git a/vendor/src/github.com/matrix-org/gomatrixserverlib/keyring.go b/vendor/src/github.com/matrix-org/gomatrixserverlib/keyring.go index cea6aa5d3..f86108bad 100644 --- a/vendor/src/github.com/matrix-org/gomatrixserverlib/keyring.go +++ b/vendor/src/github.com/matrix-org/gomatrixserverlib/keyring.go @@ -58,7 +58,7 @@ type VerifyJSONResult struct { // Whether the message passed the signature checks. // This will be nil if the message passed 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. @@ -73,7 +73,7 @@ func (k *KeyRing) VerifyJSONs(requests []VerifyJSONRequest) ([]VerifyJSONResult, for i := range requests { ids, err := ListKeyIDs(string(requests[i].ServerName), requests[i].Message) if err != nil { - results[i].Result = fmt.Errorf("gomatrixserverlib: error extracting key IDs") + results[i].Error = fmt.Errorf("gomatrixserverlib: error extracting key IDs") continue } for _, keyID := range ids { @@ -82,7 +82,7 @@ func (k *KeyRing) VerifyJSONs(requests []VerifyJSONRequest) ([]VerifyJSONResult, } } 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, ) 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 overwritten if one of the signature checks fails. // 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, ) } @@ -139,7 +139,7 @@ func (k *KeyRing) isAlgorithmSupported(keyID KeyID) bool { func (k *KeyRing) publicKeyRequests(requests []VerifyJSONRequest, results []VerifyJSONResult, keyIDs [][]KeyID) map[PublicKeyRequest]Timestamp { keyRequests := map[PublicKeyRequest]Timestamp{} 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. continue } @@ -165,7 +165,7 @@ func (k *KeyRing) checkUsingKeys( keys map[PublicKeyRequest]ServerKeys, ) { 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. // So we can skip to the next message. continue @@ -180,7 +180,7 @@ func (k *KeyRing) checkUsingKeys( if publicKey == nil { // The key wasn't valid at the timestamp we needed it to be valid at. // 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", 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, ); err != nil { // The signature wasn't valid, record the error and try the next key ID. - results[i].Result = err + results[i].Error = err continue } // The signature is valid, set the result to nil. - results[i].Result = nil + results[i].Error = nil break } } diff --git a/vendor/src/github.com/matrix-org/gomatrixserverlib/keyring_test.go b/vendor/src/github.com/matrix-org/gomatrixserverlib/keyring_test.go index dc1641367..7cb8775d4 100644 --- a/vendor/src/github.com/matrix-org/gomatrixserverlib/keyring_test.go +++ b/vendor/src/github.com/matrix-org/gomatrixserverlib/keyring_test.go @@ -70,8 +70,8 @@ func TestVerifyJSONsSuccess(t *testing.T) { if err != nil { t.Fatal(err) } - if len(results) != 1 || results[0].Result != nil { - t.Fatalf("VerifyJSON(): Wanted [{Result: nil}] got %#v", results) + if len(results) != 1 || results[0].Error != nil { + t.Fatalf("VerifyJSON(): Wanted [{Error: nil}] got %#v", results) } } @@ -86,8 +86,8 @@ func TestVerifyJSONsUnknownServerFails(t *testing.T) { if err != nil { t.Fatal(err) } - if len(results) != 1 || results[0].Result == nil { - t.Fatalf("VerifyJSON(): Wanted [{Result: }] got %#v", results) + if len(results) != 1 || results[0].Error == nil { + t.Fatalf("VerifyJSON(): Wanted [{Error: }] got %#v", results) } } @@ -103,8 +103,8 @@ func TestVerifyJSONsDistantFutureFails(t *testing.T) { if err != nil { t.Fatal(err) } - if len(results) != 1 || results[0].Result == nil { - t.Fatalf("VerifyJSON(): Wanted [{Result: }] got %#v", results) + if len(results) != 1 || results[0].Error == nil { + t.Fatalf("VerifyJSON(): Wanted [{Error: }] got %#v", results) } } diff --git a/vendor/src/github.com/matrix-org/gomatrixserverlib/request.go b/vendor/src/github.com/matrix-org/gomatrixserverlib/request.go index 01fec1bca..34ada3565 100644 --- a/vendor/src/github.com/matrix-org/gomatrixserverlib/request.go +++ b/vendor/src/github.com/matrix-org/gomatrixserverlib/request.go @@ -224,9 +224,9 @@ func VerifyHTTPRequest( util.GetLogger(req.Context()).WithError(err).Print(message) return nil, util.MessageResponse(500, message) } - if results[0].Result != nil { + if results[0].Error != nil { 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) }