mirror of https://github.com/mautrix/go.git
192 lines
5.5 KiB
Go
192 lines
5.5 KiB
Go
// Copyright (c) 2024 Tulir Asokan
|
|
//
|
|
// 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 (
|
|
"context"
|
|
"time"
|
|
|
|
"go.mau.fi/util/random"
|
|
|
|
"maunium.net/go/mautrix/event"
|
|
"maunium.net/go/mautrix/id"
|
|
)
|
|
|
|
// Callback function to process a received secret.
|
|
//
|
|
// Returning true or an error will immediately return from the wait loop, returning false will continue waiting for new responses.
|
|
type SecretReceiverFunc func(string) (bool, error)
|
|
|
|
func (mach *OlmMachine) GetOrRequestSecret(ctx context.Context, name id.Secret, receiver SecretReceiverFunc, timeout time.Duration) (err error) {
|
|
ctx, cancel := context.WithTimeout(ctx, timeout)
|
|
defer cancel()
|
|
|
|
// always offer our stored secret first, if any
|
|
secret, err := mach.CryptoStore.GetSecret(ctx, name)
|
|
if err != nil {
|
|
return err
|
|
} else if secret != "" {
|
|
if ok, err := receiver(secret); ok || err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
requestID, secretChan := random.String(64), make(chan string, 5)
|
|
mach.secretLock.Lock()
|
|
mach.secretListeners[requestID] = secretChan
|
|
mach.secretLock.Unlock()
|
|
defer func() {
|
|
mach.secretLock.Lock()
|
|
delete(mach.secretListeners, requestID)
|
|
mach.secretLock.Unlock()
|
|
}()
|
|
|
|
// request secret from any device
|
|
err = mach.sendToOneDevice(ctx, mach.Client.UserID, id.DeviceID("*"), event.ToDeviceSecretRequest, &event.SecretRequestEventContent{
|
|
Action: event.SecretRequestRequest,
|
|
RequestID: requestID,
|
|
Name: name,
|
|
RequestingDeviceID: mach.Client.DeviceID,
|
|
})
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
// best effort cancel request from all devices when returning
|
|
defer func() {
|
|
go mach.sendToOneDevice(context.Background(), mach.Client.UserID, id.DeviceID("*"), event.ToDeviceSecretRequest, &event.SecretRequestEventContent{
|
|
Action: event.SecretRequestCancellation,
|
|
RequestID: requestID,
|
|
RequestingDeviceID: mach.Client.DeviceID,
|
|
})
|
|
}()
|
|
|
|
for {
|
|
select {
|
|
case <-ctx.Done():
|
|
return ctx.Err()
|
|
case secret = <-secretChan:
|
|
if ok, err := receiver(secret); err != nil {
|
|
return err
|
|
} else if ok {
|
|
return mach.CryptoStore.PutSecret(ctx, name, secret)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (mach *OlmMachine) HandleSecretRequest(ctx context.Context, userID id.UserID, content *event.SecretRequestEventContent) {
|
|
log := mach.machOrContextLog(ctx).With().
|
|
Stringer("user_id", userID).
|
|
Stringer("requesting_device_id", content.RequestingDeviceID).
|
|
Stringer("action", content.Action).
|
|
Str("request_id", content.RequestID).
|
|
Stringer("secret", content.Name).
|
|
Logger()
|
|
|
|
log.Trace().Msg("Handling secret request")
|
|
|
|
if content.Action == event.SecretRequestCancellation {
|
|
log.Trace().Msg("Secret request cancellation is unimplemented, ignoring")
|
|
return
|
|
} else if content.Action != event.SecretRequestRequest {
|
|
log.Warn().Msg("Ignoring unknown secret request action")
|
|
return
|
|
}
|
|
|
|
// immediately ignore requests from other users
|
|
if userID != mach.Client.UserID || content.RequestingDeviceID == "" {
|
|
log.Debug().Msg("Secret request was not from our own device, ignoring")
|
|
return
|
|
}
|
|
|
|
if content.RequestingDeviceID == mach.Client.DeviceID {
|
|
log.Debug().Msg("Secret request was from this device, ignoring")
|
|
return
|
|
}
|
|
|
|
keys, err := mach.CryptoStore.GetCrossSigningKeys(ctx, mach.Client.UserID)
|
|
if err != nil {
|
|
log.Err(err).Msg("Failed to get cross signing keys from crypto store")
|
|
return
|
|
}
|
|
|
|
crossSigningKey, ok := keys[id.XSUsageSelfSigning]
|
|
if !ok {
|
|
log.Warn().Msg("Couldn't find self signing key to verify requesting device")
|
|
return
|
|
}
|
|
|
|
device, err := mach.GetOrFetchDevice(ctx, mach.Client.UserID, content.RequestingDeviceID)
|
|
if err != nil {
|
|
log.Err(err).Msg("Failed to get or fetch requesting device")
|
|
return
|
|
}
|
|
|
|
verified, err := mach.CryptoStore.IsKeySignedBy(ctx, mach.Client.UserID, device.SigningKey, mach.Client.UserID, crossSigningKey.Key)
|
|
if err != nil {
|
|
log.Err(err).Msg("Failed to check if requesting device is verified")
|
|
return
|
|
}
|
|
|
|
if !verified {
|
|
log.Warn().Msg("Requesting device is not verified, ignoring request")
|
|
return
|
|
}
|
|
|
|
secret, err := mach.CryptoStore.GetSecret(ctx, content.Name)
|
|
if err != nil {
|
|
log.Err(err).Msg("Failed to get secret from store")
|
|
return
|
|
} else if secret != "" {
|
|
log.Debug().Msg("Responding to secret request")
|
|
mach.SendEncryptedToDevice(ctx, device, event.ToDeviceSecretSend, event.Content{
|
|
Parsed: event.SecretSendEventContent{
|
|
RequestID: content.RequestID,
|
|
Secret: secret,
|
|
},
|
|
})
|
|
} else {
|
|
log.Debug().Msg("No stored secret found, secret request ignored")
|
|
}
|
|
}
|
|
|
|
func (mach *OlmMachine) receiveSecret(ctx context.Context, evt *DecryptedOlmEvent, content *event.SecretSendEventContent) {
|
|
log := mach.machOrContextLog(ctx).With().
|
|
Stringer("sender", evt.Sender).
|
|
Stringer("sender_device", evt.SenderDevice).
|
|
Str("request_id", content.RequestID).
|
|
Logger()
|
|
|
|
log.Trace().Msg("Handling secret send request")
|
|
|
|
// immediately ignore secrets from other users
|
|
if evt.Sender != mach.Client.UserID {
|
|
log.Warn().Msg("Secret send was not from our own device")
|
|
return
|
|
} else if content.Secret == "" {
|
|
log.Warn().Msg("We were sent an empty secret")
|
|
return
|
|
}
|
|
|
|
mach.secretLock.Lock()
|
|
secretChan := mach.secretListeners[content.RequestID]
|
|
mach.secretLock.Unlock()
|
|
|
|
if secretChan == nil {
|
|
log.Warn().Msg("We were sent a secret we didn't request")
|
|
return
|
|
}
|
|
|
|
// secret channel is buffered and we don't want to block
|
|
// at worst we drop _some_ of the responses
|
|
select {
|
|
case secretChan <- content.Secret:
|
|
default:
|
|
}
|
|
}
|