Cross-signing fixes, notifications via sync, federation (#1974)

* Initial work on signing key update EDUs

* Fix build

* Produce/consume EDUs

* Producer logging

* Only produce key change notifications for local users

* Better naming

* Try to notify sync

* Enable feature

* Use key change topic

* Don't bother verifying signatures, validate key lengths if we can, notifier fixes

* Copyright notices

* Remove tests from whitelist until matrix-org/sytest#1117

* Some review comment fixes

* Update to matrix-org/gomatrixserverlib@f9416ac

* Remove unneeded parameter
This commit is contained in:
Neil Alexander 2021-08-17 13:44:30 +01:00 committed by GitHub
parent 8a4b90b7dd
commit ff21675c5b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
24 changed files with 556 additions and 254 deletions

View file

@ -20,6 +20,7 @@ import (
"strings"
"time"
eduapi "github.com/matrix-org/dendrite/eduserver/api"
"github.com/matrix-org/dendrite/keyserver/types"
userapi "github.com/matrix-org/dendrite/userapi/api"
"github.com/matrix-org/gomatrixserverlib"
@ -47,6 +48,7 @@ type KeyError struct {
Err string `json:"error"`
IsInvalidSignature bool `json:"is_invalid_signature,omitempty"` // M_INVALID_SIGNATURE
IsMissingParam bool `json:"is_missing_param,omitempty"` // M_MISSING_PARAM
IsInvalidParam bool `json:"is_invalid_param,omitempty"` // M_INVALID_PARAM
}
func (k *KeyError) Error() string {
@ -62,8 +64,9 @@ const (
// DeviceMessage represents the message produced into Kafka by the key server.
type DeviceMessage struct {
Type DeviceMessageType `json:"Type,omitempty"`
*DeviceKeys `json:"DeviceKeys,omitempty"`
Type DeviceMessageType `json:"Type,omitempty"`
*DeviceKeys `json:"DeviceKeys,omitempty"`
*eduapi.OutputCrossSigningKeyUpdate `json:"CrossSigningKeyUpdate,omitempty"`
// A monotonically increasing number which represents device changes for this user.
StreamID int
}

View file

@ -0,0 +1,112 @@
// Copyright 2021 The Matrix.org Foundation C.I.C.
//
// 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 consumers
import (
"context"
"encoding/json"
"github.com/matrix-org/dendrite/internal"
"github.com/matrix-org/dendrite/keyserver/api"
"github.com/matrix-org/dendrite/keyserver/storage"
"github.com/matrix-org/dendrite/setup/config"
"github.com/matrix-org/dendrite/setup/process"
"github.com/matrix-org/gomatrixserverlib"
"github.com/sirupsen/logrus"
"github.com/Shopify/sarama"
)
type OutputCrossSigningKeyUpdateConsumer struct {
eduServerConsumer *internal.ContinualConsumer
keyDB storage.Database
keyAPI api.KeyInternalAPI
serverName string
}
func NewOutputCrossSigningKeyUpdateConsumer(
process *process.ProcessContext,
cfg *config.Dendrite,
kafkaConsumer sarama.Consumer,
keyDB storage.Database,
keyAPI api.KeyInternalAPI,
) *OutputCrossSigningKeyUpdateConsumer {
// The keyserver both produces and consumes on the TopicOutputKeyChangeEvent
// topic. We will only produce events where the UserID matches our server name,
// and we will only consume events where the UserID does NOT match our server
// name (because the update came from a remote server).
consumer := internal.ContinualConsumer{
Process: process,
ComponentName: "keyserver/keyserver",
Topic: cfg.Global.Kafka.TopicFor(config.TopicOutputKeyChangeEvent),
Consumer: kafkaConsumer,
PartitionStore: keyDB,
}
s := &OutputCrossSigningKeyUpdateConsumer{
eduServerConsumer: &consumer,
keyDB: keyDB,
keyAPI: keyAPI,
serverName: string(cfg.Global.ServerName),
}
consumer.ProcessMessage = s.onMessage
return s
}
func (s *OutputCrossSigningKeyUpdateConsumer) Start() error {
return s.eduServerConsumer.Start()
}
// onMessage is called in response to a message received on the
// key change events topic from the key server.
func (t *OutputCrossSigningKeyUpdateConsumer) onMessage(msg *sarama.ConsumerMessage) error {
var m api.DeviceMessage
if err := json.Unmarshal(msg.Value, &m); err != nil {
logrus.WithError(err).Errorf("failed to read device message from key change topic")
return nil
}
switch m.Type {
case api.TypeCrossSigningUpdate:
return t.onCrossSigningMessage(m)
default:
return nil
}
}
func (s *OutputCrossSigningKeyUpdateConsumer) onCrossSigningMessage(m api.DeviceMessage) error {
output := m.CrossSigningKeyUpdate
_, host, err := gomatrixserverlib.SplitID('@', output.UserID)
if err != nil {
logrus.WithError(err).Errorf("eduserver output log: user ID parse failure")
return nil
}
if host == gomatrixserverlib.ServerName(s.serverName) {
// Ignore any messages that contain information about our own users, as
// they already originated from this server.
return nil
}
uploadReq := &api.PerformUploadDeviceKeysRequest{
UserID: output.UserID,
}
if output.MasterKey != nil {
uploadReq.MasterKey = *output.MasterKey
}
if output.SelfSigningKey != nil {
uploadReq.SelfSigningKey = *output.SelfSigningKey
}
uploadRes := &api.PerformUploadDeviceKeysResponse{}
s.keyAPI.PerformUploadDeviceKeys(context.TODO(), uploadReq, uploadRes)
return uploadRes.Error
}

View file

@ -1,61 +0,0 @@
package consumers
import (
"fmt"
"github.com/matrix-org/dendrite/internal"
"github.com/matrix-org/dendrite/keyserver/api"
"github.com/matrix-org/dendrite/keyserver/storage"
"github.com/matrix-org/dendrite/setup/config"
"github.com/matrix-org/dendrite/setup/process"
"github.com/Shopify/sarama"
)
type OutputSigningKeyUpdateConsumer struct {
eduServerConsumer *internal.ContinualConsumer
keyDB storage.Database
keyAPI api.KeyInternalAPI
serverName string
}
func NewOutputSigningKeyUpdateConsumer(
process *process.ProcessContext,
cfg *config.Dendrite,
kafkaConsumer sarama.Consumer,
keyDB storage.Database,
keyAPI api.KeyInternalAPI,
) *OutputSigningKeyUpdateConsumer {
consumer := internal.ContinualConsumer{
Process: process,
ComponentName: "keyserver/eduserver",
Topic: cfg.Global.Kafka.TopicFor(config.TopicOutputSigningKeyUpdate),
Consumer: kafkaConsumer,
PartitionStore: keyDB,
}
s := &OutputSigningKeyUpdateConsumer{
eduServerConsumer: &consumer,
keyDB: keyDB,
keyAPI: keyAPI,
serverName: string(cfg.Global.ServerName),
}
consumer.ProcessMessage = s.onMessage
return s
}
func (s *OutputSigningKeyUpdateConsumer) Start() error {
return s.eduServerConsumer.Start()
}
func (s *OutputSigningKeyUpdateConsumer) onMessage(msg *sarama.ConsumerMessage) error {
/*
var output eduapi.OutputSigningKeyUpdate
if err := json.Unmarshal(msg.Value, &output); err != nil {
log.WithError(err).Errorf("eduserver output log: message parse failure")
return nil
}
return nil
*/
return fmt.Errorf("TODO")
}

View file

@ -19,14 +19,15 @@ import (
"context"
"crypto/ed25519"
"database/sql"
"encoding/json"
"fmt"
"strings"
eduserverAPI "github.com/matrix-org/dendrite/eduserver/api"
"github.com/matrix-org/dendrite/keyserver/api"
"github.com/matrix-org/dendrite/keyserver/types"
"github.com/matrix-org/gomatrixserverlib"
"github.com/sirupsen/logrus"
"golang.org/x/crypto/curve25519"
)
func sanityCheckKey(key gomatrixserverlib.CrossSigningKey, userID string, purpose gomatrixserverlib.CrossSigningKeyPurpose) error {
@ -45,6 +46,41 @@ func sanityCheckKey(key gomatrixserverlib.CrossSigningKey, userID string, purpos
if tokens[1] != b64 {
return fmt.Errorf("key ID isn't correct")
}
switch tokens[0] {
case "ed25519":
if len(keyData) != ed25519.PublicKeySize {
return fmt.Errorf("ed25519 key is not the correct length")
}
case "curve25519":
if len(keyData) != curve25519.PointSize {
return fmt.Errorf("curve25519 key is not the correct length")
}
default:
// We can't enforce the key length to be correct for an
// algorithm that we don't recognise, so instead we'll
// just make sure that it isn't incredibly excessive.
if l := len(keyData); l > 4096 {
return fmt.Errorf("unknown key type is too long (%d bytes)", l)
}
}
}
// Check to see if the signatures make sense
for _, forOriginUser := range key.Signatures {
for originKeyID, originSignature := range forOriginUser {
switch strings.SplitN(string(originKeyID), ":", 1)[0] {
case "ed25519":
if len(originSignature) != ed25519.SignatureSize {
return fmt.Errorf("ed25519 signature is not the correct length")
}
case "curve25519":
return fmt.Errorf("curve25519 signatures are impossible")
default:
if l := len(originSignature); l > 4096 {
return fmt.Errorf("unknown signature type is too long (%d bytes)", l)
}
}
}
}
// Does the key claim to be from the right user?
@ -69,42 +105,68 @@ func sanityCheckKey(key gomatrixserverlib.CrossSigningKey, userID string, purpos
// nolint:gocyclo
func (a *KeyInternalAPI) PerformUploadDeviceKeys(ctx context.Context, req *api.PerformUploadDeviceKeysRequest, res *api.PerformUploadDeviceKeysResponse) {
var masterKey gomatrixserverlib.Base64Bytes
// Find the keys to store.
byPurpose := map[gomatrixserverlib.CrossSigningKeyPurpose]gomatrixserverlib.CrossSigningKey{}
toStore := types.CrossSigningKeyMap{}
hasMasterKey := false
if len(req.MasterKey.Keys) > 0 {
if err := sanityCheckKey(req.MasterKey, req.UserID, gomatrixserverlib.CrossSigningKeyPurposeMaster); err != nil {
res.Error = &api.KeyError{
Err: "Master key sanity check failed: " + err.Error(),
Err: "Master key sanity check failed: " + err.Error(),
IsInvalidParam: true,
}
return
}
for _, keyData := range req.MasterKey.Keys { // iterates once, because sanityCheckKey requires one key
hasMasterKey = true
masterKey = keyData
byPurpose[gomatrixserverlib.CrossSigningKeyPurposeMaster] = req.MasterKey
for _, key := range req.MasterKey.Keys { // iterates once, see sanityCheckKey
toStore[gomatrixserverlib.CrossSigningKeyPurposeMaster] = key
}
hasMasterKey = true
}
if len(req.SelfSigningKey.Keys) > 0 {
if err := sanityCheckKey(req.SelfSigningKey, req.UserID, gomatrixserverlib.CrossSigningKeyPurposeSelfSigning); err != nil {
res.Error = &api.KeyError{
Err: "Self-signing key sanity check failed: " + err.Error(),
Err: "Self-signing key sanity check failed: " + err.Error(),
IsInvalidParam: true,
}
return
}
byPurpose[gomatrixserverlib.CrossSigningKeyPurposeSelfSigning] = req.SelfSigningKey
for _, key := range req.SelfSigningKey.Keys { // iterates once, see sanityCheckKey
toStore[gomatrixserverlib.CrossSigningKeyPurposeSelfSigning] = key
}
}
if len(req.UserSigningKey.Keys) > 0 {
if err := sanityCheckKey(req.UserSigningKey, req.UserID, gomatrixserverlib.CrossSigningKeyPurposeUserSigning); err != nil {
res.Error = &api.KeyError{
Err: "User-signing key sanity check failed: " + err.Error(),
Err: "User-signing key sanity check failed: " + err.Error(),
IsInvalidParam: true,
}
return
}
byPurpose[gomatrixserverlib.CrossSigningKeyPurposeUserSigning] = req.UserSigningKey
for _, key := range req.UserSigningKey.Keys { // iterates once, see sanityCheckKey
toStore[gomatrixserverlib.CrossSigningKeyPurposeUserSigning] = key
}
}
// If the user hasn't given a new master key, then let's go and get their
// existing keys from the database.
// If there's nothing to do then stop here.
if len(toStore) == 0 {
res.Error = &api.KeyError{
Err: "No keys were supplied in the request",
IsMissingParam: true,
}
return
}
// We can't have a self-signing or user-signing key without a master
// key, so make sure we have one of those.
if !hasMasterKey {
existingKeys, err := a.DB.CrossSigningKeysDataForUser(ctx, req.UserID)
if err != nil {
@ -114,87 +176,20 @@ func (a *KeyInternalAPI) PerformUploadDeviceKeys(ctx context.Context, req *api.P
return
}
masterKey, hasMasterKey = existingKeys[gomatrixserverlib.CrossSigningKeyPurposeMaster]
_, hasMasterKey = existingKeys[gomatrixserverlib.CrossSigningKeyPurposeMaster]
}
// 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.
// If we still can't find a master key for the user then stop the upload.
// This satisfies the "Fails to upload self-signing key without master key" test.
if !hasMasterKey {
res.Error = &api.KeyError{
Err: "No master key was found either in the database or in the request!",
IsMissingParam: true,
}
return
}
// The key ID is basically the key itself.
masterKeyID := gomatrixserverlib.KeyID(fmt.Sprintf("ed25519:%s", masterKey.Encode()))
// Work out which things we need to verify the signatures for.
toVerify := make(map[gomatrixserverlib.CrossSigningKeyPurpose]gomatrixserverlib.CrossSigningKey, 3)
toStore := types.CrossSigningKeyMap{}
if len(req.MasterKey.Keys) > 0 {
toVerify[gomatrixserverlib.CrossSigningKeyPurposeMaster] = req.MasterKey
}
if len(req.SelfSigningKey.Keys) > 0 {
toVerify[gomatrixserverlib.CrossSigningKeyPurposeSelfSigning] = req.SelfSigningKey
}
if len(req.UserSigningKey.Keys) > 0 {
toVerify[gomatrixserverlib.CrossSigningKeyPurposeUserSigning] = req.UserSigningKey
}
if len(toVerify) == 0 {
res.Error = &api.KeyError{
Err: "No supplied keys available for verification",
IsMissingParam: true,
}
return
}
for purpose, key := range toVerify {
// Collect together the key IDs we need to verify with. This will include
// all of the key IDs specified in the signatures.
keyJSON, err := json.Marshal(key)
if err != nil {
res.Error = &api.KeyError{
Err: fmt.Sprintf("The JSON of the key section is invalid: %s", err.Error()),
}
return
}
switch purpose {
case gomatrixserverlib.CrossSigningKeyPurposeMaster:
// The master key might have a signature attached to it from the
// previous key, or from a device key, but there's no real need
// to verify it. Clients will perform key checks when the master
// key changes.
default:
// Sub-keys should be signed by the master key.
if err := gomatrixserverlib.VerifyJSON(req.UserID, masterKeyID, ed25519.PublicKey(masterKey), keyJSON); err != nil {
res.Error = &api.KeyError{
Err: fmt.Sprintf("The %q sub-key failed master key signature verification: %s", purpose, err.Error()),
IsInvalidSignature: true,
}
return
}
}
// If we've reached this point then all the signatures are valid so
// add the key to the list of keys to store.
for _, keyData := range key.Keys { // iterates once, see sanityCheckKey
toStore[purpose] = keyData
}
}
if len(toStore) == 0 {
res.Error = &api.KeyError{
Err: "No supplied keys passed verification",
Err: "No master key was found",
IsMissingParam: true,
}
return
}
// Store the keys.
if err := a.DB.StoreCrossSigningKeysForUser(ctx, req.UserID, toStore); err != nil {
res.Error = &api.KeyError{
Err: fmt.Sprintf("a.DB.StoreCrossSigningKeysForUser: %s", err),
@ -203,7 +198,7 @@ func (a *KeyInternalAPI) PerformUploadDeviceKeys(ctx context.Context, req *api.P
}
// Now upload any signatures that were included with the keys.
for _, key := range toVerify {
for _, key := range byPurpose {
var targetKeyID gomatrixserverlib.KeyID
for targetKey := range key.Keys { // iterates once, see sanityCheckKey
targetKeyID = targetKey
@ -222,6 +217,28 @@ func (a *KeyInternalAPI) PerformUploadDeviceKeys(ctx context.Context, req *api.P
}
}
}
// Finally, generate a notification that we updated the keys.
if _, host, err := gomatrixserverlib.SplitID('@', req.UserID); err == nil && host == a.ThisServer {
update := eduserverAPI.CrossSigningKeyUpdate{
UserID: req.UserID,
}
if mk, ok := byPurpose[gomatrixserverlib.CrossSigningKeyPurposeMaster]; ok {
update.MasterKey = &mk
}
if ssk, ok := byPurpose[gomatrixserverlib.CrossSigningKeyPurposeSelfSigning]; ok {
update.SelfSigningKey = &ssk
}
if update.MasterKey == nil && update.SelfSigningKey == nil {
return
}
if err := a.Producer.ProduceSigningKeyUpdate(update); err != nil {
res.Error = &api.KeyError{
Err: fmt.Sprintf("a.Producer.ProduceSigningKeyUpdate: %s", err),
}
return
}
}
}
func (a *KeyInternalAPI) PerformUploadDeviceSignatures(ctx context.Context, req *api.PerformUploadDeviceSignaturesRequest, res *api.PerformUploadDeviceSignaturesResponse) {
@ -277,7 +294,7 @@ func (a *KeyInternalAPI) PerformUploadDeviceSignatures(ctx context.Context, req
}
}
if err := a.processSelfSignatures(ctx, req.UserID, queryRes, selfSignatures); err != nil {
if err := a.processSelfSignatures(ctx, selfSignatures); err != nil {
res.Error = &api.KeyError{
Err: fmt.Sprintf("a.processSelfSignatures: %s", err),
}
@ -290,10 +307,25 @@ func (a *KeyInternalAPI) PerformUploadDeviceSignatures(ctx context.Context, req
}
return
}
// Finally, generate a notification that we updated the signatures.
for userID := range req.Signatures {
if _, host, err := gomatrixserverlib.SplitID('@', userID); err == nil && host == a.ThisServer {
update := eduserverAPI.CrossSigningKeyUpdate{
UserID: userID,
}
if err := a.Producer.ProduceSigningKeyUpdate(update); err != nil {
res.Error = &api.KeyError{
Err: fmt.Sprintf("a.Producer.ProduceSigningKeyUpdate: %s", err),
}
return
}
}
}
}
func (a *KeyInternalAPI) processSelfSignatures(
ctx context.Context, _ string, queryRes *api.QueryKeysResponse,
ctx context.Context,
signatures map[string]map[gomatrixserverlib.KeyID]gomatrixserverlib.CrossSigningForKeyOrDevice,
) error {
// Here we will process:
@ -304,37 +336,8 @@ func (a *KeyInternalAPI) processSelfSignatures(
for targetKeyID, signature := range forTargetUserID {
switch sig := signature.CrossSigningBody.(type) {
case *gomatrixserverlib.CrossSigningKey:
// The user is signing their master key with one of their devices
// The QueryKeys response should contain the device key hopefully.
// First we need to marshal the blob back into JSON so we can verify
// it.
j, err := json.Marshal(sig)
if err != nil {
return fmt.Errorf("json.Marshal: %w", err)
}
for originUserID, forOriginUserID := range sig.Signatures {
originDeviceKeys, ok := queryRes.DeviceKeys[originUserID]
if !ok {
return fmt.Errorf("missing device keys for user %q", originUserID)
}
for originKeyID, originSig := range forOriginUserID {
var originKey gomatrixserverlib.DeviceKeys
if err := json.Unmarshal(originDeviceKeys[string(originKeyID)], &originKey); err != nil {
return fmt.Errorf("json.Unmarshal: %w", err)
}
originSigningKey, ok := originKey.Keys[originKeyID]
if !ok {
return fmt.Errorf("missing origin signing key %q", originKeyID)
}
originSigningKeyPublic := ed25519.PublicKey(originSigningKey)
if err := gomatrixserverlib.VerifyJSON(originUserID, originKeyID, originSigningKeyPublic, j); err != nil {
return fmt.Errorf("gomatrixserverlib.VerifyJSON: %w", err)
}
if err := a.DB.StoreCrossSigningSigsForTarget(
ctx, originUserID, originKeyID, targetUserID, targetKeyID, originSig,
); err != nil {
@ -344,35 +347,8 @@ func (a *KeyInternalAPI) processSelfSignatures(
}
case *gomatrixserverlib.DeviceKeys:
// The user is signing one of their devices with their self-signing key
// The QueryKeys response should contain the master key hopefully.
// First we need to marshal the blob back into JSON so we can verify
// it.
j, err := json.Marshal(sig)
if err != nil {
return fmt.Errorf("json.Marshal: %w", err)
}
for originUserID, forOriginUserID := range sig.Signatures {
for originKeyID, originSig := range forOriginUserID {
originSelfSigningKeys, ok := queryRes.SelfSigningKeys[originUserID]
if !ok {
return fmt.Errorf("missing self-signing key for user %q", originUserID)
}
var originSelfSigningKeyID gomatrixserverlib.KeyID
var originSelfSigningKey gomatrixserverlib.Base64Bytes
for keyID, key := range originSelfSigningKeys.Keys {
originSelfSigningKeyID, originSelfSigningKey = keyID, key
break
}
originSelfSigningKeyPublic := ed25519.PublicKey(originSelfSigningKey)
if err := gomatrixserverlib.VerifyJSON(originUserID, originSelfSigningKeyID, originSelfSigningKeyPublic, j); err != nil {
return fmt.Errorf("gomatrixserverlib.VerifyJSON: %w", err)
}
if err := a.DB.StoreCrossSigningSigsForTarget(
ctx, originUserID, originKeyID, targetUserID, targetKeyID, originSig,
); err != nil {

View file

@ -65,7 +65,7 @@ func NewInternalAPI(
}
}()
keyconsumer := consumers.NewOutputSigningKeyUpdateConsumer(
keyconsumer := consumers.NewOutputCrossSigningKeyUpdateConsumer(
base.ProcessContext, base.Cfg, consumer, db, ap,
)
if err := keyconsumer.Start(); err != nil {

View file

@ -19,6 +19,7 @@ import (
"encoding/json"
"github.com/Shopify/sarama"
eduapi "github.com/matrix-org/dendrite/eduserver/api"
"github.com/matrix-org/dendrite/keyserver/api"
"github.com/matrix-org/dendrite/keyserver/storage"
"github.com/sirupsen/logrus"
@ -73,3 +74,36 @@ func (p *KeyChange) ProduceKeyChanges(keys []api.DeviceMessage) error {
}
return nil
}
func (p *KeyChange) ProduceSigningKeyUpdate(key eduapi.CrossSigningKeyUpdate) error {
var m sarama.ProducerMessage
output := &api.DeviceMessage{
Type: api.TypeCrossSigningUpdate,
OutputCrossSigningKeyUpdate: &eduapi.OutputCrossSigningKeyUpdate{
CrossSigningKeyUpdate: key,
},
}
value, err := json.Marshal(output)
if err != nil {
return err
}
m.Topic = string(p.Topic)
m.Key = sarama.StringEncoder(key.UserID)
m.Value = sarama.ByteEncoder(value)
partition, offset, err := p.Producer.SendMessage(&m)
if err != nil {
return err
}
err = p.DB.StoreKeyChange(context.Background(), partition, offset, key.UserID)
if err != nil {
return err
}
logrus.WithFields(logrus.Fields{
"user_id": key.UserID,
}).Infof("Produced to cross-signing update topic '%s'", p.Topic)
return nil
}