package event import ( "crypto/ed25519" "encoding/base64" "encoding/json" "fmt" "github.com/dariuszjaniak/warp/server/internal/matrix/canonicaljson" "github.com/dariuszjaniak/warp/server/internal/matrix/roomversion" "github.com/dariuszjaniak/warp/server/internal/matrix/signing" ) // hashAndSign adds a content hash and a signature to a bare event. // // The order matters and is not the obvious one. The content hash is added // first, so that it covers every property of the unredacted event. The // event is then *redacted* and that copy is signed, so the signature covers // only the properties a redaction keeps — plus the hashes, which cover the // rest. The signature is then copied back onto the full event. // // The result is an event whose signature still verifies after someone // redacts it, which is what lets a server hand out a redacted event and // have the recipient believe it. func hashAndSign(raw []byte, rules roomversion.Rules, serverName string, key *signing.KeyPair) ([]byte, error) { obj, err := decodeObject(raw) if err != nil { return nil, err } sum, err := contentHash(raw) if err != nil { return nil, err } obj["hashes"], err = json.Marshal(hashes{SHA256: base64.RawStdEncoding.EncodeToString(sum[:])}) if err != nil { return nil, fmt.Errorf("%w: encode hashes: %w", ErrInvalidEvent, err) } hashed, err := canonicaljson.Marshal(obj) if err != nil { return nil, fmt.Errorf("%w: %w", ErrInvalidEvent, err) } redacted, err := redactJSON(hashed, rules) if err != nil { return nil, err } signed, err := signing.SignJSON(serverName, key, redacted) if err != nil { return nil, err } sigs, err := signing.ExtractSignatures(signed) if err != nil { return nil, err } if obj["signatures"], err = json.Marshal(sigs); err != nil { return nil, fmt.Errorf("%w: encode signatures: %w", ErrInvalidEvent, err) } return canonicaljson.Marshal(obj) } // VerifySignature checks the event for a valid signature from serverName // made with keyID. // // The event is redacted first, because that is the form that was signed. // This holds whether we were given the full event or a redacted copy, which // is why a redacted event can still be shown to be genuine. func (e *Event) VerifySignature(serverName, keyID string, pub ed25519.PublicKey) error { redacted, err := redactJSON(e.raw, e.rules) if err != nil { return err } return signing.VerifyJSON(serverName, keyID, pub, redacted) } // Sign returns the event with an additional signature from serverName. // // A normal event is signed once, by the sender's own server, when it is // built. A second signature is needed in the few places where another // server has to vouch for an event it did not send: the invited server // countersigning an invite, and a third-party invite exchanged through a // server other than the sender's. func (e *Event) Sign(serverName string, key *signing.KeyPair) (*Event, error) { redacted, err := redactJSON(e.raw, e.rules) if err != nil { return nil, err } signed, err := signing.SignJSON(serverName, key, redacted) if err != nil { return nil, err } sigs, err := signing.ExtractSignatures(signed) if err != nil { return nil, err } obj, err := decodeObject(e.raw) if err != nil { return nil, err } if obj["signatures"], err = json.Marshal(sigs); err != nil { return nil, fmt.Errorf("%w: encode signatures: %w", ErrInvalidEvent, err) } out, err := canonicaljson.Marshal(obj) if err != nil { return nil, fmt.Errorf("%w: %w", ErrInvalidEvent, err) } // Re-parse rather than patching the Event in place: an Event is // immutable, and the signature block is one of the things Parse // validates. return Parse(out, e.rules) }