federation/pdu: add more tests for signature checks
Some checks failed
Go / Lint (latest) (push) Has been cancelled
Go / Build (old, libolm) (push) Has been cancelled
Go / Build (latest, libolm) (push) Has been cancelled
Go / Build (old, goolm) (push) Has been cancelled
Go / Build (latest, goolm) (push) Has been cancelled

This commit is contained in:
Tulir Asokan 2025-08-18 00:53:23 +03:00
commit 05b711d181
3 changed files with 177 additions and 59 deletions

View file

@ -0,0 +1,49 @@
// Copyright (c) 2025 Tulir Asokan
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
//go:build goexperiment.jsonv2
package pdu_test
import (
"encoding/base64"
"testing"
"github.com/stretchr/testify/assert"
"go.mau.fi/util/exerrors"
)
func TestPDU_CalculateContentHash(t *testing.T) {
for _, test := range testPDUs {
t.Run(test.name, func(t *testing.T) {
parsed := parsePDU(test.pdu)
contentHash := exerrors.Must(parsed.CalculateContentHash())
assert.Equal(
t,
base64.RawStdEncoding.EncodeToString(parsed.Hashes.SHA256),
base64.RawStdEncoding.EncodeToString(contentHash[:]),
)
})
}
}
func TestPDU_VerifyContentHash(t *testing.T) {
for _, test := range testPDUs {
t.Run(test.name, func(t *testing.T) {
parsed := parsePDU(test.pdu)
assert.True(t, parsed.VerifyContentHash())
})
}
}
func TestPDU_GetEventID(t *testing.T) {
for _, test := range testPDUs {
t.Run(test.name, func(t *testing.T) {
gotEventID := exerrors.Must(parsePDU(test.pdu).GetEventID(test.roomVersion))
assert.Equal(t, test.eventID, gotEventID)
})
}
}

View file

@ -9,12 +9,9 @@
package pdu_test
import (
"encoding/base64"
"encoding/json/v2"
"testing"
"time"
"github.com/stretchr/testify/assert"
"go.mau.fi/util/exerrors"
"maunium.net/go/mautrix/federation/pdu"
@ -31,6 +28,14 @@ type serverDetails struct {
keys map[id.KeyID]serverKey
}
func (sd serverDetails) getKey(keyID id.KeyID, _ time.Time) (id.SigningKey, time.Time, error) {
key, ok := sd.keys[keyID]
if ok {
return key.key, key.validUntilTS, nil
}
return "", time.Time{}, nil
}
var mauniumNet = serverDetails{
serverName: "maunium.net",
keys: map[id.KeyID]serverKey{
@ -75,7 +80,23 @@ type testPDU struct {
serverDetails
}
var testPDUs = []testPDU{{
var roomV4MessageTestPDU = testPDU{
name: "m.room.message in v4 room",
pdu: `{"auth_events":["$OB87jNemaIVDHAfu0-pa_cP7OPFXUXCbFpjYVi8gll4","$RaWbTF9wQfGQgUpe1S13wzICtGTB2PNKRHUNHu9IO1c","$ZmEWOXw6cC4Rd1wTdY5OzeLJVzjhrkxFPwwKE4gguGk"],"content":{"body":"the last one is saying it shouldn't have effects","com.beeper.linkpreviews":[],"m.mentions":{},"msgtype":"m.text"},"depth":13103,"hashes":{"sha256":"c2wb8qMlvzIPCP1Wd+eYZ4BRgnGYxS97dR1UlJjVMeg"},"origin_server_ts":1752875275263,"prev_events":["$-7_BMI3BXwj3ayoxiJvraJxYWTKwjiQ6sh7CW_Brvj0"],"room_id":"!JiiOHXrIUCtcOJsZCa:matrix.org","sender":"@tulir:maunium.net","type":"m.room.message","signatures":{"maunium.net":{"ed25519:a_xxeS":"99TAqHpBkUEtgCraXsVXogmf/hnijPbgbG9eACtA+mbix3Y6gURI4QGQgcX/NhcE3pJQZ/YDjmbuvCnKvEccAA"}},"unsigned":{"age_ts":1752875275281}}`,
eventID: "$Jo_lmFR-e6lzrimzCA7DevIn2OwhuQYmd9xkcJBoqAA",
roomVersion: id.RoomV4,
serverDetails: mauniumNet,
}
var roomV12MessageTestPDU = testPDU{
name: "m.room.message in v12 room",
pdu: `{"auth_events":["$gCzdJUVV93Qory0x7p_PLG5UUiDjPJNe1H12qbHTuFA","$hyeL_nU_L3tsZ2dtZZpAHk0Skv-PqFQIipuII_By584"],"content":{"body":"meow","com.beeper.linkpreviews":[],"m.mentions":{},"msgtype":"m.text"},"depth":122,"hashes":{"sha256":"IQ0zlc+PXeEs6R3JvRkW3xTPV3zlGKSSd3x07KXGjzs"},"origin_server_ts":1755384351627,"prev_events":["$gCzdJUVV93Qory0x7p_PLG5UUiDjPJNe1H12qbHTuFA"],"room_id":"!mauT12AzsoqxV7Abvy_ApA-HNPK1LcT4GbP70_AOPyQ","sender":"@tulir_test:maunium.net","type":"m.room.message","signatures":{"maunium.net":{"ed25519:a_xxeS":"0GDMddL2k7gF4V1VU8sL3wTfhAIzAu5iVH5jeavZ2VEg3J9/tHLWXAOn2tzkLaMRWl0/XpINT2YlH/rd2U21Ag"}},"unsigned":{"age_ts":1755384351627}}`,
eventID: "$xmP-wZfpannuHG-Akogi6c4YvqxChMtdyYbUMGOrMWc",
roomVersion: id.RoomV12,
serverDetails: mauniumNet,
}
var testPDUs = []testPDU{roomV4MessageTestPDU, {
name: "m.room.message in v5 room",
pdu: `{"auth_events":["$hp0ImHqYgHTRbLeWKPeTeFmxdb5SdMJN9cfmTrTk7d0","$KAj7X7tnJbR9qYYMWJSw-1g414_KlPptbbkZm7_kUtg","$V-2ShOwZYhA_nxMijaf3lqFgIJgzE2UMeFPtOLnoBYM"],"content":{"body":"meow","com.beeper.linkpreviews":[],"m.mentions":{},"msgtype":"m.text"},"depth":2248,"hashes":{"sha256":"kV+JuLbWXJ2r6PjHT3wt8bFc/TfI1nTaSN3Lamg/xHs"},"origin_server_ts":1755422945654,"prev_events":["$49lFLem2Nk4dxHk9RDXxTdaq9InIJpmkHpzVnjKcYwg"],"room_id":"!vzBgJsjNzgHSdWsmki:mozilla.org","sender":"@tulir:maunium.net","type":"m.room.message","signatures":{"maunium.net":{"ed25519:a_xxeS":"JIl60uVgfCLBZLPoSiE7wVkJ9U5cNEPVPuv1sCCYUOq5yOW56WD1adgpBUdX2UFpYkCHvkRnyQGxU0+6HBp5BA"}},"unsigned":{"age_ts":1755422945673}}`,
eventID: "$Qn4tHfuAe6PlnKXPZnygAU9wd6RXqMKtt_ZzstHTSgA",
@ -93,13 +114,7 @@ var testPDUs = []testPDU{{
eventID: `$qkWfTL7_l3oRZO2CItW8-Q0yAmi_l_1ua629ZDqponE`,
roomVersion: id.RoomV11,
serverDetails: mauniumNet,
}, {
name: "m.room.message in v12 room",
pdu: `{"auth_events":["$gCzdJUVV93Qory0x7p_PLG5UUiDjPJNe1H12qbHTuFA","$hyeL_nU_L3tsZ2dtZZpAHk0Skv-PqFQIipuII_By584"],"content":{"body":"meow","com.beeper.linkpreviews":[],"m.mentions":{},"msgtype":"m.text"},"depth":122,"hashes":{"sha256":"IQ0zlc+PXeEs6R3JvRkW3xTPV3zlGKSSd3x07KXGjzs"},"origin_server_ts":1755384351627,"prev_events":["$gCzdJUVV93Qory0x7p_PLG5UUiDjPJNe1H12qbHTuFA"],"room_id":"!mauT12AzsoqxV7Abvy_ApA-HNPK1LcT4GbP70_AOPyQ","sender":"@tulir_test:maunium.net","type":"m.room.message","signatures":{"maunium.net":{"ed25519:a_xxeS":"0GDMddL2k7gF4V1VU8sL3wTfhAIzAu5iVH5jeavZ2VEg3J9/tHLWXAOn2tzkLaMRWl0/XpINT2YlH/rd2U21Ag"}},"unsigned":{"age_ts":1755384351627}}`,
eventID: "$xmP-wZfpannuHG-Akogi6c4YvqxChMtdyYbUMGOrMWc",
roomVersion: id.RoomV12,
serverDetails: mauniumNet,
}, {
}, roomV12MessageTestPDU, {
name: "m.room.create in v4 room",
pdu: `{"auth_events": [], "prev_events": [], "type": "m.room.create", "room_id": "!jxlRxnrZCsjpjDubDX:matrix.org", "sender": "@neilj:matrix.org", "content": {"room_version": "4", "predecessor": {"room_id": "!DYgXKezaHgMbiPMzjX:matrix.org", "event_id": "$156171636353XwPJT:matrix.org"}, "creator": "@neilj:matrix.org"}, "depth": 1, "prev_state": [], "state_key": "", "origin": "matrix.org", "origin_server_ts": 1561716363993, "hashes": {"sha256": "9tj8GpXjTAJvdNAbnuKLemZZk+Tjv2LAbGodSX6nJAo"}, "signatures": {"matrix.org": {"ed25519:auto": "2+sNt8uJUhzU4GPxnFVYtU2ZRgFdtVLT1vEZGUdJYN40zBpwYEGJy+kyb5matA+8/yLeYD9gu1O98lhleH0aCA"}}, "unsigned": {"age": 104769}}`,
eventID: "$ay_9_nPilrTpb3UxIwHHBBfFjTJb6hBAE_JzQwSjqeY",
@ -147,51 +162,3 @@ func parsePDU(pdu string) (out *pdu.PDU) {
exerrors.PanicIfNotNil(json.Unmarshal([]byte(pdu), &out))
return
}
func TestPDU_CalculateContentHash(t *testing.T) {
for _, test := range testPDUs {
t.Run(test.name, func(t *testing.T) {
parsed := parsePDU(test.pdu)
contentHash := exerrors.Must(parsed.CalculateContentHash())
assert.Equal(
t,
base64.RawStdEncoding.EncodeToString(parsed.Hashes.SHA256),
base64.RawStdEncoding.EncodeToString(contentHash[:]),
)
})
}
}
func TestPDU_VerifyContentHash(t *testing.T) {
for _, test := range testPDUs {
t.Run(test.name, func(t *testing.T) {
parsed := parsePDU(test.pdu)
assert.True(t, parsed.VerifyContentHash())
})
}
}
func TestPDU_GetEventID(t *testing.T) {
for _, test := range testPDUs {
t.Run(test.name, func(t *testing.T) {
gotEventID := exerrors.Must(parsePDU(test.pdu).GetEventID(test.roomVersion))
assert.Equal(t, test.eventID, gotEventID)
})
}
}
func TestPDU_VerifySignature(t *testing.T) {
for _, test := range testPDUs {
t.Run(test.name, func(t *testing.T) {
parsed := parsePDU(test.pdu)
err := parsed.VerifySignature(test.roomVersion, test.serverName, func(keyID id.KeyID, _ time.Time) (id.SigningKey, time.Time, error) {
key, ok := test.keys[keyID]
if ok {
return key.key, key.validUntilTS, nil
}
return "", time.Time{}, nil
})
assert.NoError(t, err)
})
}
}

View file

@ -0,0 +1,102 @@
// Copyright (c) 2025 Tulir Asokan
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
//go:build goexperiment.jsonv2
package pdu_test
import (
"crypto/ed25519"
"encoding/base64"
"encoding/json/jsontext"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.mau.fi/util/exerrors"
"maunium.net/go/mautrix/federation/pdu"
"maunium.net/go/mautrix/id"
)
func TestPDU_VerifySignature(t *testing.T) {
for _, test := range testPDUs {
t.Run(test.name, func(t *testing.T) {
parsed := parsePDU(test.pdu)
err := parsed.VerifySignature(test.roomVersion, test.serverName, test.getKey)
assert.NoError(t, err)
})
}
}
func TestPDU_VerifySignature_Fail_NoKey(t *testing.T) {
test := roomV12MessageTestPDU
parsed := parsePDU(test.pdu)
err := parsed.VerifySignature(test.roomVersion, test.serverName, func(keyID id.KeyID, minValidUntil time.Time) (key id.SigningKey, validUntil time.Time, err error) {
return
})
assert.Error(t, err)
}
func TestPDU_VerifySignature_V4ExpiredKey(t *testing.T) {
test := roomV4MessageTestPDU
parsed := parsePDU(test.pdu)
err := parsed.VerifySignature(test.roomVersion, test.serverName, func(keyID id.KeyID, minValidUntil time.Time) (key id.SigningKey, validUntil time.Time, err error) {
key = test.keys[keyID].key
validUntil = time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC)
return
})
assert.NoError(t, err)
}
func TestPDU_VerifySignature_V12ExpiredKey(t *testing.T) {
test := roomV12MessageTestPDU
parsed := parsePDU(test.pdu)
err := parsed.VerifySignature(test.roomVersion, test.serverName, func(keyID id.KeyID, minValidUntil time.Time) (key id.SigningKey, validUntil time.Time, err error) {
key = test.keys[keyID].key
validUntil = time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC)
return
})
assert.Error(t, err)
}
func TestPDU_VerifySignature_V12InvalidSignature(t *testing.T) {
test := roomV12MessageTestPDU
parsed := parsePDU(test.pdu)
for _, sigs := range parsed.Signatures {
for key := range sigs {
sigs[key] = sigs[key][:len(sigs[key])-3] + "ABC"
}
}
err := parsed.VerifySignature(test.roomVersion, test.serverName, test.getKey)
assert.Error(t, err)
}
func TestPDU_Sign(t *testing.T) {
pubKey, privKey := exerrors.Must2(ed25519.GenerateKey(nil))
evt := &pdu.PDU{
AuthEvents: []id.EventID{"$gCzdJUVV93Qory0x7p_PLG5UUiDjPJNe1H12qbHTuFA", "$hyeL_nU_L3tsZ2dtZZpAHk0Skv-PqFQIipuII_By584"},
Content: jsontext.Value(`{"msgtype":"m.text","body":"Hello, world!"}`),
Depth: 123,
OriginServerTS: 1755384351627,
PrevEvents: []id.EventID{"$gCzdJUVV93Qory0x7p_PLG5UUiDjPJNe1H12qbHTuFA"},
RoomID: "!mauT12AzsoqxV7Abvy_ApA-HNPK1LcT4GbP70_AOPyQ",
Sender: "@tulir:example.com",
Type: "m.room.message",
}
err := evt.Sign(id.RoomV12, "example.com", "ed25519:rand", privKey)
require.NoError(t, err)
err = evt.VerifySignature(id.RoomV11, "example.com", func(keyID id.KeyID, minValidUntil time.Time) (key id.SigningKey, validUntil time.Time, err error) {
if keyID == "ed25519:rand" {
key = id.SigningKey(base64.RawStdEncoding.EncodeToString(pubKey))
validUntil = time.Now()
}
return
})
require.NoError(t, err)
}