Merge branch 'master' of https://github.com/matrix-org/dendrite into add-presence

This commit is contained in:
Till Faelligen 2021-08-09 16:41:05 +02:00
commit 2b0261cd70
14 changed files with 441 additions and 94 deletions

View file

@ -120,6 +120,11 @@ func main() {
Impl: userAPI, Impl: userAPI,
} }
} }
// needs to be after the SetUserAPI call above
if base.UseHTTPAPIs {
keyserver.AddInternalRoutes(base.InternalAPIMux, keyAPI)
keyAPI = base.KeyServerHTTPClient()
}
eduInputAPI := eduserver.NewInternalAPI( eduInputAPI := eduserver.NewInternalAPI(
base, cache.New(), userAPI, base, cache.New(), userAPI,

View file

@ -37,12 +37,27 @@ func GetUserDevices(
return jsonerror.InternalServerError() return jsonerror.InternalServerError()
} }
sigReq := &keyapi.QuerySignaturesRequest{
TargetIDs: map[string][]gomatrixserverlib.KeyID{
userID: {},
},
}
sigRes := &keyapi.QuerySignaturesResponse{}
keyAPI.QuerySignatures(req.Context(), sigReq, sigRes)
response := gomatrixserverlib.RespUserDevices{ response := gomatrixserverlib.RespUserDevices{
UserID: userID, UserID: userID,
StreamID: res.StreamID, StreamID: res.StreamID,
Devices: []gomatrixserverlib.RespUserDevice{}, Devices: []gomatrixserverlib.RespUserDevice{},
} }
if masterKey, ok := sigRes.MasterKeys[userID]; ok {
response.MasterKey = &masterKey
}
if selfSigningKey, ok := sigRes.SelfSigningKeys[userID]; ok {
response.SelfSigningKey = &selfSigningKey
}
for _, dev := range res.Devices { for _, dev := range res.Devices {
var key gomatrixserverlib.RespUserDeviceKeys var key gomatrixserverlib.RespUserDeviceKeys
err := json.Unmarshal(dev.DeviceKeys.KeyJSON, &key) err := json.Unmarshal(dev.DeviceKeys.KeyJSON, &key)
@ -56,6 +71,20 @@ func GetUserDevices(
DisplayName: dev.DisplayName, DisplayName: dev.DisplayName,
Keys: key, Keys: key,
} }
if targetUser, ok := sigRes.Signatures[userID]; ok {
if targetKey, ok := targetUser[gomatrixserverlib.KeyID(dev.DeviceID)]; ok {
for sourceUserID, forSourceUser := range targetKey {
for sourceKeyID, sourceKey := range forSourceUser {
if _, ok := device.Keys.Signatures[sourceUserID]; !ok {
device.Keys.Signatures[sourceUserID] = map[gomatrixserverlib.KeyID]gomatrixserverlib.Base64Bytes{}
}
device.Keys.Signatures[sourceUserID][sourceKeyID] = sourceKey
}
}
}
}
response.Devices = append(response.Devices, device) response.Devices = append(response.Devices, device)
} }

View file

@ -72,7 +72,13 @@ func QueryDeviceKeys(
Code: 200, Code: 200,
JSON: struct { JSON: struct {
DeviceKeys interface{} `json:"device_keys"` DeviceKeys interface{} `json:"device_keys"`
}{queryRes.DeviceKeys}, MasterKeys interface{} `json:"master_keys"`
SelfSigningKeys interface{} `json:"self_signing_keys"`
}{
queryRes.DeviceKeys,
queryRes.MasterKeys,
queryRes.SelfSigningKeys,
},
} }
} }

2
go.mod
View file

@ -31,7 +31,7 @@ require (
github.com/matrix-org/go-http-js-libp2p v0.0.0-20200518170932-783164aeeda4 github.com/matrix-org/go-http-js-libp2p v0.0.0-20200518170932-783164aeeda4
github.com/matrix-org/go-sqlite3-js v0.0.0-20210709140738-b0d1ba599a6d github.com/matrix-org/go-sqlite3-js v0.0.0-20210709140738-b0d1ba599a6d
github.com/matrix-org/gomatrix v0.0.0-20210324163249-be2af5ef2e16 github.com/matrix-org/gomatrix v0.0.0-20210324163249-be2af5ef2e16
github.com/matrix-org/gomatrixserverlib v0.0.0-20210802144451-bec8d2252d83 github.com/matrix-org/gomatrixserverlib v0.0.0-20210809130922-d9c3f400582b
github.com/matrix-org/naffka v0.0.0-20210623111924-14ff508b58e0 github.com/matrix-org/naffka v0.0.0-20210623111924-14ff508b58e0
github.com/matrix-org/pinecone v0.0.0-20210623102758-74f885644c1b github.com/matrix-org/pinecone v0.0.0-20210623102758-74f885644c1b
github.com/matrix-org/util v0.0.0-20200807132607-55161520e1d4 github.com/matrix-org/util v0.0.0-20200807132607-55161520e1d4

4
go.sum
View file

@ -994,8 +994,8 @@ github.com/matrix-org/go-sqlite3-js v0.0.0-20210709140738-b0d1ba599a6d/go.mod h1
github.com/matrix-org/gomatrix v0.0.0-20190528120928-7df988a63f26/go.mod h1:3fxX6gUjWyI/2Bt7J1OLhpCzOfO/bB3AiX0cJtEKud0= github.com/matrix-org/gomatrix v0.0.0-20190528120928-7df988a63f26/go.mod h1:3fxX6gUjWyI/2Bt7J1OLhpCzOfO/bB3AiX0cJtEKud0=
github.com/matrix-org/gomatrix v0.0.0-20210324163249-be2af5ef2e16 h1:ZtO5uywdd5dLDCud4r0r55eP4j9FuUNpl60Gmntcop4= github.com/matrix-org/gomatrix v0.0.0-20210324163249-be2af5ef2e16 h1:ZtO5uywdd5dLDCud4r0r55eP4j9FuUNpl60Gmntcop4=
github.com/matrix-org/gomatrix v0.0.0-20210324163249-be2af5ef2e16/go.mod h1:/gBX06Kw0exX1HrwmoBibFA98yBk/jxKpGVeyQbff+s= github.com/matrix-org/gomatrix v0.0.0-20210324163249-be2af5ef2e16/go.mod h1:/gBX06Kw0exX1HrwmoBibFA98yBk/jxKpGVeyQbff+s=
github.com/matrix-org/gomatrixserverlib v0.0.0-20210802144451-bec8d2252d83 h1:fkUmeKj/U5TnWXTsJnVjEL0FQiVhf1r9WL4VWI00q2k= github.com/matrix-org/gomatrixserverlib v0.0.0-20210809130922-d9c3f400582b h1:8St1B8QmlvMLsOmGqW3++0akUs0250IAi+AGcr5faxw=
github.com/matrix-org/gomatrixserverlib v0.0.0-20210802144451-bec8d2252d83/go.mod h1:JsAzE1Ll3+gDWS9JSUHPJiiyAksvOOnGWF2nXdg4ZzU= github.com/matrix-org/gomatrixserverlib v0.0.0-20210809130922-d9c3f400582b/go.mod h1:JsAzE1Ll3+gDWS9JSUHPJiiyAksvOOnGWF2nXdg4ZzU=
github.com/matrix-org/naffka v0.0.0-20210623111924-14ff508b58e0 h1:HZCzy4oVzz55e+cOMiX/JtSF2UOY1evBl2raaE7ACcU= github.com/matrix-org/naffka v0.0.0-20210623111924-14ff508b58e0 h1:HZCzy4oVzz55e+cOMiX/JtSF2UOY1evBl2raaE7ACcU=
github.com/matrix-org/naffka v0.0.0-20210623111924-14ff508b58e0/go.mod h1:sjyPyRxKM5uw1nD2cJ6O2OxI6GOqyVBfNXqKjBZTBZE= github.com/matrix-org/naffka v0.0.0-20210623111924-14ff508b58e0/go.mod h1:sjyPyRxKM5uw1nD2cJ6O2OxI6GOqyVBfNXqKjBZTBZE=
github.com/matrix-org/pinecone v0.0.0-20210623102758-74f885644c1b h1:5X5vdWQ13xrNkJVqaJHPsrt7rKkMJH5iac0EtfOuxSg= github.com/matrix-org/pinecone v0.0.0-20210623102758-74f885644c1b h1:5X5vdWQ13xrNkJVqaJHPsrt7rKkMJH5iac0EtfOuxSg=

View file

@ -20,6 +20,7 @@ import (
"strings" "strings"
"time" "time"
"github.com/matrix-org/dendrite/keyserver/types"
userapi "github.com/matrix-org/dendrite/userapi/api" userapi "github.com/matrix-org/dendrite/userapi/api"
"github.com/matrix-org/gomatrixserverlib" "github.com/matrix-org/gomatrixserverlib"
) )
@ -38,6 +39,7 @@ type KeyInternalAPI interface {
QueryKeyChanges(ctx context.Context, req *QueryKeyChangesRequest, res *QueryKeyChangesResponse) QueryKeyChanges(ctx context.Context, req *QueryKeyChangesRequest, res *QueryKeyChangesResponse)
QueryOneTimeKeys(ctx context.Context, req *QueryOneTimeKeysRequest, res *QueryOneTimeKeysResponse) QueryOneTimeKeys(ctx context.Context, req *QueryOneTimeKeysRequest, res *QueryOneTimeKeysResponse)
QueryDeviceMessages(ctx context.Context, req *QueryDeviceMessagesRequest, res *QueryDeviceMessagesResponse) QueryDeviceMessages(ctx context.Context, req *QueryDeviceMessagesRequest, res *QueryDeviceMessagesResponse)
QuerySignatures(ctx context.Context, req *QuerySignaturesRequest, res *QuerySignaturesResponse)
} }
// KeyError is returned if there was a problem performing/querying the server // KeyError is returned if there was a problem performing/querying the server
@ -158,7 +160,7 @@ type PerformClaimKeysResponse struct {
type PerformUploadDeviceKeysRequest struct { type PerformUploadDeviceKeysRequest struct {
gomatrixserverlib.CrossSigningKeys gomatrixserverlib.CrossSigningKeys
// The user that uploaded the key, should be populated by the clientapi. // The user that uploaded the key, should be populated by the clientapi.
UserID string `json:"user_id"` UserID string
} }
type PerformUploadDeviceKeysResponse struct { type PerformUploadDeviceKeysResponse struct {
@ -168,7 +170,7 @@ type PerformUploadDeviceKeysResponse struct {
type PerformUploadDeviceSignaturesRequest struct { type PerformUploadDeviceSignaturesRequest struct {
Signatures map[string]map[gomatrixserverlib.KeyID]gomatrixserverlib.CrossSigningForKeyOrDevice Signatures map[string]map[gomatrixserverlib.KeyID]gomatrixserverlib.CrossSigningForKeyOrDevice
// The user that uploaded the sig, should be populated by the clientapi. // The user that uploaded the sig, should be populated by the clientapi.
UserID string `json:"user_id"` UserID string
} }
type PerformUploadDeviceSignaturesResponse struct { type PerformUploadDeviceSignaturesResponse struct {
@ -242,6 +244,24 @@ type QueryDeviceMessagesResponse struct {
Error *KeyError Error *KeyError
} }
type QuerySignaturesRequest struct {
// A map of target user ID -> target key/device IDs to retrieve signatures for
TargetIDs map[string][]gomatrixserverlib.KeyID `json:"target_ids"`
}
type QuerySignaturesResponse struct {
// A map of target user ID -> target key/device ID -> origin user ID -> origin key/device ID -> signatures
Signatures map[string]map[gomatrixserverlib.KeyID]types.CrossSigningSigMap
// A map of target user ID -> cross-signing master key
MasterKeys map[string]gomatrixserverlib.CrossSigningKey
// A map of target user ID -> cross-signing self-signing key
SelfSigningKeys map[string]gomatrixserverlib.CrossSigningKey
// A map of target user ID -> cross-signing user-signing key
UserSigningKeys map[string]gomatrixserverlib.CrossSigningKey
// The request error, if any
Error *KeyError
}
type InputDeviceListUpdateRequest struct { type InputDeviceListUpdateRequest struct {
Event gomatrixserverlib.DeviceListUpdateEvent Event gomatrixserverlib.DeviceListUpdateEvent
} }

View file

@ -15,8 +15,10 @@
package internal package internal
import ( import (
"bytes"
"context" "context"
"crypto/ed25519" "crypto/ed25519"
"database/sql"
"encoding/json" "encoding/json"
"fmt" "fmt"
"strings" "strings"
@ -77,8 +79,8 @@ func (a *KeyInternalAPI) PerformUploadDeviceKeys(ctx context.Context, req *api.P
} }
return return
} }
hasMasterKey = true
for _, keyData := range req.MasterKey.Keys { // iterates once, because sanityCheckKey requires one key for _, keyData := range req.MasterKey.Keys { // iterates once, because sanityCheckKey requires one key
hasMasterKey = true
masterKey = keyData masterKey = keyData
} }
} }
@ -104,7 +106,7 @@ func (a *KeyInternalAPI) PerformUploadDeviceKeys(ctx context.Context, req *api.P
// If the user hasn't given a new master key, then let's go and get their // If the user hasn't given a new master key, then let's go and get their
// existing keys from the database. // existing keys from the database.
if !hasMasterKey { if !hasMasterKey {
existingKeys, err := a.DB.CrossSigningKeysForUser(ctx, req.UserID) existingKeys, err := a.DB.CrossSigningKeysDataForUser(ctx, req.UserID)
if err != nil { if err != nil {
res.Error = &api.KeyError{ res.Error = &api.KeyError{
Err: "Retrieving cross-signing keys from database failed: " + err.Error(), Err: "Retrieving cross-signing keys from database failed: " + err.Error(),
@ -115,46 +117,11 @@ 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: "Master 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 {
res.Error = &api.KeyError{ res.Error = &api.KeyError{
Err: "No master key was found, either in the database or in the request!", Err: "No master key was found either in the database or in the request!",
IsMissingParam: true, IsMissingParam: true,
} }
return return
@ -175,14 +142,18 @@ func (a *KeyInternalAPI) PerformUploadDeviceKeys(ctx context.Context, req *api.P
if len(req.UserSigningKey.Keys) > 0 { if len(req.UserSigningKey.Keys) > 0 {
toVerify[gomatrixserverlib.CrossSigningKeyPurposeUserSigning] = req.UserSigningKey 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 { for purpose, key := range toVerify {
// Collect together the key IDs we need to verify with. This will include // Collect together the key IDs we need to verify with. This will include
// all of the key IDs specified in the signatures. We don't do this for // all of the key IDs specified in the signatures.
// the master key because we have no means to verify the signatures - we
// instead just need to store them.
if purpose != gomatrixserverlib.CrossSigningKeyPurposeMaster {
// Marshal the specific key back into JSON so that we can verify the
// signature of it.
keyJSON, err := json.Marshal(key) keyJSON, err := json.Marshal(key)
if err != nil { if err != nil {
res.Error = &api.KeyError{ res.Error = &api.KeyError{
@ -191,7 +162,15 @@ func (a *KeyInternalAPI) PerformUploadDeviceKeys(ctx context.Context, req *api.P
return return
} }
// Now check if the subkey is signed by the master key. 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 { if err := gomatrixserverlib.VerifyJSON(req.UserID, masterKeyID, ed25519.PublicKey(masterKey), keyJSON); err != nil {
res.Error = &api.KeyError{ res.Error = &api.KeyError{
Err: fmt.Sprintf("The %q sub-key failed master key signature verification: %s", purpose, err.Error()), Err: fmt.Sprintf("The %q sub-key failed master key signature verification: %s", purpose, err.Error()),
@ -208,17 +187,61 @@ func (a *KeyInternalAPI) PerformUploadDeviceKeys(ctx context.Context, req *api.P
} }
} }
if len(toStore) == 0 {
res.Error = &api.KeyError{
Err: "No supplied keys passed verification",
IsMissingParam: true,
}
return
}
if err := a.DB.StoreCrossSigningKeysForUser(ctx, req.UserID, toStore); err != nil { if err := a.DB.StoreCrossSigningKeysForUser(ctx, req.UserID, toStore); err != nil {
res.Error = &api.KeyError{ res.Error = &api.KeyError{
Err: fmt.Sprintf("a.DB.StoreCrossSigningKeysForUser: %s", err), Err: fmt.Sprintf("a.DB.StoreCrossSigningKeysForUser: %s", err),
} }
return
}
// Now upload any signatures that were included with the keys.
for _, key := range toVerify {
var targetKeyID gomatrixserverlib.KeyID
for targetKey := range key.Keys { // iterates once, see sanityCheckKey
targetKeyID = targetKey
}
for sigUserID, forSigUserID := range key.Signatures {
if sigUserID != req.UserID {
continue
}
for sigKeyID, sigBytes := range forSigUserID {
if err := a.DB.StoreCrossSigningSigsForTarget(ctx, sigUserID, sigKeyID, req.UserID, targetKeyID, sigBytes); err != nil {
res.Error = &api.KeyError{
Err: fmt.Sprintf("a.DB.StoreCrossSigningSigsForTarget: %s", err),
}
return
}
}
}
} }
} }
func (a *KeyInternalAPI) PerformUploadDeviceSignatures(ctx context.Context, req *api.PerformUploadDeviceSignaturesRequest, res *api.PerformUploadDeviceSignaturesResponse) { func (a *KeyInternalAPI) PerformUploadDeviceSignatures(ctx context.Context, req *api.PerformUploadDeviceSignaturesRequest, res *api.PerformUploadDeviceSignaturesResponse) {
// Before we do anything, we need the master and self-signing keys for this user.
// Then we can verify the signatures make sense.
queryReq := &api.QueryKeysRequest{
UserID: req.UserID,
UserToDevices: map[string][]string{},
}
queryRes := &api.QueryKeysResponse{}
for userID := range req.Signatures {
queryReq.UserToDevices[userID] = []string{}
}
a.QueryKeys(ctx, queryReq, queryRes)
selfSignatures := map[string]map[gomatrixserverlib.KeyID]gomatrixserverlib.CrossSigningForKeyOrDevice{} selfSignatures := map[string]map[gomatrixserverlib.KeyID]gomatrixserverlib.CrossSigningForKeyOrDevice{}
otherSignatures := map[string]map[gomatrixserverlib.KeyID]gomatrixserverlib.CrossSigningForKeyOrDevice{} otherSignatures := map[string]map[gomatrixserverlib.KeyID]gomatrixserverlib.CrossSigningForKeyOrDevice{}
// Sort signatures into two groups: one where people have signed their own
// keys and one where people have signed someone elses
for userID, forUserID := range req.Signatures { for userID, forUserID := range req.Signatures {
for keyID, keyOrDevice := range forUserID { for keyID, keyOrDevice := range forUserID {
switch key := keyOrDevice.CrossSigningBody.(type) { switch key := keyOrDevice.CrossSigningBody.(type) {
@ -254,14 +277,14 @@ func (a *KeyInternalAPI) PerformUploadDeviceSignatures(ctx context.Context, req
} }
} }
if err := a.processSelfSignatures(ctx, req.UserID, selfSignatures); err != nil { if err := a.processSelfSignatures(ctx, req.UserID, queryRes, selfSignatures); err != nil {
res.Error = &api.KeyError{ res.Error = &api.KeyError{
Err: fmt.Sprintf("a.processSelfSignatures: %s", err), Err: fmt.Sprintf("a.processSelfSignatures: %s", err),
} }
return return
} }
if err := a.processOtherSignatures(ctx, req.UserID, otherSignatures); err != nil { if err := a.processOtherSignatures(ctx, req.UserID, queryRes, otherSignatures); err != nil {
res.Error = &api.KeyError{ res.Error = &api.KeyError{
Err: fmt.Sprintf("a.processOtherSignatures: %s", err), Err: fmt.Sprintf("a.processOtherSignatures: %s", err),
} }
@ -270,7 +293,7 @@ func (a *KeyInternalAPI) PerformUploadDeviceSignatures(ctx context.Context, req
} }
func (a *KeyInternalAPI) processSelfSignatures( func (a *KeyInternalAPI) processSelfSignatures(
ctx context.Context, _ string, ctx context.Context, _ string, queryRes *api.QueryKeysResponse,
signatures map[string]map[gomatrixserverlib.KeyID]gomatrixserverlib.CrossSigningForKeyOrDevice, signatures map[string]map[gomatrixserverlib.KeyID]gomatrixserverlib.CrossSigningForKeyOrDevice,
) error { ) error {
// Here we will process: // Here we will process:
@ -281,8 +304,37 @@ func (a *KeyInternalAPI) processSelfSignatures(
for targetKeyID, signature := range forTargetUserID { for targetKeyID, signature := range forTargetUserID {
switch sig := signature.CrossSigningBody.(type) { switch sig := signature.CrossSigningBody.(type) {
case *gomatrixserverlib.CrossSigningKey: 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 { 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 { 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( if err := a.DB.StoreCrossSigningSigsForTarget(
ctx, originUserID, originKeyID, targetUserID, targetKeyID, originSig, ctx, originUserID, originKeyID, targetUserID, targetKeyID, originSig,
); err != nil { ); err != nil {
@ -292,8 +344,35 @@ func (a *KeyInternalAPI) processSelfSignatures(
} }
case *gomatrixserverlib.DeviceKeys: 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 originUserID, forOriginUserID := range sig.Signatures {
for originKeyID, originSig := range forOriginUserID { 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( if err := a.DB.StoreCrossSigningSigsForTarget(
ctx, originUserID, originKeyID, targetUserID, targetKeyID, originSig, ctx, originUserID, originKeyID, targetUserID, targetKeyID, originSig,
); err != nil { ); err != nil {
@ -312,12 +391,62 @@ func (a *KeyInternalAPI) processSelfSignatures(
} }
func (a *KeyInternalAPI) processOtherSignatures( func (a *KeyInternalAPI) processOtherSignatures(
ctx context.Context, userID string, ctx context.Context, userID string, queryRes *api.QueryKeysResponse,
signatures map[string]map[gomatrixserverlib.KeyID]gomatrixserverlib.CrossSigningForKeyOrDevice, signatures map[string]map[gomatrixserverlib.KeyID]gomatrixserverlib.CrossSigningForKeyOrDevice,
) error { ) error {
// Here we will process: // Here we will process:
// * A user signing someone else's master keys using their user-signing keys // * A user signing someone else's master keys using their user-signing keys
for targetUserID, forTargetUserID := range signatures {
for _, signature := range forTargetUserID {
switch sig := signature.CrossSigningBody.(type) {
case *gomatrixserverlib.CrossSigningKey:
// Find the local copy of the master key. We'll use this to be
// sure that the supplied stanza matches the key that we think it
// should be.
masterKey, ok := queryRes.MasterKeys[targetUserID]
if !ok {
return fmt.Errorf("failed to find master key for user %q", targetUserID)
}
// For each key ID, write the signatures. Maybe there'll be more
// than one algorithm in the future so it's best not to focus on
// everything being ed25519:.
for targetKeyID, suppliedKeyData := range sig.Keys {
// The master key will be supplied in the request, but we should
// make sure that it matches what we think the master key should
// actually be.
localKeyData, lok := masterKey.Keys[targetKeyID]
if !lok {
return fmt.Errorf("uploaded master key %q for user %q doesn't match local copy", targetKeyID, targetUserID)
} else if !bytes.Equal(suppliedKeyData, localKeyData) {
return fmt.Errorf("uploaded master key %q for user %q doesn't match local copy", targetKeyID, targetUserID)
}
// We only care about the signatures from the uploading user, so
// we will ignore anything that didn't originate from them.
userSigs, ok := sig.Signatures[userID]
if !ok {
return fmt.Errorf("there are no signatures on master key %q from uploading user %q", targetKeyID, userID)
}
for originKeyID, originSig := range userSigs {
if err := a.DB.StoreCrossSigningSigsForTarget(
ctx, userID, originKeyID, targetUserID, targetKeyID, originSig,
); err != nil {
return fmt.Errorf("a.DB.StoreCrossSigningKeysForTarget: %w", err)
}
}
}
default:
// Users should only be signing another person's master key,
// so if we're here, it's probably because it's actually a
// gomatrixserverlib.DeviceKeys, which doesn't make sense.
}
}
}
return nil return nil
} }
@ -331,21 +460,15 @@ func (a *KeyInternalAPI) crossSigningKeysFromDatabase(
continue continue
} }
for keyType, keyData := range keys { for keyType, key := range keys {
b64 := keyData.Encode() var keyID gomatrixserverlib.KeyID
keyID := gomatrixserverlib.KeyID("ed25519:" + b64) for id := range key.Keys {
key := gomatrixserverlib.CrossSigningKey{ keyID = id
UserID: userID, break
Usage: []gomatrixserverlib.CrossSigningKeyPurpose{
keyType,
},
Keys: map[gomatrixserverlib.KeyID]gomatrixserverlib.Base64Bytes{
keyID: keyData,
},
} }
sigs, err := a.DB.CrossSigningSigsForTarget(ctx, userID, keyID) sigMap, err := a.DB.CrossSigningSigsForTarget(ctx, userID, keyID)
if err != nil { if err != nil && err != sql.ErrNoRows {
logrus.WithError(err).Errorf("Failed to get cross-signing signatures for user %q key %q", userID, keyID) logrus.WithError(err).Errorf("Failed to get cross-signing signatures for user %q key %q", userID, keyID)
continue continue
} }
@ -360,7 +483,7 @@ func (a *KeyInternalAPI) crossSigningKeysFromDatabase(
key.Signatures[originUserID][originKeyID] = signature key.Signatures[originUserID][originKeyID] = signature
} }
for originUserID, forOrigin := range sigs { for originUserID, forOrigin := range sigMap {
for originKeyID, signature := range forOrigin { for originKeyID, signature := range forOrigin {
switch { switch {
case req.UserID != "" && originUserID == req.UserID: case req.UserID != "" && originUserID == req.UserID:
@ -387,3 +510,65 @@ func (a *KeyInternalAPI) crossSigningKeysFromDatabase(
} }
} }
} }
func (a *KeyInternalAPI) QuerySignatures(ctx context.Context, req *api.QuerySignaturesRequest, res *api.QuerySignaturesResponse) {
for targetUserID, forTargetUser := range req.TargetIDs {
keyMap, err := a.DB.CrossSigningKeysForUser(ctx, targetUserID)
if err != nil && err != sql.ErrNoRows {
res.Error = &api.KeyError{
Err: fmt.Sprintf("a.DB.CrossSigningKeysForUser: %s", err),
}
continue
}
for targetPurpose, targetKey := range keyMap {
switch targetPurpose {
case gomatrixserverlib.CrossSigningKeyPurposeMaster:
if res.MasterKeys == nil {
res.MasterKeys = map[string]gomatrixserverlib.CrossSigningKey{}
}
res.MasterKeys[targetUserID] = targetKey
case gomatrixserverlib.CrossSigningKeyPurposeSelfSigning:
if res.SelfSigningKeys == nil {
res.SelfSigningKeys = map[string]gomatrixserverlib.CrossSigningKey{}
}
res.SelfSigningKeys[targetUserID] = targetKey
case gomatrixserverlib.CrossSigningKeyPurposeUserSigning:
if res.UserSigningKeys == nil {
res.UserSigningKeys = map[string]gomatrixserverlib.CrossSigningKey{}
}
res.UserSigningKeys[targetUserID] = targetKey
}
}
for _, targetKeyID := range forTargetUser {
sigMap, err := a.DB.CrossSigningSigsForTarget(ctx, targetUserID, targetKeyID)
if err != nil && err != sql.ErrNoRows {
res.Error = &api.KeyError{
Err: fmt.Sprintf("a.DB.CrossSigningSigsForTarget: %s", err),
}
return
}
for sourceUserID, forSourceUser := range sigMap {
for sourceKeyID, sourceSig := range forSourceUser {
if res.Signatures == nil {
res.Signatures = map[string]map[gomatrixserverlib.KeyID]types.CrossSigningSigMap{}
}
if _, ok := res.Signatures[targetUserID]; !ok {
res.Signatures[targetUserID] = map[gomatrixserverlib.KeyID]types.CrossSigningSigMap{}
}
if _, ok := res.Signatures[targetUserID][targetKeyID]; !ok {
res.Signatures[targetUserID][targetKeyID] = types.CrossSigningSigMap{}
}
if _, ok := res.Signatures[targetUserID][targetKeyID][sourceUserID]; !ok {
res.Signatures[targetUserID][targetKeyID][sourceUserID] = map[gomatrixserverlib.KeyID]gomatrixserverlib.Base64Bytes{}
}
res.Signatures[targetUserID][targetKeyID][sourceUserID][sourceKeyID] = sourceSig
}
}
}
}
}

View file

@ -300,14 +300,40 @@ func (a *KeyInternalAPI) QueryKeys(ctx context.Context, req *api.QueryKeysReques
// attempt to satisfy key queries from the local database first as we should get device updates pushed to us // attempt to satisfy key queries from the local database first as we should get device updates pushed to us
domainToDeviceKeys = a.remoteKeysFromDatabase(ctx, res, domainToDeviceKeys) domainToDeviceKeys = a.remoteKeysFromDatabase(ctx, res, domainToDeviceKeys)
if len(domainToDeviceKeys) == 0 && len(domainToCrossSigningKeys) == 0 { if len(domainToDeviceKeys) > 0 || len(domainToCrossSigningKeys) > 0 {
return // nothing to query
}
// perform key queries for remote devices // perform key queries for remote devices
a.queryRemoteKeys(ctx, req.Timeout, res, domainToDeviceKeys, domainToCrossSigningKeys) a.queryRemoteKeys(ctx, req.Timeout, res, domainToDeviceKeys, domainToCrossSigningKeys)
} }
// Finally, append signatures that we know about
// TODO: This is horrible because we need to round-trip the signature from
// JSON, add the signatures and marshal it again, for some reason?
for userID, forUserID := range res.DeviceKeys {
for keyID, key := range forUserID {
sigMap, err := a.DB.CrossSigningSigsForTarget(ctx, userID, gomatrixserverlib.KeyID(keyID))
if err != nil {
logrus.WithError(err).Errorf("a.DB.CrossSigningSigsForTarget failed")
continue
}
if len(sigMap) == 0 {
continue
}
var deviceKey gomatrixserverlib.DeviceKeys
if err = json.Unmarshal(key, &deviceKey); err != nil {
continue
}
for sourceUserID, forSourceUser := range sigMap {
for sourceKeyID, sourceSig := range forSourceUser {
deviceKey.Signatures[sourceUserID][sourceKeyID] = sourceSig
}
}
if js, err := json.Marshal(deviceKey); err == nil {
res.DeviceKeys[userID][keyID] = js
}
}
}
}
func (a *KeyInternalAPI) remoteKeysFromDatabase( func (a *KeyInternalAPI) remoteKeysFromDatabase(
ctx context.Context, res *api.QueryKeysResponse, domainToDeviceKeys map[string]map[string][]string, ctx context.Context, res *api.QueryKeysResponse, domainToDeviceKeys map[string]map[string][]string,
) map[string]map[string][]string { ) map[string]map[string][]string {
@ -346,9 +372,15 @@ func (a *KeyInternalAPI) queryRemoteKeys(
domains := map[string]struct{}{} domains := map[string]struct{}{}
for domain := range domainToDeviceKeys { for domain := range domainToDeviceKeys {
if domain == string(a.ThisServer) {
continue
}
domains[domain] = struct{}{} domains[domain] = struct{}{}
} }
for domain := range domainToCrossSigningKeys { for domain := range domainToCrossSigningKeys {
if domain == string(a.ThisServer) {
continue
}
domains[domain] = struct{}{} domains[domain] = struct{}{}
} }
wg.Add(len(domains)) wg.Add(len(domains))
@ -380,17 +412,11 @@ func (a *KeyInternalAPI) queryRemoteKeys(
} }
for userID, body := range result.MasterKeys { for userID, body := range result.MasterKeys {
switch b := body.CrossSigningBody.(type) { res.MasterKeys[userID] = body
case *gomatrixserverlib.CrossSigningKey:
res.MasterKeys[userID] = *b
}
} }
for userID, body := range result.SelfSigningKeys { for userID, body := range result.SelfSigningKeys {
switch b := body.CrossSigningBody.(type) { res.SelfSigningKeys[userID] = body
case *gomatrixserverlib.CrossSigningKey:
res.SelfSigningKeys[userID] = *b
}
} }
// TODO: do we want to persist these somewhere now // TODO: do we want to persist these somewhere now
@ -404,8 +430,12 @@ func (a *KeyInternalAPI) queryRemoteKeysOnServer(
res *api.QueryKeysResponse, res *api.QueryKeysResponse,
) { ) {
defer wg.Done() defer wg.Done()
fedCtx, cancel := context.WithTimeout(ctx, timeout) fedCtx := ctx
if timeout > 0 {
var cancel context.CancelFunc
fedCtx, cancel = context.WithTimeout(ctx, timeout)
defer cancel() defer cancel()
}
// for users who we do not have any knowledge about, try to start doing device list updates for them // for users who we do not have any knowledge about, try to start doing device list updates for them
// by hitting /users/devices - otherwise fallback to /keys/query which has nicer bulk properties but // by hitting /users/devices - otherwise fallback to /keys/query which has nicer bulk properties but
// lack a stream ID. // lack a stream ID.

View file

@ -36,6 +36,7 @@ const (
QueryKeyChangesPath = "/keyserver/queryKeyChanges" QueryKeyChangesPath = "/keyserver/queryKeyChanges"
QueryOneTimeKeysPath = "/keyserver/queryOneTimeKeys" QueryOneTimeKeysPath = "/keyserver/queryOneTimeKeys"
QueryDeviceMessagesPath = "/keyserver/queryDeviceMessages" QueryDeviceMessagesPath = "/keyserver/queryDeviceMessages"
QuerySignaturesPath = "/keyserver/querySignatures"
) )
// NewKeyServerClient creates a KeyInternalAPI implemented by talking to a HTTP POST API. // NewKeyServerClient creates a KeyInternalAPI implemented by talking to a HTTP POST API.
@ -211,3 +212,20 @@ func (h *httpKeyInternalAPI) PerformUploadDeviceSignatures(
} }
} }
} }
func (h *httpKeyInternalAPI) QuerySignatures(
ctx context.Context,
request *api.QuerySignaturesRequest,
response *api.QuerySignaturesResponse,
) {
span, ctx := opentracing.StartSpanFromContext(ctx, "QuerySignatures")
defer span.Finish()
apiURL := h.apiURL + QuerySignaturesPath
err := httputil.PostJSON(ctx, span, h.httpClient, apiURL, request, response)
if err != nil {
response.Error = &api.KeyError{
Err: err.Error(),
}
}
}

View file

@ -62,7 +62,7 @@ func AddRoutes(internalAPIMux *mux.Router, s api.KeyInternalAPI) {
httputil.MakeInternalAPI("performUploadDeviceKeys", func(req *http.Request) util.JSONResponse { httputil.MakeInternalAPI("performUploadDeviceKeys", func(req *http.Request) util.JSONResponse {
request := api.PerformUploadDeviceKeysRequest{} request := api.PerformUploadDeviceKeysRequest{}
response := api.PerformUploadDeviceKeysResponse{} response := api.PerformUploadDeviceKeysResponse{}
if err := json.NewDecoder(req.Body).Decode(&request.CrossSigningKeys); err != nil { if err := json.NewDecoder(req.Body).Decode(&request); err != nil {
return util.MessageResponse(http.StatusBadRequest, err.Error()) return util.MessageResponse(http.StatusBadRequest, err.Error())
} }
s.PerformUploadDeviceKeys(req.Context(), &request, &response) s.PerformUploadDeviceKeys(req.Context(), &request, &response)
@ -73,7 +73,7 @@ func AddRoutes(internalAPIMux *mux.Router, s api.KeyInternalAPI) {
httputil.MakeInternalAPI("performUploadDeviceSignatures", func(req *http.Request) util.JSONResponse { httputil.MakeInternalAPI("performUploadDeviceSignatures", func(req *http.Request) util.JSONResponse {
request := api.PerformUploadDeviceSignaturesRequest{} request := api.PerformUploadDeviceSignaturesRequest{}
response := api.PerformUploadDeviceSignaturesResponse{} response := api.PerformUploadDeviceSignaturesResponse{}
if err := json.NewDecoder(req.Body).Decode(&request.Signatures); err != nil { if err := json.NewDecoder(req.Body).Decode(&request); err != nil {
return util.MessageResponse(http.StatusBadRequest, err.Error()) return util.MessageResponse(http.StatusBadRequest, err.Error())
} }
s.PerformUploadDeviceSignatures(req.Context(), &request, &response) s.PerformUploadDeviceSignatures(req.Context(), &request, &response)
@ -124,4 +124,15 @@ func AddRoutes(internalAPIMux *mux.Router, s api.KeyInternalAPI) {
return util.JSONResponse{Code: http.StatusOK, JSON: &response} return util.JSONResponse{Code: http.StatusOK, JSON: &response}
}), }),
) )
internalAPIMux.Handle(QuerySignaturesPath,
httputil.MakeInternalAPI("querySignatures", func(req *http.Request) util.JSONResponse {
request := api.QuerySignaturesRequest{}
response := api.QuerySignaturesResponse{}
if err := json.NewDecoder(req.Body).Decode(&request); err != nil {
return util.MessageResponse(http.StatusBadRequest, err.Error())
}
s.QuerySignatures(req.Context(), &request, &response)
return util.JSONResponse{Code: http.StatusOK, JSON: &response}
}),
)
} }

View file

@ -78,7 +78,8 @@ type Database interface {
// MarkDeviceListStale sets the stale bit for this user to isStale. // MarkDeviceListStale sets the stale bit for this user to isStale.
MarkDeviceListStale(ctx context.Context, userID string, isStale bool) error MarkDeviceListStale(ctx context.Context, userID string, isStale bool) error
CrossSigningKeysForUser(ctx context.Context, userID string) (types.CrossSigningKeyMap, error) CrossSigningKeysForUser(ctx context.Context, userID string) (map[gomatrixserverlib.CrossSigningKeyPurpose]gomatrixserverlib.CrossSigningKey, error)
CrossSigningKeysDataForUser(ctx context.Context, userID string) (types.CrossSigningKeyMap, error)
CrossSigningSigsForTarget(ctx context.Context, targetUserID string, targetKeyID gomatrixserverlib.KeyID) (types.CrossSigningSigMap, error) CrossSigningSigsForTarget(ctx context.Context, targetUserID string, targetKeyID gomatrixserverlib.KeyID) (types.CrossSigningSigMap, error)
StoreCrossSigningKeysForUser(ctx context.Context, userID string, keyMap types.CrossSigningKeyMap) error StoreCrossSigningKeysForUser(ctx context.Context, userID string, keyMap types.CrossSigningKeyMap) error

View file

@ -159,7 +159,46 @@ func (d *Database) MarkDeviceListStale(ctx context.Context, userID string, isSta
} }
// CrossSigningKeysForUser returns the latest known cross-signing keys for a user, if any. // CrossSigningKeysForUser returns the latest known cross-signing keys for a user, if any.
func (d *Database) CrossSigningKeysForUser(ctx context.Context, userID string) (types.CrossSigningKeyMap, error) { func (d *Database) CrossSigningKeysForUser(ctx context.Context, userID string) (map[gomatrixserverlib.CrossSigningKeyPurpose]gomatrixserverlib.CrossSigningKey, error) {
keyMap, err := d.CrossSigningKeysTable.SelectCrossSigningKeysForUser(ctx, nil, userID)
if err != nil {
return nil, fmt.Errorf("d.CrossSigningKeysTable.SelectCrossSigningKeysForUser: %w", err)
}
results := map[gomatrixserverlib.CrossSigningKeyPurpose]gomatrixserverlib.CrossSigningKey{}
for purpose, key := range keyMap {
keyID := gomatrixserverlib.KeyID("ed25519:" + key.Encode())
result := gomatrixserverlib.CrossSigningKey{
UserID: userID,
Usage: []gomatrixserverlib.CrossSigningKeyPurpose{purpose},
Keys: map[gomatrixserverlib.KeyID]gomatrixserverlib.Base64Bytes{
keyID: key,
},
}
sigMap, err := d.CrossSigningSigsTable.SelectCrossSigningSigsForTarget(ctx, nil, userID, keyID)
if err != nil {
continue
}
for sigUserID, forSigUserID := range sigMap {
if userID != sigUserID {
continue
}
if result.Signatures == nil {
result.Signatures = map[string]map[gomatrixserverlib.KeyID]gomatrixserverlib.Base64Bytes{}
}
if _, ok := result.Signatures[sigUserID]; !ok {
result.Signatures[sigUserID] = map[gomatrixserverlib.KeyID]gomatrixserverlib.Base64Bytes{}
}
for sigKeyID, sigBytes := range forSigUserID {
result.Signatures[sigUserID][sigKeyID] = sigBytes
}
}
results[purpose] = result
}
return results, nil
}
// CrossSigningKeysForUser returns the latest known cross-signing keys for a user, if any.
func (d *Database) CrossSigningKeysDataForUser(ctx context.Context, userID string) (types.CrossSigningKeyMap, error) {
return d.CrossSigningKeysTable.SelectCrossSigningKeysForUser(ctx, nil, userID) return d.CrossSigningKeysTable.SelectCrossSigningKeysForUser(ctx, nil, userID)
} }

View file

@ -49,6 +49,8 @@ func (k *mockKeyAPI) QueryDeviceMessages(ctx context.Context, req *keyapi.QueryD
} }
func (k *mockKeyAPI) InputDeviceListUpdate(ctx context.Context, req *keyapi.InputDeviceListUpdateRequest, res *keyapi.InputDeviceListUpdateResponse) { func (k *mockKeyAPI) InputDeviceListUpdate(ctx context.Context, req *keyapi.InputDeviceListUpdateRequest, res *keyapi.InputDeviceListUpdateResponse) {
}
func (k *mockKeyAPI) QuerySignatures(ctx context.Context, req *keyapi.QuerySignaturesRequest, res *keyapi.QuerySignaturesResponse) {
} }
type mockRoomserverAPI struct { type mockRoomserverAPI struct {

View file

@ -556,3 +556,4 @@ Presence change reports an event to myself
Can upload self-signing keys Can upload self-signing keys
Fails to upload self-signing keys with no auth Fails to upload self-signing keys with no auth
Fails to upload self-signing key without master key Fails to upload self-signing key without master key
can fetch self-signing keys over federation