Checking of trust for devices and users based on cross-signing

signatures

Signed-off-by: Nikos Filippakis <me@nfil.dev>
This commit is contained in:
Nikos Filippakis 2020-08-22 16:10:34 +02:00
commit 50bb209da7
11 changed files with 292 additions and 34 deletions

View file

@ -64,7 +64,7 @@ func (account *OlmAccount) getInitialKeys(userID id.UserID, deviceID id.DeviceID
deviceKeys.Signatures = mautrix.Signatures{
userID: {
id.NewDeviceKeyID(id.KeyAlgorithmEd25519, deviceID): signature,
id.NewKeyID(id.KeyAlgorithmEd25519, deviceID.String()): signature,
},
}
return deviceKeys
@ -83,7 +83,7 @@ func (account *OlmAccount) getOneTimeKeys(userID id.UserID, deviceID id.DeviceID
signature, _ := account.Internal.SignJSON(key)
key.Signatures = mautrix.Signatures{
userID: {
id.NewDeviceKeyID(id.KeyAlgorithmEd25519, deviceID): signature,
id.NewKeyID(id.KeyAlgorithmEd25519, deviceID.String()): signature,
},
}
key.IsSigned = true

View file

@ -54,12 +54,81 @@ func (mach *OlmMachine) storeCrossSigningKeys(crossSigningKeys map[id.UserID]mau
// IsDeviceTrusted returns whether a device has been determined to be trusted either through verification or cross-signing.
func (mach *OlmMachine) IsDeviceTrusted(device *DeviceIdentity) bool {
// TODO if not verified / blacklisted then check via cross-signing to determine trust
return device.Trust == TrustStateVerified
userID := device.UserID
if device.Trust == TrustStateVerified {
return true
} else if device.Trust == TrustStateBlacklisted {
return false
}
if !mach.IsUserTrusted(userID) {
return false
}
theirKeys, err := mach.CryptoStore.GetCrossSigningKeys(userID)
if err != nil {
mach.Log.Error("Error retrieving cross-singing key of user %v from database: %v", userID, err)
return false
}
theirMSK, ok := theirKeys[id.XSUsageMaster]
if !ok {
mach.Log.Error("Master key of user %v not found", userID)
return false
}
theirSSK, ok := theirKeys[id.XSUsageSelfSigning]
if !ok {
mach.Log.Error("Self-signing key of user %v not found", userID)
return false
}
sskSigExists, err := mach.CryptoStore.IsKeySignedBy(userID, theirSSK, userID, theirMSK)
if err != nil {
mach.Log.Error("Error retrieving cross-singing signatures for master key of user %v from database: %v", userID, err)
return false
}
if !sskSigExists {
mach.Log.Warn("Self-signing key of user %v is not signed by their master key", userID)
return false
}
deviceSigExists, err := mach.CryptoStore.IsKeySignedBy(userID, device.SigningKey, userID, theirSSK)
if err != nil {
mach.Log.Error("Error retrieving cross-singing signatures for master key of user %v from database: %v", userID, err)
return false
}
return deviceSigExists
}
// IsUserTrusted returns whether a user has been determined to be trusted by our user-signing key having signed their master key.
func (mach *OlmMachine) IsUserTrusted(device *DeviceIdentity) bool {
// TODO implement
return false
// In the case the user ID is our own and we have successfully retrieved our cross-signing keys, we trust our own user.
func (mach *OlmMachine) IsUserTrusted(userID id.UserID) bool {
if mach.crossSigningKeys == nil {
return false
}
if userID == mach.Client.UserID {
return true
}
// first we verify our user-signing key
sskSigs, err := mach.CryptoStore.GetSignaturesForKeyBy(mach.Client.UserID, mach.crossSigningKeys.UserSigningKey.PublicKey, mach.Client.UserID)
if err != nil {
mach.Log.Error("Error retrieving our self-singing key signatures: %v", err)
return false
}
if _, ok := sskSigs[mach.crossSigningKeys.MasterKey.PublicKey]; !ok {
// our user-signing key was not signed by our master key
return false
}
theirKeys, err := mach.CryptoStore.GetCrossSigningKeys(userID)
if err != nil {
mach.Log.Error("Error retrieving cross-singing key of user %v from database: %v", userID, err)
return false
}
theirMskKey, ok := theirKeys[id.XSUsageMaster]
if !ok {
mach.Log.Error("Master key of user %v not found", userID)
return false
}
sigExists, err := mach.CryptoStore.IsKeySignedBy(userID, theirMskKey, mach.Client.UserID, mach.crossSigningKeys.UserSigningKey.PublicKey)
if err != nil {
mach.Log.Error("Error retrieving cross-singing signatures for master key of user %v from database: %v", userID, err)
return false
}
return sigExists
}

145
crypto/cross_sign_test.go Normal file
View file

@ -0,0 +1,145 @@
// Copyright (c) 2020 Nikos Filippakis
//
// 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/.
package crypto
import (
"database/sql"
"testing"
"maunium.net/go/mautrix"
"maunium.net/go/mautrix/crypto/olm"
sqlUpgrade "maunium.net/go/mautrix/crypto/sql_store_upgrade"
"maunium.net/go/mautrix/id"
)
func getOlmMachine(t *testing.T) *OlmMachine {
db, err := sql.Open("sqlite3", ":memory:?_busy_timeout=5000")
if err != nil {
t.Fatalf("Error opening db: %v", err)
}
sqlUpgrade.Upgrade(db, "sqlite3")
sqlStore := NewSQLCryptoStore(db, "sqlite3", "accid", id.DeviceID("dev"), []byte("test"), emptyLogger{})
userID := id.UserID("@mautrix")
mk, _ := olm.NewPkSigning()
ssk, _ := olm.NewPkSigning()
usk, _ := olm.NewPkSigning()
sqlStore.PutCrossSigningKey(userID, id.XSUsageMaster, mk.PublicKey)
sqlStore.PutCrossSigningKey(userID, id.XSUsageSelfSigning, ssk.PublicKey)
sqlStore.PutCrossSigningKey(userID, id.XSUsageUserSigning, usk.PublicKey)
return &OlmMachine{
CryptoStore: sqlStore,
crossSigningKeys: &CrossSigningKeysCache{
MasterKey: mk,
SelfSigningKey: ssk,
UserSigningKey: usk,
},
Client: &mautrix.Client{
UserID: userID,
},
Log: emptyLogger{},
}
}
func TestTrustOwnDevice(t *testing.T) {
m := getOlmMachine(t)
ownDevice := &DeviceIdentity{
UserID: m.Client.UserID,
DeviceID: "device",
SigningKey: id.Ed25519("deviceKey"),
}
if m.IsDeviceTrusted(ownDevice) {
t.Error("Own device trusted while it shouldn't be")
}
m.CryptoStore.PutSignature(ownDevice.UserID, m.crossSigningKeys.SelfSigningKey.PublicKey,
ownDevice.UserID, m.crossSigningKeys.MasterKey.PublicKey, "sig1")
m.CryptoStore.PutSignature(ownDevice.UserID, ownDevice.SigningKey,
ownDevice.UserID, m.crossSigningKeys.SelfSigningKey.PublicKey, "sig2")
if !m.IsUserTrusted(ownDevice.UserID) {
t.Error("Own user not trusted while they should be")
}
if !m.IsDeviceTrusted(ownDevice) {
t.Error("Own device not trusted while it should be")
}
}
func TestTrustOtherUser(t *testing.T) {
m := getOlmMachine(t)
otherUser := id.UserID("@user")
if m.IsUserTrusted(otherUser) {
t.Error("Other user trusted while they shouldn't be")
}
theirMasterKey, _ := olm.NewPkSigning()
m.CryptoStore.PutCrossSigningKey(otherUser, id.XSUsageMaster, theirMasterKey.PublicKey)
m.CryptoStore.PutSignature(m.Client.UserID, m.crossSigningKeys.UserSigningKey.PublicKey,
m.Client.UserID, m.crossSigningKeys.MasterKey.PublicKey, "sig1")
// sign them with self-signing instead of user-signing key
m.CryptoStore.PutSignature(otherUser, theirMasterKey.PublicKey,
m.Client.UserID, m.crossSigningKeys.SelfSigningKey.PublicKey, "invalid_sig")
if m.IsUserTrusted(otherUser) {
t.Error("Other user trusted before their master key has been signed with our user-signing key")
}
m.CryptoStore.PutSignature(otherUser, theirMasterKey.PublicKey,
m.Client.UserID, m.crossSigningKeys.UserSigningKey.PublicKey, "sig2")
if !m.IsUserTrusted(otherUser) {
t.Error("Other user not trusted while they should be")
}
}
func TestTrustOtherDevice(t *testing.T) {
m := getOlmMachine(t)
otherUser := id.UserID("@user")
theirDevice := &DeviceIdentity{
UserID: otherUser,
DeviceID: "theirDevice",
SigningKey: id.Ed25519("theirDeviceKey"),
}
if m.IsUserTrusted(otherUser) {
t.Error("Other user trusted while they shouldn't be")
}
if m.IsDeviceTrusted(theirDevice) {
t.Error("Other device trusted while it shouldn't be")
}
theirMasterKey, _ := olm.NewPkSigning()
m.CryptoStore.PutCrossSigningKey(otherUser, id.XSUsageMaster, theirMasterKey.PublicKey)
theirSSK, _ := olm.NewPkSigning()
m.CryptoStore.PutCrossSigningKey(otherUser, id.XSUsageSelfSigning, theirSSK.PublicKey)
m.CryptoStore.PutSignature(m.Client.UserID, m.crossSigningKeys.UserSigningKey.PublicKey,
m.Client.UserID, m.crossSigningKeys.MasterKey.PublicKey, "sig1")
m.CryptoStore.PutSignature(otherUser, theirMasterKey.PublicKey,
m.Client.UserID, m.crossSigningKeys.UserSigningKey.PublicKey, "sig2")
if !m.IsUserTrusted(otherUser) {
t.Error("Other user not trusted while they should be")
}
m.CryptoStore.PutSignature(otherUser, theirSSK.PublicKey,
otherUser, theirMasterKey.PublicKey, "sig3")
if m.IsDeviceTrusted(theirDevice) {
t.Error("Other device trusted before it has been signed with user's SSK")
}
m.CryptoStore.PutSignature(otherUser, theirDevice.SigningKey,
otherUser, theirSSK.PublicKey, "sig4")
if !m.IsDeviceTrusted(theirDevice) {
t.Error("Other device not trusted while it should be")
}
}

View file

@ -61,7 +61,7 @@ func (mach *OlmMachine) DecryptMegolmEvent(evt *event.Event) (*event.Event, erro
// We don't want to throw these errors as the message can still be decrypted.
mach.Log.Debug("Failed to get device %s/%s to verify session %s: %v", evt.Sender, content.DeviceID, sess.ID(), err)
// TODO maybe store the info that the device is deleted?
} else if device.Trust == TrustStateVerified && len(sess.ForwardingChains) == 0 { // For some reason, matrix-nio had a comment saying not to events decrypted using a forwarded key as verified.
} else if mach.IsDeviceTrusted(device) && len(sess.ForwardingChains) == 0 { // For some reason, matrix-nio had a comment saying not to events decrypted using a forwarded key as verified.
if device.SigningKey != sess.SigningKey || device.IdentityKey != content.SenderKey {
return nil, DeviceKeyMismatch
}

View file

@ -78,7 +78,22 @@ func (mach *OlmMachine) fetchKeys(users []id.UserID, sinceToken string, includeU
newDevices[deviceID] = newDevice
for signerUserID, signerKeys := range deviceKeys.Signatures {
for signerKey, signature := range signerKeys {
if signKey, ok := deviceKeys.Keys[signerKey]; ok {
// verify and save self-signing key signature for each device
if selfSignKeys, ok := resp.SelfSigningKeys[signerUserID]; ok {
for _, pubKey := range selfSignKeys.Keys {
if verified, err := olm.VerifySignatureJSON(deviceKeys, signerUserID, pubKey.String(), pubKey); verified {
if signKey, ok := deviceKeys.Keys[id.DeviceKeyID(signerKey)]; ok {
signature := deviceKeys.Signatures[signerUserID][id.NewKeyID(id.KeyAlgorithmEd25519, pubKey.String())]
mach.Log.Trace("Verified self-signing signature for device %v: `%v`", deviceID, signature)
mach.CryptoStore.PutSignature(userID, id.Ed25519(signKey), signerUserID, pubKey, signature)
}
} else {
mach.Log.Warn("Error verifying device self-signing signature: %v", err)
}
}
}
// save signature of device made by its own device signing key
if signKey, ok := deviceKeys.Keys[id.DeviceKeyID(signerKey)]; ok {
mach.CryptoStore.PutSignature(userID, id.Ed25519(signKey), signerUserID, id.Ed25519(signKey), signature)
}
}

View file

@ -214,7 +214,7 @@ func (mach *OlmMachine) encryptGroupSessionForUser(session *OutboundGroupSession
Reason: "Device is blacklisted",
}}
session.Users[userKey] = OGSIgnored
} else if !mach.AllowUnverifiedDevices && device.Trust == TrustStateUnset {
} else if !mach.AllowUnverifiedDevices && !mach.IsDeviceTrusted(device) {
mach.Log.Debug("Not encrypting group session %s for %s of %s: device is not verified", session.ID(), deviceID, userID)
withheld[deviceID] = &event.Content{Parsed: &event.RoomKeyWithheldEventContent{
RoomID: session.RoomID,

View file

@ -169,7 +169,7 @@ func (mach *OlmMachine) defaultAllowKeyShare(device *DeviceIdentity, _ event.Req
} else if device.Trust == TrustStateBlacklisted {
mach.Log.Debug("Ignoring key request from blacklisted device %s", device.DeviceID)
return &KeyShareRejectBlacklisted
} else if device.Trust == TrustStateVerified {
} else if mach.IsDeviceTrusted(device) {
mach.Log.Debug("Accepting key request from verified device %s", device.DeviceID)
return nil
} else if mach.ShareKeysToUnverifiedDevices {

View file

@ -8,12 +8,14 @@ import "C"
import (
"crypto/rand"
"unsafe"
"maunium.net/go/mautrix/id"
)
// PkSigning stores a key pair for signing messages.
type PkSigning struct {
int *C.OlmPkSigning
PublicKey string
PublicKey id.Ed25519
Seed []byte
}
@ -55,7 +57,7 @@ func NewPkSigningFromSeed(seed []byte) (*PkSigning, error) {
unsafe.Pointer(&seed[0]), C.size_t(len(seed))) == errorVal() {
return nil, p.lastError()
}
p.PublicKey = string(pubKey)
p.PublicKey = id.Ed25519(pubKey)
p.Seed = seed
return p, nil
}

View file

@ -588,7 +588,7 @@ func (store *SQLCryptoStore) PutCrossSigningKey(userID id.UserID, usage id.Cross
err = fmt.Errorf("unsupported dialect %s", store.Dialect)
}
if err != nil {
store.Log.Warn("Failed to store device: %v", err)
store.Log.Warn("Failed to store cross-signing key: %v", err)
}
return nil
}
@ -630,33 +630,37 @@ func (store *SQLCryptoStore) PutSignature(signedUserID id.UserID, signedKey id.E
err = fmt.Errorf("unsupported dialect %s", store.Dialect)
}
if err != nil {
store.Log.Warn("Failed to store device: %v", err)
store.Log.Warn("Failed to store signature: %v", err)
}
return nil
}
// GetSignaturesForKey retrieves the stored signatures for a given cross-signing or device key.
func (store *SQLCryptoStore) GetSignaturesForKey(userID id.UserID, key id.Ed25519) (map[id.UserID]map[id.Ed25519]string, error) {
rows, err := store.DB.Query("SELECT signer_user_id, signer_key, signature FROM crypto_cross_signing_signatures WHERE signed_user_id=$1 AND signed_key=$2", userID, key)
// GetSignaturesForKeyBy retrieves the stored signatures for a given cross-signing or device key, by the given signer.
func (store *SQLCryptoStore) GetSignaturesForKeyBy(userID id.UserID, key id.Ed25519, signerID id.UserID) (map[id.Ed25519]string, error) {
rows, err := store.DB.Query("SELECT signer_key, signature FROM crypto_cross_signing_signatures WHERE signed_user_id=$1 AND signed_key=$2 AND signer_user_id=$3", userID, key, signerID)
if err != nil {
return nil, err
}
data := make(map[id.UserID]map[id.Ed25519]string)
data := make(map[id.Ed25519]string)
for rows.Next() {
var signerUserID id.UserID
var signerKey id.Ed25519
var signature string
err := rows.Scan(&signerUserID, &signerKey, &signature)
err := rows.Scan(&signerKey, &signature)
if err != nil {
return nil, err
}
signerKeys, ok := data[signerUserID]
if !ok {
signerKeys = make(map[id.Ed25519]string)
data[signerUserID] = signerKeys
}
signerKeys[signerKey] = signature
data[signerKey] = signature
}
return data, nil
}
// IsKeySignedBy returns whether a cross-signing or device key is signed by the given signer.
func (store *SQLCryptoStore) IsKeySignedBy(userID id.UserID, key id.Ed25519, signerID id.UserID, signerKey id.Ed25519) (bool, error) {
sigs, err := store.GetSignaturesForKeyBy(userID, key, signerID)
if err != nil {
return false, err
}
_, ok := sigs[signerKey]
return ok, nil
}

View file

@ -147,8 +147,10 @@ type Store interface {
GetCrossSigningKeys(id.UserID) (map[id.CrossSigningUsage]id.Ed25519, error)
// PutSignature stores a signature of a cross-signing or device key along with the signer's user ID and key.
PutSignature(id.UserID, id.Ed25519, id.UserID, id.Ed25519, string) error
// GetSignatures retrieves the stored signatures for a given cross-signing or device key.
GetSignaturesForKey(id.UserID, id.Ed25519) (map[id.UserID]map[id.Ed25519]string, error)
// GetSignaturesForKeyBy returns the signatures for a cross-signing or device key by the given signer.
GetSignaturesForKeyBy(id.UserID, id.Ed25519, id.UserID) (map[id.Ed25519]string, error)
// IsKeySignedBy returns whether a cross-signing or device key is signed by the given signer.
IsKeySignedBy(id.UserID, id.Ed25519, id.UserID, id.Ed25519) (bool, error)
}
type messageIndexKey struct {
@ -511,7 +513,11 @@ func (gs *GobStore) PutCrossSigningKey(userID id.UserID, usage id.CrossSigningUs
func (gs *GobStore) GetCrossSigningKeys(userID id.UserID) (map[id.CrossSigningUsage]id.Ed25519, error) {
gs.lock.RLock()
defer gs.lock.RUnlock()
return gs.CrossSigningKeys[userID], nil
keys, ok := gs.CrossSigningKeys[userID]
if !ok {
return map[id.CrossSigningUsage]id.Ed25519{}, nil
}
return keys, nil
}
func (gs *GobStore) PutSignature(signedUserID id.UserID, signedKey id.Ed25519, signerUserID id.UserID, signerKey id.Ed25519, signature string) error {
@ -537,12 +543,29 @@ func (gs *GobStore) PutSignature(signedUserID id.UserID, signedKey id.Ed25519, s
return err
}
func (gs *GobStore) GetSignaturesForKey(userID id.UserID, key id.Ed25519) (map[id.UserID]map[id.Ed25519]string, error) {
func (gs *GobStore) GetSignaturesForKeyBy(userID id.UserID, key id.Ed25519, signerID id.UserID) (map[id.Ed25519]string, error) {
gs.lock.RLock()
defer gs.lock.RUnlock()
userKeys, ok := gs.KeySignatures[userID]
if !ok {
return nil, nil
return map[id.Ed25519]string{}, nil
}
return userKeys[key], nil
sigsForKey, ok := userKeys[key]
if !ok {
return map[id.Ed25519]string{}, nil
}
sigsBySigner, ok := sigsForKey[signerID]
if !ok {
return map[id.Ed25519]string{}, nil
}
return sigsBySigner, nil
}
func (gs *GobStore) IsKeySignedBy(userID id.UserID, key id.Ed25519, signerID id.UserID, signerKey id.Ed25519) (bool, error) {
sigs, err := gs.GetSignaturesForKeyBy(userID, key, signerID)
if err != nil {
return false, err
}
_, ok := sigs[signerKey]
return ok, nil
}

View file

@ -208,7 +208,7 @@ func (km KeyMap) GetCurve25519(deviceID id.DeviceID) id.Curve25519 {
return id.Curve25519(val)
}
type Signatures map[id.UserID]map[id.DeviceKeyID]string
type Signatures map[id.UserID]map[id.KeyID]string
type ReqQueryKeys struct {
DeviceKeys DeviceKeysRequest `json:"device_keys"`