complement/tests/federation_keys_test.go
kegsay a669c750c8
Remove Deployment.Client and change Deploy (#676)
* Remove Deployment.Client and change Deploy

- `Deployment.Client` was used to get pre-registered clients. Now we want tests to register new users for each test, for dirty runs. So swap for `Deployment.Register` everywhere.
- `Deploy` was used to deploy a blueprint. We don't want this to enable dirty runs. So replace it with the number of servers you need e.g `Deploy(t, 2)`.

* Fix up more broken refactoring

* unbreak tests; make user localpart look nicer

* Alice and bob must share a room for presence

* Fix user directory test

* Fix race condition caused by making the room later than before
2023-10-17 18:07:43 +01:00

235 lines
6.8 KiB
Go

package tests
import (
"crypto/ed25519"
"encoding/base64"
"fmt"
"net/http"
"strings"
"testing"
"time"
"github.com/tidwall/gjson"
"github.com/tidwall/sjson"
"github.com/matrix-org/complement"
"github.com/matrix-org/complement/match"
"github.com/matrix-org/complement/must"
)
// TODO:
// Federation key API can act as a notary server via a $method request
// Key notary server should return an expired key if it can't find any others
// Key notary server must not overwrite a valid key with a spurious result from the origin server
// Test that a server can receive /keys requests:
// https://matrix.org/docs/spec/server_server/latest#get-matrix-key-v2-server-keyid
// sytest: Federation key API allows unsigned requests for keys
func TestInboundFederationKeys(t *testing.T) {
deployment := complement.Deploy(t, 1)
defer deployment.Destroy(t)
fedClient := &http.Client{
Timeout: 10 * time.Second,
Transport: deployment.RoundTripper(),
}
res, err := fedClient.Get("https://hs1/_matrix/key/v2/server")
must.NotError(t, "failed to GET /keys", err)
var keys = map[string]ed25519.PublicKey{}
var oldKeys = map[string]ed25519.PublicKey{}
body := must.MatchResponse(t, res, match.HTTPResponse{
StatusCode: 200,
JSON: []match.JSON{
match.JSONKeyTypeEqual("valid_until_ts", gjson.Number),
match.JSONKeyEqual("server_name", "hs1"),
// Check validity of verify_keys+old_verify_keys and store values
match.JSONMapEach("verify_keys", func(k, v gjson.Result) error {
// Currently we always expect ed25519 keys
if !strings.HasPrefix(k.Str, "ed25519:") {
return fmt.Errorf("complement: Key '%s' has no 'ed25519:' prefix", k.Str)
}
key := v.Get("key")
must.MatchGJSON(t, v,
match.JSONKeyPresent("key"),
match.JSONKeyTypeEqual("key", gjson.String),
)
var keyBytes []byte
keyBytes, err = base64.RawStdEncoding.DecodeString(key.Str)
if err != nil {
return fmt.Errorf("verify_keys: Key '%s' failed to decode as ed25519 base64: %s", k.Str, err)
}
keys[k.Str] = keyBytes
return nil
}),
match.JSONMapEach("old_verify_keys", func(k, v gjson.Result) error {
// Check for ed25519 prefix
if !strings.HasPrefix(k.Str, "ed25519:") {
return fmt.Errorf("old_verify_keys: Key '%s' has no 'ed25519:' prefix", k.Str)
}
must.MatchGJSON(t, v,
match.JSONKeyPresent("expired_ts"),
match.JSONKeyTypeEqual("expired_ts", gjson.Number),
)
must.MatchGJSON(t, v,
match.JSONKeyPresent("key"),
match.JSONKeyTypeEqual("key", gjson.String),
)
key := v.Get("key")
var keyBytes []byte
keyBytes, err = base64.RawStdEncoding.DecodeString(key.Str)
if err != nil {
return fmt.Errorf("old_verify_keys: Key '%s' failed to decode as ed25519 base64: %s", k.Str, err)
}
oldKeys[k.Str] = keyBytes
return nil
}),
// Check signatures
match.JSONMapEach("signatures", func(k, v gjson.Result) error {
if !v.IsObject() {
return fmt.Errorf("signatures: Value for server '%s' is not an object", k.Str)
}
for key, v := range v.Map() {
// Check for ed25519 prefix
if !strings.HasPrefix(key, "ed25519:") {
return fmt.Errorf("signatures: Key '%s' for server '%s' has no 'ed25519:' prefix", key, k.Str)
}
if v.Type != gjson.String {
return fmt.Errorf("signatures: Key '%s' for server '%s' has unexpected type, expected String, got '%s'", key, k.Str, v.Type.String())
}
_, err = base64.RawStdEncoding.DecodeString(v.Str)
if err != nil {
return fmt.Errorf("old_verify_keys: Key '%s' failed to decode as ed25519 base64: %s", k.Str, err)
}
}
return nil
}),
},
})
jsonObj := gjson.ParseBytes(body)
gotTime := time.Unix(0, jsonObj.Get("valid_until_ts").Int()*1000*1000) // ms -> ns
wantTime := time.Now()
if gotTime.Before(wantTime) {
t.Errorf("valid_until_ts: timestamp is in the past: %s < %s", gotTime, wantTime)
}
checkKeysAndSignatures(t, body, jsonObj, keys, oldKeys)
}
func checkKeysAndSignatures(t *testing.T, body []byte, jsonObj gjson.Result, keys, oldKeys map[string]ed25519.PublicKey) {
var err error
if len(keys) == 0 {
t.Fatalf("verify_keys: missing any ed25519: key")
}
if len(oldKeys) > 0 {
// Check if any old key exists in the new keys
for k := range oldKeys {
if _, ok := keys[k]; ok {
t.Fatalf("old_verify_keys: Key '%s' exists in both old_verify_keys and verify_keys", k)
}
}
}
sigObj := jsonObj.Get("signatures").Map()
// Test signatures object sanity
if _, ok := sigObj["hs1"]; !ok {
t.Fatalf("signatures: Did not contain server key for 'hs1'")
}
if len(sigObj) > 1 {
var extraKeys []string
for k := range sigObj {
if k == "hs1" {
continue
}
extraKeys = append(extraKeys, k)
}
t.Fatalf("signatures: Contained more than one server keys, extra keys: %s", strings.Join(extraKeys, ", "))
}
sigServerObj := sigObj["hs1"].Map()
type sigWithKey struct {
signature []byte
key ed25519.PublicKey
old bool
}
var signatures = map[string]sigWithKey{}
// Test signatures for all verify_keys, these *have* to exist.
for keyName, keyBytes := range keys {
sigBase64 := must.GetJSONFieldStr(t, jsonObj, fmt.Sprintf("signatures.hs1.%s", keyName))
var sigBytes []byte
sigBytes, err = base64.RawStdEncoding.DecodeString(sigBase64)
if err != nil {
t.Fatalf("signatures: Signature for key '%s' failed to decode as ed25519 base64: %s", keyName, err)
}
signatures[keyName] = sigWithKey{key: keyBytes, signature: sigBytes, old: false}
}
// Check if there's any leftover signatures, add them if they exist in the expired keys
for keyName, sig := range sigServerObj {
if _, ok := signatures[keyName]; ok {
continue
}
// Found a signature that was leftover, this *should* be an expired key, if not, abort.
keyBytes, ok := oldKeys[keyName]
if !ok {
t.Fatalf("signatures: Unknown signature for key '%s'", keyName)
}
if sig.Type != gjson.String {
t.Fatalf("signatures: Signature for Old Key '%s' has unexpected type, expected String, got '%s'", keyName, sig.Type.String())
}
var sigBytes []byte
sigBytes, err = base64.RawStdEncoding.DecodeString(sig.Str)
if err != nil {
t.Fatalf("signatures: Signature for Old key '%s' failed to decode as ed25519 base64: %s", keyName, err)
}
signatures[keyName] = sigWithKey{key: keyBytes, signature: sigBytes, old: true}
}
var bodyWithoutSig []byte
bodyWithoutSig, err = sjson.DeleteBytes(body, "signatures")
if err != nil {
t.Fatalf("failed to delete 'signatures' key: %s", err)
}
for keyName, val := range signatures {
if !ed25519.Verify(val.key, bodyWithoutSig, val.signature) {
var oldClause = ""
if val.old {
oldClause = "Old "
}
t.Fatalf("Message signature failed to verify for %sKey '%s': %s", oldClause, keyName, string(bodyWithoutSig))
}
}
}