forked from matrix-org/gomatrixserverlib
/
eventcrypto.go
238 lines (195 loc) · 6.29 KB
/
eventcrypto.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
/* Copyright 2016-2017 Vector Creations Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package gomatrixserverlib
import (
"bytes"
"crypto/sha256"
"encoding/json"
"fmt"
"golang.org/x/crypto/ed25519"
)
// addContentHashesToEvent sets the "hashes" key of the event with a SHA-256 hash of the unredacted event content.
// This hash is used to detect whether the unredacted content of the event is valid.
// Returns the event JSON with a "hashes" key added to it.
func addContentHashesToEvent(eventJSON []byte) ([]byte, error) {
var event map[string]rawJSON
if err := json.Unmarshal(eventJSON, &event); err != nil {
return nil, err
}
unsignedJSON := event["unsigned"]
delete(event, "unsigned")
delete(event, "hashes")
hashableEventJSON, err := json.Marshal(event)
if err != nil {
return nil, err
}
hashableEventJSON, err = CanonicalJSON(hashableEventJSON)
if err != nil {
return nil, err
}
sha256Hash := sha256.Sum256(hashableEventJSON)
hashes := struct {
Sha256 Base64String `json:"sha256"`
}{Base64String(sha256Hash[:])}
hashesJSON, err := json.Marshal(&hashes)
if err != nil {
return nil, err
}
if len(unsignedJSON) > 0 {
event["unsigned"] = unsignedJSON
}
event["hashes"] = rawJSON(hashesJSON)
return json.Marshal(event)
}
// checkEventContentHash checks if the unredacted content of the event matches the SHA-256 hash under the "hashes" key.
func checkEventContentHash(eventJSON []byte) error {
var event map[string]rawJSON
if err := json.Unmarshal(eventJSON, &event); err != nil {
return err
}
hashesJSON := event["hashes"]
delete(event, "signatures")
delete(event, "unsigned")
delete(event, "hashes")
var hashes struct {
Sha256 Base64String `json:"sha256"`
}
if err := json.Unmarshal(hashesJSON, &hashes); err != nil {
return err
}
hashableEventJSON, err := json.Marshal(event)
if err != nil {
return err
}
hashableEventJSON, err = CanonicalJSON(hashableEventJSON)
if err != nil {
return err
}
sha256Hash := sha256.Sum256(hashableEventJSON)
if bytes.Compare(sha256Hash[:], []byte(hashes.Sha256)) != 0 {
return fmt.Errorf("Invalid Sha256 content hash: %v != %v", sha256Hash[:], []byte(hashes.Sha256))
}
return nil
}
// ReferenceSha256HashOfEvent returns the SHA-256 hash of the redacted event content.
// This is used when referring to this event from other events.
func referenceOfEvent(eventJSON []byte) (EventReference, error) {
redactedJSON, err := redactEvent(eventJSON)
if err != nil {
return EventReference{}, err
}
var event map[string]rawJSON
if err = json.Unmarshal(redactedJSON, &event); err != nil {
return EventReference{}, err
}
delete(event, "signatures")
delete(event, "unsigned")
hashableEventJSON, err := json.Marshal(event)
if err != nil {
return EventReference{}, err
}
hashableEventJSON, err = CanonicalJSON(hashableEventJSON)
if err != nil {
return EventReference{}, err
}
sha256Hash := sha256.Sum256(hashableEventJSON)
var eventID string
if err = json.Unmarshal(event["event_id"], &eventID); err != nil {
return EventReference{}, err
}
return EventReference{eventID, sha256Hash[:]}, nil
}
// SignEvent adds a ED25519 signature to the event for the given key.
func signEvent(signingName string, keyID KeyID, privateKey ed25519.PrivateKey, eventJSON []byte) ([]byte, error) {
// Redact the event before signing so signature that will remain valid even if the event is redacted.
redactedJSON, err := redactEvent(eventJSON)
if err != nil {
return nil, err
}
// Sign the JSON, this adds a "signatures" key to the redacted event.
// TODO: Make an internal version of SignJSON that returns just the signatures so that we don't have to parse it out of the JSON.
signedJSON, err := SignJSON(signingName, keyID, privateKey, redactedJSON)
if err != nil {
return nil, err
}
var signedEvent struct {
Signatures rawJSON `json:"signatures"`
}
if err := json.Unmarshal(signedJSON, &signedEvent); err != nil {
return nil, err
}
// Unmarshal the event JSON so that we can replace the signatures key.
var event map[string]rawJSON
if err := json.Unmarshal(eventJSON, &event); err != nil {
return nil, err
}
event["signatures"] = signedEvent.Signatures
return json.Marshal(event)
}
// VerifyEventSignature checks if the event has been signed by the given ED25519 key.
func verifyEventSignature(signingName string, keyID KeyID, publicKey ed25519.PublicKey, eventJSON []byte) error {
redactedJSON, err := redactEvent(eventJSON)
if err != nil {
return err
}
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)
// MRoomMember invite events are signed by both the server sending
// the invite and the server the invite is for.
if event.Type() == MRoomMember && 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
}