Process master/self-signing keys from devices call

This commit is contained in:
Neil Alexander 2021-08-03 10:08:49 +01:00
parent a1309b3afc
commit c990e79387
No known key found for this signature in database
GPG key ID: A02A2019A2BB0944
5 changed files with 81 additions and 12 deletions

View file

@ -113,6 +113,41 @@ func (a *KeyInternalAPI) PerformUploadDeviceKeys(ctx context.Context, req *api.P
masterKey, hasMasterKey = existingKeys[gomatrixserverlib.CrossSigningKeyPurposeMaster] masterKey, hasMasterKey = existingKeys[gomatrixserverlib.CrossSigningKeyPurposeMaster]
} }
// If the user isn't a local user and we haven't successfully found a key
// through any local means then ask over federation.
if !hasMasterKey {
_, host, err := gomatrixserverlib.SplitID('@', req.UserID)
if err != nil {
res.Error = &api.KeyError{
Err: "Retrieving cross-signing keys from federation failed: " + err.Error(),
}
return
}
keys, err := a.FedClient.QueryKeys(ctx, host, map[string][]string{
req.UserID: {},
})
if err != nil {
res.Error = &api.KeyError{
Err: "Retrieving cross-signing keys from federation failed: " + err.Error(),
}
return
}
switch k := keys.MasterKeys[req.UserID].CrossSigningBody.(type) {
case *gomatrixserverlib.CrossSigningKey:
if err := sanityCheckKey(*k, req.UserID, gomatrixserverlib.CrossSigningKeyPurposeMaster); err != nil {
res.Error = &api.KeyError{
Err: "User-signing key sanity check failed: " + err.Error(),
}
return
}
default:
res.Error = &api.KeyError{
Err: "Unexpected type for master key retrieved from federation",
}
return
}
}
// If we still don't have a master key at this point then there's nothing else // If we still don't have a master key at this point then there's nothing else
// we can do - we've checked both the request and the database. // we can do - we've checked both the request and the database.
if !hasMasterKey { if !hasMasterKey {

View file

@ -82,6 +82,7 @@ type DeviceListUpdater struct {
mu *sync.Mutex // protects UserIDToMutex mu *sync.Mutex // protects UserIDToMutex
db DeviceListUpdaterDatabase db DeviceListUpdaterDatabase
api DeviceListUpdaterAPI
producer KeyChangeProducer producer KeyChangeProducer
fedClient fedsenderapi.FederationClient fedClient fedsenderapi.FederationClient
workerChans []chan gomatrixserverlib.ServerName workerChans []chan gomatrixserverlib.ServerName
@ -114,6 +115,10 @@ type DeviceListUpdaterDatabase interface {
DeviceKeysJSON(ctx context.Context, keys []api.DeviceMessage) error DeviceKeysJSON(ctx context.Context, keys []api.DeviceMessage) error
} }
type DeviceListUpdaterAPI interface {
PerformUploadDeviceKeys(ctx context.Context, req *api.PerformUploadDeviceKeysRequest, res *api.PerformUploadDeviceKeysResponse)
}
// KeyChangeProducer is the interface for producers.KeyChange useful for testing. // KeyChangeProducer is the interface for producers.KeyChange useful for testing.
type KeyChangeProducer interface { type KeyChangeProducer interface {
ProduceKeyChanges(keys []api.DeviceMessage) error ProduceKeyChanges(keys []api.DeviceMessage) error
@ -121,13 +126,14 @@ type KeyChangeProducer interface {
// NewDeviceListUpdater creates a new updater which fetches fresh device lists when they go stale. // NewDeviceListUpdater creates a new updater which fetches fresh device lists when they go stale.
func NewDeviceListUpdater( func NewDeviceListUpdater(
db DeviceListUpdaterDatabase, producer KeyChangeProducer, fedClient fedsenderapi.FederationClient, db DeviceListUpdaterDatabase, api DeviceListUpdaterAPI, producer KeyChangeProducer,
numWorkers int, fedClient fedsenderapi.FederationClient, numWorkers int,
) *DeviceListUpdater { ) *DeviceListUpdater {
return &DeviceListUpdater{ return &DeviceListUpdater{
userIDToMutex: make(map[string]*sync.Mutex), userIDToMutex: make(map[string]*sync.Mutex),
mu: &sync.Mutex{}, mu: &sync.Mutex{},
db: db, db: db,
api: api,
producer: producer, producer: producer,
fedClient: fedClient, fedClient: fedClient,
workerChans: make([]chan gomatrixserverlib.ServerName, numWorkers), workerChans: make([]chan gomatrixserverlib.ServerName, numWorkers),
@ -367,6 +373,23 @@ func (u *DeviceListUpdater) processServer(serverName gomatrixserverlib.ServerNam
} }
continue continue
} }
if res.MasterKey != nil || res.SelfSigningKey != nil {
uploadReq := &api.PerformUploadDeviceKeysRequest{
UserID: userID,
}
uploadRes := &api.PerformUploadDeviceKeysResponse{}
if res.MasterKey != nil {
if err = sanityCheckKey(*res.MasterKey, userID, gomatrixserverlib.CrossSigningKeyPurposeMaster); err == nil {
uploadReq.MasterKey = *res.MasterKey
}
}
if res.SelfSigningKey != nil {
if err = sanityCheckKey(*res.MasterKey, userID, gomatrixserverlib.CrossSigningKeyPurposeSelfSigning); err == nil {
uploadReq.SelfSigningKey = *res.SelfSigningKey
}
}
u.api.PerformUploadDeviceKeys(ctx, uploadReq, uploadRes)
}
err = u.updateDeviceList(&res) err = u.updateDeviceList(&res)
if err != nil { if err != nil {
logger.WithError(err).WithField("user_id", userID).Error("fetched device list but failed to store/emit it") logger.WithError(err).WithField("user_id", userID).Error("fetched device list but failed to store/emit it")

View file

@ -95,6 +95,13 @@ func (d *mockDeviceListUpdaterDatabase) DeviceKeysJSON(ctx context.Context, keys
return nil return nil
} }
type mockDeviceListUpdaterAPI struct {
}
func (d *mockDeviceListUpdaterAPI) PerformUploadDeviceKeys(ctx context.Context, req *api.PerformUploadDeviceKeysRequest, res *api.PerformUploadDeviceKeysResponse) {
}
type roundTripper struct { type roundTripper struct {
fn func(*http.Request) (*http.Response, error) fn func(*http.Request) (*http.Response, error)
} }
@ -122,8 +129,9 @@ func TestUpdateHavePrevID(t *testing.T) {
return true return true
}, },
} }
ap := &mockDeviceListUpdaterAPI{}
producer := &mockKeyChangeProducer{} producer := &mockKeyChangeProducer{}
updater := NewDeviceListUpdater(db, producer, nil, 1) updater := NewDeviceListUpdater(db, ap, producer, nil, 1)
event := gomatrixserverlib.DeviceListUpdateEvent{ event := gomatrixserverlib.DeviceListUpdateEvent{
DeviceDisplayName: "Foo Bar", DeviceDisplayName: "Foo Bar",
Deleted: false, Deleted: false,
@ -166,6 +174,7 @@ func TestUpdateNoPrevID(t *testing.T) {
return false return false
}, },
} }
ap := &mockDeviceListUpdaterAPI{}
producer := &mockKeyChangeProducer{} producer := &mockKeyChangeProducer{}
remoteUserID := "@alice:example.somewhere" remoteUserID := "@alice:example.somewhere"
var wg sync.WaitGroup var wg sync.WaitGroup
@ -193,7 +202,7 @@ func TestUpdateNoPrevID(t *testing.T) {
`)), `)),
}, nil }, nil
}) })
updater := NewDeviceListUpdater(db, producer, fedClient, 2) updater := NewDeviceListUpdater(db, ap, producer, fedClient, 2)
if err := updater.Start(); err != nil { if err := updater.Start(); err != nil {
t.Fatalf("failed to start updater: %s", err) t.Fatalf("failed to start updater: %s", err)
} }

View file

@ -51,20 +51,19 @@ func NewInternalAPI(
Producer: producer, Producer: producer,
DB: db, DB: db,
} }
updater := internal.NewDeviceListUpdater(db, keyChangeProducer, fedClient, 8) // 8 workers TODO: configurable
go func() {
if err := updater.Start(); err != nil {
logrus.WithError(err).Panicf("failed to start device list updater")
}
}()
ap := &internal.KeyInternalAPI{ ap := &internal.KeyInternalAPI{
DB: db, DB: db,
ThisServer: cfg.Matrix.ServerName, ThisServer: cfg.Matrix.ServerName,
FedClient: fedClient, FedClient: fedClient,
Producer: keyChangeProducer, Producer: keyChangeProducer,
Updater: updater,
} }
updater := internal.NewDeviceListUpdater(db, ap, keyChangeProducer, fedClient, 8) // 8 workers TODO: configurable
ap.Updater = updater
go func() {
if err := updater.Start(); err != nil {
logrus.WithError(err).Panicf("failed to start device list updater")
}
}()
keyconsumer := consumers.NewOutputSigningKeyUpdateConsumer( keyconsumer := consumers.NewOutputSigningKeyUpdateConsumer(
base.ProcessContext, base.Cfg, consumer, db, ap, base.ProcessContext, base.Cfg, consumer, db, ap,

View file

@ -550,3 +550,6 @@ Will not back up to an old backup version
Can create more than 10 backup versions Can create more than 10 backup versions
Can delete backup Can delete backup
Deleted & recreated backups are empty Deleted & recreated backups are empty
Can upload self-signing keys
Fails to upload self-signing keys with no auth
Fails to upload self-signing key without master key