forked from jshiffer/matterbridge
273 lines
9.4 KiB
Go
273 lines
9.4 KiB
Go
// Package session provides the methods necessary to build sessions
|
|
package session
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"go.mau.fi/libsignal/ecc"
|
|
"go.mau.fi/libsignal/keys/prekey"
|
|
"go.mau.fi/libsignal/logger"
|
|
"go.mau.fi/libsignal/protocol"
|
|
"go.mau.fi/libsignal/ratchet"
|
|
"go.mau.fi/libsignal/serialize"
|
|
"go.mau.fi/libsignal/signalerror"
|
|
"go.mau.fi/libsignal/state/record"
|
|
"go.mau.fi/libsignal/state/store"
|
|
"go.mau.fi/libsignal/util/medium"
|
|
"go.mau.fi/libsignal/util/optional"
|
|
)
|
|
|
|
// NewBuilder constructs a session builder.
|
|
func NewBuilder(sessionStore store.Session, preKeyStore store.PreKey,
|
|
signedStore store.SignedPreKey, identityStore store.IdentityKey,
|
|
remoteAddress *protocol.SignalAddress, serializer *serialize.Serializer) *Builder {
|
|
|
|
builder := Builder{
|
|
sessionStore: sessionStore,
|
|
preKeyStore: preKeyStore,
|
|
signedPreKeyStore: signedStore,
|
|
identityKeyStore: identityStore,
|
|
remoteAddress: remoteAddress,
|
|
serializer: serializer,
|
|
}
|
|
|
|
return &builder
|
|
}
|
|
|
|
// NewBuilderFromSignal Store constructs a session builder using a
|
|
// SignalProtocol Store.
|
|
func NewBuilderFromSignal(signalStore store.SignalProtocol,
|
|
remoteAddress *protocol.SignalAddress, serializer *serialize.Serializer) *Builder {
|
|
|
|
builder := Builder{
|
|
sessionStore: signalStore,
|
|
preKeyStore: signalStore,
|
|
signedPreKeyStore: signalStore,
|
|
identityKeyStore: signalStore,
|
|
remoteAddress: remoteAddress,
|
|
serializer: serializer,
|
|
}
|
|
|
|
return &builder
|
|
}
|
|
|
|
// Builder is responsible for setting up encrypted sessions.
|
|
// Once a session has been established, SessionCipher can be
|
|
// used to encrypt/decrypt messages in that session.
|
|
//
|
|
// Sessions are built from one of three different vectors:
|
|
// * PreKeyBundle retrieved from a server.
|
|
// * PreKeySignalMessage received from a client.
|
|
// * KeyExchangeMessage sent to or received from a client.
|
|
//
|
|
// Sessions are constructed per recipientId + deviceId tuple.
|
|
// Remote logical users are identified by their recipientId,
|
|
// and each logical recipientId can have multiple physical
|
|
// devices.
|
|
type Builder struct {
|
|
sessionStore store.Session
|
|
preKeyStore store.PreKey
|
|
signedPreKeyStore store.SignedPreKey
|
|
identityKeyStore store.IdentityKey
|
|
remoteAddress *protocol.SignalAddress
|
|
serializer *serialize.Serializer
|
|
}
|
|
|
|
// Process builds a new session from a session record and pre
|
|
// key signal message.
|
|
func (b *Builder) Process(sessionRecord *record.Session, message *protocol.PreKeySignalMessage) (unsignedPreKeyID *optional.Uint32, err error) {
|
|
|
|
// Check to see if the keys are trusted.
|
|
theirIdentityKey := message.IdentityKey()
|
|
if !(b.identityKeyStore.IsTrustedIdentity(b.remoteAddress, theirIdentityKey)) {
|
|
return nil, signalerror.ErrUntrustedIdentity
|
|
}
|
|
|
|
// Use version 3 of the signal/axolotl protocol.
|
|
unsignedPreKeyID, err = b.processV3(sessionRecord, message)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Save the identity key to our identity store.
|
|
b.identityKeyStore.SaveIdentity(b.remoteAddress, theirIdentityKey)
|
|
|
|
// Return the unsignedPreKeyID
|
|
return unsignedPreKeyID, nil
|
|
}
|
|
|
|
// ProcessV3 builds a new session from a session record and pre key
|
|
// signal message. After a session is constructed in this way, the embedded
|
|
// SignalMessage can be decrypted.
|
|
func (b *Builder) processV3(sessionRecord *record.Session,
|
|
message *protocol.PreKeySignalMessage) (unsignedPreKeyID *optional.Uint32, err error) {
|
|
|
|
logger.Debug("Processing message with PreKeyID: ", message.PreKeyID())
|
|
// Check to see if we've already set up a session for this V3 message.
|
|
sessionExists := sessionRecord.HasSessionState(
|
|
message.MessageVersion(),
|
|
message.BaseKey().Serialize(),
|
|
)
|
|
if sessionExists {
|
|
logger.Debug("We've already setup a session for this V3 message, letting bundled message fall through...")
|
|
return optional.NewEmptyUint32(), nil
|
|
}
|
|
|
|
// Load our signed prekey from our signed prekey store.
|
|
ourSignedPreKeyRecord := b.signedPreKeyStore.LoadSignedPreKey(message.SignedPreKeyID())
|
|
if ourSignedPreKeyRecord == nil {
|
|
return nil, fmt.Errorf("%w with ID %d", signalerror.ErrNoSignedPreKey, message.SignedPreKeyID())
|
|
}
|
|
ourSignedPreKey := ourSignedPreKeyRecord.KeyPair()
|
|
|
|
// Build the parameters of the session.
|
|
parameters := ratchet.NewEmptyReceiverParameters()
|
|
parameters.SetTheirBaseKey(message.BaseKey())
|
|
parameters.SetTheirIdentityKey(message.IdentityKey())
|
|
parameters.SetOurIdentityKeyPair(b.identityKeyStore.GetIdentityKeyPair())
|
|
parameters.SetOurSignedPreKey(ourSignedPreKey)
|
|
parameters.SetOurRatchetKey(ourSignedPreKey)
|
|
|
|
// Set our one time pre key with the one from our prekey store
|
|
// if the message contains a valid pre key id
|
|
if !message.PreKeyID().IsEmpty {
|
|
oneTimePreKey := b.preKeyStore.LoadPreKey(message.PreKeyID().Value)
|
|
if oneTimePreKey == nil {
|
|
return nil, fmt.Errorf("%w with ID %d", signalerror.ErrNoOneTimeKeyFound, message.PreKeyID().Value)
|
|
}
|
|
parameters.SetOurOneTimePreKey(oneTimePreKey.KeyPair())
|
|
} else {
|
|
parameters.SetOurOneTimePreKey(nil)
|
|
}
|
|
|
|
// If this is a fresh record, archive our current state.
|
|
if !sessionRecord.IsFresh() {
|
|
sessionRecord.ArchiveCurrentState()
|
|
}
|
|
|
|
///////// Initialize our session /////////
|
|
sessionState := sessionRecord.SessionState()
|
|
derivedKeys, sessionErr := ratchet.CalculateReceiverSession(parameters)
|
|
if sessionErr != nil {
|
|
return nil, sessionErr
|
|
}
|
|
sessionState.SetVersion(protocol.CurrentVersion)
|
|
sessionState.SetRemoteIdentityKey(parameters.TheirIdentityKey())
|
|
sessionState.SetLocalIdentityKey(parameters.OurIdentityKeyPair().PublicKey())
|
|
sessionState.SetSenderChain(parameters.OurRatchetKey(), derivedKeys.ChainKey)
|
|
sessionState.SetRootKey(derivedKeys.RootKey)
|
|
|
|
// Set the session's registration ids and base key
|
|
sessionState.SetLocalRegistrationID(b.identityKeyStore.GetLocalRegistrationId())
|
|
sessionState.SetRemoteRegistrationID(message.RegistrationID())
|
|
sessionState.SetSenderBaseKey(message.BaseKey().Serialize())
|
|
|
|
// Remove the PreKey from our store and return the message prekey id if it is valid.
|
|
if message.PreKeyID() != nil && message.PreKeyID().Value != medium.MaxValue {
|
|
return message.PreKeyID(), nil
|
|
}
|
|
return nil, nil
|
|
}
|
|
|
|
// ProcessBundle builds a new session from a PreKeyBundle retrieved
|
|
// from a server.
|
|
func (b *Builder) ProcessBundle(preKey *prekey.Bundle) error {
|
|
// Check to see if the keys are trusted.
|
|
if !(b.identityKeyStore.IsTrustedIdentity(b.remoteAddress, preKey.IdentityKey())) {
|
|
return signalerror.ErrUntrustedIdentity
|
|
}
|
|
|
|
// Check to see if the bundle has a signed pre key.
|
|
if preKey.SignedPreKey() == nil {
|
|
return signalerror.ErrNoSignedPreKey
|
|
}
|
|
|
|
// Verify the signature of the pre key
|
|
preKeyPublic := preKey.IdentityKey().PublicKey()
|
|
preKeyBytes := preKey.SignedPreKey().Serialize()
|
|
preKeySignature := preKey.SignedPreKeySignature()
|
|
if !ecc.VerifySignature(preKeyPublic, preKeyBytes, preKeySignature) {
|
|
return signalerror.ErrInvalidSignature
|
|
}
|
|
|
|
// Load our session and generate keys.
|
|
sessionRecord := b.sessionStore.LoadSession(b.remoteAddress)
|
|
ourBaseKey, err := ecc.GenerateKeyPair()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
theirSignedPreKey := preKey.SignedPreKey()
|
|
theirOneTimePreKey := preKey.PreKey()
|
|
theirOneTimePreKeyID := preKey.PreKeyID()
|
|
|
|
// Build the parameters of the session
|
|
parameters := ratchet.NewEmptySenderParameters()
|
|
parameters.SetOurBaseKey(ourBaseKey)
|
|
parameters.SetOurIdentityKey(b.identityKeyStore.GetIdentityKeyPair())
|
|
parameters.SetTheirIdentityKey(preKey.IdentityKey())
|
|
parameters.SetTheirSignedPreKey(theirSignedPreKey)
|
|
parameters.SetTheirRatchetKey(theirSignedPreKey)
|
|
parameters.SetTheirOneTimePreKey(theirOneTimePreKey)
|
|
|
|
// If this is a fresh record, archive our current state.
|
|
if !sessionRecord.IsFresh() {
|
|
sessionRecord.ArchiveCurrentState()
|
|
}
|
|
|
|
///////// Initialize our session /////////
|
|
sessionState := sessionRecord.SessionState()
|
|
derivedKeys, sessionErr := ratchet.CalculateSenderSession(parameters)
|
|
if sessionErr != nil {
|
|
return sessionErr
|
|
}
|
|
// Generate an ephemeral "ratchet" key that will be advertised to
|
|
// the receiving user.
|
|
sendingRatchetKey, keyErr := ecc.GenerateKeyPair()
|
|
if keyErr != nil {
|
|
return keyErr
|
|
}
|
|
sendingChain, chainErr := derivedKeys.RootKey.CreateChain(
|
|
parameters.TheirRatchetKey(),
|
|
sendingRatchetKey,
|
|
)
|
|
if chainErr != nil {
|
|
return chainErr
|
|
}
|
|
|
|
// Calculate the sender session.
|
|
sessionState.SetVersion(protocol.CurrentVersion)
|
|
sessionState.SetRemoteIdentityKey(parameters.TheirIdentityKey())
|
|
sessionState.SetLocalIdentityKey(parameters.OurIdentityKey().PublicKey())
|
|
sessionState.AddReceiverChain(parameters.TheirRatchetKey(), derivedKeys.ChainKey.Current())
|
|
sessionState.SetSenderChain(sendingRatchetKey, sendingChain.ChainKey)
|
|
sessionState.SetRootKey(sendingChain.RootKey)
|
|
|
|
// Update our session record with the unackowledged prekey message
|
|
sessionState.SetUnacknowledgedPreKeyMessage(
|
|
theirOneTimePreKeyID,
|
|
preKey.SignedPreKeyID(),
|
|
ourBaseKey.PublicKey(),
|
|
)
|
|
|
|
// Set the local registration ID based on the registration id in our identity key store.
|
|
sessionState.SetLocalRegistrationID(
|
|
b.identityKeyStore.GetLocalRegistrationId(),
|
|
)
|
|
|
|
// Set the remote registration ID based on the given prekey bundle registrationID.
|
|
sessionState.SetRemoteRegistrationID(
|
|
preKey.RegistrationID(),
|
|
)
|
|
|
|
// Set the sender base key in our session record state.
|
|
sessionState.SetSenderBaseKey(
|
|
ourBaseKey.PublicKey().Serialize(),
|
|
)
|
|
|
|
// Store the session in our session store and save the identity in our identity store.
|
|
b.sessionStore.StoreSession(b.remoteAddress, sessionRecord)
|
|
b.identityKeyStore.SaveIdentity(b.remoteAddress, preKey.IdentityKey())
|
|
|
|
return nil
|
|
}
|